diff --git a/src/ntqqapi/api/file.ts b/src/ntqqapi/api/file.ts index 7c99012..5f7dfe6 100644 --- a/src/ntqqapi/api/file.ts +++ b/src/ntqqapi/api/file.ts @@ -28,13 +28,13 @@ import { OnRichMediaDownloadCompleteParams } from '@/ntqqapi/listeners' import { Time } from 'cosmokit' export class NTQQFileApi { - static async getVideoUrl(peer: Peer, msgId: string, elementId: string): Promise { + static async getVideoUrl(peer: Peer, msgId: string, elementId: string) { const session = getSession() return (await session?.getRichMediaService().getVideoPlayUrlV2(peer, msgId, elementId, 0, - { downSourceType: 1, triggerType: 1 }))?.urlResult?.domainUrl[0]?.url! + { downSourceType: 1, triggerType: 1 }))?.urlResult.domainUrl[0].url } static async getFileType(filePath: string) { @@ -53,22 +53,43 @@ export class NTQQFileApi { fileName += ext } const session = getSession() - const mediaPath = session?.getMsgService().getRichMediaFilePathForGuild({ - md5HexStr: fileMd5, - fileName: fileName, - elementType: elementType, - elementSubType, - thumbSize: 0, - needCreate: true, - downloadType: 1, - file_uuid: '' - }) - await fsPromise.copyFile(filePath, mediaPath!) + let mediaPath: string + if (session) { + mediaPath = session?.getMsgService().getRichMediaFilePathForGuild({ + md5HexStr: fileMd5, + fileName: fileName, + elementType: elementType, + elementSubType, + thumbSize: 0, + needCreate: true, + downloadType: 1, + file_uuid: '' + }) + } else { + mediaPath = await invoke({ + methodName: NTMethod.MEDIA_FILE_PATH, + args: [ + { + path_info: { + md5HexStr: fileMd5, + fileName: fileName, + elementType: elementType, + elementSubType, + thumbSize: 0, + needCreate: true, + downloadType: 1, + file_uuid: '', + }, + }, + ], + }) + } + await fsPromise.copyFile(filePath, mediaPath) const fileSize = (await fsPromise.stat(filePath)).size return { md5: fileMd5, fileName, - path: mediaPath!, + path: mediaPath, fileSize, ext } @@ -89,53 +110,79 @@ export class NTQQFileApi { if (force) { try { await fsPromise.unlink(sourcePath) - } catch (e) { - // - } + } catch { } } else { return sourcePath } } - const data = await NTEventDispatch.CallNormalEvent< - ( - params: { - fileModelId: string, - downloadSourceType: number, - triggerType: number, - msgId: string, - chatType: ChatType, - peerUid: string, - elementId: string, - thumbSize: number, - downloadType: number, - filePath: string - }) => Promise, - (fileTransNotifyInfo: OnRichMediaDownloadCompleteParams) => void - >( - 'NodeIKernelMsgService/downloadRichMedia', - 'NodeIKernelMsgListener/onRichMediaDownloadComplete', - 1, - timeout, - (arg: OnRichMediaDownloadCompleteParams) => { - if (arg.msgId === msgId) { - return true + let filePath: string + if (NTEventDispatch.initialised) { + const data = await NTEventDispatch.CallNormalEvent< + ( + params: { + fileModelId: string, + downloadSourceType: number, + triggerType: number, + msgId: string, + chatType: ChatType, + peerUid: string, + elementId: string, + thumbSize: number, + downloadType: number, + filePath: string + }) => Promise, + (fileTransNotifyInfo: OnRichMediaDownloadCompleteParams) => void + >( + 'NodeIKernelMsgService/downloadRichMedia', + 'NodeIKernelMsgListener/onRichMediaDownloadComplete', + 1, + timeout, + (arg: OnRichMediaDownloadCompleteParams) => { + if (arg.msgId === msgId) { + return true + } + return false + }, + { + fileModelId: '0', + downloadSourceType: 0, + triggerType: 1, + msgId: msgId, + chatType: chatType, + peerUid: peerUid, + elementId: elementId, + thumbSize: 0, + downloadType: 1, + filePath: thumbPath } - return false - }, - { - fileModelId: '0', - downloadSourceType: 0, - triggerType: 1, - msgId: msgId, - chatType: chatType, - peerUid: peerUid, - elementId: elementId, - thumbSize: 0, - downloadType: 1, - filePath: thumbPath - } - ) - let filePath = data[1].filePath + ) + filePath = data[1].filePath + } else { + const data = await invoke<{ notifyInfo: OnRichMediaDownloadCompleteParams }>({ + methodName: NTMethod.DOWNLOAD_MEDIA, + args: [ + { + getReq: { + fileModelId: '0', + downloadSourceType: 0, + triggerType: 1, + msgId: msgId, + chatType: chatType, + peerUid: peerUid, + elementId: elementId, + thumbSize: 0, + downloadType: 1, + filePath: thumbPath, + }, + }, + null, + ], + cbCmd: ReceiveCmdS.MEDIA_DOWNLOAD_COMPLETE, + cmdCB: payload => payload.notifyInfo.msgId === msgId, + timeout + }) + filePath = data.notifyInfo.filePath + } if (filePath.startsWith('\\')) { const downloadPath = TEMP_DIR filePath = path.join(downloadPath, filePath) diff --git a/src/ntqqapi/api/friend.ts b/src/ntqqapi/api/friend.ts index 3981e5f..bb017fd 100644 --- a/src/ntqqapi/api/friend.ts +++ b/src/ntqqapi/api/friend.ts @@ -1,6 +1,6 @@ -import { Friend, FriendV2 } from '../types' +import { Friend, FriendV2, SimpleInfo, CategoryFriend } from '../types' import { ReceiveCmdS } from '../hook' -import { invoke, NTMethod } from '../ntcall' +import { invoke, NTMethod, NTClass } from '../ntcall' import { getSession } from '@/ntqqapi/wrapper' import { BuddyListReqType, NodeIKernelProfileService } from '../services' import { NTEventDispatch } from '@/common/utils/EventTask' @@ -22,7 +22,6 @@ export class NTQQFriendApi { cbCmd: ReceiveCmdS.FRIENDS, afterFirstCmd: false, }) - // log('获取好友列表', data) let _friends: Friend[] = [] for (const fData of data.data) { _friends.push(...fData.buddyList) @@ -38,62 +37,129 @@ export class NTQQFriendApi { const friendUid = data[0] const reqTime = data[1] const session = getSession() - return session?.getBuddyService().approvalFriendRequest({ - friendUid, - reqTime, - accept - }) + if (session) { + return session.getBuddyService().approvalFriendRequest({ + friendUid, + reqTime, + accept + }) + } else { + return await invoke({ + methodName: NTMethod.HANDLE_FRIEND_REQUEST, + args: [ + { + approvalInfo: { + friendUid, + reqTime, + accept, + }, + }, + ], + }) + } } static async getBuddyV2(refresh = false): Promise { - const uids: string[] = [] const session = getSession() - const buddyService = session?.getBuddyService() - const buddyListV2 = refresh ? await buddyService?.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService?.getBuddyListV2('0', BuddyListReqType.KNOMAL) - uids.push(...buddyListV2?.data.flatMap(item => item.buddyUids)!) - const data = await NTEventDispatch.CallNoListenerEvent( - 'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids - ) - return Array.from(data.values()) + if (session) { + const uids: string[] = [] + const buddyService = session.getBuddyService() + const buddyListV2 = await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) + uids.push(...buddyListV2.data.flatMap(item => item.buddyUids)) + const data = await NTEventDispatch.CallNoListenerEvent( + 'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids + ) + return Array.from(data.values()) + } else { + const data = await invoke<{ + buddyCategory: CategoryFriend[] + userSimpleInfos: Map + }>({ + className: NTClass.NODE_STORE_API, + methodName: 'getBuddyList', + args: [refresh], + cbCmd: ReceiveCmdS.FRIENDS, + afterFirstCmd: false, + }) + return Array.from(data.userSimpleInfos.values()) + } } static async getBuddyIdMap(refresh = false): Promise> { - const uids: string[] = [] const retMap: LimitedHashTable = new LimitedHashTable(5000) const session = getSession() - const buddyService = session?.getBuddyService() - const buddyListV2 = refresh ? await buddyService?.getBuddyListV2('0', BuddyListReqType.KNOMAL) : await buddyService?.getBuddyListV2('0', BuddyListReqType.KNOMAL) - uids.push(...buddyListV2?.data.flatMap(item => item.buddyUids)!) - const data = await NTEventDispatch.CallNoListenerEvent( - 'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids - ); - data.forEach((value, key) => { - retMap.set(value.uin!, value.uid!) - }) - //console.log('getBuddyIdMap', retMap.getValue) + if (session) { + const uids: string[] = [] + const buddyService = session?.getBuddyService() + const buddyListV2 = await buddyService.getBuddyListV2('0', BuddyListReqType.KNOMAL) + uids.push(...buddyListV2.data.flatMap(item => item.buddyUids)) + const data = await NTEventDispatch.CallNoListenerEvent( + 'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids + ) + data.forEach((value, key) => { + retMap.set(value.uin!, value.uid!) + }) + } else { + const data = await invoke<{ + buddyCategory: CategoryFriend[] + userSimpleInfos: Map + }>({ + className: NTClass.NODE_STORE_API, + methodName: 'getBuddyList', + args: [refresh], + cbCmd: ReceiveCmdS.FRIENDS, + afterFirstCmd: false, + }) + data.userSimpleInfos.forEach((value, key) => { + retMap.set(value.uin!, value.uid!) + }) + } return retMap } static async getBuddyV2ExWithCate(refresh = false) { - const uids: string[] = [] - const categoryMap: Map = new Map() const session = getSession() - const buddyService = session?.getBuddyService() - const buddyListV2 = refresh ? (await buddyService?.getBuddyListV2('0', BuddyListReqType.KNOMAL))?.data : (await buddyService?.getBuddyListV2('0', BuddyListReqType.KNOMAL))?.data - uids.push( - ...buddyListV2?.flatMap(item => { - item.buddyUids.forEach(uid => { - categoryMap.set(uid, { categoryId: item.categoryId, categroyName: item.categroyName }) - }) - return item.buddyUids - })!) - const data = await NTEventDispatch.CallNoListenerEvent( - 'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids - ) - return Array.from(data).map(([key, value]) => { - const category = categoryMap.get(key) - return category ? { ...value, categoryId: category.categoryId, categroyName: category.categroyName } : value - }) + if (session) { + const uids: string[] = [] + const categoryMap: Map = new Map() + const buddyService = session.getBuddyService() + const buddyListV2 = (await buddyService?.getBuddyListV2('0', BuddyListReqType.KNOMAL))?.data + uids.push( + ...buddyListV2?.flatMap(item => { + item.buddyUids.forEach(uid => { + categoryMap.set(uid, { categoryId: item.categoryId, categroyName: item.categroyName }) + }) + return item.buddyUids + })!) + const data = await NTEventDispatch.CallNoListenerEvent( + 'NodeIKernelProfileService/getCoreAndBaseInfo', 5000, 'nodeStore', uids + ) + return Array.from(data).map(([key, value]) => { + const category = categoryMap.get(key) + return category ? { ...value, categoryId: category.categoryId, categroyName: category.categroyName } : value + }) + } else { + const data = await invoke<{ + buddyCategory: CategoryFriend[] + userSimpleInfos: Map + }>({ + className: NTClass.NODE_STORE_API, + methodName: 'getBuddyList', + args: [refresh], + cbCmd: ReceiveCmdS.FRIENDS, + afterFirstCmd: false, + }) + return Array.from(data.userSimpleInfos).map(([key, value]) => { + if (value.baseInfo) { + return { + ...value, + categoryId: value.baseInfo.categoryId, + categroyName: data.buddyCategory.find(e => e.categoryId === value.baseInfo.categoryId)?.categroyName + } + } + return value + }) + } } static async isBuddy(uid: string): Promise { diff --git a/src/ntqqapi/api/msg.ts b/src/ntqqapi/api/msg.ts index c95b66c..1c4236c 100644 --- a/src/ntqqapi/api/msg.ts +++ b/src/ntqqapi/api/msg.ts @@ -2,10 +2,19 @@ import { invoke, NTMethod } from '../ntcall' import { GeneralCallResult } from '../services' import { RawMessage, SendMessageElement, Peer, ChatType2 } from '../types' import { getSelfNick, getSelfUid } from '../../common/data' -import { getBuildVersion } from '../../common/utils' import { getSession } from '@/ntqqapi/wrapper' import { NTEventDispatch } from '@/common/utils/EventTask' +function generateMsgId() { + const timestamp = Math.floor(Date.now() / 1000) + const random = Math.floor(Math.random() * Math.pow(2, 32)) + const buffer = Buffer.alloc(8) + buffer.writeUInt32BE(timestamp, 0) + buffer.writeUInt32BE(random, 4) + const msgId = BigInt("0x" + buffer.toString('hex')).toString() + return msgId +} + export class NTQQMsgApi { static async getTempChatInfo(chatType: ChatType2, peerUid: string) { const session = getSession() @@ -68,49 +77,58 @@ export class NTQQMsgApi { } static async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) { - function generateMsgId() { - const timestamp = Math.floor(Date.now() / 1000) - const random = Math.floor(Math.random() * Math.pow(2, 32)) - const buffer = Buffer.alloc(8) - buffer.writeUInt32BE(timestamp, 0) - buffer.writeUInt32BE(random, 4) - const msgId = BigInt("0x" + buffer.toString('hex')).toString() - return msgId - } - // 此处有采用Hack方法 利用数据返回正确得到对应消息 - // 与之前 Peer队列 MsgSeq队列 真正的MsgId并发不同 - // 谨慎采用 目前测试暂无问题 Developer.Mlikiowa - let msgId: string - try { - msgId = await NTQQMsgApi.getMsgUnique(peer.chatType, await NTQQMsgApi.getServerTime()) - } catch (error) { - //if (!napCatCore.session.getMsgService()['generateMsgUniqueId']) - //兜底识别策略V2 - msgId = generateMsgId() - } + const msgId = generateMsgId() peer.guildId = msgId - const data = await NTEventDispatch.CallNormalEvent< - (msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map) => Promise, - (msgList: RawMessage[]) => void - >( - 'NodeIKernelMsgService/sendMsg', - 'NodeIKernelMsgListener/onMsgInfoListUpdate', - 1, - timeout, - (msgRecords: RawMessage[]) => { - for (let msgRecord of msgRecords) { - if (msgRecord.guildId === msgId && msgRecord.sendStatus === 2) { - return true + let msgList: RawMessage[] + if (NTEventDispatch.initialised) { + const data = await NTEventDispatch.CallNormalEvent< + (msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map) => Promise, + (msgList: RawMessage[]) => void + >( + 'NodeIKernelMsgService/sendMsg', + 'NodeIKernelMsgListener/onMsgInfoListUpdate', + 1, + timeout, + (msgRecords: RawMessage[]) => { + for (const msgRecord of msgRecords) { + if (msgRecord.guildId === msgId && msgRecord.sendStatus === 2) { + return true + } } - } - return false - }, - '0', - peer, - msgElements, - new Map() - ) - const retMsg = data[1].find(msgRecord => { + return false + }, + '0', + peer, + msgElements, + new Map() + ) + msgList = data[1] + } else { + const data = await invoke<{ msgList: RawMessage[] }>({ + methodName: 'nodeIKernelMsgService/sendMsg', + cbCmd: 'nodeIKernelMsgListener/onMsgInfoListUpdate', + afterFirstCmd: false, + cmdCB: payload => { + for (const msgRecord of payload.msgList) { + if (msgRecord.guildId === msgId && msgRecord.sendStatus === 2) { + return true + } + } + return false + }, + args: [ + { + msgId: '0', + peer, + msgElements, + msgAttributeInfos: new Map() + }, + null + ], + }) + msgList = data.msgList + } + const retMsg = msgList.find(msgRecord => { if (msgRecord.guildId === msgId) { return true } @@ -118,19 +136,6 @@ export class NTQQMsgApi { return retMsg! } - static async getMsgUnique(chatType: number, time: string) { - const session = getSession() - if (getBuildVersion() >= 26702) { - return session?.getMsgService().generateMsgUniqueId(chatType, time)! - } - return session?.getMsgService().getMsgUniqueId(time)! - } - - static async getServerTime() { - const session = getSession() - return session?.getMSFService().getServerTime()! - } - static async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) { const session = getSession() return session?.getMsgService().forwardMsg(msgIds, srcPeer, [destPeer], [])! diff --git a/src/ntqqapi/hook.ts b/src/ntqqapi/hook.ts index b2e4fbb..46a6b80 100644 --- a/src/ntqqapi/hook.ts +++ b/src/ntqqapi/hook.ts @@ -49,9 +49,9 @@ export let ReceiveCmdS = { CACHE_SCAN_FINISH: 'nodeIKernelStorageCleanListener/onFinishScan', MEDIA_UPLOAD_COMPLETE: 'nodeIKernelMsgListener/onRichMediaUploadComplete', SKEY_UPDATE: 'onSkeyUpdate', -} +} as const -export type ReceiveCmd = (typeof ReceiveCmdS)[keyof typeof ReceiveCmdS] +export type ReceiveCmd = string interface NTQQApiReturnData extends Array { 0: { @@ -66,19 +66,19 @@ interface NTQQApiReturnData extends Array { }[] } -let receiveHooks: Array<{ +const logHook = false + +const receiveHooks: Array<{ method: ReceiveCmd[] hookFunc: (payload: any) => void | Promise id: string }> = [] -let callHooks: Array<{ +const callHooks: Array<{ method: NTMethod[] hookFunc: (callParams: unknown[]) => void | Promise }> = [] -const logHook = false - export function hookNTQQApiReceive(window: BrowserWindow) { const originalSend = window.webContents.send const patchSend = (channel: string, ...args: NTQQApiReturnData) => { diff --git a/src/ntqqapi/ntcall.ts b/src/ntqqapi/ntcall.ts index 54863f3..168f3e2 100644 --- a/src/ntqqapi/ntcall.ts +++ b/src/ntqqapi/ntcall.ts @@ -1,5 +1,5 @@ import { ipcMain } from 'electron' -import { hookApiCallbacks, ReceiveCmd, registerReceiveHook, removeReceiveHook } from './hook' +import { hookApiCallbacks, registerReceiveHook, removeReceiveHook } from './hook' import { log } from '../common/utils/log' import { randomUUID } from 'node:crypto' import { GeneralCallResult } from './services' @@ -57,7 +57,6 @@ export enum NTMethod { HANDLE_GROUP_REQUEST = 'nodeIKernelGroupService/operateSysNotify', QUIT_GROUP = 'nodeIKernelGroupService/quitGroup', GROUP_AT_ALL_REMAIN_COUNT = 'nodeIKernelGroupService/getGroupRemainAtTimes', - // READ_FRIEND_REQUEST = "nodeIKernelBuddyListener/onDoubtBuddyReqUnreadNumChange" HANDLE_FRIEND_REQUEST = 'nodeIKernelBuddyService/approvalFriendRequest', KICK_MEMBER = 'nodeIKernelGroupService/kickMember', MUTE_MEMBER = 'nodeIKernelGroupService/setMemberShutUp', @@ -87,10 +86,6 @@ export enum NTMethod { OPEN_EXTRA_WINDOW = 'openExternalWindow', SET_QQ_AVATAR = 'nodeIKernelProfileService/setHeader', - GET_PSKEY = 'nodeIKernelTipOffService/getPskey', - UPDATE_SKEY = 'updatePskey', - - FETCH_UNITED_COMMEND_CONFIG = 'nodeIKernelUnitedConfigService/fetchUnitedCommendConfig', // 发包需要调用的 } export enum NTChannel { @@ -99,19 +94,19 @@ export enum NTChannel { IPC_UP_1 = 'IPC_UP_1', } -interface InvokeParams { +interface InvokeParams { methodName: string className?: NTClass channel?: NTChannel classNameIsRegister?: boolean args?: unknown[] cbCmd?: string | string[] - cmdCB?: (payload: any) => boolean + cmdCB?: (payload: ReturnType) => boolean afterFirstCmd?: boolean // 是否在methodName调用完之后再去hook cbCmd timeout?: number } -export function invoke(params: InvokeParams) { +export function invoke(params: InvokeParams) { const className = params.className ?? NTClass.NT_API const channel = params.channel ?? NTChannel.IPC_UP_2 const timeout = params.timeout ?? 5000 @@ -164,7 +159,6 @@ export function invoke(params: InvokeParams) { } } setTimeout(() => { - // log("ntqq api timeout", success, channel, className, methodName) if (!success) { log(`ntqq api timeout ${channel}, ${eventName}, ${params.methodName}`, apiArgs) reject(`ntqq api timeout ${channel}, ${eventName}, ${params.methodName}, ${apiArgs}`) diff --git a/src/ntqqapi/types/user.ts b/src/ntqqapi/types/user.ts index d687734..e7018fc 100644 --- a/src/ntqqapi/types/user.ts +++ b/src/ntqqapi/types/user.ts @@ -78,8 +78,10 @@ export interface Friend extends User { export interface CategoryFriend { categoryId: number + categorySortId: number categroyName: string categroyMbCount: number + onlineCount: number buddyList: User[] }