From ef4443d0800758517891436b488754ec2f2ea33e Mon Sep 17 00:00:00 2001
From: linyuchen <lin.yu.chen@hotmail.com>
Date: Fri, 16 Feb 2024 00:47:04 +0800
Subject: [PATCH 01/12] feat: Websocket Server feat: change port not need
 restart

---
 manifest.json          |   2 +-
 package-lock.json      |  34 ++++++++-
 package.json           |   4 +-
 src/common/config.ts   |  17 +++--
 src/common/types.ts    |   1 +
 src/main/main.ts       |  17 ++++-
 src/onebot11/server.ts | 166 +++++++++++++++++++++--------------------
 src/onebot11/utils.ts  |  39 +++++++++-
 src/renderer.ts        |  13 +++-
 9 files changed, 195 insertions(+), 98 deletions(-)

diff --git a/manifest.json b/manifest.json
index 3574dc6..37c29ac 100644
--- a/manifest.json
+++ b/manifest.json
@@ -4,7 +4,7 @@
     "name": "LLOneBot",
     "slug": "LLOneBot",
     "description": "LiteLoaderQQNT的OneBotApi",
-    "version": "3.0.8",
+    "version": "3.1.0",
     "thumbnail": "./icon.png",
     "authors": [{
         "name": "linyuchen",
diff --git a/package-lock.json b/package-lock.json
index 19da495..90e7cca 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,11 +18,13 @@
         "@babel/preset-env": "^7.23.2",
         "@types/express": "^4.17.20",
         "@types/uuid": "^9.0.8",
+        "@types/ws": "^8.5.10",
         "babel-loader": "^9.1.3",
         "ts-loader": "^9.5.0",
         "typescript": "^5.2.2",
         "webpack": "^5.89.0",
-        "webpack-cli": "^5.1.4"
+        "webpack-cli": "^5.1.4",
+        "ws": "^8.16.0"
       }
     },
     "node_modules/@ampproject/remapping": {
@@ -2199,6 +2201,15 @@
       "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==",
       "dev": true
     },
+    "node_modules/@types/ws": {
+      "version": "8.5.10",
+      "resolved": "https://mirrors.cloud.tencent.com/npm/@types/ws/-/ws-8.5.10.tgz",
+      "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==",
+      "dev": true,
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
     "node_modules/@webassemblyjs/ast": {
       "version": "1.11.6",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz",
@@ -4691,6 +4702,27 @@
       "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==",
       "dev": true
     },
+    "node_modules/ws": {
+      "version": "8.16.0",
+      "resolved": "https://mirrors.cloud.tencent.com/npm/ws/-/ws-8.16.0.tgz",
+      "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": ">=5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/yallist": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
diff --git a/package.json b/package.json
index 1ec8b58..fda21bd 100644
--- a/package.json
+++ b/package.json
@@ -26,10 +26,12 @@
     "@babel/preset-env": "^7.23.2",
     "@types/express": "^4.17.20",
     "@types/uuid": "^9.0.8",
+    "@types/ws": "^8.5.10",
     "babel-loader": "^9.1.3",
     "ts-loader": "^9.5.0",
     "typescript": "^5.2.2",
     "webpack": "^5.89.0",
-    "webpack-cli": "^5.1.4"
+    "webpack-cli": "^5.1.4",
+    "ws": "^8.16.0"
   }
 }
diff --git a/src/common/config.ts b/src/common/config.ts
index 44b73d5..4fafec8 100644
--- a/src/common/config.ts
+++ b/src/common/config.ts
@@ -1,28 +1,31 @@
-import {Config} from "./types";
+import { Config } from "./types";
 
 const fs = require("fs")
 
-export class ConfigUtil{
+export class ConfigUtil {
     configPath: string;
 
     constructor(configPath: string) {
         this.configPath = configPath;
     }
 
-    getConfig(): Config{
+    getConfig(): Config {
         if (!fs.existsSync(this.configPath)) {
-            return {port:3000, hosts: ["http://192.168.1.2:5000/"]}
+            return {port: 3000, hosts: ["http://192.168.1.2:5000/"], wsPort: 3001}
         } else {
             const data = fs.readFileSync(this.configPath, "utf-8");
-            let jsonData =JSON.parse(data);
-            if (!jsonData.hosts){
+            let jsonData = JSON.parse(data);
+            if (!jsonData.hosts) {
                 jsonData.hosts = []
             }
+            if (!jsonData.wsPort){
+                jsonData.wsPort = 3001
+            }
             return jsonData;
         }
     }
 
-    setConfig(config: Config){
+    setConfig(config: Config) {
         fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2), "utf-8")
     }
 }
diff --git a/src/common/types.ts b/src/common/types.ts
index 5b892ab..1583420 100644
--- a/src/common/types.ts
+++ b/src/common/types.ts
@@ -1,5 +1,6 @@
 export interface Config {
     port: number
+    wsPort: number
     hosts: string[]
     enableBase64?: boolean
     debug?: boolean
diff --git a/src/main/main.ts b/src/main/main.ts
index 43b5400..99ab2a0 100644
--- a/src/main/main.ts
+++ b/src/main/main.ts
@@ -10,7 +10,7 @@ import {
     CHANNEL_LOG,
     CHANNEL_SET_CONFIG,
 } from "../common/channels";
-import { postMsg, startExpress } from "../onebot11/server";
+import { postMsg, startHTTPServer, startWSServer } from "../onebot11/server";
 import { CONFIG_DIR, getConfigUtil, log } from "../common/utils";
 import { addHistoryMsg, msgHistory, selfInfo } from "../common/data";
 import { hookNTQQApiReceive, ReceiveCmd, registerReceiveHook } from "../ntqqapi/hook";
@@ -37,7 +37,14 @@ function onLoad() {
         return getConfigUtil().getConfig()
     })
     ipcMain.on(CHANNEL_SET_CONFIG, (event: any, arg: Config) => {
+        let oldConfig = getConfigUtil().getConfig();
         getConfigUtil().setConfig(arg)
+        if (arg.port != oldConfig.port){
+            startHTTPServer(arg.port)
+        }
+        if (arg.wsPort != oldConfig.wsPort){
+            startWSServer(arg.wsPort)
+        }
     })
 
     ipcMain.on(CHANNEL_LOG, (event: any, arg: any) => {
@@ -67,7 +74,6 @@ function onLoad() {
 
 
     function start() {
-        log("llonebot start")
         registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.NEW_MSG, (payload) => {
             try {
                 // log("received msg length", payload.msgList.length);
@@ -90,7 +96,10 @@ function onLoad() {
             }
         })
         NTQQApi.getGroups(true).then()
-        startExpress(getConfigUtil().getConfig().port)
+        const config = getConfigUtil().getConfig()
+        startHTTPServer(config.port)
+        startWSServer(config.wsPort)
+        log("LLOneBot start")
     }
 
     const init = async () => {
@@ -130,7 +139,7 @@ function onBrowserWindowCreated(window: BrowserWindow) {
     try {
         hookNTQQApiReceive(window);
     } catch (e) {
-        log("llonebot hook error: ", e.toString())
+        log("LLOneBot hook error: ", e.toString())
     }
 }
 
diff --git a/src/onebot11/server.ts b/src/onebot11/server.ts
index 95b67f6..c499e98 100644
--- a/src/onebot11/server.ts
+++ b/src/onebot11/server.ts
@@ -1,73 +1,22 @@
-import { getConfigUtil, log } from "../common/utils";
-
-const express = require("express");
+import * as http from "http";
+import * as websocket from "ws";
+import express from "express";
 import { Request } from 'express';
 import { Response } from 'express';
-
-const JSONbig = require('json-bigint')({ storeAsString: true });
+import { getConfigUtil, log } from "../common/utils";
 import { selfInfo } from "../common/data";
 import { OB11Message, OB11Return, OB11MessageData } from './types';
 import { actionHandlers } from "./actions";
+import { OB11Response } from "./actions/utils";
+import { ActionName } from "./actions/types";
+import BaseAction from "./actions/BaseAction";
 
+let wsServer: websocket.Server = null;
 
-// @SiberianHusky 2021-08-15
-function checkSendMessage(sendMsgList: OB11MessageData[]) {
-    function checkUri(uri: string): boolean {
-        const pattern = /^(file:\/\/|http:\/\/|https:\/\/|base64:\/\/)/;
-        return pattern.test(uri);
-    }
-
-    for (let msg of sendMsgList) {
-        if (msg["type"] && msg["data"]) {
-            let type = msg["type"];
-            let data = msg["data"];
-            if (type === "text" && !data["text"]) {
-                return 400;
-            } else if (["image", "voice", "record"].includes(type)) {
-                if (!data["file"]) {
-                    return 400;
-                } else {
-                    if (checkUri(data["file"])) {
-                        return 200;
-                    } else {
-                        return 400;
-                    }
-                }
-
-            } else if (type === "at" && !data["qq"]) {
-                return 400;
-            } else if (type === "reply" && !data["id"]) {
-                return 400;
-            }
-        } else {
-            return 400
-        }
-    }
-    return 200;
-}
-
-// ==end==
-
-
-class OB11Response {
-    static res<T>(data: T, status: number = 0, message: string = ""): OB11Return<T> {
-        return {
-            status: status,
-            retcode: status,
-            data: data,
-            message: message
-        }
-    }
-    static ok<T>(data: T) {
-        return OB11Response.res<T>(data)
-    }
-    static error(err: string) {
-        return OB11Response.res(null, -1, err)
-    }
-}
-
+const JSONbig = require('json-bigint')({storeAsString: true});
 const expressAPP = express();
-expressAPP.use(express.urlencoded({ extended: true, limit: "500mb" }));
+let httpServer: http.Server = null;
+expressAPP.use(express.urlencoded({extended: true, limit: "500mb"}));
 
 expressAPP.use((req, res, next) => {
     let data = '';
@@ -86,27 +35,79 @@ expressAPP.use((req, res, next) => {
         next();
     });
 });
-// expressAPP.use(express.json({
-//     limit: '500mb',
-//     verify: (req: any, res: any, buf: any, encoding: any) => {
-//         req.rawBody = buf;
-//     }
-// }));
 
-export function startExpress(port: number) {
 
+export function startHTTPServer(port: number) {
+    if (httpServer) {
+        httpServer.close();
+    }
     expressAPP.get('/', (req: Request, res: Response) => {
-        res.send('llonebot已启动');
+        res.send('LLOneBot已启动');
     })
 
-    expressAPP.listen(port, "0.0.0.0", () => {
-        console.log(`llonebot started 0.0.0.0:${port}`);
+    httpServer = expressAPP.listen(port, "0.0.0.0", () => {
+        console.log(`LLOneBot http server started 0.0.0.0:${port}`);
     });
 }
 
+let wsEventClients: websocket.WebSocket[] = [];
+type RouterHandler = (payload: any) => Promise<OB11Return<any>>
+let routers: Record<string, RouterHandler> = {};
+
+function wsReply(wsClient: websocket.WebSocket, data: OB11Return<any> | OB11Message) {
+    try {
+        wsClient.send(JSON.stringify(data))
+    } catch (e) {
+        log("websocket 回复失败", e)
+    }
+}
+
+export function startWSServer(port: number) {
+    if (wsServer) {
+        wsServer.close((err)=>{
+            log("ws server close failed!", err)
+        })
+    }
+    wsServer = new websocket.Server({port})
+    wsServer.on("connection", (ws, req) => {
+        const url = req.url;
+        ws.send('Welcome to the LLOneBot WebSocket server! url:' + url);
+
+        if (url == "/api" || url == "/api/") {
+            ws.on("message", async (msg) => {
+
+                let receiveData: { action: ActionName, params: any } = {action: null, params: {}}
+                log("收到ws消息", msg.toString())
+                try {
+                    receiveData = JSON.parse(msg.toString())
+                } catch (e) {
+                    return wsReply(ws, OB11Response.error("json解析失败,请检查数据格式"))
+                }
+                const handle: RouterHandler | undefined = routers[receiveData.action]
+                if (!handle) {
+                    return wsReply(ws, OB11Response.error("不支持的api " + receiveData.action))
+                }
+                try {
+                    const handleResult = await handle(receiveData.params)
+                    wsReply(ws, handleResult)
+                } catch (e) {
+                    wsReply(ws, OB11Response.error(`api处理出错:${e}`))
+                }
+            })
+        } else if (url == "/event" || url == "/event/") {
+            log("event上报ws客户端已连接")
+            wsEventClients.push(ws)
+            ws.on("close", () => {
+                log("event上报ws客户端已断开")
+                wsEventClients = wsEventClients.filter((c) => c != ws)
+            })
+        }
+    })
+}
+
 
 export function postMsg(msg: OB11Message) {
-    const { reportSelfMessage } = getConfigUtil().getConfig()
+    const {reportSelfMessage} = getConfigUtil().getConfig()
     if (!reportSelfMessage) {
         if (msg.user_id == selfInfo.uin) {
             return
@@ -121,27 +122,32 @@ export function postMsg(msg: OB11Message) {
             },
             body: JSON.stringify(msg)
         }).then((res: any) => {
-            log(`新消息事件上报成功: ${host} ` + JSON.stringify(msg));
+            log(`新消息事件HTTP上报成功: ${host} ` + JSON.stringify(msg));
         }, (err: any) => {
-            log(`新消息事件上报失败: ${host} ` + err + JSON.stringify(msg));
+            log(`新消息事件HTTP上报失败: ${host} ` + err + JSON.stringify(msg));
         });
     }
+    for (const wsClient of wsEventClients) {
+        log("新消息事件ws上报", msg)
+        new Promise((resolve, reject) => {
+            wsReply(wsClient, msg);
+        }).then();
+    }
 }
 
-let routers: Record<string, (payload: any) => Promise<OB11Return<any>>> = {};
 
 function registerRouter(action: string, handle: (payload: any) => Promise<any>) {
     let url = action.toString()
     if (!action.startsWith("/")) {
         url = "/" + action
     }
+
     async function _handle(res: Response, payload: any) {
         log("receive post data", url, payload)
         try {
             const result = await handle(payload)
             res.send(result)
-        }
-        catch (e) {
+        } catch (e) {
             log(e.stack);
             res.send(OB11Response.error(e.stack.toString()))
         }
@@ -153,9 +159,9 @@ function registerRouter(action: string, handle: (payload: any) => Promise<any>)
     expressAPP.get(url, (req: Request, res: Response) => {
         _handle(res, req.query as any).then()
     });
-    routers[url] = handle
+    routers[action] = handle
 }
 
-for (const action  of actionHandlers) {
+for (const action of actionHandlers) {
     registerRouter(action.actionName, (payload) => action.handle(payload))
 }
\ No newline at end of file
diff --git a/src/onebot11/utils.ts b/src/onebot11/utils.ts
index d1cdfdc..e6e1278 100644
--- a/src/onebot11/utils.ts
+++ b/src/onebot11/utils.ts
@@ -1,6 +1,7 @@
 import { CONFIG_DIR, isGIF } from "../common/utils";
 import * as path from 'path';
 import { NTQQApi } from '../ntqqapi/ntcall';
+import { OB11MessageData } from "./types";
 const fs = require("fs").promises;
 
 export async function uri2local(fileName: string, uri: string){
@@ -59,4 +60,40 @@ export async function uri2local(fileName: string, uri: string){
     res.success = true
     res.path = filePath
     return res
-}
\ No newline at end of file
+}
+
+
+function checkSendMessage(sendMsgList: OB11MessageData[]) {
+    function checkUri(uri: string): boolean {
+        const pattern = /^(file:\/\/|http:\/\/|https:\/\/|base64:\/\/)/;
+        return pattern.test(uri);
+    }
+
+    for (let msg of sendMsgList) {
+        if (msg["type"] && msg["data"]) {
+            let type = msg["type"];
+            let data = msg["data"];
+            if (type === "text" && !data["text"]) {
+                return 400;
+            } else if (["image", "voice", "record"].includes(type)) {
+                if (!data["file"]) {
+                    return 400;
+                } else {
+                    if (checkUri(data["file"])) {
+                        return 200;
+                    } else {
+                        return 400;
+                    }
+                }
+
+            } else if (type === "at" && !data["qq"]) {
+                return 400;
+            } else if (type === "reply" && !data["id"]) {
+                return 400;
+            }
+        } else {
+            return 400
+        }
+    }
+    return 200;
+}
diff --git a/src/renderer.ts b/src/renderer.ts
index 83651f1..abe1b2c 100644
--- a/src/renderer.ts
+++ b/src/renderer.ts
@@ -29,16 +29,20 @@ async function onSettingWindowCreated(view: Element) {
             <setting-panel>
                 <setting-list class="wrap">
                     <setting-item class="vertical-list-item" data-direction="row">
-                        <setting-text>监听端口</setting-text>
+                        <setting-text>HTTP监听端口</setting-text>
                         <input id="port" type="number" value="${config.port}"/>
                     </setting-item>
+                    <setting-item class="vertical-list-item" data-direction="row">
+                        <setting-text>正向ws监听端口</setting-text>
+                        <input id="wsPort" type="number" value="${config.wsPort}"/>
+                    </setting-item>
                     <div>
-                        <button id="addHost" class="q-button">添加上报地址</button>
+                        <button id="addHost" class="q-button">添加HTTP上报地址</button>
                     </div>
                     <div id="hostItems">
                         ${hostsEleStr}
                     </div>
-                    <button id="save" class="q-button">保存(监听端口重启QQ后生效)</button>
+                    <button id="save" class="q-button">保存</button>
                 </setting-list>
             </setting-panel>
             <setting-panel>
@@ -124,11 +128,13 @@ async function onSettingWindowCreated(view: Element) {
     doc.getElementById("save")?.addEventListener("click",
         () => {
             const portEle: HTMLInputElement = document.getElementById("port") as HTMLInputElement
+            const wsPortEle: HTMLInputElement = document.getElementById("wsPort") as HTMLInputElement
             const hostEles: HTMLCollectionOf<HTMLInputElement> = document.getElementsByClassName("host") as HTMLCollectionOf<HTMLInputElement>;
             // const port = doc.querySelector("input[type=number]")?.value
             // const host = doc.querySelector("input[type=text]")?.value
             // 获取端口和host
             const port = portEle.value
+            const wsPort = wsPortEle.value
             let hosts: string[] = [];
             for (const hostEle of hostEles) {
                 if (hostEle.value) {
@@ -136,6 +142,7 @@ async function onSettingWindowCreated(view: Element) {
                 }
             }
             config.port = parseInt(port);
+            config.wsPort = parseInt(wsPort);
             config.hosts = hosts;
             window.llonebot.setConfig(config);
             alert("保存成功");

From 97200f427dd480fe54296fe9d19a0d60291bcdfc Mon Sep 17 00:00:00 2001
From: linyuchen <lin.yu.chen@hotmail.com>
Date: Fri, 16 Feb 2024 00:52:19 +0800
Subject: [PATCH 02/12] docs: update readme

---
 README.md              | 8 ++++++--
 src/onebot11/server.ts | 5 +++--
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/README.md b/README.md
index 810f7d9..20bc524 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,11 @@ LiteLoaderQQNT的OneBot11协议插件
 
 ## 支持的API
 
-目前只支持http协议,不支持websocket,事件上报也是http协议
+目前支持的协议
+- [x] http调用api
+- [x] http事件上报
+- [x] 正向websocket
+- [ ] 反向websocket
 
 主要功能:
 - [x] 发送好友消息
@@ -98,9 +102,9 @@ LiteLoaderQQNT的OneBot11协议插件
 
 ## TODO
 - [x] 重构摆脱LLAPI,目前调用LLAPI只能在renderer进程调用,需重构成在main进程调用
+- [x] 支持正向websocket
 - [ ] 转发消息记录 
 - [ ] 好友点赞api
-- [ ] 支持websocket,等个有缘人提PR实现
 
 ## onebot11文档
 <https://11.onebot.dev/>
diff --git a/src/onebot11/server.ts b/src/onebot11/server.ts
index c499e98..0a0eac7 100644
--- a/src/onebot11/server.ts
+++ b/src/onebot11/server.ts
@@ -73,7 +73,7 @@ export function startWSServer(port: number) {
         const url = req.url;
         ws.send('Welcome to the LLOneBot WebSocket server! url:' + url);
 
-        if (url == "/api" || url == "/api/") {
+        if (url == "/api" || url == "/api/" || url == "/") {
             ws.on("message", async (msg) => {
 
                 let receiveData: { action: ActionName, params: any } = {action: null, params: {}}
@@ -94,7 +94,8 @@ export function startWSServer(port: number) {
                     wsReply(ws, OB11Response.error(`api处理出错:${e}`))
                 }
             })
-        } else if (url == "/event" || url == "/event/") {
+        }
+        if (url == "/event" || url == "/event/" || url == "/") {
             log("event上报ws客户端已连接")
             wsEventClients.push(ws)
             ws.on("close", () => {

From 0eeba1d29e9aeef92af4ce04658d3ba408f7f249 Mon Sep 17 00:00:00 2001
From: linyuchen <lin.yu.chen@hotmail.com>
Date: Fri, 16 Feb 2024 10:34:56 +0800
Subject: [PATCH 03/12] fix: remove ws welcome

---
 src/onebot11/server.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/onebot11/server.ts b/src/onebot11/server.ts
index 0a0eac7..199343b 100644
--- a/src/onebot11/server.ts
+++ b/src/onebot11/server.ts
@@ -71,7 +71,7 @@ export function startWSServer(port: number) {
     wsServer = new websocket.Server({port})
     wsServer.on("connection", (ws, req) => {
         const url = req.url;
-        ws.send('Welcome to the LLOneBot WebSocket server! url:' + url);
+        // ws.send('Welcome to the LLOneBot WebSocket server! url:' + url);
 
         if (url == "/api" || url == "/api/" || url == "/") {
             ws.on("message", async (msg) => {

From 963aad1510cecba8336fecd7a1eb8f4736415941 Mon Sep 17 00:00:00 2001
From: linyuchen <lin.yu.chen@hotmail.com>
Date: Fri, 16 Feb 2024 15:54:07 +0800
Subject: [PATCH 04/12] fix: some id(int and string) compatibility

---
 manifest.json               |  2 +-
 src/main/main.ts            |  2 +-
 src/onebot11/constructor.ts | 18 +++++++++---------
 src/onebot11/server.ts      |  2 +-
 src/onebot11/types.ts       | 16 ++++++++--------
 5 files changed, 20 insertions(+), 20 deletions(-)

diff --git a/manifest.json b/manifest.json
index 37c29ac..b1ac6bf 100644
--- a/manifest.json
+++ b/manifest.json
@@ -4,7 +4,7 @@
     "name": "LLOneBot",
     "slug": "LLOneBot",
     "description": "LiteLoaderQQNT的OneBotApi",
-    "version": "3.1.0",
+    "version": "3.1.2",
     "thumbnail": "./icon.png",
     "authors": [{
         "name": "linyuchen",
diff --git a/src/main/main.ts b/src/main/main.ts
index 99ab2a0..7e56efd 100644
--- a/src/main/main.ts
+++ b/src/main/main.ts
@@ -63,7 +63,7 @@ function onLoad() {
                 if (debug) {
                     msg.raw = message;
                 }
-                if (msg.user_id == selfInfo.uin && !reportSelfMessage) {
+                if (msg.user_id.toString() == selfInfo.uin && !reportSelfMessage) {
                     return
                 }
                 postMsg(msg);
diff --git a/src/onebot11/constructor.ts b/src/onebot11/constructor.ts
index bbe7c92..d4e7d3f 100644
--- a/src/onebot11/constructor.ts
+++ b/src/onebot11/constructor.ts
@@ -18,14 +18,14 @@ export class OB11Constructor {
         const {enableBase64} = getConfigUtil().getConfig()
         const message_type = msg.chatType == ChatType.group ? "group" : "private";
         const resMsg: OB11Message = {
-            self_id: selfInfo.uin,
-            user_id: msg.senderUin,
+            self_id: parseInt(selfInfo.uin),
+            user_id: parseInt(msg.senderUin),
             time: parseInt(msg.msgTime) || 0,
             message_id: msg.msgShortId,
             real_id: msg.msgId,
             message_type: msg.chatType == ChatType.group ? "group" : "private",
             sender: {
-                user_id: msg.senderUin,
+                user_id: parseInt(msg.senderUin),
                 nickname: msg.sendNickName,
                 card: msg.sendMemberName || "",
             },
@@ -37,7 +37,7 @@ export class OB11Constructor {
         }
         if (msg.chatType == ChatType.group) {
             resMsg.sub_type = "normal"
-            resMsg.group_id = msg.peerUin
+            resMsg.group_id = parseInt(msg.peerUin)
             const member = await getGroupMember(msg.peerUin, msg.senderUin);
             if (member) {
                 resMsg.sender.role = OB11Constructor.groupMemberRole(member.role);
@@ -135,7 +135,7 @@ export class OB11Constructor {
 
     static friend(friend: User): OB11User {
         return {
-            user_id: friend.uin,
+            user_id: parseInt(friend.uin),
             nickname: friend.nick,
             remark: friend.remark
         }
@@ -144,7 +144,7 @@ export class OB11Constructor {
 
     static selfInfo(selfInfo: SelfInfo): OB11User {
         return {
-            user_id: selfInfo.uin,
+            user_id: parseInt(selfInfo.uin),
             nickname: selfInfo.nick
         }
     }
@@ -163,8 +163,8 @@ export class OB11Constructor {
 
     static groupMember(group_id: string, member: GroupMember): OB11GroupMember {
         return {
-            group_id,
-            user_id: member.uin,
+            group_id: parseInt(group_id),
+            user_id: parseInt(member.uin),
             nickname: member.nick,
             card: member.cardName
         }
@@ -177,7 +177,7 @@ export class OB11Constructor {
 
     static group(group: Group): OB11Group {
         return {
-            group_id: group.groupCode,
+            group_id: parseInt(group.groupCode),
             group_name: group.groupName
         }
     }
diff --git a/src/onebot11/server.ts b/src/onebot11/server.ts
index 199343b..9c0e93d 100644
--- a/src/onebot11/server.ts
+++ b/src/onebot11/server.ts
@@ -110,7 +110,7 @@ export function startWSServer(port: number) {
 export function postMsg(msg: OB11Message) {
     const {reportSelfMessage} = getConfigUtil().getConfig()
     if (!reportSelfMessage) {
-        if (msg.user_id == selfInfo.uin) {
+        if (msg.user_id.toString() == selfInfo.uin) {
             return
         }
     }
diff --git a/src/onebot11/types.ts b/src/onebot11/types.ts
index f07552f..ee4f4aa 100644
--- a/src/onebot11/types.ts
+++ b/src/onebot11/types.ts
@@ -2,7 +2,7 @@ import { AtType } from "../ntqqapi/types";
 import { RawMessage } from "../ntqqapi/types";
 
 export interface OB11User{
-    user_id: string;
+    user_id: number;
     nickname: string;
     remark?: string
 }
@@ -20,8 +20,8 @@ export enum OB11GroupMemberRole{
 }
 
 export interface OB11GroupMember {
-    group_id: string
-    user_id: string
+    group_id: number
+    user_id: number
     nickname: string
     card?: string
     sex?: OB11UserSex
@@ -34,14 +34,14 @@ export interface OB11GroupMember {
 }
 
 export interface OB11Group{
-    group_id: string
+    group_id: number
     group_name: string
     member_count?: number
     max_member_count?: number
 }
 
 interface OB11Sender {
-    user_id: string,
+    user_id: number,
     nickname: string,
     sex?: OB11UserSex,
     age?: number,
@@ -56,12 +56,12 @@ export enum OB11MessageType {
 }
 
 export interface OB11Message {
-    self_id?: string,
+    self_id?: number,
     time: number,
     message_id: number,
     real_id: string,
-    user_id: string,
-    group_id?: string,
+    user_id: number,
+    group_id?: number,
     message_type: "private" | "group",
     sub_type?: "friend" | "group" | "normal",
     sender: OB11Sender,

From 4f9682289cd4e5db0abc99a25bf28dd1e41f4dfd Mon Sep 17 00:00:00 2001
From: linyuchen <lin.yu.chen@hotmail.com>
Date: Fri, 16 Feb 2024 21:32:37 +0800
Subject: [PATCH 05/12] feat: api /get_version_info feat: api /can_send_image
 feat: api /can_send_record feat: ws heart & lifecycle

---
 README.md                              |   3 +
 manifest.json                          |   2 +-
 src/common/config.ts                   |  23 +++++-
 src/common/data.ts                     |   5 +-
 src/common/types.ts                    |   1 +
 src/main/main.ts                       |   6 +-
 src/onebot11/actions/CanSendImage.ts   |  10 +++
 src/onebot11/actions/CanSendRecord.ts  |  16 ++++
 src/onebot11/actions/GetStatus.ts      |  12 +++
 src/onebot11/actions/GetVersionInfo.ts |  15 ++++
 src/onebot11/actions/index.ts          |   8 +-
 src/onebot11/actions/types.ts          |   8 +-
 src/onebot11/actions/utils.ts          |   9 ++-
 src/onebot11/constructor.ts            |  29 ++++++-
 src/onebot11/server.ts                 | 100 ++++++++++++++++++++++---
 src/onebot11/types.ts                  |  33 +++++++-
 src/renderer.ts                        |  10 ++-
 17 files changed, 265 insertions(+), 25 deletions(-)
 create mode 100644 src/onebot11/actions/CanSendImage.ts
 create mode 100644 src/onebot11/actions/CanSendRecord.ts
 create mode 100644 src/onebot11/actions/GetStatus.ts
 create mode 100644 src/onebot11/actions/GetVersionInfo.ts

diff --git a/README.md b/README.md
index 20bc524..81c6140 100644
--- a/README.md
+++ b/README.md
@@ -57,6 +57,9 @@ LiteLoaderQQNT的OneBot11协议插件
 - [x] get_group_member_info
 - [x] get_friend_list
 - [x] get_msg
+- [x] get_version_info
+- [x] can_send_image
+- [x] can_send_record
 
 ## 示例
 
diff --git a/manifest.json b/manifest.json
index b1ac6bf..31d8846 100644
--- a/manifest.json
+++ b/manifest.json
@@ -4,7 +4,7 @@
     "name": "LLOneBot",
     "slug": "LLOneBot",
     "description": "LiteLoaderQQNT的OneBotApi",
-    "version": "3.1.2",
+    "version": "3.2.0",
     "thumbnail": "./icon.png",
     "authors": [{
         "name": "linyuchen",
diff --git a/src/common/config.ts b/src/common/config.ts
index 4fafec8..a751771 100644
--- a/src/common/config.ts
+++ b/src/common/config.ts
@@ -10,17 +10,36 @@ export class ConfigUtil {
     }
 
     getConfig(): Config {
+        let defaultConfig: Config = {
+            port: 3000,
+            wsPort: 3001,
+            hosts: [],
+            token: "",
+            enableBase64: false,
+            debug: false,
+            log: false,
+            reportSelfMessage: false
+        }
         if (!fs.existsSync(this.configPath)) {
-            return {port: 3000, hosts: ["http://192.168.1.2:5000/"], wsPort: 3001}
+            return defaultConfig
         } else {
             const data = fs.readFileSync(this.configPath, "utf-8");
-            let jsonData = JSON.parse(data);
+            let jsonData: Config = defaultConfig;
+            try {
+                jsonData = JSON.parse(data)
+            }
+            catch (e){
+
+            }
             if (!jsonData.hosts) {
                 jsonData.hosts = []
             }
             if (!jsonData.wsPort){
                 jsonData.wsPort = 3001
             }
+            if (!jsonData.token){
+                jsonData.token = ""
+            }
             return jsonData;
         }
     }
diff --git a/src/common/data.ts b/src/common/data.ts
index bbd23e1..2af3461 100644
--- a/src/common/data.ts
+++ b/src/common/data.ts
@@ -86,4 +86,7 @@ export function getStrangerByUin(uin: string) {
             return uidMaps[key];
         }
     }
-}
\ No newline at end of file
+}
+
+export const version = "v3.2.0"
+export const heartInterval = 15000 // 毫秒
\ No newline at end of file
diff --git a/src/common/types.ts b/src/common/types.ts
index 1583420..bb98810 100644
--- a/src/common/types.ts
+++ b/src/common/types.ts
@@ -2,6 +2,7 @@ export interface Config {
     port: number
     wsPort: number
     hosts: string[]
+    token?: string
     enableBase64?: boolean
     debug?: boolean
     reportSelfMessage?: boolean
diff --git a/src/main/main.ts b/src/main/main.ts
index 7e56efd..945c14b 100644
--- a/src/main/main.ts
+++ b/src/main/main.ts
@@ -10,7 +10,7 @@ import {
     CHANNEL_LOG,
     CHANNEL_SET_CONFIG,
 } from "../common/channels";
-import { postMsg, startHTTPServer, startWSServer } from "../onebot11/server";
+import { postMsg, setToken, startHTTPServer, startWSServer } from "../onebot11/server";
 import { CONFIG_DIR, getConfigUtil, log } from "../common/utils";
 import { addHistoryMsg, msgHistory, selfInfo } from "../common/data";
 import { hookNTQQApiReceive, ReceiveCmd, registerReceiveHook } from "../ntqqapi/hook";
@@ -45,6 +45,9 @@ function onLoad() {
         if (arg.wsPort != oldConfig.wsPort){
             startWSServer(arg.wsPort)
         }
+        if (arg.token != oldConfig.token){
+            setToken(arg.token);
+        }
     })
 
     ipcMain.on(CHANNEL_LOG, (event: any, arg: any) => {
@@ -99,6 +102,7 @@ function onLoad() {
         const config = getConfigUtil().getConfig()
         startHTTPServer(config.port)
         startWSServer(config.wsPort)
+        setToken(config.token)
         log("LLOneBot start")
     }
 
diff --git a/src/onebot11/actions/CanSendImage.ts b/src/onebot11/actions/CanSendImage.ts
new file mode 100644
index 0000000..f8c6b2c
--- /dev/null
+++ b/src/onebot11/actions/CanSendImage.ts
@@ -0,0 +1,10 @@
+import { ActionName } from "./types";
+import CanSendRecord from "./CanSendRecord";
+
+interface ReturnType{
+    yes: boolean
+}
+
+export default class CanSendImage extends CanSendRecord{
+    actionName = ActionName.CanSendImage
+}
\ No newline at end of file
diff --git a/src/onebot11/actions/CanSendRecord.ts b/src/onebot11/actions/CanSendRecord.ts
new file mode 100644
index 0000000..4d94d17
--- /dev/null
+++ b/src/onebot11/actions/CanSendRecord.ts
@@ -0,0 +1,16 @@
+import BaseAction from "./BaseAction";
+import { ActionName } from "./types";
+
+interface ReturnType{
+    yes: boolean
+}
+
+export default class CanSendRecord extends BaseAction<any, ReturnType>{
+    actionName = ActionName.CanSendRecord
+
+    protected async _handle(payload): Promise<ReturnType>{
+        return {
+            yes: true
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/onebot11/actions/GetStatus.ts b/src/onebot11/actions/GetStatus.ts
new file mode 100644
index 0000000..1e388af
--- /dev/null
+++ b/src/onebot11/actions/GetStatus.ts
@@ -0,0 +1,12 @@
+import BaseAction from "./BaseAction";
+import {OB11Status} from "../types";
+
+
+export default class GetStatus extends BaseAction<any, OB11Status> {
+    protected async _handle(payload: any): Promise<OB11Status> {
+        return {
+            online: null,
+            good: true
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/onebot11/actions/GetVersionInfo.ts b/src/onebot11/actions/GetVersionInfo.ts
new file mode 100644
index 0000000..5fadb22
--- /dev/null
+++ b/src/onebot11/actions/GetVersionInfo.ts
@@ -0,0 +1,15 @@
+import BaseAction from "./BaseAction";
+import { OB11Version } from "../types";
+import {version} from "../../common/data";
+import { ActionName } from "./types";
+
+export default class GetVersionInfo extends BaseAction<any, OB11Version>{
+    actionName = ActionName.GetVersionInfo
+    protected async _handle(payload: any): Promise<OB11Version> {
+        return {
+            app_name: "LLOneBot",
+            protocol_version: "v11",
+            app_version: version
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/onebot11/actions/index.ts b/src/onebot11/actions/index.ts
index 42fcda6..ae75b78 100644
--- a/src/onebot11/actions/index.ts
+++ b/src/onebot11/actions/index.ts
@@ -9,6 +9,9 @@ import SendGroupMsg from './SendGroupMsg'
 import SendPrivateMsg from './SendPrivateMsg'
 import SendMsg from './SendMsg'
 import DeleteMsg from "./DeleteMsg";
+import GetVersionInfo from "./GetVersionInfo";
+import CanSendRecord from "./CanSendRecord";
+import CanSendImage from "./CanSendImage";
 
 export const actionHandlers = [
     new GetMsg(),
@@ -16,5 +19,8 @@ export const actionHandlers = [
     new GetFriendList(),
     new GetGroupList(), new GetGroupInfo(), new GetGroupMemberList(), new GetGroupMemberInfo(),
     new SendGroupMsg(), new SendPrivateMsg(), new SendMsg(),
-    new DeleteMsg()
+    new DeleteMsg(),
+    new GetVersionInfo(),
+    new CanSendRecord(),
+    new CanSendImage()
 ]
\ No newline at end of file
diff --git a/src/onebot11/actions/types.ts b/src/onebot11/actions/types.ts
index fdfeeb5..39f0b25 100644
--- a/src/onebot11/actions/types.ts
+++ b/src/onebot11/actions/types.ts
@@ -1,3 +1,5 @@
+import GetVersionInfo from "./GetVersionInfo";
+
 export type BaseCheckResult = ValidCheckResult | InvalidCheckResult
 
 export interface ValidCheckResult {
@@ -22,5 +24,9 @@ export enum ActionName{
     SendMsg = "send_msg",
     SendGroupMsg = "send_group_msg",
     SendPrivateMsg = "send_private_msg",
-    DeleteMsg = "delete_msg"
+    DeleteMsg = "delete_msg",
+    GetVersionInfo = "get_version_info",
+    GetStatus = "get_status",
+    CanSendRecord = "can_send_record",
+    CanSendImage = "can_send_image",
 }
\ No newline at end of file
diff --git a/src/onebot11/actions/utils.ts b/src/onebot11/actions/utils.ts
index 8120d7f..9edfc90 100644
--- a/src/onebot11/actions/utils.ts
+++ b/src/onebot11/actions/utils.ts
@@ -1,18 +1,19 @@
 import { OB11Return } from '../types';
 
 export class OB11Response {
-    static res<T>(data: T, status: number = 0, message: string = ""): OB11Return<T> {
+    static res<T>(data: T, status: number = 0, message: string = "", echo=""): OB11Return<T> {
         return {
             status: status,
             retcode: status,
             data: data,
-            message: message
+            message: message,
+            echo,
         }
     }
     static ok<T>(data: T) {
         return OB11Response.res<T>(data)
     }
-    static error(err: string) {
-        return OB11Response.res(null, -1, err)
+    static error(err: string, status=-1) {
+        return OB11Response.res(null, status, err)
     }
 }
diff --git a/src/onebot11/constructor.ts b/src/onebot11/constructor.ts
index d4e7d3f..d8afb2e 100644
--- a/src/onebot11/constructor.ts
+++ b/src/onebot11/constructor.ts
@@ -4,10 +4,10 @@ import {
     OB11Message,
     OB11Group,
     OB11GroupMember,
-    OB11User
+    OB11User, OB11LifeCycleEvent, OB11HeartEvent
 } from "./types";
 import { AtType, ChatType, Group, GroupMember, IMAGE_HTTP_HOST, RawMessage, SelfInfo, User } from '../ntqqapi/types';
-import { getFriend, getGroupMember, getHistoryMsgBySeq, msgHistory, selfInfo } from '../common/data';
+import { getFriend, getGroupMember, getHistoryMsgBySeq, heartInterval, msgHistory, selfInfo } from '../common/data';
 import { file2base64, getConfigUtil, log } from "../common/utils";
 import { NTQQApi } from "../ntqqapi/ntcall";
 
@@ -41,6 +41,7 @@ export class OB11Constructor {
             const member = await getGroupMember(msg.peerUin, msg.senderUin);
             if (member) {
                 resMsg.sender.role = OB11Constructor.groupMemberRole(member.role);
+                resMsg.sender.nickname = member.nick
             }
         } else if (msg.chatType == ChatType.friend) {
             resMsg.sub_type = "friend"
@@ -185,4 +186,28 @@ export class OB11Constructor {
     static groups(groups: Group[]): OB11Group[] {
         return groups.map(OB11Constructor.group)
     }
+
+    static lifeCycleEvent(): OB11LifeCycleEvent {
+        return {
+            time: Math.floor(Date.now() / 1000),
+            self_id: parseInt(selfInfo.uin),
+            post_type: "meta_event",
+            meta_event_type: "lifecycle",
+            sub_type: "connect"
+        }
+    }
+
+    static heartEvent(): OB11HeartEvent {
+        return {
+            time: Math.floor(Date.now() / 1000),
+            self_id: parseInt(selfInfo.uin),
+            post_type: "meta_event",
+            meta_event_type: "heartbeat",
+            status: {
+                online: true,
+                good: true
+            },
+            interval: heartInterval
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/onebot11/server.ts b/src/onebot11/server.ts
index 9c0e93d..b69aab7 100644
--- a/src/onebot11/server.ts
+++ b/src/onebot11/server.ts
@@ -1,17 +1,20 @@
 import * as http from "http";
 import * as websocket from "ws";
+import urlParse from "url";
 import express from "express";
 import { Request } from 'express';
 import { Response } from 'express';
 import { getConfigUtil, log } from "../common/utils";
-import { selfInfo } from "../common/data";
-import { OB11Message, OB11Return, OB11MessageData } from './types';
+import { heartInterval, selfInfo } from "../common/data";
+import { OB11Message, OB11Return, OB11MessageData, OB11LifeCycleEvent, OB11MetaEvent } from './types';
 import { actionHandlers } from "./actions";
 import { OB11Response } from "./actions/utils";
 import { ActionName } from "./actions/types";
 import BaseAction from "./actions/BaseAction";
+import { OB11Constructor } from "./constructor";
 
 let wsServer: websocket.Server = null;
+let accessToken = ""
 
 const JSONbig = require('json-bigint')({storeAsString: true});
 const expressAPP = express();
@@ -36,6 +39,35 @@ 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();
+        }
+        log("receive http url token", token)
+    }
+
+    if (accessToken) {
+        if (token != accessToken) {
+            return res.status(403).send(JSON.stringify({message: 'token verify failed!'}));
+        }
+    }
+
+
+    next();
+
+};
+
+export function setToken(token: string) {
+    accessToken = token
+}
 
 export function startHTTPServer(port: number) {
     if (httpServer) {
@@ -54,9 +86,10 @@ let wsEventClients: websocket.WebSocket[] = [];
 type RouterHandler = (payload: any) => Promise<OB11Return<any>>
 let routers: Record<string, RouterHandler> = {};
 
-function wsReply(wsClient: websocket.WebSocket, data: OB11Return<any> | OB11Message) {
+function wsReply(wsClient: websocket.WebSocket, data: OB11Return<any> | OB11Message | OB11MetaEvent) {
     try {
         wsClient.send(JSON.stringify(data))
+        log("ws 消息上报", data)
     } catch (e) {
         log("websocket 回复失败", e)
     }
@@ -64,31 +97,66 @@ function wsReply(wsClient: websocket.WebSocket, data: OB11Return<any> | OB11Mess
 
 export function startWSServer(port: number) {
     if (wsServer) {
-        wsServer.close((err)=>{
+        wsServer.close((err) => {
             log("ws server close failed!", err)
         })
     }
     wsServer = new websocket.Server({port})
     wsServer.on("connection", (ws, req) => {
         const url = req.url;
+        log("received ws connect", url)
+        let token: string = ""
+        const authHeader = req.headers['authorization'];
+        if (authHeader) {
+            token = authHeader.split("Bearer ").pop()
+            log("receive ws header token", token);
+        } else {
+            const parsedUrl = urlParse.parse(url, true);
+            const urlToken = parsedUrl.query.access_token;
+            if (urlToken) {
+                if (Array.isArray(urlToken)) {
+                    token = urlToken[0]
+                } else {
+                    token = urlToken
+                }
+                log("receive ws url token", token);
+            }
+
+        }
+        if (accessToken) {
+            if (token != accessToken) {
+                ws.send(JSON.stringify(OB11Response.res(null, 1403, "token验证失败")))
+                return ws.close()
+            }
+        }
+        // const queryParams = querystring.parse(parsedUrl.query);
+        // let token = req
         // ws.send('Welcome to the LLOneBot WebSocket server! url:' + url);
 
+
         if (url == "/api" || url == "/api/" || url == "/") {
             ws.on("message", async (msg) => {
 
-                let receiveData: { action: ActionName, params: any } = {action: null, params: {}}
+                let receiveData: { action: ActionName, params: any, echo?: string } = {action: null, params: {}}
+                let echo = ""
                 log("收到ws消息", msg.toString())
                 try {
                     receiveData = JSON.parse(msg.toString())
+                    echo = receiveData.echo
                 } catch (e) {
-                    return wsReply(ws, OB11Response.error("json解析失败,请检查数据格式"))
+                    return wsReply(ws, {...OB11Response.error("json解析失败,请检查数据格式"), echo})
                 }
                 const handle: RouterHandler | undefined = routers[receiveData.action]
                 if (!handle) {
-                    return wsReply(ws, OB11Response.error("不支持的api " + receiveData.action))
+                    let handleResult = OB11Response.error("不支持的api " + receiveData.action, 1404)
+                    handleResult.echo = echo
+                    return wsReply(ws, handleResult)
                 }
                 try {
-                    const handleResult = await handle(receiveData.params)
+                    let handleResult = await handle(receiveData.params)
+                    if (echo){
+                        handleResult.echo = echo
+                    }
                     wsReply(ws, handleResult)
                 } catch (e) {
                     wsReply(ws, OB11Response.error(`api处理出错:${e}`))
@@ -98,7 +166,19 @@ export function startWSServer(port: number) {
         if (url == "/event" || url == "/event/" || url == "/") {
             log("event上报ws客户端已连接")
             wsEventClients.push(ws)
+            try {
+                wsReply(ws, OB11Constructor.lifeCycleEvent())
+            }catch (e){
+                log("发送生命周期失败", e)
+            }
+            // 心跳
+            let wsHeart = setInterval(()=>{
+                if (wsEventClients.find(c => c == ws)){
+                    wsReply(ws, OB11Constructor.heartEvent())
+                }
+            }, heartInterval)
             ws.on("close", () => {
+                clearInterval(wsHeart);
                 log("event上报ws客户端已断开")
                 wsEventClients = wsEventClients.filter((c) => c != ws)
             })
@@ -154,10 +234,10 @@ function registerRouter(action: string, handle: (payload: any) => Promise<any>)
         }
     }
 
-    expressAPP.post(url, (req: Request, res: Response) => {
+    expressAPP.post(url, expressAuthorize, (req: Request, res: Response) => {
         _handle(res, req.body).then()
     });
-    expressAPP.get(url, (req: Request, res: Response) => {
+    expressAPP.get(url, expressAuthorize, (req: Request, res: Response) => {
         _handle(res, req.query as any).then()
     });
     routers[action] = handle
diff --git a/src/onebot11/types.ts b/src/onebot11/types.ts
index ee4f4aa..611ad3f 100644
--- a/src/onebot11/types.ts
+++ b/src/onebot11/types.ts
@@ -89,7 +89,8 @@ export interface OB11Return<DataType> {
     status: number
     retcode: number
     data: DataType
-    message: string
+    message: string,
+    echo?: string
 }
 
 export interface OB11SendMsgReturn extends OB11Return<{message_id: string}>{}
@@ -139,4 +140,34 @@ export interface OB11PostSendMsg {
     user_id: string,
     group_id?: string,
     message: OB11MessageData[] | string | OB11MessageData;
+}
+
+export interface OB11Version {
+    app_name: "LLOneBot"
+    app_version: string
+    protocol_version: "v11"
+}
+
+
+export interface OB11MetaEvent {
+    time: number
+    self_id: number
+    post_type: "meta_event"
+    meta_event_type: "lifecycle" | "heartbeat"
+}
+
+export interface OB11LifeCycleEvent extends OB11MetaEvent{
+    meta_event_type: "lifecycle"
+    sub_type: "enable" | "disable" | "connect"
+}
+
+export interface OB11Status {
+    online: boolean | null,
+    good: boolean
+}
+
+export interface OB11HeartEvent extends OB11MetaEvent{
+    meta_event_type: "heartbeat"
+    status: OB11Status
+    interval: number
 }
\ No newline at end of file
diff --git a/src/renderer.ts b/src/renderer.ts
index abe1b2c..55319d9 100644
--- a/src/renderer.ts
+++ b/src/renderer.ts
@@ -36,6 +36,10 @@ async function onSettingWindowCreated(view: Element) {
                         <setting-text>正向ws监听端口</setting-text>
                         <input id="wsPort" type="number" value="${config.wsPort}"/>
                     </setting-item>
+                    <setting-item class="vertical-list-item" data-direction="row">
+                        <setting-text>Access Token</setting-text>
+                        <input id="token" type="text" placeholder="可为空" value="${config.token}"/>
+                    </setting-item>
                     <div>
                         <button id="addHost" class="q-button">添加HTTP上报地址</button>
                     </div>
@@ -130,20 +134,24 @@ async function onSettingWindowCreated(view: Element) {
             const portEle: HTMLInputElement = document.getElementById("port") as HTMLInputElement
             const wsPortEle: HTMLInputElement = document.getElementById("wsPort") as HTMLInputElement
             const hostEles: HTMLCollectionOf<HTMLInputElement> = document.getElementsByClassName("host") as HTMLCollectionOf<HTMLInputElement>;
+            const tokenEle = document.getElementById("token") as HTMLInputElement;
             // const port = doc.querySelector("input[type=number]")?.value
             // const host = doc.querySelector("input[type=text]")?.value
             // 获取端口和host
             const port = portEle.value
             const wsPort = wsPortEle.value
+            const token = tokenEle.value
+
             let hosts: string[] = [];
             for (const hostEle of hostEles) {
                 if (hostEle.value) {
-                    hosts.push(hostEle.value);
+                    hosts.push(hostEle.value.trim());
                 }
             }
             config.port = parseInt(port);
             config.wsPort = parseInt(wsPort);
             config.hosts = hosts;
+            config.token = token.trim();
             window.llonebot.setConfig(config);
             alert("保存成功");
         })

From d54111ce948b381b810ce1860c88b424b9759133 Mon Sep 17 00:00:00 2001
From: linyuchen <lin.yu.chen@hotmail.com>
Date: Fri, 16 Feb 2024 22:48:43 +0800
Subject: [PATCH 06/12] fix: ws url token parse

---
 manifest.json          | 2 +-
 src/common/data.ts     | 2 +-
 src/common/utils.ts    | 2 +-
 src/onebot11/server.ts | 4 ++--
 4 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/manifest.json b/manifest.json
index 31d8846..bd5ee7f 100644
--- a/manifest.json
+++ b/manifest.json
@@ -4,7 +4,7 @@
     "name": "LLOneBot",
     "slug": "LLOneBot",
     "description": "LiteLoaderQQNT的OneBotApi",
-    "version": "3.2.0",
+    "version": "3.2.1",
     "thumbnail": "./icon.png",
     "authors": [{
         "name": "linyuchen",
diff --git a/src/common/data.ts b/src/common/data.ts
index 2af3461..91163d6 100644
--- a/src/common/data.ts
+++ b/src/common/data.ts
@@ -88,5 +88,5 @@ export function getStrangerByUin(uin: string) {
     }
 }
 
-export const version = "v3.2.0"
+export const version = "v3.2.1"
 export const heartInterval = 15000 // 毫秒
\ No newline at end of file
diff --git a/src/common/utils.ts b/src/common/utils.ts
index 78a52c2..f4e3955 100644
--- a/src/common/utils.ts
+++ b/src/common/utils.ts
@@ -33,7 +33,7 @@ export function log(...msg: any[]) {
         }
         logMsg += msgItem + " ";
     }
-    logMsg = `${currentDateTime} ${userInfo}: ${logMsg}\n`
+    logMsg = `${currentDateTime} ${userInfo}: ${logMsg}\n\n`
     // sendLog(...msg);
     // console.log(msg)
     fs.appendFile(path.join(CONFIG_DIR , `llonebot-${currentDate}.log`), logMsg, (err: any) => {
diff --git a/src/onebot11/server.ts b/src/onebot11/server.ts
index b69aab7..c479501 100644
--- a/src/onebot11/server.ts
+++ b/src/onebot11/server.ts
@@ -103,7 +103,7 @@ export function startWSServer(port: number) {
     }
     wsServer = new websocket.Server({port})
     wsServer.on("connection", (ws, req) => {
-        const url = req.url;
+        const url = req.url.split("?").shift();
         log("received ws connect", url)
         let token: string = ""
         const authHeader = req.headers['authorization'];
@@ -111,7 +111,7 @@ export function startWSServer(port: number) {
             token = authHeader.split("Bearer ").pop()
             log("receive ws header token", token);
         } else {
-            const parsedUrl = urlParse.parse(url, true);
+            const parsedUrl = urlParse.parse(req.url, true);
             const urlToken = parsedUrl.query.access_token;
             if (urlToken) {
                 if (Array.isArray(urlToken)) {

From e554d805b5ebc3477cd5466b51f4b44eeb0ad91b Mon Sep 17 00:00:00 2001
From: YuChuXi <81864000+YuChuXi@users.noreply.github.com>
Date: Sat, 17 Feb 2024 01:39:06 +0800
Subject: [PATCH 07/12] =?UTF-8?q?=E4=BF=AE=E4=B8=9C=E8=A5=BF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

fix: get_group_info和get_group_list都返回群列表
---
 src/onebot11/actions/GetGroupInfo.ts | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/onebot11/actions/GetGroupInfo.ts b/src/onebot11/actions/GetGroupInfo.ts
index af59af1..0ff09a1 100644
--- a/src/onebot11/actions/GetGroupInfo.ts
+++ b/src/onebot11/actions/GetGroupInfo.ts
@@ -1,5 +1,5 @@
 import { OB11Group } from '../types';
-import { getGroup, groups } from "../../common/data";
+import { getGroup } from "../../common/data";
 import { OB11Constructor } from "../constructor";
 import BaseAction from "./BaseAction";
 import { ActionName } from "./types";
@@ -8,17 +8,17 @@ interface PayloadType {
     group_id: number
 }
 
-class GetGroupInfo extends BaseAction<PayloadType, OB11Group[]> {
+class GetGroupInfo extends BaseAction<PayloadType, OB11Group> {
     actionName = ActionName.GetGroupInfo
 
     protected async _handle(payload: PayloadType) {
         const group = await getGroup(payload.group_id.toString())
         if (group) {
-            return OB11Constructor.groups(groups)
+            return OB11Constructor.group(group)
         } else {
             throw `群${payload.group_id}不存在`
         }
     }
 }
 
-export default GetGroupInfo
\ No newline at end of file
+export default GetGroupInfo

From ba387b40ca49c60ab46f409082570cd4dfb83443 Mon Sep 17 00:00:00 2001
From: linyuchen <lin.yu.chen@hotmail.com>
Date: Sat, 17 Feb 2024 01:42:14 +0800
Subject: [PATCH 08/12] =?UTF-8?q?=E6=9A=82=E5=AD=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 manifest.json                      | 52 ++++++++++++-----------
 src/common/data.ts                 |  2 +-
 src/ntqqapi/hook.ts                |  2 +-
 src/onebot11/actions/GetStatus.ts  |  2 +
 src/onebot11/actions/index.ts      |  4 +-
 src/onebot11/constructor.ts        | 29 ++-----------
 src/onebot11/events/constructor.ts | 58 ++++++++++++++++++++++++++
 src/onebot11/events/types.ts       | 67 ++++++++++++++++++++++++++++++
 src/onebot11/server.ts             | 14 +++----
 src/onebot11/types.ts              | 20 +--------
 10 files changed, 168 insertions(+), 82 deletions(-)
 create mode 100644 src/onebot11/events/constructor.ts
 create mode 100644 src/onebot11/events/types.ts

diff --git a/manifest.json b/manifest.json
index bd5ee7f..521ef5a 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,31 +1,33 @@
 {
-    "manifest_version": 4,
-    "type": "extension",
-    "name": "LLOneBot",
-    "slug": "LLOneBot",
-    "description": "LiteLoaderQQNT的OneBotApi",
-    "version": "3.2.1",
-    "thumbnail": "./icon.png",
-    "authors": [{
-        "name": "linyuchen",
-        "link": "https://github.com/linyuchen"
-    }],
-    "repository": {
+  "manifest_version": 4,
+  "type": "extension",
+  "name": "LLOneBot",
+  "slug": "LLOneBot",
+  "description": "LiteLoaderQQNT的OneBotApi",
+  "version": "3.3.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"
-    }
-},
-    "platform": [
-        "win32",
-        "linux",
-        "darwin"
-    ],
-    "injects": {
-        "renderer": "./renderer.js",
-        "main": "./main.js",
-        "preload": "./preload.js"
+      "tag": "latest",
+      "name": "LLOneBot.zip"
     }
+  },
+  "platform": [
+    "win32",
+    "linux",
+    "darwin"
+  ],
+  "injects": {
+    "renderer": "./renderer.js",
+    "main": "./main.js",
+    "preload": "./preload.js"
+  }
 }
diff --git a/src/common/data.ts b/src/common/data.ts
index 91163d6..0027f11 100644
--- a/src/common/data.ts
+++ b/src/common/data.ts
@@ -88,5 +88,5 @@ export function getStrangerByUin(uin: string) {
     }
 }
 
-export const version = "v3.2.1"
+export const version = "v3.3.0"
 export const heartInterval = 15000 // 毫秒
\ No newline at end of file
diff --git a/src/ntqqapi/hook.ts b/src/ntqqapi/hook.ts
index 3b281b3..d937a16 100644
--- a/src/ntqqapi/hook.ts
+++ b/src/ntqqapi/hook.ts
@@ -41,7 +41,7 @@ let receiveHooks: Array<{
 export function hookNTQQApiReceive(window: BrowserWindow) {
     const originalSend = window.webContents.send;
     const patchSend = (channel: string, ...args: NTQQApiReturnData) => {
-        // log(`received ntqq api message: ${channel}`, JSON.stringify(args))
+        log(`received ntqq api message: ${channel}`, JSON.stringify(args))
         if (args?.[1] instanceof Array) {
             for (let receiveData of args?.[1]) {
                 const ntQQApiMethodName = receiveData.cmdName;
diff --git a/src/onebot11/actions/GetStatus.ts b/src/onebot11/actions/GetStatus.ts
index 1e388af..2df98db 100644
--- a/src/onebot11/actions/GetStatus.ts
+++ b/src/onebot11/actions/GetStatus.ts
@@ -1,8 +1,10 @@
 import BaseAction from "./BaseAction";
 import {OB11Status} from "../types";
+import { ActionName } from "./types";
 
 
 export default class GetStatus extends BaseAction<any, OB11Status> {
+    actionName = ActionName.GetStatus
     protected async _handle(payload: any): Promise<OB11Status> {
         return {
             online: null,
diff --git a/src/onebot11/actions/index.ts b/src/onebot11/actions/index.ts
index ae75b78..7ebaa6d 100644
--- a/src/onebot11/actions/index.ts
+++ b/src/onebot11/actions/index.ts
@@ -12,6 +12,7 @@ import DeleteMsg from "./DeleteMsg";
 import GetVersionInfo from "./GetVersionInfo";
 import CanSendRecord from "./CanSendRecord";
 import CanSendImage from "./CanSendImage";
+import GetStatus from "./GetStatus";
 
 export const actionHandlers = [
     new GetMsg(),
@@ -22,5 +23,6 @@ export const actionHandlers = [
     new DeleteMsg(),
     new GetVersionInfo(),
     new CanSendRecord(),
-    new CanSendImage()
+    new CanSendImage(),
+    new GetStatus()
 ]
\ No newline at end of file
diff --git a/src/onebot11/constructor.ts b/src/onebot11/constructor.ts
index d8afb2e..fd1511c 100644
--- a/src/onebot11/constructor.ts
+++ b/src/onebot11/constructor.ts
@@ -4,15 +4,16 @@ import {
     OB11Message,
     OB11Group,
     OB11GroupMember,
-    OB11User, OB11LifeCycleEvent, OB11HeartEvent
+    OB11User
 } from "./types";
 import { AtType, ChatType, Group, GroupMember, IMAGE_HTTP_HOST, RawMessage, SelfInfo, User } from '../ntqqapi/types';
 import { getFriend, getGroupMember, getHistoryMsgBySeq, heartInterval, msgHistory, selfInfo } from '../common/data';
 import { file2base64, getConfigUtil, log } from "../common/utils";
 import { NTQQApi } from "../ntqqapi/ntcall";
+import {OB11EventConstructor} from "./events/constructor";
 
 
-export class OB11Constructor {
+export class OB11Constructor extends OB11EventConstructor{
     static async message(msg: RawMessage): Promise<OB11Message> {
 
         const {enableBase64} = getConfigUtil().getConfig()
@@ -186,28 +187,4 @@ export class OB11Constructor {
     static groups(groups: Group[]): OB11Group[] {
         return groups.map(OB11Constructor.group)
     }
-
-    static lifeCycleEvent(): OB11LifeCycleEvent {
-        return {
-            time: Math.floor(Date.now() / 1000),
-            self_id: parseInt(selfInfo.uin),
-            post_type: "meta_event",
-            meta_event_type: "lifecycle",
-            sub_type: "connect"
-        }
-    }
-
-    static heartEvent(): OB11HeartEvent {
-        return {
-            time: Math.floor(Date.now() / 1000),
-            self_id: parseInt(selfInfo.uin),
-            post_type: "meta_event",
-            meta_event_type: "heartbeat",
-            status: {
-                online: true,
-                good: true
-            },
-            interval: heartInterval
-        }
-    }
 }
\ No newline at end of file
diff --git a/src/onebot11/events/constructor.ts b/src/onebot11/events/constructor.ts
new file mode 100644
index 0000000..3ce9221
--- /dev/null
+++ b/src/onebot11/events/constructor.ts
@@ -0,0 +1,58 @@
+import {
+    OB11EventBase,
+    OB11EventPostType, OB11FriendRecallNoticeEvent,
+    OB11GroupRecallNoticeEvent,
+    OB11HeartEvent,
+    OB11LifeCycleEvent, OB11MetaEvent, OB11NoticeBase
+} from "./types";
+import { heartInterval, selfInfo } from "../../common/data";
+
+function eventBase(post_type: OB11EventPostType): OB11EventBase {
+    return {
+        time: Math.floor(Date.now() / 1000),
+        self_id: parseInt(selfInfo.uin),
+        post_type
+    }
+}
+
+export class OB11EventConstructor {
+    static lifeCycle(): OB11LifeCycleEvent {
+        return {
+            ...eventBase(OB11EventPostType.META) as OB11MetaEvent,
+            meta_event_type: "lifecycle",
+            sub_type: "connect"
+        }
+    }
+
+    static heart(): OB11HeartEvent {
+        return {
+            ...eventBase(OB11EventPostType.META) as OB11MetaEvent,
+            meta_event_type: "heartbeat",
+            status: {
+                online: true,
+                good: true
+            },
+            interval: heartInterval
+        }
+    }
+
+    static groupRecall(group_id: string, user_id: string, operator_id: string, message_id: number): OB11GroupRecallNoticeEvent {
+        return {
+            ...eventBase(OB11EventPostType.NOTICE) as OB11NoticeBase,
+            notice_type: "group_recall",
+            group_id: parseInt(group_id),
+            user_id: parseInt(user_id),
+            operator_id: parseInt(operator_id),
+            message_id
+        }
+    }
+
+    static friendRecall(user_id: string, operator_id: string, message_id: number): OB11FriendRecallNoticeEvent {
+        return {
+            ...eventBase(OB11EventPostType.NOTICE) as OB11NoticeBase,
+            notice_type: "friend_recall",
+            user_id: parseInt(user_id),
+            message_id
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/onebot11/events/types.ts b/src/onebot11/events/types.ts
new file mode 100644
index 0000000..e1d9224
--- /dev/null
+++ b/src/onebot11/events/types.ts
@@ -0,0 +1,67 @@
+import { OB11Status } from "../types";
+
+export enum OB11EventPostType{
+    META = "meta_event",
+    NOTICE = "notice"
+}
+
+export interface OB11EventBase {
+    time: number
+    self_id: number
+    post_type: OB11EventPostType
+}
+
+export interface OB11MetaEvent extends OB11EventBase{
+    post_type: OB11EventPostType.META
+    meta_event_type: "lifecycle" | "heartbeat"
+}
+
+export interface OB11NoticeBase extends OB11EventBase{
+    post_type: OB11EventPostType.NOTICE
+    notice_type: "group_admin" | "group_decrease" | "group_increase" | "group_ban" | "friend_add" | "group_recall" | "friend_recall"
+}
+
+interface OB11GroupNoticeBase extends OB11NoticeBase{
+    group_id: number
+    user_id: number
+}
+
+export interface OB11GroupAdminNoticeEvent extends OB11GroupNoticeBase{
+    notice_type: "group_admin"
+    sub_type: "set" | "unset"
+}
+
+export interface OB11GroupMemberDecNoticeEvent extends OB11GroupNoticeBase{
+    notice_type: "group_decrease"
+    sub_type: "leave" | "kick" | "kick_me"
+    operator_id: number
+}
+
+export interface OB11GroupMemberIncNoticeEvent extends OB11GroupNoticeBase{
+    notice_type: "group_increase"
+    sub_type: "approve" | "invite"
+    operator_id: number
+}
+
+export interface OB11GroupRecallNoticeEvent extends OB11GroupNoticeBase{
+    notice_type: "group_recall"
+    operator_id: number
+    message_id: number
+}
+
+export interface OB11FriendRecallNoticeEvent extends OB11NoticeBase{
+    notice_type: "friend_recall"
+    user_id: number
+    message_id: number
+}
+
+export interface OB11LifeCycleEvent extends OB11MetaEvent {
+    meta_event_type: "lifecycle"
+    sub_type: "enable" | "disable" | "connect"
+}
+
+export interface OB11HeartEvent extends OB11MetaEvent {
+    meta_event_type: "heartbeat"
+    status: OB11Status
+    interval: number
+}
\ No newline at end of file
diff --git a/src/onebot11/server.ts b/src/onebot11/server.ts
index c479501..ec43b40 100644
--- a/src/onebot11/server.ts
+++ b/src/onebot11/server.ts
@@ -6,12 +6,13 @@ import { Request } from 'express';
 import { Response } from 'express';
 import { getConfigUtil, log } from "../common/utils";
 import { heartInterval, selfInfo } from "../common/data";
-import { OB11Message, OB11Return, OB11MessageData, OB11LifeCycleEvent, OB11MetaEvent } from './types';
+import { OB11Message, OB11Return, OB11MessageData } from './types';
 import { actionHandlers } from "./actions";
 import { OB11Response } from "./actions/utils";
 import { ActionName } from "./actions/types";
 import BaseAction from "./actions/BaseAction";
 import { OB11Constructor } from "./constructor";
+import { OB11LifeCycleEvent, OB11MetaEvent } from "./events/types";
 
 let wsServer: websocket.Server = null;
 let accessToken = ""
@@ -104,7 +105,7 @@ export function startWSServer(port: number) {
     wsServer = new websocket.Server({port})
     wsServer.on("connection", (ws, req) => {
         const url = req.url.split("?").shift();
-        log("received ws connect", url)
+        log("receive ws connect", url)
         let token: string = ""
         const authHeader = req.headers['authorization'];
         if (authHeader) {
@@ -129,11 +130,6 @@ export function startWSServer(port: number) {
                 return ws.close()
             }
         }
-        // const queryParams = querystring.parse(parsedUrl.query);
-        // let token = req
-        // ws.send('Welcome to the LLOneBot WebSocket server! url:' + url);
-
-
         if (url == "/api" || url == "/api/" || url == "/") {
             ws.on("message", async (msg) => {
 
@@ -167,14 +163,14 @@ export function startWSServer(port: number) {
             log("event上报ws客户端已连接")
             wsEventClients.push(ws)
             try {
-                wsReply(ws, OB11Constructor.lifeCycleEvent())
+                wsReply(ws, OB11Constructor.lifeCycle())
             }catch (e){
                 log("发送生命周期失败", e)
             }
             // 心跳
             let wsHeart = setInterval(()=>{
                 if (wsEventClients.find(c => c == ws)){
-                    wsReply(ws, OB11Constructor.heartEvent())
+                    wsReply(ws, OB11Constructor.heart())
                 }
             }, heartInterval)
             ws.on("close", () => {
diff --git a/src/onebot11/types.ts b/src/onebot11/types.ts
index 611ad3f..32e5248 100644
--- a/src/onebot11/types.ts
+++ b/src/onebot11/types.ts
@@ -1,5 +1,4 @@
-import { AtType } from "../ntqqapi/types";
-import { RawMessage } from "../ntqqapi/types";
+import { AtType, RawMessage } from "../ntqqapi/types";
 
 export interface OB11User{
     user_id: number;
@@ -149,25 +148,8 @@ export interface OB11Version {
 }
 
 
-export interface OB11MetaEvent {
-    time: number
-    self_id: number
-    post_type: "meta_event"
-    meta_event_type: "lifecycle" | "heartbeat"
-}
-
-export interface OB11LifeCycleEvent extends OB11MetaEvent{
-    meta_event_type: "lifecycle"
-    sub_type: "enable" | "disable" | "connect"
-}
-
 export interface OB11Status {
     online: boolean | null,
     good: boolean
 }
 
-export interface OB11HeartEvent extends OB11MetaEvent{
-    meta_event_type: "heartbeat"
-    status: OB11Status
-    interval: number
-}
\ No newline at end of file

From 06ad92b84681c54da407894fe006ebdfcb8f7b78 Mon Sep 17 00:00:00 2001
From: linyuchen <lin.yu.chen@hotmail.com>
Date: Sat, 17 Feb 2024 01:44:21 +0800
Subject: [PATCH 09/12] ver: 3.2.2

---
 manifest.json      | 2 +-
 src/common/data.ts | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/manifest.json b/manifest.json
index bd5ee7f..070bacd 100644
--- a/manifest.json
+++ b/manifest.json
@@ -4,7 +4,7 @@
     "name": "LLOneBot",
     "slug": "LLOneBot",
     "description": "LiteLoaderQQNT的OneBotApi",
-    "version": "3.2.1",
+    "version": "3.2.2",
     "thumbnail": "./icon.png",
     "authors": [{
         "name": "linyuchen",
diff --git a/src/common/data.ts b/src/common/data.ts
index 91163d6..f3207cf 100644
--- a/src/common/data.ts
+++ b/src/common/data.ts
@@ -88,5 +88,5 @@ export function getStrangerByUin(uin: string) {
     }
 }
 
-export const version = "v3.2.1"
+export const version = "v3.2.2"
 export const heartInterval = 15000 // 毫秒
\ No newline at end of file

From 1a1d673c8ccd5d7ec10a731bdbfe2740d13588f6 Mon Sep 17 00:00:00 2001
From: linyuchen <lin.yu.chen@hotmail.com>
Date: Sat, 17 Feb 2024 20:06:17 +0800
Subject: [PATCH 10/12] feat: face msg feat: recall notice

---
 src/main/main.ts                   | 59 ++++++++++++++++++++---------
 src/ntqqapi/constructor.ts         | 27 +++++++++++--
 src/ntqqapi/hook.ts                | 15 ++++----
 src/ntqqapi/types.ts               | 49 ++++++++++++++++--------
 src/onebot11/actions/GetMsg.ts     |  3 ++
 src/onebot11/actions/SendMsg.ts    | 61 ++++++++++++++----------------
 src/onebot11/constructor.ts        |  3 ++
 src/onebot11/events/constructor.ts | 14 +++----
 src/onebot11/events/types.ts       |  6 +--
 src/onebot11/server.ts             | 20 +++++-----
 src/onebot11/types.ts              | 19 +++++++---
 11 files changed, 173 insertions(+), 103 deletions(-)

diff --git a/src/main/main.ts b/src/main/main.ts
index 945c14b..a897bc6 100644
--- a/src/main/main.ts
+++ b/src/main/main.ts
@@ -1,22 +1,16 @@
 // 运行在 Electron 主进程 下的插件入口
 
-import * as path from "path";
 import { BrowserWindow, ipcMain } from 'electron';
-import * as util from 'util';
 
 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";
 import { postMsg, setToken, startHTTPServer, startWSServer } from "../onebot11/server";
 import { CONFIG_DIR, getConfigUtil, log } from "../common/utils";
-import { addHistoryMsg, msgHistory, selfInfo } from "../common/data";
+import { addHistoryMsg, getGroupMember, msgHistory, selfInfo, uidMaps } from "../common/data";
 import { hookNTQQApiReceive, ReceiveCmd, registerReceiveHook } from "../ntqqapi/hook";
 import { OB11Constructor } from "../onebot11/constructor";
 import { NTQQApi } from "../ntqqapi/ntcall";
-import { Group, RawMessage, SelfInfo } from "../ntqqapi/types";
+import { ChatType, RawMessage } from "../ntqqapi/types";
 
 const fs = require('fs');
 
@@ -39,13 +33,13 @@ function onLoad() {
     ipcMain.on(CHANNEL_SET_CONFIG, (event: any, arg: Config) => {
         let oldConfig = getConfigUtil().getConfig();
         getConfigUtil().setConfig(arg)
-        if (arg.port != oldConfig.port){
+        if (arg.port != oldConfig.port) {
             startHTTPServer(arg.port)
         }
-        if (arg.wsPort != oldConfig.wsPort){
+        if (arg.wsPort != oldConfig.wsPort) {
             startWSServer(arg.wsPort)
         }
-        if (arg.token != oldConfig.token){
+        if (arg.token != oldConfig.token) {
             setToken(arg.token);
         }
     })
@@ -58,6 +52,7 @@ function onLoad() {
     function postRawMsg(msgList: RawMessage[]) {
         const {debug, reportSelfMessage} = getConfigUtil().getConfig();
         for (let message of msgList) {
+            log("收到新消息", message)
             message.msgShortId = msgHistory[message.msgId]?.msgShortId
             if (!message.msgShortId) {
                 addHistoryMsg(message)
@@ -76,7 +71,7 @@ function onLoad() {
     }
 
 
-    function start() {
+    async function start() {
         registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.NEW_MSG, (payload) => {
             try {
                 // log("received msg length", payload.msgList.length);
@@ -85,7 +80,38 @@ function onLoad() {
                 log("report message error: ", e.toString())
             }
         })
-
+        registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.UPDATE_MSG, async (payload) => {
+            for (const message of payload.msgList) {
+                // log("message update", message, message.sendStatus)
+                if (message.sendStatus === 2) {
+                    // 撤回消息上报
+                    const oriMessage = msgHistory[message.msgId]
+                    if (!oriMessage) {
+                        continue
+                    }
+                    if (message.chatType == ChatType.friend) {
+                        const friendRecallEvent = OB11Constructor.friendRecallEvent(message.senderUin, oriMessage.msgShortId)
+                        postMsg(friendRecallEvent)
+                    } else if (message.chatType == ChatType.group) {
+                        let operatorId = message.senderUin
+                        for (const element of message.elements) {
+                            const operatorUid = element.grayTipElement?.revokeElement.operatorUid
+                            const operator = await getGroupMember(message.peerUin, null, operatorUid)
+                            operatorId = operator.uin
+                        }
+                        const groupRecallEvent = OB11Constructor.groupRecallEvent(
+                            message.peerUin,
+                            message.senderUin,
+                            operatorId,
+                            oriMessage.msgShortId
+                        )
+                        postMsg(groupRecallEvent)
+                    }
+                    continue
+                }
+                addHistoryMsg(message)
+            }
+        })
         registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmd.SELF_SEND_MSG, (payload) => {
             const {reportSelfMessage} = getConfigUtil().getConfig()
             if (!reportSelfMessage) {
@@ -128,9 +154,8 @@ function onLoad() {
                 log("get self nickname failed", e.toString())
                 return setTimeout(init, 1000)
             }
-            start();
-        }
-        else{
+            start().then();
+        } else {
             setTimeout(init, 1000)
         }
     }
diff --git a/src/ntqqapi/constructor.ts b/src/ntqqapi/constructor.ts
index 76a38b0..a966026 100644
--- a/src/ntqqapi/constructor.ts
+++ b/src/ntqqapi/constructor.ts
@@ -1,5 +1,13 @@
-import {ElementType, SendPicElement, SendPttElement, SendReplyElement, SendTextElement, AtType} from "./types";
-import {NTQQApi} from "./ntcall";
+import {
+    ElementType,
+    SendPicElement,
+    SendPttElement,
+    SendReplyElement,
+    SendTextElement,
+    AtType,
+    SendFaceElement
+} from "./types";
+import { NTQQApi } from "./ntcall";
 
 
 export class SendMsgElementConstructor {
@@ -44,7 +52,7 @@ export class SendMsgElementConstructor {
         }
     }
 
-    static async pic(picPath: string): Promise<SendPicElement>{
+    static async pic(picPath: string): Promise<SendPicElement> {
         const {md5, fileName, path, fileSize} = await NTQQApi.uploadFile(picPath);
         const imageSize = await NTQQApi.getImageSize(picPath);
         const picElement = {
@@ -70,7 +78,7 @@ export class SendMsgElementConstructor {
         };
     }
 
-    static async ptt(pttPath: string):Promise<SendPttElement> {
+    static async ptt(pttPath: string): Promise<SendPttElement> {
         const {md5, fileName, path, fileSize} = await NTQQApi.uploadFile(pttPath);
         return {
             elementType: ElementType.PTT,
@@ -94,4 +102,15 @@ export class SendMsgElementConstructor {
             }
         };
     }
+
+    static face(faceId: number): SendFaceElement {
+        return {
+            elementType: ElementType.FACE,
+            elementId: "",
+            faceElement: {
+                faceIndex: faceId,
+                faceType: 1
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/ntqqapi/hook.ts b/src/ntqqapi/hook.ts
index d937a16..6e954fd 100644
--- a/src/ntqqapi/hook.ts
+++ b/src/ntqqapi/hook.ts
@@ -34,14 +34,14 @@ interface NTQQApiReturnData<PayloadType = unknown> extends Array<any> {
 
 let receiveHooks: Array<{
     method: ReceiveCmd,
-    hookFunc: (payload: any) => void,
+    hookFunc: ((payload: any) => void | Promise<void>)
     id: string
 }> = []
 
 export function hookNTQQApiReceive(window: BrowserWindow) {
     const originalSend = window.webContents.send;
     const patchSend = (channel: string, ...args: NTQQApiReturnData) => {
-        log(`received ntqq api message: ${channel}`, JSON.stringify(args))
+        // log(`received ntqq api message: ${channel}`, JSON.stringify(args))
         if (args?.[1] instanceof Array) {
             for (let receiveData of args?.[1]) {
                 const ntQQApiMethodName = receiveData.cmdName;
@@ -50,7 +50,10 @@ export function hookNTQQApiReceive(window: BrowserWindow) {
                     if (hook.method === ntQQApiMethodName) {
                         new Promise((resolve, reject) => {
                             try {
-                                hook.hookFunc(receiveData.payload);
+                                let _ = hook.hookFunc(receiveData.payload)
+                                if (hook.hookFunc.constructor.name === "AsyncFunction"){
+                                    (_ as Promise<void>).then()
+                                }
                             } catch (e) {
                                 log("hook error", e, receiveData.payload)
                             }
@@ -129,11 +132,7 @@ registerReceiveHook<{
 //     log("user info", payload);
 // })
 
-registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.UPDATE_MSG, (payload) => {
-    for (const message of payload.msgList) {
-        addHistoryMsg(message)
-    }
-})
+
 
 registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.NEW_MSG, (payload) => {
     for (const message of payload.msgList) {
diff --git a/src/ntqqapi/types.ts b/src/ntqqapi/types.ts
index da86cf8..da4ff74 100644
--- a/src/ntqqapi/types.ts
+++ b/src/ntqqapi/types.ts
@@ -7,13 +7,14 @@ export interface User {
     remark?: string
 }
 
-export interface SelfInfo extends User{
+export interface SelfInfo extends User {
 
 }
 
-export interface Friend extends User{}
+export interface Friend extends User {
+}
 
-export interface Group{
+export interface Group {
     groupCode: string,
     maxMember: number,
     memberCount: number,
@@ -62,6 +63,7 @@ export enum ElementType {
     TEXT = 1,
     PIC = 2,
     PTT = 4,
+    FACE = 6,
     REPLY = 7,
 }
 
@@ -76,6 +78,7 @@ export interface SendTextElement {
         atNtUid: string,
     }
 }
+
 export interface SendPttElement {
     elementType: ElementType.PTT,
     elementId: "",
@@ -127,7 +130,13 @@ export interface SendReplyElement {
     }
 }
 
-export type SendMessageElement = SendTextElement | SendPttElement | SendPicElement | SendReplyElement
+export interface SendFaceElement {
+    elementType: ElementType.FACE,
+    elementId: "",
+    faceElement: FaceElement
+}
+
+export type SendMessageElement = SendTextElement | SendPttElement | SendPicElement | SendReplyElement | SendFaceElement
 
 export enum AtType {
     notAt = 0,
@@ -140,6 +149,7 @@ export enum ChatType {
     group = 2,
     temp = 100
 }
+
 export interface PttElement {
     canConvert2Text: boolean;
     duration: number; // 秒数
@@ -180,6 +190,22 @@ export interface PicElement {
     fileUuid: string;
 }
 
+export interface GrayTipElement {
+    revokeElement: {
+        operatorRole: string;
+        operatorUid: string;
+        operatorNick: string;
+        operatorRemark: string;
+        operatorMemRemark?: string;
+        wording: string;  // 自定义的撤回提示语
+    }
+}
+
+export interface FaceElement {
+    faceIndex: number,
+    faceType: 1
+}
+
 export interface RawMessage {
     msgId: string;
     msgShortId?: number;  // 自己维护的消息id
@@ -191,6 +217,7 @@ export interface RawMessage {
     sendNickName: string;
     sendMemberName?: string; // 发送者群名片
     chatType: ChatType;
+    sendStatus?: number;  // 消息状态,2是已撤回
     elements: {
         elementId: string,
         replyElement: {
@@ -208,17 +235,7 @@ export interface RawMessage {
         picElement: PicElement;
         pttElement: PttElement;
         arkElement: ArkElement;
+        grayTipElement: GrayTipElement;
+        faceElement: FaceElement;
     }[];
 }
-
-export interface MessageElement {
-    raw: RawMessage;
-    peer: any;
-    sender: {
-        uid: string; // 一串加密的字符串
-        memberName: string;
-        nickname: string;
-    };
-}
-
-
diff --git a/src/onebot11/actions/GetMsg.ts b/src/onebot11/actions/GetMsg.ts
index 538c2be..7dedbcd 100644
--- a/src/onebot11/actions/GetMsg.ts
+++ b/src/onebot11/actions/GetMsg.ts
@@ -17,6 +17,9 @@ class GetMsg extends BaseAction<PayloadType, OB11Message> {
 
     protected async _handle(payload: PayloadType){
         // log("history msg ids", Object.keys(msgHistory));
+        if (!payload.message_id){
+            throw("参数message_id不能为空")
+        }
         const msg = getHistoryMsgByShortId(payload.message_id)
         if (msg) {
             const msgData = await OB11Constructor.message(msg);
diff --git a/src/onebot11/actions/SendMsg.ts b/src/onebot11/actions/SendMsg.ts
index 5f374f1..e7b9700 100644
--- a/src/onebot11/actions/SendMsg.ts
+++ b/src/onebot11/actions/SendMsg.ts
@@ -1,19 +1,10 @@
-import { AtType, ChatType, Group } from "../../ntqqapi/types";
-import {
-    addHistoryMsg,
-    friends,
-    getGroup,
-    getHistoryMsgByShortId,
-    getStrangerByUin,
-} from "../../common/data";
+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 } from "../../ntqqapi/ntcall";
-import { Peer } from "../../ntqqapi/ntcall";
-import { SendMessageElement } from "../../ntqqapi/types";
+import { NTQQApi, Peer } from "../../ntqqapi/ntcall";
 import { SendMsgElementConstructor } from "../../ntqqapi/constructor";
 import { uri2local } from "../utils";
 import { v4 as uuid4 } from 'uuid';
-import { log } from "../../common/utils";
 import BaseAction from "./BaseAction";
 import { ActionName } from "./types";
 import * as fs from "fs";
@@ -25,7 +16,7 @@ export interface ReturnDataType {
 class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
     actionName = ActionName.SendMsg
 
-    protected async _handle(payload: OB11PostSendMsg){
+    protected async _handle(payload: OB11PostSendMsg) {
         const peer: Peer = {
             chatType: ChatType.friend,
             peerUid: ""
@@ -40,18 +31,16 @@ class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
             peer.chatType = ChatType.group
             // peer.name = group.name
             peer.peerUid = group.groupCode
-        }
-        else if (payload?.user_id) {
+        } else if (payload?.user_id) {
             const friend = friends.find(f => f.uin == payload.user_id.toString())
             if (friend) {
                 // peer.name = friend.nickName
                 peer.peerUid = friend.uid
-            }
-            else {
+            } else {
                 peer.chatType = ChatType.temp
                 const tempUser = getStrangerByUin(payload.user_id.toString())
                 if (!tempUser) {
-                    throw(`找不到私聊对象${payload.user_id}`)
+                    throw (`找不到私聊对象${payload.user_id}`)
                 }
                 // peer.name = tempUser.nickName
                 peer.peerUid = tempUser.uid
@@ -64,8 +53,7 @@ class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
                     text: payload.message
                 }
             }] as OB11MessageData[]
-        }
-        else if (!Array.isArray(payload.message)) {
+        } else if (!Array.isArray(payload.message)) {
             payload.message = [payload.message]
         }
         const sendElements: SendMessageElement[] = []
@@ -76,22 +64,23 @@ class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
                     if (text) {
                         sendElements.push(SendMsgElementConstructor.text(sendMsg.data!.text))
                     }
-                } break;
+                }
+                    break;
                 case OB11MessageDataType.at: {
                     let atQQ = sendMsg.data?.qq;
                     if (atQQ) {
                         atQQ = atQQ.toString()
                         if (atQQ === "all") {
                             sendElements.push(SendMsgElementConstructor.at(atQQ, atQQ, AtType.atAll, "全体成员"))
-                        }
-                        else {
+                        } else {
                             const atMember = group?.members.find(m => m.uin == atQQ)
                             if (atMember) {
                                 sendElements.push(SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick))
                             }
                         }
                     }
-                } break;
+                }
+                    break;
                 case OB11MessageDataType.reply: {
                     let replyMsgId = sendMsg.data.id;
                     if (replyMsgId) {
@@ -101,20 +90,27 @@ class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
                             sendElements.push(SendMsgElementConstructor.reply(replyMsg.msgSeq, replyMsg.msgId, replyMsg.senderUin, replyMsg.senderUin))
                         }
                     }
-                } break;
+                }
+                    break;
+                case OB11MessageDataType.face: {
+                    const faceId = sendMsg.data?.id
+                    if (faceId) {
+                        sendElements.push(SendMsgElementConstructor.face(parseInt(faceId)))
+                    }
+                }
+                    break;
                 case OB11MessageDataType.image:
                 case OB11MessageDataType.voice: {
                     const file = sendMsg.data?.file
                     if (file) {
                         const {path, isLocal} = (await uri2local(uuid4(), file))
                         if (path) {
-                            if (!isLocal){ // 只删除http和base64转过来的文件
+                            if (!isLocal) { // 只删除http和base64转过来的文件
                                 deleteAfterSentFiles.push(path)
                             }
-                            if (sendMsg.type === OB11MessageDataType.image){
+                            if (sendMsg.type === OB11MessageDataType.image) {
                                 sendElements.push(await SendMsgElementConstructor.pic(path))
-                            }
-                            else {
+                            } else {
                                 sendElements.push(await SendMsgElementConstructor.ptt(path))
                             }
                         }
@@ -126,10 +122,11 @@ class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
         try {
             const returnMsg = await NTQQApi.sendMsg(peer, sendElements)
             addHistoryMsg(returnMsg)
-            deleteAfterSentFiles.map(f=>fs.unlink(f, ()=>{}))
-            return { message_id: returnMsg.msgShortId }
+            deleteAfterSentFiles.map(f => fs.unlink(f, () => {
+            }))
+            return {message_id: returnMsg.msgShortId}
         } catch (e) {
-            throw(e.toString())
+            throw (e.toString())
         }
     }
 }
diff --git a/src/onebot11/constructor.ts b/src/onebot11/constructor.ts
index fd1511c..b404918 100644
--- a/src/onebot11/constructor.ts
+++ b/src/onebot11/constructor.ts
@@ -113,6 +113,9 @@ export class OB11Constructor extends OB11EventConstructor{
             } else if (element.arkElement) {
                 message_data["type"] = OB11MessageDataType.json;
                 message_data["data"]["data"] = element.arkElement.bytesData;
+            } else if (element.faceElement){
+                message_data["type"] = OB11MessageDataType.face;
+                message_data["data"]["id"] = element.faceElement.faceIndex.toString();
             }
             if (message_data.data.http_file) {
                 message_data.data.file = message_data.data.http_file
diff --git a/src/onebot11/events/constructor.ts b/src/onebot11/events/constructor.ts
index 3ce9221..06cd850 100644
--- a/src/onebot11/events/constructor.ts
+++ b/src/onebot11/events/constructor.ts
@@ -3,7 +3,7 @@ import {
     OB11EventPostType, OB11FriendRecallNoticeEvent,
     OB11GroupRecallNoticeEvent,
     OB11HeartEvent,
-    OB11LifeCycleEvent, OB11MetaEvent, OB11NoticeBase
+    OB11LifeCycleEvent, OB11MetaEvent, OB11NoticeEvent
 } from "./types";
 import { heartInterval, selfInfo } from "../../common/data";
 
@@ -16,7 +16,7 @@ function eventBase(post_type: OB11EventPostType): OB11EventBase {
 }
 
 export class OB11EventConstructor {
-    static lifeCycle(): OB11LifeCycleEvent {
+    static lifeCycleEvent(): OB11LifeCycleEvent {
         return {
             ...eventBase(OB11EventPostType.META) as OB11MetaEvent,
             meta_event_type: "lifecycle",
@@ -24,7 +24,7 @@ export class OB11EventConstructor {
         }
     }
 
-    static heart(): OB11HeartEvent {
+    static heartEvent(): OB11HeartEvent {
         return {
             ...eventBase(OB11EventPostType.META) as OB11MetaEvent,
             meta_event_type: "heartbeat",
@@ -36,9 +36,9 @@ export class OB11EventConstructor {
         }
     }
 
-    static groupRecall(group_id: string, user_id: string, operator_id: string, message_id: number): OB11GroupRecallNoticeEvent {
+    static groupRecallEvent(group_id: string, user_id: string, operator_id: string, message_id: number): OB11GroupRecallNoticeEvent {
         return {
-            ...eventBase(OB11EventPostType.NOTICE) as OB11NoticeBase,
+            ...eventBase(OB11EventPostType.NOTICE) as OB11NoticeEvent,
             notice_type: "group_recall",
             group_id: parseInt(group_id),
             user_id: parseInt(user_id),
@@ -47,9 +47,9 @@ export class OB11EventConstructor {
         }
     }
 
-    static friendRecall(user_id: string, operator_id: string, message_id: number): OB11FriendRecallNoticeEvent {
+    static friendRecallEvent(user_id: string, message_id: number): OB11FriendRecallNoticeEvent {
         return {
-            ...eventBase(OB11EventPostType.NOTICE) as OB11NoticeBase,
+            ...eventBase(OB11EventPostType.NOTICE) as OB11NoticeEvent,
             notice_type: "friend_recall",
             user_id: parseInt(user_id),
             message_id
diff --git a/src/onebot11/events/types.ts b/src/onebot11/events/types.ts
index e1d9224..5902114 100644
--- a/src/onebot11/events/types.ts
+++ b/src/onebot11/events/types.ts
@@ -16,12 +16,12 @@ export interface OB11MetaEvent extends OB11EventBase{
     meta_event_type: "lifecycle" | "heartbeat"
 }
 
-export interface OB11NoticeBase extends OB11EventBase{
+export interface OB11NoticeEvent extends OB11EventBase{
     post_type: OB11EventPostType.NOTICE
     notice_type: "group_admin" | "group_decrease" | "group_increase" | "group_ban" | "friend_add" | "group_recall" | "friend_recall"
 }
 
-interface OB11GroupNoticeBase extends OB11NoticeBase{
+interface OB11GroupNoticeBase extends OB11NoticeEvent{
     group_id: number
     user_id: number
 }
@@ -49,7 +49,7 @@ export interface OB11GroupRecallNoticeEvent extends OB11GroupNoticeBase{
     message_id: number
 }
 
-export interface OB11FriendRecallNoticeEvent extends OB11NoticeBase{
+export interface OB11FriendRecallNoticeEvent extends OB11NoticeEvent{
     notice_type: "friend_recall"
     user_id: number
     message_id: number
diff --git a/src/onebot11/server.ts b/src/onebot11/server.ts
index ec43b40..a11ef2f 100644
--- a/src/onebot11/server.ts
+++ b/src/onebot11/server.ts
@@ -12,7 +12,7 @@ import { OB11Response } from "./actions/utils";
 import { ActionName } from "./actions/types";
 import BaseAction from "./actions/BaseAction";
 import { OB11Constructor } from "./constructor";
-import { OB11LifeCycleEvent, OB11MetaEvent } from "./events/types";
+import { OB11EventBase, OB11LifeCycleEvent, OB11MetaEvent, OB11NoticeEvent } from "./events/types";
 
 let wsServer: websocket.Server = null;
 let accessToken = ""
@@ -60,8 +60,6 @@ const expressAuthorize = (req: Request, res: Response, next: () => void) => {
             return res.status(403).send(JSON.stringify({message: 'token verify failed!'}));
         }
     }
-
-
     next();
 
 };
@@ -87,7 +85,7 @@ let wsEventClients: websocket.WebSocket[] = [];
 type RouterHandler = (payload: any) => Promise<OB11Return<any>>
 let routers: Record<string, RouterHandler> = {};
 
-function wsReply(wsClient: websocket.WebSocket, data: OB11Return<any> | OB11Message | OB11MetaEvent) {
+function wsReply(wsClient: websocket.WebSocket, data: OB11Return<any> | PostMsgType) {
     try {
         wsClient.send(JSON.stringify(data))
         log("ws 消息上报", data)
@@ -163,14 +161,14 @@ export function startWSServer(port: number) {
             log("event上报ws客户端已连接")
             wsEventClients.push(ws)
             try {
-                wsReply(ws, OB11Constructor.lifeCycle())
+                wsReply(ws, OB11Constructor.lifeCycleEvent())
             }catch (e){
                 log("发送生命周期失败", e)
             }
             // 心跳
             let wsHeart = setInterval(()=>{
                 if (wsEventClients.find(c => c == ws)){
-                    wsReply(ws, OB11Constructor.heart())
+                    wsReply(ws, OB11Constructor.heartEvent())
                 }
             }, heartInterval)
             ws.on("close", () => {
@@ -182,11 +180,13 @@ export function startWSServer(port: number) {
     })
 }
 
+type PostMsgType = OB11Message | OB11MetaEvent | OB11NoticeEvent
 
-export function postMsg(msg: OB11Message) {
+export function postMsg(msg: PostMsgType) {
     const {reportSelfMessage} = getConfigUtil().getConfig()
+    // 判断msg是否是event
     if (!reportSelfMessage) {
-        if (msg.user_id.toString() == selfInfo.uin) {
+        if ((msg as OB11Message).user_id.toString() == selfInfo.uin) {
             return
         }
     }
@@ -231,10 +231,10 @@ function registerRouter(action: string, handle: (payload: any) => Promise<any>)
     }
 
     expressAPP.post(url, expressAuthorize, (req: Request, res: Response) => {
-        _handle(res, req.body).then()
+        _handle(res, req.body || {}).then()
     });
     expressAPP.get(url, expressAuthorize, (req: Request, res: Response) => {
-        _handle(res, req.query as any).then()
+        _handle(res, req.query as any || {}).then()
     });
     routers[action] = handle
 }
diff --git a/src/onebot11/types.ts b/src/onebot11/types.ts
index 32e5248..30ed198 100644
--- a/src/onebot11/types.ts
+++ b/src/onebot11/types.ts
@@ -1,18 +1,18 @@
 import { AtType, RawMessage } from "../ntqqapi/types";
 
-export interface OB11User{
+export interface OB11User {
     user_id: number;
     nickname: string;
     remark?: string
 }
 
-export enum OB11UserSex{
+export enum OB11UserSex {
     male = "male",
     female = "female",
     unknown = "unknown"
 }
 
-export enum OB11GroupMemberRole{
+export enum OB11GroupMemberRole {
     owner = "owner",
     admin = "admin",
     member = "member",
@@ -32,7 +32,7 @@ export interface OB11GroupMember {
     title?: string
 }
 
-export interface OB11Group{
+export interface OB11Group {
     group_id: number
     group_name: string
     member_count?: number
@@ -92,7 +92,8 @@ export interface OB11Return<DataType> {
     echo?: string
 }
 
-export interface OB11SendMsgReturn extends OB11Return<{message_id: string}>{}
+export interface OB11SendMsgReturn extends OB11Return<{ message_id: string }> {
+}
 
 export enum OB11MessageDataType {
     text = "text",
@@ -100,7 +101,8 @@ export enum OB11MessageDataType {
     voice = "record",
     at = "at",
     reply = "reply",
-    json = "json"
+    json = "json",
+    face = "face"
 }
 
 export type OB11MessageData = {
@@ -132,6 +134,11 @@ export type OB11MessageData = {
     data: {
         id: string,
     }
+} | {
+    type: OB11MessageDataType.face,
+    data: {
+        id: string
+    }
 }
 
 export interface OB11PostSendMsg {

From ee4206c33d0ea6f1d103cefd65850c7d7623c24d Mon Sep 17 00:00:00 2001
From: linyuchen <lin.yu.chen@hotmail.com>
Date: Sat, 17 Feb 2024 20:10:08 +0800
Subject: [PATCH 11/12] docs: update readme

---
 README.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/README.md b/README.md
index 81c6140..a951b63 100644
--- a/README.md
+++ b/README.md
@@ -36,6 +36,7 @@ LiteLoaderQQNT的OneBot11协议插件
 
 消息格式支持:
 - [x] 文字
+- [x] 表情
 - [x] 图片
 - [x] 引用消息
 - [x] @群成员

From e5edfd78eb2c31faf0ba49c83d46b556cb5f49d4 Mon Sep 17 00:00:00 2001
From: linyuchen <lin.yu.chen@hotmail.com>
Date: Sat, 17 Feb 2024 20:19:09 +0800
Subject: [PATCH 12/12] docs: update readme

---
 README.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/README.md b/README.md
index a951b63..2c6c8fe 100644
--- a/README.md
+++ b/README.md
@@ -33,6 +33,7 @@ LiteLoaderQQNT的OneBot11协议插件
 - [x] 撤回消息
 - [x] 上报好友消息
 - [x] 上报群消息
+- [x] 上报好友、群消息撤回
 
 消息格式支持:
 - [x] 文字
@@ -59,6 +60,7 @@ LiteLoaderQQNT的OneBot11协议插件
 - [x] get_friend_list
 - [x] get_msg
 - [x] get_version_info
+- [x] get_status
 - [x] can_send_image
 - [x] can_send_record