@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 +86 -0
- package/dist/index.d.ts +127 -0
- package/dist/index.js +590 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
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
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|