From bc3930a2ceeec99a749e710a43c8f37484c6557b Mon Sep 17 00:00:00 2001 From: DrSmoothl <1787882683@qq.com> Date: Tue, 27 Oct 2026 14:58:54 +0800 Subject: [PATCH] feat(onebot): expose administrator permission actions Expose administrator permission controls through OneBot, the SDK, and MCP. Document limits and unknown states, and validate public request and result contracts. --- packages/mcp/src/generated/catalog.json | 299 +++++++++++++++++- packages/mcp/src/generated/catalog.ts | 299 +++++++++++++++++- packages/onebot/src/actions/group-admin.ts | 114 +++++++ .../onebot/tests/actions-group-admin.test.ts | 99 ++++++ packages/sdk/README.md | 37 +++ packages/sdk/src/actions.ts | 3 + packages/sdk/src/types/actions.ts | 7 + packages/sdk/src/types/params.ts | 30 ++ packages/sdk/src/types/results.ts | 10 + .../sdk/tests/fixtures/action-contracts.ts | 21 ++ packages/sdk/tests/fixtures/action-types.mts | 48 +++ 11 files changed, 965 insertions(+), 2 deletions(-) diff --git a/packages/mcp/src/generated/catalog.json b/packages/mcp/src/generated/catalog.json index 6b2f05cb..a404f3a2 100644 --- a/packages/mcp/src/generated/catalog.json +++ b/packages/mcp/src/generated/catalog.json @@ -6510,6 +6510,111 @@ }, "category": "扩展" }, + { + "name": "get_group_admin_permissions", + "aliases": [], + "summary": "读取指定管理员的撤回、移除成员及每日移除上限", + "returns": "服务器返回的管理员权限;未返回或未指定的开关为 null。每日上限仅在移除权限开启时返回,0 表示不限。", + "returnsSchema": { + "type": "object", + "properties": { + "group_id": { + "type": "integer" + }, + "user_id": { + "type": "integer" + }, + "recall_member_message": { + "type": [ + "boolean", + "null" + ], + "description": "撤回成员消息;null 表示服务器未指定" + }, + "kick_member": { + "type": [ + "boolean", + "null" + ], + "description": "移除群成员;null 表示服务器未指定" + }, + "kick_member_per_day": { + "type": [ + "integer", + "null" + ], + "description": "每日可移除人数,0 不限;权限关闭或未指定时为 null" + } + }, + "required": [ + "group_id", + "user_id", + "recall_member_message", + "kick_member", + "kick_member_per_day" + ], + "additionalProperties": false + }, + "examples": [ + { + "summary": "读取管理员权限", + "params": { + "group_id": 20001, + "user_id": 10001 + }, + "notes": "user_id 为目标管理员 QQ 号;不包含接收验证消息开关。" + } + ], + "readOnly": true, + "params": [ + { + "name": "group_id", + "type": "uint", + "required": true, + "role": "group_id", + "schema": { + "type": "integer", + "minimum": 1 + }, + "desc": "群号" + }, + { + "name": "user_id", + "type": "uint", + "required": true, + "role": "member_id", + "schema": { + "type": "integer", + "minimum": 1 + }, + "desc": "QQ 号" + } + ], + "invariants": [], + "inputSchema": { + "type": "object", + "properties": { + "group_id": { + "type": "integer", + "minimum": 1, + "description": "群号", + "x-role": "group_id" + }, + "user_id": { + "type": "integer", + "minimum": 1, + "description": "QQ 号", + "x-role": "member_id" + } + }, + "required": [ + "group_id", + "user_id" + ], + "additionalProperties": true + }, + "category": "群管理" + }, { "name": "get_group_admin_settings", "aliases": [], @@ -16515,6 +16620,198 @@ }, "category": "群管理" }, + { + "name": "set_group_admin_permissions", + "aliases": [], + "summary": "设置指定管理员的撤回和移除成员权限", + "returnsSchema": { + "type": "null", + "description": "服务器确认请求后 data 为 null;不代表已经过客户端回读验证。可用 get_group_admin_permissions 查询。" + }, + "examples": [ + { + "summary": "允许管理员每日移除至多 10 人", + "params": { + "group_id": 20001, + "user_id": 10001, + "kick_member": true, + "kick_member_per_day": 10 + }, + "notes": "需要当前账号具有修改权限;不会授予管理员角色。仅修改显式字段。开启移除必须提供上限,0 表示不限;修改上限需同时传 kick_member=true。" + } + ], + "readOnly": false, + "params": [ + { + "name": "group_id", + "type": "uint", + "required": true, + "role": "group_id", + "schema": { + "type": "integer", + "minimum": 1 + }, + "desc": "群号" + }, + { + "name": "user_id", + "type": "uint", + "required": true, + "role": "member_id", + "schema": { + "type": "integer", + "minimum": 1 + }, + "desc": "QQ 号" + }, + { + "name": "recall_member_message", + "type": "bool", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "kick_member", + "type": "bool", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "kick_member_per_day", + "type": "int", + "required": false, + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 200 + } + } + ], + "invariants": [ + "at least one of: recall_member_message, kick_member", + "kick_member=true requires kick_member_per_day: 0, 5, 10, 20, 50, 100 or 200", + "kick_member_per_day requires kick_member=true" + ], + "inputSchema": { + "type": "object", + "properties": { + "group_id": { + "type": "integer", + "minimum": 1, + "description": "群号", + "x-role": "group_id" + }, + "user_id": { + "type": "integer", + "minimum": 1, + "description": "QQ 号", + "x-role": "member_id" + }, + "recall_member_message": { + "type": "boolean" + }, + "kick_member": { + "type": "boolean" + }, + "kick_member_per_day": { + "type": "integer", + "minimum": 0, + "maximum": 200 + } + }, + "required": [ + "group_id", + "user_id" + ], + "additionalProperties": true + }, + "category": "群管理" + }, + { + "name": "set_group_admin_receive_verify", + "aliases": [], + "summary": "设置指定管理员是否接收加群验证消息", + "returnsSchema": { + "type": "null", + "description": "服务器响应确认目标群和管理员后 data 为 null;未确认目标时返回错误。" + }, + "examples": [ + { + "summary": "开启接收验证消息", + "params": { + "group_id": 20001, + "user_id": 10001, + "enable": true + }, + "notes": "user_id 为目标管理员 QQ 号;需要当前账号具有修改权限。独立于撤回和移除成员权限。" + } + ], + "readOnly": false, + "params": [ + { + "name": "group_id", + "type": "uint", + "required": true, + "role": "group_id", + "schema": { + "type": "integer", + "minimum": 1 + }, + "desc": "群号" + }, + { + "name": "user_id", + "type": "uint", + "required": true, + "role": "member_id", + "schema": { + "type": "integer", + "minimum": 1 + }, + "desc": "QQ 号" + }, + { + "name": "enable", + "type": "bool", + "required": true, + "schema": { + "type": "boolean" + } + } + ], + "invariants": [], + "inputSchema": { + "type": "object", + "properties": { + "group_id": { + "type": "integer", + "minimum": 1, + "description": "群号", + "x-role": "group_id" + }, + "user_id": { + "type": "integer", + "minimum": 1, + "description": "QQ 号", + "x-role": "member_id" + }, + "enable": { + "type": "boolean" + } + }, + "required": [ + "group_id", + "user_id", + "enable" + ], + "additionalProperties": true + }, + "category": "群管理" + }, { "name": "set_group_album_media_like", "aliases": [], @@ -20428,7 +20725,7 @@ }, { "category": "群管理", - "count": 21 + "count": 24 }, { "category": "群文件", diff --git a/packages/mcp/src/generated/catalog.ts b/packages/mcp/src/generated/catalog.ts index 7ca183d5..00a4e63f 100644 --- a/packages/mcp/src/generated/catalog.ts +++ b/packages/mcp/src/generated/catalog.ts @@ -6513,6 +6513,111 @@ export const ACTIONS: CatalogAction[] = [ }, "category": "扩展" }, + { + "name": "get_group_admin_permissions", + "aliases": [], + "summary": "读取指定管理员的撤回、移除成员及每日移除上限", + "returns": "服务器返回的管理员权限;未返回或未指定的开关为 null。每日上限仅在移除权限开启时返回,0 表示不限。", + "returnsSchema": { + "type": "object", + "properties": { + "group_id": { + "type": "integer" + }, + "user_id": { + "type": "integer" + }, + "recall_member_message": { + "type": [ + "boolean", + "null" + ], + "description": "撤回成员消息;null 表示服务器未指定" + }, + "kick_member": { + "type": [ + "boolean", + "null" + ], + "description": "移除群成员;null 表示服务器未指定" + }, + "kick_member_per_day": { + "type": [ + "integer", + "null" + ], + "description": "每日可移除人数,0 不限;权限关闭或未指定时为 null" + } + }, + "required": [ + "group_id", + "user_id", + "recall_member_message", + "kick_member", + "kick_member_per_day" + ], + "additionalProperties": false + }, + "examples": [ + { + "summary": "读取管理员权限", + "params": { + "group_id": 20001, + "user_id": 10001 + }, + "notes": "user_id 为目标管理员 QQ 号;不包含接收验证消息开关。" + } + ], + "readOnly": true, + "params": [ + { + "name": "group_id", + "type": "uint", + "required": true, + "role": "group_id", + "schema": { + "type": "integer", + "minimum": 1 + }, + "desc": "群号" + }, + { + "name": "user_id", + "type": "uint", + "required": true, + "role": "member_id", + "schema": { + "type": "integer", + "minimum": 1 + }, + "desc": "QQ 号" + } + ], + "invariants": [], + "inputSchema": { + "type": "object", + "properties": { + "group_id": { + "type": "integer", + "minimum": 1, + "description": "群号", + "x-role": "group_id" + }, + "user_id": { + "type": "integer", + "minimum": 1, + "description": "QQ 号", + "x-role": "member_id" + } + }, + "required": [ + "group_id", + "user_id" + ], + "additionalProperties": true + }, + "category": "群管理" + }, { "name": "get_group_admin_settings", "aliases": [], @@ -16518,6 +16623,198 @@ export const ACTIONS: CatalogAction[] = [ }, "category": "群管理" }, + { + "name": "set_group_admin_permissions", + "aliases": [], + "summary": "设置指定管理员的撤回和移除成员权限", + "returnsSchema": { + "type": "null", + "description": "服务器确认请求后 data 为 null;不代表已经过客户端回读验证。可用 get_group_admin_permissions 查询。" + }, + "examples": [ + { + "summary": "允许管理员每日移除至多 10 人", + "params": { + "group_id": 20001, + "user_id": 10001, + "kick_member": true, + "kick_member_per_day": 10 + }, + "notes": "需要当前账号具有修改权限;不会授予管理员角色。仅修改显式字段。开启移除必须提供上限,0 表示不限;修改上限需同时传 kick_member=true。" + } + ], + "readOnly": false, + "params": [ + { + "name": "group_id", + "type": "uint", + "required": true, + "role": "group_id", + "schema": { + "type": "integer", + "minimum": 1 + }, + "desc": "群号" + }, + { + "name": "user_id", + "type": "uint", + "required": true, + "role": "member_id", + "schema": { + "type": "integer", + "minimum": 1 + }, + "desc": "QQ 号" + }, + { + "name": "recall_member_message", + "type": "bool", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "kick_member", + "type": "bool", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "kick_member_per_day", + "type": "int", + "required": false, + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 200 + } + } + ], + "invariants": [ + "at least one of: recall_member_message, kick_member", + "kick_member=true requires kick_member_per_day: 0, 5, 10, 20, 50, 100 or 200", + "kick_member_per_day requires kick_member=true" + ], + "inputSchema": { + "type": "object", + "properties": { + "group_id": { + "type": "integer", + "minimum": 1, + "description": "群号", + "x-role": "group_id" + }, + "user_id": { + "type": "integer", + "minimum": 1, + "description": "QQ 号", + "x-role": "member_id" + }, + "recall_member_message": { + "type": "boolean" + }, + "kick_member": { + "type": "boolean" + }, + "kick_member_per_day": { + "type": "integer", + "minimum": 0, + "maximum": 200 + } + }, + "required": [ + "group_id", + "user_id" + ], + "additionalProperties": true + }, + "category": "群管理" + }, + { + "name": "set_group_admin_receive_verify", + "aliases": [], + "summary": "设置指定管理员是否接收加群验证消息", + "returnsSchema": { + "type": "null", + "description": "服务器响应确认目标群和管理员后 data 为 null;未确认目标时返回错误。" + }, + "examples": [ + { + "summary": "开启接收验证消息", + "params": { + "group_id": 20001, + "user_id": 10001, + "enable": true + }, + "notes": "user_id 为目标管理员 QQ 号;需要当前账号具有修改权限。独立于撤回和移除成员权限。" + } + ], + "readOnly": false, + "params": [ + { + "name": "group_id", + "type": "uint", + "required": true, + "role": "group_id", + "schema": { + "type": "integer", + "minimum": 1 + }, + "desc": "群号" + }, + { + "name": "user_id", + "type": "uint", + "required": true, + "role": "member_id", + "schema": { + "type": "integer", + "minimum": 1 + }, + "desc": "QQ 号" + }, + { + "name": "enable", + "type": "bool", + "required": true, + "schema": { + "type": "boolean" + } + } + ], + "invariants": [], + "inputSchema": { + "type": "object", + "properties": { + "group_id": { + "type": "integer", + "minimum": 1, + "description": "群号", + "x-role": "group_id" + }, + "user_id": { + "type": "integer", + "minimum": 1, + "description": "QQ 号", + "x-role": "member_id" + }, + "enable": { + "type": "boolean" + } + }, + "required": [ + "group_id", + "user_id", + "enable" + ], + "additionalProperties": true + }, + "category": "群管理" + }, { "name": "set_group_album_media_like", "aliases": [], @@ -20432,7 +20729,7 @@ export const CATEGORIES: CatalogCategory[] = [ }, { "category": "群管理", - "count": 21 + "count": 24 }, { "category": "群文件", diff --git a/packages/onebot/src/actions/group-admin.ts b/packages/onebot/src/actions/group-admin.ts index 0df16001..aa8b210a 100644 --- a/packages/onebot/src/actions/group-admin.ts +++ b/packages/onebot/src/actions/group-admin.ts @@ -466,6 +466,120 @@ export const actions = [ }, }), + groupUserAction({ + name: 'get_group_admin_permissions', + summary: '读取指定管理员的撤回、移除成员及每日移除上限', + readOnly: true, + examples: [ + { + summary: '读取管理员权限', + params: { group_id: 20001, user_id: 10001 }, + notes: 'user_id 为目标管理员 QQ 号;不包含接收验证消息开关。', + }, + ], + returns: + '服务器返回的管理员权限;未返回或未指定的开关为 null。每日上限仅在移除权限开启时返回,0 表示不限。', + returnsSchema: { + type: 'object', + properties: { + group_id: { type: 'integer' }, + user_id: { type: 'integer' }, + recall_member_message: { + type: ['boolean', 'null'], + description: '撤回成员消息;null 表示服务器未指定', + }, + kick_member: { + type: ['boolean', 'null'], + description: '移除群成员;null 表示服务器未指定', + }, + kick_member_per_day: { + type: ['integer', 'null'], + description: '每日可移除人数,0 不限;权限关闭或未指定时为 null', + }, + }, + required: [ + 'group_id', + 'user_id', + 'recall_member_message', + 'kick_member', + 'kick_member_per_day', + ], + additionalProperties: false, + }, + run: async (p, ctx) => + okResponse(await ctx.bridge.apis.groupAdmin.getAdminPermissions(p.group_id, p.user_id)), + }), + + groupUserAction({ + name: 'set_group_admin_permissions', + summary: '设置指定管理员的撤回和移除成员权限', + examples: [ + { + summary: '允许管理员每日移除至多 10 人', + params: { + group_id: 20001, + user_id: 10001, + kick_member: true, + kick_member_per_day: 10, + }, + notes: '需要当前账号具有修改权限;不会授予管理员角色。仅修改显式字段。开启移除必须提供上限,0 表示不限;修改上限需同时传 kick_member=true。', + }, + ], + returnsSchema: { + type: 'null', + description: + '服务器确认请求后 data 为 null;不代表已经过客户端回读验证。可用 get_group_admin_permissions 查询。', + }, + params: { + recall_member_message: f.bool().optional(), + kick_member: f.bool().optional(), + kick_member_per_day: f.int({ min: 0, max: 200 }).optional(), + }, + rules: (r) => [ + r.atLeastOneOf('recall_member_message', 'kick_member'), + r.rule( + 'kick_member=true requires kick_member_per_day: 0, 5, 10, 20, 50, 100 or 200', + (p) => + p.kick_member !== true || + (p.kick_member_per_day !== undefined && + [0, 5, 10, 20, 50, 100, 200].includes(p.kick_member_per_day)), + ), + r.rule( + 'kick_member_per_day requires kick_member=true', + (p) => p.kick_member_per_day === undefined || p.kick_member === true, + ), + ], + run: async (p, ctx) => { + await ctx.bridge.apis.groupAdmin.setAdminPermissions(p.group_id, p.user_id, { + recallMemberMessage: p.recall_member_message, + kickMember: p.kick_member, + kickMemberPerDay: p.kick_member_per_day, + }); + return okResponse(); + }, + }), + + groupUserAction({ + name: 'set_group_admin_receive_verify', + summary: '设置指定管理员是否接收加群验证消息', + examples: [ + { + summary: '开启接收验证消息', + params: { group_id: 20001, user_id: 10001, enable: true }, + notes: 'user_id 为目标管理员 QQ 号;需要当前账号具有修改权限。独立于撤回和移除成员权限。', + }, + ], + returnsSchema: { + type: 'null', + description: '服务器响应确认目标群和管理员后 data 为 null;未确认目标时返回错误。', + }, + params: { enable: f.bool() }, + run: async (p, ctx) => { + await ctx.bridge.apis.groupAdmin.setAdminReceiveVerify(p.group_id, p.user_id, p.enable); + return okResponse(); + }, + }), + groupUserAction({ name: 'set_group_card', examples: [ diff --git a/packages/onebot/tests/actions-group-admin.test.ts b/packages/onebot/tests/actions-group-admin.test.ts index 50dfa5d7..d78f7c40 100644 --- a/packages/onebot/tests/actions-group-admin.test.ts +++ b/packages/onebot/tests/actions-group-admin.test.ts @@ -48,6 +48,9 @@ const getGroupTags = requireAction('get_group_tags'); const addGroupTag = requireAction('add_group_tag'); const deleteGroupTag = requireAction('delete_group_tag'); const setGroupAdmin = requireAction('set_group_admin'); +const getAdminPermissions = requireAction('get_group_admin_permissions'); +const setAdminPermissions = requireAction('set_group_admin_permissions'); +const setAdminReceiveVerify = requireAction('set_group_admin_receive_verify'); const setGroupCard = requireAction('set_group_card'); const setGroupName = requireAction('set_group_name'); const setGroupLeave = requireAction('set_group_leave'); @@ -73,6 +76,9 @@ describe('group admin actions catalog', () => { ['add_group_tag'], ['delete_group_tag'], ['set_group_admin'], + ['get_group_admin_permissions'], + ['set_group_admin_permissions'], + ['set_group_admin_receive_verify'], ['set_group_card'], ['set_group_name'], ['set_group_leave'], @@ -103,6 +109,9 @@ describe('group admin actions catalog', () => { 'normal', 'normal', 'normal', + 'normal', + 'normal', + 'normal', ]); }); }); @@ -983,6 +992,96 @@ describe('set_group_member_permissions describe', () => { }); }); +describe('per-administrator permissions', () => { + const target = { group_id: 12345, user_id: 67890 }; + + it.each([ + {}, + { kick_member_per_day: 20 }, + { kick_member: true }, + { kick_member: false, kick_member_per_day: 20 }, + { kick_member: true, kick_member_per_day: 1 }, + { kick_member: true, kick_member_per_day: 201 }, + ])('rejects an ambiguous or invalid patch before effects: %j', async (patch) => { + const set = vi.fn(); + const { ctx } = adminCtx({ setAdminPermissions: set }); + expect(setAdminPermissions.parse({ ...target, ...patch }).ok).toBe(false); + await expect( + setAdminPermissions.toHandler(ctx)({ ...target, ...patch }), + ).resolves.toMatchObject({ status: 'failed' }); + expect(set).not.toHaveBeenCalled(); + }); + + it.each([0, 5, 10, 20, 50, 100, 200])( + 'accepts a supported explicit daily limit %s', + (limit) => { + expect( + setAdminPermissions.parse({ + ...target, + kick_member: 'yes', + kick_member_per_day: String(limit), + }), + ).toMatchObject({ + ok: true, + value: { ...target, kick_member: true, kick_member_per_day: limit }, + }); + }, + ); + + it('forwards false and preserves omitted permissions without promoting the member', async () => { + const set = vi.fn(async () => {}); + const promote = vi.fn(); + const { ctx } = adminCtx({ setAdminPermissions: set, setAdmin: promote }); + await expect( + setAdminPermissions.toHandler(ctx)({ ...target, recall_member_message: 'no' }), + ).resolves.toEqual({ status: 'ok', retcode: 0, data: null }); + expect(set).toHaveBeenCalledExactlyOnceWith(12345, 67890, { + recallMemberMessage: false, + kickMember: undefined, + kickMemberPerDay: undefined, + }); + expect(promote).not.toHaveBeenCalled(); + }); + + it('requires an explicit receive-verification switch and forwards false', async () => { + expect(setAdminReceiveVerify.parse(target)).toMatchObject({ ok: false, field: 'enable' }); + const set = vi.fn(async () => {}); + const { ctx } = adminCtx({ setAdminReceiveVerify: set }); + await expect( + setAdminReceiveVerify.toHandler(ctx)({ ...target, enable: 'off' }), + ).resolves.toEqual({ status: 'ok', retcode: 0, data: null }); + expect(set).toHaveBeenCalledExactlyOnceWith(12345, 67890, false); + }); + + it('returns nullable permissions without defaulting them to true', async () => { + const snapshot = { + ...target, + recall_member_message: null, + kick_member: true, + kick_member_per_day: 0, + }; + const get = vi.fn(async () => snapshot); + const { ctx } = adminCtx({ getAdminPermissions: get }); + await expect(getAdminPermissions.toHandler(ctx)(target)).resolves.toEqual({ + status: 'ok', + retcode: 0, + data: snapshot, + }); + expect(get).toHaveBeenCalledExactlyOnceWith(12345, 67890); + expect(getAdminPermissions.describe().readOnly).toBe(true); + }); + + it('propagates a rejected or unconfirmed update', async () => { + const set = vi.fn(async () => { + throw new Error('target not confirmed'); + }); + const { ctx } = adminCtx({ setAdminReceiveVerify: set }); + await expect( + setAdminReceiveVerify.toHandler(ctx)({ ...target, enable: true }), + ).rejects.toThrow('target not confirmed'); + }); +}); + describe('set_group_admin parse', () => { it('defaults enable to true', () => { expect(setGroupAdmin.parse({ group_id: 314159, user_id: 271828 })).toEqual({ diff --git a/packages/sdk/README.md b/packages/sdk/README.md index 5539ce0f..54a12b36 100644 --- a/packages/sdk/README.md +++ b/packages/sdk/README.md @@ -227,6 +227,43 @@ if (feed) { 列表续页必须原样传回 `next_cursor`,根据 `has_more` 判断结束,不能因本页不足 10 条而停止。详情中的首批评论位于 `feed.feed.cell_comment.comments`;后续评论使用详情或上一评论页的 `attach_info` 作为 `cursor`。读取回复时,首次可省略 `cursor` 使用完整评论自带的 `reply_attach_info`,之后传上一回复页的 `reply_attach_info`。这些游标各有用途,列表游标还绑定当前账号。登录或访问权限失败会抛出 `SnowLumaApiError`。 +### 单个管理员权限 + +`set_group_admin` 用于授予或取消管理员身份。对已有管理员,可通过以下动作读取或修改权限;调用账号需要具有 QQ 服务端要求的管理权限。 + +| 动作 | 参数与结果 | +| --- | --- | +| `get_group_admin_permissions` | `group_id`、`user_id`;返回撤回权限、移除权限和每日移除上限 | +| `set_group_admin_permissions` | `group_id`、`user_id`,以及至少一个 `recall_member_message` / `kick_member` | +| `set_group_admin_receive_verify` | `group_id`、`user_id`、必填 `enable`;独立设置接收加群验证消息 | + +```ts +await bot.raw('set_group_admin_permissions', { + group_id: 123456, + user_id: 10001, + recall_member_message: false, + kick_member: true, + kick_member_per_day: 20, +}); + +const permissions = await bot.raw('get_group_admin_permissions', { + group_id: 123456, + user_id: 10001, +}); + +await bot.raw('set_group_admin_receive_verify', { + group_id: 123456, + user_id: 10001, + enable: false, +}); +``` + +只修改显式提供的权限。开启移除权限必须明确提供 `kick_member_per_day`,支持 `0`、`5`、`10`、`20`、`50`、`100`、`200`,其中 `0` 表示不限。修改上限也须同时传 `kick_member: true`;关闭或省略移除权限时不能提供上限。SDK 的 `kick_member` 使用布尔值,以便类型检查约束上限。 + +读取结果包含 `group_id`、`user_id`、`recall_member_message`、`kick_member`、`kick_member_per_day`。服务器未指定的开关返回 `null`;移除权限关闭或未指定时,上限也为 `null`。当前读取接口不返回接收验证消息状态。 + +两个写动作分别发送一次请求,不自动重试或授予管理员身份。权限写入成功表示服务器确认请求;接收验证消息写入还会核对响应中的群号和成功管理员列表。两者均返回 `null`。完整的客户端显示和实际权限效果仍需真实 QQ 验证,不能以接口成功替代验收。 + ## Message Builder ```ts diff --git a/packages/sdk/src/actions.ts b/packages/sdk/src/actions.ts index c1006d0b..4727fdc4 100644 --- a/packages/sdk/src/actions.ts +++ b/packages/sdk/src/actions.ts @@ -29,6 +29,9 @@ export const SNOWLUMA_ACTIONS = [ 'set_group_add_option', 'set_group_search', 'set_group_admin', + 'get_group_admin_permissions', + 'set_group_admin_permissions', + 'set_group_admin_receive_verify', 'set_group_card', 'set_group_name', 'set_group_leave', diff --git a/packages/sdk/src/types/actions.ts b/packages/sdk/src/types/actions.ts index 4656bd01..3d6e5c38 100644 --- a/packages/sdk/src/types/actions.ts +++ b/packages/sdk/src/types/actions.ts @@ -53,6 +53,9 @@ import type { SetGroupAddRequestParams, SetGroupAddOptionParams, SetGroupAdminParams, + GroupAdminTargetParams, + SetGroupAdminPermissionsParams, + SetGroupAdminReceiveVerifyParams, SetGroupBanParams, SetGroupCardParams, SetGroupKickParams, @@ -145,6 +148,7 @@ import type { FriendCategoryResult, FriendMessageHistory, GroupAdminSettings, + GroupAdminPermissions, GroupCategoryTagList, GroupAtAllRemainInfo, GroupFileSystemInfo, @@ -242,6 +246,9 @@ export interface SnowLumaActionMap { add_group_tag: { params: GroupCategoryTagNameParams; data: EmptyData }; delete_group_tag: { params: GroupCategoryTagNameParams; data: EmptyData }; set_group_admin: { params: SetGroupAdminParams; data: EmptyData }; + get_group_admin_permissions: { params: GroupAdminTargetParams; data: GroupAdminPermissions }; + set_group_admin_permissions: { params: SetGroupAdminPermissionsParams; data: EmptyData }; + set_group_admin_receive_verify: { params: SetGroupAdminReceiveVerifyParams; data: EmptyData }; set_group_card: { params: SetGroupCardParams; data: EmptyData }; set_group_name: { params: SetGroupNameParams; data: EmptyData }; set_group_leave: { params: GroupIdParams; data: EmptyData }; diff --git a/packages/sdk/src/types/params.ts b/packages/sdk/src/types/params.ts index 6a5e7559..96a9b74d 100644 --- a/packages/sdk/src/types/params.ts +++ b/packages/sdk/src/types/params.ts @@ -508,6 +508,36 @@ export interface SetGroupNewMemberHistoryVisibilityParams extends ActionGroupIdP visible: ActionBooleanInput; } +export interface GroupAdminTargetParams extends ActionGroupIdParams { + user_id: ActionNumberInput; +} + +export type GroupAdminKickLimit = 0 | 5 | 10 | 20 | 50 | 100 | 200; + +/** Only supplied permissions change. Enabling removal requires an explicit limit. */ +export type SetGroupAdminPermissionsParams = GroupAdminTargetParams & + ( + | { + recall_member_message: ActionBooleanInput; + kick_member?: never; + kick_member_per_day?: never; + } + | { + recall_member_message?: ActionBooleanInput; + kick_member: false; + kick_member_per_day?: never; + } + | { + recall_member_message?: ActionBooleanInput; + kick_member: true; + kick_member_per_day: GroupAdminKickLimit | `${GroupAdminKickLimit}`; + } + ); + +export interface SetGroupAdminReceiveVerifyParams extends GroupAdminTargetParams { + enable: ActionBooleanInput; +} + export type SetGroupMemberPermissionsParams = ActionGroupIdParams & ( | { diff --git a/packages/sdk/src/types/results.ts b/packages/sdk/src/types/results.ts index f31dd2ba..51e6c463 100644 --- a/packages/sdk/src/types/results.ts +++ b/packages/sdk/src/types/results.ts @@ -270,6 +270,16 @@ export type GroupAdminSettings = { no_code_finger_open: number; }; +export interface GroupAdminPermissions { + group_id: number; + user_id: number; + /** Null means the server did not specify this permission. */ + recall_member_message: boolean | null; + kick_member: boolean | null; + /** Zero means unlimited; null means removal is disabled or unspecified. */ + kick_member_per_day: number | null; +} + export interface GroupTodoListItem { message_id: number; message_seq: number; diff --git a/packages/sdk/tests/fixtures/action-contracts.ts b/packages/sdk/tests/fixtures/action-contracts.ts index 4ed3ff5a..50418201 100644 --- a/packages/sdk/tests/fixtures/action-contracts.ts +++ b/packages/sdk/tests/fixtures/action-contracts.ts @@ -30,6 +30,15 @@ const qzonePublicReply = { // The same examples are compiled against the published declarations and parsed // by the real server action definitions. They never invoke QQ or perform I/O. export const actionParams = { + get_group_admin_permissions: { group_id: '10001', user_id: '10002' }, + set_group_admin_permissions: { + group_id: '10001', + user_id: '10002', + recall_member_message: false, + kick_member: true, + kick_member_per_day: '20', + }, + set_group_admin_receive_verify: { group_id: '10001', user_id: '10002', enable: 'on' }, _get_friend_dress: { user_id: '10002' }, add_custom_face: { file: 'file:///fixture.png' }, cancel_group_album_media_like: mediaLike, @@ -180,6 +189,9 @@ export const actionParams = { // Keep this independent of SnowLumaActionMap: a missing map entry must fail the // consumer's coverage check rather than silently remove that example. export type CoveredAction = + | 'get_group_admin_permissions' + | 'set_group_admin_permissions' + | 'set_group_admin_receive_verify' | '_get_friend_dress' | 'add_custom_face' | 'cancel_group_album_media_like' @@ -268,6 +280,15 @@ const flashInfo = { }; export const actionResults = { + get_group_admin_permissions: { + group_id: 10001, + user_id: 10002, + recall_member_message: null, + kick_member: true, + kick_member_per_day: 20, + }, + set_group_admin_permissions: null, + set_group_admin_receive_verify: null, _get_friend_dress: { target_uin: '10002', is_svip: true, diff --git a/packages/sdk/tests/fixtures/action-types.mts b/packages/sdk/tests/fixtures/action-types.mts index fb7e254e..ed617272 100644 --- a/packages/sdk/tests/fixtures/action-types.mts +++ b/packages/sdk/tests/fixtures/action-types.mts @@ -43,6 +43,54 @@ export type QuickAlias = AssertTrue< declare const http: SnowLumaHttpClient; declare const ws: SnowLumaWebSocketClient; +const adminPermissions = await http.raw('get_group_admin_permissions', { + group_id: 10001, + user_id: 10002, +}); +export const adminRecall: boolean | null = adminPermissions.recall_member_message; +export const adminKickLimit: number | null = adminPermissions.kick_member_per_day; +await http.raw('set_group_admin_permissions', { + group_id: 10001, + user_id: 10002, + recall_member_message: false, +}); +await http.raw('set_group_admin_permissions', { + group_id: 10001, + user_id: 10002, + kick_member: false, +}); +await http.raw('set_group_admin_permissions', { + group_id: 10001, + user_id: 10002, + kick_member: true, + kick_member_per_day: 0, +}); +await http.raw('set_group_admin_receive_verify', { + group_id: 10001, + user_id: 10002, + enable: false, +}); +// @ts-expect-error a patch must change a permission +http.raw('set_group_admin_permissions', { group_id: 10001, user_id: 10002 }); +// @ts-expect-error enabling removal requires an explicit limit +http.raw('set_group_admin_permissions', { group_id: 10001, user_id: 10002, kick_member: true }); +http.raw('set_group_admin_permissions', { + group_id: 10001, + user_id: 10002, + kick_member: false, + // @ts-expect-error a limit without enabling removal is invalid + kick_member_per_day: 20, +}); +http.raw('set_group_admin_permissions', { + group_id: 10001, + user_id: 10002, + kick_member: true, + // @ts-expect-error only supported limits may be written + kick_member_per_day: 1, +}); +// @ts-expect-error receive-verification enable is required +http.raw('set_group_admin_receive_verify', { group_id: 10001, user_id: 10002 }); + export const uploadedFile: Promise = http.uploadFileStream( new Blob(['file']), );