fix(bridge): preserve full ext-biz-info on media upload round-trip

The NTV2 upload-response decoder used stub schemas that omitted fields
QQ NT echoes back (notably video.fromScene/toScene, pic.fromScene/
toScene/oldFileId, the ptt scene/waveform set), so finalizeMediaMsgInfo
re-encoded the commonElem.pbElem with those fields missing. For c2c
video this triggered `send private message rejected: result=79` on
every send_private_msg [{type:'video'}] call — the upload itself
fast-pathed fine but the follow-up SendMessage was rejected by NT.

Drop the duplicate NTV2 placeholder schemas and route NTV2ExtBizInfo
through the element-side PicExtBizInfo / VideoExtBizInfo /
PttExtBizInfo, which are field-complete. Also zero out the main
video sub-file's width/height/time/videoFormat in the upload request
to mirror NapCat — those fields belong on the thumb, not the source
mp4, and non-zero values trip schema validation on c2c sends.
This commit is contained in:
DrSmoothl
2026-05-13 22:19:45 +08:00
parent e438f353af
commit 00a70b1ccc
2 changed files with 18 additions and 32 deletions
@@ -460,10 +460,14 @@ export async function uploadVideoMsgInfo(
fileHash: video.md5Hex,
fileSha1: video.sha1Hex,
fileName: 'nya.mp4',
type: { type: 2, picFormat: 0, videoFormat: video.videoFormat, voiceFormat: 0 },
height: video.height,
width: video.width,
time: video.duration,
// NapCat sends type.videoFormat=0 and width/height/time=0
// for the main video sub-file (only the thumb carries
// dimensions). The QQ NT server treats non-zero values
// here as a schema mismatch on c2c sends.
type: { type: 2, picFormat: 0, videoFormat: 0, voiceFormat: 0 },
height: 0,
width: 0,
time: 0,
original: 0,
},
subFileType: 0,
+10 -28
View File
@@ -234,35 +234,17 @@ export const NTV2UploadInfoSchema = {
subFileType: { field: 2, type: 'uint32' as const },
} satisfies ProtoSchema;
export const NTV2PicReserveC2CSchema = {
subType: { field: 1, type: 'uint32' as const },
} satisfies ProtoSchema;
export const NTV2PicReserveTroopSchema = {
subType: { field: 1, type: 'uint32' as const },
} satisfies ProtoSchema;
export const NTV2PicExtBizInfoSchema = {
bizType: { field: 1, type: 'uint32' as const },
textSummary: { field: 2, type: 'string' as const },
reserveC2c: { field: 11, type: 'message' as const, schema: NTV2PicReserveC2CSchema },
reserveTroop: { field: 12, type: 'message' as const, schema: NTV2PicReserveTroopSchema },
} satisfies ProtoSchema;
export const NTV2VideoExtBizInfoSchema = {
bytesPbReserve: { field: 3, type: 'bytes' as const },
} satisfies ProtoSchema;
export const NTV2PttExtBizInfoSchema = {
bytesReserve: { field: 11, type: 'bytes' as const },
bytesPbReserve: { field: 12, type: 'bytes' as const },
bytesGeneralFlags: { field: 13, type: 'bytes' as const },
} satisfies ProtoSchema;
// The NTV2 upload request + response carry the same ExtBizInfo shape that
// element-side incoming-message decoding uses. Pulling those schemas in
// directly (instead of redefining stub copies) keeps the round-trip
// `server response → finalizeMediaMsgInfo → commonElem.pbElem` from
// silently dropping fields the QQ NT server later validates against —
// the video `fromScene`/`toScene` drop was already responsible for c2c
// `result=79` rejections on `send_private_msg [{type:'video'}]`.
export const NTV2ExtBizInfoSchema = {
pic: { field: 1, type: 'message' as const, schema: NTV2PicExtBizInfoSchema },
video: { field: 2, type: 'message' as const, schema: NTV2VideoExtBizInfoSchema },
ptt: { field: 3, type: 'message' as const, schema: NTV2PttExtBizInfoSchema },
pic: { field: 1, type: 'message' as const, schema: PicExtBizInfoSchema },
video: { field: 2, type: 'message' as const, schema: VideoExtBizInfoSchema },
ptt: { field: 3, type: 'message' as const, schema: PttExtBizInfoSchema },
busiType: { field: 10, type: 'uint32' as const },
} satisfies ProtoSchema;