refactor: packet

This commit is contained in:
pk5ls20
2024-10-14 13:59:34 +08:00
parent ec41d19d06
commit ef39e7ab01
27 changed files with 42 additions and 39 deletions

View File

@@ -1,127 +0,0 @@
import { LogWrapper } from "@/common/log";
import { LRUCache } from "@/common/lru-cache";
import WebSocket from "ws";
import { createHash } from "crypto";
export class PacketClient {
private websocket: WebSocket | undefined;
public isConnected: boolean = false;
private reconnectAttempts: number = 0;
private maxReconnectAttempts: number = 5;
//trace_id-type callback
private cb = new LRUCache<string, any>(500);
constructor(private url: string, public logger: LogWrapper) { }
connect(): Promise<void> {
return new Promise((resolve, reject) => {
this.logger.log.bind(this.logger)(`Attempting to connect to ${this.url}`);
this.websocket = new WebSocket(this.url);
this.websocket.on('error', (err) => this.logger.logError.bind(this.logger)('[Core] [Packet Server] Error:', err.message));
this.websocket.onopen = () => {
this.isConnected = true;
this.reconnectAttempts = 0;
this.logger.log.bind(this.logger)(`Connected to ${this.url}`);
resolve();
};
this.websocket.onerror = (error) => {
this.logger.logError.bind(this.logger)(`WebSocket error: ${error}`);
reject(error);
};
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.isConnected = false;
this.logger.logWarn.bind(this.logger)(`Disconnected from ${this.url}`);
this.attemptReconnect();
};
});
}
private attemptReconnect(): void {
try {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
this.logger.logError.bind(this.logger)(`Reconnecting attempt ${this.reconnectAttempts}`);
setTimeout(() => {
this.connect().catch((error) => {
this.logger.logError.bind(this.logger)(`Reconnect attempt failed: ${error.message}`);
});
}, 5000 * this.reconnectAttempts);
} else {
this.logger.logError.bind(this.logger)(`Max reconnect attempts reached. Could not reconnect to ${this.url}`);
}
} catch (error: any) {
this.logger.logError.bind(this.logger)(`Error attempting to reconnect: ${error.message}`);
}
}
async registerCallback(trace_id: string, type: string, callback: any): Promise<void> {
this.cb.put(createHash('md5').update(trace_id).digest('hex') + type, callback);
}
async init(pid: number, recv: string, send: string): Promise<void> {
if (!this.isConnected || !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));
}
async sendCommand(cmd: string, data: string, trace_id: string, rsp: boolean = false, timeout: number = 5000, sendcb: any = () => { }): Promise<any> {
return new Promise<any>((resolve, reject) => {
if (!this.isConnected || !this.websocket) {
throw new Error("WebSocket is not connected");
}
const commandMessage = {
action: 'send',
cmd: cmd,
data: data,
trace_id: trace_id
};
this.websocket.send(JSON.stringify(commandMessage));
if (rsp) {
this.registerCallback(trace_id, 'recv', (json: any) => {
clearTimeout(timeoutHandle);
resolve(json);
});
}
this.registerCallback(trace_id, 'send', (json: any) => {
sendcb(json);
if (!rsp) {
clearTimeout(timeoutHandle);
resolve(json);
}
});
const timeoutHandle = setTimeout(() => {
reject(new Error(`sendCommand timed out after ${timeout} ms`));
}, timeout);
});
}
private async handleMessage(message: any): Promise<void> {
try {
let json = JSON.parse(message.toString());
let trace_id_md5 = json.trace_id_md5;
let action = json?.type ?? 'init';
let event = this.cb.get(trace_id_md5 + action);
if (event) {
await event(json.data);
}
//console.log("Received message:", json);
} catch (error) {
this.logger.logError.bind(this.logger)(`Error parsing message: ${error}`);
}
}
}

View File

@@ -1,61 +0,0 @@
import {PushMsgBody} from "@/core/proto/message/message";
import {NapProtoEncodeStructType} from "@/core/proto/NapProto";
import {SendMessageElement} from "@/core";
import * as crypto from "crypto";
import {IPacketMsgElement} from "@/core/helper/packet/msg/element";
export interface PacketForwardNode {
groupId?: number
senderId: number
senderName: string
time: number
msg: IPacketMsgElement<SendMessageElement>[]
}
export class PacketMsgBuilder {
buildFakeMsg(selfUid: string, element: PacketForwardNode[]): NapProtoEncodeStructType<typeof PushMsgBody>[] {
return element.map((node): NapProtoEncodeStructType<typeof PushMsgBody> => {
const avatar = `https://q.qlogo.cn/headimg_dl?dst_uin=${node.senderId}&spec=640&img_type=jpg`
return {
responseHead: {
fromUid: "",
fromUin: node.senderId,
toUid: node.groupId ? undefined : selfUid,
forward: node.groupId ? undefined : {
friendName: node.senderName,
},
grp: node.groupId ? {
groupUin: node.groupId,
memberName: node.senderName,
unknown5: 2
} : undefined,
},
contentHead: {
type: node.groupId ? 82 : 9,
subType: node.groupId ? undefined : 4,
divSeq: node.groupId ? undefined : 4,
msgId: crypto.randomBytes(4).readUInt32LE(0),
sequence: crypto.randomBytes(4).readUInt32LE(0),
timeStamp: Math.floor(Date.now() / 1000),
field7: BigInt(1),
field8: 0,
field9: 0,
forward: {
field1: 0,
field2: 0,
field3: node.groupId ? 0 : 2,
unknownBase64: avatar,
avatar: avatar
}
},
body: {
richText: {
elems: node.msg.map(
(msg) => msg.buildElement() ?? []
)
}
}
}
});
}
}

View File

@@ -1,117 +0,0 @@
import {NapProtoEncodeStructType, NapProtoMsg} from "@/core/proto/NapProto";
import {Elem, MentionExtra} from "@/core/proto/message/element";
import {
AtType,
SendArkElement,
SendFaceElement,
SendFileElement,
SendMessageElement,
SendPicElement,
SendPttElement,
SendReplyElement,
SendTextElement,
SendVideoElement
} from "@/core";
// raw <-> packet
// TODO: check ob11 -> raw impl!
// TODO: parse to raw element
export abstract class IPacketMsgElement<T extends SendMessageElement> {
protected constructor(rawElement: T) {
}
buildContent(): Uint8Array | undefined {
return undefined;
}
buildElement(): NapProtoEncodeStructType<typeof Elem> | undefined {
return undefined;
}
}
export class PacketMsgTextElement extends IPacketMsgElement<SendTextElement> {
text: string;
constructor(element: SendTextElement) {
super(element);
console.log(JSON.stringify(element));
this.text = element.textElement.content;
}
buildElement(): NapProtoEncodeStructType<typeof Elem> {
return {
text: {
str: this.text
}
};
}
}
export class PacketMsgAtElement extends PacketMsgTextElement {
targetUid: string;
atAll: boolean;
constructor(element: SendTextElement) {
super(element);
this.targetUid = element.textElement.atNtUid;
this.atAll = element.textElement.atType === AtType.atAll
}
buildElement(): NapProtoEncodeStructType<typeof Elem> {
const res = new NapProtoMsg(MentionExtra).encode({
type: this.atAll ? 1 : 2,
uin: 0,
field5: 0,
uid: this.targetUid,
}
)
return {
text: {
str: this.text,
pbReserve: res
}
}
}
}
export class PacketMsgPttElement extends IPacketMsgElement<SendPttElement> {
constructor(element: SendPttElement) {
super(element);
}
}
export class PacketMsgPicElement extends IPacketMsgElement<SendPicElement> {
constructor(element: SendPicElement) {
super(element);
}
}
export class PacketMsgReplyElement extends IPacketMsgElement<SendReplyElement> {
constructor(element: SendReplyElement) {
super(element);
}
}
export class PacketMsgFaceElement extends IPacketMsgElement<SendFaceElement> {
constructor(element: SendFaceElement) {
super(element);
}
}
export class PacketMsgFileElement extends IPacketMsgElement<SendFileElement> {
constructor(element: SendFileElement) {
super(element);
}
}
export class PacketMsgVideoElement extends IPacketMsgElement<SendVideoElement> {
constructor(element: SendVideoElement) {
super(element);
}
}
export class PacketMsgLightAppElement extends IPacketMsgElement<SendArkElement> {
constructor(element: SendArkElement) {
super(element);
}
}

View File

@@ -1,122 +0,0 @@
import * as zlib from "node:zlib";
import { NapProtoMsg } from "@/core/proto/NapProto";
import { OidbSvcTrpcTcpBase } from "@/core/proto/oidb/OidbBase";
import { OidbSvcTrpcTcp0X9067_202 } from "@/core/proto/oidb/Oidb.0x9067_202";
import { OidbSvcTrpcTcp0X8FC_2, OidbSvcTrpcTcp0X8FC_2_Body } from "@/core/proto/oidb/Oidb.0x8FC_2";
import { OidbSvcTrpcTcp0XFE1_2 } from "@/core/proto/oidb/Oidb.fe1_2";
import { OidbSvcTrpcTcp0XED3_1 } from "@/core/proto/oidb/Oidb.ed3_1";
import {LongMsgResult, SendLongMsgReq} from "@/core/proto/message/action";
import {PacketForwardNode, PacketMsgBuilder} from "@/core/helper/packet/msg/builder";
export type PacketHexStr = string & { readonly hexNya: unique symbol };
export class PacketPacker {
private packetBuilder: PacketMsgBuilder
constructor() {
this.packetBuilder = new PacketMsgBuilder();
}
private toHexStr(byteArray: Uint8Array): PacketHexStr {
return Buffer.from(byteArray).toString('hex') as PacketHexStr;
}
packOidbPacket(cmd: number, subCmd: number, body: Uint8Array, isUid: boolean = true, isLafter: boolean = false): Uint8Array {
return new NapProtoMsg(OidbSvcTrpcTcpBase).encode({
command: cmd,
subCommand: subCmd,
body: body,
isReserved: isUid ? 1 : 0
});
}
packPokePacket(group: number, peer: number): PacketHexStr {
const oidb_0xed3 = new NapProtoMsg(OidbSvcTrpcTcp0XED3_1).encode({
uin: peer,
groupUin: group,
friendUin: group,
ext: 0
});
return this.toHexStr(this.packOidbPacket(0xed3, 1, oidb_0xed3));
}
packRkeyPacket(): PacketHexStr {
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.toHexStr(this.packOidbPacket(0x9067, 202, oidb_0x9067_202));
}
packSetSpecialTittlePacket(groupCode: string, uid: string, tittle: string): PacketHexStr {
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.toHexStr(this.packOidbPacket(0x8FC, 2, oidb_0x8FC_2, false, false));
}
packStatusPacket(uin: number): PacketHexStr {
let oidb_0xfe1_2 = new NapProtoMsg(OidbSvcTrpcTcp0XFE1_2).encode({
uin: uin,
key: [{ key: 27372 }]
});
return this.toHexStr(this.packOidbPacket(0xfe1, 2, oidb_0xfe1_2));
}
packUploadForwardMsg(selfUid: string, msg: PacketForwardNode[], groupUin: number = 0) : PacketHexStr {
// console.log("packUploadForwardMsg START!!!", selfUid, msg, groupUin);
const msgBody = this.packetBuilder.buildFakeMsg(selfUid, msg);
const longMsgResultData = new NapProtoMsg(LongMsgResult).encode(
{
action: {
actionCommand: "MultiMsg",
actionData: {
msgBody: msgBody
}
}
}
)
// console.log("packUploadForwardMsg LONGMSGRESULT!!!", this.toHexStr(longMsgResultData));
const payload = zlib.gzipSync(Buffer.from(longMsgResultData));
// console.log("packUploadForwardMsg PAYLOAD!!!", payload);
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
}
}
)
// console.log("packUploadForwardMsg REQ!!!", req);
return this.toHexStr(req);
}
}