Merge branch 'dev'

This commit is contained in:
linyuchen 2024-03-15 14:37:31 +08:00
commit dcd4533eb3
6 changed files with 185 additions and 91 deletions

View File

@ -151,22 +151,13 @@ function onLoad() {
log(arg); log(arg);
}) })
let postedMsgIds: Record<string, any> = {}
async function postReceiveMsg(msgList: RawMessage[]) { async function postReceiveMsg(msgList: RawMessage[]) {
const {debug, reportSelfMessage} = getConfigUtil().getConfig(); const {debug, reportSelfMessage} = getConfigUtil().getConfig();
for (let message of msgList) { 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) // log("收到新消息", message.msgId, message.msgSeq)
// if (message.senderUin !== selfInfo.uin){ // if (message.senderUin !== selfInfo.uin){
message.msgShortId = await dbUtil.addMsg(message); message.msgShortId = await dbUtil.addMsg(message);
// } // }
OB11Constructor.message(message).then((msg) => { OB11Constructor.message(message).then((msg) => {
@ -180,8 +171,8 @@ function onLoad() {
postOB11Event(msg); postOB11Event(msg);
// log("post msg", msg) // log("post msg", msg)
}).catch(e => log("constructMessage error: ", e.stack.toString())); }).catch(e => log("constructMessage error: ", e.stack.toString()));
OB11Constructor.GroupEvent(message).then(groupEvent=>{ OB11Constructor.GroupEvent(message).then(groupEvent => {
if (groupEvent){ if (groupEvent) {
// log("post group event", groupEvent); // log("post group event", groupEvent);
postOB11Event(groupEvent); postOB11Event(groupEvent);
} }
@ -200,7 +191,7 @@ function onLoad() {
registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.UPDATE_MSG, async (payload) => { registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.UPDATE_MSG, async (payload) => {
for (const message of payload.msgList) { for (const message of payload.msgList) {
// log("message update", message.sendStatus, message.msgId, message.msgSeq) // 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) const oriMessage = await dbUtil.getMsgByLongId(message.msgId)
if (!oriMessage) { if (!oriMessage) {
@ -321,7 +312,7 @@ function onLoad() {
let groupInviteEvent = new OB11GroupRequestEvent(); let groupInviteEvent = new OB11GroupRequestEvent();
groupInviteEvent.group_id = parseInt(notify.group.groupCode); groupInviteEvent.group_id = parseInt(notify.group.groupCode);
let user_id = (await getFriend("", notify.user2.uid))?.uin let user_id = (await getFriend("", notify.user2.uid))?.uin
if (!user_id){ if (!user_id) {
user_id = (await NTQQApi.getUserDetailInfo(notify.user2.uid))?.uin user_id = (await NTQQApi.getUserDetailInfo(notify.user2.uid))?.uin
} }
groupInviteEvent.user_id = parseInt(user_id); groupInviteEvent.user_id = parseInt(user_id);
@ -362,6 +353,7 @@ function onLoad() {
let startTime = 0; let startTime = 0;
async function start() { async function start() {
log("llonebot pid", process.pid)
startTime = Date.now(); startTime = Date.now();
startReceiveHook().then(); startReceiveHook().then();
NTQQApi.getGroups(true).then() NTQQApi.getGroups(true).then()
@ -428,6 +420,10 @@ function onLoad() {
// 创建窗口时触发 // 创建窗口时触发
function onBrowserWindowCreated(window: BrowserWindow) { function onBrowserWindowCreated(window: BrowserWindow) {
if (selfInfo.uid) {
return
}
log("window create", window.webContents.getURL().toString())
try { try {
hookNTQQApiCall(window); hookNTQQApiCall(window);
hookNTQQApiReceive(window); hookNTQQApiReceive(window);

View File

@ -220,24 +220,23 @@ registerReceiveHook<{
// 新消息 // 新消息
registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.NEW_MSG, (payload) => { registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.NEW_MSG, (payload) => {
const {autoDeleteFile} = getConfigUtil().getConfig(); const {autoDeleteFile} = getConfigUtil().getConfig();
if (!autoDeleteFile) {
return
}
for (const message of payload.msgList) { for (const message of payload.msgList) {
// log("收到新消息push到历史记录", message.msgId) // log("收到新消息push到历史记录", message.msgId)
// dbUtil.addMsg(message).then() // dbUtil.addMsg(message).then()
// 清理文件 // 清理文件
if (!autoDeleteFile) {
continue
}
for (const msgElement of message.elements) { for (const msgElement of message.elements) {
if (msgElement.videoElement) {
log("收到视频消息", msgElement.videoElement)
log("視頻缩略图", msgElement.videoElement.thumbPath.get(0));
}
setTimeout(() => { setTimeout(() => {
const picPath = msgElement.picElement?.sourcePath const picPath = msgElement.picElement?.sourcePath
const picThumbPath = [...msgElement.picElement?.thumbPath.values()]
const pttPath = msgElement.pttElement?.filePath const pttPath = msgElement.pttElement?.filePath
const filePath = msgElement.fileElement?.filePath const filePath = msgElement.fileElement?.filePath
const videoPath = msgElement.videoElement?.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) { if (msgElement.picElement) {
pathList.push(...Object.values(msgElement.picElement.thumbPath)) pathList.push(...Object.values(msgElement.picElement.thumbPath))
} }

View File

@ -79,6 +79,7 @@ export enum NTQQApiMethod {
SET_MEMBER_ROLE = "nodeIKernelGroupService/modifyMemberRole", SET_MEMBER_ROLE = "nodeIKernelGroupService/modifyMemberRole",
PUBLISH_GROUP_BULLETIN = "nodeIKernelGroupService/publishGroupBulletinBulletin", PUBLISH_GROUP_BULLETIN = "nodeIKernelGroupService/publishGroupBulletinBulletin",
SET_GROUP_NAME = "nodeIKernelGroupService/modifyGroupName", SET_GROUP_NAME = "nodeIKernelGroupService/modifyGroupName",
SET_GROUP_TITLE = "nodeIKernelGroupService/modifyMemberSpecialTitle",
CACHE_SET_SILENCE = 'nodeIKernelStorageCleanService/setSilentScan', CACHE_SET_SILENCE = 'nodeIKernelStorageCleanService/setSilentScan',
CACHE_ADD_SCANNED_PATH = 'nodeIKernelStorageCleanService/addCacheScanedPaths', CACHE_ADD_SCANNED_PATH = 'nodeIKernelStorageCleanService/addCacheScanedPaths',
@ -727,6 +728,28 @@ export class NTQQApi {
}) })
} }
static async call(cmdName: string, args: any[],) {
return await callNTQQApi<GeneralCallResult>({
methodName: cmdName,
args: [
...args,
]
})
}
static async setGroupTitle(groupQQ: string, uid: string, title: string) {
return await callNTQQApi<GeneralCallResult>({
methodName: NTQQApiMethod.SET_GROUP_TITLE,
args: [
{
groupCode: groupQQ,
uid,
title
}, null
]
})
}
static publishGroupBulletin(groupQQ: string, title: string, content: string) { static publishGroupBulletin(groupQQ: string, title: string, content: string) {
} }

View File

@ -19,12 +19,14 @@ class GetMsg extends BaseAction<PayloadType, OB11Message> {
if (!payload.message_id) { if (!payload.message_id) {
throw ("参数message_id不能为空") throw ("参数message_id不能为空")
} }
const msg = await dbUtil.getMsgByShortId(payload.message_id) let msg = await dbUtil.getMsgByShortId(payload.message_id)
if (msg) { if(!msg) {
return await OB11Constructor.message(msg) msg = await dbUtil.getMsgByLongId(payload.message_id.toString())
} else { }
if (!msg){
throw ("消息不存在") throw ("消息不存在")
} }
return await OB11Constructor.message(msg)
} }
} }

View File

@ -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 {friends, getFriend, getGroup, getGroupMember, getUidByUin, selfInfo,} from "../../common/data";
import { import {
OB11MessageCustomMusic, OB11MessageCustomMusic,
@ -18,7 +26,6 @@ import {log, sleep} from "../../common/utils";
import {decodeCQCode} from "../cqcode"; import {decodeCQCode} from "../cqcode";
import {dbUtil} from "../../common/db"; import {dbUtil} from "../../common/db";
import {ALLOW_SEND_TEMP_MSG} from "../../common/config"; import {ALLOW_SEND_TEMP_MSG} from "../../common/config";
import {FileCache} from "../../common/types";
function checkSendMessage(sendMsgList: OB11MessageData[]) { function checkSendMessage(sendMsgList: OB11MessageData[]) {
function checkUri(uri: string): boolean { function checkUri(uri: string): boolean {
@ -93,6 +100,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
} }
protected async _handle(payload: OB11PostSendMsg) { protected async _handle(payload: OB11PostSendMsg) {
const peer: Peer = { const peer: Peer = {
chatType: ChatType.friend, chatType: ChatType.friend,
peerUid: "" peerUid: ""
@ -157,9 +165,10 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
} }
// log("send msg:", peer, sendElements) // log("send msg:", peer, sendElements)
const {sendElements, deleteAfterSentFiles} = await this.createSendElements(messages, group) const {sendElements, deleteAfterSentFiles} = await this.createSendElements(messages, group)
const returnMsg = await this.send(peer, sendElements, deleteAfterSentFiles) const returnMsg = await this.send(peer, sendElements, deleteAfterSentFiles)
deleteAfterSentFiles.map(f => fs.unlink(f, () => {})); deleteAfterSentFiles.map(f => fs.unlink(f, () => {
return {message_id: returnMsg.msgShortId} }));
return {message_id: returnMsg.msgShortId}
} }
protected convertMessage2List(message: OB11MessageMixType) { protected convertMessage2List(message: OB11MessageMixType) {
@ -184,23 +193,58 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
return 0 return 0
} }
private async cloneMsg(msg: RawMessage): Promise<RawMessage> {
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 // 返回一个合并转发的消息id
private async handleForwardNode(destPeer: Peer, messageNodes: OB11MessageNode[], group: Group | undefined) { private async handleForwardNode(destPeer: Peer, messageNodes: OB11MessageNode[], group: Group | undefined) {
const selfPeer: Peer = {
const selfPeer = {
chatType: ChatType.friend, chatType: ChatType.friend,
peerUid: selfInfo.uid peerUid: selfInfo.uid
} }
let selfNodeMsgList: RawMessage[] = []; // 自己给自己发的消息 let nodeMsgIds: string[] = []
let originalNodeMsgList: RawMessage[] = []; // 先判断一遍是不是id和自定义混用
let sendForwardElements: SendMessageElement[] = [] let needClone = messageNodes.filter(node => node.data.id).length && messageNodes.filter(node => !node.data.id).length
for (const messageNode of messageNodes) { for (const messageNode of messageNodes) {
// 一个node表示一个人的消息 // 一个node表示一个人的消息
let nodeId = messageNode.data.id; let nodeId = messageNode.data.id;
// 有nodeId表示一个子转发消息卡片 // 有nodeId表示一个子转发消息卡片
if (nodeId) { if (nodeId) {
let nodeMsg = await dbUtil.getMsgByShortId(parseInt(nodeId)); let nodeMsg = await dbUtil.getMsgByShortId(parseInt(nodeId));
if (nodeMsg) { if (!needClone) {
originalNodeMsgList.push(nodeMsg); nodeMsgIds.push(nodeMsg.msgId)
} else {
if (nodeMsg.peerUid !== selfInfo.uid) {
const cloneMsg = await this.cloneMsg(nodeMsg)
if (cloneMsg) {
nodeMsgIds.push(cloneMsg.msgId)
}
}
} }
} else { } else {
// 自定义的消息 // 自定义的消息
@ -211,57 +255,68 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
deleteAfterSentFiles deleteAfterSentFiles
} = await this.createSendElements(this.convertMessage2List(messageNode.data.content), group); } = await this.createSendElements(this.convertMessage2List(messageNode.data.content), group);
log("开始生成转发节点", sendElements); log("开始生成转发节点", sendElements);
sendForwardElements.push(...sendElements); let sendElementsSplit: SendMessageElement[][] = []
const nodeMsg = await this.send(selfPeer, sendElements, deleteAfterSentFiles, true); let splitIndex = 0;
selfNodeMsgList.push(nodeMsg); for (const ele of sendElements) {
log("转发节点生成成功", nodeMsg.msgId); if (!sendElementsSplit[splitIndex]) {
await sleep(500); 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) { } catch (e) {
log("生效转发消息节点失败", e) log("生转发消息节点失败", e)
} }
} }
} }
let nodeIds: string[] = [] // 检查srcPeer是否一致不一致则需要克隆成自己的消息, 让所有srcPeer都变成自己的使其保持一致才能够转发
// 检查是否需要克隆直接引用消息id的节点 let nodeMsgArray: Array<RawMessage> = []
let srcPeer: Peer = null;
let needSendSelf = false; let needSendSelf = false;
if (sendForwardElements.length) { for (const [index, msgId] of nodeMsgIds.entries()) {
needSendSelf = true const nodeMsg = await dbUtil.getMsgByLongId(msgId)
} else { if (nodeMsg) {
needSendSelf = !originalNodeMsgList.every((msg, index) => msg.peerUid === originalNodeMsgList[0].peerUid && msg.recallTime.length < 2) 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) { if (needSendSelf) {
nodeIds = selfNodeMsgList.map(msg => msg.msgId); log("需要克隆转发消息");
let sendElements: SendMessageElement[] = []; for (const [index, msg] of nodeMsgArray.entries()) {
for (const originalNodeMsg of originalNodeMsgList) { if (msg.peerUid !== selfInfo.uid) {
if (originalNodeMsg.peerUid === selfInfo.uid && originalNodeMsg.recallTime.length < 2) { const cloneMsg = await this.cloneMsg(msg)
nodeIds.push(originalNodeMsg.msgId) if (cloneMsg) {
} else { // 需要进行克隆 nodeMsgIds[index] = cloneMsg.msgId
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)
} }
} }
} }
} 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之间用换行符分隔 // elements之间用换行符分隔
// let _sendForwardElements: SendMessageElement[] = [] // let _sendForwardElements: SendMessageElement[] = []
@ -274,7 +329,8 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
// await sleep(500); // await sleep(500);
// 开发转发 // 开发转发
try { try {
return await NTQQApi.multiForwardMsg(srcPeer, destPeer, nodeIds) log("开发转发", nodeMsgIds)
return await NTQQApi.multiForwardMsg(srcPeer, destPeer, nodeMsgIds)
} catch (e) { } catch (e) {
log("forward failed", e) log("forward failed", e)
return null; return null;
@ -297,7 +353,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
} }
break; break;
case OB11MessageDataType.at: { case OB11MessageDataType.at: {
if (!group){ if (!group) {
continue continue
} }
let atQQ = sendMsg.data?.qq; let atQQ = sendMsg.data?.qq;
@ -341,18 +397,19 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
const payloadFileName = sendMsg.data?.name const payloadFileName = sendMsg.data?.name
if (file) { if (file) {
const cache = await dbUtil.getFileCache(file) const cache = await dbUtil.getFileCache(file)
if (cache){ if (cache) {
if (fs.existsSync(cache.filePath)){ if (fs.existsSync(cache.filePath)) {
file = "file://" + cache.filePath file = "file://" + cache.filePath
} } else if (cache.downloadFunc) {
else if (cache.downloadFunc){
await cache.downloadFunc() await cache.downloadFunc()
file = cache.filePath; file = cache.filePath;
log("找到文件缓存", file); } else if (cache.url) {
file = cache.url
} }
log("找到文件缓存", file);
} }
const {path, isLocal, fileName, errMsg} = (await uri2local(file)) const {path, isLocal, fileName, errMsg} = (await uri2local(file))
if (errMsg){ if (errMsg) {
throw errMsg throw errMsg
} }
if (path) { if (path) {

View File

@ -6,12 +6,16 @@ import {dbUtil} from "../common/db";
const fs = require("fs").promises; const fs = require("fs").promises;
export async function uri2local(uri: string, fileName: string = null) { type Uri2LocalRes = {
if (!fileName) { success: boolean,
fileName = uuidv4(); errMsg: string,
} fileName: string,
let filePath = path.join(DATA_DIR, fileName) ext: string,
let url = new URL(uri); path: string,
isLocal: boolean
}
export async function uri2local(uri: string, fileName: string = null) : Promise<Uri2LocalRes>{
let res = { let res = {
success: false, success: false,
errMsg: "", errMsg: "",
@ -20,6 +24,19 @@ export async function uri2local(uri: string, fileName: string = null) {
path: "", path: "",
isLocal: false 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:") { if (url.protocol == "base64:") {
// base64转成文件 // base64转成文件
let base64Data = uri.split("base64://")[1] let base64Data = uri.split("base64://")[1]