@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.
- package/CHANGELOG.md +55 -0
- package/README.md +51 -76
- package/adapters/discord.ts +46 -0
- package/agent/tools/add_role.ts +24 -0
- package/agent/tools/create_thread.ts +25 -0
- package/agent/tools/forum_post.ts +24 -0
- package/agent/tools/list_roles.ts +22 -0
- package/agent/tools/react.ts +22 -0
- package/agent/tools/remove_role.ts +24 -0
- package/agent/tools/send_embed.ts +37 -0
- package/lib/discord-agent-deps.d.ts +35 -0
- package/lib/discord-agent-deps.js +32 -0
- package/lib/endpoint.d.ts +66 -119
- package/lib/endpoint.js +308 -1047
- package/lib/gateway.d.ts +122 -0
- package/lib/gateway.js +235 -0
- package/lib/index.d.ts +6 -18
- package/lib/index.js +6 -330
- package/lib/platform-permit.d.ts +1 -2
- package/lib/platform-permit.js +4 -2
- package/lib/protocol.d.ts +135 -0
- package/lib/protocol.js +234 -0
- package/lib/webhook.d.ts +13 -0
- package/lib/webhook.js +86 -0
- package/package.json +48 -31
- package/plugin.ts +13 -0
- package/schema.json +63 -0
- package/src/discord-agent-deps.ts +79 -0
- package/src/endpoint.ts +385 -1167
- package/src/gateway.ts +337 -0
- package/src/index.ts +55 -332
- package/src/platform-permit.ts +1 -2
- package/src/protocol.ts +392 -0
- package/src/webhook.ts +121 -0
- package/client/Dashboard.tsx +0 -195
- package/client/index.tsx +0 -11
- package/client/tsconfig.json +0 -7
- package/client/utils/api.ts +0 -30
- package/dist/index.js +0 -29
- package/lib/adapter.d.ts +0 -25
- package/lib/adapter.d.ts.map +0 -1
- package/lib/adapter.js +0 -96
- package/lib/adapter.js.map +0 -1
- package/lib/endpoint-interactions.d.ts +0 -34
- package/lib/endpoint-interactions.d.ts.map +0 -1
- package/lib/endpoint-interactions.js +0 -284
- package/lib/endpoint-interactions.js.map +0 -1
- package/lib/endpoint.d.ts.map +0 -1
- package/lib/endpoint.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/platform-permit.d.ts.map +0 -1
- package/lib/platform-permit.js.map +0 -1
- package/lib/segment-mapper.d.ts +0 -2
- package/lib/segment-mapper.d.ts.map +0 -1
- package/lib/segment-mapper.js +0 -2
- package/lib/segment-mapper.js.map +0 -1
- package/lib/types.d.ts +0 -49
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js +0 -2
- package/lib/types.js.map +0 -1
- package/plugin.yml +0 -3
- package/src/adapter.ts +0 -108
- package/src/endpoint-interactions.ts +0 -354
- package/src/segment-mapper.ts +0 -1
- package/src/types.ts +0 -60
- /package/{skills/discord → agent}/PERMITS.md +0 -0
- /package/{skills/discord/SKILL.md → agent/skills/discord.md} +0 -0
|
@@ -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 类
|
package/src/segment-mapper.ts
DELETED
|
@@ -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>;
|
|
File without changes
|
|
File without changes
|