From 9dfa31bdbc636d9aec00ac91b1cafb3b755cb4a1 Mon Sep 17 00:00:00 2001 From: DrSmoothl <1787882683@qq.com> Date: Sun, 14 Jun 2026 23:45:59 +0800 Subject: [PATCH] fix(onebot): allow negative message_id in get_{group,friend}_msg_history MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit message_id is a signed int32 hash (hashMessageIdInt32) and is frequently NEGATIVE, but both history actions declared it as `f.int({min:0}).default(0)` — so a real anchor like message_id=-497311472 was rejected at param validation (retcode 1400, 0ms) before run() fired, and the server-side history fetch never happened (the client then reports "未找到足够的群聊记录"). Drop the min:0 bound → `f.int().default(0)`: negatives pass, absent/present-0 still mean "fetch latest", count stays ≥0. Matches every other action, which uses f.messageId() for signed message IDs. Regression tests: negative group/friend anchors now reach the handler; absent message_id still defaults to 0. --- packages/onebot/src/actions/extended.ts | 11 ++++-- .../onebot/tests/extended-actions.test.ts | 36 +++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/packages/onebot/src/actions/extended.ts b/packages/onebot/src/actions/extended.ts index d8cb20af..cdb33bde 100644 --- a/packages/onebot/src/actions/extended.ts +++ b/packages/onebot/src/actions/extended.ts @@ -219,8 +219,12 @@ export const actions = [ summary: '获取群消息历史', readOnly: true, params: { - // 原实现用 `asNumber(message_id) || 0`,present 0 也映射为 0。 - message_id: f.int({ min: 0 }).default(0), + // message_id is a signed int32 hash (hashMessageIdInt32) and is + // frequently NEGATIVE — a `{min:0}` validator rejects a real anchor at + // param-validation time (retcode 1400) before run() ever fires. Use a + // plain signed int; `.default(0)` keeps absent/present-0 → "fetch latest" + // (matches the original `asNumber(message_id) || 0`). `count` stays ≥0. + message_id: f.int().default(0), count: f.int({ min: 0 }).default(20), }, run: async (p, ctx) => { @@ -235,7 +239,8 @@ export const actions = [ readOnly: true, params: { user_id: f.uint(), - message_id: f.int({ min: 0 }).default(0), + // Signed int32 hash, frequently negative — see get_group_msg_history. + message_id: f.int().default(0), count: f.int({ min: 0 }).default(20), }, run: async (p, ctx) => { diff --git a/packages/onebot/tests/extended-actions.test.ts b/packages/onebot/tests/extended-actions.test.ts index 1bd8f97a..1ab9886b 100644 --- a/packages/onebot/tests/extended-actions.test.ts +++ b/packages/onebot/tests/extended-actions.test.ts @@ -212,6 +212,42 @@ describe('extended-actions / bot_exit', () => { }); }); +// ─── History: message_id is a signed int32 hash and is frequently NEGATIVE ─── +// Regression: get_{group,friend}_msg_history declared message_id with a +// `{min:0}` validator, so a real (negative) anchor message_id was rejected at +// param-validation time (retcode 1400) before the handler ran — see the +// `message_id=-497311472 ⇒ failed (0ms)` report. + +describe('extended-actions / history accepts negative message_id', () => { + it('get_group_msg_history forwards a negative anchor to the handler', async () => { + const getGroupMsgHistory = vi.fn(async () => [{ message_id: -1, message_type: 'group' }]); + const ctx = fakeCtx(fakeBridge(), { getGroupMsgHistory } as any); + const res = await makeHandler(ctx).handle('get_group_msg_history', { + group_id: 100, message_id: -497311472, count: 100, + }); + expect(res).toMatchObject({ status: 'ok', retcode: 0 }); + expect(getGroupMsgHistory).toHaveBeenCalledWith(100, -497311472, 100); + }); + + it('get_friend_msg_history forwards a negative anchor to the handler', async () => { + const getFriendMsgHistory = vi.fn(async () => [{ message_id: -1, message_type: 'private' }]); + const ctx = fakeCtx(fakeBridge(), { getFriendMsgHistory } as any); + const res = await makeHandler(ctx).handle('get_friend_msg_history', { + user_id: 12345, message_id: -670862300, count: 100, + }); + expect(res).toMatchObject({ status: 'ok', retcode: 0 }); + expect(getFriendMsgHistory).toHaveBeenCalledWith(12345, -670862300, 100); + }); + + it('still defaults an absent message_id to 0 (fetch-latest semantics)', async () => { + const getGroupMsgHistory = vi.fn(async () => []); + const ctx = fakeCtx(fakeBridge(), { getGroupMsgHistory } as any); + const res = await makeHandler(ctx).handle('get_group_msg_history', { group_id: 100 }); + expect(res).toMatchObject({ status: 'ok', retcode: 0 }); + expect(getGroupMsgHistory).toHaveBeenCalledWith(100, 0, 20); + }); +}); + // ─── Tier 1: nc_get_packet_status / nc_get_rkey ─── describe('extended-actions / nc_get_packet_status', () => {