@zhin.js/adapter-discord 1.1.26 → 1.1.28

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 (52) hide show
  1. package/CHANGELOG.md +175 -0
  2. package/LICENSE +21 -0
  3. package/PERMITS.md +15 -14
  4. package/README.md +7 -2
  5. package/adapters/discord/index.js +3 -0
  6. package/adapters/discord/index.ts +3 -0
  7. package/agents/discord/conversation.js +70 -0
  8. package/agents/discord/conversation.ts +77 -0
  9. package/agents/discord/messages.js +18 -2
  10. package/agents/discord/messages.ts +18 -2
  11. package/agents/discord/skills/discord/SKILL.md +1 -1
  12. package/agents/discord/skills/discord/tools/react/index.js +7 -6
  13. package/agents/discord/skills/discord/tools/react/index.ts +8 -7
  14. package/agents/discord/skills/discord/tools/send_embed/index.js +6 -11
  15. package/agents/discord/skills/discord/tools/send_embed/index.ts +7 -12
  16. package/agents/discord/skills/discord-guild/tools/add_role/index.js +11 -9
  17. package/agents/discord/skills/discord-guild/tools/add_role/index.ts +11 -10
  18. package/agents/discord/skills/discord-guild/tools/create_thread/index.js +5 -5
  19. package/agents/discord/skills/discord-guild/tools/create_thread/index.ts +6 -6
  20. package/agents/discord/skills/discord-guild/tools/forum_post/index.js +7 -3
  21. package/agents/discord/skills/discord-guild/tools/forum_post/index.ts +7 -4
  22. package/agents/discord/skills/discord-guild/tools/list_roles/index.js +7 -8
  23. package/agents/discord/skills/discord-guild/tools/list_roles/index.ts +8 -9
  24. package/agents/discord/skills/discord-guild/tools/remove_role/index.js +11 -9
  25. package/agents/discord/skills/discord-guild/tools/remove_role/index.ts +11 -10
  26. package/lib/channel-visibility.d.ts +9 -0
  27. package/lib/channel-visibility.js +44 -0
  28. package/lib/endpoint.d.ts +1 -0
  29. package/lib/endpoint.js +76 -12
  30. package/lib/gateway.d.ts +1 -0
  31. package/lib/gateway.js +21 -8
  32. package/lib/index.d.ts +1 -1
  33. package/lib/index.js +1 -1
  34. package/lib/platform-permit.d.ts +4 -11
  35. package/lib/platform-permit.js +7 -40
  36. package/lib/protocol.d.ts +18 -0
  37. package/lib/protocol.js +32 -3
  38. package/lib/sender-standing.d.ts +18 -0
  39. package/lib/sender-standing.js +91 -0
  40. package/lib/webhook.d.ts +2 -6
  41. package/lib/webhook.js +23 -55
  42. package/package.json +23 -25
  43. package/plugin.js +0 -6
  44. package/schema.json +0 -56
  45. package/src/channel-visibility.ts +44 -0
  46. package/src/endpoint.ts +77 -11
  47. package/src/gateway.ts +22 -5
  48. package/src/index.ts +1 -6
  49. package/src/platform-permit.ts +5 -52
  50. package/src/protocol.ts +43 -3
  51. package/src/sender-standing.ts +91 -0
  52. package/src/webhook.ts +21 -58
@@ -1,12 +1,11 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
2
  import { messages as t } from "../../../../messages.js";
3
+ import { sendToCurrentConversation } from "../../../../conversation.js";
3
4
  import { defineAgentTool } from '@zhin.js/tool';
4
5
  import { z } from 'zod';
5
- import { requireDiscordGatewayClient } from '@zhin.js/adapter-discord';
6
6
  export default defineAgentTool({
7
- description: '发送 Discord 富文本嵌入消息(Embed)',
7
+ description: '在当前 Discord 频道(或线程)发送富文本嵌入消息(Embed)',
8
8
  inputSchema: z.object({
9
- channel_id: z.string().describe('频道 ID'),
10
9
  title: z.string().optional().describe('Embed 标题'),
11
10
  description: z.string().optional().describe('Embed 描述'),
12
11
  color: z.number().optional().describe('颜色值(十进制,如 0x00ff00 = 65280)'),
@@ -15,8 +14,7 @@ export default defineAgentTool({
15
14
  }),
16
15
  adapter: 'discord',
17
16
  tags: ['discord'],
18
- async execute({ channel_id, title, description, color, url, fields }, context) {
19
- const client = requireDiscordGatewayClient(context.$client);
17
+ async execute({ title, description, color, url, fields }, context) {
20
18
  const embedData = {};
21
19
  if (title)
22
20
  embedData.title = title;
@@ -34,11 +32,8 @@ export default defineAgentTool({
34
32
  return { success: false, message: t(context.locale, 'invalidFields') };
35
33
  }
36
34
  }
37
- const channel = await client.channels.fetch(channel_id);
38
- if (!channel?.isSendable()) {
39
- throw new Error(t(context.locale, 'notTextChannel', { p0: channel_id }));
40
- }
41
- const msg = await channel.send({ embeds: [embedData] });
42
- return { success: true, message_id: msg.id, message: t(context.locale, 'embedSent') };
35
+ // `discord:embed` is the Discord adapter's namespaced outbound segment (rendered by formatOutboundBody).
36
+ const result = await sendToCurrentConversation(context, { type: 'discord:embed', data: embedData });
37
+ return { ...result, message: t(context.locale, result.success ? 'embedSent' : 'operationFailed') };
43
38
  },
44
39
  });
@@ -1,12 +1,11 @@
1
1
  import { messages as t } from "../../../../messages.js";
2
+ import { sendToCurrentConversation } from "../../../../conversation.js";
2
3
  import { defineAgentTool } from '@zhin.js/tool';
3
4
  import { z } from 'zod';
4
- import { requireDiscordGatewayClient } from '@zhin.js/adapter-discord';
5
5
 
6
- export default defineAgentTool<{ channel_id: string; title?: string; description?: string; color?: number; url?: string; fields?: string }>({
7
- description: '发送 Discord 富文本嵌入消息(Embed)',
6
+ export default defineAgentTool<{ title?: string; description?: string; color?: number; url?: string; fields?: string }>({
7
+ description: '在当前 Discord 频道(或线程)发送富文本嵌入消息(Embed)',
8
8
  inputSchema: z.object({
9
- channel_id: z.string().describe('频道 ID'),
10
9
  title: z.string().optional().describe('Embed 标题'),
11
10
  description: z.string().optional().describe('Embed 描述'),
12
11
  color: z.number().optional().describe('颜色值(十进制,如 0x00ff00 = 65280)'),
@@ -15,8 +14,7 @@ export default defineAgentTool<{ channel_id: string; title?: string; description
15
14
  }),
16
15
  adapter: 'discord',
17
16
  tags: ['discord'],
18
- async execute({ channel_id, title, description, color, url, fields }: { channel_id: string; title?: string; description?: string; color?: number; url?: string; fields?: string }, context) {
19
- const client = requireDiscordGatewayClient(context.$client);
17
+ async execute({ title, description, color, url, fields }, context) {
20
18
  const embedData: Record<string, unknown> = {};
21
19
  if (title) embedData.title = title;
22
20
  if (description) embedData.description = description;
@@ -29,11 +27,8 @@ export default defineAgentTool<{ channel_id: string; title?: string; description
29
27
  return { success: false, message: t(context.locale, 'invalidFields') };
30
28
  }
31
29
  }
32
- const channel = await client.channels.fetch(channel_id);
33
- if (!channel?.isSendable()) {
34
- throw new Error(t(context.locale, 'notTextChannel', { p0: channel_id }));
35
- }
36
- const msg = await channel.send({ embeds: [embedData] } as never);
37
- return { success: true, message_id: msg.id, message: t(context.locale, 'embedSent') };
30
+ // `discord:embed` is the Discord adapter's namespaced outbound segment (rendered by formatOutboundBody).
31
+ const result = await sendToCurrentConversation(context, { type: 'discord:embed', data: embedData });
32
+ return { ...result, message: t(context.locale, result.success ? 'embedSent' : 'operationFailed') };
38
33
  },
39
34
  });
@@ -1,26 +1,28 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
2
  import { messages as t } from "../../../../messages.js";
3
+ import { currentGuildChannel, requireRoleBelowRequester } from "../../../../conversation.js";
3
4
  import { defineAgentTool } from '@zhin.js/tool';
4
5
  import { z } from 'zod';
5
6
  import { requireDiscordGatewayClient } from '@zhin.js/adapter-discord';
6
- import { platformPermission } from '@zhin.js/permission';
7
7
  export default defineAgentTool({
8
- description: '给成员添加 Discord 角色',
8
+ description: '给当前 Discord 服务器的成员添加身份组(只能是低于你最高身份组的身份组)',
9
9
  inputSchema: z.object({
10
- guild_id: z.string().describe('服务器 ID'),
11
10
  user_id: z.string().describe('用户 ID'),
12
- role_id: z.string().describe('角色 ID'),
11
+ role_id: z.string().describe('身份组 ID'),
13
12
  }),
14
13
  adapter: 'discord',
15
14
  scopes: ["channel"],
16
15
  tags: ['discord'],
17
- permissions: [platformPermission('discord', 'manage_roles')],
18
- async execute({ guild_id, user_id, role_id }, context) {
16
+ permissions: ['platform-permission:manage_roles', 'bot-permission:manage_roles'],
17
+ async execute({ user_id, role_id }, context) {
18
+ const { guildId } = currentGuildChannel(context);
19
19
  const client = requireDiscordGatewayClient(context.$client);
20
- const guild = await client.guilds.fetch(guild_id);
20
+ const guild = await client.guilds.fetch(guildId);
21
+ const denied = await requireRoleBelowRequester(context, guild, role_id);
22
+ if (denied)
23
+ return { success: false, message: denied };
21
24
  const member = await guild.members.fetch(user_id);
22
25
  await member.roles.add(role_id);
23
- const success = true;
24
- return { success, message: success ? t(context.locale, 'memberRoleAdded', { p0: user_id }) : t(context.locale, 'operationFailed') };
26
+ return { success: true, message: t(context.locale, 'memberRoleAdded', { p0: user_id }) };
25
27
  },
26
28
  });
@@ -1,28 +1,29 @@
1
1
  import { messages as t } from "../../../../messages.js";
2
+ import { currentGuildChannel, requireRoleBelowRequester } from "../../../../conversation.js";
2
3
  import { defineAgentTool } from '@zhin.js/tool';
3
4
  import { z } from 'zod';
4
5
  import { requireDiscordGatewayClient } from '@zhin.js/adapter-discord';
5
- import { platformPermission } from '@zhin.js/permission';
6
6
 
7
- export default defineAgentTool<{ guild_id: string; user_id: string; role_id: string }>({
8
- description: '给成员添加 Discord 角色',
7
+ export default defineAgentTool<{ user_id: string; role_id: string }>({
8
+ description: '给当前 Discord 服务器的成员添加身份组(只能是低于你最高身份组的身份组)',
9
9
  inputSchema: z.object({
10
- guild_id: z.string().describe('服务器 ID'),
11
10
  user_id: z.string().describe('用户 ID'),
12
- role_id: z.string().describe('角色 ID'),
11
+ role_id: z.string().describe('身份组 ID'),
13
12
  }),
14
13
  adapter: 'discord',
15
14
  scopes: ["channel"],
16
15
  tags: ['discord'],
17
- permissions: [platformPermission('discord', 'manage_roles')],
18
- async execute({ guild_id, user_id, role_id }: { guild_id: string; user_id: string; role_id: string }, context) {
16
+ permissions: ['platform-permission:manage_roles', 'bot-permission:manage_roles'],
17
+ async execute({ user_id, role_id }, context) {
18
+ const { guildId } = currentGuildChannel(context);
19
19
  const client = requireDiscordGatewayClient(context.$client);
20
- const guild = await client.guilds.fetch(guild_id);
20
+ const guild = await client.guilds.fetch(guildId);
21
+ const denied = await requireRoleBelowRequester(context, guild, role_id);
22
+ if (denied) return { success: false, message: denied };
21
23
  const member = await guild.members.fetch(user_id) as {
22
24
  roles: { add(id: string): Promise<unknown> };
23
25
  };
24
26
  await member.roles.add(role_id);
25
- const success = true;
26
- return { success, message: success ? t(context.locale, 'memberRoleAdded', { p0: user_id }) : t(context.locale, 'operationFailed') };
27
+ return { success: true, message: t(context.locale, 'memberRoleAdded', { p0: user_id }) };
27
28
  },
28
29
  });
@@ -1,14 +1,13 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
2
  import { messages as t } from "../../../../messages.js";
3
+ import { currentGuildChannel } from "../../../../conversation.js";
3
4
  import { defineAgentTool } from '@zhin.js/tool';
4
5
  import { z } from 'zod';
5
6
  import { requireDiscordGatewayClient } from '@zhin.js/adapter-discord';
6
- import { platformPermission } from '@zhin.js/permission';
7
7
  import { ThreadAutoArchiveDuration } from 'discord.js';
8
8
  export default defineAgentTool({
9
- description: '在 Discord 频道中创建帖子/子线程',
9
+ description: '在当前 Discord 频道中创建帖子/子线程',
10
10
  inputSchema: z.object({
11
- channel_id: z.string().describe('频道 ID'),
12
11
  name: z.string().describe('帖子标题'),
13
12
  message_id: z.string().optional().describe('基于某条消息创建(可选)'),
14
13
  auto_archive_duration: z.number().optional().describe('自动归档时间(分钟:60/1440/4320/10080)'),
@@ -16,8 +15,9 @@ export default defineAgentTool({
16
15
  adapter: 'discord',
17
16
  scopes: ["channel"],
18
17
  tags: ['discord'],
19
- permissions: [platformPermission('discord', 'manage_channels')],
20
- async execute({ channel_id, name, message_id, auto_archive_duration }, context) {
18
+ permissions: ['platform-permission:manage_channels', 'bot-permission:create_public_threads'],
19
+ async execute({ name, message_id, auto_archive_duration }, context) {
20
+ const { channelId: channel_id } = currentGuildChannel(context);
21
21
  const client = requireDiscordGatewayClient(context.$client);
22
22
  const channel = await client.channels.fetch(channel_id);
23
23
  if (!channel || !('threads' in channel) || channel.isThreadOnly())
@@ -1,14 +1,13 @@
1
1
  import { messages as t } from "../../../../messages.js";
2
+ import { currentGuildChannel } from "../../../../conversation.js";
2
3
  import { defineAgentTool } from '@zhin.js/tool';
3
4
  import { z } from 'zod';
4
5
  import { requireDiscordGatewayClient } from '@zhin.js/adapter-discord';
5
- import { platformPermission } from '@zhin.js/permission';
6
6
  import { ThreadAutoArchiveDuration } from 'discord.js';
7
7
 
8
- export default defineAgentTool<{ channel_id: string; name: string; message_id?: string; auto_archive_duration?: number }>({
9
- description: '在 Discord 频道中创建帖子/子线程',
8
+ export default defineAgentTool<{ name: string; message_id?: string; auto_archive_duration?: number }>({
9
+ description: '在当前 Discord 频道中创建帖子/子线程',
10
10
  inputSchema: z.object({
11
- channel_id: z.string().describe('频道 ID'),
12
11
  name: z.string().describe('帖子标题'),
13
12
  message_id: z.string().optional().describe('基于某条消息创建(可选)'),
14
13
  auto_archive_duration: z.number().optional().describe('自动归档时间(分钟:60/1440/4320/10080)'),
@@ -16,8 +15,9 @@ export default defineAgentTool<{ channel_id: string; name: string; message_id?:
16
15
  adapter: 'discord',
17
16
  scopes: ["channel"],
18
17
  tags: ['discord'],
19
- permissions: [platformPermission('discord', 'manage_channels')],
20
- async execute({ channel_id, name, message_id, auto_archive_duration }: { channel_id: string; name: string; message_id?: string; auto_archive_duration?: number }, context) {
18
+ permissions: ['platform-permission:manage_channels', 'bot-permission:create_public_threads'],
19
+ async execute({ name, message_id, auto_archive_duration }, context) {
20
+ const { channelId: channel_id } = currentGuildChannel(context);
21
21
  const client = requireDiscordGatewayClient(context.$client);
22
22
  const channel = await client.channels.fetch(channel_id);
23
23
  if (!channel || !('threads' in channel) || channel.isThreadOnly()) throw new Error(t(context.locale, 'threadsUnsupported', { p0: channel_id }));
@@ -1,13 +1,13 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
2
  import { messages as t } from "../../../../messages.js";
3
+ import { currentChannel } from "../../../../conversation.js";
3
4
  import { defineAgentTool } from '@zhin.js/tool';
4
5
  import { z } from 'zod';
5
6
  import { requireDiscordGatewayClient } from '@zhin.js/adapter-discord';
6
7
  import { ChannelType } from 'discord.js';
7
8
  export default defineAgentTool({
8
- description: '在 Discord 论坛频道中创建帖子',
9
+ description: '在当前 Discord 论坛频道中创建帖子(需在该论坛的帖子里发起)',
9
10
  inputSchema: z.object({
10
- channel_id: z.string().describe('论坛频道 ID'),
11
11
  name: z.string().describe('帖子标题'),
12
12
  content: z.string().describe('帖子内容'),
13
13
  tags: z.string().optional().describe('标签名,逗号分隔(可选)'),
@@ -15,7 +15,11 @@ export default defineAgentTool({
15
15
  adapter: 'discord',
16
16
  scopes: ["channel"],
17
17
  tags: ['discord'],
18
- async execute({ channel_id, name, content, tags }, context) {
18
+ async execute({ name, content, tags }, context) {
19
+ // A forum post reports the forum as its conversation id and the post as `threadId`.
20
+ const { conversationChannelId: channel_id, guildId } = currentChannel(context);
21
+ if (!guildId)
22
+ throw new Error(t(context.locale, 'guildChannelRequired'));
19
23
  const client = requireDiscordGatewayClient(context.$client);
20
24
  const tagList = tags ? tags.split(',').map((t) => t.trim()) : undefined;
21
25
  const channel = await client.channels.fetch(channel_id);
@@ -1,13 +1,13 @@
1
1
  import { messages as t } from "../../../../messages.js";
2
+ import { currentChannel } from "../../../../conversation.js";
2
3
  import { defineAgentTool } from '@zhin.js/tool';
3
4
  import { z } from 'zod';
4
5
  import { requireDiscordGatewayClient } from '@zhin.js/adapter-discord';
5
6
  import { ChannelType } from 'discord.js';
6
7
 
7
- export default defineAgentTool<{ channel_id: string; name: string; content: string; tags?: string }>({
8
- description: '在 Discord 论坛频道中创建帖子',
8
+ export default defineAgentTool<{ name: string; content: string; tags?: string }>({
9
+ description: '在当前 Discord 论坛频道中创建帖子(需在该论坛的帖子里发起)',
9
10
  inputSchema: z.object({
10
- channel_id: z.string().describe('论坛频道 ID'),
11
11
  name: z.string().describe('帖子标题'),
12
12
  content: z.string().describe('帖子内容'),
13
13
  tags: z.string().optional().describe('标签名,逗号分隔(可选)'),
@@ -15,7 +15,10 @@ export default defineAgentTool<{ channel_id: string; name: string; content: stri
15
15
  adapter: 'discord',
16
16
  scopes: ["channel"],
17
17
  tags: ['discord'],
18
- async execute({ channel_id, name, content, tags }: { channel_id: string; name: string; content: string; tags?: string }, context) {
18
+ async execute({ name, content, tags }, context) {
19
+ // A forum post reports the forum as its conversation id and the post as `threadId`.
20
+ const { conversationChannelId: channel_id, guildId } = currentChannel(context);
21
+ if (!guildId) throw new Error(t(context.locale, 'guildChannelRequired'));
19
22
  const client = requireDiscordGatewayClient(context.$client);
20
23
  const tagList = tags ? tags.split(',').map((t: string) => t.trim()) : undefined;
21
24
  const channel = await client.channels.fetch(channel_id);
@@ -1,20 +1,19 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { currentGuildChannel } from '../../../../conversation.js';
2
3
  import { defineAgentTool } from '@zhin.js/tool';
3
4
  import { z } from 'zod';
4
5
  import { requireDiscordGatewayClient } from '@zhin.js/adapter-discord';
5
- import { platformPermission } from '@zhin.js/permission';
6
6
  export default defineAgentTool({
7
- description: '获取 Discord 服务器角色列表',
8
- inputSchema: z.object({
9
- guild_id: z.string().describe('服务器 ID'),
10
- }),
7
+ description: '获取当前 Discord 服务器的身份组列表',
8
+ inputSchema: z.object({}),
11
9
  adapter: 'discord',
12
10
  scopes: ["channel"],
13
11
  tags: ['discord'],
14
- permissions: [platformPermission('discord', 'manage_roles')],
15
- async execute({ guild_id }, context) {
12
+ permissions: ['platform-permission:manage_roles'],
13
+ async execute(_args, context) {
14
+ const { guildId } = currentGuildChannel(context);
16
15
  const client = requireDiscordGatewayClient(context.$client);
17
- const guild = await client.guilds.fetch(guild_id);
16
+ const guild = await client.guilds.fetch(guildId);
18
17
  await guild.roles.fetch();
19
18
  const cache = guild.roles.cache;
20
19
  const roles = [...cache.values()].map((role) => ({
@@ -1,20 +1,19 @@
1
+ import { currentGuildChannel } from '../../../../conversation.js';
1
2
  import { defineAgentTool } from '@zhin.js/tool';
2
3
  import { z } from 'zod';
3
4
  import { requireDiscordGatewayClient } from '@zhin.js/adapter-discord';
4
- import { platformPermission } from '@zhin.js/permission';
5
5
 
6
- export default defineAgentTool<{ guild_id: string }>({
7
- description: '获取 Discord 服务器角色列表',
8
- inputSchema: z.object({
9
- guild_id: z.string().describe('服务器 ID'),
10
- }),
6
+ export default defineAgentTool<Record<string, never>>({
7
+ description: '获取当前 Discord 服务器的身份组列表',
8
+ inputSchema: z.object({}),
11
9
  adapter: 'discord',
12
10
  scopes: ["channel"],
13
11
  tags: ['discord'],
14
- permissions: [platformPermission('discord', 'manage_roles')],
15
- async execute({ guild_id }: { guild_id: string }, context) {
12
+ permissions: ['platform-permission:manage_roles'],
13
+ async execute(_args, context) {
14
+ const { guildId } = currentGuildChannel(context);
16
15
  const client = requireDiscordGatewayClient(context.$client);
17
- const guild = await client.guilds.fetch(guild_id);
16
+ const guild = await client.guilds.fetch(guildId);
18
17
  await guild.roles.fetch();
19
18
  const cache = guild.roles.cache as Map<string, {
20
19
  id: string;
@@ -1,26 +1,28 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
2
  import { messages as t } from "../../../../messages.js";
3
+ import { currentGuildChannel, requireRoleBelowRequester } from "../../../../conversation.js";
3
4
  import { defineAgentTool } from '@zhin.js/tool';
4
5
  import { z } from 'zod';
5
6
  import { requireDiscordGatewayClient } from '@zhin.js/adapter-discord';
6
- import { platformPermission } from '@zhin.js/permission';
7
7
  export default defineAgentTool({
8
- description: '移除成员的 Discord 角色',
8
+ description: '移除当前 Discord 服务器成员的身份组(只能是低于你最高身份组的身份组)',
9
9
  inputSchema: z.object({
10
- guild_id: z.string().describe('服务器 ID'),
11
10
  user_id: z.string().describe('用户 ID'),
12
- role_id: z.string().describe('角色 ID'),
11
+ role_id: z.string().describe('身份组 ID'),
13
12
  }),
14
13
  adapter: 'discord',
15
14
  scopes: ["channel"],
16
15
  tags: ['discord'],
17
- permissions: [platformPermission('discord', 'manage_roles')],
18
- async execute({ guild_id, user_id, role_id }, context) {
16
+ permissions: ['platform-permission:manage_roles', 'bot-permission:manage_roles'],
17
+ async execute({ user_id, role_id }, context) {
18
+ const { guildId } = currentGuildChannel(context);
19
19
  const client = requireDiscordGatewayClient(context.$client);
20
- const guild = await client.guilds.fetch(guild_id);
20
+ const guild = await client.guilds.fetch(guildId);
21
+ const denied = await requireRoleBelowRequester(context, guild, role_id);
22
+ if (denied)
23
+ return { success: false, message: denied };
21
24
  const member = await guild.members.fetch(user_id);
22
25
  await member.roles.remove(role_id);
23
- const success = true;
24
- return { success, message: success ? t(context.locale, 'memberRoleRemoved', { p0: user_id }) : t(context.locale, 'operationFailed') };
26
+ return { success: true, message: t(context.locale, 'memberRoleRemoved', { p0: user_id }) };
25
27
  },
26
28
  });
@@ -1,28 +1,29 @@
1
1
  import { messages as t } from "../../../../messages.js";
2
+ import { currentGuildChannel, requireRoleBelowRequester } from "../../../../conversation.js";
2
3
  import { defineAgentTool } from '@zhin.js/tool';
3
4
  import { z } from 'zod';
4
5
  import { requireDiscordGatewayClient } from '@zhin.js/adapter-discord';
5
- import { platformPermission } from '@zhin.js/permission';
6
6
 
7
- export default defineAgentTool<{ guild_id: string; user_id: string; role_id: string }>({
8
- description: '移除成员的 Discord 角色',
7
+ export default defineAgentTool<{ user_id: string; role_id: string }>({
8
+ description: '移除当前 Discord 服务器成员的身份组(只能是低于你最高身份组的身份组)',
9
9
  inputSchema: z.object({
10
- guild_id: z.string().describe('服务器 ID'),
11
10
  user_id: z.string().describe('用户 ID'),
12
- role_id: z.string().describe('角色 ID'),
11
+ role_id: z.string().describe('身份组 ID'),
13
12
  }),
14
13
  adapter: 'discord',
15
14
  scopes: ["channel"],
16
15
  tags: ['discord'],
17
- permissions: [platformPermission('discord', 'manage_roles')],
18
- async execute({ guild_id, user_id, role_id }: { guild_id: string; user_id: string; role_id: string }, context) {
16
+ permissions: ['platform-permission:manage_roles', 'bot-permission:manage_roles'],
17
+ async execute({ user_id, role_id }, context) {
18
+ const { guildId } = currentGuildChannel(context);
19
19
  const client = requireDiscordGatewayClient(context.$client);
20
- const guild = await client.guilds.fetch(guild_id);
20
+ const guild = await client.guilds.fetch(guildId);
21
+ const denied = await requireRoleBelowRequester(context, guild, role_id);
22
+ if (denied) return { success: false, message: denied };
21
23
  const member = await guild.members.fetch(user_id) as {
22
24
  roles: { remove(id: string): Promise<unknown> };
23
25
  };
24
26
  await member.roles.remove(role_id);
25
- const success = true;
26
- return { success, message: success ? t(context.locale, 'memberRoleRemoved', { p0: user_id }) : t(context.locale, 'operationFailed') };
27
+ return { success: true, message: t(context.locale, 'memberRoleRemoved', { p0: user_id }) };
27
28
  },
28
29
  });
@@ -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;