diff --git "a/doc/\346\233\264\346\226\260\346\227\245\345\277\227.txt" "b/doc/\346\233\264\346\226\260\346\227\245\345\277\227.txt" index d6fed8249..c3f2a297d 100644 --- "a/doc/\346\233\264\346\226\260\346\227\245\345\277\227.txt" +++ "b/doc/\346\233\264\346\226\260\346\227\245\345\277\227.txt" @@ -1,3 +1,9 @@ +V7.12.14 +更新时间 2026-05-16 + +* Milky 修复 get_history_messages API 获取包含 ark 的消息时可能报错 + +================= V7.12.13 更新时间 2026-05-05 diff --git a/package-dist.json b/package-dist.json index d2073d7cb..5c994244c 100644 --- a/package-dist.json +++ b/package-dist.json @@ -1 +1 @@ -{"name":"llonebot-dist","version":"7.12.13","type":"module","description":"","main":"llbot.js","author":"linyuchen","repository":{"type":"git","url":"https://github.com/LLOneBot/LuckyLilliaBot"}} \ No newline at end of file +{"name":"llonebot-dist","version":"7.12.14","type":"module","description":"","main":"llbot.js","author":"linyuchen","repository":{"type":"git","url":"https://github.com/LLOneBot/LuckyLilliaBot"}} \ No newline at end of file diff --git a/package.json b/package.json index 3dd4444fe..be8098a72 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "dependencies": { "@cordisjs/plugin-logger": "^1.0.2", "@cordisjs/plugin-timer": "^1.1.1", - "@hono/node-server": "^2.0.0", + "@hono/node-server": "^2.0.2", "@minatojs/driver-sqlite": "^5.0.3", "@saltify/milky-types": "^1.2.2", "@saltify/typeproto": "^0.4.1", @@ -30,11 +30,11 @@ "compare-versions": "^6.1.1", "cordis": "^4.0.0-rc.4", "cosmokit": "^1.8.1", - "fast-xml-parser": "^5.7.2", + "fast-xml-parser": "^5.7.3", "file-type": "^22.0.1", "fluent-ffmpeg": "^2.1.3", "get-port": "^7.2.0", - "hono": "^4.12.15", + "hono": "^4.12.18", "image-size": "^2.0.2", "json5": "^2.2.3", "minato": "^4.0.1", @@ -55,7 +55,7 @@ "ts-case-convert": "^2.1.0", "tsx": "^4.21.0", "typescript": "^6.0.3", - "vite": "^8.0.10", + "vite": "^8.0.11", "vite-plugin-cp": "^6.0.3", "vitest": "^4.1.5" }, diff --git a/src/common/types.ts b/src/common/types.ts index 773be8478..df71bc1da 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -147,25 +147,12 @@ export interface CheckVersion { version: string } - export interface FileCache { - fileName: string - fileSize: string - msgId: string - peerUid: string - chatType: number - elementId: string - elementType: number -} - -export interface FileCacheV2 { fileName: string fileSize: string fileUuid: string - msgId: string msgTime: number - peerUid: string chatType: number - elementId: string elementType: number + md5HexStr: string } diff --git a/src/common/utils/file.ts b/src/common/utils/file.ts index 538aa0597..671db3f8c 100644 --- a/src/common/utils/file.ts +++ b/src/common/utils/file.ts @@ -7,6 +7,7 @@ import { TEMP_DIR } from '../globalVars' import { randomUUID, createHash } from 'node:crypto' import { fileURLToPath } from 'node:url' import { Context } from 'cordis' +import { ChatType, ElementType } from '@/ntqqapi/types' // 定义一个异步函数来检查文件是否存在 export function checkFileReceived(path: string, timeout: number = 3000): Promise { @@ -122,7 +123,7 @@ export async function uri2local(ctx: Context, uri: string, needExt?: boolean): P let filePath = path.join(TEMP_DIR, fileName) await fsPromise.writeFile(filePath, res.data) if (needExt) { - const ext = (await ctx.ntFileApi.getFileType(filePath)).ext + const ext = (await getFileType(filePath)).ext fileName += `.${ext}` const newPath = `${filePath}.${ext}` await fsPromise.rename(filePath, newPath) @@ -141,7 +142,7 @@ export async function uri2local(ctx: Context, uri: string, needExt?: boolean): P const base64 = uri.replace(/^base64:\/\//, '') await fsPromise.writeFile(filePath, base64, 'base64') if (needExt) { - const ext = (await ctx.ntFileApi.getFileType(filePath)).ext + const ext = (await getFileType(filePath)).ext filename += `.${ext}` await fsPromise.rename(filePath, `${filePath}.${ext}`) filePath = `${filePath}.${ext}` @@ -158,7 +159,7 @@ export async function uri2local(ctx: Context, uri: string, needExt?: boolean): P let filePath = path.join(TEMP_DIR, filename) await fsPromise.writeFile(filePath, base64, 'base64') if (needExt) { - const ext = (await ctx.ntFileApi.getFileType(filePath)).ext + const ext = (await getFileType(filePath)).ext filename += `.${ext}` await fsPromise.rename(filePath, `${filePath}.${ext}`) filePath = `${filePath}.${ext}` @@ -174,15 +175,21 @@ export async function uri2local(ctx: Context, uri: string, needExt?: boolean): P fileCache = await ctx.store.getFileCacheByName(uri) } if (fileCache?.length) { - const downloadPath = await ctx.ntFileApi.downloadMedia( - fileCache[0].msgId, - fileCache[0].chatType, - fileCache[0].peerUid, - fileCache[0].elementId, - '', - '', - ) - return { success: true, errMsg: '', fileName: fileCache[0].fileName, path: downloadPath, isLocal: true } + const isGroup = fileCache[0].chatType === ChatType.Group + let url + if (fileCache[0].elementType === ElementType.Pic) { + const originImageUrl = `/download?appid=${isGroup ? 1407 : 1406}&fileid=${fileCache[0].fileUuid}&spec=0` + url = await ctx.ntFileApi.getImageUrl(originImageUrl, fileCache[0].md5HexStr) + } else if (fileCache[0].elementType === ElementType.Video) { + url = await ctx.ntFileApi.getVideoUrl(fileCache[0].fileUuid, isGroup) + } else if (fileCache[0].elementType === ElementType.Ptt) { + url = await ctx.ntFileApi.getPttUrl(fileCache[0].fileUuid, isGroup) + } + if (url) { + return await uri2local(ctx, url, needExt) + } else { + return { success: false, errMsg: `不支持的文件类型: ${fileCache[0].elementType}`, fileName: '', path: '', isLocal: false } + } } } diff --git a/src/common/utils/misc.ts b/src/common/utils/misc.ts index 1721819bf..c1cc22414 100644 --- a/src/common/utils/misc.ts +++ b/src/common/utils/misc.ts @@ -70,3 +70,7 @@ export function uint32ToIPV4Addr(value: number) { export function sleep(ms = 0) { return new Promise((resolve) => setTimeout(resolve, ms)) } + +export function isHttpUrl(str: string) { + return /^https?:\/\/.+/.test(str) +} diff --git a/src/main/main.ts b/src/main/main.ts index eb652df37..61401b687 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -17,7 +17,6 @@ import { Context } from 'cordis' import { selfInfo, LOG_DIR, TEMP_DIR, dbDir } from '../common/globalVars' import { NTQQFileApi, - NTQQFileCacheApi, NTQQFriendApi, NTQQGroupApi, NTLoginApi, @@ -64,7 +63,6 @@ async function onLoad() { ctx.plugin(ConfigService) ctx.plugin(PMHQ) ctx.plugin(NTQQFileApi) - ctx.plugin(NTQQFileCacheApi) ctx.plugin(NTQQFriendApi) ctx.plugin(NTQQGroupApi) ctx.plugin(NTLoginApi) diff --git a/src/main/pmhq/index.ts b/src/main/pmhq/index.ts index 2a95015be..49fda600c 100644 --- a/src/main/pmhq/index.ts +++ b/src/main/pmhq/index.ts @@ -1,5 +1,5 @@ import { PMHQBase } from './base' -import { FriendMixin, GroupMixin, MediaMixin, MessageMixin, UserMixin } from './mixins' +import { FriendMixin, GroupMixin, MediaMixin, MessageMixin, SystemMixin, UserMixin } from './mixins' export type { PBData, @@ -49,7 +49,7 @@ function applyMixins PMHQBase>(Base: T) url: `https://${download.downloadDns}/ftn_handler/${download.downloadUrl.toString('hex')}/?fname=${encodeURIComponent(fileName)}` } } + + async setFriendRequest(targetUid: string, accept: number) { + const body = Oidb.SetFriendRequestReq.encode({ + targetUid, + accept, + }) + const data = Oidb.Base.encode({ + command: 0xb5d, + subCommand: 44, + body, + }) + await this.httpSendPB('OidbSvcTrpcTcp.0xb5d_44', data) + } + + async setFilteredFriendRequestReq(selfUid: string, requestUid: string) { + const body = Oidb.SetFilteredFriendRequestReq.encode({ + selfUid, + requestUid, + }) + const data = Oidb.Base.encode({ + command: 0xd72, + subCommand: 0, + body, + }) + await this.httpSendPB('OidbSvcTrpcTcp.0xd72_0', data) + } + + async fetchFriends() { + const body = Oidb.IncPullReq.encode({ + reqCount: 500, + flag: 1, + requestBiz: [{ + bizType: 1, + bizData: { + extBusi: [102, 103, 20002, 20009, 20031, 20037, 27394] + } + }] + }) + const data = Oidb.Base.encode({ + command: 0xfd4, + subCommand: 1, + body, + }) + const res = await this.httpSendPB('OidbSvcTrpcTcp.0xfd4_1', data) + const oidbRespBody = Oidb.Base.decode(Buffer.from(res.pb, 'hex')).body + return Oidb.IncPullResp.decode(oidbRespBody) + } + + async getFriendRecommendContactArk(uin: number) { + const body = Oidb.GetFriendRecommendContactArkReq.encode({ + uin, + phoneNumber: '-', + jumpUrl: `mqqapi://card/show_pslcard?src_type=internal&source=sharecard&version=1&uin=${uin}`, + }) + const data = Oidb.Base.encode({ + command: 0x12b6, + subCommand: 0, + body, + }) + const res = await this.httpSendPB('OidbSvcTrpcTcp.0x12b6_0', data) + const oidbRespBody = Oidb.Base.decode(Buffer.from(res.pb, 'hex')).body + return Oidb.GetFriendRecommendContactArkResp.decode(oidbRespBody) + } } } diff --git a/src/main/pmhq/mixins/index.ts b/src/main/pmhq/mixins/index.ts index afa7a405b..38db23fd6 100644 --- a/src/main/pmhq/mixins/index.ts +++ b/src/main/pmhq/mixins/index.ts @@ -3,3 +3,4 @@ export { GroupMixin } from './group' export { MediaMixin } from './media' export { MessageMixin } from './message' export { UserMixin } from './user' +export { SystemMixin } from './system' diff --git a/src/main/pmhq/mixins/media.ts b/src/main/pmhq/mixins/media.ts index 11347e538..70fb084ae 100644 --- a/src/main/pmhq/mixins/media.ts +++ b/src/main/pmhq/mixins/media.ts @@ -66,8 +66,7 @@ export function MediaMixin PMHQBase>(Base: T) const data = Oidb.Base.encode({ command: 0x126d, subCommand: 200, body }) const res = await this.httpSendPB('OidbSvcTrpcTcp.0x126d_200', data) const oidbRespBody = Oidb.Base.decode(Buffer.from(res.pb, 'hex')).body - const { download } = Media.NTV2RichMediaResp.decode(oidbRespBody) - return `https://${download?.info?.domain}${download?.info?.urlPath}${download?.rKeyParam}` + return Media.NTV2RichMediaResp.decode(oidbRespBody) } async getGroupPttUrl(fileUuid: string) { @@ -82,8 +81,7 @@ export function MediaMixin PMHQBase>(Base: T) const data = Oidb.Base.encode({ command: 0x126e, subCommand: 200, body }) const res = await this.httpSendPB('OidbSvcTrpcTcp.0x126e_200', data) const oidbRespBody = Oidb.Base.decode(Buffer.from(res.pb, 'hex')).body - const { download } = Media.NTV2RichMediaResp.decode(oidbRespBody) - return `https://${download!.info.domain}${download!.info.urlPath}${download!.rKeyParam}` + return Media.NTV2RichMediaResp.decode(oidbRespBody) } async getGroupVideoUrl(fileUuid: string) { @@ -98,8 +96,7 @@ export function MediaMixin PMHQBase>(Base: T) const data = Oidb.Base.encode({ command: 0x11ea, subCommand: 200, body }) const res = await this.httpSendPB('OidbSvcTrpcTcp.0x11ea_200', data) const oidbRespBody = Oidb.Base.decode(Buffer.from(res.pb, 'hex')).body - const { download } = Media.NTV2RichMediaResp.decode(oidbRespBody) - return `https://${download!.info.domain}${download!.info.urlPath}${download!.rKeyParam}` + return Media.NTV2RichMediaResp.decode(oidbRespBody) } async getPrivateVideoUrl(fileUuid: string) { @@ -114,8 +111,7 @@ export function MediaMixin PMHQBase>(Base: T) const data = Oidb.Base.encode({ command: 0x11e9, subCommand: 200, body }) const res = await this.httpSendPB('OidbSvcTrpcTcp.0x11e9_200', data) const oidbRespBody = Oidb.Base.decode(Buffer.from(res.pb, 'hex')).body - const { download } = Media.NTV2RichMediaResp.decode(oidbRespBody) - return `https://${download!.info.domain}${download!.info.urlPath}${download!.rKeyParam}` + return Media.NTV2RichMediaResp.decode(oidbRespBody) } async getHighwaySession() { @@ -284,5 +280,80 @@ export function MediaMixin PMHQBase>(Base: T) sha3CheckSum, } } + + async getGroupImageUploadInfo(groupCode: string, filePath: string) { + const peer = { + chatType: ChatType.Group, + peerUid: groupCode, + guildId: '' + } + const body = await NTV2RichMedia.buildUploadReq( + peer, + { type: 'image', filePath }, + { + pic: { + summary: '[图片]', + bytesPbReserveC2c: Buffer.from([0x08, 0x00, 0x18, 0x00, 0x20, 0x00, 0x4A, 0x00, 0x50, 0x00, 0x62, 0x00, 0x92, 0x01, 0x00, 0x9A, 0x01, 0x00, 0xAA, 0x01, 0x0C, 0x08, 0x00, 0x12, 0x00, 0x18, 0x00, 0x20, 0x00, 0x28, 0x00, 0x3A, 0x00]) + } + }, + ) + const data = Oidb.Base.encode({ command: 0x11c4, subCommand: 100, body }) + const res = await this.httpSendPB('OidbSvcTrpcTcp.0x11c4_100', data) + const oidbRespBody = Oidb.Base.decode(Buffer.from(res.pb, 'hex')).body + const { upload } = Media.NTV2RichMediaResp.decode(oidbRespBody) + return { + info: upload.msgInfo, + compat: upload.compatQMsg, + ext: NTV2RichMedia.generateExt(upload) + } + } + + async getC2CImageUploadInfo(peerUid: string, filePath: string) { + const peer = { + chatType: ChatType.C2C, + peerUid, + guildId: '' + } + const body = await NTV2RichMedia.buildUploadReq( + peer, + { type: 'image', filePath }, + { + pic: { + summary: '[图片]', + bytesPbReserveC2c: Buffer.from([0x08, 0x00, 0x18, 0x00, 0x20, 0x00, 0x4A, 0x00, 0x50, 0x00, 0x62, 0x00, 0x92, 0x01, 0x00, 0x9A, 0x01, 0x00, 0xAA, 0x01, 0x0C, 0x08, 0x00, 0x12, 0x00, 0x18, 0x00, 0x20, 0x00, 0x28, 0x00, 0x3A, 0x00]) + } + }, + ) + const data = Oidb.Base.encode({ command: 0x11c5, subCommand: 100, body }) + const res = await this.httpSendPB('OidbSvcTrpcTcp.0x11c5_100', data) + const oidbRespBody = Oidb.Base.decode(Buffer.from(res.pb, 'hex')).body + const { upload } = Media.NTV2RichMediaResp.decode(oidbRespBody) + return { + info: upload.msgInfo, + compat: upload.compatQMsg, + ext: NTV2RichMedia.generateExt(upload) + } + } + + async imageOcr(imageUrl: string) { + const body = Oidb.ImageOcrReq.encode({ + version: 1, + client: 0, + entrance: 1, + ocrReqBody: { + imageUrl, + originMd5: '', + afterCompressMd5: '', + afterCompressFileSize: '', + afterCompressWeight: '', + afterCompressHeight: '', + isCut: false + } + }) + const data = Oidb.Base.encode({ command: 0xe07, subCommand: 0, body }) + const res = await this.httpSendPB('OidbSvcTrpcTcp.0xe07_0', data) + const oidbRespBody = Oidb.Base.decode(Buffer.from(res.pb, 'hex')).body + return Oidb.ImageOcrResp.decode(oidbRespBody) + } } } diff --git a/src/main/pmhq/mixins/system.ts b/src/main/pmhq/mixins/system.ts new file mode 100644 index 000000000..4b095a67a --- /dev/null +++ b/src/main/pmhq/mixins/system.ts @@ -0,0 +1,17 @@ +import { Oidb } from '@/ntqqapi/proto' +import type { PMHQBase } from '../base' + +export function SystemMixin PMHQBase>(Base: T) { + return class extends Base { + async fetchPins() { + const data = Oidb.Base.encode({ + command: 0x12b3, + subCommand: 0, + body: Buffer.alloc(0), + }) + const res = await this.httpSendPB('OidbSvcTrpcTcp.0x12b3_0', data) + const oidbRespBody = Oidb.Base.decode(Buffer.from(res.pb, 'hex')).body + return Oidb.FetchPinsResp.decode(oidbRespBody) + } + } +} diff --git a/src/main/store.ts b/src/main/store.ts index 0d859b938..d0f6d84ce 100644 --- a/src/main/store.ts +++ b/src/main/store.ts @@ -1,7 +1,7 @@ import { Peer, RawMessage } from '@/ntqqapi/types' import { createHash } from 'node:crypto' import { BidiMap } from '@/common/utils/table' -import { FileCacheV2 } from '@/common/types' +import { FileCache } from '@/common/types' import { Context, Service } from 'cordis' import { noop } from 'cosmokit' @@ -20,7 +20,7 @@ declare module 'minato' { chatType: number peerUid: string } - file_v2: FileCacheV2 + file: FileCache forward: { rootMsgId: string parentMsgId: string @@ -73,16 +73,14 @@ class Store extends Service { }, { primary: 'shortId' }) - this.ctx.model.extend('file_v2', { + this.ctx.model.extend('file', { fileName: 'string', fileSize: 'string', fileUuid: 'string(128)', - msgId: 'string(24)', msgTime: 'unsigned(10)', - peerUid: 'string(24)', chatType: 'unsigned', - elementId: 'string(24)', elementType: 'unsigned', + md5HexStr: 'string(32)' }, { primary: 'fileUuid', indexes: ['fileName'] @@ -183,24 +181,24 @@ class Store extends Service { return this.cache.getValue(cacheKey) } - async addFileCache(data: FileCacheV2) { + async addFileCache(data: FileCache) { // 判断 fileUuid 是否存在 - const existingFile = await this.ctx.database.get('file_v2', { fileUuid: data.fileUuid }) + const existingFile = await this.ctx.database.get('file', { fileUuid: data.fileUuid }) if (existingFile.length) { return existingFile } - this.ctx.database.upsert('file_v2', [data], 'fileUuid').then() + this.ctx.database.upsert('file', [data], 'fileUuid').then() .catch(e => this.ctx.logger.error('addFileCache database error:', e)) } getFileCacheByName(fileName: string) { - return this.ctx.database.get('file_v2', { fileName }, { + return this.ctx.database.get('file', { fileName }, { sort: { msgTime: 'desc' } }) } getFileCacheById(fileUuid: string) { - return this.ctx.database.get('file_v2', { fileUuid }) + return this.ctx.database.get('file', { fileUuid }) } async addMsgCache(msg: RawMessage) { diff --git a/src/milky/api/friend.ts b/src/milky/api/friend.ts index 41392b8aa..c6e6414df 100644 --- a/src/milky/api/friend.ts +++ b/src/milky/api/friend.ts @@ -119,14 +119,10 @@ const AcceptFriendRequest = defineApi( AcceptFriendRequestInput, z.object({}), async (ctx, payload) => { - let result: GeneralCallResult if (payload.is_filtered) { - result = await ctx.ntFriendApi.approvalDoubtBuddyReq(payload.initiator_uid) + await ctx.ntFriendApi.approvalDoubtFriendRequest(payload.initiator_uid) } else { - result = await ctx.ntFriendApi.handleFriendRequest(payload.initiator_uid, '0', true) - } - if (result.result !== 0) { - return Failed(-500, result.errMsg) + await ctx.ntFriendApi.approvalFriendRequest(payload.initiator_uid, true) } return Ok({}) } @@ -138,10 +134,7 @@ const RejectFriendRequest = defineApi( z.object({}), async (ctx, payload) => { if (!payload.is_filtered) { - const result = await ctx.ntFriendApi.handleFriendRequest(payload.initiator_uid, '0', false) - if (result.result !== 0) { - return Failed(-500, result.errMsg) - } + await ctx.ntFriendApi.approvalFriendRequest(payload.initiator_uid, false) } return Ok({}) } diff --git a/src/milky/api/message.ts b/src/milky/api/message.ts index ef4050059..0004cbe03 100644 --- a/src/milky/api/message.ts +++ b/src/milky/api/message.ts @@ -32,7 +32,7 @@ const SendPrivateMessage = defineApi( return Failed(-404, 'User not found') } const peer = { chatType: 1, peerUid: uid, guildId: '' } - const isBuddy = await ctx.ntFriendApi.isBuddy(uid) + const isBuddy = await ctx.ntFriendApi.isFriend(uid) if (!isBuddy) { const result = await ctx.ntMsgApi.getTempChatInfo(100, uid) if (result.tmpChatInfo.groupCode) { @@ -98,7 +98,7 @@ const RecallPrivateMessage = defineApi( return Failed(-404, 'User not found') } const peer = { chatType: 1, peerUid: uid, guildId: '' } - const isBuddy = await ctx.ntFriendApi.isBuddy(uid) + const isBuddy = await ctx.ntFriendApi.isFriend(uid) if (!isBuddy) { const result = await ctx.ntMsgApi.getTempChatInfo(100, uid) if (result.tmpChatInfo.groupCode) { @@ -289,7 +289,7 @@ const GetResourceTempUrl = defineApi( const url = `${IMAGE_HTTP_HOST_NT}/download?appid=${appid}&fileid=${payload.resource_id}&spec=0${rkey}` return Ok({ url }) } else if (appid === 1413 || appid === 1415) { - const url = await ctx.ntFileApi.getVideoUrlByPacket(payload.resource_id, appid === 1415) + const url = await ctx.ntFileApi.getVideoUrl(payload.resource_id, appid === 1415) return Ok({ url }) } else { ctx.logger.warn(`GetResourceTempUrl: not yet supported appid: ${appid}`) diff --git a/src/milky/api/system.ts b/src/milky/api/system.ts index c8541fe38..916c7e3da 100644 --- a/src/milky/api/system.ts +++ b/src/milky/api/system.ts @@ -100,23 +100,11 @@ const GetFriendList = defineApi( 'get_friend_list', GetFriendListInput, GetFriendListOutput, - async (ctx) => { - const friends = await ctx.ntFriendApi.getBuddyList() - const category: Map = new Map() + async (ctx, payload) => { + const result = await ctx.ntFriendApi.getFriendList(payload.no_cache) const friendList = [] - for (const friend of friends) { - const { categoryId } = friend.baseInfo - if (!category.has(categoryId)) { - category.set(categoryId, await ctx.ntFriendApi.getCategoryById(categoryId)) - } - friendList.push(transformFriend(friend, category.get(categoryId)!)) + for (const friend of result.friends) { + friendList.push(transformFriend(friend, result.categories.get(friend.categoryId)!)) } return Ok({ friends: friendList, @@ -129,14 +117,12 @@ const GetFriendInfo = defineApi( GetFriendInfoInput, GetFriendInfoOutput, async (ctx, payload) => { - const uid = await ctx.ntUserApi.getUidByUin(payload.user_id.toString()) - if (!uid) { - return Failed(-404, 'User not found') + const result = await ctx.ntFriendApi.getFriendInfoByUin(payload.user_id, payload.no_cache) + if (!result) { + return Failed(-404, 'Friend not found') } - const friend = await ctx.ntUserApi.getUserSimpleInfo(uid, payload.no_cache) - const category = await ctx.ntFriendApi.getCategoryById(friend.baseInfo.categoryId) return Ok({ - friend: transformFriend(friend, category), + friend: transformFriend(result.friend, result.category), }) } ) @@ -245,39 +231,20 @@ const GetPeerPins = defineApi( z.object({}), GetPeerPinsOutput, async (ctx) => { - const friends = await ctx.ntFriendApi.getBuddyList() - const category: Map = new Map() - const { groups } = await ctx.pmhq.fetchGroups() + const result = await ctx.ntSystemApi.getPins() return Ok({ friends: await Promise.all( - friends.filter(e => e.relationFlags && e.relationFlags.topTime !== '0').map(async e => { - const { categoryId } = e.baseInfo - if (!category.has(categoryId)) { - category.set(categoryId, await ctx.ntFriendApi.getCategoryById(categoryId)) - } - return transformFriend(e, category.get(categoryId)!) + result.friends.map(async (e) => { + const info = await ctx.ntFriendApi.getFriendInfoByUid(e.uid, false) + return transformFriend(info!.friend, info!.category) }) ), - groups: groups.filter(e => e.info.topTime).map(e => { - return { - group_id: e.groupCode, - group_name: e.info.groupName, - member_count: e.info.memberCount, - max_member_count: e.info.memberMax, - remark: e.customInfo.remark ?? '', - created_time: e.info.createdTime, - description: e.info.richDescription ?? '', - question: e.info.question ?? '', - announcement: e.info.announcement ?? '' - } - }) + groups: await Promise.all( + result.groups.map(async (e) => { + const info = await ctx.ntGroupApi.getGroupDetailInfo(e.groupCode.toString()) + return transformGroup(info) + }) + ) }) } ) diff --git a/src/milky/transform/entity.ts b/src/milky/transform/entity.ts index 960b42818..94795e038 100644 --- a/src/milky/transform/entity.ts +++ b/src/milky/transform/entity.ts @@ -1,5 +1,5 @@ import { FriendEntity, GroupEntity, GroupFileEntity, GroupFolderEntity, GroupMemberEntity } from '@saltify/milky-types' -import { CategoryFriend, GroupDetailInfo, Sex, SimpleInfo } from '@/ntqqapi/types' +import { Category, Friend, GroupDetailInfo, Sex } from '@/ntqqapi/types' import { GroupMember } from '@/ntqqapi/types' import { InferProtoModel } from '@saltify/typeproto' import { Oidb } from '@/ntqqapi/proto' @@ -10,16 +10,19 @@ export function transformGender(gender: Sex): 'male' | 'female' | 'unknown' { return 'unknown' } -export function transformFriend(friend: SimpleInfo, category: CategoryFriend): FriendEntity { +export function transformFriend( + friend: Friend, + category: Category +): FriendEntity { return { - user_id: +friend.uin, - nickname: friend.coreInfo.nick, - sex: transformGender(friend.baseInfo.sex), - qid: friend.baseInfo.qid, - remark: friend.coreInfo.remark, + user_id: friend.uin, + nickname: friend.nick, + sex: transformGender(friend.sex), + qid: friend.qid, + remark: friend.remark, category: { category_id: category.categoryId, - category_name: category.categroyName, + category_name: category.categoryName, }, } } diff --git a/src/milky/transform/message/incoming.ts b/src/milky/transform/message/incoming.ts index 14c825385..2ec6586e5 100644 --- a/src/milky/transform/message/incoming.ts +++ b/src/milky/transform/message/incoming.ts @@ -21,7 +21,25 @@ export async function transformIncomingPrivateMessage( sender_id: +message.senderUin, time: +message.msgTime, segments: await transformIncomingSegments(ctx, message), - friend: transformFriend(friend, category), + friend: transformFriend({ + uid: friend.uid, + uin: +friend.uin, + categoryId: friend.baseInfo.categoryId, + nick: friend.coreInfo.nick, + longNick: friend.baseInfo.longNick, + remark: friend.coreInfo.remark, + qid: friend.baseInfo.qid, + age: friend.baseInfo.age, + sex: friend.baseInfo.sex, + birthdayYear: friend.baseInfo.birthday_year, + birthdayMonth: friend.baseInfo.birthday_month, + birthdayDay: friend.baseInfo.birthday_day, + }, { + categoryId: category.categoryId, + categoryName: category.categroyName, + categoryMemberCount: category.categroyMbCount, + categorySortId: category.categorySortId, + }), } } @@ -115,7 +133,7 @@ export async function transformIncomingSegments(ctx: Context, message: RawMessag type: 'image', data: { resource_id: element.picElement!.fileUuid, - temp_url: await ctx.ntFileApi.getImageUrl(element.picElement!), + temp_url: await ctx.ntFileApi.getImageUrl(element.picElement!.originImageUrl, element.picElement!.md5HexStr), width: element.picElement!.picWidth, height: element.picElement!.picHeight, summary: element.picElement!.summary || '[图片]', @@ -140,7 +158,7 @@ export async function transformIncomingSegments(ctx: Context, message: RawMessag type: 'video', data: { resource_id: element.videoElement!.fileUuid, - temp_url: await ctx.ntFileApi.getVideoUrlByPacket(element.videoElement!.fileUuid, message.chatType === ChatType.Group), + temp_url: await ctx.ntFileApi.getVideoUrl(element.videoElement!.fileUuid, message.chatType === ChatType.Group), width: element.videoElement!.thumbWidth, height: element.videoElement!.thumbHeight, duration: element.videoElement!.fileTime, @@ -189,25 +207,28 @@ export async function transformIncomingSegments(ctx: Context, message: RawMessag case ElementType.Ark: { const { arkElement } = element - const data = JSON.parse(arkElement!.bytesData) - if (data.app === 'com.tencent.multimsg' && data.meta.detail.resid) { - segments.push({ - type: 'forward', - data: { - forward_id: data.meta.detail.resid, - title: data.meta.detail.source, - preview: data.meta.detail.news.map((item: { text: string }) => item.text), - summary: data.meta.detail.summary, - }, - }) - } else { - segments.push({ - type: 'light_app', - data: { - app_name: data.app, - json_payload: arkElement!.bytesData, - }, - }) + const match = arkElement!.bytesData.match(/"app"\s*:\s*"([^"]*)"/) + if (match?.[1]) { + if (match[1] === 'com.tencent.multimsg') { + const data = JSON.parse(arkElement!.bytesData) + segments.push({ + type: 'forward', + data: { + forward_id: data.meta.detail.resid, + title: data.meta.detail.source, + preview: data.meta.detail.news.map((item: { text: string }) => item.text), + summary: data.meta.detail.summary, + }, + }) + } else { + segments.push({ + type: 'light_app', + data: { + app_name: match[1], + json_payload: arkElement!.bytesData, + }, + }) + } } break } @@ -260,7 +281,7 @@ export async function transformIncomingForwardedMessage(ctx: Context, message: I } else if (serviceType === 48 && (businessType === 11 || businessType === 21)) { const { msgInfoBody } = Media.MsgInfo.decode(elem.commonElem.pbElem) const { index } = msgInfoBody[0] - const url = await ctx.ntFileApi.getVideoUrlByPacket(index.fileUuid, businessType === 21) + const url = await ctx.ntFileApi.getVideoUrl(index.fileUuid, businessType === 21) segments.push({ type: 'video', data: { diff --git a/src/milky/transform/message/outgoing.ts b/src/milky/transform/message/outgoing.ts index 8d97df9c2..c0c342623 100644 --- a/src/milky/transform/message/outgoing.ts +++ b/src/milky/transform/message/outgoing.ts @@ -225,54 +225,11 @@ class ForwardMessageEncoder { this.preview = '' } - async packImage(data: RichMediaUploadCompleteNotify, busiType: number) { - const imageSize = await this.ctx.ntFileApi.getImageSize(data.filePath) + async packImage(msgInfo: InferProtoModelInput) { return { commonElem: { serviceType: 48, - pbElem: Media.MsgInfo.encode({ - msgInfoBody: [{ - index: { - info: { - fileSize: +data.commonFileInfo.fileSize, - md5HexStr: data.commonFileInfo.md5, - sha1HexStr: data.commonFileInfo.sha, - fileName: data.commonFileInfo.fileName, - fileType: { - type: 1, - picFormat: imageSize.type === 'gif' ? 2000 : 1000 - }, - width: imageSize.width, - height: imageSize.height, - time: 0, - original: 1 - }, - fileUuid: data.fileId, - storeID: 1, - expire: this.isGroup ? 2678400 : 157680000 - }, - pic: { - urlPath: `/download?appid=${this.isGroup ? 1407 : 1406}&fileid=${data.fileId}`, - ext: { - originalParam: '&spec=0', - bigParam: '&spec=720', - thumbParam: '&spec=198' - }, - domain: 'multimedia.nt.qq.com.cn' - }, - fileExist: true - }], - extBizInfo: { - pic: { - bizType: 0, - summary: '', - fromScene: this.isGroup ? 2 : 1, // 怀旧版 PCQQ 私聊收图需要 - toScene: this.isGroup ? 2 : 1, // 怀旧版 PCQQ 私聊收图需要 - oldFileId: this.isGroup ? 574859779 : undefined // 怀旧版 PCQQ 群聊收图需要 - }, - busiType - } - }), + pbElem: Media.MsgInfo.encode(msgInfo), businessType: this.isGroup ? 20 : 10 } } @@ -353,9 +310,14 @@ class ForwardMessageEncoder { const imageBuffer = await resolveMilkyUri(segment.data.uri) const tempPath = path.join(TEMP_DIR, `image-${randomUUID()}`) await writeFile(tempPath, imageBuffer) - const data = await this.ctx.ntFileApi.uploadRMFileWithoutMsg(tempPath, this.isGroup ? RMBizType.GroupPic : RMBizType.C2CPic, this.isGroup ? this.peerUid : selfInfo.uid) + let data + if (this.isGroup) { + data = await this.ctx.ntFileApi.uploadGroupImage(this.peerUid, tempPath) + } else { + data = await this.ctx.ntFileApi.uploadC2CImage(this.peerUid, tempPath) + } const busiType = segment.data.sub_type === 'sticker' ? 1 : 0 - this.children.push(await this.packImage(data, busiType)) + this.children.push(await this.packImage(data.msgInfo)) this.preview += busiType === 1 ? '[动画表情]' : '[图片]' unlink(tempPath).catch(noop) } else if (type === 'forward') { diff --git a/src/ntqqapi/api/file.ts b/src/ntqqapi/api/file.ts index 12525fc4b..43b8cb755 100644 --- a/src/ntqqapi/api/file.ts +++ b/src/ntqqapi/api/file.ts @@ -1,25 +1,14 @@ import { NTMethod } from '../ntcall' -import { GeneralCallResult } from '../services' import { - CacheFileList, - CacheFileListItem, - CacheFileType, - CacheScanResult, - ChatCacheListItemBasic, - ChatType, ElementType, IMAGE_HTTP_HOST, IMAGE_HTTP_HOST_NT, - PicElement, } from '../types' import path from 'node:path' -import { createReadStream, existsSync } from 'node:fs' -import { ReceiveCmdS } from '../hook' +import { createReadStream } from 'node:fs' import { RkeyManager } from '@/ntqqapi/helper/rkey' -import { RichMediaDownloadCompleteNotify, RichMediaUploadCompleteNotify, RMBizType, Peer } from '@/ntqqapi/types/msg' -import { calculateSha1StreamBytes, getFileType, getImageSize, getMd5HexFromFile } from '@/common/utils/file' -import { copyFile, unlink } from 'node:fs/promises' -import { noop, Time } from 'cosmokit' +import { calculateSha1StreamBytes, getFileType, getMd5HexFromFile } from '@/common/utils/file' +import { copyFile } from 'node:fs/promises' import { Service, Context } from 'cordis' import { selfInfo } from '@/common/globalVars' import { FlashFileListItem, FlashFileSetInfo } from '@/ntqqapi/types/flashfile' @@ -29,7 +18,6 @@ import { Media } from '../proto' declare module 'cordis' { interface Context { ntFileApi: NTQQFileApi - ntFileCacheApi: NTQQFileCacheApi } } @@ -43,48 +31,26 @@ export class NTQQFileApi extends Service { this.rkeyManager = new RkeyManager(ctx, 'https://llob.linyuchen.net/rkey') } - async getVideoUrlByPacket(fileUuid: string, isGroup: boolean) { + async getVideoUrl(fileUuid: string, isGroup: boolean) { if (isGroup) { - return await this.ctx.pmhq.getGroupVideoUrl(fileUuid) + const { download } = await this.ctx.pmhq.getGroupVideoUrl(fileUuid) + return `https://${download!.info.domain}${download!.info.urlPath}${download!.rKeyParam}` } else { - return await this.ctx.pmhq.getPrivateVideoUrl(fileUuid) + const { download } = await this.ctx.pmhq.getPrivateVideoUrl(fileUuid) + return `https://${download!.info.domain}${download!.info.urlPath}${download!.rKeyParam}` } } async getPttUrl(fileUuid: string, isGroup: boolean) { if (isGroup) { - return await this.ctx.pmhq.getGroupPttUrl(fileUuid) + const { download } = await this.ctx.pmhq.getGroupPttUrl(fileUuid) + return `https://${download!.info.domain}${download!.info.urlPath}${download!.rKeyParam}` } else { - return await this.ctx.pmhq.getPrivatePttUrl(fileUuid) + const { download } = await this.ctx.pmhq.getPrivatePttUrl(fileUuid) + return `https://${download!.info.domain}${download!.info.urlPath}${download!.rKeyParam}` } } - async getVideoUrl(peer: Peer, msgId: string, elementId: string) { - try { - const data = await this.ctx.pmhq.invoke('nodeIKernelRichMediaService/getVideoPlayUrlV2', [ - peer, - msgId, - elementId, - 0, // video code format, 0: H264, 1: H265 ? - { - downSourceType: 1, - triggerType: 0, // 是否下载到本地 - }, - ]) - if (data.result !== 0) { - this.ctx.logger.warn('getVideoUrl', data) - } - return data.urlResult.domainUrl[0]?.url ?? '' - } catch (e) { - this.ctx.logger.warn('getVideoUrl error', e) - return '' - } - } - - async getFileType(filePath: string) { - return await getFileType(filePath) - } - async getRichMediaFilePath(md5HexStr: string, fileName: string, elementType: ElementType, elementSubType = 0) { return await this.ctx.pmhq.invoke(NTMethod.MEDIA_FILE_PATH, [ { @@ -105,7 +71,7 @@ export class NTQQFileApi extends Service { const fileMd5 = await getMd5HexFromFile(filePath) let fileName = path.basename(filePath) if (!fileName.includes('.')) { - const ext = (await this.getFileType(filePath))?.ext + const ext = (await getFileType(filePath))?.ext fileName += ext ? '.' + ext : '' } const mediaPath = await this.getRichMediaFilePath(fileMd5, fileName, elementType, elementSubType) @@ -117,59 +83,8 @@ export class NTQQFileApi extends Service { } } - async downloadMedia( - msgId: string, - chatType: ChatType, - peerUid: string, - elementId: string, - thumbPath = '', - sourcePath = '', - timeout = 1000 * 60 * 30, - force = false, - ) { - // 用于下载收到的消息中的图片等 - if (sourcePath && existsSync(sourcePath)) { - if (force) { - unlink(sourcePath).catch(noop) - } else { - return sourcePath - } - } - const data = await this.ctx.pmhq.invoke( - 'nodeIKernelMsgService/downloadRichMedia', - [{ - fileModelId: '0', - downloadSourceType: 0, - triggerType: 1, - msgId: msgId, - chatType: chatType, - peerUid: peerUid, - elementId: elementId, - thumbSize: 0, - downloadType: 1, - filePath: thumbPath, - }], - { - resultCmd: ReceiveCmdS.MEDIA_DOWNLOAD_COMPLETE, - resultCb: payload => payload.msgId === msgId, - timeout, - }, - ) - return data.filePath - } - - async getImageSize(filePath: string): Promise<{ type: string, width: number, height: number }> { - const fileType = await getFileType(filePath) - const size = await getImageSize(filePath) - return { - type: fileType.ext, - ...size, - } - } - - async getImageUrl(element: PicElement) { - const url = element.originImageUrl // 没有域名 - const md5HexStr = element.md5HexStr + async getImageUrl(originImageUrl: string, md5HexStr: string) { + const url = originImageUrl // 没有域名 if (url) { const parsedUrl = new URL(IMAGE_HTTP_HOST + url) //临时解析拼接 @@ -195,53 +110,12 @@ export class NTQQFileApi extends Service { } } - async downloadFileForModelId(peer: Peer, fileModelId: string, timeout = 2 * Time.minute) { - const data = await this.ctx.pmhq.invoke( - 'nodeIKernelRichMediaService/downloadFileForModelId', - [ - peer, - [fileModelId], - '', // savePath - ], - { - resultCmd: ReceiveCmdS.MEDIA_DOWNLOAD_COMPLETE, - resultCb: payload => payload.fileModelId === fileModelId, - timeout, - }, - ) - return data.filePath - } - - async ocrImage(path: string) { - return await this.ctx.pmhq.invoke( - 'nodeIKernelNodeMiscService/wantWinScreenOCR', - [ - path, - ], - { - timeout: 2 * Time.minute, - }, - ) - } - - async uploadRMFileWithoutMsg(filePath: string, bizType: RMBizType, peerUid: string) { - const data = await this.ctx.pmhq.invoke( - 'nodeIKernelRichMediaService/uploadRMFileWithoutMsg', - [ - { - filePath, - bizType, - peerUid, - useNTV2: true, - }, - ], - { - resultCmd: ReceiveCmdS.MEDIA_UPLOAD_COMPLETE, - resultCb: payload => payload.filePath === filePath, - timeout: 20 * Time.second, - }, - ) - return data + async ocrImage(imageUrl: string) { + const res = await this.ctx.pmhq.imageOcr(imageUrl) + if (res.retCode) { + throw new Error(res.wording) + } + return res.ocrRspBody } async uploadFlashFile(title: string, filePaths: string[]) { @@ -398,6 +272,7 @@ export class NTQQFileApi extends Service { } ]) } + async uploadGroupVideo(groupCode: string, filePath: string, thumbPath: string) { const result = await this.ctx.pmhq.getGroupVideoUploadInfo(groupCode, filePath, thumbPath) const highwaySession = await this.ctx.pmhq.getHighwaySession() @@ -627,56 +502,62 @@ export class NTQQFileApi extends Service { crcMedia: result.crcMedia } } -} - -export class NTQQFileCacheApi extends Service { - static inject = ['pmhq'] - - constructor(protected ctx: Context) { - super(ctx, 'ntFileCacheApi') - } - - async setCacheSilentScan(isSilent: boolean = true) { - return await this.ctx.pmhq.invoke(NTMethod.CACHE_SET_SILENCE, [{ isSilent }]) - } - - getCacheSessionPathList() { - // return invoke>(NTMethod.CACHE_PATH_SESSION, []) - } - scanCache() { - this.ctx.pmhq.invoke(ReceiveCmdS.CACHE_SCAN_FINISH, []) - return this.ctx.pmhq.invoke(NTMethod.CACHE_SCAN, [], { timeout: 300 * Time.second }) - } - - getHotUpdateCachePath() { - // return invoke(NTMethod.CACHE_PATH_HOT_UPDATE, []) - } - - getDesktopTmpPath() { - // return invoke(NTMethod.CACHE_PATH_DESKTOP_TEMP, []) - } - - getFileCacheInfo(fileType: CacheFileType, pageSize: number = 1000, lastRecord?: CacheFileListItem) { - const _lastRecord = lastRecord ? lastRecord : { fileType: fileType } - - return this.ctx.pmhq.invoke(NTMethod.CACHE_FILE_GET, [{ - fileType: fileType, - restart: true, - pageSize: pageSize, - order: 1, - lastRecord: _lastRecord, - }]) + async uploadGroupImage(groupCode: string, filePath: string) { + const result = await this.ctx.pmhq.getGroupImageUploadInfo(groupCode, filePath) + const highwaySession = await this.ctx.pmhq.getHighwaySession() + const maxBlockSize = 1024 * 1024 + if (result.ext.uKey) { + const { index } = result.ext.msgInfoBody[0] + const trans = { + uin: selfInfo.uin, + cmd: 1004, + readable: createReadStream(filePath, { highWaterMark: maxBlockSize }), + sum: Buffer.from(index.info.md5HexStr, 'hex'), + size: index.info.fileSize, + ticket: highwaySession.sigSession, + ext: Media.NTV2RichMediaHighwayExt.encode(result.ext), + server: highwaySession.highwayHostAndPorts[1][0].host, + port: highwaySession.highwayHostAndPorts[1][0].port + } + try { + await new HighwayTcpSession(trans).upload() + } catch { + await new HighwayHttpSession(trans).upload() + } + } + return { + msgInfo: result.info, + compat: result.compat + } } - async clearChatCache(chats: ChatCacheListItemBasic[] = [], fileKeys: string[] = []) { - return await this.ctx.pmhq.invoke(NTMethod.CACHE_CHAT_CLEAR, [{ - chats, - fileKeys, - }]) + async uploadC2CImage(peerUid: string, filePath: string) { + const result = await this.ctx.pmhq.getC2CImageUploadInfo(peerUid, filePath) + const highwaySession = await this.ctx.pmhq.getHighwaySession() + const maxBlockSize = 1024 * 1024 + if (result.ext.uKey) { + const { index } = result.ext.msgInfoBody[0] + const trans = { + uin: selfInfo.uin, + cmd: 1003, + readable: createReadStream(filePath, { highWaterMark: maxBlockSize }), + sum: Buffer.from(index.info.md5HexStr, 'hex'), + size: index.info.fileSize, + ticket: highwaySession.sigSession, + ext: Media.NTV2RichMediaHighwayExt.encode(result.ext), + server: highwaySession.highwayHostAndPorts[1][0].host, + port: highwaySession.highwayHostAndPorts[1][0].port + } + try { + await new HighwayTcpSession(trans).upload() + } catch { + await new HighwayHttpSession(trans).upload() + } + } + return { + msgInfo: result.info, + compat: result.compat + } } } - diff --git a/src/ntqqapi/api/friend.ts b/src/ntqqapi/api/friend.ts index 5c366e531..d9cec34a0 100644 --- a/src/ntqqapi/api/friend.ts +++ b/src/ntqqapi/api/friend.ts @@ -1,7 +1,7 @@ -import { SimpleInfo } from '../types' -import { NTMethod } from '../ntcall' +import { Category, Friend } from '../types' import { Context, Service } from 'cordis' import { GeneralCallResult } from '../services' +import { selfInfo } from '@/common/globalVars' declare module 'cordis' { interface Context { @@ -11,59 +11,93 @@ declare module 'cordis' { export class NTQQFriendApi extends Service { static inject = ['ntUserApi', 'ntSystemApi', 'pmhq'] + friendsCache: Friend[] = [] + categoriesCache: Map = new Map() constructor(protected ctx: Context) { super(ctx, 'ntFriendApi') } - /** reqTime 可为 0 */ - async handleFriendRequest(friendUid: string, reqTime: string, accept: boolean) { - return await this.ctx.pmhq.invoke(NTMethod.HANDLE_FRIEND_REQUEST, [{ - friendUid, - reqTime, - accept, - }, - ]) + async approvalFriendRequest(friendUid: string, accept: boolean) { + await this.ctx.pmhq.setFriendRequest(friendUid, accept ? 3 : 5) + } + + async getFriendList(forceUpdate: boolean) { + if (forceUpdate || this.friendsCache.length === 0) { + const res = await this.ctx.pmhq.fetchFriends() + this.friendsCache = res.friendList.map(friend => { + const biz = friend.subBiz.get(1)! + return { + uid: friend.uid, + uin: friend.uin, + categoryId: friend.categoryId, + nick: biz.data.get(20002)!.toString(), + longNick: biz.data.get(102)!.toString(), + remark: biz.data.get(103)!.toString(), + qid: biz.data.get(27394)!.toString(), + age: biz.numData.get(20037)!, + sex: biz.numData.get(20009)!, + birthdayYear: (biz.data.get(20031)![0] << 8) | biz.data.get(20031)![1], + birthdayMonth: biz.data.get(20031)![2], + birthdayDay: biz.data.get(20031)![3], + } + }) + this.categoriesCache.clear() + for (const cat of res.category) { + this.categoriesCache.set(cat.categoryId, cat) + } + } + return { + friends: this.friendsCache, + categories: this.categoriesCache + } } - async getBuddyList(): Promise { - const data = await this.ctx.pmhq.invoke( - 'getBuddyList', - [], - {}, - ) - return data - } - - async getBuddyV2(forceRefresh: boolean) { - const deviceInfo = await this.ctx.ntSystemApi.getDeviceInfo() - const version = +deviceInfo.buildVer.split('-')[1] - let result - if (version >= 41679) { - result = await this.ctx.pmhq.invoke('nodeIKernelBuddyService/getBuddyListV2', ['', forceRefresh, 0]) - } else { - result = await this.ctx.pmhq.invoke('nodeIKernelBuddyService/getBuddyListV2', [forceRefresh, 0]) + async getFriendInfoByUin(uin: number, forceUpdate: boolean) { + const result = await this.getFriendList(forceUpdate) + let categories = result.categories + let friend = result.friends.find(e => e.uin === uin) + if (!friend) { + const result = await this.getFriendList(true) + categories = result.categories + friend = result.friends.find(e => e.uin === uin) } + if (!friend) { + return + } + const category = categories.get(friend.categoryId)! + return { + friend, + category + } + } - return result + async getFriendInfoByUid(uid: string, forceUpdate: boolean) { + const result = await this.getFriendList(forceUpdate) + let categories = result.categories + let friend = result.friends.find(e => e.uid === uid) + if (!friend) { + const result = await this.getFriendList(true) + categories = result.categories + friend = result.friends.find(e => e.uid === uid) + } + if (!friend) { + return + } + const category = categories.get(friend.categoryId)! + return { + friend, + category + } } - async isBuddy(uid: string): Promise { - return await this.ctx.pmhq.invoke('nodeIKernelBuddyService/isBuddy', [uid]) + async isFriend(uid: string): Promise { + return (await this.getFriendInfoByUid(uid, false)) !== undefined } - async getBuddyRecommendContact(uin: string) { - const ret = await this.ctx.pmhq.invoke('nodeIKernelBuddyService/getBuddyRecommendContactArkJson', [uin, '-']) - return ret.arkMsg + async getFriendRecommendContactArk(uin: number) { + const { ark } = await this.ctx.pmhq.getFriendRecommendContactArk(uin) + return ark } async setBuddyRemark(uid: string, remark = '') { @@ -100,8 +134,8 @@ export class NTQQFriendApi extends Service { ) } - async approvalDoubtBuddyReq(uid: string) { - return await this.ctx.pmhq.invoke('nodeIKernelBuddyService/approvalDoubtBuddyReq', [uid, '', '']) + async approvalDoubtFriendRequest(requestUid: string) { + return await this.ctx.pmhq.setFilteredFriendRequestReq(selfInfo.uid, requestUid) } async getBuddyReq() { diff --git a/src/ntqqapi/api/system.ts b/src/ntqqapi/api/system.ts index 14034f538..479dff1c7 100644 --- a/src/ntqqapi/api/system.ts +++ b/src/ntqqapi/api/system.ts @@ -42,7 +42,11 @@ export class NTQQSystemApi extends Service { }>('getDeviceInfo', []) } - async scanQRCode(path: string){ + async scanQRCode(path: string) { return await this.ctx.pmhq.invoke('nodeIKernelNodeMiscService/scanQBar', [path]) } + + async getPins() { + return await this.ctx.pmhq.fetchPins() + } } diff --git a/src/ntqqapi/entities.ts b/src/ntqqapi/entities.ts index 6a1240cd5..3a726e7ef 100644 --- a/src/ntqqapi/entities.ts +++ b/src/ntqqapi/entities.ts @@ -16,7 +16,7 @@ import { SendVideoElement, } from './types' import { stat, copyFile, unlink, mkdir } from 'node:fs/promises' -import { getMd5HexFromFile } from '../common/utils/file' +import { getFileType, getImageSize, getMd5HexFromFile } from '../common/utils/file' import { createThumb, getVideoInfo } from '../common/utils/video' import { encodeSilk } from '../common/utils/audio' import { Context } from 'cordis' @@ -70,16 +70,17 @@ export namespace SendElement { throw new Error(`文件异常,大小为 0: ${picPath}`) } const { md5, fileName, path } = await ctx.ntFileApi.uploadFile(picPath, ElementType.Pic, subType) - const imageSize = await ctx.ntFileApi.getImageSize(picPath) + const fileType = await getFileType(picPath) + const size = await getImageSize(picPath) const picElement = { md5HexStr: md5, fileSize: fileSize.toString(), - picWidth: imageSize.width, - picHeight: imageSize.height, + picWidth: size.width, + picHeight: size.height, fileName: fileName, sourcePath: path, original: true, - picType: imageSize.type === 'gif' ? PicType.GIF : PicType.JPEG, + picType: fileType.ext === 'gif' ? PicType.GIF : PicType.JPEG, picSubType: subType, fileUuid: '', fileSubId: '', diff --git a/src/ntqqapi/helper/ntv2RichMedia.ts b/src/ntqqapi/helper/ntv2RichMedia.ts index a4a0aa954..eac937e10 100644 --- a/src/ntqqapi/helper/ntv2RichMedia.ts +++ b/src/ntqqapi/helper/ntv2RichMedia.ts @@ -120,7 +120,7 @@ export namespace NTV2RichMedia { msgInfoBody: upload.msgInfo.msgInfoBody, blockSize, hash: { - fileSha1: [] as Buffer[] + fileSha1: [Buffer.alloc(0)] as Buffer[] } } satisfies InferProtoModelInput } else { @@ -131,7 +131,7 @@ export namespace NTV2RichMedia { msgInfoBody: upload.msgInfo.msgInfoBody, blockSize, hash: { - fileSha1: [] as Buffer[] + fileSha1: [Buffer.alloc(0)] as Buffer[] } } satisfies InferProtoModelInput } diff --git a/src/ntqqapi/proto/oidb.ts b/src/ntqqapi/proto/oidb.ts index 01ca27074..7e234321c 100644 --- a/src/ntqqapi/proto/oidb.ts +++ b/src/ntqqapi/proto/oidb.ts @@ -352,4 +352,125 @@ export namespace Oidb { nextIndex: ProtoField(13, 'uint32') }) }) + + /** OidbSvcTrpcTcp.0xe07_0 */ + export const ImageOcrReq = ProtoMessage.of({ + version: ProtoField(1, 'uint32'), + client: ProtoField(2, 'uint32'), + entrance: ProtoField(3, 'uint32'), + ocrReqBody: ProtoField(10, { + imageUrl: ProtoField(1, 'string'), + languageType: ProtoField(2, 'uint32'), + scene: ProtoField(3, 'uint32'), + originMd5: ProtoField(10, 'string'), + afterCompressMd5: ProtoField(11, 'string'), + afterCompressFileSize: ProtoField(12, 'string'), + afterCompressWeight: ProtoField(13, 'string'), + afterCompressHeight: ProtoField(14, 'string'), + isCut: ProtoField(15, 'bool') + }) + }) + + export const ImageOcrResp = ProtoMessage.of({ + retCode: ProtoField(1, 'int32', 'optional'), + errMsg: ProtoField(2, 'string'), + wording: ProtoField(3, 'string', 'optional'), + ocrRspBody: ProtoField(10, { + textDetections: ProtoField(1, { + detectedText: ProtoField(1, 'string'), + confidence: ProtoField(2, 'uint32'), + polygon: ProtoField(3, { + coordinates: ProtoField(1, { + x: ProtoField(1, 'int32'), + y: ProtoField(2, 'int32') + }, 'repeated') + }), + advancedInfo: ProtoField(4, 'string') + }, 'repeated'), + language: ProtoField(2, 'string'), + requestId: ProtoField(3, 'string'), + ocrLanguageList: ProtoField(101, 'string', 'repeated'), + dstTranslateLanguageList: ProtoField(102, 'string', 'repeated'), + languageList: ProtoField(103, { + languageCode: ProtoField(1, 'string'), + languageDesc: ProtoField(2, 'string') + }, 'repeated'), + afterCompressWeight: ProtoField(111, 'uint32'), + afterCompressHeight: ProtoField(112, 'uint32') + }) + }) + + /** OidbSvcTrpcTcp.0xb5d_44 */ + export const SetFriendRequestReq = ProtoMessage.of({ + accept: ProtoField(1, 'uint32'), + targetUid: ProtoField(2, 'string') + }) + + /** OidbSvcTrpcTcp.0xd72_0 */ + export const SetFilteredFriendRequestReq = ProtoMessage.of({ + selfUid: ProtoField(1, 'string'), + requestUid: ProtoField(2, 'string') + }) + + /** OidbSvcTrpcTcp.0xfd4_1 */ + export const IncPullReq = ProtoMessage.of({ + reqCount: ProtoField(2, 'uint32'), + time: ProtoField(3, 'uint32'), + localSeq: ProtoField(4, 'uint32'), + cookie: ProtoField(5, 'bytes', 'optional'), + flag: ProtoField(6, 'int32'), + proxySeq: ProtoField(7, 'uint32'), + requestBiz: ProtoField(10001, { + bizType: ProtoField(1, 'int32'), + bizData: ProtoField(2, { + extBusi: ProtoField(1, 'int32', 'repeated'), + }) + }, 'repeated'), + extSnsFlagKey: ProtoField(10002, 'uint32', 'repeated'), + extPrivateIdListKey: ProtoField(10003, 'uint32', 'repeated') + }) + + export const IncPullResp = ProtoMessage.of({ + seq: ProtoField(1, 'uint32'), + cookie: ProtoField(2, 'bytes', 'optional'), + isEnd: ProtoField(3, 'bool'), + time: ProtoField(6, 'uint32'), + selfUin: ProtoField(7, 'uint32'), + smallSeq: ProtoField(8, 'uint32'), + friendList: ProtoField(101, { + uid: ProtoField(1, 'string'), + categoryId: ProtoField(2, 'int32'), + uin: ProtoField(3, 'uint32'), + subBiz: ProtoField(10001, ['int32', { + numData: ProtoField(1, ['int32', 'int32']), + data: ProtoField(2, ['int32', 'bytes']) + }]) + }, 'repeated'), + category: ProtoField(102, { + categoryId: ProtoField(1, 'int32'), + categoryName: ProtoField(2, 'string'), + categoryMemberCount: ProtoField(3, 'int32'), + categorySortId: ProtoField(4, 'int32') + }, 'repeated') + }) + + export const FetchPinsResp = ProtoMessage.of({ + friends: ProtoField(1, { + uid: ProtoField(1, 'string') + }, 'repeated'), + groups: ProtoField(3, { + groupCode: ProtoField(1, 'uint32') + }, 'repeated') + }) + + /** OidbSvcTrpcTcp.0x12b6_0 */ + export const GetFriendRecommendContactArkReq = ProtoMessage.of({ + uin: ProtoField(1, 'uint32'), + phoneNumber: ProtoField(2, 'string'), + jumpUrl: ProtoField(3, 'string'), + }) + + export const GetFriendRecommendContactArkResp = ProtoMessage.of({ + ark: ProtoField(1, 'string') + }) } diff --git a/src/ntqqapi/types/msg.ts b/src/ntqqapi/types/msg.ts index 556c820a3..5c996a7de 100644 --- a/src/ntqqapi/types/msg.ts +++ b/src/ntqqapi/types/msg.ts @@ -133,7 +133,7 @@ export interface ReplyElement { } export interface FileElement { - fileMd5?: string + fileMd5: string fileName: string filePath: string fileSize: string diff --git a/src/ntqqapi/types/user.ts b/src/ntqqapi/types/user.ts index 140f51288..ecd6550bd 100644 --- a/src/ntqqapi/types/user.ts +++ b/src/ntqqapi/types/user.ts @@ -247,3 +247,25 @@ export interface MiniProfile { zone: string } } + +export interface Friend { + uid: string + uin: number + categoryId: number + nick: string + longNick: string + remark: string + qid: string + age: number + sex: number + birthdayYear: number + birthdayMonth: number + birthdayDay: number +} + +export interface Category { + categoryId: number + categoryName: string + categoryMemberCount: number + categorySortId: number +} diff --git a/src/onebot11/action/file/GetFile.ts b/src/onebot11/action/file/GetFile.ts index 0f5cf049d..57dced74c 100644 --- a/src/onebot11/action/file/GetFile.ts +++ b/src/onebot11/action/file/GetFile.ts @@ -1,8 +1,8 @@ import { BaseAction, Schema } from '../BaseAction' import { readFile } from 'node:fs/promises' import { ActionName } from '../types' -import { Peer, ElementType } from '@/ntqqapi/types' -import { parseBool } from '@/common/utils' +import { ElementType, ChatType } from '@/ntqqapi/types' +import { parseBool, uri2local } from '@/common/utils' export interface GetFilePayload { file: string // 文件名或者fileUuid @@ -34,14 +34,11 @@ export abstract class GetFileBase extends BaseAction e.elementId === fileCache[0].elementId) - if (!findEle) { - throw new Error('element not found') - } - res.url = await this.ctx.ntFileApi.getImageUrl(findEle.picElement!) + const originImageUrl = `/download?appid=${isGroup ? 1407 : 1406}&fileid=${fileCache[0].fileUuid}&spec=0` + res.url = await this.ctx.ntFileApi.getImageUrl(originImageUrl, fileCache[0].md5HexStr) } else if (fileCache[0].elementType === ElementType.Video) { - res.url = await this.ctx.ntFileApi.getVideoUrl(peer, fileCache[0].msgId, fileCache[0].elementId) + res.url = await this.ctx.ntFileApi.getVideoUrl(fileCache[0].fileUuid, isGroup) } else if (fileCache[0].elementType === ElementType.Ptt) { - res.url = await this.ctx.ntFileApi.getPttUrl(fileCache[0].fileUuid, peer.chatType === 2) + res.url = await this.ctx.ntFileApi.getPttUrl(fileCache[0].fileUuid, isGroup) } if (enableLocalFile2Url && downloadPath && (res.file === res.url || res.url === undefined)) { try { diff --git a/src/onebot11/action/file/GetRecord.ts b/src/onebot11/action/file/GetRecord.ts index e315a8607..aea0a851b 100644 --- a/src/onebot11/action/file/GetRecord.ts +++ b/src/onebot11/action/file/GetRecord.ts @@ -3,6 +3,7 @@ import { ActionName } from '../types' import { decodeSilk } from '@/common/utils/audio' import { BaseAction, Schema } from '../BaseAction' import { stat, readFile } from 'node:fs/promises' +import { uri2local } from '@/common/utils' interface Payload { file: string @@ -26,15 +27,11 @@ export default class GetRecord extends BaseAction { protected async _handle(payload: Payload): Promise { const fileCache = await this.ctx.store.getFileCacheByName(payload.file) if (fileCache?.length) { - const downloadPath = await this.ctx.ntFileApi.downloadMedia( - fileCache[0].msgId, - fileCache[0].chatType, - fileCache[0].peerUid, - fileCache[0].elementId, - '', - '' - ) - const file = await decodeSilk(this.ctx, downloadPath, payload.out_format) + const originFile = await uri2local(this.ctx, fileCache[0].fileUuid, true) + if (originFile.errMsg) { + throw new Error(originFile.errMsg) + } + const file = await decodeSilk(this.ctx, originFile.path, payload.out_format) const res: Response = { file, file_name: path.basename(file), diff --git a/src/onebot11/action/go-cqhttp/GetForwardMsg.ts b/src/onebot11/action/go-cqhttp/GetForwardMsg.ts index a9db10dab..44ddfb814 100644 --- a/src/onebot11/action/go-cqhttp/GetForwardMsg.ts +++ b/src/onebot11/action/go-cqhttp/GetForwardMsg.ts @@ -62,7 +62,7 @@ export class GetForwardMsg extends BaseAction { } const messages: (OB11ForwardMessage | undefined)[] = await Promise.all( data.msgList.map(async (msg) => { - const res = await OB11Entities.message(this.ctx, msg, rootMsgId, peer, config) + const res = await OB11Entities.message(this.ctx, msg, config) if (res) { const segments = message2List(res.message) for (const item of segments) { diff --git a/src/onebot11/action/go-cqhttp/GetGroupMsgHistory.ts b/src/onebot11/action/go-cqhttp/GetGroupMsgHistory.ts index 70c51479f..d556f7280 100644 --- a/src/onebot11/action/go-cqhttp/GetGroupMsgHistory.ts +++ b/src/onebot11/action/go-cqhttp/GetGroupMsgHistory.ts @@ -43,7 +43,7 @@ export class GetGroupMsgHistory extends BaseAction { rawMsg = msg } } - return OB11Entities.message(this.ctx, rawMsg, undefined, undefined, config) + return OB11Entities.message(this.ctx, rawMsg, config) })) return { list: filterNullable(ob11MsgList), seq: +msgList[0].msgSeq } } diff --git a/src/onebot11/action/go-cqhttp/OCRImage.ts b/src/onebot11/action/go-cqhttp/OCRImage.ts index ee65eb4fc..077e2d29d 100644 --- a/src/onebot11/action/go-cqhttp/OCRImage.ts +++ b/src/onebot11/action/go-cqhttp/OCRImage.ts @@ -2,7 +2,9 @@ import { noop } from 'cosmokit' import { BaseAction, Schema } from '../BaseAction' import { ActionName } from '../types' import { uri2local } from '@/common/utils/file' -import { access, unlink } from 'node:fs/promises' +import { unlink } from 'node:fs/promises' +import { isHttpUrl } from '@/common/utils' +import { selfInfo } from '@/common/globalVars' interface Payload { image: string @@ -29,39 +31,31 @@ export class OCRImage extends BaseAction { }) protected async _handle(payload: Payload) { - const { errMsg, isLocal, path, success } = await uri2local(this.ctx, payload.image, true) - if (!success) { - throw new Error(errMsg) - } - await access(path) - - const data = await this.ctx.ntFileApi.ocrImage(path) - if (!isLocal) { - unlink(path).catch(noop) - } - if (data.code !== 0) { - throw new Error(data.errMsg) - } - - const texts = data.result.map(item => { - const ret: TextDetection = { - text: item.text, - confidence: 1, - coordinates: [] + let url + if (isHttpUrl(payload.image)) { + url = payload.image + } else { + const { errMsg, isLocal, path, success } = await uri2local(this.ctx, payload.image) + if (!success) { + throw new Error(errMsg) } - for (let i = 0; i < 4; i++) { - const pt = item[`pt${i + 1}`] - ret.coordinates.push({ - x: +pt.x, - y: +pt.y - }) + const { msgInfo } = await this.ctx.ntFileApi.uploadC2CImage(selfInfo.uid, path) + if (!isLocal) { + unlink(path).catch(noop) } - return ret - }) + const { pic, index } = msgInfo.msgInfoBody[0] + url = await this.ctx.ntFileApi.getImageUrl(pic!.urlPath + pic!.ext.originalParam, index.info.md5HexStr) + } + + const { textDetections, language } = await this.ctx.ntFileApi.ocrImage(url) return { - texts, - language: '' + texts: textDetections.map(item => ({ + text: item.detectedText, + confidence: item.confidence, + coordinates: item.polygon.coordinates + })), + language } } } diff --git a/src/onebot11/action/llbot/msg/GetFriendMsgHistory.ts b/src/onebot11/action/llbot/msg/GetFriendMsgHistory.ts index bb6788dbe..75ddfe582 100644 --- a/src/onebot11/action/llbot/msg/GetFriendMsgHistory.ts +++ b/src/onebot11/action/llbot/msg/GetFriendMsgHistory.ts @@ -42,7 +42,7 @@ export class GetFriendMsgHistory extends BaseAction { rawMsg = msg } } - return OB11Entities.message(this.ctx, rawMsg, undefined, undefined, config) + return OB11Entities.message(this.ctx, rawMsg, config) })) return { list: filterNullable(ob11MsgList), seq: +msgList[0].msgSeq } } @@ -50,7 +50,7 @@ export class GetFriendMsgHistory extends BaseAction { async _handle(payload: Payload, config: ParseMessageConfig): Promise { const uid = await this.ctx.ntUserApi.getUidByUin(payload.user_id.toString()) if (!uid) throw new Error(`无法获取用户信息`) - const isBuddy = await this.ctx.ntFriendApi.isBuddy(uid) + const isBuddy = await this.ctx.ntFriendApi.isFriend(uid) const peer: Peer = { chatType: isBuddy ? ChatType.C2C : ChatType.TempC2CFromGroup, peerUid: uid, diff --git a/src/onebot11/action/llbot/user/GetFriendWithCategory.ts b/src/onebot11/action/llbot/user/GetFriendWithCategory.ts index e830b8956..1460ba2da 100644 --- a/src/onebot11/action/llbot/user/GetFriendWithCategory.ts +++ b/src/onebot11/action/llbot/user/GetFriendWithCategory.ts @@ -9,7 +9,6 @@ interface Category { categorySortId: number categoryName: string categoryMbCount: number - onlineCount: number buddyList: OB11User[] } @@ -17,37 +16,17 @@ export class GetFriendWithCategory extends BaseAction<{}, Category[]> { actionName = ActionName.GetFriendsWithCategory protected async _handle() { - const res = await this.ctx.ntFriendApi.getBuddyV2(true) - if (res.result !== 0) { - throw new Error(res.errMsg) - } - const buddyList = await this.ctx.ntFriendApi.getBuddyList() - const buddyMap = new Map() - for (const buddy of buddyList) { - buddyMap.set(buddy.uid!, buddy) - } - const category: CategoryFriend[] = [] - for (const item of res.data) { - const buddyList = [] - for (const uid of item.buddyUids) { - buddyList.push(buddyMap.get(uid)!) - } - category.push({ - ...item, - buddyList - }) - } - return category.map(item => { - return { - categoryId: item.categoryId, - categorySortId: item.categorySortId, - categoryName: item.categroyName, - categoryMbCount: item.categroyMbCount, - onlineCount: item.onlineCount, - buddyList: item.buddyList!.map(buddy => { - return OB11Entities.friend(buddy) + const result = await this.ctx.ntFriendApi.getFriendList(true) + return result.categories.values().map(item => ({ + categoryId: item.categoryId, + categorySortId: item.categorySortId, + categoryName: item.categoryName, + categoryMbCount: item.categoryMemberCount, + buddyList: result.friends + .filter(friend => friend.categoryId === item.categoryId) + .map(friend => { + return OB11Entities.friend(friend) }) - } - }) + })).toArray() } } diff --git a/src/onebot11/action/llbot/user/SetDoubtFriendsAddRequest.ts b/src/onebot11/action/llbot/user/SetDoubtFriendsAddRequest.ts index 99110a752..39847db80 100644 --- a/src/onebot11/action/llbot/user/SetDoubtFriendsAddRequest.ts +++ b/src/onebot11/action/llbot/user/SetDoubtFriendsAddRequest.ts @@ -12,10 +12,7 @@ export class SetDoubtFriendsAddRequest extends BaseAction { }) protected async _handle(payload: Payload) { - const res = await this.ctx.ntFriendApi.approvalDoubtBuddyReq(payload.flag) - if (res.result !== 0) { - throw new Error(res.errMsg) - } + await this.ctx.ntFriendApi.approvalDoubtFriendRequest(payload.flag) return null } } diff --git a/src/onebot11/action/msg/GetMsg.ts b/src/onebot11/action/msg/GetMsg.ts index dd54a4200..8911bff2e 100644 --- a/src/onebot11/action/msg/GetMsg.ts +++ b/src/onebot11/action/msg/GetMsg.ts @@ -38,7 +38,7 @@ class GetMsg extends BaseAction { } else { msg = res.msgList[0] } - const retMsg = await OB11Entities.message(this.ctx, msg, undefined, undefined, config) + const retMsg = await OB11Entities.message(this.ctx, msg, config) if (!retMsg) { throw new Error('消息为空') } diff --git a/src/onebot11/action/system/CleanCache.ts b/src/onebot11/action/system/CleanCache.ts index a349319ef..a350fc70f 100644 --- a/src/onebot11/action/system/CleanCache.ts +++ b/src/onebot11/action/system/CleanCache.ts @@ -1,85 +1,11 @@ import { BaseAction } from '../BaseAction' import { ActionName } from '../types' -import fs from 'node:fs' -import Path from 'node:path' -import { ChatCacheListItemBasic, CacheFileType } from '@/ntqqapi/types' export default class CleanCache extends BaseAction<{}, null> { actionName = ActionName.CleanCache protected async _handle() { + // TODO: 删除 LLBot 临时文件目录内所有文件 return null - /*const cacheFilePaths: string[] = [] - - await this.ctx.ntFileCacheApi.setCacheSilentScan(false) - - cacheFilePaths.push(await this.ctx.ntFileCacheApi.getHotUpdateCachePath()) - cacheFilePaths.push(await this.ctx.ntFileCacheApi.getDesktopTmpPath()) - - const list = await this.ctx.ntFileCacheApi.getCacheSessionPathList() - list.forEach((e) => cacheFilePaths.push(e.value)) - - // await NTQQApi.addCacheScannedPaths(); // XXX: 调用就崩溃,原因目前还未知 - const cacheScanResult = await this.ctx.ntFileCacheApi.scanCache() - const cacheSize = parseInt(cacheScanResult.size[6]) - - if (cacheScanResult.result !== 0) { - throw 'Something went wrong while scanning cache. Code: ' + cacheScanResult.result - } - - await this.ctx.ntFileCacheApi.setCacheSilentScan(true) - if (cacheSize > 0 && cacheFilePaths.length > 2) { - // 存在缓存文件且大小不为 0 时执行清理动作 - // await NTQQApi.clearCache([ 'tmp', 'hotUpdate', ...cacheScanResult ]) // XXX: 也是调用就崩溃,调用 fs 删除得了 - deleteCachePath(cacheFilePaths) - } - - // 获取聊天记录列表 - // NOTE: 以防有人不需要删除聊天记录,暂时先注释掉,日后加个开关 - // const privateChatCache = await getCacheList(ChatType.friend); // 私聊消息 - // const groupChatCache = await getCacheList(ChatType.group); // 群聊消息 - // const chatCacheList = [ ...privateChatCache, ...groupChatCache ]; - const chatCacheList: ChatCacheListItemBasic[] = [] - - // 获取聊天缓存文件列表 - const cacheFileList: string[] = [] - - for (const name in CacheFileType) { - if (!isNaN(parseInt(name))) continue - - const fileType = CacheFileType[name] as unknown as CacheFileType - - cacheFileList.push(...(await this.ctx.ntFileCacheApi.getFileCacheInfo(fileType)).infos.map((file) => file.fileKey)) - } - - // 一并清除 - await this.ctx.ntFileCacheApi.clearChatCache(chatCacheList, cacheFileList)*/ - } -} - -function deleteCachePath(pathList: string[]) { - const emptyPath = (path: string) => { - if (!fs.existsSync(path)) return - const files = fs.readdirSync(path) - files.forEach((file) => { - const filePath = Path.resolve(path, file) - const stats = fs.statSync(filePath) - if (stats.isDirectory()) emptyPath(filePath) - else { - try { - fs.unlinkSync(filePath) - }catch (e) { - } - } - }) - try { - fs.rmdirSync(path) - }catch (e) { - - } - } - - for (const path of pathList) { - emptyPath(path) } } diff --git a/src/onebot11/action/user/GetFriendList.ts b/src/onebot11/action/user/GetFriendList.ts index e2722b2c9..3f8c64397 100644 --- a/src/onebot11/action/user/GetFriendList.ts +++ b/src/onebot11/action/user/GetFriendList.ts @@ -7,7 +7,7 @@ export class GetFriendList extends BaseAction<{}, OB11User[]> { actionName = ActionName.GetFriendList protected async _handle() { - const buddyList = await this.ctx.ntFriendApi.getBuddyList() - return OB11Entities.friends(buddyList) + const result = await this.ctx.ntFriendApi.getFriendList(true) + return OB11Entities.friends(result.friends) } } diff --git a/src/onebot11/action/user/SetFriendAddRequest.ts b/src/onebot11/action/user/SetFriendAddRequest.ts index 4bbb9dd35..0a665b9d4 100644 --- a/src/onebot11/action/user/SetFriendAddRequest.ts +++ b/src/onebot11/action/user/SetFriendAddRequest.ts @@ -17,18 +17,9 @@ export default class SetFriendAddRequest extends BaseAction { }) protected async _handle(payload: Payload) { - const data = payload.flag.split('|') - if (data.length < 2) { - throw new Error('无效的flag') - } - const uid = data[0] - const reqTime = data[1] - const res = await this.ctx.ntFriendApi.handleFriendRequest(uid, reqTime, payload.approve) - if (res.result !== 0) { - throw new Error(res.errMsg) - } + await this.ctx.ntFriendApi.approvalFriendRequest(payload.flag, payload.approve) if (payload.remark) { - const res = await this.ctx.ntFriendApi.setBuddyRemark(uid, payload.remark) + const res = await this.ctx.ntFriendApi.setBuddyRemark(payload.flag, payload.remark) if (res.result !== 0) { throw new Error(res.errMsg) } diff --git a/src/onebot11/adapter.ts b/src/onebot11/adapter.ts index a454f895b..df8ef83ff 100644 --- a/src/onebot11/adapter.ts +++ b/src/onebot11/adapter.ts @@ -54,10 +54,10 @@ declare module 'cordis' { class Onebot11Adapter extends Service { static inject = [ - 'ntMsgApi', 'ntFileApi', 'ntFileCacheApi', - 'ntFriendApi', 'ntGroupApi', 'ntUserApi', - 'ntWebApi', 'ntSystemApi', 'store', 'app', - 'logger', 'pmhq', 'timer', 'config' + 'ntMsgApi', 'ntFileApi', 'ntFriendApi', + 'ntGroupApi', 'ntUserApi', 'ntWebApi', + 'ntSystemApi', 'store', 'app', 'logger', + 'pmhq', 'timer', 'config' ] private connect: (OB11Http | OB11HttpPost | OB11WebSocket | OB11WebSocketReverse)[] private actionMap: Map> @@ -261,7 +261,7 @@ class Onebot11Adapter extends Service { private async handleFriendRequest(req: FriendRequest) { const uin = await this.ctx.ntUserApi.getUinByUid(req.friendUid) - const flag = req.friendUid + '|' + req.reqTime + const flag = req.friendUid const friendRequestEvent = new OB11FriendRequestEvent( +uin, req.extWords, diff --git a/src/onebot11/entities.ts b/src/onebot11/entities.ts index 78ecfeb66..e99101e73 100644 --- a/src/onebot11/entities.ts +++ b/src/onebot11/entities.ts @@ -7,6 +7,7 @@ import { } from './types' import { ChatType, + Friend, GrayTipElementSubType, GroupMember, JsonGrayTipBusId, @@ -39,14 +40,12 @@ export namespace OB11Entities { export async function message( ctx: Context, msg: RawMessage, - rootMsgID?: string, - peer?: Peer, config?: ParseMessageConfig ): Promise { if (!msg.senderUin || msg.senderUin === '0' || msg.msgType === 1) return //跳过空消息 const selfUin = selfInfo.uin const msgShortId = ctx.store.createMsgShortId(msg) - const { segments, cqCode } = await transformIncomingSegments(ctx, msg, rootMsgID, peer) + const { segments, cqCode } = await transformIncomingSegments(ctx, msg) const resMsg: OB11Message = { self_id: Number(selfUin), user_id: Number(msg.senderUin), @@ -293,22 +292,22 @@ export namespace OB11Entities { } } - export function friend(raw: SimpleInfo): OB11User { + export function friend(raw: Friend): OB11User { return { - user_id: +raw.coreInfo.uin, - nickname: raw.coreInfo.nick, - remark: raw.coreInfo.remark || raw.coreInfo.nick, - sex: sex(raw.baseInfo.sex), - birthday_year: raw.baseInfo.birthday_year, - birthday_month: raw.baseInfo.birthday_month, - birthday_day: raw.baseInfo.birthday_day, - age: raw.baseInfo.age, - qid: raw.baseInfo.qid, - long_nick: raw.baseInfo.longNick, + user_id: raw.uin, + nickname: raw.nick, + remark: raw.remark, + sex: sex(raw.sex), + birthday_year: raw.birthdayYear, + birthday_month: raw.birthdayMonth, + birthday_day: raw.birthdayDay, + age: raw.age, + qid: raw.qid, + long_nick: raw.longNick, } } - export function friends(raw: SimpleInfo[]): OB11User[] { + export function friends(raw: Friend[]): OB11User[] { return raw.map(friend) } diff --git a/src/onebot11/helper/createMessage.ts b/src/onebot11/helper/createMessage.ts index d17366ba0..65afa3022 100644 --- a/src/onebot11/helper/createMessage.ts +++ b/src/onebot11/helper/createMessage.ts @@ -167,7 +167,7 @@ export async function createSendElements( break case OB11MessageDataType.Contact: { const { type, id } = segment.data - const data = type === 'qq' ? ctx.ntFriendApi.getBuddyRecommendContact(id) : ctx.ntGroupApi.getGroupRecommendContact(id) + const data = type === 'qq' ? ctx.ntFriendApi.getFriendRecommendContactArk(+id) : ctx.ntGroupApi.getGroupRecommendContact(id) sendElements.push(SendElement.ark(await data)) } break @@ -312,7 +312,7 @@ export async function createPeer(ctx: Context, payload: CreatePeerPayload, mode if ((mode === CreatePeerMode.Private || mode === CreatePeerMode.Normal) && payload.user_id) { const uid = await ctx.ntUserApi.getUidByUin(payload.user_id.toString(), payload.group_id?.toString()) if (!uid) throw new Error('无法获取用户信息') - const isBuddy = await ctx.ntFriendApi.isBuddy(uid) + const isBuddy = await ctx.ntFriendApi.isFriend(uid) if (!isBuddy) { const res = await ctx.ntMsgApi.getTempChatInfo(ChatType.TempC2CFromGroup, uid) if (res.tmpChatInfo.groupCode) { diff --git a/src/onebot11/helper/createMultiMessage.ts b/src/onebot11/helper/createMultiMessage.ts index dc7f1a489..2d1aec028 100644 --- a/src/onebot11/helper/createMultiMessage.ts +++ b/src/onebot11/helper/createMultiMessage.ts @@ -97,54 +97,11 @@ export class MessageEncoder { this.preview = '' } - async packImage(data: RichMediaUploadCompleteNotify, busiType: number) { - const imageSize = await this.ctx.ntFileApi.getImageSize(data.filePath) + async packImage(msgInfo: InferProtoModelInput) { return { commonElem: { serviceType: 48, - pbElem: Media.MsgInfo.encode({ - msgInfoBody: [{ - index: { - info: { - fileSize: +data.commonFileInfo.fileSize, - md5HexStr: data.commonFileInfo.md5, - sha1HexStr: data.commonFileInfo.sha, - fileName: data.commonFileInfo.fileName, - fileType: { - type: 1, - picFormat: imageSize.type === 'gif' ? 2000 : 1000 - }, - width: imageSize.width, - height: imageSize.height, - time: 0, - original: 1 - }, - fileUuid: data.fileId, - storeID: 1, - expire: this.isGroup ? 2678400 : 157680000 - }, - pic: { - urlPath: `/download?appid=${this.isGroup ? 1407 : 1406}&fileid=${data.fileId}`, - ext: { - originalParam: '&spec=0', - bigParam: '&spec=720', - thumbParam: '&spec=198' - }, - domain: 'multimedia.nt.qq.com.cn' - }, - fileExist: true - }], - extBizInfo: { - pic: { - bizType: 0, - summary: '', - fromScene: this.isGroup ? 2 : 1, // 怀旧版 PCQQ 私聊收图需要 - toScene: this.isGroup ? 2 : 1, // 怀旧版 PCQQ 私聊收图需要 - oldFileId: this.isGroup ? 574859779 : undefined // 怀旧版 PCQQ 群聊收图需要 - }, - busiType - } - }), + pbElem: Media.MsgInfo.encode(msgInfo), businessType: this.isGroup ? 20 : 10 } } @@ -272,11 +229,14 @@ export class MessageEncoder { if (fileSize === 0) { throw new Error(`文件异常,大小为 0: ${picPath}`) } - const { path } = await this.ctx.ntFileApi.uploadFile(picPath, ElementType.Pic, busiType) - const data = await this.ctx.ntFileApi.uploadRMFileWithoutMsg(path, this.isGroup ? 4 : 3, this.isGroup ? this.peer.peerUid : selfInfo.uid) - this.children.push(await this.packImage(data, busiType)) + let data + if (this.isGroup) { + data = await this.ctx.ntFileApi.uploadGroupImage(this.peer.peerUid, picPath) + } else { + data = await this.ctx.ntFileApi.uploadC2CImage(this.peer.peerUid, picPath) + } + this.children.push(await this.packImage(data.msgInfo)) this.preview += busiType === 1 ? '[动画表情]' : '[图片]' - this.deleteAfterSentFiles.push(path) } else if (type === OB11MessageDataType.Forward) { // 处理 forward 类型:支持 id(已有 resid)或 content(嵌套节点) const forwardData = data as { id?: string; content?: OB11MessageData[]; source?: string; news?: { text: string }[]; summary?: string; prompt?: string } diff --git a/src/onebot11/helper/decodeMultiMessage.ts b/src/onebot11/helper/decodeMultiMessage.ts index 8916f9fef..798452332 100644 --- a/src/onebot11/helper/decodeMultiMessage.ts +++ b/src/onebot11/helper/decodeMultiMessage.ts @@ -40,7 +40,7 @@ export async function decodeMultiMessage(ctx: Context, items: InferProtoModel ctx.logger.error(e)) + await ctx.ntFriendApi.approvalFriendRequest(request.flag, quickAction.approve).catch(e => ctx.logger.error(e)) if (!isNullable(quickAction.remark)) { - ctx.ntFriendApi.setBuddyRemark(uid, quickAction.remark).catch(e => ctx.logger.error(e)) + ctx.ntFriendApi.setBuddyRemark(request.flag, quickAction.remark).catch(e => ctx.logger.error(e)) } } } diff --git a/src/onebot11/transform/message/incoming.ts b/src/onebot11/transform/message/incoming.ts index ea2c2822d..aa2558ef7 100644 --- a/src/onebot11/transform/message/incoming.ts +++ b/src/onebot11/transform/message/incoming.ts @@ -5,12 +5,8 @@ import { Context } from 'cordis' import { Dict } from 'cosmokit' import { pathToFileURL } from 'node:url' -export async function transformIncomingSegments( - ctx: Context, - message: RawMessage, - rootMsgID?: string, - peer?: Peer -): Promise<{ segments: OB11MessageData[], cqCode: string }> { +export async function transformIncomingSegments(ctx: Context, message: RawMessage) + : Promise<{ segments: OB11MessageData[], cqCode: string }> { const segments: OB11MessageData[] = [] let cqCode = '' @@ -102,33 +98,23 @@ export async function transformIncomingSegments( data: { file: picElement.fileName, subType: picElement.picSubType, - url: await ctx.ntFileApi.getImageUrl(picElement), + url: await ctx.ntFileApi.getImageUrl(picElement.originImageUrl, picElement.md5HexStr), file_size: fileSize, } } ctx.store.addFileCache({ - peerUid: message.peerUid, - msgId: message.msgId, msgTime: +message.msgTime, chatType: message.chatType, - elementId: element.elementId, elementType: element.elementType, fileName: picElement.fileName, fileUuid: picElement.fileUuid, fileSize, - }).then() + md5HexStr: picElement.md5HexStr, + }) } else if (element.videoElement) { const { videoElement } = element - const videoUrl = await ctx.ntFileApi.getVideoUrl( - peer ?? { - chatType: message.chatType, - peerUid: message.peerUid, - guildId: '' - }, - rootMsgID ?? message.msgId, - element.elementId, - ) + const videoUrl = await ctx.ntFileApi.getVideoUrl(videoElement.fileUuid, message.chatType === ChatType.Group) const fileSize = videoElement.fileSize ?? '0' messageSegment = { type: OB11MessageDataType.Video, @@ -140,16 +126,14 @@ export async function transformIncomingSegments( } } ctx.store.addFileCache({ - peerUid: message.peerUid, - msgId: message.msgId, msgTime: +message.msgTime, chatType: message.chatType, - elementId: element.elementId, elementType: element.elementType, fileName: videoElement.fileName, fileUuid: videoElement.fileUuid!, fileSize, - }).then() + md5HexStr: videoElement.videoMd5 + }) } else if (element.fileElement) { const { fileElement } = element @@ -165,16 +149,14 @@ export async function transformIncomingSegments( } } ctx.store.addFileCache({ - peerUid: message.peerUid, - msgId: message.msgId, msgTime: +message.msgTime, chatType: message.chatType, - elementId: element.elementId, elementType: element.elementType, fileName: fileElement.fileName, - fileUuid: fileElement.fileUuid!, + fileUuid: fileElement.fileUuid, fileSize, - }).then() + md5HexStr: fileElement.fileMd5 + }) } else if (element.pttElement) { const { pttElement } = element @@ -189,16 +171,14 @@ export async function transformIncomingSegments( } } ctx.store.addFileCache({ - peerUid: message.peerUid, - msgId: message.msgId, msgTime: +message.msgTime, chatType: message.chatType, - elementId: element.elementId, elementType: element.elementType, fileName: pttElement.fileName, fileUuid: pttElement.fileUuid, fileSize, - }).then() + md5HexStr: pttElement.md5HexStr + }) } else if (element.arkElement) { const { arkElement } = element diff --git a/src/satori/adapter.ts b/src/satori/adapter.ts index 1a5d4fa05..b63381223 100644 --- a/src/satori/adapter.ts +++ b/src/satori/adapter.ts @@ -23,10 +23,9 @@ declare module 'cordis' { class SatoriAdapter extends Service { static inject = [ - 'ntMsgApi', 'ntFileApi', 'ntFileCacheApi', - 'ntFriendApi', 'ntGroupApi', 'ntUserApi', - 'ntWebApi', 'store', 'app', 'logger', - 'pmhq' + 'ntMsgApi', 'ntFileApi', 'ntFriendApi', + 'ntGroupApi', 'ntUserApi', 'ntWebApi', + 'store', 'app', 'logger', 'pmhq' ] private selfId: string private server: SatoriServer diff --git a/src/satori/api/friend/approve.ts b/src/satori/api/friend/approve.ts index b7697ba64..c16654f97 100644 --- a/src/satori/api/friend/approve.ts +++ b/src/satori/api/friend/approve.ts @@ -8,15 +8,6 @@ interface Payload { } export const handleFriendRequest: Handler, Payload> = async (ctx, payload) => { - const data = payload.message_id.split('|') - if (data.length < 2) { - throw new Error('无效的 message_id') - } - const uid = data[0] - const reqTime = data[1] - const res = await ctx.ntFriendApi.handleFriendRequest(uid, reqTime, payload.approve) - if (res.result !== 0) { - throw new Error(res.errMsg) - } + await ctx.ntFriendApi.approvalFriendRequest(payload.message_id, payload.approve) return {} } diff --git a/src/satori/api/friend/list.ts b/src/satori/api/friend/list.ts index e57b63d89..5c7f4a24c 100644 --- a/src/satori/api/friend/list.ts +++ b/src/satori/api/friend/list.ts @@ -7,11 +7,11 @@ interface Payload { } export const getFriendList: Handler, Payload> = async (ctx) => { - const friends = await ctx.ntFriendApi.getBuddyList() + const result = await ctx.ntFriendApi.getFriendList(true) return { - data: friends.map(e => ({ - user: decodeUser(e.coreInfo), - nick: e.coreInfo.remark + data: result.friends.map(e => ({ + user: decodeUser(e), + nick: e.remark })) } } diff --git a/src/satori/event/user.ts b/src/satori/event/user.ts index 986967315..54e2836a7 100644 --- a/src/satori/event/user.ts +++ b/src/satori/event/user.ts @@ -3,7 +3,7 @@ import { FriendRequest } from '@/ntqqapi/types' import { decodeUser } from '../utils' export async function parseFriendRequest(bot: SatoriAdapter, input: FriendRequest) { - const flag = input.friendUid + '|' + input.reqTime + const flag = input.friendUid const user = await bot.ctx.ntUserApi.getUserSimpleInfo(input.friendUid) return bot.event('friend-request', { diff --git a/src/satori/message.ts b/src/satori/message.ts index 30f9d3149..eeecd856b 100644 --- a/src/satori/message.ts +++ b/src/satori/message.ts @@ -533,54 +533,17 @@ async function ntToProto(ctx: Context, input: NT.SendMessageElement, peer: NT.Pe } else if (input.elementType === NT.ElementType.Pic) { const isGroup = peer.chatType === NT.ChatType.Group const path = input.picElement.sourcePath! - const data = await ctx.ntFileApi.uploadRMFileWithoutMsg(path, isGroup ? 4 : 3, peer.peerUid) + let data + if (isGroup) { + data = await ctx.ntFileApi.uploadGroupImage(peer.peerUid, path) + } else { + data = await ctx.ntFileApi.uploadC2CImage(peer.peerUid, path) + } return { element: { commonElem: { serviceType: 48, - pbElem: Media.MsgInfo.encode({ - msgInfoBody: [{ - index: { - info: { - fileSize: +data.commonFileInfo.fileSize, - md5HexStr: data.commonFileInfo.md5, - sha1HexStr: data.commonFileInfo.sha, - fileName: data.commonFileInfo.fileName, - fileType: { - type: 1, - picFormat: input.picElement.picType - }, - width: input.picElement.picWidth, - height: input.picElement.picHeight, - time: 0, - original: 1 - }, - fileUuid: data.fileId, - storeID: 1, - expire: isGroup ? 2678400 : 157680000 - }, - pic: { - urlPath: `/download?appid=${isGroup ? 1407 : 1406}&fileid=${data.fileId}`, - ext: { - originalParam: '&spec=0', - bigParam: '&spec=720', - thumbParam: '&spec=198' - }, - domain: 'multimedia.nt.qq.com.cn' - }, - fileExist: true - }], - extBizInfo: { - pic: { - bizType: 0, - summary: '', - fromScene: isGroup ? 2 : 1, // 怀旧版 PCQQ 私聊收图需要 - toScene: isGroup ? 2 : 1, // 怀旧版 PCQQ 私聊收图需要 - oldFileId: isGroup ? 574859779 : undefined // 怀旧版 PCQQ 群聊收图需要 - }, - busiType: input.picElement.picSubType - } - }), + pbElem: Media.MsgInfo.encode(data.msgInfo), businessType: isGroup ? 20 : 10 } }, diff --git a/src/satori/utils.ts b/src/satori/utils.ts index 1ed1ea90b..6fabe8ffa 100644 --- a/src/satori/utils.ts +++ b/src/satori/utils.ts @@ -3,10 +3,9 @@ import * as NT from '@/ntqqapi/types' import * as Universal from '@satorijs/protocol' import { Context } from 'cordis' import { ObjectToSnake } from 'ts-case-convert' -import { pathToFileURL } from 'node:url' interface User { - uin: string + uin: number | string nick: string remark?: string } @@ -36,10 +35,10 @@ const robotUinRanges = [ export function decodeUser(user: User): ObjectToSnake { return { - id: user.uin, + id: user.uin.toString(), name: user.nick, avatar: `http://q.qlogo.cn/headimg_dl?dst_uin=${user.uin}&spec=640`, - is_bot: robotUinRanges.some(e => user.uin >= e.minUin && user.uin <= e.maxUin) + is_bot: robotUinRanges.some(e => +user.uin >= +e.minUin && +user.uin <= +e.maxUin) } } @@ -102,7 +101,7 @@ async function decodeElement(ctx: Context, data: NT.RawMessage, quoted = false) } } else if (v.picElement) { // img - const src = await ctx.ntFileApi.getImageUrl(v.picElement) + const src = await ctx.ntFileApi.getImageUrl(v.picElement.originImageUrl, v.picElement.md5HexStr) buffer.push(h.img(src, { width: v.picElement.picWidth, height: v.picElement.picHeight, @@ -114,11 +113,7 @@ async function decodeElement(ctx: Context, data: NT.RawMessage, quoted = false) buffer.push(h.audio(src, { duration: v.pttElement.duration })) } else if (v.videoElement) { // video - const src = (await ctx.ntFileApi.getVideoUrl({ - chatType: data.chatType, - peerUid: data.peerUid, - guildId: '' - }, data.msgId, v.elementId)) || pathToFileURL(v.videoElement.filePath).href + const src = await ctx.ntFileApi.getVideoUrl(v.videoElement.fileUuid, data.chatType === NT.ChatType.Group) buffer.push(h.video(src)) } else if (v.marketFaceElement) { // llonebot:market-face @@ -229,7 +224,7 @@ export async function getPeer(ctx: Context, channelId: string): Promise const uin = channelId.replace('private:', '') const uid = await ctx.ntUserApi.getUidByUin(uin) if (!uid) throw new Error('无法获取用户信息') - const isBuddy = await ctx.ntFriendApi.isBuddy(uid) + const isBuddy = await ctx.ntFriendApi.isFriend(uid) if (!isBuddy) { const res = await ctx.ntMsgApi.getTempChatInfo(NT.ChatType.TempC2CFromGroup, uid) if (res.tmpChatInfo.groupCode) { diff --git a/src/version.ts b/src/version.ts index 00ec7cc3b..581ef3e69 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const version = '7.12.13' +export const version = '7.12.14' diff --git a/src/webui/BE/routes/dashboard.ts b/src/webui/BE/routes/dashboard.ts index 19aba010c..74bf9e213 100644 --- a/src/webui/BE/routes/dashboard.ts +++ b/src/webui/BE/routes/dashboard.ts @@ -11,7 +11,7 @@ export function createDashboardRoutes(ctx: Context): Hono { if (!app) { return c.json({ success: false, message: '服务尚未就绪,请等待登录完成' }, 503) } - const friends = await ctx.ntFriendApi.getBuddyList() + const friends = await ctx.ntFriendApi.getFriendList(false) const groups = await ctx.ntGroupApi.getGroups(false) // 获取 QQ 进程资源 @@ -33,7 +33,7 @@ export function createDashboardRoutes(ctx: Context): Hono { return c.json({ success: true, data: { - friendCount: friends.length, + friendCount: friends.friends.length, groupCount: groups.length, messageReceived: app.messageReceivedCount, messageSent: app.messageSentCount, diff --git a/src/webui/BE/routes/webqq/notifications.ts b/src/webui/BE/routes/webqq/notifications.ts index 8370e836d..4f47d4d80 100644 --- a/src/webui/BE/routes/webqq/notifications.ts +++ b/src/webui/BE/routes/webqq/notifications.ts @@ -50,7 +50,7 @@ export function createNotificationRoutes(ctx: Context): Hono { isDecide: reqItem.isDecide, reqType: reqItem.reqType, addSource: reqItem.addSource || '', - flag: `${reqItem.friendUid}|${reqItem.reqTime}` + flag: reqItem.friendUid } })) return c.json({ success: true, data: enriched }) @@ -92,7 +92,7 @@ export function createNotificationRoutes(ctx: Context): Hono { if (!uid) { return c.json({ success: false, message: '缺少必要参数' }, 400) } - await ctx.ntFriendApi.approvalDoubtBuddyReq(uid) + await ctx.ntFriendApi.approvalDoubtFriendRequest(uid) return c.json({ success: true }) } catch (e) { ctx.logger.error('处理被过滤好友申请失败:', e) @@ -132,11 +132,7 @@ export function createNotificationRoutes(ctx: Context): Hono { if (!flag || !action) { return c.json({ success: false, message: '缺少必要参数' }, 400) } - const [friendUid, reqTime] = flag.split('|') - if (!friendUid) { - return c.json({ success: false, message: '无效的 flag 参数' }, 400) - } - await ctx.ntFriendApi.handleFriendRequest(friendUid, reqTime || '0', action === 'approve') + await ctx.ntFriendApi.approvalFriendRequest(flag, action === 'approve') return c.json({ success: true }) } catch (e) { ctx.logger.error('处理好友申请失败:', e) diff --git a/src/webui/BE/server.ts b/src/webui/BE/server.ts index 13a6efcf3..a7c08ac24 100644 --- a/src/webui/BE/server.ts +++ b/src/webui/BE/server.ts @@ -216,7 +216,7 @@ export class WebuiServer extends Service { isDecide: req.isDecide, reqType: req.reqType, addSource: req.addSource || '', - flag: `${req.friendUid}|${req.reqTime}` + flag: req.friendUid } }) } catch (e) { diff --git a/test/webui/helpers/mockContext.ts b/test/webui/helpers/mockContext.ts index 9b6b60e7e..f7d885161 100644 --- a/test/webui/helpers/mockContext.ts +++ b/test/webui/helpers/mockContext.ts @@ -8,11 +8,14 @@ export function createMockContext() { quickLoginWithUin: vi.fn(), }, ntFriendApi: { - getBuddyList: vi.fn(() => Promise.resolve([])), + getFriendList: vi.fn(() => Promise.resolve({ + friends: [], + categories: new Map() + })), getBuddyReq: vi.fn(() => Promise.resolve({ buddyReqs: [] })), getDoubtBuddyReq: vi.fn(() => Promise.resolve({ doubtList: [] })), - approvalDoubtBuddyReq: vi.fn(() => Promise.resolve()), - handleFriendRequest: vi.fn(() => Promise.resolve()), + approvalDoubtFriendRequest: vi.fn(() => Promise.resolve()), + approvalFriendRequest: vi.fn(() => Promise.resolve()), }, ntGroupApi: { getGroups: vi.fn(() => Promise.resolve([])), diff --git a/test/webui/routes/dashboard.test.ts b/test/webui/routes/dashboard.test.ts index 5d655658c..86e39e773 100644 --- a/test/webui/routes/dashboard.test.ts +++ b/test/webui/routes/dashboard.test.ts @@ -32,7 +32,10 @@ describe('dashboard routes', () => { if (key === 'pmhq') return ctx.pmhq return undefined }) - ctx.ntFriendApi.getBuddyList.mockResolvedValue([{}, {}, {}]) + ctx.ntFriendApi.getFriendList.mockResolvedValue({ + friends: [{}, {}, {}], + categories: new Map() + }) ctx.ntGroupApi.getGroups.mockResolvedValue([{}, {}]) const app = createTestApp(createDashboardRoutes(ctx)) diff --git a/test/webui/routes/webqq/notifications.test.ts b/test/webui/routes/webqq/notifications.test.ts index 72696119b..32a91e91d 100644 --- a/test/webui/routes/webqq/notifications.test.ts +++ b/test/webui/routes/webqq/notifications.test.ts @@ -115,7 +115,7 @@ describe('notification routes', () => { body: JSON.stringify({ uid: 'u1' }), }) expect(res.status).toBe(200) - expect(ctx.ntFriendApi.approvalDoubtBuddyReq).toHaveBeenCalledWith('u1') + expect(ctx.ntFriendApi.approvalDoubtFriendRequest).toHaveBeenCalledWith('u1') }) }) @@ -179,10 +179,10 @@ describe('notification routes', () => { const res = await app.request('/notifications/friend/handle', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ flag: 'uid123|1000', action: 'approve' }), + body: JSON.stringify({ flag: 'uid123', action: 'approve' }), }) expect(res.status).toBe(200) - expect(ctx.ntFriendApi.handleFriendRequest).toHaveBeenCalledWith('uid123', '1000', true) + expect(ctx.ntFriendApi.approvalFriendRequest).toHaveBeenCalledWith('uid123', true) }) }) }) diff --git a/yarn.lock b/yarn.lock index 7c7dc6eb0..d6248ef87 100644 --- a/yarn.lock +++ b/yarn.lock @@ -246,12 +246,12 @@ __metadata: languageName: node linkType: hard -"@hono/node-server@npm:^2.0.0": - version: 2.0.0 - resolution: "@hono/node-server@npm:2.0.0" +"@hono/node-server@npm:^2.0.2": + version: 2.0.2 + resolution: "@hono/node-server@npm:2.0.2" peerDependencies: hono: ^4 - checksum: 10c0/bc4b2ca237b29deff442a158e8fedafa0c05945e87600b05fe47226d32e55a4b194c25c934cc1af4658fb8c04a7cae2030295bba0f84de216559706d69945ad9 + checksum: 10c0/11019c5be0172ea329bd30c4ffa5c5021afa07c9e8701af8a08d2370e977564d648b17534b8ad8f3ef71895d76cd6cc003e15a120046e737303d560a7483c5dc languageName: node linkType: hard @@ -322,6 +322,13 @@ __metadata: languageName: node linkType: hard +"@oxc-project/types@npm:=0.128.0": + version: 0.128.0 + resolution: "@oxc-project/types@npm:0.128.0" + checksum: 10c0/b6999b1b6b012d979364231a2c0c9204bca814a73f8417234edd39bf352a081779dad72aaf18ac60a676fb904c1408b63553e4e1230d7408a4f885002d66c809 + languageName: node + linkType: hard + "@rolldown/binding-android-arm64@npm:1.0.0-rc.17": version: 1.0.0-rc.17 resolution: "@rolldown/binding-android-arm64@npm:1.0.0-rc.17" @@ -329,6 +336,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-android-arm64@npm:1.0.0-rc.18": + version: 1.0.0-rc.18 + resolution: "@rolldown/binding-android-arm64@npm:1.0.0-rc.18" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@rolldown/binding-darwin-arm64@npm:1.0.0-rc.17": version: 1.0.0-rc.17 resolution: "@rolldown/binding-darwin-arm64@npm:1.0.0-rc.17" @@ -336,6 +350,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-darwin-arm64@npm:1.0.0-rc.18": + version: 1.0.0-rc.18 + resolution: "@rolldown/binding-darwin-arm64@npm:1.0.0-rc.18" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@rolldown/binding-darwin-x64@npm:1.0.0-rc.17": version: 1.0.0-rc.17 resolution: "@rolldown/binding-darwin-x64@npm:1.0.0-rc.17" @@ -343,6 +364,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-darwin-x64@npm:1.0.0-rc.18": + version: 1.0.0-rc.18 + resolution: "@rolldown/binding-darwin-x64@npm:1.0.0-rc.18" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@rolldown/binding-freebsd-x64@npm:1.0.0-rc.17": version: 1.0.0-rc.17 resolution: "@rolldown/binding-freebsd-x64@npm:1.0.0-rc.17" @@ -350,6 +378,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-freebsd-x64@npm:1.0.0-rc.18": + version: 1.0.0-rc.18 + resolution: "@rolldown/binding-freebsd-x64@npm:1.0.0-rc.18" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-rc.17": version: 1.0.0-rc.17 resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-rc.17" @@ -357,6 +392,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-rc.18": + version: 1.0.0-rc.18 + resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-rc.18" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@rolldown/binding-linux-arm64-gnu@npm:1.0.0-rc.17": version: 1.0.0-rc.17 resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.0.0-rc.17" @@ -364,6 +406,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-linux-arm64-gnu@npm:1.0.0-rc.18": + version: 1.0.0-rc.18 + resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.0.0-rc.18" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "@rolldown/binding-linux-arm64-musl@npm:1.0.0-rc.17": version: 1.0.0-rc.17 resolution: "@rolldown/binding-linux-arm64-musl@npm:1.0.0-rc.17" @@ -371,6 +420,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-linux-arm64-musl@npm:1.0.0-rc.18": + version: 1.0.0-rc.18 + resolution: "@rolldown/binding-linux-arm64-musl@npm:1.0.0-rc.18" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "@rolldown/binding-linux-ppc64-gnu@npm:1.0.0-rc.17": version: 1.0.0-rc.17 resolution: "@rolldown/binding-linux-ppc64-gnu@npm:1.0.0-rc.17" @@ -378,6 +434,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-linux-ppc64-gnu@npm:1.0.0-rc.18": + version: 1.0.0-rc.18 + resolution: "@rolldown/binding-linux-ppc64-gnu@npm:1.0.0-rc.18" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + "@rolldown/binding-linux-s390x-gnu@npm:1.0.0-rc.17": version: 1.0.0-rc.17 resolution: "@rolldown/binding-linux-s390x-gnu@npm:1.0.0-rc.17" @@ -385,6 +448,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-linux-s390x-gnu@npm:1.0.0-rc.18": + version: 1.0.0-rc.18 + resolution: "@rolldown/binding-linux-s390x-gnu@npm:1.0.0-rc.18" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + "@rolldown/binding-linux-x64-gnu@npm:1.0.0-rc.17": version: 1.0.0-rc.17 resolution: "@rolldown/binding-linux-x64-gnu@npm:1.0.0-rc.17" @@ -392,6 +462,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-linux-x64-gnu@npm:1.0.0-rc.18": + version: 1.0.0-rc.18 + resolution: "@rolldown/binding-linux-x64-gnu@npm:1.0.0-rc.18" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "@rolldown/binding-linux-x64-musl@npm:1.0.0-rc.17": version: 1.0.0-rc.17 resolution: "@rolldown/binding-linux-x64-musl@npm:1.0.0-rc.17" @@ -399,6 +476,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-linux-x64-musl@npm:1.0.0-rc.18": + version: 1.0.0-rc.18 + resolution: "@rolldown/binding-linux-x64-musl@npm:1.0.0-rc.18" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + "@rolldown/binding-openharmony-arm64@npm:1.0.0-rc.17": version: 1.0.0-rc.17 resolution: "@rolldown/binding-openharmony-arm64@npm:1.0.0-rc.17" @@ -406,6 +490,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-openharmony-arm64@npm:1.0.0-rc.18": + version: 1.0.0-rc.18 + resolution: "@rolldown/binding-openharmony-arm64@npm:1.0.0-rc.18" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + "@rolldown/binding-wasm32-wasi@npm:1.0.0-rc.17": version: 1.0.0-rc.17 resolution: "@rolldown/binding-wasm32-wasi@npm:1.0.0-rc.17" @@ -417,6 +508,17 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-wasm32-wasi@npm:1.0.0-rc.18": + version: 1.0.0-rc.18 + resolution: "@rolldown/binding-wasm32-wasi@npm:1.0.0-rc.18" + dependencies: + "@emnapi/core": "npm:1.10.0" + "@emnapi/runtime": "npm:1.10.0" + "@napi-rs/wasm-runtime": "npm:^1.1.4" + conditions: cpu=wasm32 + languageName: node + linkType: hard + "@rolldown/binding-win32-arm64-msvc@npm:1.0.0-rc.17": version: 1.0.0-rc.17 resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.0.0-rc.17" @@ -424,6 +526,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-win32-arm64-msvc@npm:1.0.0-rc.18": + version: 1.0.0-rc.18 + resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.0.0-rc.18" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@rolldown/binding-win32-x64-msvc@npm:1.0.0-rc.17": version: 1.0.0-rc.17 resolution: "@rolldown/binding-win32-x64-msvc@npm:1.0.0-rc.17" @@ -431,6 +540,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-win32-x64-msvc@npm:1.0.0-rc.18": + version: 1.0.0-rc.18 + resolution: "@rolldown/binding-win32-x64-msvc@npm:1.0.0-rc.18" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@rolldown/pluginutils@npm:1.0.0-rc.17": version: 1.0.0-rc.17 resolution: "@rolldown/pluginutils@npm:1.0.0-rc.17" @@ -438,6 +554,13 @@ __metadata: languageName: node linkType: hard +"@rolldown/pluginutils@npm:1.0.0-rc.18": + version: 1.0.0-rc.18 + resolution: "@rolldown/pluginutils@npm:1.0.0-rc.18" + checksum: 10c0/c09f2ebe53762df23b725f452a3f7ee45968824b062a38ec06054e368551e8c5e1874b0ef28143ff3b1b9d6d5ca60177a34378bdd672e899c3646fb8d0bd5aff + languageName: node + linkType: hard + "@saltify/milky-types@npm:^1.2.2": version: 1.2.2 resolution: "@saltify/milky-types@npm:1.2.2" @@ -1040,26 +1163,26 @@ __metadata: languageName: node linkType: hard -"fast-xml-builder@npm:^1.1.5": - version: 1.1.5 - resolution: "fast-xml-builder@npm:1.1.5" +"fast-xml-builder@npm:^1.1.7": + version: 1.1.9 + resolution: "fast-xml-builder@npm:1.1.9" dependencies: path-expression-matcher: "npm:^1.1.3" - checksum: 10c0/b814ba5559cb3140de46d2846045607ab4d4c0bfc312a49d22c91efb9f7cd7004971314841e5823eeb467a5bf403e3ade8371b7912200e111df027d42ae51715 + checksum: 10c0/75253ccb2c6cc0fa66f69e1082493af3b15333c5c243f42092e8853649387e37899b148f47233c57af34e6252fe86b388de0e66c61fb4c4c137a417b56b712b0 languageName: node linkType: hard -"fast-xml-parser@npm:^5.7.2": - version: 5.7.2 - resolution: "fast-xml-parser@npm:5.7.2" +"fast-xml-parser@npm:^5.7.3": + version: 5.7.3 + resolution: "fast-xml-parser@npm:5.7.3" dependencies: "@nodable/entities": "npm:^2.1.0" - fast-xml-builder: "npm:^1.1.5" + fast-xml-builder: "npm:^1.1.7" path-expression-matcher: "npm:^1.5.0" strnum: "npm:^2.2.3" bin: fxparser: src/cli/cli.js - checksum: 10c0/d48439ce0700add82f5e7c6ccc5a1f06483beb7cd8e88caa83c6406843e52f14988e60d05cbb3a86ffe07e073807674c807e0764d94a280e1c96d7e2011dae8e + checksum: 10c0/eeb802855e852ce16121396297f04131c6dbc74f863be94f19e26e386656bdb31677af469ddc6627983a48b99d8842888460ac5413063cb648fde547bb579978 languageName: node linkType: hard @@ -1178,10 +1301,10 @@ __metadata: languageName: node linkType: hard -"hono@npm:^4.12.15": - version: 4.12.15 - resolution: "hono@npm:4.12.15" - checksum: 10c0/83f5778525bd4b706ddf1877c5f3cec0c8a3a0edf0944f8dfb041179c23a854166b8566f6e9be95894199a22e3b3aba139582d4ba24776e707c453d293bda24d +"hono@npm:^4.12.18": + version: 4.12.18 + resolution: "hono@npm:4.12.18" + checksum: 10c0/b0b9688fd9e41a1847b077d579dc0e92a28b67c247c6ee7d1e751c0bae269824c30c7773feff1a2874e40ea36a3d2f9d1fc5ba618a28ecdf2ca1b33ed2473864 languageName: node linkType: hard @@ -1386,7 +1509,7 @@ __metadata: dependencies: "@cordisjs/plugin-logger": "npm:^1.0.2" "@cordisjs/plugin-timer": "npm:^1.1.1" - "@hono/node-server": "npm:^2.0.0" + "@hono/node-server": "npm:^2.0.2" "@minatojs/driver-sqlite": "npm:^5.0.3" "@saltify/milky-types": "npm:^1.2.2" "@saltify/typeproto": "npm:^0.4.1" @@ -1400,11 +1523,11 @@ __metadata: compare-versions: "npm:^6.1.1" cordis: "npm:^4.0.0-rc.4" cosmokit: "npm:^1.8.1" - fast-xml-parser: "npm:^5.7.2" + fast-xml-parser: "npm:^5.7.3" file-type: "npm:^22.0.1" fluent-ffmpeg: "npm:^2.1.3" get-port: "npm:^7.2.0" - hono: "npm:^4.12.15" + hono: "npm:^4.12.18" image-size: "npm:^2.0.2" json5: "npm:^2.2.3" minato: "npm:^4.0.1" @@ -1416,7 +1539,7 @@ __metadata: ts-case-convert: "npm:^2.1.0" tsx: "npm:^4.21.0" typescript: "npm:^6.0.3" - vite: "npm:^8.0.10" + vite: "npm:^8.0.11" vite-plugin-cp: "npm:^6.0.3" vitest: "npm:^4.1.5" ws: "npm:^8.20.0" @@ -1606,6 +1729,17 @@ __metadata: languageName: node linkType: hard +"postcss@npm:^8.5.14": + version: 8.5.14 + resolution: "postcss@npm:8.5.14" + dependencies: + nanoid: "npm:^3.3.11" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/48138207cf5ef5581be1bfe2cb65ccfe0ac75e43888ba045afc8ed6043d7b56aeb3b9a9fe5b353ff554be943cd0cc15d826ccb991525159175971e5ee8ab0237 + languageName: node + linkType: hard + "proc-log@npm:^6.0.0": version: 6.1.0 resolution: "proc-log@npm:6.1.0" @@ -1716,6 +1850,64 @@ __metadata: languageName: node linkType: hard +"rolldown@npm:1.0.0-rc.18": + version: 1.0.0-rc.18 + resolution: "rolldown@npm:1.0.0-rc.18" + dependencies: + "@oxc-project/types": "npm:=0.128.0" + "@rolldown/binding-android-arm64": "npm:1.0.0-rc.18" + "@rolldown/binding-darwin-arm64": "npm:1.0.0-rc.18" + "@rolldown/binding-darwin-x64": "npm:1.0.0-rc.18" + "@rolldown/binding-freebsd-x64": "npm:1.0.0-rc.18" + "@rolldown/binding-linux-arm-gnueabihf": "npm:1.0.0-rc.18" + "@rolldown/binding-linux-arm64-gnu": "npm:1.0.0-rc.18" + "@rolldown/binding-linux-arm64-musl": "npm:1.0.0-rc.18" + "@rolldown/binding-linux-ppc64-gnu": "npm:1.0.0-rc.18" + "@rolldown/binding-linux-s390x-gnu": "npm:1.0.0-rc.18" + "@rolldown/binding-linux-x64-gnu": "npm:1.0.0-rc.18" + "@rolldown/binding-linux-x64-musl": "npm:1.0.0-rc.18" + "@rolldown/binding-openharmony-arm64": "npm:1.0.0-rc.18" + "@rolldown/binding-wasm32-wasi": "npm:1.0.0-rc.18" + "@rolldown/binding-win32-arm64-msvc": "npm:1.0.0-rc.18" + "@rolldown/binding-win32-x64-msvc": "npm:1.0.0-rc.18" + "@rolldown/pluginutils": "npm:1.0.0-rc.18" + dependenciesMeta: + "@rolldown/binding-android-arm64": + optional: true + "@rolldown/binding-darwin-arm64": + optional: true + "@rolldown/binding-darwin-x64": + optional: true + "@rolldown/binding-freebsd-x64": + optional: true + "@rolldown/binding-linux-arm-gnueabihf": + optional: true + "@rolldown/binding-linux-arm64-gnu": + optional: true + "@rolldown/binding-linux-arm64-musl": + optional: true + "@rolldown/binding-linux-ppc64-gnu": + optional: true + "@rolldown/binding-linux-s390x-gnu": + optional: true + "@rolldown/binding-linux-x64-gnu": + optional: true + "@rolldown/binding-linux-x64-musl": + optional: true + "@rolldown/binding-openharmony-arm64": + optional: true + "@rolldown/binding-wasm32-wasi": + optional: true + "@rolldown/binding-win32-arm64-msvc": + optional: true + "@rolldown/binding-win32-x64-msvc": + optional: true + bin: + rolldown: bin/cli.mjs + checksum: 10c0/699b8545a9a8b85ed4c639122163a6f46f84404fd88262bafa9549b01546744db625fd4425fceb4658c888de1671323170de1f837f6f6bb93e243e6e1d48c114 + languageName: node + linkType: hard + "schemastery@npm:^3.18.0": version: 3.18.0 resolution: "schemastery@npm:3.18.0" @@ -1979,7 +2171,7 @@ __metadata: languageName: node linkType: hard -"vite@npm:^6.0.0 || ^7.0.0 || ^8.0.0, vite@npm:^8.0.10": +"vite@npm:^6.0.0 || ^7.0.0 || ^8.0.0": version: 8.0.10 resolution: "vite@npm:8.0.10" dependencies: @@ -2036,6 +2228,63 @@ __metadata: languageName: node linkType: hard +"vite@npm:^8.0.11": + version: 8.0.11 + resolution: "vite@npm:8.0.11" + dependencies: + fsevents: "npm:~2.3.3" + lightningcss: "npm:^1.32.0" + picomatch: "npm:^4.0.4" + postcss: "npm:^8.5.14" + rolldown: "npm:1.0.0-rc.18" + tinyglobby: "npm:^0.2.16" + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + "@vitejs/devtools": ^0.1.18 + esbuild: ^0.27.0 || ^0.28.0 + jiti: ">=1.21.0" + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + "@vitejs/devtools": + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/504ec6064761239e7063426dd123ea68cd540cb2d475bf72f5b1062313b9c79984831f56a20891ed5e08b2753d34171ee7a75cbadf9365e975d1f68634f0a10f + languageName: node + linkType: hard + "vitest@npm:^4.1.5": version: 4.1.5 resolution: "vitest@npm:4.1.5"