@zhin.js/adapter-discord 1.0.87 → 1.1.2

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