diff --git a/config.json b/config.json new file mode 100644 index 00000000..23fc8d11 --- /dev/null +++ b/config.json @@ -0,0 +1 @@ +{"ip": "127.0.0.1", "port": 8086} \ No newline at end of file diff --git a/src/core/apis/group.ts b/src/core/apis/group.ts index beaad23f..3a22f0c7 100644 --- a/src/core/apis/group.ts +++ b/src/core/apis/group.ts @@ -56,10 +56,7 @@ export class NTQQGroupApi { async sendPacketPoke(group: string, peer: string) { let data = encodeGroupPoke(group, peer); let hex = Buffer.from(data).toString('hex'); - let retdata = await this.core.apis.PacketApi.sendPacket('OidbSvcTrpcTcp.0xed3_1', hex); - //await RequestUtil.HttpGetJson('http://127.0.0.1:8086/send', 'POST', { data: hex }, { 'Content-Type': 'application/json' }, false, true); - //let ret = await this.core.context.session.getMsgService().sendSsoCmdReqByContend('LightAppSvc.mini_app_i', hex.slice(0, hex.length / 2)); - // let ret = await this.core.context.session.getMsgService().sendSsoCmdReqByContend('OidbSvcTrpcTcp.0xfe1_2', hex.toString('hex')); + let retdata = await this.core.apis.PacketApi.sendPacket('OidbSvcTrpcTcp.0xed3_1', hex, true); console.log('sendPacketPoke', retdata); } async fetchGroupEssenceList(groupCode: string) { diff --git a/src/core/apis/packet.ts b/src/core/apis/packet.ts index c64d5781..3dff42f2 100644 --- a/src/core/apis/packet.ts +++ b/src/core/apis/packet.ts @@ -2,6 +2,7 @@ import { InstanceContext, NapCatCore } from '..'; import { RequestUtil } from '@/common/request'; import offset from '@/core/external/offset.json'; import * as crypto from 'crypto'; +import { PacketClient } from '../helper/packet'; interface OffsetType { [key: string]: { @@ -17,30 +18,26 @@ export class NTQQPacketApi { serverUrl: string | undefined; qqversion: string | undefined; isInit: boolean = false; + PacketClient: PacketClient | undefined; constructor(context: InstanceContext, core: NapCatCore) { this.context = context; this.core = core; - this.InitSendPacket('127.0.0.1:8086', '9.9.15-28418', '1001').then().catch(); + this.InitSendPacket('127.0.0.1:8086', '9.9.15-28418', '1001').then().catch(this.core.context.logger.logError.bind(this.core.context.logger)); } async InitSendPacket(serverUrl: string, qqversion: string, uin: string) { this.serverUrl = serverUrl; this.qqversion = qqversion; let offsetTable: OffsetType = offset; if (!offsetTable[qqversion]) return false; - let url = 'http://' + this.serverUrl + '/init'; - let postdata = { recv: offsetTable[qqversion].recv, send: offsetTable[qqversion].send, qqver: qqversion, uin: uin, pid: process.pid }; - try { - let ret = await RequestUtil.HttpGetJson(url, 'POST', postdata, { 'Content-Type': 'application/json' }, true, true); - if (ret.status !== 'ok') throw new Error('InitSendPacket failed' + JSON.stringify(ret, null, 2)); - } catch (error) { - let logger = this.core.context.logger; - logger.logError.bind(logger)('InitSendPacket', error); - return false; - } + let url = 'ws://' + this.serverUrl + '/ws'; + // let postdata = { recv: offsetTable[qqversion].recv, send: offsetTable[qqversion].send, qqver: qqversion, uin: uin, pid: process.pid }; + this.PacketClient = new PacketClient(url, this.core.context.logger); + await this.PacketClient.connect(); + await this.PacketClient.init(process.pid, offsetTable[qqversion].recv, offsetTable[qqversion].send); this.isInit = true; return this.isInit; } - async randText(len: number) { + randText(len: number) { let text = ''; let possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; for (let i = 0; i < len; i++) { @@ -48,25 +45,13 @@ export class NTQQPacketApi { } return text; } - async sendPacket(cmd: string, data: string, rep = false) { - return new Promise(async (resolve, reject) => { - //获取data的HASH + async sendPacket(cmd: string, data: string, rsp = false) { + return new Promise((resolve, reject) => { let md5 = crypto.createHash('md5').update(data).digest('hex'); - let url = 'http://' + this.serverUrl + '/send'; - let geturl = 'http://' + this.serverUrl + '/get'; - let trace_id = (await this.randText(4) + md5 + data).slice(0, data.length / 2); - let postdata = { data: data, trace_id: trace_id, cmd: cmd }; - - RequestUtil.HttpGetJson(url, 'POST', postdata, { 'Content-Type': 'application/json' }, true, true).then((res) => { - if (!rep) { - this.core.context.session.getMsgService().sendSsoCmdReqByContend(cmd, trace_id).then(e => resolve(res)).catch(e => reject(e)) - } else { - let getpostdata = { data: data, trace_id: trace_id, cmd: cmd }; - RequestUtil.HttpGetJson(geturl, 'POST', getpostdata, { 'Content-Type': 'application/json' }, true, true).then((rsp) => { - resolve(rsp) - }).catch((e) => reject(e)); - } - }).catch((e) => reject(e)); + let trace_id = (this.randText(4) + md5 + data).slice(0, data.length / 2); + this.PacketClient?.sendCommand(cmd, data, trace_id, rsp, 5000, async () => { + await this.core.context.session.getMsgService().sendSsoCmdReqByContend(cmd, trace_id); + }).then((res) => resolve(res)).catch((e) => reject(e)); }); } } \ No newline at end of file diff --git a/src/core/helper/packet.ts b/src/core/helper/packet.ts new file mode 100644 index 00000000..65c2e2b1 --- /dev/null +++ b/src/core/helper/packet.ts @@ -0,0 +1,115 @@ +import { LogWrapper } from "@/common/log"; +import { LRUCache } from "@/common/lru-cache"; +import WebSocket from "ws"; + +export class PacketClient { + private websocket: WebSocket | undefined; + private isConnected: boolean = false; + private reconnectAttempts: number = 0; + private maxReconnectAttempts: number = 5; + private cb = new LRUCache(500); + constructor(private url: string, public logger: LogWrapper) { } + + connect(): Promise { + 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.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); + }; + + this.websocket.onclose = () => { + this.isConnected = false; + this.logger.logWarn.bind(this.logger)(`Disconnected from ${this.url}`); + this.attemptReconnect(); + }; + }); + } + + private attemptReconnect(): void { + if (this.reconnectAttempts < this.maxReconnectAttempts) { + this.reconnectAttempts++; + this.logger.logError.bind(this.logger)(`Reconnecting attempt ${this.reconnectAttempts}`); + setTimeout(() => this.connect(), 1000 * this.reconnectAttempts); + } else { + this.logger.logError.bind(this.logger)(`Max reconnect attempts reached. Could not reconnect to ${this.url}`); + } + } + async registerCallback(trace_id: string, type: string, callback: any): Promise { + this.cb.put(trace_id, { type: type, callback: callback }); + } + + async init(pid: number, recv: string, send: string): Promise { + 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 { + return new Promise((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)); + this.registerCallback(trace_id, 'send', (json: any) => { + sendcb(json); + if (!rsp) { + clearTimeout(timeoutHandle); + resolve(json); + } else { + this.registerCallback(trace_id, 'recv', (json: any) => { + clearTimeout(timeoutHandle); + resolve(json); + }); + } + }); + const timeoutHandle = setTimeout(() => { + reject(new Error(`sendCommand timed out after ${timeout} ms`)); + }, timeout); + }); + } + private async handleMessage(message: any): Promise { + try { + + let json = JSON.parse(message.toString()); + let trace_id = json.trace_id; + let event = this.cb.get(trace_id); + if (event?.type == 'all' || event?.type == json.type) { + await event?.callback(json.data); + } + //console.log("Received message:", json); + } catch (error) { + this.logger.logError.bind(this.logger)(`Error parsing message: ${error}`); + } + } +} \ No newline at end of file