@zhin.js/adapter-discord 1.1.26 → 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 CHANGED
@@ -1,5 +1,11 @@
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
+
3
9
  ## 1.1.26
4
10
 
5
11
  ### 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/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.26",
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",
@@ -36,24 +36,24 @@
36
36
  "discord.js": "^14.27.0",
37
37
  "undici": "6.29.1",
38
38
  "@zhin.js/adapter": "1.1.32",
39
- "@zhin.js/agent-feature": "1.1.16",
40
39
  "@zhin.js/console-contract": "1.1.5",
41
- "@zhin.js/core": "1.1.60",
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/tool": "1.1.18"
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.32",
54
53
  "@zhin.js/command": "^1.1.17",
55
- "@zhin.js/core": "^1.1.60",
56
- "zhin.js": "^1.1.25"
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": {
@@ -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/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,