@zhin.js/adapter-discord 1.1.25 → 1.1.27
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 +15 -0
- package/README.md +1 -0
- package/lib/channel-visibility.d.ts +9 -0
- package/lib/channel-visibility.js +44 -0
- package/lib/console-spec.js +115 -10
- package/lib/endpoint.d.ts +1 -0
- package/lib/endpoint.js +5 -0
- package/package.json +11 -11
- package/src/channel-visibility.ts +44 -0
- package/src/console-spec.ts +115 -10
- package/src/endpoint.ts +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @zhin.js/adapter-discord
|
|
2
2
|
|
|
3
|
+
## 1.1.27
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ae4c8ae: fix(adapter-discord): `listChannels` 默认过滤 Discord 混淆频道(`flags` 含 `CHANNEL_OBFUSCATED` = `1 << 17`)。自 2026-11-16 起,机器人缺少 `VIEW_CHANNEL` 的频道会以 `___hidden___` 名称经 Gateway 下发,而 HTTP `GET /guilds/{id}/channels` 不再返回它们。Console 频道列表不再显示机器人无权查看的频道,与 Discord HTTP 列表一致。在 Discord 开发者后台 Overview > Bot 打开 Private Channel Obfuscation,可以让 Gateway 提前下发这些混淆频道(该开关是临时测试;HTTP 列表没有对应的提前开关)。
|
|
8
|
+
|
|
9
|
+
## 1.1.26
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 0d76a7c: Overhaul Console interactive workbenches, platform-specific adapter account onboarding/login flows, DeclarativePageView visual/interactive capabilities, and official utility plugin Console pages.
|
|
14
|
+
- Updated dependencies [0d76a7c]
|
|
15
|
+
- @zhin.js/adapter@1.1.32
|
|
16
|
+
- @zhin.js/core@1.1.60
|
|
17
|
+
|
|
3
18
|
## 1.1.25
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@ Zhin.js Discord 适配器(Plugin Runtime),默认通过 **Gateway WebSocket
|
|
|
7
7
|
- Gateway WebSocket 入站(默认;无需公网 HTTPS / host)
|
|
8
8
|
- 解析 text / mention / attachment / embed / sticker / button
|
|
9
9
|
- 支持私聊、群组与服务器频道
|
|
10
|
+
- `listChannels` 会过滤带 `CHANNEL_OBFUSCATED`(`1 << 17`)的混淆频道:自 2026-11-16 起,机器人缺少 `VIEW_CHANNEL` 时 Gateway 仍会下发这些频道(名字常为 `___hidden___`),HTTP 频道列表则不会返回它们。
|
|
10
11
|
- 出站 `send({ conversation, payload })` → Discord channel message(Markdown content / media / embed / keyboard)
|
|
11
12
|
- 约定式 `defineAdapter` / `definePlugin`(无需 `usePlugin`)
|
|
12
13
|
- Interactions HTTP webhook(需 `httpHostToken`),支持验签、PING 与 slash command
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const DISCORD_CHANNEL_FLAG_OBFUSCATED: number;
|
|
2
|
+
type ChannelFlagsLike = number | bigint | {
|
|
3
|
+
readonly bitfield?: number | bigint;
|
|
4
|
+
} | null | undefined;
|
|
5
|
+
/** 频道是否被 Discord 混淆(机器人无 `VIEW_CHANNEL`)。兼容 discord.js 的 `ChannelFlagsBitField` 与原始 `flags` 数值。 */
|
|
6
|
+
export declare function isDiscordChannelObfuscated(channel: {
|
|
7
|
+
readonly flags?: ChannelFlagsLike;
|
|
8
|
+
} | null | undefined): boolean;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ChannelFlagsBitField } from 'discord.js';
|
|
2
|
+
/**
|
|
3
|
+
* Discord 频道混淆(Channel Obfuscation)判定。
|
|
4
|
+
*
|
|
5
|
+
* 自 2026-11-16 起,Gateway 会继续下发机器人缺少 `VIEW_CHANNEL` 的频道,但其元数据被混淆:
|
|
6
|
+
* `name` 变为 `"___hidden___"`,并在 `flags` 上置 `CHANNEL_OBFUSCATED`;HTTP 的
|
|
7
|
+
* `GET /guilds/{guild.id}/channels` 则直接不返回这些频道。
|
|
8
|
+
*
|
|
9
|
+
* 出处:
|
|
10
|
+
* - https://docs.discord.com/developers/resources/channel#channel-object-channel-flags
|
|
11
|
+
* (Channel Flags 表:`CHANNEL_OBFUSCATED` = `1 << 17`)
|
|
12
|
+
* - https://docs.discord.com/developers/resources/channel#channel-object-obfuscated-channels
|
|
13
|
+
* - https://docs.discord.com/developers/change-log (2026-08-12 "Channel Obfuscation for Users and Bots")
|
|
14
|
+
* - discord-api-types `ChannelFlags.ChannelObfuscated`(0.38.54 起,值为 `131072`)。
|
|
15
|
+
* discord.js 14.27.0 把它声明为 dependencies `^0.38.49`,并通过
|
|
16
|
+
* `ChannelFlagsBitField.Flags` 暴露同一枚举。0.38.49–0.38.53 上该枚举是 `undefined`,
|
|
17
|
+
* 因此这里用 `?? (1 << 17)` 兜底,避免过滤悄悄失效。
|
|
18
|
+
*
|
|
19
|
+
* 官方要求以 flag 判定,不要依赖 `name` 等被混淆字段。
|
|
20
|
+
*/
|
|
21
|
+
function obfuscatedFlagBit() {
|
|
22
|
+
return ChannelFlagsBitField.Flags.ChannelObfuscated ?? (1 << 17);
|
|
23
|
+
}
|
|
24
|
+
export const DISCORD_CHANNEL_FLAG_OBFUSCATED = obfuscatedFlagBit();
|
|
25
|
+
function readFlagBits(flags) {
|
|
26
|
+
if (flags == null)
|
|
27
|
+
return 0;
|
|
28
|
+
if (typeof flags === 'number')
|
|
29
|
+
return flags;
|
|
30
|
+
if (typeof flags === 'bigint')
|
|
31
|
+
return Number(flags & 0xffffffffn);
|
|
32
|
+
const bitfield = flags.bitfield;
|
|
33
|
+
if (typeof bitfield === 'number')
|
|
34
|
+
return bitfield;
|
|
35
|
+
if (typeof bitfield === 'bigint')
|
|
36
|
+
return Number(bitfield & 0xffffffffn);
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
/** 频道是否被 Discord 混淆(机器人无 `VIEW_CHANNEL`)。兼容 discord.js 的 `ChannelFlagsBitField` 与原始 `flags` 数值。 */
|
|
40
|
+
export function isDiscordChannelObfuscated(channel) {
|
|
41
|
+
if (!channel)
|
|
42
|
+
return false;
|
|
43
|
+
return (readFlagBits(channel.flags) & obfuscatedFlagBit()) !== 0;
|
|
44
|
+
}
|
package/lib/console-spec.js
CHANGED
|
@@ -2,16 +2,121 @@ export const discordConsoleSpec = {
|
|
|
2
2
|
adapterKey: 'discord',
|
|
3
3
|
adapterDisplayName: 'Discord',
|
|
4
4
|
fields: [
|
|
5
|
-
{
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
{
|
|
6
|
+
key: 'token',
|
|
7
|
+
required: true,
|
|
8
|
+
env: true,
|
|
9
|
+
label: 'Bot Token (机器人令牌)',
|
|
10
|
+
labelI18n: { en: 'Bot Token' },
|
|
11
|
+
group: '基础认证',
|
|
12
|
+
groupI18n: { en: 'Basic Auth' },
|
|
13
|
+
placeholder: 'MTIzNDU2Nzg5MDEyMzQ1Njc4OQ.G...',
|
|
14
|
+
description: 'Discord Developer Portal 生成的 Bot Token',
|
|
15
|
+
descriptionI18n: { en: 'Discord bot token' },
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
key: 'connection',
|
|
19
|
+
label: '连接模式',
|
|
20
|
+
labelI18n: { en: 'Connection Mode' },
|
|
21
|
+
group: '连接与回调',
|
|
22
|
+
groupI18n: { en: 'Connection & Webhook' },
|
|
23
|
+
placeholder: 'gateway',
|
|
24
|
+
description: 'gateway(WebSocket 长连接)或 interactions(HTTP 交互回调)',
|
|
25
|
+
descriptionI18n: { en: 'gateway or interactions' },
|
|
26
|
+
options: [
|
|
27
|
+
{ value: 'gateway', label: 'Gateway WebSocket(推荐)', labelI18n: { en: 'Gateway WebSocket (Recommended)' } },
|
|
28
|
+
{ value: 'interactions', label: 'HTTP Interactions 回调', labelI18n: { en: 'HTTP Interactions' } },
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
key: 'applicationId',
|
|
33
|
+
label: 'Application ID (应用 ID)',
|
|
34
|
+
labelI18n: { en: 'Application ID' },
|
|
35
|
+
group: '连接与回调',
|
|
36
|
+
groupI18n: { en: 'Connection & Webhook' },
|
|
37
|
+
placeholder: '123456789012345678',
|
|
38
|
+
description: 'Interactions 模式必填的应用 ID(修改此项将替换账号)',
|
|
39
|
+
descriptionI18n: { en: 'Application ID for Interactions; identity changes replace the account' },
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
key: 'publicKey',
|
|
43
|
+
label: 'Ed25519 公钥 (Public Key)',
|
|
44
|
+
labelI18n: { en: 'Ed25519 Public Key' },
|
|
45
|
+
group: '连接与回调',
|
|
46
|
+
groupI18n: { en: 'Connection & Webhook' },
|
|
47
|
+
placeholder: '64 位十六进制公钥字符串',
|
|
48
|
+
placeholderI18n: { en: '64-char hex Ed25519 public key' },
|
|
49
|
+
description: 'Interactions 模式下校验请求签名的应用公钥',
|
|
50
|
+
descriptionI18n: { en: 'Application Ed25519 public key' },
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
key: 'interactionsPath',
|
|
54
|
+
label: 'Interactions 本地路由',
|
|
55
|
+
labelI18n: { en: 'Interactions Local Path' },
|
|
56
|
+
group: '连接与回调',
|
|
57
|
+
groupI18n: { en: 'Connection & Webhook' },
|
|
58
|
+
placeholder: '/discord/interactions',
|
|
59
|
+
description: 'Interactions 模式监听的本地 HTTP 路径',
|
|
60
|
+
descriptionI18n: { en: 'Local Interactions route' },
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
key: 'defaultActivity.name',
|
|
64
|
+
label: '状态活动名称',
|
|
65
|
+
labelI18n: { en: 'Activity Name' },
|
|
66
|
+
group: '流式体验与代理',
|
|
67
|
+
groupI18n: { en: 'Streaming & Proxy' },
|
|
68
|
+
placeholder: '与 Zhin 对话中',
|
|
69
|
+
description: '机器人在线状态展示的活动名称(可选)',
|
|
70
|
+
descriptionI18n: { en: 'SDK activity name' },
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
key: 'defaultActivity.type',
|
|
74
|
+
label: '状态活动类型',
|
|
75
|
+
labelI18n: { en: 'Activity Type' },
|
|
76
|
+
group: '流式体验与代理',
|
|
77
|
+
groupI18n: { en: 'Streaming & Proxy' },
|
|
78
|
+
placeholder: 'PLAYING',
|
|
79
|
+
description: 'PLAYING、STREAMING、LISTENING、WATCHING 或 COMPETING',
|
|
80
|
+
descriptionI18n: { en: 'PLAYING, STREAMING, LISTENING, WATCHING or COMPETING' },
|
|
81
|
+
options: [
|
|
82
|
+
{ value: 'PLAYING', label: '正在玩 (PLAYING)', labelI18n: { en: 'PLAYING' } },
|
|
83
|
+
{ value: 'STREAMING', label: '正在直播 (STREAMING)', labelI18n: { en: 'STREAMING' } },
|
|
84
|
+
{ value: 'LISTENING', label: '正在听 (LISTENING)', labelI18n: { en: 'LISTENING' } },
|
|
85
|
+
{ value: 'WATCHING', label: '正在看 (WATCHING)', labelI18n: { en: 'WATCHING' } },
|
|
86
|
+
{ value: 'COMPETING', label: '正在参与 (COMPETING)', labelI18n: { en: 'COMPETING' } },
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
key: 'defaultActivity.url',
|
|
91
|
+
label: '状态直播链接',
|
|
92
|
+
labelI18n: { en: 'Activity Stream URL' },
|
|
93
|
+
group: '流式体验与代理',
|
|
94
|
+
groupI18n: { en: 'Streaming & Proxy' },
|
|
95
|
+
placeholder: 'https://twitch.tv/...',
|
|
96
|
+
description: 'STREAMING 状态下的直播链接(可选)',
|
|
97
|
+
descriptionI18n: { en: 'SDK activity URL' },
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
key: 'restApiProxy.port',
|
|
101
|
+
type: 'number',
|
|
102
|
+
label: 'REST API 代理端口',
|
|
103
|
+
labelI18n: { en: 'REST Proxy Port' },
|
|
104
|
+
group: '流式体验与代理',
|
|
105
|
+
groupI18n: { en: 'Streaming & Proxy' },
|
|
106
|
+
placeholder: '例如 7890',
|
|
107
|
+
description: '本地回环 REST API 代理端口(可选)',
|
|
108
|
+
descriptionI18n: { en: 'Configured REST loopback proxy port' },
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
key: 'gatewayFaultProxyUrl',
|
|
112
|
+
label: 'Gateway 代理地址',
|
|
113
|
+
labelI18n: { en: 'Gateway Proxy URL' },
|
|
114
|
+
group: '流式体验与代理',
|
|
115
|
+
groupI18n: { en: 'Streaming & Proxy' },
|
|
116
|
+
placeholder: 'ws://127.0.0.1:7890',
|
|
117
|
+
description: '本地 Gateway 代理或故障注入地址(可选)',
|
|
118
|
+
descriptionI18n: { en: 'Configured local Gateway fault proxy URL' },
|
|
119
|
+
},
|
|
15
120
|
],
|
|
16
121
|
docsUrl: 'https://zhin.js.org/adapters/discord',
|
|
17
122
|
readPermission: 'discord:read',
|
package/lib/endpoint.d.ts
CHANGED
|
@@ -74,5 +74,6 @@ export declare class DiscordInteractionsEndpoint extends DiscordRestClient imple
|
|
|
74
74
|
/**
|
|
75
75
|
* DiscordGatewayEndpoint 的 EndpointManagement 语义端口(参照 qq 的工厂模式)。
|
|
76
76
|
* 数据源为 discord.js SDK 缓存:guilds.cache / guild.channels.cache / guild.members。
|
|
77
|
+
* listChannels 会过滤带 CHANNEL_OBFUSCATED flag 的混淆频道(见 channel-visibility.ts)。
|
|
77
78
|
*/
|
|
78
79
|
export declare function createDiscordEndpointManagement(requireClient: () => DiscordClientTransport): EndpointManagement;
|
package/lib/endpoint.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createDiscordSelfProfile, createDiscordRestSelfProfile } from './self-profile.js';
|
|
2
2
|
import { DiscordRestRequestError } from './rest-request-error.js';
|
|
3
|
+
import { isDiscordChannelObfuscated } from './channel-visibility.js';
|
|
3
4
|
/**
|
|
4
5
|
* DiscordEndpoint — lifecycle, outbound, admit, gateway / interactions modes, agent tool surface.
|
|
5
6
|
*/
|
|
@@ -515,6 +516,7 @@ async function resolveDiscordContent(fetch, token, reference, context) {
|
|
|
515
516
|
/**
|
|
516
517
|
* DiscordGatewayEndpoint 的 EndpointManagement 语义端口(参照 qq 的工厂模式)。
|
|
517
518
|
* 数据源为 discord.js SDK 缓存:guilds.cache / guild.channels.cache / guild.members。
|
|
519
|
+
* listChannels 会过滤带 CHANNEL_OBFUSCATED flag 的混淆频道(见 channel-visibility.ts)。
|
|
518
520
|
*/
|
|
519
521
|
export function createDiscordEndpointManagement(requireClient) {
|
|
520
522
|
return Object.freeze({
|
|
@@ -545,6 +547,9 @@ export function createDiscordEndpointManagement(requireClient) {
|
|
|
545
547
|
continue;
|
|
546
548
|
if (channel.type !== ChannelType.GuildText)
|
|
547
549
|
continue;
|
|
550
|
+
// 机器人无 VIEW_CHANNEL 的频道会被 Gateway 混淆下发(HTTP 列表已不返回),默认过滤以保持两条路径一致。
|
|
551
|
+
if (isDiscordChannelObfuscated(channel))
|
|
552
|
+
continue;
|
|
548
553
|
channels.push({
|
|
549
554
|
id: String(channel.id),
|
|
550
555
|
name: channel.name ? String(channel.name) : undefined,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-discord",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.27",
|
|
4
4
|
"description": "Zhin.js Discord adapter for Plugin Runtime (Gateway WebSocket)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -35,25 +35,25 @@
|
|
|
35
35
|
"@discordjs/ws": "1.2.3",
|
|
36
36
|
"discord.js": "^14.27.0",
|
|
37
37
|
"undici": "6.29.1",
|
|
38
|
-
"@zhin.js/adapter": "1.1.
|
|
39
|
-
"@zhin.js/agent-feature": "1.1.16",
|
|
38
|
+
"@zhin.js/adapter": "1.1.32",
|
|
40
39
|
"@zhin.js/console-contract": "1.1.5",
|
|
41
|
-
"@zhin.js/core": "1.1.59",
|
|
42
40
|
"@zhin.js/feature-kit": "1.1.15",
|
|
41
|
+
"@zhin.js/core": "1.1.60",
|
|
43
42
|
"@zhin.js/host-http": "1.1.17",
|
|
44
43
|
"@zhin.js/im-contract": "1.1.13",
|
|
45
44
|
"@zhin.js/logger": "1.1.2",
|
|
46
|
-
"@zhin.js/page": "1.1.17",
|
|
47
|
-
"@zhin.js/permission": "1.1.15",
|
|
48
45
|
"@zhin.js/skill": "1.1.16",
|
|
49
|
-
"@zhin.js/
|
|
46
|
+
"@zhin.js/agent-feature": "1.1.16",
|
|
47
|
+
"@zhin.js/permission": "1.1.15",
|
|
48
|
+
"@zhin.js/tool": "1.1.18",
|
|
49
|
+
"@zhin.js/page": "1.1.17"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"zod": "^4.0.0",
|
|
53
|
-
"@zhin.js/adapter": "^1.1.31",
|
|
54
53
|
"@zhin.js/command": "^1.1.17",
|
|
55
|
-
"
|
|
56
|
-
"zhin.js": "^1.1.
|
|
54
|
+
"zhin.js": "^1.1.25",
|
|
55
|
+
"@zhin.js/adapter": "^1.1.32",
|
|
56
|
+
"@zhin.js/core": "^1.1.60"
|
|
57
57
|
},
|
|
58
58
|
"peerDependenciesMeta": {
|
|
59
59
|
"@zhin.js/command": {
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"vitest": "^4.1.10",
|
|
73
73
|
"zod": "^4.4.3",
|
|
74
74
|
"@zhin.js/host-http": "1.1.17",
|
|
75
|
-
"zhin.js": "1.1.
|
|
75
|
+
"zhin.js": "1.1.25"
|
|
76
76
|
},
|
|
77
77
|
"files": [
|
|
78
78
|
"adapters",
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ChannelFlagsBitField } from 'discord.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Discord 频道混淆(Channel Obfuscation)判定。
|
|
5
|
+
*
|
|
6
|
+
* 自 2026-11-16 起,Gateway 会继续下发机器人缺少 `VIEW_CHANNEL` 的频道,但其元数据被混淆:
|
|
7
|
+
* `name` 变为 `"___hidden___"`,并在 `flags` 上置 `CHANNEL_OBFUSCATED`;HTTP 的
|
|
8
|
+
* `GET /guilds/{guild.id}/channels` 则直接不返回这些频道。
|
|
9
|
+
*
|
|
10
|
+
* 出处:
|
|
11
|
+
* - https://docs.discord.com/developers/resources/channel#channel-object-channel-flags
|
|
12
|
+
* (Channel Flags 表:`CHANNEL_OBFUSCATED` = `1 << 17`)
|
|
13
|
+
* - https://docs.discord.com/developers/resources/channel#channel-object-obfuscated-channels
|
|
14
|
+
* - https://docs.discord.com/developers/change-log (2026-08-12 "Channel Obfuscation for Users and Bots")
|
|
15
|
+
* - discord-api-types `ChannelFlags.ChannelObfuscated`(0.38.54 起,值为 `131072`)。
|
|
16
|
+
* discord.js 14.27.0 把它声明为 dependencies `^0.38.49`,并通过
|
|
17
|
+
* `ChannelFlagsBitField.Flags` 暴露同一枚举。0.38.49–0.38.53 上该枚举是 `undefined`,
|
|
18
|
+
* 因此这里用 `?? (1 << 17)` 兜底,避免过滤悄悄失效。
|
|
19
|
+
*
|
|
20
|
+
* 官方要求以 flag 判定,不要依赖 `name` 等被混淆字段。
|
|
21
|
+
*/
|
|
22
|
+
function obfuscatedFlagBit(): number {
|
|
23
|
+
return ChannelFlagsBitField.Flags.ChannelObfuscated ?? (1 << 17);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const DISCORD_CHANNEL_FLAG_OBFUSCATED = obfuscatedFlagBit();
|
|
27
|
+
|
|
28
|
+
type ChannelFlagsLike = number | bigint | { readonly bitfield?: number | bigint } | null | undefined;
|
|
29
|
+
|
|
30
|
+
function readFlagBits(flags: ChannelFlagsLike): number {
|
|
31
|
+
if (flags == null) return 0;
|
|
32
|
+
if (typeof flags === 'number') return flags;
|
|
33
|
+
if (typeof flags === 'bigint') return Number(flags & 0xffffffffn);
|
|
34
|
+
const bitfield = flags.bitfield;
|
|
35
|
+
if (typeof bitfield === 'number') return bitfield;
|
|
36
|
+
if (typeof bitfield === 'bigint') return Number(bitfield & 0xffffffffn);
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 频道是否被 Discord 混淆(机器人无 `VIEW_CHANNEL`)。兼容 discord.js 的 `ChannelFlagsBitField` 与原始 `flags` 数值。 */
|
|
41
|
+
export function isDiscordChannelObfuscated(channel: { readonly flags?: ChannelFlagsLike } | null | undefined): boolean {
|
|
42
|
+
if (!channel) return false;
|
|
43
|
+
return (readFlagBits(channel.flags) & obfuscatedFlagBit()) !== 0;
|
|
44
|
+
}
|
package/src/console-spec.ts
CHANGED
|
@@ -7,16 +7,121 @@ export const discordConsoleSpec = {
|
|
|
7
7
|
adapterKey: 'discord',
|
|
8
8
|
adapterDisplayName: 'Discord',
|
|
9
9
|
fields: [
|
|
10
|
-
{
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
10
|
+
{
|
|
11
|
+
key: 'token',
|
|
12
|
+
required: true,
|
|
13
|
+
env: true,
|
|
14
|
+
label: 'Bot Token (机器人令牌)',
|
|
15
|
+
labelI18n: { en: 'Bot Token' },
|
|
16
|
+
group: '基础认证',
|
|
17
|
+
groupI18n: { en: 'Basic Auth' },
|
|
18
|
+
placeholder: 'MTIzNDU2Nzg5MDEyMzQ1Njc4OQ.G...',
|
|
19
|
+
description: 'Discord Developer Portal 生成的 Bot Token',
|
|
20
|
+
descriptionI18n: { en: 'Discord bot token' },
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
key: 'connection',
|
|
24
|
+
label: '连接模式',
|
|
25
|
+
labelI18n: { en: 'Connection Mode' },
|
|
26
|
+
group: '连接与回调',
|
|
27
|
+
groupI18n: { en: 'Connection & Webhook' },
|
|
28
|
+
placeholder: 'gateway',
|
|
29
|
+
description: 'gateway(WebSocket 长连接)或 interactions(HTTP 交互回调)',
|
|
30
|
+
descriptionI18n: { en: 'gateway or interactions' },
|
|
31
|
+
options: [
|
|
32
|
+
{ value: 'gateway', label: 'Gateway WebSocket(推荐)', labelI18n: { en: 'Gateway WebSocket (Recommended)' } },
|
|
33
|
+
{ value: 'interactions', label: 'HTTP Interactions 回调', labelI18n: { en: 'HTTP Interactions' } },
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
key: 'applicationId',
|
|
38
|
+
label: 'Application ID (应用 ID)',
|
|
39
|
+
labelI18n: { en: 'Application ID' },
|
|
40
|
+
group: '连接与回调',
|
|
41
|
+
groupI18n: { en: 'Connection & Webhook' },
|
|
42
|
+
placeholder: '123456789012345678',
|
|
43
|
+
description: 'Interactions 模式必填的应用 ID(修改此项将替换账号)',
|
|
44
|
+
descriptionI18n: { en: 'Application ID for Interactions; identity changes replace the account' },
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
key: 'publicKey',
|
|
48
|
+
label: 'Ed25519 公钥 (Public Key)',
|
|
49
|
+
labelI18n: { en: 'Ed25519 Public Key' },
|
|
50
|
+
group: '连接与回调',
|
|
51
|
+
groupI18n: { en: 'Connection & Webhook' },
|
|
52
|
+
placeholder: '64 位十六进制公钥字符串',
|
|
53
|
+
placeholderI18n: { en: '64-char hex Ed25519 public key' },
|
|
54
|
+
description: 'Interactions 模式下校验请求签名的应用公钥',
|
|
55
|
+
descriptionI18n: { en: 'Application Ed25519 public key' },
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
key: 'interactionsPath',
|
|
59
|
+
label: 'Interactions 本地路由',
|
|
60
|
+
labelI18n: { en: 'Interactions Local Path' },
|
|
61
|
+
group: '连接与回调',
|
|
62
|
+
groupI18n: { en: 'Connection & Webhook' },
|
|
63
|
+
placeholder: '/discord/interactions',
|
|
64
|
+
description: 'Interactions 模式监听的本地 HTTP 路径',
|
|
65
|
+
descriptionI18n: { en: 'Local Interactions route' },
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
key: 'defaultActivity.name',
|
|
69
|
+
label: '状态活动名称',
|
|
70
|
+
labelI18n: { en: 'Activity Name' },
|
|
71
|
+
group: '流式体验与代理',
|
|
72
|
+
groupI18n: { en: 'Streaming & Proxy' },
|
|
73
|
+
placeholder: '与 Zhin 对话中',
|
|
74
|
+
description: '机器人在线状态展示的活动名称(可选)',
|
|
75
|
+
descriptionI18n: { en: 'SDK activity name' },
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
key: 'defaultActivity.type',
|
|
79
|
+
label: '状态活动类型',
|
|
80
|
+
labelI18n: { en: 'Activity Type' },
|
|
81
|
+
group: '流式体验与代理',
|
|
82
|
+
groupI18n: { en: 'Streaming & Proxy' },
|
|
83
|
+
placeholder: 'PLAYING',
|
|
84
|
+
description: 'PLAYING、STREAMING、LISTENING、WATCHING 或 COMPETING',
|
|
85
|
+
descriptionI18n: { en: 'PLAYING, STREAMING, LISTENING, WATCHING or COMPETING' },
|
|
86
|
+
options: [
|
|
87
|
+
{ value: 'PLAYING', label: '正在玩 (PLAYING)', labelI18n: { en: 'PLAYING' } },
|
|
88
|
+
{ value: 'STREAMING', label: '正在直播 (STREAMING)', labelI18n: { en: 'STREAMING' } },
|
|
89
|
+
{ value: 'LISTENING', label: '正在听 (LISTENING)', labelI18n: { en: 'LISTENING' } },
|
|
90
|
+
{ value: 'WATCHING', label: '正在看 (WATCHING)', labelI18n: { en: 'WATCHING' } },
|
|
91
|
+
{ value: 'COMPETING', label: '正在参与 (COMPETING)', labelI18n: { en: 'COMPETING' } },
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
key: 'defaultActivity.url',
|
|
96
|
+
label: '状态直播链接',
|
|
97
|
+
labelI18n: { en: 'Activity Stream URL' },
|
|
98
|
+
group: '流式体验与代理',
|
|
99
|
+
groupI18n: { en: 'Streaming & Proxy' },
|
|
100
|
+
placeholder: 'https://twitch.tv/...',
|
|
101
|
+
description: 'STREAMING 状态下的直播链接(可选)',
|
|
102
|
+
descriptionI18n: { en: 'SDK activity URL' },
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
key: 'restApiProxy.port',
|
|
106
|
+
type: 'number',
|
|
107
|
+
label: 'REST API 代理端口',
|
|
108
|
+
labelI18n: { en: 'REST Proxy Port' },
|
|
109
|
+
group: '流式体验与代理',
|
|
110
|
+
groupI18n: { en: 'Streaming & Proxy' },
|
|
111
|
+
placeholder: '例如 7890',
|
|
112
|
+
description: '本地回环 REST API 代理端口(可选)',
|
|
113
|
+
descriptionI18n: { en: 'Configured REST loopback proxy port' },
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
key: 'gatewayFaultProxyUrl',
|
|
117
|
+
label: 'Gateway 代理地址',
|
|
118
|
+
labelI18n: { en: 'Gateway Proxy URL' },
|
|
119
|
+
group: '流式体验与代理',
|
|
120
|
+
groupI18n: { en: 'Streaming & Proxy' },
|
|
121
|
+
placeholder: 'ws://127.0.0.1:7890',
|
|
122
|
+
description: '本地 Gateway 代理或故障注入地址(可选)',
|
|
123
|
+
descriptionI18n: { en: 'Configured local Gateway fault proxy URL' },
|
|
124
|
+
},
|
|
20
125
|
] as readonly AdapterConsoleField[],
|
|
21
126
|
docsUrl: 'https://zhin.js.org/adapters/discord',
|
|
22
127
|
readPermission: 'discord:read',
|
package/src/endpoint.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createDiscordRestApiAgent } from './rest-api-proxy.js';
|
|
2
2
|
import { createDiscordSelfProfile, createDiscordRestSelfProfile } from './self-profile.js';
|
|
3
3
|
import { DiscordRestRequestError } from './rest-request-error.js';
|
|
4
|
+
import { isDiscordChannelObfuscated } from './channel-visibility.js';
|
|
4
5
|
/**
|
|
5
6
|
* DiscordEndpoint — lifecycle, outbound, admit, gateway / interactions modes, agent tool surface.
|
|
6
7
|
*/
|
|
@@ -606,6 +607,7 @@ async function resolveDiscordContent(
|
|
|
606
607
|
/**
|
|
607
608
|
* DiscordGatewayEndpoint 的 EndpointManagement 语义端口(参照 qq 的工厂模式)。
|
|
608
609
|
* 数据源为 discord.js SDK 缓存:guilds.cache / guild.channels.cache / guild.members。
|
|
610
|
+
* listChannels 会过滤带 CHANNEL_OBFUSCATED flag 的混淆频道(见 channel-visibility.ts)。
|
|
609
611
|
*/
|
|
610
612
|
export function createDiscordEndpointManagement(
|
|
611
613
|
requireClient: () => DiscordClientTransport,
|
|
@@ -634,6 +636,8 @@ export function createDiscordEndpointManagement(
|
|
|
634
636
|
for (const channel of guild.channels?.cache?.values() ?? []) {
|
|
635
637
|
if (!channel?.id) continue;
|
|
636
638
|
if (channel.type !== ChannelType.GuildText) continue;
|
|
639
|
+
// 机器人无 VIEW_CHANNEL 的频道会被 Gateway 混淆下发(HTTP 列表已不返回),默认过滤以保持两条路径一致。
|
|
640
|
+
if (isDiscordChannelObfuscated(channel)) continue;
|
|
637
641
|
channels.push({
|
|
638
642
|
id: String(channel.id),
|
|
639
643
|
name: channel.name ? String(channel.name) : undefined,
|