@zhin.js/adapter-discord 5.0.2 → 6.0.0

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 +62 -0
  2. package/README.md +49 -79
  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 +105 -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
package/src/gateway.ts ADDED
@@ -0,0 +1,337 @@
1
+ import { createReadStream, promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+ import {
4
+ Client,
5
+ GatewayIntentBits,
6
+ EmbedBuilder,
7
+ AttachmentBuilder,
8
+ ActionRowBuilder,
9
+ ButtonBuilder,
10
+ ButtonStyle,
11
+ REST,
12
+ Routes,
13
+ PermissionFlagsBits,
14
+ type MessageCreateOptions,
15
+ type Message as DiscordMessage,
16
+ } from 'discord.js';
17
+ import { formatCompact, getLogger } from '@zhin.js/logger';
18
+ import {
19
+ activityTypeCode,
20
+ resolveChannelKind,
21
+ type DiscordButtonInbound,
22
+ type DiscordInboundMessage,
23
+ type DiscordOutboundBody,
24
+ type ResolvedDiscordGatewayConfig,
25
+ } from './protocol.js';
26
+
27
+ const logger = getLogger('discord');
28
+
29
+ export const DEFAULT_INTENTS = [
30
+ GatewayIntentBits.Guilds,
31
+ GatewayIntentBits.GuildMessages,
32
+ GatewayIntentBits.MessageContent,
33
+ GatewayIntentBits.DirectMessages,
34
+ GatewayIntentBits.GuildMembers,
35
+ GatewayIntentBits.GuildMessageReactions,
36
+ ];
37
+
38
+ /** Minimal client surface used by the endpoint (real discord.js or test mock). */
39
+ export interface DiscordClientTransport {
40
+ login(token: string): Promise<string>;
41
+ destroy(): Promise<void>;
42
+ on(event: string, listener: (...args: unknown[]) => void): void;
43
+ once(event: string, listener: (...args: unknown[]) => void): void;
44
+ removeAllListeners(): void;
45
+ readonly user?: {
46
+ readonly id: string;
47
+ readonly tag?: string;
48
+ setActivity?(name: string, options?: { type?: number; url?: string }): void;
49
+ } | null;
50
+ channels: {
51
+ fetch(id: string): Promise<{
52
+ id: string;
53
+ type: number;
54
+ isTextBased(): boolean;
55
+ send?(options: MessageCreateOptions): Promise<{ id: string }>;
56
+ messages?: {
57
+ fetch(id: string): Promise<{
58
+ react(emoji: string): Promise<unknown>;
59
+ reactions: {
60
+ resolve(emoji: unknown): { users: { remove(userId: string): Promise<unknown> } } | null;
61
+ cache: { find(fn: (r: { emoji: { toString(): string; name?: string | null; id?: string | null } }) => boolean): { users: { remove(userId: string): Promise<unknown> } } | undefined };
62
+ };
63
+ }>;
64
+ };
65
+ threads?: {
66
+ create(options: Record<string, unknown>): Promise<{ id: string }>;
67
+ };
68
+ availableTags?: Array<{ id: string; name: string }>;
69
+ } | null>;
70
+ };
71
+ guilds: {
72
+ fetch(id: string): Promise<{
73
+ id: string;
74
+ name: string;
75
+ ownerId: string;
76
+ memberCount: number;
77
+ createdAt?: Date | null;
78
+ iconURL?(options?: { size?: number }): string | null;
79
+ roles: {
80
+ fetch(): Promise<unknown>;
81
+ cache: Map<string, {
82
+ id: string;
83
+ name: string;
84
+ hexColor: string;
85
+ position: number;
86
+ permissions: { bitfield: bigint };
87
+ }> | { map(fn: (role: {
88
+ id: string;
89
+ name: string;
90
+ hexColor: string;
91
+ position: number;
92
+ permissions: { bitfield: bigint };
93
+ }) => unknown): unknown[] };
94
+ };
95
+ members: {
96
+ fetch(userId: string | { limit?: number }): Promise<unknown>;
97
+ ban(userId: string, options?: { reason?: string; deleteMessageSeconds?: number }): Promise<unknown>;
98
+ unban(userId: string, reason?: string): Promise<unknown>;
99
+ };
100
+ }>;
101
+ cache: { values(): IterableIterator<{ id: string }> };
102
+ };
103
+ }
104
+
105
+ export type CreateDiscordClient = (intents: readonly number[]) => DiscordClientTransport;
106
+
107
+ export function defaultCreateClient(intents: readonly number[]): DiscordClientTransport {
108
+ return new Client({ intents: [...intents] }) as unknown as DiscordClientTransport;
109
+ }
110
+
111
+ export function resolveSenderRole(msg: DiscordInboundMessage): string | undefined {
112
+ if (msg.isGuildOwner) return 'owner';
113
+ const tokens = msg.permissionTokens ?? [];
114
+ if (tokens.includes('ADMINISTRATOR') || tokens.includes('MODERATE_MEMBERS')) return 'admin';
115
+ if (msg.guildId) return 'member';
116
+ return undefined;
117
+ }
118
+
119
+ export function normalizeDiscordMessage(raw: unknown): DiscordInboundMessage | null {
120
+ if (!raw || typeof raw !== 'object') return null;
121
+ const msg = raw as DiscordMessage;
122
+ if (!msg.author || !msg.channel) return null;
123
+
124
+ const permissionTokens: string[] = [];
125
+ let isGuildOwner = false;
126
+ const member = msg.member;
127
+ const guild = msg.guild;
128
+ if (member && guild) {
129
+ const checks: Array<[bigint, string]> = [
130
+ [PermissionFlagsBits.Administrator, 'ADMINISTRATOR'],
131
+ [PermissionFlagsBits.ManageRoles, 'MANAGE_ROLES'],
132
+ [PermissionFlagsBits.ModerateMembers, 'MODERATE_MEMBERS'],
133
+ [PermissionFlagsBits.ManageChannels, 'MANAGE_CHANNELS'],
134
+ [PermissionFlagsBits.ManageGuild, 'MANAGE_GUILD'],
135
+ ];
136
+ for (const [bit, name] of checks) {
137
+ if (member.permissions.has(bit)) permissionTokens.push(name);
138
+ }
139
+ if (guild.ownerId === msg.author.id) {
140
+ isGuildOwner = true;
141
+ permissionTokens.push('guild_owner', 'ADMINISTRATOR');
142
+ }
143
+ }
144
+
145
+ return {
146
+ id: msg.id,
147
+ content: msg.content ?? '',
148
+ channelId: msg.channel.id,
149
+ channelKind: resolveChannelKind(msg.channel.type),
150
+ authorId: msg.author.id,
151
+ authorName: member?.displayName || msg.author.displayName || msg.author.username,
152
+ authorBot: msg.author.bot,
153
+ createdTimestamp: msg.createdTimestamp,
154
+ guildId: guild?.id,
155
+ isGuildOwner,
156
+ permissionTokens,
157
+ attachments: [...msg.attachments.values()].map((a) => ({
158
+ id: a.id,
159
+ name: a.name ?? undefined,
160
+ url: a.url,
161
+ contentType: a.contentType ?? undefined,
162
+ size: a.size,
163
+ })),
164
+ embedTitles: msg.embeds.map((e) => e.title || e.description || 'embed').filter(Boolean) as string[],
165
+ stickerNames: [...msg.stickers.values()].map((s) => s.name),
166
+ replyToId: msg.reference?.messageId ?? undefined,
167
+ };
168
+ }
169
+
170
+ export async function toMessageCreateOptions(body: DiscordOutboundBody): Promise<MessageCreateOptions> {
171
+ const options: MessageCreateOptions = {};
172
+ if (body.content) options.content = body.content;
173
+ if (body.embeds?.length) {
174
+ options.embeds = body.embeds.map((data) => {
175
+ const embed = new EmbedBuilder();
176
+ if (data.title) embed.setTitle(String(data.title));
177
+ if (data.description) embed.setDescription(String(data.description));
178
+ if (data.color != null) embed.setColor(data.color as number);
179
+ if (data.url) embed.setURL(String(data.url));
180
+ const thumb = data.thumbnail as { url?: string } | undefined;
181
+ if (thumb?.url) embed.setThumbnail(thumb.url);
182
+ const image = data.image as { url?: string } | undefined;
183
+ if (image?.url) embed.setImage(image.url);
184
+ if (data.author) embed.setAuthor(data.author as { name: string });
185
+ if (data.footer) embed.setFooter(data.footer as { text: string });
186
+ if (data.timestamp) embed.setTimestamp(new Date(String(data.timestamp)));
187
+ if (Array.isArray(data.fields)) embed.addFields(data.fields as Array<{ name: string; value: string }>);
188
+ return embed;
189
+ });
190
+ }
191
+ if (body.files?.length) {
192
+ const files: AttachmentBuilder[] = [];
193
+ for (const file of body.files) {
194
+ if (file.file && await fileExists(file.file)) {
195
+ files.push(new AttachmentBuilder(createReadStream(file.file), {
196
+ name: file.name || path.basename(file.file),
197
+ }));
198
+ } else if (file.url) {
199
+ files.push(new AttachmentBuilder(file.url, { name: file.name || 'attachment' }));
200
+ }
201
+ }
202
+ if (files.length) options.files = files;
203
+ }
204
+ if (body.components?.length) {
205
+ options.components = body.components.map((row) =>
206
+ new ActionRowBuilder<ButtonBuilder>().addComponents(
207
+ ...row.components.map((btn) => {
208
+ const b = new ButtonBuilder()
209
+ .setCustomId(btn.custom_id)
210
+ .setLabel(btn.label)
211
+ .setDisabled(!!btn.disabled);
212
+ if (btn.style === 4) b.setStyle(ButtonStyle.Danger);
213
+ else if (btn.style === 1) b.setStyle(ButtonStyle.Primary);
214
+ else b.setStyle(ButtonStyle.Secondary);
215
+ return b;
216
+ }),
217
+ ),
218
+ );
219
+ }
220
+ return options;
221
+ }
222
+
223
+ async function registerSlashCommands(
224
+ config: ResolvedDiscordGatewayConfig,
225
+ applicationId: string,
226
+ ): Promise<void> {
227
+ if (!config.slashCommands?.length) return;
228
+ const rest = new REST({ version: '10' }).setToken(config.token);
229
+ if (config.globalCommands) {
230
+ await rest.put(Routes.applicationCommands(applicationId), {
231
+ body: config.slashCommands,
232
+ });
233
+ logger.info(formatCompact({ op: 'slash_commands', scope: 'global' }));
234
+ }
235
+ }
236
+
237
+ async function fileExists(filePath: string): Promise<boolean> {
238
+ try {
239
+ await fs.access(filePath);
240
+ return true;
241
+ } catch {
242
+ return false;
243
+ }
244
+ }
245
+
246
+ export interface DiscordGatewayConnectHandlers {
247
+ onMessage(msg: DiscordInboundMessage): void;
248
+ onButton(interaction: DiscordButtonInbound): void;
249
+ }
250
+
251
+ export async function connectDiscordGatewayClient(
252
+ client: DiscordClientTransport,
253
+ config: ResolvedDiscordGatewayConfig,
254
+ handlers: DiscordGatewayConnectHandlers,
255
+ ): Promise<void> {
256
+ return new Promise((resolve, reject) => {
257
+ let settled = false;
258
+
259
+ client.on('messageCreate', (raw) => {
260
+ const msg = normalizeDiscordMessage(raw);
261
+ if (!msg) return;
262
+ // clientReady 之后 client.user 一定可用;消息事件只会在此之后到达
263
+ const botId = client.user?.id;
264
+ const mentions = (raw as DiscordMessage).mentions;
265
+ const mentionedBot = !!botId && mentions?.users?.has?.(botId) === true;
266
+ handlers.onMessage(mentionedBot ? { ...msg, mentionedBot: true } : msg);
267
+ });
268
+
269
+ client.on('interactionCreate', (raw) => {
270
+ const interaction = raw as {
271
+ isButton?(): boolean;
272
+ deferUpdate?(): Promise<unknown>;
273
+ id: string;
274
+ customId: string;
275
+ channel?: { id: string; type: number } | null;
276
+ user: { id: string; username?: string; displayName?: string };
277
+ message?: { id: string };
278
+ };
279
+ if (!interaction.isButton?.()) return;
280
+ void interaction.deferUpdate?.().catch(() => { /* already ack */ });
281
+ if (!interaction.channel) return;
282
+ handlers.onButton({
283
+ id: interaction.id,
284
+ customId: interaction.customId,
285
+ channelId: interaction.channel.id,
286
+ channelKind: resolveChannelKind(interaction.channel.type),
287
+ userId: interaction.user.id,
288
+ userName: interaction.user.username || interaction.user.displayName || interaction.user.id,
289
+ sourceMessageId: interaction.message?.id,
290
+ });
291
+ });
292
+
293
+ client.once('clientReady', () => {
294
+ void (async () => {
295
+ try {
296
+ if (config.defaultActivity && client.user?.setActivity) {
297
+ client.user.setActivity(config.defaultActivity.name, {
298
+ type: activityTypeCode(config.defaultActivity.type),
299
+ url: config.defaultActivity.url,
300
+ });
301
+ }
302
+ if (config.enableSlashCommands && config.slashCommands?.length && client.user) {
303
+ await registerSlashCommands(config, client.user.id);
304
+ }
305
+ if (!settled) {
306
+ settled = true;
307
+ resolve();
308
+ }
309
+ } catch (error) {
310
+ if (!settled) {
311
+ settled = true;
312
+ reject(error);
313
+ }
314
+ }
315
+ })();
316
+ });
317
+
318
+ client.on('error', (error) => {
319
+ logger.error('Discord client error:', error);
320
+ if (!settled) {
321
+ settled = true;
322
+ reject(error instanceof Error ? error : new Error(String(error)));
323
+ }
324
+ });
325
+
326
+ client.on('warn', (info) => {
327
+ logger.warn('Discord client warning:', info);
328
+ });
329
+
330
+ client.login(config.token).catch((error) => {
331
+ if (!settled) {
332
+ settled = true;
333
+ reject(error instanceof Error ? error : new Error(String(error)));
334
+ }
335
+ });
336
+ });
337
+ }
package/src/index.ts CHANGED
@@ -1,159 +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 } 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,
32
+ platformPermit,
11
33
  registerDiscordPlatformPermitChecker,
12
- } from "./platform-permit.js";
13
- import { setDiscordAgentDeps } from "./discord-agent-deps.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
- setDiscordAgentDeps({
51
- getEndpoint: (endpointId) => {
52
- const endpoint = discord.endpoints.get(endpointId);
53
- if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
54
- return endpoint;
55
- },
56
- getGatewayEndpoint: (endpointId) => {
57
- const endpoint = discord.endpoints.get(endpointId);
58
- if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
59
- if ((endpoint.$config as { connection?: string }).connection !== 'gateway') {
60
- throw new Error('此工具仅支持 connection: gateway');
61
- }
62
- return endpoint;
63
- },
64
- getAdapter: () => discord,
65
- });
66
- const sceneTools = createSceneManagementTools(
67
- discord as unknown as ISceneManagement,
68
- 'discord',
69
- { permitResolver: discordGroupPermitResolver, registerChecker: false },
70
- );
71
- disposers.push(...sceneTools.map(t => toolService.addTool(t, plugin.name)));
72
-
73
- return () => disposers.forEach(d => d());
74
- });
75
-
76
- // ── Web 控制台 ─────────────────────────────────────────────────────────
77
- useContext("web", (pageManager) => {
78
- pageManager.addEntry({
79
- id: "discord",
80
- development: path.resolve(import.meta.dirname, "../client/index.tsx"),
81
- production: path.resolve(import.meta.dirname, "../dist/index.js"),
82
- meta: { name: "Discord" },
83
- });
84
- });
85
-
86
- useContext("router", "discord", (router: Router, discord: DiscordAdapter) => {
87
- router.get("/api/discord/endpoints", async (ctx: any) => {
88
- try {
89
- const endpoints = Array.from(discord.endpoints.values());
90
- const result = endpoints.map((endpoint: any) => {
91
- try {
92
- const client = endpoint.client || endpoint;
93
- return {
94
- name: endpoint.$config.name,
95
- connected: endpoint.$connected || false,
96
- mode: endpoint.$config.connection || "gateway",
97
- guildCount: client.guilds?.cache?.size || 0,
98
- channelCount: client.channels?.cache?.size || 0,
99
- status: endpoint.$connected ? "online" : "offline",
100
- user: client.user ? { tag: client.user.tag, id: client.user.id } : null,
101
- };
102
- } catch {
103
- return { name: endpoint.$config.name, connected: false, mode: "unknown", guildCount: 0, channelCount: 0, status: "error", user: null };
104
- }
105
- });
106
- ctx.body = { success: true, data: result };
107
- } catch {
108
- ctx.status = 500;
109
- ctx.body = { success: false, error: "获取 Endpoint 数据失败" };
110
- }
111
- });
112
-
113
- // Endpoint 连接/断开
114
- router.post("/api/discord/endpoints/:name/connect", async (ctx: any) => {
115
- try {
116
- const endpoint = discord.endpoints.get(ctx.params.name);
117
- if (!endpoint) { ctx.status = 404; ctx.body = { success: false, error: "Endpoint 不存在" }; return; }
118
- if (endpoint.$connected) { ctx.body = { success: true, message: "已经在线" }; return; }
119
- await endpoint.$connect();
120
- ctx.body = { success: true, message: "连接成功" };
121
- } catch (e: unknown) {
122
- ctx.status = 500;
123
- ctx.body = { success: false, error: e instanceof Error ? e.message : "连接失败" };
124
- }
125
- });
126
-
127
- router.post("/api/discord/endpoints/:name/disconnect", async (ctx: any) => {
128
- try {
129
- const endpoint = discord.endpoints.get(ctx.params.name);
130
- if (!endpoint) { ctx.status = 404; ctx.body = { success: false, error: "Endpoint 不存在" }; return; }
131
- if (!endpoint.$connected) { ctx.body = { success: true, message: "已经离线" }; return; }
132
- await endpoint.$disconnect();
133
- ctx.body = { success: true, message: "已断开" };
134
- } catch (e: unknown) {
135
- ctx.status = 500;
136
- ctx.body = { success: false, error: e instanceof Error ? e.message : "断开失败" };
137
- }
138
- });
139
-
140
- // 服务器列表(仅 Gateway 模式)
141
- router.get("/api/discord/endpoints/:name/guilds", async (ctx: any) => {
142
- try {
143
- const endpoint: any = discord.endpoints.get(ctx.params.name);
144
- if (!endpoint) { ctx.status = 404; ctx.body = { success: false, error: "Endpoint 不存在" }; return; }
145
- if (!endpoint.$connected) { ctx.status = 400; ctx.body = { success: false, error: "Endpoint 未连接" }; return; }
146
- const client = endpoint.client || endpoint;
147
- const guilds = client.guilds?.cache?.map((g: any) => ({
148
- id: g.id,
149
- name: g.name,
150
- memberCount: g.memberCount,
151
- icon: g.iconURL({ size: 64 }),
152
- })) || [];
153
- ctx.body = { success: true, data: guilds };
154
- } catch (e: unknown) {
155
- ctx.status = 500;
156
- ctx.body = { success: false, error: e instanceof Error ? e.message : "获取服务器列表失败" };
157
- }
158
- });
159
- });
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,7 +1,7 @@
1
1
  /**
2
2
  * Discord platform permit — Guild 权限位
3
3
  */
4
- import { registerPlatformPermitChecker, type Message } from 'zhin.js';
4
+ import { registerPlatformPermitChecker, type Message } from '@zhin.js/core';
5
5
 
6
6
  const ADAPTER = 'discord';
7
7