@scotthamilton77/discord-bot-lib 0.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/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026, Scott Hamilton
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
14
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
+ PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,287 @@
1
+ # @scotthamilton77/discord-bot-lib
2
+
3
+ <!-- Badges: npm version, CI status, license, Node version — add when publishing -->
4
+
5
+ TypeScript library for managing Discord bot identities, messaging, and multi-bot coordination. Wraps discord.js with a clean API surface: factory-based construction, event-driven callbacks, async iterables, and built-in message chunking.
6
+
7
+ ## Features
8
+
9
+ - **Factory construction** -- `Bot.fromConfig()` connects, verifies guilds, and returns a ready bot in one call
10
+ - **Dual receive API** -- event-driven callbacks (`onMention`, `onReply`, `onMessage`) and async iterables (`mentions()`, `replies()`, `messages()`)
11
+ - **Multi-bot management** -- `ConnectorManager` aggregates mentions across bots with lifecycle events
12
+ - **Guided onboarding** -- `BotOnboarding` walks through token validation, server invitation, and permission verification
13
+ - **Message chunking** -- automatically splits messages at paragraph/line/word boundaries to stay within Discord's 2000-char limit
14
+ - **File attachments** -- send files from Buffer or file path, with size validation and filename sanitization
15
+ - **History pagination** -- `fetchHistory()` async generator pages through channel history with automatic rate-limit retry
16
+ - **Dual build** -- ships ESM + CJS + type declarations via tsup
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install @scotthamilton77/discord-bot-lib
22
+ ```
23
+
24
+ discord.js is a direct dependency -- no separate peer install needed.
25
+
26
+ ## Quick Start
27
+
28
+ ```typescript
29
+ import { Bot } from "@scotthamilton77/discord-bot-lib";
30
+
31
+ const bot = await Bot.fromConfig({
32
+ id: "my-bot",
33
+ name: "GreeterBot",
34
+ token: process.env.DISCORD_TOKEN!,
35
+ });
36
+
37
+ // Respond to @mentions
38
+ bot.onMention(async (event) => {
39
+ await bot.send(event.channelId, `Hello, ${event.author.username}!`);
40
+ });
41
+
42
+ // Respond to replies to messages this bot sent
43
+ bot.onReply(async (event, original) => {
44
+ await bot.send(event.channelId, `You replied to my message ${original.messageId}`);
45
+ });
46
+
47
+ // Listen to all messages in a specific channel
48
+ bot.onMessage("123456789012345678", (event) => {
49
+ console.log(`${event.author.username}: ${event.content}`);
50
+ });
51
+
52
+ // Error handling
53
+ bot.on("error", (err) => console.error("Bot error:", err.message));
54
+ ```
55
+
56
+ ## Async Iterables
57
+
58
+ Every event type is also available as an `AsyncIterable`, useful for sequential processing with `for await`:
59
+
60
+ ```typescript
61
+ import { Bot } from "@scotthamilton77/discord-bot-lib";
62
+
63
+ const bot = await Bot.fromConfig({
64
+ id: "iter-bot",
65
+ name: "IterBot",
66
+ token: process.env.DISCORD_TOKEN!,
67
+ });
68
+
69
+ // Process mentions one at a time
70
+ for await (const event of bot.mentions()) {
71
+ await bot.reply(event.channelId, event.messageId, "Got it!");
72
+ }
73
+ ```
74
+
75
+ ```typescript
76
+ // Filter channel messages with a predicate
77
+ for await (const event of bot.messages("123456789012345678", {
78
+ filter: (msg) => msg.content.startsWith("!cmd"),
79
+ })) {
80
+ await bot.send(event.channelId, `Command received: ${event.content}`);
81
+ }
82
+ ```
83
+
84
+ ## Multi-Bot Management
85
+
86
+ `ConnectorManager` registers multiple bots and aggregates their events:
87
+
88
+ ```typescript
89
+ import { Bot, ConnectorManager } from "@scotthamilton77/discord-bot-lib";
90
+
91
+ const manager = new ConnectorManager();
92
+
93
+ const botA = await Bot.fromConfig({ id: "a", name: "Alpha", token: TOKEN_A });
94
+ const botB = await Bot.fromConfig({ id: "b", name: "Bravo", token: TOKEN_B });
95
+
96
+ manager.addBot(botA);
97
+ manager.addBot(botB);
98
+
99
+ // Single handler receives mentions from all bots
100
+ manager.onMention((event, bot) => {
101
+ console.log(`${bot.name} was mentioned by ${event.author.username}`);
102
+ });
103
+
104
+ // Lifecycle events
105
+ manager.on("botError", (bot, error) => {
106
+ console.error(`Error from ${(bot as { name: string }).name}:`, error);
107
+ });
108
+
109
+ // Status overview
110
+ console.log(manager.status());
111
+ // [{ id: "a", name: "Alpha", status: "ready" }, ...]
112
+
113
+ // Clean shutdown
114
+ await manager.shutdown();
115
+ ```
116
+
117
+ ## Bot Onboarding
118
+
119
+ `BotOnboarding` provides a guided 3-step setup flow -- useful for interactive CLIs or admin panels:
120
+
121
+ ```typescript
122
+ import { BotOnboarding } from "@scotthamilton77/discord-bot-lib";
123
+
124
+ const onboarding = new BotOnboarding("new-bot", "NewBot");
125
+
126
+ // Steps: provide_token -> invite_to_server -> verify_permissions
127
+ for (const step of onboarding.steps) {
128
+ console.log(`${step.id}: ${step.label} [${step.status}]`);
129
+ console.log(` ${step.instructions}`);
130
+ }
131
+
132
+ // Complete steps in order
133
+ const tokenResult = await onboarding.steps[0]!.complete(process.env.DISCORD_TOKEN!);
134
+ if (!tokenResult.success) throw new Error(tokenResult.error);
135
+
136
+ const inviteResult = await onboarding.steps[1]!.complete();
137
+ if (!inviteResult.success) throw new Error(inviteResult.error);
138
+
139
+ const verifyResult = await onboarding.steps[2]!.complete();
140
+ if (!verifyResult.success) throw new Error(verifyResult.error);
141
+
142
+ // Ready bot is available after successful onboarding
143
+ const bot = onboarding.bot!;
144
+ console.log(`${bot.name} is ${bot.status}`);
145
+ ```
146
+
147
+ ## Sending Messages
148
+
149
+ Messages can be plain strings, or objects with optional embeds and file attachments:
150
+
151
+ ```typescript
152
+ // Plain text (auto-chunked if over 2000 chars)
153
+ await bot.send(channelId, "Hello, world!");
154
+
155
+ // With embeds
156
+ await bot.send(channelId, {
157
+ content: "Check this out:",
158
+ embeds: [{ title: "My Embed", description: "Some rich content" }],
159
+ });
160
+
161
+ // With file attachments (Buffer or absolute file path)
162
+ await bot.send(channelId, {
163
+ content: "Here's the report:",
164
+ files: [
165
+ { name: "report.csv", data: Buffer.from("a,b,c\n1,2,3") },
166
+ { name: "chart.png", data: "/absolute/path/to/chart.png" },
167
+ ],
168
+ });
169
+
170
+ // Reply to a specific message
171
+ await bot.reply(channelId, messageId, "Thanks for your message!");
172
+
173
+ // Direct message
174
+ await bot.sendDM(userId, "Private hello!");
175
+
176
+ // Reactions
177
+ await bot.react(channelId, messageId, "\ud83d\udc4d");
178
+
179
+ // Edit a message
180
+ await bot.editMessage(channelId, messageId, "Updated content");
181
+ ```
182
+
183
+ ## Fetching History
184
+
185
+ ```typescript
186
+ // Fetch recent messages (up to 100)
187
+ const recent = await bot.fetchMessages(channelId, 50);
188
+
189
+ // Page through history with an async generator
190
+ for await (const page of bot.fetchHistory(channelId, { after: lastSeenId, limit: 1000 })) {
191
+ for (const msg of page) {
192
+ console.log(`${msg.author.username}: ${msg.content}`);
193
+ }
194
+ }
195
+
196
+ // Fetch backwards from a message
197
+ for await (const page of bot.fetchHistory(channelId, { before: messageId, limit: 500 })) {
198
+ // pages arrive in reverse chronological order
199
+ }
200
+
201
+ // Get attachments for a specific message
202
+ const attachments = await bot.getMessageAttachments(channelId, messageId);
203
+ ```
204
+
205
+ ## Message Utilities
206
+
207
+ ```typescript
208
+ import {
209
+ chunkMessage,
210
+ DISCORD_MAX_MESSAGE_LENGTH,
211
+ validateAttachmentSize,
212
+ sanitizeAttachmentName,
213
+ downloadAttachment,
214
+ MAX_ATTACHMENT_BYTES,
215
+ } from "@scotthamilton77/discord-bot-lib";
216
+
217
+ // Split long text into Discord-safe chunks
218
+ const chunks = chunkMessage(longText);
219
+ // Splits at paragraph -> line -> word -> hard-cut boundaries
220
+
221
+ // Validate attachment size (throws if > 25 MB)
222
+ validateAttachmentSize({ size: file.size, name: file.name, id: file.id });
223
+
224
+ // Sanitize filenames (replaces unsafe chars with underscores)
225
+ const safeName = sanitizeAttachmentName({ name: "report[final].csv", id: "123" });
226
+ // "report_final_.csv"
227
+
228
+ // Download a Discord attachment to a buffer
229
+ const { buffer, filename, contentType } = await downloadAttachment(attachment);
230
+ ```
231
+
232
+ ## API Reference
233
+
234
+ ### Classes
235
+
236
+ | Export | Description |
237
+ |---|---|
238
+ | `Bot` | Single bot identity. Created via `Bot.fromConfig(config)`. Handles sending, receiving, and channel operations. |
239
+ | `ConnectorManager` | Multi-bot registry with aggregated mention handling and lifecycle events. |
240
+ | `BotOnboarding` | Guided 3-step setup: token validation, server invite, permission check. |
241
+ | `EventBuffer<T>` | Single-consumer `AsyncIterable<T>` bridge between push events and `for await` loops. |
242
+
243
+ ### Functions
244
+
245
+ | Export | Description |
246
+ |---|---|
247
+ | `chunkMessage(text, limit?)` | Split text into chunks within Discord's 2000-char limit. |
248
+ | `validateAttachmentSize(attachment)` | Throws if attachment exceeds 25 MB. |
249
+ | `sanitizeAttachmentName(attachment)` | Replace unsafe filename characters with underscores. |
250
+ | `downloadAttachment(attachment)` | Download a Discord attachment to a `Buffer`. |
251
+
252
+ ### Constants
253
+
254
+ | Export | Value |
255
+ |---|---|
256
+ | `DISCORD_MAX_MESSAGE_LENGTH` | `2000` |
257
+ | `MAX_ATTACHMENT_BYTES` | `26214400` (25 MB) |
258
+ | `DEFAULT_SENT_MESSAGE_CACHE_SIZE` | `1000` |
259
+
260
+ ### Key Types
261
+
262
+ | Type | Description |
263
+ |---|---|
264
+ | `BotConfig` | Configuration for `Bot.fromConfig()` -- id, name, token, optional intents/channels/cacheSize. |
265
+ | `BotStatus` | `"unregistered" \| "configuring" \| "connecting" \| "verifying" \| "ready" \| "disconnected" \| "failed"` |
266
+ | `MessageEvent` | Incoming message with author, content, channelId, mentions, and `raw` discord.js Message. |
267
+ | `SentMessage` | Tracked outgoing message with messageId, channelId, timestamp, and `raw` escape hatch. |
268
+ | `MessageContent` | `string \| { content?: string; embeds?: unknown[]; files?: FileAttachment[] }` |
269
+ | `FetchedMessage` | Lightweight message from `fetchMessages()` / `fetchHistory()`. |
270
+ | `FetchHistoryOptions` | Discriminated union: `{ after: string } \| { before: string }` with optional `limit`. |
271
+ | `MessageFilter` | `(message: MessageEvent) => boolean` predicate for `onMessage()` / `messages()`. |
272
+ | `FileAttachment` | `{ data: Buffer \| string; name: string }` for sending files. |
273
+ | `OnboardingStep` | Step in the onboarding flow with id, label, instructions, status, and `complete()`. |
274
+ | `AttachmentLike` | Minimal shape for attachment validation utilities. |
275
+ | `DownloadableAttachment` | Extends `AttachmentLike` with `url` and `contentType` for downloading. |
276
+ | `DownloadedAttachment` | Result of `downloadAttachment()`: buffer, filename, contentType. |
277
+ | `ManagerEvents` | Lifecycle event signatures for `ConnectorManager`. |
278
+
279
+ ## Requirements
280
+
281
+ - Node.js >= 20
282
+ - TypeScript >= 5.5 (for development)
283
+ - discord.js ^14.25 (included as a dependency)
284
+
285
+ ## License
286
+
287
+ ISC