@zhin.js/adapter-discord 5.0.1 → 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 (68) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +51 -76
  3. package/adapters/discord.ts +46 -0
  4. package/agent/tools/add_role.ts +24 -0
  5. package/agent/tools/create_thread.ts +25 -0
  6. package/agent/tools/forum_post.ts +24 -0
  7. package/agent/tools/list_roles.ts +22 -0
  8. package/agent/tools/react.ts +22 -0
  9. package/agent/tools/remove_role.ts +24 -0
  10. package/agent/tools/send_embed.ts +37 -0
  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 +66 -119
  14. package/lib/endpoint.js +308 -1047
  15. package/lib/gateway.d.ts +122 -0
  16. package/lib/gateway.js +235 -0
  17. package/lib/index.d.ts +6 -18
  18. package/lib/index.js +6 -330
  19. package/lib/platform-permit.d.ts +1 -2
  20. package/lib/platform-permit.js +4 -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 +48 -31
  26. package/plugin.ts +13 -0
  27. package/schema.json +63 -0
  28. package/src/discord-agent-deps.ts +79 -0
  29. package/src/endpoint.ts +385 -1167
  30. package/src/gateway.ts +337 -0
  31. package/src/index.ts +55 -332
  32. package/src/platform-permit.ts +1 -2
  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/adapter.d.ts +0 -25
  41. package/lib/adapter.d.ts.map +0 -1
  42. package/lib/adapter.js +0 -96
  43. package/lib/adapter.js.map +0 -1
  44. package/lib/endpoint-interactions.d.ts +0 -34
  45. package/lib/endpoint-interactions.d.ts.map +0 -1
  46. package/lib/endpoint-interactions.js +0 -284
  47. package/lib/endpoint-interactions.js.map +0 -1
  48. package/lib/endpoint.d.ts.map +0 -1
  49. package/lib/endpoint.js.map +0 -1
  50. package/lib/index.d.ts.map +0 -1
  51. package/lib/index.js.map +0 -1
  52. package/lib/platform-permit.d.ts.map +0 -1
  53. package/lib/platform-permit.js.map +0 -1
  54. package/lib/segment-mapper.d.ts +0 -2
  55. package/lib/segment-mapper.d.ts.map +0 -1
  56. package/lib/segment-mapper.js +0 -2
  57. package/lib/segment-mapper.js.map +0 -1
  58. package/lib/types.d.ts +0 -49
  59. package/lib/types.d.ts.map +0 -1
  60. package/lib/types.js +0 -2
  61. package/lib/types.js.map +0 -1
  62. package/plugin.yml +0 -3
  63. package/src/adapter.ts +0 -108
  64. package/src/endpoint-interactions.ts +0 -354
  65. package/src/segment-mapper.ts +0 -1
  66. package/src/types.ts +0 -60
  67. /package/{skills/discord → agent}/PERMITS.md +0 -0
  68. /package/{skills/discord/SKILL.md → agent/skills/discord.md} +0 -0
package/src/index.ts CHANGED
@@ -1,335 +1,58 @@
1
- /**
2
- * Discord 适配器入口:单一适配器,支持 Gateway / Interactions(connection: gateway | interactions)
3
- */
4
- import path from "node:path";
5
- import { usePlugin, type Plugin, type Context, type ISceneManagement, createSceneManagementTools, type ToolFeature } from "zhin.js";
6
- import type { Router } from "@zhin.js/host-router";
7
- import { PageManager } from "@zhin.js/host-api";
8
- import { DiscordAdapter, type DiscordEndpointLike } from "./adapter.js";
9
- import {
1
+ export {
2
+ activityTypeCode,
3
+ formatButtonContent,
4
+ formatInboundContent,
5
+ formatOutboundBody,
6
+ resolveChannelKind,
7
+ resolveDiscordConfig,
8
+ senderDisplayName,
9
+ type DiscordAdapterConfig,
10
+ type DiscordButtonInbound,
11
+ type DiscordInboundAttachment,
12
+ type DiscordInboundMessage,
13
+ type DiscordOutboundBody,
14
+ type DiscordWireSegment,
15
+ type ResolvedDiscordConfig,
16
+ type ResolvedDiscordGatewayConfig,
17
+ type ResolvedDiscordInteractionsConfig,
18
+ } from './protocol.js';
19
+
20
+ export {
21
+ getDiscordAgentDeps,
22
+ registerDiscordAgentEndpoint,
23
+ setDiscordAgentDeps,
24
+ type DiscordAgentDeps,
25
+ type DiscordAgentEndpoint,
26
+ } from './discord-agent-deps.js';
27
+
28
+ export {
29
+ checkDiscordPlatformPermit,
10
30
  discordGroupPermitResolver,
31
+ normalizeDiscordSenderForPermit,
11
32
  platformPermit,
12
33
  registerDiscordPlatformPermitChecker,
13
- } from "./platform-permit.js";
14
-
15
- declare module "zhin.js" {
16
- namespace Plugin {
17
- interface Contexts {
18
- router: import("@zhin.js/host-router").Router;
19
- web: PageManager;
20
- }
21
- }
22
- interface Adapters {
23
- discord: DiscordAdapter;
24
- }
25
- }
26
-
27
- export * from "./types.js";
28
- export { DiscordEndpoint } from "./endpoint.js";
29
- export { DiscordInteractionsEndpoint } from "./endpoint-interactions.js";
30
- export { DiscordAdapter, type DiscordEndpointLike } from "./adapter.js";
31
-
32
- const plugin = usePlugin();
33
- const { provide, useContext } = plugin;
34
- provide({
35
- name: "discord",
36
- description: "Discord 适配器(Gateway / Interactions)",
37
- mounted: async (p: Plugin) => {
38
- const adapter = new DiscordAdapter(p);
39
- await adapter.start();
40
- return adapter;
41
- },
42
- dispose: async (adapter: DiscordAdapter) => {
43
- await adapter.stop();
44
- },
45
- });
46
-
47
- useContext('tool', 'discord', (toolService: ToolFeature, discord: DiscordAdapter) => {
48
- const disposers: (() => void)[] = [];
49
- disposers.push(registerDiscordPlatformPermitChecker());
50
- const sceneTools = createSceneManagementTools(
51
- discord as unknown as ISceneManagement,
52
- 'discord',
53
- { permitResolver: discordGroupPermitResolver, registerChecker: false },
54
- );
55
- disposers.push(...sceneTools.map(t => toolService.addTool(t, plugin.name)));
56
-
57
- function getGatewayBot(endpointId: string): DiscordEndpointLike {
58
- const endpoint = discord.endpoints.get(endpointId);
59
- if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
60
- if ((endpoint.$config as { connection?: string }).connection !== 'gateway') {
61
- throw new Error('此工具仅支持 connection: gateway');
62
- }
63
- return endpoint;
64
- }
65
-
66
- disposers.push(toolService.addTool({
67
- name: 'discord_add_role',
68
- description: '给成员添加 Discord 角色',
69
- parameters: {
70
- type: 'object',
71
- properties: {
72
- endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
73
- guild_id: { type: 'string', description: '服务器 ID' },
74
- user_id: { type: 'string', description: '用户 ID' },
75
- role_id: { type: 'string', description: '角色 ID' },
76
- },
77
- required: ['endpoint_id', 'guild_id', 'user_id', 'role_id'],
78
- },
79
- platforms: ['discord'],
80
- tags: ['discord'],
81
- permissions: [platformPermit('manage_roles')],
82
- execute: async (args: Record<string, any>) => {
83
- const endpoint = getGatewayBot(args.endpoint_id) as any;
84
- const success = await endpoint.addRole(args.guild_id, args.user_id, args.role_id);
85
- return { success, message: success ? `已给用户 ${args.user_id} 添加角色` : '操作失败' };
86
- },
87
- }, plugin.name));
88
-
89
- disposers.push(toolService.addTool({
90
- name: 'discord_remove_role',
91
- description: '移除成员的 Discord 角色',
92
- parameters: {
93
- type: 'object',
94
- properties: {
95
- endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
96
- guild_id: { type: 'string', description: '服务器 ID' },
97
- user_id: { type: 'string', description: '用户 ID' },
98
- role_id: { type: 'string', description: '角色 ID' },
99
- },
100
- required: ['endpoint_id', 'guild_id', 'user_id', 'role_id'],
101
- },
102
- platforms: ['discord'],
103
- tags: ['discord'],
104
- permissions: [platformPermit('manage_roles')],
105
- execute: async (args: Record<string, any>) => {
106
- const endpoint = getGatewayBot(args.endpoint_id) as any;
107
- const success = await endpoint.removeRole(args.guild_id, args.user_id, args.role_id);
108
- return { success, message: success ? `已移除用户 ${args.user_id} 的角色` : '操作失败' };
109
- },
110
- }, plugin.name));
111
-
112
- disposers.push(toolService.addTool({
113
- name: 'discord_list_roles',
114
- description: '获取 Discord 服务器角色列表',
115
- parameters: {
116
- type: 'object',
117
- properties: {
118
- endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
119
- guild_id: { type: 'string', description: '服务器 ID' },
120
- },
121
- required: ['endpoint_id', 'guild_id'],
122
- },
123
- platforms: ['discord'],
124
- tags: ['discord'],
125
- permissions: [platformPermit('manage_roles')],
126
- execute: async (args: Record<string, any>) => {
127
- const endpoint = getGatewayBot(args.endpoint_id) as any;
128
- const roles = await endpoint.getRoles(args.guild_id);
129
- return { roles, count: roles.length };
130
- },
131
- }, plugin.name));
132
-
133
- disposers.push(toolService.addTool({
134
- name: 'discord_create_thread',
135
- description: '在 Discord 频道中创建帖子/子线程',
136
- parameters: {
137
- type: 'object',
138
- properties: {
139
- endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
140
- channel_id: { type: 'string', description: '频道 ID' },
141
- name: { type: 'string', description: '帖子标题' },
142
- message_id: { type: 'string', description: '基于某条消息创建(可选)' },
143
- auto_archive_duration: {
144
- type: 'number',
145
- description: '自动归档时间(分钟:60/1440/4320/10080)',
146
- },
147
- },
148
- required: ['endpoint_id', 'channel_id', 'name'],
149
- },
150
- platforms: ['discord'],
151
- tags: ['discord'],
152
- permissions: [platformPermit('manage_channels')],
153
- execute: async (args: Record<string, any>) => {
154
- const endpoint = getGatewayBot(args.endpoint_id) as any;
155
- const thread = await endpoint.createThread(args.channel_id, args.name, args.message_id, args.auto_archive_duration);
156
- return { success: true, thread_id: thread.id, message: `帖子 "${args.name}" 已创建` };
157
- },
158
- }, plugin.name));
159
-
160
- disposers.push(toolService.addTool({
161
- name: 'discord_react',
162
- description: '对 Discord 消息添加表情反应',
163
- parameters: {
164
- type: 'object',
165
- properties: {
166
- endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
167
- channel_id: { type: 'string', description: '频道 ID' },
168
- message_id: { type: 'string', description: '消息 ID' },
169
- emoji: {
170
- type: 'string',
171
- description: '表情(Unicode 表情或自定义表情如 <:name:id>)',
172
- },
173
- },
174
- required: ['endpoint_id', 'channel_id', 'message_id', 'emoji'],
175
- },
176
- platforms: ['discord'],
177
- tags: ['discord'],
178
- execute: async (args: Record<string, any>) => {
179
- const endpoint = getGatewayBot(args.endpoint_id) as any;
180
- await endpoint.addReaction(args.channel_id, args.message_id, args.emoji);
181
- return { success: true, message: `已添加反应 ${args.emoji}` };
182
- },
183
- }, plugin.name));
184
-
185
- disposers.push(toolService.addTool({
186
- name: 'discord_send_embed',
187
- description: '发送 Discord 富文本嵌入消息(Embed)',
188
- parameters: {
189
- type: 'object',
190
- properties: {
191
- endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
192
- channel_id: { type: 'string', description: '频道 ID' },
193
- title: { type: 'string', description: 'Embed 标题' },
194
- description: { type: 'string', description: 'Embed 描述' },
195
- color: { type: 'number', description: '颜色值(十进制,如 0x00ff00 = 65280)' },
196
- url: { type: 'string', description: '标题链接(可选)' },
197
- fields: {
198
- type: 'string',
199
- description: '字段,JSON 格式: [{"name":"k","value":"v","inline":false}]',
200
- },
201
- },
202
- required: ['endpoint_id', 'channel_id'],
203
- },
204
- platforms: ['discord'],
205
- tags: ['discord'],
206
- execute: async (args: Record<string, any>) => {
207
- const endpoint = getGatewayBot(args.endpoint_id) as any;
208
- const embedData: any = {};
209
- if (args.title) embedData.title = args.title;
210
- if (args.description) embedData.description = args.description;
211
- if (args.color) embedData.color = args.color;
212
- if (args.url) embedData.url = args.url;
213
- if (args.fields) {
214
- try {
215
- embedData.fields = JSON.parse(args.fields);
216
- } catch {
217
- return { success: false, message: 'fields 格式错误,应为 JSON 数组' };
218
- }
219
- }
220
- const msg = await endpoint.sendEmbed(args.channel_id, embedData);
221
- return { success: true, message_id: msg.id, message: 'Embed 已发送' };
222
- },
223
- }, plugin.name));
224
-
225
- disposers.push(toolService.addTool({
226
- name: 'discord_forum_post',
227
- description: '在 Discord 论坛频道中创建帖子',
228
- parameters: {
229
- type: 'object',
230
- properties: {
231
- endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
232
- channel_id: { type: 'string', description: '论坛频道 ID' },
233
- name: { type: 'string', description: '帖子标题' },
234
- content: { type: 'string', description: '帖子内容' },
235
- tags: { type: 'string', description: '标签名,逗号分隔(可选)' },
236
- },
237
- required: ['endpoint_id', 'channel_id', 'name', 'content'],
238
- },
239
- platforms: ['discord'],
240
- tags: ['discord'],
241
- execute: async (args: Record<string, any>) => {
242
- const endpoint = getGatewayBot(args.endpoint_id) as any;
243
- const tagList = args.tags ? args.tags.split(',').map((t: string) => t.trim()) : undefined;
244
- const thread = await endpoint.createForumPost(args.channel_id, args.name, args.content, tagList);
245
- return { success: true, thread_id: thread.id, message: `论坛帖 "${args.name}" 已创建` };
246
- },
247
- }, plugin.name));
248
-
249
- return () => disposers.forEach(d => d());
250
- });
251
-
252
- // ── Web 控制台 ─────────────────────────────────────────────────────────
253
- useContext("web", (pageManager) => {
254
- pageManager.addEntry({
255
- id: "discord",
256
- development: path.resolve(import.meta.dirname, "../client/index.tsx"),
257
- production: path.resolve(import.meta.dirname, "../dist/index.js"),
258
- meta: { name: "Discord" },
259
- });
260
- });
261
-
262
- useContext("router", "discord", (router: Router, discord: DiscordAdapter) => {
263
- router.get("/api/discord/endpoints", async (ctx: any) => {
264
- try {
265
- const endpoints = Array.from(discord.endpoints.values());
266
- const result = endpoints.map((endpoint: any) => {
267
- try {
268
- const client = endpoint.client || endpoint;
269
- return {
270
- name: endpoint.$config.name,
271
- connected: endpoint.$connected || false,
272
- mode: endpoint.$config.connection || "gateway",
273
- guildCount: client.guilds?.cache?.size || 0,
274
- channelCount: client.channels?.cache?.size || 0,
275
- status: endpoint.$connected ? "online" : "offline",
276
- user: client.user ? { tag: client.user.tag, id: client.user.id } : null,
277
- };
278
- } catch {
279
- return { name: endpoint.$config.name, connected: false, mode: "unknown", guildCount: 0, channelCount: 0, status: "error", user: null };
280
- }
281
- });
282
- ctx.body = { success: true, data: result };
283
- } catch {
284
- ctx.status = 500;
285
- ctx.body = { success: false, error: "获取 Endpoint 数据失败" };
286
- }
287
- });
288
-
289
- // Endpoint 连接/断开
290
- router.post("/api/discord/endpoints/:name/connect", async (ctx: any) => {
291
- try {
292
- const endpoint = discord.endpoints.get(ctx.params.name);
293
- if (!endpoint) { ctx.status = 404; ctx.body = { success: false, error: "Endpoint 不存在" }; return; }
294
- if (endpoint.$connected) { ctx.body = { success: true, message: "已经在线" }; return; }
295
- await endpoint.$connect();
296
- ctx.body = { success: true, message: "连接成功" };
297
- } catch (e: unknown) {
298
- ctx.status = 500;
299
- ctx.body = { success: false, error: e instanceof Error ? e.message : "连接失败" };
300
- }
301
- });
302
-
303
- router.post("/api/discord/endpoints/:name/disconnect", async (ctx: any) => {
304
- try {
305
- const endpoint = discord.endpoints.get(ctx.params.name);
306
- if (!endpoint) { ctx.status = 404; ctx.body = { success: false, error: "Endpoint 不存在" }; return; }
307
- if (!endpoint.$connected) { ctx.body = { success: true, message: "已经离线" }; return; }
308
- await endpoint.$disconnect();
309
- ctx.body = { success: true, message: "已断开" };
310
- } catch (e: unknown) {
311
- ctx.status = 500;
312
- ctx.body = { success: false, error: e instanceof Error ? e.message : "断开失败" };
313
- }
314
- });
315
-
316
- // 服务器列表(仅 Gateway 模式)
317
- router.get("/api/discord/endpoints/:name/guilds", async (ctx: any) => {
318
- try {
319
- const endpoint: any = discord.endpoints.get(ctx.params.name);
320
- if (!endpoint) { ctx.status = 404; ctx.body = { success: false, error: "Endpoint 不存在" }; return; }
321
- if (!endpoint.$connected) { ctx.status = 400; ctx.body = { success: false, error: "Endpoint 未连接" }; return; }
322
- const client = endpoint.client || endpoint;
323
- const guilds = client.guilds?.cache?.map((g: any) => ({
324
- id: g.id,
325
- name: g.name,
326
- memberCount: g.memberCount,
327
- icon: g.iconURL({ size: 64 }),
328
- })) || [];
329
- ctx.body = { success: true, data: guilds };
330
- } catch (e: unknown) {
331
- ctx.status = 500;
332
- ctx.body = { success: false, error: e instanceof Error ? e.message : "获取服务器列表失败" };
333
- }
334
- });
335
- });
34
+ } from './platform-permit.js';
35
+
36
+ export {
37
+ DiscordGatewayEndpoint,
38
+ DiscordInteractionsEndpoint,
39
+ type CreateDiscordClient,
40
+ type DiscordClientTransport,
41
+ type DiscordEndpointOptions,
42
+ type DiscordInteractionsEndpointOptions,
43
+ } from './endpoint.js';
44
+
45
+ export {
46
+ connectDiscordGatewayClient,
47
+ defaultCreateClient,
48
+ normalizeDiscordMessage,
49
+ resolveSenderRole,
50
+ toMessageCreateOptions,
51
+ type DiscordGatewayConnectHandlers,
52
+ } from './gateway.js';
53
+
54
+ export {
55
+ handleDiscordInteractionRequest,
56
+ registerDiscordInteractionRoutes,
57
+ type DiscordInteractionsHandler,
58
+ } from './webhook.js';
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * Discord platform permit — Guild 权限位
3
3
  */
4
- import type { Message } from 'zhin.js';
5
- import { registerPlatformPermitChecker } from 'zhin.js';
4
+ import { registerPlatformPermitChecker, type Message } from '@zhin.js/core';
6
5
 
7
6
  const ADAPTER = 'discord';
8
7