From 4704faa011789a2df9a0ab7c1adb4268a3c86ba4 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: Fri, 9 Aug 2024 14:39:06 +0800 Subject: [PATCH] chore: file utils --- src/common/utils/file.ts | 301 +++++++++++++++++++++++++ src/core/apis/file.ts | 475 ++++++++++++++++++++------------------- src/core/core.ts | 21 +- 3 files changed, 558 insertions(+), 239 deletions(-) create mode 100644 src/common/utils/file.ts diff --git a/src/common/utils/file.ts b/src/common/utils/file.ts new file mode 100644 index 00000000..360ea5a4 --- /dev/null +++ b/src/common/utils/file.ts @@ -0,0 +1,301 @@ +import fs from 'fs'; +import fsPromise, { stat } from 'fs/promises'; +import crypto from 'crypto'; +import util from 'util'; +import path from 'node:path'; +import * as fileType from 'file-type'; +import { randomUUID } from 'crypto'; +import { LogWrapper } from './log'; + + + +export function isGIF(path: string) { + const buffer = Buffer.alloc(4); + const fd = fs.openSync(path, 'r'); + fs.readSync(fd, buffer, 0, 4, 0); + fs.closeSync(fd); + return buffer.toString() === 'GIF8'; +} + +// 定义一个异步函数来检查文件是否存在 +export function checkFileReceived(path: string, timeout: number = 3000): Promise { + return new Promise((resolve, reject) => { + const startTime = Date.now(); + + function check() { + if (fs.existsSync(path)) { + resolve(); + } else if (Date.now() - startTime > timeout) { + reject(new Error(`文件不存在: ${path}`)); + } else { + setTimeout(check, 100); + } + } + + check(); + }); +} +// 定义一个异步函数来检查文件是否存在 +export async function checkFileReceived2(path: string, timeout: number = 3000): Promise { + // 使用 Promise.race 来同时进行文件状态检查和超时计时 + // Promise.race 会返回第一个解决(resolve)或拒绝(reject)的 Promise + await Promise.race([ + checkFile(path), + timeoutPromise(timeout, `文件不存在: ${path}`), + ]); +} + +// 转换超时时间至 Promise +function timeoutPromise(timeout: number, errorMsg: string): Promise { + return new Promise((_, reject) => { + setTimeout(() => { + reject(new Error(errorMsg)); + }, timeout); + }); +} + +// 异步检查文件是否存在 +async function checkFile(path: string): Promise { + try { + await stat(path); + } catch (error: any) { + if (error.code === 'ENOENT') { + // 如果文件不存在,则抛出一个错误 + throw new Error(`文件不存在: ${path}`); + } else { + // 对于 stat 调用的其他错误,重新抛出 + throw error; + } + } + // 如果文件存在,则无需做任何事情,Promise 解决(resolve)自身 +} +export async function file2base64(path: string) { + const readFile = util.promisify(fs.readFile); + const result = { + err: '', + data: '' + }; + try { + // 读取文件内容 + // if (!fs.existsSync(path)){ + // path = path.replace("\\Ori\\", "\\Thumb\\"); + // } + try { + await checkFileReceived(path, 5000); + } catch (e: any) { + result.err = e.toString(); + return result; + } + const data = await readFile(path); + // 转换为Base64编码 + result.data = data.toString('base64'); + } catch (err: any) { + result.err = err.toString(); + } + return result; +} + + +export function calculateFileMD5(filePath: string): Promise { + return new Promise((resolve, reject) => { + // 创建一个流式读取器 + const stream = fs.createReadStream(filePath); + const hash = crypto.createHash('md5'); + + stream.on('data', (data: Buffer) => { + // 当读取到数据时,更新哈希对象的状态 + hash.update(data); + }); + + stream.on('end', () => { + // 文件读取完成,计算哈希 + const md5 = hash.digest('hex'); + resolve(md5); + }); + + stream.on('error', (err: Error) => { + // 处理可能的读取错误 + reject(err); + }); + }); +} + +export interface HttpDownloadOptions { + url: string; + headers?: Record | string; +} + +export async function httpDownload(options: string | HttpDownloadOptions): Promise { + const chunks: Buffer[] = []; + let url: string; + let headers: Record = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36' + }; + if (typeof options === 'string') { + url = options; + const host = new URL(url).hostname; + headers['Host'] = host; + } else { + url = options.url; + if (options.headers) { + if (typeof options.headers === 'string') { + headers = JSON.parse(options.headers); + } else { + headers = options.headers; + } + } + } + const fetchRes = await fetch(url, { headers }).catch((err) => { + if (err.cause) { + throw err.cause; + } + throw err; + }); + if (!fetchRes.ok) throw new Error(`下载文件失败: ${fetchRes.statusText}`); + + const blob = await fetchRes.blob(); + const buffer = await blob.arrayBuffer(); + return Buffer.from(buffer); +} + +type Uri2LocalRes = { + success: boolean, + errMsg: string, + fileName: string, + ext: string, + path: string, + isLocal: boolean +} + +export async function uri2local(TempDir: string, UriOrPath: string, fileName: string | null = null): Promise { + const res = { + success: false, + errMsg: '', + fileName: '', + ext: '', + path: '', + isLocal: false + }; + if (!fileName) fileName = randomUUID(); + let filePath = path.join(TempDir, fileName);//临时目录 + let url = null; + //区分path和uri + try { + if (fs.existsSync(UriOrPath)) url = new URL('file://' + UriOrPath); + } catch (error: any) { } + try { + url = new URL(UriOrPath); + } catch (error: any) { } + + //验证url + if (!url) { + res.errMsg = `UriOrPath ${UriOrPath} 解析失败,可能${UriOrPath}不存在`; + return res; + } + + if (url.protocol == 'base64:') { + // base64转成文件 + const base64Data = UriOrPath.split('base64://')[1]; + try { + const buffer = Buffer.from(base64Data, 'base64'); + fs.writeFileSync(filePath, buffer); + } catch (e: any) { + res.errMsg = 'base64文件下载失败,' + e.toString(); + return res; + } + } else if (url.protocol == 'http:' || url.protocol == 'https:') { + // 下载文件 + let buffer: Buffer | null = null; + try { + buffer = await httpDownload(UriOrPath); + } catch (e: any) { + res.errMsg = `${url}下载失败,` + e.toString(); + return res; + } + try { + const pathInfo = path.parse(decodeURIComponent(url.pathname)); + if (pathInfo.name) { + fileName = pathInfo.name; + if (pathInfo.ext) { + fileName += pathInfo.ext; + // res.ext = pathInfo.ext + } + } + fileName = fileName.replace(/[/\\:*?"<>|]/g, '_'); + res.fileName = fileName; + filePath = path.join(TempDir, randomUUID() + fileName); + fs.writeFileSync(filePath, buffer); + } catch (e: any) { + res.errMsg = `${url}下载失败,` + e.toString(); + return res; + } + } else { + let pathname: string; + if (url.protocol === 'file:') { + // await fs.copyFile(url.pathname, filePath); + pathname = decodeURIComponent(url.pathname); + if (process.platform === 'win32') { + filePath = pathname.slice(1); + } else { + filePath = pathname; + } + } + else { + // 26702执行forword file文件操作 不应该在这里乱来 + // const cache = await dbUtil.getFileCacheByName(uri); + // if (cache) { + // filePath = cache.path; + // } else { + // filePath = uri; + // } + } + res.isLocal = true; + } + // else{ + // res.errMsg = `不支持的file协议,` + url.protocol + // return res + // } + // if (isGIF(filePath) && !res.isLocal) { + // await fs.rename(filePath, filePath + ".gif"); + // filePath += ".gif"; + // } + if (!res.isLocal && !res.ext) { + try { + const ext: string | undefined = (await fileType.fileTypeFromFile(filePath))?.ext; + if (ext) { + fs.renameSync(filePath, filePath + `.${ext}`); + filePath += `.${ext}`; + res.fileName += `.${ext}`; + res.ext = ext; + } + } catch (e) { + // log("获取文件类型失败", filePath,e.stack) + } + } + res.success = true; + res.path = filePath; + return res; +} + +export async function copyFolder(sourcePath: string, destPath: string, logger: LogWrapper) { + try { + const entries = await fsPromise.readdir(sourcePath, { withFileTypes: true }); + await fsPromise.mkdir(destPath, { recursive: true }); + for (const entry of entries) { + const srcPath = path.join(sourcePath, entry.name); + const dstPath = path.join(destPath, entry.name); + if (entry.isDirectory()) { + await copyFolder(srcPath, dstPath, logger); + } else { + try { + await fsPromise.copyFile(srcPath, dstPath); + } catch (error) { + logger.logError(`无法复制文件 '${srcPath}' 到 '${dstPath}': ${error}`); + // 这里可以决定是否要继续复制其他文件 + } + } + } + } catch (error) { + logger.logError('复制文件夹时出错:', error); + } +} diff --git a/src/core/apis/file.ts b/src/core/apis/file.ts index 70b91f18..d0dd7796 100644 --- a/src/core/apis/file.ts +++ b/src/core/apis/file.ts @@ -1,9 +1,9 @@ import { - CacheFileListItem, - CacheFileType, - ChatCacheListItemBasic, - ChatType, - ElementType, IMAGE_HTTP_HOST, IMAGE_HTTP_HOST_NT, Peer, PicElement + CacheFileListItem, + CacheFileType, + ChatCacheListItemBasic, + ChatType, + ElementType, IMAGE_HTTP_HOST, IMAGE_HTTP_HOST_NT, Peer, PicElement } from '@/core/entities'; import path from 'path'; import fs from 'fs'; @@ -14,83 +14,84 @@ import imageSize from 'image-size'; import { ISizeCalculationResult } from 'image-size/dist/types/interface'; import { NodeIKernelSearchService } from '../services/NodeIKernelSearchService'; import { RkeyManager } from '../helper/rkey'; +import { calculateFileMD5 } from '@/common/utils/file'; export class NTQQFileApi { - context: InstanceContext; - core: NapCatCore; - rkeyManager: RkeyManager; - constructor(context: InstanceContext, core: NapCatCore) { - this.context = context; - this.core = core; - this.rkeyManager = new RkeyManager('http://napcat-sign.wumiao.wang:2082/rkey', this.context.logger); - } - async getFileType(filePath: string) { - return fileType.fileTypeFromFile(filePath); - } + context: InstanceContext; + core: NapCatCore; + rkeyManager: RkeyManager; + constructor(context: InstanceContext, core: NapCatCore) { + this.context = context; + this.core = core; + this.rkeyManager = new RkeyManager('http://napcat-sign.wumiao.wang:2082/rkey', this.context.logger); + } + async getFileType(filePath: string) { + return fileType.fileTypeFromFile(filePath); + } - async copyFile(filePath: string, destPath: string) { - await this.context.wrapper.util.copyFile(filePath, destPath); - } + async copyFile(filePath: string, destPath: string) { + await this.context.wrapper.util.copyFile(filePath, destPath); + } - async getFileSize(filePath: string): Promise { - return await this.context.wrapper.util.getFileSize(filePath); - } - async getVideoUrl(peer: Peer, msgId: string, elementId: string) { - return (await this.context.session.getRichMediaService().getVideoPlayUrlV2(peer, msgId, elementId, 0, { downSourceType: 1, triggerType: 1 })).urlResult.domainUrl; - } - // 上传文件到QQ的文件夹 - async uploadFile(filePath: string, elementType: ElementType = ElementType.PIC, elementSubType: number = 0) { + async getFileSize(filePath: string): Promise { + return await this.context.wrapper.util.getFileSize(filePath); + } + async getVideoUrl(peer: Peer, msgId: string, elementId: string) { + return (await this.context.session.getRichMediaService().getVideoPlayUrlV2(peer, msgId, elementId, 0, { downSourceType: 1, triggerType: 1 })).urlResult.domainUrl; + } + // 上传文件到QQ的文件夹 + async uploadFile(filePath: string, elementType: ElementType = ElementType.PIC, elementSubType: number = 0) { // napCatCore.wrapper.util. - const fileMd5 = await calculateFileMD5(filePath); - let ext: string = (await this.getFileType(filePath))?.ext as string || ''; - if (ext) { - ext = '.' + ext; - } - let fileName = `${path.basename(filePath)}`; - if (fileName.indexOf('.') === -1) { - fileName += ext; - } - const mediaPath = this.context.session.getMsgService().getRichMediaFilePathForGuild({ - md5HexStr: fileMd5, - fileName: fileName, - elementType: elementType, - elementSubType, - thumbSize: 0, - needCreate: true, - downloadType: 1, - file_uuid: '' - }); - await this.copyFile(filePath, mediaPath!); - const fileSize = await this.getFileSize(filePath); - return { - md5: fileMd5, - fileName, - path: mediaPath, - fileSize, - ext - }; + const fileMd5 = await calculateFileMD5(filePath); + let ext: string = (await this.getFileType(filePath))?.ext as string || ''; + if (ext) { + ext = '.' + ext; } - async downloadMediaByUuid() { + let fileName = `${path.basename(filePath)}`; + if (fileName.indexOf('.') === -1) { + fileName += ext; + } + const mediaPath = this.context.session.getMsgService().getRichMediaFilePathForGuild({ + md5HexStr: fileMd5, + fileName: fileName, + elementType: elementType, + elementSubType, + thumbSize: 0, + needCreate: true, + downloadType: 1, + file_uuid: '' + }); + await this.copyFile(filePath, mediaPath!); + const fileSize = await this.getFileSize(filePath); + return { + md5: fileMd5, + fileName, + path: mediaPath, + fileSize, + ext + }; + } + async downloadMediaByUuid() { //napCatCore.session.getRichMediaService().downloadFileForFileUuid(); - } - async downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, timeout = 1000 * 60 * 2, force: boolean = false) { + } + async downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, timeout = 1000 * 60 * 2, force: boolean = false) { //logDebug('receive downloadMedia task', msgId, chatType, peerUid, elementId, thumbPath, sourcePath, timeout, force); // 用于下载收到的消息中的图片等 - if (sourcePath && fs.existsSync(sourcePath)) { - if (force) { - try { - await fsPromises.unlink(sourcePath); - } catch (e) { - // - } - } else { - return sourcePath; - } + if (sourcePath && fs.existsSync(sourcePath)) { + if (force) { + try { + await fsPromises.unlink(sourcePath); + } catch (e) { + // } - const data = await this.core.eventWrapper.CallNormalEvent< + } else { + return sourcePath; + } + } + const data = await this.core.eventWrapper.CallNormalEvent< ( - params: { + params: { fileModelId: string, downloadSourceType: number, triggerType: number, @@ -103,111 +104,111 @@ export class NTQQFileApi { filePath: string }) => Promise, (fileTransNotifyInfo: OnRichMediaDownloadCompleteParams) => void - >( - 'NodeIKernelMsgService/downloadRichMedia', - 'NodeIKernelMsgListener/onRichMediaDownloadComplete', - 1, - timeout, - (arg: OnRichMediaDownloadCompleteParams) => { - if (arg.msgId === msgId) { - return true; - } - return false; - }, - { - fileModelId: '0', - downloadSourceType: 0, - triggerType: 1, - msgId: msgId, - chatType: chatType, - peerUid: peerUid, - elementId: elementId, - thumbSize: 0, - downloadType: 1, - filePath: thumbPath - } - ); - let filePath = data[1].filePath; - if (filePath.startsWith('\\')) { - // log('filePath start with \\'); - const downloadPath = sessionConfig.defaultFileDownloadPath; - //logDebug('downloadPath', downloadPath); - filePath = path.join(downloadPath, filePath); - // 下载路径是下载文件夹的相对路径 + >( + 'NodeIKernelMsgService/downloadRichMedia', + 'NodeIKernelMsgListener/onRichMediaDownloadComplete', + 1, + timeout, + (arg: OnRichMediaDownloadCompleteParams) => { + if (arg.msgId === msgId) { + return true; } - return filePath; + return false; + }, + { + fileModelId: '0', + downloadSourceType: 0, + triggerType: 1, + msgId: msgId, + chatType: chatType, + peerUid: peerUid, + elementId: elementId, + thumbSize: 0, + downloadType: 1, + filePath: thumbPath + } + ); + let filePath = data[1].filePath; + if (filePath.startsWith('\\')) { + // log('filePath start with \\'); + const downloadPath = sessionConfig.defaultFileDownloadPath; + //logDebug('downloadPath', downloadPath); + filePath = path.join(downloadPath, filePath); + // 下载路径是下载文件夹的相对路径 } + return filePath; + } - async getImageSize(filePath: string): Promise { - return new Promise((resolve, reject) => { - imageSize(filePath, (err, dimensions) => { - if (err) { - reject(err); - } else { - resolve(dimensions); - } - }); - }); - } - async addFileCache(peer: Peer, msgId: string, msgSeq: string, senderUid: string, elemId: string, elemType: string, fileSize: string, fileName: string) { - let GroupData; - let BuddyData; - if (peer.chatType === ChatType.group) { - GroupData = - [{ - groupCode: peer.peerUid, - isConf: false, - hasModifyConfGroupFace: true, - hasModifyConfGroupName: true, - groupName: "NapCat.Cached", - remark: "NapCat.Cached" - }]; - } else if (peer.chatType === ChatType.friend) { - BuddyData = [{ - category_name: 'NapCat.Cached', - peerUid: peer.peerUid, - peerUin: peer.peerUid, - remark: 'NapCat.Cached' - }]; + async getImageSize(filePath: string): Promise { + return new Promise((resolve, reject) => { + imageSize(filePath, (err, dimensions) => { + if (err) { + reject(err); } else { - return undefined; + resolve(dimensions); } - - return this.context.session.getSearchService().addSearchHistory({ - type: 4, - contactList: [], - id: -1, - groupInfos: [], - msgs: [], - fileInfos: [ - { - chatType: peer.chatType, - buddyChatInfo: BuddyData || [], - discussChatInfo: [], - groupChatInfo: GroupData || [], - dataLineChatInfo: [], - tmpChatInfo: [], - msgId: msgId, - msgSeq: msgSeq, - msgTime: Math.floor(Date.now() / 1000).toString(), - senderUid: senderUid, - senderNick: 'NapCat.Cached', - senderRemark: 'NapCat.Cached', - senderCard: 'NapCat.Cached', - elemId: elemId, - elemType: elemType, - fileSize: fileSize, - filePath: '', - fileName: fileName, - hits: [{ - start: 12, - end: 14 - }] - } - ] - }); + }); + }); + } + async addFileCache(peer: Peer, msgId: string, msgSeq: string, senderUid: string, elemId: string, elemType: string, fileSize: string, fileName: string) { + let GroupData; + let BuddyData; + if (peer.chatType === ChatType.group) { + GroupData = + [{ + groupCode: peer.peerUid, + isConf: false, + hasModifyConfGroupFace: true, + hasModifyConfGroupName: true, + groupName: "NapCat.Cached", + remark: "NapCat.Cached" + }]; + } else if (peer.chatType === ChatType.friend) { + BuddyData = [{ + category_name: 'NapCat.Cached', + peerUid: peer.peerUid, + peerUin: peer.peerUid, + remark: 'NapCat.Cached' + }]; + } else { + return undefined; } - async searchfile(keys: string[]) { + + return this.context.session.getSearchService().addSearchHistory({ + type: 4, + contactList: [], + id: -1, + groupInfos: [], + msgs: [], + fileInfos: [ + { + chatType: peer.chatType, + buddyChatInfo: BuddyData || [], + discussChatInfo: [], + groupChatInfo: GroupData || [], + dataLineChatInfo: [], + tmpChatInfo: [], + msgId: msgId, + msgSeq: msgSeq, + msgTime: Math.floor(Date.now() / 1000).toString(), + senderUid: senderUid, + senderNick: 'NapCat.Cached', + senderRemark: 'NapCat.Cached', + senderCard: 'NapCat.Cached', + elemId: elemId, + elemType: elemType, + fileSize: fileSize, + filePath: '', + fileName: fileName, + hits: [{ + start: 12, + end: 14 + }] + } + ] + }); + } + async searchfile(keys: string[]) { type EventType = NodeIKernelSearchService['searchFileWithKeywords']; interface OnListener { searchId: string, @@ -250,98 +251,98 @@ export class NTQQFileApi { const Event = this.core.eventWrapper.createEventFunction('NodeIKernelSearchService/searchFileWithKeywords'); let id = ''; const Listener = this.core.eventWrapper.RegisterListen<(params: OnListener) => void>('NodeIKernelSearchListener/onSearchFileKeywordsResult', 1, 20000, (params) => { - if (id !== '' && params.searchId == id) { - return true; - } - return false; + if (id !== '' && params.searchId == id) { + return true; + } + return false; }); id = await Event!(keys, 12); const [ret] = (await Listener); return ret; + } + async getImageUrl(element: PicElement) { + if (!element) { + return ''; } - async getImageUrl(element: PicElement) { - if (!element) { - return ''; - } - const url: string = element.originImageUrl!; // 没有域名 - const md5HexStr = element.md5HexStr; - const fileMd5 = element.md5HexStr; - const fileUuid = element.fileUuid; + const url: string = element.originImageUrl!; // 没有域名 + const md5HexStr = element.md5HexStr; + const fileMd5 = element.md5HexStr; + const fileUuid = element.fileUuid; - if (url) { - const UrlParse = new URL(IMAGE_HTTP_HOST + url);//临时解析拼接 - const imageAppid = UrlParse.searchParams.get('appid'); - const isNewPic = imageAppid && ['1406', '1407'].includes(imageAppid); - if (isNewPic) { - let UrlRkey = UrlParse.searchParams.get('rkey'); - if (UrlRkey) { - return IMAGE_HTTP_HOST_NT + url; - } - const rkeyData = await this.rkeyManager.getRkey(); - UrlRkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey; - return IMAGE_HTTP_HOST_NT + url + `${UrlRkey}`; - } else { - // 老的图片url,不需要rkey - return IMAGE_HTTP_HOST + url; - } - } else if (fileMd5 || md5HexStr) { - // 没有url,需要自己拼接 - return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 || md5HexStr)!.toUpperCase()}/0`; + if (url) { + const UrlParse = new URL(IMAGE_HTTP_HOST + url);//临时解析拼接 + const imageAppid = UrlParse.searchParams.get('appid'); + const isNewPic = imageAppid && ['1406', '1407'].includes(imageAppid); + if (isNewPic) { + let UrlRkey = UrlParse.searchParams.get('rkey'); + if (UrlRkey) { + return IMAGE_HTTP_HOST_NT + url; } - this.context.logger.logDebug('图片url获取失败', element); - return ''; + const rkeyData = await this.rkeyManager.getRkey(); + UrlRkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey; + return IMAGE_HTTP_HOST_NT + url + `${UrlRkey}`; + } else { + // 老的图片url,不需要rkey + return IMAGE_HTTP_HOST + url; + } + } else if (fileMd5 || md5HexStr) { + // 没有url,需要自己拼接 + return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 || md5HexStr)!.toUpperCase()}/0`; } + this.context.logger.logDebug('图片url获取失败', element); + return ''; + } } export class NTQQFileCacheApi { - context: InstanceContext; - core: NapCatCore; - constructor(context: InstanceContext, core: NapCatCore) { - this.context = context; - this.core = core; - } - async setCacheSilentScan(isSilent: boolean = true) { - return ''; - } + context: InstanceContext; + core: NapCatCore; + constructor(context: InstanceContext, core: NapCatCore) { + this.context = context; + this.core = core; + } + async setCacheSilentScan(isSilent: boolean = true) { + return ''; + } - getCacheSessionPathList() { - return ''; - } + getCacheSessionPathList() { + return ''; + } - clearCache(cacheKeys: Array = ['tmp', 'hotUpdate']) { + clearCache(cacheKeys: Array = ['tmp', 'hotUpdate']) { // 参数未验证 - return this.context.session.getStorageCleanService().clearCacheDataByKeys(cacheKeys); - } + return this.context.session.getStorageCleanService().clearCacheDataByKeys(cacheKeys); + } - addCacheScannedPaths(pathMap: object = {}) { - return this.context.session.getStorageCleanService().addCacheScanedPaths(pathMap); - } + addCacheScannedPaths(pathMap: object = {}) { + return this.context.session.getStorageCleanService().addCacheScanedPaths(pathMap); + } - scanCache() { + scanCache() { //return (await this.context.session.getStorageCleanService().scanCache()).size; - } + } - getHotUpdateCachePath() { + getHotUpdateCachePath() { // 未实现 - return ''; - } + return ''; + } - getDesktopTmpPath() { + getDesktopTmpPath() { // 未实现 - return ''; - } + return ''; + } - getChatCacheList(type: ChatType, pageSize: number = 1000, pageIndex: number = 0) { - return this.context.session.getStorageCleanService().getChatCacheInfo(type, pageSize, 1, pageIndex); - } + getChatCacheList(type: ChatType, pageSize: number = 1000, pageIndex: number = 0) { + return this.context.session.getStorageCleanService().getChatCacheInfo(type, pageSize, 1, pageIndex); + } - getFileCacheInfo(fileType: CacheFileType, pageSize: number = 1000, lastRecord?: CacheFileListItem) { - const _lastRecord = lastRecord ? lastRecord : { fileType: fileType }; + getFileCacheInfo(fileType: CacheFileType, pageSize: number = 1000, lastRecord?: CacheFileListItem) { + const _lastRecord = lastRecord ? lastRecord : { fileType: fileType }; //需要五个参数 //return napCatCore.session.getStorageCleanService().getFileCacheInfo(); - } + } - async clearChatCache(chats: ChatCacheListItemBasic[] = [], fileKeys: string[] = []) { - return this.context.session.getStorageCleanService().clearChatCacheInfo(chats, fileKeys); - } + async clearChatCache(chats: ChatCacheListItemBasic[] = [], fileKeys: string[] = []) { + return this.context.session.getStorageCleanService().clearChatCacheInfo(chats, fileKeys); + } } diff --git a/src/core/core.ts b/src/core/core.ts index 79040875..5d7fbe80 100644 --- a/src/core/core.ts +++ b/src/core/core.ts @@ -9,7 +9,7 @@ import { sleep } from "@/common/utils/helper"; import { SelfInfo, LineDevice, SelfStatusInfo } from "./entities"; import { LegacyNTEventWrapper } from "@/common/framework/event-legacy"; import { NTQQFriendApi, NTQQGroupApi, NTQQMsgApi, NTQQUserApi, NTQQWebApi } from "./apis"; - +import os from "node:os"; export enum NapCatCoreWorkingEnv { Unknown = 0, Shell = 1, @@ -31,7 +31,8 @@ export class NapCatCore { readonly ApiContext: NTApiContext; readonly eventWrapper: LegacyNTEventWrapper; // readonly eventChannel: NTEventChannel; - + NapCatDataPath: string; + NapCatTempPath: string; // runtime info, not readonly selfInfo: SelfInfo; // 通过构造器递过去的 runtime info 应该尽量少 @@ -47,10 +48,26 @@ export class NapCatCore { UserApi: new NTQQUserApi(this.context, this), GroupApi: new NTQQGroupApi(this.context, this) }; + this.NapCatDataPath = path.join(this.dataPath, 'NapCat'); + fs.mkdirSync(this.NapCatDataPath, { recursive: true }); + this.NapCatTempPath = path.join(this.NapCatDataPath, 'temp'); + // 创建临时目录 + if (!fs.existsSync(this.NapCatTempPath)) { + fs.mkdirSync(this.NapCatTempPath, { recursive: true }); + } } getApiContext() { return this.ApiContext; } + get dataPath(): string { + let result = this.context.wrapper.util.getNTUserDataInfoConfig(); + if (!result) { + result = path.resolve(os.homedir(), './.config/QQ'); + fs.mkdirSync(result, { recursive: true }); + } + return result; + } + // Renamed from 'InitDataListener' async initNapCatCoreListeners() { const msgListener = new MsgListener();