diff --git a/README.md b/README.md index a560163..0fb8b43 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ## 安装方法 -1.安装[NTQQLiteLoader](https://liteloaderqqnt.github.io/guide/install.html) +1.安装[LiteLoaderQQNT](https://liteloaderqqnt.github.io/guide/install.html) 2.安装修改后的[LiteLoaderQQNT-Plugin-LLAPI](https://github.com/linyuchen/LiteLoaderQQNT-Plugin-LLAPI/releases),原版的功能有缺陷 @@ -21,13 +21,16 @@ 目前只支持http协议POST方法,不支持websocket,事件上报也是http协议 +主要功能: +- [x] 发送好友消息 +- [x] 发送临时消息 +- [x] 发送群消息 +- [x] 获取好友列表 - [x] 获取群列表 - [x] 获取群成员列表 -- [x] 获取好友列表 -- [x] 发送群消息 -- [x] 发送好友消息 - [x] 撤回消息 - [x] 上报好友消息 +- [x] 上报临时消息 - [x] 上报群消息 消息格式支持: @@ -58,5 +61,70 @@ *暂时不支持`"message": "hello"`这种message为字符串的形式* +## 一些坑 + +
+ 下载了插件但是没有看到在NTQQ中生效 +
+ 检查是否下载的是插件release的版本,如果是源码的话需要自行编译。依然不生效请查阅LiteLoaderQQNT的文档 +
+
+ +
+ 调用接口报404 +
+ 目前没有支持全部的onebot规范接口,请检查是否调用了不支持的接口,并且所有接口都只支持POST方法,调用GET方法会报404 +
+
+ +
+ 发送不了图片和语音 +
+ 检查当前操作用户是否有LiteLoaderQQNT/data/LLOneBot的写入权限,如Windows把QQ上安装到C盘有可能会出现无权限导致发送失败 +
+
+ +
+ 不支持cq码 +
+ cq码已经过时了,没有支持的打算(主要是我不用这玩意儿,加上我懒) +
+
+ +
+ onebot 12对接不了 +
+ onebot 12只写了部分兼容,没有完整测试,不保证能用,慎用 +
+
+ +
+ QQ多开时事件没有上报 +
+ 小概率事件,有可能是IPC通信串台了(不确定),重启QQ可解决,目前正在想办法修复 +
+
+ +
+ QQ变得很卡 +
+ 这是你的群特别多导致的,因为启动后会批量获取群成员列表,获取完之后就正常了 +
+
+ +
+ 如何查看日志 +
+ LiteLoaderQQNT/data/LLOneBot/*.log +
+
+ +## TODO + +- [x] 接口返回更详细的错误信息,目前消息发不出去也会返回发送成功(这河里吗) +- [ ] 转发消息记录 +- [ ] 支持websocket,等个有缘人提PR实现 +- [ ] 重构摆脱LLAPI,目前调用LLAPI只能在renderer进程调用,需重构成在main进程调用 + ## onebot11文档 diff --git a/manifest.json b/manifest.json index 5ff5984..8804270 100644 --- a/manifest.json +++ b/manifest.json @@ -4,20 +4,20 @@ "name": "LLOneBot", "slug": "LLOneBot", "description": "LiteLoaderQQNT的OneBotApi", - "version": "2.0.4", + "version": "2.1.0", "thumbnail": "./icon.png", "authors": [{ "name": "linyuchen", "link": "https://github.com/linyuchen" }], "repository": { - "repo": "linyuchen/LiteLoaderQQNT-OneBotApi", - "branch": "main", - "release": { - "tag": "latest", - "name": "LLOneBot.zip" - } - }, + "repo": "linyuchen/LiteLoaderQQNT-OneBotApi", + "branch": "main", + "release": { + "tag": "latest", + "name": "LLOneBot.zip" + } +}, "platform": [ "win32", "linux", diff --git a/package-lock.json b/package-lock.json index d3ac971..eee467c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "express": "^4.18.2" + "express": "^4.18.2", + "uuid": "^9.0.1" }, "devDependencies": { "@babel/preset-env": "^7.23.2", @@ -5213,6 +5214,18 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://mirrors.cloud.tencent.com/npm/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://mirrors.cloud.tencent.com/npm/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 10924a8..4cdd843 100644 --- a/package.json +++ b/package.json @@ -17,16 +17,17 @@ "author": "", "license": "ISC", "dependencies": { - "express": "^4.18.2" + "express": "^4.18.2", + "uuid": "^9.0.1" }, "devDependencies": { - "electron": "^27.0.2", "@babel/preset-env": "^7.23.2", "@types/express": "^4.17.20", "babel-loader": "^9.1.3", + "electron": "^27.0.2", "ts-loader": "^9.5.0", + "typescript": "^5.2.2", "webpack": "^5.89.0", - "webpack-cli": "^5.1.4", - "typescript": "^5.2.2" + "webpack-cli": "^5.1.4" } } diff --git a/src/common/types.ts b/src/common/types.ts index baf228b..e9f7627 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -3,6 +3,12 @@ export enum AtType { atUser = 2 } +export enum ChatType { + friend = 1, + group = 2, + temp = 100 +} + export type GroupMemberInfo = { avatarPath: string; cardName: string; @@ -44,7 +50,7 @@ export type Group = { } export type Peer = { - chatType: "private" | "group" | "friend" + chatType: ChatType name: string uid: string // qq号 } @@ -52,8 +58,10 @@ export type Peer = { export type MessageElement = { raw: { msgId: string, + msgTime: string, msgSeq: string, senderUin: string; // 发送者QQ号 + chatType: ChatType, elements: { replyElement: { senderUid: string, // 原消息发送者QQ号 @@ -139,7 +147,7 @@ export type SendMessage = { } export type PostDataAction = "send_private_msg" | "send_group_msg" | "get_group_list" -| "get_friend_list" | "delete_msg" | "get_login_info" | "get_group_member_list" | "get_group_member_info" + | "get_friend_list" | "delete_msg" | "get_login_info" | "get_group_member_list" | "get_group_member_info" export type PostDataSendMsg = { action: PostDataAction @@ -152,9 +160,17 @@ export type PostDataSendMsg = { user_id: string, group_id: string, message: SendMessage[]; + ipc_uuid?: string } export type Config = { port: number, hosts: string[], } + +export type SendMsgResult = { + status: number, + retcode: number, + data: any, + message: string, +} \ No newline at end of file diff --git a/src/global.d.ts b/src/global.d.ts index 2ecbbb1..6f38bfb 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -6,7 +6,7 @@ import { Peer, PostDataSendMsg, SelfInfo, - SendMessage, + SendMessage, SendMsgResult, User } from "./common/types"; @@ -43,9 +43,10 @@ declare var llonebot: { setConfig(config: Config):void; getConfig():Promise; setSelfInfo(selfInfo: SelfInfo):void; - downloadFile(arg: {uri: string, localFilePath: string}):Promise; + downloadFile(arg: {uri: string, fileName: string}):Promise<{errMsg: string, path: string}>; deleteFile(path: string[]):Promise; getRunningStatus(): Promise; + sendSendMsgResult(sessionId: string, msgResult: SendMsgResult): void; }; declare global { diff --git a/src/main/HttpServer.ts b/src/main/HttpServer.ts index 92ee868..4a891f7 100644 --- a/src/main/HttpServer.ts +++ b/src/main/HttpServer.ts @@ -2,7 +2,7 @@ import {log} from "./utils"; const express = require("express"); import {sendIPCRecallQQMsg, sendIPCSendQQMsg} from "./IPCSend"; -import {OnebotGroupMemberRole, PostDataAction, PostDataSendMsg, SendMessage} from "../common/types"; +import {OnebotGroupMemberRole, PostDataAction, PostDataSendMsg, SendMessage, SendMsgResult} from "../common/types"; import {friends, groups, selfInfo} from "./data"; // @SiberianHusky 2021-08-15 @@ -18,7 +18,7 @@ function checkSendMessage(sendMsgList: SendMessage[]) { let data = msg["data"]; if (type === "text" && !data["text"]) { return 400; - } else if (["image", "voice"].includes(type)) { + } else if (["image", "voice", "record"].includes(type)) { if (!data["file"]) { return 400; } @@ -45,7 +45,7 @@ function checkSendMessage(sendMsgList: SendMessage[]) { } // ==end== -function handlePost(jsonData: any) { +function handlePost(jsonData: any, handleSendResult: (data: SendMsgResult)=>void) { log("API receive post:" + JSON.stringify(jsonData)) if (!jsonData.params) { jsonData.params = JSON.parse(JSON.stringify(jsonData)); @@ -57,6 +57,7 @@ function handlePost(jsonData: any) { data: {}, message: '' } + if (jsonData.action == "get_login_info") { resData["data"] = selfInfo } else if (jsonData.action == "send_private_msg" || jsonData.action == "send_group_msg") { @@ -70,7 +71,8 @@ function handlePost(jsonData: any) { if (resData.status == 200) { resData.message = "发送成功"; resData.data = jsonData.message; - sendIPCSendQQMsg(jsonData); + sendIPCSendQQMsg(jsonData, handleSendResult); + return; } else { resData.message = "发送失败, 请检查消息格式"; resData.data = jsonData.message; @@ -151,8 +153,10 @@ export function startExpress(port: number) { app.post('/' + action, (req: any, res: any) => { let jsonData: PostDataSendMsg = req.body; jsonData.action = action - let resData = handlePost(jsonData) - res.send(resData) + let resData = handlePost(jsonData, (data:SendMsgResult)=>{res.send(data)}) + if (resData){ + res.send(resData) + } }); } @@ -171,8 +175,10 @@ export function startExpress(port: number) { // 处理POST请求的路由 app.post('/', (req: any, res: any) => { let jsonData: PostDataSendMsg = req.body; - let resData = handlePost(jsonData) - res.send(resData) + let resData = handlePost(jsonData, (data:SendMsgResult)=>{res.send(data)}) + if (resData){ + res.send(resData) + } }); app.post('/send_msg', (req: any, res: any) => { let jsonData: PostDataSendMsg = req.body; @@ -187,11 +193,13 @@ export function startExpress(port: number) { jsonData.action = "send_private_msg" } } - let resData = handlePost(jsonData) - res.send(resData) + let resData = handlePost(jsonData, (data:SendMsgResult)=>{res.send(data)}) + if (resData){ + res.send(resData) + } }) app.listen(port, "0.0.0.0", () => { - console.log(`服务器已启动,监听端口 ${port}`); + console.log(`llonebot started 0.0.0.0:${port}`); }); } \ No newline at end of file diff --git a/src/main/IPCSend.ts b/src/main/IPCSend.ts index c69b45c..8d7d3bf 100644 --- a/src/main/IPCSend.ts +++ b/src/main/IPCSend.ts @@ -1,6 +1,8 @@ import {ipcMain, webContents} from 'electron'; -import {PostDataSendMsg} from "../common/types"; +import {PostDataSendMsg, SendMsgResult} from "../common/types"; import {CHANNEL_RECALL_MSG, CHANNEL_SEND_MSG} from "../common/IPCChannel"; +import {v4 as uuid4} from "uuid"; +import {log} from "./utils"; function sendIPCMsg(channel: string, data: any) { let contents = webContents.getAllWebContents(); @@ -14,7 +16,17 @@ function sendIPCMsg(channel: string, data: any) { } -export function sendIPCSendQQMsg(postData: PostDataSendMsg) { +export function sendIPCSendQQMsg(postData: PostDataSendMsg, handleSendResult: (data: SendMsgResult) => void) { + const onceSessionId = "llonebot_send_msg_" + uuid4(); + postData.ipc_uuid = onceSessionId; + ipcMain.once(onceSessionId, (event: any, sendResult: SendMsgResult) => { + // log("llonebot send msg ipcMain.once:" + JSON.stringify(sendResult)); + try { + handleSendResult(sendResult) + } catch (e) { + log("llonebot send msg ipcMain.once error:" + JSON.stringify(e)) + } + }) sendIPCMsg(CHANNEL_SEND_MSG, postData); } diff --git a/src/main/main.ts b/src/main/main.ts index c6653ff..de0a992 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -17,7 +17,7 @@ import { } from "../common/IPCChannel"; import {ConfigUtil} from "./config"; import {startExpress} from "./HttpServer"; -import {isGIF, log} from "./utils"; +import {CONFIG_DIR, isGIF, log} from "./utils"; import {friends, groups, selfInfo} from "./data"; import {} from "../global"; @@ -29,40 +29,78 @@ let running = false; // 加载插件时触发 function onLoad() { log("main onLoaded"); + // const config_dir = browserWindow.LiteLoader.plugins["LLOneBot"].path.data; - const config_dir = global.LiteLoader.plugins["LLOneBot"].path.data; function getConfigUtil() { - const configFilePath = path.join(config_dir, `config_${selfInfo.user_id}.json`) + const configFilePath = path.join(CONFIG_DIR, `config_${selfInfo.user_id}.json`) return new ConfigUtil(configFilePath) } - if (!fs.existsSync(config_dir)) { - fs.mkdirSync(config_dir, {recursive: true}); + if (!fs.existsSync(CONFIG_DIR)) { + fs.mkdirSync(CONFIG_DIR, {recursive: true}); } ipcMain.handle(CHANNEL_GET_CONFIG, (event: any, arg: any) => { return getConfigUtil().getConfig() }) - ipcMain.handle(CHANNEL_DOWNLOAD_FILE, async (event: any, arg: {uri: string, localFilePath: string}) => { + ipcMain.handle(CHANNEL_DOWNLOAD_FILE, async (event: any, arg: { uri: string, fileName: string }): Promise<{ + success: boolean, + errMsg: string, + path: string + }> => { + let filePath = path.join(CONFIG_DIR, arg.fileName) let url = new URL(arg.uri); - if (url.protocol == "base64:"){ + if (url.protocol == "base64:") { // base64转成文件 let base64Data = arg.uri.split("base64://")[1] - const buffer = Buffer.from(base64Data, 'base64'); + try { + const buffer = Buffer.from(base64Data, 'base64'); - fs.writeFileSync(arg.localFilePath, buffer); - } - else if (url.protocol == "http:" || url.protocol == "https:") { + fs.writeFileSync(filePath, buffer); + } catch (e: any) { + return { + success: false, + errMsg: `base64文件下载失败,` + e.toString(), + path: "" + } + } + } else if (url.protocol == "http:" || url.protocol == "https:") { // 下载文件 let res = await fetch(url) + if (!res.ok) { + return { + success: false, + errMsg: `${url}下载失败,` + res.statusText, + path: "" + } + } let blob = await res.blob(); let buffer = await blob.arrayBuffer(); - fs.writeFileSync(arg.localFilePath, Buffer.from(buffer)); + try { + fs.writeFileSync(filePath, Buffer.from(buffer)); + } catch (e: any) { + return { + success: false, + errMsg: `${url}下载失败,` + e.toString(), + path: "" + } + } } - if (isGIF(arg.localFilePath)) { - fs.renameSync(arg.localFilePath, arg.localFilePath + ".gif"); - arg.localFilePath += ".gif"; + else{ + return { + success: false, + errMsg: `不支持的file协议,` + url.protocol, + path: "" + } } - return arg.localFilePath; + if (isGIF(filePath)) { + fs.renameSync(filePath, filePath + ".gif"); + filePath += ".gif"; + } + return { + success: true, + errMsg: "", + path: filePath + }; }) ipcMain.on(CHANNEL_SET_CONFIG, (event: any, arg: Config) => { getConfigUtil().setConfig(arg) @@ -103,7 +141,7 @@ function onLoad() { }) ipcMain.on(CHANNEL_POST_ONEBOT_DATA, (event: any, arg: any) => { - for(const host of getConfigUtil().getConfig().hosts) { + for (const host of getConfigUtil().getConfig().hosts) { try { fetch(host, { method: "POST", @@ -113,9 +151,9 @@ function onLoad() { }, body: JSON.stringify(arg) }).then((res: any) => { - log("新消息事件上传"); + log(`新消息事件上传成功: ${host} ` + JSON.stringify(arg)); }, (err: any) => { - log("新消息事件上传失败:" + err + JSON.stringify(arg)); + log(`新消息事件上传失败: ${host} ` + err + JSON.stringify(arg)); }); } catch (e: any) { log(e.toString()) diff --git a/src/main/utils.ts b/src/main/utils.ts index 727e07f..2a52378 100644 --- a/src/main/utils.ts +++ b/src/main/utils.ts @@ -1,8 +1,19 @@ +import * as path from "path"; +import {json} from "express"; +import {selfInfo} from "./data"; + const fs = require('fs'); +export const CONFIG_DIR = global.LiteLoader.plugins["LLOneBot"].path.data; export function log(msg: any) { let currentDateTime = new Date().toLocaleString(); - fs.appendFile("./llonebot.log", currentDateTime + ":" + msg + "\n", (err: any) => { + const date = new Date(); + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + const currentDate = `${year}-${month}-${day}`; + const userInfo = selfInfo.user_id ? `${selfInfo.nickname}(${selfInfo.user_id})` : "" + fs.appendFile(path.join(CONFIG_DIR , `llonebot-${currentDate}.log`), currentDateTime + ` ${userInfo}:` + JSON.stringify(msg) + "\n", (err: any) => { }) } @@ -13,4 +24,5 @@ export function isGIF(path: string) { fs.readSync(fd, buffer, 0, 4, 0); fs.closeSync(fd); return buffer.toString() === 'GIF8' -} \ No newline at end of file +} + diff --git a/src/preload.ts b/src/preload.ts index f19256a..8252323 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -1,6 +1,6 @@ // Electron 主进程 与 渲染进程 交互的桥梁 -import {Config, Group, PostDataSendMsg, SelfInfo, User} from "./common/types"; +import {Config, Group, PostDataSendMsg, SelfInfo, SendMsgResult, User} from "./common/types"; import { CHANNEL_DOWNLOAD_FILE, CHANNEL_GET_CONFIG, @@ -33,6 +33,9 @@ contextBridge.exposeInMainWorld("llonebot", { updateFriends: (friends: User[]) => { ipcRenderer.send(CHANNEL_UPDATE_FRIENDS, friends); }, + sendSendMsgResult: (sessionId: string, msgResult: SendMsgResult)=>{ + ipcRenderer.send(sessionId, msgResult); + }, listenSendMessage: (handle: (jsonData: PostDataSendMsg) => void) => { ipcRenderer.send(CHANNEL_LOG, "发送消息API已注册"); ipcRenderer.on(CHANNEL_SEND_MSG, (event: any, args: PostDataSendMsg) => { diff --git a/src/renderer.ts b/src/renderer.ts index 545d670..ec16bcc 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -2,15 +2,31 @@ // import express from "express"; // const { ipcRenderer } = require('electron'); -import {AtType, Group, MessageElement, OnebotGroupMemberRole, Peer, PostDataSendMsg, User} from "./common/types"; -import * as stream from "stream"; -import {raw} from "express"; +import { + AtType, + ChatType, + Group, + MessageElement, + OnebotGroupMemberRole, + Peer, + PostDataSendMsg, SendMsgResult, + User +} from "./common/types"; let self_qq: string = "" let groups: Group[] = [] let friends: User[] = [] let msgHistory: MessageElement[] = [] let uid_maps: Record = {} // 一串加密的字符串 -> qq号 + +function getStrangerByUin(uin: string) { + for (const key in uid_maps) { + if (uid_maps[key].uin === uin) { + return uid_maps[key]; + } + } +} + async function getUserInfo(uid: string): Promise { let user = uid_maps[uid] if (!user) { @@ -80,6 +96,11 @@ async function getGroupMembers(group_qq: string, forced: boolean = false) { if (!group!.members!.find(m => m.uid == member.uid)) { group!.members!.push(member) } + uid_maps[member.uid] = { + uin: member.uin, + uid: member.uid, + nickName: member.nick + }; } window.llonebot.updateGroups(groups) console.log(`更新群${group.name}成员列表`, group) @@ -100,9 +121,8 @@ async function getGroupMember(group_qq: string, member_uid: string) { } } - async function handleNewMessage(messages: MessageElement[]) { - // console.log("llonebot 收到消息:", messages); + console.log("llonebot 收到消息:", messages); for (let message of messages) { let onebot_message_data: any = { self: { @@ -110,7 +130,7 @@ async function handleNewMessage(messages: MessageElement[]) { user_id: self_qq }, self_id: self_qq, - time: 0, + time: parseInt(message.raw.msgTime || "0"), type: "message", post_type: "message", message_type: message.peer.chatType, @@ -121,9 +141,10 @@ async function handleNewMessage(messages: MessageElement[]) { raw_message: "", font: 14 } - if (message.peer.chatType == "group") { + if (message.raw.chatType == ChatType.group) { let group_id = message.peer.uid let group = (await getGroup(group_id))! + onebot_message_data.message_type = onebot_message_data.sub_type = "group" onebot_message_data["group_id"] = message.peer.uid let groupMember = await getGroupMember(group_id, message.sender.uid) onebot_message_data["user_id"] = groupMember!.uin @@ -134,13 +155,25 @@ async function handleNewMessage(messages: MessageElement[]) { role: OnebotGroupMemberRole[groupMember!.role] } // console.log("收到群消息", onebot_message_data) - } else if (message.peer.chatType == "private" || message.peer.chatType == "friend") { + } else if (message.raw.chatType == ChatType.friend) { onebot_message_data["user_id"] = message.raw.senderUin; - let friend = await getFriend(message.raw.senderUin) + onebot_message_data.message_type = onebot_message_data.sub_type = "friend" + let friend = await getFriend(message.raw.senderUin); onebot_message_data.sender = { user_id: friend!.uin, nickname: friend!.nickName } + } else if (message.raw.chatType == ChatType.temp) { + let senderQQ = message.raw.senderUin; + let senderUid = message.sender.uid; + let sender = await getUserInfo(senderUid); + onebot_message_data["user_id"] = senderQQ; + onebot_message_data.message_type = "friend" + onebot_message_data.sub_type = "group"; + onebot_message_data.sender = { + user_id: senderQQ, + nickname: sender.nickName + } } for (let element of message.raw.elements) { let message_data: any = { @@ -168,6 +201,7 @@ async function handleNewMessage(messages: MessageElement[]) { if (!element.picElement.sourcePath.startsWith("/")) { startS += "/" } + // todo: 转成base64 message_data["data"]["file"] = startS + element.picElement.sourcePath } else if (element.replyElement) { message_data["type"] = "reply" @@ -175,6 +209,9 @@ async function handleNewMessage(messages: MessageElement[]) { } onebot_message_data.message.push(message_data) } + if (msgHistory.length > 10000) { + msgHistory.splice(0, 100) + } msgHistory.push(message) console.log("发送上传消息给ipc main", onebot_message_data) window.llonebot.postData(onebot_message_data); @@ -183,6 +220,12 @@ async function handleNewMessage(messages: MessageElement[]) { async function listenSendMessage(postData: PostDataSendMsg) { console.log("收到发送消息请求", postData); + let sendMsgResult: SendMsgResult = { + retcode: 0, + status: 0, + data: {}, + message: "发送成功" + } if (postData.action == "send_private_msg" || postData.action == "send_group_msg") { let peer: Peer | null = null; if (!postData.params) { @@ -195,22 +238,41 @@ async function listenSendMessage(postData: PostDataSendMsg) { if (postData.action == "send_private_msg") { let friend = await getFriend(postData.params.user_id) if (friend) { + console.log("好友消息", postData) peer = { - chatType: "private", + chatType: ChatType.friend, name: friend.nickName, uid: friend.uid } + } else { + // 临时消息 + console.log("发送临时消息", postData) + let receiver = getStrangerByUin(postData.params.user_id); + if (receiver) { + peer = { + chatType: ChatType.temp, + name: receiver.nickName, + uid: receiver.uid + } + } else { + sendMsgResult.status = -1; + sendMsgResult.retcode = -1; + sendMsgResult.message = `发送失败,未找到对象${postData.params.user_id},检查他是否为好友或是群友`; + } } } else if (postData.action == "send_group_msg") { let group = await getGroup(postData.params.group_id) if (group) { peer = { - chatType: "group", + chatType: ChatType.group, name: group.name, uid: group.uid } } else { + sendMsgResult.status = -1; + sendMsgResult.retcode = -1; + sendMsgResult.message = `发送失败,未找到群${postData.params.group_id}`; console.log("未找到群, 发送群消息失败", postData) } } @@ -245,10 +307,23 @@ async function listenSendMessage(postData: PostDataSendMsg) { if (uri.protocol == "file:") { localFilePath = url.split("file://")[1] } else { - localFilePath = await window.llonebot.downloadFile({uri: url, localFilePath: localFilePath}) - sendFiles.push(localFilePath); + const {errMsg, path} = await window.llonebot.downloadFile({ + uri: url, + fileName: `${Date.now()}${ext}` + }) + console.log("下载文件结果", errMsg, path) + if (errMsg) { + console.log("下载文件失败", errMsg); + sendMsgResult.status = -1; + sendMsgResult.retcode = -1; + sendMsgResult.message = `发送失败,下载文件失败,${errMsg}`; + break; + } else { + localFilePath = path; + } } message.file = localFilePath + sendFiles.push(localFilePath); } else if (message.type == "reply") { let msgId = message.data?.id || message.msgId let replyMessage = msgHistory.find(msg => msg.raw.msgId == msgId) @@ -257,13 +332,27 @@ async function listenSendMessage(postData: PostDataSendMsg) { } } console.log("发送消息", postData) + if (sendMsgResult.status !== 0) { + window.llonebot.sendSendMsgResult(postData.ipc_uuid, sendMsgResult) + return; + } window.LLAPI.sendMessage(peer, postData.params.message).then(res => { console.log("消息发送成功:", peer, postData.params.message) if (sendFiles.length) { window.llonebot.deleteFile(sendFiles); } + window.llonebot.sendSendMsgResult(postData.ipc_uuid, sendMsgResult) }, - err => console.log("消息发送失败", postData, err)) + err => { + sendMsgResult.status = -1; + sendMsgResult.retcode = -1; + sendMsgResult.message = `发送失败,${err}`; + window.llonebot.sendSendMsgResult(postData.ipc_uuid, sendMsgResult) + console.log("消息发送失败", postData, err) + }) + } else { + console.log(sendMsgResult, postData); + window.llonebot.sendSendMsgResult(postData.ipc_uuid, sendMsgResult) } } } @@ -308,7 +397,7 @@ function onNewMessages(messages: MessageElement[]) { // console.log("chatListEle", chatListEle) } -async function initAccountInfo(){ +async function initAccountInfo() { let accountInfo = await window.LLAPI.getAccountInfo(); window.llonebot.log("getAccountInfo " + JSON.stringify(accountInfo)); if (!accountInfo.uid) { @@ -325,21 +414,23 @@ async function initAccountInfo(){ function onLoad() { window.llonebot.log("llonebot render onLoad"); - window.llonebot.getRunningStatus().then(running=>{ + window.llonebot.getRunningStatus().then(running => { if (running) { return; } initAccountInfo().then( - (initSuccess)=>{ + (initSuccess) => { if (!initSuccess) { return; } if (friends.length == 0) { - getFriends().then(()=>{}); + getFriends().then(() => { + }); } if (groups.length == 0) { - getGroups().then(()=>{ - getGroupsMembers(groups).then(()=>{}); + getGroups().then(() => { + getGroupsMembers(groups).then(() => { + }); }); } window.LLAPI.on("new-messages", onNewMessages); @@ -354,93 +445,93 @@ function onLoad() { recallMessage(arg.message_id) }) window.llonebot.log("llonebot loaded"); - // window.LLAPI.add_qmenu((qContextMenu: Node) => { - // let btn = document.createElement("a") - // btn.className = "q-context-menu-item q-context-menu-item--normal vue-component" - // btn.setAttribute("aria-disabled", "false") - // btn.setAttribute("role", "menuitem") - // btn.setAttribute("tabindex", "-1") - // btn.onclick = () => { - // // window.LLAPI.getPeer().then(peer => { - // // // console.log("current peer", peer) - // // if (peer && peer.chatType == "group") { - // // getGroupMembers(peer.uid, true).then(()=> { - // // console.log("获取群成员列表成功", groups); - // // alert("获取群成员列表成功") - // // }) - // // } - // // }) - // async function func() { - // for (const group of groups) { - // await getGroupMembers(group.uid, true) - // } - // } - // - // func().then(() => { - // console.log("获取群成员列表结果", groups); - // // 找到members数量为空的群 - // groups.map(group => { - // if (group.members.length == 0) { - // console.log(`${group.name}群成员为空`) - // } - // }) - // window.llonebot.updateGroups(groups) - // }) - // } - // btn.innerText = "获取群成员列表" - // console.log(qContextMenu) - // // qContextMenu.appendChild(btn) - // }) - // - // window.LLAPI.on("context-msg-menu", (event, target, msgIds) => { - // console.log("msg menu", event, target, msgIds); - // }) - // - // // console.log("getAccountInfo", LLAPI.getAccountInfo()); - // function getChatListEle() { - // chatListEle = document.getElementsByClassName("viewport-list__inner") - // console.log("chatListEle", chatListEle) - // if (chatListEle.length == 0) { - // setTimeout(getChatListEle, 500) - // } else { - // try { - // // 选择要观察的目标节点 - // const targetNode = chatListEle[0]; - // - // // 创建一个观察器实例并传入回调函数 - // const observer = new MutationObserver(function (mutations) { - // mutations.forEach(function (mutation) { - // // console.log("chat list changed", mutation.type); // 输出 mutation 的类型 - // // 获得当前聊天窗口 - // window.LLAPI.getPeer().then(peer => { - // // console.log("current peer", peer) - // if (peer && peer.chatType == "group") { - // getGroupMembers(peer.uid, false).then() - // } - // }) - // }); - // }); - // - // // 配置观察选项 - // const config = {attributes: true, childList: true, subtree: true}; - // - // // 传入目标节点和观察选项 - // observer.observe(targetNode, config); - // - // } catch (e) { - // window.llonebot.log(e) - // } - // } - // } - // - // // getChatListEle(); + // window.LLAPI.add_qmenu((qContextMenu: Node) => { + // let btn = document.createElement("a") + // btn.className = "q-context-menu-item q-context-menu-item--normal vue-component" + // btn.setAttribute("aria-disabled", "false") + // btn.setAttribute("role", "menuitem") + // btn.setAttribute("tabindex", "-1") + // btn.onclick = () => { + // // window.LLAPI.getPeer().then(peer => { + // // // console.log("current peer", peer) + // // if (peer && peer.chatType == "group") { + // // getGroupMembers(peer.uid, true).then(()=> { + // // console.log("获取群成员列表成功", groups); + // // alert("获取群成员列表成功") + // // }) + // // } + // // }) + // async function func() { + // for (const group of groups) { + // await getGroupMembers(group.uid, true) + // } + // } + // + // func().then(() => { + // console.log("获取群成员列表结果", groups); + // // 找到members数量为空的群 + // groups.map(group => { + // if (group.members.length == 0) { + // console.log(`${group.name}群成员为空`) + // } + // }) + // window.llonebot.updateGroups(groups) + // }) + // } + // btn.innerText = "获取群成员列表" + // console.log(qContextMenu) + // // qContextMenu.appendChild(btn) + // }) + // + // window.LLAPI.on("context-msg-menu", (event, target, msgIds) => { + // console.log("msg menu", event, target, msgIds); + // }) + // + // // console.log("getAccountInfo", LLAPI.getAccountInfo()); + // function getChatListEle() { + // chatListEle = document.getElementsByClassName("viewport-list__inner") + // console.log("chatListEle", chatListEle) + // if (chatListEle.length == 0) { + // setTimeout(getChatListEle, 500) + // } else { + // try { + // // 选择要观察的目标节点 + // const targetNode = chatListEle[0]; + // + // // 创建一个观察器实例并传入回调函数 + // const observer = new MutationObserver(function (mutations) { + // mutations.forEach(function (mutation) { + // // console.log("chat list changed", mutation.type); // 输出 mutation 的类型 + // // 获得当前聊天窗口 + // window.LLAPI.getPeer().then(peer => { + // // console.log("current peer", peer) + // if (peer && peer.chatType == "group") { + // getGroupMembers(peer.uid, false).then() + // } + // }) + // }); + // }); + // + // // 配置观察选项 + // const config = {attributes: true, childList: true, subtree: true}; + // + // // 传入目标节点和观察选项 + // observer.observe(targetNode, config); + // + // } catch (e) { + // window.llonebot.log(e) + // } + // } + // } + // + // // getChatListEle(); } ); }); } // 打开设置界面时触发 -async function onSettingWindowCreated (view: Element) { +async function onSettingWindowCreated(view: Element) { window.llonebot.log("setting window created"); const {port, hosts} = await window.llonebot.getConfig()