@zhin.js/adapter-discord 1.0.87 → 1.1.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.
- package/CHANGELOG.md +855 -66
- package/README.md +61 -67
- package/adapters/discord.js +41 -0
- package/adapters/discord.ts +57 -0
- package/agent/PERMITS.md +25 -0
- package/{skills/discord/SKILL.md → agent/skills/discord.md} +1 -1
- package/agent/tools/add_role.ts +26 -0
- package/agent/tools/create_thread.ts +28 -0
- package/agent/tools/forum_post.ts +33 -0
- package/agent/tools/list_roles.ts +34 -0
- package/agent/tools/react.ts +24 -0
- package/agent/tools/remove_role.ts +26 -0
- package/agent/tools/send_embed.ts +38 -0
- package/commands/endpoint/add/[id].js +3 -0
- package/commands/endpoint/add/[id].ts +3 -0
- package/commands/endpoint/list.js +3 -0
- package/commands/endpoint/list.ts +3 -0
- package/commands/endpoint/remove/[id].js +3 -0
- package/commands/endpoint/remove/[id].ts +3 -0
- package/lib/client.d.ts +16 -0
- package/lib/client.js +8 -0
- package/lib/discord-endpoint-commands.d.ts +1 -0
- package/lib/discord-endpoint-commands.js +18 -0
- package/lib/discord-runtime-state.d.ts +1 -0
- package/lib/discord-runtime-state.js +6 -0
- package/lib/endpoint.d.ts +72 -0
- package/lib/endpoint.js +458 -0
- package/lib/gateway.d.ts +144 -0
- package/lib/gateway.js +303 -0
- package/lib/index.d.ts +6 -18
- package/lib/index.js +6 -323
- package/lib/platform-permit.d.ts +14 -0
- package/lib/platform-permit.js +42 -0
- package/lib/protocol.d.ts +162 -0
- package/lib/protocol.js +333 -0
- package/lib/side-event-dispatch.d.ts +8 -0
- package/lib/side-event-dispatch.js +24 -0
- package/lib/webhook.d.ts +14 -0
- package/lib/webhook.js +88 -0
- package/package.json +62 -29
- package/plugin.js +19 -0
- package/schema.json +141 -0
- package/src/client.ts +24 -0
- package/src/discord-endpoint-commands.ts +19 -0
- package/src/discord-runtime-state.ts +7 -0
- package/src/endpoint.ts +562 -0
- package/src/gateway.ts +426 -0
- package/src/index.ts +55 -323
- package/src/platform-permit.ts +57 -0
- package/src/protocol.ts +502 -0
- package/src/side-event-dispatch.ts +37 -0
- package/src/webhook.ts +123 -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 -17
- package/dist/index.js +0 -29
- package/lib/adapter.d.ts +0 -19
- package/lib/adapter.d.ts.map +0 -1
- package/lib/adapter.js +0 -90
- package/lib/adapter.js.map +0 -1
- package/lib/bot-interactions.d.ts +0 -33
- package/lib/bot-interactions.d.ts.map +0 -1
- package/lib/bot-interactions.js +0 -278
- package/lib/bot-interactions.js.map +0 -1
- package/lib/bot.d.ts +0 -117
- package/lib/bot.d.ts.map +0 -1
- package/lib/bot.js +0 -904
- package/lib/bot.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.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 -107
- package/src/bot-interactions.ts +0 -348
- package/src/bot.ts +0 -1037
- package/src/types.ts +0 -60
package/README.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# @zhin.js/adapter-discord
|
|
2
2
|
|
|
3
|
-
Zhin.js Discord
|
|
3
|
+
Zhin.js Discord 适配器(Plugin Runtime),默认通过 **Gateway WebSocket**(discord.js)收发消息(无需 host-router / host-http)。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 功能
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
7
|
+
- Gateway WebSocket 入站(默认;无需公网 HTTPS / host)
|
|
8
|
+
- 解析 text / mention / attachment / embed / sticker / button
|
|
9
|
+
- 支持私聊、群组与服务器频道
|
|
10
|
+
- 出站 `send({ conversation, payload })` → Discord channel message(Markdown content / media / embed / keyboard)
|
|
11
|
+
- 约定式 `defineAdapter` / `definePlugin`(无需 `usePlugin`)
|
|
12
|
+
- Interactions HTTP webhook 延期(需 `httpHostToken`);配置 `connection: interactions` 会明确报错
|
|
13
13
|
|
|
14
14
|
## 安装
|
|
15
15
|
|
|
@@ -17,89 +17,83 @@ Zhin.js Discord 适配器,支持 Discord 机器人的消息收发和服务器
|
|
|
17
17
|
pnpm add @zhin.js/adapter-discord discord.js
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
## Plugin Runtime
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
- `@zhin.js/adapter` — 约定式 `adapters/discord.ts`(`defineAdapter`)
|
|
23
|
+
- `@zhin.js/core` — `Endpoint.emit(...)` 入站、`outboundMessageToken` 出站
|
|
24
|
+
- `zhin.js` — `plugin.ts`(`definePlugin`)
|
|
25
|
+
- 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`
|
|
26
|
+
- **无需** `@zhin.js/host-http` / `@zhin.js/host-router`(Gateway 路径)
|
|
23
27
|
|
|
24
|
-
|
|
28
|
+
入站:`gateway.receive({ conversation, message, content: text, sender, metadata })`(`conversation` 为 `ConversationRef`,guild 频道带 `parent` guild 容器)
|
|
29
|
+
出站:`send({ conversation, payload })` → discord.js channel.send
|
|
25
30
|
|
|
26
|
-
###
|
|
31
|
+
### 平台权限(platform permit)
|
|
27
32
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
- "@zhin.js/adapter-discord"
|
|
31
|
-
|
|
32
|
-
bots:
|
|
33
|
-
- context: discord
|
|
34
|
-
connection: gateway
|
|
35
|
-
name: my-discord-bot
|
|
36
|
-
token: "${DISCORD_BOT_TOKEN}"
|
|
37
|
-
# 可选: intents, enableSlashCommands, defaultActivity, slashCommands
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
### Interactions(connection: interactions)
|
|
41
|
-
|
|
42
|
-
```yaml
|
|
43
|
-
plugins:
|
|
44
|
-
- "@zhin.js/http"
|
|
45
|
-
- "@zhin.js/adapter-discord"
|
|
46
|
-
|
|
47
|
-
bots:
|
|
48
|
-
- context: discord
|
|
49
|
-
connection: interactions
|
|
50
|
-
name: my-discord-bot
|
|
51
|
-
token: "${DISCORD_BOT_TOKEN}"
|
|
52
|
-
applicationId: "${DISCORD_APP_ID}"
|
|
53
|
-
publicKey: "${DISCORD_PUBLIC_KEY}"
|
|
54
|
-
interactionsPath: "/discord/interactions"
|
|
55
|
-
```
|
|
33
|
+
- sender role 已恢复:Gateway 入站 `metadata.role` / `metadata.permissions`(来自 member 权限位与 guild owner 判定,见 `src/gateway.ts` `resolveSenderRole`)。
|
|
34
|
+
- `plugin.ts` 在 generation setup 注册 checker,并在 dispose 注销;Plugin Runtime CapabilityIngress 与 ToolSystem 统一经 Core `canAccessTool()` 消费 `permissions`。
|
|
56
35
|
|
|
57
|
-
|
|
36
|
+
## 前置条件
|
|
58
37
|
|
|
59
|
-
|
|
38
|
+
| 要求 | 说明 |
|
|
39
|
+
|------|------|
|
|
40
|
+
| **Bot Token** | [Discord Developer Portal](https://discord.com/developers/applications) 创建应用并获取 Token |
|
|
41
|
+
| **MESSAGE CONTENT INTENT** | 需开启才能读取消息正文 |
|
|
42
|
+
| **Gateway(默认)** | 本地/生产均可;discord.js 连接 Gateway,无需公网 HTTPS |
|
|
43
|
+
| **host-http** | Gateway **不需要**;Interactions webhook 延期至下一棒 |
|
|
60
44
|
|
|
61
|
-
|
|
45
|
+
必填字段(`endpoints[i]`):`name`、`token`。
|
|
62
46
|
|
|
63
|
-
|
|
64
|
-
import { usePlugin, MessageCommand } from 'zhin.js'
|
|
47
|
+
## 最小配置
|
|
65
48
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
49
|
+
```yaml
|
|
50
|
+
# zhin.config.yml(Plugin Runtime)
|
|
51
|
+
plugins:
|
|
52
|
+
discord:
|
|
53
|
+
# connection: gateway # 默认
|
|
54
|
+
endpoints:
|
|
55
|
+
- name: my-discord-bot
|
|
56
|
+
token: ${DISCORD_BOT_TOKEN}
|
|
73
57
|
```
|
|
74
58
|
|
|
75
|
-
|
|
59
|
+
根插件 `zhin.plugins`(或项目图)需引用 `@zhin.js/adapter-discord`(`instanceKey: discord`)。
|
|
76
60
|
|
|
77
|
-
|
|
78
|
-
import { usePlugin } from 'zhin.js'
|
|
61
|
+
## 环境变量
|
|
79
62
|
|
|
80
|
-
|
|
63
|
+
| 变量 | 说明 |
|
|
64
|
+
|------|------|
|
|
65
|
+
| `DISCORD_BOT_TOKEN` | Bot Token |
|
|
66
|
+
| `DISCORD_BOT_NAME` | 可选,默认 endpoint 名 |
|
|
81
67
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
await next()
|
|
87
|
-
})
|
|
88
|
-
```
|
|
68
|
+
## Interactions(HTTP)
|
|
69
|
+
|
|
70
|
+
`connection: interactions` 经 `httpHostToken` 注册 POST 路由(默认 `/discord/interactions`),Ed25519 验签后处理 PING 与 slash command;出站走 Discord REST `channels/.../messages`。需配置 `applicationId` 与 `publicKey`。
|
|
89
71
|
|
|
90
72
|
## AI 工具(Skill)
|
|
91
73
|
|
|
92
|
-
|
|
74
|
+
| 类别 | 路径 |
|
|
75
|
+
|------|------|
|
|
76
|
+
| Permit 词汇 | `agent/PERMITS.md` |
|
|
77
|
+
| 平台工具(7 个) | `agent/tools/`(`discord_*`:角色、Embed、反应等) |
|
|
78
|
+
| 技能说明 | `agent/skills/discord.md` |
|
|
93
79
|
|
|
94
|
-
|
|
80
|
+
工具使用 Discord Snowflake ID 标识 `guild_id`、`user_id`、`channel_id`。
|
|
95
81
|
|
|
96
82
|
## Discord 开发者配置
|
|
97
83
|
|
|
98
84
|
1. 前往 [Discord Developer Portal](https://discord.com/developers/applications)
|
|
99
85
|
2. 创建应用并获取 Bot Token
|
|
100
|
-
3. 开启 **MESSAGE CONTENT INTENT
|
|
86
|
+
3. 开启 **MESSAGE CONTENT INTENT**
|
|
101
87
|
4. 通过 OAuth2 URL 邀请 Bot 加入服务器
|
|
102
|
-
|
|
88
|
+
|
|
89
|
+
## 故障排查
|
|
90
|
+
|
|
91
|
+
| 现象 | 排查 |
|
|
92
|
+
| --- | --- |
|
|
93
|
+
| Gateway 反复断线 | 检查 Token、网络代理、Gateway Intents 与应用后台配置 |
|
|
94
|
+
| 能上线但收不到正文 | 启用 Message Content Intent,并给 Bot 频道读取权限 |
|
|
95
|
+
| 能收不能发 | 检查 Send Messages、Embed Links 与附件权限 |
|
|
96
|
+
| `connection: interactions` 启动失败 | 当前生产路径使用 Gateway;改回 `gateway` |
|
|
103
97
|
|
|
104
98
|
## 许可证
|
|
105
99
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
/**
|
|
3
|
+
* Convention entry: discover `adapters/discord.ts` → defineAdapter.
|
|
4
|
+
*/
|
|
5
|
+
import { defineAdapter } from 'zhin.js/adapter';
|
|
6
|
+
import { httpHostToken } from '@zhin.js/host-http';
|
|
7
|
+
import { DiscordGatewayEndpoint, DiscordInteractionsEndpoint, } from "../lib/endpoint.js";
|
|
8
|
+
import { resolveDiscordConfig, } from "../lib/protocol.js";
|
|
9
|
+
import { discordRuntimeStateToken } from "../lib/discord-runtime-state.js";
|
|
10
|
+
export { DiscordGatewayEndpoint, DiscordInteractionsEndpoint, } from "../lib/endpoint.js";
|
|
11
|
+
export default defineAdapter({
|
|
12
|
+
capabilities: ['inbound', 'outbound'],
|
|
13
|
+
operations: (context) => context.config.connection === 'interactions'
|
|
14
|
+
? ['recall']
|
|
15
|
+
: ['recall', 'reaction'],
|
|
16
|
+
// 媒体 url 直发或由 AttachmentBuilder 物化本地文件上传;message components 原生按钮承载交互段。
|
|
17
|
+
segments: {
|
|
18
|
+
outboundMedia: ['url', 'upload'],
|
|
19
|
+
interactive: 'native',
|
|
20
|
+
markdown: 'native',
|
|
21
|
+
},
|
|
22
|
+
create(context) {
|
|
23
|
+
const config = resolveDiscordConfig(context.config);
|
|
24
|
+
// 注册到插件运行时状态(discord.endpoint list 的"运行中"数据源)
|
|
25
|
+
context.use(discordRuntimeStateToken).endpoints.set(config.id, {
|
|
26
|
+
id: config.id,
|
|
27
|
+
mode: config.connection,
|
|
28
|
+
});
|
|
29
|
+
if (config.connection === 'interactions') {
|
|
30
|
+
return new DiscordInteractionsEndpoint({
|
|
31
|
+
id: context.id,
|
|
32
|
+
http: context.use(httpHostToken),
|
|
33
|
+
config,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return new DiscordGatewayEndpoint({
|
|
37
|
+
id: context.id,
|
|
38
|
+
config,
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convention entry: discover `adapters/discord.ts` → defineAdapter.
|
|
3
|
+
*/
|
|
4
|
+
import { defineAdapter } from 'zhin.js/adapter';
|
|
5
|
+
import { httpHostToken } from '@zhin.js/host-http';
|
|
6
|
+
import {
|
|
7
|
+
DiscordGatewayEndpoint,
|
|
8
|
+
DiscordInteractionsEndpoint,
|
|
9
|
+
} from '../src/endpoint.js';
|
|
10
|
+
import {
|
|
11
|
+
resolveDiscordConfig,
|
|
12
|
+
type DiscordAdapterConfig,
|
|
13
|
+
} from '../src/protocol.js';
|
|
14
|
+
import { discordRuntimeStateToken } from '../src/discord-runtime-state.js';
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
DiscordGatewayEndpoint,
|
|
18
|
+
DiscordInteractionsEndpoint,
|
|
19
|
+
} from '../src/endpoint.js';
|
|
20
|
+
export type {
|
|
21
|
+
CreateDiscordClient,
|
|
22
|
+
DiscordClientTransport,
|
|
23
|
+
DiscordEndpointOptions,
|
|
24
|
+
DiscordInteractionsEndpointOptions,
|
|
25
|
+
} from '../src/endpoint.js';
|
|
26
|
+
|
|
27
|
+
export default defineAdapter<DiscordAdapterConfig>({
|
|
28
|
+
capabilities: ['inbound', 'outbound'],
|
|
29
|
+
operations: (context) => context.config.connection === 'interactions'
|
|
30
|
+
? ['recall']
|
|
31
|
+
: ['recall', 'reaction'],
|
|
32
|
+
// 媒体 url 直发或由 AttachmentBuilder 物化本地文件上传;message components 原生按钮承载交互段。
|
|
33
|
+
segments: {
|
|
34
|
+
outboundMedia: ['url', 'upload'],
|
|
35
|
+
interactive: 'native',
|
|
36
|
+
markdown: 'native',
|
|
37
|
+
},
|
|
38
|
+
create(context) {
|
|
39
|
+
const config = resolveDiscordConfig(context.config);
|
|
40
|
+
// 注册到插件运行时状态(discord.endpoint list 的"运行中"数据源)
|
|
41
|
+
context.use(discordRuntimeStateToken).endpoints.set(config.id, {
|
|
42
|
+
id: config.id,
|
|
43
|
+
mode: config.connection,
|
|
44
|
+
});
|
|
45
|
+
if (config.connection === 'interactions') {
|
|
46
|
+
return new DiscordInteractionsEndpoint({
|
|
47
|
+
id: context.id,
|
|
48
|
+
http: context.use(httpHostToken),
|
|
49
|
+
config,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return new DiscordGatewayEndpoint({
|
|
53
|
+
id: context.id,
|
|
54
|
+
config,
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
});
|
package/agent/PERMITS.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Discord Platform Permits
|
|
2
|
+
|
|
3
|
+
## 入站字段
|
|
4
|
+
|
|
5
|
+
Guild 频道消息从 `msg.member.permissions` bitfield 解析:
|
|
6
|
+
|
|
7
|
+
| 字段 | 取值 |
|
|
8
|
+
|------|------|
|
|
9
|
+
| `$sender.role` | `owner`(`guild.ownerId === author.id`)· `member` |
|
|
10
|
+
| `$sender.permissions` | `guild_owner` · `ADMINISTRATOR` · `MANAGE_ROLES` · `MODERATE_MEMBERS` · `MANAGE_CHANNELS` · … |
|
|
11
|
+
|
|
12
|
+
需 `GuildMembers` intent;缺失时 checker 保守拒绝。
|
|
13
|
+
|
|
14
|
+
## Permit 词汇表
|
|
15
|
+
|
|
16
|
+
| `platform(discord,…)` | 含义 |
|
|
17
|
+
|-----------------------|------|
|
|
18
|
+
| `guild_owner` | 服务器主 |
|
|
19
|
+
| `moderate_members` | 踢人/禁言 |
|
|
20
|
+
| `manage_roles` | 管理身份组 |
|
|
21
|
+
| `manage_channels` | 管理频道/线程 |
|
|
22
|
+
|
|
23
|
+
## 工厂映射
|
|
24
|
+
|
|
25
|
+
`scene_admin` → `moderate_members` · `scene_owner` → `guild_owner`
|
|
@@ -101,4 +101,4 @@ Discord 中几乎所有权限都由角色控制。用户说「给他管理员」
|
|
|
101
101
|
1. **Discord 昵称是服务器级别的**,一个用户在不同服务器可以有不同昵称。
|
|
102
102
|
2. **角色操作需要角色 ID**,不能用角色名直接操作。先 `list_roles` 查。
|
|
103
103
|
3. **Embed 消息不是普通文本**,它有固定结构(title/description/color/fields),用于发送富文本格式的消息。
|
|
104
|
-
4. **Bot 只能操作低于自身角色的成员**。如果目标用户的最高角色高于
|
|
104
|
+
4. **Bot 只能操作低于自身角色的成员**。如果目标用户的最高角色高于 Endpoint 的角色,操作会失败。
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/agent/tools';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { requireDiscordGatewayClient } from '../../src/client.js';
|
|
4
|
+
import { platformPermit } from '../../src/platform-permit.js';
|
|
5
|
+
|
|
6
|
+
export default defineAgentTool<{ guild_id: string; user_id: string; role_id: string }>({
|
|
7
|
+
description: '给成员添加 Discord 角色',
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
guild_id: z.string().describe('服务器 ID'),
|
|
10
|
+
user_id: z.string().describe('用户 ID'),
|
|
11
|
+
role_id: z.string().describe('角色 ID'),
|
|
12
|
+
}),
|
|
13
|
+
adapter: 'discord',
|
|
14
|
+
tags: ['discord'],
|
|
15
|
+
permissions: [platformPermit('manage_roles')],
|
|
16
|
+
async execute({ guild_id, user_id, role_id }: { guild_id: string; user_id: string; role_id: string }, context) {
|
|
17
|
+
const client = requireDiscordGatewayClient(context.$client);
|
|
18
|
+
const guild = await client.guilds.fetch(guild_id);
|
|
19
|
+
const member = await guild.members.fetch(user_id) as {
|
|
20
|
+
roles: { add(id: string): Promise<unknown> };
|
|
21
|
+
};
|
|
22
|
+
await member.roles.add(role_id);
|
|
23
|
+
const success = true;
|
|
24
|
+
return { success, message: success ? `已给用户 ${user_id} 添加角色` : '操作失败' };
|
|
25
|
+
},
|
|
26
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/agent/tools';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { requireDiscordGatewayClient } from '../../src/client.js';
|
|
4
|
+
import { platformPermit } from '../../src/platform-permit.js';
|
|
5
|
+
|
|
6
|
+
export default defineAgentTool<{ channel_id: string; name: string; message_id?: string; auto_archive_duration?: number }>({
|
|
7
|
+
description: '在 Discord 频道中创建帖子/子线程',
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
channel_id: z.string().describe('频道 ID'),
|
|
10
|
+
name: z.string().describe('帖子标题'),
|
|
11
|
+
message_id: z.string().optional().describe('基于某条消息创建(可选)'),
|
|
12
|
+
auto_archive_duration: z.number().optional().describe('自动归档时间(分钟:60/1440/4320/10080)'),
|
|
13
|
+
}),
|
|
14
|
+
adapter: 'discord',
|
|
15
|
+
tags: ['discord'],
|
|
16
|
+
permissions: [platformPermit('manage_channels')],
|
|
17
|
+
async execute({ channel_id, name, message_id, auto_archive_duration }: { channel_id: string; name: string; message_id?: string; auto_archive_duration?: number }, context) {
|
|
18
|
+
const client = requireDiscordGatewayClient(context.$client);
|
|
19
|
+
const channel = await client.channels.fetch(channel_id);
|
|
20
|
+
if (!channel?.threads) throw new Error(`Channel ${channel_id} 不支持创建帖子`);
|
|
21
|
+
const thread = await channel.threads.create({
|
|
22
|
+
name,
|
|
23
|
+
autoArchiveDuration: auto_archive_duration || 1440,
|
|
24
|
+
...(message_id ? { startMessage: message_id } : {}),
|
|
25
|
+
});
|
|
26
|
+
return { success: true, thread_id: thread.id, message: `帖子 "${name}" 已创建` };
|
|
27
|
+
},
|
|
28
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/agent/tools';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { requireDiscordGatewayClient } from '../../src/client.js';
|
|
4
|
+
import { ChannelType } from 'discord.js';
|
|
5
|
+
|
|
6
|
+
export default defineAgentTool<{ channel_id: string; name: string; content: string; tags?: string }>({
|
|
7
|
+
description: '在 Discord 论坛频道中创建帖子',
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
channel_id: z.string().describe('论坛频道 ID'),
|
|
10
|
+
name: z.string().describe('帖子标题'),
|
|
11
|
+
content: z.string().describe('帖子内容'),
|
|
12
|
+
tags: z.string().optional().describe('标签名,逗号分隔(可选)'),
|
|
13
|
+
}),
|
|
14
|
+
adapter: 'discord',
|
|
15
|
+
tags: ['discord'],
|
|
16
|
+
async execute({ channel_id, name, content, tags }: { channel_id: string; name: string; content: string; tags?: string }, context) {
|
|
17
|
+
const client = requireDiscordGatewayClient(context.$client);
|
|
18
|
+
const tagList = tags ? tags.split(',').map((t: string) => t.trim()) : undefined;
|
|
19
|
+
const channel = await client.channels.fetch(channel_id);
|
|
20
|
+
if (!channel?.threads || channel.type !== ChannelType.GuildForum) {
|
|
21
|
+
throw new Error(`Channel ${channel_id} 不是论坛频道`);
|
|
22
|
+
}
|
|
23
|
+
const tagIds = tagList?.length
|
|
24
|
+
? channel.availableTags?.filter((tag) => tagList.includes(tag.name)).map((tag) => tag.id)
|
|
25
|
+
: undefined;
|
|
26
|
+
const thread = await channel.threads.create({
|
|
27
|
+
name,
|
|
28
|
+
message: { content },
|
|
29
|
+
...(tagIds?.length ? { appliedTags: tagIds } : {}),
|
|
30
|
+
});
|
|
31
|
+
return { success: true, thread_id: thread.id, message: `论坛帖 "${name}" 已创建` };
|
|
32
|
+
},
|
|
33
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/agent/tools';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { requireDiscordGatewayClient } from '../../src/client.js';
|
|
4
|
+
import { platformPermit } from '../../src/platform-permit.js';
|
|
5
|
+
|
|
6
|
+
export default defineAgentTool<{ guild_id: string }>({
|
|
7
|
+
description: '获取 Discord 服务器角色列表',
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
guild_id: z.string().describe('服务器 ID'),
|
|
10
|
+
}),
|
|
11
|
+
adapter: 'discord',
|
|
12
|
+
tags: ['discord'],
|
|
13
|
+
permissions: [platformPermit('manage_roles')],
|
|
14
|
+
async execute({ guild_id }: { guild_id: string }, context) {
|
|
15
|
+
const client = requireDiscordGatewayClient(context.$client);
|
|
16
|
+
const guild = await client.guilds.fetch(guild_id);
|
|
17
|
+
await guild.roles.fetch();
|
|
18
|
+
const cache = guild.roles.cache as Map<string, {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
hexColor: string;
|
|
22
|
+
position: number;
|
|
23
|
+
permissions: { bitfield: bigint };
|
|
24
|
+
}>;
|
|
25
|
+
const roles = [...cache.values()].map((role) => ({
|
|
26
|
+
id: role.id,
|
|
27
|
+
name: role.name,
|
|
28
|
+
color: role.hexColor,
|
|
29
|
+
position: role.position,
|
|
30
|
+
permissions: role.permissions.bitfield.toString(),
|
|
31
|
+
}));
|
|
32
|
+
return { roles, count: roles.length };
|
|
33
|
+
},
|
|
34
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/agent/tools';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { requireDiscordGatewayClient } from '../../src/client.js';
|
|
4
|
+
|
|
5
|
+
export default defineAgentTool<{ channel_id: string; message_id: string; emoji: string }>({
|
|
6
|
+
description: '对 Discord 消息添加表情反应',
|
|
7
|
+
inputSchema: z.object({
|
|
8
|
+
channel_id: z.string().describe('频道 ID'),
|
|
9
|
+
message_id: z.string().describe('消息 ID'),
|
|
10
|
+
emoji: z.string().describe('表情(Unicode 表情或自定义表情如 <:name:id>)'),
|
|
11
|
+
}),
|
|
12
|
+
adapter: 'discord',
|
|
13
|
+
tags: ['discord'],
|
|
14
|
+
async execute({ channel_id, message_id, emoji }: { channel_id: string; message_id: string; emoji: string }, context) {
|
|
15
|
+
const client = requireDiscordGatewayClient(context.$client);
|
|
16
|
+
const channel = await client.channels.fetch(channel_id);
|
|
17
|
+
if (!channel?.isTextBased() || !channel.messages) {
|
|
18
|
+
throw new Error(`Channel ${channel_id} 不是文本频道`);
|
|
19
|
+
}
|
|
20
|
+
const message = await channel.messages.fetch(message_id);
|
|
21
|
+
await message.react(emoji);
|
|
22
|
+
return { success: true, message: `已添加反应 ${emoji}` };
|
|
23
|
+
},
|
|
24
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/agent/tools';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { requireDiscordGatewayClient } from '../../src/client.js';
|
|
4
|
+
import { platformPermit } from '../../src/platform-permit.js';
|
|
5
|
+
|
|
6
|
+
export default defineAgentTool<{ guild_id: string; user_id: string; role_id: string }>({
|
|
7
|
+
description: '移除成员的 Discord 角色',
|
|
8
|
+
inputSchema: z.object({
|
|
9
|
+
guild_id: z.string().describe('服务器 ID'),
|
|
10
|
+
user_id: z.string().describe('用户 ID'),
|
|
11
|
+
role_id: z.string().describe('角色 ID'),
|
|
12
|
+
}),
|
|
13
|
+
adapter: 'discord',
|
|
14
|
+
tags: ['discord'],
|
|
15
|
+
permissions: [platformPermit('manage_roles')],
|
|
16
|
+
async execute({ guild_id, user_id, role_id }: { guild_id: string; user_id: string; role_id: string }, context) {
|
|
17
|
+
const client = requireDiscordGatewayClient(context.$client);
|
|
18
|
+
const guild = await client.guilds.fetch(guild_id);
|
|
19
|
+
const member = await guild.members.fetch(user_id) as {
|
|
20
|
+
roles: { remove(id: string): Promise<unknown> };
|
|
21
|
+
};
|
|
22
|
+
await member.roles.remove(role_id);
|
|
23
|
+
const success = true;
|
|
24
|
+
return { success, message: success ? `已移除用户 ${user_id} 的角色` : '操作失败' };
|
|
25
|
+
},
|
|
26
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/agent/tools';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { requireDiscordGatewayClient } from '../../src/client.js';
|
|
4
|
+
|
|
5
|
+
export default defineAgentTool<{ channel_id: string; title?: string; description?: string; color?: number; url?: string; fields?: string }>({
|
|
6
|
+
description: '发送 Discord 富文本嵌入消息(Embed)',
|
|
7
|
+
inputSchema: z.object({
|
|
8
|
+
channel_id: z.string().describe('频道 ID'),
|
|
9
|
+
title: z.string().optional().describe('Embed 标题'),
|
|
10
|
+
description: z.string().optional().describe('Embed 描述'),
|
|
11
|
+
color: z.number().optional().describe('颜色值(十进制,如 0x00ff00 = 65280)'),
|
|
12
|
+
url: z.string().optional().describe('标题链接(可选)'),
|
|
13
|
+
fields: z.string().optional().describe('字段,JSON 格式: [{"name":"k","value":"v","inline":false}]'),
|
|
14
|
+
}),
|
|
15
|
+
adapter: 'discord',
|
|
16
|
+
tags: ['discord'],
|
|
17
|
+
async execute({ channel_id, title, description, color, url, fields }: { channel_id: string; title?: string; description?: string; color?: number; url?: string; fields?: string }, context) {
|
|
18
|
+
const client = requireDiscordGatewayClient(context.$client);
|
|
19
|
+
const embedData: Record<string, unknown> = {};
|
|
20
|
+
if (title) embedData.title = title;
|
|
21
|
+
if (description) embedData.description = description;
|
|
22
|
+
if (color) embedData.color = color;
|
|
23
|
+
if (url) embedData.url = url;
|
|
24
|
+
if (fields) {
|
|
25
|
+
try {
|
|
26
|
+
embedData.fields = JSON.parse(fields);
|
|
27
|
+
} catch {
|
|
28
|
+
return { success: false, message: 'fields 格式错误,应为 JSON 数组' };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const channel = await client.channels.fetch(channel_id);
|
|
32
|
+
if (!channel?.isTextBased() || !channel.send) {
|
|
33
|
+
throw new Error(`Channel ${channel_id} 不是文本频道`);
|
|
34
|
+
}
|
|
35
|
+
const msg = await channel.send({ embeds: [embedData] } as never);
|
|
36
|
+
return { success: true, message_id: msg.id, message: 'Embed 已发送' };
|
|
37
|
+
},
|
|
38
|
+
});
|
package/lib/client.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DiscordRestClient } from './endpoint.js';
|
|
2
|
+
import type { DiscordClientTransport } from './gateway.js';
|
|
3
|
+
/** Exact Client variants produced by the Gateway and Interactions Endpoints. */
|
|
4
|
+
export type DiscordClient = DiscordClientTransport | DiscordRestClient;
|
|
5
|
+
export type DiscordClientEventMap = Record<string, unknown>;
|
|
6
|
+
/** Narrow an adapter Client for Gateway-only SDK operations. */
|
|
7
|
+
export declare function requireDiscordGatewayClient(client: DiscordClient): DiscordClientTransport;
|
|
8
|
+
declare module '@zhin.js/feature-kit' {
|
|
9
|
+
interface AdapterClientRegistry {
|
|
10
|
+
readonly discord: {
|
|
11
|
+
readonly client: DiscordClient;
|
|
12
|
+
readonly events: DiscordClientEventMap;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export declare const discordClient: import("@zhin.js/adapter").EndpointClientToken<DiscordClient, DiscordClientEventMap>;
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { defineEndpointClient } from 'zhin.js/adapter';
|
|
2
|
+
/** Narrow an adapter Client for Gateway-only SDK operations. */
|
|
3
|
+
export function requireDiscordGatewayClient(client) {
|
|
4
|
+
if ('guilds' in client && 'channels' in client)
|
|
5
|
+
return client;
|
|
6
|
+
throw new Error('This Discord tool requires a Gateway Endpoint Client');
|
|
7
|
+
}
|
|
8
|
+
export const discordClient = defineEndpointClient('discord');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const discordEndpointCommands: import("@zhin.js/adapter").EndpointCommands<Readonly<import("@zhin.js/command").CommandDefinition<unknown, unknown, import("@zhin.js/command").CommandMessage, string | undefined>>>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `discord.endpoint` 命令族:由 @zhin.js/adapter 的 createEndpointCommands 套件生成。
|
|
3
|
+
* commands/endpoint/ 下的 list / add / remove 直接默认导出这三项。
|
|
4
|
+
* 注:applicationId / publicKey 为顶层共享字段(slash commands / interactions 用),
|
|
5
|
+
* endpoints[i] 仅需 name + token(见 schema.json),故 add 只录入 token。
|
|
6
|
+
*/
|
|
7
|
+
import { createEndpointCommands } from 'zhin.js/adapter';
|
|
8
|
+
import { defineCommand } from 'zhin.js/command';
|
|
9
|
+
import { discordRuntimeStateToken } from './discord-runtime-state.js';
|
|
10
|
+
export const discordEndpointCommands = createEndpointCommands({
|
|
11
|
+
adapterKey: 'discord',
|
|
12
|
+
adapterDisplayName: 'Discord',
|
|
13
|
+
fields: [
|
|
14
|
+
{ key: 'token', required: true, env: true, description: 'Discord bot token' },
|
|
15
|
+
],
|
|
16
|
+
running: (use) => use(discordRuntimeStateToken).endpoints.values(),
|
|
17
|
+
describeEntry: (entry) => `token: ${String(entry.token)}`,
|
|
18
|
+
}, defineCommand);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const discordRuntimeStateToken: import("zhin.js").Token<import("@zhin.js/adapter").EndpointRuntimeState>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
|
|
3
|
+
* 由 plugin.ts setup() provide,adapter create 与 `discord.endpoint` 命令共享(同一 owner generation)。
|
|
4
|
+
*/
|
|
5
|
+
import { defineEndpointRuntimeStateToken } from 'zhin.js/adapter';
|
|
6
|
+
export const discordRuntimeStateToken = defineEndpointRuntimeStateToken('discord');
|