mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2024-11-21 09:36:35 +00:00
Compare commits
11 Commits
479b971b0c
...
e46d274a75
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e46d274a75 | ||
![]() |
ad6f21980c | ||
![]() |
017b8b7f15 | ||
![]() |
9b448b17e6 | ||
![]() |
f9996a9987 | ||
![]() |
000ef55273 | ||
![]() |
e1ac0f02b4 | ||
![]() |
b9297e3f1d | ||
![]() |
34d0669ca8 | ||
![]() |
25e42720cf | ||
![]() |
f7c1951191 |
@ -13,6 +13,7 @@
|
||||
"devDependencies": {
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@log4js-node/log4js-api": "^1.0.2",
|
||||
"@napneko/nap-proto-core": "^0.0.2",
|
||||
"@protobuf-ts/runtime": "^2.9.4",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-typescript": "^11.1.6",
|
||||
|
@ -1,6 +1,6 @@
|
||||
export class LRUCache<K, V> {
|
||||
private capacity: number;
|
||||
private cache: Map<K, V>;
|
||||
public cache: Map<K, V>;
|
||||
|
||||
constructor(capacity: number) {
|
||||
this.capacity = capacity;
|
||||
|
@ -4,7 +4,7 @@ import { ChatType, InstanceContext, NapCatCore } from '..';
|
||||
import offset from '@/core/external/offset.json';
|
||||
import { PacketSession } from "@/core/packet/session";
|
||||
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 { OidbSvcTrpcTcpBase, OidbSvcTrpcTcpBaseRsp } from '@/core/packet/proto/oidb/OidbBase';
|
||||
import { OidbSvcTrpcTcp0XFE1_2RSP } from '@/core/packet/proto/oidb/Oidb.0XFE1_2';
|
||||
@ -48,14 +48,9 @@ export class NTQQPacketApi {
|
||||
this.core = core;
|
||||
this.logger = core.context.logger;
|
||||
this.packetSession = undefined;
|
||||
const config = this.core.configLoader.configData;
|
||||
if (config) {
|
||||
this.InitSendPacket(this.context.basicInfoWrapper.getFullQQVesion())
|
||||
.then()
|
||||
.catch(this.core.context.logger.logError.bind(this.core.context.logger));
|
||||
} else {
|
||||
this.core.context.logger.logWarn('PacketServer未配置,NapCat.Packet将不会加载!');
|
||||
}
|
||||
this.InitSendPacket(this.context.basicInfoWrapper.getFullQQVesion())
|
||||
.then()
|
||||
.catch(this.core.context.logger.logError.bind(this.core.context.logger));
|
||||
}
|
||||
|
||||
get available(): boolean {
|
||||
@ -66,7 +61,11 @@ export class NTQQPacketApi {
|
||||
this.qqVersion = qqversion;
|
||||
const table = typedOffset[qqversion + '-' + os.arch()];
|
||||
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;
|
||||
}
|
||||
this.packetSession = new PacketSession(this.core);
|
||||
|
3
src/core/external/napcat.json
vendored
3
src/core/external/napcat.json
vendored
@ -3,5 +3,6 @@
|
||||
"consoleLog": true,
|
||||
"fileLogLevel": "debug",
|
||||
"consoleLogLevel": "info",
|
||||
"packetBackend": "auto",
|
||||
"packetServer": ""
|
||||
}
|
||||
}
|
||||
|
@ -30,10 +30,6 @@ export abstract class PacketClient {
|
||||
this.config = core.configLoader.configData;
|
||||
}
|
||||
|
||||
get available(): boolean {
|
||||
return this.isAvailable;
|
||||
}
|
||||
|
||||
private randText(len: number): string {
|
||||
let text = '';
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
@ -43,13 +39,11 @@ export abstract class PacketClient {
|
||||
return text;
|
||||
}
|
||||
|
||||
static create(core: NapCatCore): PacketClient {
|
||||
throw new Error("Must be implemented by subclasses");
|
||||
get available(): boolean {
|
||||
return this.isAvailable;
|
||||
}
|
||||
|
||||
static compatibilityScore(logger: LogWrapper): number {
|
||||
throw new Error("Must be implemented by subclasses");
|
||||
}
|
||||
abstract check(core: NapCatCore): boolean;
|
||||
|
||||
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> {
|
||||
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 = () => {
|
||||
}): Promise<RecvPacketData> {
|
||||
return new Promise<RecvPacketData>((resolve, reject) => {
|
||||
if (!this.isAvailable) {
|
||||
throw new Error("WebSocket is not connected");
|
||||
}
|
||||
this.sendCommandImpl(cmd, data, trace_id);
|
||||
if (rsp) {
|
||||
this.registerCallback(trace_id, 'recv', async (json: RecvPacketData) => {
|
||||
clearTimeout(timeoutHandle);
|
||||
@ -81,6 +72,7 @@ export abstract class PacketClient {
|
||||
resolve(json);
|
||||
}
|
||||
});
|
||||
this.sendCommandImpl(cmd, data, trace_id);
|
||||
const timeoutHandle = setTimeout(() => {
|
||||
reject(new Error(`sendCommand timed out after ${timeout} ms for ${cmd} with trace_id ${trace_id}`));
|
||||
}, timeout);
|
||||
@ -98,6 +90,7 @@ export abstract class PacketClient {
|
||||
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));
|
||||
});
|
||||
|
@ -3,16 +3,19 @@ import { NapCatCore } from "@/core";
|
||||
import path, { dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import fs from "fs";
|
||||
import { console } from "inspector";
|
||||
import { PacketClient } from "@/core/packet/client/client";
|
||||
import { constants, platform, type } from "node:os";
|
||||
import { LogWrapper } from "@/common/log";
|
||||
|
||||
import { constants } from "node:os";
|
||||
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 {
|
||||
static supportedPlatforms = ['win32.x64'];
|
||||
private MoeHooExport: any = { exports: {} };
|
||||
|
||||
protected constructor(core: NapCatCore) {
|
||||
private readonly supportedPlatforms = ['win32.x64'];
|
||||
private MoeHooExport: { exports: NativePacketExportType } = { exports: {} };
|
||||
private sendEvent = new LRUCache<number, string>(500);//seq->trace_id
|
||||
constructor(core: NapCatCore) {
|
||||
super(core);
|
||||
}
|
||||
|
||||
@ -20,44 +23,56 @@ export class NativePacketClient extends PacketClient {
|
||||
return this.isAvailable;
|
||||
}
|
||||
|
||||
static compatibilityScore(logger: LogWrapper): number {
|
||||
check(): boolean {
|
||||
const platform = process.platform + '.' + process.arch;
|
||||
if (!this.supportedPlatforms.includes(platform)) {
|
||||
logger.logError(`[NativePacketClient] Unsupported platform: ${platform}`);
|
||||
return 0;
|
||||
this.logger.logWarn(`[Core] [Packet:Native] 不支持的平台: ${platform}`);
|
||||
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)) {
|
||||
logger.logError(`[NativePacketClient] Missing moehoo binary: ${moehoo_path}`);
|
||||
return 0;
|
||||
this.logger.logWarn(`[Core] [Packet:Native] 缺失运行时文件: ${moehoo_path}`);
|
||||
return false;
|
||||
}
|
||||
return 10;
|
||||
}
|
||||
|
||||
static create(core: NapCatCore): NativePacketClient {
|
||||
return new NativePacketClient(core);
|
||||
return true;
|
||||
}
|
||||
|
||||
async init(pid: number, recv: string, send: string): Promise<void> {
|
||||
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);
|
||||
this.MoeHooExport.exports.InitHook(pid, recv, send, (type: number, uin: string, seq: number, cmd: string, hex_data: string) => {
|
||||
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('MoeHooExport:', this.MoeHooExport);
|
||||
console.log('recv:', recv, 'send:',);
|
||||
this.MoeHooExport.exports.InitHook?.(send, recv, (type: number, uin: string, cmd: string, seq: number, hex_data: string) => {
|
||||
const trace_id = createHash('md5').update(Buffer.from(hex_data, 'hex')).digest('hex');
|
||||
if (type === 0 && this.cb.get(trace_id + 'recv')) {
|
||||
//此时为send 提取seq
|
||||
this.sendEvent.put(seq, trace_id);
|
||||
}
|
||||
// TODO: cannot use console.log here, fxxk tx
|
||||
// Error [ERR_INSPECTOR_NOT_AVAILABLE]: Inspector is not available
|
||||
// console.log('type:', type, 'uin:', uin, 'seq:', seq, 'cmd:', cmd, 'hex_data:', hex_data);
|
||||
if (type === 1 && this.sendEvent.get(seq)) {
|
||||
//此时为recv 调用callback
|
||||
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;
|
||||
}
|
||||
|
||||
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> {
|
||||
|
@ -1,38 +1,37 @@
|
||||
import { Data, WebSocket } from "ws";
|
||||
import { NapCatCore } from "@/core";
|
||||
import { PacketClient, RecvPacket } from "@/core/packet/client/client";
|
||||
import { LogWrapper } from "@/common/log";
|
||||
|
||||
export class wsPacketClient extends PacketClient {
|
||||
private websocket: WebSocket | undefined;
|
||||
private reconnectAttempts: number = 0;
|
||||
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`;
|
||||
|
||||
protected constructor(core: NapCatCore) {
|
||||
constructor(core: NapCatCore) {
|
||||
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 {
|
||||
return 10;
|
||||
}
|
||||
|
||||
static create(core: NapCatCore): wsPacketClient {
|
||||
return new wsPacketClient(core);
|
||||
check(): boolean {
|
||||
if (!this.clientUrl) {
|
||||
this.logger.logWarn(`[Core] [Packet:Server] 未配置服务器地址`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
connect(cb: () => void): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
//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.onopen = () => {
|
||||
this.isAvailable = true;
|
||||
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();
|
||||
resolve();
|
||||
};
|
||||
@ -62,14 +61,14 @@ export class wsPacketClient extends PacketClient {
|
||||
this.reconnectAttempts++;
|
||||
setTimeout(() => {
|
||||
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);
|
||||
} 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) {
|
||||
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] 重连时出错: ${error.message}`);
|
||||
this.logger.logError.bind(this.logger)(`[Core] [Packet:Server] 重连时出错: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ 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 "@/core/packet/proto/NapProto";
|
||||
import { NapProtoMsg } from "@napneko/nap-proto-core";
|
||||
import { HttpConn0x6ff_501Response } from "@/core/packet/proto/action/action";
|
||||
import { PacketHighwayClient } from "@/core/packet/highway/client";
|
||||
import { NTV2RichMediaResp } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaResp";
|
||||
@ -59,7 +59,7 @@ export class PacketHighwaySession {
|
||||
|
||||
private async checkAvailable() {
|
||||
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) {
|
||||
this.logger.logWarn('[Highway] sigSession or sessionKey not available!');
|
||||
|
@ -4,7 +4,7 @@ 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 "@/core/packet/proto/NapProto";
|
||||
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";
|
||||
|
@ -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 { NTHighwayIPv4 } from "@/core/packet/proto/highway/highway";
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as crypto from "crypto";
|
||||
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 { PacketMsg, PacketSendMsgElement } from "@/core/packet/message/message";
|
||||
import { IPacketMsgElement, PacketMsgTextElement } from "@/core/packet/message/element";
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as zlib from "node:zlib";
|
||||
import { NapProtoEncodeStructType, NapProtoMsg } from "@/core/packet/proto/NapProto";
|
||||
import { NapProtoEncodeStructType, NapProtoMsg } from "@napneko/nap-proto-core";
|
||||
import {
|
||||
CustomFace,
|
||||
Elem,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as zlib from "node:zlib";
|
||||
import * as crypto from "node:crypto";
|
||||
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 { OidbSvcTrpcTcp0X9067_202 } from "@/core/packet/proto/oidb/Oidb.0x9067_202";
|
||||
import { OidbSvcTrpcTcp0X8FC_2, OidbSvcTrpcTcp0X8FC_2_Body } from "@/core/packet/proto/oidb/Oidb.0x8FC_2";
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
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";
|
||||
|
||||
export const FaceRoamRequest = {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const MiniAppAdaptShareInfoReq = {
|
||||
appId: ProtoField(2, ScalarType.STRING),
|
||||
|
@ -1,5 +1,5 @@
|
||||
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";
|
||||
|
||||
export const DataHighwayHead = {
|
||||
|
@ -1,5 +1,5 @@
|
||||
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";
|
||||
|
||||
export const LongMsgResult = {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const C2C = {
|
||||
uin: ProtoField(1, ScalarType.UINT32, true),
|
||||
|
@ -1,5 +1,5 @@
|
||||
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";
|
||||
|
||||
export const Attr = {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const Elem = {
|
||||
text: ProtoField(1, () => Text, true),
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const GroupRecallMsg = {
|
||||
type: ProtoField(1, ScalarType.UINT32),
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 { RichText } from "@/core/packet/proto/message/component";
|
||||
import { C2C } from "@/core/packet/proto/message/c2c";
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const FriendRecall = {
|
||||
info: ProtoField(1, () => FriendRecallInfo),
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const ForwardHead = {
|
||||
field1: ProtoField(1, ScalarType.UINT32, true),
|
||||
|
@ -1,5 +1,5 @@
|
||||
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";
|
||||
|
||||
export const OidbSvcTrpcTcp0XE37_800 = {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const OidbSvcTrpcTcp0XFE1_2 = {
|
||||
uin: ProtoField(1, ScalarType.UINT32),
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const OidbSvcTrpcTcp0x6D6 = {
|
||||
file: ProtoField(1, () => OidbSvcTrpcTcp0x6D6Upload, true),
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
|
||||
//设置群头衔 OidbSvcTrpcTcp.0x8fc_2
|
||||
@ -13,4 +13,4 @@ export const OidbSvcTrpcTcp0X8FC_2_Body = {
|
||||
export const OidbSvcTrpcTcp0X8FC_2 = {
|
||||
groupUin: ProtoField(1, ScalarType.UINT32),
|
||||
body: ProtoField(3, ScalarType.BYTES),
|
||||
};
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
import { MultiMediaReqHead } from "./common/Ntv2.RichMediaReq";
|
||||
|
||||
//Req
|
||||
|
@ -1,5 +1,5 @@
|
||||
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";
|
||||
|
||||
export const OidbSvcTrpcTcp0X929D_0 = {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const OidbSvcTrpcTcp0XE37_1200 = {
|
||||
subCommand: ProtoField(1, ScalarType.UINT32, true),
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const OidbSvcTrpcTcp0XE37_1700 = {
|
||||
command: ProtoField(1, ScalarType.UINT32, true),
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const OidbSvcTrpcTcp0XEB7_Body = {
|
||||
uin: ProtoField(1, ScalarType.STRING),
|
||||
@ -9,4 +9,4 @@ export const OidbSvcTrpcTcp0XEB7_Body = {
|
||||
|
||||
export const OidbSvcTrpcTcp0XEB7 = {
|
||||
body: ProtoField(2, () => OidbSvcTrpcTcp0XEB7_Body),
|
||||
};
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
// Send Poke
|
||||
export const OidbSvcTrpcTcp0XED3_1 = {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const OidbSvcTrpcTcpBase = {
|
||||
command: ProtoField(1, ScalarType.UINT32),
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ScalarType } from "@protobuf-ts/runtime";
|
||||
import { ProtoField } from "../../NapProto";
|
||||
import { ProtoField } from "@napneko/nap-proto-core";
|
||||
|
||||
export const NTV2RichMediaReq = {
|
||||
ReqHead: ProtoField(1, () => MultiMediaReqHead),
|
||||
|
@ -1,5 +1,5 @@
|
||||
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";
|
||||
|
||||
export const NTV2RichMediaResp = {
|
||||
|
@ -7,12 +7,12 @@ import { wsPacketClient } from "@/core/packet/client/wsClient";
|
||||
import { NapCatCore } from "@/core";
|
||||
|
||||
type clientPriority = {
|
||||
[key: number]: typeof PacketClient;
|
||||
[key: number]: (core: NapCatCore) => PacketClient;
|
||||
}
|
||||
|
||||
const clientPriority: clientPriority = {
|
||||
10: NativePacketClient,
|
||||
1: wsPacketClient,
|
||||
10: (core: NapCatCore) => new NativePacketClient(core),
|
||||
1: (core: NapCatCore) => new wsPacketClient(core),
|
||||
};
|
||||
|
||||
export class PacketSession {
|
||||
@ -23,27 +23,40 @@ export class PacketSession {
|
||||
|
||||
constructor(core: NapCatCore) {
|
||||
this.logger = core.context.logger;
|
||||
this.client = this.judgeClient(core);
|
||||
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;
|
||||
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 {
|
||||
let selectedClient: typeof PacketClient | null = null;
|
||||
let maxScore = -1;
|
||||
for (const key in clientPriority) {
|
||||
const priority = parseInt(key);
|
||||
const ClientClass = clientPriority[priority];
|
||||
const score = priority * ClientClass.compatibilityScore(core.context.logger);
|
||||
if (score > maxScore) {
|
||||
maxScore = score;
|
||||
selectedClient = ClientClass;
|
||||
}
|
||||
}
|
||||
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("No compatible PacketClient found");
|
||||
throw new Error("[Core] [Packet] 无可用的后端,NapCat.Packet将不会加载!");
|
||||
}
|
||||
this.logger.log(`[Packet] 自动选择了: ${selectedClient.name}`);
|
||||
return selectedClient.create(core);
|
||||
this.logger.log(`[Core] [Packet] 自动选择 ${selectedClient.constructor.name} 作为后端`);
|
||||
return selectedClient;
|
||||
}
|
||||
}
|
||||
|
BIN
src/native/packet/MoeHoo.win32.x64.node
Normal file
BIN
src/native/packet/MoeHoo.win32.x64.node
Normal file
Binary file not shown.
@ -9,7 +9,7 @@ export abstract class GetPacketStatusDepends<PT, RT> extends BaseAction<PT, RT>
|
||||
if (!this.core.apis.PacketApi.available) {
|
||||
return {
|
||||
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);
|
||||
|
@ -26,6 +26,7 @@ const FrameworkBaseConfigPlugin: PluginOption[] = [
|
||||
targets: [
|
||||
{ src: './manifest.json', dest: 'dist' },
|
||||
{ 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: './src/onebot/config/onebot11.json', dest: 'dist/config/' },
|
||||
{ src: './src/framework/liteloader.cjs', dest: 'dist' },
|
||||
@ -42,6 +43,7 @@ const ShellBaseConfigPlugin: PluginOption[] = [
|
||||
cp({
|
||||
targets: [
|
||||
{ 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: './src/core/external/napcat.json', dest: 'dist/config/' },
|
||||
{ src: './src/onebot/config/onebot11.json', dest: 'dist/config/' },
|
||||
|
Loading…
x
Reference in New Issue
Block a user