From c70b2eaa305230e5245e81bf0ed837a6c60fe03e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=89=8B=E7=93=9C=E4=B8=80=E5=8D=81=E9=9B=AA?= Date: Thu, 8 Aug 2024 17:51:15 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E8=BF=9B=E5=BA=A6=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/framework/event-legacy.ts | 262 +++++++++++++++ src/common/framework/event.ts | 138 ++++++++ src/common/framework/napcat.ts | 14 + src/common/framwork/napcat.ts | 24 -- src/common/utils/QQBasicInfo.ts | 131 ++++---- src/common/utils/helper.ts | 464 +++++++------------------- src/common/utils/proxy-handler.ts | 0 src/common/utils/system.ts | 1 - src/core/core.ts | 71 ++-- src/core/index.ts | 0 src/core/wrapper/data.ts | 8 + src/core/wrapper/helper.ts | 55 ++++ src/core/wrapper/wrapper.ts | 474 ++++++++++++++------------- src/liteloader/napcat.ts | 68 +++- src/onebot/index.ts | 5 +- src/shell/napcat.ts | 125 ++++++- 16 files changed, 1139 insertions(+), 701 deletions(-) create mode 100644 src/common/framework/event-legacy.ts create mode 100644 src/common/framework/event.ts create mode 100644 src/common/framework/napcat.ts delete mode 100644 src/common/framwork/napcat.ts create mode 100644 src/common/utils/proxy-handler.ts create mode 100644 src/core/index.ts create mode 100644 src/core/wrapper/data.ts create mode 100644 src/core/wrapper/helper.ts diff --git a/src/common/framework/event-legacy.ts b/src/common/framework/event-legacy.ts new file mode 100644 index 00000000..27be1010 --- /dev/null +++ b/src/common/framework/event-legacy.ts @@ -0,0 +1,262 @@ +import { NodeIQQNTWrapperSession } from "@/core/wrapper/wrapper"; +import { randomUUID } from "crypto"; + +interface Internal_MapKey { + timeout: number; + createtime: number; + func: (...arg: any[]) => any; + checker: ((...args: any[]) => boolean) | undefined; +} + +export class ListenerClassBase { + [key: string]: string; +} + +export interface ListenerIBase { + // eslint-disable-next-line @typescript-eslint/no-misused-new + new (listener: any): ListenerClassBase; +} + +export class NTEventWrapper { + private ListenerMap: { [key: string]: ListenerIBase } | undefined; //ListenerName-Unique -> Listener构造函数 + private WrapperSession: NodeIQQNTWrapperSession | undefined; //WrapperSession + private ListenerManger: Map = new Map(); //ListenerName-Unique -> Listener实例 + private EventTask = new Map>>(); //tasks ListenerMainName -> ListenerSubName-> uuid -> {timeout,createtime,func} + constructor() {} + createProxyDispatch(ListenerMainName: string) { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const current = this; + return new Proxy( + {}, + { + get(target: any, prop: any, receiver: any) { + // console.log('get', prop, typeof target[prop]); + if (typeof target[prop] === "undefined") { + // 如果方法不存在,返回一个函数,这个函数调用existentMethod + return (...args: any[]) => { + current.DispatcherListener.apply(current, [ListenerMainName, prop, ...args]).then(); + }; + } + // 如果方法存在,正常返回 + return Reflect.get(target, prop, receiver); + }, + } + ); + } + init({ + ListenerMap, + WrapperSession, + }: { + ListenerMap: { [key: string]: typeof ListenerClassBase }; + WrapperSession: NodeIQQNTWrapperSession; + }) { + this.ListenerMap = ListenerMap; + this.WrapperSession = WrapperSession; + } + CreatEventFunction any>(eventName: string): T | undefined { + const eventNameArr = eventName.split("/"); + type eventType = { + [key: string]: () => { [key: string]: (...params: Parameters) => Promise> }; + }; + if (eventNameArr.length > 1) { + const serviceName = "get" + eventNameArr[0].replace("NodeIKernel", ""); + const eventName = eventNameArr[1]; + //getNodeIKernelGroupListener,GroupService + //console.log('2', eventName); + const services = (this.WrapperSession as unknown as eventType)[serviceName](); + let event = services[eventName]; + //重新绑定this + event = event.bind(services); + if (event) { + return event as T; + } + return undefined; + } + } + CreatListenerFunction(listenerMainName: string, uniqueCode: string = ""): T { + const ListenerType = this.ListenerMap![listenerMainName]; + let Listener = this.ListenerManger.get(listenerMainName + uniqueCode); + if (!Listener && ListenerType) { + Listener = new ListenerType(this.createProxyDispatch(listenerMainName)); + const ServiceSubName = listenerMainName.match(/^NodeIKernel(.*?)Listener$/)![1]; + const Service = "NodeIKernel" + ServiceSubName + "Service/addKernel" + ServiceSubName + "Listener"; + const addfunc = this.CreatEventFunction<(listener: T) => number>(Service); + addfunc!(Listener as T); + //console.log(addfunc!(Listener as T)); + this.ListenerManger.set(listenerMainName + uniqueCode, Listener); + } + return Listener as T; + } + //统一回调清理事件 + async DispatcherListener(ListenerMainName: string, ListenerSubName: string, ...args: any[]) { + //console.log("[EventDispatcher]",ListenerMainName, ListenerSubName, ...args); + this.EventTask.get(ListenerMainName) + ?.get(ListenerSubName) + ?.forEach((task, uuid) => { + //console.log(task.func, uuid, task.createtime, task.timeout); + if (task.createtime + task.timeout < Date.now()) { + this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.delete(uuid); + return; + } + if (task.checker && task.checker(...args)) { + task.func(...args); + } + }); + } + async CallNoListenerEvent Promise | any>( + EventName = "", + timeout: number = 3000, + ...args: Parameters + ) { + return new Promise>>(async (resolve, reject) => { + const EventFunc = this.CreatEventFunction(EventName); + let complete = false; + const Timeouter = setTimeout(() => { + if (!complete) { + reject(new Error("NTEvent EventName:" + EventName + " timeout")); + } + }, timeout); + const retData = await EventFunc!(...args); + complete = true; + resolve(retData); + }); + } + async RegisterListen void>( + ListenerName = "", + waitTimes = 1, + timeout = 5000, + checker: (...args: Parameters) => boolean + ) { + return new Promise>((resolve, reject) => { + const ListenerNameList = ListenerName.split("/"); + const ListenerMainName = ListenerNameList[0]; + const ListenerSubName = ListenerNameList[1]; + const id = randomUUID(); + let complete = 0; + let retData: Parameters | undefined = undefined; + const databack = () => { + if (complete == 0) { + reject(new Error(" ListenerName:" + ListenerName + " timeout")); + } else { + resolve(retData!); + } + }; + const Timeouter = setTimeout(databack, timeout); + const eventCallbak = { + timeout: timeout, + createtime: Date.now(), + checker: checker, + func: (...args: Parameters) => { + complete++; + retData = args; + if (complete >= waitTimes) { + clearTimeout(Timeouter); + databack(); + } + }, + }; + if (!this.EventTask.get(ListenerMainName)) { + this.EventTask.set(ListenerMainName, new Map()); + } + if (!this.EventTask.get(ListenerMainName)?.get(ListenerSubName)) { + this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map()); + } + this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak); + this.CreatListenerFunction(ListenerMainName); + }); + } + async CallNormalEvent< + EventType extends (...args: any[]) => Promise, + ListenerType extends (...args: any[]) => void + >( + EventName = "", + ListenerName = "", + waitTimes = 1, + timeout: number = 3000, + checker: (...args: Parameters) => boolean, + ...args: Parameters + ) { + return new Promise<[EventRet: Awaited>, ...Parameters]>( + async (resolve, reject) => { + const id = randomUUID(); + let complete = 0; + let retData: Parameters | undefined = undefined; + let retEvent: any = {}; + const databack = () => { + if (complete == 0) { + reject( + new Error( + "Timeout: NTEvent EventName:" + + EventName + + " ListenerName:" + + ListenerName + + " EventRet:\n" + + JSON.stringify(retEvent, null, 4) + + "\n" + ) + ); + } else { + resolve([retEvent as Awaited>, ...retData!]); + } + }; + + const ListenerNameList = ListenerName.split("/"); + const ListenerMainName = ListenerNameList[0]; + const ListenerSubName = ListenerNameList[1]; + + const Timeouter = setTimeout(databack, timeout); + + const eventCallbak = { + timeout: timeout, + createtime: Date.now(), + checker: checker, + func: (...args: any[]) => { + complete++; + //console.log('func', ...args); + retData = args as Parameters; + if (complete >= waitTimes) { + clearTimeout(Timeouter); + databack(); + } + }, + }; + if (!this.EventTask.get(ListenerMainName)) { + this.EventTask.set(ListenerMainName, new Map()); + } + if (!this.EventTask.get(ListenerMainName)?.get(ListenerSubName)) { + this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map()); + } + this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak); + this.CreatListenerFunction(ListenerMainName); + const EventFunc = this.CreatEventFunction(EventName); + retEvent = await EventFunc!(...(args as any[])); + } + ); + } +} +export const NTEventDispatch = new NTEventWrapper(); + +// 示例代码 快速创建事件 +// let NTEvent = new NTEventWrapper(); +// let TestEvent = NTEvent.CreatEventFunction<(force: boolean) => Promise>('NodeIKernelProfileLikeService/GetTest'); +// if (TestEvent) { +// TestEvent(true); +// } + +// 示例代码 快速创建监听Listener类 +// let NTEvent = new NTEventWrapper(); +// NTEvent.CreatListenerFunction('NodeIKernelMsgListener', 'core') + +// 调用接口 +//let NTEvent = new NTEventWrapper(); +//let ret = await NTEvent.CallNormalEvent<(force: boolean) => Promise, (data1: string, data2: number) => void>('NodeIKernelProfileLikeService/GetTest', 'NodeIKernelMsgListener/onAddSendMsg', 1, 3000, true); + +// 注册监听 解除监听 +// NTEventDispatch.RigisterListener('NodeIKernelMsgListener/onAddSendMsg','core',cb); +// NTEventDispatch.UnRigisterListener('NodeIKernelMsgListener/onAddSendMsg','core'); + +// let GetTest = NTEventDispatch.CreatEvent('NodeIKernelProfileLikeService/GetTest','NodeIKernelMsgListener/onAddSendMsg',Mode); +// GetTest('test'); + +// always模式 +// NTEventDispatch.CreatEvent('NodeIKernelProfileLikeService/GetTest','NodeIKernelMsgListener/onAddSendMsg',Mode,(...args:any[])=>{ console.log(args) }); diff --git a/src/common/framework/event.ts b/src/common/framework/event.ts new file mode 100644 index 00000000..a35a0ebf --- /dev/null +++ b/src/common/framework/event.ts @@ -0,0 +1,138 @@ +import type { NodeIQQNTWrapperSession, WrapperNodeApi } from "@/core/wrapper/wrapper"; +import { randomUUID } from "node:crypto"; +import EventEmitter from "node:events"; + +export type ListenerClassBase = Record; + +export interface ListenerIBase { + // eslint-disable-next-line @typescript-eslint/no-misused-new + new(listener: any): ListenerClassBase; +} + +export class NTEventChannel extends EventEmitter { + private wrapperApi: WrapperNodeApi; + private wrapperSession: NodeIQQNTWrapperSession; + private listenerRefStorage = new Map(); + constructor(WrapperApi: WrapperNodeApi, WrapperSession: NodeIQQNTWrapperSession) { + super(); + this.on('error', () => { }); + this.wrapperApi = WrapperApi; + this.wrapperSession = WrapperSession; + } + + dispatcherListener(ListenerEvent: string, ...args: any[]) { + this.emit(ListenerEvent, ...args); + } + + createProxyDispatch(ListenerMainName: string) { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const current = this; + return new Proxy({}, { + get(_target: any, prop: any, _receiver: any) { + return (...args: any[]) => { + current.dispatcherListener.apply(current, [ListenerMainName + '/' + prop, ...args]); + }; + } + }); + } + + async getOrInitListener(listenerMainName: string): Promise { + const ListenerType = this.wrapperApi[listenerMainName]; + //获取NTQQ 外部 Listener包装 + if (!ListenerType) throw new Error('Init Listener not found'); + let Listener = this.listenerRefStorage.get(listenerMainName); + //判断是否已创建 创建则跳过 + if (!Listener && ListenerType) { + Listener = new ListenerType(this.createProxyDispatch(listenerMainName)); + if (!Listener) throw new Error('Init Listener failed'); + //实例化NTQQ Listener外包装 + const ServiceSubName = listenerMainName.match(/^NodeIKernel(.*?)Listener$/)![1]; + const Service = 'NodeIKernel' + ServiceSubName + 'Service/addKernel' + ServiceSubName + 'Listener'; + const addfunc = this.createEventFunction<(listener: T) => number>(Service); + //添加Listener到NTQQ + addfunc!(Listener as T); + this.listenerRefStorage.set(listenerMainName, Listener); + //保存Listener实例 + } + return Listener as T; + } + + async createEventWithListener any, ListenerType extends (...args: any) => any> + ( + eventName: string, + listenerName: string, + waitTimes = 1, + timeout: number = 3000, + checker: (...args: Parameters) => boolean, + ...eventArg: Parameters + ) { + return new Promise<[EventRet: Awaited>, ...Parameters]>(async (resolve, reject) => { + const ListenerNameList = listenerName.split('/'); + const ListenerMainName = ListenerNameList[0]; + //const ListenerSubName = ListenerNameList[1]; + this.getOrInitListener(ListenerMainName); + let complete = 0; + let retData: Parameters | undefined = undefined; + let retEvent: any = {}; + const databack = () => { + if (complete == 0) { + reject(new Error('Timeout: NTEvent EventName:' + eventName + ' ListenerName:' + listenerName + ' EventRet:\n' + JSON.stringify(retEvent, null, 4) + '\n')); + } else { + resolve([retEvent as Awaited>, ...retData!]); + } + }; + const Timeouter = setTimeout(databack, timeout); + let callback = (...args: Parameters) => { + if (checker(...args)) { + complete++; + if (complete >= waitTimes) { + clearTimeout(Timeouter); + this.removeListener(listenerName, callback); + databack(); + } + } + }; + this.on(listenerName, callback); + const EventFunc = this.createEventFunction(eventName); + retEvent = await EventFunc!(...(eventArg as any[])); + }); + } + + private createEventFunction any>(eventName: string): T | undefined { + const eventNameArr = eventName.split('/'); + type eventType = { + [key: string]: () => { [key: string]: (...params: Parameters) => Promise> } + } + if (eventNameArr.length > 1) { + const serviceName = 'get' + eventNameArr[0].replace('NodeIKernel', ''); + const eventName = eventNameArr[1]; + //getNodeIKernelGroupListener,GroupService + //console.log('2', eventName); + const services = (this.wrapperSession as unknown as eventType)[serviceName](); + let event = services[eventName] + //重新绑定this + .bind(services); + if (event) { + return event as T; + } + return undefined; + } + } + + async callEvent Promise | any>( + EventName = '', timeout: number = 3000, ...args: Parameters) { + return new Promise>>(async (resolve, reject) => { + const EventFunc = this.createEventFunction(EventName); + let complete = false; + const Timeouter = setTimeout(() => { + if (!complete) { + reject(new Error('NTEvent EventName:' + EventName + ' timeout')); + } + }, timeout); + const retData = await EventFunc!(...args); + complete = true; + resolve(retData); + }); + } +} +//NTEvent2.0 \ No newline at end of file diff --git a/src/common/framework/napcat.ts b/src/common/framework/napcat.ts new file mode 100644 index 00000000..68b5735f --- /dev/null +++ b/src/common/framework/napcat.ts @@ -0,0 +1,14 @@ +import path, { dirname } from "path"; +import { fileURLToPath } from "url"; + +export class NapCatPathWrapper { + binaryPath: string; + logsPath: string; + configPath: string; + + constructor(mainPath: string = dirname(fileURLToPath(import.meta.url))) { + this.binaryPath = mainPath; + this.logsPath = path.join(this.binaryPath, "logs"); + this.configPath = path.join(this.binaryPath, "config"); + } +} diff --git a/src/common/framwork/napcat.ts b/src/common/framwork/napcat.ts deleted file mode 100644 index 4a55ab60..00000000 --- a/src/common/framwork/napcat.ts +++ /dev/null @@ -1,24 +0,0 @@ -import path, { dirname } from "path"; -import { fileURLToPath } from "url"; - -class NapCatPathWrapper { - NapCat_Main_Path: string | undefined; - NapCat_Logs_Path: string | undefined; - NapCat_Config_Path: string | undefined; - constructor() { } - Init(MainPath: string = dirname(fileURLToPath(import.meta.url))) { - this.NapCat_Main_Path = MainPath; - this.NapCat_Logs_Path = path.join(this.NapCat_Main_Path, "logs"); - this.NapCat_Config_Path = path.join(this.NapCat_Main_Path, "config"); - } - getScriptPath() { - return this.NapCat_Main_Path; - } - getLogsPath() { - return this.NapCat_Logs_Path; - } - getConfigPath() { - return this.NapCat_Config_Path; - } -} -export let NapCatPath: NapCatPathWrapper | undefined; \ No newline at end of file diff --git a/src/common/utils/QQBasicInfo.ts b/src/common/utils/QQBasicInfo.ts index 214c7117..2b406ec2 100644 --- a/src/common/utils/QQBasicInfo.ts +++ b/src/common/utils/QQBasicInfo.ts @@ -1,67 +1,78 @@ -import path from 'node:path'; -import fs from 'node:fs'; -import { systemPlatform } from '@/common/utils/system'; -import { getDefaultQQVersionConfigInfo, getQQVersionConfigPath } from './helper'; -import AppidTable from '@/core/external/appid.json'; -import { LogWrapper } from './log'; +import path from "node:path"; +import fs from "node:fs"; +import { systemPlatform } from "@/common/utils/system"; +import { getDefaultQQVersionConfigInfo, getQQVersionConfigPath } from "./helper"; +import AppidTable from "@/core/external/appid.json"; +import { LogWrapper } from "./log"; export class QQBasicInfoWrapper { - QQMainPath: string | undefined; - QQPackageInfoPath: string | undefined; - QQVersionConfigPath: string | undefined; - isQuickUpdate: boolean | undefined; - QQVersionConfig: QQVersionConfigType | undefined; - QQPackageInfo: QQPackageInfoType | undefined; - QQVersionAppid: string | undefined; - QQVersionQua: string | undefined; - context: { logger: LogWrapper; }; - constructor(context: { logger: LogWrapper }) { - //基础目录获取 - this.context = context; - this.QQMainPath = process.execPath; - this.QQPackageInfoPath = path.join(path.dirname(this.QQMainPath), 'resources', 'app', 'package.json'); - this.QQVersionConfigPath = getQQVersionConfigPath(this.QQMainPath); + QQMainPath: string | undefined; + QQPackageInfoPath: string | undefined; + QQVersionConfigPath: string | undefined; + isQuickUpdate: boolean | undefined; + QQVersionConfig: QQVersionConfigType | undefined; + QQPackageInfo: QQPackageInfoType | undefined; + QQVersionAppid: string | undefined; + QQVersionQua: string | undefined; + context: { logger: LogWrapper }; - //基础信息获取 无快更则启用默认模板填充 - this.isQuickUpdate = !!this.QQVersionConfigPath; - this.QQVersionConfig = this.isQuickUpdate ? JSON.parse(fs.readFileSync(this.QQVersionConfigPath!).toString()) : getDefaultQQVersionConfigInfo(); - this.QQPackageInfo = JSON.parse(fs.readFileSync(this.QQPackageInfoPath).toString()); - let { appid: IQQVersionAppid, qua: IQQVersionQua } = this.getAppidV2(); - this.QQVersionAppid = IQQVersionAppid; - this.QQVersionQua = IQQVersionQua; - } + constructor(context: { logger: LogWrapper }) { + //基础目录获取 + this.context = context; + this.QQMainPath = process.execPath; + this.QQPackageInfoPath = path.join(path.dirname(this.QQMainPath), "resources", "app", "package.json"); + this.QQVersionConfigPath = getQQVersionConfigPath(this.QQMainPath); - //基础函数 - getQQBuildStr() { - return this.isQuickUpdate ? this.QQVersionConfig?.buildId : this.QQPackageInfo?.buildVersion; - } - getFullQQVesion() { return this.isQuickUpdate ? this.QQVersionConfig?.curVersion : this.QQPackageInfo?.version; } - - requireMinNTQQBuild(buildStr: string) { - let currentBuild = parseInt(this.getQQBuildStr() || '0') - if (currentBuild == 0) throw new Error('QQBuildStr获取失败') - return currentBuild >= parseInt(buildStr); - } - //此方法不要直接使用 - getQUAInternal() { - return systemPlatform === 'linux' ? `V1_LNX_NQ_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B` : `V1_WIN_NQ_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`; - } - getAppidV2(): { appid: string, qua: string } { - const appidTbale = AppidTable as unknown as QQAppidTableType; - try { - let fullVersion = this.getFullQQVesion(); - if (!fullVersion) throw new Error('QQ版本获取失败'); - const data = appidTbale[fullVersion]; - if (data) { - return data; - } + //基础信息获取 无快更则启用默认模板填充 + this.isQuickUpdate = !!this.QQVersionConfigPath; + this.QQVersionConfig = this.isQuickUpdate + ? JSON.parse(fs.readFileSync(this.QQVersionConfigPath!).toString()) + : getDefaultQQVersionConfigInfo(); + this.QQPackageInfo = JSON.parse(fs.readFileSync(this.QQPackageInfoPath).toString()); + let { appid: IQQVersionAppid, qua: IQQVersionQua } = this.getAppidV2(); + this.QQVersionAppid = IQQVersionAppid; + this.QQVersionQua = IQQVersionQua; } - catch (e) { - this.context.logger.log(`[QQ版本兼容性检测] 获取Appid异常 请检测NapCat/QQNT是否正常`); + + //基础函数 + getQQBuildStr() { + return this.isQuickUpdate ? this.QQVersionConfig?.buildId : this.QQPackageInfo?.buildVersion; + } + + getFullQQVesion() { + let version = this.isQuickUpdate ? this.QQVersionConfig?.curVersion : this.QQPackageInfo?.version; + if(!version) throw new Error("QQ版本获取失败"); + return version; + } + + requireMinNTQQBuild(buildStr: string) { + let currentBuild = parseInt(this.getQQBuildStr() || "0"); + if (currentBuild == 0) throw new Error("QQBuildStr获取失败"); + return currentBuild >= parseInt(buildStr); + } + //此方法不要直接使用 + getQUAInternal() { + return systemPlatform === "linux" + ? `V1_LNX_NQ_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B` + : `V1_WIN_NQ_${this.getFullQQVesion()}_${this.getQQBuildStr()}_GW_B`; + } + getAppidV2(): { appid: string; qua: string } { + const appidTbale = AppidTable as unknown as QQAppidTableType; + try { + let fullVersion = this.getFullQQVesion(); + if (!fullVersion) throw new Error("QQ版本获取失败"); + const data = appidTbale[fullVersion]; + if (data) { + return data; + } + } catch (e) { + this.context.logger.log(`[QQ版本兼容性检测] 获取Appid异常 请检测NapCat/QQNT是否正常`); + } + // 以下是兜底措施 + this.context.logger.log( + `[QQ版本兼容性检测] ${this.getFullQQVesion()} 版本兼容性不佳,可能会导致一些功能无法正常使用` + ); + return { appid: systemPlatform === "linux" ? "537237950" : "537237765", qua: this.getQUAInternal() }; } - // 以下是兜底措施 - this.context.logger.log(`[QQ版本兼容性检测] ${this.getFullQQVesion()} 版本兼容性不佳,可能会导致一些功能无法正常使用`); - return { appid: systemPlatform === 'linux' ? '537237950' : '537237765', qua: this.getQUAInternal() }; - } } -export let QQBasicInfo: QQBasicInfoWrapper | undefined; \ No newline at end of file +export let QQBasicInfo: QQBasicInfoWrapper | undefined; diff --git a/src/common/utils/helper.ts b/src/common/utils/helper.ts index bc3dc1ce..307afb42 100644 --- a/src/common/utils/helper.ts +++ b/src/common/utils/helper.ts @@ -1,390 +1,154 @@ -import crypto from 'node:crypto'; -import path from 'node:path'; -import fs from 'fs'; -import { dirname } from 'node:path'; -import { fileURLToPath } from 'node:url'; -import * as fsPromise from 'node:fs/promises'; -import os from 'node:os'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); +import crypto from "node:crypto"; +import path from "node:path"; +import fs from "fs"; +import * as fsPromise from "node:fs/promises"; +import os from "node:os"; //下面这个类是用于将uid+msgid合并的类 + export class UUIDConverter { - static encode(highStr: string, lowStr: string): string { - const high = BigInt(highStr); - const low = BigInt(lowStr); - const highHex = high.toString(16).padStart(16, '0'); - const lowHex = low.toString(16).padStart(16, '0'); - const combinedHex = highHex + lowHex; - const uuid = `${combinedHex.substring(0, 8)}-${combinedHex.substring(8, 12)}-${combinedHex.substring(12, 16)}-${combinedHex.substring(16, 20)}-${combinedHex.substring(20)}`; - return uuid; - } - static decode(uuid: string): { high: string, low: string } { - const hex = uuid.replace(/-/g, ''); - const high = BigInt('0x' + hex.substring(0, 16)); - const low = BigInt('0x' + hex.substring(16)); - return { high: high.toString(), low: low.toString() }; - } + static encode(highStr: string, lowStr: string): string { + const high = BigInt(highStr); + const low = BigInt(lowStr); + const highHex = high.toString(16).padStart(16, "0"); + const lowHex = low.toString(16).padStart(16, "0"); + const combinedHex = highHex + lowHex; + const uuid = `${combinedHex.substring(0, 8)}-${combinedHex.substring(8, 12)}-${combinedHex.substring( + 12, + 16 + )}-${combinedHex.substring(16, 20)}-${combinedHex.substring(20)}`; + return uuid; + } + static decode(uuid: string): { high: string; low: string } { + const hex = uuid.replace(/-/g, ""); + const high = BigInt("0x" + hex.substring(0, 16)); + const low = BigInt("0x" + hex.substring(16)); + return { high: high.toString(), low: low.toString() }; + } } - export function sleep(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)); } export function PromiseTimer(promise: Promise, ms: number): Promise { - const timeoutPromise = new Promise((_, reject) => - setTimeout(() => reject(new Error('PromiseTimer: Operation timed out')), ms) - ); - return Promise.race([promise, timeoutPromise]); + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error("PromiseTimer: Operation timed out")), ms) + ); + return Promise.race([promise, timeoutPromise]); } export async function runAllWithTimeout(tasks: Promise[], timeout: number): Promise { - const wrappedTasks = tasks.map(task => - PromiseTimer(task, timeout).then( - result => ({ status: 'fulfilled', value: result }), - error => ({ status: 'rejected', reason: error }) - ) - ); - const results = await Promise.all(wrappedTasks); - return results - .filter(result => result.status === 'fulfilled') - .map(result => (result as { status: 'fulfilled'; value: T }).value); + const wrappedTasks = tasks.map((task) => + PromiseTimer(task, timeout).then( + (result) => ({ status: "fulfilled", value: result }), + (error) => ({ status: "rejected", reason: error }) + ) + ); + const results = await Promise.all(wrappedTasks); + return results + .filter((result) => result.status === "fulfilled") + .map((result) => (result as { status: "fulfilled"; value: T }).value); } export function getMd5(s: string) { - - const h = crypto.createHash('md5'); - h.update(s); - return h.digest('hex'); + const h = crypto.createHash("md5"); + h.update(s); + return h.digest("hex"); } export function isNull(value: any) { - return value === undefined || value === null; + return value === undefined || value === null; } export function isNumeric(str: string) { - return /^\d+$/.test(str); + return /^\d+$/.test(str); } - export function truncateString(obj: any, maxLength = 500) { - if (obj !== null && typeof obj === 'object') { - Object.keys(obj).forEach(key => { - if (typeof obj[key] === 'string') { - // 如果是字符串且超过指定长度,则截断 - if (obj[key].length > maxLength) { - obj[key] = obj[key].substring(0, maxLength) + '...'; - } - } else if (typeof obj[key] === 'object') { - // 如果是对象或数组,则递归调用 - truncateString(obj[key], maxLength); - } - }); - } - return obj; -} -export function simpleDecorator(target: any, context: any) { -} - -// export function CacheClassFunc(ttl: number = 3600 * 1000, customKey: string = '') { -// const cache = new Map(); -// return function CacheClassFuncDecorator(originalMethod: Function, context: ClassMethodDecoratorContext) { -// async function CacheClassFuncDecoratorInternal(this: any, ...args: any[]) { -// const key = `${customKey}${String(context.name)}.(${args.map(arg => JSON.stringify(arg)).join(', ')})`; -// const cachedValue = cache.get(key); -// if (cachedValue && cachedValue.expiry > Date.now()) { -// return cachedValue.value; -// } -// const result = originalMethod.call(this, ...args); -// cache.set(key, { expiry: Date.now() + ttl, value: result }); -// return result; -// } -// return CacheClassFuncDecoratorInternal; -// } -// } -export function CacheClassFuncAsync(ttl: number = 3600 * 1000, customKey: string = '') { - //console.log('CacheClassFuncAsync', ttl, customKey); - function logExecutionTime(target: any, methodName: string, descriptor: PropertyDescriptor) { - //console.log('logExecutionTime', target, methodName, descriptor); - const cache = new Map(); - const originalMethod = descriptor.value; - descriptor.value = async function (...args: any[]) { - const key = `${customKey}${String(methodName)}.(${args.map(arg => JSON.stringify(arg)).join(', ')})`; - cache.forEach((value, key) => { - if (value.expiry < Date.now()) { - cache.delete(key); - } - }); - const cachedValue = cache.get(key); - if (cachedValue && cachedValue.expiry > Date.now()) { - return cachedValue.value; - } - // const start = Date.now(); - const result = await originalMethod.apply(this, args); - // const end = Date.now(); - // console.log(`Method ${methodName} executed in ${end - start} ms.`); - cache.set(key, { expiry: Date.now() + ttl, value: result }); - return result; - }; - } - return logExecutionTime; -} -export function CacheClassFuncAsyncExtend(ttl: number = 3600 * 1000, customKey: string = '', checker: any = (...data: any[]) => { return true; }) { - //console.log('CacheClassFuncAsync', ttl, customKey); - function logExecutionTime(target: any, methodName: string, descriptor: PropertyDescriptor) { - //console.log('logExecutionTime', target, methodName, descriptor); - const cache = new Map(); - const originalMethod = descriptor.value; - descriptor.value = async function (...args: any[]) { - const key = `${customKey}${String(methodName)}.(${args.map(arg => JSON.stringify(arg)).join(', ')})`; - cache.forEach((value, key) => { - if (value.expiry < Date.now()) { - cache.delete(key); - } - }); - const cachedValue = cache.get(key); - if (cachedValue && cachedValue.expiry > Date.now()) { - return cachedValue.value; - } - // const start = Date.now(); - const result = await originalMethod.apply(this, args); - if (!checker(...args, result)) { - return result;//丢弃缓存 - } - // const end = Date.now(); - // console.log(`Method ${methodName} executed in ${end - start} ms.`); - cache.set(key, { expiry: Date.now() + ttl, value: result }); - return result; - }; - } - return logExecutionTime; -} -// export function CacheClassFuncAsync(ttl: number = 3600 * 1000, customKey: string = ''): any { -// const cache = new Map(); - -// // 注意:在JavaScript装饰器中,我们通常不直接处理ClassMethodDecoratorContext这样的类型, -// // 因为装饰器的参数通常是目标类(对于类装饰器)、属性名(对于属性装饰器)等。 -// // 对于方法装饰器,我们关注的是方法本身及其描述符。 -// // 但这里我们维持原逻辑,假设有一个自定义的处理上下文的方式。 - -// return function (originalMethod: Function): any { -// console.log(originalMethod); -// // 由于JavaScript装饰器原生不支持异步直接定义,我们保持async定义以便处理异步方法。 -// async function decoratorWrapper(this: any, ...args: any[]): Promise { -// console.log(...args); -// const key = `${customKey}${originalMethod.name}.(${args.map(arg => JSON.stringify(arg)).join(', ')})`; -// const cachedValue = cache.get(key); -// // 遍历cache 清除expiry内容 -// cache.forEach((value, key) => { -// if (value.expiry < Date.now()) { -// cache.delete(key); -// } -// }); -// if (cachedValue && cachedValue.expiry > Date.now()) { -// return cachedValue.value; -// } - -// // 直接await异步方法的结果 -// const result = await originalMethod.apply(this, args); -// cache.set(key, { expiry: Date.now() + ttl, value: result }); -// return result; -// } - -// // 返回装饰后的方法,保持与原方法相同的名称和描述符(如果需要更精细的控制,可以考虑使用Object.getOwnPropertyDescriptor等) -// return decoratorWrapper; -// }; -// } - -/** - * 函数缓存装饰器,根据方法名、参数、自定义key生成缓存键,在一定时间内返回缓存结果 - * @param ttl 超时时间,单位毫秒 - * @param customKey 自定义缓存键前缀,可为空,防止方法名参数名一致时导致缓存键冲突 - * @returns 处理后缓存或调用原方法的结果 - */ -export function cacheFunc(ttl: number, customKey: string = '') { - const cache = new Map(); - - return function (target: any, propertyKey: string, descriptor: PropertyDescriptor): PropertyDescriptor { - const originalMethod = descriptor.value; - const className = target.constructor.name; // 获取类名 - const methodName = propertyKey; // 获取方法名 - descriptor.value = async function (...args: any[]) { - const cacheKey = `${customKey}${className}.${methodName}:${JSON.stringify(args)}`; - const cached = cache.get(cacheKey); - if (cached && cached.expiry > Date.now()) { - return cached.value; - } else { - const result = await originalMethod.apply(this, args); - cache.set(cacheKey, { value: result, expiry: Date.now() + ttl }); - return result; - } - }; - - return descriptor; - }; -} -export function isValidOldConfig(config: any) { - if (typeof config !== 'object') { - return false; - } - const requiredKeys = [ - 'httpHost', 'httpPort', 'httpPostUrls', 'httpSecret', - 'wsHost', 'wsPort', 'wsReverseUrls', 'enableHttp', - 'enableHttpHeart', 'enableHttpPost', 'enableWs', 'enableWsReverse', - 'messagePostFormat', 'reportSelfMessage', 'enableLocalFile2Url', - 'debug', 'heartInterval', 'token', 'musicSignUrl' - ]; - for (const key of requiredKeys) { - if (!(key in config)) { - return false; + if (obj !== null && typeof obj === "object") { + Object.keys(obj).forEach((key) => { + if (typeof obj[key] === "string") { + // 如果是字符串且超过指定长度,则截断 + if (obj[key].length > maxLength) { + obj[key] = obj[key].substring(0, maxLength) + "..."; + } + } else if (typeof obj[key] === "object") { + // 如果是对象或数组,则递归调用 + truncateString(obj[key], maxLength); + } + }); } - } - if (!Array.isArray(config.httpPostUrls) || !Array.isArray(config.wsReverseUrls)) { - return false; - } - if (config.httpPostUrls.some((url: any) => typeof url !== 'string')) { - return false; - } - if (config.wsReverseUrls.some((url: any) => typeof url !== 'string')) { - return false; - } - if (typeof config.httpPort !== 'number' || typeof config.wsPort !== 'number' || typeof config.heartInterval !== 'number') { - return false; - } - if ( - typeof config.enableHttp !== 'boolean' || - typeof config.enableHttpHeart !== 'boolean' || - typeof config.enableHttpPost !== 'boolean' || - typeof config.enableWs !== 'boolean' || - typeof config.enableWsReverse !== 'boolean' || - typeof config.enableLocalFile2Url !== 'boolean' || - typeof config.reportSelfMessage !== 'boolean' - ) { - return false; - } - if (config.messagePostFormat !== 'array' && config.messagePostFormat !== 'string') { - return false; - } - return true; -} -export function migrateConfig(oldConfig: any) { - const newConfig = { - http: { - enable: oldConfig.enableHttp, - host: oldConfig.httpHost, - port: oldConfig.httpPort, - secret: oldConfig.httpSecret, - enableHeart: oldConfig.enableHttpHeart, - enablePost: oldConfig.enableHttpPost, - postUrls: oldConfig.httpPostUrls, - }, - ws: { - enable: oldConfig.enableWs, - host: oldConfig.wsHost, - port: oldConfig.wsPort, - }, - reverseWs: { - enable: oldConfig.enableWsReverse, - urls: oldConfig.wsReverseUrls, - }, - GroupLocalTime: { - Record: false, - RecordList: [] - }, - debug: oldConfig.debug, - heartInterval: oldConfig.heartInterval, - messagePostFormat: oldConfig.messagePostFormat, - enableLocalFile2Url: oldConfig.enableLocalFile2Url, - musicSignUrl: oldConfig.musicSignUrl, - reportSelfMessage: oldConfig.reportSelfMessage, - token: oldConfig.token, - - }; - return newConfig; + return obj; } export function isEqual(obj1: any, obj2: any) { - if (obj1 === obj2) return true; - if (obj1 == null || obj2 == null) return false; - if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return obj1 === obj2; + if (obj1 === obj2) return true; + if (obj1 == null || obj2 == null) return false; + if (typeof obj1 !== "object" || typeof obj2 !== "object") return obj1 === obj2; - const keys1 = Object.keys(obj1); - const keys2 = Object.keys(obj2); + const keys1 = Object.keys(obj1); + const keys2 = Object.keys(obj2); - if (keys1.length !== keys2.length) return false; + if (keys1.length !== keys2.length) return false; - for (const key of keys1) { - if (!isEqual(obj1[key], obj2[key])) return false; - } - return true; + for (const key of keys1) { + if (!isEqual(obj1[key], obj2[key])) return false; + } + return true; } export function getDefaultQQVersionConfigInfo(): QQVersionConfigType { - if (os.platform() === 'linux') { + if (os.platform() === "linux") { + return { + baseVersion: "3.2.12-26702", + curVersion: "3.2.12-26702", + prevVersion: "", + onErrorVersions: [], + buildId: "26702", + }; + } return { - baseVersion: '3.2.12-26702', - curVersion: '3.2.12-26702', - prevVersion: '', - onErrorVersions: [], - buildId: '26702' + baseVersion: "9.9.15-26702", + curVersion: "9.9.15-26702", + prevVersion: "", + onErrorVersions: [], + buildId: "26702", }; - } - return { - baseVersion: '9.9.15-26702', - curVersion: '9.9.15-26702', - prevVersion: '', - onErrorVersions: [], - buildId: '26702' - }; -} -export async function promisePipeline(promises: Promise[], callback: (result: any) => boolean): Promise { - let callbackCalled = false; - for (const promise of promises) { - if (callbackCalled) break; - try { - const result = await promise; - if (!callbackCalled) { - callbackCalled = callback(result); - } - } catch (error) { - console.error('Error in promise pipeline:', error); - } - } } -export function getQQVersionConfigPath(exePath: string = ''): string | undefined { - let configVersionInfoPath; - if (os.platform() !== 'linux') { - configVersionInfoPath = path.join(path.dirname(exePath), 'resources', 'app', 'versions', 'config.json'); - } else { - const userPath = os.homedir(); - const appDataPath = path.resolve(userPath, './.config/QQ'); - configVersionInfoPath = path.resolve(appDataPath, './versions/config.json'); - } - if (typeof configVersionInfoPath !== 'string') { - return undefined; - } - if (!fs.existsSync(configVersionInfoPath)) { - return undefined; - } - return configVersionInfoPath; +export function getQQVersionConfigPath(exePath: string = ""): string | undefined { + let configVersionInfoPath; + if (os.platform() !== "linux") { + configVersionInfoPath = path.join(path.dirname(exePath), "resources", "app", "versions", "config.json"); + } else { + const userPath = os.homedir(); + const appDataPath = path.resolve(userPath, "./.config/QQ"); + configVersionInfoPath = path.resolve(appDataPath, "./versions/config.json"); + } + if (typeof configVersionInfoPath !== "string") { + return undefined; + } + if (!fs.existsSync(configVersionInfoPath)) { + return undefined; + } + return configVersionInfoPath; } + export async function deleteOldFiles(directoryPath: string, daysThreshold: number) { - try { - const files = await fsPromise.readdir(directoryPath); + try { + const files = await fsPromise.readdir(directoryPath); - for (const file of files) { - const filePath = path.join(directoryPath, file); - const stats = await fsPromise.stat(filePath); - const lastModifiedTime = stats.mtimeMs; - const currentTime = Date.now(); - const timeDifference = currentTime - lastModifiedTime; - const daysDifference = timeDifference / (1000 * 60 * 60 * 24); + for (const file of files) { + const filePath = path.join(directoryPath, file); + const stats = await fsPromise.stat(filePath); + const lastModifiedTime = stats.mtimeMs; + const currentTime = Date.now(); + const timeDifference = currentTime - lastModifiedTime; + const daysDifference = timeDifference / (1000 * 60 * 60 * 24); - if (daysDifference > daysThreshold) { - await fsPromise.unlink(filePath); // Delete the file - //console.log(`Deleted: ${filePath}`); - } + if (daysDifference > daysThreshold) { + await fsPromise.unlink(filePath); // Delete the file + //console.log(`Deleted: ${filePath}`); + } + } + } catch (error) { + //console.error('Error deleting files:', error); } - } catch (error) { - //console.error('Error deleting files:', error); - } } diff --git a/src/common/utils/proxy-handler.ts b/src/common/utils/proxy-handler.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/common/utils/system.ts b/src/common/utils/system.ts index 18b476c8..305e35f6 100644 --- a/src/common/utils/system.ts +++ b/src/common/utils/system.ts @@ -65,7 +65,6 @@ async function getMacMachineId(): Promise { const homeDir = os.homedir(); - export const systemPlatform = os.platform(); export const cpuArch = os.arch(); export const systemVersion = os.release(); diff --git a/src/core/core.ts b/src/core/core.ts index 623ebd90..ca83a1ae 100644 --- a/src/core/core.ts +++ b/src/core/core.ts @@ -1,42 +1,49 @@ -import { logDebug } from "@/common/utils/log"; +import { LogWrapper } from "@/common/utils/log"; +import { NodeIQQNTWrapperSession, WrapperNodeApi } from "./wrapper/wrapper"; +import path from "node:path"; +import fs from "node:fs"; import { NodeIKernelLoginService } from "./services"; -import { NodeIQQNTWrapperSession } from "./wrapper/wrapper"; -export enum NCoreWorkMode { +export enum NapCatCoreWorkingEnv { Unknown = 0, Shell = 1, - LiteLoader = 2 + LiteLoader = 2, } -export class NapCatCore { - public WorkMode: NCoreWorkMode = NCoreWorkMode.Unknown; - public isInit: boolean = false; - public session: NodeIQQNTWrapperSession | undefined; - private proxyHandler = { - get(target: any, prop: any, receiver: any) { - // console.log('get', prop, typeof target[prop]); - if (typeof target[prop] === 'undefined') { - // 如果方法不存在,返回一个函数,这个函数调用existentMethod - return (...args: unknown[]) => { - logDebug(`${target.constructor.name} has no method ${prop}`); - }; - } - // 如果方法存在,正常返回 - return Reflect.get(target, prop, receiver); - } - }; - get IsInit(): boolean { - return this.isInit; + +export function loadQQWrapper(QQVersion: string): WrapperNodeApi { + let wrapperNodePath = path.resolve(path.dirname(process.execPath), './resources/app/wrapper.node'); + if (!fs.existsSync(wrapperNodePath)) { + wrapperNodePath = path.join(path.dirname(process.execPath), `resources/app/versions/${QQVersion}/wrapper.node`); } + const nativemodule: any = { exports: {} }; + process.dlopen(nativemodule, wrapperNodePath); + return nativemodule.exports; } -export class NapCatShell extends NapCatCore { - public WorkMode: NCoreWorkMode = NCoreWorkMode.Shell; - Init() { + +export class NapCatCore { + readonly workingEnv: NapCatCoreWorkingEnv; + readonly wrapper: WrapperNodeApi; + readonly session: NodeIQQNTWrapperSession; + readonly logger: LogWrapper; + readonly loginService: NodeIKernelLoginService; + + constructor( + env: NapCatCoreWorkingEnv, + wrapper: WrapperNodeApi, + session: NodeIQQNTWrapperSession, + logger: LogWrapper, + loginService: NodeIKernelLoginService, + QQVersion: string + ) { + this.workingEnv = env; + this.logger = logger; + this.wrapper = wrapper; + this.session = session; + this.loginService = loginService; + } + + // Renamed from 'InitDataListener' + initNapCatCoreListeners() { } } -export class NapCatLiteLoader extends NapCatCore { - public WorkMode: NCoreWorkMode = NCoreWorkMode.LiteLoader; - Init(WrapperSession: NodeIQQNTWrapperSession, LoginService: NodeIKernelLoginService) { - - } -} \ No newline at end of file diff --git a/src/core/index.ts b/src/core/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/core/wrapper/data.ts b/src/core/wrapper/data.ts new file mode 100644 index 00000000..36d4a9be --- /dev/null +++ b/src/core/wrapper/data.ts @@ -0,0 +1,8 @@ +import { SelfInfo } from "../entities"; + +export const selfInfo: SelfInfo = { + uid: '', + uin: '', + nick: '', + online: true +}; \ No newline at end of file diff --git a/src/core/wrapper/helper.ts b/src/core/wrapper/helper.ts new file mode 100644 index 00000000..69a4e72d --- /dev/null +++ b/src/core/wrapper/helper.ts @@ -0,0 +1,55 @@ +import path from "node:path"; +import fs from "node:fs"; +import { PlatformType, VendorType, WrapperSessionInitConfig } from "./wrapper"; +import { getMachineId, hostname, systemName, systemVersion } from "@/common/utils/system"; + +export async function genSessionConfig(QQVersionAppid: string, QQVersion: string, selfUin: string, selfUid: string, account_path: string): Promise { + const downloadPath = path.join(account_path, 'NapCat', 'temp'); + fs.mkdirSync(downloadPath, { recursive: true }); + let guid: string = await getMachineId();//26702 支持JS获取guid值 在LoginService中获取 TODO mlikiow a + const config: WrapperSessionInitConfig = { + selfUin, + selfUid, + desktopPathConfig: { + account_path // 可以通过NodeQQNTWrapperUtil().getNTUserDataInfoConfig()获取 + }, + clientVer: QQVersion, // 9.9.8-22355 + a2: '', + d2: '', + d2Key: '', + machineId: '', + platform: PlatformType.KWINDOWS, // 3是Windows? + platVer: systemVersion, // 系统版本号, 应该可以固定 + appid: QQVersionAppid, + rdeliveryConfig: { + appKey: '', + systemId: 0, + appId: '', + logicEnvironment: '', + platform: PlatformType.KWINDOWS, + language: '', + sdkVersion: '', + userId: '', + appVersion: '', + osVersion: '', + bundleId: '', + serverUrl: '', + fixedAfterHitKeys: [''] + }, + defaultFileDownloadPath: downloadPath, + deviceInfo: { + guid, + buildVer: QQVersion, + localId: 2052, + devName: hostname, + devType: systemName, + vendorName: '', + osVer: systemVersion, + vendorOsName: systemName, + setMute: false, + vendorType: VendorType.KNOSETONIOS + }, + deviceConfig: '{"appearance":{"isSplitViewMode":true},"msg":{}}' + }; + return config; +} diff --git a/src/core/wrapper/wrapper.ts b/src/core/wrapper/wrapper.ts index 863bc302..4685ae7c 100644 --- a/src/core/wrapper/wrapper.ts +++ b/src/core/wrapper/wrapper.ts @@ -1,347 +1,361 @@ -import path from 'node:path'; -import fs from 'node:fs'; +import path from "node:path"; +import fs from "node:fs"; +import { NodeIDependsAdapter, NodeIDispatcherAdapter, NodeIGlobalAdapter } from "../adapters"; import { - NodeIDependsAdapter, - NodeIDispatcherAdapter, - NodeIGlobalAdapter, -} from '../adapters'; + NodeIKernelSessionListener, + NodeIKernelMsgListener, + NodeIKernelLoginListener, + NodeIKernelBuddyListener, + NodeIKernelGroupListener, + NodeIKernelProfileListener, +} from "../listeners"; import { - NodeIKernelSessionListener, - NodeIKernelMsgListener, - NodeIKernelLoginListener, - NodeIKernelBuddyListener, - NodeIKernelGroupListener, - NodeIKernelProfileListener, -} from '../listeners'; -import { - NodeIKernelLoginService, - NodeIKernelMsgService, - NodeIKernelBuddyService, - NodeIKernelGroupService, - NodeIKernelProfileService, - NodeIKernelProfileLikeService, - NodeIKernelTicketService, - NodeIKernelTipOffService, - NodeIKernelRichMediaService, - NodeIKernelAvatarService, -} from '../services'; -import { NodeIKernelStorageCleanService } from '../services/NodeIKernelStorageCleanService'; -import { NodeIKernelRobotService } from '../services/NodeIKernelRobotService'; -import { NodeIKernelNodeMiscService } from '../services/NodeIKernelNodeMiscService'; -import { NodeIKernelUixConvertService } from '../services/NodeIKernelUixConvertService'; -import { NodeIKernelMsgBackupService } from '../services/NodeIKernelMsgBackupService'; -import { NodeIKernelAlbumService } from '../services/NodeIKernelAlbumService'; -import { NodeIKernelTianShuService } from '../services/NodeIKernelTianShuService'; -import { NodeIKernelUnitedConfigService } from '../services/NodeIKernelUnitedConfigService'; -import { NodeIKernelSearchService } from '../services/NodeIKernelSearchService'; -import { NodeIKernelCollectionService } from '../services/NodeIKernelCollectionService'; -import { NodeIKernelRecentContactService } from '../services/NodeIKernelRecentContactService'; -import { NodeIKernelMSFService } from '../services/NodeIKernelMSFService'; -import { NodeIkernelTestPerformanceService } from '../services/NodeIkernelTestPerformanceService'; -import { NodeIKernelECDHService } from '../services/NodeIKernelECDHService'; + NodeIKernelLoginService, + NodeIKernelMsgService, + NodeIKernelBuddyService, + NodeIKernelGroupService, + NodeIKernelProfileService, + NodeIKernelProfileLikeService, + NodeIKernelTicketService, + NodeIKernelTipOffService, + NodeIKernelRichMediaService, + NodeIKernelAvatarService, +} from "../services"; +import { NodeIKernelStorageCleanService } from "../services/NodeIKernelStorageCleanService"; +import { NodeIKernelRobotService } from "../services/NodeIKernelRobotService"; +import { NodeIKernelNodeMiscService } from "../services/NodeIKernelNodeMiscService"; +import { NodeIKernelUixConvertService } from "../services/NodeIKernelUixConvertService"; +import { NodeIKernelMsgBackupService } from "../services/NodeIKernelMsgBackupService"; +import { NodeIKernelAlbumService } from "../services/NodeIKernelAlbumService"; +import { NodeIKernelTianShuService } from "../services/NodeIKernelTianShuService"; +import { NodeIKernelUnitedConfigService } from "../services/NodeIKernelUnitedConfigService"; +import { NodeIKernelSearchService } from "../services/NodeIKernelSearchService"; +import { NodeIKernelCollectionService } from "../services/NodeIKernelCollectionService"; +import { NodeIKernelRecentContactService } from "../services/NodeIKernelRecentContactService"; +import { NodeIKernelMSFService } from "../services/NodeIKernelMSFService"; +import { NodeIkernelTestPerformanceService } from "../services/NodeIkernelTestPerformanceService"; +import { NodeIKernelECDHService } from "../services/NodeIKernelECDHService"; export interface NodeQQNTWrapperUtil { - // eslint-disable-next-line @typescript-eslint/no-misused-new - new(): NodeQQNTWrapperUtil + // eslint-disable-next-line @typescript-eslint/no-misused-new + new (): NodeQQNTWrapperUtil; - getNTUserDataInfoConfig(): string + getNTUserDataInfoConfig(): string; - emptyWorkingSet(n: number): void + emptyWorkingSet(n: number): void; - getSsoCmdOfOidbReq(arg1: number, arg2: number): unknown, + getSsoCmdOfOidbReq(arg1: number, arg2: number): unknown; - getSsoBufferOfOidbReq(...args: unknown[]): unknown,//有点看不懂参数定义 待补充 好像是三个参数 + getSsoBufferOfOidbReq(...args: unknown[]): unknown; //有点看不懂参数定义 待补充 好像是三个参数 - getOidbRspInfo(arg: string): unknown,//可能是错的 + getOidbRspInfo(arg: string): unknown; //可能是错的 - getFileSize(path: string): Promise,//直接的猜测 + getFileSize(path: string): Promise; //直接的猜测 - genFileMd5Buf(arg: string): unknown,//可能是错的 + genFileMd5Buf(arg: string): unknown; //可能是错的 - genFileMd5Hex(path: string): unknown,//直接的猜测 + genFileMd5Hex(path: string): unknown; //直接的猜测 - genFileShaBuf(path: string): unknown,//直接的猜测 + genFileShaBuf(path: string): unknown; //直接的猜测 - genFileCumulateSha1(path: string): unknown,//直接的猜测 + genFileCumulateSha1(path: string): unknown; //直接的猜测 - genFileShaHex(path: string): unknown,//直接的猜测 + genFileShaHex(path: string): unknown; //直接的猜测 - fileIsExist(path: string): unknown, + fileIsExist(path: string): unknown; - startTrace(path: string): unknown,//可能是错的 + startTrace(path: string): unknown; //可能是错的 - copyFile(src: string, dst: string): unknown, + copyFile(src: string, dst: string): unknown; - genFileShaAndMd5Hex(path: string, unknown: number): unknown,//可能是错的 + genFileShaAndMd5Hex(path: string, unknown: number): unknown; //可能是错的 - setTraceInfo(unknown: Object): unknown, + setTraceInfo(unknown: Object): unknown; - encodeOffLine(unknown: Object): unknown, + encodeOffLine(unknown: Object): unknown; - decodeOffLine(arg: string): unknown,//可能是错的 传递hex + decodeOffLine(arg: string): unknown; //可能是错的 传递hex - DecoderRecentInfo(arg: string): unknown,//可能是错的 传递hex + DecoderRecentInfo(arg: string): unknown; //可能是错的 传递hex - getPinyin(arg0: string, arg1: boolean): unknown, + getPinyin(arg0: string, arg1: boolean): unknown; - matchInPinyin(arg0: any[], arg1: string): unknown,//参数特复杂 arg0是个复杂数据类型 + matchInPinyin(arg0: any[], arg1: string): unknown; //参数特复杂 arg0是个复杂数据类型 - makeDirByPath(arg0: string): unknown, + makeDirByPath(arg0: string): unknown; - emptyWorkingSet(arg0: number): unknown,//参数是UINT32 + emptyWorkingSet(arg0: number): unknown; //参数是UINT32 - runProcess(arg0: string, arg1: boolean): unknown, + runProcess(arg0: string, arg1: boolean): unknown; - runProcessArgs(arg0: string, arg1: { [key: string]: string; }, arg2: boolean): unknown, + runProcessArgs(arg0: string, arg1: { [key: string]: string }, arg2: boolean): unknown; - calcThumbSize(arg0: number, arg1: number, arg2: Object): unknown, + calcThumbSize(arg0: number, arg1: number, arg2: Object): unknown; - fullWordToHalfWord(arg0: string): unknown, + fullWordToHalfWord(arg0: string): unknown; - getNTUserDataInfoConfig(): unknown, + getNTUserDataInfoConfig(): unknown; - pathIsReadableAndWriteable(path: string): unknown,//直接的猜测 + pathIsReadableAndWriteable(path: string): unknown; //直接的猜测 - resetUserDataSavePathToDocument(): unknown, + resetUserDataSavePathToDocument(): unknown; - getSoBuildInfo(): any,//例如 0[0]_d491dc01e0a_0 + getSoBuildInfo(): any; //例如 0[0]_d491dc01e0a_0 - registerCountInstruments(arg0: string, arg1: string[], arg2: number, arg3: number): unknown, + registerCountInstruments(arg0: string, arg1: string[], arg2: number, arg3: number): unknown; - registerValueInstruments(arg0: string, arg1: string[], arg2: number, arg3: number): unknown, + registerValueInstruments(arg0: string, arg1: string[], arg2: number, arg3: number): unknown; - registerValueInstrumentsWithBoundary(arg0: string, arg1: unknown, arg2: unknown, arg3: number, arg4: number): unknown, + registerValueInstrumentsWithBoundary( + arg0: string, + arg1: unknown, + arg2: unknown, + arg3: number, + arg4: number + ): unknown; - reportCountIndicators(arg0: string, arg1: Map, arg2: string, arg3: number, arg4: boolean): unknown, + reportCountIndicators( + arg0: string, + arg1: Map, + arg2: string, + arg3: number, + arg4: boolean + ): unknown; - reportValueIndicators(arg0: string, arg1: Map, arg2: string, arg3: boolean, arg4: number): unknown, + reportValueIndicators( + arg0: string, + arg1: Map, + arg2: string, + arg3: boolean, + arg4: number + ): unknown; - checkNewUserDataSaveDirAvailable(arg0: string): unknown, + checkNewUserDataSaveDirAvailable(arg0: string): unknown; - copyUserData(arg0: string, arg1: string): Promise, + copyUserData(arg0: string, arg1: string): Promise; - setUserDataSaveDirectory(arg0: string): Promise, + setUserDataSaveDirectory(arg0: string): Promise; - hasOtherRunningQQProcess(): boolean, + hasOtherRunningQQProcess(): boolean; - quitAllRunningQQProcess(arg: boolean): unknown, + quitAllRunningQQProcess(arg: boolean): unknown; - checkNvidiaConfig(): unknown, + checkNvidiaConfig(): unknown; - repairNvidiaConfig(): unknown, + repairNvidiaConfig(): unknown; - getNvidiaDriverVersion(): unknown, + getNvidiaDriverVersion(): unknown; - isNull(): unknown + isNull(): unknown; } export interface NodeIQQNTWrapperSession { - // eslint-disable-next-line @typescript-eslint/no-misused-new - new(): NodeIQQNTWrapperSession; + // eslint-disable-next-line @typescript-eslint/no-misused-new + new (): NodeIQQNTWrapperSession; - init( - wrapperSessionInitConfig: WrapperSessionInitConfig, - nodeIDependsAdapter: NodeIDependsAdapter, - nodeIDispatcherAdapter: NodeIDispatcherAdapter, - nodeIKernelSessionListener: NodeIKernelSessionListener - ): void; + init( + wrapperSessionInitConfig: WrapperSessionInitConfig, + nodeIDependsAdapter: NodeIDependsAdapter, + nodeIDispatcherAdapter: NodeIDispatcherAdapter, + nodeIKernelSessionListener: NodeIKernelSessionListener + ): void; - startNT(n: 0): void; + startNT(n: 0): void; - startNT(): void; + startNT(): void; - getBdhUploadService(): unknown; - - getECDHService(): NodeIKernelECDHService; + getBdhUploadService(): unknown; - getMsgService(): NodeIKernelMsgService; + getECDHService(): NodeIKernelECDHService; - getProfileService(): NodeIKernelProfileService; + getMsgService(): NodeIKernelMsgService; - getProfileLikeService(): NodeIKernelProfileLikeService; + getProfileService(): NodeIKernelProfileService; - getGroupService(): NodeIKernelGroupService; + getProfileLikeService(): NodeIKernelProfileLikeService; - getStorageCleanService(): NodeIKernelStorageCleanService; + getGroupService(): NodeIKernelGroupService; - getBuddyService(): NodeIKernelBuddyService; + getStorageCleanService(): NodeIKernelStorageCleanService; - getRobotService(): NodeIKernelRobotService; + getBuddyService(): NodeIKernelBuddyService; - getTicketService(): NodeIKernelTicketService; + getRobotService(): NodeIKernelRobotService; - getTipOffService(): NodeIKernelTipOffService; + getTicketService(): NodeIKernelTicketService; - getNodeMiscService(): NodeIKernelNodeMiscService; + getTipOffService(): NodeIKernelTipOffService; - getRichMediaService(): NodeIKernelRichMediaService; + getNodeMiscService(): NodeIKernelNodeMiscService; - getMsgBackupService(): NodeIKernelMsgBackupService; + getRichMediaService(): NodeIKernelRichMediaService; - getAlbumService(): NodeIKernelAlbumService; + getMsgBackupService(): NodeIKernelMsgBackupService; - getTianShuService(): NodeIKernelTianShuService; + getAlbumService(): NodeIKernelAlbumService; - getUnitedConfigService(): NodeIKernelUnitedConfigService; + getTianShuService(): NodeIKernelTianShuService; - getSearchService(): NodeIKernelSearchService; + getUnitedConfigService(): NodeIKernelUnitedConfigService; - getDirectSessionService(): unknown; + getSearchService(): NodeIKernelSearchService; - getRDeliveryService(): unknown; + getDirectSessionService(): unknown; - getAvatarService(): NodeIKernelAvatarService; + getRDeliveryService(): unknown; - getFeedChannelService(): unknown; + getAvatarService(): NodeIKernelAvatarService; - getYellowFaceService(): unknown; + getFeedChannelService(): unknown; - getCollectionService(): NodeIKernelCollectionService; + getYellowFaceService(): unknown; - getSettingService(): unknown; + getCollectionService(): NodeIKernelCollectionService; - getQiDianService(): unknown; + getSettingService(): unknown; - getFileAssistantService(): unknown; + getQiDianService(): unknown; - getGuildService(): unknown; + getFileAssistantService(): unknown; - getSkinService(): unknown; + getGuildService(): unknown; - getTestPerformanceService(): NodeIkernelTestPerformanceService; + getSkinService(): unknown; - getQQPlayService(): unknown; + getTestPerformanceService(): NodeIkernelTestPerformanceService; - getDbToolsService(): unknown; + getQQPlayService(): unknown; - getUixConvertService(): NodeIKernelUixConvertService; + getDbToolsService(): unknown; - getOnlineStatusService(): unknown; + getUixConvertService(): NodeIKernelUixConvertService; - getRemotingService(): unknown; + getOnlineStatusService(): unknown; - getGroupTabService(): unknown; + getRemotingService(): unknown; - getGroupSchoolService(): unknown; + getGroupTabService(): unknown; - getLiteBusinessService(): unknown; + getGroupSchoolService(): unknown; - getGuildMsgService(): unknown; + getLiteBusinessService(): unknown; - getLockService(): unknown; + getGuildMsgService(): unknown; - getMSFService(): NodeIKernelMSFService; + getLockService(): unknown; - getGuildHotUpdateService(): unknown; + getMSFService(): NodeIKernelMSFService; - getAVSDKService(): unknown; + getGuildHotUpdateService(): unknown; - getRecentContactService(): NodeIKernelRecentContactService; + getAVSDKService(): unknown; - getConfigMgrService(): unknown; + getRecentContactService(): NodeIKernelRecentContactService; + + getConfigMgrService(): unknown; } export interface EnginInitDesktopConfig { - base_path_prefix: string, - platform_type: 3, - app_type: 4, - app_version: string, - os_version: string, - use_xlog: true, - qua: string, - global_path_config: { - desktopGlobalPath: string, - }, - thumb_config: { maxSide: 324, minSide: 48, longLimit: 6, density: 2 } + base_path_prefix: string; + platform_type: 3; + app_type: 4; + app_version: string; + os_version: string; + use_xlog: true; + qua: string; + global_path_config: { + desktopGlobalPath: string; + }; + thumb_config: { maxSide: 324; minSide: 48; longLimit: 6; density: 2 }; } export interface NodeIQQNTWrapperEngine { - // eslint-disable-next-line @typescript-eslint/no-misused-new - new(): NodeIQQNTWrapperEngine; - initWithDeskTopConfig(config: EnginInitDesktopConfig, nodeIGlobalAdapter: NodeIGlobalAdapter): void; + // eslint-disable-next-line @typescript-eslint/no-misused-new + new (): NodeIQQNTWrapperEngine; + initWithDeskTopConfig(config: EnginInitDesktopConfig, nodeIGlobalAdapter: NodeIGlobalAdapter): void; } export interface WrapperNodeApi { - [key: string]: any; + [key: string]: any; - NodeIKernelBuddyListener: NodeIKernelBuddyListener; - NodeIKernelGroupListener: NodeIKernelGroupListener; - NodeQQNTWrapperUtil: NodeQQNTWrapperUtil; - NodeIQQNTWrapperSession: NodeIQQNTWrapperSession; - NodeIKernelMsgListener: NodeIKernelMsgListener; - NodeIQQNTWrapperEngine: NodeIQQNTWrapperEngine; - NodeIGlobalAdapter: NodeIGlobalAdapter; - NodeIDependsAdapter: NodeIDependsAdapter; - NodeIDispatcherAdapter: NodeIDispatcherAdapter; - NodeIKernelSessionListener: NodeIKernelSessionListener; - NodeIKernelLoginService: NodeIKernelLoginService; - NodeIKernelLoginListener: NodeIKernelLoginListener; - NodeIKernelProfileService: NodeIKernelProfileService; - NodeIKernelProfileListener: NodeIKernelProfileListener; + NodeIKernelBuddyListener: NodeIKernelBuddyListener; + NodeIKernelGroupListener: NodeIKernelGroupListener; + NodeQQNTWrapperUtil: NodeQQNTWrapperUtil; + NodeIQQNTWrapperSession: NodeIQQNTWrapperSession; + NodeIKernelMsgListener: NodeIKernelMsgListener; + NodeIQQNTWrapperEngine: NodeIQQNTWrapperEngine; + NodeIGlobalAdapter: NodeIGlobalAdapter; + NodeIDependsAdapter: NodeIDependsAdapter; + NodeIDispatcherAdapter: NodeIDispatcherAdapter; + NodeIKernelSessionListener: NodeIKernelSessionListener; + NodeIKernelLoginService: NodeIKernelLoginService; + NodeIKernelLoginListener: NodeIKernelLoginListener; + NodeIKernelProfileService: NodeIKernelProfileService; + NodeIKernelProfileListener: NodeIKernelProfileListener; } export enum PlatformType { - KUNKNOWN, - KANDROID, - KIOS, - KWINDOWS, - KMAC + KUNKNOWN, + KANDROID, + KIOS, + KWINDOWS, + KMAC, } export enum DeviceType { - KUNKNOWN, - KPHONE, - KPAD, - KCOMPUTER + KUNKNOWN, + KPHONE, + KPAD, + KCOMPUTER, } //推送类型 export enum VendorType { - KNOSETONIOS = 0, - KSUPPORTGOOGLEPUSH = 99, - KSUPPORTHMS = 3, - KSUPPORTOPPOPUSH = 4, - KSUPPORTTPNS = 2, - KSUPPORTVIVOPUSH = 5, - KUNSUPPORTANDROIDPUSH = 1 + KNOSETONIOS = 0, + KSUPPORTGOOGLEPUSH = 99, + KSUPPORTHMS = 3, + KSUPPORTOPPOPUSH = 4, + KSUPPORTTPNS = 2, + KSUPPORTVIVOPUSH = 5, + KUNSUPPORTANDROIDPUSH = 1, } export interface WrapperSessionInitConfig { - selfUin: string - selfUid: string - desktopPathConfig: { - account_path: string // 可以通过NodeQQNTWrapperUtil().getNTUserDataInfoConfig()获取 - } - clientVer: string // 9.9.8-22355 - a2: string, - d2: string, - d2Key: string, - machineId: string, - platform: PlatformType, // 3是Windows? - platVer: string, // 系统版本号, 应该可以固定 - appid: string, - rdeliveryConfig: { - appKey: string, - systemId: number, - appId: string, - logicEnvironment: string, - platform: PlatformType, - language: string, - sdkVersion: string, - userId: string, - appVersion: string, - osVersion: string, - bundleId: string, - serverUrl: string, - fixedAfterHitKeys: string[] - } - defaultFileDownloadPath: string, // 这个可以通过环境变量获取? - deviceInfo: { - guid: string, - buildVer: string, - localId: number, - devName: string, - devType: string, - vendorName: string, - osVer: string, - vendorOsName: string, - setMute: boolean, - vendorType: VendorType - }, - deviceConfig: '{"appearance":{"isSplitViewMode":true},"msg":{}}' -} \ No newline at end of file + selfUin: string; + selfUid: string; + desktopPathConfig: { + account_path: string; // 可以通过NodeQQNTWrapperUtil().getNTUserDataInfoConfig()获取 + }; + clientVer: string; // 9.9.8-22355 + a2: string; + d2: string; + d2Key: string; + machineId: string; + platform: PlatformType; // 3是Windows? + platVer: string; // 系统版本号, 应该可以固定 + appid: string; + rdeliveryConfig: { + appKey: string; + systemId: number; + appId: string; + logicEnvironment: string; + platform: PlatformType; + language: string; + sdkVersion: string; + userId: string; + appVersion: string; + osVersion: string; + bundleId: string; + serverUrl: string; + fixedAfterHitKeys: string[]; + }; + defaultFileDownloadPath: string; // 这个可以通过环境变量获取? + deviceInfo: { + guid: string; + buildVer: string; + localId: number; + devName: string; + devType: string; + vendorName: string; + osVer: string; + vendorOsName: string; + setMute: boolean; + vendorType: VendorType; + }; + deviceConfig: '{"appearance":{"isSplitViewMode":true},"msg":{}}'; +} diff --git a/src/liteloader/napcat.ts b/src/liteloader/napcat.ts index 83abe55d..9cdaa380 100644 --- a/src/liteloader/napcat.ts +++ b/src/liteloader/napcat.ts @@ -1,4 +1,68 @@ +import { NTEventChannel } from "@/common/framework/event"; +import { NapCatPathWrapper } from "@/common/framework/napcat"; +import { sleep } from "@/common/utils/helper"; +import { LogWrapper } from "@/common/utils/log"; +import { proxiedListenerOf } from "@/common/utils/proxy-handler"; +import { QQBasicInfoWrapper } from "@/common/utils/QQBasicInfo"; +import { NapCatCoreWorkingEnv, loadQQWrapper } from "@/core/core"; +import { LoginListener } from "@/core/listeners"; +import { NodeIKernelLoginService } from "@/core/services"; +import { selfInfo } from "@/core/wrapper/data"; +import { WrapperNodeApi, NodeIQQNTWrapperSession } from "@/core/wrapper/wrapper"; +import { NapCatOneBot11Adapter } from "@/onebot"; + //LiteLoader ES入口文件 -export async function NCoreInitLiteLoader() { +export async function NCoreInitLiteLoader(session: NodeIQQNTWrapperSession, loginService: NodeIKernelLoginService) { + //在进入本层前是否登录未进行判断 console.log("NapCat LiteLoader App Loading..."); -} \ No newline at end of file + let Basicframework = new NapCatPathWrapper(); + let logger = new LogWrapper(Basicframework.logsPath); + let BasicInfo = new QQBasicInfoWrapper({ logger }); + let LLNC = new NapCatLiteLoader(logger, session, loginService, BasicInfo); + + //直到登录成功后,执行下一步 + await new Promise((resolve) => { + let OBLoginListener = new LoginListener(); + OBLoginListener.onQRCodeLoginSucceed = async (arg) => resolve(); + loginService.addKernelLoginListener(new LLNC.wrapper.NodeIKernelLoginListener(proxiedListenerOf(OBLoginListener, logger))); + }); + //启动WebUi + + //初始化LLNC的Onebot实现 + new NapCatOneBot11Adapter(); + +} + +export class NapCatLiteLoader { + public workingEnv: NapCatCoreWorkingEnv = NapCatCoreWorkingEnv.LiteLoader; + public wrapper: WrapperNodeApi; + public EventChannel: NTEventChannel; + public session: NodeIQQNTWrapperSession; + public logger: LogWrapper; + public loginListener: LoginListener; + //public core: NapCatCore; + constructor( + logger: LogWrapper, + session: NodeIQQNTWrapperSession, + loginService: NodeIKernelLoginService, + QQBasic: QQBasicInfoWrapper + ) { + this.session = session; + this.logger = logger; + //context保存 + this.wrapper = loadQQWrapper(QQBasic.getFullQQVesion()); + //载入Wrapper.node + this.EventChannel = new NTEventChannel(this.wrapper, session); + this.loginListener = new LoginListener(); + this.loginListener.onQRCodeLoginSucceed = async (arg) => { + await sleep(2500); // TODO: 等待登录完成 init那堆不知道多久完成 搞清楚之前先用个sleep 2500顶着 + selfInfo.uin = arg.uin; + selfInfo.uid = arg.uid; + // 保存基础登录信息 + // 初始化DataListener + }; + loginService.addKernelLoginListener(new this.wrapper.NodeIKernelLoginListener( + proxiedListenerOf(this.loginListener, logger) + )); + } +} diff --git a/src/onebot/index.ts b/src/onebot/index.ts index 7f3b456c..fe25f932 100644 --- a/src/onebot/index.ts +++ b/src/onebot/index.ts @@ -1 +1,4 @@ -//OneBot实现类 \ No newline at end of file +//OneBot实现类 +export class NapCatOneBot11Adapter{ + +} \ No newline at end of file diff --git a/src/shell/napcat.ts b/src/shell/napcat.ts index 89efbce7..fc9c0827 100644 --- a/src/shell/napcat.ts +++ b/src/shell/napcat.ts @@ -1,4 +1,127 @@ +import { NapCatPathWrapper } from "@/common/framework/napcat"; +import { LogWrapper } from "@/common/utils/log"; +import { QQBasicInfoWrapper } from "@/common/utils/QQBasicInfo"; +import { hostname, systemVersion } from "@/common/utils/system"; +import { DependsAdapter, DispatcherAdapter, GlobalAdapter } from "@/core/adapters"; +import { NapCatCoreWorkingEnv, NapCatCore, loadQQWrapper } from "@/core/core"; +import { LoginListener, SessionListener } from "@/core/listeners"; +import { NodeIKernelLoginService } from "@/core/services"; +import { WrapperNodeApi, NodeIQQNTWrapperEngine, NodeQQNTWrapperUtil, NodeIQQNTWrapperSession } from "@/core/wrapper/wrapper"; +import path from "path"; +import fs from "fs"; +import os from "os"; +import { genSessionConfig } from "@/core/wrapper/helper"; +import { selfInfo } from "@/core/wrapper/data"; + // NapCat Shell App ES 入口文件 export async function NCoreInitShell() { console.log("NapCat Shell App Loading..."); -} \ No newline at end of file + let Basicframework = new NapCatPathWrapper(); + let logger = new LogWrapper(Basicframework.logsPath); + let BasicInfo = new QQBasicInfoWrapper({ logger }); + new NapCatShell(logger, BasicInfo); +} +export class NapCatShell { + public QQWrapper: WrapperNodeApi; + public WorkMode: NapCatCoreWorkingEnv = NapCatCoreWorkingEnv.Shell; + public Core: NapCatCore | undefined; + public engine: NodeIQQNTWrapperEngine; + public util: NodeQQNTWrapperUtil; + loginService: NodeIKernelLoginService; + session: NodeIQQNTWrapperSession; + loginListener: LoginListener; + + get dataPath(): string { + let result = this.util.getNTUserDataInfoConfig(); + if (!result) { + result = path.resolve(os.homedir(), './.config/QQ'); + fs.mkdirSync(result, { recursive: true }); + } + return result; + } + + get dataPathGlobal(): string { + return path.resolve(this.dataPath, './nt_qq/global'); + } + private initSession(BasicInfo: QQBasicInfoWrapper, uin: string, uid: string): Promise { + return new Promise(async (res, rej) => { + if (!BasicInfo.QQVersionAppid) throw new Error("QQVersionAppid must be provided"); + const sessionConfig = await genSessionConfig(BasicInfo.QQVersionAppid, BasicInfo.getFullQQVesion(), uin, uid, this.dataPath); + const sessionListener = new SessionListener(); + sessionListener.onSessionInitComplete = (r: unknown) => { + if ((r as number) === 0) { + return res(0); + } + rej(r); + }; + // const oldOnSendOidbRepl = this.session.onSendOidbRepl; + // this.session.onSendOidbRepl = (...args: unknown[]) => { + // console.log('onSendOidbRepl', args); + // return oldOnSendOidbRepl(...args); + // }; + this.session.init(sessionConfig, + new this.QQWrapper.NodeIDependsAdapter(new DependsAdapter()), + new this.QQWrapper.NodeIDispatcherAdapter(new DispatcherAdapter()), + new this.QQWrapper.NodeIKernelSessionListener(sessionListener) + ); + try { + this.session.startNT(0); + } catch (__) { /* Empty */ + try { + this.session.startNT(); + } catch (e) { + rej('init failed ' + e); + } + } + }); + } + constructor(logger: LogWrapper, QQBasic: QQBasicInfoWrapper) { + this.QQWrapper = loadQQWrapper(QQBasic.getFullQQVesion()); + this.engine = new this.QQWrapper.NodeIQQNTWrapperEngine(); + this.util = new this.QQWrapper.NodeQQNTWrapperUtil(); + this.loginService = new this.QQWrapper.NodeIKernelLoginService(); + this.session = new this.QQWrapper.NodeIQQNTWrapperSession(); + this.loginListener = new LoginListener(); + this.loginListener.onUserLoggedIn = (userid: string) => { + logger.logError('当前账号(' + userid + ')已登录,无法重复登录'); + }; + this.initConfig(QQBasic.getFullQQVesion(), QQBasic.QQVersionAppid, QQBasic.QQVersionQua); + this.loginListener.onQRCodeLoginSucceed = (arg) => { + this.initSession(QQBasic, arg.uin, arg.uid).then((r) => { + selfInfo.uin = arg.uin; + selfInfo.uid = arg.uid; + const dataPath = path.resolve(this.dataPath, './NapCat/data'); + fs.mkdirSync(dataPath, { recursive: true }); + logger.logDebug('本账号数据/缓存目录:', dataPath); + //this.initDataListener(); + }).catch((e) => { + logger.logError('initSession failed', e); + throw new Error(`启动失败: ${JSON.stringify(e)}`); + }); + }; + } + initConfig(QQVersion: string | undefined, QQVersionAppid: string | undefined, QQVersionQua: string | undefined) { + if (!QQVersion || !QQVersionAppid || !QQVersionQua) throw new Error('QQVersion, QQVersionAppid, QQVersionQua must be provided'); + this.engine.initWithDeskTopConfig({ + base_path_prefix: '', + platform_type: 3, + app_type: 4, + app_version: QQVersion, + os_version: 'Windows 10 Pro', + use_xlog: true, + qua: QQVersionQua, + global_path_config: { + desktopGlobalPath: this.dataPathGlobal, + }, + thumb_config: { maxSide: 324, minSide: 48, longLimit: 6, density: 2 } + }, new this.QQWrapper.NodeIGlobalAdapter(new GlobalAdapter())); + this.loginService.initConfig({ + machineId: '', + appid: QQVersionAppid, + platVer: systemVersion, + commonPath: this.dataPathGlobal, + clientVer: QQVersion, + hostName: hostname + }); + } +}