fix(onebot): allow negative message_id in get_{group,friend}_msg_history

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.
This commit is contained in:
DrSmoothl
2026-06-14 23:45:59 +08:00
parent 8dc4ff48f8
commit 9dfa31bdbc
2 changed files with 44 additions and 3 deletions
+8 -3
View File
@@ -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) => {
@@ -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', () => {