mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
refactor: packet x1
This commit is contained in:
@@ -16,8 +16,7 @@
|
|||||||
"@eslint/eslintrc": "^3.1.0",
|
"@eslint/eslintrc": "^3.1.0",
|
||||||
"@eslint/js": "^9.14.0",
|
"@eslint/js": "^9.14.0",
|
||||||
"@log4js-node/log4js-api": "^1.0.2",
|
"@log4js-node/log4js-api": "^1.0.2",
|
||||||
"@napneko/nap-proto-core": "^0.0.2",
|
"@napneko/nap-proto-core": "^0.0.4",
|
||||||
"@protobuf-ts/runtime": "^2.9.4",
|
|
||||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"@rollup/plugin-typescript": "^11.1.6",
|
"@rollup/plugin-typescript": "^11.1.6",
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.17",
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { PacketMsg } from "@/core/packet/message/message";
|
|
||||||
import * as crypto from "node:crypto";
|
import * as crypto from "node:crypto";
|
||||||
|
import { PacketMsg } from "@/core/packet/message/message";
|
||||||
|
|
||||||
interface ForwardMsgJson {
|
interface ForwardMsgJson {
|
||||||
app: string
|
app: string
|
||||||
|
@@ -433,7 +433,7 @@ export class NTQQFileApi {
|
|||||||
const rkey_expired_private = !this.packetRkey || this.packetRkey[0].time + Number(this.packetRkey[0].ttl) < Date.now() / 1000;
|
const rkey_expired_private = !this.packetRkey || this.packetRkey[0].time + Number(this.packetRkey[0].ttl) < Date.now() / 1000;
|
||||||
const rkey_expired_group = !this.packetRkey || this.packetRkey[0].time + Number(this.packetRkey[0].ttl) < Date.now() / 1000;
|
const rkey_expired_group = !this.packetRkey || this.packetRkey[0].time + Number(this.packetRkey[0].ttl) < Date.now() / 1000;
|
||||||
if (rkey_expired_private || rkey_expired_group) {
|
if (rkey_expired_private || rkey_expired_group) {
|
||||||
this.packetRkey = await this.core.apis.PacketApi.sendRkeyPacket();
|
this.packetRkey = await this.core.apis.PacketApi.pkt.operation.FetchRkey();
|
||||||
}
|
}
|
||||||
if (this.packetRkey && this.packetRkey.length > 0) {
|
if (this.packetRkey && this.packetRkey.length > 0) {
|
||||||
rkeyData.group_rkey = this.packetRkey[1].rkey.slice(6);
|
rkeyData.group_rkey = this.packetRkey[1].rkey.slice(6);
|
||||||
|
@@ -1,33 +1,10 @@
|
|||||||
import * as crypto from 'crypto';
|
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import { ChatType, InstanceContext, NapCatCore } from '..';
|
|
||||||
import offset from '@/core/external/offset.json';
|
import offset from '@/core/external/offset.json';
|
||||||
import { PacketSession } from "@/core/packet/session";
|
import { InstanceContext, NapCatCore } from "@/core";
|
||||||
import { OidbPacket, PacketHexStr } from "@/core/packet/packer";
|
|
||||||
import { NapProtoMsg, NapProtoEncodeStructType, NapProtoDecodeStructType } from "@napneko/nap-proto-core";
|
|
||||||
import { OidbSvcTrpcTcp0X9067_202_Rsp_Body } from '@/core/packet/proto/oidb/Oidb.0x9067_202';
|
|
||||||
import { OidbSvcTrpcTcpBase, OidbSvcTrpcTcpBaseRsp } from '@/core/packet/proto/oidb/OidbBase';
|
|
||||||
import { OidbSvcTrpcTcp0XFE1_2RSP } from '@/core/packet/proto/oidb/Oidb.0XFE1_2';
|
|
||||||
import { LogWrapper } from "@/common/log";
|
import { LogWrapper } from "@/common/log";
|
||||||
import { SendLongMsgResp } from "@/core/packet/proto/message/action";
|
import { PacketClientSession } from "@/core/packet/clientSession";
|
||||||
import { PacketMsg } from "@/core/packet/message/message";
|
|
||||||
import { OidbSvcTrpcTcp0x6D6Response } from "@/core/packet/proto/oidb/Oidb.0x6D6";
|
|
||||||
import {
|
|
||||||
PacketMsgFileElement,
|
|
||||||
PacketMsgPicElement,
|
|
||||||
PacketMsgPttElement,
|
|
||||||
PacketMsgVideoElement
|
|
||||||
} from "@/core/packet/message/element";
|
|
||||||
import { MiniAppReqParams, MiniAppRawData } from "@/core/packet/entities/miniApp";
|
|
||||||
import { MiniAppAdaptShareInfoResp } from "@/core/packet/proto/action/miniAppAdaptShareInfo";
|
|
||||||
import { AIVoiceChatType, AIVoiceItemList } from "@/core/packet/entities/aiChat";
|
|
||||||
import { OidbSvcTrpcTcp0X929B_0Resp, OidbSvcTrpcTcp0X929D_0Resp } from "@/core/packet/proto/oidb/Oidb.0x929";
|
|
||||||
import { IndexNode, MsgInfo } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
|
||||||
import { NTV2RichMediaResp } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaResp";
|
|
||||||
import { RecvPacketData } from "@/core/packet/client/client";
|
|
||||||
import { napCatVersion } from "@/common/version";
|
import { napCatVersion } from "@/common/version";
|
||||||
|
|
||||||
|
|
||||||
interface OffsetType {
|
interface OffsetType {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
recv: string;
|
recv: string;
|
||||||
@@ -42,27 +19,27 @@ export class NTQQPacketApi {
|
|||||||
core: NapCatCore;
|
core: NapCatCore;
|
||||||
logger: LogWrapper;
|
logger: LogWrapper;
|
||||||
qqVersion: string | undefined;
|
qqVersion: string | undefined;
|
||||||
packetSession: PacketSession | undefined;
|
pkt: PacketClientSession;
|
||||||
|
|
||||||
constructor(context: InstanceContext, core: NapCatCore) {
|
constructor(context: InstanceContext, core: NapCatCore) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.core = core;
|
this.core = core;
|
||||||
this.logger = core.context.logger;
|
this.logger = core.context.logger;
|
||||||
this.packetSession = undefined;
|
this.pkt = new PacketClientSession(core);
|
||||||
this.InitSendPacket(this.context.basicInfoWrapper.getFullQQVesion())
|
this.InitSendPacket(this.context.basicInfoWrapper.getFullQQVesion())
|
||||||
.then()
|
.then()
|
||||||
.catch(this.core.context.logger.logError.bind(this.core.context.logger));
|
.catch(this.core.context.logger.logError.bind(this.core.context.logger));
|
||||||
}
|
}
|
||||||
|
|
||||||
get available(): boolean {
|
get available(): boolean {
|
||||||
return this.packetSession?.client.available ?? false;
|
return this.pkt?.available;
|
||||||
}
|
}
|
||||||
|
|
||||||
async InitSendPacket(qqversion: string) {
|
async InitSendPacket(qqVer: string) {
|
||||||
this.qqVersion = qqversion;
|
this.qqVersion = qqVer;
|
||||||
const table = typedOffset[qqversion + '-' + os.arch()];
|
const table = typedOffset[qqVer + '-' + os.arch()];
|
||||||
if (!table) {
|
if (!table) {
|
||||||
this.logger.logError(`[Core] [Packet] PacketBackend 不支持当前QQ版本架构:${qqversion}-${os.arch()},
|
this.logger.logError(`[Core] [Packet] PacketBackend 不支持当前QQ版本架构:${qqVer}-${os.arch()},
|
||||||
请参照 https://github.com/NapNeko/NapCatQQ/releases/tag/v${napCatVersion} 配置正确的QQ版本!`);
|
请参照 https://github.com/NapNeko/NapCatQQ/releases/tag/v${napCatVersion} 配置正确的QQ版本!`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -70,173 +47,7 @@ export class NTQQPacketApi {
|
|||||||
this.logger.logWarn('[Core] [Packet] 已禁用PacketBackend,NapCat.Packet将不会加载!');
|
this.logger.logWarn('[Core] [Packet] 已禁用PacketBackend,NapCat.Packet将不会加载!');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.packetSession = new PacketSession(this.core);
|
await this.pkt.init(process.pid, table.recv, table.send);
|
||||||
const cb = () => {
|
|
||||||
if (this.packetSession && this.packetSession.client) {
|
|
||||||
this.packetSession.client.init(process.pid, table.recv, table.send).then().catch(this.logger.logError.bind(this.logger));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
await this.packetSession.client.connect(cb);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendPacket(cmd: string, data: PacketHexStr, rsp = false): Promise<RecvPacketData> {
|
|
||||||
return this.packetSession!.client.sendPacket(cmd, data, rsp);
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendOidbPacket(pkt: OidbPacket, rsp = false): Promise<RecvPacketData> {
|
|
||||||
return this.sendPacket(pkt.cmd, pkt.data, rsp);
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendPokePacket(peer: number, group?: number) {
|
|
||||||
const data = this.packetSession?.packer.packPokePacket(peer, group);
|
|
||||||
await this.sendOidbPacket(data!, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendRkeyPacket() {
|
|
||||||
const packet = this.packetSession?.packer.packRkeyPacket();
|
|
||||||
const ret = await this.sendOidbPacket(packet!, true);
|
|
||||||
if (!ret?.hex_data) return [];
|
|
||||||
const body = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(Buffer.from(ret.hex_data, 'hex')).body;
|
|
||||||
const retData = new NapProtoMsg(OidbSvcTrpcTcp0X9067_202_Rsp_Body).decode(body);
|
|
||||||
return retData.data.rkeyList;
|
|
||||||
}
|
|
||||||
async sendGroupSignPacket(groupCode: string) {
|
|
||||||
const packet = this.packetSession?.packer.packGroupSignReq(this.core.selfInfo.uin, groupCode);
|
|
||||||
await this.sendOidbPacket(packet!, true);
|
|
||||||
}
|
|
||||||
async sendStatusPacket(uin: number): Promise<{ status: number; ext_status: number; } | undefined> {
|
|
||||||
let status = 0;
|
|
||||||
try {
|
|
||||||
const packet = this.packetSession?.packer.packStatusPacket(uin);
|
|
||||||
const ret = await this.sendOidbPacket(packet!, true);
|
|
||||||
const data = Buffer.from(ret.hex_data, 'hex');
|
|
||||||
const ext = new NapProtoMsg(OidbSvcTrpcTcp0XFE1_2RSP).decode(new NapProtoMsg(OidbSvcTrpcTcpBase).decode(data).body).data.status.value;
|
|
||||||
// ext & 0xff00 + ext >> 16 & 0xff
|
|
||||||
const extBigInt = BigInt(ext); // 转换为 BigInt
|
|
||||||
if (extBigInt <= 10n) {
|
|
||||||
return { status: Number(extBigInt) * 10, ext_status: 0 };
|
|
||||||
}
|
|
||||||
status = Number((extBigInt & 0xff00n) + ((extBigInt >> 16n) & 0xffn)); // 使用 BigInt 操作符
|
|
||||||
return { status: 10, ext_status: status };
|
|
||||||
} catch (error) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendSetSpecialTittlePacket(groupCode: string, uid: string, tittle: string) {
|
|
||||||
const data = this.packetSession?.packer.packSetSpecialTittlePacket(groupCode, uid, tittle);
|
|
||||||
await this.sendOidbPacket(data!, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: can simplify this
|
|
||||||
async uploadResources(msg: PacketMsg[], groupUin: number = 0) {
|
|
||||||
const reqList = [];
|
|
||||||
for (const m of msg) {
|
|
||||||
for (const e of m.msg) {
|
|
||||||
if (e instanceof PacketMsgPicElement) {
|
|
||||||
reqList.push(this.packetSession?.highwaySession.uploadImage({
|
|
||||||
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
if (e instanceof PacketMsgFileElement) {
|
|
||||||
reqList.push(this.packetSession?.highwaySession.uploadFile({
|
|
||||||
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
|
|
||||||
peerUid: groupUin ? String(groupUin) : this.core.selfInfo.uid
|
|
||||||
}, e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const res = await Promise.allSettled(reqList);
|
|
||||||
this.logger.log(`上传资源${res.length}个,失败${res.filter(r => r.status === 'rejected').length}个`);
|
|
||||||
res.forEach((result, index) => {
|
|
||||||
if (result.status === 'rejected') {
|
|
||||||
this.logger.logError(`上传第${index + 1}个资源失败:${result.reason}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendUploadForwardMsg(msg: PacketMsg[], groupUin: number = 0) {
|
|
||||||
await this.uploadResources(msg, groupUin);
|
|
||||||
const data = await this.packetSession?.packer.packUploadForwardMsg(this.core.selfInfo.uid, msg, groupUin);
|
|
||||||
const ret = await this.sendPacket('trpc.group.long_msg_interface.MsgService.SsoSendLongMsg', data!, true);
|
|
||||||
this.logger.logDebug('sendUploadForwardMsg', ret);
|
|
||||||
const resp = new NapProtoMsg(SendLongMsgResp).decode(Buffer.from(ret.hex_data, 'hex'));
|
|
||||||
return resp.result.resId;
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendGroupFileDownloadReq(groupUin: number, fileUUID: string) {
|
|
||||||
const data = this.packetSession?.packer.packGroupFileDownloadReq(groupUin, fileUUID);
|
|
||||||
const ret = await this.sendOidbPacket(data!, true);
|
|
||||||
const body = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(Buffer.from(ret.hex_data, 'hex')).body;
|
|
||||||
const resp = new NapProtoMsg(OidbSvcTrpcTcp0x6D6Response).decode(body);
|
|
||||||
if (resp.download.retCode !== 0) {
|
|
||||||
throw new Error(`sendGroupFileDownloadReq error: ${resp.download.clientWording}`);
|
|
||||||
}
|
|
||||||
return `https://${resp.download.downloadDns}/ftn_handler/${Buffer.from(resp.download.downloadUrl).toString('hex')}/?fname=`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendGroupPttFileDownloadReq(groupUin: number, node: NapProtoEncodeStructType<typeof IndexNode>) {
|
|
||||||
const data = this.packetSession?.packer.packGroupPttFileDownloadReq(groupUin, node);
|
|
||||||
const ret = await this.sendOidbPacket(data!, true);
|
|
||||||
const body = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(Buffer.from(ret.hex_data, 'hex')).body;
|
|
||||||
const resp = new NapProtoMsg(NTV2RichMediaResp).decode(body);
|
|
||||||
const info = resp.download.info;
|
|
||||||
return `https://${info.domain}${info.urlPath}${resp.download.rKeyParam}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendMiniAppShareInfoReq(param: MiniAppReqParams) {
|
|
||||||
const data = this.packetSession?.packer.packMiniAppAdaptShareInfo(param);
|
|
||||||
const ret = await this.sendPacket("LightAppSvc.mini_app_share.AdaptShareInfo", data!, true);
|
|
||||||
const body = new NapProtoMsg(MiniAppAdaptShareInfoResp).decode(Buffer.from(ret.hex_data, 'hex'));
|
|
||||||
return JSON.parse(body.content.jsonContent) as MiniAppRawData;
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendFetchAiVoiceListReq(groupUin: number, chatType: AIVoiceChatType) : Promise<AIVoiceItemList[] | null> {
|
|
||||||
const data = this.packetSession?.packer.packFetchAiVoiceListReq(groupUin, chatType);
|
|
||||||
const ret = await this.sendOidbPacket(data!, true);
|
|
||||||
const body = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(Buffer.from(ret.hex_data, 'hex')).body;
|
|
||||||
const resp = new NapProtoMsg(OidbSvcTrpcTcp0X929D_0Resp).decode(body);
|
|
||||||
if (!resp.content) return null;
|
|
||||||
return resp.content.map((item) => {
|
|
||||||
return {
|
|
||||||
category: item.category,
|
|
||||||
voices: item.voices
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendAiVoiceChatReq(groupUin: number, voiceId: string, text: string, chatType: AIVoiceChatType): Promise<NapProtoDecodeStructType<typeof MsgInfo>> {
|
|
||||||
let reqTime = 0;
|
|
||||||
const reqMaxTime = 30;
|
|
||||||
const sessionId = crypto.randomBytes(4).readUInt32BE(0);
|
|
||||||
while (true) {
|
|
||||||
if (reqTime >= reqMaxTime) {
|
|
||||||
throw new Error(`sendAiVoiceChatReq failed after ${reqMaxTime} times`);
|
|
||||||
}
|
|
||||||
reqTime++;
|
|
||||||
const data = this.packetSession?.packer.packAiVoiceChatReq(groupUin, voiceId, text, chatType, sessionId);
|
|
||||||
const ret = await this.sendOidbPacket(data!, true);
|
|
||||||
const body = new NapProtoMsg(OidbSvcTrpcTcpBase).decode(Buffer.from(ret.hex_data, 'hex'));
|
|
||||||
if (body.errorCode) {
|
|
||||||
throw new Error(`sendAiVoiceChatReq retCode: ${body.errorCode} error: ${body.errorMsg}`);
|
|
||||||
}
|
|
||||||
const resp = new NapProtoMsg(OidbSvcTrpcTcp0X929B_0Resp).decode(body.body);
|
|
||||||
if (!resp.msgInfo) continue;
|
|
||||||
return resp.msgInfo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
31
src/core/external/proto/EmojiLikeToOthers.proto
vendored
31
src/core/external/proto/EmojiLikeToOthers.proto
vendored
@@ -1,31 +0,0 @@
|
|||||||
syntax = 'proto3';
|
|
||||||
package SysMessage;
|
|
||||||
|
|
||||||
message EmojiLikeToOthersWrapper1 {
|
|
||||||
EmojiLikeToOthersWrapper2 wrapper = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message EmojiLikeToOthersWrapper2 {
|
|
||||||
EmojiLikeToOthersWrapper3 body = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message EmojiLikeToOthersWrapper3 {
|
|
||||||
EmojiLikeToOthersMsgSpec msgSpec = 2;
|
|
||||||
EmojiLikeToOthersAttributes attributes = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message EmojiLikeToOthersMsgSpec {
|
|
||||||
uint32 msgSeq = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message EmojiLikeToOthersAttributes {
|
|
||||||
enum Operation {
|
|
||||||
FALLBACK = 0;
|
|
||||||
LIKE = 1;
|
|
||||||
UNLIKE = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
string emojiId = 1;
|
|
||||||
string senderUid = 4;
|
|
||||||
Operation operation = 5;
|
|
||||||
}
|
|
9
src/core/external/proto/GreyTipWrapper.proto
vendored
9
src/core/external/proto/GreyTipWrapper.proto
vendored
@@ -1,9 +0,0 @@
|
|||||||
syntax = 'proto3';
|
|
||||||
package SysMessage;
|
|
||||||
|
|
||||||
message GreyTipWrapper {
|
|
||||||
uint32 subTypeId = 1;
|
|
||||||
uint32 groupCode = 4;
|
|
||||||
uint32 subTypeIdMinusOne = 13;
|
|
||||||
bytes rest = 44;
|
|
||||||
}
|
|
18
src/core/external/proto/ProfileLikeTip.proto
vendored
18
src/core/external/proto/ProfileLikeTip.proto
vendored
@@ -1,18 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
package SysMessage;
|
|
||||||
|
|
||||||
message likeDetail {
|
|
||||||
string txt = 1;
|
|
||||||
int64 uin = 3;
|
|
||||||
string nickname = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message likeMsg {
|
|
||||||
int32 times = 1;
|
|
||||||
int32 time = 2;
|
|
||||||
likeDetail detail = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message profileLikeTip {
|
|
||||||
likeMsg msg = 14;
|
|
||||||
}
|
|
36
src/core/external/proto/SysMessage.proto
vendored
36
src/core/external/proto/SysMessage.proto
vendored
@@ -1,36 +0,0 @@
|
|||||||
syntax = 'proto3';
|
|
||||||
package SysMessage;
|
|
||||||
|
|
||||||
message SysMessage {
|
|
||||||
repeated SysMessageHeader header = 1;
|
|
||||||
repeated SysMessageMsgSpec msgSpec = 2;
|
|
||||||
SysMessageBodyWrapper bodyWrapper = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SysMessageHeader {
|
|
||||||
uint32 PeerNumber = 1;
|
|
||||||
string PeerString = 2;
|
|
||||||
uint32 Uin = 5;
|
|
||||||
optional string Uid = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SysMessageMsgSpec {
|
|
||||||
uint32 msgType = 1;
|
|
||||||
uint32 subType = 2;
|
|
||||||
uint32 subSubType = 3;
|
|
||||||
uint32 msgSeq = 5;
|
|
||||||
uint32 time = 6;
|
|
||||||
uint64 msgId = 12;
|
|
||||||
uint32 other = 13;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SysMessageBodyWrapper {
|
|
||||||
bytes wrappedBody = 2;
|
|
||||||
// Find the first [08], or ignore the first 7 bytes?
|
|
||||||
// And it becomes another ProtoBuf message.
|
|
||||||
}
|
|
||||||
|
|
||||||
message KeyValuePair {
|
|
||||||
string key = 1;
|
|
||||||
string value = 2;
|
|
||||||
}
|
|
61
src/core/helper/adaptDecoder.ts
Normal file
61
src/core/helper/adaptDecoder.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
// TODO: further refactor in NapCat.Packet v2
|
||||||
|
import { NapProtoMsg, ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
||||||
|
|
||||||
|
export const LikeDetail = {
|
||||||
|
txt: ProtoField(1, ScalarType.STRING),
|
||||||
|
uin: ProtoField(3, ScalarType.INT64),
|
||||||
|
nickname: ProtoField(5, ScalarType.STRING)
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LikeMsg = {
|
||||||
|
times: ProtoField(1, ScalarType.INT32),
|
||||||
|
time: ProtoField(2, ScalarType.INT32),
|
||||||
|
detail: ProtoField(3, () => LikeDetail)
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ProfileLikeSubTip = {
|
||||||
|
msg: ProtoField(14, () => LikeMsg)
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ProfileLikeTip = {
|
||||||
|
msgType: ProtoField(1, ScalarType.INT32),
|
||||||
|
subType: ProtoField(2, ScalarType.INT32),
|
||||||
|
content: ProtoField(203, () => ProfileLikeSubTip)
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SysMessageHeader = {
|
||||||
|
PeerNumber: ProtoField(1, ScalarType.UINT32),
|
||||||
|
PeerString: ProtoField(2, ScalarType.STRING),
|
||||||
|
Uin: ProtoField(5, ScalarType.UINT32),
|
||||||
|
Uid: ProtoField(6, ScalarType.STRING, true)
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SysMessageMsgSpec = {
|
||||||
|
msgType: ProtoField(1, ScalarType.UINT32),
|
||||||
|
subType: ProtoField(2, ScalarType.UINT32),
|
||||||
|
subSubType: ProtoField(3, ScalarType.UINT32),
|
||||||
|
msgSeq: ProtoField(5, ScalarType.UINT32),
|
||||||
|
time: ProtoField(6, ScalarType.UINT32),
|
||||||
|
msgId: ProtoField(12, ScalarType.UINT64),
|
||||||
|
other: ProtoField(13, ScalarType.UINT32)
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SysMessageBodyWrapper = {
|
||||||
|
wrappedBody: ProtoField(2, ScalarType.BYTES)
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SysMessage = {
|
||||||
|
header: ProtoField(1, () => SysMessageHeader, false, true),
|
||||||
|
msgSpec: ProtoField(2, () => SysMessageMsgSpec, false, true),
|
||||||
|
bodyWrapper: ProtoField(3, () => SysMessageBodyWrapper)
|
||||||
|
};
|
||||||
|
|
||||||
|
export function decodeProfileLikeTip(buffer: Uint8Array) {
|
||||||
|
const msg = new NapProtoMsg(ProfileLikeTip);
|
||||||
|
return msg.decode(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeSysMessage(buffer: Uint8Array) {
|
||||||
|
const msg = new NapProtoMsg(SysMessage);
|
||||||
|
return msg.decode(buffer);
|
||||||
|
}
|
@@ -1,9 +1,7 @@
|
|||||||
import { LRUCache } from "@/common/lru-cache";
|
import { LRUCache } from "@/common/lru-cache";
|
||||||
import { NapCatCore } from "@/core";
|
|
||||||
import { LogWrapper } from "@/common/log";
|
|
||||||
import crypto, { createHash } from "crypto";
|
import crypto, { createHash } from "crypto";
|
||||||
import { OidbPacket, PacketHexStr } from "@/core/packet/packer";
|
import { PacketContext } from "@/core/packet/context/packetContext";
|
||||||
import { NapCatConfig } from "@/core/helper/config";
|
import { OidbPacket, PacketHexStr } from "@/core/packet/transformer/base";
|
||||||
|
|
||||||
export interface RecvPacket {
|
export interface RecvPacket {
|
||||||
type: string, // 仅recv
|
type: string, // 仅recv
|
||||||
@@ -17,38 +15,28 @@ export interface RecvPacketData {
|
|||||||
hex_data: string
|
hex_data: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class PacketClient {
|
function randText(len: number): string {
|
||||||
readonly napCatCore: NapCatCore;
|
let text = '';
|
||||||
protected readonly logger: LogWrapper;
|
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class IPacketClient {
|
||||||
|
protected readonly context: PacketContext;
|
||||||
protected readonly cb = new LRUCache<string, (json: RecvPacketData) => Promise<void>>(500); // trace_id-type callback
|
protected readonly cb = new LRUCache<string, (json: RecvPacketData) => Promise<void>>(500); // trace_id-type callback
|
||||||
protected isAvailable: boolean = false;
|
available: boolean = false;
|
||||||
protected config: NapCatConfig;
|
|
||||||
|
|
||||||
protected constructor(core: NapCatCore) {
|
protected constructor(context: PacketContext) {
|
||||||
this.napCatCore = core;
|
this.context = context;
|
||||||
this.logger = core.context.logger;
|
|
||||||
this.config = core.configLoader.configData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private randText(len: number): string {
|
abstract check(): boolean;
|
||||||
let text = '';
|
|
||||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
get available(): boolean {
|
|
||||||
return this.isAvailable;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract check(core: NapCatCore): boolean;
|
|
||||||
|
|
||||||
abstract init(pid: number, recv: string, send: string): Promise<void>;
|
abstract init(pid: number, recv: string, send: string): Promise<void>;
|
||||||
|
|
||||||
abstract connect(cb: () => void): Promise<void>;
|
|
||||||
|
|
||||||
abstract sendCommandImpl(cmd: string, data: string, trace_id: string): void;
|
abstract sendCommandImpl(cmd: string, data: string, trace_id: string): void;
|
||||||
|
|
||||||
private async registerCallback(trace_id: string, type: string, callback: (json: RecvPacketData) => Promise<void>): Promise<void> {
|
private async registerCallback(trace_id: string, type: string, callback: (json: RecvPacketData) => Promise<void>): Promise<void> {
|
||||||
@@ -58,9 +46,14 @@ export abstract class PacketClient {
|
|||||||
private async sendCommand(cmd: string, data: string, trace_id: string, rsp: boolean = false, timeout: number = 20000, sendcb: (json: RecvPacketData) => void = () => {
|
private async sendCommand(cmd: string, data: string, trace_id: string, rsp: boolean = false, timeout: number = 20000, sendcb: (json: RecvPacketData) => void = () => {
|
||||||
}): Promise<RecvPacketData> {
|
}): Promise<RecvPacketData> {
|
||||||
return new Promise<RecvPacketData>((resolve, reject) => {
|
return new Promise<RecvPacketData>((resolve, reject) => {
|
||||||
|
if (!this.available) {
|
||||||
|
reject(new Error('packetBackend 当前不可用!'));
|
||||||
|
}
|
||||||
|
|
||||||
const timeoutHandle = setTimeout(() => {
|
const timeoutHandle = setTimeout(() => {
|
||||||
reject(new Error(`sendCommand timed out after ${timeout} ms for ${cmd} with trace_id ${trace_id}`));
|
reject(new Error(`sendCommand timed out after ${timeout} ms for ${cmd} with trace_id ${trace_id}`));
|
||||||
}, timeout);
|
}, timeout);
|
||||||
|
|
||||||
this.registerCallback(trace_id, 'send', async (json: RecvPacketData) => {
|
this.registerCallback(trace_id, 'send', async (json: RecvPacketData) => {
|
||||||
sendcb(json);
|
sendcb(json);
|
||||||
if (!rsp) {
|
if (!rsp) {
|
||||||
@@ -68,30 +61,23 @@ export abstract class PacketClient {
|
|||||||
resolve(json);
|
resolve(json);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (rsp) {
|
if (rsp) {
|
||||||
this.registerCallback(trace_id, 'recv', async (json: RecvPacketData) => {
|
this.registerCallback(trace_id, 'recv', async (json: RecvPacketData) => {
|
||||||
clearTimeout(timeoutHandle);
|
clearTimeout(timeoutHandle);
|
||||||
resolve(json);
|
resolve(json);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sendCommandImpl(cmd, data, trace_id);
|
this.sendCommandImpl(cmd, data, trace_id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendPacket(cmd: string, data: PacketHexStr, rsp = false): Promise<RecvPacketData> {
|
async sendPacket(cmd: string, data: PacketHexStr, rsp = false): Promise<RecvPacketData> {
|
||||||
return new Promise((resolve, reject) => {
|
const md5 = crypto.createHash('md5').update(data).digest('hex');
|
||||||
if (!this.available) {
|
const trace_id = (randText(4) + md5 + data).slice(0, data.length / 2);
|
||||||
this.logger.logError('NapCat.Packet 未初始化!');
|
return this.sendCommand(cmd, data, trace_id, rsp, 20000, async () => {
|
||||||
return undefined;
|
await this.context.napcore.sendSsoCmdReqByContend(cmd, trace_id);
|
||||||
}
|
|
||||||
|
|
||||||
const md5 = crypto.createHash('md5').update(data).digest('hex');
|
|
||||||
const trace_id = (this.randText(4) + md5 + data).slice(0, data.length / 2);
|
|
||||||
|
|
||||||
this.sendCommand(cmd, data, trace_id, rsp, 20000, async () => {
|
|
||||||
//console.log('sendPacket:', cmd, data, trace_id);
|
|
||||||
await this.napCatCore.context.session.getMsgService().sendSsoCmdReqByContend(cmd, trace_id);
|
|
||||||
}).then((res) => resolve(res)).catch((e: Error) => reject(e));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@@ -1,37 +1,36 @@
|
|||||||
import crypto, { createHash } from "crypto";
|
import { createHash } from "crypto";
|
||||||
import { NapCatCore } from "@/core";
|
|
||||||
import path, { dirname } from "path";
|
import path, { dirname } from "path";
|
||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import { PacketClient } from "@/core/packet/client/client";
|
import { IPacketClient } from "@/core/packet/client/baseClient";
|
||||||
import { constants } from "node:os";
|
import { constants } from "node:os";
|
||||||
import { LRUCache } from "@/common/lru-cache";
|
import { LRUCache } from "@/common/lru-cache";
|
||||||
//0 send 1recv
|
import { PacketContext } from "@/core/packet/context/packetContext";
|
||||||
|
|
||||||
|
// 0 send 1 recv
|
||||||
export interface NativePacketExportType {
|
export interface NativePacketExportType {
|
||||||
InitHook?: (recv: string, send: string, callback: (type: number, uin: string, cmd: string, seq: number, hex_data: string) => void) => boolean;
|
InitHook?: (recv: string, send: string, callback: (type: number, uin: string, cmd: string, seq: number, hex_data: string) => void) => boolean;
|
||||||
SendPacket?: (cmd: string, data: string, trace_id: string) => void;
|
SendPacket?: (cmd: string, data: string, trace_id: string) => void;
|
||||||
}
|
}
|
||||||
export class NativePacketClient extends PacketClient {
|
|
||||||
|
export class NativePacketClient extends IPacketClient {
|
||||||
private readonly supportedPlatforms = ['win32.x64', 'linux.x64', 'linux.arm64'];
|
private readonly supportedPlatforms = ['win32.x64', 'linux.x64', 'linux.arm64'];
|
||||||
private MoeHooExport: { exports: NativePacketExportType } = { exports: {} };
|
private MoeHooExport: { exports: NativePacketExportType } = { exports: {} };
|
||||||
private sendEvent = new LRUCache<number, string>(500);//seq->trace_id
|
private sendEvent = new LRUCache<number, string>(500); // seq->trace_id
|
||||||
constructor(core: NapCatCore) {
|
|
||||||
super(core);
|
|
||||||
}
|
|
||||||
|
|
||||||
get available(): boolean {
|
constructor(context: PacketContext) {
|
||||||
return this.isAvailable;
|
super(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
check(): boolean {
|
check(): boolean {
|
||||||
const platform = process.platform + '.' + process.arch;
|
const platform = process.platform + '.' + process.arch;
|
||||||
if (!this.supportedPlatforms.includes(platform)) {
|
if (!this.supportedPlatforms.includes(platform)) {
|
||||||
this.logger.logWarn(`[Core] [Packet:Native] 不支持的平台: ${platform}`);
|
this.context.logger.warn(`不支持的平台: ${platform}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './moehoo/MoeHoo.' + platform + '.node');
|
const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './moehoo/MoeHoo.' + platform + '.node');
|
||||||
if (!fs.existsSync(moehoo_path)) {
|
if (!fs.existsSync(moehoo_path)) {
|
||||||
this.logger.logWarn(`[Core] [Packet:Native] 缺失运行时文件: ${moehoo_path}`);
|
this.context.logger.warn(`[Core] [Packet:Native] 缺失运行时文件: ${moehoo_path}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -54,27 +53,13 @@ export class NativePacketClient extends PacketClient {
|
|||||||
// console.log('callback:', callback, trace_id);
|
// console.log('callback:', callback, trace_id);
|
||||||
callback?.({ seq, cmd, hex_data });
|
callback?.({ seq, cmd, hex_data });
|
||||||
}
|
}
|
||||||
|
|
||||||
// const callback = this.cb.get(createHash('md5').update(Buffer.from(hex_data, 'hex')).digest('hex') + (type === 0 ? 'send' : 'recv'));
|
|
||||||
// if (callback) {
|
|
||||||
// callback({ seq, cmd, hex_data });
|
|
||||||
// } else {
|
|
||||||
// this.logger.logError(`Callback not found for hex_data: ${hex_data}`);
|
|
||||||
// }
|
|
||||||
//console.log('type:', type, 'cmd:', cmd, 'trace_id:', trace_id);
|
|
||||||
});
|
});
|
||||||
this.isAvailable = true;
|
this.available = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
sendCommandImpl(cmd: string, data: string, trace_id: string): void {
|
sendCommandImpl(cmd: string, data: string, trace_id: string): void {
|
||||||
const trace_id_md5 = createHash('md5').update(trace_id).digest('hex');
|
const trace_id_md5 = createHash('md5').update(trace_id).digest('hex');
|
||||||
//console.log('sendCommandImpl:', cmd, data, trace_id_md5);
|
|
||||||
this.MoeHooExport.exports.SendPacket?.(cmd, data, trace_id_md5);
|
this.MoeHooExport.exports.SendPacket?.(cmd, data, trace_id_md5);
|
||||||
this.cb.get(trace_id_md5 + 'send')?.({ seq: 0, cmd, hex_data: '' });
|
this.cb.get(trace_id_md5 + 'send')?.({ seq: 0, cmd, hex_data: '' });
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(cb: () => void): Promise<void> {
|
|
||||||
cb();
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,98 +1,100 @@
|
|||||||
import { Data, WebSocket } from "ws";
|
import { Data, WebSocket } from "ws";
|
||||||
import { NapCatCore } from "@/core";
|
import { IPacketClient, RecvPacket } from "@/core/packet/client/baseClient";
|
||||||
import { PacketClient, RecvPacket } from "@/core/packet/client/client";
|
import { PacketContext } from "@/core/packet/context/packetContext";
|
||||||
|
|
||||||
export class wsPacketClient extends PacketClient {
|
export class wsPacketClient extends IPacketClient {
|
||||||
private websocket: WebSocket | undefined;
|
private websocket: WebSocket | null = null;
|
||||||
private reconnectAttempts: number = 0;
|
private reconnectAttempts: number = 0;
|
||||||
private readonly maxReconnectAttempts: number = 60; // 现在暂时不可配置
|
private readonly maxReconnectAttempts: number = 60; // 现在暂时不可配置
|
||||||
private readonly clientUrl: string | null = null;
|
private readonly clientUrl: string;
|
||||||
private clientUrlWrap: (url: string) => string = (url: string) => `ws://${url}/ws`;
|
private readonly clientUrlWrap: (url: string) => string = (url: string) => `ws://${url}/ws`;
|
||||||
|
|
||||||
constructor(core: NapCatCore) {
|
private isInitialized: boolean = false;
|
||||||
super(core);
|
private initPayload: { pid: number, recv: string, send: string } | null = null;
|
||||||
this.clientUrl = this.config.packetServer ? this.clientUrlWrap( this.config.packetServer) : null;
|
|
||||||
|
constructor(context: PacketContext) {
|
||||||
|
super(context);
|
||||||
|
this.clientUrl = this.context.napcore.config.packetServer
|
||||||
|
? this.clientUrlWrap(this.context.napcore.config.packetServer)
|
||||||
|
: this.clientUrlWrap('127.0.0.1:8083');
|
||||||
}
|
}
|
||||||
|
|
||||||
check(): boolean {
|
check(): boolean {
|
||||||
if (!this.clientUrl) {
|
if (!this.context.napcore.config.packetServer) {
|
||||||
this.logger.logWarn(`[Core] [Packet:Server] 未配置服务器地址`);
|
this.context.logger.warn(`wsPacketClient 未配置服务器地址`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(cb: () => void): Promise<void> {
|
async init(pid: number, recv: string, send: string): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
this.initPayload = { pid, recv, send };
|
||||||
//this.logger.log.bind(this.logger)(`[Core] [Packet Server] Attempting to connect to ${this.clientUrl}`);
|
await this.connectWithRetry();
|
||||||
this.websocket = new WebSocket(this.clientUrl!);
|
}
|
||||||
this.websocket.on('error', (err) => { }/*this.logger.logError.bind(this.logger)('[Core] [Packet Server] Error:', err.message)*/);
|
|
||||||
|
|
||||||
|
sendCommandImpl(cmd: string, data: string, trace_id: string): void {
|
||||||
|
if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
|
||||||
|
this.websocket.send(JSON.stringify({
|
||||||
|
action: 'send',
|
||||||
|
cmd,
|
||||||
|
data,
|
||||||
|
trace_id
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
this.context.logger.warn(`WebSocket 未连接,无法发送命令: ${cmd}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async connectWithRetry(): Promise<void> {
|
||||||
|
while (this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||||
|
try {
|
||||||
|
await this.connect();
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
this.reconnectAttempts++;
|
||||||
|
this.context.logger.warn(`第 ${this.reconnectAttempts}/${this.maxReconnectAttempts} 次尝试重连失败!`);
|
||||||
|
await this.delay(5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.context.logger.error(`wsPacketClient 在 ${this.clientUrl} 达到最大重连次数 (${this.maxReconnectAttempts})!`);
|
||||||
|
throw new Error(`无法连接到 WebSocket 服务器:${this.clientUrl}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private connect(): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.websocket = new WebSocket(this.clientUrl);
|
||||||
this.websocket.onopen = () => {
|
this.websocket.onopen = () => {
|
||||||
this.isAvailable = true;
|
this.available = true;
|
||||||
this.reconnectAttempts = 0;
|
this.reconnectAttempts = 0;
|
||||||
this.logger.log.bind(this.logger)(`[Core] [Packet:Server] 已连接到 ${this.clientUrl}`);
|
this.context.logger.info(`wsPacketClient 已连接到 ${this.clientUrl}`);
|
||||||
cb();
|
if (!this.isInitialized && this.initPayload) {
|
||||||
|
this.websocket!.send(JSON.stringify({
|
||||||
|
action: 'init',
|
||||||
|
...this.initPayload
|
||||||
|
}));
|
||||||
|
this.isInitialized = true;
|
||||||
|
}
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.websocket.onerror = (error) => {
|
|
||||||
//this.logger.logError.bind(this.logger)(`WebSocket error: ${error}`);
|
|
||||||
reject(new Error(`${error.message}`));
|
|
||||||
};
|
|
||||||
|
|
||||||
this.websocket.onmessage = (event) => {
|
|
||||||
// const message = JSON.parse(event.data.toString());
|
|
||||||
// console.log("Received message:", message);
|
|
||||||
this.handleMessage(event.data).then().catch();
|
|
||||||
};
|
|
||||||
|
|
||||||
this.websocket.onclose = () => {
|
this.websocket.onclose = () => {
|
||||||
this.isAvailable = false;
|
this.available = false;
|
||||||
//this.logger.logWarn.bind(this.logger)(`[Core] [Packet Server] Disconnected from ${this.clientUrl}`);
|
this.context.logger.warn(`WebSocket 连接关闭,尝试重连...`);
|
||||||
this.attemptReconnect(cb);
|
reject(new Error('WebSocket 连接关闭'));
|
||||||
|
};
|
||||||
|
this.websocket.onmessage = (event) => this.handleMessage(event.data).catch(err => {
|
||||||
|
this.context.logger.error(`处理消息时出错: ${err}`);
|
||||||
|
});
|
||||||
|
this.websocket.onerror = (error) => {
|
||||||
|
this.available = false;
|
||||||
|
this.context.logger.error(`WebSocket 出错: ${error.message}`);
|
||||||
|
this.websocket?.close();
|
||||||
|
reject(error);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private attemptReconnect(cb: any): void {
|
private delay(ms: number): Promise<void> {
|
||||||
try {
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
||||||
this.reconnectAttempts++;
|
|
||||||
setTimeout(() => {
|
|
||||||
this.connect(cb).catch((error) => {
|
|
||||||
this.logger.logError.bind(this.logger)(`[Core] [Packet:Server] 尝试重连失败:${error.message}`);
|
|
||||||
});
|
|
||||||
}, 5000 * this.reconnectAttempts);
|
|
||||||
} else {
|
|
||||||
this.logger.logError.bind(this.logger)(`[Core] [Packet:Server] ${this.clientUrl} 已达到最大重连次数!`);
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
this.logger.logError.bind(this.logger)(`[Core] [Packet:Server] 重连时出错: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async init(pid: number, recv: string, send: string): Promise<void> {
|
|
||||||
if (!this.isAvailable || !this.websocket) {
|
|
||||||
throw new Error("WebSocket is not connected");
|
|
||||||
}
|
|
||||||
const initMessage = {
|
|
||||||
action: 'init',
|
|
||||||
pid: pid,
|
|
||||||
recv: recv,
|
|
||||||
send: send
|
|
||||||
};
|
|
||||||
this.websocket.send(JSON.stringify(initMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
sendCommandImpl(cmd: string, data: string, trace_id: string) : void {
|
|
||||||
const commandMessage = {
|
|
||||||
action: 'send',
|
|
||||||
cmd: cmd,
|
|
||||||
data: data,
|
|
||||||
trace_id: trace_id
|
|
||||||
};
|
|
||||||
this.websocket!.send(JSON.stringify(commandMessage));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleMessage(message: Data): Promise<void> {
|
private async handleMessage(message: Data): Promise<void> {
|
||||||
@@ -100,13 +102,10 @@ export class wsPacketClient extends PacketClient {
|
|||||||
const json: RecvPacket = JSON.parse(message.toString());
|
const json: RecvPacket = JSON.parse(message.toString());
|
||||||
const trace_id_md5 = json.trace_id_md5;
|
const trace_id_md5 = json.trace_id_md5;
|
||||||
const action = json?.type ?? 'init';
|
const action = json?.type ?? 'init';
|
||||||
const event = this.cb.get(trace_id_md5 + action);
|
const event = this.cb.get(`${trace_id_md5}${action}`);
|
||||||
if (event) {
|
if (event) await event(json.data);
|
||||||
await event(json.data);
|
|
||||||
}
|
|
||||||
//console.log("Received message:", json);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.logError.bind(this.logger)(`Error parsing message: ${error}`);
|
this.context.logger.error(`解析ws消息时出错: ${(error as Error).message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
27
src/core/packet/clientSession.ts
Normal file
27
src/core/packet/clientSession.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { PacketContext } from "@/core/packet/context/packetContext";
|
||||||
|
import { NapCatCore } from "@/core";
|
||||||
|
|
||||||
|
export class PacketClientSession {
|
||||||
|
private readonly context: PacketContext;
|
||||||
|
|
||||||
|
constructor(core: NapCatCore) {
|
||||||
|
this.context = new PacketContext(core);
|
||||||
|
}
|
||||||
|
|
||||||
|
init(pid: number, recv: string, send: string): Promise<void> {
|
||||||
|
return this.context.client.init(pid, recv, send);
|
||||||
|
}
|
||||||
|
|
||||||
|
get available() {
|
||||||
|
return this.context.client.available;
|
||||||
|
}
|
||||||
|
|
||||||
|
get operation() {
|
||||||
|
return this.context.operation;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: global message element adapter (?
|
||||||
|
get msgConverter() {
|
||||||
|
return this.context.msgConverter;
|
||||||
|
}
|
||||||
|
}
|
82
src/core/packet/context/clientContext.ts
Normal file
82
src/core/packet/context/clientContext.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import { PacketContext } from "@/core/packet/context/packetContext";
|
||||||
|
import { IPacketClient } from "@/core/packet/client/baseClient";
|
||||||
|
import { NativePacketClient } from "@/core/packet/client/nativeClient";
|
||||||
|
import { wsPacketClient } from "@/core/packet/client/wsClient";
|
||||||
|
import { OidbPacket } from "@/core/packet/transformer/base";
|
||||||
|
|
||||||
|
type clientPriority = {
|
||||||
|
[key: number]: (context: PacketContext) => IPacketClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientPriority: clientPriority = {
|
||||||
|
10: (context: PacketContext) => new NativePacketClient(context),
|
||||||
|
1: (context: PacketContext) => new wsPacketClient(context),
|
||||||
|
};
|
||||||
|
|
||||||
|
export class PacketClientContext {
|
||||||
|
private readonly _client: IPacketClient;
|
||||||
|
private readonly context: PacketContext;
|
||||||
|
|
||||||
|
constructor(context: PacketContext) {
|
||||||
|
this.context = context;
|
||||||
|
this._client = this.newClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
get available(): boolean {
|
||||||
|
return this._client.available;
|
||||||
|
}
|
||||||
|
|
||||||
|
async init(pid: number, recv: string, send: string): Promise<void> {
|
||||||
|
await this._client.init(pid, recv, send);
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendOidbPacket(pkt: OidbPacket, rsp = false): Promise<Buffer> {
|
||||||
|
console.log("REQ", pkt.cmd, pkt.data);
|
||||||
|
const raw = await this._client.sendOidbPacket(pkt, rsp);
|
||||||
|
console.log("RES", raw.cmd, raw.hex_data);
|
||||||
|
return Buffer.from(raw.hex_data, "hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
private newClient(): IPacketClient {
|
||||||
|
const prefer = this.context.napcore.config.packetBackend;
|
||||||
|
let client: IPacketClient | null;
|
||||||
|
switch (prefer) {
|
||||||
|
case "native":
|
||||||
|
this.context.logger.info("使用指定的 NativePacketClient 作为后端");
|
||||||
|
client = new NativePacketClient(this.context);
|
||||||
|
break;
|
||||||
|
case "frida":
|
||||||
|
this.context.logger.info("[Core] [Packet] 使用指定的 FridaPacketClient 作为后端");
|
||||||
|
client = new wsPacketClient(this.context);
|
||||||
|
break;
|
||||||
|
case "auto":
|
||||||
|
case undefined:
|
||||||
|
client = this.judgeClient();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.context.logger.error(`未知的PacketBackend ${prefer},请检查配置文件!`);
|
||||||
|
client = null;
|
||||||
|
}
|
||||||
|
if (!(client && client.check())) {
|
||||||
|
throw new Error("[Core] [Packet] 无可用的后端,NapCat.Packet将不会加载!");
|
||||||
|
}
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
private judgeClient(): IPacketClient {
|
||||||
|
const sortedClients = Object.entries(clientPriority)
|
||||||
|
.map(([priority, clientFactory]) => {
|
||||||
|
const client = clientFactory(this.context);
|
||||||
|
const score = +priority * +client.check();
|
||||||
|
return { client, score };
|
||||||
|
})
|
||||||
|
.filter(({ score }) => score > 0)
|
||||||
|
.sort((a, b) => b.score - a.score);
|
||||||
|
const selectedClient = sortedClients[0]?.client;
|
||||||
|
if (!selectedClient) {
|
||||||
|
throw new Error("[Core] [Packet] 无可用的后端,NapCat.Packet将不会加载!");
|
||||||
|
}
|
||||||
|
this.context.logger.info(`自动选择 ${selectedClient.constructor.name} 作为后端`);
|
||||||
|
return selectedClient;
|
||||||
|
}
|
||||||
|
}
|
35
src/core/packet/context/loggerContext.ts
Normal file
35
src/core/packet/context/loggerContext.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { LogLevel, LogWrapper } from "@/common/log";
|
||||||
|
import { PacketContext } from "@/core/packet/context/packetContext";
|
||||||
|
|
||||||
|
// TODO: check bind?
|
||||||
|
export class PacketLogger {
|
||||||
|
private readonly napLogger: LogWrapper;
|
||||||
|
|
||||||
|
constructor(context: PacketContext) {
|
||||||
|
this.napLogger = context.napcore.logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _log(level: LogLevel, ...msg: any[]): void {
|
||||||
|
this.napLogger._log(level, "[Core] [Packet] " + msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug(...msg: any[]): void {
|
||||||
|
this._log(LogLevel.DEBUG, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
info(...msg: any[]): void {
|
||||||
|
this._log(LogLevel.INFO, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
warn(...msg: any[]): void {
|
||||||
|
this._log(LogLevel.WARN, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
error(...msg: any[]): void {
|
||||||
|
this._log(LogLevel.ERROR, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
fatal(...msg: any[]): void {
|
||||||
|
this._log(LogLevel.FATAL, msg);
|
||||||
|
}
|
||||||
|
}
|
36
src/core/packet/context/napCoreContext.ts
Normal file
36
src/core/packet/context/napCoreContext.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { NapCatCore } from "@/core";
|
||||||
|
|
||||||
|
export interface NapCoreCompatBasicInfo {
|
||||||
|
readonly uin: number;
|
||||||
|
readonly uid: string;
|
||||||
|
readonly uin2uid: (uin: number) => Promise<string>;
|
||||||
|
readonly uid2uin: (uid: string) => Promise<number>;
|
||||||
|
readonly sendSsoCmdReqByContend: (cmd: string, trace_id: string) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NapCoreContext {
|
||||||
|
private readonly core: NapCatCore;
|
||||||
|
|
||||||
|
constructor(core: NapCatCore) {
|
||||||
|
this.core = core;
|
||||||
|
}
|
||||||
|
|
||||||
|
get logger() {
|
||||||
|
return this.core.context.logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
get basicInfo() {
|
||||||
|
return {
|
||||||
|
uin: +this.core.selfInfo.uin,
|
||||||
|
uid: this.core.selfInfo.uid,
|
||||||
|
uin2uid: (uin: number) => this.core.apis.UserApi.getUidByUinV2(String(uin)).then(res => res ?? ''),
|
||||||
|
uid2uin: (uid: string) => this.core.apis.UserApi.getUinByUidV2(uid).then(res => +res),
|
||||||
|
} as NapCoreCompatBasicInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
get config() {
|
||||||
|
return this.core.configLoader.configData;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendSsoCmdReqByContend = (cmd: string, trace_id: string) => this.core.context.session.getMsgService().sendSsoCmdReqByContend(cmd, trace_id);
|
||||||
|
}
|
165
src/core/packet/context/operationContext.ts
Normal file
165
src/core/packet/context/operationContext.ts
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import * as crypto from 'crypto';
|
||||||
|
import { PacketContext } from "@/core/packet/context/packetContext";
|
||||||
|
import * as trans from "@/core/packet/transformer";
|
||||||
|
import { PacketMsg } from "@/core/packet/message/message";
|
||||||
|
import {
|
||||||
|
PacketMsgFileElement,
|
||||||
|
PacketMsgPicElement,
|
||||||
|
PacketMsgPttElement,
|
||||||
|
PacketMsgVideoElement
|
||||||
|
} from "@/core/packet/message/element";
|
||||||
|
import { ChatType } from "@/core";
|
||||||
|
import { MiniAppRawData, MiniAppReqParams } from "@/core/packet/entities/miniApp";
|
||||||
|
import { AIVoiceChatType } from "@/core/packet/entities/aiChat";
|
||||||
|
import { NapProtoDecodeStructType, NapProtoEncodeStructType } from "@napneko/nap-proto-core";
|
||||||
|
import { IndexNode, MsgInfo } from "@/core/packet/transformer/proto";
|
||||||
|
|
||||||
|
export class PacketOperationContext {
|
||||||
|
private context: PacketContext;
|
||||||
|
constructor(context: PacketContext) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
async GroupPoke(groupUin: number, uin: number) {
|
||||||
|
const req = trans.SendPoke.build(groupUin, uin);
|
||||||
|
await this.context.client.sendOidbPacket(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
async FriendPoke(uin: number) {
|
||||||
|
const req = trans.SendPoke.build(uin);
|
||||||
|
await this.context.client.sendOidbPacket(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
async FetchRkey() {
|
||||||
|
const req = trans.FetchRkey.build();
|
||||||
|
const resp = await this.context.client.sendOidbPacket(req, true);
|
||||||
|
const res = trans.FetchRkey.parse(resp);
|
||||||
|
return res.data.rkeyList;
|
||||||
|
}
|
||||||
|
|
||||||
|
async GroupSign(groupUin: number) {
|
||||||
|
const req = trans.GroupSign.build(this.context.napcore.basicInfo.uin, groupUin);
|
||||||
|
await this.context.client.sendOidbPacket(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetStrangerStatus(uin: number) {
|
||||||
|
let status = 0;
|
||||||
|
try {
|
||||||
|
const req = trans.GetStrangerInfo.build(uin);
|
||||||
|
const resp = await this.context.client.sendOidbPacket(req, true);
|
||||||
|
const res = trans.GetStrangerInfo.parse(resp);
|
||||||
|
const extBigInt = BigInt(res.data.status.value);
|
||||||
|
if (extBigInt <= 10n) {
|
||||||
|
return { status: Number(extBigInt) * 10, ext_status: 0 };
|
||||||
|
}
|
||||||
|
status = Number((extBigInt & 0xff00n) + ((extBigInt >> 16n) & 0xffn));
|
||||||
|
return { status: 10, ext_status: status };
|
||||||
|
} catch (e) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async SetGroupSpecialTitle(groupUin: number, uid: string, tittle: string) {
|
||||||
|
const req = trans.SetSpecialTitle.build(groupUin, uid, tittle);
|
||||||
|
await this.context.client.sendOidbPacket(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
async UploadResources(msg: PacketMsg[], groupUin: number = 0) {
|
||||||
|
const reqList = [];
|
||||||
|
for (const m of msg) {
|
||||||
|
for (const e of m.msg) {
|
||||||
|
if (e instanceof PacketMsgPicElement) {
|
||||||
|
reqList.push(this.context.highway.uploadImage({
|
||||||
|
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
|
||||||
|
peerUid: groupUin ? String(groupUin) : this.context.napcore.basicInfo.uid
|
||||||
|
}, e));
|
||||||
|
}
|
||||||
|
if (e instanceof PacketMsgVideoElement) {
|
||||||
|
reqList.push(this.context.highway.uploadVideo({
|
||||||
|
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
|
||||||
|
peerUid: groupUin ? String(groupUin) : this.context.napcore.basicInfo.uid
|
||||||
|
}, e));
|
||||||
|
}
|
||||||
|
if (e instanceof PacketMsgPttElement) {
|
||||||
|
reqList.push(this.context.highway.uploadPtt({
|
||||||
|
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
|
||||||
|
peerUid: groupUin ? String(groupUin) : this.context.napcore.basicInfo.uid
|
||||||
|
}, e));
|
||||||
|
}
|
||||||
|
if (e instanceof PacketMsgFileElement) {
|
||||||
|
reqList.push(this.context.highway.uploadFile({
|
||||||
|
chatType: groupUin ? ChatType.KCHATTYPEGROUP : ChatType.KCHATTYPEC2C,
|
||||||
|
peerUid: groupUin ? String(groupUin) : this.context.napcore.basicInfo.uid
|
||||||
|
}, e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const res = await Promise.allSettled(reqList);
|
||||||
|
this.context.logger.info(`上传资源${res.length}个,失败${res.filter(r => r.status === 'rejected').length}个`);
|
||||||
|
res.forEach((result, index) => {
|
||||||
|
if (result.status === 'rejected') {
|
||||||
|
this.context.logger.error(`上传第${index + 1}个资源失败:${result.reason.stack}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async UploadForwardMsg(msg: PacketMsg[], groupUin: number = 0) {
|
||||||
|
await this.UploadResources(msg, groupUin);
|
||||||
|
const req = trans.UploadForwardMsg.build(this.context.napcore.basicInfo.uid, msg, groupUin);
|
||||||
|
const resp = await this.context.client.sendOidbPacket(req, true);
|
||||||
|
const res = trans.UploadForwardMsg.parse(resp);
|
||||||
|
return res.result.resId;
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetGroupFileUrl(groupUin: number, fileUUID: string) {
|
||||||
|
const req = trans.DownloadGroupFile.build(groupUin, fileUUID);
|
||||||
|
const resp = await this.context.client.sendOidbPacket(req, true);
|
||||||
|
const res = trans.DownloadGroupFile.parse(resp);
|
||||||
|
return `https://${res.download.downloadDns}/ftn_handler/${Buffer.from(res.download.downloadUrl).toString('hex')}/?fname=`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: why type hint is not working here?
|
||||||
|
async GetGroupPttUrl(groupUin: number, node: NapProtoEncodeStructType<typeof IndexNode>) {
|
||||||
|
const req = trans.DownloadGroupPtt.build(groupUin, node);
|
||||||
|
const resp = await this.context.client.sendOidbPacket(req, true);
|
||||||
|
const res = trans.DownloadGroupPtt.parse(resp);
|
||||||
|
return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetMiniAppAdaptShareInfo(param: MiniAppReqParams) {
|
||||||
|
const req = trans.GetMiniAppAdaptShareInfo.build(param);
|
||||||
|
const resp = await this.context.client.sendOidbPacket(req, true);
|
||||||
|
const res = trans.GetMiniAppAdaptShareInfo.parse(resp);
|
||||||
|
return JSON.parse(res.content.jsonContent) as MiniAppRawData;
|
||||||
|
}
|
||||||
|
|
||||||
|
async FetchAiVoiceList(groupUin: number, chatType: AIVoiceChatType) {
|
||||||
|
const req = trans.FetchAiVoiceList.build(groupUin, chatType);
|
||||||
|
const resp = await this.context.client.sendOidbPacket(req, true);
|
||||||
|
const res = trans.FetchAiVoiceList.parse(resp);
|
||||||
|
if (!res.content) return null;
|
||||||
|
return res.content.map((item) => {
|
||||||
|
return {
|
||||||
|
category: item.category,
|
||||||
|
voices: item.voices
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async GetAiVoice(groupUin: number, voiceId: string, text: string, chatType: AIVoiceChatType): Promise<NapProtoDecodeStructType<typeof MsgInfo>> {
|
||||||
|
let reqTime = 0;
|
||||||
|
const reqMaxTime = 30;
|
||||||
|
const sessionId = crypto.randomBytes(4).readUInt32BE(0);
|
||||||
|
while (true) {
|
||||||
|
if (reqTime >= reqMaxTime) {
|
||||||
|
throw new Error(`sendAiVoiceChatReq failed after ${reqMaxTime} times`);
|
||||||
|
}
|
||||||
|
reqTime++;
|
||||||
|
const req = trans.GetAiVoice.build(groupUin, voiceId, text, sessionId, chatType);
|
||||||
|
const resp = await this.context.client.sendOidbPacket(req, true);
|
||||||
|
const res = trans.GetAiVoice.parse(resp);
|
||||||
|
if (!res.msgInfo) continue;
|
||||||
|
return res.msgInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
src/core/packet/context/packetContext.ts
Normal file
25
src/core/packet/context/packetContext.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { PacketHighwayContext } from "@/core/packet/highway/highwayContext";
|
||||||
|
import { NapCatCore } from "@/core";
|
||||||
|
import { PacketLogger } from "@/core/packet/context/loggerContext";
|
||||||
|
import { NapCoreContext } from "@/core/packet/context/napCoreContext";
|
||||||
|
import { PacketClientContext } from "@/core/packet/context/clientContext";
|
||||||
|
import { PacketOperationContext } from "@/core/packet/context/operationContext";
|
||||||
|
import { PacketMsgConverter } from "@/core/packet/message/converter";
|
||||||
|
|
||||||
|
export class PacketContext {
|
||||||
|
readonly napcore: NapCoreContext;
|
||||||
|
readonly logger: PacketLogger;
|
||||||
|
readonly client: PacketClientContext;
|
||||||
|
readonly highway: PacketHighwayContext;
|
||||||
|
readonly msgConverter: PacketMsgConverter;
|
||||||
|
readonly operation: PacketOperationContext;
|
||||||
|
|
||||||
|
constructor(core: NapCatCore) {
|
||||||
|
this.napcore = new NapCoreContext(core);
|
||||||
|
this.logger = new PacketLogger(this);
|
||||||
|
this.client = new PacketClientContext(this);
|
||||||
|
this.highway = new PacketHighwayContext(this);
|
||||||
|
this.msgConverter = new PacketMsgConverter();
|
||||||
|
this.operation = new PacketOperationContext(this);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,8 +1,9 @@
|
|||||||
import * as stream from 'node:stream';
|
import * as stream from 'node:stream';
|
||||||
import { ReadStream } from "node:fs";
|
import { ReadStream } from "node:fs";
|
||||||
import { PacketHighwaySig } from "@/core/packet/highway/session";
|
import { HighwayTcpUploader } from "@/core/packet/highway/uploader/highwayTcpUploader";
|
||||||
import { HighwayHttpUploader, HighwayTcpUploader } from "@/core/packet/highway/uploader";
|
import { HighwayHttpUploader } from "@/core/packet/highway/uploader/highwayHttpUploader";
|
||||||
import { LogWrapper } from "@/common/log";
|
import { PacketHighwaySig } from "@/core/packet/highway/highwayContext";
|
||||||
|
import { PacketLogger } from "@/core/packet/context/loggerContext";
|
||||||
|
|
||||||
export interface PacketHighwayTrans {
|
export interface PacketHighwayTrans {
|
||||||
uin: string;
|
uin: string;
|
||||||
@@ -24,9 +25,9 @@ export class PacketHighwayClient {
|
|||||||
sig: PacketHighwaySig;
|
sig: PacketHighwaySig;
|
||||||
server: string = 'htdata3.qq.com';
|
server: string = 'htdata3.qq.com';
|
||||||
port: number = 80;
|
port: number = 80;
|
||||||
logger: LogWrapper;
|
logger: PacketLogger;
|
||||||
|
|
||||||
constructor(sig: PacketHighwaySig, logger: LogWrapper, server: string = 'htdata3.qq.com', port: number = 80) {
|
constructor(sig: PacketHighwaySig, logger: PacketLogger, server: string = 'htdata3.qq.com', port: number = 80) {
|
||||||
this.sig = sig;
|
this.sig = sig;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
@@ -59,12 +60,12 @@ export class PacketHighwayClient {
|
|||||||
const tcpUploader = new HighwayTcpUploader(trans, this.logger);
|
const tcpUploader = new HighwayTcpUploader(trans, this.logger);
|
||||||
await tcpUploader.upload();
|
await tcpUploader.upload();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.logError(`[Highway] upload failed: ${e}, fallback to http upload`);
|
this.logger.error(`[Highway] upload failed: ${e}, fallback to http upload`);
|
||||||
try {
|
try {
|
||||||
const httpUploader = new HighwayHttpUploader(trans, this.logger);
|
const httpUploader = new HighwayHttpUploader(trans, this.logger);
|
||||||
await httpUploader.upload();
|
await httpUploader.upload();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.logError(`[Highway] http upload failed: ${e}`);
|
this.logger.error(`[Highway] http upload failed: ${e}`);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,24 +1,21 @@
|
|||||||
import * as fs from "node:fs";
|
|
||||||
import { ChatType, Peer } from "@/core";
|
|
||||||
import { LogWrapper } from "@/common/log";
|
|
||||||
import { PacketPacker } from "@/core/packet/packer";
|
|
||||||
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
|
||||||
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 { PacketContext } from "@/core/packet/context/packetContext";
|
||||||
import { OidbSvcTrpcTcpBaseRsp } from "@/core/packet/proto/oidb/OidbBase";
|
import { PacketLogger } from "@/core/packet/context/loggerContext";
|
||||||
|
import FetchSessionKey from "@/core/packet/transformer/highway/FetchSessionKey";
|
||||||
|
import { int32ip2str, oidbIpv4s2HighwayIpv4s } from "@/core/packet/highway/utils";
|
||||||
import {
|
import {
|
||||||
PacketMsgFileElement,
|
PacketMsgFileElement,
|
||||||
PacketMsgPicElement,
|
PacketMsgPicElement,
|
||||||
PacketMsgPttElement,
|
PacketMsgPttElement,
|
||||||
PacketMsgVideoElement
|
PacketMsgVideoElement
|
||||||
} from "@/core/packet/message/element";
|
} from "@/core/packet/message/element";
|
||||||
import { FileUploadExt, NTV2RichMediaHighwayExt } from "@/core/packet/proto/highway/highway";
|
import { ChatType, Peer } from "@/core";
|
||||||
import { int32ip2str, oidbIpv4s2HighwayIpv4s } from "@/core/packet/highway/utils";
|
|
||||||
import { calculateSha1, calculateSha1StreamBytes, computeMd5AndLengthWithLimit } from "@/core/packet/utils/crypto/hash";
|
import { calculateSha1, calculateSha1StreamBytes, computeMd5AndLengthWithLimit } from "@/core/packet/utils/crypto/hash";
|
||||||
import { OidbSvcTrpcTcp0x6D6Response } from "@/core/packet/proto/oidb/Oidb.0x6D6";
|
import UploadGroupImage from "@/core/packet/transformer/highway/UploadGroupImage";
|
||||||
import { OidbSvcTrpcTcp0XE37_800Response, OidbSvcTrpcTcp0XE37Response } from "@/core/packet/proto/oidb/Oidb.0XE37_800";
|
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||||
import { PacketClient } from "@/core/packet/client/client";
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
|
import * as trans from "@/core/packet/transformer";
|
||||||
|
import fs from "fs";
|
||||||
|
|
||||||
export const BlockSize = 1024 * 1024;
|
export const BlockSize = 1024 * 1024;
|
||||||
|
|
||||||
@@ -35,34 +32,28 @@ export interface PacketHighwaySig {
|
|||||||
serverAddr: HighwayServerAddr[]
|
serverAddr: HighwayServerAddr[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PacketHighwaySession {
|
export class PacketHighwayContext {
|
||||||
protected packetClient: PacketClient;
|
private context: PacketContext;
|
||||||
protected packetHighwayClient: PacketHighwayClient;
|
|
||||||
protected sig: PacketHighwaySig;
|
protected sig: PacketHighwaySig;
|
||||||
protected logger: LogWrapper;
|
protected logger: PacketLogger;
|
||||||
protected packer: PacketPacker;
|
protected hwClient: PacketHighwayClient;
|
||||||
private cachedPrepareReq: Promise<void> | null = null;
|
private cachedPrepareReq: Promise<void> | null = null;
|
||||||
|
|
||||||
constructor(logger: LogWrapper, client: PacketClient, packer: PacketPacker) {
|
constructor(context: PacketContext) {
|
||||||
this.packetClient = client;
|
this.context = context;
|
||||||
this.logger = logger;
|
|
||||||
this.sig = {
|
this.sig = {
|
||||||
uin: this.packetClient.napCatCore.selfInfo.uin,
|
uin: String(context.napcore.basicInfo.uin),
|
||||||
uid: this.packetClient.napCatCore.selfInfo.uid,
|
uid: context.napcore.basicInfo.uid,
|
||||||
sigSession: null,
|
sigSession: null,
|
||||||
sessionKey: null,
|
sessionKey: null,
|
||||||
serverAddr: [],
|
serverAddr: [],
|
||||||
};
|
};
|
||||||
this.packer = packer;
|
this.logger = context.logger;
|
||||||
this.packetHighwayClient = new PacketHighwayClient(this.sig, this.logger);
|
this.hwClient = new PacketHighwayClient(this.sig, context.logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async checkAvailable() {
|
private async checkAvailable() {
|
||||||
if (!this.packetClient.available) {
|
|
||||||
throw new Error('packetBackend不可用,请参照文档 https://napneko.github.io/config/advanced 和启动日志检查packetBackend状态或进行配置!');
|
|
||||||
}
|
|
||||||
if (this.sig.sigSession === null || this.sig.sessionKey === null) {
|
if (this.sig.sigSession === null || this.sig.sessionKey === null) {
|
||||||
this.logger.logWarn('[Highway] sigSession or sessionKey not available!');
|
|
||||||
if (this.cachedPrepareReq === null) {
|
if (this.cachedPrepareReq === null) {
|
||||||
this.cachedPrepareReq = this.prepareUpload().finally(() => {
|
this.cachedPrepareReq = this.prepareUpload().finally(() => {
|
||||||
this.cachedPrepareReq = null;
|
this.cachedPrepareReq = null;
|
||||||
@@ -73,17 +64,16 @@ export class PacketHighwaySession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async prepareUpload(): Promise<void> {
|
private async prepareUpload(): Promise<void> {
|
||||||
const packet = this.packer.packHttp0x6ff_501();
|
this.logger.debug('[Highway] on prepareUpload!');
|
||||||
const req = await this.packetClient.sendPacket('HttpConn.0x6ff_501', packet, true);
|
const packet = FetchSessionKey.build();
|
||||||
const rsp = new NapProtoMsg(HttpConn0x6ff_501Response).decode(
|
const req = await this.context.client.sendOidbPacket(packet, true);
|
||||||
Buffer.from(req.hex_data, 'hex')
|
const rsp = FetchSessionKey.parse(req);
|
||||||
);
|
|
||||||
this.sig.sigSession = rsp.httpConn.sigSession;
|
this.sig.sigSession = rsp.httpConn.sigSession;
|
||||||
this.sig.sessionKey = rsp.httpConn.sessionKey;
|
this.sig.sessionKey = rsp.httpConn.sessionKey;
|
||||||
for (const info of rsp.httpConn.serverInfos) {
|
for (const info of rsp.httpConn.serverInfos) {
|
||||||
if (info.serviceType !== 1) continue;
|
if (info.serviceType !== 1) continue;
|
||||||
for (const addr of info.serverAddrs) {
|
for (const addr of info.serverAddrs) {
|
||||||
this.logger.logDebug(`[Highway PrepareUpload] server addr add: ${int32ip2str(addr.ip)}:${addr.port}`);
|
this.logger.debug(`[Highway PrepareUpload] server addr add: ${int32ip2str(addr.ip)}:${addr.port}`);
|
||||||
this.sig.serverAddr.push({
|
this.sig.serverAddr.push({
|
||||||
ip: int32ip2str(addr.ip),
|
ip: int32ip2str(addr.ip),
|
||||||
port: addr.port
|
port: addr.port
|
||||||
@@ -95,9 +85,9 @@ export class PacketHighwaySession {
|
|||||||
async uploadImage(peer: Peer, img: PacketMsgPicElement): Promise<void> {
|
async uploadImage(peer: Peer, img: PacketMsgPicElement): Promise<void> {
|
||||||
await this.checkAvailable();
|
await this.checkAvailable();
|
||||||
if (peer.chatType === ChatType.KCHATTYPEGROUP) {
|
if (peer.chatType === ChatType.KCHATTYPEGROUP) {
|
||||||
await this.uploadGroupImageReq(+peer.peerUid, img);
|
await this.uploadGroupImage(+peer.peerUid, img);
|
||||||
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
|
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
|
||||||
await this.uploadC2CImageReq(peer.peerUid, img);
|
await this.uploadC2CImage(peer.peerUid, img);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`[Highway] unsupported chatType: ${peer.chatType}`);
|
throw new Error(`[Highway] unsupported chatType: ${peer.chatType}`);
|
||||||
}
|
}
|
||||||
@@ -109,9 +99,9 @@ export class PacketHighwaySession {
|
|||||||
throw new Error(`[Highway] 视频文件过大: ${(+(video.fileSize ?? 0) / (1024 * 1024)).toFixed(2)} MB > 100 MB,请使用文件上传!`);
|
throw new Error(`[Highway] 视频文件过大: ${(+(video.fileSize ?? 0) / (1024 * 1024)).toFixed(2)} MB > 100 MB,请使用文件上传!`);
|
||||||
}
|
}
|
||||||
if (peer.chatType === ChatType.KCHATTYPEGROUP) {
|
if (peer.chatType === ChatType.KCHATTYPEGROUP) {
|
||||||
await this.uploadGroupVideoReq(+peer.peerUid, video);
|
await this.uploadGroupVideo(+peer.peerUid, video);
|
||||||
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
|
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
|
||||||
await this.uploadC2CVideoReq(peer.peerUid, video);
|
await this.uploadC2CVideo(peer.peerUid, video);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`[Highway] unsupported chatType: ${peer.chatType}`);
|
throw new Error(`[Highway] unsupported chatType: ${peer.chatType}`);
|
||||||
}
|
}
|
||||||
@@ -120,9 +110,9 @@ export class PacketHighwaySession {
|
|||||||
async uploadPtt(peer: Peer, ptt: PacketMsgPttElement): Promise<void> {
|
async uploadPtt(peer: Peer, ptt: PacketMsgPttElement): Promise<void> {
|
||||||
await this.checkAvailable();
|
await this.checkAvailable();
|
||||||
if (peer.chatType === ChatType.KCHATTYPEGROUP) {
|
if (peer.chatType === ChatType.KCHATTYPEGROUP) {
|
||||||
await this.uploadGroupPttReq(+peer.peerUid, ptt);
|
await this.uploadGroupPtt(+peer.peerUid, ptt);
|
||||||
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
|
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
|
||||||
await this.uploadC2CPttReq(peer.peerUid, ptt);
|
await this.uploadC2CPtt(peer.peerUid, ptt);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`[Highway] unsupported chatType: ${peer.chatType}`);
|
throw new Error(`[Highway] unsupported chatType: ${peer.chatType}`);
|
||||||
}
|
}
|
||||||
@@ -131,29 +121,26 @@ export class PacketHighwaySession {
|
|||||||
async uploadFile(peer: Peer, file: PacketMsgFileElement): Promise<void> {
|
async uploadFile(peer: Peer, file: PacketMsgFileElement): Promise<void> {
|
||||||
await this.checkAvailable();
|
await this.checkAvailable();
|
||||||
if (peer.chatType === ChatType.KCHATTYPEGROUP) {
|
if (peer.chatType === ChatType.KCHATTYPEGROUP) {
|
||||||
await this.uploadGroupFileReq(+peer.peerUid, file);
|
await this.uploadGroupFile(+peer.peerUid, file);
|
||||||
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
|
} else if (peer.chatType === ChatType.KCHATTYPEC2C) {
|
||||||
await this.uploadC2CFileReq(peer.peerUid, file);
|
await this.uploadC2CFile(peer.peerUid, file);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`[Highway] unsupported chatType: ${peer.chatType}`);
|
throw new Error(`[Highway] unsupported chatType: ${peer.chatType}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async uploadGroupImageReq(groupUin: number, img: PacketMsgPicElement): Promise<void> {
|
private async uploadGroupImage(groupUin: number, img: PacketMsgPicElement): Promise<void> {
|
||||||
img.sha1 = Buffer.from(await calculateSha1(img.path)).toString('hex');
|
img.sha1 = Buffer.from(await calculateSha1(img.path)).toString('hex');
|
||||||
const preReq = await this.packer.packUploadGroupImgReq(groupUin, img);
|
const req = UploadGroupImage.build(groupUin, img);
|
||||||
const preRespRaw = await this.packetClient.sendOidbPacket(preReq, true);
|
const resp = await this.context.client.sendOidbPacket(req, true);
|
||||||
const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(
|
const preRespData = UploadGroupImage.parse(resp);
|
||||||
Buffer.from(preRespRaw.hex_data, 'hex')
|
|
||||||
);
|
|
||||||
const preRespData = new NapProtoMsg(NTV2RichMediaResp).decode(preResp.body);
|
|
||||||
const ukey = preRespData.upload.uKey;
|
const ukey = preRespData.upload.uKey;
|
||||||
if (ukey && ukey != "") {
|
if (ukey && ukey != "") {
|
||||||
this.logger.logDebug(`[Highway] uploadGroupImageReq get upload ukey: ${ukey}, need upload!`);
|
this.logger.debug(`[Highway] uploadGroupImageReq get upload ukey: ${ukey}, need upload!`);
|
||||||
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||||
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
||||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||||
const extend = new NapProtoMsg(NTV2RichMediaHighwayExt).encode({
|
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
|
||||||
fileUuid: index.fileUuid,
|
fileUuid: index.fileUuid,
|
||||||
uKey: ukey,
|
uKey: ukey,
|
||||||
network: {
|
network: {
|
||||||
@@ -165,7 +152,7 @@ export class PacketHighwaySession {
|
|||||||
fileSha1: [sha1]
|
fileSha1: [sha1]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await this.packetHighwayClient.upload(
|
await this.hwClient.upload(
|
||||||
1004,
|
1004,
|
||||||
fs.createReadStream(img.path, { highWaterMark: BlockSize }),
|
fs.createReadStream(img.path, { highWaterMark: BlockSize }),
|
||||||
img.size,
|
img.size,
|
||||||
@@ -173,27 +160,24 @@ export class PacketHighwaySession {
|
|||||||
extend
|
extend
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.logger.logDebug(`[Highway] uploadGroupImageReq get upload invalid ukey ${ukey}, don't need upload!`);
|
this.logger.debug(`[Highway] uploadGroupImageReq get upload invalid ukey ${ukey}, don't need upload!`);
|
||||||
}
|
}
|
||||||
img.msgInfo = preRespData.upload.msgInfo;
|
img.msgInfo = preRespData.upload.msgInfo;
|
||||||
// img.groupPicExt = new NapProtoMsg(CustomFace).decode(preRespData.tcpUpload.compatQMsg)
|
// img.groupPicExt = new NapProtoMsg(CustomFace).decode(preRespData.tcpUpload.compatQMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async uploadC2CImageReq(peerUid: string, img: PacketMsgPicElement): Promise<void> {
|
private async uploadC2CImage(peerUid: string, img: PacketMsgPicElement): Promise<void> {
|
||||||
img.sha1 = Buffer.from(await calculateSha1(img.path)).toString('hex');
|
img.sha1 = Buffer.from(await calculateSha1(img.path)).toString('hex');
|
||||||
const preReq = await this.packer.packUploadC2CImgReq(peerUid, img);
|
const req = trans.UploadPrivateImage.build(peerUid, img);
|
||||||
const preRespRaw = await this.packetClient.sendOidbPacket(preReq, true);
|
const resp = await this.context.client.sendOidbPacket(req, true);
|
||||||
const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(
|
const preRespData = trans.UploadPrivateImage.parse(resp);
|
||||||
Buffer.from(preRespRaw.hex_data, 'hex')
|
|
||||||
);
|
|
||||||
const preRespData = new NapProtoMsg(NTV2RichMediaResp).decode(preResp.body);
|
|
||||||
const ukey = preRespData.upload.uKey;
|
const ukey = preRespData.upload.uKey;
|
||||||
if (ukey && ukey != "") {
|
if (ukey && ukey != "") {
|
||||||
this.logger.logDebug(`[Highway] uploadC2CImageReq get upload ukey: ${ukey}, need upload!`);
|
this.logger.debug(`[Highway] uploadC2CImageReq get upload ukey: ${ukey}, need upload!`);
|
||||||
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||||
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
||||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||||
const extend = new NapProtoMsg(NTV2RichMediaHighwayExt).encode({
|
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
|
||||||
fileUuid: index.fileUuid,
|
fileUuid: index.fileUuid,
|
||||||
uKey: ukey,
|
uKey: ukey,
|
||||||
network: {
|
network: {
|
||||||
@@ -205,7 +189,7 @@ export class PacketHighwaySession {
|
|||||||
fileSha1: [sha1]
|
fileSha1: [sha1]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await this.packetHighwayClient.upload(
|
await this.hwClient.upload(
|
||||||
1003,
|
1003,
|
||||||
fs.createReadStream(img.path, { highWaterMark: BlockSize }),
|
fs.createReadStream(img.path, { highWaterMark: BlockSize }),
|
||||||
img.size,
|
img.size,
|
||||||
@@ -213,27 +197,24 @@ export class PacketHighwaySession {
|
|||||||
extend
|
extend
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.logger.logDebug(`[Highway] uploadC2CImageReq get upload invalid ukey ${ukey}, don't need upload!`);
|
this.logger.debug(`[Highway] uploadC2CImageReq get upload invalid ukey ${ukey}, don't need upload!`);
|
||||||
}
|
}
|
||||||
img.msgInfo = preRespData.upload.msgInfo;
|
img.msgInfo = preRespData.upload.msgInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async uploadGroupVideoReq(groupUin: number, video: PacketMsgVideoElement): Promise<void> {
|
private async uploadGroupVideo(groupUin: number, video: PacketMsgVideoElement): Promise<void> {
|
||||||
if (!video.filePath || !video.thumbPath) throw new Error("video.filePath or video.thumbPath is empty");
|
if (!video.filePath || !video.thumbPath) throw new Error("video.filePath or video.thumbPath is empty");
|
||||||
video.fileSha1 = Buffer.from(await calculateSha1(video.filePath)).toString('hex');
|
video.fileSha1 = Buffer.from(await calculateSha1(video.filePath)).toString('hex');
|
||||||
video.thumbSha1 = Buffer.from(await calculateSha1(video.thumbPath)).toString('hex');
|
video.thumbSha1 = Buffer.from(await calculateSha1(video.thumbPath)).toString('hex');
|
||||||
const preReq = await this.packer.packUploadGroupVideoReq(groupUin, video);
|
const req = trans.UploadGroupVideo.build(groupUin, video);
|
||||||
const preRespRaw = await this.packetClient.sendOidbPacket(preReq, true);
|
const resp = await this.context.client.sendOidbPacket(req, true);
|
||||||
const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(
|
const preRespData = trans.UploadGroupVideo.parse(resp);
|
||||||
Buffer.from(preRespRaw.hex_data, 'hex')
|
|
||||||
);
|
|
||||||
const preRespData = new NapProtoMsg(NTV2RichMediaResp).decode(preResp.body);
|
|
||||||
const ukey = preRespData.upload.uKey;
|
const ukey = preRespData.upload.uKey;
|
||||||
if (ukey && ukey != "") {
|
if (ukey && ukey != "") {
|
||||||
this.logger.logDebug(`[Highway] uploadGroupVideoReq get upload video ukey: ${ukey}, need upload!`);
|
this.logger.debug(`[Highway] uploadGroupVideoReq get upload video ukey: ${ukey}, need upload!`);
|
||||||
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||||
const extend = new NapProtoMsg(NTV2RichMediaHighwayExt).encode({
|
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
|
||||||
fileUuid: index.fileUuid,
|
fileUuid: index.fileUuid,
|
||||||
uKey: ukey,
|
uKey: ukey,
|
||||||
network: {
|
network: {
|
||||||
@@ -245,7 +226,7 @@ export class PacketHighwaySession {
|
|||||||
fileSha1: await calculateSha1StreamBytes(video.filePath!)
|
fileSha1: await calculateSha1StreamBytes(video.filePath!)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await this.packetHighwayClient.upload(
|
await this.hwClient.upload(
|
||||||
1005,
|
1005,
|
||||||
fs.createReadStream(video.filePath!, { highWaterMark: BlockSize }),
|
fs.createReadStream(video.filePath!, { highWaterMark: BlockSize }),
|
||||||
+video.fileSize!,
|
+video.fileSize!,
|
||||||
@@ -253,15 +234,15 @@ export class PacketHighwaySession {
|
|||||||
extend
|
extend
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.logger.logDebug(`[Highway] uploadGroupVideoReq get upload invalid ukey ${ukey}, don't need upload!`);
|
this.logger.debug(`[Highway] uploadGroupVideoReq get upload invalid ukey ${ukey}, don't need upload!`);
|
||||||
}
|
}
|
||||||
const subFile = preRespData.upload.subFileInfos[0];
|
const subFile = preRespData.upload.subFileInfos[0];
|
||||||
if (subFile.uKey && subFile.uKey != "") {
|
if (subFile.uKey && subFile.uKey != "") {
|
||||||
this.logger.logDebug(`[Highway] uploadGroupVideoReq get upload video thumb ukey: ${subFile.uKey}, need upload!`);
|
this.logger.debug(`[Highway] uploadGroupVideoReq get upload video thumb ukey: ${subFile.uKey}, need upload!`);
|
||||||
const index = preRespData.upload.msgInfo.msgInfoBody[1].index;
|
const index = preRespData.upload.msgInfo.msgInfoBody[1].index;
|
||||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||||
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
||||||
const extend = new NapProtoMsg(NTV2RichMediaHighwayExt).encode({
|
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
|
||||||
fileUuid: index.fileUuid,
|
fileUuid: index.fileUuid,
|
||||||
uKey: subFile.uKey,
|
uKey: subFile.uKey,
|
||||||
network: {
|
network: {
|
||||||
@@ -273,7 +254,7 @@ export class PacketHighwaySession {
|
|||||||
fileSha1: [sha1]
|
fileSha1: [sha1]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await this.packetHighwayClient.upload(
|
await this.hwClient.upload(
|
||||||
1006,
|
1006,
|
||||||
fs.createReadStream(video.thumbPath!, { highWaterMark: BlockSize }),
|
fs.createReadStream(video.thumbPath!, { highWaterMark: BlockSize }),
|
||||||
+video.thumbSize!,
|
+video.thumbSize!,
|
||||||
@@ -281,27 +262,24 @@ export class PacketHighwaySession {
|
|||||||
extend
|
extend
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.logger.logDebug(`[Highway] uploadGroupVideoReq get upload invalid thumb ukey ${subFile.uKey}, don't need upload!`);
|
this.logger.debug(`[Highway] uploadGroupVideoReq get upload invalid thumb ukey ${subFile.uKey}, don't need upload!`);
|
||||||
}
|
}
|
||||||
video.msgInfo = preRespData.upload.msgInfo;
|
video.msgInfo = preRespData.upload.msgInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async uploadC2CVideoReq(peerUid: string, video: PacketMsgVideoElement): Promise<void> {
|
private async uploadC2CVideo(peerUid: string, video: PacketMsgVideoElement): Promise<void> {
|
||||||
if (!video.filePath || !video.thumbPath) throw new Error("video.filePath or video.thumbPath is empty");
|
if (!video.filePath || !video.thumbPath) throw new Error("video.filePath or video.thumbPath is empty");
|
||||||
video.fileSha1 = Buffer.from(await calculateSha1(video.filePath)).toString('hex');
|
video.fileSha1 = Buffer.from(await calculateSha1(video.filePath)).toString('hex');
|
||||||
video.thumbSha1 = Buffer.from(await calculateSha1(video.thumbPath)).toString('hex');
|
video.thumbSha1 = Buffer.from(await calculateSha1(video.thumbPath)).toString('hex');
|
||||||
const preReq = await this.packer.packUploadC2CVideoReq(peerUid, video);
|
const req = trans.UploadPrivateVideo.build(peerUid, video);
|
||||||
const preRespRaw = await this.packetClient.sendOidbPacket(preReq, true);
|
const resp = await this.context.client.sendOidbPacket(req, true);
|
||||||
const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(
|
const preRespData = trans.UploadPrivateVideo.parse(resp);
|
||||||
Buffer.from(preRespRaw.hex_data, 'hex')
|
|
||||||
);
|
|
||||||
const preRespData = new NapProtoMsg(NTV2RichMediaResp).decode(preResp.body);
|
|
||||||
const ukey = preRespData.upload.uKey;
|
const ukey = preRespData.upload.uKey;
|
||||||
if (ukey && ukey != "") {
|
if (ukey && ukey != "") {
|
||||||
this.logger.logDebug(`[Highway] uploadC2CVideoReq get upload video ukey: ${ukey}, need upload!`);
|
this.logger.debug(`[Highway] uploadC2CVideoReq get upload video ukey: ${ukey}, need upload!`);
|
||||||
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||||
const extend = new NapProtoMsg(NTV2RichMediaHighwayExt).encode({
|
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
|
||||||
fileUuid: index.fileUuid,
|
fileUuid: index.fileUuid,
|
||||||
uKey: ukey,
|
uKey: ukey,
|
||||||
network: {
|
network: {
|
||||||
@@ -313,7 +291,7 @@ export class PacketHighwaySession {
|
|||||||
fileSha1: await calculateSha1StreamBytes(video.filePath!)
|
fileSha1: await calculateSha1StreamBytes(video.filePath!)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await this.packetHighwayClient.upload(
|
await this.hwClient.upload(
|
||||||
1001,
|
1001,
|
||||||
fs.createReadStream(video.filePath!, { highWaterMark: BlockSize }),
|
fs.createReadStream(video.filePath!, { highWaterMark: BlockSize }),
|
||||||
+video.fileSize!,
|
+video.fileSize!,
|
||||||
@@ -321,15 +299,15 @@ export class PacketHighwaySession {
|
|||||||
extend
|
extend
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.logger.logDebug(`[Highway] uploadC2CVideoReq get upload invalid ukey ${ukey}, don't need upload!`);
|
this.logger.debug(`[Highway] uploadC2CVideoReq get upload invalid ukey ${ukey}, don't need upload!`);
|
||||||
}
|
}
|
||||||
const subFile = preRespData.upload.subFileInfos[0];
|
const subFile = preRespData.upload.subFileInfos[0];
|
||||||
if (subFile.uKey && subFile.uKey != "") {
|
if (subFile.uKey && subFile.uKey != "") {
|
||||||
this.logger.logDebug(`[Highway] uploadC2CVideoReq get upload video thumb ukey: ${subFile.uKey}, need upload!`);
|
this.logger.debug(`[Highway] uploadC2CVideoReq get upload video thumb ukey: ${subFile.uKey}, need upload!`);
|
||||||
const index = preRespData.upload.msgInfo.msgInfoBody[1].index;
|
const index = preRespData.upload.msgInfo.msgInfoBody[1].index;
|
||||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||||
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
||||||
const extend = new NapProtoMsg(NTV2RichMediaHighwayExt).encode({
|
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
|
||||||
fileUuid: index.fileUuid,
|
fileUuid: index.fileUuid,
|
||||||
uKey: subFile.uKey,
|
uKey: subFile.uKey,
|
||||||
network: {
|
network: {
|
||||||
@@ -341,7 +319,7 @@ export class PacketHighwaySession {
|
|||||||
fileSha1: [sha1]
|
fileSha1: [sha1]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await this.packetHighwayClient.upload(
|
await this.hwClient.upload(
|
||||||
1002,
|
1002,
|
||||||
fs.createReadStream(video.thumbPath!, { highWaterMark: BlockSize }),
|
fs.createReadStream(video.thumbPath!, { highWaterMark: BlockSize }),
|
||||||
+video.thumbSize!,
|
+video.thumbSize!,
|
||||||
@@ -349,26 +327,23 @@ export class PacketHighwaySession {
|
|||||||
extend
|
extend
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.logger.logDebug(`[Highway] uploadC2CVideoReq get upload invalid thumb ukey ${subFile.uKey}, don't need upload!`);
|
this.logger.debug(`[Highway] uploadC2CVideoReq get upload invalid thumb ukey ${subFile.uKey}, don't need upload!`);
|
||||||
}
|
}
|
||||||
video.msgInfo = preRespData.upload.msgInfo;
|
video.msgInfo = preRespData.upload.msgInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async uploadGroupPttReq(groupUin: number, ptt: PacketMsgPttElement): Promise<void> {
|
private async uploadGroupPtt(groupUin: number, ptt: PacketMsgPttElement): Promise<void> {
|
||||||
ptt.fileSha1 = Buffer.from(await calculateSha1(ptt.filePath)).toString('hex');
|
ptt.fileSha1 = Buffer.from(await calculateSha1(ptt.filePath)).toString('hex');
|
||||||
const preReq = await this.packer.packUploadGroupPttReq(groupUin, ptt);
|
const req = trans.UploadGroupPtt.build(groupUin, ptt);
|
||||||
const preRespRaw = await this.packetClient.sendOidbPacket(preReq, true);
|
const resp = await this.context.client.sendOidbPacket(req, true);
|
||||||
const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(
|
const preRespData = trans.UploadGroupPtt.parse(resp);
|
||||||
Buffer.from(preRespRaw.hex_data, 'hex')
|
|
||||||
);
|
|
||||||
const preRespData = new NapProtoMsg(NTV2RichMediaResp).decode(preResp.body);
|
|
||||||
const ukey = preRespData.upload.uKey;
|
const ukey = preRespData.upload.uKey;
|
||||||
if (ukey && ukey != "") {
|
if (ukey && ukey != "") {
|
||||||
this.logger.logDebug(`[Highway] uploadGroupPttReq get upload ptt ukey: ${ukey}, need upload!`);
|
this.logger.debug(`[Highway] uploadGroupPttReq get upload ptt ukey: ${ukey}, need upload!`);
|
||||||
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||||
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
||||||
const extend = new NapProtoMsg(NTV2RichMediaHighwayExt).encode({
|
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
|
||||||
fileUuid: index.fileUuid,
|
fileUuid: index.fileUuid,
|
||||||
uKey: ukey,
|
uKey: ukey,
|
||||||
network: {
|
network: {
|
||||||
@@ -380,7 +355,7 @@ export class PacketHighwaySession {
|
|||||||
fileSha1: [sha1]
|
fileSha1: [sha1]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await this.packetHighwayClient.upload(
|
await this.hwClient.upload(
|
||||||
1008,
|
1008,
|
||||||
fs.createReadStream(ptt.filePath, { highWaterMark: BlockSize }),
|
fs.createReadStream(ptt.filePath, { highWaterMark: BlockSize }),
|
||||||
ptt.fileSize,
|
ptt.fileSize,
|
||||||
@@ -388,26 +363,23 @@ export class PacketHighwaySession {
|
|||||||
extend
|
extend
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.logger.logDebug(`[Highway] uploadGroupPttReq get upload invalid ukey ${ukey}, don't need upload!`);
|
this.logger.debug(`[Highway] uploadGroupPttReq get upload invalid ukey ${ukey}, don't need upload!`);
|
||||||
}
|
}
|
||||||
ptt.msgInfo = preRespData.upload.msgInfo;
|
ptt.msgInfo = preRespData.upload.msgInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async uploadC2CPttReq(peerUid: string, ptt: PacketMsgPttElement): Promise<void> {
|
private async uploadC2CPtt(peerUid: string, ptt: PacketMsgPttElement): Promise<void> {
|
||||||
ptt.fileSha1 = Buffer.from(await calculateSha1(ptt.filePath)).toString('hex');
|
ptt.fileSha1 = Buffer.from(await calculateSha1(ptt.filePath)).toString('hex');
|
||||||
const preReq = await this.packer.packUploadC2CPttReq(peerUid, ptt);
|
const req = trans.UploadPrivatePtt.build(peerUid, ptt);
|
||||||
const preRespRaw = await this.packetClient.sendOidbPacket(preReq, true);
|
const resp = await this.context.client.sendOidbPacket(req, true);
|
||||||
const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(
|
const preRespData = trans.UploadPrivatePtt.parse(resp);
|
||||||
Buffer.from(preRespRaw.hex_data, 'hex')
|
|
||||||
);
|
|
||||||
const preRespData = new NapProtoMsg(NTV2RichMediaResp).decode(preResp.body);
|
|
||||||
const ukey = preRespData.upload.uKey;
|
const ukey = preRespData.upload.uKey;
|
||||||
if (ukey && ukey != "") {
|
if (ukey && ukey != "") {
|
||||||
this.logger.logDebug(`[Highway] uploadC2CPttReq get upload ptt ukey: ${ukey}, need upload!`);
|
this.logger.debug(`[Highway] uploadC2CPttReq get upload ptt ukey: ${ukey}, need upload!`);
|
||||||
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
const index = preRespData.upload.msgInfo.msgInfoBody[0].index;
|
||||||
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
const md5 = Buffer.from(index.info.fileHash, 'hex');
|
||||||
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
const sha1 = Buffer.from(index.info.fileSha1, 'hex');
|
||||||
const extend = new NapProtoMsg(NTV2RichMediaHighwayExt).encode({
|
const extend = new NapProtoMsg(proto.NTV2RichMediaHighwayExt).encode({
|
||||||
fileUuid: index.fileUuid,
|
fileUuid: index.fileUuid,
|
||||||
uKey: ukey,
|
uKey: ukey,
|
||||||
network: {
|
network: {
|
||||||
@@ -419,7 +391,7 @@ export class PacketHighwaySession {
|
|||||||
fileSha1: [sha1]
|
fileSha1: [sha1]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await this.packetHighwayClient.upload(
|
await this.hwClient.upload(
|
||||||
1007,
|
1007,
|
||||||
fs.createReadStream(ptt.filePath, { highWaterMark: BlockSize }),
|
fs.createReadStream(ptt.filePath, { highWaterMark: BlockSize }),
|
||||||
ptt.fileSize,
|
ptt.fileSize,
|
||||||
@@ -427,24 +399,21 @@ export class PacketHighwaySession {
|
|||||||
extend
|
extend
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.logger.logDebug(`[Highway] uploadC2CPttReq get upload invalid ukey ${ukey}, don't need upload!`);
|
this.logger.debug(`[Highway] uploadC2CPttReq get upload invalid ukey ${ukey}, don't need upload!`);
|
||||||
}
|
}
|
||||||
ptt.msgInfo = preRespData.upload.msgInfo;
|
ptt.msgInfo = preRespData.upload.msgInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async uploadGroupFileReq(groupUin: number, file: PacketMsgFileElement): Promise<void> {
|
private async uploadGroupFile(groupUin: number, file: PacketMsgFileElement): Promise<void> {
|
||||||
file.isGroupFile = true;
|
file.isGroupFile = true;
|
||||||
file.fileMd5 = await computeMd5AndLengthWithLimit(file.filePath);
|
file.fileMd5 = await computeMd5AndLengthWithLimit(file.filePath);
|
||||||
file.fileSha1 = await calculateSha1(file.filePath);
|
file.fileSha1 = await calculateSha1(file.filePath);
|
||||||
const preReq = await this.packer.packUploadGroupFileReq(groupUin, file);
|
const req = trans.UploadGroupFile.build(groupUin, file);
|
||||||
const preRespRaw = await this.packetClient.sendOidbPacket(preReq, true);
|
const resp = await this.context.client.sendOidbPacket(req, true);
|
||||||
const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(
|
const preRespData = trans.UploadGroupFile.parse(resp);
|
||||||
Buffer.from(preRespRaw.hex_data, 'hex')
|
|
||||||
);
|
|
||||||
const preRespData = new NapProtoMsg(OidbSvcTrpcTcp0x6D6Response).decode(preResp.body);
|
|
||||||
if (!preRespData?.upload?.boolFileExist) {
|
if (!preRespData?.upload?.boolFileExist) {
|
||||||
this.logger.logDebug(`[Highway] uploadGroupFileReq file not exist, need upload!`);
|
this.logger.debug(`[Highway] uploadGroupFileReq file not exist, need upload!`);
|
||||||
const ext = new NapProtoMsg(FileUploadExt).encode({
|
const ext = new NapProtoMsg(proto.FileUploadExt).encode({
|
||||||
unknown1: 100,
|
unknown1: 100,
|
||||||
unknown2: 1,
|
unknown2: 1,
|
||||||
entry: {
|
entry: {
|
||||||
@@ -485,7 +454,7 @@ export class PacketHighwaySession {
|
|||||||
},
|
},
|
||||||
unknown200: 0,
|
unknown200: 0,
|
||||||
});
|
});
|
||||||
await this.packetHighwayClient.upload(
|
await this.hwClient.upload(
|
||||||
71,
|
71,
|
||||||
fs.createReadStream(file.filePath, { highWaterMark: BlockSize }),
|
fs.createReadStream(file.filePath, { highWaterMark: BlockSize }),
|
||||||
file.fileSize,
|
file.fileSize,
|
||||||
@@ -493,24 +462,21 @@ export class PacketHighwaySession {
|
|||||||
ext
|
ext
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.logger.logDebug(`[Highway] uploadGroupFileReq file exist, don't need upload!`);
|
this.logger.debug(`[Highway] uploadGroupFileReq file exist, don't need upload!`);
|
||||||
}
|
}
|
||||||
file.fileUuid = preRespData.upload.fileId;
|
file.fileUuid = preRespData.upload.fileId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async uploadC2CFileReq(peerUid: string, file: PacketMsgFileElement): Promise<void> {
|
private async uploadC2CFile(peerUid: string, file: PacketMsgFileElement): Promise<void> {
|
||||||
file.isGroupFile = false;
|
file.isGroupFile = false;
|
||||||
file.fileMd5 = await computeMd5AndLengthWithLimit(file.filePath);
|
file.fileMd5 = await computeMd5AndLengthWithLimit(file.filePath);
|
||||||
file.fileSha1 = await calculateSha1(file.filePath);
|
file.fileSha1 = await calculateSha1(file.filePath);
|
||||||
const preReq = await this.packer.packUploadC2CFileReq(this.sig.uid, peerUid, file);
|
const req = await trans.UploadPrivateFile.build(this.sig.uid, peerUid, file);
|
||||||
const preRespRaw = await this.packetClient.sendOidbPacket( preReq, true);
|
const res = await this.context.client.sendOidbPacket(req, true);
|
||||||
const preResp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(
|
const preRespData = trans.UploadPrivateFile.parse(res);
|
||||||
Buffer.from(preRespRaw.hex_data, 'hex')
|
|
||||||
);
|
|
||||||
const preRespData = new NapProtoMsg(OidbSvcTrpcTcp0XE37Response).decode(preResp.body);
|
|
||||||
if (!preRespData.upload?.boolFileExist) {
|
if (!preRespData.upload?.boolFileExist) {
|
||||||
this.logger.logDebug(`[Highway] uploadC2CFileReq file not exist, need upload!`);
|
this.logger.debug(`[Highway] uploadC2CFileReq file not exist, need upload!`);
|
||||||
const ext = new NapProtoMsg(FileUploadExt).encode({
|
const ext = new NapProtoMsg(proto.FileUploadExt).encode({
|
||||||
unknown1: 100,
|
unknown1: 100,
|
||||||
unknown2: 1,
|
unknown2: 1,
|
||||||
entry: {
|
entry: {
|
||||||
@@ -550,7 +516,7 @@ export class PacketHighwaySession {
|
|||||||
unknown200: 1,
|
unknown200: 1,
|
||||||
unknown3: 0
|
unknown3: 0
|
||||||
});
|
});
|
||||||
await this.packetHighwayClient.upload(
|
await this.hwClient.upload(
|
||||||
95,
|
95,
|
||||||
fs.createReadStream(file.filePath, { highWaterMark: BlockSize }),
|
fs.createReadStream(file.filePath, { highWaterMark: BlockSize }),
|
||||||
file.fileSize,
|
file.fileSize,
|
||||||
@@ -560,10 +526,9 @@ export class PacketHighwaySession {
|
|||||||
}
|
}
|
||||||
file.fileUuid = preRespData.upload?.uuid;
|
file.fileUuid = preRespData.upload?.uuid;
|
||||||
file.fileHash = preRespData.upload?.fileAddon;
|
file.fileHash = preRespData.upload?.fileAddon;
|
||||||
const FetchExistFileReq = this.packer.packOfflineFileDownloadReq(file.fileUuid!, file.fileHash!, this.sig.uid, peerUid);
|
const fileExistReq = trans.DownloadOfflineFile.build(file.fileUuid!, file.fileHash!, this.sig.uid, peerUid);
|
||||||
const resp = await this.packetClient.sendOidbPacket(FetchExistFileReq, true);
|
const fileExistRes = await this.context.client.sendOidbPacket(fileExistReq, true);
|
||||||
const oidb_resp = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(Buffer.from(resp.hex_data, 'hex'));
|
file._e37_800_rsp = trans.DownloadOfflineFile.parse(fileExistRes);
|
||||||
file._e37_800_rsp = new NapProtoMsg(OidbSvcTrpcTcp0XE37_800Response).decode(oidb_resp.body);
|
|
||||||
file._private_send_uid = this.sig.uid;
|
file._private_send_uid = this.sig.uid;
|
||||||
file._private_recv_uid = peerUid;
|
file._private_recv_uid = peerUid;
|
||||||
}
|
}
|
@@ -1,215 +0,0 @@
|
|||||||
import * as net from "node:net";
|
|
||||||
import * as crypto from "node:crypto";
|
|
||||||
import * as http from "node:http";
|
|
||||||
import * as stream from "node:stream";
|
|
||||||
import { LogWrapper } from "@/common/log";
|
|
||||||
import * as tea from "@/core/packet/utils/crypto/tea";
|
|
||||||
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
|
||||||
import { ReqDataHighwayHead, RespDataHighwayHead } from "@/core/packet/proto/highway/highway";
|
|
||||||
import { BlockSize } from "@/core/packet/highway/session";
|
|
||||||
import { PacketHighwayTrans } from "@/core/packet/highway/client";
|
|
||||||
import { Frame } from "@/core/packet/highway/frame";
|
|
||||||
|
|
||||||
abstract class HighwayUploader {
|
|
||||||
readonly trans: PacketHighwayTrans;
|
|
||||||
readonly logger: LogWrapper;
|
|
||||||
|
|
||||||
constructor(trans: PacketHighwayTrans, logger: LogWrapper) {
|
|
||||||
this.trans = trans;
|
|
||||||
this.logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
private encryptTransExt(key: Uint8Array) {
|
|
||||||
if (!this.trans.encrypt) return;
|
|
||||||
this.trans.ext = tea.encrypt(Buffer.from(this.trans.ext), Buffer.from(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected timeout(): Promise<void> {
|
|
||||||
return new Promise<void>((_, reject) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
reject(new Error(`[Highway] timeout after ${this.trans.timeout}s`));
|
|
||||||
}, (this.trans.timeout ?? Infinity) * 1000
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
buildPicUpHead(offset: number, bodyLength: number, bodyMd5: Uint8Array): Uint8Array {
|
|
||||||
return new NapProtoMsg(ReqDataHighwayHead).encode({
|
|
||||||
msgBaseHead: {
|
|
||||||
version: 1,
|
|
||||||
uin: this.trans.uin,
|
|
||||||
command: "PicUp.DataUp",
|
|
||||||
seq: 0,
|
|
||||||
retryTimes: 0,
|
|
||||||
appId: 1600001604,
|
|
||||||
dataFlag: 16,
|
|
||||||
commandId: this.trans.cmd,
|
|
||||||
},
|
|
||||||
msgSegHead: {
|
|
||||||
serviceId: 0,
|
|
||||||
filesize: BigInt(this.trans.size),
|
|
||||||
dataOffset: BigInt(offset),
|
|
||||||
dataLength: bodyLength,
|
|
||||||
serviceTicket: this.trans.ticket,
|
|
||||||
md5: bodyMd5,
|
|
||||||
fileMd5: this.trans.sum,
|
|
||||||
cacheAddr: 0,
|
|
||||||
cachePort: 0,
|
|
||||||
},
|
|
||||||
bytesReqExtendInfo: this.trans.ext,
|
|
||||||
timestamp: BigInt(0),
|
|
||||||
msgLoginSigHead: {
|
|
||||||
uint32LoginSigType: 8,
|
|
||||||
appId: 1600001604,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract upload(): Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
class HighwayTcpUploaderTransform extends stream.Transform {
|
|
||||||
uploader: HighwayTcpUploader;
|
|
||||||
offset: number;
|
|
||||||
|
|
||||||
constructor(uploader: HighwayTcpUploader) {
|
|
||||||
super();
|
|
||||||
this.uploader = uploader;
|
|
||||||
this.offset = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
_transform(data: Buffer, _: BufferEncoding, callback: stream.TransformCallback) {
|
|
||||||
let chunkOffset = 0;
|
|
||||||
while (chunkOffset < data.length) {
|
|
||||||
const chunkSize = Math.min(BlockSize, data.length - chunkOffset);
|
|
||||||
const chunk = data.subarray(chunkOffset, chunkOffset + chunkSize);
|
|
||||||
const chunkMd5 = crypto.createHash('md5').update(chunk).digest();
|
|
||||||
const head = this.uploader.buildPicUpHead(this.offset, chunk.length, chunkMd5);
|
|
||||||
chunkOffset += chunk.length;
|
|
||||||
this.offset += chunk.length;
|
|
||||||
this.push(Frame.pack(Buffer.from(head), chunk));
|
|
||||||
}
|
|
||||||
callback(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class HighwayTcpUploader extends HighwayUploader {
|
|
||||||
async upload(): Promise<void> {
|
|
||||||
const controller = new AbortController();
|
|
||||||
const { signal } = controller;
|
|
||||||
const upload = new Promise<void>((resolve, reject) => {
|
|
||||||
const highwayTransForm = new HighwayTcpUploaderTransform(this);
|
|
||||||
const socket = net.connect(this.trans.port, this.trans.server, () => {
|
|
||||||
this.trans.data.pipe(highwayTransForm).pipe(socket, { end: false });
|
|
||||||
});
|
|
||||||
const handleRspHeader = (header: Buffer) => {
|
|
||||||
const rsp = new NapProtoMsg(RespDataHighwayHead).decode(header);
|
|
||||||
if (rsp.errorCode !== 0) {
|
|
||||||
socket.end();
|
|
||||||
reject(new Error(`[Highway] tcpUpload failed (code=${rsp.errorCode})`));
|
|
||||||
}
|
|
||||||
const percent = ((Number(rsp.msgSegHead?.dataOffset) + Number(rsp.msgSegHead?.dataLength)) / Number(rsp.msgSegHead?.filesize)).toFixed(2);
|
|
||||||
this.logger.logDebug(`[Highway] tcpUpload ${rsp.errorCode} | ${percent} | ${Buffer.from(header).toString('hex')}`);
|
|
||||||
if (Number(rsp.msgSegHead?.dataOffset) + Number(rsp.msgSegHead?.dataLength) >= Number(rsp.msgSegHead?.filesize)) {
|
|
||||||
this.logger.logDebug('[Highway] tcpUpload finished.');
|
|
||||||
socket.end();
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
socket.on('data', (chunk: Buffer) => {
|
|
||||||
if (signal.aborted) {
|
|
||||||
socket.end();
|
|
||||||
reject(new Error('Upload aborted due to timeout'));
|
|
||||||
}
|
|
||||||
const [head, _] = Frame.unpack(chunk);
|
|
||||||
handleRspHeader(head);
|
|
||||||
});
|
|
||||||
socket.on('close', () => {
|
|
||||||
this.logger.logDebug('[Highway] tcpUpload socket closed.');
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
socket.on('error', (err) => {
|
|
||||||
socket.end();
|
|
||||||
reject(new Error(`[Highway] tcpUpload socket.on error: ${err}`));
|
|
||||||
});
|
|
||||||
this.trans.data.on('error', (err) => {
|
|
||||||
socket.end();
|
|
||||||
reject(new Error(`[Highway] tcpUpload readable error: ${err}`));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const timeout = this.timeout().catch((err) => {
|
|
||||||
controller.abort();
|
|
||||||
throw new Error(err.message);
|
|
||||||
});
|
|
||||||
await Promise.race([upload, timeout]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class HighwayHttpUploader extends HighwayUploader {
|
|
||||||
async upload(): Promise<void> {
|
|
||||||
const controller = new AbortController();
|
|
||||||
const { signal } = controller;
|
|
||||||
const upload = (async () => {
|
|
||||||
let offset = 0;
|
|
||||||
for await (const chunk of this.trans.data) {
|
|
||||||
if (signal.aborted) {
|
|
||||||
throw new Error('Upload aborted due to timeout');
|
|
||||||
}
|
|
||||||
const block = chunk as Buffer;
|
|
||||||
try {
|
|
||||||
await this.uploadBlock(block, offset);
|
|
||||||
} catch (err) {
|
|
||||||
throw new Error(`[Highway] httpUpload Error uploading block at offset ${offset}: ${err}`);
|
|
||||||
}
|
|
||||||
offset += block.length;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
const timeout = this.timeout().catch((err) => {
|
|
||||||
controller.abort();
|
|
||||||
throw new Error(err.message);
|
|
||||||
});
|
|
||||||
await Promise.race([upload, timeout]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async uploadBlock(block: Buffer, offset: number): Promise<void> {
|
|
||||||
const chunkMD5 = crypto.createHash('md5').update(block).digest();
|
|
||||||
const payload = this.buildPicUpHead(offset, block.length, chunkMD5);
|
|
||||||
const frame = Frame.pack(Buffer.from(payload), block);
|
|
||||||
const resp = await this.httpPostHighwayContent(frame, `http://${this.trans.server}:${this.trans.port}/cgi-bin/httpconn?htcmd=0x6FF0087&uin=${this.trans.uin}`);
|
|
||||||
const [head, body] = Frame.unpack(resp);
|
|
||||||
const headData = new NapProtoMsg(RespDataHighwayHead).decode(head);
|
|
||||||
this.logger.logDebug(`[Highway] httpUploadBlock: ${headData.errorCode} | ${headData.msgSegHead?.retCode} | ${headData.bytesRspExtendInfo} | ${head.toString('hex')} | ${body.toString('hex')}`);
|
|
||||||
if (headData.errorCode !== 0) throw new Error(`[Highway] httpUploadBlock failed (code=${headData.errorCode})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async httpPostHighwayContent(frame: Buffer, serverURL: string): Promise<Buffer> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
try {
|
|
||||||
const options: http.RequestOptions = {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Connection': 'keep-alive',
|
|
||||||
'Accept-Encoding': 'identity',
|
|
||||||
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2)',
|
|
||||||
'Content-Length': frame.length.toString(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const req = http.request(serverURL, options, (res) => {
|
|
||||||
const data: Buffer[] = [];
|
|
||||||
res.on('data', (chunk) => {
|
|
||||||
data.push(chunk);
|
|
||||||
});
|
|
||||||
res.on('end', () => {
|
|
||||||
resolve(Buffer.concat(data));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
req.write(frame);
|
|
||||||
req.on('error', (error) => {
|
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
75
src/core/packet/highway/uploader/highwayHttpUploader.ts
Normal file
75
src/core/packet/highway/uploader/highwayHttpUploader.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import crypto from "node:crypto";
|
||||||
|
import http from "node:http";
|
||||||
|
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||||
|
import { IHighwayUploader } from "@/core/packet/highway/uploader/highwayUploader";
|
||||||
|
import { Frame } from "@/core/packet/highway/frame";
|
||||||
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
|
|
||||||
|
export class HighwayHttpUploader extends IHighwayUploader {
|
||||||
|
async upload(): Promise<void> {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const { signal } = controller;
|
||||||
|
const upload = (async () => {
|
||||||
|
let offset = 0;
|
||||||
|
for await (const chunk of this.trans.data) {
|
||||||
|
if (signal.aborted) {
|
||||||
|
throw new Error('Upload aborted due to timeout');
|
||||||
|
}
|
||||||
|
const block = chunk as Buffer;
|
||||||
|
try {
|
||||||
|
await this.uploadBlock(block, offset);
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`[Highway] httpUpload Error uploading block at offset ${offset}: ${err}`);
|
||||||
|
}
|
||||||
|
offset += block.length;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
const timeout = this.timeout().catch((err) => {
|
||||||
|
controller.abort();
|
||||||
|
throw new Error(err.message);
|
||||||
|
});
|
||||||
|
await Promise.race([upload, timeout]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async uploadBlock(block: Buffer, offset: number): Promise<void> {
|
||||||
|
const chunkMD5 = crypto.createHash('md5').update(block).digest();
|
||||||
|
const payload = this.buildPicUpHead(offset, block.length, chunkMD5);
|
||||||
|
const frame = Frame.pack(Buffer.from(payload), block);
|
||||||
|
const resp = await this.httpPostHighwayContent(frame, `http://${this.trans.server}:${this.trans.port}/cgi-bin/httpconn?htcmd=0x6FF0087&uin=${this.trans.uin}`);
|
||||||
|
const [head, body] = Frame.unpack(resp);
|
||||||
|
const headData = new NapProtoMsg(proto.RespDataHighwayHead).decode(head);
|
||||||
|
this.logger.debug(`[Highway] httpUploadBlock: ${headData.errorCode} | ${headData.msgSegHead?.retCode} | ${headData.bytesRspExtendInfo} | ${head.toString('hex')} | ${body.toString('hex')}`);
|
||||||
|
if (headData.errorCode !== 0) throw new Error(`[Highway] httpUploadBlock failed (code=${headData.errorCode})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async httpPostHighwayContent(frame: Buffer, serverURL: string): Promise<Buffer> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const options: http.RequestOptions = {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Connection': 'keep-alive',
|
||||||
|
'Accept-Encoding': 'identity',
|
||||||
|
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2)',
|
||||||
|
'Content-Length': frame.length.toString(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const req = http.request(serverURL, options, (res) => {
|
||||||
|
const data: Buffer[] = [];
|
||||||
|
res.on('data', (chunk) => {
|
||||||
|
data.push(chunk);
|
||||||
|
});
|
||||||
|
res.on('end', () => {
|
||||||
|
resolve(Buffer.concat(data));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
req.write(frame);
|
||||||
|
req.on('error', (error) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
85
src/core/packet/highway/uploader/highwayTcpUploader.ts
Normal file
85
src/core/packet/highway/uploader/highwayTcpUploader.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import net from "node:net";
|
||||||
|
import stream from "node:stream";
|
||||||
|
import crypto from "node:crypto";
|
||||||
|
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||||
|
import { BlockSize } from "@/core/packet/highway/highwayContext";
|
||||||
|
import { Frame } from "@/core/packet/highway/frame";
|
||||||
|
import { IHighwayUploader } from "@/core/packet/highway/uploader/highwayUploader";
|
||||||
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
|
|
||||||
|
class HighwayTcpUploaderTransform extends stream.Transform {
|
||||||
|
uploader: HighwayTcpUploader;
|
||||||
|
offset: number;
|
||||||
|
|
||||||
|
constructor(uploader: HighwayTcpUploader) {
|
||||||
|
super();
|
||||||
|
this.uploader = uploader;
|
||||||
|
this.offset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_transform(data: Buffer, _: BufferEncoding, callback: stream.TransformCallback) {
|
||||||
|
let chunkOffset = 0;
|
||||||
|
while (chunkOffset < data.length) {
|
||||||
|
const chunkSize = Math.min(BlockSize, data.length - chunkOffset);
|
||||||
|
const chunk = data.subarray(chunkOffset, chunkOffset + chunkSize);
|
||||||
|
const chunkMd5 = crypto.createHash('md5').update(chunk).digest();
|
||||||
|
const head = this.uploader.buildPicUpHead(this.offset, chunk.length, chunkMd5);
|
||||||
|
chunkOffset += chunk.length;
|
||||||
|
this.offset += chunk.length;
|
||||||
|
this.push(Frame.pack(Buffer.from(head), chunk));
|
||||||
|
}
|
||||||
|
callback(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HighwayTcpUploader extends IHighwayUploader {
|
||||||
|
async upload(): Promise<void> {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const { signal } = controller;
|
||||||
|
const upload = new Promise<void>((resolve, reject) => {
|
||||||
|
const highwayTransForm = new HighwayTcpUploaderTransform(this);
|
||||||
|
const socket = net.connect(this.trans.port, this.trans.server, () => {
|
||||||
|
this.trans.data.pipe(highwayTransForm).pipe(socket, { end: false });
|
||||||
|
});
|
||||||
|
const handleRspHeader = (header: Buffer) => {
|
||||||
|
const rsp = new NapProtoMsg(proto.RespDataHighwayHead).decode(header);
|
||||||
|
if (rsp.errorCode !== 0) {
|
||||||
|
socket.end();
|
||||||
|
reject(new Error(`[Highway] tcpUpload failed (code=${rsp.errorCode})`));
|
||||||
|
}
|
||||||
|
const percent = ((Number(rsp.msgSegHead?.dataOffset) + Number(rsp.msgSegHead?.dataLength)) / Number(rsp.msgSegHead?.filesize)).toFixed(2);
|
||||||
|
this.logger.debug(`[Highway] tcpUpload ${rsp.errorCode} | ${percent} | ${Buffer.from(header).toString('hex')}`);
|
||||||
|
if (Number(rsp.msgSegHead?.dataOffset) + Number(rsp.msgSegHead?.dataLength) >= Number(rsp.msgSegHead?.filesize)) {
|
||||||
|
this.logger.debug('[Highway] tcpUpload finished.');
|
||||||
|
socket.end();
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
socket.on('data', (chunk: Buffer) => {
|
||||||
|
if (signal.aborted) {
|
||||||
|
socket.end();
|
||||||
|
reject(new Error('Upload aborted due to timeout'));
|
||||||
|
}
|
||||||
|
const [head, _] = Frame.unpack(chunk);
|
||||||
|
handleRspHeader(head);
|
||||||
|
});
|
||||||
|
socket.on('close', () => {
|
||||||
|
this.logger.debug('[Highway] tcpUpload socket closed.');
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
socket.on('error', (err) => {
|
||||||
|
socket.end();
|
||||||
|
reject(new Error(`[Highway] tcpUpload socket.on error: ${err}`));
|
||||||
|
});
|
||||||
|
this.trans.data.on('error', (err) => {
|
||||||
|
socket.end();
|
||||||
|
reject(new Error(`[Highway] tcpUpload readable error: ${err}`));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const timeout = this.timeout().catch((err) => {
|
||||||
|
controller.abort();
|
||||||
|
throw new Error(err.message);
|
||||||
|
});
|
||||||
|
await Promise.race([upload, timeout]);
|
||||||
|
}
|
||||||
|
}
|
63
src/core/packet/highway/uploader/highwayUploader.ts
Normal file
63
src/core/packet/highway/uploader/highwayUploader.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import * as tea from "@/core/packet/utils/crypto/tea";
|
||||||
|
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||||
|
import { PacketHighwayTrans } from "@/core/packet/highway/client";
|
||||||
|
import { PacketLogger } from "@/core/packet/context/loggerContext";
|
||||||
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
|
|
||||||
|
export abstract class IHighwayUploader {
|
||||||
|
readonly trans: PacketHighwayTrans;
|
||||||
|
readonly logger: PacketLogger;
|
||||||
|
|
||||||
|
constructor(trans: PacketHighwayTrans, logger: PacketLogger) {
|
||||||
|
this.trans = trans;
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private encryptTransExt(key: Uint8Array) {
|
||||||
|
if (!this.trans.encrypt) return;
|
||||||
|
this.trans.ext = tea.encrypt(Buffer.from(this.trans.ext), Buffer.from(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected timeout(): Promise<void> {
|
||||||
|
return new Promise<void>((_, reject) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
reject(new Error(`[Highway] timeout after ${this.trans.timeout}s`));
|
||||||
|
}, (this.trans.timeout ?? Infinity) * 1000
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
buildPicUpHead(offset: number, bodyLength: number, bodyMd5: Uint8Array): Uint8Array {
|
||||||
|
return new NapProtoMsg(proto.ReqDataHighwayHead).encode({
|
||||||
|
msgBaseHead: {
|
||||||
|
version: 1,
|
||||||
|
uin: this.trans.uin,
|
||||||
|
command: "PicUp.DataUp",
|
||||||
|
seq: 0,
|
||||||
|
retryTimes: 0,
|
||||||
|
appId: 1600001604,
|
||||||
|
dataFlag: 16,
|
||||||
|
commandId: this.trans.cmd,
|
||||||
|
},
|
||||||
|
msgSegHead: {
|
||||||
|
serviceId: 0,
|
||||||
|
filesize: BigInt(this.trans.size),
|
||||||
|
dataOffset: BigInt(offset),
|
||||||
|
dataLength: bodyLength,
|
||||||
|
serviceTicket: this.trans.ticket,
|
||||||
|
md5: bodyMd5,
|
||||||
|
fileMd5: this.trans.sum,
|
||||||
|
cacheAddr: 0,
|
||||||
|
cachePort: 0,
|
||||||
|
},
|
||||||
|
bytesReqExtendInfo: this.trans.ext,
|
||||||
|
timestamp: BigInt(0),
|
||||||
|
msgLoginSigHead: {
|
||||||
|
uint32LoginSigType: 8,
|
||||||
|
appId: 1600001604,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract upload(): Promise<void>;
|
||||||
|
}
|
@@ -1,13 +1,13 @@
|
|||||||
import { NapProtoEncodeStructType } from "@napneko/nap-proto-core";
|
import { NapProtoEncodeStructType } from "@napneko/nap-proto-core";
|
||||||
import { IPv4 } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaResp";
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
import { NTHighwayIPv4 } from "@/core/packet/proto/highway/highway";
|
|
||||||
|
|
||||||
export const int32ip2str = (ip: number) => {
|
export const int32ip2str = (ip: number) => {
|
||||||
ip = ip & 0xffffffff;
|
ip = ip & 0xffffffff;
|
||||||
return [ip & 0xff, (ip & 0xff00) >> 8, (ip & 0xff0000) >> 16, ((ip & 0xff000000) >> 24) & 0xff].join('.');
|
return [ip & 0xff, (ip & 0xff00) >> 8, (ip & 0xff0000) >> 16, ((ip & 0xff000000) >> 24) & 0xff].join('.');
|
||||||
};
|
};
|
||||||
|
|
||||||
export const oidbIpv4s2HighwayIpv4s = (ipv4s: NapProtoEncodeStructType<typeof IPv4>[]): NapProtoEncodeStructType<typeof NTHighwayIPv4>[] =>{
|
export const oidbIpv4s2HighwayIpv4s = (ipv4s: NapProtoEncodeStructType<typeof proto.IPv4>[]): NapProtoEncodeStructType<typeof proto.NTHighwayIPv4>[] =>{
|
||||||
return ipv4s.map((ip) => {
|
return ipv4s.map((ip) => {
|
||||||
return {
|
return {
|
||||||
domain: {
|
domain: {
|
||||||
@@ -15,6 +15,6 @@ export const oidbIpv4s2HighwayIpv4s = (ipv4s: NapProtoEncodeStructType<typeof IP
|
|||||||
ip: int32ip2str(ip.outIP!),
|
ip: int32ip2str(ip.outIP!),
|
||||||
},
|
},
|
||||||
port: ip.outPort!
|
port: ip.outPort!
|
||||||
} as NapProtoEncodeStructType<typeof NTHighwayIPv4>;
|
} as NapProtoEncodeStructType<typeof proto.NTHighwayIPv4>;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@@ -1,21 +1,20 @@
|
|||||||
import * as crypto from "crypto";
|
import * as crypto from "crypto";
|
||||||
import { PushMsgBody } from "@/core/packet/proto/message/message";
|
import { PushMsgBody } from "@/core/packet/transformer/proto";
|
||||||
import { NapProtoEncodeStructType } from "@napneko/nap-proto-core";
|
import { NapProtoEncodeStructType } from "@napneko/nap-proto-core";
|
||||||
import { LogWrapper } from "@/common/log";
|
|
||||||
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/message/message";
|
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/message/message";
|
||||||
import { IPacketMsgElement, PacketMsgTextElement } from "@/core/packet/message/element";
|
import { IPacketMsgElement, PacketMsgTextElement } from "@/core/packet/message/element";
|
||||||
import { SendTextElement } from "@/core";
|
import { SendTextElement } from "@/core";
|
||||||
|
|
||||||
export class PacketMsgBuilder {
|
export class PacketMsgBuilder {
|
||||||
private logger: LogWrapper;
|
// private logger: LogWrapper;
|
||||||
|
//
|
||||||
constructor(logger: LogWrapper) {
|
// constructor(logger: LogWrapper) {
|
||||||
this.logger = logger;
|
// this.logger = logger;
|
||||||
}
|
// }
|
||||||
|
|
||||||
protected static failBackText = new PacketMsgTextElement(
|
protected static failBackText = new PacketMsgTextElement(
|
||||||
{
|
{
|
||||||
textElement: { content: "[该消息类型暂不支持查看]" }!
|
textElement: { content: "[该消息类型暂不支持查看]" }
|
||||||
} as SendTextElement
|
} as SendTextElement
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -27,7 +26,7 @@ export class PacketMsgBuilder {
|
|||||||
}, undefined);
|
}, undefined);
|
||||||
const msgElement = node.msg.flatMap(msg => msg.buildElement() ?? []);
|
const msgElement = node.msg.flatMap(msg => msg.buildElement() ?? []);
|
||||||
if (!msgContent && !msgElement.length) {
|
if (!msgContent && !msgElement.length) {
|
||||||
this.logger.logWarn(`[PacketMsgBuilder] buildFakeMsg: 空的msgContent和msgElement!`);
|
// this.logger.logWarn(`[PacketMsgBuilder] buildFakeMsg: 空的msgContent和msgElement!`);
|
||||||
msgElement.push(PacketMsgBuilder.failBackText.buildElement());
|
msgElement.push(PacketMsgBuilder.failBackText.buildElement());
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
@@ -32,7 +32,6 @@ import {
|
|||||||
PacketMultiMsgElement
|
PacketMultiMsgElement
|
||||||
} from "@/core/packet/message/element";
|
} from "@/core/packet/message/element";
|
||||||
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/message/message";
|
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/message/message";
|
||||||
import { LogWrapper } from "@/common/log";
|
|
||||||
|
|
||||||
const SupportedElementTypes = [
|
const SupportedElementTypes = [
|
||||||
ElementType.TEXT,
|
ElementType.TEXT,
|
||||||
@@ -77,50 +76,12 @@ export type rawMsgWithSendMsg = {
|
|||||||
msg: PacketSendMsgElement[]
|
msg: PacketSendMsgElement[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: make it become adapter?
|
||||||
export class PacketMsgConverter {
|
export class PacketMsgConverter {
|
||||||
private logger: LogWrapper;
|
|
||||||
|
|
||||||
constructor(logger: LogWrapper) {
|
|
||||||
this.logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
private isValidElementType(type: ElementType): type is keyof ElementToPacketMsgConverters {
|
private isValidElementType(type: ElementType): type is keyof ElementToPacketMsgConverters {
|
||||||
return SupportedElementTypes.includes(type);
|
return SupportedElementTypes.includes(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
rawMsgWithSendMsgToPacketMsg(msg: rawMsgWithSendMsg): PacketMsg {
|
|
||||||
return {
|
|
||||||
senderUid: msg.senderUid ?? '',
|
|
||||||
senderUin: msg.senderUin,
|
|
||||||
senderName: msg.senderName,
|
|
||||||
groupId: msg.groupId,
|
|
||||||
time: msg.time,
|
|
||||||
msg: msg.msg.map((element) => {
|
|
||||||
if (!this.isValidElementType(element.elementType)) return null;
|
|
||||||
return this.rawToPacketMsgConverters[element.elementType](element as MessageElement);
|
|
||||||
}).filter((e) => e !== null)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
rawMsgToPacketMsg(msg: RawMessage, ctxPeer: Peer): PacketMsg {
|
|
||||||
return {
|
|
||||||
seq: +msg.msgSeq,
|
|
||||||
groupId: ctxPeer.chatType === ChatType.KCHATTYPEGROUP ? +msg.peerUid : undefined,
|
|
||||||
senderUid: msg.senderUid,
|
|
||||||
senderUin: +msg.senderUin,
|
|
||||||
senderName: msg.sendMemberName && msg.sendMemberName !== ''
|
|
||||||
? msg.sendMemberName
|
|
||||||
: msg.sendNickName && msg.sendNickName !== ''
|
|
||||||
? msg.sendNickName
|
|
||||||
: "QQ用户",
|
|
||||||
time: +msg.msgTime,
|
|
||||||
msg: msg.elements.map((element) => {
|
|
||||||
if (!this.isValidElementType(element.elementType)) return null;
|
|
||||||
return this.rawToPacketMsgConverters[element.elementType](element);
|
|
||||||
}).filter((e) => e !== null)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private rawToPacketMsgConverters: ElementToPacketMsgConverters = {
|
private rawToPacketMsgConverters: ElementToPacketMsgConverters = {
|
||||||
[ElementType.TEXT]: (element) => {
|
[ElementType.TEXT]: (element) => {
|
||||||
if (element.textElement?.atType) {
|
if (element.textElement?.atType) {
|
||||||
@@ -160,4 +121,37 @@ export class PacketMsgConverter {
|
|||||||
return new PacketMultiMsgElement(element as SendStructLongMsgElement);
|
return new PacketMultiMsgElement(element as SendStructLongMsgElement);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
rawMsgWithSendMsgToPacketMsg(msg: rawMsgWithSendMsg): PacketMsg {
|
||||||
|
return {
|
||||||
|
senderUid: msg.senderUid ?? '',
|
||||||
|
senderUin: msg.senderUin,
|
||||||
|
senderName: msg.senderName,
|
||||||
|
groupId: msg.groupId,
|
||||||
|
time: msg.time,
|
||||||
|
msg: msg.msg.map((element) => {
|
||||||
|
if (!this.isValidElementType(element.elementType)) return null;
|
||||||
|
return this.rawToPacketMsgConverters[element.elementType](element as MessageElement);
|
||||||
|
}).filter((e) => e !== null)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
rawMsgToPacketMsg(msg: RawMessage, ctxPeer: Peer): PacketMsg {
|
||||||
|
return {
|
||||||
|
seq: +msg.msgSeq,
|
||||||
|
groupId: ctxPeer.chatType === ChatType.KCHATTYPEGROUP ? +msg.peerUid : undefined,
|
||||||
|
senderUid: msg.senderUid,
|
||||||
|
senderUin: +msg.senderUin,
|
||||||
|
senderName: msg.sendMemberName && msg.sendMemberName !== ''
|
||||||
|
? msg.sendMemberName
|
||||||
|
: msg.sendNickName && msg.sendNickName !== ''
|
||||||
|
? msg.sendNickName
|
||||||
|
: "QQ用户",
|
||||||
|
time: +msg.msgTime,
|
||||||
|
msg: msg.elements.map((element) => {
|
||||||
|
if (!this.isValidElementType(element.elementType)) return null;
|
||||||
|
return this.rawToPacketMsgConverters[element.elementType](element);
|
||||||
|
}).filter((e) => e !== null)
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,8 +7,12 @@ import {
|
|||||||
MentionExtra,
|
MentionExtra,
|
||||||
NotOnlineImage,
|
NotOnlineImage,
|
||||||
QBigFaceExtra,
|
QBigFaceExtra,
|
||||||
QSmallFaceExtra
|
QSmallFaceExtra,
|
||||||
} from "@/core/packet/proto/message/element";
|
MsgInfo,
|
||||||
|
OidbSvcTrpcTcp0XE37_800Response,
|
||||||
|
FileExtra,
|
||||||
|
GroupFileExtra
|
||||||
|
} from "@/core/packet/transformer/proto";
|
||||||
import {
|
import {
|
||||||
AtType,
|
AtType,
|
||||||
PicType,
|
PicType,
|
||||||
@@ -24,11 +28,8 @@ import {
|
|||||||
SendTextElement,
|
SendTextElement,
|
||||||
SendVideoElement
|
SendVideoElement
|
||||||
} from "@/core";
|
} from "@/core";
|
||||||
import { MsgInfo } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
|
||||||
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/message/message";
|
|
||||||
import { ForwardMsgBuilder } from "@/common/forward-msg-builder";
|
import { ForwardMsgBuilder } from "@/common/forward-msg-builder";
|
||||||
import { FileExtra, GroupFileExtra } from "@/core/packet/proto/message/component";
|
import { PacketMsg, PacketSendMsgElement } from "@/core/packet/message/message";
|
||||||
import { OidbSvcTrpcTcp0XE37_800Response } from "@/core/packet/proto/oidb/Oidb.0XE37_800";
|
|
||||||
|
|
||||||
// raw <-> packet
|
// raw <-> packet
|
||||||
// TODO: SendStructLongMsgElement
|
// TODO: SendStructLongMsgElement
|
||||||
|
@@ -1,803 +0,0 @@
|
|||||||
import * as zlib from "node:zlib";
|
|
||||||
import * as crypto from "node:crypto";
|
|
||||||
import { computeMd5AndLengthWithLimit } from "@/core/packet/utils/crypto/hash";
|
|
||||||
import { NapProtoEncodeStructType, NapProtoMsg } from "@napneko/nap-proto-core";
|
|
||||||
import { OidbSvcTrpcTcpBase } from "@/core/packet/proto/oidb/OidbBase";
|
|
||||||
import { OidbSvcTrpcTcp0X9067_202 } from "@/core/packet/proto/oidb/Oidb.0x9067_202";
|
|
||||||
import { OidbSvcTrpcTcp0X8FC_2, OidbSvcTrpcTcp0X8FC_2_Body } from "@/core/packet/proto/oidb/Oidb.0x8FC_2";
|
|
||||||
import { OidbSvcTrpcTcp0XFE1_2 } from "@/core/packet/proto/oidb/Oidb.0XFE1_2";
|
|
||||||
import { OidbSvcTrpcTcp0XED3_1 } from "@/core/packet/proto/oidb/Oidb.0xED3_1";
|
|
||||||
import { IndexNode, NTV2RichMediaReq } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
|
||||||
import { HttpConn0x6ff_501 } from "@/core/packet/proto/action/action";
|
|
||||||
import { LongMsgResult, SendLongMsgReq } from "@/core/packet/proto/message/action";
|
|
||||||
import { PacketMsgBuilder } from "@/core/packet/message/builder";
|
|
||||||
import {
|
|
||||||
PacketMsgFileElement,
|
|
||||||
PacketMsgPicElement,
|
|
||||||
PacketMsgPttElement,
|
|
||||||
PacketMsgVideoElement
|
|
||||||
} from "@/core/packet/message/element";
|
|
||||||
import { LogWrapper } from "@/common/log";
|
|
||||||
import { PacketMsg } from "@/core/packet/message/message";
|
|
||||||
import { OidbSvcTrpcTcp0x6D6 } from "@/core/packet/proto/oidb/Oidb.0x6D6";
|
|
||||||
import { OidbSvcTrpcTcp0XE37_1200 } from "@/core/packet/proto/oidb/Oidb.0xE37_1200";
|
|
||||||
import { PacketMsgConverter } from "@/core/packet/message/converter";
|
|
||||||
import { OidbSvcTrpcTcp0XE37_1700 } from "@/core/packet/proto/oidb/Oidb.0xE37_1700";
|
|
||||||
import { OidbSvcTrpcTcp0XE37_800 } from "@/core/packet/proto/oidb/Oidb.0XE37_800";
|
|
||||||
import { OidbSvcTrpcTcp0XEB7 } from "./proto/oidb/Oidb.0xEB7";
|
|
||||||
import { MiniAppReqParams } from "@/core/packet/entities/miniApp";
|
|
||||||
import { MiniAppAdaptShareInfoReq } from "@/core/packet/proto/action/miniAppAdaptShareInfo";
|
|
||||||
import { AIVoiceChatType } from "@/core/packet/entities/aiChat";
|
|
||||||
import { OidbSvcTrpcTcp0X929B_0, OidbSvcTrpcTcp0X929D_0 } from "@/core/packet/proto/oidb/Oidb.0x929";
|
|
||||||
import { PacketClient } from "@/core/packet/client/client";
|
|
||||||
|
|
||||||
export type PacketHexStr = string & { readonly hexNya: unique symbol };
|
|
||||||
|
|
||||||
export interface OidbPacket {
|
|
||||||
cmd: string;
|
|
||||||
data: PacketHexStr
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PacketPacker {
|
|
||||||
readonly logger: LogWrapper;
|
|
||||||
readonly client: PacketClient;
|
|
||||||
readonly packetBuilder: PacketMsgBuilder;
|
|
||||||
readonly packetConverter: PacketMsgConverter;
|
|
||||||
|
|
||||||
constructor(logger: LogWrapper, client: PacketClient) {
|
|
||||||
this.logger = logger;
|
|
||||||
this.client = client;
|
|
||||||
this.packetBuilder = new PacketMsgBuilder(logger);
|
|
||||||
this.packetConverter = new PacketMsgConverter(logger);
|
|
||||||
}
|
|
||||||
|
|
||||||
private packetPacket(byteArray: Uint8Array): PacketHexStr {
|
|
||||||
return Buffer.from(byteArray).toString('hex') as PacketHexStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
packOidbPacket(cmd: number, subCmd: number, body: Uint8Array, isUid: boolean = true, isLafter: boolean = false): OidbPacket {
|
|
||||||
const data = new NapProtoMsg(OidbSvcTrpcTcpBase).encode({
|
|
||||||
command: cmd,
|
|
||||||
subCommand: subCmd,
|
|
||||||
body: body,
|
|
||||||
isReserved: isUid ? 1 : 0
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
cmd: `OidbSvcTrpcTcp.0x${cmd.toString(16).toUpperCase()}_${subCmd}`,
|
|
||||||
data: this.packetPacket(data)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
packPokePacket(peer: number, group?: number): OidbPacket {
|
|
||||||
const oidb_0xed3 = new NapProtoMsg(OidbSvcTrpcTcp0XED3_1).encode({
|
|
||||||
uin: peer,
|
|
||||||
groupUin: group,
|
|
||||||
friendUin: group ?? peer,
|
|
||||||
ext: 0
|
|
||||||
});
|
|
||||||
return this.packOidbPacket(0xed3, 1, oidb_0xed3);
|
|
||||||
}
|
|
||||||
|
|
||||||
packRkeyPacket(): OidbPacket {
|
|
||||||
const oidb_0x9067_202 = new NapProtoMsg(OidbSvcTrpcTcp0X9067_202).encode({
|
|
||||||
reqHead: {
|
|
||||||
common: {
|
|
||||||
requestId: 1,
|
|
||||||
command: 202
|
|
||||||
},
|
|
||||||
scene: {
|
|
||||||
requestType: 2,
|
|
||||||
businessType: 1,
|
|
||||||
sceneType: 0
|
|
||||||
},
|
|
||||||
client: {
|
|
||||||
agentType: 2
|
|
||||||
}
|
|
||||||
},
|
|
||||||
downloadRKeyReq: {
|
|
||||||
key: [10, 20, 2]
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return this.packOidbPacket(0x9067, 202, oidb_0x9067_202);
|
|
||||||
}
|
|
||||||
|
|
||||||
packSetSpecialTittlePacket(groupCode: string, uid: string, tittle: string): OidbPacket {
|
|
||||||
const oidb_0x8FC_2_body = new NapProtoMsg(OidbSvcTrpcTcp0X8FC_2_Body).encode({
|
|
||||||
targetUid: uid,
|
|
||||||
specialTitle: tittle,
|
|
||||||
expiredTime: -1,
|
|
||||||
uinName: tittle
|
|
||||||
});
|
|
||||||
const oidb_0x8FC_2 = new NapProtoMsg(OidbSvcTrpcTcp0X8FC_2).encode({
|
|
||||||
groupUin: +groupCode,
|
|
||||||
body: oidb_0x8FC_2_body
|
|
||||||
});
|
|
||||||
return this.packOidbPacket(0x8FC, 2, oidb_0x8FC_2, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
packStatusPacket(uin: number): OidbPacket {
|
|
||||||
const oidb_0xfe1_2 = new NapProtoMsg(OidbSvcTrpcTcp0XFE1_2).encode({
|
|
||||||
uin: uin,
|
|
||||||
key: [{ key: 27372 }]
|
|
||||||
});
|
|
||||||
return this.packOidbPacket(0xfe1, 2, oidb_0xfe1_2);
|
|
||||||
}
|
|
||||||
|
|
||||||
async packUploadForwardMsg(selfUid: string, msg: PacketMsg[], groupUin: number = 0): Promise<PacketHexStr> {
|
|
||||||
const msgBody = this.packetBuilder.buildFakeMsg(selfUid, msg);
|
|
||||||
const longMsgResultData = new NapProtoMsg(LongMsgResult).encode(
|
|
||||||
{
|
|
||||||
action: {
|
|
||||||
actionCommand: "MultiMsg",
|
|
||||||
actionData: {
|
|
||||||
msgBody: msgBody
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const payload = zlib.gzipSync(Buffer.from(longMsgResultData));
|
|
||||||
const req = new NapProtoMsg(SendLongMsgReq).encode(
|
|
||||||
{
|
|
||||||
info: {
|
|
||||||
type: groupUin === 0 ? 1 : 3,
|
|
||||||
uid: {
|
|
||||||
uid: groupUin === 0 ? selfUid : groupUin.toString(),
|
|
||||||
},
|
|
||||||
groupUin: groupUin,
|
|
||||||
payload: payload
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
field1: 4, field2: 1, field3: 7, field4: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
// this.logger.logDebug("packUploadForwardMsg REQ!!!", req);
|
|
||||||
return this.packetPacket(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
// highway part
|
|
||||||
packHttp0x6ff_501(): PacketHexStr {
|
|
||||||
return this.packetPacket(new NapProtoMsg(HttpConn0x6ff_501).encode({
|
|
||||||
httpConn: {
|
|
||||||
field1: 0,
|
|
||||||
field2: 0,
|
|
||||||
field3: 16,
|
|
||||||
field4: 1,
|
|
||||||
field6: 3,
|
|
||||||
serviceTypes: [1, 5, 10, 21],
|
|
||||||
// tgt: "", // TODO: do we really need tgt? seems not
|
|
||||||
field9: 2,
|
|
||||||
field10: 9,
|
|
||||||
field11: 8,
|
|
||||||
ver: "1.0.1"
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
async packUploadGroupImgReq(groupUin: number, img: PacketMsgPicElement): Promise<OidbPacket> {
|
|
||||||
const req = new NapProtoMsg(NTV2RichMediaReq).encode(
|
|
||||||
{
|
|
||||||
reqHead: {
|
|
||||||
common: {
|
|
||||||
requestId: 1,
|
|
||||||
command: 100
|
|
||||||
},
|
|
||||||
scene: {
|
|
||||||
requestType: 2,
|
|
||||||
businessType: 1,
|
|
||||||
sceneType: 2,
|
|
||||||
group: {
|
|
||||||
groupUin: groupUin
|
|
||||||
},
|
|
||||||
},
|
|
||||||
client: {
|
|
||||||
agentType: 2
|
|
||||||
}
|
|
||||||
},
|
|
||||||
upload: {
|
|
||||||
uploadInfo: [
|
|
||||||
{
|
|
||||||
fileInfo: {
|
|
||||||
fileSize: +img.size,
|
|
||||||
fileHash: img.md5,
|
|
||||||
fileSha1: img.sha1!,
|
|
||||||
fileName: img.name,
|
|
||||||
type: {
|
|
||||||
type: 1,
|
|
||||||
picFormat: img.picType, //TODO: extend NapCat imgType /cc @MliKiowa
|
|
||||||
videoFormat: 0,
|
|
||||||
voiceFormat: 0,
|
|
||||||
},
|
|
||||||
width: img.width,
|
|
||||||
height: img.height,
|
|
||||||
time: 0,
|
|
||||||
original: 1
|
|
||||||
},
|
|
||||||
subFileType: 0,
|
|
||||||
}
|
|
||||||
],
|
|
||||||
tryFastUploadCompleted: true,
|
|
||||||
srvSendMsg: false,
|
|
||||||
clientRandomId: crypto.randomBytes(8).readBigUInt64BE() & BigInt('0x7FFFFFFFFFFFFFFF'),
|
|
||||||
compatQMsgSceneType: 2,
|
|
||||||
extBizInfo: {
|
|
||||||
pic: {
|
|
||||||
bytesPbReserveTroop: Buffer.from("0800180020004200500062009201009a0100a2010c080012001800200028003a00", 'hex'),
|
|
||||||
textSummary: "Nya~", // TODO:
|
|
||||||
},
|
|
||||||
video: {
|
|
||||||
bytesPbReserve: Buffer.alloc(0),
|
|
||||||
},
|
|
||||||
ptt: {
|
|
||||||
bytesPbReserve: Buffer.alloc(0),
|
|
||||||
bytesReserve: Buffer.alloc(0),
|
|
||||||
bytesGeneralFlags: Buffer.alloc(0),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clientSeq: 0,
|
|
||||||
noNeedCompatMsg: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return this.packOidbPacket(0x11c4, 100, req, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
async packUploadC2CImgReq(peerUin: string, img: PacketMsgPicElement): Promise<OidbPacket> {
|
|
||||||
const req = new NapProtoMsg(NTV2RichMediaReq).encode({
|
|
||||||
reqHead: {
|
|
||||||
common: {
|
|
||||||
requestId: 1,
|
|
||||||
command: 100
|
|
||||||
},
|
|
||||||
scene: {
|
|
||||||
requestType: 2,
|
|
||||||
businessType: 1,
|
|
||||||
sceneType: 1,
|
|
||||||
c2C: {
|
|
||||||
accountType: 2,
|
|
||||||
targetUid: peerUin
|
|
||||||
},
|
|
||||||
},
|
|
||||||
client: {
|
|
||||||
agentType: 2,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
upload: {
|
|
||||||
uploadInfo: [
|
|
||||||
{
|
|
||||||
fileInfo: {
|
|
||||||
fileSize: +img.size,
|
|
||||||
fileHash: img.md5,
|
|
||||||
fileSha1: img.sha1!,
|
|
||||||
fileName: img.name,
|
|
||||||
type: {
|
|
||||||
type: 1,
|
|
||||||
picFormat: img.picType, //TODO: extend NapCat imgType /cc @MliKiowa
|
|
||||||
videoFormat: 0,
|
|
||||||
voiceFormat: 0,
|
|
||||||
},
|
|
||||||
width: img.width,
|
|
||||||
height: img.height,
|
|
||||||
time: 0,
|
|
||||||
original: 1
|
|
||||||
},
|
|
||||||
subFileType: 0,
|
|
||||||
}
|
|
||||||
],
|
|
||||||
tryFastUploadCompleted: true,
|
|
||||||
srvSendMsg: false,
|
|
||||||
clientRandomId: crypto.randomBytes(8).readBigUInt64BE() & BigInt('0x7FFFFFFFFFFFFFFF'),
|
|
||||||
compatQMsgSceneType: 1,
|
|
||||||
extBizInfo: {
|
|
||||||
pic: {
|
|
||||||
bytesPbReserveTroop: Buffer.from("0800180020004200500062009201009a0100a2010c080012001800200028003a00", 'hex'),
|
|
||||||
textSummary: "Nya~", // TODO:
|
|
||||||
},
|
|
||||||
video: {
|
|
||||||
bytesPbReserve: Buffer.alloc(0),
|
|
||||||
},
|
|
||||||
ptt: {
|
|
||||||
bytesPbReserve: Buffer.alloc(0),
|
|
||||||
bytesReserve: Buffer.alloc(0),
|
|
||||||
bytesGeneralFlags: Buffer.alloc(0),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clientSeq: 0,
|
|
||||||
noNeedCompatMsg: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return this.packOidbPacket(0x11c5, 100, req, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
async packUploadGroupVideoReq(groupUin: number, video: PacketMsgVideoElement): Promise<OidbPacket> {
|
|
||||||
if (!video.fileSize || !video.thumbSize) throw new Error("video.fileSize or video.thumbSize is empty");
|
|
||||||
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: video.fileSha1,
|
|
||||||
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: video.thumbSha1,
|
|
||||||
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.packOidbPacket(0x11EA, 100, req, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
async packUploadC2CVideoReq(peerUin: string, video: PacketMsgVideoElement): Promise<OidbPacket> {
|
|
||||||
if (!video.fileSize || !video.thumbSize) throw new Error("video.fileSize or video.thumbSize is empty");
|
|
||||||
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: video.fileSha1,
|
|
||||||
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: video.thumbSha1,
|
|
||||||
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.packOidbPacket(0x11E9, 100, req, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
async packUploadGroupPttReq(groupUin: number, ptt: PacketMsgPttElement): Promise<OidbPacket> {
|
|
||||||
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: ptt.fileSha1,
|
|
||||||
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.packOidbPacket(0x126E, 100, req, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
async packUploadC2CPttReq(peerUin: string, ptt: PacketMsgPttElement): Promise<OidbPacket> {
|
|
||||||
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: ptt.fileSha1,
|
|
||||||
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.packOidbPacket(0x126D, 100, req, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
async packUploadGroupFileReq(groupUin: number, file: PacketMsgFileElement): Promise<OidbPacket> {
|
|
||||||
const body = new NapProtoMsg(OidbSvcTrpcTcp0x6D6).encode({
|
|
||||||
file: {
|
|
||||||
groupUin: groupUin,
|
|
||||||
appId: 4,
|
|
||||||
busId: 102,
|
|
||||||
entrance: 6,
|
|
||||||
targetDirectory: '/', // TODO:
|
|
||||||
fileName: file.fileName,
|
|
||||||
localDirectory: `/${file.fileName}`,
|
|
||||||
fileSize: BigInt(file.fileSize),
|
|
||||||
fileMd5: file.fileMd5,
|
|
||||||
fileSha1: file.fileSha1,
|
|
||||||
fileSha3: Buffer.alloc(0),
|
|
||||||
field15: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return this.packOidbPacket(0x6D6, 0, body, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
async packUploadC2CFileReq(selfUid: string, peerUid: string, file: PacketMsgFileElement): Promise<OidbPacket> {
|
|
||||||
const body = new NapProtoMsg(OidbSvcTrpcTcp0XE37_1700).encode({
|
|
||||||
command: 1700,
|
|
||||||
seq: 0,
|
|
||||||
upload: {
|
|
||||||
senderUid: selfUid,
|
|
||||||
receiverUid: peerUid,
|
|
||||||
fileSize: file.fileSize,
|
|
||||||
fileName: file.fileName,
|
|
||||||
md510MCheckSum: await computeMd5AndLengthWithLimit(file.filePath, 10 * 1024 * 1024),
|
|
||||||
sha1CheckSum: file.fileSha1,
|
|
||||||
localPath: "/",
|
|
||||||
md5CheckSum: file.fileMd5,
|
|
||||||
sha3CheckSum: Buffer.alloc(0)
|
|
||||||
},
|
|
||||||
businessId: 3,
|
|
||||||
clientType: 1,
|
|
||||||
flagSupportMediaPlatform: 1
|
|
||||||
});
|
|
||||||
return this.packOidbPacket(0xE37, 1700, body, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
packOfflineFileDownloadReq(fileUUID: string, fileHash: string, senderUid: string, receiverUid: string): OidbPacket {
|
|
||||||
return this.packOidbPacket(0xE37, 800, new NapProtoMsg(OidbSvcTrpcTcp0XE37_800).encode({
|
|
||||||
subCommand: 800,
|
|
||||||
field2: 0,
|
|
||||||
body: {
|
|
||||||
senderUid: senderUid,
|
|
||||||
receiverUid: receiverUid,
|
|
||||||
fileUuid: fileUUID,
|
|
||||||
fileHash: fileHash,
|
|
||||||
},
|
|
||||||
field101: 3,
|
|
||||||
field102: 1,
|
|
||||||
field200: 1,
|
|
||||||
}), false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
packGroupFileDownloadReq(groupUin: number, fileUUID: string): OidbPacket {
|
|
||||||
return this.packOidbPacket(0x6D6, 2, new NapProtoMsg(OidbSvcTrpcTcp0x6D6).encode({
|
|
||||||
download: {
|
|
||||||
groupUin: groupUin,
|
|
||||||
appId: 7,
|
|
||||||
busId: 102,
|
|
||||||
fileId: fileUUID
|
|
||||||
}
|
|
||||||
}), true, false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
packC2CFileDownloadReq(selfUid: string, fileUUID: string, fileHash: string): PacketHexStr {
|
|
||||||
return this.packetPacket(
|
|
||||||
new NapProtoMsg(OidbSvcTrpcTcp0XE37_1200).encode({
|
|
||||||
subCommand: 1200,
|
|
||||||
field2: 1,
|
|
||||||
body: {
|
|
||||||
receiverUid: selfUid,
|
|
||||||
fileUuid: fileUUID,
|
|
||||||
type: 2,
|
|
||||||
fileHash: fileHash,
|
|
||||||
t2: 0
|
|
||||||
},
|
|
||||||
field101: 3,
|
|
||||||
field102: 103,
|
|
||||||
field200: 1,
|
|
||||||
field99999: Buffer.from([0xc0, 0x85, 0x2c, 0x01])
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
packGroupPttFileDownloadReq(groupUin: number, node: NapProtoEncodeStructType<typeof IndexNode>): OidbPacket {
|
|
||||||
return this.packOidbPacket(0x126E, 200, new NapProtoMsg(NTV2RichMediaReq).encode({
|
|
||||||
reqHead: {
|
|
||||||
common: {
|
|
||||||
requestId: 4,
|
|
||||||
command: 200
|
|
||||||
},
|
|
||||||
scene: {
|
|
||||||
requestType: 1,
|
|
||||||
businessType: 3,
|
|
||||||
sceneType: 2,
|
|
||||||
group: {
|
|
||||||
groupUin: groupUin
|
|
||||||
}
|
|
||||||
},
|
|
||||||
client: {
|
|
||||||
agentType: 2
|
|
||||||
}
|
|
||||||
},
|
|
||||||
download: {
|
|
||||||
node: node,
|
|
||||||
download: {
|
|
||||||
video: {
|
|
||||||
busiType: 0,
|
|
||||||
sceneType: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}), true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
packGroupSignReq(uin: string, groupCode: string): OidbPacket {
|
|
||||||
return this.packOidbPacket(0XEB7, 1, new NapProtoMsg(OidbSvcTrpcTcp0XEB7).encode(
|
|
||||||
{
|
|
||||||
body: {
|
|
||||||
uin: uin,
|
|
||||||
groupUin: groupCode,
|
|
||||||
version: "9.0.90"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
), false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
packMiniAppAdaptShareInfo(req: MiniAppReqParams): PacketHexStr {
|
|
||||||
return this.packetPacket(
|
|
||||||
new NapProtoMsg(MiniAppAdaptShareInfoReq).encode(
|
|
||||||
{
|
|
||||||
appId: req.sdkId,
|
|
||||||
body: {
|
|
||||||
extInfo: {
|
|
||||||
field2: Buffer.alloc(0)
|
|
||||||
},
|
|
||||||
appid: req.appId,
|
|
||||||
title: req.title,
|
|
||||||
desc: req.desc,
|
|
||||||
time: BigInt(Date.now()),
|
|
||||||
scene: req.scene,
|
|
||||||
templateType: req.templateType,
|
|
||||||
businessType: req.businessType,
|
|
||||||
picUrl: req.picUrl,
|
|
||||||
vidUrl: "",
|
|
||||||
jumpUrl: req.jumpUrl,
|
|
||||||
iconUrl: req.iconUrl,
|
|
||||||
verType: req.verType,
|
|
||||||
shareType: req.shareType,
|
|
||||||
versionId: req.versionId,
|
|
||||||
withShareTicket: req.withShareTicket,
|
|
||||||
webURL: "",
|
|
||||||
appidRich: Buffer.alloc(0),
|
|
||||||
template: {
|
|
||||||
templateId: "",
|
|
||||||
templateData: ""
|
|
||||||
},
|
|
||||||
field20: ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
packFetchAiVoiceListReq(groupUin: number, chatType: AIVoiceChatType): OidbPacket {
|
|
||||||
return this.packOidbPacket(0x929D, 0,
|
|
||||||
new NapProtoMsg(OidbSvcTrpcTcp0X929D_0).encode({
|
|
||||||
groupUin: groupUin,
|
|
||||||
chatType: chatType
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
packAiVoiceChatReq(groupUin: number, voiceId: string, text: string, chatType: AIVoiceChatType, sessionId: number): OidbPacket {
|
|
||||||
return this.packOidbPacket(0x929B, 0,
|
|
||||||
new NapProtoMsg(OidbSvcTrpcTcp0X929B_0).encode({
|
|
||||||
groupUin: groupUin,
|
|
||||||
voiceId: voiceId,
|
|
||||||
text: text,
|
|
||||||
chatType: chatType,
|
|
||||||
session: {
|
|
||||||
sessionId: sessionId
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,49 +0,0 @@
|
|||||||
// TODO: refactor with NapProto
|
|
||||||
import { MessageType, BinaryReader, ScalarType } from '@protobuf-ts/runtime';
|
|
||||||
|
|
||||||
export const BodyInner = new MessageType("BodyInner", [
|
|
||||||
{ no: 1, name: "msgType", kind: "scalar", T: ScalarType.UINT32 /* uint32 */, opt: true },
|
|
||||||
{ no: 2, name: "subType", kind: "scalar", T: ScalarType.UINT32 /* uint32 */, opt: true }
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const NoifyData = new MessageType("NoifyData", [
|
|
||||||
{ no: 1, name: "skip", kind: "scalar", T: ScalarType.BYTES /* bytes */, opt: true },
|
|
||||||
{ no: 2, name: "innerData", kind: "scalar", T: ScalarType.BYTES /* bytes */, opt: true }
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const MsgHead = new MessageType("MsgHead", [
|
|
||||||
{ no: 2, name: "bodyInner", kind: "message", T: () => BodyInner, opt: true },
|
|
||||||
{ no: 3, name: "noifyData", kind: "message", T: () => NoifyData, opt: true }
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const Message = new MessageType("Message", [
|
|
||||||
{ no: 1, name: "msgHead", kind: "message", T: () => MsgHead }
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const SubDetail = new MessageType("SubDetail", [
|
|
||||||
{ no: 1, name: "msgSeq", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
|
||||||
{ no: 2, name: "msgTime", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
|
||||||
{ no: 6, name: "senderUid", kind: "scalar", T: ScalarType.STRING /* string */ }
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const RecallDetails = new MessageType("RecallDetails", [
|
|
||||||
{ no: 1, name: "operatorUid", kind: "scalar", T: ScalarType.STRING /* string */ },
|
|
||||||
{ no: 3, name: "subDetail", kind: "message", T: () => SubDetail }
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const RecallGroup = new MessageType("RecallGroup", [
|
|
||||||
{ no: 1, name: "type", kind: "scalar", T: ScalarType.INT32 /* int32 */ },
|
|
||||||
{ no: 4, name: "peerUid", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
|
||||||
{ no: 11, name: "recallDetails", kind: "message", T: () => RecallDetails },
|
|
||||||
{ no: 37, name: "grayTipsSeq", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ }
|
|
||||||
]);
|
|
||||||
|
|
||||||
export function decodeMessage(buffer: Uint8Array): any {
|
|
||||||
const reader = new BinaryReader(buffer);
|
|
||||||
return Message.internalBinaryRead(reader, reader.len, { readUnknownField: true, readerFactory: () => new BinaryReader(buffer) });
|
|
||||||
}
|
|
||||||
|
|
||||||
export function decodeRecallGroup(buffer: Uint8Array): any {
|
|
||||||
const reader = new BinaryReader(buffer);
|
|
||||||
return RecallGroup.internalBinaryRead(reader, reader.len, { readUnknownField: true, readerFactory: () => new BinaryReader(buffer) });
|
|
||||||
}
|
|
@@ -1,59 +0,0 @@
|
|||||||
// TODO: refactor with NapProto
|
|
||||||
import { MessageType, BinaryReader, ScalarType, RepeatType } from '@protobuf-ts/runtime';
|
|
||||||
|
|
||||||
export const LikeDetail = new MessageType("likeDetail", [
|
|
||||||
{ no: 1, name: "txt", kind: "scalar", T: ScalarType.STRING /* string */ },
|
|
||||||
{ no: 3, name: "uin", kind: "scalar", T: ScalarType.INT64 /* int64 */ },
|
|
||||||
{ no: 5, name: "nickname", kind: "scalar", T: ScalarType.STRING /* string */ }
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const LikeMsg = new MessageType("likeMsg", [
|
|
||||||
{ no: 1, name: "times", kind: "scalar", T: ScalarType.INT32 /* int32 */ },
|
|
||||||
{ no: 2, name: "time", kind: "scalar", T: ScalarType.INT32 /* int32 */ },
|
|
||||||
{ no: 3, name: "detail", kind: "message", T: () => LikeDetail }
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const ProfileLikeSubTip = new MessageType("profileLikeSubTip", [
|
|
||||||
{ no: 14, name: "msg", kind: "message", T: () => LikeMsg }
|
|
||||||
]);
|
|
||||||
export const ProfileLikeTip = new MessageType("profileLikeTip", [
|
|
||||||
{ no: 1, name: "msgType", kind: "scalar", T: ScalarType.INT32 /* int32 */ },
|
|
||||||
{ no: 2, name: "subType", kind: "scalar", T: ScalarType.INT32 /* int32 */ },
|
|
||||||
{ no: 203, name: "content", kind: "message", T: () => ProfileLikeSubTip }
|
|
||||||
]);
|
|
||||||
export const SysMessageHeader = new MessageType("SysMessageHeader", [
|
|
||||||
{ no: 1, name: "PeerNumber", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
|
||||||
{ no: 2, name: "PeerString", kind: "scalar", T: ScalarType.STRING /* string */ },
|
|
||||||
{ no: 5, name: "Uin", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
|
||||||
{ no: 6, name: "Uid", kind: "scalar", T: ScalarType.STRING /* string */, opt: true }
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const SysMessageMsgSpec = new MessageType("SysMessageMsgSpec", [
|
|
||||||
{ no: 1, name: "msgType", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
|
||||||
{ no: 2, name: "subType", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
|
||||||
{ no: 3, name: "subSubType", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
|
||||||
{ no: 5, name: "msgSeq", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
|
||||||
{ no: 6, name: "time", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ },
|
|
||||||
{ no: 12, name: "msgId", kind: "scalar", T: ScalarType.UINT64 /* uint64 */ },
|
|
||||||
{ no: 13, name: "other", kind: "scalar", T: ScalarType.UINT32 /* uint32 */ }
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const SysMessageBodyWrapper = new MessageType("SysMessageBodyWrapper", [
|
|
||||||
{ no: 2, name: "wrappedBody", kind: "scalar", T: ScalarType.BYTES /* bytes */ }
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const SysMessage = new MessageType("SysMessage", [
|
|
||||||
{ no: 1, name: "header", kind: "message", T: () => SysMessageHeader, repeat: RepeatType.UNPACKED },
|
|
||||||
{ no: 2, name: "msgSpec", kind: "message", T: () => SysMessageMsgSpec, repeat: RepeatType.UNPACKED },
|
|
||||||
{ no: 3, name: "bodyWrapper", kind: "message", T: () => SysMessageBodyWrapper }
|
|
||||||
]);
|
|
||||||
|
|
||||||
export function decodeProfileLikeTip(buffer: Uint8Array): any {
|
|
||||||
const reader = new BinaryReader(buffer);
|
|
||||||
return ProfileLikeTip.internalBinaryRead(reader, reader.len, { readUnknownField: true, readerFactory: () => new BinaryReader(buffer) });
|
|
||||||
}
|
|
||||||
|
|
||||||
export function decodeSysMessage(buffer: Uint8Array): any {
|
|
||||||
const reader = new BinaryReader(buffer);
|
|
||||||
return SysMessage.internalBinaryRead(reader, reader.len, { readUnknownField: true, readerFactory: () => new BinaryReader(buffer) });
|
|
||||||
}
|
|
0
src/core/packet/service/base.ts
Normal file
0
src/core/packet/service/base.ts
Normal file
@@ -1,73 +0,0 @@
|
|||||||
import { PacketHighwaySession } from "@/core/packet/highway/session";
|
|
||||||
import { LogWrapper } from "@/common/log";
|
|
||||||
import { PacketPacker } from "@/core/packet/packer";
|
|
||||||
import { PacketClient } from "@/core/packet/client/client";
|
|
||||||
import { NativePacketClient } from "@/core/packet/client/nativeClient";
|
|
||||||
import { wsPacketClient } from "@/core/packet/client/wsClient";
|
|
||||||
import { NapCatCore } from "@/core";
|
|
||||||
|
|
||||||
type clientPriority = {
|
|
||||||
[key: number]: (core: NapCatCore) => PacketClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
const clientPriority: clientPriority = {
|
|
||||||
10: (core: NapCatCore) => new NativePacketClient(core),
|
|
||||||
1: (core: NapCatCore) => new wsPacketClient(core),
|
|
||||||
};
|
|
||||||
|
|
||||||
export class PacketSession {
|
|
||||||
readonly logger: LogWrapper;
|
|
||||||
readonly client: PacketClient ;
|
|
||||||
readonly packer: PacketPacker;
|
|
||||||
readonly highwaySession: PacketHighwaySession;
|
|
||||||
|
|
||||||
constructor(core: NapCatCore) {
|
|
||||||
this.logger = core.context.logger;
|
|
||||||
this.client = this.newClient(core);
|
|
||||||
this.packer = new PacketPacker(this.logger, this.client);
|
|
||||||
this.highwaySession = new PacketHighwaySession(this.logger, this.client, this.packer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private newClient(core: NapCatCore): PacketClient {
|
|
||||||
const prefer = core.configLoader.configData.packetBackend;
|
|
||||||
let client: PacketClient | null;
|
|
||||||
switch (prefer) {
|
|
||||||
case "native":
|
|
||||||
this.logger.log("[Core] [Packet] 使用指定的 NativePacketClient 作为后端");
|
|
||||||
client = new NativePacketClient(core);
|
|
||||||
break;
|
|
||||||
case "frida":
|
|
||||||
this.logger.log("[Core] [Packet] 使用指定的 FridaPacketClient 作为后端");
|
|
||||||
client = new wsPacketClient(core);
|
|
||||||
break;
|
|
||||||
case "auto":
|
|
||||||
case undefined:
|
|
||||||
client = this.judgeClient(core);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
this.logger.logError(`[Core] [Packet] 未知的PacketBackend ${prefer},请检查配置文件!`);
|
|
||||||
client = null;
|
|
||||||
}
|
|
||||||
if (!(client && client.check(core))) {
|
|
||||||
throw new Error("[Core] [Packet] 无可用的后端,NapCat.Packet将不会加载!");
|
|
||||||
}
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
private judgeClient(core: NapCatCore): PacketClient {
|
|
||||||
const sortedClients = Object.entries(clientPriority)
|
|
||||||
.map(([priority, clientFactory]) => {
|
|
||||||
const client = clientFactory(core);
|
|
||||||
const score = +priority * +client.check(core);
|
|
||||||
return { client, score };
|
|
||||||
})
|
|
||||||
.filter(({ score }) => score > 0)
|
|
||||||
.sort((a, b) => b.score - a.score);
|
|
||||||
const selectedClient = sortedClients[0]?.client;
|
|
||||||
if (!selectedClient) {
|
|
||||||
throw new Error("[Core] [Packet] 无可用的后端,NapCat.Packet将不会加载!");
|
|
||||||
}
|
|
||||||
this.logger.log(`[Core] [Packet] 自动选择 ${selectedClient.constructor.name} 作为后端`);
|
|
||||||
return selectedClient;
|
|
||||||
}
|
|
||||||
}
|
|
26
src/core/packet/transformer/action/FetchAiVoiceList.ts
Normal file
26
src/core/packet/transformer/action/FetchAiVoiceList.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
|
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||||
|
import { OidbPacket, PacketTransformer } from "@/core/packet/transformer/base";
|
||||||
|
import OidbBase from "@/core/packet/transformer/oidb/oidbBase";
|
||||||
|
import { AIVoiceChatType } from "@/core/packet/entities/aiChat";
|
||||||
|
|
||||||
|
class FetchAiVoiceList extends PacketTransformer<typeof proto.OidbSvcTrpcTcp0X929D_0Resp> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
build(groupUin: number, chatType: AIVoiceChatType): OidbPacket {
|
||||||
|
const data = new NapProtoMsg(proto.OidbSvcTrpcTcp0X929D_0).encode({
|
||||||
|
groupUin: groupUin,
|
||||||
|
chatType: chatType
|
||||||
|
});
|
||||||
|
return OidbBase.build(0x929D, 0, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(data: Buffer) {
|
||||||
|
const oidbBody = OidbBase.parse(data).body;
|
||||||
|
return new NapProtoMsg(proto.OidbSvcTrpcTcp0X929D_0Resp).decode(oidbBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new FetchAiVoiceList();
|
31
src/core/packet/transformer/action/GetAiVoice.ts
Normal file
31
src/core/packet/transformer/action/GetAiVoice.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
|
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||||
|
import { OidbPacket, PacketTransformer } from "@/core/packet/transformer/base";
|
||||||
|
import OidbBase from "@/core/packet/transformer/oidb/oidbBase";
|
||||||
|
import { AIVoiceChatType } from "@/core/packet/entities/aiChat";
|
||||||
|
|
||||||
|
class GetAiVoice extends PacketTransformer<typeof proto.OidbSvcTrpcTcp0X929B_0Resp> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
build(groupUin: number, voiceId: string, text: string, sessionId: number, chatType: AIVoiceChatType): OidbPacket {
|
||||||
|
const data = new NapProtoMsg(proto.OidbSvcTrpcTcp0X929B_0).encode({
|
||||||
|
groupUin: groupUin,
|
||||||
|
voiceId: voiceId,
|
||||||
|
text: text,
|
||||||
|
chatType: chatType,
|
||||||
|
session: {
|
||||||
|
sessionId: sessionId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return OidbBase.build(0x929B, 0, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(data: Buffer) {
|
||||||
|
const oidbBody = OidbBase.parse(data).body;
|
||||||
|
return new NapProtoMsg(proto.OidbSvcTrpcTcp0X929B_0Resp).decode(oidbBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new GetAiVoice();
|
@@ -0,0 +1,53 @@
|
|||||||
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
|
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||||
|
import { OidbPacket, PacketHexStrBuilder, PacketTransformer } from "@/core/packet/transformer/base";
|
||||||
|
import { MiniAppReqParams } from "@/core/packet/entities/miniApp";
|
||||||
|
|
||||||
|
class GetMiniAppAdaptShareInfo extends PacketTransformer<typeof proto.MiniAppAdaptShareInfoResp> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
build(req: MiniAppReqParams): OidbPacket {
|
||||||
|
const data = new NapProtoMsg(proto.MiniAppAdaptShareInfoReq).encode({
|
||||||
|
appId: req.sdkId,
|
||||||
|
body: {
|
||||||
|
extInfo: {
|
||||||
|
field2: Buffer.alloc(0)
|
||||||
|
},
|
||||||
|
appid: req.appId,
|
||||||
|
title: req.title,
|
||||||
|
desc: req.desc,
|
||||||
|
time: BigInt(Date.now()),
|
||||||
|
scene: req.scene,
|
||||||
|
templateType: req.templateType,
|
||||||
|
businessType: req.businessType,
|
||||||
|
picUrl: req.picUrl,
|
||||||
|
vidUrl: "",
|
||||||
|
jumpUrl: req.jumpUrl,
|
||||||
|
iconUrl: req.iconUrl,
|
||||||
|
verType: req.verType,
|
||||||
|
shareType: req.shareType,
|
||||||
|
versionId: req.versionId,
|
||||||
|
withShareTicket: req.withShareTicket,
|
||||||
|
webURL: "",
|
||||||
|
appidRich: Buffer.alloc(0),
|
||||||
|
template: {
|
||||||
|
templateId: "",
|
||||||
|
templateData: ""
|
||||||
|
},
|
||||||
|
field20: ""
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
cmd: "LightAppSvc.mini_app_share.AdaptShareInfo",
|
||||||
|
data: PacketHexStrBuilder(data)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(data: Buffer) {
|
||||||
|
return new NapProtoMsg(proto.MiniAppAdaptShareInfoResp).decode(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new GetMiniAppAdaptShareInfo();
|
25
src/core/packet/transformer/action/GetStrangerInfo.ts
Normal file
25
src/core/packet/transformer/action/GetStrangerInfo.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
|
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||||
|
import { OidbPacket, PacketTransformer } from "@/core/packet/transformer/base";
|
||||||
|
import OidbBase from "@/core/packet/transformer/oidb/oidbBase";
|
||||||
|
|
||||||
|
class GetStrangerInfo extends PacketTransformer<typeof proto.OidbSvcTrpcTcp0XFE1_2RSP> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
build(uin: number): OidbPacket {
|
||||||
|
const body = new NapProtoMsg(proto.OidbSvcTrpcTcp0XFE1_2).encode({
|
||||||
|
uin: uin,
|
||||||
|
key: [{ key: 27372 }]
|
||||||
|
});
|
||||||
|
return OidbBase.build(0XFE1, 2, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(data: Buffer) {
|
||||||
|
const oidbBody = OidbBase.parse(data).body;
|
||||||
|
return new NapProtoMsg(proto.OidbSvcTrpcTcp0XFE1_2RSP).decode(oidbBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new GetStrangerInfo();
|
29
src/core/packet/transformer/action/GroupSign.ts
Normal file
29
src/core/packet/transformer/action/GroupSign.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
|
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||||
|
import { OidbPacket, PacketTransformer } from "@/core/packet/transformer/base";
|
||||||
|
import OidbBase from "@/core/packet/transformer/oidb/oidbBase";
|
||||||
|
|
||||||
|
class GroupSign extends PacketTransformer<typeof proto.OidbSvcTrpcTcpBase> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
build(uin: number, groupCode: number): OidbPacket {
|
||||||
|
const body = new NapProtoMsg(proto.OidbSvcTrpcTcp0XEB7).encode(
|
||||||
|
{
|
||||||
|
body: {
|
||||||
|
uin: String(uin),
|
||||||
|
groupUin: String(groupCode),
|
||||||
|
version: "9.0.90"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return OidbBase.build(0XEB7, 1, body, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(data: Buffer) {
|
||||||
|
return OidbBase.parse(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new GroupSign();
|
26
src/core/packet/transformer/action/SendPoke.ts
Normal file
26
src/core/packet/transformer/action/SendPoke.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
|
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||||
|
import { OidbPacket, PacketTransformer } from "@/core/packet/transformer/base";
|
||||||
|
import OidbBase from "@/core/packet/transformer/oidb/oidbBase";
|
||||||
|
|
||||||
|
class SendPoke extends PacketTransformer<typeof proto.OidbSvcTrpcTcpBase> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
build(peer: number, group?: number): OidbPacket {
|
||||||
|
const data = new NapProtoMsg(proto.OidbSvcTrpcTcp0XED3_1).encode({
|
||||||
|
uin: peer,
|
||||||
|
groupUin: group,
|
||||||
|
friendUin: group ?? peer,
|
||||||
|
ext: 0
|
||||||
|
});
|
||||||
|
return OidbBase.build(0xED3, 1, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(data: Buffer) {
|
||||||
|
return OidbBase.parse(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new SendPoke();
|
30
src/core/packet/transformer/action/SetSpecialTitle.ts
Normal file
30
src/core/packet/transformer/action/SetSpecialTitle.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
|
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||||
|
import { OidbPacket, PacketTransformer } from "@/core/packet/transformer/base";
|
||||||
|
import OidbBase from "@/core/packet/transformer/oidb/oidbBase";
|
||||||
|
|
||||||
|
class SetSpecialTitle extends PacketTransformer<typeof proto.OidbSvcTrpcTcpBase> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
build(groupCode: number, uid: string, tittle: string): OidbPacket {
|
||||||
|
const oidb_0x8FC_2_body = new NapProtoMsg(proto.OidbSvcTrpcTcp0X8FC_2_Body).encode({
|
||||||
|
targetUid: uid,
|
||||||
|
specialTitle: tittle,
|
||||||
|
expiredTime: -1,
|
||||||
|
uinName: tittle
|
||||||
|
});
|
||||||
|
const oidb_0x8FC_2 = new NapProtoMsg(proto.OidbSvcTrpcTcp0X8FC_2).encode({
|
||||||
|
groupUin: +groupCode,
|
||||||
|
body: oidb_0x8FC_2_body
|
||||||
|
});
|
||||||
|
return OidbBase.build(0x8FC, 2, oidb_0x8FC_2, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(data: Buffer) {
|
||||||
|
return OidbBase.parse(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new SetSpecialTitle();
|
7
src/core/packet/transformer/action/index.ts
Normal file
7
src/core/packet/transformer/action/index.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export { default as FetchAiVoiceList } from './FetchAiVoiceList';
|
||||||
|
export { default as GetAiVoice } from './GetAiVoice';
|
||||||
|
export { default as GetMiniAppAdaptShareInfo } from './GetMiniAppAdaptShareInfo';
|
||||||
|
export { default as GroupSign } from './GroupSign';
|
||||||
|
export { default as GetStrangerInfo } from './GetStrangerInfo';
|
||||||
|
export { default as SendPoke } from './SendPoke';
|
||||||
|
export { default as SetSpecialTitle } from './SetSpecialTitle';
|
25
src/core/packet/transformer/base.ts
Normal file
25
src/core/packet/transformer/base.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { NapProtoDecodeStructType } from "@napneko/nap-proto-core";
|
||||||
|
import { PacketMsgBuilder } from "@/core/packet/message/builder";
|
||||||
|
|
||||||
|
export type PacketHexStr = string & { readonly hexNya: unique symbol };
|
||||||
|
|
||||||
|
export const PacketHexStrBuilder = (str: Uint8Array): PacketHexStr => {
|
||||||
|
return Buffer.from(str).toString('hex') as PacketHexStr;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface OidbPacket {
|
||||||
|
cmd: string;
|
||||||
|
data: PacketHexStr
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class PacketTransformer<T> {
|
||||||
|
protected msgBuilder: PacketMsgBuilder;
|
||||||
|
|
||||||
|
protected constructor() {
|
||||||
|
this.msgBuilder = new PacketMsgBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract build(...args: any[]): OidbPacket | Promise<OidbPacket>;
|
||||||
|
|
||||||
|
abstract parse(data: Buffer): NapProtoDecodeStructType<T>;
|
||||||
|
}
|
33
src/core/packet/transformer/highway/DownloadGroupFile.ts
Normal file
33
src/core/packet/transformer/highway/DownloadGroupFile.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
|
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||||
|
import { OidbPacket, PacketTransformer } from "@/core/packet/transformer/base";
|
||||||
|
import OidbBase from "@/core/packet/transformer/oidb/oidbBase";
|
||||||
|
|
||||||
|
class DownloadGroupFile extends PacketTransformer<typeof proto.OidbSvcTrpcTcp0x6D6Response> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
build(groupUin: number, fileUUID: string): OidbPacket {
|
||||||
|
const body = new NapProtoMsg(proto.OidbSvcTrpcTcp0x6D6).encode({
|
||||||
|
download: {
|
||||||
|
groupUin: groupUin,
|
||||||
|
appId: 7,
|
||||||
|
busId: 102,
|
||||||
|
fileId: fileUUID
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return OidbBase.build(0x6D6, 2, body, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(data: Buffer) {
|
||||||
|
const oidbBody = OidbBase.parse(data).body;
|
||||||
|
const res = new NapProtoMsg(proto.OidbSvcTrpcTcp0x6D6Response).decode(oidbBody);
|
||||||
|
if (res.download.retCode !== 0) {
|
||||||
|
throw new Error(`sendGroupFileDownloadReq error: ${res.download.clientWording} (code=${res.download.retCode})`);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new DownloadGroupFile();
|
49
src/core/packet/transformer/highway/DownloadGroupPtt.ts
Normal file
49
src/core/packet/transformer/highway/DownloadGroupPtt.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
|
import { NapProtoEncodeStructType, NapProtoMsg } from "@napneko/nap-proto-core";
|
||||||
|
import { OidbPacket, PacketTransformer } from "@/core/packet/transformer/base";
|
||||||
|
import OidbBase from "@/core/packet/transformer/oidb/oidbBase";
|
||||||
|
|
||||||
|
class DownloadGroupPtt extends PacketTransformer<typeof proto.NTV2RichMediaResp> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
build(groupUin: number, node: NapProtoEncodeStructType<typeof proto.IndexNode>): OidbPacket {
|
||||||
|
const body = new NapProtoMsg(proto.NTV2RichMediaReq).encode({
|
||||||
|
reqHead: {
|
||||||
|
common: {
|
||||||
|
requestId: 4,
|
||||||
|
command: 200
|
||||||
|
},
|
||||||
|
scene: {
|
||||||
|
requestType: 1,
|
||||||
|
businessType: 3,
|
||||||
|
sceneType: 2,
|
||||||
|
group: {
|
||||||
|
groupUin: groupUin
|
||||||
|
}
|
||||||
|
},
|
||||||
|
client: {
|
||||||
|
agentType: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
download: {
|
||||||
|
node: node,
|
||||||
|
download: {
|
||||||
|
video: {
|
||||||
|
busiType: 0,
|
||||||
|
sceneType: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return OidbBase.build(0x126E, 200, body, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(data: Buffer) {
|
||||||
|
const oidbBody = OidbBase.parse(data).body;
|
||||||
|
return new NapProtoMsg(proto.NTV2RichMediaResp).decode(oidbBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new DownloadGroupPtt();
|
35
src/core/packet/transformer/highway/DownloadOfflineFile.ts
Normal file
35
src/core/packet/transformer/highway/DownloadOfflineFile.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
|
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||||
|
import { OidbPacket, PacketTransformer } from "@/core/packet/transformer/base";
|
||||||
|
import OidbBase from "@/core/packet/transformer/oidb/oidbBase";
|
||||||
|
|
||||||
|
class DownloadOfflineFile extends PacketTransformer<typeof proto.OidbSvcTrpcTcp0XE37Response> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
build(fileUUID: string, fileHash: string, senderUid: string, receiverUid: string): OidbPacket {
|
||||||
|
const body = new NapProtoMsg(proto.OidbSvcTrpcTcp0XE37_800).encode({
|
||||||
|
subCommand: 800,
|
||||||
|
field2: 0,
|
||||||
|
body: {
|
||||||
|
senderUid: senderUid,
|
||||||
|
receiverUid: receiverUid,
|
||||||
|
fileUuid: fileUUID,
|
||||||
|
fileHash: fileHash,
|
||||||
|
},
|
||||||
|
field101: 3,
|
||||||
|
field102: 1,
|
||||||
|
field200: 1,
|
||||||
|
});
|
||||||
|
return OidbBase.build(0xE37, 800, body, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: check
|
||||||
|
parse(data: Buffer) {
|
||||||
|
const oidbBody = OidbBase.parse(data).body;
|
||||||
|
return new NapProtoMsg(proto.OidbSvcTrpcTcp0XE37Response).decode(oidbBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new DownloadOfflineFile();
|
36
src/core/packet/transformer/highway/DownloadPrivateFile.ts
Normal file
36
src/core/packet/transformer/highway/DownloadPrivateFile.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
|
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||||
|
import { OidbPacket, PacketTransformer } from "@/core/packet/transformer/base";
|
||||||
|
import OidbBase from "@/core/packet/transformer/oidb/oidbBase";
|
||||||
|
|
||||||
|
class DownloadPrivateFile extends PacketTransformer<typeof proto.OidbSvcTrpcTcp0XE37_1200Response> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
build(selfUid: string, fileUUID: string, fileHash: string): OidbPacket {
|
||||||
|
const body = new NapProtoMsg(proto.OidbSvcTrpcTcp0XE37_1200).encode({
|
||||||
|
subCommand: 1200,
|
||||||
|
field2: 1,
|
||||||
|
body: {
|
||||||
|
receiverUid: selfUid,
|
||||||
|
fileUuid: fileUUID,
|
||||||
|
type: 2,
|
||||||
|
fileHash: fileHash,
|
||||||
|
t2: 0
|
||||||
|
},
|
||||||
|
field101: 3,
|
||||||
|
field102: 103,
|
||||||
|
field200: 1,
|
||||||
|
field99999: Buffer.from([0xc0, 0x85, 0x2c, 0x01])
|
||||||
|
});
|
||||||
|
return OidbBase.build(0xE37, 1200, body, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(data: Buffer) {
|
||||||
|
const oidbBody = OidbBase.parse(data).body;
|
||||||
|
return new NapProtoMsg(proto.OidbSvcTrpcTcp0XE37_1200Response).decode(oidbBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new DownloadPrivateFile();
|
37
src/core/packet/transformer/highway/FetchSessionKey.ts
Normal file
37
src/core/packet/transformer/highway/FetchSessionKey.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
|
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||||
|
import { OidbPacket, PacketHexStrBuilder, PacketTransformer } from "@/core/packet/transformer/base";
|
||||||
|
|
||||||
|
class FetchSessionKey extends PacketTransformer<typeof proto.HttpConn0x6ff_501Response> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
build(): OidbPacket {
|
||||||
|
const req = new NapProtoMsg(proto.HttpConn0x6ff_501).encode({
|
||||||
|
httpConn: {
|
||||||
|
field1: 0,
|
||||||
|
field2: 0,
|
||||||
|
field3: 16,
|
||||||
|
field4: 1,
|
||||||
|
field6: 3,
|
||||||
|
serviceTypes: [1, 5, 10, 21],
|
||||||
|
// tgt: "", // TODO: do we really need tgt? seems not
|
||||||
|
field9: 2,
|
||||||
|
field10: 9,
|
||||||
|
field11: 8,
|
||||||
|
ver: "1.0.1"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
cmd: "HttpConn.0x6ff_501",
|
||||||
|
data: PacketHexStrBuilder(req)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(data: Buffer) {
|
||||||
|
return new NapProtoMsg(proto.HttpConn0x6ff_501Response).decode(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new FetchSessionKey();
|
38
src/core/packet/transformer/highway/UploadGroupFile.ts
Normal file
38
src/core/packet/transformer/highway/UploadGroupFile.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
|
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||||
|
import { OidbPacket, PacketTransformer } from "@/core/packet/transformer/base";
|
||||||
|
import OidbBase from "@/core/packet/transformer/oidb/oidbBase";
|
||||||
|
import { PacketMsgFileElement } from "@/core/packet/message/element";
|
||||||
|
|
||||||
|
class UploadGroupFile extends PacketTransformer<typeof proto.OidbSvcTrpcTcp0x6D6Response> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
build(groupUin: number, file: PacketMsgFileElement): OidbPacket {
|
||||||
|
const body = new NapProtoMsg(proto.OidbSvcTrpcTcp0x6D6).encode({
|
||||||
|
file: {
|
||||||
|
groupUin: groupUin,
|
||||||
|
appId: 4,
|
||||||
|
busId: 102,
|
||||||
|
entrance: 6,
|
||||||
|
targetDirectory: '/', // TODO:
|
||||||
|
fileName: file.fileName,
|
||||||
|
localDirectory: `/${file.fileName}`,
|
||||||
|
fileSize: BigInt(file.fileSize),
|
||||||
|
fileMd5: file.fileMd5,
|
||||||
|
fileSha1: file.fileSha1,
|
||||||
|
fileSha3: Buffer.alloc(0),
|
||||||
|
field15: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return OidbBase.build(0x6D6, 0, body, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(data: Buffer) {
|
||||||
|
const oidbBody = OidbBase.parse(data).body;
|
||||||
|
return new NapProtoMsg(proto.OidbSvcTrpcTcp0x6D6Response).decode(oidbBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new UploadGroupFile();
|
87
src/core/packet/transformer/highway/UploadGroupImage.ts
Normal file
87
src/core/packet/transformer/highway/UploadGroupImage.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
|
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||||
|
import { OidbPacket, PacketTransformer } from "@/core/packet/transformer/base";
|
||||||
|
import OidbBase from "@/core/packet/transformer/oidb/oidbBase";
|
||||||
|
import crypto from "node:crypto";
|
||||||
|
import { PacketMsgPicElement } from "@/core/packet/message/element";
|
||||||
|
|
||||||
|
class UploadGroupImage extends PacketTransformer<typeof proto.NTV2RichMediaResp> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
build(groupUin: number, img: PacketMsgPicElement): OidbPacket {
|
||||||
|
const data = new NapProtoMsg(proto.NTV2RichMediaReq).encode(
|
||||||
|
{
|
||||||
|
reqHead: {
|
||||||
|
common: {
|
||||||
|
requestId: 1,
|
||||||
|
command: 100
|
||||||
|
},
|
||||||
|
scene: {
|
||||||
|
requestType: 2,
|
||||||
|
businessType: 1,
|
||||||
|
sceneType: 2,
|
||||||
|
group: {
|
||||||
|
groupUin: groupUin
|
||||||
|
},
|
||||||
|
},
|
||||||
|
client: {
|
||||||
|
agentType: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
upload: {
|
||||||
|
uploadInfo: [
|
||||||
|
{
|
||||||
|
fileInfo: {
|
||||||
|
fileSize: +img.size,
|
||||||
|
fileHash: img.md5,
|
||||||
|
fileSha1: img.sha1!,
|
||||||
|
fileName: img.name,
|
||||||
|
type: {
|
||||||
|
type: 1,
|
||||||
|
picFormat: img.picType, //TODO: extend NapCat imgType /cc @MliKiowa
|
||||||
|
videoFormat: 0,
|
||||||
|
voiceFormat: 0,
|
||||||
|
},
|
||||||
|
width: img.width,
|
||||||
|
height: img.height,
|
||||||
|
time: 0,
|
||||||
|
original: 1
|
||||||
|
},
|
||||||
|
subFileType: 0,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
tryFastUploadCompleted: true,
|
||||||
|
srvSendMsg: false,
|
||||||
|
clientRandomId: crypto.randomBytes(8).readBigUInt64BE() & BigInt('0x7FFFFFFFFFFFFFFF'),
|
||||||
|
compatQMsgSceneType: 2,
|
||||||
|
extBizInfo: {
|
||||||
|
pic: {
|
||||||
|
bytesPbReserveTroop: Buffer.from("0800180020004200500062009201009a0100a2010c080012001800200028003a00", 'hex'),
|
||||||
|
textSummary: "Nya~", // TODO:
|
||||||
|
},
|
||||||
|
video: {
|
||||||
|
bytesPbReserve: Buffer.alloc(0),
|
||||||
|
},
|
||||||
|
ptt: {
|
||||||
|
bytesPbReserve: Buffer.alloc(0),
|
||||||
|
bytesReserve: Buffer.alloc(0),
|
||||||
|
bytesGeneralFlags: Buffer.alloc(0),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clientSeq: 0,
|
||||||
|
noNeedCompatMsg: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return OidbBase.build(0x11C4, 100, data, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(data: Buffer) {
|
||||||
|
const oidbBody = OidbBase.parse(data).body;
|
||||||
|
return new NapProtoMsg(proto.NTV2RichMediaResp).decode(oidbBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new UploadGroupImage();
|
84
src/core/packet/transformer/highway/UploadGroupPtt.ts
Normal file
84
src/core/packet/transformer/highway/UploadGroupPtt.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
|
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||||
|
import { OidbPacket, PacketTransformer } from "@/core/packet/transformer/base";
|
||||||
|
import OidbBase from "@/core/packet/transformer/oidb/oidbBase";
|
||||||
|
import crypto from "node:crypto";
|
||||||
|
import { PacketMsgPttElement } from "@/core/packet/message/element";
|
||||||
|
|
||||||
|
class UploadGroupPtt extends PacketTransformer<typeof proto.NTV2RichMediaResp> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
build(groupUin: number, ptt: PacketMsgPttElement): OidbPacket {
|
||||||
|
const data = new NapProtoMsg(proto.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: ptt.fileSha1,
|
||||||
|
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 OidbBase.build(0x126E, 100, data, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(data: Buffer) {
|
||||||
|
const oidbBody = OidbBase.parse(data).body;
|
||||||
|
return new NapProtoMsg(proto.NTV2RichMediaResp).decode(oidbBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new UploadGroupPtt();
|
104
src/core/packet/transformer/highway/UploadGroupVideo.ts
Normal file
104
src/core/packet/transformer/highway/UploadGroupVideo.ts
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
|
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||||
|
import { OidbPacket, PacketTransformer } from "@/core/packet/transformer/base";
|
||||||
|
import OidbBase from "@/core/packet/transformer/oidb/oidbBase";
|
||||||
|
import crypto from "node:crypto";
|
||||||
|
import { PacketMsgVideoElement } from "@/core/packet/message/element";
|
||||||
|
|
||||||
|
class UploadGroupVideo extends PacketTransformer<typeof proto.NTV2RichMediaResp> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
build(groupUin: number, video: PacketMsgVideoElement): OidbPacket {
|
||||||
|
if (!video.fileSize || !video.thumbSize) throw new Error("video.fileSize or video.thumbSize is empty");
|
||||||
|
const data = new NapProtoMsg(proto.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: video.fileSha1,
|
||||||
|
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: video.thumbSha1,
|
||||||
|
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 OidbBase.build(0x11EA, 100, data, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(data: Buffer) {
|
||||||
|
const oidbBody = OidbBase.parse(data).body;
|
||||||
|
return new NapProtoMsg(proto.NTV2RichMediaResp).decode(oidbBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new UploadGroupVideo();
|
41
src/core/packet/transformer/highway/UploadPrivateFile.ts
Normal file
41
src/core/packet/transformer/highway/UploadPrivateFile.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
|
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||||
|
import { OidbPacket, PacketTransformer } from "@/core/packet/transformer/base";
|
||||||
|
import OidbBase from "@/core/packet/transformer/oidb/oidbBase";
|
||||||
|
import { PacketMsgFileElement } from "@/core/packet/message/element";
|
||||||
|
import { computeMd5AndLengthWithLimit } from "@/core/packet/utils/crypto/hash";
|
||||||
|
|
||||||
|
class UploadPrivateFile extends PacketTransformer<typeof proto.OidbSvcTrpcTcp0XE37Response> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
async build(selfUid: string, peerUid: string, file: PacketMsgFileElement): Promise<OidbPacket> {
|
||||||
|
const body = new NapProtoMsg(proto.OidbSvcTrpcTcp0XE37_1700).encode({
|
||||||
|
command: 1700,
|
||||||
|
seq: 0,
|
||||||
|
upload: {
|
||||||
|
senderUid: selfUid,
|
||||||
|
receiverUid: peerUid,
|
||||||
|
fileSize: file.fileSize,
|
||||||
|
fileName: file.fileName,
|
||||||
|
md510MCheckSum: await computeMd5AndLengthWithLimit(file.filePath, 10 * 1024 * 1024),
|
||||||
|
sha1CheckSum: file.fileSha1,
|
||||||
|
localPath: "/",
|
||||||
|
md5CheckSum: file.fileMd5,
|
||||||
|
sha3CheckSum: Buffer.alloc(0)
|
||||||
|
},
|
||||||
|
businessId: 3,
|
||||||
|
clientType: 1,
|
||||||
|
flagSupportMediaPlatform: 1
|
||||||
|
});
|
||||||
|
return OidbBase.build(0xE37, 1700, body, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(data: Buffer) {
|
||||||
|
const oidbBody = OidbBase.parse(data).body;
|
||||||
|
return new NapProtoMsg(proto.OidbSvcTrpcTcp0XE37Response).decode(oidbBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new UploadPrivateFile();
|
87
src/core/packet/transformer/highway/UploadPrivateImage.ts
Normal file
87
src/core/packet/transformer/highway/UploadPrivateImage.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
|
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||||
|
import { OidbPacket, PacketTransformer } from "@/core/packet/transformer/base";
|
||||||
|
import OidbBase from "@/core/packet/transformer/oidb/oidbBase";
|
||||||
|
import crypto from "node:crypto";
|
||||||
|
import { PacketMsgPicElement } from "@/core/packet/message/element";
|
||||||
|
|
||||||
|
class UploadPrivateImage extends PacketTransformer<typeof proto.NTV2RichMediaResp> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
build(peerUin: string, img: PacketMsgPicElement): OidbPacket {
|
||||||
|
const data = new NapProtoMsg(proto.NTV2RichMediaReq).encode({
|
||||||
|
reqHead: {
|
||||||
|
common: {
|
||||||
|
requestId: 1,
|
||||||
|
command: 100
|
||||||
|
},
|
||||||
|
scene: {
|
||||||
|
requestType: 2,
|
||||||
|
businessType: 1,
|
||||||
|
sceneType: 1,
|
||||||
|
c2C: {
|
||||||
|
accountType: 2,
|
||||||
|
targetUid: peerUin
|
||||||
|
},
|
||||||
|
},
|
||||||
|
client: {
|
||||||
|
agentType: 2,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
upload: {
|
||||||
|
uploadInfo: [
|
||||||
|
{
|
||||||
|
fileInfo: {
|
||||||
|
fileSize: +img.size,
|
||||||
|
fileHash: img.md5,
|
||||||
|
fileSha1: img.sha1!,
|
||||||
|
fileName: img.name,
|
||||||
|
type: {
|
||||||
|
type: 1,
|
||||||
|
picFormat: img.picType, //TODO: extend NapCat imgType /cc @MliKiowa
|
||||||
|
videoFormat: 0,
|
||||||
|
voiceFormat: 0,
|
||||||
|
},
|
||||||
|
width: img.width,
|
||||||
|
height: img.height,
|
||||||
|
time: 0,
|
||||||
|
original: 1
|
||||||
|
},
|
||||||
|
subFileType: 0,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
tryFastUploadCompleted: true,
|
||||||
|
srvSendMsg: false,
|
||||||
|
clientRandomId: crypto.randomBytes(8).readBigUInt64BE() & BigInt('0x7FFFFFFFFFFFFFFF'),
|
||||||
|
compatQMsgSceneType: 1,
|
||||||
|
extBizInfo: {
|
||||||
|
pic: {
|
||||||
|
bytesPbReserveTroop: Buffer.from("0800180020004200500062009201009a0100a2010c080012001800200028003a00", 'hex'),
|
||||||
|
textSummary: "Nya~", // TODO:
|
||||||
|
},
|
||||||
|
video: {
|
||||||
|
bytesPbReserve: Buffer.alloc(0),
|
||||||
|
},
|
||||||
|
ptt: {
|
||||||
|
bytesPbReserve: Buffer.alloc(0),
|
||||||
|
bytesReserve: Buffer.alloc(0),
|
||||||
|
bytesGeneralFlags: Buffer.alloc(0),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clientSeq: 0,
|
||||||
|
noNeedCompatMsg: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return OidbBase.build(0x11C5, 100, data,true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(data: Buffer) {
|
||||||
|
const oidbBody = OidbBase.parse(data).body;
|
||||||
|
return new NapProtoMsg(proto.NTV2RichMediaResp).decode(oidbBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new UploadPrivateImage();
|
81
src/core/packet/transformer/highway/UploadPrivatePtt.ts
Normal file
81
src/core/packet/transformer/highway/UploadPrivatePtt.ts
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
|
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||||
|
import { OidbPacket, PacketTransformer } from "@/core/packet/transformer/base";
|
||||||
|
import OidbBase from "@/core/packet/transformer/oidb/oidbBase";
|
||||||
|
import crypto from "node:crypto";
|
||||||
|
import { PacketMsgPttElement } from "@/core/packet/message/element";
|
||||||
|
|
||||||
|
class UploadPrivatePtt extends PacketTransformer<typeof proto.NTV2RichMediaResp> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
build(peerUin: string, ptt: PacketMsgPttElement): OidbPacket {
|
||||||
|
const data = new NapProtoMsg(proto.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: ptt.fileSha1,
|
||||||
|
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 OidbBase.build(0x126D, 100, data, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(data: Buffer) {
|
||||||
|
const oidbBody = OidbBase.parse(data).body;
|
||||||
|
return new NapProtoMsg(proto.NTV2RichMediaResp).decode(oidbBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new UploadPrivatePtt();
|
105
src/core/packet/transformer/highway/UploadPrivateVideo.ts
Normal file
105
src/core/packet/transformer/highway/UploadPrivateVideo.ts
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
|
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||||
|
import { OidbPacket, PacketTransformer } from "@/core/packet/transformer/base";
|
||||||
|
import OidbBase from "@/core/packet/transformer/oidb/oidbBase";
|
||||||
|
import crypto from "node:crypto";
|
||||||
|
import { PacketMsgVideoElement } from "@/core/packet/message/element";
|
||||||
|
|
||||||
|
class UploadPrivateVideo extends PacketTransformer<typeof proto.NTV2RichMediaResp> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
build(peerUin: string, video: PacketMsgVideoElement): OidbPacket {
|
||||||
|
if (!video.fileSize || !video.thumbSize) throw new Error("video.fileSize or video.thumbSize is empty");
|
||||||
|
const data = new NapProtoMsg(proto.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: video.fileSha1,
|
||||||
|
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: video.thumbSha1,
|
||||||
|
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 OidbBase.build(0x11E9, 100, data, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(data: Buffer) {
|
||||||
|
const oidbBody = OidbBase.parse(data).body;
|
||||||
|
return new NapProtoMsg(proto.NTV2RichMediaResp).decode(oidbBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new UploadPrivateVideo();
|
13
src/core/packet/transformer/highway/index.ts
Normal file
13
src/core/packet/transformer/highway/index.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export { default as DownloadGroupFile } from './DownloadGroupFile';
|
||||||
|
export { default as DownloadGroupPtt } from './DownloadGroupPtt';
|
||||||
|
export { default as DownloadOfflineFile } from './DownloadOfflineFile';
|
||||||
|
export { default as DownloadPrivateFile } from './DownloadPrivateFile';
|
||||||
|
export { default as FetchSessionKey } from './FetchSessionKey';
|
||||||
|
export { default as UploadGroupFile } from './UploadGroupFile';
|
||||||
|
export { default as UploadGroupImage } from './UploadGroupImage';
|
||||||
|
export { default as UploadGroupPtt } from './UploadGroupPtt';
|
||||||
|
export { default as UploadGroupVideo } from './UploadGroupVideo';
|
||||||
|
export { default as UploadPrivateFile } from './UploadPrivateFile';
|
||||||
|
export { default as UploadPrivateImage } from './UploadPrivateImage';
|
||||||
|
export { default as UploadPrivatePtt } from './UploadPrivatePtt';
|
||||||
|
export { default as UploadPrivateVideo } from './UploadPrivateVideo';
|
4
src/core/packet/transformer/index.ts
Normal file
4
src/core/packet/transformer/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export * from './action';
|
||||||
|
export * from './highway';
|
||||||
|
export * from './message';
|
||||||
|
export * from './system';
|
51
src/core/packet/transformer/message/UploadForwardMsg.ts
Normal file
51
src/core/packet/transformer/message/UploadForwardMsg.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import zlib from "node:zlib";
|
||||||
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
|
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||||
|
import { OidbPacket, PacketHexStrBuilder, PacketTransformer } from "@/core/packet/transformer/base";
|
||||||
|
import { PacketMsg } from "@/core/packet/message/message";
|
||||||
|
|
||||||
|
class UploadForwardMsg extends PacketTransformer<typeof proto.SendLongMsgResp> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
build(selfUid: string, msg: PacketMsg[], groupUin: number = 0): OidbPacket {
|
||||||
|
const msgBody = this.msgBuilder.buildFakeMsg(selfUid, msg);
|
||||||
|
const longMsgResultData = new NapProtoMsg(proto.LongMsgResult).encode(
|
||||||
|
{
|
||||||
|
action: {
|
||||||
|
actionCommand: "MultiMsg",
|
||||||
|
actionData: {
|
||||||
|
msgBody: msgBody
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const payload = zlib.gzipSync(Buffer.from(longMsgResultData));
|
||||||
|
const req = new NapProtoMsg(proto.SendLongMsgReq).encode(
|
||||||
|
{
|
||||||
|
info: {
|
||||||
|
type: groupUin === 0 ? 1 : 3,
|
||||||
|
uid: {
|
||||||
|
uid: groupUin === 0 ? selfUid : groupUin.toString(),
|
||||||
|
},
|
||||||
|
groupUin: groupUin,
|
||||||
|
payload: payload
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
field1: 4, field2: 1, field3: 7, field4: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
cmd: "trpc.group.long_msg_interface.MsgService.SsoSendLongMsg",
|
||||||
|
data: PacketHexStrBuilder(req)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(data: Buffer) {
|
||||||
|
return new NapProtoMsg(proto.SendLongMsgResp).decode(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new UploadForwardMsg();
|
1
src/core/packet/transformer/message/index.ts
Normal file
1
src/core/packet/transformer/message/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default as UploadForwardMsg } from './UploadForwardMsg';
|
32
src/core/packet/transformer/oidb/oidbBase.ts
Normal file
32
src/core/packet/transformer/oidb/oidbBase.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
|
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||||
|
import { OidbPacket, PacketHexStrBuilder, PacketTransformer } from "@/core/packet/transformer/base";
|
||||||
|
|
||||||
|
class OidbBase extends PacketTransformer<typeof proto.OidbSvcTrpcTcpBase> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
build(cmd: number, subCmd: number, body: Uint8Array, isUid: boolean = true, isLafter: boolean = false): OidbPacket {
|
||||||
|
const data = new NapProtoMsg(proto.OidbSvcTrpcTcpBase).encode({
|
||||||
|
command: cmd,
|
||||||
|
subCommand: subCmd,
|
||||||
|
body: body,
|
||||||
|
isReserved: isUid ? 1 : 0
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
cmd: `OidbSvcTrpcTcp.0x${cmd.toString(16).toUpperCase()}_${subCmd}`,
|
||||||
|
data: PacketHexStrBuilder(data),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(data: Buffer) {
|
||||||
|
const res = new NapProtoMsg(proto.OidbSvcTrpcTcpBase).decode(data);
|
||||||
|
if (res.errorCode !== 0) {
|
||||||
|
throw new Error(`OidbSvcTrpcTcpBase parse error: ${res.errorMsg} (code=${res.errorCode})`);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new OidbBase();
|
@@ -1,6 +1,6 @@
|
|||||||
import { ScalarType } from "@protobuf-ts/runtime";
|
import { ScalarType } from "@protobuf-ts/runtime";
|
||||||
import { ProtoField } from "@napneko/nap-proto-core";
|
import { ProtoField } from "@napneko/nap-proto-core";
|
||||||
import { ContentHead, MessageBody, MessageControl, RoutingHead } from "@/core/packet/proto/message/message";
|
import { ContentHead, MessageBody, MessageControl, RoutingHead } from "@/core/packet/transformer/proto";
|
||||||
|
|
||||||
export const FaceRoamRequest = {
|
export const FaceRoamRequest = {
|
||||||
comm: ProtoField(1, () => PlatInfo, true),
|
comm: ProtoField(1, () => PlatInfo, true),
|
@@ -1,5 +1,4 @@
|
|||||||
import { ScalarType } from "@protobuf-ts/runtime";
|
import { ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
||||||
import { ProtoField } from "@napneko/nap-proto-core";
|
|
||||||
|
|
||||||
export const MiniAppAdaptShareInfoReq = {
|
export const MiniAppAdaptShareInfoReq = {
|
||||||
appId: ProtoField(2, ScalarType.STRING),
|
appId: ProtoField(2, ScalarType.STRING),
|
@@ -1,6 +1,5 @@
|
|||||||
import { ScalarType } from "@protobuf-ts/runtime";
|
import { ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
||||||
import { ProtoField } from "@napneko/nap-proto-core";
|
import { MsgInfoBody } from "@/core/packet/transformer/proto";
|
||||||
import { MsgInfo, MsgInfoBody } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
|
||||||
|
|
||||||
export const DataHighwayHead = {
|
export const DataHighwayHead = {
|
||||||
version: ProtoField(1, ScalarType.UINT32),
|
version: ProtoField(1, ScalarType.UINT32),
|
31
src/core/packet/transformer/proto/index.ts
Normal file
31
src/core/packet/transformer/proto/index.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// action folder
|
||||||
|
export * from "./action/action";
|
||||||
|
export * from "./action/miniAppAdaptShareInfo";
|
||||||
|
|
||||||
|
// highway folder
|
||||||
|
export * from "./highway/highway";
|
||||||
|
|
||||||
|
// message folder
|
||||||
|
export * from "./message/action";
|
||||||
|
export * from "./message/c2c";
|
||||||
|
export * from "./message/component";
|
||||||
|
export * from "./message/element";
|
||||||
|
export * from "./message/group";
|
||||||
|
export * from "./message/message";
|
||||||
|
export * from "./message/notify";
|
||||||
|
export * from "./message/routing";
|
||||||
|
|
||||||
|
// oidb folder
|
||||||
|
export * from "./oidb/common/Ntv2.RichMediaReq";
|
||||||
|
export * from "./oidb/common/Ntv2.RichMediaResp";
|
||||||
|
export * from "./oidb/Oidb.0x6D6";
|
||||||
|
export * from "./oidb/Oidb.0x8FC_2";
|
||||||
|
export * from "./oidb/Oidb.0x9067_202";
|
||||||
|
export * from "./oidb/Oidb.0x929";
|
||||||
|
export * from "./oidb/Oidb.0xE37_1200";
|
||||||
|
export * from "./oidb/Oidb.0xE37_1700";
|
||||||
|
export * from "./oidb/Oidb.0XE37_800";
|
||||||
|
export * from "./oidb/Oidb.0xEB7";
|
||||||
|
export * from "./oidb/Oidb.0xED3_1";
|
||||||
|
export * from "./oidb/Oidb.0XFE1_2";
|
||||||
|
export * from "./oidb/OidbBase";
|
@@ -1,6 +1,5 @@
|
|||||||
import { ScalarType } from "@protobuf-ts/runtime";
|
import { ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
||||||
import { ProtoField } from "@napneko/nap-proto-core";
|
import { PushMsgBody } from "@/core/packet/transformer/proto";
|
||||||
import { PushMsgBody } from "@/core/packet/proto/message/message";
|
|
||||||
|
|
||||||
export const LongMsgResult = {
|
export const LongMsgResult = {
|
||||||
action: ProtoField(2, () => LongMsgAction)
|
action: ProtoField(2, () => LongMsgAction)
|
@@ -1,5 +1,4 @@
|
|||||||
import { ScalarType } from "@protobuf-ts/runtime";
|
import { ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
||||||
import { ProtoField } from "@napneko/nap-proto-core";
|
|
||||||
|
|
||||||
export const C2C = {
|
export const C2C = {
|
||||||
uin: ProtoField(1, ScalarType.UINT32, true),
|
uin: ProtoField(1, ScalarType.UINT32, true),
|
@@ -1,6 +1,5 @@
|
|||||||
import { ScalarType } from "@protobuf-ts/runtime";
|
import { ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
||||||
import { ProtoField } from "@napneko/nap-proto-core";
|
import { Elem } from "@/core/packet/transformer/proto";
|
||||||
import { Elem } from "@/core/packet/proto/message/element";
|
|
||||||
|
|
||||||
export const Attr = {
|
export const Attr = {
|
||||||
codePage: ProtoField(1, ScalarType.INT32),
|
codePage: ProtoField(1, ScalarType.INT32),
|
@@ -1,5 +1,4 @@
|
|||||||
import { ScalarType } from "@protobuf-ts/runtime";
|
import { ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
||||||
import { ProtoField } from "@napneko/nap-proto-core";
|
|
||||||
|
|
||||||
export const Elem = {
|
export const Elem = {
|
||||||
text: ProtoField(1, () => Text, true),
|
text: ProtoField(1, () => Text, true),
|
@@ -1,5 +1,4 @@
|
|||||||
import { ScalarType } from "@protobuf-ts/runtime";
|
import { ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
||||||
import { ProtoField } from "@napneko/nap-proto-core";
|
|
||||||
|
|
||||||
export const GroupRecallMsg = {
|
export const GroupRecallMsg = {
|
||||||
type: ProtoField(1, ScalarType.UINT32),
|
type: ProtoField(1, ScalarType.UINT32),
|
@@ -1,8 +1,14 @@
|
|||||||
import { ScalarType } from "@protobuf-ts/runtime";
|
import { ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
||||||
import { ProtoField } from "@napneko/nap-proto-core";
|
import {
|
||||||
import { ForwardHead, Grp, GrpTmp, ResponseForward, ResponseGrp, Trans0X211, WPATmp } from "@/core/packet/proto/message/routing";
|
C2C,
|
||||||
import { RichText } from "@/core/packet/proto/message/component";
|
ForwardHead,
|
||||||
import { C2C } from "@/core/packet/proto/message/c2c";
|
Grp,
|
||||||
|
GrpTmp,
|
||||||
|
ResponseForward,
|
||||||
|
ResponseGrp, RichText,
|
||||||
|
Trans0X211,
|
||||||
|
WPATmp
|
||||||
|
} from "@/core/packet/transformer/proto";
|
||||||
|
|
||||||
export const ContentHead = {
|
export const ContentHead = {
|
||||||
type: ProtoField(1, ScalarType.UINT32),
|
type: ProtoField(1, ScalarType.UINT32),
|
@@ -1,5 +1,4 @@
|
|||||||
import { ScalarType } from "@protobuf-ts/runtime";
|
import { ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
||||||
import { ProtoField } from "@napneko/nap-proto-core";
|
|
||||||
|
|
||||||
export const FriendRecall = {
|
export const FriendRecall = {
|
||||||
info: ProtoField(1, () => FriendRecallInfo),
|
info: ProtoField(1, () => FriendRecallInfo),
|
@@ -1,5 +1,4 @@
|
|||||||
import { ScalarType } from "@protobuf-ts/runtime";
|
import { ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
||||||
import { ProtoField } from "@napneko/nap-proto-core";
|
|
||||||
|
|
||||||
export const ForwardHead = {
|
export const ForwardHead = {
|
||||||
field1: ProtoField(1, ScalarType.UINT32, true),
|
field1: ProtoField(1, ScalarType.UINT32, true),
|
@@ -1,6 +1,5 @@
|
|||||||
import { ScalarType } from "@protobuf-ts/runtime";
|
import { ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
||||||
import { ProtoField } from "@napneko/nap-proto-core";
|
import { OidbSvcTrpcTcp0XE37_800_1200Metadata } from "@/core/packet/transformer/proto";
|
||||||
import { OidbSvcTrpcTcp0XE37_800_1200Metadata } from "@/core/packet/proto/oidb/Oidb.0xE37_1200";
|
|
||||||
|
|
||||||
export const OidbSvcTrpcTcp0XE37_800 = {
|
export const OidbSvcTrpcTcp0XE37_800 = {
|
||||||
subCommand: ProtoField(1, ScalarType.UINT32),
|
subCommand: ProtoField(1, ScalarType.UINT32),
|
@@ -1,5 +1,4 @@
|
|||||||
import { ScalarType } from "@protobuf-ts/runtime";
|
import { ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
||||||
import { ProtoField } from "@napneko/nap-proto-core";
|
|
||||||
|
|
||||||
export const OidbSvcTrpcTcp0XFE1_2 = {
|
export const OidbSvcTrpcTcp0XFE1_2 = {
|
||||||
uin: ProtoField(1, ScalarType.UINT32),
|
uin: ProtoField(1, ScalarType.UINT32),
|
@@ -1,5 +1,4 @@
|
|||||||
import { ScalarType } from "@protobuf-ts/runtime";
|
import { ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
||||||
import { ProtoField } from "@napneko/nap-proto-core";
|
|
||||||
|
|
||||||
export const OidbSvcTrpcTcp0x6D6 = {
|
export const OidbSvcTrpcTcp0x6D6 = {
|
||||||
file: ProtoField(1, () => OidbSvcTrpcTcp0x6D6Upload, true),
|
file: ProtoField(1, () => OidbSvcTrpcTcp0x6D6Upload, true),
|
@@ -1,5 +1,4 @@
|
|||||||
import { ScalarType } from "@protobuf-ts/runtime";
|
import { ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
||||||
import { ProtoField } from "@napneko/nap-proto-core";
|
|
||||||
|
|
||||||
|
|
||||||
//设置群头衔 OidbSvcTrpcTcp.0x8fc_2
|
//设置群头衔 OidbSvcTrpcTcp.0x8fc_2
|
@@ -1,5 +1,4 @@
|
|||||||
import { ScalarType } from "@protobuf-ts/runtime";
|
import { ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
||||||
import { ProtoField } from "@napneko/nap-proto-core";
|
|
||||||
import { MultiMediaReqHead } from "./common/Ntv2.RichMediaReq";
|
import { MultiMediaReqHead } from "./common/Ntv2.RichMediaReq";
|
||||||
|
|
||||||
//Req
|
//Req
|
@@ -1,6 +1,6 @@
|
|||||||
import { ScalarType } from "@protobuf-ts/runtime";
|
import { ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
||||||
import { ProtoField } from "@napneko/nap-proto-core";
|
import { MsgInfo } from "@/core/packet/transformer/proto";
|
||||||
import { MsgInfo } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
|
||||||
|
|
||||||
export const OidbSvcTrpcTcp0X929D_0 = {
|
export const OidbSvcTrpcTcp0X929D_0 = {
|
||||||
groupUin: ProtoField(1, ScalarType.UINT32),
|
groupUin: ProtoField(1, ScalarType.UINT32),
|
@@ -1,5 +1,4 @@
|
|||||||
import { ScalarType } from "@protobuf-ts/runtime";
|
import { ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
||||||
import { ProtoField } from "@napneko/nap-proto-core";
|
|
||||||
|
|
||||||
export const OidbSvcTrpcTcp0XE37_1200 = {
|
export const OidbSvcTrpcTcp0XE37_1200 = {
|
||||||
subCommand: ProtoField(1, ScalarType.UINT32, true),
|
subCommand: ProtoField(1, ScalarType.UINT32, true),
|
@@ -1,5 +1,4 @@
|
|||||||
import { ScalarType } from "@protobuf-ts/runtime";
|
import { ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
||||||
import { ProtoField } from "@napneko/nap-proto-core";
|
|
||||||
|
|
||||||
export const OidbSvcTrpcTcp0XE37_1700 = {
|
export const OidbSvcTrpcTcp0XE37_1700 = {
|
||||||
command: ProtoField(1, ScalarType.UINT32, true),
|
command: ProtoField(1, ScalarType.UINT32, true),
|
@@ -1,5 +1,4 @@
|
|||||||
import { ScalarType } from "@protobuf-ts/runtime";
|
import { ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
||||||
import { ProtoField } from "@napneko/nap-proto-core";
|
|
||||||
|
|
||||||
export const OidbSvcTrpcTcp0XEB7_Body = {
|
export const OidbSvcTrpcTcp0XEB7_Body = {
|
||||||
uin: ProtoField(1, ScalarType.STRING),
|
uin: ProtoField(1, ScalarType.STRING),
|
@@ -1,5 +1,4 @@
|
|||||||
import { ScalarType } from "@protobuf-ts/runtime";
|
import { ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
||||||
import { ProtoField } from "@napneko/nap-proto-core";
|
|
||||||
|
|
||||||
// Send Poke
|
// Send Poke
|
||||||
export const OidbSvcTrpcTcp0XED3_1 = {
|
export const OidbSvcTrpcTcp0XED3_1 = {
|
@@ -1,5 +1,4 @@
|
|||||||
import { ScalarType } from "@protobuf-ts/runtime";
|
import { ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
||||||
import { ProtoField } from "@napneko/nap-proto-core";
|
|
||||||
|
|
||||||
export const OidbSvcTrpcTcpBase = {
|
export const OidbSvcTrpcTcpBase = {
|
||||||
command: ProtoField(1, ScalarType.UINT32),
|
command: ProtoField(1, ScalarType.UINT32),
|
@@ -1,5 +1,4 @@
|
|||||||
import { ScalarType } from "@protobuf-ts/runtime";
|
import { ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
||||||
import { ProtoField } from "@napneko/nap-proto-core";
|
|
||||||
|
|
||||||
export const NTV2RichMediaReq = {
|
export const NTV2RichMediaReq = {
|
||||||
ReqHead: ProtoField(1, () => MultiMediaReqHead),
|
ReqHead: ProtoField(1, () => MultiMediaReqHead),
|
@@ -1,6 +1,6 @@
|
|||||||
import { ScalarType } from "@protobuf-ts/runtime";
|
import { ProtoField, ScalarType } from "@napneko/nap-proto-core";
|
||||||
import { ProtoField } from "@napneko/nap-proto-core";
|
import { CommonHead, MsgInfo, PicUrlExtInfo, VideoExtInfo } from "@/core/packet/transformer/proto";
|
||||||
import { CommonHead, MsgInfo, PicUrlExtInfo, VideoExtInfo } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
|
||||||
|
|
||||||
export const NTV2RichMediaResp = {
|
export const NTV2RichMediaResp = {
|
||||||
respHead: ProtoField(1, () => MultiMediaRespHead),
|
respHead: ProtoField(1, () => MultiMediaRespHead),
|
40
src/core/packet/transformer/system/FetchRkey.ts
Normal file
40
src/core/packet/transformer/system/FetchRkey.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import * as proto from "@/core/packet/transformer/proto";
|
||||||
|
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||||
|
import { OidbPacket, PacketTransformer } from "@/core/packet/transformer/base";
|
||||||
|
import OidbBase from "@/core/packet/transformer/oidb/oidbBase";
|
||||||
|
|
||||||
|
class FetchRkey extends PacketTransformer<typeof proto.OidbSvcTrpcTcp0X9067_202_Rsp_Body> {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
build(): OidbPacket {
|
||||||
|
const data = new NapProtoMsg(proto.OidbSvcTrpcTcp0X9067_202).encode({
|
||||||
|
reqHead: {
|
||||||
|
common: {
|
||||||
|
requestId: 1,
|
||||||
|
command: 202
|
||||||
|
},
|
||||||
|
scene: {
|
||||||
|
requestType: 2,
|
||||||
|
businessType: 1,
|
||||||
|
sceneType: 0
|
||||||
|
},
|
||||||
|
client: {
|
||||||
|
agentType: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
downloadRKeyReq: {
|
||||||
|
key: [10, 20, 2]
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return OidbBase.build(0x9067, 202, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(data: Buffer) {
|
||||||
|
const oidbBody = OidbBase.parse(data).body;
|
||||||
|
return new NapProtoMsg(proto.OidbSvcTrpcTcp0X9067_202_Rsp_Body).decode(oidbBody);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new FetchRkey();
|
1
src/core/packet/transformer/system/index.ts
Normal file
1
src/core/packet/transformer/system/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default as FetchRkey } from './FetchRkey';
|
@@ -4,7 +4,7 @@ import {
|
|||||||
MiniAppRawData,
|
MiniAppRawData,
|
||||||
MiniAppReqCustomParams,
|
MiniAppReqCustomParams,
|
||||||
MiniAppReqTemplateParams
|
MiniAppReqTemplateParams
|
||||||
} from "@/core/packet/entities/miniApp";
|
} from "@/core/packet/client/entities/miniApp";
|
||||||
|
|
||||||
type MiniAppTemplateNameList = "bili" | "weibo";
|
type MiniAppTemplateNameList = "bili" | "weibo";
|
||||||
|
|
@@ -28,7 +28,7 @@ export class GetAiCharacters extends GetPacketStatusDepends<Payload, GetAiCharac
|
|||||||
payloadSchema = SchemaData;
|
payloadSchema = SchemaData;
|
||||||
|
|
||||||
async _handle(payload: Payload) {
|
async _handle(payload: Payload) {
|
||||||
const rawList = await this.core.apis.PacketApi.sendFetchAiVoiceListReq(+payload.group_id, +(payload.chat_type ?? 1) as AIVoiceChatType);
|
const rawList = await this.core.apis.PacketApi.pkt.operation.FetchAiVoiceList(+payload.group_id, +(payload.chat_type ?? 1) as AIVoiceChatType);
|
||||||
return rawList?.map((item) => ({
|
return rawList?.map((item) => ({
|
||||||
type: item.category,
|
type: item.category,
|
||||||
characters: item.voices.map((voice) => ({
|
characters: item.voices.map((voice) => ({
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
import { ActionName } from '../types';
|
import { ActionName } from '../types';
|
||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||||
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
|
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
|
||||||
|
import { MiniAppInfo, MiniAppInfoHelper } from "@/core/packet/utils/helper/miniAppHelper";
|
||||||
import { MiniAppData, MiniAppRawData, MiniAppReqCustomParams, MiniAppReqParams } from "@/core/packet/entities/miniApp";
|
import { MiniAppData, MiniAppRawData, MiniAppReqCustomParams, MiniAppReqParams } from "@/core/packet/entities/miniApp";
|
||||||
import { MiniAppInfo, MiniAppInfoHelper } from "@/core/packet/helper/miniAppHelper";
|
|
||||||
|
|
||||||
const SchemaData = {
|
const SchemaData = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -77,7 +77,7 @@ export class GetMiniAppArk extends GetPacketStatusDepends<Payload, {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const arkData = await this.core.apis.PacketApi.sendMiniAppShareInfoReq(reqParam);
|
const arkData = await this.core.apis.PacketApi.pkt.operation.GetMiniAppAdaptShareInfo(reqParam);
|
||||||
return {
|
return {
|
||||||
data: payload.rawArkData ? arkData : MiniAppInfoHelper.RawToSend(arkData)
|
data: payload.rawArkData ? arkData : MiniAppInfoHelper.RawToSend(arkData)
|
||||||
};
|
};
|
||||||
|
@@ -6,6 +6,6 @@ export class GetRkey extends GetPacketStatusDepends<null, Array<any>> {
|
|||||||
actionName = ActionName.GetRkey;
|
actionName = ActionName.GetRkey;
|
||||||
|
|
||||||
async _handle() {
|
async _handle() {
|
||||||
return await this.core.apis.PacketApi.sendRkeyPacket();
|
return await this.core.apis.PacketApi.pkt.operation.FetchRkey();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,6 +17,6 @@ export class GetUserStatus extends GetPacketStatusDepends<Payload, { status: num
|
|||||||
payloadSchema = SchemaData;
|
payloadSchema = SchemaData;
|
||||||
|
|
||||||
async _handle(payload: Payload) {
|
async _handle(payload: Payload) {
|
||||||
return await this.core.apis.PacketApi.sendStatusPacket(+payload.user_id);
|
return await this.core.apis.PacketApi.pkt.operation.GetStrangerStatus(+payload.user_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,9 +17,9 @@ export class SetGroupSign extends BaseAction<Payload, any> {
|
|||||||
payloadSchema = SchemaData;
|
payloadSchema = SchemaData;
|
||||||
|
|
||||||
async _handle(payload: Payload) {
|
async _handle(payload: Payload) {
|
||||||
return await this.core.apis.PacketApi.sendGroupSignPacket(payload.group_id.toString());
|
return await this.core.apis.PacketApi.pkt.operation.GroupSign(+payload.group_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export class SendGroupSign extends SetGroupSign {
|
export class SendGroupSign extends SetGroupSign {
|
||||||
actionName = ActionName.SendGroupSign;
|
actionName = ActionName.SendGroupSign;
|
||||||
}
|
}
|
||||||
|
@@ -20,6 +20,6 @@ export class SetSpecialTittle extends GetPacketStatusDepends<Payload, any> {
|
|||||||
async _handle(payload: Payload) {
|
async _handle(payload: Payload) {
|
||||||
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||||
if(!uid) throw new Error('User not found');
|
if(!uid) throw new Error('User not found');
|
||||||
await this.core.apis.PacketApi.sendSetSpecialTittlePacket(payload.group_id.toString(), uid, payload.special_title);
|
await this.core.apis.PacketApi.pkt.operation.SetGroupSpecialTitle(+payload.group_id, uid, payload.special_title);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,7 @@ export class GetGroupFileUrl extends GetPacketStatusDepends<Payload, GetGroupFil
|
|||||||
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id) || FileNapCatOneBotUUID.decodeModelId(payload.file_id);
|
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id) || FileNapCatOneBotUUID.decodeModelId(payload.file_id);
|
||||||
if (contextMsgFile?.fileUUID) {
|
if (contextMsgFile?.fileUUID) {
|
||||||
return {
|
return {
|
||||||
url: await this.core.apis.PacketApi.sendGroupFileDownloadReq(+payload.group_id, contextMsgFile.fileUUID)
|
url: await this.core.apis.PacketApi.pkt.operation.GetGroupFileUrl(+payload.group_id, contextMsgFile.fileUUID)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
throw new Error('real fileUUID not found!');
|
throw new Error('real fileUUID not found!');
|
||||||
|
@@ -20,7 +20,7 @@ export class GetAiRecord extends GetPacketStatusDepends<Payload, string> {
|
|||||||
payloadSchema = SchemaData;
|
payloadSchema = SchemaData;
|
||||||
|
|
||||||
async _handle(payload: Payload) {
|
async _handle(payload: Payload) {
|
||||||
const rawRsp = await this.core.apis.PacketApi.sendAiVoiceChatReq(+payload.group_id, payload.character, payload.text, AIVoiceChatType.Sound);
|
const rawRsp = await this.core.apis.PacketApi.pkt.operation.GetAiVoice(+payload.group_id, payload.character, payload.text, AIVoiceChatType.Sound);
|
||||||
return await this.core.apis.PacketApi.sendGroupPttFileDownloadReq(+payload.group_id, rawRsp.msgInfoBody[0].index);
|
return await this.core.apis.PacketApi.pkt.operation.GetGroupPttUrl(+payload.group_id, rawRsp.msgInfoBody[0].index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,6 @@ export class GroupPoke extends GetPacketStatusDepends<Payload, any> {
|
|||||||
payloadSchema = SchemaData;
|
payloadSchema = SchemaData;
|
||||||
|
|
||||||
async _handle(payload: Payload) {
|
async _handle(payload: Payload) {
|
||||||
await this.core.apis.PacketApi.sendPokePacket(+payload.user_id, +payload.group_id);
|
await this.core.apis.PacketApi.pkt.operation.GroupPoke(+payload.user_id, +payload.group_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import { ActionName } from '../types';
|
import { ActionName } from '../types';
|
||||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||||
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
|
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
|
||||||
import { AIVoiceChatType } from "@/core/packet/entities/aiChat";
|
|
||||||
import { uri2local } from "@/common/file";
|
import { uri2local } from "@/common/file";
|
||||||
import { ChatType, Peer } from "@/core";
|
import { ChatType, Peer } from "@/core";
|
||||||
|
import { AIVoiceChatType } from "@/core/packet/entities/aiChat";
|
||||||
|
|
||||||
const SchemaData = {
|
const SchemaData = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@@ -24,8 +24,8 @@ export class SendGroupAiRecord extends GetPacketStatusDepends<Payload, {
|
|||||||
payloadSchema = SchemaData;
|
payloadSchema = SchemaData;
|
||||||
|
|
||||||
async _handle(payload: Payload) {
|
async _handle(payload: Payload) {
|
||||||
const rawRsp = await this.core.apis.PacketApi.sendAiVoiceChatReq(+payload.group_id, payload.character, payload.text, AIVoiceChatType.Sound);
|
const rawRsp = await this.core.apis.PacketApi.pkt.operation.GetAiVoice(+payload.group_id, payload.character, payload.text, AIVoiceChatType.Sound);
|
||||||
const url = await this.core.apis.PacketApi.sendGroupPttFileDownloadReq(+payload.group_id, rawRsp.msgInfoBody[0].index);
|
const url = await this.core.apis.PacketApi.pkt.operation.GetGroupPttUrl(+payload.group_id, rawRsp.msgInfoBody[0].index);
|
||||||
const { path, fileName, errMsg, success } = (await uri2local(this.core.NapCatTempPath, url));
|
const { path, fileName, errMsg, success } = (await uri2local(this.core.NapCatTempPath, url));
|
||||||
if (!success) {
|
if (!success) {
|
||||||
throw new Error(errMsg);
|
throw new Error(errMsg);
|
||||||
|
@@ -18,11 +18,11 @@ type PlayloadType = FromSchema<typeof SchemaData>;
|
|||||||
class MarkMsgAsRead extends BaseAction<PlayloadType, null> {
|
class MarkMsgAsRead extends BaseAction<PlayloadType, null> {
|
||||||
async getPeer(payload: PlayloadType): Promise<Peer> {
|
async getPeer(payload: PlayloadType): Promise<Peer> {
|
||||||
if (payload.message_id) {
|
if (payload.message_id) {
|
||||||
let s_peer = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id)?.Peer;
|
const s_peer = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id)?.Peer;
|
||||||
if (s_peer) {
|
if (s_peer) {
|
||||||
return s_peer;
|
return s_peer;
|
||||||
}
|
}
|
||||||
let l_peer = MessageUnique.getPeerByMsgId(payload.message_id.toString())?.Peer;
|
const l_peer = MessageUnique.getPeerByMsgId(payload.message_id.toString())?.Peer;
|
||||||
if (l_peer) {
|
if (l_peer) {
|
||||||
return l_peer;
|
return l_peer;
|
||||||
}
|
}
|
||||||
|
@@ -11,10 +11,10 @@ import { decodeCQCode } from '@/onebot/cqcode';
|
|||||||
import { MessageUnique } from '@/common/message-unique';
|
import { MessageUnique } from '@/common/message-unique';
|
||||||
import { ChatType, ElementType, NapCatCore, Peer, RawMessage, SendArkElement, SendMessageElement } from '@/core';
|
import { ChatType, ElementType, NapCatCore, Peer, RawMessage, SendArkElement, SendMessageElement } from '@/core';
|
||||||
import BaseAction from '../BaseAction';
|
import BaseAction from '../BaseAction';
|
||||||
import { rawMsgWithSendMsg } from "@/core/packet/message/converter";
|
|
||||||
import { PacketMsg } from "@/core/packet/message/message";
|
|
||||||
import { ForwardMsgBuilder } from "@/common/forward-msg-builder";
|
import { ForwardMsgBuilder } from "@/common/forward-msg-builder";
|
||||||
import { stringifyWithBigInt } from "@/common/helper";
|
import { stringifyWithBigInt } from "@/common/helper";
|
||||||
|
import { PacketMsg } from "@/core/packet/message/message";
|
||||||
|
import { rawMsgWithSendMsg } from "@/core/packet/message/converter";
|
||||||
|
|
||||||
export interface ReturnDataType {
|
export interface ReturnDataType {
|
||||||
message_id: number;
|
message_id: number;
|
||||||
@@ -192,7 +192,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
msg: sendElements,
|
msg: sendElements,
|
||||||
};
|
};
|
||||||
logger.logDebug(`handleForwardedNodesPacket[SendRaw] 开始转换 ${stringifyWithBigInt(packetMsgElements)}`);
|
logger.logDebug(`handleForwardedNodesPacket[SendRaw] 开始转换 ${stringifyWithBigInt(packetMsgElements)}`);
|
||||||
const transformedMsg = this.core.apis.PacketApi.packetSession?.packer.packetConverter.rawMsgWithSendMsgToPacketMsg(packetMsgElements);
|
const transformedMsg = this.core.apis.PacketApi.pkt.msgConverter.rawMsgWithSendMsgToPacketMsg(packetMsgElements);
|
||||||
logger.logDebug(`handleForwardedNodesPacket[SendRaw] 转换为 ${stringifyWithBigInt(transformedMsg)}`);
|
logger.logDebug(`handleForwardedNodesPacket[SendRaw] 转换为 ${stringifyWithBigInt(transformedMsg)}`);
|
||||||
packetMsg.push(transformedMsg!);
|
packetMsg.push(transformedMsg!);
|
||||||
} else if (node.data.id) {
|
} else if (node.data.id) {
|
||||||
@@ -205,7 +205,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
const msg = (await this.core.apis.MsgApi.getMsgsByMsgId(nodeMsg.Peer, [nodeMsg.MsgId])).msgList[0];
|
const msg = (await this.core.apis.MsgApi.getMsgsByMsgId(nodeMsg.Peer, [nodeMsg.MsgId])).msgList[0];
|
||||||
logger.logDebug(`handleForwardedNodesPacket[PureRaw] 开始转换 ${stringifyWithBigInt(msg)}`);
|
logger.logDebug(`handleForwardedNodesPacket[PureRaw] 开始转换 ${stringifyWithBigInt(msg)}`);
|
||||||
await this.core.apis.FileApi.downloadRawMsgMedia([msg]);
|
await this.core.apis.FileApi.downloadRawMsgMedia([msg]);
|
||||||
const transformedMsg = this.core.apis.PacketApi.packetSession?.packer.packetConverter.rawMsgToPacketMsg(msg, msgPeer);
|
const transformedMsg = this.core.apis.PacketApi.pkt.msgConverter.rawMsgToPacketMsg(msg, msgPeer);
|
||||||
logger.logDebug(`handleForwardedNodesPacket[PureRaw] 转换为 ${stringifyWithBigInt(transformedMsg)}`);
|
logger.logDebug(`handleForwardedNodesPacket[PureRaw] 转换为 ${stringifyWithBigInt(transformedMsg)}`);
|
||||||
packetMsg.push(transformedMsg!);
|
packetMsg.push(transformedMsg!);
|
||||||
} else {
|
} else {
|
||||||
@@ -216,7 +216,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
logger.logWarn('handleForwardedNodesPacket 元素为空!');
|
logger.logWarn('handleForwardedNodesPacket 元素为空!');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const resid = await this.core.apis.PacketApi.sendUploadForwardMsg(packetMsg, msgPeer.chatType === ChatType.KCHATTYPEGROUP ? +msgPeer.peerUid : 0);
|
const resid = await this.core.apis.PacketApi.pkt.operation.UploadForwardMsg(packetMsg, msgPeer.chatType === ChatType.KCHATTYPEGROUP ? +msgPeer.peerUid : 0);
|
||||||
const forwardJson = ForwardMsgBuilder.fromPacketMsg(resid, packetMsg, source, news, summary, prompt);
|
const forwardJson = ForwardMsgBuilder.fromPacketMsg(resid, packetMsg, source, news, summary, prompt);
|
||||||
return {
|
return {
|
||||||
finallySendElements: {
|
finallySendElements: {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user