26 包 monorepo(TS 全源码): - snowcat-core: NT 内核 API 封装 + listener(含 9.9.37 六回调适配)+ Packet 发包 - snowcat-onebot: OneBot v11 全动作 + MCP Streamable HTTP 适配器 + 稳定线 - snowcat-webui-frontend/backend: 雪猫控制台(React+HeroUI) - snowcat-shell: 单文件 bundle 出口(vite inlineDynamicImports) - snowcat-native: napi2native / MoeHoo / ffmpeg / pty 运行时 - 56 项 SnowLuma 独门动作移植(qzone 全家桶/群相册/装扮/闪照/收藏) - 工具件: 解包器/启动器/便携制作器/doctor GitHub 关联已全量指向 DandanLLab/SnowCatQQ
171 lines
5.6 KiB
TypeScript
171 lines
5.6 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import { NapProtoDecodeStructType, NapProtoEncodeStructType, NapProtoMsg } from 'snowcat-protobuf';
|
|
import { ElementType, NTVideoType } from '@/snowcat-core/types/msg';
|
|
import { SendVideoElement } from '@/snowcat-core/types/element';
|
|
import {
|
|
PacketMsgPicElement,
|
|
PacketMsgVideoElement,
|
|
} from '@/snowcat-core/packet/message/element';
|
|
import { PacketMsgConverter } from '@/snowcat-core/packet/message/converter';
|
|
import { assertUploadResults } from '@/snowcat-core/packet/context/uploadResult';
|
|
import { Elem, MsgInfo } from '@/snowcat-core/packet/transformer/proto';
|
|
import { NTV2RichMediaReq } from '@/snowcat-core/packet/transformer/proto/oidb/common/Ntv2.RichMediaReq';
|
|
import UploadGroupVideo from '@/snowcat-core/packet/transformer/highway/UploadGroupVideo';
|
|
import OidbBase from '@/snowcat-core/packet/transformer/oidb/oidbBase';
|
|
|
|
function createVideoMsgInfo (): NapProtoEncodeStructType<typeof MsgInfo> {
|
|
return {
|
|
msgInfoBody: [
|
|
{
|
|
index: {
|
|
info: {
|
|
fileSize: 1024,
|
|
fileHash: '00112233445566778899aabbccddeeff',
|
|
fileSha1: '00112233445566778899aabbccddeeff00112233',
|
|
fileName: 'clip.mp4',
|
|
type: {
|
|
type: 2,
|
|
videoFormat: NTVideoType.VIDEO_FORMAT_MP4,
|
|
},
|
|
width: 1920,
|
|
height: 1080,
|
|
time: 12,
|
|
},
|
|
fileUuid: 'video-uuid',
|
|
},
|
|
},
|
|
{
|
|
index: {
|
|
info: {
|
|
fileSize: 2048,
|
|
fileHash: 'ffeeddccbbaa99887766554433221100',
|
|
fileSha1: 'ffeeddccbbaa99887766554433221100ffeeddcc',
|
|
fileName: 'thumb.jpg',
|
|
type: {
|
|
type: 1,
|
|
picFormat: 0,
|
|
},
|
|
width: 640,
|
|
height: 360,
|
|
},
|
|
fileUuid: 'thumb-uuid',
|
|
},
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
function createSendVideoElement (): SendVideoElement {
|
|
return {
|
|
elementType: ElementType.VIDEO,
|
|
elementId: '',
|
|
videoElement: {
|
|
filePath: 'clip.mp4',
|
|
fileName: 'clip.mp4',
|
|
videoMd5: '00112233445566778899aabbccddeeff',
|
|
thumbMd5: 'ffeeddccbbaa99887766554433221100',
|
|
fileTime: 12,
|
|
fileFormat: NTVideoType.VIDEO_FORMAT_MP4,
|
|
fileSize: '1024',
|
|
fileWidth: 1920,
|
|
fileHeight: 1080,
|
|
thumbWidth: 640,
|
|
thumbHeight: 360,
|
|
thumbSize: 2048,
|
|
thumbPath: new Map([[0, 'thumb.jpg']]),
|
|
},
|
|
};
|
|
}
|
|
|
|
function decodeElem (
|
|
elem: NapProtoEncodeStructType<typeof Elem>
|
|
): NapProtoDecodeStructType<typeof Elem> {
|
|
const codec = new NapProtoMsg(Elem);
|
|
return codec.decode(codec.encode(elem));
|
|
}
|
|
|
|
function createCommonVideoElem (businessType: 11 | 21): NapProtoDecodeStructType<typeof Elem> {
|
|
return decodeElem({
|
|
commonElem: {
|
|
serviceType: 48,
|
|
businessType,
|
|
pbElem: new NapProtoMsg(MsgInfo).encode(createVideoMsgInfo()),
|
|
},
|
|
});
|
|
}
|
|
|
|
describe('packet video forwarding', () => {
|
|
it('emits only the common rich-media element so mobile rendering is not blocked', () => {
|
|
const video = new PacketMsgVideoElement(createSendVideoElement());
|
|
video.msgInfo = createVideoMsgInfo();
|
|
video.businessType = 11;
|
|
|
|
const elems = video.buildElement();
|
|
|
|
expect(elems).toHaveLength(1);
|
|
expect(elems[0]?.videoFile).toBeUndefined();
|
|
expect(elems[0]?.commonElem?.serviceType).toBe(48);
|
|
expect(elems[0]?.commonElem?.businessType).toBe(11);
|
|
expect(video.valid).toBe(true);
|
|
});
|
|
|
|
it('parses common business type 11/21 as video instead of image', () => {
|
|
for (const businessType of [11, 21] as const) {
|
|
const commonElem = createCommonVideoElem(businessType);
|
|
expect(PacketMsgPicElement.parseElement(commonElem)).toBeUndefined();
|
|
|
|
const parsed = new PacketMsgConverter().packetMsgToRaw([commonElem]);
|
|
|
|
expect(parsed).toHaveLength(1);
|
|
expect(parsed[0]?.[0].elementType).toBe(ElementType.VIDEO);
|
|
expect(parsed[0]?.[0].picElement).toBeUndefined();
|
|
expect(parsed[0]?.[0].videoElement).toMatchObject({
|
|
fileName: 'clip.mp4',
|
|
fileTime: 12,
|
|
fileWidth: 1920,
|
|
fileHeight: 1080,
|
|
thumbWidth: 640,
|
|
thumbHeight: 360,
|
|
fileUuid: 'video-uuid',
|
|
});
|
|
}
|
|
});
|
|
|
|
it('preserves video metadata in the rich-media upload request', () => {
|
|
const video = new PacketMsgVideoElement(createSendVideoElement());
|
|
video.fileSha1 = '00112233445566778899aabbccddeeff00112233';
|
|
video.thumbSha1 = 'ffeeddccbbaa99887766554433221100ffeeddcc';
|
|
|
|
const packet = UploadGroupVideo.build(123456, video);
|
|
const envelope = OidbBase.parse(packet.data);
|
|
const request = new NapProtoMsg(NTV2RichMediaReq).decode(envelope.body);
|
|
const videoInfo = request.upload?.uploadInfo[0]?.fileInfo;
|
|
const thumbInfo = request.upload?.uploadInfo[1]?.fileInfo;
|
|
|
|
expect(videoInfo).toMatchObject({
|
|
fileName: 'clip.mp4',
|
|
fileSize: 1024,
|
|
width: 1920,
|
|
height: 1080,
|
|
time: 12,
|
|
});
|
|
expect(videoInfo?.type.videoFormat).toBe(NTVideoType.VIDEO_FORMAT_MP4);
|
|
expect(thumbInfo?.fileName).toBe('ffeeddccbbaa99887766554433221100.jpg');
|
|
});
|
|
|
|
it('propagates resource upload failures instead of silently continuing', () => {
|
|
const uploadError = new Error('thumb upload failed');
|
|
const logger = {
|
|
warn: vi.fn(),
|
|
error: vi.fn(),
|
|
};
|
|
|
|
expect(() => assertUploadResults([
|
|
{ status: 'fulfilled', value: undefined },
|
|
{ status: 'rejected', reason: uploadError },
|
|
], logger)).toThrow('上传转发消息资源失败');
|
|
expect(logger.warn).toHaveBeenCalledWith('上传资源2个,失败1个');
|
|
expect(logger.error).toHaveBeenCalledOnce();
|
|
});
|
|
});
|