diff --git a/src/main/main.ts b/src/main/main.ts index 27442c6..97d62b8 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -151,22 +151,13 @@ function onLoad() { log(arg); }) - let postedMsgIds: Record = {} async function postReceiveMsg(msgList: RawMessage[]) { const {debug, reportSelfMessage} = getConfigUtil().getConfig(); for (let message of msgList) { - if (postedMsgIds[message.msgId]) { // 如果QQ开启了独立窗口,会导致消息重复上报,这里加个记录避免重复上报 - continue - } - postedMsgIds[message.msgId] = true - // 超过容量清空 - if (Object.keys(postedMsgIds).length > 10000) { - postedMsgIds = {} - } // log("收到新消息", message.msgId, message.msgSeq) // if (message.senderUin !== selfInfo.uin){ - message.msgShortId = await dbUtil.addMsg(message); + message.msgShortId = await dbUtil.addMsg(message); // } OB11Constructor.message(message).then((msg) => { @@ -180,8 +171,8 @@ function onLoad() { postOB11Event(msg); // log("post msg", msg) }).catch(e => log("constructMessage error: ", e.stack.toString())); - OB11Constructor.GroupEvent(message).then(groupEvent=>{ - if (groupEvent){ + OB11Constructor.GroupEvent(message).then(groupEvent => { + if (groupEvent) { // log("post group event", groupEvent); postOB11Event(groupEvent); } @@ -200,7 +191,7 @@ function onLoad() { registerReceiveHook<{ msgList: Array }>(ReceiveCmd.UPDATE_MSG, async (payload) => { for (const message of payload.msgList) { // log("message update", message.sendStatus, message.msgId, message.msgSeq) - if (message.recallTime != "0") { + if (message.recallTime != "0") { //todo: 这个判断方法不太好,应该使用灰色消息元素来判断 // 撤回消息上报 const oriMessage = await dbUtil.getMsgByLongId(message.msgId) if (!oriMessage) { @@ -321,7 +312,7 @@ function onLoad() { let groupInviteEvent = new OB11GroupRequestEvent(); groupInviteEvent.group_id = parseInt(notify.group.groupCode); let user_id = (await getFriend("", notify.user2.uid))?.uin - if (!user_id){ + if (!user_id) { user_id = (await NTQQApi.getUserDetailInfo(notify.user2.uid))?.uin } groupInviteEvent.user_id = parseInt(user_id); @@ -362,6 +353,7 @@ function onLoad() { let startTime = 0; async function start() { + log("llonebot pid", process.pid) startTime = Date.now(); startReceiveHook().then(); NTQQApi.getGroups(true).then() @@ -428,6 +420,10 @@ function onLoad() { // 创建窗口时触发 function onBrowserWindowCreated(window: BrowserWindow) { + if (selfInfo.uid) { + return + } + log("window create", window.webContents.getURL().toString()) try { hookNTQQApiCall(window); hookNTQQApiReceive(window); diff --git a/src/ntqqapi/hook.ts b/src/ntqqapi/hook.ts index 529bd02..c50f6d7 100644 --- a/src/ntqqapi/hook.ts +++ b/src/ntqqapi/hook.ts @@ -220,24 +220,23 @@ registerReceiveHook<{ // 新消息 registerReceiveHook<{ msgList: Array }>(ReceiveCmd.NEW_MSG, (payload) => { const {autoDeleteFile} = getConfigUtil().getConfig(); + if (!autoDeleteFile) { + return + } for (const message of payload.msgList) { // log("收到新消息,push到历史记录", message.msgId) // dbUtil.addMsg(message).then() // 清理文件 - if (!autoDeleteFile) { - continue - } + for (const msgElement of message.elements) { - if (msgElement.videoElement) { - log("收到视频消息", msgElement.videoElement) - log("視頻缩略图", msgElement.videoElement.thumbPath.get(0)); - } setTimeout(() => { const picPath = msgElement.picElement?.sourcePath + const picThumbPath = [...msgElement.picElement?.thumbPath.values()] const pttPath = msgElement.pttElement?.filePath const filePath = msgElement.fileElement?.filePath const videoPath = msgElement.videoElement?.filePath - const pathList = [picPath, pttPath, filePath, videoPath] + const videoThumbPath: string[] = [...msgElement.videoElement?.thumbPath.values()] + const pathList = [picPath, ...picThumbPath, pttPath, filePath, videoPath, ...videoThumbPath] if (msgElement.picElement) { pathList.push(...Object.values(msgElement.picElement.thumbPath)) } diff --git a/src/ntqqapi/ntcall.ts b/src/ntqqapi/ntcall.ts index 3310b8d..65c2f8e 100644 --- a/src/ntqqapi/ntcall.ts +++ b/src/ntqqapi/ntcall.ts @@ -79,6 +79,7 @@ export enum NTQQApiMethod { SET_MEMBER_ROLE = "nodeIKernelGroupService/modifyMemberRole", PUBLISH_GROUP_BULLETIN = "nodeIKernelGroupService/publishGroupBulletinBulletin", SET_GROUP_NAME = "nodeIKernelGroupService/modifyGroupName", + SET_GROUP_TITLE = "nodeIKernelGroupService/modifyMemberSpecialTitle", CACHE_SET_SILENCE = 'nodeIKernelStorageCleanService/setSilentScan', CACHE_ADD_SCANNED_PATH = 'nodeIKernelStorageCleanService/addCacheScanedPaths', @@ -727,6 +728,28 @@ export class NTQQApi { }) } + static async call(cmdName: string, args: any[],) { + return await callNTQQApi({ + methodName: cmdName, + args: [ + ...args, + ] + }) + } + + static async setGroupTitle(groupQQ: string, uid: string, title: string) { + return await callNTQQApi({ + methodName: NTQQApiMethod.SET_GROUP_TITLE, + args: [ + { + groupCode: groupQQ, + uid, + title + }, null + ] + }) + } + static publishGroupBulletin(groupQQ: string, title: string, content: string) { } diff --git a/src/onebot11/action/GetMsg.ts b/src/onebot11/action/GetMsg.ts index 41ae545..bee6462 100644 --- a/src/onebot11/action/GetMsg.ts +++ b/src/onebot11/action/GetMsg.ts @@ -19,12 +19,14 @@ class GetMsg extends BaseAction { if (!payload.message_id) { throw ("参数message_id不能为空") } - const msg = await dbUtil.getMsgByShortId(payload.message_id) - if (msg) { - return await OB11Constructor.message(msg) - } else { + let msg = await dbUtil.getMsgByShortId(payload.message_id) + if(!msg) { + msg = await dbUtil.getMsgByLongId(payload.message_id.toString()) + } + if (!msg){ throw ("消息不存在") } + return await OB11Constructor.message(msg) } } diff --git a/src/onebot11/action/SendMsg.ts b/src/onebot11/action/SendMsg.ts index d58f0ac..914c8c2 100644 --- a/src/onebot11/action/SendMsg.ts +++ b/src/onebot11/action/SendMsg.ts @@ -1,4 +1,12 @@ -import {AtType, ChatType, Group, RawMessage, SendArkElement, SendMessageElement} from "../../ntqqapi/types"; +import { + AtType, + ChatType, + ElementType, + Group, + RawMessage, + SendArkElement, + SendMessageElement +} from "../../ntqqapi/types"; import {friends, getFriend, getGroup, getGroupMember, getUidByUin, selfInfo,} from "../../common/data"; import { OB11MessageCustomMusic, @@ -18,7 +26,6 @@ import {log, sleep} from "../../common/utils"; import {decodeCQCode} from "../cqcode"; import {dbUtil} from "../../common/db"; import {ALLOW_SEND_TEMP_MSG} from "../../common/config"; -import {FileCache} from "../../common/types"; function checkSendMessage(sendMsgList: OB11MessageData[]) { function checkUri(uri: string): boolean { @@ -93,6 +100,7 @@ export class SendMsg extends BaseAction { } protected async _handle(payload: OB11PostSendMsg) { + const peer: Peer = { chatType: ChatType.friend, peerUid: "" @@ -157,9 +165,10 @@ export class SendMsg extends BaseAction { } // log("send msg:", peer, sendElements) const {sendElements, deleteAfterSentFiles} = await this.createSendElements(messages, group) - const returnMsg = await this.send(peer, sendElements, deleteAfterSentFiles) - deleteAfterSentFiles.map(f => fs.unlink(f, () => {})); - return {message_id: returnMsg.msgShortId} + const returnMsg = await this.send(peer, sendElements, deleteAfterSentFiles) + deleteAfterSentFiles.map(f => fs.unlink(f, () => { + })); + return {message_id: returnMsg.msgShortId} } protected convertMessage2List(message: OB11MessageMixType) { @@ -184,23 +193,58 @@ export class SendMsg extends BaseAction { return 0 } + private async cloneMsg(msg: RawMessage): Promise { + log("克隆的目标消息", msg) + let sendElements: SendMessageElement[] = []; + for (const ele of msg.elements) { + sendElements.push(ele as SendMessageElement) + // Object.keys(ele).forEach((eleKey) => { + // if (eleKey.endsWith("Element")) { + // } + + } + if (sendElements.length === 0) { + log("需要clone的消息无法解析,将会忽略掉", msg) + } + log("克隆消息", sendElements) + try { + const nodeMsg = await NTQQApi.sendMsg({ + chatType: ChatType.friend, + peerUid: selfInfo.uid + }, sendElements, true); + await sleep(500); + return nodeMsg + } catch (e) { + log(e, "克隆转发消息失败,将忽略本条消息", msg); + } + + } + // 返回一个合并转发的消息id private async handleForwardNode(destPeer: Peer, messageNodes: OB11MessageNode[], group: Group | undefined) { - const selfPeer: Peer = { + + const selfPeer = { chatType: ChatType.friend, peerUid: selfInfo.uid } - let selfNodeMsgList: RawMessage[] = []; // 自己给自己发的消息 - let originalNodeMsgList: RawMessage[] = []; - let sendForwardElements: SendMessageElement[] = [] + let nodeMsgIds: string[] = [] + // 先判断一遍是不是id和自定义混用 + let needClone = messageNodes.filter(node => node.data.id).length && messageNodes.filter(node => !node.data.id).length for (const messageNode of messageNodes) { // 一个node表示一个人的消息 let nodeId = messageNode.data.id; // 有nodeId表示一个子转发消息卡片 if (nodeId) { let nodeMsg = await dbUtil.getMsgByShortId(parseInt(nodeId)); - if (nodeMsg) { - originalNodeMsgList.push(nodeMsg); + if (!needClone) { + nodeMsgIds.push(nodeMsg.msgId) + } else { + if (nodeMsg.peerUid !== selfInfo.uid) { + const cloneMsg = await this.cloneMsg(nodeMsg) + if (cloneMsg) { + nodeMsgIds.push(cloneMsg.msgId) + } + } } } else { // 自定义的消息 @@ -211,57 +255,68 @@ export class SendMsg extends BaseAction { deleteAfterSentFiles } = await this.createSendElements(this.convertMessage2List(messageNode.data.content), group); log("开始生成转发节点", sendElements); - sendForwardElements.push(...sendElements); - const nodeMsg = await this.send(selfPeer, sendElements, deleteAfterSentFiles, true); - selfNodeMsgList.push(nodeMsg); - log("转发节点生成成功", nodeMsg.msgId); - await sleep(500); + let sendElementsSplit: SendMessageElement[][] = [] + let splitIndex = 0; + for (const ele of sendElements) { + if (!sendElementsSplit[splitIndex]) { + sendElementsSplit[splitIndex] = [] + } + + if (ele.elementType === ElementType.FILE || ele.elementType === ElementType.VIDEO) { + if (sendElementsSplit[splitIndex].length > 0) { + splitIndex++; + } + sendElementsSplit[splitIndex] = [ele] + splitIndex++; + } else { + sendElementsSplit[splitIndex].push(ele) + } + log(sendElementsSplit) + } + // log("分割后的转发节点", sendElementsSplit) + for (const eles of sendElementsSplit) { + const nodeMsg = await this.send(selfPeer, eles, [], true); + nodeMsgIds.push(nodeMsg.msgId) + await sleep(500); + log("转发节点生成成功", nodeMsg.msgId); + } + deleteAfterSentFiles.map(f => fs.unlink(f, () => { + })); + } catch (e) { - log("生效转发消息节点失败", e) + log("生成转发消息节点失败", e) } } } - let nodeIds: string[] = [] - // 检查是否需要克隆直接引用消息id的节点 + // 检查srcPeer是否一致,不一致则需要克隆成自己的消息, 让所有srcPeer都变成自己的,使其保持一致才能够转发 + let nodeMsgArray: Array = [] + let srcPeer: Peer = null; let needSendSelf = false; - if (sendForwardElements.length) { - needSendSelf = true - } else { - needSendSelf = !originalNodeMsgList.every((msg, index) => msg.peerUid === originalNodeMsgList[0].peerUid && msg.recallTime.length < 2) + for (const [index, msgId] of nodeMsgIds.entries()) { + const nodeMsg = await dbUtil.getMsgByLongId(msgId) + if (nodeMsg) { + nodeMsgArray.push(nodeMsg) + if (!srcPeer) { + srcPeer = {chatType: nodeMsg.chatType, peerUid: nodeMsg.peerUid} + } else if (srcPeer.peerUid !== nodeMsg.peerUid) { + needSendSelf = true + srcPeer = selfPeer + } + } } + log("nodeMsgArray", nodeMsgArray); + nodeMsgIds = nodeMsgArray.map(msg => msg.msgId); if (needSendSelf) { - nodeIds = selfNodeMsgList.map(msg => msg.msgId); - let sendElements: SendMessageElement[] = []; - for (const originalNodeMsg of originalNodeMsgList) { - if (originalNodeMsg.peerUid === selfInfo.uid && originalNodeMsg.recallTime.length < 2) { - nodeIds.push(originalNodeMsg.msgId) - } else { // 需要进行克隆 - Object.keys(originalNodeMsg.elements).forEach((eleKey) => { - if (eleKey !== "elementId") { - sendForwardElements.push(originalNodeMsg.elements[eleKey]) - sendElements.push(originalNodeMsg.elements[eleKey]) - } - }) - try { - const nodeMsg = await NTQQApi.sendMsg(selfPeer, sendElements, true); - nodeIds.push(nodeMsg.msgId) - log("克隆转发消息到节点") - } catch (e) { - log("克隆转发消息失败", e) + log("需要克隆转发消息"); + for (const [index, msg] of nodeMsgArray.entries()) { + if (msg.peerUid !== selfInfo.uid) { + const cloneMsg = await this.cloneMsg(msg) + if (cloneMsg) { + nodeMsgIds[index] = cloneMsg.msgId } } } - } else { - nodeIds = originalNodeMsgList.map(msg => msg.msgId) - } - - let srcPeer = selfPeer; - if (!needSendSelf) { - srcPeer = { - chatType: originalNodeMsgList[0].chatType === ChatType.group ? ChatType.group : ChatType.friend, - peerUid: originalNodeMsgList[0].peerUid - } } // elements之间用换行符分隔 // let _sendForwardElements: SendMessageElement[] = [] @@ -274,7 +329,8 @@ export class SendMsg extends BaseAction { // await sleep(500); // 开发转发 try { - return await NTQQApi.multiForwardMsg(srcPeer, destPeer, nodeIds) + log("开发转发", nodeMsgIds) + return await NTQQApi.multiForwardMsg(srcPeer, destPeer, nodeMsgIds) } catch (e) { log("forward failed", e) return null; @@ -297,7 +353,7 @@ export class SendMsg extends BaseAction { } break; case OB11MessageDataType.at: { - if (!group){ + if (!group) { continue } let atQQ = sendMsg.data?.qq; @@ -341,18 +397,19 @@ export class SendMsg extends BaseAction { const payloadFileName = sendMsg.data?.name if (file) { const cache = await dbUtil.getFileCache(file) - if (cache){ - if (fs.existsSync(cache.filePath)){ + if (cache) { + if (fs.existsSync(cache.filePath)) { file = "file://" + cache.filePath - } - else if (cache.downloadFunc){ + } else if (cache.downloadFunc) { await cache.downloadFunc() file = cache.filePath; - log("找到文件缓存", file); + } else if (cache.url) { + file = cache.url } + log("找到文件缓存", file); } const {path, isLocal, fileName, errMsg} = (await uri2local(file)) - if (errMsg){ + if (errMsg) { throw errMsg } if (path) { diff --git a/src/onebot11/utils.ts b/src/onebot11/utils.ts index 7b0fcca..040565d 100644 --- a/src/onebot11/utils.ts +++ b/src/onebot11/utils.ts @@ -6,12 +6,16 @@ import {dbUtil} from "../common/db"; const fs = require("fs").promises; -export async function uri2local(uri: string, fileName: string = null) { - if (!fileName) { - fileName = uuidv4(); - } - let filePath = path.join(DATA_DIR, fileName) - let url = new URL(uri); +type Uri2LocalRes = { + success: boolean, + errMsg: string, + fileName: string, + ext: string, + path: string, + isLocal: boolean +} + +export async function uri2local(uri: string, fileName: string = null) : Promise{ let res = { success: false, errMsg: "", @@ -20,6 +24,19 @@ export async function uri2local(uri: string, fileName: string = null) { path: "", isLocal: false } + if (!fileName) { + fileName = uuidv4(); + } + let filePath = path.join(DATA_DIR, fileName) + let url = null; + try{ + url = new URL(uri); + }catch (e) { + res.errMsg = `uri ${uri} 解析失败,` + e.toString() + ` 可能${uri}不存在` + return res + } + + // log("uri protocol", url.protocol, uri); if (url.protocol == "base64:") { // base64转成文件 let base64Data = uri.split("base64://")[1]