From 8b8a9df89476bd0c7fa4383ede1773dcc3b249e3 Mon Sep 17 00:00:00 2001 From: DrSmoothl <1787882683@qq.com> Date: Tue, 27 Oct 2026 15:13:17 +0800 Subject: [PATCH] feat(onebot): read administrator verification message preference Include verification message reception in administrator permission results. Preserve unavailable states and surface failed reads. Update SDK contracts, action metadata, documentation and isolated behavior coverage. --- packages/core/src/bridge/apis/group-admin.ts | 10 +- .../core/tests/actions/group-admin.test.ts | 115 ++++++++++--- packages/mcp/src/generated/catalog.json | 14 +- packages/mcp/src/generated/catalog.ts | 14 +- packages/onebot/src/actions/group-admin.ts | 9 +- .../onebot/tests/actions-group-admin.test.ts | 38 +++-- .../oidb-actions/group-admin-permissions.ts | 27 ++++ .../group-admin/get-admin-receive-verify.ts | 72 +++++++++ .../get-admin-receive-verify.test.ts | 152 ++++++++++++++++++ packages/sdk/README.md | 4 +- packages/sdk/src/types/results.ts | 2 + .../sdk/tests/fixtures/action-contracts.ts | 1 + packages/sdk/tests/fixtures/action-types.mts | 1 + 13 files changed, 412 insertions(+), 47 deletions(-) create mode 100644 packages/protocol/src/oidb-services/group-admin/get-admin-receive-verify.ts create mode 100644 packages/protocol/tests/oidb-services/group-admin/get-admin-receive-verify.test.ts diff --git a/packages/core/src/bridge/apis/group-admin.ts b/packages/core/src/bridge/apis/group-admin.ts index b29b38f6..bff70a48 100644 --- a/packages/core/src/bridge/apis/group-admin.ts +++ b/packages/core/src/bridge/apis/group-admin.ts @@ -9,6 +9,7 @@ import { SetAddOption } from '@snowluma/protocol/oidb-services/group-admin/set-a import { SetAddRequest } from '@snowluma/protocol/oidb-services/group-admin/set-add-request'; import { SetAdmin } from '@snowluma/protocol/oidb-services/group-admin/set-admin'; import { GetAdminPermissions } from '@snowluma/protocol/oidb-services/group-admin/get-admin-permissions'; +import { GetAdminReceiveVerify } from '@snowluma/protocol/oidb-services/group-admin/get-admin-receive-verify'; import { SetAdminPermissions, type GroupAdminPermissionPatch, @@ -355,14 +356,18 @@ export class GroupAdminApi { async getAdminPermissions(groupId: number, userId: number) { const logger = log.child({ uin: Number(this.ctx.identity.uin) }); logger.debug('Reading administrator permissions group=%s user=%s', groupId, userId); - const snapshot = await GetAdminPermissions.invoke(this.ctx, { groupId, userId }); + const [snapshot, receiveVerify] = await Promise.all([ + GetAdminPermissions.invoke(this.ctx, { groupId, userId }), + GetAdminReceiveVerify.invoke(this.ctx, { groupId, userId }), + ]); logger.debug( - 'Administrator permissions received group=%s user=%s recall=%s kick=%s limit=%s', + 'Administrator permissions received group=%s user=%s recall=%s kick=%s limit=%s receiveVerify=%s', groupId, userId, snapshot.recallMemberMessage, snapshot.kickMember, snapshot.kickMemberPerDay, + receiveVerify, ); return { group_id: groupId, @@ -370,6 +375,7 @@ export class GroupAdminApi { recall_member_message: snapshot.recallMemberMessage, kick_member: snapshot.kickMember, kick_member_per_day: snapshot.kickMemberPerDay, + receive_verify: receiveVerify, }; } diff --git a/packages/core/tests/actions/group-admin.test.ts b/packages/core/tests/actions/group-admin.test.ts index 4fd12799..5c45caf4 100644 --- a/packages/core/tests/actions/group-admin.test.ts +++ b/packages/core/tests/actions/group-admin.test.ts @@ -4,6 +4,7 @@ import type { OidbBase, OidbSvcTrpcTcp0x88D_0Response } from '@snowluma/proto-de import type { OidbGetGroupExtResp } from '@snowluma/proto-defs/oidb-actions/group-ext'; import type { GetGroupAdminPermissionsResponse, + GetGroupAdminReceiveVerifyResponse, SetGroupAdminPermissionsRequest, SetGroupAdminReceiveVerifyRequest, SetGroupAdminReceiveVerifyResponse, @@ -120,34 +121,112 @@ function packRobotExt(robotMemberSwitch?: number, robotMemberExamine?: number) { } describe('apis/group-admin', () => { - it('maps the administrator snapshot without inventing missing permissions', async () => { + it.each([ + [0, true], + [64, false], + [undefined, null], + ] as const)( + 'maps receive-verification state %s without inventing missing permissions', + async (flag, enabled) => { + const bridge = mockBridge(); + bridge.sendRawPacket.mockResolvedValueOnce( + packResponse( + protobuf_encode>({ + body: { + groupCode: 12345n, + members: [ + { + memberUin: 67890n, + ext: { permissions: { items: [{ item: 1, value: 2 }] } }, + }, + ], + }, + }), + ), + ); + bridge.sendRawPacket.mockResolvedValueOnce( + packResponse( + protobuf_encode>({ + body: { + groupCode: 12345n, + members: [ + { + identity: { uid: 'resolved-uid', uin: 67890n }, + memberFlagExt: flag, + }, + ], + }, + }), + ), + ); + await expect( + new GroupAdminApi(bridge as unknown as BridgeContext).getAdminPermissions( + 12345, + 67890, + ), + ).resolves.toEqual({ + group_id: 12345, + user_id: 67890, + recall_member_message: null, + kick_member: false, + kick_member_per_day: null, + receive_verify: enabled, + }); + expect(bridge.sendRawPacket).toHaveBeenCalledTimes(2); + expect(bridge.resolveUserUid).toHaveBeenCalledExactlyOnceWith(67890, 12345); + }, + ); + + it.each(['OidbSvcTrpcTcp.0x9164_2', 'OidbSvcTrpcTcp.0xfe7_4'])( + 'rejects the combined snapshot when %s fails', + async (failedCommand) => { + const bridge = mockBridge(); + bridge.sendRawPacket.mockImplementation(async (command: string) => { + if (command === failedCommand) { + return packOidbFail(1287); + } + + if (command === 'OidbSvcTrpcTcp.0x9164_2') { + return packResponse( + protobuf_encode>({ + body: { groupCode: 12345n, members: [{ memberUin: 67890n }] }, + }), + ); + } + + return packResponse( + protobuf_encode>({ + body: { + groupCode: 12345n, + members: [{ identity: { uid: 'resolved-uid' }, memberFlagExt: 0 }], + }, + }), + ); + }); + await expect( + new GroupAdminApi(bridge as unknown as BridgeContext).getAdminPermissions( + 12345, + 67890, + ), + ).rejects.toThrow('OIDB error 1287'); + expect(bridge.sendRawPacket).toHaveBeenCalledTimes(2); + }, + ); + + it('propagates identity resolution failures when reading administrator permissions', async () => { const bridge = mockBridge(); + bridge.resolveUserUid.mockRejectedValueOnce(new Error('identity unavailable')); bridge.sendRawPacket.mockResolvedValueOnce( packResponse( protobuf_encode>({ - body: { - groupCode: 12345n, - members: [ - { - memberUin: 67890n, - ext: { permissions: { items: [{ item: 1, value: 2 }] } }, - }, - ], - }, + body: { groupCode: 12345n, members: [{ memberUin: 67890n }] }, }), ), ); await expect( new GroupAdminApi(bridge as unknown as BridgeContext).getAdminPermissions(12345, 67890), - ).resolves.toEqual({ - group_id: 12345, - user_id: 67890, - recall_member_message: null, - kick_member: false, - kick_member_per_day: null, - }); + ).rejects.toThrow('identity unavailable'); expect(bridge.sendRawPacket).toHaveBeenCalledTimes(1); - expect(bridge.resolveUserUid).not.toHaveBeenCalled(); }); it('writes administrator capabilities and receive-verification through separate services', async () => { diff --git a/packages/mcp/src/generated/catalog.json b/packages/mcp/src/generated/catalog.json index a404f3a2..abf58e1c 100644 --- a/packages/mcp/src/generated/catalog.json +++ b/packages/mcp/src/generated/catalog.json @@ -6513,7 +6513,7 @@ { "name": "get_group_admin_permissions", "aliases": [], - "summary": "读取指定管理员的撤回、移除成员及每日移除上限", + "summary": "读取指定管理员的撤回、移除成员、每日移除上限及接收验证消息状态", "returns": "服务器返回的管理员权限;未返回或未指定的开关为 null。每日上限仅在移除权限开启时返回,0 表示不限。", "returnsSchema": { "type": "object", @@ -6544,6 +6544,13 @@ "null" ], "description": "每日可移除人数,0 不限;权限关闭或未指定时为 null" + }, + "receive_verify": { + "type": [ + "boolean", + "null" + ], + "description": "是否接收加群验证消息;null 表示服务器未返回状态" } }, "required": [ @@ -6551,7 +6558,8 @@ "user_id", "recall_member_message", "kick_member", - "kick_member_per_day" + "kick_member_per_day", + "receive_verify" ], "additionalProperties": false }, @@ -6562,7 +6570,7 @@ "group_id": 20001, "user_id": 10001 }, - "notes": "user_id 为目标管理员 QQ 号;不包含接收验证消息开关。" + "notes": "user_id 为目标管理员 QQ 号;包含接收加群验证消息状态。" } ], "readOnly": true, diff --git a/packages/mcp/src/generated/catalog.ts b/packages/mcp/src/generated/catalog.ts index 00a4e63f..0c23e4c4 100644 --- a/packages/mcp/src/generated/catalog.ts +++ b/packages/mcp/src/generated/catalog.ts @@ -6516,7 +6516,7 @@ export const ACTIONS: CatalogAction[] = [ { "name": "get_group_admin_permissions", "aliases": [], - "summary": "读取指定管理员的撤回、移除成员及每日移除上限", + "summary": "读取指定管理员的撤回、移除成员、每日移除上限及接收验证消息状态", "returns": "服务器返回的管理员权限;未返回或未指定的开关为 null。每日上限仅在移除权限开启时返回,0 表示不限。", "returnsSchema": { "type": "object", @@ -6547,6 +6547,13 @@ export const ACTIONS: CatalogAction[] = [ "null" ], "description": "每日可移除人数,0 不限;权限关闭或未指定时为 null" + }, + "receive_verify": { + "type": [ + "boolean", + "null" + ], + "description": "是否接收加群验证消息;null 表示服务器未返回状态" } }, "required": [ @@ -6554,7 +6561,8 @@ export const ACTIONS: CatalogAction[] = [ "user_id", "recall_member_message", "kick_member", - "kick_member_per_day" + "kick_member_per_day", + "receive_verify" ], "additionalProperties": false }, @@ -6565,7 +6573,7 @@ export const ACTIONS: CatalogAction[] = [ "group_id": 20001, "user_id": 10001 }, - "notes": "user_id 为目标管理员 QQ 号;不包含接收验证消息开关。" + "notes": "user_id 为目标管理员 QQ 号;包含接收加群验证消息状态。" } ], "readOnly": true, diff --git a/packages/onebot/src/actions/group-admin.ts b/packages/onebot/src/actions/group-admin.ts index aa8b210a..27d0e680 100644 --- a/packages/onebot/src/actions/group-admin.ts +++ b/packages/onebot/src/actions/group-admin.ts @@ -468,13 +468,13 @@ export const actions = [ groupUserAction({ name: 'get_group_admin_permissions', - summary: '读取指定管理员的撤回、移除成员及每日移除上限', + summary: '读取指定管理员的撤回、移除成员、每日移除上限及接收验证消息状态', readOnly: true, examples: [ { summary: '读取管理员权限', params: { group_id: 20001, user_id: 10001 }, - notes: 'user_id 为目标管理员 QQ 号;不包含接收验证消息开关。', + notes: 'user_id 为目标管理员 QQ 号;包含接收加群验证消息状态。', }, ], returns: @@ -496,6 +496,10 @@ export const actions = [ type: ['integer', 'null'], description: '每日可移除人数,0 不限;权限关闭或未指定时为 null', }, + receive_verify: { + type: ['boolean', 'null'], + description: '是否接收加群验证消息;null 表示服务器未返回状态', + }, }, required: [ 'group_id', @@ -503,6 +507,7 @@ export const actions = [ 'recall_member_message', 'kick_member', 'kick_member_per_day', + 'receive_verify', ], additionalProperties: false, }, diff --git a/packages/onebot/tests/actions-group-admin.test.ts b/packages/onebot/tests/actions-group-admin.test.ts index d78f7c40..4df98a21 100644 --- a/packages/onebot/tests/actions-group-admin.test.ts +++ b/packages/onebot/tests/actions-group-admin.test.ts @@ -1053,23 +1053,27 @@ describe('per-administrator permissions', () => { 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.each([true, false, null])( + 'returns receive-verification state %s and nullable permissions', + async (receiveVerify) => { + const snapshot = { + ...target, + recall_member_message: null, + kick_member: true, + kick_member_per_day: 0, + receive_verify: receiveVerify, + }; + 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 () => { diff --git a/packages/proto-defs/src/oidb-actions/group-admin-permissions.ts b/packages/proto-defs/src/oidb-actions/group-admin-permissions.ts index d546e6da..d9b43651 100644 --- a/packages/proto-defs/src/oidb-actions/group-admin-permissions.ts +++ b/packages/proto-defs/src/oidb-actions/group-admin-permissions.ts @@ -63,3 +63,30 @@ export interface SetGroupAdminReceiveVerifyResponse { groupCode?: pb<1, uint_64>; result?: pb<2, GroupAdminReceiveVerifyResult>; } + +export interface GroupAdminReceiveVerifyIdentity { + uid?: pb<2, string>; + uin?: pb_optional<4, uint_64>; +} + +export interface GroupAdminReceiveVerifyFilter { + memberFlagExt?: pb<104, uint_32>; +} + +export interface GetGroupAdminReceiveVerifyRequest { + groupCode?: pb<1, uint_64>; + field2?: pb<2, uint_32>; + field3?: pb_optional<3, uint_32>; + filter?: pb<4, GroupAdminReceiveVerifyFilter>; + members?: pb_repeated<5, GroupAdminReceiveVerifyIdentity>; +} + +export interface GroupAdminReceiveVerifyMember { + identity?: pb<1, GroupAdminReceiveVerifyIdentity>; + memberFlagExt?: pb_optional<104, uint_32>; +} + +export interface GetGroupAdminReceiveVerifyResponse { + groupCode?: pb<1, uint_64>; + members?: pb_repeated<2, GroupAdminReceiveVerifyMember>; +} diff --git a/packages/protocol/src/oidb-services/group-admin/get-admin-receive-verify.ts b/packages/protocol/src/oidb-services/group-admin/get-admin-receive-verify.ts new file mode 100644 index 00000000..f6c704da --- /dev/null +++ b/packages/protocol/src/oidb-services/group-admin/get-admin-receive-verify.ts @@ -0,0 +1,72 @@ +import { protobuf_decode, protobuf_encode } from '@snowluma/proton'; +import type { OidbBase } from '@snowluma/proto-defs/oidb'; +import type { + GetGroupAdminReceiveVerifyRequest, + GetGroupAdminReceiveVerifyResponse, +} from '@snowluma/proto-defs/oidb-actions/group-admin-permissions'; +import type { BridgeContext } from '../../bridge-context'; +import { invokeOidb, type OidbSender } from '../../oidb-service'; + +export namespace GetAdminReceiveVerify { + export const command = 0xfe7; + export const subCommand = 4; + export const uinForm = false; + export type Deps = OidbSender & Pick; + export interface Params { + groupId: number; + userId: number; + } + + interface RequestParams { + groupId: number; + userUid: string; + } + + export const serialize = (_ctx: Deps, p: RequestParams): GetGroupAdminReceiveVerifyRequest => ({ + groupCode: BigInt(p.groupId), + field2: 3, + field3: 0, + filter: { memberFlagExt: 1 }, + members: [{ uid: p.userUid }], + }); + export const deserialize = ( + _ctx: Deps, + body: GetGroupAdminReceiveVerifyResponse, + ): GetGroupAdminReceiveVerifyResponse => body; + export const encode = (env: OidbBase): Uint8Array => + protobuf_encode>(env); + export const decode = (bytes: Uint8Array): OidbBase => + protobuf_decode>(bytes); + + export async function invoke(deps: Deps, params: Params): Promise { + const userUid = await deps.resolveUserUid(params.userId, params.groupId); + const body = await invokeOidb(deps, GetAdminReceiveVerify, { + groupId: params.groupId, + userUid, + }); + + if (body.groupCode !== BigInt(params.groupId)) { + throw new Error( + 'administrator receive-verify response has a missing or mismatched group', + ); + } + + const members = (body.members ?? []).filter((member) => member.identity?.uid === userUid); + + if (members.length !== 1) { + throw new Error( + 'administrator receive-verify response must contain exactly one matching member', + ); + } + + const member = members[0]!; + const returnedUin = member.identity?.uin; + + if (returnedUin != null && returnedUin !== BigInt(params.userId)) { + throw new Error('administrator receive-verify response has a mismatched member UIN'); + } + + // Missing data must remain unknown; an explicit zero is a known enabled state. + return member.memberFlagExt == null ? null : (member.memberFlagExt & 64) === 0; + } +} diff --git a/packages/protocol/tests/oidb-services/group-admin/get-admin-receive-verify.test.ts b/packages/protocol/tests/oidb-services/group-admin/get-admin-receive-verify.test.ts new file mode 100644 index 00000000..9050e8de --- /dev/null +++ b/packages/protocol/tests/oidb-services/group-admin/get-admin-receive-verify.test.ts @@ -0,0 +1,152 @@ +import { describe, expect, it, vi } from 'vitest'; +import type { SendPacketResult } from '@snowluma/common/packet-sender'; +import { GetAdminReceiveVerify } from '../../../src/oidb-services/group-admin/get-admin-receive-verify'; +import { env, m, s, v } from '../_pb-oracle'; + +const target = { groupId: 12345, userId: 67890 }; +const uid = 'test-member-uid'; + +function member(flag?: number, memberUid = uid, uin: number | undefined = target.userId) { + return m(2, [ + ...m(1, [...s(2, memberUid), ...(uin === undefined ? [] : v(4, uin))]), + ...(flag === undefined ? [] : v(104, flag)), + ]); +} + +function response(members: number[], group: number | undefined = target.groupId) { + return m(4, [...(group === undefined ? [] : v(1, group)), ...members]); +} + +function sender(body = response(member(0))) { + const result: SendPacketResult = { + success: true, + gotResponse: true, + errorCode: 0, + errorMessage: '', + responseData: Buffer.from(body), + }; + return { + sendRawPacket: vi.fn(async (_command: string, _bytes: Uint8Array) => result), + resolveUserUid: vi.fn(async (_userId: number, _groupId?: number) => uid), + }; +} + +describe('administrator receive-verification read', () => { + it('resolves the member once in its group and requests only the verification state', async () => { + const deps = sender(); + await expect(GetAdminReceiveVerify.invoke(deps, target)).resolves.toBe(true); + expect(deps.resolveUserUid).toHaveBeenCalledExactlyOnceWith(target.userId, target.groupId); + expect(deps.sendRawPacket).toHaveBeenCalledTimes(1); + const [command, bytes] = deps.sendRawPacket.mock.calls[0]!; + expect(command).toBe('OidbSvcTrpcTcp.0xfe7_4'); + expect(Buffer.from(bytes).toString('hex')).toBe( + env( + 0xfe7, + 4, + [ + ...v(1, target.groupId), + ...v(2, 3), + ...v(3, 0), + ...m(4, v(104, 1)), + ...m(5, s(2, uid)), + ], + false, + ), + ); + }); + + it.each([ + [0, true], + [64, false], + [32, true], + [96, false], + [0x80000000, true], + [0xffffffff, false], + [undefined, null], + ] as const)('decodes flag %s as %s while preserving missing data', async (flag, enabled) => { + const deps = sender(response(member(flag))); + await expect(GetAdminReceiveVerify.invoke(deps, target)).resolves.toBe(enabled); + }); + + it('selects the requested member even when unrelated members are returned first', async () => { + const deps = sender(response([...member(64, 'unrelated-member', 10001), ...member(0)])); + await expect(GetAdminReceiveVerify.invoke(deps, target)).resolves.toBe(true); + }); + + it('accepts UID-bound responses without an optional numeric identity', async () => { + const deps = sender(response(m(2, [...m(1, s(2, uid)), ...v(104, 64)]))); + await expect(GetAdminReceiveVerify.invoke(deps, target)).resolves.toBe(false); + }); + + it('preserves large numeric group and member identifiers', async () => { + const large = { groupId: 2 ** 40 + 17, userId: 2 ** 41 + 19 }; + const deps = sender(response(member(0, uid, large.userId), large.groupId)); + await expect(GetAdminReceiveVerify.invoke(deps, large)).resolves.toBe(true); + expect(Buffer.from(deps.sendRawPacket.mock.calls[0]![1]).toString('hex')).toBe( + env( + 0xfe7, + 4, + [ + ...v(1, large.groupId), + ...v(2, 3), + ...v(3, 0), + ...m(4, v(104, 1)), + ...m(5, s(2, uid)), + ], + false, + ), + ); + }); + + it.each([ + ['missing group', m(4, member(0)), 'missing or mismatched group'], + ['wrong group', response(member(0), 999), 'missing or mismatched group'], + ['missing member', response([]), 'exactly one matching member'], + ['wrong member', response(member(0, 'other-uid')), 'exactly one matching member'], + [ + 'missing UID', + response(m(2, [...m(1, v(4, target.userId)), ...v(104, 0)])), + 'exactly one matching member', + ], + [ + 'duplicate member', + response([...member(0), ...member(64)]), + 'exactly one matching member', + ], + ['conflicting numeric identity', response(member(0, uid, 999)), 'mismatched member UIN'], + ] as const)( + 'rejects %s rather than reporting a successful state', + async (_label, body, error) => { + const deps = sender([...body]); + await expect(GetAdminReceiveVerify.invoke(deps, target)).rejects.toThrow(error); + }, + ); + + it('does not send when identity resolution fails', async () => { + const deps = sender(); + deps.resolveUserUid.mockRejectedValueOnce(new Error('identity unavailable')); + await expect(GetAdminReceiveVerify.invoke(deps, target)).rejects.toThrow( + 'identity unavailable', + ); + expect(deps.sendRawPacket).not.toHaveBeenCalled(); + }); + + it('propagates server rejection even if the body contains a valid state', async () => { + const deps = sender([...v(3, 1287), ...response(member(0))]); + await expect(GetAdminReceiveVerify.invoke(deps, target)).rejects.toThrow('OIDB error 1287'); + }); + + it('propagates transport failures without retrying', async () => { + const deps = sender(); + deps.sendRawPacket.mockRejectedValueOnce(new Error('transport disconnected')); + await expect(GetAdminReceiveVerify.invoke(deps, target)).rejects.toThrow( + 'transport disconnected', + ); + expect(deps.sendRawPacket).toHaveBeenCalledTimes(1); + }); + + it('rejects truncated responses', async () => { + const deps = sender([0x22, 0x80]); + await expect(GetAdminReceiveVerify.invoke(deps, target)).rejects.toThrow(); + }); +}); diff --git a/packages/sdk/README.md b/packages/sdk/README.md index 54a12b36..655a9572 100644 --- a/packages/sdk/README.md +++ b/packages/sdk/README.md @@ -233,7 +233,7 @@ if (feed) { | 动作 | 参数与结果 | | --- | --- | -| `get_group_admin_permissions` | `group_id`、`user_id`;返回撤回权限、移除权限和每日移除上限 | +| `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`;独立设置接收加群验证消息 | @@ -260,7 +260,7 @@ await bot.raw('set_group_admin_receive_verify', { 只修改显式提供的权限。开启移除权限必须明确提供 `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`。当前读取接口不返回接收验证消息状态。 +读取结果包含 `group_id`、`user_id`、`recall_member_message`、`kick_member`、`kick_member_per_day`、`receive_verify`。`receive_verify` 表示是否接收加群验证消息。服务器未返回或未指定的状态为 `null`;移除权限关闭或未指定时,上限也为 `null`。各项来自独立查询,不保证同一时刻的状态;任何查询失败都会报错。 两个写动作分别发送一次请求,不自动重试或授予管理员身份。权限写入成功表示服务器确认请求;接收验证消息写入还会核对响应中的群号和成功管理员列表。两者均返回 `null`。完整的客户端显示和实际权限效果仍需真实 QQ 验证,不能以接口成功替代验收。 diff --git a/packages/sdk/src/types/results.ts b/packages/sdk/src/types/results.ts index 51e6c463..2270593c 100644 --- a/packages/sdk/src/types/results.ts +++ b/packages/sdk/src/types/results.ts @@ -278,6 +278,8 @@ export interface GroupAdminPermissions { kick_member: boolean | null; /** Zero means unlimited; null means removal is disabled or unspecified. */ kick_member_per_day: number | null; + /** Whether join-verification messages are received; null means the state was not returned. */ + receive_verify: boolean | null; } export interface GroupTodoListItem { diff --git a/packages/sdk/tests/fixtures/action-contracts.ts b/packages/sdk/tests/fixtures/action-contracts.ts index 50418201..e21d309c 100644 --- a/packages/sdk/tests/fixtures/action-contracts.ts +++ b/packages/sdk/tests/fixtures/action-contracts.ts @@ -286,6 +286,7 @@ export const actionResults = { recall_member_message: null, kick_member: true, kick_member_per_day: 20, + receive_verify: false, }, set_group_admin_permissions: null, set_group_admin_receive_verify: null, diff --git a/packages/sdk/tests/fixtures/action-types.mts b/packages/sdk/tests/fixtures/action-types.mts index ed617272..1ef12609 100644 --- a/packages/sdk/tests/fixtures/action-types.mts +++ b/packages/sdk/tests/fixtures/action-types.mts @@ -49,6 +49,7 @@ const adminPermissions = await http.raw('get_group_admin_permissions', { }); export const adminRecall: boolean | null = adminPermissions.recall_member_message; export const adminKickLimit: number | null = adminPermissions.kick_member_per_day; +export const adminReceiveVerify: boolean | null = adminPermissions.receive_verify; await http.raw('set_group_admin_permissions', { group_id: 10001, user_id: 10002,