@aarekaz/switchboard-discord 0.3.1

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 ADDED
@@ -0,0 +1,86 @@
1
+ # @aarekaz/switchboard-discord
2
+
3
+ Discord adapter for Switchboard SDK.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @aarekaz/switchboard-core @aarekaz/switchboard-discord
9
+ # or
10
+ pnpm add @aarekaz/switchboard-core @aarekaz/switchboard-discord
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ Simply import the Discord adapter and it will auto-register with Switchboard core:
16
+
17
+ ```typescript
18
+ import { createBot } from '@aarekaz/switchboard-core';
19
+ import '@aarekaz/switchboard-discord'; // Auto-registers Discord adapter
20
+
21
+ const bot = createBot({
22
+ platform: 'discord',
23
+ credentials: {
24
+ token: process.env.DISCORD_TOKEN,
25
+ },
26
+ });
27
+
28
+ bot.onMessage(async (message) => {
29
+ if (message.text.includes('ping')) {
30
+ await bot.reply(message, 'pong! 🏓');
31
+ }
32
+ });
33
+
34
+ await bot.start();
35
+ ```
36
+
37
+ ## Getting a Discord Bot Token
38
+
39
+ 1. Go to [Discord Developer Portal](https://discord.com/developers/applications)
40
+ 2. Create a new application
41
+ 3. Go to "Bot" → "Add Bot"
42
+ 4. Copy the token
43
+ 5. Enable "Message Content Intent" under "Privileged Gateway Intents"
44
+
45
+ ## Inviting Your Bot
46
+
47
+ 1. Go to "OAuth2" → "URL Generator"
48
+ 2. Select scopes: `bot`
49
+ 3. Select permissions: `Send Messages`, `Read Message History`, `Add Reactions`
50
+ 4. Copy the generated URL and open it in your browser
51
+ 5. Select a server and authorize
52
+
53
+ ## Discord-Specific Features
54
+
55
+ You can use Discord-specific features by passing platform-specific options:
56
+
57
+ ```typescript
58
+ import { EmbedBuilder } from 'discord.js';
59
+
60
+ await bot.sendMessage('channel-id', 'Check out this embed!', {
61
+ discord: {
62
+ embeds: [
63
+ new EmbedBuilder()
64
+ .setTitle('Hello!')
65
+ .setDescription('This is a Discord embed')
66
+ .setColor(0x0099ff),
67
+ ],
68
+ },
69
+ });
70
+ ```
71
+
72
+ ## Known Limitations
73
+
74
+ - **Message editing/deletion**: Requires implementing message caching (coming in Phase 5)
75
+ - **Reactions**: Requires implementing message caching (coming in Phase 5)
76
+ - **Threads**: Requires implementing message caching (coming in Phase 5)
77
+
78
+ These features will be fully supported once we implement the message cache system.
79
+
80
+ ## Documentation
81
+
82
+ For full documentation, visit [github.com/Aarekaz/switchboard](https://github.com/Aarekaz/switchboard)
83
+
84
+ ## License
85
+
86
+ MIT
@@ -0,0 +1,127 @@
1
+ import { PlatformAdapter, Result, UnifiedMessage, MessageRef, UnifiedEvent, Channel, User } from '@aarekaz/switchboard-core';
2
+ import { EmbedBuilder, ActionRowBuilder, ButtonBuilder } from 'discord.js';
3
+
4
+ /**
5
+ * Discord-specific types and configurations
6
+ */
7
+
8
+ /**
9
+ * Credentials required to connect to Discord
10
+ */
11
+ interface DiscordCredentials {
12
+ /** Discord bot token from the Developer Portal */
13
+ token: string;
14
+ }
15
+ /**
16
+ * Discord-specific configuration options
17
+ */
18
+ interface DiscordConfig {
19
+ /** Custom intents (advanced users only) */
20
+ intents?: number[];
21
+ /** Whether to cache messages (default: true) */
22
+ cacheMessages?: boolean;
23
+ /** Maximum number of messages to cache per channel (default: 100) */
24
+ messageCacheSize?: number;
25
+ }
26
+ /**
27
+ * Discord-specific message options
28
+ */
29
+ interface DiscordMessageOptions {
30
+ /** Discord embeds (rich message formatting) */
31
+ embeds?: EmbedBuilder[];
32
+ /** Discord components (buttons, select menus, etc.) */
33
+ components?: ActionRowBuilder<ButtonBuilder>[];
34
+ /** Whether to mention the replied user (default: false) */
35
+ allowedMentions?: {
36
+ repliedUser?: boolean;
37
+ };
38
+ /** Whether to suppress embeds (default: false) */
39
+ suppressEmbeds?: boolean;
40
+ }
41
+
42
+ /**
43
+ * Discord Platform Adapter
44
+ * Implements the Switchboard PlatformAdapter interface for Discord
45
+ */
46
+
47
+ /**
48
+ * Discord adapter implementation
49
+ */
50
+ declare class DiscordAdapter implements PlatformAdapter {
51
+ readonly name = "discord-adapter";
52
+ readonly platform: "discord";
53
+ private client;
54
+ private eventHandlers;
55
+ private config;
56
+ constructor(config?: DiscordConfig);
57
+ /**
58
+ * Connect to Discord
59
+ */
60
+ connect(credentials: unknown): Promise<void>;
61
+ /**
62
+ * Disconnect from Discord
63
+ */
64
+ disconnect(): Promise<void>;
65
+ /**
66
+ * Check if connected to Discord
67
+ */
68
+ isConnected(): boolean;
69
+ /**
70
+ * Send a message to a Discord channel
71
+ */
72
+ sendMessage(channelId: string, text: string, options?: unknown): Promise<Result<UnifiedMessage>>;
73
+ /**
74
+ * Edit a message
75
+ */
76
+ editMessage(messageRef: MessageRef, newText: string): Promise<Result<UnifiedMessage>>;
77
+ /**
78
+ * Delete a message
79
+ */
80
+ deleteMessage(messageRef: MessageRef): Promise<Result<void>>;
81
+ /**
82
+ * Add a reaction to a message
83
+ */
84
+ addReaction(messageRef: MessageRef, emoji: string): Promise<Result<void>>;
85
+ /**
86
+ * Remove a reaction from a message
87
+ */
88
+ removeReaction(messageRef: MessageRef, emoji: string): Promise<Result<void>>;
89
+ /**
90
+ * Create a thread from a message
91
+ */
92
+ createThread(messageRef: MessageRef, text: string): Promise<Result<UnifiedMessage>>;
93
+ /**
94
+ * Upload a file to a channel
95
+ */
96
+ uploadFile(channelId: string, file: unknown, options?: unknown): Promise<Result<UnifiedMessage>>;
97
+ /**
98
+ * Register an event handler
99
+ */
100
+ onEvent(handler: (event: UnifiedEvent) => void): void;
101
+ /**
102
+ * Get list of channels
103
+ */
104
+ getChannels(): Promise<Result<Channel[]>>;
105
+ /**
106
+ * Get list of users (in a specific channel or guild)
107
+ */
108
+ getUsers(channelId?: string): Promise<Result<User[]>>;
109
+ /**
110
+ * Normalize a Discord message to UnifiedMessage
111
+ */
112
+ normalizeMessage(platformMessage: unknown): UnifiedMessage;
113
+ /**
114
+ * Normalize a Discord event to UnifiedEvent
115
+ */
116
+ normalizeEvent(platformEvent: unknown): UnifiedEvent;
117
+ /**
118
+ * Set up Discord event listeners
119
+ */
120
+ private setupEventListeners;
121
+ /**
122
+ * Emit an event to all registered handlers
123
+ */
124
+ private emitEvent;
125
+ }
126
+
127
+ export { DiscordAdapter, type DiscordConfig, type DiscordCredentials, type DiscordMessageOptions };
package/dist/index.js ADDED
@@ -0,0 +1,590 @@
1
+ import { registry, ConnectionError, err, MessageSendError, ok, MessageEditError, MessageDeleteError, ReactionError } from '@aarekaz/switchboard-core';
2
+ import { Client, Partials, GatewayIntentBits } from 'discord.js';
3
+
4
+ // src/register.ts
5
+
6
+ // src/normalizers.ts
7
+ function normalizeMessage(message) {
8
+ let text = message.content;
9
+ const attachments = Array.from(message.attachments.values()).map(
10
+ (attachment) => ({
11
+ id: attachment.id,
12
+ filename: attachment.name || "unknown",
13
+ url: attachment.url,
14
+ mimeType: attachment.contentType || "application/octet-stream",
15
+ size: attachment.size
16
+ })
17
+ );
18
+ const threadId = message.thread?.id || (message.channel.isThread() ? message.channelId : void 0);
19
+ return {
20
+ id: message.id,
21
+ channelId: message.channelId,
22
+ userId: message.author.id,
23
+ text,
24
+ timestamp: message.createdAt,
25
+ threadId,
26
+ attachments: attachments.length > 0 ? attachments : void 0,
27
+ platform: "discord",
28
+ _raw: message
29
+ };
30
+ }
31
+ function normalizeChannel(channel) {
32
+ return {
33
+ id: channel.id,
34
+ name: channel.isDMBased() ? "DM" : channel.name,
35
+ type: channel.isDMBased() ? "dm" : "public",
36
+ platform: "discord"
37
+ };
38
+ }
39
+ function normalizeUser(user) {
40
+ return {
41
+ id: user.id,
42
+ username: user.username,
43
+ displayName: user.displayName || user.username,
44
+ isBot: user.bot || false,
45
+ platform: "discord"
46
+ };
47
+ }
48
+ function normalizeMessageEvent(message) {
49
+ return {
50
+ type: "message",
51
+ message: normalizeMessage(message)
52
+ };
53
+ }
54
+ function normalizeReactionEvent(reaction, user, action) {
55
+ const emoji = reaction.emoji.name || "\u2753";
56
+ return {
57
+ type: "reaction",
58
+ messageId: reaction.message.id,
59
+ userId: user.id,
60
+ emoji,
61
+ action
62
+ };
63
+ }
64
+ function toDiscordEmoji(emoji) {
65
+ if (emoji.startsWith("<") && emoji.endsWith(">")) {
66
+ return emoji;
67
+ }
68
+ return emoji;
69
+ }
70
+
71
+ // src/adapter.ts
72
+ var DiscordAdapter = class {
73
+ name = "discord-adapter";
74
+ platform = "discord";
75
+ client = null;
76
+ eventHandlers = /* @__PURE__ */ new Set();
77
+ config;
78
+ constructor(config = {}) {
79
+ this.config = config;
80
+ }
81
+ /**
82
+ * Connect to Discord
83
+ */
84
+ async connect(credentials) {
85
+ const discordCreds = credentials;
86
+ if (!discordCreds.token) {
87
+ throw new ConnectionError(
88
+ "discord",
89
+ new Error("Discord token is required")
90
+ );
91
+ }
92
+ try {
93
+ this.client = new Client({
94
+ intents: this.config.intents || [
95
+ GatewayIntentBits.Guilds,
96
+ GatewayIntentBits.GuildMessages,
97
+ GatewayIntentBits.MessageContent,
98
+ GatewayIntentBits.DirectMessages,
99
+ GatewayIntentBits.GuildMessageReactions
100
+ ],
101
+ partials: [Partials.Message, Partials.Channel, Partials.Reaction]
102
+ });
103
+ this.setupEventListeners();
104
+ await this.client.login(discordCreds.token);
105
+ await new Promise((resolve) => {
106
+ this.client.once("ready", () => resolve());
107
+ });
108
+ } catch (error) {
109
+ throw new ConnectionError("discord", error);
110
+ }
111
+ }
112
+ /**
113
+ * Disconnect from Discord
114
+ */
115
+ async disconnect() {
116
+ if (this.client) {
117
+ this.client.destroy();
118
+ this.client = null;
119
+ }
120
+ }
121
+ /**
122
+ * Check if connected to Discord
123
+ */
124
+ isConnected() {
125
+ return this.client !== null && this.client.isReady();
126
+ }
127
+ /**
128
+ * Send a message to a Discord channel
129
+ */
130
+ async sendMessage(channelId, text, options) {
131
+ if (!this.client) {
132
+ return err(new ConnectionError("discord", new Error("Not connected")));
133
+ }
134
+ try {
135
+ const channel = await this.client.channels.fetch(channelId);
136
+ if (!channel || !channel.isTextBased()) {
137
+ return err(
138
+ new MessageSendError(
139
+ "discord",
140
+ channelId,
141
+ new Error("Channel not found or not text-based")
142
+ )
143
+ );
144
+ }
145
+ const discordOptions = options?.discord;
146
+ const message = await channel.send({
147
+ content: text,
148
+ embeds: discordOptions?.embeds,
149
+ components: discordOptions?.components,
150
+ allowedMentions: discordOptions?.allowedMentions
151
+ });
152
+ return ok(normalizeMessage(message));
153
+ } catch (error) {
154
+ return err(
155
+ new MessageSendError(
156
+ "discord",
157
+ channelId,
158
+ error instanceof Error ? error : new Error(String(error))
159
+ )
160
+ );
161
+ }
162
+ }
163
+ /**
164
+ * Edit a message
165
+ */
166
+ async editMessage(messageRef, newText) {
167
+ if (!this.client) {
168
+ return err(new ConnectionError("discord", new Error("Not connected")));
169
+ }
170
+ try {
171
+ const messageId = typeof messageRef === "string" ? messageRef : messageRef.id;
172
+ const channelId = typeof messageRef === "string" ? null : messageRef.channelId;
173
+ if (!channelId) {
174
+ return err(
175
+ new MessageEditError(
176
+ "discord",
177
+ messageId,
178
+ new Error(
179
+ 'Cannot edit message: channel context not found.\nPass the full message object instead:\n bot.editMessage(message, "text") // \u2705 Works\n bot.editMessage(message.id, "text") // \u274C Missing channel context'
180
+ )
181
+ )
182
+ );
183
+ }
184
+ const channel = await this.client.channels.fetch(channelId);
185
+ if (!channel || !("messages" in channel)) {
186
+ return err(
187
+ new MessageEditError(
188
+ "discord",
189
+ messageId,
190
+ new Error(`Channel ${channelId} not found or is not a text channel`)
191
+ )
192
+ );
193
+ }
194
+ const message = await channel.messages.fetch(messageId);
195
+ const editedMessage = await message.edit(newText);
196
+ return ok(normalizeMessage(editedMessage));
197
+ } catch (error) {
198
+ const messageId = typeof messageRef === "string" ? messageRef : messageRef.id;
199
+ return err(
200
+ new MessageEditError(
201
+ "discord",
202
+ messageId,
203
+ error instanceof Error ? error : new Error(String(error))
204
+ )
205
+ );
206
+ }
207
+ }
208
+ /**
209
+ * Delete a message
210
+ */
211
+ async deleteMessage(messageRef) {
212
+ if (!this.client) {
213
+ return err(new ConnectionError("discord", new Error("Not connected")));
214
+ }
215
+ try {
216
+ const messageId = typeof messageRef === "string" ? messageRef : messageRef.id;
217
+ const channelId = typeof messageRef === "string" ? null : messageRef.channelId;
218
+ if (!channelId) {
219
+ return err(
220
+ new MessageDeleteError(
221
+ "discord",
222
+ messageId,
223
+ new Error(
224
+ "Cannot delete message: channel context not found.\nPass the full message object instead:\n bot.deleteMessage(message) // \u2705 Works\n bot.deleteMessage(message.id) // \u274C Missing channel context"
225
+ )
226
+ )
227
+ );
228
+ }
229
+ const channel = await this.client.channels.fetch(channelId);
230
+ if (!channel || !("messages" in channel)) {
231
+ return err(
232
+ new MessageDeleteError(
233
+ "discord",
234
+ messageId,
235
+ new Error(`Channel ${channelId} not found or is not a text channel`)
236
+ )
237
+ );
238
+ }
239
+ const message = await channel.messages.fetch(messageId);
240
+ await message.delete();
241
+ return ok(void 0);
242
+ } catch (error) {
243
+ const messageId = typeof messageRef === "string" ? messageRef : messageRef.id;
244
+ return err(
245
+ new MessageDeleteError(
246
+ "discord",
247
+ messageId,
248
+ error instanceof Error ? error : new Error(String(error))
249
+ )
250
+ );
251
+ }
252
+ }
253
+ /**
254
+ * Add a reaction to a message
255
+ */
256
+ async addReaction(messageRef, emoji) {
257
+ if (!this.client) {
258
+ return err(new ConnectionError("discord", new Error("Not connected")));
259
+ }
260
+ try {
261
+ const messageId = typeof messageRef === "string" ? messageRef : messageRef.id;
262
+ const channelId = typeof messageRef === "string" ? null : messageRef.channelId;
263
+ if (!channelId) {
264
+ return err(
265
+ new ReactionError(
266
+ "discord",
267
+ messageId,
268
+ emoji,
269
+ new Error(
270
+ "Cannot add reaction: channel context not found.\nPass the full message object instead:\n bot.addReaction(message, emoji) // \u2705 Works\n bot.addReaction(message.id, emoji) // \u274C Missing channel context"
271
+ )
272
+ )
273
+ );
274
+ }
275
+ const channel = await this.client.channels.fetch(channelId);
276
+ if (!channel || !("messages" in channel)) {
277
+ return err(
278
+ new ReactionError(
279
+ "discord",
280
+ messageId,
281
+ emoji,
282
+ new Error(`Channel ${channelId} not found or is not a text channel`)
283
+ )
284
+ );
285
+ }
286
+ const message = await channel.messages.fetch(messageId);
287
+ const discordEmoji = toDiscordEmoji(emoji);
288
+ await message.react(discordEmoji);
289
+ return ok(void 0);
290
+ } catch (error) {
291
+ const messageId = typeof messageRef === "string" ? messageRef : messageRef.id;
292
+ return err(
293
+ new ReactionError(
294
+ "discord",
295
+ messageId,
296
+ emoji,
297
+ error instanceof Error ? error : new Error(String(error))
298
+ )
299
+ );
300
+ }
301
+ }
302
+ /**
303
+ * Remove a reaction from a message
304
+ */
305
+ async removeReaction(messageRef, emoji) {
306
+ if (!this.client) {
307
+ return err(new ConnectionError("discord", new Error("Not connected")));
308
+ }
309
+ try {
310
+ const messageId = typeof messageRef === "string" ? messageRef : messageRef.id;
311
+ const channelId = typeof messageRef === "string" ? null : messageRef.channelId;
312
+ if (!channelId) {
313
+ return err(
314
+ new ReactionError(
315
+ "discord",
316
+ messageId,
317
+ emoji,
318
+ new Error(
319
+ "Cannot remove reaction: channel context not found.\nPass the full message object instead:\n bot.removeReaction(message, emoji) // \u2705 Works\n bot.removeReaction(message.id, emoji) // \u274C Missing channel context"
320
+ )
321
+ )
322
+ );
323
+ }
324
+ const channel = await this.client.channels.fetch(channelId);
325
+ if (!channel || !("messages" in channel)) {
326
+ return err(
327
+ new ReactionError(
328
+ "discord",
329
+ messageId,
330
+ emoji,
331
+ new Error(`Channel ${channelId} not found or is not a text channel`)
332
+ )
333
+ );
334
+ }
335
+ const message = await channel.messages.fetch(messageId);
336
+ const discordEmoji = toDiscordEmoji(emoji);
337
+ await message.reactions.cache.get(discordEmoji)?.users.remove(this.client.user.id);
338
+ return ok(void 0);
339
+ } catch (error) {
340
+ const messageId = typeof messageRef === "string" ? messageRef : messageRef.id;
341
+ return err(
342
+ new ReactionError(
343
+ "discord",
344
+ messageId,
345
+ emoji,
346
+ error instanceof Error ? error : new Error(String(error))
347
+ )
348
+ );
349
+ }
350
+ }
351
+ /**
352
+ * Create a thread from a message
353
+ */
354
+ async createThread(messageRef, text) {
355
+ if (!this.client) {
356
+ return err(new ConnectionError("discord", new Error("Not connected")));
357
+ }
358
+ try {
359
+ const messageId = typeof messageRef === "string" ? messageRef : messageRef.id;
360
+ const channelId = typeof messageRef === "string" ? null : messageRef.channelId;
361
+ if (!channelId) {
362
+ return err(
363
+ new MessageSendError(
364
+ "discord",
365
+ "unknown",
366
+ new Error(
367
+ "Cannot create thread: channel context not found.\nPass the full message object instead:\n bot.createThread(message, text) // \u2705 Works\n bot.createThread(message.id, text) // \u274C Missing channel context"
368
+ )
369
+ )
370
+ );
371
+ }
372
+ const channel = await this.client.channels.fetch(channelId);
373
+ if (!channel || !("messages" in channel)) {
374
+ return err(
375
+ new MessageSendError(
376
+ "discord",
377
+ channelId,
378
+ new Error(`Channel ${channelId} not found or is not a text channel`)
379
+ )
380
+ );
381
+ }
382
+ const message = await channel.messages.fetch(messageId);
383
+ const thread = await message.startThread({
384
+ name: text.substring(0, 100)
385
+ // Discord thread names max 100 chars
386
+ });
387
+ const threadMessage = await thread.send(text);
388
+ return ok(normalizeMessage(threadMessage));
389
+ } catch (error) {
390
+ const channelId = typeof messageRef === "string" ? "unknown" : messageRef.channelId;
391
+ return err(
392
+ new MessageSendError(
393
+ "discord",
394
+ channelId,
395
+ error instanceof Error ? error : new Error(String(error))
396
+ )
397
+ );
398
+ }
399
+ }
400
+ /**
401
+ * Upload a file to a channel
402
+ */
403
+ async uploadFile(channelId, file, options) {
404
+ if (!this.client) {
405
+ return err(new ConnectionError("discord", new Error("Not connected")));
406
+ }
407
+ try {
408
+ const channel = await this.client.channels.fetch(channelId);
409
+ if (!channel || !channel.isTextBased()) {
410
+ return err(
411
+ new MessageSendError(
412
+ "discord",
413
+ channelId,
414
+ new Error("Channel not found or not text-based")
415
+ )
416
+ );
417
+ }
418
+ return err(
419
+ new MessageSendError(
420
+ "discord",
421
+ channelId,
422
+ new Error("File upload not yet implemented")
423
+ )
424
+ );
425
+ } catch (error) {
426
+ return err(
427
+ new MessageSendError(
428
+ "discord",
429
+ channelId,
430
+ error instanceof Error ? error : new Error(String(error))
431
+ )
432
+ );
433
+ }
434
+ }
435
+ /**
436
+ * Register an event handler
437
+ */
438
+ onEvent(handler) {
439
+ this.eventHandlers.add(handler);
440
+ }
441
+ /**
442
+ * Get list of channels
443
+ */
444
+ async getChannels() {
445
+ if (!this.client) {
446
+ return err(new ConnectionError("discord", new Error("Not connected")));
447
+ }
448
+ try {
449
+ const channels = [];
450
+ for (const [, channel] of this.client.channels.cache) {
451
+ if (channel.isTextBased() && !channel.isThread()) {
452
+ channels.push(normalizeChannel(channel));
453
+ }
454
+ }
455
+ return ok(channels);
456
+ } catch (error) {
457
+ return err(
458
+ new ConnectionError(
459
+ "discord",
460
+ error instanceof Error ? error : new Error(String(error))
461
+ )
462
+ );
463
+ }
464
+ }
465
+ /**
466
+ * Get list of users (in a specific channel or guild)
467
+ */
468
+ async getUsers(channelId) {
469
+ if (!this.client) {
470
+ return err(new ConnectionError("discord", new Error("Not connected")));
471
+ }
472
+ try {
473
+ const users = [];
474
+ if (channelId) {
475
+ const channel = await this.client.channels.fetch(channelId);
476
+ if (channel && "members" in channel) {
477
+ for (const [, member] of channel.members) {
478
+ users.push(normalizeUser(member.user));
479
+ }
480
+ }
481
+ } else {
482
+ for (const [, user] of this.client.users.cache) {
483
+ users.push(normalizeUser(user));
484
+ }
485
+ }
486
+ return ok(users);
487
+ } catch (error) {
488
+ return err(
489
+ new ConnectionError(
490
+ "discord",
491
+ error instanceof Error ? error : new Error(String(error))
492
+ )
493
+ );
494
+ }
495
+ }
496
+ /**
497
+ * Normalize a Discord message to UnifiedMessage
498
+ */
499
+ normalizeMessage(platformMessage) {
500
+ return normalizeMessage(platformMessage);
501
+ }
502
+ /**
503
+ * Normalize a Discord event to UnifiedEvent
504
+ */
505
+ normalizeEvent(platformEvent) {
506
+ return normalizeMessageEvent(platformEvent);
507
+ }
508
+ /**
509
+ * Set up Discord event listeners
510
+ */
511
+ setupEventListeners() {
512
+ if (!this.client) return;
513
+ this.client.on("messageCreate", (message) => {
514
+ if (message.author.bot) return;
515
+ const event = normalizeMessageEvent(message);
516
+ this.emitEvent(event);
517
+ });
518
+ this.client.on("messageReactionAdd", async (reaction, user) => {
519
+ if (reaction.partial) {
520
+ try {
521
+ await reaction.fetch();
522
+ } catch (error) {
523
+ console.error("Failed to fetch reaction:", error);
524
+ return;
525
+ }
526
+ }
527
+ if (user.partial) {
528
+ try {
529
+ await user.fetch();
530
+ } catch (error) {
531
+ console.error("Failed to fetch user:", error);
532
+ return;
533
+ }
534
+ }
535
+ if (user.bot) return;
536
+ const event = normalizeReactionEvent(reaction, user, "added");
537
+ this.emitEvent(event);
538
+ });
539
+ this.client.on("messageReactionRemove", async (reaction, user) => {
540
+ if (reaction.partial) {
541
+ try {
542
+ await reaction.fetch();
543
+ } catch (error) {
544
+ console.error("Failed to fetch reaction:", error);
545
+ return;
546
+ }
547
+ }
548
+ if (user.partial) {
549
+ try {
550
+ await user.fetch();
551
+ } catch (error) {
552
+ console.error("Failed to fetch user:", error);
553
+ return;
554
+ }
555
+ }
556
+ if (user.bot) return;
557
+ const event = normalizeReactionEvent(reaction, user, "removed");
558
+ this.emitEvent(event);
559
+ });
560
+ this.client.on("ready", () => {
561
+ console.log(`[Switchboard] Discord bot connected as ${this.client.user.tag}`);
562
+ });
563
+ this.client.on("error", (error) => {
564
+ console.error("Discord client error:", error);
565
+ });
566
+ }
567
+ /**
568
+ * Emit an event to all registered handlers
569
+ */
570
+ emitEvent(event) {
571
+ for (const handler of this.eventHandlers) {
572
+ try {
573
+ handler(event);
574
+ } catch (error) {
575
+ console.error("Error in event handler:", error);
576
+ }
577
+ }
578
+ }
579
+ };
580
+
581
+ // src/register.ts
582
+ var discordAdapter = new DiscordAdapter();
583
+ registry.register("discord", discordAdapter);
584
+ if (process.env.NODE_ENV !== "production") {
585
+ console.log("[Switchboard] Discord adapter registered");
586
+ }
587
+
588
+ export { DiscordAdapter };
589
+ //# sourceMappingURL=index.js.map
590
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/normalizers.ts","../src/adapter.ts","../src/register.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Normalizers for converting Discord types to Switchboard unified types\n */\n\nimport type {\n UnifiedMessage,\n Attachment,\n Channel,\n User,\n UnifiedEvent,\n MessageEvent,\n ReactionEvent,\n} from '@aarekaz/switchboard-core';\nimport type {\n Message,\n TextChannel,\n DMChannel,\n User as DiscordUser,\n MessageReaction,\n PartialMessageReaction,\n PartialUser,\n} from 'discord.js';\n\n/**\n * Normalizes a Discord message to UnifiedMessage\n */\nexport function normalizeMessage(message: Message): UnifiedMessage {\n // Get plain text content\n let text = message.content;\n\n // Extract attachments\n const attachments: Attachment[] = Array.from(message.attachments.values()).map(\n (attachment) => ({\n id: attachment.id,\n filename: attachment.name || 'unknown',\n url: attachment.url,\n mimeType: attachment.contentType || 'application/octet-stream',\n size: attachment.size,\n })\n );\n\n // Determine thread ID (Discord uses channel ID for threads)\n const threadId = message.thread?.id || (message.channel.isThread() ? message.channelId : undefined);\n\n return {\n id: message.id,\n channelId: message.channelId,\n userId: message.author.id,\n text,\n timestamp: message.createdAt,\n threadId,\n attachments: attachments.length > 0 ? attachments : undefined,\n platform: 'discord',\n _raw: message,\n };\n}\n\n/**\n * Normalizes a Discord channel to Switchboard Channel\n */\nexport function normalizeChannel(\n channel: TextChannel | DMChannel\n): Channel {\n return {\n id: channel.id,\n name: channel.isDMBased() ? 'DM' : (channel as TextChannel).name,\n type: channel.isDMBased() ? 'dm' : 'public',\n platform: 'discord',\n };\n}\n\n/**\n * Normalizes a Discord user to Switchboard User\n */\nexport function normalizeUser(user: DiscordUser): User {\n return {\n id: user.id,\n username: user.username,\n displayName: user.displayName || user.username,\n isBot: user.bot || false,\n platform: 'discord',\n };\n}\n\n/**\n * Normalizes a Discord message event to UnifiedEvent\n */\nexport function normalizeMessageEvent(message: Message): MessageEvent {\n return {\n type: 'message',\n message: normalizeMessage(message),\n };\n}\n\n/**\n * Normalizes a Discord reaction event to UnifiedEvent\n */\nexport function normalizeReactionEvent(\n reaction: MessageReaction | PartialMessageReaction,\n user: DiscordUser | PartialUser,\n action: 'added' | 'removed'\n): ReactionEvent {\n // Handle partial reactions by using the emoji identifier\n const emoji = reaction.emoji.name || '❓';\n\n return {\n type: 'reaction',\n messageId: reaction.message.id,\n userId: user.id,\n emoji,\n action,\n };\n}\n\n/**\n * Extracts emoji string from Discord reaction\n * Handles both standard emoji and custom Discord emoji\n */\nexport function getEmojiString(emoji: string): string {\n // Discord custom emoji format: <:name:id> or <a:name:id> for animated\n // We'll just use the emoji string as-is for standard emoji\n // For custom emoji, Discord.js handles the formatting\n return emoji;\n}\n\n/**\n * Converts a Switchboard emoji to Discord format\n * Standard emoji pass through, custom emoji need special handling\n */\nexport function toDiscordEmoji(emoji: string): string {\n // If it's already in Discord custom emoji format, return as-is\n if (emoji.startsWith('<') && emoji.endsWith('>')) {\n return emoji;\n }\n\n // Otherwise, assume it's a standard emoji\n return emoji;\n}\n","/**\n * Discord Platform Adapter\n * Implements the Switchboard PlatformAdapter interface for Discord\n */\n\nimport {\n Client,\n GatewayIntentBits,\n Message,\n TextChannel,\n ThreadChannel,\n Partials,\n} from 'discord.js';\nimport type {\n PlatformAdapter,\n Result,\n UnifiedMessage,\n MessageRef,\n UnifiedEvent,\n Channel,\n User,\n} from '@aarekaz/switchboard-core';\nimport {\n ok,\n err,\n ConnectionError,\n MessageSendError,\n MessageEditError,\n MessageDeleteError,\n ReactionError,\n} from '@aarekaz/switchboard-core';\nimport {\n normalizeMessage,\n normalizeChannel,\n normalizeUser,\n normalizeMessageEvent,\n normalizeReactionEvent,\n toDiscordEmoji,\n} from './normalizers.js';\nimport type { DiscordCredentials, DiscordConfig, DiscordMessageOptions } from './types.js';\n\n/**\n * Discord adapter implementation\n */\nexport class DiscordAdapter implements PlatformAdapter {\n readonly name = 'discord-adapter';\n readonly platform = 'discord' as const;\n\n private client: Client | null = null;\n private eventHandlers: Set<(event: UnifiedEvent) => void> = new Set();\n private config: DiscordConfig;\n\n constructor(config: DiscordConfig = {}) {\n this.config = config;\n }\n\n /**\n * Connect to Discord\n */\n async connect(credentials: unknown): Promise<void> {\n const discordCreds = credentials as DiscordCredentials;\n\n if (!discordCreds.token) {\n throw new ConnectionError(\n 'discord',\n new Error('Discord token is required')\n );\n }\n\n try {\n // Create Discord client with necessary intents\n this.client = new Client({\n intents: this.config.intents || [\n GatewayIntentBits.Guilds,\n GatewayIntentBits.GuildMessages,\n GatewayIntentBits.MessageContent,\n GatewayIntentBits.DirectMessages,\n GatewayIntentBits.GuildMessageReactions,\n ],\n partials: [Partials.Message, Partials.Channel, Partials.Reaction],\n });\n\n // Set up event listeners\n this.setupEventListeners();\n\n // Login to Discord\n await this.client.login(discordCreds.token);\n\n // Wait for the client to be ready\n await new Promise<void>((resolve) => {\n this.client!.once('ready', () => resolve());\n });\n } catch (error) {\n throw new ConnectionError('discord', error);\n }\n }\n\n /**\n * Disconnect from Discord\n */\n async disconnect(): Promise<void> {\n if (this.client) {\n this.client.destroy();\n this.client = null;\n }\n }\n\n /**\n * Check if connected to Discord\n */\n isConnected(): boolean {\n return this.client !== null && this.client.isReady();\n }\n\n /**\n * Send a message to a Discord channel\n */\n async sendMessage(\n channelId: string,\n text: string,\n options?: unknown\n ): Promise<Result<UnifiedMessage>> {\n if (!this.client) {\n return err(new ConnectionError('discord', new Error('Not connected')));\n }\n\n try {\n const channel = await this.client.channels.fetch(channelId);\n\n if (!channel || !channel.isTextBased()) {\n return err(\n new MessageSendError(\n 'discord',\n channelId,\n new Error('Channel not found or not text-based')\n )\n );\n }\n\n // Handle Discord-specific options\n const discordOptions = (options as { discord?: DiscordMessageOptions })\n ?.discord;\n\n const message = await (channel as TextChannel).send({\n content: text,\n embeds: discordOptions?.embeds,\n components: discordOptions?.components,\n allowedMentions: discordOptions?.allowedMentions,\n });\n\n return ok(normalizeMessage(message));\n } catch (error) {\n return err(\n new MessageSendError(\n 'discord',\n channelId,\n error instanceof Error ? error : new Error(String(error))\n )\n );\n }\n }\n\n /**\n * Edit a message\n */\n async editMessage(\n messageRef: MessageRef,\n newText: string\n ): Promise<Result<UnifiedMessage>> {\n if (!this.client) {\n return err(new ConnectionError('discord', new Error('Not connected')));\n }\n\n try {\n // Extract message ID and channel ID from MessageRef\n const messageId = typeof messageRef === 'string' ? messageRef : messageRef.id;\n const channelId = typeof messageRef === 'string'\n ? null\n : messageRef.channelId;\n\n if (!channelId) {\n return err(\n new MessageEditError(\n 'discord',\n messageId,\n new Error(\n 'Cannot edit message: channel context not found.\\n' +\n 'Pass the full message object instead:\\n' +\n ' bot.editMessage(message, \"text\") // ✅ Works\\n' +\n ' bot.editMessage(message.id, \"text\") // ❌ Missing channel context'\n )\n )\n );\n }\n\n // Fetch the channel\n const channel = await this.client.channels.fetch(channelId);\n if (!channel || !('messages' in channel)) {\n return err(\n new MessageEditError(\n 'discord',\n messageId,\n new Error(`Channel ${channelId} not found or is not a text channel`)\n )\n );\n }\n\n // Fetch and edit the message\n const message = await (channel as TextChannel).messages.fetch(messageId);\n const editedMessage = await message.edit(newText);\n\n return ok(normalizeMessage(editedMessage));\n } catch (error) {\n const messageId = typeof messageRef === 'string' ? messageRef : messageRef.id;\n return err(\n new MessageEditError(\n 'discord',\n messageId,\n error instanceof Error ? error : new Error(String(error))\n )\n );\n }\n }\n\n /**\n * Delete a message\n */\n async deleteMessage(messageRef: MessageRef): Promise<Result<void>> {\n if (!this.client) {\n return err(new ConnectionError('discord', new Error('Not connected')));\n }\n\n try {\n // Extract message ID and channel ID from MessageRef\n const messageId = typeof messageRef === 'string' ? messageRef : messageRef.id;\n const channelId = typeof messageRef === 'string'\n ? null\n : messageRef.channelId;\n\n if (!channelId) {\n return err(\n new MessageDeleteError(\n 'discord',\n messageId,\n new Error(\n 'Cannot delete message: channel context not found.\\n' +\n 'Pass the full message object instead:\\n' +\n ' bot.deleteMessage(message) // ✅ Works\\n' +\n ' bot.deleteMessage(message.id) // ❌ Missing channel context'\n )\n )\n );\n }\n\n // Fetch the channel\n const channel = await this.client.channels.fetch(channelId);\n if (!channel || !('messages' in channel)) {\n return err(\n new MessageDeleteError(\n 'discord',\n messageId,\n new Error(`Channel ${channelId} not found or is not a text channel`)\n )\n );\n }\n\n // Fetch and delete the message\n const message = await (channel as TextChannel).messages.fetch(messageId);\n await message.delete();\n\n return ok(undefined);\n } catch (error) {\n const messageId = typeof messageRef === 'string' ? messageRef : messageRef.id;\n return err(\n new MessageDeleteError(\n 'discord',\n messageId,\n error instanceof Error ? error : new Error(String(error))\n )\n );\n }\n }\n\n /**\n * Add a reaction to a message\n */\n async addReaction(messageRef: MessageRef, emoji: string): Promise<Result<void>> {\n if (!this.client) {\n return err(new ConnectionError('discord', new Error('Not connected')));\n }\n\n try {\n // Extract message ID and channel ID from MessageRef\n const messageId = typeof messageRef === 'string' ? messageRef : messageRef.id;\n const channelId = typeof messageRef === 'string'\n ? null\n : messageRef.channelId;\n\n if (!channelId) {\n return err(\n new ReactionError(\n 'discord',\n messageId,\n emoji,\n new Error(\n 'Cannot add reaction: channel context not found.\\n' +\n 'Pass the full message object instead:\\n' +\n ' bot.addReaction(message, emoji) // ✅ Works\\n' +\n ' bot.addReaction(message.id, emoji) // ❌ Missing channel context'\n )\n )\n );\n }\n\n // Fetch the channel\n const channel = await this.client.channels.fetch(channelId);\n if (!channel || !('messages' in channel)) {\n return err(\n new ReactionError(\n 'discord',\n messageId,\n emoji,\n new Error(`Channel ${channelId} not found or is not a text channel`)\n )\n );\n }\n\n // Fetch the message and add reaction\n const message = await (channel as TextChannel).messages.fetch(messageId);\n const discordEmoji = toDiscordEmoji(emoji);\n await message.react(discordEmoji);\n\n return ok(undefined);\n } catch (error) {\n const messageId = typeof messageRef === 'string' ? messageRef : messageRef.id;\n return err(\n new ReactionError(\n 'discord',\n messageId,\n emoji,\n error instanceof Error ? error : new Error(String(error))\n )\n );\n }\n }\n\n /**\n * Remove a reaction from a message\n */\n async removeReaction(\n messageRef: MessageRef,\n emoji: string\n ): Promise<Result<void>> {\n if (!this.client) {\n return err(new ConnectionError('discord', new Error('Not connected')));\n }\n\n try {\n // Extract message ID and channel ID from MessageRef\n const messageId = typeof messageRef === 'string' ? messageRef : messageRef.id;\n const channelId = typeof messageRef === 'string'\n ? null\n : messageRef.channelId;\n\n if (!channelId) {\n return err(\n new ReactionError(\n 'discord',\n messageId,\n emoji,\n new Error(\n 'Cannot remove reaction: channel context not found.\\n' +\n 'Pass the full message object instead:\\n' +\n ' bot.removeReaction(message, emoji) // ✅ Works\\n' +\n ' bot.removeReaction(message.id, emoji) // ❌ Missing channel context'\n )\n )\n );\n }\n\n // Fetch the channel\n const channel = await this.client.channels.fetch(channelId);\n if (!channel || !('messages' in channel)) {\n return err(\n new ReactionError(\n 'discord',\n messageId,\n emoji,\n new Error(`Channel ${channelId} not found or is not a text channel`)\n )\n );\n }\n\n // Fetch the message and remove reaction\n const message = await (channel as TextChannel).messages.fetch(messageId);\n const discordEmoji = toDiscordEmoji(emoji);\n\n // Remove the bot's own reaction\n await message.reactions.cache.get(discordEmoji)?.users.remove(this.client.user!.id);\n\n return ok(undefined);\n } catch (error) {\n const messageId = typeof messageRef === 'string' ? messageRef : messageRef.id;\n return err(\n new ReactionError(\n 'discord',\n messageId,\n emoji,\n error instanceof Error ? error : new Error(String(error))\n )\n );\n }\n }\n\n /**\n * Create a thread from a message\n */\n async createThread(\n messageRef: MessageRef,\n text: string\n ): Promise<Result<UnifiedMessage>> {\n if (!this.client) {\n return err(new ConnectionError('discord', new Error('Not connected')));\n }\n\n try {\n // Extract message ID and channel ID from MessageRef\n const messageId = typeof messageRef === 'string' ? messageRef : messageRef.id;\n const channelId = typeof messageRef === 'string'\n ? null\n : messageRef.channelId;\n\n if (!channelId) {\n return err(\n new MessageSendError(\n 'discord',\n 'unknown',\n new Error(\n 'Cannot create thread: channel context not found.\\n' +\n 'Pass the full message object instead:\\n' +\n ' bot.createThread(message, text) // ✅ Works\\n' +\n ' bot.createThread(message.id, text) // ❌ Missing channel context'\n )\n )\n );\n }\n\n // Fetch the channel\n const channel = await this.client.channels.fetch(channelId);\n if (!channel || !('messages' in channel)) {\n return err(\n new MessageSendError(\n 'discord',\n channelId,\n new Error(`Channel ${channelId} not found or is not a text channel`)\n )\n );\n }\n\n // Fetch the message and create thread\n const message = await (channel as TextChannel).messages.fetch(messageId);\n const thread = await message.startThread({\n name: text.substring(0, 100), // Discord thread names max 100 chars\n });\n\n // Send the first message in the thread\n const threadMessage = await thread.send(text);\n\n return ok(normalizeMessage(threadMessage));\n } catch (error) {\n const channelId = typeof messageRef === 'string' ? 'unknown' : messageRef.channelId;\n return err(\n new MessageSendError(\n 'discord',\n channelId,\n error instanceof Error ? error : new Error(String(error))\n )\n );\n }\n }\n\n /**\n * Upload a file to a channel\n */\n async uploadFile(\n channelId: string,\n file: unknown,\n options?: unknown\n ): Promise<Result<UnifiedMessage>> {\n if (!this.client) {\n return err(new ConnectionError('discord', new Error('Not connected')));\n }\n\n try {\n const channel = await this.client.channels.fetch(channelId);\n\n if (!channel || !channel.isTextBased()) {\n return err(\n new MessageSendError(\n 'discord',\n channelId,\n new Error('Channel not found or not text-based')\n )\n );\n }\n\n // Handle file upload\n // This is a simplified implementation - production would handle Buffer, Stream, File, etc.\n return err(\n new MessageSendError(\n 'discord',\n channelId,\n new Error('File upload not yet implemented')\n )\n );\n } catch (error) {\n return err(\n new MessageSendError(\n 'discord',\n channelId,\n error instanceof Error ? error : new Error(String(error))\n )\n );\n }\n }\n\n /**\n * Register an event handler\n */\n onEvent(handler: (event: UnifiedEvent) => void): void {\n this.eventHandlers.add(handler);\n }\n\n /**\n * Get list of channels\n */\n async getChannels(): Promise<Result<Channel[]>> {\n if (!this.client) {\n return err(new ConnectionError('discord', new Error('Not connected')));\n }\n\n try {\n const channels: Channel[] = [];\n\n for (const [, channel] of this.client.channels.cache) {\n if (channel.isTextBased() && !channel.isThread()) {\n channels.push(normalizeChannel(channel as TextChannel));\n }\n }\n\n return ok(channels);\n } catch (error) {\n return err(\n new ConnectionError(\n 'discord',\n error instanceof Error ? error : new Error(String(error))\n )\n );\n }\n }\n\n /**\n * Get list of users (in a specific channel or guild)\n */\n async getUsers(channelId?: string): Promise<Result<User[]>> {\n if (!this.client) {\n return err(new ConnectionError('discord', new Error('Not connected')));\n }\n\n try {\n const users: User[] = [];\n\n if (channelId) {\n const channel = await this.client.channels.fetch(channelId);\n if (channel && 'members' in channel) {\n for (const [, member] of (channel as any).members) {\n users.push(normalizeUser(member.user));\n }\n }\n } else {\n // Get all cached users\n for (const [, user] of this.client.users.cache) {\n users.push(normalizeUser(user));\n }\n }\n\n return ok(users);\n } catch (error) {\n return err(\n new ConnectionError(\n 'discord',\n error instanceof Error ? error : new Error(String(error))\n )\n );\n }\n }\n\n /**\n * Normalize a Discord message to UnifiedMessage\n */\n normalizeMessage(platformMessage: unknown): UnifiedMessage {\n return normalizeMessage(platformMessage as Message);\n }\n\n /**\n * Normalize a Discord event to UnifiedEvent\n */\n normalizeEvent(platformEvent: unknown): UnifiedEvent {\n // This would handle different Discord event types\n // For now, we assume it's a message event\n return normalizeMessageEvent(platformEvent as Message);\n }\n\n /**\n * Set up Discord event listeners\n */\n private setupEventListeners(): void {\n if (!this.client) return;\n\n // Message events\n this.client.on('messageCreate', (message: Message) => {\n // Ignore bot messages\n if (message.author.bot) return;\n\n const event = normalizeMessageEvent(message);\n this.emitEvent(event);\n });\n\n // Reaction events\n this.client.on('messageReactionAdd', async (reaction, user) => {\n // Handle partial reactions\n if (reaction.partial) {\n try {\n await reaction.fetch();\n } catch (error) {\n console.error('Failed to fetch reaction:', error);\n return;\n }\n }\n\n if (user.partial) {\n try {\n await user.fetch();\n } catch (error) {\n console.error('Failed to fetch user:', error);\n return;\n }\n }\n\n if (user.bot) return;\n\n const event = normalizeReactionEvent(reaction, user, 'added');\n this.emitEvent(event);\n });\n\n this.client.on('messageReactionRemove', async (reaction, user) => {\n // Handle partial reactions\n if (reaction.partial) {\n try {\n await reaction.fetch();\n } catch (error) {\n console.error('Failed to fetch reaction:', error);\n return;\n }\n }\n\n if (user.partial) {\n try {\n await user.fetch();\n } catch (error) {\n console.error('Failed to fetch user:', error);\n return;\n }\n }\n\n if (user.bot) return;\n\n const event = normalizeReactionEvent(reaction, user, 'removed');\n this.emitEvent(event);\n });\n\n // Ready event\n this.client.on('ready', () => {\n console.log(`[Switchboard] Discord bot connected as ${this.client!.user!.tag}`);\n });\n\n // Error handling\n this.client.on('error', (error) => {\n console.error('Discord client error:', error);\n });\n }\n\n /**\n * Emit an event to all registered handlers\n */\n private emitEvent(event: UnifiedEvent): void {\n for (const handler of this.eventHandlers) {\n try {\n handler(event);\n } catch (error) {\n console.error('Error in event handler:', error);\n }\n }\n }\n}\n","/**\n * Auto-registration for Discord adapter\n * This file is imported as a side effect to register the Discord adapter\n */\n\nimport { registry } from '@aarekaz/switchboard-core';\nimport { DiscordAdapter } from './adapter.js';\n\n// Create and register the Discord adapter\nconst discordAdapter = new DiscordAdapter();\nregistry.register('discord', discordAdapter);\n\n// Log registration (only in development)\nif (process.env.NODE_ENV !== 'production') {\n console.log('[Switchboard] Discord adapter registered');\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@aarekaz/switchboard-discord",
3
+ "version": "0.3.1",
4
+ "description": "Discord adapter for Switchboard SDK",
5
+ "keywords": [
6
+ "switchboard",
7
+ "discord",
8
+ "bot",
9
+ "adapter"
10
+ ],
11
+ "homepage": "https://github.com/Aarekaz/switchboard#readme",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/Aarekaz/switchboard.git",
15
+ "directory": "packages/discord"
16
+ },
17
+ "license": "MIT",
18
+ "author": "Aarekaz",
19
+ "type": "module",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js"
24
+ }
25
+ },
26
+ "main": "./dist/index.js",
27
+ "module": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
29
+ "files": [
30
+ "dist",
31
+ "README.md"
32
+ ],
33
+ "dependencies": {
34
+ "discord.js": "^14.14.1",
35
+ "@aarekaz/switchboard-core": "0.3.1"
36
+ },
37
+ "devDependencies": {
38
+ "tsup": "^8.0.1",
39
+ "typescript": "^5.3.3"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "scripts": {
45
+ "build": "tsup",
46
+ "dev": "tsup --watch",
47
+ "typecheck": "tsc --noEmit",
48
+ "clean": "rm -rf dist *.tsbuildinfo"
49
+ }
50
+ }