diff --git a/README.md b/README.md index 0e152ea..c3bfa56 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,116 @@ # LLOneBot API -将LiteLoaderQQNT API封装成OneBot11/12标准的API, V12没有完整测试 +将NTQQLiteLoaderAPI封装成OneBot11/12标准的API, V12没有完整测试 + +*注意:本文档对应的是 LiteLoader 1.0.0及以上版本,如果你使用的是旧版本请切换到本项目v1分支查看文档* + +## 安装方法 + +1.安装[LiteLoaderQQNT](https://liteloaderqqnt.github.io/guide/install.html) + +2.安装修改后的[LiteLoaderQQNT-Plugin-LLAPI](https://github.com/linyuchen/LiteLoaderQQNT-Plugin-LLAPI/releases),原版的功能有缺陷 + +3.安装本项目插件[OneBotApi](https://github.com/linyuchen/LiteLoaderQQNT-OneBotApi/releases/), 注意本插件2.0以下的版本不支持LiteLoader 1.0.0及以上版本 + +*关于插件的安装方法: 上述的两个插件都没有上架NTQQLiteLoader插件市场,需要自己下载复制到插件目录* + +*插件目录:`LiteLoaderQQNT/plugins`* + +## 支持的API + +目前只支持http协议POST方法,不支持websocket,事件上报也是http协议 + +主要功能: +- [x] 发送好友消息 +- [x] 发送群消息 +- [x] 获取好友列表 +- [x] 获取群列表 +- [x] 获取群成员列表 +- [x] 撤回消息 +- [x] 上报好友消息 +- [x] 上报群消息 + +消息格式支持: +- [x] 文字 +- [x] 图片 +- [x] 引用消息 +- [x] @群成员 +- [x] 语音 +- [x] json消息(只上报) +- [ ] 红包 +- [ ] 转发消息记录 +- [ ] xml + +支持的api: +- [x] get_login_info +- [x] send_msg +- [x] send_group_msg +- [x] send_private_msg +- [x] delete_msg +- [x] get_group_list +- [x] get_group_member_list +- [x] get_group_member_info +- [x] get_friend_list + +**自己发送成功的消息也会上报,可以用于获取需要撤回消息的id** + +## 示例 + +![](doc/image/example.jpg) + +*暂时不支持`"message": "hello"`这种message为字符串的形式* + +## 一些坑 + +
+ 下载了插件但是没有看到在NTQQ中生效 +
+ 检查是否下载的是插件release的版本,如果是源码的话需要自行编译。依然不生效请查阅LiteLoaderQQNT的文档 +
+
+ +
+ 调用接口报404 +
+ 目前没有支持全部的onebot规范接口,请检查是否调用了不支持的接口,并且所有接口都只支持POST方法,调用GET方法会报404 +
+
+ +
+ 发送不了图片和语音 +
+ 检查当前操作用户是否有LiteLoaderQQNT/data/LLOneBot的写入权限,如Windows把QQ上安装到C盘有可能会出现无权限导致发送失败 +
+
+ +
+ 不支持cq码 +
+ cq码已经过时了,没有支持的打算(主要是我不用这玩意儿,加上我懒) +
+
+ +
+ onebot 12对接不了 +
+ onebot 12只写了部分兼容,没有完整测试,不保证能用,慎用 +
+
+ +
+ QQ变得很卡 +
+ 这是你的群特别多导致的,因为启动后会批量获取群成员列表,获取完之后就正常了 +
+
+ + +## TODO + +- [ ] 转发消息记录 +- [ ] 好友点赞api +- [ ] 支持websocket,等个有缘人提PR实现 +- [ ] 重构摆脱LLAPI,目前调用LLAPI只能在renderer进程调用,需重构成在main进程调用 + +## onebot11文档 + diff --git a/manifest.json b/manifest.json index ec5a7f6..967fea2 100644 --- a/manifest.json +++ b/manifest.json @@ -4,7 +4,7 @@ "name": "LLOneBot", "slug": "LLOneBot", "description": "LiteLoaderQQNT的OneBotApi", - "version": "2.1.2", + "version": "2.3.0", "thumbnail": "./icon.png", "authors": [{ "name": "linyuchen", diff --git a/package-lock.json b/package-lock.json index eee467c..bad3962 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "express": "^4.18.2", + "json-bigint": "^1.0.0", "uuid": "^9.0.1" }, "devDependencies": { @@ -2751,6 +2752,14 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://mirrors.cloud.tencent.com/npm/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "engines": { + "node": "*" + } + }, "node_modules/body-parser": { "version": "1.20.1", "resolved": "https://mirrors.cloud.tencent.com/npm/body-parser/-/body-parser-1.20.1.tgz", @@ -4009,6 +4018,14 @@ "node": ">=4" } }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://mirrors.cloud.tencent.com/npm/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://mirrors.cloud.tencent.com/npm/json-buffer/-/json-buffer-3.0.1.tgz", diff --git a/package.json b/package.json index 4cdd843..cd84ed2 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "license": "ISC", "dependencies": { "express": "^4.18.2", + "json-bigint": "^1.0.0", "uuid": "^9.0.1" }, "devDependencies": { diff --git a/src/common/IPCChannel.ts b/src/common/IPCChannel.ts index a45a084..c505539 100644 --- a/src/common/IPCChannel.ts +++ b/src/common/IPCChannel.ts @@ -10,4 +10,5 @@ export const CHANNEL_POST_ONEBOT_DATA = "llonebot_post_onebot_data" export const CHANNEL_SET_SELF_INFO= "llonebot_set_self_info" export const CHANNEL_DOWNLOAD_FILE= "llonebot_download_file" export const CHANNEL_DELETE_FILE= "llonebot_delete_file" -export const CHANNEL_GET_RUNNING_STATUS= "llonebot_get_running_status" \ No newline at end of file +export const CHANNEL_GET_RUNNING_STATUS= "llonebot_get_running_status" +export const CHANNEL_FILE2BASE64= "llonebot_file2base64" \ No newline at end of file diff --git a/src/common/types.ts b/src/common/types.ts index c81c4de..1a00311 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -10,7 +10,7 @@ export enum ChatType { temp = 100 } -export type GroupMemberInfo = { +export interface GroupMemberInfo { avatarPath: string; cardName: string; cardType: number; @@ -31,12 +31,12 @@ export const OnebotGroupMemberRole = { } -export type SelfInfo = { +export interface SelfInfo { user_id: string; nickname: string; } -export type User = { +export interface User { avatarUrl?: string; bio?: string; // 签名 nickName: string; @@ -44,19 +44,46 @@ export type User = { uin: string; // QQ号 } -export type Group = { +export interface Group { uid: string; // 群号 name: string; members?: GroupMemberInfo[]; } -export type Peer = { +export interface Peer { chatType: ChatType name: string uid: string // qq号 } -export type MessageElement = { +export interface PttElement { + canConvert2Text: boolean + duration: number // 秒数 + fileBizId: null + fileId: number // 0 + fileName: string // "e4d09c784d5a2abcb2f9980bdc7acfe6.amr" + filePath: string // "/Users//Library/Containers/com.tencent.qq/Data/Library/Application Support/QQ/nt_qq_a6b15c9820595d25a56c1633ce19ad40/nt_data/Ptt/2023-11/Ori/e4d09c784d5a2abcb2f9980bdc7acfe6.amr" + fileSize: string // "4261" + fileSubId: string // "0" + fileUuid: string // "90j3z7rmRphDPrdVgP9udFBaYar#oK0TWZIV" + formatType: string // 1 + invalidState: number // 0 + md5HexStr: string // "e4d09c784d5a2abcb2f9980bdc7acfe6" + playState: number // 0 + progress: number // 0 + text: string // "" + transferStatus: number // 0 + translateStatus: number // 0 + voiceChangeType: number // 0 + voiceType: number // 0 + waveAmplitudes: number[] +} + +export interface ArkElement{ + bytesData: string +} + +export interface MessageElement { raw: { msgId: string, msgTime: string, @@ -84,28 +111,8 @@ export type MessageElement = { fileName: string fileUuid: string }, - pttElement: { - canConvert2Text: boolean - duration: number // 秒数 - fileBizId: null - fileId: number // 0 - fileName: string // "e4d09c784d5a2abcb2f9980bdc7acfe6.amr" - filePath: string // "/Users/C5366155/Library/Containers/com.tencent.qq/Data/Library/Application Support/QQ/nt_qq_a6b15c9820595d25a56c1633ce19ad40/nt_data/Ptt/2023-11/Ori/e4d09c784d5a2abcb2f9980bdc7acfe6.amr" - fileSize: string // "4261" - fileSubId: string // "0" - fileUuid: string // "90j3z7rmRphDPrdVgP9udFBaYar#oK0TWZIV" - formatType: string // 1 - invalidState: number // 0 - md5HexStr: string // "e4d09c784d5a2abcb2f9980bdc7acfe6" - playState: number // 0 - progress: number // 0 - text: string // "" - transferStatus: number // 0 - translateStatus: number // 0 - voiceChangeType: number // 0 - voiceType: number // 0 - waveAmplitudes: number[] - } + pttElement: PttElement, + arkElement: ArkElement }[] } peer: Peer, @@ -116,8 +123,17 @@ export type MessageElement = { } } +export enum MessageType { + text = "text", + image = "image", + voice = "record", + at = "at", + reply = "reply", + json = "json" +} + export type SendMessage = { - type: "text", + type: MessageType.text, content: string, data?: { text: string, // 纯文本 @@ -129,7 +145,7 @@ export type SendMessage = { file: string // 本地路径 } } | { - type: "at", + type: MessageType.at, atType?: AtType, content?: string, atUid?: string, @@ -138,7 +154,7 @@ export type SendMessage = { qq: string // at的qq号 } } | { - type: "reply", + type: MessageType.reply, msgId: string, msgSeq: string, senderUin: string, @@ -150,7 +166,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" -export type PostDataSendMsg = { +export interface PostDataSendMsg { action: PostDataAction message_type?: "private" | "group" params?: { @@ -164,14 +180,18 @@ export type PostDataSendMsg = { ipc_uuid?: string } -export type Config = { - port: number, - hosts: string[], +export interface Config { + port: number + hosts: string[] + enableBase64?: boolean + debug?: boolean + reportSelfMessage?: boolean + log?: boolean } -export type SendMsgResult = { - status: number, - retcode: number, - data: any, - message: string, +export interface 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 6f38bfb..221edbf 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -4,7 +4,7 @@ import { GroupMemberInfo, MessageElement, Peer, - PostDataSendMsg, + PostDataSendMsg, PttElement, SelfInfo, SendMessage, SendMsgResult, User @@ -27,6 +27,7 @@ declare var LLAPI: { getGroupMemberList(group_id: string, num: number): Promise<{result: { infos: Map }}> getPeer(): Promise add_qmenu(func: (qContextMenu: Node)=>void): void + Ptt2Text(msgId:string, peer: Peer, elements: MessageElement[]): Promise }; @@ -47,6 +48,7 @@ declare var llonebot: { deleteFile(path: string[]):Promise; getRunningStatus(): Promise; sendSendMsgResult(sessionId: string, msgResult: SendMsgResult): void; + file2base64(path: string): Promise<{err: string, data: string}>; }; declare global { diff --git a/src/main/HttpServer.ts b/src/main/HttpServer.ts index 4a891f7..f3fa148 100644 --- a/src/main/HttpServer.ts +++ b/src/main/HttpServer.ts @@ -1,6 +1,7 @@ import {log} from "./utils"; const express = require("express"); +const JSONbig = require('json-bigint'); import {sendIPCRecallQQMsg, sendIPCSendQQMsg} from "./IPCSend"; import {OnebotGroupMemberRole, PostDataAction, PostDataSendMsg, SendMessage, SendMsgResult} from "../common/types"; import {friends, groups, selfInfo} from "./data"; @@ -21,12 +22,10 @@ function checkSendMessage(sendMsgList: SendMessage[]) { } else if (["image", "voice", "record"].includes(type)) { if (!data["file"]) { return 400; - } - else{ + } else { if (checkUri(data["file"])) { return 200; - } - else{ + } else { return 400; } } @@ -36,16 +35,16 @@ function checkSendMessage(sendMsgList: SendMessage[]) { } else if (type === "reply" && !data["id"]) { return 400; } - } - else{ + } else { return 400 } } return 200; } + // ==end== -function handlePost(jsonData: any, handleSendResult: (data: SendMsgResult)=>void) { +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)); @@ -137,7 +136,7 @@ function handlePost(jsonData: any, handleSendResult: (data: SendMsgResult)=>void } }) } else if (jsonData.action == "delete_msg") { - sendIPCRecallQQMsg(jsonData.message_id) + sendIPCRecallQQMsg(String(jsonData.message_id)) } return resData } @@ -147,14 +146,30 @@ export function startExpress(port: number) { const app = express(); // 中间件,用于解析POST请求的请求体 app.use(express.urlencoded({extended: true, limit: "500mb"})); - app.use(express.json({limit: '500mb'})); + app.use(express.json({ + limit: '500mb', + verify: (req: any, res: any, buf: any, encoding: any) => { + req.rawBody = buf; + } + })); + app.use((req: any, res: any, next: any) => { + try { + req.body = JSONbig.parse(req.rawBody.toString()); + next(); + } catch (error) { + // next(error); + next(); + } + }); function parseToOnebot12(action: PostDataAction) { app.post('/' + action, (req: any, res: any) => { let jsonData: PostDataSendMsg = req.body; jsonData.action = action - let resData = handlePost(jsonData, (data:SendMsgResult)=>{res.send(data)}) - if (resData){ + let resData = handlePost(jsonData, (data: SendMsgResult) => { + res.send(data) + }) + if (resData) { res.send(resData) } }); @@ -175,8 +190,10 @@ export function startExpress(port: number) { // 处理POST请求的路由 app.post('/', (req: any, res: any) => { let jsonData: PostDataSendMsg = req.body; - let resData = handlePost(jsonData, (data:SendMsgResult)=>{res.send(data)}) - if (resData){ + let resData = handlePost(jsonData, (data: SendMsgResult) => { + res.send(data) + }) + if (resData) { res.send(resData) } }); @@ -193,8 +210,10 @@ export function startExpress(port: number) { jsonData.action = "send_private_msg" } } - let resData = handlePost(jsonData, (data:SendMsgResult)=>{res.send(data)}) - if (resData){ + let resData = handlePost(jsonData, (data: SendMsgResult) => { + res.send(data) + }) + if (resData) { res.send(resData) } }) diff --git a/src/main/IPCSend.ts b/src/main/IPCSend.ts index 8d7d3bf..2bf00c4 100644 --- a/src/main/IPCSend.ts +++ b/src/main/IPCSend.ts @@ -31,5 +31,5 @@ export function sendIPCSendQQMsg(postData: PostDataSendMsg, handleSendResult: (d } export function sendIPCRecallQQMsg(message_id: string) { - sendIPCMsg(CHANNEL_RECALL_MSG, {message_id}); + sendIPCMsg(CHANNEL_RECALL_MSG, {message_id: message_id}); } \ No newline at end of file diff --git a/src/main/main.ts b/src/main/main.ts index de0a992..c0840cd 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -2,6 +2,7 @@ import * as path from "path"; import {ipcMain} from 'electron'; +import * as util from 'util'; import {Config, Group, SelfInfo, User} from "../common/types"; import { @@ -13,11 +14,11 @@ import { CHANNEL_SET_CONFIG, CHANNEL_START_HTTP_SERVER, CHANNEL_UPDATE_FRIENDS, - CHANNEL_UPDATE_GROUPS, CHANNEL_DELETE_FILE, CHANNEL_GET_RUNNING_STATUS + CHANNEL_UPDATE_GROUPS, CHANNEL_DELETE_FILE, CHANNEL_GET_RUNNING_STATUS, CHANNEL_FILE2BASE64 } from "../common/IPCChannel"; import {ConfigUtil} from "./config"; import {startExpress} from "./HttpServer"; -import {CONFIG_DIR, isGIF, log} from "./utils"; +import {checkFileReceived, CONFIG_DIR, getConfigUtil, isGIF, log} from "./utils"; import {friends, groups, selfInfo} from "./data"; import {} from "../global"; @@ -31,10 +32,7 @@ function onLoad() { log("main onLoaded"); // const config_dir = browserWindow.LiteLoader.plugins["LLOneBot"].path.data; - function getConfigUtil() { - 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}); @@ -54,7 +52,6 @@ function onLoad() { let base64Data = arg.uri.split("base64://")[1] try { const buffer = Buffer.from(base64Data, 'base64'); - fs.writeFileSync(filePath, buffer); } catch (e: any) { return { @@ -180,6 +177,32 @@ function onLoad() { ipcMain.handle(CHANNEL_GET_RUNNING_STATUS, (event: any, arg: any) => { return running; }) + + ipcMain.handle(CHANNEL_FILE2BASE64, async (event: any, path: string): Promise<{err: string, data: string}> => { + const readFile = util.promisify(fs.readFile); + let result = { + err: "", + data: "" + } + try { + // 读取文件内容 + // if (!fs.existsSync(path)){ + // path = path.replace("\\Ori\\", "\\Thumb\\"); + // } + try { + await checkFileReceived(path, 5000); + } catch (e: any) { + result.err = e.toString(); + return result; + } + const data = await readFile(path); + // 转换为Base64编码 + result.data = data.toString('base64'); + } catch (err) { + result.err = err.toString(); + } + return result; + }) } diff --git a/src/main/utils.ts b/src/main/utils.ts index 2a52378..bfafa66 100644 --- a/src/main/utils.ts +++ b/src/main/utils.ts @@ -1,11 +1,21 @@ import * as path from "path"; import {json} from "express"; import {selfInfo} from "./data"; +import {ConfigUtil} from "./config"; const fs = require('fs'); export const CONFIG_DIR = global.LiteLoader.plugins["LLOneBot"].path.data; + +export function getConfigUtil() { + const configFilePath = path.join(CONFIG_DIR, `config_${selfInfo.user_id}.json`) + return new ConfigUtil(configFilePath) +} + export function log(msg: any) { + if (!getConfigUtil().getConfig().log){ + return + } let currentDateTime = new Date().toLocaleString(); const date = new Date(); const year = date.getFullYear(); @@ -26,3 +36,22 @@ export function isGIF(path: string) { return buffer.toString() === 'GIF8' } + +// 定义一个异步函数来检查文件是否存在 +export function checkFileReceived(path: string, timeout: number=3000): Promise { + return new Promise((resolve, reject) => { + const startTime = Date.now(); + + function check() { + if (fs.existsSync(path)) { + resolve(); + } else if (Date.now() - startTime > timeout) { + reject(new Error(`文件不存在: ${path}`)); + } else { + setTimeout(check, 100); + } + } + + check(); + }); +} diff --git a/src/preload.ts b/src/preload.ts index 8252323..93be597 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -14,7 +14,7 @@ import { CHANNEL_UPDATE_FRIENDS, CHANNEL_UPDATE_GROUPS, CHANNEL_DELETE_FILE, - CHANNEL_GET_RUNNING_STATUS + CHANNEL_GET_RUNNING_STATUS, CHANNEL_FILE2BASE64 } from "./common/IPCChannel"; @@ -70,6 +70,9 @@ contextBridge.exposeInMainWorld("llonebot", { }, getRunningStatus: () => { return ipcRenderer.invoke(CHANNEL_GET_RUNNING_STATUS); + }, + file2base64: (localFilePath: string) => { + return ipcRenderer.invoke(CHANNEL_FILE2BASE64, localFilePath); } // startExpress, }); \ No newline at end of file diff --git a/src/renderer.ts b/src/renderer.ts index 8ce229d..8f7bf07 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -6,12 +6,14 @@ import { AtType, ChatType, Group, - MessageElement, + MessageElement, MessageType, OnebotGroupMemberRole, Peer, - PostDataSendMsg, SendMsgResult, + PostDataSendMsg, + SendMsgResult, User } from "./common/types"; +import {checkFileReceived} from "./main/utils"; let self_qq: string = "" let groups: Group[] = [] @@ -123,6 +125,7 @@ async function getGroupMember(group_qq: string, member_uid: string) { async function handleNewMessage(messages: MessageElement[]) { console.log("llonebot 收到消息:", messages); + const {debug, enableBase64, reportSelfMessage} = await window.llonebot.getConfig(); for (let message of messages) { let onebot_message_data: any = { self: { @@ -141,6 +144,9 @@ async function handleNewMessage(messages: MessageElement[]) { raw_message: "", font: 14 } + if (debug) { + onebot_message_data.raw = JSON.parse(JSON.stringify(message)) + } if (message.raw.chatType == ChatType.group) { let group_id = message.peer.uid let group = (await getGroup(group_id))! @@ -181,10 +187,11 @@ async function handleNewMessage(messages: MessageElement[]) { data: {}, type: "unknown" } - if (element.textElement?.atType == AtType.atUser) { + if (element.textElement && element.textElement?.atType !== AtType.notAt) { message_data["type"] = "at" - if (element.textElement.atUid != "0") { - message_data["data"]["mention"] = element.textElement.atUid + if (element.textElement.atType == AtType.atAll) { + message_data["data"]["mention"] = "all" + message_data["data"]["qq"] = "all" } else { let uid = element.textElement.atNtUid let atMember = await getGroupMember(message.peer.uid, uid) @@ -198,24 +205,51 @@ async function handleNewMessage(messages: MessageElement[]) { message_data["type"] = "image" message_data["data"]["file_id"] = element.picElement.fileUuid message_data["data"]["path"] = element.picElement.sourcePath - let startS = "file://" - if (!element.picElement.sourcePath.startsWith("/")) { - startS += "/" - } - // todo: 转成base64 - message_data["data"]["file"] = startS + element.picElement.sourcePath + message_data["data"]["file"] = element.picElement.sourcePath } else if (element.replyElement) { message_data["type"] = "reply" message_data["data"]["id"] = msgHistory.find(msg => msg.raw.msgSeq == element.replyElement.replayMsgSeq)?.raw.msgId + } else if (element.pttElement) { + message_data["type"] = MessageType.voice; + message_data["data"]["file"] = element.pttElement.filePath + message_data["data"]["file_id"] = element.pttElement.fileUuid + // console.log("收到语音消息", message.raw.msgId, message.peer, element.pttElement) + // window.LLAPI.Ptt2Text(message.raw.msgId, message.peer, messages).then(text => { + // console.log("语音转文字结果", text); + // }).catch(err => { + // console.log("语音转文字失败", err); + // }) + } else if (element.arkElement) { + message_data["type"] = MessageType.json; + message_data["data"]["data"] = element.arkElement.bytesData; + } + if (message_data.data.file) { + let filePath: string = message_data.data.file; + message_data.data.file = "file://" + filePath + if (enableBase64) { + // filePath = filePath.replace("\\Ori\\", "\\Thumb\\") + let {err, data} = await window.llonebot.file2base64(filePath); + if (err) { + console.log("文件转base64失败", err) + } else { + message_data.data.file = "base64://" + data + } + } + } + if (message_data.type !== "unknown"){ + onebot_message_data.message.push(message_data); } - 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); + if (!reportSelfMessage && onebot_message_data["user_id"] == self_qq){ + console.log("开启了不上传自己发送的消息,进行拦截 ", onebot_message_data); + } else { + console.log("发送上传消息给ipc main", onebot_message_data); + window.llonebot.postData(onebot_message_data); + } } } @@ -285,11 +319,18 @@ async function listenSendMessage(postData: PostDataSendMsg) { message.type = "text" message.atType = AtType.atUser let atUid = message.data?.qq || message.atUid - let group = await getGroup(postData.params.group_id) - let atMember = group.members.find(member => member.uin == atUid) - message.atNtUid = atMember.uid - message.atUid = atUid - message.content = `@${atMember.cardName || atMember.nick}` + if (atUid == "all") { + message.atType = AtType.atAll + atUid = "0"; + message.content = `@全体成员` + } + else { + let group = await getGroup(postData.params.group_id) + let atMember = group.members.find(member => member.uin == atUid) + message.atNtUid = atMember.uid + message.atUid = atUid + message.content = `@${atMember.cardName || atMember.nick}` + } } else if (message.type == "text") { message.content = message.data?.text || message.content } else if (message.type == "image" || message.type == "voice" || message.type == "record") { @@ -432,11 +473,10 @@ function onLoad() { getGroups().then(() => { getGroupsMembers(groups).then(() => { }); - }) + }); } window.LLAPI.on("new-messages", onNewMessages); window.LLAPI.on("new-send-messages", onNewMessages); - window.llonebot.log("llonebot render start"); window.llonebot.startExpress(); @@ -444,6 +484,7 @@ function onLoad() { listenSendMessage(postData).then().catch(err => console.log("listenSendMessage err", err)) }) window.llonebot.listenRecallMessage((arg: { message_id: string }) => { + // console.log("listenRecallMessage", arg) recallMessage(arg.message_id) }) window.llonebot.log("llonebot loaded"); @@ -535,7 +576,7 @@ function onLoad() { // 打开设置界面时触发 async function onSettingWindowCreated(view: Element) { window.llonebot.log("setting window created"); - const {port, hosts} = await window.llonebot.getConfig() + let config = await window.llonebot.getConfig() function creatHostEleStr(host: string) { let eleStr = ` @@ -545,22 +586,23 @@ async function onSettingWindowCreated(view: Element) { style="width:60%;padding: 5px" placeholder="如果localhost上报失败试试局域网ip"/> + ` return eleStr } let hostsEleStr = "" - for (const host of hosts) { + for (const host of config.hosts) { hostsEleStr += creatHostEleStr(host); } let html = ` -
+
- + 监听端口 - +
@@ -571,8 +613,51 @@ async function onSettingWindowCreated(view: Element) { + + +
+
上报文件进行base64编码
+
不开启时,上报文件将以本地路径形式发送
+
+ +
+ +
+
debug模式
+
开启后上报消息添加raw字段附带原始消息
+
+ +
+ +
+
上报自身消息
+
开启后上报自己发出的消息
+
+ +
+ +
+
日志
+
日志目录:${window.LiteLoader.plugins["LLOneBot"].path.data}
+
+ +
+
+ ` const parser = new DOMParser(); @@ -589,6 +674,25 @@ async function onSettingWindowCreated(view: Element) { doc.getElementById("addHost").addEventListener("click", () => addHostEle()) + function switchClick(eleId: string, configKey: string) { + doc.getElementById(eleId)?.addEventListener("click", (e) => { + const switchEle = e.target as HTMLInputElement + if (config[configKey]) { + config[configKey] = false + switchEle.removeAttribute("is-active") + } else { + config[configKey] = true + switchEle.setAttribute("is-active", "") + } + window.llonebot.setConfig(config) + }) + } + + switchClick("debug", "debug"); + switchClick("switchBase64", "enableBase64"); + switchClick("reportSelfMessage", "reportSelfMessage"); + switchClick("log", "log"); + doc.getElementById("save")?.addEventListener("click", () => { const portEle: HTMLInputElement = document.getElementById("port") as HTMLInputElement @@ -603,12 +707,13 @@ async function onSettingWindowCreated(view: Element) { hosts.push(hostEle.value); } } - window.llonebot.setConfig({ - port: parseInt(port), - hosts: hosts - }) + config.port = parseInt(port); + config.hosts = hosts; + window.llonebot.setConfig(config); alert("保存成功"); }) + + doc.body.childNodes.forEach(node => { view.appendChild(node); }); @@ -616,8 +721,8 @@ async function onSettingWindowCreated(view: Element) { } -setTimeout(onLoad, 5000); +setTimeout(onLoad, 5000) export { onSettingWindowCreated -} \ No newline at end of file +}