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,84 @@
import { describe, expect, it, vi } from 'vitest';
import { protobuf_decode, protobuf_encode } from '@snowluma/proton';
import type { OidbBase } from '@snowluma/proto-defs/oidb';
import type {
OidbAiVoiceListReq,
OidbAiVoiceListResp,
} from '@snowluma/proto-defs/oidb-actions/media';
import type { SendPacketResult } from '@snowluma/common/packet-sender';
import { FetchAiVoiceList } from '../../../src/oidb-services/extras/fetch-ai-voice-list';
function makeSender(body?: OidbAiVoiceListResp) {
const responseData =
body !== undefined
? Buffer.from(protobuf_encode<OidbBase<OidbAiVoiceListResp>>({ body }))
: Buffer.alloc(0);
const r: SendPacketResult = {
success: true,
gotResponse: true,
errorCode: 0,
errorMessage: '',
responseData,
};
return { sendRawPacket: vi.fn(async () => r) };
}
describe('FetchAiVoiceList namespace', () => {
it('declares 0x929D_0', () => {
expect(FetchAiVoiceList.command).toBe(0x929d);
expect(FetchAiVoiceList.subCommand).toBe(0);
});
describe('serialize', () => {
it('passes groupUin / chatType verbatim', () => {
expect(FetchAiVoiceList.serialize({} as any, { groupId: 4242, chatType: 1 })).toEqual({
groupUin: 4242,
chatType: 1,
});
});
});
describe('deserialize', () => {
it('returns content array verbatim', () => {
const out = FetchAiVoiceList.deserialize({} as any, {
content: [
{
category: 'cute',
voices: [
{ voiceId: 'v1', voiceDisplayName: 'V1', voiceExampleUrl: 'http://x' },
],
},
],
});
expect(out).toEqual([
{
category: 'cute',
voices: [
{ voiceId: 'v1', voiceDisplayName: 'V1', voiceExampleUrl: 'http://x' },
],
},
]);
});
it('returns [] when content is omitted', () => {
expect(FetchAiVoiceList.deserialize({} as any, {})).toEqual([]);
});
});
describe('invoke (e2e)', () => {
it('routes to OidbSvcTrpcTcp.0x929d_0', async () => {
const sender = makeSender({ content: [] });
await FetchAiVoiceList.invoke(sender, { groupId: 100, chatType: 1 });
expect(sender.sendRawPacket.mock.calls[0]![0]).toBe('OidbSvcTrpcTcp.0x929d_0');
});
it('encodes envelope body correctly', async () => {
const sender = makeSender({ content: [] });
await FetchAiVoiceList.invoke(sender, { groupId: 4242, chatType: 2 });
const [, bytes] = sender.sendRawPacket.mock.calls[0]!;
const env = protobuf_decode<OidbBase<OidbAiVoiceListReq>>(bytes);
expect(env.body).toMatchObject({ groupUin: 4242, chatType: 2 });
});
});
});
@@ -0,0 +1,131 @@
import { describe, expect, it, vi } from 'vitest';
import { protobuf_decode, protobuf_encode } from '@snowluma/proton';
import type { OidbBase } from '@snowluma/proto-defs/oidb';
import type { OidbAiVoiceReq, OidbAiVoiceResp } from '@snowluma/proto-defs/oidb-actions/media';
import type { SendPacketResult } from '@snowluma/common/packet-sender';
import { FetchAiVoice } from '../../../src/oidb-services/extras/fetch-ai-voice';
function makeSender(body?: OidbAiVoiceResp) {
const responseData =
body !== undefined
? Buffer.from(protobuf_encode<OidbBase<OidbAiVoiceResp>>({ body }))
: Buffer.alloc(0);
const r: SendPacketResult = {
success: true,
gotResponse: true,
errorCode: 0,
errorMessage: '',
responseData,
};
return { sendRawPacket: vi.fn(async () => r) };
}
describe('FetchAiVoice namespace', () => {
it('declares 0x929B_0', () => {
expect(FetchAiVoice.command).toBe(0x929b);
expect(FetchAiVoice.subCommand).toBe(0);
});
describe('serialize', () => {
it('packages every field including sessionId into session sub-message', () => {
const out = FetchAiVoice.serialize({} as any, {
groupId: 100,
voiceId: 'v',
text: 'hi',
chatType: 1,
sessionId: 0xcafe,
});
expect(out).toEqual({
groupUin: 100,
voiceId: 'v',
text: 'hi',
chatType: 1,
session: { sessionId: 0xcafe },
});
});
});
describe('deserialize', () => {
it('returns the first msgInfoBody index', () => {
const node = { fileUuid: 'uuid', subType: 1 };
expect(
FetchAiVoice.deserialize(
{} as any,
{
field2: 319,
field3: 20,
msgInfo: { msgInfoBody: [{ index: node }] },
} as any,
),
).toEqual(node);
});
it('returns null when msgInfo is empty / missing', () => {
expect(FetchAiVoice.deserialize({} as any, {} as any)).toBeNull();
expect(FetchAiVoice.deserialize({} as any, { msgInfo: {} } as any)).toBeNull();
expect(
FetchAiVoice.deserialize({} as any, { msgInfo: { msgInfoBody: [] } } as any),
).toBeNull();
});
});
describe('invoke (e2e)', () => {
it('routes to OidbSvcTrpcTcp.0x929b_0', async () => {
const sender = makeSender({
msgInfo: { msgInfoBody: [{ index: { fileUuid: 'u' } }] },
} as any);
await FetchAiVoice.invoke(sender, {
groupId: 1,
voiceId: 'v',
text: 't',
chatType: 1,
sessionId: 1,
});
expect(sender.sendRawPacket.mock.calls[0]![0]).toBe('OidbSvcTrpcTcp.0x929b_0');
});
it('returns the parsed media node', async () => {
const node = { fileUuid: 'uuid', subType: 1 };
const sender = makeSender({
field2: 319,
field3: 20,
msgInfo: { msgInfoBody: [{ index: node }] },
} as any);
const out = await FetchAiVoice.invoke(sender, {
groupId: 1,
voiceId: 'v',
text: 't',
chatType: 1,
sessionId: 1,
});
expect(out).toMatchObject(node);
});
it('returns null when server polls back empty msgInfo (in-flight)', async () => {
const sender = makeSender({} as any);
const out = await FetchAiVoice.invoke(sender, {
groupId: 1,
voiceId: 'v',
text: 't',
chatType: 1,
sessionId: 1,
});
expect(out).toBeNull();
});
it('passes the sessionId through to the wire envelope verbatim', async () => {
const sender = makeSender({} as any);
await FetchAiVoice.invoke(sender, {
groupId: 1,
voiceId: 'v',
text: 't',
chatType: 1,
sessionId: 0xcafebabe,
});
const [, bytes] = sender.sendRawPacket.mock.calls[0]!;
const env = protobuf_decode<OidbBase<OidbAiVoiceReq>>(bytes);
expect(env.body?.session?.sessionId).toBe(0xcafebabe);
});
});
});
@@ -0,0 +1,128 @@
import { describe, expect, it, vi } from 'vitest';
import { protobuf_decode } from '@snowluma/proton';
import type { SendPacketResult } from '@snowluma/common/packet-sender';
import type { OidbBase } from '@snowluma/proto-defs/oidb';
import type { OidbQueryGroupTopBannersReq } from '@snowluma/proto-defs/oidb-actions/base';
import { GetGroupTodoList } from '../../../src/oidb-services/extras/get-group-todo-list';
// Fixed wire fixture shaped after a live QQ 3.2.32-51802 0x9474_0 response.
// It deliberately uses the current common-banner representation rather than
// constructing the response with the production protobuf encoder.
const ACTIVE_TODO_RESPONSE = Buffer.from(
'08f4a802100018002291010a8e01080f10021a06353633315f3040d09ba8bb06' +
'a2016a0a3f0a1d68747470733a2f2f6578616d706c652e746573742f746f646f' +
'2e706e67120ce7bea4e5be85e58a9eefbd9c1a0ce6b58be8af95e5be85e58a9e' +
'28023003121b08031a177b22736571223a353633312c2272616e646f6d223a307d' +
'18c0ffa7bb0620c1ffa7bb06c802a29c01d002c801e80201f002012a006000',
'hex',
);
function makeSender(responseData = Buffer.alloc(0)) {
const result: SendPacketResult = {
success: true,
gotResponse: true,
errorCode: 0,
errorMessage: '',
responseData,
};
return { sendRawPacket: vi.fn(async () => result) };
}
describe('GetGroupTodoList namespace', () => {
it('queries 0x9474_0 with the todo-only banner flag', async () => {
const sender = makeSender();
await GetGroupTodoList.invoke(sender, { groupId: 941657197 });
const [wireName, bytes] = sender.sendRawPacket.mock.calls[0]!;
expect(wireName).toBe('OidbSvcTrpcTcp.0x9474_0');
expect(GetGroupTodoList.subCommand).toBe(0);
const envelope = protobuf_decode<OidbBase<OidbQueryGroupTopBannersReq>>(bytes);
expect(envelope).toMatchObject({
command: 0x9474,
body: { groupId: 941657197n, bannerFlag: 1 },
});
});
it('decodes the current common-banner todo and validates both identity sources', async () => {
const sender = makeSender(ACTIVE_TODO_RESPONSE);
await expect(GetGroupTodoList.invoke(sender, { groupId: 941657197 })).resolves.toEqual([
{
sourceId: '5631_0',
sequence: 5631,
random: 0,
text: '测试待办',
createdAt: 1735000000,
updatedAt: 1735000001,
},
]);
});
it('filters disappeared todo tombstones', () => {
expect(
GetGroupTodoList.deserialize({} as never, {
banners: [{ bizType: 15, bannerType: 2, isDisappear: true, bizId: 20002 }],
}),
).toEqual([]);
});
it('supports the legacy todo text shape without inventing timestamps', () => {
expect(
GetGroupTodoList.deserialize({} as never, {
banners: [
{
msgId: Buffer.from('88_0'),
isDisappear: false,
todoBanner: { text: '旧版待办' },
},
],
}),
).toEqual([
{
sourceId: '88_0',
sequence: 88,
random: 0,
text: '旧版待办',
createdAt: 0,
updatedAt: 0,
},
]);
});
it('rejects an active todo whose message identity cannot be mapped', () => {
expect(() =>
GetGroupTodoList.deserialize({} as never, {
banners: [
{
bizType: 15,
bannerType: 2,
msgId: Buffer.from('not-a-message-id'),
isDisappear: false,
bizId: 20002,
},
],
}),
).toThrow(/invalid group todo message identity/);
});
it('rejects disagreement between msgId and the jump payload', () => {
expect(() =>
GetGroupTodoList.deserialize({} as never, {
banners: [
{
bizType: 15,
bannerType: 2,
msgId: Buffer.from('5631_0'),
isDisappear: false,
bizId: 20002,
commonBanner: {
jumpInfo: { jumpParam: Buffer.from('{"seq":5632,"random":0}') },
},
},
],
}),
).toThrow(/identity mismatch/);
});
});
@@ -0,0 +1,150 @@
import { describe, expect, it, vi } from 'vitest';
import { protobuf_decode, protobuf_encode } from '@snowluma/proton';
import type { pb, pb_repeated, uint_32, uint_64 } from '@snowluma/proton';
import type { OidbBase } from '@snowluma/proto-defs/oidb';
import type {
OidbStrangerStatusReq,
OidbStrangerStatusResp,
} from '@snowluma/proto-defs/oidb-actions/base';
import type { SendPacketResult } from '@snowluma/common/packet-sender';
import { GetStrangerStatus } from '../../../src/oidb-services/extras/get-stranger-status';
interface ObservedStatusProperty {
key?: pb<1, uint_32>;
value?: pb<2, uint_64>;
}
interface ObservedStatusProperties {
entries?: pb_repeated<1, ObservedStatusProperty>;
}
interface ObservedStatusData {
targetUin?: pb<1, uint_32>;
properties?: pb<2, ObservedStatusProperties>;
uin?: pb<3, uint_32>;
}
interface ObservedStatusResponse {
data?: pb<1, ObservedStatusData>;
}
function makeSender(body?: OidbStrangerStatusResp) {
const responseData =
body !== undefined
? Buffer.from(protobuf_encode<OidbBase<OidbStrangerStatusResp>>({ body }))
: Buffer.alloc(0);
const r: SendPacketResult = {
success: true,
gotResponse: true,
errorCode: 0,
errorMessage: '',
responseData,
};
return { sendRawPacket: vi.fn(async () => r) };
}
describe('GetStrangerStatus namespace', () => {
it('declares 0xFE1_2 with uinForm=true', () => {
expect(GetStrangerStatus.command).toBe(0xfe1);
expect(GetStrangerStatus.subCommand).toBe(2);
expect(GetStrangerStatus.uinForm).toBe(true);
});
describe('serialize', () => {
it('always queries only key 27372 (status property)', () => {
const out = GetStrangerStatus.serialize({} as any, { uin: 100200 });
expect(out).toEqual({ uin: 100200, key: [{ key: 27372 }] });
});
});
describe('deserialize', () => {
it('low-band (≤10) values map to (value*10, 0)', () => {
expect(
GetStrangerStatus.deserialize({} as any, {
data: { properties: { entries: [{ key: 27372, value: 7n }] } },
}),
).toEqual({ status: 70, ext_status: 0 });
expect(
GetStrangerStatus.deserialize({} as any, {
data: { properties: { entries: [{ key: 27372, value: 10n }] } },
}),
).toEqual({ status: 100, ext_status: 0 });
});
it('high-band values decompose into (0xff00 + (>>16 & 0xff)) ext_status', () => {
// 0x42F100 → bits 8..15 = 0xF1<<8 = 0xF100, bits 16..23 = 0x42 → 0xF142
expect(
GetStrangerStatus.deserialize({} as any, {
data: { properties: { entries: [{ key: 27372, value: 0x42f100n }] } },
}),
).toEqual({ status: 10, ext_status: 0xf142 });
});
it('returns null when the requested status property is missing', () => {
expect(GetStrangerStatus.deserialize({} as any, {})).toBeNull();
expect(GetStrangerStatus.deserialize({} as any, { data: {} })).toBeNull();
expect(
GetStrangerStatus.deserialize({} as any, { data: { properties: {} } }),
).toBeNull();
expect(
GetStrangerStatus.deserialize({} as any, {
data: { properties: { entries: [{ key: 10000, value: 7n }] } },
}),
).toBeNull();
});
});
describe('invoke (e2e)', () => {
it('decodes the property-list hierarchy returned by 0xFE1_2', async () => {
const responseData = Buffer.from(
protobuf_encode<OidbBase<ObservedStatusResponse>>({
body: {
data: {
targetUin: 100200,
properties: {
entries: [
{ key: 10000, value: 1n },
{ key: 27372, value: 4n },
],
},
uin: 100200,
},
},
}),
);
const result: SendPacketResult = {
success: true,
gotResponse: true,
errorCode: 0,
errorMessage: '',
responseData,
};
const sender = { sendRawPacket: vi.fn(async () => result) };
await expect(GetStrangerStatus.invoke(sender, { uin: 100200 })).resolves.toEqual({
status: 40,
ext_status: 0,
});
});
it('routes to OidbSvcTrpcTcp.0xfe1_2 with uinForm=true (envelope reserved=1)', async () => {
const sender = makeSender({
data: { properties: { entries: [{ key: 27372, value: 5n }] } },
});
await GetStrangerStatus.invoke(sender, { uin: 100200 });
const [wireName, bytes] = sender.sendRawPacket.mock.calls[0]!;
expect(wireName).toBe('OidbSvcTrpcTcp.0xfe1_2');
const env = protobuf_decode<OidbBase<OidbStrangerStatusReq>>(bytes);
expect(env.reserved).toBe(1);
});
it('returns the decoded status object', async () => {
const sender = makeSender({
data: { properties: { entries: [{ key: 27372, value: 5n }] } },
});
const out = await GetStrangerStatus.invoke(sender, { uin: 1 });
expect(out).toEqual({ status: 50, ext_status: 0 });
});
});
});
@@ -0,0 +1,73 @@
import { describe, expect, it, vi } from 'vitest';
import { protobuf_decode } from '@snowluma/proton';
import type { OidbBase } from '@snowluma/proto-defs/oidb';
import type { OidbGroupTodo } from '@snowluma/proto-defs/oidb-actions/base';
import type { SendPacketResult } from '@snowluma/common/packet-sender';
import { GroupTodo } from '../../../src/oidb-services/extras/group-todo';
function makeSender() {
const r: SendPacketResult = {
success: true,
gotResponse: true,
errorCode: 0,
errorMessage: '',
responseData: Buffer.alloc(0),
};
return { sendRawPacket: vi.fn(async () => r) };
}
describe('GroupTodo namespace', () => {
it('declares 0xF90', () => {
expect(GroupTodo.command).toBe(0xf90);
});
describe('resolveSubCommand', () => {
it('maps set → 1', () => {
expect(GroupTodo.resolveSubCommand({ groupId: 1, msgSeq: 1n, action: 'set' })).toBe(1);
});
it('maps complete → 2', () => {
expect(
GroupTodo.resolveSubCommand({ groupId: 1, msgSeq: 1n, action: 'complete' }),
).toBe(2);
});
it('maps cancel → 3', () => {
expect(GroupTodo.resolveSubCommand({ groupId: 1, msgSeq: 1n, action: 'cancel' })).toBe(
3,
);
});
});
describe('serialize', () => {
it('packages groupUin + msgSeq verbatim regardless of action', () => {
for (const action of ['set', 'complete', 'cancel'] as const) {
const out = GroupTodo.serialize({} as any, {
groupId: 12345,
msgSeq: 9876543210n,
action,
});
expect(out).toEqual({ groupUin: 12345, msgSeq: 9876543210n });
}
});
});
describe('invoke (e2e)', () => {
it.each([
['set', 'OidbSvcTrpcTcp.0xf90_1'] as const,
['complete', 'OidbSvcTrpcTcp.0xf90_2'] as const,
['cancel', 'OidbSvcTrpcTcp.0xf90_3'] as const,
])('action %s routes to %s', async (action, expectedWire) => {
const sender = makeSender();
await GroupTodo.invoke(sender, { groupId: 12345, msgSeq: 100n, action });
expect(sender.sendRawPacket.mock.calls[0]![0]).toBe(expectedWire);
});
it('encodes envelope body correctly', async () => {
const sender = makeSender();
await GroupTodo.invoke(sender, { groupId: 100, msgSeq: 999n, action: 'set' });
const [, bytes] = sender.sendRawPacket.mock.calls[0]!;
const env = protobuf_decode<OidbBase<OidbGroupTodo>>(bytes);
expect(env.body).toEqual({ groupUin: 100, msgSeq: 999n });
});
});
});