diff --git a/package.json b/package.json index f7be4610..3928e3cc 100644 --- a/package.json +++ b/package.json @@ -44,13 +44,13 @@ "vite": "^5.2.6", "vite-plugin-cp": "^4.0.8", "vite-tsconfig-paths": "^5.1.0", - "winston": "^3.17.0" + "winston": "^3.17.0", + "fluent-ffmpeg": "^2.1.2" }, "dependencies": { "express": "^5.0.0", - "fluent-ffmpeg": "^2.1.2", - "qrcode-terminal": "^0.12.0", "silk-wasm": "^3.6.1", - "ws": "^8.18.0" + "ws": "^8.18.0", + "qrcode-terminal": "^0.12.0" } -} +} \ No newline at end of file diff --git a/src/common/event.ts b/src/common/event.ts index 98686a5b..821b0eb2 100644 --- a/src/common/event.ts +++ b/src/common/event.ts @@ -21,9 +21,9 @@ type FuncKeys = Extract< export type ListenerClassBase = Record; export class NTEventWrapper { - private WrapperSession: NodeIQQNTWrapperSession | undefined; //WrapperSession - private listenerManager: Map = new Map(); //ListenerName-Unique -> Listener实例 - private EventTask = new Map>>(); //tasks ListenerMainName -> ListenerSubName-> uuid -> {timeout,createtime,func} + private readonly WrapperSession: NodeIQQNTWrapperSession | undefined; //WrapperSession + private readonly listenerManager: Map = new Map(); //ListenerName-Unique -> Listener实例 + private readonly EventTask = new Map>>(); //tasks ListenerMainName -> ListenerSubName-> uuid -> {timeout,createtime,func} constructor( wrapperSession: NodeIQQNTWrapperSession, @@ -120,9 +120,9 @@ export class NTEventWrapper { ListenerType extends (...args: any) => any = EnsureFunc, >( listenerAndMethod: `${Listener}/${ListenerMethod}`, + checker: (...args: Parameters) => boolean, waitTimes = 1, timeout = 5000, - checker: (...args: Parameters) => boolean, ) { return new Promise>((resolve, reject) => { const ListenerNameList = listenerAndMethod.split('/'); @@ -181,36 +181,36 @@ export class NTEventWrapper { callbackTimesToWait = 1, timeout = 5000, ) { + const id = randomUUID(); + let complete = 0; + let retData: Parameters | undefined = undefined; + let retEvent: any = {}; + + function sendDataCallback(resolve: any, reject: any) { + if (complete == 0) { + reject( + new Error( + 'Timeout: NTEvent serviceAndMethod:' + + serviceAndMethod + + ' ListenerName:' + + listenerAndMethod + + ' EventRet:\n' + + JSON.stringify(retEvent, null, 4) + + '\n', + ), + ); + } else { + resolve([retEvent as Awaited>, ...retData!]); + } + } + + const ListenerNameList = listenerAndMethod.split('/'); + const ListenerMainName = ListenerNameList[0]; + const ListenerSubName = ListenerNameList[1]; + return new Promise<[EventRet: Awaited>, ...Parameters]>( - async (resolve, reject) => { - const id = randomUUID(); - let complete = 0; - let retData: Parameters | undefined = undefined; - let retEvent: any = {}; - - function sendDataCallback() { - if (complete == 0) { - reject( - new Error( - 'Timeout: NTEvent serviceAndMethod:' + - serviceAndMethod + - ' ListenerName:' + - listenerAndMethod + - ' EventRet:\n' + - JSON.stringify(retEvent, null, 4) + - '\n', - ), - ); - } else { - resolve([retEvent as Awaited>, ...retData!]); - } - } - - const ListenerNameList = listenerAndMethod.split('/'); - const ListenerMainName = ListenerNameList[0]; - const ListenerSubName = ListenerNameList[1]; - - const timeoutRef = setTimeout(sendDataCallback, timeout); + (resolve, reject) => { + const timeoutRef = setTimeout(() => sendDataCallback(resolve, reject), timeout); const eventCallback = { timeout: timeout, @@ -221,7 +221,7 @@ export class NTEventWrapper { retData = args as Parameters; if (complete >= callbackTimesToWait) { clearTimeout(timeoutRef); - sendDataCallback(); + sendDataCallback(resolve, reject); } }, }; @@ -233,23 +233,26 @@ export class NTEventWrapper { } this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallback); this.createListenerFunction(ListenerMainName); - const eventFunction = this.createEventFunction(serviceAndMethod); - retEvent = await eventFunction!(...(args)); - if (!checkerEvent(retEvent) && timeoutRef.hasRef()) { - clearTimeout(timeoutRef); - reject( - new Error( - 'EventChecker Failed: NTEvent serviceAndMethod:' + - serviceAndMethod + - ' ListenerName:' + - listenerAndMethod + - ' EventRet:\n' + - JSON.stringify(retEvent, null, 4) + - '\n', - ), - ); - } + this.createEventFunction(serviceAndMethod)!(...(args)) + .then((eventResult: any) => { + retEvent = eventResult; + if (!checkerEvent(retEvent) && timeoutRef.hasRef()) { + clearTimeout(timeoutRef); + reject( + new Error( + 'EventChecker Failed: NTEvent serviceAndMethod:' + + serviceAndMethod + + ' ListenerName:' + + listenerAndMethod + + ' EventRet:\n' + + JSON.stringify(retEvent, null, 4) + + '\n', + ), + ); + } + }) + .catch(reject); }, ); } diff --git a/src/common/forward-msg-builder.ts b/src/common/forward-msg-builder.ts index 55ecd65f..c091c542 100644 --- a/src/common/forward-msg-builder.ts +++ b/src/common/forward-msg-builder.ts @@ -54,11 +54,7 @@ export class ForwardMsgBuilder { const id = crypto.randomUUID(); const isGroupMsg = msg.some(m => m.isGroupMsg); if (!source) { - source = isGroupMsg ? "群聊的聊天记录" : - msg.length - ? Array.from(new Set(msg.slice(0, 4).map(m => m.senderName))) - .join('和') + '的聊天记录' - : '聊天记录'; + source = isGroupMsg ? "群聊的聊天记录" : msg.map(m => m.senderName).filter((v, i, a) => a.indexOf(v) === i).slice(0, 4).join('和') + '的聊天记录'; } if (!news) { news = msg.length === 0 ? [{ @@ -111,7 +107,7 @@ export class ForwardMsgBuilder { senderName: msg.senderName, isGroupMsg: msg.groupId !== undefined, msg: msg.msg.map(m => ({ - preview: m.valid? m.toPreview() : "[该消息类型暂不支持查看]", + preview: m.valid ? m.toPreview() : "[该消息类型暂不支持查看]", })) })), source, news, summary, prompt); } diff --git a/src/common/log.ts b/src/common/log.ts index 346baaed..569d8200 100644 --- a/src/common/log.ts +++ b/src/common/log.ts @@ -74,26 +74,30 @@ export class LogWrapper { } files.forEach(file => { const filePath = path.join(logDir, file); - fs.stat(filePath, (err, stats) => { + this.deleteOldLogFile(filePath, oneWeekAgo); + }); + }); + } + + private deleteOldLogFile(filePath: string, oneWeekAgo: number) { + fs.stat(filePath, (err, stats) => { + if (err) { + this.logger.error('Failed to get file stats', err); + return; + } + if (stats.mtime.getTime() < oneWeekAgo) { + fs.unlink(filePath, err => { if (err) { - this.logger.error('Failed to get file stats', err); - return; - } - if (stats.mtime.getTime() < oneWeekAgo) { - fs.unlink(filePath, err => { - if (err) { - if (err.code === 'ENOENT') { - this.logger.warn(`File already deleted: ${file}`); - } else { - this.logger.error('Failed to delete old log file', err); - } - } else { - this.logger.info(`Deleted old log file: ${file}`); - } - }); + if (err.code === 'ENOENT') { + this.logger.warn(`File already deleted: ${filePath}`); + } else { + this.logger.error('Failed to delete old log file', err); + } + } else { + this.logger.info(`Deleted old log file: ${filePath}`); } }); - }); + } }); } @@ -198,7 +202,7 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string { tokens.push(`群聊 [${msg.peerName}(${msg.peerUin})]`); } if (msg.senderUin !== '0') { - tokens.push(`[${msg.sendMemberName || msg.sendRemarkName || msg.sendNickName}(${msg.senderUin})]`); + tokens.push(`[${msg.sendMemberName ?? msg.sendRemarkName ?? msg.sendNickName}(${msg.senderUin})]`); } } else if (msg.chatType == ChatType.KCHATTYPEDATALINE) { tokens.push('移动设备'); @@ -206,76 +210,85 @@ export function rawMessageToText(msg: RawMessage, recursiveLevel = 0): string { tokens.push(`临时消息 (${msg.peerUin})`); } - function msgElementToText(element: MessageElement) { - if (element.textElement) { - if (element.textElement.atType === AtType.notAt) { - const originalContentLines = element.textElement.content.split('\n'); - return `${originalContentLines[0]}${originalContentLines.length > 1 ? ' ...' : ''}`; - } else if (element.textElement.atType === AtType.atAll) { - return `@全体成员`; - } else if (element.textElement.atType === AtType.atUser) { - return `${element.textElement.content} (${element.textElement.atUid})`; - } - } - - if (element.replyElement) { - const recordMsgOrNull = msg.records.find( - record => element.replyElement!.sourceMsgIdInRecords === record.msgId, - ); - return `[回复消息 ${recordMsgOrNull && - recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020' - ? - rawMessageToText(recordMsgOrNull, recursiveLevel + 1) : - `未找到消息记录 (MsgId = ${element.replyElement.sourceMsgIdInRecords})` - }]`; - } - - if (element.picElement) { - return '[图片]'; - } - - if (element.fileElement) { - return `[文件 ${element.fileElement.fileName}]`; - } - - if (element.videoElement) { - return '[视频]'; - } - - if (element.pttElement) { - return `[语音 ${element.pttElement.duration}s]`; - } - - if (element.arkElement) { - return '[卡片消息]'; - } - - if (element.faceElement) { - return `[表情 ${element.faceElement.faceText ?? ''}]`; - } - - if (element.marketFaceElement) { - return element.marketFaceElement.faceName; - } - - if (element.markdownElement) { - return '[Markdown 消息]'; - } - - if (element.multiForwardMsgElement) { - return '[转发消息]'; - } - - if (element.elementType === ElementType.GreyTip) { - return '[灰条消息]'; - } - - return `[未实现 (ElementType = ${element.elementType})]`; - } - for (const element of msg.elements) { - tokens.push(msgElementToText(element)); + tokens.push(msgElementToText(element, msg, recursiveLevel)); } return tokens.join(' '); } + +function msgElementToText(element: MessageElement, msg: RawMessage, recursiveLevel: number): string { + if (element.textElement) { + return textElementToText(element.textElement); + } + + if (element.replyElement) { + return replyElementToText(element.replyElement, msg, recursiveLevel); + } + + if (element.picElement) { + return '[图片]'; + } + + if (element.fileElement) { + return `[文件 ${element.fileElement.fileName}]`; + } + + if (element.videoElement) { + return '[视频]'; + } + + if (element.pttElement) { + return `[语音 ${element.pttElement.duration}s]`; + } + + if (element.arkElement) { + return '[卡片消息]'; + } + + if (element.faceElement) { + return `[表情 ${element.faceElement.faceText ?? ''}]`; + } + + if (element.marketFaceElement) { + return element.marketFaceElement.faceName; + } + + if (element.markdownElement) { + return '[Markdown 消息]'; + } + + if (element.multiForwardMsgElement) { + return '[转发消息]'; + } + + if (element.elementType === ElementType.GreyTip) { + return '[灰条消息]'; + } + + return `[未实现 (ElementType = ${element.elementType})]`; +} + +function textElementToText(textElement: any): string { + if (textElement.atType === AtType.notAt) { + const originalContentLines = textElement.content.split('\n'); + return `${originalContentLines[0]}${originalContentLines.length > 1 ? ' ...' : ''}`; + } else if (textElement.atType === AtType.atAll) { + return `@全体成员`; + } else if (textElement.atType === AtType.atUser) { + return `${textElement.content} (${textElement.atUid})`; + } + return ''; +} + +function replyElementToText(replyElement: any, msg: RawMessage, recursiveLevel: number): string { + const recordMsgOrNull = msg.records.find( + record => replyElement.sourceMsgIdInRecords === record.msgId, + ); + return `[回复消息 ${recordMsgOrNull && + recordMsgOrNull.peerUin != '284840486' && recordMsgOrNull.peerUin != '1094950020' + ? + rawMessageToText(recordMsgOrNull, recursiveLevel + 1) : + `未找到消息记录 (MsgId = ${replyElement.sourceMsgIdInRecords})` + }]`; +} \ No newline at end of file diff --git a/src/common/lru-cache.ts b/src/common/lru-cache.ts index 39c879f0..ba2cef1d 100644 --- a/src/common/lru-cache.ts +++ b/src/common/lru-cache.ts @@ -30,4 +30,13 @@ export class LRUCache { } this.cache.set(key, value); } + public resetCapacity(newCapacity: number): void { + this.capacity = newCapacity; + while (this.cache.size > this.capacity) { + const firstKey = this.cache.keys().next().value; + if (firstKey !== undefined) { + this.cache.delete(firstKey); + } + } + } } \ No newline at end of file diff --git a/src/common/message-unique.ts b/src/common/message-unique.ts index db980a94..d6ca43fc 100644 --- a/src/common/message-unique.ts +++ b/src/common/message-unique.ts @@ -2,8 +2,8 @@ import { Peer } from '@/core'; import crypto from 'crypto'; export class LimitedHashTable { - private keyToValue: Map = new Map(); - private valueToKey: Map = new Map(); + private readonly keyToValue: Map = new Map(); + private readonly valueToKey: Map = new Map(); private maxSize: number; constructor(maxSize: number) { @@ -75,8 +75,8 @@ export class LimitedHashTable { } class MessageUniqueWrapper { - private msgDataMap: LimitedHashTable; - private msgIdMap: LimitedHashTable; + private readonly msgDataMap: LimitedHashTable; + private readonly msgIdMap: LimitedHashTable; constructor(maxMap: number = 1000) { this.msgIdMap = new LimitedHashTable(maxMap); diff --git a/src/common/request.ts b/src/common/request.ts index f3e1cebe..344feced 100644 --- a/src/common/request.ts +++ b/src/common/request.ts @@ -9,48 +9,47 @@ export class RequestUtil { return new Promise((resolve, reject) => { const req = client.get(url, (res) => { let cookies: { [key: string]: string } = {}; - const handleRedirect = (res: http.IncomingMessage) => { - //console.log(res.headers.location); - if (res.statusCode === 301 || res.statusCode === 302) { - if (res.headers.location) { - const redirectUrl = new URL(res.headers.location, url); - RequestUtil.HttpsGetCookies(redirectUrl.href).then((redirectCookies) => { - // 合并重定向过程中的cookies - cookies = { ...cookies, ...redirectCookies }; - resolve(cookies); - }).catch((err) => { - reject(err); - }); - } else { - resolve(cookies); - } - } else { - resolve(cookies); - } - }; - res.on('data', () => { - }); // Necessary to consume the stream + + res.on('data', () => { }); // Necessary to consume the stream res.on('end', () => { - handleRedirect(res); + this.handleRedirect(res, url, cookies) + .then(resolve) + .catch(reject); }); + if (res.headers['set-cookie']) { - //console.log(res.headers['set-cookie']); - res.headers['set-cookie'].forEach((cookie) => { - const parts = cookie.split(';')[0].split('='); - const key = parts[0]; - const value = parts[1]; - if (key && value && key.length > 0 && value.length > 0) { - cookies[key] = value; - } - }); + this.extractCookies(res.headers['set-cookie'], cookies); } }); + req.on('error', (error: any) => { reject(error); }); }); } + private static async handleRedirect(res: http.IncomingMessage, url: string, cookies: { [key: string]: string }): Promise<{ [key: string]: string }> { + if (res.statusCode === 301 || res.statusCode === 302) { + if (res.headers.location) { + const redirectUrl = new URL(res.headers.location, url); + const redirectCookies = await this.HttpsGetCookies(redirectUrl.href); + // 合并重定向过程中的cookies + return { ...cookies, ...redirectCookies }; + } + } + return cookies; + } + + private static extractCookies(setCookieHeaders: string[], cookies: { [key: string]: string }) { + setCookieHeaders.forEach((cookie) => { + const parts = cookie.split(';')[0].split('='); + const key = parts[0]; + const value = parts[1]; + if (key && value && key.length > 0 && value.length > 0) { + cookies[key] = value; + } + }); + } // 请求和回复都是JSON data传原始内容 自动编码json static async HttpGetJson(url: string, method: string = 'GET', data?: any, headers: { diff --git a/src/core/apis/file.ts b/src/core/apis/file.ts index a3a34c77..652897ff 100644 --- a/src/core/apis/file.ts +++ b/src/core/apis/file.ts @@ -300,18 +300,18 @@ export class NTQQFileApi { element.elementType === ElementType.FILE ) { switch (element.elementType) { - case ElementType.PIC: + case ElementType.PIC: element.picElement!.sourcePath = elementResults[elementIndex]; - break; - case ElementType.VIDEO: + break; + case ElementType.VIDEO: element.videoElement!.filePath = elementResults[elementIndex]; - break; - case ElementType.PTT: + break; + case ElementType.PTT: element.pttElement!.filePath = elementResults[elementIndex]; - break; - case ElementType.FILE: + break; + case ElementType.FILE: element.fileElement!.filePath = elementResults[elementIndex]; - break; + break; } elementIndex++; } @@ -357,15 +357,13 @@ export class NTQQFileApi { async getImageSize(filePath: string): Promise { return new Promise((resolve, reject) => { - imageSize(filePath, (err, dimensions) => { + imageSize(filePath, (err: Error | null, dimensions) => { if (err) { - reject(err); + reject(new Error(err.message)); + } else if (!dimensions) { + reject(new Error('获取图片尺寸失败')); } else { - if (!dimensions) { - reject(new Error('获取图片尺寸失败')); - } else { - resolve(dimensions); - } + resolve(dimensions); } }); }); diff --git a/src/core/apis/friend.ts b/src/core/apis/friend.ts index b805c729..7e852dd0 100644 --- a/src/core/apis/friend.ts +++ b/src/core/apis/friend.ts @@ -15,7 +15,7 @@ export class NTQQFriendApi { } async getBuddyV2SimpleInfoMap(refresh = false) { const buddyService = this.context.session.getBuddyService(); - const buddyListV2 = refresh ? await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL); + const buddyListV2 = await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL); const uids = buddyListV2.data.flatMap(item => item.buddyUids); return await this.core.eventWrapper.callNoListenerEvent( 'NodeIKernelProfileService/getCoreAndBaseInfo', @@ -44,7 +44,7 @@ export class NTQQFriendApi { async getBuddyV2ExWithCate(refresh = false) { const categoryMap: Map = new Map(); const buddyService = this.context.session.getBuddyService(); - const buddyListV2 = refresh ? (await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)).data : (await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)).data; + const buddyListV2 = (await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL)).data; const uids = buddyListV2.flatMap(item => { item.buddyUids.forEach(uid => { categoryMap.set(uid, { categoryId: item.categoryId, categoryName: item.categroyName }); diff --git a/src/core/apis/group.ts b/src/core/apis/group.ts index 711b864e..0380186c 100644 --- a/src/core/apis/group.ts +++ b/src/core/apis/group.ts @@ -25,9 +25,10 @@ export class NTQQGroupApi { constructor(context: InstanceContext, core: NapCatCore) { this.context = context; this.core = core; - this.initCache().then().catch(context.logger.logError.bind(context.logger)); } - + async initApi() { + this.initCache().then().catch(this.context.logger.logError.bind(this.context.logger)); + } async initCache() { this.groups = await this.getGroups(); for (const group of this.groups) { @@ -54,7 +55,7 @@ export class NTQQGroupApi { }, pskey); } async getGroupShutUpMemberList(groupCode: string) { - const data = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onShutUpMemberListChanged', 1, 1000, (group_id) => group_id === groupCode); + const data = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onShutUpMemberListChanged', (group_id) => group_id === groupCode, 1, 1000); this.context.session.getGroupService().getGroupShutUpMemberList(groupCode); return (await data)[1]; } @@ -258,9 +259,9 @@ export class NTQQGroupApi { async getGroupMemberV2(GroupCode: string, uid: string, forced = false) { const Listener = this.core.eventWrapper.registerListen( 'NodeIKernelGroupListener/onMemberInfoChange', + (params, _, members) => params === GroupCode && members.size > 0, 1, forced ? 5000 : 250, - (params, _, members) => params === GroupCode && members.size > 0, ); const retData = await ( this.core.eventWrapper @@ -318,13 +319,13 @@ export class NTQQGroupApi { return undefined; } - async tryGetGroupMembersV2(modeListener = false, groupQQ: string, num = 30, timeout = 100): Promise<{ + async tryGetGroupMembersV2(groupQQ: string, modeListener = false, num = 30, timeout = 100): Promise<{ infos: Map; finish: boolean; hasNext: boolean | undefined; }> { const sceneId = this.context.session.getGroupService().createMemberListScene(groupQQ, 'groupMemberList_MainWindow_1'); - const once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', 0, timeout, (params) => params.sceneId === sceneId) + const once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', (params) => params.sceneId === sceneId, 0, timeout) .catch(() => { }); const result = await this.context.session.getGroupService().getNextMemberList(sceneId, undefined, num); if (result.errCode !== 0) { @@ -352,7 +353,7 @@ export class NTQQGroupApi { listenerMode: boolean; }> { const sceneId = this.context.session.getGroupService().createMemberListScene(groupQQ, 'groupMemberList_MainWindow_1'); - const once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', 0, timeout, (params) => params.sceneId === sceneId) + const once = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onMemberListChange', (params) => params.sceneId === sceneId, 0, timeout) .catch(() => { }); const result = await this.context.session.getGroupService().getNextMemberList(sceneId, undefined, num); if (result.errCode !== 0) { @@ -371,7 +372,7 @@ export class NTQQGroupApi { infos: new Map([...(resMode2?.infos ?? []), ...result.result.infos]), finish: result.result.finish, hasNext: resMode2?.hasNext, - listenerMode: resMode2?.hasNext !== undefined ? true : false + listenerMode: resMode2?.hasNext !== undefined }; } diff --git a/src/core/apis/msg.ts b/src/core/apis/msg.ts index 37041dd4..120d3351 100644 --- a/src/core/apis/msg.ts +++ b/src/core/apis/msg.ts @@ -144,7 +144,7 @@ export class NTQQMsgApi { params, ], () => true, - () => true, // Todo: 应当通过 groupFileListResult 判断 + () => true, // 应当通过 groupFileListResult 判断 1, 5000, ); @@ -194,7 +194,7 @@ export class NTQQMsgApi { async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) { //唉?!我有个想法 if (peer.chatType === ChatType.KCHATTYPETEMPC2CFROMGROUP && peer.guildId && peer.guildId !== '') { - const member = await this.core.apis.GroupApi.getGroupMember(peer.guildId, peer.peerUid!); + const member = await this.core.apis.GroupApi.getGroupMember(peer.guildId, peer.peerUid); if (member) { await this.PrepareTempChat(peer.peerUid, peer.guildId, member.nick); } diff --git a/src/core/apis/packet.ts b/src/core/apis/packet.ts index c8c65021..e29dcee9 100644 --- a/src/core/apis/packet.ts +++ b/src/core/apis/packet.ts @@ -26,14 +26,15 @@ export class NTQQPacketApi { this.context = context; this.core = core; this.logger = core.context.logger; - this.InitSendPacket(this.context.basicInfoWrapper.getFullQQVesion()) + } + async initApi() { + await this.InitSendPacket(this.context.basicInfoWrapper.getFullQQVesion()) .then() .catch((err) => { this.logger.logError.bind(this.core.context.logger); this.errStack.push(err); }); } - get available(): boolean { return this.pkt?.available ?? false; } diff --git a/src/core/index.ts b/src/core/index.ts index a9f11647..81ef2947 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -84,11 +84,10 @@ export function getMajorPath(QQVersion: string): string { } export class NapCatCore { readonly context: InstanceContext; - readonly apis: StableNTApiWrapper; readonly eventWrapper: NTEventWrapper; - // readonly eventChannel: NTEventChannel; - NapCatDataPath: string; - NapCatTempPath: string; + NapCatDataPath: string = ''; + NapCatTempPath: string = ''; + apis: StableNTApiWrapper; // runtime info, not readonly selfInfo: SelfInfo; util: NodeQQNTWrapperUtil; @@ -112,6 +111,8 @@ export class NapCatCore { UserApi: new NTQQUserApi(this.context, this), GroupApi: new NTQQGroupApi(this.context, this), }; + } + async initCore() { this.NapCatDataPath = path.join(this.dataPath, 'NapCat'); fs.mkdirSync(this.NapCatDataPath, { recursive: true }); this.NapCatTempPath = path.join(this.NapCatDataPath, 'temp'); @@ -119,7 +120,13 @@ export class NapCatCore { if (!fs.existsSync(this.NapCatTempPath)) { fs.mkdirSync(this.NapCatTempPath, { recursive: true }); } - + //遍历this.apis[i].initApi 如果存在该函数进行async 调用 + for (const apiKey in this.apis) { + const api = this.apis[apiKey as keyof StableNTApiWrapper]; + if ('initApi' in api && typeof api.initApi === 'function') { + await api.initApi(); + } + } this.initNapCatCoreListeners().then().catch(this.context.logger.logError.bind(this.context.logger)); this.context.logger.setFileLogEnabled( @@ -133,7 +140,6 @@ export class NapCatCore { this.configLoader.configData.consoleLogLevel as LogLevel, ); } - get dataPath(): string { let result = this.context.wrapper.NodeQQNTWrapperUtil.getNTUserDataInfoConfig(); if (!result) { diff --git a/src/core/services/NodeIKernelGroupService.ts b/src/core/services/NodeIKernelGroupService.ts index dcc3ebab..d5684800 100644 --- a/src/core/services/NodeIKernelGroupService.ts +++ b/src/core/services/NodeIKernelGroupService.ts @@ -89,7 +89,7 @@ export interface NodeIKernelGroupService { isEssenceMsg(req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise; - queryCachedEssenceMsg(req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise; + queryCachedEssenceMsg(req: { groupCode: string, msgRandom: number, msgSeq: number }): Promise<{ items: Array }>; fetchGroupEssenceList(req: { groupCode: string, diff --git a/src/core/wrapper.ts b/src/core/wrapper.ts index 63978b0c..1fd448a0 100644 --- a/src/core/wrapper.ts +++ b/src/core/wrapper.ts @@ -29,10 +29,7 @@ import { NodeIKernelECDHService } from './services/NodeIKernelECDHService'; import { NodeIO3MiscService } from './services/NodeIO3MiscService'; export interface NodeQQNTWrapperUtil { - get(): unknown; - - // eslint-disable-next-line @typescript-eslint/no-misused-new - new(): NodeQQNTWrapperUtil; + get(): NodeQQNTWrapperUtil; getNTUserDataInfoConfig(): string; diff --git a/src/framework/napcat.ts b/src/framework/napcat.ts index 9c947594..a01397ba 100644 --- a/src/framework/napcat.ts +++ b/src/framework/napcat.ts @@ -23,7 +23,7 @@ export async function NCoreInitFramework( ) { //在进入本层前是否登录未进行判断 console.log('NapCat Framework App Loading...'); - + process.on('uncaughtException', (err) => { console.log('[NapCat] [Error] Unhandled Exception:', err.message); }); @@ -55,11 +55,12 @@ export async function NCoreInitFramework( // await sleep(2500); // 初始化 NapCatFramework const loaderObject = new NapCatFramework(wrapper, session, logger, loginService, selfInfo, basicInfoWrapper, pathWrapper); + await loaderObject.core.initCore(); //启动WebUi InitWebUi(logger, pathWrapper).then().catch(logger.logError.bind(logger)); //初始化LLNC的Onebot实现 - new NapCatOneBot11Adapter(loaderObject.core, loaderObject.context, pathWrapper); + await new NapCatOneBot11Adapter(loaderObject.core, loaderObject.context, pathWrapper).InitOneBot(); } export class NapCatFramework { diff --git a/src/native/external/MoeHoo.win32.node b/src/native/external/MoeHoo.win32.node deleted file mode 100644 index 89703140..00000000 Binary files a/src/native/external/MoeHoo.win32.node and /dev/null differ diff --git a/src/onebot/index.ts b/src/onebot/index.ts index 712a1e01..1cd65819 100644 --- a/src/onebot/index.ts +++ b/src/onebot/index.ts @@ -45,8 +45,6 @@ import { OB11FriendRecallNoticeEvent } from '@/onebot/event/notice/OB11FriendRec import { OB11GroupRecallNoticeEvent } from '@/onebot/event/notice/OB11GroupRecallNoticeEvent'; import { LRUCache } from '@/common/lru-cache'; import { NodeIKernelRecentContactListener } from '@/core/listeners/NodeIKernelRecentContactListener'; -import { Native } from '@/native'; -import { decodeMessage, decodeRecallGroup } from "@/core/helper/adaptSysMessageDecoder"; import { BotOfflineEvent } from './event/notice/BotOfflineEvent'; //OneBot实现类 @@ -58,7 +56,6 @@ export class NapCatOneBot11Adapter { apis: StableOneBotApiWrapper; networkManager: OB11NetworkManager; actions: ActionMap; - nativeCore: Native | undefined; private bootTime = Date.now() / 1000; recallMsgCache = new LRUCache(100); @@ -75,41 +72,8 @@ export class NapCatOneBot11Adapter { }; this.actions = createActionMap(this, core); this.networkManager = new OB11NetworkManager(); - // this.registerNative(core, context).catch(e => this.context.logger.logWarn.bind(this.context.logger)('初始化Native失败', e)).then(); - this.InitOneBot() - .catch(e => this.context.logger.logError.bind(this.context.logger)('初始化OneBot失败', e)); + } - } - async registerNative(core: NapCatCore, context: InstanceContext) { - try { - this.nativeCore = new Native(context.pathWrapper.binaryPath); - if (!this.nativeCore.inited) throw new Error('Native Not Init'); - // this.nativeCore.registerRecallCallback(async (hex: string) => { - // try { - // const data = decodeMessage(Buffer.from(hex, 'hex')); - // //data.MsgHead.BodyInner.MsgType SubType - // const bodyInner = data.msgHead?.bodyInner; - // //context.logger.log("[appNative] Parse MsgType:" + bodyInner.msgType + " / SubType:" + bodyInner.subType); - // if (bodyInner && bodyInner.msgType == 732 && bodyInner.subType == 17 && data?.msgHead?.noifyData?.innerData) { - // const RecallData = Buffer.from(data?.msgHead?.noifyData?.innerData); - // //跳过 4字节 群号 + 不知道的1字节 +2字节 长度 - // const uid = RecallData.readUint32BE(); - // const buffer = Buffer.from(RecallData.toString('hex').slice(14), 'hex'); - // const seq: number = decodeRecallGroup(buffer).recallDetails.subDetail.msgSeq; - // const peer: Peer = { chatType: ChatType.KCHATTYPEGROUP, peerUid: uid.toString() }; - // context.logger.log("[Native] 群消息撤回 Peer: " + uid.toString() + " / MsgSeq:" + seq); - // const msgs = await core.apis.MsgApi.queryMsgsWithFilterExWithSeq(peer, seq.toString()); - // this.recallMsgCache.put(msgs.msgList[0].msgId, msgs.msgList[0]); - // } - // } catch (error: any) { - // context.logger.logWarn("[Native] Error:", (error as Error).message, ' HEX:', hex); - // } - // }); - } catch (error) { - context.logger.logWarn("[Native] Error:", (error as Error).message); - return; - } - } async InitOneBot() { const selfInfo = this.core.selfInfo; const ob11Config = this.configLoader.configData; diff --git a/src/shell/napcat.ts b/src/shell/napcat.ts index ff5f59d3..9d1a8abd 100644 --- a/src/shell/napcat.ts +++ b/src/shell/napcat.ts @@ -292,7 +292,7 @@ export async function NCoreInitShell() { fs.mkdirSync(dataPath, { recursive: true }); logger.logDebug('本账号数据/缓存目录:', accountDataPath); - new NapCatShell( + await new NapCatShell( wrapper, session, logger, @@ -300,7 +300,7 @@ export async function NCoreInitShell() { selfInfo, basicInfoWrapper, pathWrapper, - ); + ).InitNapCat(); } export class NapCatShell { @@ -327,8 +327,13 @@ export class NapCatShell { }; this.core = new NapCatCore(this.context, selfInfo); - // TODO: complete ob11 adapter initialization logic - new NapCatOneBot11Adapter(this.core, this.context, pathWrapper); + + + } + async InitNapCat() { + await this.core.initCore(); + new NapCatOneBot11Adapter(this.core, this.context, this.context.pathWrapper).InitOneBot() + .catch(e => this.context.logger.logError.bind(this.context.logger)('初始化OneBot失败', e)); } } diff --git a/vite.config.ts b/vite.config.ts index aa4c0df8..794278d3 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -4,7 +4,7 @@ import { resolve } from 'path'; import nodeResolve from '@rollup/plugin-node-resolve'; import { builtinModules } from 'module'; //依赖排除 -const external = ['silk-wasm', 'ws', 'express', 'fluent-ffmpeg', 'qrcode-terminal']; +const external = ['silk-wasm', 'ws', 'express', 'qrcode-terminal']; const nodeModules = [...builtinModules, builtinModules.map(m => `node:${m}`)].flat(); function genCpModule(module: string) { return { src: `./node_modules/${module}`, dest: `dist/node_modules/${module}`, flatten: false }; @@ -42,7 +42,6 @@ const FrameworkBaseConfigPlugin: PluginOption[] = [ 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/' },