@zhin.js/adapter-discord 5.0.2 → 5.0.3

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.
Files changed (73) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +46 -77
  3. package/adapters/discord.ts +46 -0
  4. package/agent/tools/add_role.ts +2 -2
  5. package/agent/tools/create_thread.ts +2 -2
  6. package/agent/tools/forum_post.ts +2 -2
  7. package/agent/tools/list_roles.ts +2 -2
  8. package/agent/tools/react.ts +2 -2
  9. package/agent/tools/remove_role.ts +2 -2
  10. package/agent/tools/send_embed.ts +2 -2
  11. package/lib/discord-agent-deps.d.ts +35 -0
  12. package/lib/discord-agent-deps.js +32 -0
  13. package/lib/endpoint.d.ts +71 -0
  14. package/lib/endpoint.js +355 -0
  15. package/lib/gateway.d.ts +122 -0
  16. package/lib/gateway.js +235 -0
  17. package/lib/index.d.ts +6 -0
  18. package/lib/index.js +6 -0
  19. package/lib/platform-permit.d.ts +15 -0
  20. package/lib/{src/platform-permit.js → platform-permit.js} +1 -2
  21. package/lib/protocol.d.ts +135 -0
  22. package/lib/protocol.js +234 -0
  23. package/lib/webhook.d.ts +13 -0
  24. package/lib/webhook.js +86 -0
  25. package/package.json +43 -36
  26. package/plugin.ts +13 -0
  27. package/schema.json +63 -0
  28. package/src/discord-agent-deps.ts +68 -11
  29. package/src/endpoint.ts +385 -1167
  30. package/src/gateway.ts +337 -0
  31. package/src/index.ts +56 -157
  32. package/src/platform-permit.ts +1 -1
  33. package/src/protocol.ts +392 -0
  34. package/src/webhook.ts +121 -0
  35. package/client/Dashboard.tsx +0 -195
  36. package/client/index.tsx +0 -11
  37. package/client/tsconfig.json +0 -7
  38. package/client/utils/api.ts +0 -30
  39. package/dist/index.js +0 -29
  40. package/lib/agent/tools/add_role.js +0 -22
  41. package/lib/agent/tools/add_role.js.map +0 -1
  42. package/lib/agent/tools/create_thread.js +0 -23
  43. package/lib/agent/tools/create_thread.js.map +0 -1
  44. package/lib/agent/tools/forum_post.js +0 -22
  45. package/lib/agent/tools/forum_post.js.map +0 -1
  46. package/lib/agent/tools/list_roles.js +0 -20
  47. package/lib/agent/tools/list_roles.js.map +0 -1
  48. package/lib/agent/tools/react.js +0 -20
  49. package/lib/agent/tools/react.js.map +0 -1
  50. package/lib/agent/tools/remove_role.js +0 -22
  51. package/lib/agent/tools/remove_role.js.map +0 -1
  52. package/lib/agent/tools/send_embed.js +0 -40
  53. package/lib/agent/tools/send_embed.js.map +0 -1
  54. package/lib/src/adapter.js +0 -96
  55. package/lib/src/adapter.js.map +0 -1
  56. package/lib/src/discord-agent-deps.js +0 -10
  57. package/lib/src/discord-agent-deps.js.map +0 -1
  58. package/lib/src/endpoint-interactions.js +0 -284
  59. package/lib/src/endpoint-interactions.js.map +0 -1
  60. package/lib/src/endpoint.js +0 -1093
  61. package/lib/src/endpoint.js.map +0 -1
  62. package/lib/src/index.js +0 -159
  63. package/lib/src/index.js.map +0 -1
  64. package/lib/src/platform-permit.js.map +0 -1
  65. package/lib/src/segment-mapper.js +0 -2
  66. package/lib/src/segment-mapper.js.map +0 -1
  67. package/lib/src/types.js +0 -2
  68. package/lib/src/types.js.map +0 -1
  69. package/plugin.yml +0 -3
  70. package/src/adapter.ts +0 -108
  71. package/src/endpoint-interactions.ts +0 -354
  72. package/src/segment-mapper.ts +0 -1
  73. package/src/types.ts +0 -60
@@ -1,354 +0,0 @@
1
- /**
2
- * Discord Interactions Endpoint 实现
3
- */
4
- import {
5
- Client,
6
- GatewayIntentBits,
7
- Message as DiscordMessage,
8
- TextChannel,
9
- DMChannel,
10
- NewsChannel,
11
- ThreadChannel,
12
- EmbedBuilder,
13
- AttachmentBuilder,
14
- MessageCreateOptions,
15
- ChannelType,
16
- REST,
17
- Routes,
18
- ApplicationCommandData,
19
- ChatInputCommandInteraction,
20
- InteractionType,
21
- InteractionResponseType,
22
- } from "discord.js";
23
- import nacl from "tweetnacl";
24
- import { Endpoint, Message, SendOptions, SendContent, MessageSegment, segment,} from 'zhin.js';
25
- import { registerFetchRoute, type Router, type RouterContext } from "@zhin.js/host-router/router";
26
- import type { DiscordInteractionsConfig } from "./types.js";
27
- import type { DiscordAdapter } from "./adapter.js";
28
-
29
- export class DiscordInteractionsEndpoint
30
- extends Client
31
- implements Endpoint<DiscordInteractionsConfig, any> {
32
- $connected: boolean;
33
- private router: Router;
34
- private slashCommandHandlers: Map<
35
- string,
36
- (interaction: any) => Promise<void>
37
- > = new Map();
38
-
39
- get pluginLogger() {
40
- return this.adapter.plugin.logger;
41
- }
42
-
43
- get $id() {
44
- return this.$config.name;
45
- }
46
-
47
- constructor(public adapter: DiscordAdapter, router: Router, public $config: DiscordInteractionsConfig) {
48
- const intents = $config.intents || [
49
- GatewayIntentBits.Guilds,
50
- GatewayIntentBits.GuildMessages,
51
- GatewayIntentBits.MessageContent,
52
- ];
53
-
54
- super({ intents });
55
- this.$connected = false;
56
- this.router = router;
57
-
58
- // 设置交互端点路由
59
- this.setupInteractionsEndpoint();
60
- }
61
-
62
- private setupInteractionsEndpoint(): void {
63
- // 设置路由处理 Discord Interactions
64
- registerFetchRoute(this.router, "POST", this.$config.interactionsPath, (ctx: RouterContext) => {
65
- void this.handleInteraction(ctx);
66
- });
67
- }
68
-
69
- private async handleInteraction(ctx: RouterContext): Promise<void> {
70
- try {
71
- const signature = ctx.get("x-signature-ed25519");
72
- const timestamp = ctx.get("x-signature-timestamp");
73
- const bodyString = JSON.stringify(ctx.request.body);
74
-
75
- if (!signature || !timestamp) {
76
- ctx.status = 401;
77
- ctx.body = "Unauthorized";
78
- return;
79
- }
80
-
81
- // 验证请求签名
82
- if (!this.verifyDiscordSignature(bodyString, signature, timestamp)) {
83
- this.pluginLogger.warn("Invalid Discord signature");
84
- ctx.status = 401;
85
- ctx.body = "Unauthorized";
86
- return;
87
- }
88
-
89
- const interaction = ctx.request.body as Record<string, unknown>;
90
-
91
- // 处理不同类型的交互
92
- if (interaction.type === InteractionType.Ping) {
93
- // PING - Discord 验证端点
94
- ctx.body = { type: InteractionResponseType.Pong };
95
- } else if (interaction.type === InteractionType.ApplicationCommand) {
96
- // APPLICATION_COMMAND - 应用命令
97
- const response = await this.handleApplicationCommand(interaction);
98
- ctx.body = response;
99
- } else {
100
- // 其他交互类型
101
- ctx.status = 400;
102
- ctx.body = "Unsupported interaction type";
103
- }
104
- } catch (error) {
105
- this.pluginLogger.error("Interactions error:", error);
106
- ctx.status = 500;
107
- ctx.body = "Internal Server Error";
108
- }
109
- }
110
-
111
- private verifyDiscordSignature(
112
- body: string,
113
- signature: string,
114
- timestamp: string
115
- ): boolean {
116
- try {
117
- const publicKey = Buffer.from(this.$config.publicKey, "hex");
118
- const sig = Buffer.from(signature, "hex");
119
- const message = Buffer.from(timestamp + body, "utf8");
120
-
121
- return nacl.sign.detached.verify(message, sig, publicKey);
122
- } catch (error) {
123
- this.pluginLogger.error("Signature verification error:", error);
124
- return false;
125
- }
126
- }
127
-
128
- private async handleApplicationCommand(interaction: any): Promise<any> {
129
- // 处理应用命令
130
- const commandName = interaction.data.name;
131
-
132
- // 转换为标准消息格式并分发
133
- const message = this.formatInteractionAsMessage(interaction);
134
- this.adapter.emit("message.receive", message);
135
-
136
- // 查找自定义处理器
137
- const handler = this.slashCommandHandlers.get(commandName);
138
- if (handler) {
139
- try {
140
- await handler(interaction);
141
- } catch (error) {
142
- this.pluginLogger.error(
143
- `Error in slash command handler for ${commandName}:`,
144
- error
145
- );
146
- }
147
- }
148
-
149
- // 默认响应
150
- return {
151
- type: InteractionResponseType.ChannelMessageWithSource,
152
- data: {
153
- content: `处理命令: ${commandName}`,
154
- flags: 64, // EPHEMERAL - 只有用户可见
155
- },
156
- };
157
- }
158
-
159
- private formatInteractionAsMessage(interaction: any): Message<any> {
160
- const channelType = interaction.guild_id ? "channel" : "private";
161
- const channelId = interaction.channel_id;
162
-
163
- // 解析命令参数为内容
164
- const options = interaction.data.options || [];
165
- const content = [segment.text(`/${interaction.data.name}`)];
166
-
167
- for (const option of options) {
168
- content.push(segment.text(` ${option.name}:${option.value}`));
169
- }
170
-
171
- return Message.from(interaction, {
172
- $id: interaction.id,
173
- $adapter: "discord",
174
- $endpoint: this.$config.name,
175
- $sender: {
176
- id: interaction.user?.id || interaction.member?.user?.id,
177
- name: interaction.user?.username || interaction.member?.user?.username,
178
- },
179
- $channel: {
180
- id: channelId,
181
- type: channelType as any,
182
- },
183
- $raw: JSON.stringify(interaction),
184
- $timestamp: Date.now(),
185
- $content: content,
186
- $recall: async () => {
187
- // Interactions 消息无法撤回
188
- },
189
- $reply: async (content: SendContent): Promise<string> => {
190
- return this.$sendMessage({
191
- ...this.$formatMessage(interaction),
192
- content: content,
193
- });
194
- },
195
- });
196
- }
197
-
198
- private formatSendContent(content: SendContent): any {
199
- if (typeof content === "string") {
200
- return { content };
201
- }
202
-
203
- if (Array.isArray(content)) {
204
- const textParts: string[] = [];
205
- let embed: any = null;
206
-
207
- for (const item of content) {
208
- if (typeof item === "string") {
209
- textParts.push(item);
210
- } else {
211
- const segment = item as MessageSegment;
212
- switch (segment.type) {
213
- case "text":
214
- textParts.push(segment.data.text || segment.data.content || "");
215
- break;
216
- case "embed":
217
- embed = segment.data;
218
- break;
219
- }
220
- }
221
- }
222
-
223
- const result: any = {};
224
- if (textParts.length > 0) {
225
- result.content = textParts.join("");
226
- }
227
- if (embed) {
228
- result.embeds = [embed];
229
- }
230
-
231
- return result;
232
- }
233
-
234
- return { content: String(content) };
235
- }
236
-
237
- async $connect(): Promise<void> {
238
- try {
239
- // 注册 Slash Commands
240
- if (this.$config.slashCommands) {
241
- await this.registerSlashCommands();
242
- }
243
-
244
- // 如果启用 Gateway,连接 Discord Gateway
245
- if (this.$config.useGateway) {
246
- await this.login(this.$config.token);
247
-
248
- // 设置活动状态
249
- if (this.$config.defaultActivity) {
250
- this.user?.setActivity(this.$config.defaultActivity.name, {
251
- type: this.getActivityType(this.$config.defaultActivity.type),
252
- url: this.$config.defaultActivity.url,
253
- });
254
- }
255
- }
256
-
257
- this.$connected = true;
258
- this.pluginLogger.info(
259
- `Discord interactions bot connected: ${this.$config.name}`
260
- );
261
- this.pluginLogger.info(
262
- `Interactions endpoint: ${this.$config.interactionsPath}`
263
- );
264
- } catch (error) {
265
- this.pluginLogger.error("Failed to connect Discord interactions bot:", error);
266
- throw error;
267
- }
268
- }
269
-
270
- async $disconnect(): Promise<void> {
271
- try {
272
- if (this.isReady()) {
273
- await this.destroy();
274
- }
275
- this.$connected = false;
276
- this.pluginLogger.info("Discord interactions bot disconnected");
277
- } catch (error) {
278
- this.pluginLogger.error(
279
- "Error disconnecting Discord interactions bot:",
280
- error
281
- );
282
- }
283
- }
284
-
285
- // Slash Commands 管理
286
- private async registerSlashCommands(): Promise<void> {
287
- if (!this.$config.slashCommands) return;
288
-
289
- try {
290
- const rest = new REST({ version: "10" }).setToken(this.$config.token);
291
-
292
- if (this.$config.globalCommands) {
293
- await rest.put(Routes.applicationCommands(this.$config.applicationId), {
294
- body: this.$config.slashCommands,
295
- });
296
- this.pluginLogger.info("Successfully registered global slash commands");
297
- } else {
298
- this.pluginLogger.info(
299
- "Note: Guild commands registration requires connecting to Gateway first"
300
- );
301
- }
302
- } catch (error) {
303
- this.pluginLogger.error("Failed to register slash commands:", error);
304
- }
305
- }
306
-
307
- // 添加 Slash Command 处理器
308
- addSlashCommandHandler(
309
- commandName: string,
310
- handler: (interaction: any) => Promise<void>
311
- ): void {
312
- this.slashCommandHandlers.set(commandName, handler);
313
- }
314
-
315
- // 移除 Slash Command 处理器
316
- removeSlashCommandHandler(commandName: string): boolean {
317
- return this.slashCommandHandlers.delete(commandName);
318
- }
319
-
320
- // 工具方法
321
- private getActivityType(type: string): any {
322
- const activityTypes: any = {
323
- PLAYING: 0,
324
- STREAMING: 1,
325
- LISTENING: 2,
326
- WATCHING: 3,
327
- COMPETING: 5,
328
- };
329
- return activityTypes[type] || 0;
330
- }
331
-
332
- // 简化实现 - 只支持基本消息格式化和发送
333
- $formatMessage(msg: any): Message<any> {
334
- return this.formatInteractionAsMessage(msg);
335
- }
336
-
337
- async $sendMessage(options: SendOptions): Promise<string> {
338
- // 简化实现 - 通过 REST API 发送消息
339
- try {
340
- const rest = new REST({ version: "10" }).setToken(this.$config.token);
341
- const messageContent = this.formatSendContent(options.content);
342
-
343
- await rest.post(Routes.channelMessages(options.id), {
344
- body: messageContent,
345
- });
346
- } catch (error) {
347
- this.pluginLogger.error("Failed to send message:", error);
348
- }
349
- return "";
350
- }
351
- async $recallMessage(id: string): Promise<void> { }
352
- }
353
-
354
- // 定义 Adapter 类
@@ -1 +0,0 @@
1
- export { toCanonicalSegments, fromCanonicalSegments } from 'zhin.js';
package/src/types.ts DELETED
@@ -1,60 +0,0 @@
1
- /**
2
- * Discord 适配器类型定义:单一适配器,connection 区分 Gateway / Interactions
3
- */
4
- import type {
5
- Message as DiscordMessage,
6
- GatewayIntentBits,
7
- ApplicationCommandData,
8
- } from "discord.js";
9
-
10
- export type { GatewayIntentBits, ApplicationCommandData };
11
-
12
- /** 配置公共字段;单一适配器 context 均为 'discord',连接方式由 connection 区分 */
13
- export interface DiscordConfigBase {
14
- context: "discord";
15
- name: string;
16
- token: string;
17
- }
18
-
19
- /** Gateway 连接:常规 WebSocket 网关 */
20
- export interface DiscordGatewayConfig extends DiscordConfigBase {
21
- connection: "gateway";
22
- intents?: GatewayIntentBits[];
23
- enableSlashCommands?: boolean;
24
- globalCommands?: boolean;
25
- defaultActivity?: {
26
- name: string;
27
- type: "PLAYING" | "STREAMING" | "LISTENING" | "WATCHING" | "COMPETING";
28
- url?: string;
29
- };
30
- slashCommands?: ApplicationCommandData[];
31
- }
32
-
33
- /** Interactions 连接:HTTP 交互端点(斜杠命令等) */
34
- export interface DiscordInteractionsConfig extends DiscordConfigBase {
35
- connection: "interactions";
36
- applicationId: string;
37
- publicKey: string;
38
- interactionsPath: string;
39
- useGateway?: boolean;
40
- intents?: GatewayIntentBits[];
41
- slashCommands?: ApplicationCommandData[];
42
- globalCommands?: boolean;
43
- defaultActivity?: {
44
- name: string;
45
- type: "PLAYING" | "STREAMING" | "LISTENING" | "WATCHING" | "COMPETING";
46
- url?: string;
47
- };
48
- }
49
-
50
- export type DiscordEndpointConfig = DiscordGatewayConfig | DiscordInteractionsConfig;
51
-
52
- export interface DiscordEndpoint {
53
- $config: DiscordGatewayConfig;
54
- }
55
-
56
- export interface DiscordInteractionsEndpoint {
57
- $config: DiscordInteractionsConfig;
58
- }
59
-
60
- export type DiscordChannelMessage = DiscordMessage<boolean>;