Files
DrSmoothl c16064d7e3 refactor(packages): rename @snowluma/bridge → @snowluma/protocol
The package previously named "bridge" houses the protocol-byte layer
(OIDB cmds, msg-push decoders, highway uploaders, element-builder,
event bus). The name "bridge" suggested transport/lifecycle, which it
isn't — that role lives in core/hook and core/bridge/manager. Rename
to match what's actually inside.

This is a pure rename: zero logic changes, all 73 import sites updated
mechanically and TS-verified. core/onebot dependencies key updated.
ESLint override path adjusted for the moved oidb-services directory.

Rename detection threshold: git log --find-renames=20% recommended
when following history across this commit — content-replacement
(@snowluma/bridge → @snowluma/protocol) dropped per-file similarity
below git's default 50% on import-heavy files.
2026-05-23 23:31:03 +08:00

124 lines
3.9 KiB
TypeScript

// Receive-side decode for `com.tencent.multimsg` LightApp — verifies
// the inverse of element-builder.makeForwardElem so a forward sent by
// SnowLuma (or any QQ-NT / Lagrange / NapCat client) round-trips
// back to `{type: 'forward', resId, forwardUuid}` on the receiver.
//
// Without this the receiver-side decoder sees `lightApp` and falls
// back to a generic `{type: 'json', text: <json>}` element, which
// means the OneBot layer can't surface a forward bubble OR walk into
// the nested forward via fetch(resId).
import { describe, expect, it } from 'vitest';
import { deflateSync } from 'zlib';
import { decodeRichBody } from '../../src/msg-push/rich-body-decoder';
import type { MessageBody } from '@snowluma/proto-defs/message';
function lightAppBytes(json: unknown): Uint8Array {
const buf = deflateSync(Buffer.from(JSON.stringify(json), 'utf8'));
const out = new Uint8Array(buf.length + 1);
out[0] = 0x01; // deflate prefix
out.set(buf, 1);
return out;
}
describe('decodeRichBody / forward LightApp', () => {
it('emits {type:"forward", resId, forwardUuid} for com.tencent.multimsg', () => {
const body: MessageBody = {
richText: {
elems: [
{
lightApp: {
data: lightAppBytes({
app: 'com.tencent.multimsg',
meta: { detail: { resid: 'inner-res-1', uniseq: 'uuid-1' } },
}),
},
} as any,
],
},
};
const out = decodeRichBody(body, true);
expect(out).toEqual([{ type: 'forward', resId: 'inner-res-1', forwardUuid: 'uuid-1' }]);
});
it('omits forwardUuid when the sender did not set uniseq (XML-era forwards)', () => {
const body: MessageBody = {
richText: {
elems: [
{
lightApp: {
data: lightAppBytes({
app: 'com.tencent.multimsg',
meta: { detail: { resid: 'only-resid' } },
}),
},
} as any,
],
},
};
const out = decodeRichBody(body, true);
expect(out).toEqual([{ type: 'forward', resId: 'only-resid' }]);
});
it('falls back to {type:"json"} for non-multimsg LightApp (e.g. mini-app card)', () => {
const body: MessageBody = {
richText: {
elems: [
{
lightApp: {
data: lightAppBytes({ app: 'com.tencent.miniapp_01', meta: {} }),
},
} as any,
],
},
};
const out = decodeRichBody(body, true);
expect(out).toHaveLength(1);
expect(out[0]!.type).toBe('json');
expect(typeof (out[0] as any).text).toBe('string');
});
it('falls back to {type:"json"} when com.tencent.multimsg is missing resid (malformed)', () => {
const body: MessageBody = {
richText: {
elems: [
{
lightApp: {
data: lightAppBytes({
app: 'com.tencent.multimsg',
meta: { detail: {} },
}),
},
} as any,
],
},
};
const out = decodeRichBody(body, true);
expect(out).toHaveLength(1);
expect(out[0]!.type).toBe('json');
});
it('still decodes the legacy richMsg serviceID=35 path (backward compat with mobile QQ)', () => {
// Older clients (and some bridges) still emit the m_resid XML
// shape. The decoder must keep treating it as a forward element
// so SnowLuma can fetch the resid downstream.
const xml = '<?xml version="1.0"?><msg m_resid="legacy-res" />';
const xmlBuf = new Uint8Array(xml.length + 1);
xmlBuf[0] = 0x00;
xmlBuf.set(new TextEncoder().encode(xml), 1);
const body: MessageBody = {
richText: {
elems: [
{
richMsg: { serviceId: 35, template1: xmlBuf },
} as any,
],
},
};
const out = decodeRichBody(body, true);
expect(out).toHaveLength(1);
expect(out[0]).toMatchObject({ type: 'forward', resId: 'legacy-res' });
});
});