From 1938eef746380176364b7b127ce6ee5311d43883 Mon Sep 17 00:00:00 2001 From: linyuchen Date: Mon, 19 Feb 2024 21:48:50 +0800 Subject: [PATCH] fix: send multi forward msg --- src/common/channels.ts | 4 +- src/common/utils.ts | 5 +- src/global.d.ts | 9 +- src/main/ipcsend.ts | 10 +- src/main/main.ts | 13 +- src/ntqqapi/hook.ts | 26 ++- src/ntqqapi/ntcall.ts | 284 +++++++++++++++++++-------- src/ntqqapi/types.ts | 6 +- src/onebot11/actions/SendMsg.ts | 146 ++++++++++++-- src/onebot11/actions/TestForwdMsg.ts | 34 ++++ src/onebot11/actions/index.ts | 2 + src/onebot11/actions/types.ts | 1 + src/onebot11/server.ts | 34 ++-- src/onebot11/types.ts | 91 +++++---- src/preload.ts | 21 +- src/renderer.ts | 17 +- 16 files changed, 500 insertions(+), 203 deletions(-) create mode 100644 src/onebot11/actions/TestForwdMsg.ts diff --git a/src/common/channels.ts b/src/common/channels.ts index 0005841..5e517ea 100644 --- a/src/common/channels.ts +++ b/src/common/channels.ts @@ -1,3 +1,5 @@ +import {Peer} from "../ntqqapi/ntcall"; + export const CHANNEL_GET_CONFIG = "llonebot_get_config" export const CHANNEL_SET_CONFIG = "llonebot_set_config" -export const CHANNEL_LOG = "llonebot_log" +export const CHANNEL_LOG = "llonebot_log" \ No newline at end of file diff --git a/src/common/utils.ts b/src/common/utils.ts index f4e3955..0962619 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -2,8 +2,6 @@ import * as path from "path"; import {selfInfo} from "./data"; import {ConfigUtil} from "./config"; import util from "util"; -import { sendLog } from '../main/ipcsend'; - const fs = require('fs'); export const CONFIG_DIR = global.LiteLoader.plugins["LLOneBot"].path.data; @@ -94,3 +92,6 @@ export async function file2base64(path: string){ } return result; } + +export const sleep = (ms: number): Promise => + new Promise((resolve) => setTimeout(resolve, ms)) \ No newline at end of file diff --git a/src/global.d.ts b/src/global.d.ts index d410524..d88bd91 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,15 +1,10 @@ -import { Config } from "./common/types"; +import {LLOneBot} from "./preload"; -declare var llonebot: { - log(data: any): void, - setConfig(config: Config):void; - getConfig():Promise; -}; declare global { interface Window { - llonebot: typeof llonebot; + llonebot: LLOneBot; LiteLoader: any; } } \ No newline at end of file diff --git a/src/main/ipcsend.ts b/src/main/ipcsend.ts index b40da4f..303e9d2 100644 --- a/src/main/ipcsend.ts +++ b/src/main/ipcsend.ts @@ -1,18 +1,12 @@ import {webContents} from 'electron'; -import { CHANNEL_LOG } from '../common/channels'; - -function sendIPCMsg(channel: string, ...data: any) { +function sendIPCMsg(channel: string, data: any) { let contents = webContents.getAllWebContents(); for (const content of contents) { try { - content.send(channel, ...data) + content.send(channel, data) } catch (e) { console.log("llonebot send ipc msg to render error:", e) } } } - -export function sendLog(...args){ - sendIPCMsg(CHANNEL_LOG, ...args) -} diff --git a/src/main/main.ts b/src/main/main.ts index a897bc6..07961d8 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -7,7 +7,7 @@ import { CHANNEL_GET_CONFIG, CHANNEL_LOG, CHANNEL_SET_CONFIG, } from "../common/ import { postMsg, setToken, startHTTPServer, startWSServer } from "../onebot11/server"; import { CONFIG_DIR, getConfigUtil, log } from "../common/utils"; import { addHistoryMsg, getGroupMember, msgHistory, selfInfo, uidMaps } from "../common/data"; -import { hookNTQQApiReceive, ReceiveCmd, registerReceiveHook } from "../ntqqapi/hook"; +import { hookNTQQApiCall, hookNTQQApiReceive, ReceiveCmd, registerReceiveHook } from "../ntqqapi/hook"; import { OB11Constructor } from "../onebot11/constructor"; import { NTQQApi } from "../ntqqapi/ntcall"; import { ChatType, RawMessage } from "../ntqqapi/types"; @@ -20,10 +20,6 @@ let running = false; // 加载插件时触发 function onLoad() { log("llonebot main onLoad"); - - // const config_dir = browserWindow.LiteLoader.plugins["LLOneBot"].path.data; - - if (!fs.existsSync(CONFIG_DIR)) { fs.mkdirSync(CONFIG_DIR, {recursive: true}); } @@ -52,7 +48,7 @@ function onLoad() { function postRawMsg(msgList: RawMessage[]) { const {debug, reportSelfMessage} = getConfigUtil().getConfig(); for (let message of msgList) { - log("收到新消息", message) + // log("收到新消息", message) message.msgShortId = msgHistory[message.msgId]?.msgShortId if (!message.msgShortId) { addHistoryMsg(message) @@ -82,8 +78,8 @@ function onLoad() { }) registerReceiveHook<{ msgList: Array }>(ReceiveCmd.UPDATE_MSG, async (payload) => { for (const message of payload.msgList) { - // log("message update", message, message.sendStatus) - if (message.sendStatus === 2) { + // log("message update", message.sendStatus, message) + if (message.recallTime != "0") { // 撤回消息上报 const oriMessage = msgHistory[message.msgId] if (!oriMessage) { @@ -166,6 +162,7 @@ function onLoad() { // 创建窗口时触发 function onBrowserWindowCreated(window: BrowserWindow) { try { + hookNTQQApiCall(window); hookNTQQApiReceive(window); } catch (e) { log("LLOneBot hook error: ", e.toString()) diff --git a/src/ntqqapi/hook.ts b/src/ntqqapi/hook.ts index 6e954fd..5601127 100644 --- a/src/ntqqapi/hook.ts +++ b/src/ntqqapi/hook.ts @@ -1,8 +1,7 @@ import { BrowserWindow } from 'electron'; -import { getConfigUtil, log } from "../common/utils"; +import { log } from "../common/utils"; import { NTQQApi, NTQQApiClass, sendMessagePool } from "./ntcall"; -import { Group, User } from "./types"; -import { RawMessage } from "./types"; +import { Group, RawMessage, User } from "./types"; import { addHistoryMsg, friends, groups, msgHistory } from "../common/data"; import { v4 as uuidv4 } from 'uuid'; @@ -51,7 +50,7 @@ export function hookNTQQApiReceive(window: BrowserWindow) { new Promise((resolve, reject) => { try { let _ = hook.hookFunc(receiveData.payload) - if (hook.hookFunc.constructor.name === "AsyncFunction"){ + if (hook.hookFunc.constructor.name === "AsyncFunction") { (_ as Promise).then() } } catch (e) { @@ -78,6 +77,24 @@ export function hookNTQQApiReceive(window: BrowserWindow) { window.webContents.send = patchSend; } +export function hookNTQQApiCall(window: BrowserWindow) { + // 监听调用NTQQApi + let webContents = window.webContents as any; + const ipc_message_proxy = webContents._events["-ipc-message"]?.[0] || webContents._events["-ipc-message"]; + + const proxyIpcMsg = new Proxy(ipc_message_proxy, { + apply(target, thisArg, args) { + log("call NTQQ api", thisArg, args); + return target.apply(thisArg, args); + }, + }); + // if (webContents._events["-ipc-message"]?.[0]) { + // webContents._events["-ipc-message"][0] = proxyIpcMsg; + // } else { + // webContents._events["-ipc-message"] = proxyIpcMsg; + // } +} + export function registerReceiveHook(method: ReceiveCmd, hookFunc: (payload: PayloadType) => void): string { const id = uuidv4() receiveHooks.push({ @@ -133,7 +150,6 @@ registerReceiveHook<{ // }) - registerReceiveHook<{ msgList: Array }>(ReceiveCmd.NEW_MSG, (payload) => { for (const message of payload.msgList) { // log("收到新消息,push到历史记录", message) diff --git a/src/ntqqapi/ntcall.ts b/src/ntqqapi/ntcall.ts index 7d1e5ba..38be41e 100644 --- a/src/ntqqapi/ntcall.ts +++ b/src/ntqqapi/ntcall.ts @@ -1,13 +1,10 @@ -import { ipcMain } from "electron"; -import { v4 as uuidv4 } from "uuid"; -import { ReceiveCmd, hookApiCallbacks, registerReceiveHook, removeReceiveHook } from "./hook"; -import { log } from "../common/utils"; -import { ChatType, Friend, PicElement, SelfInfo, User } from "./types"; -import { Group } from "./types"; -import { GroupMember } from "./types"; -import { RawMessage } from "./types"; -import { SendMessageElement } from "./types"; +import {ipcMain} from "electron"; +import {v4 as uuidv4} from "uuid"; +import {hookApiCallbacks, ReceiveCmd, registerReceiveHook, removeReceiveHook} from "./hook"; +import {log} from "../common/utils"; +import {ChatType, Friend, Group, GroupMember, RawMessage, SelfInfo, SendMessageElement, User} from "./types"; import * as fs from "fs"; +import {addHistoryMsg, msgHistory, selfInfo} from "../common/data"; interface IPCReceiveEvent { eventName: string @@ -29,7 +26,6 @@ export enum NTQQApiClass { export enum NTQQApiMethod { LIKE_FRIEND = "nodeIKernelProfileLikeService/setBuddyProfileLike", - UPDATE_MSG = "nodeIKernelMsgListener/onMsgInfoListUpdate", SELF_INFO = "fetchAuthData", FRIENDS = "nodeIKernelBuddyService/getBuddyList", GROUPS = "nodeIKernelGroupService/getGroupList", @@ -44,7 +40,8 @@ export enum NTQQApiMethod { MEDIA_FILE_PATH = "nodeIKernelMsgService/getRichMediaFilePathForGuild", RECALL_MSG = "nodeIKernelMsgService/recallMsg", SEND_MSG = "nodeIKernelMsgService/sendMsg", - DOWNLOAD_MEDIA = "nodeIKernelMsgService/downloadRichMedia" + DOWNLOAD_MEDIA = "nodeIKernelMsgService/downloadRichMedia", + MULTI_FORWARD_MSG = "nodeIKernelMsgService/multiForwardMsgWithComment" // 合并转发 } enum NTQQApiChannel { @@ -64,8 +61,24 @@ enum CallBackType { METHOD } +interface NTQQApiParams { + methodName: NTQQApiMethod, + className?: NTQQApiClass, + channel?: NTQQApiChannel, + args?: unknown[], + cbCmd?: ReceiveCmd | null + timeoutSecond?: number, +} -function callNTQQApi(channel: NTQQApiChannel, className: NTQQApiClass, methodName: NTQQApiMethod, args: unknown[] = [], cbCmd: ReceiveCmd | null = null, timeout = 5) { +function callNTQQApi(params: NTQQApiParams) { + let { + className, methodName, channel, args, + cbCmd, timeoutSecond: timeout + } = params; + className = className ?? NTQQApiClass.NT_API; + channel = channel ?? NTQQApiChannel.IPC_UP_2; + args = args ?? []; + timeout = timeout ?? 5; const uuid = uuidv4(); // log("callNTQQApi", channel, className, methodName, args, uuid) return new Promise((resolve: (data: ReturnType) => void, reject) => { @@ -102,16 +115,18 @@ function callNTQQApi(channel: NTQQApiChannel, className: NTQQApiClas reject(`ntqq api timeout ${channel}, ${className}, ${methodName}`) } }, _timeout) - + const eventName = className + "-" + channel[channel.length - 1]; + const apiArgs = [methodName, ...args] ipcMain.emit( channel, {}, - {type: 'request', callbackId: uuid, eventName: className + "-" + channel[channel.length - 1]}, - [methodName, ...args], + {type: 'request', callbackId: uuid, eventName}, + apiArgs ) }) } + export let sendMessagePool: Record void) | null> = {}// peerUid: callbackFunnc interface GeneralCallResult { @@ -123,34 +138,49 @@ interface GeneralCallResult { export class NTQQApi { // static likeFriend = defineNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.LIKE_FRIEND) static likeFriend(uid: string, count = 1) { - return callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.LIKE_FRIEND, [{ - doLikeUserInfo: { - friendUid: uid, - sourceId: 71, - doLikeCount: count, - doLikeTollCount: 0 - } - }, - null]) + return callNTQQApi({ + methodName: NTQQApiMethod.LIKE_FRIEND, + args: [{ + doLikeUserInfo: { + friendUid: uid, + sourceId: 71, + doLikeCount: count, + doLikeTollCount: 0 + } + }, null] + }) } static getSelfInfo() { - return callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.GLOBAL_DATA, NTQQApiMethod.SELF_INFO, [], null, 2) - + return callNTQQApi({ + className: NTQQApiClass.GLOBAL_DATA, + methodName: NTQQApiMethod.SELF_INFO, timeoutSecond: 2 + }) } static async getUserInfo(uid: string) { - const result = await callNTQQApi<{ - profiles: Map - }>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.USER_INFO, - [{force: true, uids: [uid]}, undefined], ReceiveCmd.USER_INFO) + const result = await callNTQQApi<{ profiles: Map }>({ + methodName: NTQQApiMethod.USER_INFO, + args: [{force: true, uids: [uid]}, undefined], + cbCmd: ReceiveCmd.USER_INFO + }) return result.profiles.get(uid) } static async getFriends(forced = false) { const data = await callNTQQApi<{ - data: { categoryId: number, categroyName: string, categroyMbCount: number, buddyList: Friend[] }[] - }>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.FRIENDS, [{force_update: forced}, undefined], ReceiveCmd.FRIENDS) + data: { + categoryId: number, + categroyName: string, + categroyMbCount: number, + buddyList: Friend[] + }[] + }>( + { + methodName: NTQQApiMethod.FRIENDS, + args: [{force_update: forced}, undefined], + cbCmd: ReceiveCmd.FRIENDS + }) let _friends: Friend[] = []; for (const fData of data.data) { _friends.push(...fData.buddyList) @@ -166,26 +196,31 @@ export class NTQQApi { const result = await callNTQQApi<{ updateType: number, groupList: Group[] - }>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.GROUPS, [{force_update: forced}, undefined], cbCmd) + }>({methodName: NTQQApiMethod.GROUPS, args: [{force_update: forced}, undefined], cbCmd}) return result.groupList } static async getGroupMembers(groupQQ: string, num = 3000) { - const sceneId = await callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.GROUP_MEMBER_SCENE, [{ - groupCode: groupQQ, - scene: "groupMemberList_MainWindow" - }]) + const sceneId = await callNTQQApi({ + methodName: NTQQApiMethod.GROUP_MEMBER_SCENE, + args: [{ + groupCode: groupQQ, + scene: "groupMemberList_MainWindow" + }] + }) // log("get group member sceneId", sceneId); try { const result = await callNTQQApi<{ result: { infos: any } - }>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.GROUP_MEMBERS, - [{ + }>({ + methodName: NTQQApiMethod.GROUP_MEMBERS, + args: [{ sceneId: sceneId, num: num }, null - ]) + ] + }) // log("members info", typeof result.result.infos, Object.keys(result.result.infos)) let values = result.result.infos.values() @@ -200,31 +235,38 @@ export class NTQQApi { static getFileType(filePath: string) { - return callNTQQApi<{ - ext: string - }>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.FS_API, NTQQApiMethod.FILE_TYPE, [filePath]) + return callNTQQApi<{ ext: string }>({ + className: NTQQApiClass.FS_API, methodName: NTQQApiMethod.FILE_TYPE, args: [filePath] + }) } static getFileMd5(filePath: string) { - return callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.FS_API, NTQQApiMethod.FILE_MD5, [filePath]) + return callNTQQApi({ + className: NTQQApiClass.FS_API, + methodName: NTQQApiMethod.FILE_MD5, + args: [filePath] + }) } static copyFile(filePath: string, destPath: string) { - return callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.FS_API, NTQQApiMethod.FILE_COPY, [{ - fromPath: filePath, - toPath: destPath - }]) + return callNTQQApi({ + className: NTQQApiClass.FS_API, methodName: NTQQApiMethod.FILE_COPY, args: [{ + fromPath: filePath, + toPath: destPath + }] + }) } static getImageSize(filePath: string) { - return callNTQQApi<{ - width: number, - height: number - }>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.FS_API, NTQQApiMethod.IMAGE_SIZE, [filePath]) + return callNTQQApi<{ width: number, height: number }>({ + className: NTQQApiClass.FS_API, methodName: NTQQApiMethod.IMAGE_SIZE, args: [filePath] + }) } static getFileSize(filePath: string) { - return callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.FS_API, NTQQApiMethod.FILE_SIZE, [filePath]) + return callNTQQApi({ + className: NTQQApiClass.FS_API, methodName: NTQQApiMethod.FILE_SIZE, args: [filePath] + }) } // 上传文件到QQ的文件夹 @@ -233,23 +275,25 @@ export class NTQQApi { let ext = (await NTQQApi.getFileType(filePath))?.ext if (ext) { ext = "." + ext - } - else{ + } else { ext = "" } const fileName = `${md5}${ext}`; - const mediaPath = await callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.MEDIA_FILE_PATH, [{ - path_info: { - md5HexStr: md5, - fileName: fileName, - elementType: 2, - elementSubType: 0, - thumbSize: 0, - needCreate: true, - downloadType: 1, - file_uuid: "" - } - }]) + const mediaPath = await callNTQQApi({ + methodName: NTQQApiMethod.MEDIA_FILE_PATH, + args: [{ + path_info: { + md5HexStr: md5, + fileName: fileName, + elementType: 2, + elementSubType: 0, + thumbSize: 0, + needCreate: true, + downloadType: 1, + file_uuid: "" + } + }] + }) log("media path", mediaPath) await NTQQApi.copyFile(filePath, mediaPath); const fileSize = await NTQQApi.getFileSize(filePath); @@ -280,28 +324,32 @@ export class NTQQApi { }, undefined, ] - await callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.DOWNLOAD_MEDIA, apiParams) + await callNTQQApi({methodName: NTQQApiMethod.DOWNLOAD_MEDIA, args: apiParams}) return sourcePath } static recallMsg(peer: Peer, msgIds: string[]) { - return callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.RECALL_MSG, [{ - peer, - msgIds - }, null]) + return callNTQQApi({ + methodName: NTQQApiMethod.RECALL_MSG, args: [{ + peer, + msgIds + }, null] + }) } - static sendMsg(peer: Peer, msgElements: SendMessageElement[]) { + static sendMsg(peer: Peer, msgElements: SendMessageElement[], waitComplete = false) { const sendTimeout = 10 * 1000 return new Promise((resolve, reject) => { const peerUid = peer.peerUid; let usingTime = 0; let success = false; + let isTimeout = false; const checkSuccess = () => { if (!success) { sendMessagePool[peerUid] = null; + isTimeout = true; reject("发送超时") } } @@ -311,29 +359,99 @@ export class NTQQApi { let lastSending = sendMessagePool[peerUid] if (sendTimeout < usingTime) { sendMessagePool[peerUid] = null; + isTimeout = true; reject("发送超时") } if (!!lastSending) { // log("有正在发送的消息,等待中...") - usingTime += 100; - setTimeout(checkLastSend, 100); + usingTime += 500; + setTimeout(checkLastSend, 500); } else { log("可以进行发送消息,设置发送成功回调", sendMessagePool) sendMessagePool[peerUid] = (rawMessage: RawMessage) => { - success = true; sendMessagePool[peerUid] = null; - resolve(rawMessage); + const checkSendComplete = () => { + if (isTimeout) { + return reject("发送超时") + } + if (msgHistory[rawMessage.msgId]?.sendStatus == 2) { + success = true; + resolve(rawMessage); + } else { + setTimeout(checkSendComplete, 500) + } + } + if (waitComplete) { + checkSendComplete(); + } else { + success = true; + resolve(rawMessage); + } } } } checkLastSend() - callNTQQApi(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.SEND_MSG, [{ - msgId: "0", - peer, msgElements, - msgAttributeInfos: new Map(), - }, null]).then() + callNTQQApi({ + methodName: NTQQApiMethod.SEND_MSG, + args: [{ + msgId: "0", + peer, msgElements, + msgAttributeInfos: new Map(), + }, null] + }).then() }) - } + static multiForwardMsg(srcPeer: Peer, destPeer: Peer, msgIds: string[]) { + let msgInfos = msgIds.map(id => { + return {msgId: id, senderShowName: "LLOneBot"} + }) + const apiArgs = [ + { + msgInfos, + srcContact: srcPeer, + dstContact: destPeer, + commentElements: [], + msgAttributeInfos: new Map() + }, + null, + ] + return new Promise((resolve, reject) => { + let complete = false + setTimeout(() => { + if (!complete) { + reject("转发消息超时"); + } + }, 5000) + registerReceiveHook(ReceiveCmd.SELF_SEND_MSG, (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; + addHistoryMsg(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)); + } + }) + }) + } } \ No newline at end of file diff --git a/src/ntqqapi/types.ts b/src/ntqqapi/types.ts index da4ff74..af63b4c 100644 --- a/src/ntqqapi/types.ts +++ b/src/ntqqapi/types.ts @@ -211,13 +211,15 @@ export interface RawMessage { msgShortId?: number; // 自己维护的消息id msgTime: string; msgSeq: string; - senderUin: string; // 发送者QQ号 + senderUid: string; + senderUin?: string; // 发送者QQ号 peerUid: string; // 群号 或者 QQ uid peerUin: string; // 群号 或者 发送者QQ号 sendNickName: string; sendMemberName?: string; // 发送者群名片 chatType: ChatType; - sendStatus?: number; // 消息状态,2是已撤回 + sendStatus?: number; // 消息状态,别人发的2是已撤回,自己发的2是已发送 + recallTime: string; // 撤回时间, "0"是没有撤回 elements: { elementId: string, replyElement: { diff --git a/src/onebot11/actions/SendMsg.ts b/src/onebot11/actions/SendMsg.ts index e7b9700..4e28109 100644 --- a/src/onebot11/actions/SendMsg.ts +++ b/src/onebot11/actions/SendMsg.ts @@ -1,13 +1,14 @@ -import { AtType, ChatType, Group, SendMessageElement } from "../../ntqqapi/types"; -import { addHistoryMsg, friends, getGroup, getHistoryMsgByShortId, getStrangerByUin, } from "../../common/data"; -import { OB11MessageData, OB11MessageDataType, OB11PostSendMsg } from '../types'; -import { NTQQApi, Peer } from "../../ntqqapi/ntcall"; -import { SendMsgElementConstructor } from "../../ntqqapi/constructor"; -import { uri2local } from "../utils"; -import { v4 as uuid4 } from 'uuid'; +import {AtType, ChatType, Group, SendMessageElement} from "../../ntqqapi/types"; +import {addHistoryMsg, friends, getGroup, getHistoryMsgByShortId, getStrangerByUin, selfInfo,} from "../../common/data"; +import {OB11MessageData, OB11MessageDataType, OB11MessageNode, OB11PostSendMsg} from '../types'; +import {NTQQApi, Peer} from "../../ntqqapi/ntcall"; +import {SendMsgElementConstructor} from "../../ntqqapi/constructor"; +import {uri2local} from "../utils"; +import {v4 as uuid4} from 'uuid'; import BaseAction from "./BaseAction"; -import { ActionName } from "./types"; +import {ActionName, BaseCheckResult} from "./types"; import * as fs from "fs"; +import {log, sleep} from "../../common/utils"; export interface ReturnDataType { message_id: number @@ -16,12 +17,26 @@ export interface ReturnDataType { class SendMsg extends BaseAction { actionName = ActionName.SendMsg + protected async check(payload: OB11PostSendMsg): Promise { + const messages = this.convertMessage2List(payload); + const fmNum = this.forwardMsgNum(payload) + if ( fmNum && fmNum != messages.length) { + return { + valid: false, + message: "转发消息不能和普通消息混在一起发送,转发需要保证message只有type为node的元素" + } + } + return { + valid: true, + } + } + protected async _handle(payload: OB11PostSendMsg) { const peer: Peer = { chatType: ChatType.friend, peerUid: "" } - let deleteAfterSentFiles: string[] = [] + let group: Group | undefined = undefined; if (payload?.group_id) { group = await getGroup(payload.group_id.toString()) @@ -46,6 +61,26 @@ class SendMsg extends BaseAction { peer.peerUid = tempUser.uid } } + const messages = this.convertMessage2List(payload); + if (this.forwardMsgNum(payload)) { + try { + const returnMsg = await this.handleForwardNode(peer, messages as OB11MessageNode[], group) + return {message_id: returnMsg.msgShortId} + } catch (e) { + throw ("发送转发消息失败 " + e.toString()) + } + } + // log("send msg:", peer, sendElements) + const {sendElements, deleteAfterSentFiles} = await this.createSendElements(messages, group) + try { + const returnMsg = await this.send(peer, sendElements, deleteAfterSentFiles) + return {message_id: returnMsg.msgShortId} + } catch (e) { + throw (e.toString()) + } + } + + private convertMessage2List(payload: OB11PostSendMsg) { if (typeof payload.message === "string") { payload.message = [{ type: OB11MessageDataType.text, @@ -56,8 +91,62 @@ class SendMsg extends BaseAction { } else if (!Array.isArray(payload.message)) { payload.message = [payload.message] } - const sendElements: SendMessageElement[] = [] - for (let sendMsg of payload.message) { + return payload.message; + } + + private forwardMsgNum(payload: OB11PostSendMsg): number { + if (Array.isArray(payload.message)) { + return payload.message.filter(msg => msg.type == OB11MessageDataType.node).length + } + return 0 + } + + // 返回一个合并转发的消息id + private async handleForwardNode(destPeer: Peer, messageNodes: OB11MessageNode[], group: Group | undefined) { + const selfPeer: Peer = { + chatType: ChatType.friend, + peerUid: selfInfo.uid + } + let nodeIds: string[] = [] + for (const messageNode of messageNodes) { + // 一个node表示一个人的消息 + + let nodeId = messageNode.data.id; + // 有nodeId表示一个子转发消息卡片 + if (nodeId) { + nodeIds.push(nodeId) + } else { + // 自定义的消息 + // 提取消息段,发给自己生成消息id + const { + sendElements, + deleteAfterSentFiles + } = await this.createSendElements(messageNode.data.content, group) + try { + const nodeMsg = await this.send(selfPeer, sendElements, deleteAfterSentFiles, true); + nodeIds.push(nodeMsg.msgId) + } catch (e) { + log("生效转发消息节点失败") + } + } + } + + // 开发转发 + try { + return await NTQQApi.multiForwardMsg(selfPeer, destPeer, nodeIds) + } catch (e) { + log("forward failed", e) + return null; + } + } + + private async createSendElements(messageData: OB11MessageData[], group: Group | undefined, ignoreTypes: OB11MessageDataType[] = []) { + let sendElements: SendMessageElement[] = [] + let deleteAfterSentFiles: string[] = [] + for (let sendMsg of messageData) { + if (ignoreTypes.includes(sendMsg.type)) { + continue + } switch (sendMsg.type) { case OB11MessageDataType.text: { const text = sendMsg.data?.text; @@ -116,19 +205,36 @@ class SendMsg extends BaseAction { } } } + break; + // case OB11MessageDataType.node: { + // try { + // await this.handleForwardNode(peer, sendMsg, group); + // } catch (e) { + // log("forward msg crash", e.stack) + // } + // } } + } - // log("send msg:", peer, sendElements) - try { - const returnMsg = await NTQQApi.sendMsg(peer, sendElements) - addHistoryMsg(returnMsg) - deleteAfterSentFiles.map(f => fs.unlink(f, () => { - })) - return {message_id: returnMsg.msgShortId} - } catch (e) { - throw (e.toString()) + + return { + sendElements, + deleteAfterSentFiles } } + + private async send(peer: Peer, sendElements: SendMessageElement[], deleteAfterSentFiles: string[], waitComplete=false) { + if (!sendElements.length) { + throw ("消息体无法解析") + } + const returnMsg = await NTQQApi.sendMsg(peer, sendElements, waitComplete) + addHistoryMsg(returnMsg) + deleteAfterSentFiles.map(f => fs.unlink(f, () => { + })) + return returnMsg + } + + } export default SendMsg \ No newline at end of file diff --git a/src/onebot11/actions/TestForwdMsg.ts b/src/onebot11/actions/TestForwdMsg.ts new file mode 100644 index 0000000..24b3fd0 --- /dev/null +++ b/src/onebot11/actions/TestForwdMsg.ts @@ -0,0 +1,34 @@ +import { OB11Message } from '../types'; +import BaseAction from "./BaseAction"; +import { ActionName } from "./types"; +import { NTQQApi, Peer } from "../../ntqqapi/ntcall"; +import { ChatType } from "../../ntqqapi/types"; +import { selfInfo } from "../../common/data"; +import { SendMsgElementConstructor } from "../../ntqqapi/constructor"; +import {sleep} from "../../common/utils"; + + +export interface PayloadType { + message: string, + group_id: string +} + +export default class TestForwardMsg extends BaseAction { + actionName = ActionName.TestForwardMsg + + protected async _handle(payload: PayloadType) { + // log("history msg ids", Object.keys(msgHistory)); + const selfPeer: Peer = { + chatType: ChatType.friend, + peerUid: selfInfo.uid + } + const sendMsg = await NTQQApi.sendMsg(selfPeer, [SendMsgElementConstructor.text(payload.message)]) + const sendMsg2 = await NTQQApi.sendMsg(selfPeer, [SendMsgElementConstructor.text(payload.message)]) + await NTQQApi.multiForwardMsg( + selfPeer, + {chatType: ChatType.group, peerUid: payload.group_id, guildId: ""}, + [sendMsg.msgId, sendMsg2.msgId] + ) + return null + } +} \ No newline at end of file diff --git a/src/onebot11/actions/index.ts b/src/onebot11/actions/index.ts index 7ebaa6d..2baa5dc 100644 --- a/src/onebot11/actions/index.ts +++ b/src/onebot11/actions/index.ts @@ -13,8 +13,10 @@ import GetVersionInfo from "./GetVersionInfo"; import CanSendRecord from "./CanSendRecord"; import CanSendImage from "./CanSendImage"; import GetStatus from "./GetStatus"; +import TestForwardMsg from "./TestForwdMsg"; export const actionHandlers = [ + new TestForwardMsg(), new GetMsg(), new GetLoginInfo(), new GetFriendList(), diff --git a/src/onebot11/actions/types.ts b/src/onebot11/actions/types.ts index 39f0b25..b604b24 100644 --- a/src/onebot11/actions/types.ts +++ b/src/onebot11/actions/types.ts @@ -14,6 +14,7 @@ export interface InvalidCheckResult { } export enum ActionName{ + TestForwardMsg = "test_forward_msg", GetLoginInfo = "get_login_info", GetFriendList = "get_friend_list", GetGroupInfo = "get_group_info", diff --git a/src/onebot11/server.ts b/src/onebot11/server.ts index a11ef2f..56f6391 100644 --- a/src/onebot11/server.ts +++ b/src/onebot11/server.ts @@ -41,24 +41,28 @@ expressAPP.use((req, res, next) => { }); const expressAuthorize = (req: Request, res: Response, next: () => void) => { - let token = "" - const authHeader = req.get("authorization") - if (authHeader) { - token = authHeader.split("Bearer ").pop() - log("receive http header token", token) - } else if (req.query.access_token) { - if (Array.isArray(req.query.access_token)) { - token = req.query.access_token[0].toString(); - } else { - token = req.query.access_token.toString(); + try { + let token = "" + const authHeader = req.get("authorization") + if (authHeader) { + token = authHeader.split("Bearer ").pop() + log("receive http header token", token) + } else if (req.query.access_token) { + if (Array.isArray(req.query.access_token)) { + token = req.query.access_token[0].toString(); + } else { + token = req.query.access_token.toString(); + } + log("receive http url token", token) } - log("receive http url token", token) - } - if (accessToken) { - if (token != accessToken) { - return res.status(403).send(JSON.stringify({message: 'token verify failed!'})); + if (accessToken) { + if (token != accessToken) { + return res.status(403).send(JSON.stringify({message: 'token verify failed!'})); + } } + }catch (e) { + log("receive http failed", e.stack) } next(); diff --git a/src/onebot11/types.ts b/src/onebot11/types.ts index 30ed198..ad9c9c4 100644 --- a/src/onebot11/types.ts +++ b/src/onebot11/types.ts @@ -71,19 +71,6 @@ export interface OB11Message { raw?: RawMessage } -export type OB11ApiName = - "send_msg" - | "send_private_msg" - | "send_group_msg" - | "get_group_list" - | "get_group_info" - | "get_friend_list" - | "delete_msg" - | "get_login_info" - | "get_group_member_list" - | "get_group_member_info" - | "get_msg" - export interface OB11Return { status: number retcode: number @@ -102,45 +89,69 @@ export enum OB11MessageDataType { at = "at", reply = "reply", json = "json", - face = "face" + face = "face", + node = "node" // 合并转发消息 } -export type OB11MessageData = { +export interface OB11MessageText { type: OB11MessageDataType.text, - content: string, - data?: { + data: { text: string, // 纯文本 } -} | { - type: "image" | "voice" | "record", - file: string, // 本地路径 - data?: { - file: string // 本地路径 - } -} | { - type: OB11MessageDataType.at, - atType?: AtType, - content?: string, - atUid?: string, - atNtUid?: string, - data?: { - qq: string // at的qq号 - } -} | { - type: OB11MessageDataType.reply, - msgId: string, - msgSeq: string, - senderUin: string, +} + +interface OB11MessageFileBase { data: { - id: string, + file: string } -} | { - type: OB11MessageDataType.face, +} + +export interface OB11MessageImage extends OB11MessageFileBase { + type: OB11MessageDataType.image +} + +export interface OB11MessageRecord extends OB11MessageFileBase { + type: OB11MessageDataType.voice +} + +export interface OB11MessageAt { + type: OB11MessageDataType.at + data: { + qq: string | "all" + } +} + +export interface OB11MessageReply { + type: OB11MessageDataType.reply data: { id: string } } +export interface OB11MessageFace { + type: OB11MessageDataType.face + data: { + id: string + } +} + +export interface OB11MessageNode { + type: OB11MessageDataType.node + data: { + id?: string + user_id?: number + nickname: string + content: OB11MessageData[] + } +} + +export type OB11MessageData = + OB11MessageText | + OB11MessageFace | + OB11MessageAt | OB11MessageReply | + OB11MessageImage | OB11MessageRecord | + OB11MessageNode + export interface OB11PostSendMsg { message_type?: "private" | "group" user_id: string, diff --git a/src/preload.ts b/src/preload.ts index afde46f..2f3d27e 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -1,25 +1,24 @@ // Electron 主进程 与 渲染进程 交互的桥梁 import {Config} from "./common/types"; -import { - CHANNEL_GET_CONFIG, - CHANNEL_LOG, - CHANNEL_SET_CONFIG, -} from "./common/channels"; - - +import {CHANNEL_GET_CONFIG, CHANNEL_LOG, CHANNEL_SET_CONFIG,} from "./common/channels"; const {contextBridge} = require("electron"); const {ipcRenderer} = require('electron'); -// 在window对象下导出只读对象 -contextBridge.exposeInMainWorld("llonebot", { +const llonebot = { log: (data: any) => { ipcRenderer.send(CHANNEL_LOG, data); }, - setConfig: (config: Config)=>{ + setConfig: (config: Config) => { ipcRenderer.send(CHANNEL_SET_CONFIG, config); }, getConfig: async () => { return ipcRenderer.invoke(CHANNEL_GET_CONFIG); }, -}); \ No newline at end of file +} + +export type LLOneBot = typeof llonebot; + +// 在window对象下导出只读对象 +contextBridge.exposeInMainWorld("llonebot", llonebot); +; \ No newline at end of file diff --git a/src/renderer.ts b/src/renderer.ts index 55319d9..b379192 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -67,7 +67,7 @@ async function onSettingWindowCreated(view: Element) {
上报自身消息
-
开启后上报自己发出的消息
+
慎用,不然会自己和自己聊个不停
@@ -165,6 +165,21 @@ async function onSettingWindowCreated(view: Element) { } +function init() { + let hash = location.hash; + if (hash === "#/blank") { + return; + } +} + + +if (location.hash === "#/blank") { + (window as any).navigation.addEventListener("navigatesuccess", init, {once: true}); +} else { + init(); +} + + export { onSettingWindowCreated }