Compare commits

...

11 Commits

Author SHA1 Message Date
pk5ls20
e46d274a75
feat: add new NapCat config key: packetBackend
- Acceptable values: `native`, `frida`, `auto`, `disable`
- Default value is set to `auto`
2024-11-05 14:45:02 +08:00
pk5ls20
ad6f21980c
refactor: auto judge client 2024-11-05 14:24:54 +08:00
pk5ls20
017b8b7f15
chore: better log 2024-11-05 13:52:56 +08:00
pk5ls20
9b448b17e6
refactor: NapProto -> https://github.com/NapNeko/NapProto 2024-11-05 12:47:28 +08:00
手瓜一十雪
f9996a9987 fix: 日志乱飞版本 2024-11-05 11:24:36 +08:00
手瓜一十雪
000ef55273 fix: 一点小问题 2024-11-05 10:25:41 +08:00
手瓜一十雪
e1ac0f02b4 fix: 搞炸了 让我思考下 2024-11-05 10:22:52 +08:00
手瓜一十雪
b9297e3f1d fix 2024-11-05 10:18:11 +08:00
手瓜一十雪
34d0669ca8 fix 2024-11-05 10:16:06 +08:00
手瓜一十雪
25e42720cf fix: 开始初步调试 2024-11-05 10:14:00 +08:00
手瓜一十雪
f7c1951191 fix: 一些异常类型 2024-11-05 10:07:56 +08:00
42 changed files with 145 additions and 283 deletions

View File

@ -13,6 +13,7 @@
"devDependencies": { "devDependencies": {
"@babel/preset-typescript": "^7.24.7", "@babel/preset-typescript": "^7.24.7",
"@log4js-node/log4js-api": "^1.0.2", "@log4js-node/log4js-api": "^1.0.2",
"@napneko/nap-proto-core": "^0.0.2",
"@protobuf-ts/runtime": "^2.9.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",

View File

@ -1,6 +1,6 @@
export class LRUCache<K, V> { export class LRUCache<K, V> {
private capacity: number; private capacity: number;
private cache: Map<K, V>; public cache: Map<K, V>;
constructor(capacity: number) { constructor(capacity: number) {
this.capacity = capacity; this.capacity = capacity;

View File

@ -4,7 +4,7 @@ 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 { PacketSession } from "@/core/packet/session";
import { OidbPacket, PacketHexStr } from "@/core/packet/packer"; import { OidbPacket, PacketHexStr } from "@/core/packet/packer";
import { NapProtoEncodeStructType, NapProtoDecodeStructType, NapProtoMsg } from '@/core/packet/proto/NapProto'; import { NapProtoMsg, NapProtoEncodeStructType, NapProtoDecodeStructType } from "@napneko/nap-proto-core";
import { OidbSvcTrpcTcp0X9067_202_Rsp_Body } from '@/core/packet/proto/oidb/Oidb.0x9067_202'; import { OidbSvcTrpcTcp0X9067_202_Rsp_Body } from '@/core/packet/proto/oidb/Oidb.0x9067_202';
import { OidbSvcTrpcTcpBase, OidbSvcTrpcTcpBaseRsp } from '@/core/packet/proto/oidb/OidbBase'; import { OidbSvcTrpcTcpBase, OidbSvcTrpcTcpBaseRsp } from '@/core/packet/proto/oidb/OidbBase';
import { OidbSvcTrpcTcp0XFE1_2RSP } from '@/core/packet/proto/oidb/Oidb.0XFE1_2'; import { OidbSvcTrpcTcp0XFE1_2RSP } from '@/core/packet/proto/oidb/Oidb.0XFE1_2';
@ -48,14 +48,9 @@ export class NTQQPacketApi {
this.core = core; this.core = core;
this.logger = core.context.logger; this.logger = core.context.logger;
this.packetSession = undefined; this.packetSession = undefined;
const config = this.core.configLoader.configData;
if (config) {
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));
} else {
this.core.context.logger.logWarn('PacketServer未配置NapCat.Packet将不会加载');
}
} }
get available(): boolean { get available(): boolean {
@ -66,7 +61,11 @@ export class NTQQPacketApi {
this.qqVersion = qqversion; this.qqVersion = qqversion;
const table = typedOffset[qqversion + '-' + os.arch()]; const table = typedOffset[qqversion + '-' + os.arch()];
if (!table) { if (!table) {
this.logger.logError('PacketServer Offset table not found for QQVersion: ', qqversion + '-' + os.arch()); this.logger.logError('[Core] [Packet] PacketServer Offset table not found for QQVersion: ', qqversion + '-' + os.arch());
return false;
}
if (this.core.configLoader.configData.packetBackend === 'disable') {
this.logger.logWarn('[Core] [Packet] 已禁用Packet后端NapCat.Packet将不会加载');
return false; return false;
} }
this.packetSession = new PacketSession(this.core); this.packetSession = new PacketSession(this.core);

View File

@ -3,5 +3,6 @@
"consoleLog": true, "consoleLog": true,
"fileLogLevel": "debug", "fileLogLevel": "debug",
"consoleLogLevel": "info", "consoleLogLevel": "info",
"packetBackend": "auto",
"packetServer": "" "packetServer": ""
} }

View File

@ -30,10 +30,6 @@ export abstract class PacketClient {
this.config = core.configLoader.configData; this.config = core.configLoader.configData;
} }
get available(): boolean {
return this.isAvailable;
}
private randText(len: number): string { private randText(len: number): string {
let text = ''; let text = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
@ -43,13 +39,11 @@ export abstract class PacketClient {
return text; return text;
} }
static create(core: NapCatCore): PacketClient { get available(): boolean {
throw new Error("Must be implemented by subclasses"); return this.isAvailable;
} }
static compatibilityScore(logger: LogWrapper): number { abstract check(core: NapCatCore): boolean;
throw new Error("Must be implemented by subclasses");
}
abstract init(pid: number, recv: string, send: string): Promise<void>; abstract init(pid: number, recv: string, send: string): Promise<void>;
@ -59,15 +53,12 @@ export abstract class PacketClient {
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> {
this.cb.put(createHash('md5').update(trace_id).digest('hex') + type, callback); this.cb.put(createHash('md5').update(trace_id).digest('hex') + type, callback);
console.log(this.cb.cache);
} }
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.isAvailable) {
throw new Error("WebSocket is not connected");
}
this.sendCommandImpl(cmd, data, trace_id);
if (rsp) { if (rsp) {
this.registerCallback(trace_id, 'recv', async (json: RecvPacketData) => { this.registerCallback(trace_id, 'recv', async (json: RecvPacketData) => {
clearTimeout(timeoutHandle); clearTimeout(timeoutHandle);
@ -81,6 +72,7 @@ export abstract class PacketClient {
resolve(json); resolve(json);
} }
}); });
this.sendCommandImpl(cmd, data, trace_id);
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);
@ -98,6 +90,7 @@ export abstract class PacketClient {
const trace_id = (this.randText(4) + md5 + data).slice(0, data.length / 2); const trace_id = (this.randText(4) + md5 + data).slice(0, data.length / 2);
this.sendCommand(cmd, data, trace_id, rsp, 20000, async () => { 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); await this.napCatCore.context.session.getMsgService().sendSsoCmdReqByContend(cmd, trace_id);
}).then((res) => resolve(res)).catch((e: Error) => reject(e)); }).then((res) => resolve(res)).catch((e: Error) => reject(e));
}); });

View File

@ -3,16 +3,19 @@ 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 { console } from "inspector";
import { PacketClient } from "@/core/packet/client/client"; import { PacketClient } from "@/core/packet/client/client";
import { constants, platform, type } from "node:os"; import { constants } from "node:os";
import { LogWrapper } from "@/common/log"; import { LRUCache } from "@/common/lru-cache";
//0 send 1recv
export interface NativePacketExportType {
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;
}
export class NativePacketClient extends PacketClient { export class NativePacketClient extends PacketClient {
static supportedPlatforms = ['win32.x64']; private readonly supportedPlatforms = ['win32.x64'];
private MoeHooExport: any = { exports: {} }; private MoeHooExport: { exports: NativePacketExportType } = { exports: {} };
private sendEvent = new LRUCache<number, string>(500);//seq->trace_id
protected constructor(core: NapCatCore) { constructor(core: NapCatCore) {
super(core); super(core);
} }
@ -20,44 +23,56 @@ export class NativePacketClient extends PacketClient {
return this.isAvailable; return this.isAvailable;
} }
static compatibilityScore(logger: LogWrapper): number { check(): boolean {
const platform = process.platform + '.' + process.arch; const platform = process.platform + '.' + process.arch;
if (!this.supportedPlatforms.includes(platform)) { if (!this.supportedPlatforms.includes(platform)) {
logger.logError(`[NativePacketClient] Unsupported platform: ${platform}`); this.logger.logWarn(`[Core] [Packet:Native] 不支持的平台: ${platform}`);
return 0; 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)) {
logger.logError(`[NativePacketClient] Missing moehoo binary: ${moehoo_path}`); this.logger.logWarn(`[Core] [Packet:Native] 缺失运行时文件: ${moehoo_path}`);
return 0; return false;
} }
return 10; return true;
}
static create(core: NapCatCore): NativePacketClient {
return new NativePacketClient(core);
} }
async init(pid: number, recv: string, send: string): Promise<void> { async init(pid: number, recv: string, send: string): Promise<void> {
const platform = process.platform + '.' + process.arch; const platform = process.platform + '.' + process.arch;
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');
process.dlopen(this.MoeHooExport, moehoo_path, constants.dlopen.RTLD_LAZY); process.dlopen(this.MoeHooExport, moehoo_path, constants.dlopen.RTLD_LAZY);
this.MoeHooExport.exports.InitHook(pid, recv, send, (type: number, uin: string, seq: number, cmd: string, hex_data: string) => { console.log('MoeHooExport:', this.MoeHooExport);
const callback = this.cb.get(createHash('md5').update(Buffer.from(hex_data, 'hex')).digest('hex') + (type === 0 ? 'send' : 'recv')); console.log('recv:', recv, 'send:',);
if (callback) { this.MoeHooExport.exports.InitHook?.(send, recv, (type: number, uin: string, cmd: string, seq: number, hex_data: string) => {
callback({ seq, cmd, hex_data }); const trace_id = createHash('md5').update(Buffer.from(hex_data, 'hex')).digest('hex');
} else { if (type === 0 && this.cb.get(trace_id + 'recv')) {
this.logger.logError(`Callback not found for hex_data: ${hex_data}`); //此时为send 提取seq
this.sendEvent.put(seq, trace_id);
} }
// TODO: cannot use console.log here, fxxk tx if (type === 1 && this.sendEvent.get(seq)) {
// Error [ERR_INSPECTOR_NOT_AVAILABLE]: Inspector is not available //此时为recv 调用callback
// console.log('type:', type, 'uin:', uin, 'seq:', seq, 'cmd:', cmd, 'hex_data:', hex_data); const trace_id = this.sendEvent.get(seq);
const callback = this.cb.get(trace_id + 'recv');
console.log('callback:', callback, trace_id);
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.isAvailable = true;
} }
sendCommandImpl(cmd: string, data: string, trace_id: string): void { sendCommandImpl(cmd: string, data: string, trace_id: string): void {
this.MoeHooExport.exports.SendPacket(cmd, data, crypto.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.cb.get(trace_id_md5 + 'send')?.({ seq: 0, cmd, hex_data: '' });
} }
connect(cb: () => void): Promise<void> { connect(cb: () => void): Promise<void> {

View File

@ -1,38 +1,37 @@
import { Data, WebSocket } from "ws"; import { Data, WebSocket } from "ws";
import { NapCatCore } from "@/core"; import { NapCatCore } from "@/core";
import { PacketClient, RecvPacket } from "@/core/packet/client/client"; import { PacketClient, RecvPacket } from "@/core/packet/client/client";
import { LogWrapper } from "@/common/log";
export class wsPacketClient extends PacketClient { export class wsPacketClient extends PacketClient {
private websocket: WebSocket | undefined; private websocket: WebSocket | undefined;
private reconnectAttempts: number = 0; private reconnectAttempts: number = 0;
private readonly maxReconnectAttempts: number = 60; // 现在暂时不可配置 private readonly maxReconnectAttempts: number = 60; // 现在暂时不可配置
private readonly clientUrl: string = ''; private readonly clientUrl: string | null = null;
private clientUrlWrap: (url: string) => string = (url: string) => `ws://${url}/ws`; private clientUrlWrap: (url: string) => string = (url: string) => `ws://${url}/ws`;
protected constructor(core: NapCatCore) { constructor(core: NapCatCore) {
super(core); super(core);
this.clientUrl = this.clientUrlWrap(this.config.packetServer ?? '127.0.0.1:8086'); this.clientUrl = this.config.packetServer ? this.clientUrlWrap( this.config.packetServer) : null;
} }
static compatibilityScore(logger: LogWrapper): number { check(): boolean {
return 10; if (!this.clientUrl) {
this.logger.logWarn(`[Core] [Packet:Server] 未配置服务器地址`);
return false;
} }
return true;
static create(core: NapCatCore): wsPacketClient {
return new wsPacketClient(core);
} }
connect(cb: () => void): Promise<void> { connect(cb: () => void): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
//this.logger.log.bind(this.logger)(`[Core] [Packet Server] Attempting to connect to ${this.clientUrl}`); //this.logger.log.bind(this.logger)(`[Core] [Packet Server] Attempting to connect to ${this.clientUrl}`);
this.websocket = new WebSocket(this.clientUrl); this.websocket = new WebSocket(this.clientUrl!);
this.websocket.on('error', (err) => { }/*this.logger.logError.bind(this.logger)('[Core] [Packet Server] Error:', err.message)*/); this.websocket.on('error', (err) => { }/*this.logger.logError.bind(this.logger)('[Core] [Packet Server] Error:', err.message)*/);
this.websocket.onopen = () => { this.websocket.onopen = () => {
this.isAvailable = true; this.isAvailable = true;
this.reconnectAttempts = 0; this.reconnectAttempts = 0;
this.logger.log.bind(this.logger)(`[Core] [Packet Server] 已连接到 ${this.clientUrl}`); this.logger.log.bind(this.logger)(`[Core] [Packet:Server] 已连接到 ${this.clientUrl}`);
cb(); cb();
resolve(); resolve();
}; };
@ -62,14 +61,14 @@ export class wsPacketClient extends PacketClient {
this.reconnectAttempts++; this.reconnectAttempts++;
setTimeout(() => { setTimeout(() => {
this.connect(cb).catch((error) => { this.connect(cb).catch((error) => {
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] 尝试重连失败:${error.message}`); this.logger.logError.bind(this.logger)(`[Core] [Packet:Server] 尝试重连失败:${error.message}`);
}); });
}, 5000 * this.reconnectAttempts); }, 5000 * this.reconnectAttempts);
} else { } else {
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] ${this.clientUrl} 已达到最大重连次数!`); this.logger.logError.bind(this.logger)(`[Core] [Packet:Server] ${this.clientUrl} 已达到最大重连次数!`);
} }
} catch (error: any) { } catch (error: any) {
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] 重连时出错: ${error.message}`); this.logger.logError.bind(this.logger)(`[Core] [Packet:Server] 重连时出错: ${error.message}`);
} }
} }

View File

@ -2,7 +2,7 @@ import * as fs from "node:fs";
import { ChatType, Peer } from "@/core"; import { ChatType, Peer } from "@/core";
import { LogWrapper } from "@/common/log"; import { LogWrapper } from "@/common/log";
import { PacketPacker } from "@/core/packet/packer"; import { PacketPacker } from "@/core/packet/packer";
import { NapProtoMsg } from "@/core/packet/proto/NapProto"; import { NapProtoMsg } from "@napneko/nap-proto-core";
import { HttpConn0x6ff_501Response } from "@/core/packet/proto/action/action"; import { HttpConn0x6ff_501Response } from "@/core/packet/proto/action/action";
import { PacketHighwayClient } from "@/core/packet/highway/client"; import { PacketHighwayClient } from "@/core/packet/highway/client";
import { NTV2RichMediaResp } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaResp"; import { NTV2RichMediaResp } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaResp";
@ -59,7 +59,7 @@ export class PacketHighwaySession {
private async checkAvailable() { private async checkAvailable() {
if (!this.packetClient.available) { if (!this.packetClient.available) {
throw new Error('packetServer不可用请参照文档 https://napneko.github.io/config/advanced 检查packetServer状态或进行配置'); 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!'); this.logger.logWarn('[Highway] sigSession or sessionKey not available!');

View File

@ -4,7 +4,7 @@ import * as http from "node:http";
import * as stream from "node:stream"; import * as stream from "node:stream";
import { LogWrapper } from "@/common/log"; import { LogWrapper } from "@/common/log";
import * as tea from "@/core/packet/utils/crypto/tea"; import * as tea from "@/core/packet/utils/crypto/tea";
import { NapProtoMsg } from "@/core/packet/proto/NapProto"; import { NapProtoMsg } from "@napneko/nap-proto-core";
import { ReqDataHighwayHead, RespDataHighwayHead } from "@/core/packet/proto/highway/highway"; import { ReqDataHighwayHead, RespDataHighwayHead } from "@/core/packet/proto/highway/highway";
import { BlockSize } from "@/core/packet/highway/session"; import { BlockSize } from "@/core/packet/highway/session";
import { PacketHighwayTrans } from "@/core/packet/highway/client"; import { PacketHighwayTrans } from "@/core/packet/highway/client";

View File

@ -1,4 +1,4 @@
import { NapProtoEncodeStructType } from "@/core/packet/proto/NapProto"; import { NapProtoEncodeStructType } from "@napneko/nap-proto-core";
import { IPv4 } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaResp"; import { IPv4 } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaResp";
import { NTHighwayIPv4 } from "@/core/packet/proto/highway/highway"; import { NTHighwayIPv4 } from "@/core/packet/proto/highway/highway";

View File

@ -1,6 +1,6 @@
import * as crypto from "crypto"; import * as crypto from "crypto";
import { PushMsgBody } from "@/core/packet/proto/message/message"; import { PushMsgBody } from "@/core/packet/proto/message/message";
import { NapProtoEncodeStructType } from "@/core/packet/proto/NapProto"; import { NapProtoEncodeStructType } from "@napneko/nap-proto-core";
import { LogWrapper } from "@/common/log"; 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";

View File

@ -1,5 +1,5 @@
import * as zlib from "node:zlib"; import * as zlib from "node:zlib";
import { NapProtoEncodeStructType, NapProtoMsg } from "@/core/packet/proto/NapProto"; import { NapProtoEncodeStructType, NapProtoMsg } from "@napneko/nap-proto-core";
import { import {
CustomFace, CustomFace,
Elem, Elem,

View File

@ -1,7 +1,7 @@
import * as zlib from "node:zlib"; import * as zlib from "node:zlib";
import * as crypto from "node:crypto"; import * as crypto from "node:crypto";
import { computeMd5AndLengthWithLimit } from "@/core/packet/utils/crypto/hash"; import { computeMd5AndLengthWithLimit } from "@/core/packet/utils/crypto/hash";
import { NapProtoEncodeStructType, NapProtoMsg } from "@/core/packet/proto/NapProto"; import { NapProtoEncodeStructType, NapProtoMsg } from "@napneko/nap-proto-core";
import { OidbSvcTrpcTcpBase } from "@/core/packet/proto/oidb/OidbBase"; import { OidbSvcTrpcTcpBase } from "@/core/packet/proto/oidb/OidbBase";
import { OidbSvcTrpcTcp0X9067_202 } from "@/core/packet/proto/oidb/Oidb.0x9067_202"; 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 { OidbSvcTrpcTcp0X8FC_2, OidbSvcTrpcTcp0X8FC_2_Body } from "@/core/packet/proto/oidb/Oidb.0x8FC_2";

View File

@ -1,161 +0,0 @@
import { MessageType, PartialMessage, RepeatType, ScalarType } from '@protobuf-ts/runtime';
import { PartialFieldInfo } from "@protobuf-ts/runtime/build/types/reflection-info";
type LowerCamelCase<S extends string> = CamelCaseHelper<S, false, true>;
type CamelCaseHelper<
S extends string,
CapNext extends boolean,
IsFirstChar extends boolean
> = S extends `${infer F}${infer R}`
? F extends '_'
? CamelCaseHelper<R, true, false>
: F extends `${number}`
? `${F}${CamelCaseHelper<R, true, false>}`
: CapNext extends true
? `${Uppercase<F>}${CamelCaseHelper<R, false, false>}`
: IsFirstChar extends true
? `${Lowercase<F>}${CamelCaseHelper<R, false, false>}`
: `${F}${CamelCaseHelper<R, false, false>}`
: '';
type ScalarTypeToTsType<T extends ScalarType> =
T extends ScalarType.DOUBLE | ScalarType.FLOAT | ScalarType.INT32 | ScalarType.FIXED32 | ScalarType.UINT32 | ScalarType.SFIXED32 | ScalarType.SINT32 ? number :
T extends ScalarType.INT64 | ScalarType.UINT64 | ScalarType.FIXED64 | ScalarType.SFIXED64 | ScalarType.SINT64 ? bigint :
T extends ScalarType.BOOL ? boolean :
T extends ScalarType.STRING ? string :
T extends ScalarType.BYTES ? Uint8Array :
never;
interface BaseProtoFieldType<T, O extends boolean, R extends O extends true ? false : boolean> {
kind: 'scalar' | 'message';
no: number;
type: T;
optional: O;
repeat: R;
}
interface ScalarProtoFieldType<T extends ScalarType, O extends boolean, R extends O extends true ? false : boolean> extends BaseProtoFieldType<T, O, R> {
kind: 'scalar';
}
interface MessageProtoFieldType<T extends () => ProtoMessageType, O extends boolean, R extends O extends true ? false : boolean> extends BaseProtoFieldType<T, O, R> {
kind: 'message';
}
type ProtoFieldType =
| ScalarProtoFieldType<ScalarType, boolean, boolean>
| MessageProtoFieldType<() => ProtoMessageType, boolean, boolean>;
type ProtoMessageType = {
[key: string]: ProtoFieldType;
};
export function ProtoField<T extends ScalarType, O extends boolean = false, R extends O extends true ? false : boolean = false>(no: number, type: T, optional?: O, repeat?: R): ScalarProtoFieldType<T, O, R>;
export function ProtoField<T extends () => ProtoMessageType, O extends boolean = false, R extends O extends true ? false : boolean = false>(no: number, type: T, optional?: O, repeat?: R): MessageProtoFieldType<T, O, R>;
export function ProtoField(no: number, type: ScalarType | (() => ProtoMessageType), optional?: boolean, repeat?: boolean): ProtoFieldType {
if (typeof type === 'function') {
return { kind: 'message', no: no, type: type, optional: optional ?? false, repeat: repeat ?? false };
} else {
return { kind: 'scalar', no: no, type: type, optional: optional ?? false, repeat: repeat ?? false };
}
}
type ProtoFieldReturnType<T, E extends boolean> = NonNullable<T> extends ScalarProtoFieldType<infer S, infer O, infer R>
? ScalarTypeToTsType<S>
: T extends NonNullable<MessageProtoFieldType<infer S, infer O, infer R>>
? NonNullable<NapProtoStructType<ReturnType<S>, E>>
: never;
type RequiredFieldsBaseType<T, E extends boolean> = {
[K in keyof T as T[K] extends { optional: true } ? never : LowerCamelCase<K & string>]:
T[K] extends { repeat: true }
? ProtoFieldReturnType<T[K], E>[]
: ProtoFieldReturnType<T[K], E>
}
type OptionalFieldsBaseType<T, E extends boolean> = {
[K in keyof T as T[K] extends { optional: true } ? LowerCamelCase<K & string> : never]?:
T[K] extends { repeat: true }
? ProtoFieldReturnType<T[K], E>[]
: ProtoFieldReturnType<T[K], E>
}
type RequiredFieldsType<T, E extends boolean> = E extends true ? Partial<RequiredFieldsBaseType<T, E>> : RequiredFieldsBaseType<T, E>;
type OptionalFieldsType<T, E extends boolean> = E extends true ? Partial<OptionalFieldsBaseType<T, E>> : OptionalFieldsBaseType<T, E>;
type NapProtoStructType<T, E extends boolean> = RequiredFieldsType<T, E> & OptionalFieldsType<T, E>;
export type NapProtoEncodeStructType<T> = NapProtoStructType<T, true>;
export type NapProtoDecodeStructType<T> = NapProtoStructType<T, false>;
class NapProtoRealMsg<T extends ProtoMessageType> {
private readonly _field: PartialFieldInfo[];
private readonly _proto_msg: MessageType<NapProtoStructType<T, boolean>>;
private static cache = new WeakMap<ProtoMessageType, NapProtoRealMsg<any>>();
private constructor(fields: T) {
this._field = Object.keys(fields).map(key => {
const field = fields[key];
if (field.kind === 'scalar') {
const repeatType = field.repeat
? [ScalarType.STRING, ScalarType.BYTES].includes(field.type)
? RepeatType.UNPACKED
: RepeatType.PACKED
: RepeatType.NO;
return {
no: field.no,
name: key,
kind: 'scalar',
T: field.type,
opt: field.optional,
repeat: repeatType,
};
} else if (field.kind === 'message') {
return {
no: field.no,
name: key,
kind: 'message',
repeat: field.repeat ? RepeatType.PACKED : RepeatType.NO,
T: () => NapProtoRealMsg.getInstance(field.type())._proto_msg,
};
}
}) as PartialFieldInfo[];
this._proto_msg = new MessageType<NapProtoStructType<T, boolean>>('nya', this._field);
}
static getInstance<T extends ProtoMessageType>(fields: T): NapProtoRealMsg<T> {
let instance = this.cache.get(fields);
if (!instance) {
instance = new NapProtoRealMsg(fields);
this.cache.set(fields, instance);
}
return instance;
}
encode(data: NapProtoEncodeStructType<T>): Uint8Array {
return this._proto_msg.toBinary(this._proto_msg.create(data as PartialMessage<NapProtoEncodeStructType<T>>));
}
decode(data: Uint8Array): NapProtoDecodeStructType<T> {
return this._proto_msg.fromBinary(data) as NapProtoDecodeStructType<T>;
}
}
export class NapProtoMsg<T extends ProtoMessageType> {
private realMsg: NapProtoRealMsg<T>;
constructor(fields: T) {
this.realMsg = NapProtoRealMsg.getInstance(fields);
}
encode(data: NapProtoEncodeStructType<T>): Uint8Array {
return this.realMsg.encode(data);
}
decode(data: Uint8Array): NapProtoDecodeStructType<T> {
return this.realMsg.decode(data);
}
}

View File

@ -1,5 +1,5 @@
import { ScalarType } from "@protobuf-ts/runtime"; import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto"; 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/proto/message/message";
export const FaceRoamRequest = { export const FaceRoamRequest = {

View File

@ -1,5 +1,5 @@
import { ScalarType } from "@protobuf-ts/runtime"; import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto"; import { ProtoField } from "@napneko/nap-proto-core";
export const MiniAppAdaptShareInfoReq = { export const MiniAppAdaptShareInfoReq = {
appId: ProtoField(2, ScalarType.STRING), appId: ProtoField(2, ScalarType.STRING),

View File

@ -1,5 +1,5 @@
import { ScalarType } from "@protobuf-ts/runtime"; import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto"; import { ProtoField } from "@napneko/nap-proto-core";
import { MsgInfo, MsgInfoBody } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq"; import { MsgInfo, MsgInfoBody } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
export const DataHighwayHead = { export const DataHighwayHead = {

View File

@ -1,5 +1,5 @@
import { ScalarType } from "@protobuf-ts/runtime"; import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto"; import { ProtoField } from "@napneko/nap-proto-core";
import { PushMsgBody } from "@/core/packet/proto/message/message"; import { PushMsgBody } from "@/core/packet/proto/message/message";
export const LongMsgResult = { export const LongMsgResult = {

View File

@ -1,5 +1,5 @@
import { ScalarType } from "@protobuf-ts/runtime"; import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto"; 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),

View File

@ -1,5 +1,5 @@
import { ScalarType } from "@protobuf-ts/runtime"; import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto"; import { ProtoField } from "@napneko/nap-proto-core";
import { Elem } from "@/core/packet/proto/message/element"; import { Elem } from "@/core/packet/proto/message/element";
export const Attr = { export const Attr = {

View File

@ -1,5 +1,5 @@
import { ScalarType } from "@protobuf-ts/runtime"; import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto"; import { ProtoField } from "@napneko/nap-proto-core";
export const Elem = { export const Elem = {
text: ProtoField(1, () => Text, true), text: ProtoField(1, () => Text, true),

View File

@ -1,5 +1,5 @@
import { ScalarType } from "@protobuf-ts/runtime"; import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto"; import { ProtoField } from "@napneko/nap-proto-core";
export const GroupRecallMsg = { export const GroupRecallMsg = {
type: ProtoField(1, ScalarType.UINT32), type: ProtoField(1, ScalarType.UINT32),

View File

@ -1,5 +1,5 @@
import { ScalarType } from "@protobuf-ts/runtime"; import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto"; import { ProtoField } from "@napneko/nap-proto-core";
import { ForwardHead, Grp, GrpTmp, ResponseForward, ResponseGrp, Trans0X211, WPATmp } from "@/core/packet/proto/message/routing"; import { ForwardHead, Grp, GrpTmp, ResponseForward, ResponseGrp, Trans0X211, WPATmp } from "@/core/packet/proto/message/routing";
import { RichText } from "@/core/packet/proto/message/component"; import { RichText } from "@/core/packet/proto/message/component";
import { C2C } from "@/core/packet/proto/message/c2c"; import { C2C } from "@/core/packet/proto/message/c2c";

View File

@ -1,5 +1,5 @@
import { ScalarType } from "@protobuf-ts/runtime"; import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto"; import { ProtoField } from "@napneko/nap-proto-core";
export const FriendRecall = { export const FriendRecall = {
info: ProtoField(1, () => FriendRecallInfo), info: ProtoField(1, () => FriendRecallInfo),

View File

@ -1,5 +1,5 @@
import { ScalarType } from "@protobuf-ts/runtime"; import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto"; 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),

View File

@ -1,5 +1,5 @@
import { ScalarType } from "@protobuf-ts/runtime"; import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto"; import { ProtoField } from "@napneko/nap-proto-core";
import { OidbSvcTrpcTcp0XE37_800_1200Metadata } from "@/core/packet/proto/oidb/Oidb.0xE37_1200"; import { OidbSvcTrpcTcp0XE37_800_1200Metadata } from "@/core/packet/proto/oidb/Oidb.0xE37_1200";
export const OidbSvcTrpcTcp0XE37_800 = { export const OidbSvcTrpcTcp0XE37_800 = {

View File

@ -1,5 +1,5 @@
import { ScalarType } from "@protobuf-ts/runtime"; import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto"; 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),

View File

@ -1,5 +1,5 @@
import { ScalarType } from "@protobuf-ts/runtime"; import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto"; import { ProtoField } from "@napneko/nap-proto-core";
export const OidbSvcTrpcTcp0x6D6 = { export const OidbSvcTrpcTcp0x6D6 = {
file: ProtoField(1, () => OidbSvcTrpcTcp0x6D6Upload, true), file: ProtoField(1, () => OidbSvcTrpcTcp0x6D6Upload, true),

View File

@ -1,5 +1,5 @@
import { ScalarType } from "@protobuf-ts/runtime"; import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto"; import { ProtoField } from "@napneko/nap-proto-core";
//设置群头衔 OidbSvcTrpcTcp.0x8fc_2 //设置群头衔 OidbSvcTrpcTcp.0x8fc_2

View File

@ -1,5 +1,5 @@
import { ScalarType } from "@protobuf-ts/runtime"; import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto"; import { ProtoField } from "@napneko/nap-proto-core";
import { MultiMediaReqHead } from "./common/Ntv2.RichMediaReq"; import { MultiMediaReqHead } from "./common/Ntv2.RichMediaReq";
//Req //Req

View File

@ -1,5 +1,5 @@
import { ScalarType } from "@protobuf-ts/runtime"; import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto"; import { ProtoField } from "@napneko/nap-proto-core";
import { MsgInfo } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq"; import { MsgInfo } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
export const OidbSvcTrpcTcp0X929D_0 = { export const OidbSvcTrpcTcp0X929D_0 = {

View File

@ -1,5 +1,5 @@
import { ScalarType } from "@protobuf-ts/runtime"; import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto"; 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),

View File

@ -1,5 +1,5 @@
import { ScalarType } from "@protobuf-ts/runtime"; import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto"; 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),

View File

@ -1,5 +1,5 @@
import { ScalarType } from "@protobuf-ts/runtime"; import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto"; 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),

View File

@ -1,5 +1,5 @@
import { ScalarType } from "@protobuf-ts/runtime"; import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto"; import { ProtoField } from "@napneko/nap-proto-core";
// Send Poke // Send Poke
export const OidbSvcTrpcTcp0XED3_1 = { export const OidbSvcTrpcTcp0XED3_1 = {

View File

@ -1,5 +1,5 @@
import { ScalarType } from "@protobuf-ts/runtime"; import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto"; import { ProtoField } from "@napneko/nap-proto-core";
export const OidbSvcTrpcTcpBase = { export const OidbSvcTrpcTcpBase = {
command: ProtoField(1, ScalarType.UINT32), command: ProtoField(1, ScalarType.UINT32),

View File

@ -1,5 +1,5 @@
import { ScalarType } from "@protobuf-ts/runtime"; import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../../NapProto"; import { ProtoField } from "@napneko/nap-proto-core";
export const NTV2RichMediaReq = { export const NTV2RichMediaReq = {
ReqHead: ProtoField(1, () => MultiMediaReqHead), ReqHead: ProtoField(1, () => MultiMediaReqHead),

View File

@ -1,5 +1,5 @@
import { ScalarType } from "@protobuf-ts/runtime"; import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../../NapProto"; import { ProtoField } from "@napneko/nap-proto-core";
import { CommonHead, MsgInfo, PicUrlExtInfo, VideoExtInfo } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq"; import { CommonHead, MsgInfo, PicUrlExtInfo, VideoExtInfo } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
export const NTV2RichMediaResp = { export const NTV2RichMediaResp = {

View File

@ -7,12 +7,12 @@ import { wsPacketClient } from "@/core/packet/client/wsClient";
import { NapCatCore } from "@/core"; import { NapCatCore } from "@/core";
type clientPriority = { type clientPriority = {
[key: number]: typeof PacketClient; [key: number]: (core: NapCatCore) => PacketClient;
} }
const clientPriority: clientPriority = { const clientPriority: clientPriority = {
10: NativePacketClient, 10: (core: NapCatCore) => new NativePacketClient(core),
1: wsPacketClient, 1: (core: NapCatCore) => new wsPacketClient(core),
}; };
export class PacketSession { export class PacketSession {
@ -23,27 +23,40 @@ export class PacketSession {
constructor(core: NapCatCore) { constructor(core: NapCatCore) {
this.logger = core.context.logger; this.logger = core.context.logger;
this.client = this.judgeClient(core); this.client = this.newClient(core);
this.packer = new PacketPacker(this.logger, this.client); this.packer = new PacketPacker(this.logger, this.client);
this.highwaySession = new PacketHighwaySession(this.logger, this.client, this.packer); this.highwaySession = new PacketHighwaySession(this.logger, this.client, this.packer);
} }
private newClient(core: NapCatCore): PacketClient {
const prefer = core.configLoader.configData.packetBackend;
switch (prefer) {
case "native":
return new NativePacketClient(core);
case "frida":
return new wsPacketClient(core);
case "auto":
case undefined:
return this.judgeClient(core);
default:
throw new Error(`[Core] [Packet] 未知的Packet后端类型 ${prefer},请检查配置文件!`);
}
}
private judgeClient(core: NapCatCore): PacketClient { private judgeClient(core: NapCatCore): PacketClient {
let selectedClient: typeof PacketClient | null = null; const sortedClients = Object.entries(clientPriority)
let maxScore = -1; .map(([priority, clientFactory]) => {
for (const key in clientPriority) { const client = clientFactory(core);
const priority = parseInt(key); const score = +priority * +client.check(core);
const ClientClass = clientPriority[priority]; return { client, score };
const score = priority * ClientClass.compatibilityScore(core.context.logger); })
if (score > maxScore) { .filter(({ score }) => score > 0)
maxScore = score; .sort((a, b) => b.score - a.score);
selectedClient = ClientClass; const selectedClient = sortedClients[0]?.client;
}
}
if (!selectedClient) { if (!selectedClient) {
throw new Error("No compatible PacketClient found"); throw new Error("[Core] [Packet] 无可用的后端NapCat.Packet将不会加载");
} }
this.logger.log(`[Packet] 自动选择了: ${selectedClient.name}`); this.logger.log(`[Core] [Packet] 自动选择 ${selectedClient.constructor.name} 作为后端`);
return selectedClient.create(core); return selectedClient;
} }
} }

Binary file not shown.

View File

@ -9,7 +9,7 @@ export abstract class GetPacketStatusDepends<PT, RT> extends BaseAction<PT, RT>
if (!this.core.apis.PacketApi.available) { if (!this.core.apis.PacketApi.available) {
return { return {
valid: false, valid: false,
message: "packetServer不可用请参照文档 https://napneko.github.io/config/advanced 检查packetServer状态或进行配置!", message: "packetBackend不可用请参照文档 https://napneko.github.io/config/advanced 和启动日志检查packetBackend状态或进行配置!",
}; };
} }
return await super.check(payload); return await super.check(payload);

View File

@ -26,6 +26,7 @@ const FrameworkBaseConfigPlugin: PluginOption[] = [
targets: [ targets: [
{ src: './manifest.json', dest: 'dist' }, { src: './manifest.json', dest: 'dist' },
{ src: './src/core/external/napcat.json', dest: 'dist/config/' }, { src: './src/core/external/napcat.json', dest: 'dist/config/' },
{ src: './src/native/packet', dest: 'dist/moehoo', flatten: false },
{ src: './static/', dest: 'dist/static/', flatten: false }, { src: './static/', dest: 'dist/static/', flatten: false },
{ src: './src/onebot/config/onebot11.json', dest: 'dist/config/' }, { src: './src/onebot/config/onebot11.json', dest: 'dist/config/' },
{ src: './src/framework/liteloader.cjs', dest: 'dist' }, { src: './src/framework/liteloader.cjs', dest: 'dist' },
@ -42,6 +43,7 @@ const ShellBaseConfigPlugin: PluginOption[] = [
cp({ cp({
targets: [ targets: [
{ src: './src/native/external', dest: 'dist/native', flatten: false }, { src: './src/native/external', dest: 'dist/native', flatten: false },
{ src: './src/native/packet', dest: 'dist/moehoo', flatten: false },
{ src: './static/', dest: 'dist/static/', flatten: false }, { src: './static/', dest: 'dist/static/', flatten: false },
{ src: './src/core/external/napcat.json', dest: 'dist/config/' }, { src: './src/core/external/napcat.json', dest: 'dist/config/' },
{ src: './src/onebot/config/onebot11.json', dest: 'dist/config/' }, { src: './src/onebot/config/onebot11.json', dest: 'dist/config/' },