From 4678253815759b4a4101c7abc4e01bae504f2b3b Mon Sep 17 00:00:00 2001 From: idranme Date: Sun, 11 Aug 2024 00:18:54 +0800 Subject: [PATCH 1/5] sync --- src/common/utils/index.ts | 3 +- src/ntqqapi/api/msg.ts | 60 +++++++++++---------------------------- src/ntqqapi/api/user.ts | 52 +++++++++++++-------------------- 3 files changed, 37 insertions(+), 78 deletions(-) diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts index fa95572..fd55490 100644 --- a/src/common/utils/index.ts +++ b/src/common/utils/index.ts @@ -15,5 +15,4 @@ if (!fs.existsSync(TEMP_DIR)) { } export { getVideoInfo } from './video' export { checkFfmpeg } from './video' -export { encodeSilk } from './audio' -export { isQQ998 } from './QQBasicInfo' \ No newline at end of file +export { encodeSilk } from './audio' \ No newline at end of file diff --git a/src/ntqqapi/api/msg.ts b/src/ntqqapi/api/msg.ts index f01de90..5603453 100644 --- a/src/ntqqapi/api/msg.ts +++ b/src/ntqqapi/api/msg.ts @@ -5,7 +5,7 @@ import { selfInfo } from '../../common/data' import { ReceiveCmdS, registerReceiveHook } from '../hook' import { log } from '../../common/utils/log' import { sleep } from '../../common/utils/helper' -import { isQQ998, getBuildVersion } from '../../common/utils' +import { getBuildVersion } from '../../common/utils' import { getSession } from '@/ntqqapi/wrapper' import { NTEventDispatch } from '@/common/utils/EventTask' @@ -72,25 +72,6 @@ export class NTQQMsgApi { return session?.getMsgService().getTempChatInfo(chatType, peerUid)! } - static enterOrExitAIO(peer: Peer, enter: boolean) { - return callNTQQApi({ - methodName: NTQQApiMethod.ENTER_OR_EXIT_AIO, - args: [ - { - "info_list": [ - { - peer, - "option": enter ? 1 : 2 - } - ] - }, - { - "send": true - }, - ], - }) - } - static async setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, set: boolean = true) { // nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览 // nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid @@ -158,20 +139,18 @@ export class NTQQMsgApi { }) } - static async getMsgHistory(peer: Peer, msgId: string, count: number) { + static async getMsgsByMsgId(peer: Peer | undefined, msgIds: string[] | undefined) { + if (!peer) throw new Error('peer is not allowed') + if (!msgIds) throw new Error('msgIds is not allowed') + const session = getSession() + //Mlikiowa: 参数不合规会导致NC异常崩溃 原因是TX未对进入参数判断 对应Android标记@NotNull AndroidJADX分析可得 + return await session?.getMsgService().getMsgsByMsgId(peer, msgIds)! + } + + static async getMsgHistory(peer: Peer, msgId: string, count: number, isReverseOrder: boolean = false) { + const session = getSession() // 消息时间从旧到新 - return await callNTQQApi({ - methodName: isQQ998 ? NTQQApiMethod.ACTIVE_CHAT_HISTORY : NTQQApiMethod.HISTORY_MSG, - args: [ - { - peer, - msgId, - cnt: count, - queryOrder: true, - }, - null, - ], - }) + return session?.getMsgService().getMsgsIncludeSelf(peer, msgId, count, isReverseOrder)! } static async fetchRecentContact() { @@ -196,16 +175,11 @@ export class NTQQMsgApi { } static async recallMsg(peer: Peer, msgIds: string[]) { - return await callNTQQApi({ - methodName: NTQQApiMethod.RECALL_MSG, - args: [ - { - peer, - msgIds, - }, - null, - ], - }) + const session = getSession() + return await session?.getMsgService().recallMsg({ + chatType: peer.chatType, + peerUid: peer.peerUid + }, msgIds) } static async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) { diff --git a/src/ntqqapi/api/user.ts b/src/ntqqapi/api/user.ts index bdc0f3b..37a1e4d 100644 --- a/src/ntqqapi/api/user.ts +++ b/src/ntqqapi/api/user.ts @@ -2,7 +2,7 @@ import { callNTQQApi, GeneralCallResult, NTQQApiClass, NTQQApiMethod } from '../ import { SelfInfo, User, UserDetailInfoByUin, UserDetailInfoByUinV2 } from '../types' import { ReceiveCmdS } from '../hook' import { selfInfo, friends, groupMembers } from '@/common/data' -import { CacheClassFuncAsync, isQQ998, log, sleep, getBuildVersion } from '@/common/utils' +import { CacheClassFuncAsync, log, getBuildVersion } from '@/common/utils' import { getSession } from '@/ntqqapi/wrapper' import { RequestUtil } from '@/common/utils/request' import { NodeIKernelProfileService, UserDetailSource, ProfileBizType } from '../services' @@ -85,39 +85,25 @@ export class NTQQUserApi { if (getBuildVersion() >= 26702) { return this.fetchUserDetailInfo(uid) } - // this.getUserInfo(uid) - let methodName = !isQQ998 ? NTQQApiMethod.USER_DETAIL_INFO : NTQQApiMethod.USER_DETAIL_INFO_WITH_BIZ_INFO - if (!withBizInfo) { - methodName = NTQQApiMethod.USER_DETAIL_INFO - } - const fetchInfo = async () => { - const result = await callNTQQApi<{ info: User }>({ - methodName, - cbCmd: ReceiveCmdS.USER_DETAIL_INFO, - afterFirstCmd: false, - cmdCB: (payload) => { - const success = payload.info.uid == uid - // log("get user detail info", success, uid, payload) - return success + type EventService = NodeIKernelProfileService['getUserDetailInfoWithBizInfo'] + type EventListener = NodeIKernelProfileListener['onProfileDetailInfoChanged'] + const [_retData, profile] = await NTEventDispatch.CallNormalEvent + + ( + 'NodeIKernelProfileService/getUserDetailInfoWithBizInfo', + 'NodeIKernelProfileListener/onProfileDetailInfoChanged', + 2, + 5000, + (profile: User) => { + if (profile.uid === uid) { + return true + } + return false }, - args: [ - { - uid, - }, - null, - ], - }) - const info = result.info - return info - } - // 首次请求两次才能拿到的等级信息 - if (!userInfoCache[uid] && getLevel) { - await fetchInfo() - await sleep(1000) - } - const userInfo = await fetchInfo() - userInfoCache[uid] = userInfo - return userInfo + uid, + [0] + ) + return profile } // return 'p_uin=o0xxx; p_skey=orXDssiGF8axxxxxxxxxxxxxx_; skey=' From 1472c9c9498baa05d67c9a64fe98bf1e756cd8ec Mon Sep 17 00:00:00 2001 From: idranme Date: Sun, 11 Aug 2024 00:23:17 +0800 Subject: [PATCH 2/5] opt --- src/common/utils/QQBasicInfo.ts | 35 --------------------------------- 1 file changed, 35 deletions(-) diff --git a/src/common/utils/QQBasicInfo.ts b/src/common/utils/QQBasicInfo.ts index 2e7c5e1..d0c8bb8 100644 --- a/src/common/utils/QQBasicInfo.ts +++ b/src/common/utils/QQBasicInfo.ts @@ -1,5 +1,4 @@ import path from 'node:path' -import fs from 'node:fs' import os from 'node:os' import { systemPlatform } from './system' @@ -38,32 +37,6 @@ type QQPkgInfo = { platform: string eleArch: string } -type QQVersionConfigInfo = { - baseVersion: string - curVersion: string - prevVersion: string - onErrorVersions: Array - buildId: string -} - -let _qqVersionConfigInfo: QQVersionConfigInfo = { - 'baseVersion': '9.9.9-23361', - 'curVersion': '9.9.9-23361', - 'prevVersion': '', - 'onErrorVersions': [], - 'buildId': '23361', -} - -if (fs.existsSync(configVersionInfoPath)) { - try { - const _ = JSON.parse(fs.readFileSync(configVersionInfoPath).toString()) - _qqVersionConfigInfo = Object.assign(_qqVersionConfigInfo, _) - } catch (e) { - console.error('Load QQ version config info failed, Use default version', e) - } -} - -export const qqVersionConfigInfo: QQVersionConfigInfo = _qqVersionConfigInfo export const qqPkgInfo: QQPkgInfo = require(pkgInfoPath) // platform_type: 3, @@ -74,14 +47,6 @@ export const qqPkgInfo: QQPkgInfo = require(pkgInfoPath) // platVer: '10.0.26100', // clientVer: '9.9.9-23159', -let _appid: string = '537213803' // 默认为 Windows 平台的 appid -if (systemPlatform === 'linux') { - _appid = '537213827' -} -// todo: mac 平台的 appid -export const appid = _appid -export const isQQ998: boolean = qqPkgInfo.buildVersion >= '22106' - export function getBuildVersion(): number { return +qqPkgInfo.buildVersion } \ No newline at end of file From 1da720e0a72d32b2b18bc755d376940c9fd8cec0 Mon Sep 17 00:00:00 2001 From: idranme Date: Sun, 11 Aug 2024 02:13:38 +0800 Subject: [PATCH 3/5] sync --- src/main/main.ts | 6 +- src/ntqqapi/api/msg.ts | 329 ++++++++------------ src/ntqqapi/hook.ts | 13 +- src/ntqqapi/types/msg.ts | 1 + src/onebot11/action/msg/ForwardSingleMsg.ts | 16 +- src/onebot11/action/msg/SendMsg.ts | 10 +- 6 files changed, 140 insertions(+), 235 deletions(-) diff --git a/src/main/main.ts b/src/main/main.ts index 33cf743..42000b8 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -36,7 +36,7 @@ import { OB11FriendRequestEvent } from '../onebot11/event/request/OB11FriendRequ import path from 'node:path' import { dbUtil } from '../common/db' import { setConfig } from './setConfig' -import { NTQQUserApi, NTQQGroupApi, sentMessages } from '../ntqqapi/api' +import { NTQQUserApi, NTQQGroupApi } from '../ntqqapi/api' import { checkNewVersion, upgradeLLOneBot } from '../common/utils/upgrade' import { log } from '../common/utils/log' import { getConfigUtil } from '../common/config' @@ -208,10 +208,6 @@ function onLoad() { const recallMsgIds: string[] = [] // 避免重复上报 registerReceiveHook<{ msgList: Array }>([ReceiveCmdS.UPDATE_MSG], async (payload) => { for (const message of payload.msgList) { - const sentMessage = sentMessages[message.msgId] - if (sentMessage) { - Object.assign(sentMessage, message) - } log('message update', message.msgId, message) if (message.recallTime != '0') { if (recallMsgIds.includes(message.msgId)) { diff --git a/src/ntqqapi/api/msg.ts b/src/ntqqapi/api/msg.ts index 5603453..e70a1f9 100644 --- a/src/ntqqapi/api/msg.ts +++ b/src/ntqqapi/api/msg.ts @@ -1,123 +1,50 @@ import { callNTQQApi, GeneralCallResult, NTQQApiMethod } from '../ntcall' -import { ChatType, RawMessage, SendMessageElement, Peer, ChatType2 } from '../types' -import { dbUtil } from '../../common/db' +import { RawMessage, SendMessageElement, Peer, ChatType2 } from '../types' import { selfInfo } from '../../common/data' -import { ReceiveCmdS, registerReceiveHook } from '../hook' -import { log } from '../../common/utils/log' -import { sleep } from '../../common/utils/helper' import { getBuildVersion } from '../../common/utils' import { getSession } from '@/ntqqapi/wrapper' import { NTEventDispatch } from '@/common/utils/EventTask' -export let sendMessagePool: Record void) | null> = {} // peerUid: callbackFunc - -export let sentMessages: Record = {} // msgId: RawMessage - -async function sendWaiter(peer: Peer, waitComplete = true, timeout: number = 10000) { - // 等待上一个相同的peer发送完 - const peerUid = peer.peerUid - let checkLastSendUsingTime = 0 - const waitLastSend = async () => { - if (checkLastSendUsingTime > timeout) { - throw '发送超时' - } - let lastSending = sendMessagePool[peer.peerUid] - if (lastSending) { - // log("有正在发送的消息,等待中...") - await sleep(500) - checkLastSendUsingTime += 500 - return await waitLastSend() - } - else { - return - } - } - await waitLastSend() - - let sentMessage: RawMessage | null = null - sendMessagePool[peerUid] = async (rawMessage: RawMessage) => { - delete sendMessagePool[peerUid] - sentMessage = rawMessage - sentMessages[rawMessage.msgId] = rawMessage - } - - let checkSendCompleteUsingTime = 0 - const checkSendComplete = async (): Promise => { - if (sentMessage) { - if (waitComplete) { - if (sentMessage.sendStatus == 2) { - delete sentMessages[sentMessage.msgId] - return sentMessage - } - } - else { - delete sentMessages[sentMessage.msgId] - return sentMessage - } - // log(`给${peerUid}发送消息成功`) - } - checkSendCompleteUsingTime += 500 - if (checkSendCompleteUsingTime > timeout) { - throw '发送超时' - } - await sleep(500) - return await checkSendComplete() - } - return checkSendComplete() -} - export class NTQQMsgApi { static async getTempChatInfo(chatType: ChatType2, peerUid: string) { const session = getSession() return session?.getMsgService().getTempChatInfo(chatType, peerUid)! } + static async prepareTempChat(toUserUid: string, GroupCode: string, nickname: string) { + //By Jadx/Ida Mlikiowa + let TempGameSession = { + nickname: '', + gameAppId: '', + selfTinyId: '', + peerRoleId: '', + peerOpenId: '', + } + const session = getSession() + return session?.getMsgService().prepareTempChat({ + chatType: ChatType2.KCHATTYPETEMPC2CFROMGROUP, + peerUid: toUserUid, + peerNickname: nickname, + fromGroupCode: GroupCode, + sig: '', + selfPhone: '', + selfUid: selfInfo.uid, + gameSession: TempGameSession + }) + } + static async setEmojiLike(peer: Peer, msgSeq: string, emojiId: string, set: boolean = true) { // nt_qq//global//nt_data//Emoji//emoji-resource//sysface_res/apng/ 下可以看到所有QQ表情预览 // nt_qq\global\nt_data\Emoji\emoji-resource\face_config.json 里面有所有表情的id, 自带表情id是QSid, 标准emoji表情id是QCid // 其实以官方文档为准是最好的,https://bot.q.qq.com/wiki/develop/api-v2/openapi/emoji/model.html#EmojiType emojiId = emojiId.toString() - return await callNTQQApi({ - methodName: NTQQApiMethod.EMOJI_LIKE, - args: [ - { - peer, - msgSeq, - emojiId, - emojiType: emojiId.length > 3 ? '2' : '1', - setEmoji: set, - }, - null, - ], - }) + const session = getSession() + return session?.getMsgService().setMsgEmojiLikes(peer, msgSeq, emojiId, emojiId.length > 3 ? '2' : '1', set) } static async getMultiMsg(peer: Peer, rootMsgId: string, parentMsgId: string) { - return await callNTQQApi({ - methodName: NTQQApiMethod.GET_MULTI_MSG, - args: [ - { - peer, - rootMsgId, - parentMsgId, - }, - null, - ], - }) - } - - static async getMsgBoxInfo(peer: Peer) { - return await callNTQQApi({ - methodName: NTQQApiMethod.GET_MSG_BOX_INFO, - args: [ - { - contacts: [ - peer - ], - }, - null, - ], - }) + const session = getSession() + return session?.getMsgService().getMultiMsg(peer, rootMsgId, parentMsgId)! } static async activateChat(peer: Peer) { @@ -153,27 +80,6 @@ export class NTQQMsgApi { return session?.getMsgService().getMsgsIncludeSelf(peer, msgId, count, isReverseOrder)! } - static async fetchRecentContact() { - await callNTQQApi({ - methodName: NTQQApiMethod.RECENT_CONTACT, - args: [ - { - fetchParam: { - anchorPointContact: { - contactId: '', - sortField: '', - pos: 0, - }, - relativeMoveCount: 0, - listType: 2, // 1普通消息,2群助手内的消息 - count: 200, - fetchOld: true, - }, - }, - ], - }) - } - static async recallMsg(peer: Peer, msgIds: string[]) { const session = getSession() return await session?.getMsgService().recallMsg({ @@ -183,29 +89,57 @@ export class NTQQMsgApi { } static async sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) { - if (getBuildVersion() >= 26702) { - return NTQQMsgApi.sendMsgV2(peer, msgElements, waitComplete, timeout) + 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 } - const waiter = sendWaiter(peer, waitComplete, timeout) - callNTQQApi({ - methodName: NTQQApiMethod.SEND_MSG, - args: [ - { - msgId: '0', - peer, - msgElements, - msgAttributeInfos: new Map(), - }, - null, - ], - }).then() - return await waiter + // 此处有采用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() + } + 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 + } + } + return false + }, + '0', + peer, + msgElements, + new Map() + ) + const retMsg = data[1].find(msgRecord => { + if (msgRecord.guildId === msgId) { + return true + } + }) + return retMsg! } static async sendMsgV2(peer: Peer, msgElements: SendMessageElement[], waitComplete = true, timeout = 10000) { - if (peer.chatType === ChatType.temp) { - //await NTQQMsgApi.PrepareTempChat().then().catch() - } function generateMsgId() { const timestamp = Math.floor(Date.now() / 1000) const random = Math.floor(Math.random() * Math.pow(2, 32)) @@ -269,74 +203,65 @@ export class NTQQMsgApi { } static async forwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) { - const waiter = sendWaiter(destPeer, true, 10000) - callNTQQApi({ - methodName: NTQQApiMethod.FORWARD_MSG, - args: [ - { - msgIds: msgIds, - srcContact: srcPeer, - dstContacts: [destPeer], - commentElements: [], - msgAttributeInfos: new Map(), - }, - null, - ], - }).then().catch(log) - return await waiter + const session = getSession() + return session?.getMsgService().forwardMsg(msgIds, srcPeer, [destPeer], [])! } - static async multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) { - const msgInfos = msgIds.map((id) => { + static async multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]): Promise { + const msgInfos = msgIds.map(id => { return { msgId: id, senderShowName: selfInfo.nick } }) - const apiArgs = [ - { - msgInfos, - srcContact: srcPeer, - dstContact: destPeer, - commentElements: [], - msgAttributeInfos: new Map(), + let data = await NTEventDispatch.CallNormalEvent< + (msgInfo: typeof msgInfos, srcPeer: Peer, destPeer: Peer, comment: Array, attr: Map,) => Promise, + (msgList: RawMessage[]) => void + >( + 'NodeIKernelMsgService/multiForwardMsgWithComment', + 'NodeIKernelMsgListener/onMsgInfoListUpdate', + 1, + 5000, + (msgRecords: RawMessage[]) => { + for (let msgRecord of msgRecords) { + if (msgRecord.peerUid == destPeer.peerUid && msgRecord.senderUid == selfInfo.uid) { + return true + } + } + return false }, - null, - ] - return await new Promise((resolve, reject) => { - let complete = false - setTimeout(() => { - if (!complete) { - reject('转发消息超时') - } - }, 5000) - registerReceiveHook(ReceiveCmdS.SELF_SEND_MSG, async (payload: { msgRecord: RawMessage }) => { - const msg = payload.msgRecord - // 需要判断它是转发的消息,并且识别到是当前转发的这一条 - const arkElement = msg.elements.find((ele) => ele.arkElement) - if (!arkElement) { - // log("收到的不是转发消息") - return - } - const forwardData: any = JSON.parse(arkElement.arkElement.bytesData) - if (forwardData.app != 'com.tencent.multimsg') { - return - } - if (msg.peerUid == destPeer.peerUid && msg.senderUid == selfInfo.uid) { - complete = true - await dbUtil.addMsg(msg) - resolve(msg) - log('转发消息成功:', payload) - } - }) - callNTQQApi({ - methodName: NTQQApiMethod.MULTI_FORWARD_MSG, - args: apiArgs, - }).then((result) => { - log('转发消息结果:', result, apiArgs) - if (result.result !== 0) { - complete = true - reject('转发消息失败,' + JSON.stringify(result)) - } - }) + msgInfos, + srcPeer, + destPeer, + [], + new Map() + ) + for (let msg of data[1]) { + const arkElement = msg.elements.find(ele => ele.arkElement) + if (!arkElement) { + continue + } + const forwardData: any = JSON.parse(arkElement.arkElement.bytesData) + if (forwardData.app != 'com.tencent.multimsg') { + continue + } + if (msg.peerUid == destPeer.peerUid && msg.senderUid == selfInfo.uid) { + return msg + } + } + throw new Error('转发消息超时') + } + + static async queryMsgsWithFilterExWithSeq(peer: Peer, msgSeq: string) { + const session = getSession() + const ret = await session?.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, { + chatInfo: peer,//此处为Peer 为关键查询参数 没有啥也没有 by mlik iowa + filterMsgType: [], + filterSendersUid: [], + filterMsgToTime: '0', + filterMsgFromTime: '0', + isReverseOrder: false, + isIncludeCurrent: true, + pageLimit: 1, }) + return ret! } static async getMsgsBySeqAndCount(peer: Peer, seq: string, count: number, desc: boolean, z: boolean) { diff --git a/src/ntqqapi/hook.ts b/src/ntqqapi/hook.ts index b9eea90..df22738 100644 --- a/src/ntqqapi/hook.ts +++ b/src/ntqqapi/hook.ts @@ -1,6 +1,6 @@ import type { BrowserWindow } from 'electron' import { NTQQApiClass, NTQQApiMethod } from './ntcall' -import { NTQQMsgApi, sendMessagePool } from './api/msg' +import { NTQQMsgApi } from './api/msg' import { CategoryFriend, ChatType, Group, GroupMember, GroupMemberRole, RawMessage } from './types' import { deleteGroup, @@ -454,18 +454,7 @@ export async function startHook() { registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmdS.SELF_SEND_MSG, ({ msgRecord }) => { const message = msgRecord - const peerUid = message.peerUid - // log("收到自己发送成功的消息", Object.keys(sendMessagePool), message); - // log("收到自己发送成功的消息", message.msgId, message.msgSeq); dbUtil.addMsg(message).then() - const sendCallback = sendMessagePool[peerUid] - if (sendCallback) { - try { - sendCallback(message) - } catch (e: any) { - log('receive self msg error', e.stack) - } - } }) registerReceiveHook<{ info: { status: number } }>(ReceiveCmdS.SELF_STATUS, (info) => { diff --git a/src/ntqqapi/types/msg.ts b/src/ntqqapi/types/msg.ts index 397c897..ec70771 100644 --- a/src/ntqqapi/types/msg.ts +++ b/src/ntqqapi/types/msg.ts @@ -462,6 +462,7 @@ export interface RawMessage { senderUin?: string // 发送者QQ号 peerUid: string // 群号 或者 QQ uid peerUin: string // 群号 或者 发送者QQ号 + guildId: string sendNickName: string sendMemberName?: string // 发送者群名片 chatType: ChatType diff --git a/src/onebot11/action/msg/ForwardSingleMsg.ts b/src/onebot11/action/msg/ForwardSingleMsg.ts index d0fb299..1c37f47 100644 --- a/src/onebot11/action/msg/ForwardSingleMsg.ts +++ b/src/onebot11/action/msg/ForwardSingleMsg.ts @@ -11,11 +11,7 @@ interface Payload { user_id?: number | string } -interface Response { - message_id: number -} - -abstract class ForwardSingleMsg extends BaseAction { +abstract class ForwardSingleMsg extends BaseAction { protected async getTargetPeer(payload: Payload): Promise { if (payload.user_id) { const peerUid = await NTQQUserApi.getUidByUin(payload.user_id.toString()) @@ -27,13 +23,13 @@ abstract class ForwardSingleMsg extends BaseAction { return { chatType: ChatType.group, peerUid: payload.group_id!.toString() } } - protected async _handle(payload: Payload): Promise { + protected async _handle(payload: Payload): Promise { const msg = await dbUtil.getMsgByShortId(payload.message_id) if (!msg) { throw new Error(`无法找到消息${payload.message_id}`) } const peer = await this.getTargetPeer(payload) - const sentMsg = await NTQQMsgApi.forwardMsg( + const ret = await NTQQMsgApi.forwardMsg( { chatType: msg.chatType, peerUid: msg.peerUid, @@ -41,8 +37,10 @@ abstract class ForwardSingleMsg extends BaseAction { peer, [msg.msgId], ) - const ob11MsgId = await dbUtil.addMsg(sentMsg) - return { message_id: ob11MsgId! } + if (ret.result !== 0) { + throw new Error(`转发消息失败 ${ret.errMsg}`) + } + return null } } diff --git a/src/onebot11/action/msg/SendMsg.ts b/src/onebot11/action/msg/SendMsg.ts index 722fdb3..9e6ade3 100644 --- a/src/onebot11/action/msg/SendMsg.ts +++ b/src/onebot11/action/msg/SendMsg.ts @@ -583,13 +583,9 @@ export class SendMsg extends BaseAction { if (nodeMsgIds.length === 0) { throw Error('转发消息失败,节点为空') } - try { - log('开发转发', nodeMsgIds) - return await NTQQMsgApi.multiForwardMsg(srcPeer!, destPeer, nodeMsgIds) - } catch (e) { - log('forward failed', e) - return null - } + const returnMsg = await NTQQMsgApi.multiForwardMsg(srcPeer!, destPeer, nodeMsgIds) + returnMsg.msgShortId = await dbUtil.addMsg(returnMsg) + return returnMsg } } From c815e0ca6becf6afadefa4ea00d51c733f5a09d0 Mon Sep 17 00:00:00 2001 From: idranme Date: Sun, 11 Aug 2024 12:16:53 +0800 Subject: [PATCH 4/5] sync --- src/ntqqapi/api/file.ts | 183 +++++++++++++++------------- src/onebot11/action/file/GetFile.ts | 2 +- src/onebot11/constructor.ts | 2 +- 3 files changed, 100 insertions(+), 87 deletions(-) diff --git a/src/ntqqapi/api/file.ts b/src/ntqqapi/api/file.ts index dec2d5c..6023958 100644 --- a/src/ntqqapi/api/file.ts +++ b/src/ntqqapi/api/file.ts @@ -9,15 +9,21 @@ import { ChatType, ElementType, IMAGE_HTTP_HOST, - IMAGE_HTTP_HOST_NT, PicElement, + IMAGE_HTTP_HOST_NT, + PicElement, } from '../types' import path from 'node:path' import fs from 'node:fs' import { ReceiveCmdS } from '../hook' -import { log } from '@/common/utils' +import { log, TEMP_DIR } from '@/common/utils' import { rkeyManager } from '@/ntqqapi/api/rkey' import { getSession } from '@/ntqqapi/wrapper' import { Peer } from '@/ntqqapi/types/msg' +import { calculateFileMD5 } from '@/common/utils/file' +import { fileTypeFromFile } from 'file-type' +import fsPromise from 'node:fs/promises' +import { NTEventDispatch } from '@/common/utils/EventTask' +import { OnRichMediaDownloadCompleteParams } from '@/ntqqapi/listeners' export class NTQQFileApi { static async getVideoUrl(peer: Peer, msgId: string, elementId: string): Promise { @@ -30,19 +36,7 @@ export class NTQQFileApi { } static async getFileType(filePath: string) { - return await callNTQQApi<{ ext: string }>({ - className: NTQQApiClass.FS_API, - methodName: NTQQApiMethod.FILE_TYPE, - args: [filePath], - }) - } - - static async getFileMd5(filePath: string) { - return await callNTQQApi({ - className: NTQQApiClass.FS_API, - methodName: NTQQApiMethod.FILE_MD5, - args: [filePath], - }) + return fileTypeFromFile(filePath) } static async copyFile(filePath: string, destPath: string) { @@ -67,44 +61,35 @@ export class NTQQFileApi { } // 上传文件到QQ的文件夹 - static async uploadFile(filePath: string, elementType: ElementType = ElementType.PIC, elementSubType: number = 0) { - const md5 = await NTQQFileApi.getFileMd5(filePath) - let ext = (await NTQQFileApi.getFileType(filePath))?.ext + static async uploadFile(filePath: string, elementType: ElementType = ElementType.PIC, elementSubType = 0) { + const fileMd5 = await calculateFileMD5(filePath) + let ext = (await NTQQFileApi.getFileType(filePath))?.ext || '' if (ext) { ext = '.' + ext - } else { - ext = '' } let fileName = `${path.basename(filePath)}` if (fileName.indexOf('.') === -1) { fileName += ext } - const mediaPath = await callNTQQApi({ - methodName: NTQQApiMethod.MEDIA_FILE_PATH, - args: [ - { - path_info: { - md5HexStr: md5, - fileName: fileName, - elementType: elementType, - elementSubType, - thumbSize: 0, - needCreate: true, - downloadType: 1, - file_uuid: '', - }, - }, - ], + const session = getSession() + const mediaPath = session?.getMsgService().getRichMediaFilePathForGuild({ + md5HexStr: fileMd5, + fileName: fileName, + elementType: elementType, + elementSubType, + thumbSize: 0, + needCreate: true, + downloadType: 1, + file_uuid: '' }) - log('media path', mediaPath) - await NTQQFileApi.copyFile(filePath, mediaPath) - const fileSize = await NTQQFileApi.getFileSize(filePath) + await fsPromise.copyFile(filePath, mediaPath!) + const fileSize = (await fsPromise.stat(filePath)).size return { - md5, + md5: fileMd5, fileName, - path: mediaPath, + path: mediaPath!, fileSize, - ext, + ext } } @@ -115,44 +100,67 @@ export class NTQQFileApi { elementId: string, thumbPath: string, sourcePath: string, - force: boolean = false, + timeout = 1000 * 60 * 2, + force = false ) { // 用于下载收到的消息中的图片等 if (sourcePath && fs.existsSync(sourcePath)) { if (force) { - fs.unlinkSync(sourcePath) + try { + await fsPromise.unlink(sourcePath) + } catch (e) { + // + } } else { return sourcePath } } - const apiParams = [ + 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 + }, { - getReq: { - fileModelId: '0', - downloadSourceType: 0, - triggerType: 1, - msgId: msgId, - chatType: chatType, - peerUid: peerUid, - elementId: elementId, - thumbSize: 0, - downloadType: 1, - filePath: thumbPath, - }, - }, - null, - ] - // log("需要下载media", sourcePath); - await callNTQQApi({ - methodName: NTQQApiMethod.DOWNLOAD_MEDIA, - args: apiParams, - cbCmd: ReceiveCmdS.MEDIA_DOWNLOAD_COMPLETE, - cmdCB: (payload: { notifyInfo: { filePath: string; msgId: string } }) => { - log('media 下载完成判断', payload.notifyInfo.msgId, msgId) - return payload.notifyInfo.msgId == msgId - }, - }) - return sourcePath + fileModelId: '0', + downloadSourceType: 0, + triggerType: 1, + msgId: msgId, + chatType: chatType, + peerUid: peerUid, + elementId: elementId, + thumbSize: 0, + downloadType: 1, + filePath: thumbPath + } + ) + let filePath = data[1].filePath + if (filePath.startsWith('\\')) { + const downloadPath = TEMP_DIR + filePath = path.join(downloadPath, filePath) + // 下载路径是下载文件夹的相对路径 + } + return filePath } static async getImageSize(filePath: string) { @@ -163,22 +171,27 @@ export class NTQQFileApi { }) } - static async getImageUrl(picElement: PicElement, chatType: ChatType) { - const isPrivateImage = chatType !== ChatType.group - const url = picElement.originImageUrl // 没有域名 - const md5HexStr = picElement.md5HexStr - const fileMd5 = picElement.md5HexStr - const fileUuid = picElement.fileUuid + static async getImageUrl(element: PicElement) { + if (!element) { + return '' + } + const url: string = element.originImageUrl! // 没有域名 + const md5HexStr = element.md5HexStr + const fileMd5 = element.md5HexStr + const fileUuid = element.fileUuid + if (url) { - if (url.startsWith('/download')) { - // console.log('rkey', rkey); - if (url.includes('&rkey=')) { + const UrlParse = new URL(IMAGE_HTTP_HOST + url) //临时解析拼接 + const imageAppid = UrlParse.searchParams.get('appid') + const isNewPic = imageAppid && ['1406', '1407'].includes(imageAppid) + if (isNewPic) { + let UrlRkey = UrlParse.searchParams.get('rkey') + if (UrlRkey) { return IMAGE_HTTP_HOST_NT + url } - - const rkeyData = await rkeyManager.getRkey(); - const existsRKey = isPrivateImage ? rkeyData.private_rkey : rkeyData.group_rkey; - return IMAGE_HTTP_HOST_NT + url + `${existsRKey}` + const rkeyData = await rkeyManager.getRkey() + UrlRkey = imageAppid === '1406' ? rkeyData.private_rkey : rkeyData.group_rkey + return IMAGE_HTTP_HOST_NT + url + `${UrlRkey}` } else { // 老的图片url,不需要rkey return IMAGE_HTTP_HOST + url @@ -187,7 +200,7 @@ export class NTQQFileApi { // 没有url,需要自己拼接 return `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${(fileMd5 || md5HexStr)!.toUpperCase()}/0` } - log('图片url获取失败', picElement) + log('图片url获取失败', element) return '' } } diff --git a/src/onebot11/action/file/GetFile.ts b/src/onebot11/action/file/GetFile.ts index a231de0..f59d7c8 100644 --- a/src/onebot11/action/file/GetFile.ts +++ b/src/onebot11/action/file/GetFile.ts @@ -37,7 +37,7 @@ export abstract class GetFileBase extends BaseAction Date: Sun, 11 Aug 2024 12:17:47 +0800 Subject: [PATCH 5/5] chore: v3.28.4 --- manifest.json | 4 ++-- scripts/gen-manifest.ts | 2 +- src/version.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/manifest.json b/manifest.json index 1c52050..c82ed56 100644 --- a/manifest.json +++ b/manifest.json @@ -4,7 +4,7 @@ "name": "LLOneBot", "slug": "LLOneBot", "description": "实现 OneBot 11 协议,用以 QQ 机器人开发", - "version": "3.28.3", + "version": "3.28.4", "icon": "./icon.webp", "authors": [ { @@ -13,7 +13,7 @@ } ], "repository": { - "repo": "linyuchen/LiteLoaderQQNT-OneBotApi", + "repo": "LLOneBot/LLOneBot", "branch": "main", "release": { "tag": "latest", diff --git a/scripts/gen-manifest.ts b/scripts/gen-manifest.ts index a4b41a6..fe48785 100644 --- a/scripts/gen-manifest.ts +++ b/scripts/gen-manifest.ts @@ -16,7 +16,7 @@ const manifest = { } ], repository: { - repo: 'linyuchen/LiteLoaderQQNT-OneBotApi', + repo: 'LLOneBot/LLOneBot', branch: 'main', release: { tag: 'latest', diff --git a/src/version.ts b/src/version.ts index 0f9bef4..2c86664 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const version = '3.28.3' +export const version = '3.28.4'