mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
feat: add ptt msg pack & upload
This commit is contained in:
@@ -12,7 +12,7 @@ import { LogWrapper } from "@/common/log";
|
|||||||
import { SendLongMsgResp } from "@/core/packet/proto/message/action";
|
import { SendLongMsgResp } from "@/core/packet/proto/message/action";
|
||||||
import { PacketMsg } from "@/core/packet/msg/message";
|
import { PacketMsg } from "@/core/packet/msg/message";
|
||||||
import { OidbSvcTrpcTcp0x6D6Response } from "@/core/packet/proto/oidb/Oidb.0x6D6";
|
import { OidbSvcTrpcTcp0x6D6Response } from "@/core/packet/proto/oidb/Oidb.0x6D6";
|
||||||
import { PacketMsgPicElement, PacketMsgVideoElement } from "@/core/packet/msg/element";
|
import { PacketMsgPicElement, PacketMsgPttElement, PacketMsgVideoElement } from "@/core/packet/msg/element";
|
||||||
|
|
||||||
|
|
||||||
interface OffsetType {
|
interface OffsetType {
|
||||||
@@ -111,7 +111,7 @@ export class NTQQPacketApi {
|
|||||||
await this.sendPacket('OidbSvcTrpcTcp.0x8fc_2', data!, true);
|
await this.sendPacket('OidbSvcTrpcTcp.0x8fc_2', data!, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async uploadResources(msg: PacketMsg[], groupUin: number = 0) {
|
async uploadResources(msg: PacketMsg[], groupUin: number = 0) {
|
||||||
const reqList = [];
|
const reqList = [];
|
||||||
for (const m of msg) {
|
for (const m of msg) {
|
||||||
for (const e of m.msg) {
|
for (const e of m.msg) {
|
||||||
@@ -127,6 +127,12 @@ export class NTQQPacketApi {
|
|||||||
peerUid: groupUin ? String(groupUin) : this.core.selfInfo.uid
|
peerUid: groupUin ? String(groupUin) : this.core.selfInfo.uid
|
||||||
}, e));
|
}, e));
|
||||||
}
|
}
|
||||||
|
if (e instanceof PacketMsgPttElement) {
|
||||||
|
reqList.push(this.packetSession?.highwaySession.uploadPtt({
|
||||||
|
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
|
||||||
|
peerUid: groupUin ? String(groupUin) : this.core.selfInfo.uid
|
||||||
|
}, e));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Promise.all(reqList);
|
return Promise.all(reqList);
|
||||||
|
@@ -8,7 +8,7 @@ import { HttpConn0x6ff_501Response } from "@/core/packet/proto/action/action";
|
|||||||
import { PacketHighwayClient } from "@/core/packet/highway/client";
|
import { PacketHighwayClient } from "@/core/packet/highway/client";
|
||||||
import { NTV2RichMediaResp } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaResp";
|
import { NTV2RichMediaResp } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaResp";
|
||||||
import { OidbSvcTrpcTcpBaseRsp } from "@/core/packet/proto/oidb/OidbBase";
|
import { OidbSvcTrpcTcpBaseRsp } from "@/core/packet/proto/oidb/OidbBase";
|
||||||
import { PacketMsgPicElement, PacketMsgVideoElement } from "@/core/packet/msg/element";
|
import { PacketMsgPicElement, PacketMsgPttElement, PacketMsgVideoElement } from "@/core/packet/msg/element";
|
||||||
import { NTV2RichMediaHighwayExt } from "@/core/packet/proto/highway/highway";
|
import { NTV2RichMediaHighwayExt } from "@/core/packet/proto/highway/highway";
|
||||||
import { int32ip2str, oidbIpv4s2HighwayIpv4s } from "@/core/packet/highway/utils";
|
import { int32ip2str, oidbIpv4s2HighwayIpv4s } from "@/core/packet/highway/utils";
|
||||||
import { calculateSha1StreamBytes } from "@/core/packet/utils/crypto/hash";
|
import { calculateSha1StreamBytes } from "@/core/packet/utils/crypto/hash";
|
||||||
@@ -106,6 +106,17 @@ export class PacketHighwaySession {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async uploadPtt(peer: Peer, ptt: PacketMsgPttElement): Promise<void> {
|
||||||
|
await this.checkAvailable();
|
||||||
|
if (peer.chatType === ChatType.KCHATTYPEGROUP) {
|
||||||
|
await this.uploadGroupPttReq(Number(peer.peerUid), ptt);
|
||||||
|
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
|
||||||
|
await this.uploadC2CPttReq(peer.peerUid, ptt);
|
||||||
|
} else {
|
||||||
|
throw new Error(`[Highway] unsupported chatType: ${peer.chatType}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async uploadGroupImageReq(groupUin: number, img: PacketMsgPicElement): Promise<void> {
|
private async uploadGroupImageReq(groupUin: number, img: PacketMsgPicElement): Promise<void> {
|
||||||
const preReq = await this.packer.packUploadGroupImgReq(groupUin, img);
|
const preReq = await this.packer.packUploadGroupImgReq(groupUin, img);
|
||||||
const preRespRaw = await this.packetClient.sendPacket('OidbSvcTrpcTcp.0x11c4_100', preReq, true);
|
const preRespRaw = await this.packetClient.sendPacket('OidbSvcTrpcTcp.0x11c4_100', preReq, true);
|
||||||
@@ -313,4 +324,80 @@ export class PacketHighwaySession {
|
|||||||
}
|
}
|
||||||
video.msgInfo = preRespData.upload.msgInfo;
|
video.msgInfo = preRespData.upload.msgInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async uploadGroupPttReq(groupUin: number, ptt: PacketMsgPttElement): Promise<void> {
|
||||||
|
const preReq = await this.packer.packUploadGroupPttReq(groupUin, ptt);
|
||||||
|
const preRespRaw = await this.packetClient.sendPacket('OidbSvcTrpcTcp.0x126e_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] uploadGroupPttReq get upload ptt ukey: ${ukey}, need upload!`);
|
||||||
|
const index = preRespData.upload.msgInfo.msgInfoBody[0].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: ukey,
|
||||||
|
network: {
|
||||||
|
ipv4S: oidbIpv4s2HighwayIpv4s(preRespData.upload.ipv4S)
|
||||||
|
},
|
||||||
|
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
|
||||||
|
blockSize: BlockSize,
|
||||||
|
hash: {
|
||||||
|
fileSha1: [sha1]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await this.packetHighwayClient.upload(
|
||||||
|
1008,
|
||||||
|
fs.createReadStream(ptt.filePath, {highWaterMark: BlockSize}),
|
||||||
|
ptt.fileSize,
|
||||||
|
md5,
|
||||||
|
extend
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.logger.logDebug(`[Highway] uploadGroupPttReq get upload invalid ukey ${ukey}, don't need upload!`);
|
||||||
|
}
|
||||||
|
ptt.msgInfo = preRespData.upload.msgInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async uploadC2CPttReq(peerUid: string, ptt: PacketMsgPttElement): Promise<void> {
|
||||||
|
const preReq = await this.packer.packUploadC2CPttReq(peerUid, ptt);
|
||||||
|
const preRespRaw = await this.packetClient.sendPacket('OidbSvcTrpcTcp.0x126d_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] uploadC2CPttReq get upload ptt ukey: ${ukey}, need upload!`);
|
||||||
|
const index = preRespData.upload.msgInfo.msgInfoBody[0].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: ukey,
|
||||||
|
network: {
|
||||||
|
ipv4S: oidbIpv4s2HighwayIpv4s(preRespData.upload.ipv4S)
|
||||||
|
},
|
||||||
|
msgInfoBody: preRespData.upload.msgInfo.msgInfoBody,
|
||||||
|
blockSize: BlockSize,
|
||||||
|
hash: {
|
||||||
|
fileSha1: [sha1]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await this.packetHighwayClient.upload(
|
||||||
|
1007,
|
||||||
|
fs.createReadStream(ptt.filePath, {highWaterMark: BlockSize}),
|
||||||
|
ptt.fileSize,
|
||||||
|
md5,
|
||||||
|
extend
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.logger.logDebug(`[Highway] uploadC2CPttReq get upload invalid ukey ${ukey}, don't need upload!`);
|
||||||
|
}
|
||||||
|
ptt.msgInfo = preRespData.upload.msgInfo;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -319,14 +319,39 @@ export class PacketMsgVideoElement extends IPacketMsgElement<SendVideoElement> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PacketMsgFileElement extends IPacketMsgElement<SendFileElement> {
|
export class PacketMsgPttElement extends IPacketMsgElement<SendPttElement> {
|
||||||
constructor(element: SendFileElement) {
|
filePath: string;
|
||||||
|
fileSize: number;
|
||||||
|
fileMd5: string;
|
||||||
|
fileDuration: number;
|
||||||
|
msgInfo: NapProtoEncodeStructType<typeof MsgInfo> | null = null;
|
||||||
|
|
||||||
|
constructor(element: SendPttElement) {
|
||||||
super(element);
|
super(element);
|
||||||
|
this.filePath = element.pttElement.filePath;
|
||||||
|
this.fileSize = +element.pttElement.fileSize; // TODO: cc
|
||||||
|
this.fileMd5 = element.pttElement.md5HexStr;
|
||||||
|
this.fileDuration = Math.round(element.pttElement.duration); // TODO: cc
|
||||||
|
}
|
||||||
|
|
||||||
|
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: 22,
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
toPreview(): string {
|
||||||
|
return "[语音]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PacketMsgPttElement extends IPacketMsgElement<SendPttElement> {
|
export class PacketMsgFileElement extends IPacketMsgElement<SendFileElement> {
|
||||||
constructor(element: SendPttElement) {
|
constructor(element: SendFileElement) {
|
||||||
super(element);
|
super(element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,7 +11,7 @@ import { NTV2RichMediaReq } from "@/core/packet/proto/oidb/common/Ntv2.RichMedia
|
|||||||
import { HttpConn0x6ff_501 } from "@/core/packet/proto/action/action";
|
import { HttpConn0x6ff_501 } from "@/core/packet/proto/action/action";
|
||||||
import { LongMsgResult, SendLongMsgReq } from "@/core/packet/proto/message/action";
|
import { LongMsgResult, SendLongMsgReq } from "@/core/packet/proto/message/action";
|
||||||
import { PacketMsgBuilder } from "@/core/packet/msg/builder";
|
import { PacketMsgBuilder } from "@/core/packet/msg/builder";
|
||||||
import { PacketMsgPicElement, PacketMsgVideoElement } from "@/core/packet/msg/element";
|
import { PacketMsgPicElement, PacketMsgPttElement, PacketMsgVideoElement } from "@/core/packet/msg/element";
|
||||||
import { LogWrapper } from "@/common/log";
|
import { LogWrapper } from "@/common/log";
|
||||||
import { PacketMsg } from "@/core/packet/msg/message";
|
import { PacketMsg } from "@/core/packet/msg/message";
|
||||||
import { OidbSvcTrpcTcp0x6D6 } from "@/core/packet/proto/oidb/Oidb.0x6D6";
|
import { OidbSvcTrpcTcp0x6D6 } from "@/core/packet/proto/oidb/Oidb.0x6D6";
|
||||||
@@ -466,6 +466,135 @@ export class PacketPacker {
|
|||||||
return this.toHexStr(this.packOidbPacket(0x11E9, 100, req, true, false));
|
return this.toHexStr(this.packOidbPacket(0x11E9, 100, req, true, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async packUploadGroupPttReq(groupUin: number, ptt: PacketMsgPttElement): Promise<PacketHexStr> {
|
||||||
|
const pttSha1 = await calculateSha1(ptt.filePath);
|
||||||
|
const req = new NapProtoMsg(NTV2RichMediaReq).encode({
|
||||||
|
reqHead: {
|
||||||
|
common: {
|
||||||
|
requestId: 1,
|
||||||
|
command: 100
|
||||||
|
},
|
||||||
|
scene: {
|
||||||
|
requestType: 2,
|
||||||
|
businessType: 3,
|
||||||
|
sceneType: 2,
|
||||||
|
group: {
|
||||||
|
groupUin: groupUin
|
||||||
|
}
|
||||||
|
},
|
||||||
|
client: {
|
||||||
|
agentType: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
upload: {
|
||||||
|
uploadInfo: [
|
||||||
|
{
|
||||||
|
fileInfo: {
|
||||||
|
fileSize: ptt.fileSize,
|
||||||
|
fileHash: ptt.fileMd5,
|
||||||
|
fileSha1: this.toHexStr(pttSha1),
|
||||||
|
fileName: `${ptt.fileMd5}.amr`,
|
||||||
|
type: {
|
||||||
|
type: 3,
|
||||||
|
picFormat: 0,
|
||||||
|
videoFormat: 0,
|
||||||
|
voiceFormat: 1
|
||||||
|
},
|
||||||
|
height: 0,
|
||||||
|
width: 0,
|
||||||
|
time: ptt.fileDuration,
|
||||||
|
original: 0
|
||||||
|
},
|
||||||
|
subFileType: 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
tryFastUploadCompleted: true,
|
||||||
|
srvSendMsg: false,
|
||||||
|
clientRandomId: crypto.randomBytes(8).readBigUInt64BE() & BigInt('0x7FFFFFFFFFFFFFFF'),
|
||||||
|
compatQMsgSceneType: 2,
|
||||||
|
extBizInfo: {
|
||||||
|
pic: {
|
||||||
|
textSummary: "Nya~",
|
||||||
|
},
|
||||||
|
video: {
|
||||||
|
bytesPbReserve: Buffer.alloc(0),
|
||||||
|
},
|
||||||
|
ptt: {
|
||||||
|
bytesPbReserve: Buffer.alloc(0),
|
||||||
|
bytesReserve: Buffer.from([0x08, 0x00, 0x38, 0x00]),
|
||||||
|
bytesGeneralFlags: Buffer.from([0x9a, 0x01, 0x07, 0xaa, 0x03, 0x04, 0x08, 0x08, 0x12, 0x00]),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clientSeq: 0,
|
||||||
|
noNeedCompatMsg: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return this.toHexStr(this.packOidbPacket(0x126E, 100, req, true, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
async packUploadC2CPttReq(peerUin: string, ptt: PacketMsgPttElement): Promise<PacketHexStr> {
|
||||||
|
const pttSha1 = await calculateSha1(ptt.filePath);
|
||||||
|
const req = new NapProtoMsg(NTV2RichMediaReq).encode({
|
||||||
|
reqHead: {
|
||||||
|
common: {
|
||||||
|
requestId: 4,
|
||||||
|
command: 100
|
||||||
|
},
|
||||||
|
scene: {
|
||||||
|
requestType: 2,
|
||||||
|
businessType: 3,
|
||||||
|
sceneType: 1,
|
||||||
|
c2C: {
|
||||||
|
accountType: 2,
|
||||||
|
targetUid: peerUin
|
||||||
|
}
|
||||||
|
},
|
||||||
|
client: {
|
||||||
|
agentType: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
upload: {
|
||||||
|
uploadInfo: [
|
||||||
|
{
|
||||||
|
fileInfo: {
|
||||||
|
fileSize: ptt.fileSize,
|
||||||
|
fileHash: ptt.fileMd5,
|
||||||
|
fileSha1: this.toHexStr(pttSha1),
|
||||||
|
fileName: `${ptt.fileMd5}.amr`,
|
||||||
|
type: {
|
||||||
|
type: 3,
|
||||||
|
picFormat: 0,
|
||||||
|
videoFormat: 0,
|
||||||
|
voiceFormat: 1
|
||||||
|
},
|
||||||
|
height: 0,
|
||||||
|
width: 0,
|
||||||
|
time: ptt.fileDuration,
|
||||||
|
original: 0
|
||||||
|
},
|
||||||
|
subFileType: 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
tryFastUploadCompleted: true,
|
||||||
|
srvSendMsg: false,
|
||||||
|
clientRandomId: crypto.randomBytes(8).readBigUInt64BE() & BigInt('0x7FFFFFFFFFFFFFFF'),
|
||||||
|
compatQMsgSceneType: 1,
|
||||||
|
extBizInfo: {
|
||||||
|
pic: {
|
||||||
|
textSummary: "Nya~",
|
||||||
|
},
|
||||||
|
ptt: {
|
||||||
|
bytesReserve: Buffer.from([0x08, 0x00, 0x38, 0x00]),
|
||||||
|
bytesGeneralFlags: Buffer.from([0x9a, 0x01, 0x0b, 0xaa, 0x03, 0x08, 0x08, 0x04, 0x12, 0x04, 0x00, 0x00, 0x00, 0x00]),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clientSeq: 0,
|
||||||
|
noNeedCompatMsg: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return this.toHexStr(this.packOidbPacket(0x126D, 100, req, true, false));
|
||||||
|
}
|
||||||
|
|
||||||
packGroupFileDownloadReq(groupUin: number, fileUUID: string): PacketHexStr {
|
packGroupFileDownloadReq(groupUin: number, fileUUID: string): PacketHexStr {
|
||||||
return this.toHexStr(
|
return this.toHexStr(
|
||||||
this.packOidbPacket(0x6D6, 2, new NapProtoMsg(OidbSvcTrpcTcp0x6D6).encode({
|
this.packOidbPacket(0x6D6, 2, new NapProtoMsg(OidbSvcTrpcTcp0x6D6).encode({
|
||||||
|
Reference in New Issue
Block a user