SnowLuma v2.4.2 source snapshot
Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
Publish MCP to npm / Build, test, and publish MCP (push) Waiting to run
Publish SDK to npm / Build, test, and publish SDK (push) Waiting to run
Sync action catalog to docs / sync (push) Waiting to run
Publish UI to npm / Build and publish UI (push) Waiting to run
Build and Release / preflight (push) Waiting to run

This commit is contained in:
TanTan committed 2026-11-13 09:02:00 +08:00
commit fa9c73363d
1367 files changed
+355735

No files matched your search

@@ -0,0 +1,328 @@
// Regression for #137: inviting an official robot (e.g. 2854207029) into a
// group makes QQ push the `group_member_increase` notice TWICE, so SnowLuma
// reported two `notice.group_increase` while a normal member produced one.
// QQ NT dedups system messages in `sys_msg_mgr.cc::ProcessRecvSysMsg` by a
// global key whose per-message discriminators are msg_seq (contentHead field 5
// = head.sequence) and random (contentHead field 4 = head.msgId); kernel-based
// bots see events only after that dedup. SnowLuma reads the raw OlPush, so
// parseMsgPush must replicate the drop for routes in QQ NT's static system-
// message table, but only when given a dedup tracker (the live path). Ordinary
// chat routes remain outside that table even when their outer msgType matches.
import { getLogLevel, setLogLevel, subscribeLogs, type LogEntry } from '@snowluma/common/logger';
import { describe, expect, it } from 'vitest';
import type { PacketInfo } from '@snowluma/common/protocol-types';
import { protobuf_encode } from '@snowluma/proton';
import type { PushMsg, PushMsgBody } from '@snowluma/proto-defs/message';
import type { GroupChange, OnlineDeviceNotify } from '@snowluma/proto-defs/notify';
import { IdentityService } from '../../src/identity-service';
import {
deriveSysMsgDedupIdentity,
parseMsgPush,
SysMsgDedup,
type SysMsgDedupIdentity,
} from '../../src/msg-push';
const identity = IdentityService.memory('2000000001');
function pushPacket(message: PushMsgBody): PacketInfo {
return {
pid: 0,
uin: '2000000001',
serviceCmd: 'trpc.msg.olpush.OlPushService.MsgPush',
seqId: 0,
retCode: 0,
fromClient: false,
body: protobuf_encode<PushMsg>({ message }),
};
}
// A type-33 group member-increase push (the #137 shape). `memberUid` is a
// numeric string so Identity.findUinByUid returns it without a map lookup.
function memberIncreasePush(opts: {
groupId: number;
memberUin: number;
sequence: number;
msgId: number;
}): PacketInfo {
return pushPacket({
responseHead: { fromUin: opts.groupId, fromUid: '' },
contentHead: {
msgType: 33,
subType: 0,
sequence: opts.sequence,
timestamp: 1781540572,
msgId: opts.msgId,
},
body: {
msgContent: protobuf_encode<GroupChange>({
groupUin: opts.groupId,
memberUid: String(opts.memberUin),
}),
},
});
}
function c2cPacket(msgType: number, subType: number): PacketInfo {
return pushPacket({
responseHead: { fromUin: 10001, fromUid: 'u_x' },
contentHead: { msgType, subType, sequence: 900, timestamp: 1781540572, msgId: 7 },
body: { richText: { elems: [{ text: { str: 'hi' } }] } },
});
}
function onlineDevicesPush(deviceName: string): PacketInfo {
return pushPacket({
responseHead: { fromUin: 2000000001, fromUid: '' },
contentHead: {
msgType: 528,
subType: 349,
sequence: 800,
timestamp: 1781540572,
msgId: 555,
},
body: {
msgContent: protobuf_encode<OnlineDeviceNotify>({
devices: [
{ appId: 537242075, instanceId: 202, clientType: 1, platform: 3, deviceName },
],
}),
},
});
}
function dedupIdentity(overrides: Partial<SysMsgDedupIdentity> = {}): SysMsgDedupIdentity {
return {
peerUid: '700',
chatType: 2,
sequence: 500,
random: 12345,
...overrides,
};
}
function deriveIdentity(opts: {
msgType: number;
subType: number;
sequence?: number;
msgId?: number;
fromUin?: number;
fromUid?: string;
}) {
return deriveSysMsgDedupIdentity({
head: {
msgType: opts.msgType,
subType: opts.subType,
c2cCmd: 0,
sequence: opts.sequence ?? 500,
ntMsgSeq: 0,
timestamp: 1781540572,
msgId: opts.msgId ?? 12345,
},
fromUin: opts.fromUin ?? 700,
fromUid: opts.fromUid ?? 'u_peer',
});
}
describe('deriveSysMsgDedupIdentity', () => {
it.each([
[528, 290],
[166, 75],
[166, 129],
[166, 131],
[166, 133],
[166, 135],
[167, 133],
])('maps native C2C route %d/%d to fromUid and chatType 1', (msgType, subType) => {
expect(deriveIdentity({ msgType, subType, fromUin: 700, fromUid: 'u_peer' })).toEqual({
peerUid: 'u_peer',
chatType: 1,
sequence: 500,
random: 12345,
});
});
it.each([
[732, 12],
[732, 16],
[732, 20],
[33, 0],
[34, 0],
[85, 0],
])('maps native group route %d/%d to decimal fromUin and chatType 2', (msgType, subType) => {
expect(deriveIdentity({ msgType, subType, fromUin: 700, fromUid: 'ignored' })).toEqual({
peerUid: '700',
chatType: 2,
sequence: 500,
random: 12345,
});
});
it('[#266] fails open for an outer package pair absent from QQ NT’s static table', () => {
expect(deriveIdentity({ msgType: 528, subType: 99 })).toBeNull();
});
it('fails open when the native sequence, random, or route-specific peer is missing', () => {
expect(deriveIdentity({ msgType: 33, subType: 0, sequence: 0 })).toBeNull();
expect(deriveIdentity({ msgType: 33, subType: 0, msgId: 0 })).toBeNull();
expect(deriveIdentity({ msgType: 33, subType: 0, fromUin: 0 })).toBeNull();
expect(deriveIdentity({ msgType: 528, subType: 290, fromUid: '' })).toBeNull();
});
});
describe('SysMsgDedup', () => {
it('flags the second push with the same native global key', () => {
const d = new SysMsgDedup();
expect(d.seenDuplicate(dedupIdentity())).toBe(false);
expect(d.seenDuplicate(dedupIdentity())).toBe(true);
});
it('treats a different sequence, random, peer, or chat type as distinct', () => {
const d = new SysMsgDedup();
expect(d.seenDuplicate(dedupIdentity())).toBe(false);
expect(d.seenDuplicate(dedupIdentity({ sequence: 501 }))).toBe(false);
expect(d.seenDuplicate(dedupIdentity({ random: 99999 }))).toBe(false);
expect(d.seenDuplicate(dedupIdentity({ peerUid: '701' }))).toBe(false);
expect(d.seenDuplicate(dedupIdentity({ chatType: 1 }))).toBe(false);
});
it('[#266] dedups different listed outer routes that derive the same native identity', () => {
const d = new SysMsgDedup();
const first = deriveIdentity({ msgType: 166, subType: 133 });
const alternateRoute = deriveIdentity({ msgType: 167, subType: 133 });
expect(first).not.toBeNull();
expect(alternateRoute).toEqual(first);
expect(d.seenDuplicate(first!)).toBe(false);
expect(d.seenDuplicate(alternateRoute!)).toBe(true);
});
it('[#266] does not record an unlisted outer route', () => {
const d = new SysMsgDedup();
const unlisted = deriveIdentity({ msgType: 528, subType: 99 });
const listed = deriveIdentity({ msgType: 528, subType: 290 });
expect(unlisted).toBeNull();
expect(listed).not.toBeNull();
expect(d.seenDuplicate(listed!)).toBe(false);
});
it('never dedups a manually supplied identity without a server discriminator or peer', () => {
const d = new SysMsgDedup();
expect(d.seenDuplicate(dedupIdentity({ sequence: 0 }))).toBe(false);
expect(d.seenDuplicate(dedupIdentity({ sequence: 0 }))).toBe(false);
expect(d.seenDuplicate(dedupIdentity({ random: 0 }))).toBe(false);
expect(d.seenDuplicate(dedupIdentity({ peerUid: '' }))).toBe(false);
});
it('evicts the oldest key once capacity is exceeded (bounded memory)', () => {
const d = new SysMsgDedup(2);
expect(d.seenDuplicate(dedupIdentity({ sequence: 1, random: 1 }))).toBe(false);
expect(d.seenDuplicate(dedupIdentity({ sequence: 2, random: 1 }))).toBe(false);
expect(d.seenDuplicate(dedupIdentity({ sequence: 3, random: 1 }))).toBe(false);
expect(d.seenDuplicate(dedupIdentity({ sequence: 1, random: 1 }))).toBe(false);
expect(d.seenDuplicate(dedupIdentity({ sequence: 3, random: 1 }))).toBe(true);
});
});
describe('parseMsgPush — system-push dedup (#137)', () => {
it('drops the second identical official-robot member-increase push', () => {
const dedup = new SysMsgDedup();
const push = () =>
memberIncreasePush({ groupId: 700, memberUin: 2854207029, sequence: 800, msgId: 555 });
const first = parseMsgPush(push(), identity, dedup);
expect(first).toHaveLength(1);
expect(first[0]).toMatchObject({
kind: 'group_member_join',
groupId: 700,
userUin: 2854207029,
});
const second = parseMsgPush(push(), identity, dedup);
expect(second).toEqual([]);
});
it('traces the actual duplicate branch before returning zero events', () => {
const previousLevel = getLogLevel();
const entries: LogEntry[] = [];
setLogLevel('trace');
const unsubscribe = subscribeLogs((entry) => entries.push(entry));
try {
const dedup = new SysMsgDedup();
const push = () =>
memberIncreasePush({
groupId: 700,
memberUin: 2854207029,
sequence: 800,
msgId: 555,
});
expect(parseMsgPush(push(), identity, dedup)).toHaveLength(1);
expect(parseMsgPush(push(), identity, dedup)).toEqual([]);
expect(
entries.filter((entry) => entry.scope === 'MsgPush' && entry.level === 'trace'),
).toEqual([
expect.objectContaining({
message:
'packet_branch serviceCmd="trpc.msg.olpush.OlPushService.MsgPush" seqId=0 branch=duplicate_system_push peer="700" chatType=2 messageSeq=800 messageRandom=555',
}),
]);
} finally {
unsubscribe();
setLogLevel(previousLevel);
}
});
it('keeps both pushes when no dedup tracker is supplied (forward re-parse path)', () => {
const push = () =>
memberIncreasePush({ groupId: 700, memberUin: 2854207029, sequence: 800, msgId: 555 });
expect(parseMsgPush(push(), identity)).toHaveLength(1);
expect(parseMsgPush(push(), identity)).toHaveLength(1);
});
it('keeps distinct member-increase events (different members → different seq/msgId)', () => {
const dedup = new SysMsgDedup();
const a = parseMsgPush(
memberIncreasePush({ groupId: 700, memberUin: 111, sequence: 800, msgId: 555 }),
identity,
dedup,
);
const b = parseMsgPush(
memberIncreasePush({ groupId: 700, memberUin: 222, sequence: 801, msgId: 556 }),
identity,
dedup,
);
expect(a).toHaveLength(1);
expect(b).toHaveLength(1);
expect(b[0]).toMatchObject({ userUin: 222 });
});
it('[#266] dedups a listed C2C route before its outer type is decoded as a chat message', () => {
const dedup = new SysMsgDedup();
expect(parseMsgPush(c2cPacket(166, 133), identity, dedup)).toHaveLength(1);
expect(parseMsgPush(c2cPacket(166, 133), identity, dedup)).toEqual([]);
});
it('[#266] records a listed undecoded route before a matching listed route is dispatched', () => {
const dedup = new SysMsgDedup();
expect(parseMsgPush(c2cPacket(167, 133), identity, dedup)).toEqual([]);
expect(parseMsgPush(c2cPacket(166, 133), identity, dedup)).toEqual([]);
});
it('does not dedup ordinary chat messages outside the native system-message table', () => {
const dedup = new SysMsgDedup();
expect(parseMsgPush(c2cPacket(166, 0), identity, dedup)).toHaveLength(1);
expect(parseMsgPush(c2cPacket(166, 0), identity, dedup)).toHaveLength(1);
});
it('does not dedup replacement state snapshots with the same server identity', () => {
const dedup = new SysMsgDedup();
expect(parseMsgPush(onlineDevicesPush('DESKTOP-A'), identity, dedup)).toMatchObject([
{ kind: 'online_devices_changed', devices: [{ deviceName: 'DESKTOP-A' }] },
]);
expect(parseMsgPush(onlineDevicesPush('DESKTOP-B'), identity, dedup)).toMatchObject([
{ kind: 'online_devices_changed', devices: [{ deviceName: 'DESKTOP-B' }] },
]);
});
});