mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2024-11-21 09:36:35 +00:00
feat & fix: add video msg pack & upload, fix some bugs in uploading c2c elements
This commit is contained in:
parent
5190b26399
commit
4082b651c5
@ -12,7 +12,7 @@ import { LogWrapper } from "@/common/log";
|
||||
import { SendLongMsgResp } from "@/core/packet/proto/message/action";
|
||||
import { PacketMsg } from "@/core/packet/msg/message";
|
||||
import { OidbSvcTrpcTcp0x6D6Response } from "@/core/packet/proto/oidb/Oidb.0x6D6";
|
||||
import { PacketMsgPicElement } from "@/core/packet/msg/element";
|
||||
import { PacketMsgPicElement, PacketMsgVideoElement } from "@/core/packet/msg/element";
|
||||
|
||||
|
||||
interface OffsetType {
|
||||
@ -118,7 +118,13 @@ export class NTQQPacketApi {
|
||||
if (e instanceof PacketMsgPicElement) {
|
||||
reqList.push(this.packetSession?.highwaySession.uploadImage({
|
||||
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
|
||||
peerUid: String(groupUin) ? String(groupUin) : this.core.selfInfo.uid
|
||||
peerUid: groupUin ? String(groupUin) : this.core.selfInfo.uid
|
||||
}, e));
|
||||
}
|
||||
if (e instanceof PacketMsgVideoElement) {
|
||||
reqList.push(this.packetSession?.highwaySession.uploadVideo({
|
||||
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
|
||||
peerUid: groupUin ? String(groupUin) : this.core.selfInfo.uid
|
||||
}, e));
|
||||
}
|
||||
}
|
||||
|
@ -8,9 +8,10 @@ import { HttpConn0x6ff_501Response } from "@/core/packet/proto/action/action";
|
||||
import { PacketHighwayClient } from "@/core/packet/highway/client";
|
||||
import { NTV2RichMediaResp } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaResp";
|
||||
import { OidbSvcTrpcTcpBaseRsp } from "@/core/packet/proto/oidb/OidbBase";
|
||||
import { PacketMsgPicElement } from "@/core/packet/msg/element";
|
||||
import { PacketMsgPicElement, PacketMsgVideoElement } from "@/core/packet/msg/element";
|
||||
import { NTV2RichMediaHighwayExt } from "@/core/packet/proto/highway/highway";
|
||||
import { int32ip2str, oidbIpv4s2HighwayIpv4s } from "@/core/packet/highway/utils";
|
||||
import { calculateSha1StreamBytes } from "@/core/packet/utils/crypto/hash";
|
||||
|
||||
export const BlockSize = 1024 * 1024;
|
||||
|
||||
@ -94,6 +95,17 @@ export class PacketHighwaySession {
|
||||
}
|
||||
}
|
||||
|
||||
async uploadVideo(peer: Peer, video: PacketMsgVideoElement): Promise<void> {
|
||||
await this.checkAvailable();
|
||||
if (peer.chatType === ChatType.KCHATTYPEGROUP) {
|
||||
await this.uploadGroupVideoReq(Number(peer.peerUid), video);
|
||||
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
|
||||
await this.uploadC2CVideoReq(peer.peerUid, video);
|
||||
} else {
|
||||
throw new Error(`[Highway] unsupported chatType: ${peer.chatType}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async uploadGroupImageReq(groupUin: number, img: PacketMsgPicElement): Promise<void> {
|
||||
const preReq = await this.packer.packUploadGroupImgReq(groupUin, img);
|
||||
const preRespRaw = await this.packetClient.sendPacket('OidbSvcTrpcTcp.0x11c4_100', preReq, true);
|
||||
@ -103,7 +115,7 @@ export class PacketHighwaySession {
|
||||
const preRespData = new NapProtoMsg(NTV2RichMediaResp).decode(preResp.body);
|
||||
const ukey = preRespData.upload.uKey;
|
||||
if (ukey && ukey != "") {
|
||||
this.logger.logDebug(`[Highway] get upload ukey: ${ukey}, need upload!`);
|
||||
this.logger.logDebug(`[Highway] uploadGroupImageReq get upload ukey: ${ukey}, need upload!`);
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||
@ -121,13 +133,13 @@ export class PacketHighwaySession {
|
||||
});
|
||||
await this.packetHighwayClient.upload(
|
||||
1004,
|
||||
fs.createReadStream(img.path, { highWaterMark: BlockSize }),
|
||||
fs.createReadStream(img.path, {highWaterMark: BlockSize}),
|
||||
img.size,
|
||||
md5,
|
||||
extend
|
||||
);
|
||||
} else {
|
||||
this.logger.logDebug(`[Highway] get upload invalid ukey ${ukey}, don't need upload!`);
|
||||
this.logger.logDebug(`[Highway] uploadGroupImageReq get upload invalid ukey ${ukey}, don't need upload!`);
|
||||
}
|
||||
img.msgInfo = preRespData.upload.msgInfo;
|
||||
// img.groupPicExt = new NapProtoMsg(CustomFace).decode(preRespData.tcpUpload.compatQMsg)
|
||||
@ -142,7 +154,7 @@ export class PacketHighwaySession {
|
||||
const preRespData = new NapProtoMsg(NTV2RichMediaResp).decode(preResp.body);
|
||||
const ukey = preRespData.upload.uKey;
|
||||
if (ukey && ukey != "") {
|
||||
this.logger.logDebug(`[Highway] get upload ukey: ${ukey}, need upload!`);
|
||||
this.logger.logDebug(`[Highway] uploadC2CImageReq get upload ukey: ${ukey}, need upload!`);
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||
@ -160,12 +172,145 @@ export class PacketHighwaySession {
|
||||
});
|
||||
await this.packetHighwayClient.upload(
|
||||
1003,
|
||||
fs.createReadStream(img.path, { highWaterMark: BlockSize }),
|
||||
fs.createReadStream(img.path, {highWaterMark: BlockSize}),
|
||||
img.size,
|
||||
md5,
|
||||
extend
|
||||
);
|
||||
} else {
|
||||
this.logger.logDebug(`[Highway] uploadC2CImageReq get upload invalid ukey ${ukey}, don't need upload!`);
|
||||
}
|
||||
img.msgInfo = preRespData.upload.msgInfo;
|
||||
}
|
||||
|
||||
private async uploadGroupVideoReq(groupUin: number, video: PacketMsgVideoElement): Promise<void> {
|
||||
const preReq = await this.packer.packUploadGroupVideoReq(groupUin, video);
|
||||
const preRespRaw = await this.packetClient.sendPacket('OidbSvcTrpcTcp.0x11ea_100', preReq, true);
|
||||
const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(
|
||||
Buffer.from(preRespRaw.hex_data, 'hex')
|
||||
);
|
||||
const preRespData = new NapProtoMsg(NTV2RichMediaResp).decode(preResp.body);
|
||||
const ukey = preRespData.upload.uKey;
|
||||
if (ukey && ukey != "") {
|
||||
this.logger.logDebug(`[Highway] uploadGroupVideoReq get upload video ukey: ${ukey}, need upload!`);
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||
const extend = new NapProtoMsg(NTV2RichMediaHighwayExt).encode({
|
||||
fileUuid: index.fileUuid,
|
||||
uKey: ukey,
|
||||
network: {
|
||||
ipv4S: oidbIpv4s2HighwayIpv4s(preRespData.upload.ipv4S)
|
||||
},
|
||||
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
|
||||
blockSize: BlockSize,
|
||||
hash: {
|
||||
fileSha1: await calculateSha1StreamBytes(video.filePath!)
|
||||
}
|
||||
})
|
||||
await this.packetHighwayClient.upload(
|
||||
1005,
|
||||
fs.createReadStream(video.filePath!, {highWaterMark: BlockSize}),
|
||||
+video.fileSize!,
|
||||
md5,
|
||||
extend
|
||||
);
|
||||
} else {
|
||||
this.logger.logDebug(`[Highway] uploadGroupVideoReq get upload invalid ukey ${ukey}, don't need upload!`);
|
||||
}
|
||||
const subFile = preRespData.upload.subFileInfos[0];
|
||||
if (subFile.uKey && subFile.uKey != "") {
|
||||
this.logger.logDebug(`[Highway] uploadGroupVideoReq get upload video thumb ukey: ${subFile.uKey}, need upload!`);
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[1].index;
|
||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
||||
const extend = new NapProtoMsg(NTV2RichMediaHighwayExt).encode({
|
||||
fileUuid: index.fileUuid,
|
||||
uKey: subFile.uKey,
|
||||
network: {
|
||||
ipv4S: oidbIpv4s2HighwayIpv4s(subFile.ipv4S)
|
||||
},
|
||||
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
|
||||
blockSize: BlockSize,
|
||||
hash: {
|
||||
fileSha1: [sha1]
|
||||
}
|
||||
});
|
||||
await this.packetHighwayClient.upload(
|
||||
1006,
|
||||
fs.createReadStream(video.thumbPath!, {highWaterMark: BlockSize}),
|
||||
+video.thumbSize!,
|
||||
md5,
|
||||
extend
|
||||
);
|
||||
} else {
|
||||
this.logger.logDebug(`[Highway] uploadGroupVideoReq get upload invalid thumb ukey ${subFile.uKey}, don't need upload!`);
|
||||
}
|
||||
video.msgInfo = preRespData.upload.msgInfo;
|
||||
}
|
||||
|
||||
private async uploadC2CVideoReq(peerUid: string, video: PacketMsgVideoElement): Promise<void> {
|
||||
const preReq = await this.packer.packUploadC2CVideoReq(peerUid, video);
|
||||
const preRespRaw = await this.packetClient.sendPacket('OidbSvcTrpcTcp.0x11e9_100', preReq, true);
|
||||
console.log(preRespRaw);
|
||||
const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(
|
||||
Buffer.from(preRespRaw.hex_data, 'hex')
|
||||
);
|
||||
const preRespData = new NapProtoMsg(NTV2RichMediaResp).decode(preResp.body);
|
||||
const ukey = preRespData.upload.uKey;
|
||||
if (ukey && ukey != "") {
|
||||
this.logger.logDebug(`[Highway] uploadC2CVideoReq get upload video ukey: ${ukey}, need upload!`);
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||
const extend = new NapProtoMsg(NTV2RichMediaHighwayExt).encode({
|
||||
fileUuid: index.fileUuid,
|
||||
uKey: ukey,
|
||||
network: {
|
||||
ipv4S: oidbIpv4s2HighwayIpv4s(preRespData.upload.ipv4S)
|
||||
},
|
||||
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
|
||||
blockSize: BlockSize,
|
||||
hash: {
|
||||
fileSha1: await calculateSha1StreamBytes(video.filePath!)
|
||||
}
|
||||
})
|
||||
await this.packetHighwayClient.upload(
|
||||
1001,
|
||||
fs.createReadStream(video.filePath!, {highWaterMark: BlockSize}),
|
||||
+video.fileSize!,
|
||||
md5,
|
||||
extend
|
||||
);
|
||||
} else {
|
||||
this.logger.logDebug(`[Highway] uploadC2CVideoReq get upload invalid ukey ${ukey}, don't need upload!`);
|
||||
}
|
||||
const subFile = preRespData.upload.subFileInfos[0];
|
||||
if (subFile.uKey && subFile.uKey != "") {
|
||||
this.logger.logDebug(`[Highway] uploadC2CVideoReq get upload video thumb ukey: ${subFile.uKey}, need upload!`);
|
||||
const index = preRespData.upload.msgInfo.msgInfoBody[1].index;
|
||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
||||
const extend = new NapProtoMsg(NTV2RichMediaHighwayExt).encode({
|
||||
fileUuid: index.fileUuid,
|
||||
uKey: subFile.uKey,
|
||||
network: {
|
||||
ipv4S: oidbIpv4s2HighwayIpv4s(subFile.ipv4S)
|
||||
},
|
||||
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
|
||||
blockSize: BlockSize,
|
||||
hash: {
|
||||
fileSha1: [sha1]
|
||||
}
|
||||
});
|
||||
await this.packetHighwayClient.upload(
|
||||
1002,
|
||||
fs.createReadStream(video.thumbPath!, {highWaterMark: BlockSize}),
|
||||
+video.thumbSize!,
|
||||
md5,
|
||||
extend
|
||||
);
|
||||
} else {
|
||||
this.logger.logDebug(`[Highway] uploadC2CVideoReq get upload invalid thumb ukey ${subFile.uKey}, don't need upload!`);
|
||||
}
|
||||
video.msgInfo = preRespData.upload.msgInfo;
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ export abstract class IPacketMsgElement<T extends PacketSendMsgElement> {
|
||||
}
|
||||
|
||||
toPreview(): string {
|
||||
return '[nya~]';
|
||||
return '[暂不支持该消息类型喵~]';
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,19 +84,15 @@ export class PacketMsgAtElement extends PacketMsgTextElement {
|
||||
text: {
|
||||
str: this.text,
|
||||
pbReserve: new NapProtoMsg(MentionExtra).encode({
|
||||
type: this.atAll ? 1 : 2,
|
||||
uin: 0,
|
||||
field5: 0,
|
||||
uid: this.targetUid,
|
||||
}
|
||||
type: this.atAll ? 1 : 2,
|
||||
uin: 0,
|
||||
field5: 0,
|
||||
uid: this.targetUid,
|
||||
}
|
||||
)
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
toPreview(): string {
|
||||
return `@${this.targetUid} ${this.text}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class PacketMsgPicElement extends IPacketMsgElement<SendPicElement> {
|
||||
@ -189,7 +185,7 @@ export class PacketMsgReplyElement extends IPacketMsgElement<SendReplyElement> {
|
||||
}
|
||||
|
||||
toPreview(): string {
|
||||
return "[回复]";
|
||||
return "[回复消息]";
|
||||
}
|
||||
}
|
||||
|
||||
@ -285,8 +281,41 @@ export class PacketMsgMarkFaceElement extends IPacketMsgElement<SendMarketFaceEl
|
||||
}
|
||||
|
||||
export class PacketMsgVideoElement extends IPacketMsgElement<SendVideoElement> {
|
||||
fileSize?: string;
|
||||
filePath?: string;
|
||||
thumbSize?: number;
|
||||
thumbPath?: string;
|
||||
fileMd5?: string;
|
||||
thumbMd5?: string;
|
||||
thumbWidth?: number;
|
||||
thumbHeight?: number;
|
||||
msgInfo: NapProtoEncodeStructType<typeof MsgInfo> | null = null;
|
||||
|
||||
constructor(element: SendVideoElement) {
|
||||
super(element);
|
||||
this.fileSize = element.videoElement.fileSize;
|
||||
this.filePath = element.videoElement.filePath;
|
||||
this.thumbSize = element.videoElement.thumbSize;
|
||||
this.thumbPath = element.videoElement.thumbPath?.get(0);
|
||||
this.fileMd5 = element.videoElement.videoMd5
|
||||
this.thumbMd5 = element.videoElement.thumbMd5;
|
||||
this.thumbWidth = element.videoElement.thumbWidth;
|
||||
this.thumbHeight = element.videoElement.thumbHeight;
|
||||
}
|
||||
|
||||
buildElement(): NapProtoEncodeStructType<typeof Elem>[] {
|
||||
assert(this.msgInfo !== null, 'msgInfo is null, expected not null');
|
||||
return [{
|
||||
commonElem: {
|
||||
serviceType: 48,
|
||||
pbElem: new NapProtoMsg(MsgInfo).encode(this.msgInfo),
|
||||
businessType: 21,
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
toPreview(): string {
|
||||
return "[视频]";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ import { NTV2RichMediaReq } from "@/core/packet/proto/oidb/common/Ntv2.RichMedia
|
||||
import { HttpConn0x6ff_501 } from "@/core/packet/proto/action/action";
|
||||
import { LongMsgResult, SendLongMsgReq } from "@/core/packet/proto/message/action";
|
||||
import { PacketMsgBuilder } from "@/core/packet/msg/builder";
|
||||
import { PacketMsgPicElement } from "@/core/packet/msg/element";
|
||||
import { PacketMsgPicElement, PacketMsgVideoElement } from "@/core/packet/msg/element";
|
||||
import { LogWrapper } from "@/common/log";
|
||||
import { PacketMsg } from "@/core/packet/msg/message";
|
||||
import { OidbSvcTrpcTcp0x6D6 } from "@/core/packet/proto/oidb/Oidb.0x6D6";
|
||||
@ -289,6 +289,183 @@ export class PacketPacker {
|
||||
return this.toHexStr(this.packOidbPacket(0x11c5, 100, req, true, false));
|
||||
}
|
||||
|
||||
async packUploadGroupVideoReq(groupUin: number, video: PacketMsgVideoElement): Promise<PacketHexStr> {
|
||||
if (!video.filePath || !video.thumbPath) throw new Error("video.filePath or video.thumbPath is empty");
|
||||
if (!video.fileSize || !video.thumbSize) throw new Error("video.fileSize or video.thumbSize is empty");
|
||||
const videoSha1 = await calculateSha1(video.filePath ?? "");
|
||||
const videoThumbSha1 = await calculateSha1(video.thumbPath ?? "");
|
||||
const req = new NapProtoMsg(NTV2RichMediaReq).encode({
|
||||
reqHead: {
|
||||
common: {
|
||||
requestId: 3,
|
||||
command: 100
|
||||
},
|
||||
scene: {
|
||||
requestType: 2,
|
||||
businessType: 2,
|
||||
sceneType: 2,
|
||||
group: {
|
||||
groupUin: groupUin
|
||||
},
|
||||
},
|
||||
client: {
|
||||
agentType: 2
|
||||
}
|
||||
},
|
||||
upload: {
|
||||
uploadInfo: [
|
||||
{
|
||||
fileInfo: {
|
||||
fileSize: +video.fileSize,
|
||||
fileHash: video.fileMd5,
|
||||
fileSha1: this.toHexStr(videoSha1),
|
||||
fileName: "nya.mp4",
|
||||
type: {
|
||||
type: 2,
|
||||
picFormat: 0,
|
||||
videoFormat: 0,
|
||||
voiceFormat: 0
|
||||
},
|
||||
height: 0,
|
||||
width: 0,
|
||||
time: 0,
|
||||
original: 0
|
||||
},
|
||||
subFileType: 0
|
||||
}, {
|
||||
fileInfo: {
|
||||
fileSize: +video.thumbSize,
|
||||
fileHash: video.thumbMd5,
|
||||
fileSha1: this.toHexStr(videoThumbSha1),
|
||||
fileName: "nya.jpg",
|
||||
type: {
|
||||
type: 1,
|
||||
picFormat: 0,
|
||||
videoFormat: 0,
|
||||
voiceFormat: 0
|
||||
},
|
||||
height: video.thumbHeight,
|
||||
width: video.thumbWidth,
|
||||
time: 0,
|
||||
original: 0
|
||||
},
|
||||
subFileType: 100
|
||||
}
|
||||
],
|
||||
tryFastUploadCompleted: true,
|
||||
srvSendMsg: false,
|
||||
clientRandomId: crypto.randomBytes(8).readBigUInt64BE() & BigInt('0x7FFFFFFFFFFFFFFF'),
|
||||
compatQMsgSceneType: 2,
|
||||
extBizInfo: {
|
||||
pic : {
|
||||
bizType: 0,
|
||||
textSummary: "Nya~",
|
||||
},
|
||||
video: {
|
||||
bytesPbReserve: Buffer.from([0x80, 0x01, 0x00]),
|
||||
},
|
||||
ptt: {
|
||||
bytesPbReserve: Buffer.alloc(0),
|
||||
bytesReserve: Buffer.alloc(0),
|
||||
bytesGeneralFlags: Buffer.alloc(0),
|
||||
}
|
||||
},
|
||||
clientSeq: 0,
|
||||
noNeedCompatMsg: false
|
||||
}
|
||||
});
|
||||
return this.toHexStr(this.packOidbPacket(0x11EA, 100, req, true, false));
|
||||
}
|
||||
|
||||
async packUploadC2CVideoReq(peerUin: string, video: PacketMsgVideoElement): Promise<PacketHexStr> {
|
||||
if (!video.filePath || !video.thumbPath) throw new Error("video.filePath or video.thumbPath is empty");
|
||||
if (!video.fileSize || !video.thumbSize) throw new Error("video.fileSize or video.thumbSize is empty");
|
||||
const videoSha1 = await calculateSha1(video.filePath ?? "");
|
||||
const videoThumbSha1 = await calculateSha1(video.thumbPath ?? "");
|
||||
const req = new NapProtoMsg(NTV2RichMediaReq).encode({
|
||||
reqHead: {
|
||||
common: {
|
||||
requestId: 3,
|
||||
command: 100
|
||||
},
|
||||
scene: {
|
||||
requestType: 2,
|
||||
businessType: 2,
|
||||
sceneType: 1,
|
||||
c2C: {
|
||||
accountType: 2,
|
||||
targetUid: peerUin
|
||||
}
|
||||
},
|
||||
client: {
|
||||
agentType: 2
|
||||
}
|
||||
},
|
||||
upload: {
|
||||
uploadInfo: [
|
||||
{
|
||||
fileInfo: {
|
||||
fileSize: +video.fileSize,
|
||||
fileHash: video.fileMd5,
|
||||
fileSha1: this.toHexStr(videoSha1),
|
||||
fileName: "nya.mp4",
|
||||
type: {
|
||||
type: 2,
|
||||
picFormat: 0,
|
||||
videoFormat: 0,
|
||||
voiceFormat: 0
|
||||
},
|
||||
height: 0,
|
||||
width: 0,
|
||||
time: 0,
|
||||
original: 0
|
||||
},
|
||||
subFileType: 0
|
||||
}, {
|
||||
fileInfo: {
|
||||
fileSize: +video.thumbSize,
|
||||
fileHash: video.thumbMd5,
|
||||
fileSha1: this.toHexStr(videoThumbSha1),
|
||||
fileName: "nya.jpg",
|
||||
type: {
|
||||
type: 1,
|
||||
picFormat: 0,
|
||||
videoFormat: 0,
|
||||
voiceFormat: 0
|
||||
},
|
||||
height: video.thumbHeight,
|
||||
width: video.thumbWidth,
|
||||
time: 0,
|
||||
original: 0
|
||||
},
|
||||
subFileType: 100
|
||||
}
|
||||
],
|
||||
tryFastUploadCompleted: true,
|
||||
srvSendMsg: false,
|
||||
clientRandomId: crypto.randomBytes(8).readBigUInt64BE() & BigInt('0x7FFFFFFFFFFFFFFF'),
|
||||
compatQMsgSceneType: 2,
|
||||
extBizInfo: {
|
||||
pic : {
|
||||
bizType: 0,
|
||||
textSummary: "Nya~",
|
||||
},
|
||||
video: {
|
||||
bytesPbReserve: Buffer.from([0x80, 0x01, 0x00]),
|
||||
},
|
||||
ptt: {
|
||||
bytesPbReserve: Buffer.alloc(0),
|
||||
bytesReserve: Buffer.alloc(0),
|
||||
bytesGeneralFlags: Buffer.alloc(0),
|
||||
}
|
||||
},
|
||||
clientSeq: 0,
|
||||
noNeedCompatMsg: false
|
||||
}
|
||||
});
|
||||
return this.toHexStr(this.packOidbPacket(0x11E9, 100, req, true, false));
|
||||
}
|
||||
|
||||
packGroupFileDownloadReq(groupUin: number, fileUUID: string): PacketHexStr {
|
||||
return this.toHexStr(
|
||||
this.packOidbPacket(0x6D6, 2, new NapProtoMsg(OidbSvcTrpcTcp0x6D6).encode({
|
||||
|
@ -2,6 +2,7 @@
|
||||
import * as crypto from 'crypto';
|
||||
import * as stream from 'stream';
|
||||
import * as fs from 'fs';
|
||||
import {CalculateStreamBytesTransform} from "@/core/packet/utils/crypto/sha1StreamBytesTransform";
|
||||
|
||||
function sha1Stream(readable: stream.Readable) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@ -14,3 +15,21 @@ export function calculateSha1(filePath: string): Promise<Buffer> {
|
||||
const readable = fs.createReadStream(filePath);
|
||||
return sha1Stream(readable);
|
||||
}
|
||||
|
||||
export function calculateSha1StreamBytes(filePath: string): Promise<Buffer[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const readable = fs.createReadStream(filePath);
|
||||
const calculateStreamBytes = new CalculateStreamBytesTransform();
|
||||
const byteArrayList: Buffer[] = [];
|
||||
calculateStreamBytes.on('data', (chunk: Buffer) => {
|
||||
byteArrayList.push(chunk);
|
||||
});
|
||||
calculateStreamBytes.on('end', () => {
|
||||
resolve(byteArrayList);
|
||||
});
|
||||
calculateStreamBytes.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
readable.pipe(calculateStreamBytes);
|
||||
});
|
||||
}
|
||||
|
19
src/core/packet/utils/crypto/sha1Stream.test.ts
Normal file
19
src/core/packet/utils/crypto/sha1Stream.test.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import crypto from 'crypto';
|
||||
import assert from 'assert';
|
||||
import { Sha1Stream } from './sha1Stream';
|
||||
|
||||
function testSha1Stream() {
|
||||
for (let i = 0; i < 100000; i++) {
|
||||
const randomLength = Math.floor(Math.random() * 1024);
|
||||
const randomData = crypto.randomBytes(randomLength);
|
||||
const sha1Stream = new Sha1Stream();
|
||||
sha1Stream.update(randomData);
|
||||
const hash = sha1Stream.final();
|
||||
const expectedDigest = crypto.createHash('sha1').update(randomData).digest();
|
||||
assert.strictEqual(hash.toString('hex'), expectedDigest.toString('hex'));
|
||||
console.log(`Test ${i + 1}: Passed`);
|
||||
}
|
||||
console.log('All tests passed successfully.');
|
||||
}
|
||||
|
||||
testSha1Stream();
|
118
src/core/packet/utils/crypto/sha1Stream.ts
Normal file
118
src/core/packet/utils/crypto/sha1Stream.ts
Normal file
@ -0,0 +1,118 @@
|
||||
export class Sha1Stream {
|
||||
readonly Sha1BlockSize = 64;
|
||||
readonly Sha1DigestSize = 20;
|
||||
private readonly _padding = Buffer.concat([Buffer.from([0x80]), Buffer.alloc(63)]);
|
||||
private readonly _state = new Uint32Array(5);
|
||||
private readonly _count = new Uint32Array(2);
|
||||
private readonly _buffer = Buffer.allocUnsafe(this.Sha1BlockSize);
|
||||
private readonly _w = new Uint32Array(80);
|
||||
|
||||
constructor() {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
private reset(): void {
|
||||
this._state[0] = 0x67452301;
|
||||
this._state[1] = 0xEFCDAB89;
|
||||
this._state[2] = 0x98BADCFE;
|
||||
this._state[3] = 0x10325476;
|
||||
this._state[4] = 0xC3D2E1F0;
|
||||
this._count[0] = 0;
|
||||
this._count[1] = 0;
|
||||
this._buffer.fill(0);
|
||||
}
|
||||
|
||||
private rotateLeft(v: number, o: number): number {
|
||||
return ((v << o) | (v >>> (32 - o))) >>> 0;
|
||||
}
|
||||
|
||||
private transform(chunk: Buffer, offset: number): void {
|
||||
const w = this._w;
|
||||
const view = new DataView(chunk.buffer, chunk.byteOffset + offset, 64);
|
||||
|
||||
for (let i = 0; i < 16; i++) {
|
||||
w[i] = view.getUint32(i * 4, false);
|
||||
}
|
||||
|
||||
for (let i = 16; i < 80; i++) {
|
||||
w[i] = this.rotateLeft(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1) >>> 0;
|
||||
}
|
||||
|
||||
let a = this._state[0];
|
||||
let b = this._state[1];
|
||||
let c = this._state[2];
|
||||
let d = this._state[3];
|
||||
let e = this._state[4];
|
||||
|
||||
for (let i = 0; i < 80; i++) {
|
||||
const [f, k] = (i < 20) ? [(b & c) | ((~b) & d), 0x5A827999] :
|
||||
(i < 40) ? [b ^ c ^ d, 0x6ED9EBA1] :
|
||||
(i < 60) ? [(b & c) | (b & d) | (c & d), 0x8F1BBCDC] :
|
||||
[b ^ c ^ d, 0xCA62C1D6];
|
||||
const temp = (this.rotateLeft(a, 5) + f + k + e + w[i]) >>> 0;
|
||||
e = d;
|
||||
d = c;
|
||||
c = this.rotateLeft(b, 30) >>> 0;
|
||||
b = a;
|
||||
a = temp;
|
||||
}
|
||||
|
||||
this._state[0] = (this._state[0] + a) >>> 0;
|
||||
this._state[1] = (this._state[1] + b) >>> 0;
|
||||
this._state[2] = (this._state[2] + c) >>> 0;
|
||||
this._state[3] = (this._state[3] + d) >>> 0;
|
||||
this._state[4] = (this._state[4] + e) >>> 0;
|
||||
}
|
||||
|
||||
public update(data: Buffer, len?: number): void {
|
||||
let index = ((this._count[0] >>> 3) & 0x3F) >>> 0;
|
||||
const dataLen = len ?? data.length;
|
||||
this._count[0] = (this._count[0] + (dataLen << 3)) >>> 0;
|
||||
|
||||
if (this._count[0] < (dataLen << 3)) this._count[1] = (this._count[1] + 1) >>> 0;
|
||||
|
||||
this._count[1] = (this._count[1] + (dataLen >>> 29)) >>> 0;
|
||||
|
||||
let partLen = (this.Sha1BlockSize - index) >>> 0;
|
||||
let i = 0;
|
||||
|
||||
if (dataLen >= partLen) {
|
||||
data.copy(this._buffer, index, 0, partLen);
|
||||
this.transform(this._buffer, 0);
|
||||
for (i = partLen; (i + this.Sha1BlockSize) <= dataLen; i = (i + this.Sha1BlockSize) >>> 0) {
|
||||
this.transform(data, i);
|
||||
}
|
||||
index = 0;
|
||||
}
|
||||
|
||||
data.copy(this._buffer, index, i, dataLen);
|
||||
}
|
||||
|
||||
public hash(bigEndian: boolean = true): Buffer {
|
||||
const digest = Buffer.allocUnsafe(this.Sha1DigestSize);
|
||||
if (bigEndian) {
|
||||
for (let i = 0; i < 5; i++) digest.writeUInt32BE(this._state[i], i * 4);
|
||||
} else {
|
||||
for (let i = 0; i < 5; i++) digest.writeUInt32LE(this._state[i], i * 4);
|
||||
}
|
||||
return digest;
|
||||
}
|
||||
|
||||
public final(): Buffer {
|
||||
const digest = Buffer.allocUnsafe(this.Sha1DigestSize);
|
||||
const bits = Buffer.allocUnsafe(8)
|
||||
bits.writeUInt32BE(this._count[1], 0);
|
||||
bits.writeUInt32BE(this._count[0], 4);
|
||||
|
||||
let index = ((this._count[0] >>> 3) & 0x3F) >>> 0;
|
||||
const padLen = ((index < 56) ? (56 - index) : (120 - index)) >>> 0;
|
||||
this.update(this._padding, padLen);
|
||||
this.update(bits);
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
digest.writeUInt32BE(this._state[i], i * 4);
|
||||
}
|
||||
|
||||
return digest;
|
||||
}
|
||||
}
|
53
src/core/packet/utils/crypto/sha1StreamBytesTransform.ts
Normal file
53
src/core/packet/utils/crypto/sha1StreamBytesTransform.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import * as stream from "node:stream";
|
||||
import {Sha1Stream} from "@/core/packet/utils/crypto/sha1Stream";
|
||||
|
||||
export class CalculateStreamBytesTransform extends stream.Transform {
|
||||
private readonly blockSize = 1024 * 1024;
|
||||
private sha1: Sha1Stream;
|
||||
private buffer: Buffer;
|
||||
private bytesRead: number;
|
||||
private readonly byteArrayList: Buffer[];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.sha1 = new Sha1Stream();
|
||||
this.buffer = Buffer.alloc(0);
|
||||
this.bytesRead = 0;
|
||||
this.byteArrayList = [];
|
||||
}
|
||||
|
||||
_transform(chunk: Buffer, _: BufferEncoding, callback: stream.TransformCallback): void {
|
||||
try {
|
||||
this.buffer = Buffer.concat([this.buffer, chunk]);
|
||||
let offset = 0;
|
||||
while (this.buffer.length - offset >= this.sha1.Sha1BlockSize) {
|
||||
const block = this.buffer.subarray(offset, offset + this.sha1.Sha1BlockSize);
|
||||
this.sha1.update(block);
|
||||
offset += this.sha1.Sha1BlockSize;
|
||||
this.bytesRead += this.sha1.Sha1BlockSize;
|
||||
if (this.bytesRead % this.blockSize === 0) {
|
||||
const digest = this.sha1.hash(false);
|
||||
this.byteArrayList.push(Buffer.from(digest));
|
||||
}
|
||||
}
|
||||
this.buffer = this.buffer.subarray(offset);
|
||||
callback(null);
|
||||
} catch (err) {
|
||||
callback(err as Error);
|
||||
}
|
||||
}
|
||||
|
||||
_flush(callback: stream.TransformCallback): void {
|
||||
try {
|
||||
if (this.buffer.length > 0) this.sha1.update(this.buffer);
|
||||
const finalDigest = this.sha1.final();
|
||||
this.byteArrayList.push(Buffer.from(finalDigest));
|
||||
for (const digest of this.byteArrayList) {
|
||||
this.push(digest);
|
||||
}
|
||||
callback(null);
|
||||
} catch (err) {
|
||||
callback(err as Error);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user