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
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:
commit
fa9c73363d
1367 files changed
+355735
No files matched your search
@@ -0,0 +1,287 @@
|
||||
// Regression for #102: a group invite makes QQ emit a content-less c2c push
|
||||
// (msgType=166/0, body.richText.elems=[]) right after the invite card. That
|
||||
// phantom used to surface to OneBot clients as a confusing "[空消息]".
|
||||
// parseMsgPush must drop a message that decodes to zero elements *when its body
|
||||
// is genuinely empty*, while still keeping (and warning about) a message that
|
||||
// carried content we merely failed to decode.
|
||||
|
||||
import { getLogLevel, setLogLevel, subscribeLogs, type LogEntry } from '@snowluma/common/logger';
|
||||
import type { PacketInfo } from '@snowluma/common/protocol-types';
|
||||
import { protobuf_encode } from '@snowluma/proton';
|
||||
import type { FileExtra, PushMsg, PushMsgBody } from '@snowluma/proto-defs/message';
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
import type { IdentityService } from '../../src/identity-service';
|
||||
import { parseMsgPush } from '../../src/msg-push';
|
||||
|
||||
const identity = { findFriend: () => undefined } as unknown as IdentityService;
|
||||
const previousLogLevel = getLogLevel();
|
||||
|
||||
afterEach(() => {
|
||||
setLogLevel(previousLogLevel);
|
||||
});
|
||||
|
||||
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 }),
|
||||
};
|
||||
}
|
||||
|
||||
function traceMessages(entries: LogEntry[]): string[] {
|
||||
return entries.filter((entry) => entry.level === 'trace').map((entry) => entry.message);
|
||||
}
|
||||
|
||||
describe('parseMsgPush — empty message drop (#102)', () => {
|
||||
it('records an invalid push context before returning no events', () => {
|
||||
const entries: LogEntry[] = [];
|
||||
setLogLevel('trace');
|
||||
const unsubscribe = subscribeLogs((entry) => entries.push(entry));
|
||||
|
||||
try {
|
||||
const pkt = pushPacket({
|
||||
responseHead: { fromUin: 10001, fromUid: 'u_x' },
|
||||
});
|
||||
|
||||
expect(parseMsgPush(pkt, identity)).toEqual([]);
|
||||
expect(traceMessages(entries)).toContain(
|
||||
'packet_branch serviceCmd="trpc.msg.olpush.OlPushService.MsgPush" seqId=0 branch=push_context_invalid',
|
||||
);
|
||||
} finally {
|
||||
unsubscribe();
|
||||
}
|
||||
});
|
||||
|
||||
it('records the precise branch when dropping a genuinely empty message', () => {
|
||||
const entries: LogEntry[] = [];
|
||||
setLogLevel('trace');
|
||||
const unsubscribe = subscribeLogs((entry) => entries.push(entry));
|
||||
|
||||
try {
|
||||
const out = parseMsgPush(
|
||||
pushPacket({
|
||||
responseHead: { fromUin: 10001, fromUid: 'u_x' },
|
||||
contentHead: {
|
||||
msgType: 166,
|
||||
subType: 0,
|
||||
sequence: 59962,
|
||||
timestamp: 1781540572,
|
||||
msgId: 1963990184,
|
||||
},
|
||||
body: { richText: { elems: [] } },
|
||||
}),
|
||||
identity,
|
||||
);
|
||||
|
||||
expect(out).toEqual([]);
|
||||
expect(traceMessages(entries)).toContain(
|
||||
'packet_branch serviceCmd="trpc.msg.olpush.OlPushService.MsgPush" seqId=0 branch=empty_message msgType=166 subType=0 messageSeq=59962',
|
||||
);
|
||||
} finally {
|
||||
unsubscribe();
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps a friend message that actually has content', () => {
|
||||
const out = parseMsgPush(
|
||||
pushPacket({
|
||||
responseHead: { fromUin: 10001, fromUid: 'u_x' },
|
||||
contentHead: {
|
||||
msgType: 166,
|
||||
subType: 0,
|
||||
sequence: 32743,
|
||||
ntMsgSeq: 63214,
|
||||
timestamp: 1781540572,
|
||||
msgId: 1,
|
||||
},
|
||||
body: { richText: { elems: [{ text: { str: 'hi' } }] } },
|
||||
}),
|
||||
identity,
|
||||
);
|
||||
expect(out).toHaveLength(1);
|
||||
expect(out[0]).toMatchObject({
|
||||
kind: 'friend_message',
|
||||
senderUid: 'u_x',
|
||||
msgSeq: 32743,
|
||||
ntMsgSeq: 63214,
|
||||
clientSeq: 32743,
|
||||
elements: [{ type: 'text', text: 'hi' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('drops an offline-file download receipt instead of a friend file message (#442)', () => {
|
||||
const extra = protobuf_encode<FileExtra>({
|
||||
file: {
|
||||
fileUuid: 'fid-sent-by-bot',
|
||||
fileName: 'doc.bin',
|
||||
fileSize: 6119608n,
|
||||
subcmd: 1,
|
||||
downloadFlag: 2,
|
||||
fileHash: 'abc',
|
||||
},
|
||||
});
|
||||
const out = parseMsgPush(
|
||||
pushPacket({
|
||||
responseHead: { fromUin: 10001, fromUid: 'u_x' },
|
||||
contentHead: { msgType: 529, subType: 0, sequence: 1, timestamp: 1, msgId: 1 },
|
||||
body: { msgContent: extra },
|
||||
}),
|
||||
identity,
|
||||
);
|
||||
expect(out).toEqual([]);
|
||||
});
|
||||
|
||||
it('keeps a real incoming c2c file (#442)', () => {
|
||||
const extra = protobuf_encode<FileExtra>({
|
||||
file: {
|
||||
fileType: 1,
|
||||
fileUuid: 'fid-from-user',
|
||||
fileName: 'doc.bin',
|
||||
fileSize: 10n,
|
||||
subcmd: 1,
|
||||
},
|
||||
});
|
||||
const out = parseMsgPush(
|
||||
pushPacket({
|
||||
responseHead: { fromUin: 10001, fromUid: 'u_x' },
|
||||
contentHead: { msgType: 529, subType: 0, sequence: 2, timestamp: 1, msgId: 2 },
|
||||
body: { msgContent: extra },
|
||||
}),
|
||||
identity,
|
||||
);
|
||||
expect(out).toHaveLength(1);
|
||||
expect(out[0]).toMatchObject({
|
||||
kind: 'friend_message',
|
||||
elements: [{ type: 'file', fileId: 'fid-from-user', fileName: 'doc.bin' }],
|
||||
});
|
||||
});
|
||||
|
||||
it.each(['msgContent', 'richText'] as const)(
|
||||
'drops a subcommand receipt in %s without a download flag (#477)',
|
||||
(location) => {
|
||||
const entries: LogEntry[] = [];
|
||||
setLogLevel('trace');
|
||||
const unsubscribe = subscribeLogs((entry) => entries.push(entry));
|
||||
const file = {
|
||||
fileType: 1,
|
||||
subcmd: 2,
|
||||
fileUuid: 'already-sent',
|
||||
fileName: 'test.json',
|
||||
fileSize: 42n,
|
||||
};
|
||||
|
||||
try {
|
||||
const out = parseMsgPush(
|
||||
pushPacket({
|
||||
responseHead: { fromUin: 10001, fromUid: 'u_x' },
|
||||
contentHead: {
|
||||
msgType: 529,
|
||||
subType: 4,
|
||||
c2cCmd: 4,
|
||||
sequence: 10,
|
||||
timestamp: 1,
|
||||
msgId: 1,
|
||||
},
|
||||
body:
|
||||
location === 'msgContent'
|
||||
? { msgContent: protobuf_encode<FileExtra>({ file }) }
|
||||
: { richText: { notOnlineFile: file } },
|
||||
}),
|
||||
identity,
|
||||
);
|
||||
expect(out).toEqual([]);
|
||||
expect(
|
||||
traceMessages(entries).some((message) =>
|
||||
message.includes('branch=offline_file_receipt'),
|
||||
),
|
||||
).toBe(true);
|
||||
} finally {
|
||||
unsubscribe();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it('records the precise branch for a C2C control push with content', () => {
|
||||
// 166 + c2c_cmd ∈ {1,73,75,129,131,133,135,192} is a control/system signal
|
||||
// QQ NT routes via OnRecvSysMsg, never a bubble — drop regardless of body.
|
||||
const entries: LogEntry[] = [];
|
||||
setLogLevel('trace');
|
||||
const unsubscribe = subscribeLogs((entry) => entries.push(entry));
|
||||
|
||||
try {
|
||||
const out = parseMsgPush(
|
||||
pushPacket({
|
||||
responseHead: { fromUin: 10001, fromUid: 'u_x' },
|
||||
contentHead: {
|
||||
msgType: 166,
|
||||
subType: 0,
|
||||
c2cCmd: 75,
|
||||
sequence: 200,
|
||||
timestamp: 1781540572,
|
||||
msgId: 3,
|
||||
},
|
||||
body: { richText: { elems: [{ text: { str: 'noise' } }] } },
|
||||
}),
|
||||
identity,
|
||||
);
|
||||
|
||||
expect(out).toEqual([]);
|
||||
expect(traceMessages(entries)).toContain(
|
||||
'packet_branch serviceCmd="trpc.msg.olpush.OlPushService.MsgPush" seqId=0 branch=c2c_control_push msgType=166 subType=0 c2cCmd=75 messageSeq=200',
|
||||
);
|
||||
} finally {
|
||||
unsubscribe();
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps a normal 166 message whose c2cCmd is not a control command', () => {
|
||||
const out = parseMsgPush(
|
||||
pushPacket({
|
||||
responseHead: { fromUin: 10001, fromUid: 'u_x' },
|
||||
contentHead: {
|
||||
msgType: 166,
|
||||
subType: 0,
|
||||
c2cCmd: 0,
|
||||
sequence: 201,
|
||||
timestamp: 1781540572,
|
||||
msgId: 4,
|
||||
},
|
||||
body: { richText: { elems: [{ text: { str: 'hi' } }] } },
|
||||
}),
|
||||
identity,
|
||||
);
|
||||
expect(out).toHaveLength(1);
|
||||
expect(out[0]).toMatchObject({
|
||||
kind: 'friend_message',
|
||||
elements: [{ type: 'text', text: 'hi' }],
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps an empty-decoded message whose body still carried content (missing decoder)', () => {
|
||||
// A commonElem service type the rich-body decoder ignores → 0 elements, but
|
||||
// the body was not empty, so the message must survive (and be warned about),
|
||||
// not be silently dropped.
|
||||
const out = parseMsgPush(
|
||||
pushPacket({
|
||||
responseHead: { fromUin: 10001, fromUid: 'u_x' },
|
||||
contentHead: {
|
||||
msgType: 166,
|
||||
subType: 0,
|
||||
sequence: 101,
|
||||
timestamp: 1781540572,
|
||||
msgId: 2,
|
||||
},
|
||||
body: {
|
||||
richText: { elems: [{ commonElem: { serviceType: 999, businessType: 0 } }] },
|
||||
},
|
||||
}),
|
||||
identity,
|
||||
);
|
||||
expect(out).toHaveLength(1);
|
||||
expect(out[0]).toMatchObject({ kind: 'friend_message', elements: [] });
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user