feat(onebot): accept join question and answer on set_group_add_option

add_type 4 takes group_question and group_answer. add_type 5 (and 55)
takes group_question. get_group_admin_settings also returns both.

Fixes #383
This commit is contained in:
DrSmoothl
2026-08-17 19:12:43 +08:00
parent 3cb3024b39
commit cd6ada0caa
8 changed files with 148 additions and 9 deletions
+11 -2
View File
@@ -39,6 +39,8 @@ import type { BridgeContext } from '../bridge-context';
export type GroupAdminSettings = {
add_type: number;
group_question: string;
group_answer: string;
robot_member_switch: number;
robot_member_examine: number;
member_invite_policy: GroupMemberInvitePolicy;
@@ -77,6 +79,8 @@ export class GroupAdminApi {
const permissions = decodeGroupMemberPermissions(privilegeFlag);
return {
add_type: results.addType ?? 0,
group_question: results.question ?? '',
group_answer: results.answer ?? '',
robot_member_switch: robot.robotMemberSwitch,
robot_member_examine: robot.robotMemberExamine,
member_invite_policy: decodeMemberInvitePolicy(privilegeFlag),
@@ -97,8 +101,13 @@ export class GroupAdminApi {
return MuteAll.invoke(this.ctx, { groupId, enable });
}
setAddOption(groupId: number, addType: number): Promise<void> {
return SetAddOption.invoke(this.ctx, { groupId, addType });
setAddOption(
groupId: number,
addType: number,
groupQuestion?: string,
groupAnswer?: string,
): Promise<void> {
return SetAddOption.invoke(this.ctx, { groupId, addType, groupQuestion, groupAnswer });
}
setSearch(groupId: number, noFingerOpen?: number, noCodeFingerOpen?: number): Promise<void> {
@@ -231,11 +231,24 @@ describe('apis/group-admin', () => {
const addOptEnv = protobuf_decode<OidbBase<Oidb0x89a_0AddOption>>(bridge.sendRawPacket.mock.calls[4]![1]);
expect(addOptEnv.body).toMatchObject({ groupUin: 1n, settings: { addType: 2 } });
expect(addOptEnv.body?.settings?.groupQuestion ?? undefined).toBeUndefined();
const searchEnv = protobuf_decode<OidbBase<Oidb0x89a_0Search>>(bridge.sendRawPacket.mock.calls[5]![1]);
expect(searchEnv.body?.groupUin).toBe(1n);
});
it('setAddOption forwards question and answer for type 4', async () => {
const bridge = mockBridge();
await new GroupAdminApi(bridge as any).setAddOption(12345, 4, 'how', '42');
const envelope = protobuf_decode<OidbBase<Oidb0x89a_0AddOption>>(
bridge.sendRawPacket.mock.calls[0]![1],
);
expect(envelope.body).toMatchObject({
groupUin: 12345n,
settings: { addType: 4, groupQuestion: 'how', groupAnswer: '42' },
});
});
it('setAddRequest picks _1 / _2 based on filtered flag', async () => {
const bridge = mockBridge();
await new GroupAdminApi(bridge as any).setAddRequest(12345, 5, 1, true, 'ok', false);
@@ -446,6 +459,8 @@ describe('apis/group-admin', () => {
if (cmd === 'OidbSvcTrpcTcp.0x88d_0') {
return packAdminDetail({
addType: 2,
question: 'how',
answer: '42',
privilegeFlag: 0x84018001,
groupFlagExt4: 0x80000005,
noFingerOpen: 1,
@@ -460,6 +475,8 @@ describe('apis/group-admin', () => {
const out = await new GroupAdminApi(bridge as any).getAdminSettings(12345);
expect(out).toEqual({
add_type: 2,
group_question: 'how',
group_answer: '42',
robot_member_switch: 1,
robot_member_examine: 2,
member_invite_policy: 'disabled',
@@ -490,6 +507,8 @@ describe('apis/group-admin', () => {
await expect(new GroupAdminApi(bridge as any).getAdminSettings(12345)).resolves.toEqual({
add_type: 0,
group_question: '',
group_answer: '',
robot_member_switch: 0,
robot_member_examine: 0,
member_invite_policy: 'require_approval',
+32
View File
@@ -3874,6 +3874,14 @@
"type": "integer",
"description": "加群选项(同 set_group_add_option.add_type)"
},
"group_question": {
"type": "string",
"description": "加群问题(同 set_group_add_option.group_question)"
},
"group_answer": {
"type": "string",
"description": "加群答案(同 set_group_add_option.group_answer)"
},
"robot_member_switch": {
"type": "integer",
"description": "机器人加群开关(同 set_group_robot_add_option)"
@@ -3919,6 +3927,8 @@
},
"required": [
"add_type",
"group_question",
"group_answer",
"robot_member_switch",
"robot_member_examine",
"member_invite_policy",
@@ -9617,6 +9627,22 @@
"minimum": 0
},
"default": 0
},
{
"name": "group_question",
"type": "string",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "group_answer",
"type": "string",
"required": false,
"schema": {
"type": "string"
}
}
],
"invariants": [],
@@ -9633,6 +9659,12 @@
"type": "integer",
"minimum": 0,
"default": 0
},
"group_question": {
"type": "string"
},
"group_answer": {
"type": "string"
}
},
"required": [
+32
View File
@@ -3877,6 +3877,14 @@ export const ACTIONS: CatalogAction[] = [
"type": "integer",
"description": "加群选项(同 set_group_add_option.add_type)"
},
"group_question": {
"type": "string",
"description": "加群问题(同 set_group_add_option.group_question)"
},
"group_answer": {
"type": "string",
"description": "加群答案(同 set_group_add_option.group_answer)"
},
"robot_member_switch": {
"type": "integer",
"description": "机器人加群开关(同 set_group_robot_add_option)"
@@ -3922,6 +3930,8 @@ export const ACTIONS: CatalogAction[] = [
},
"required": [
"add_type",
"group_question",
"group_answer",
"robot_member_switch",
"robot_member_examine",
"member_invite_policy",
@@ -9620,6 +9630,22 @@ export const ACTIONS: CatalogAction[] = [
"minimum": 0
},
"default": 0
},
{
"name": "group_question",
"type": "string",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "group_answer",
"type": "string",
"required": false,
"schema": {
"type": "string"
}
}
],
"invariants": [],
@@ -9636,6 +9662,12 @@ export const ACTIONS: CatalogAction[] = [
"type": "integer",
"minimum": 0,
"default": 0
},
"group_question": {
"type": "string"
},
"group_answer": {
"type": "string"
}
},
"required": [
+12 -2
View File
@@ -45,9 +45,15 @@ export const actions = [
groupAction({
name: 'set_group_add_option',
summary: '设置加群选项',
params: { add_type: f.int({ min: 0 }).default(0) },
params: {
add_type: f.int({ min: 0 }).default(0),
group_question: f.string().optional(),
group_answer: f.string().optional(),
},
run: async (p, ctx) => {
await ctx.bridge.apis.groupAdmin.setAddOption(p.group_id, p.add_type);
await ctx.bridge.apis.groupAdmin.setAddOption(
p.group_id, p.add_type, p.group_question, p.group_answer,
);
return okResponse();
},
}),
@@ -124,6 +130,8 @@ export const actions = [
type: 'object',
properties: {
add_type: { type: 'integer', description: '加群选项(同 set_group_add_option.add_type)' },
group_question: { type: 'string', description: '加群问题(同 set_group_add_option.group_question)' },
group_answer: { type: 'string', description: '加群答案(同 set_group_add_option.group_answer)' },
robot_member_switch: { type: 'integer', description: '机器人加群开关(同 set_group_robot_add_option)' },
robot_member_examine: { type: 'integer', description: '机器人加群审核(同 set_group_robot_add_option)' },
member_invite_policy: {
@@ -140,6 +148,8 @@ export const actions = [
},
required: [
'add_type',
'group_question',
'group_answer',
'robot_member_switch',
'robot_member_examine',
'member_invite_policy',
@@ -454,18 +454,18 @@ describe('set_group_add_option parse', () => {
it('defaults add_type to 0 and keeps a present 0', () => {
expect(setGroupAddOption.parse({ group_id: 12345 })).toEqual({
ok: true,
value: { group_id: 12345, add_type: 0 },
value: { group_id: 12345, add_type: 0, group_question: undefined, group_answer: undefined },
});
expect(setGroupAddOption.parse({ group_id: 12345, add_type: 0 })).toEqual({
ok: true,
value: { group_id: 12345, add_type: 0 },
value: { group_id: 12345, add_type: 0, group_question: undefined, group_answer: undefined },
});
});
it('accepts a positive add_type and rejects a negative one', () => {
expect(setGroupAddOption.parse({ group_id: 12345, add_type: '2' })).toEqual({
ok: true,
value: { group_id: 12345, add_type: 2 },
value: { group_id: 12345, add_type: 2, group_question: undefined, group_answer: undefined },
});
expect(setGroupAddOption.parse({ group_id: 12345, add_type: -1 })).toEqual({
ok: false,
@@ -473,6 +473,23 @@ describe('set_group_add_option parse', () => {
reason: 'must be >= 0',
});
});
it('keeps optional question and answer when present', () => {
expect(setGroupAddOption.parse({
group_id: 12345,
add_type: 4,
group_question: 'q',
group_answer: 'a',
})).toEqual({
ok: true,
value: {
group_id: 12345,
add_type: 4,
group_question: 'q',
group_answer: 'a',
},
});
});
});
describe('set_group_add_option toHandler', () => {
@@ -483,7 +500,7 @@ describe('set_group_add_option toHandler', () => {
await expect(setGroupAddOption.toHandler(ctx)({ group_id: 12345 }))
.resolves.toEqual({ status: 'ok', retcode: 0, data: null });
expect(setAddOption.mock.calls).toEqual([[12345, 0]]);
expect(setAddOption.mock.calls).toEqual([[12345, 0, undefined, undefined]]);
});
it('forwards an explicit add_type', async () => {
@@ -495,7 +512,21 @@ describe('set_group_add_option toHandler', () => {
add_type: 3,
})).resolves.toEqual({ status: 'ok', retcode: 0, data: null });
expect(setAddOption.mock.calls).toEqual([[941657197, 3]]);
expect(setAddOption.mock.calls).toEqual([[941657197, 3, undefined, undefined]]);
});
it('forwards question and answer', async () => {
const setAddOption = vi.fn(async () => {});
const { ctx } = adminCtx({ setAddOption });
await expect(setGroupAddOption.toHandler(ctx)({
group_id: 941657197,
add_type: 4,
group_question: 'q',
group_answer: 'a',
})).resolves.toEqual({ status: 'ok', retcode: 0, data: null });
expect(setAddOption.mock.calls).toEqual([[941657197, 4, 'q', 'a']]);
});
});
@@ -780,6 +811,8 @@ describe('get_group_admin_settings toHandler', () => {
it('forwards group_id and returns the current settings', async () => {
const settings = {
add_type: 2,
group_question: 'q',
group_answer: 'a',
robot_member_switch: 1,
robot_member_examine: 0,
member_invite_policy: 'require_approval',
+2
View File
@@ -88,6 +88,8 @@ export interface SetGroupWholeBanParams extends GroupIdParams {
export interface SetGroupAddOptionParams extends GroupIdParams {
add_type: number;
group_question?: string;
group_answer?: string;
}
export interface SetGroupAdminParams extends GroupIdParams {
+2
View File
@@ -219,6 +219,8 @@ export interface GroupAtAllRemainInfo {
export type GroupAdminSettings = {
add_type: number;
group_question: string;
group_answer: string;
robot_member_switch: number;
robot_member_examine: number;
member_invite_policy: 'disabled' | 'require_approval' | 'no_approval' | 'no_approval_under_100';