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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] =?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/16] =?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/16] 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/16] 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/16] 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/16] 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
 

From 9b8b9a203cd103a0ecd2ed0452cdb2f5f1987c07 Mon Sep 17 00:00:00 2001
From: linyuchen <lin.yu.chen@hotmail.com>
Date: Sat, 17 Feb 2024 23:38:18 +0800
Subject: [PATCH 13/16] fix: group member_count & member_max_count

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

diff --git a/manifest.json b/manifest.json
index 521ef5a..4d0e65c 100644
--- a/manifest.json
+++ b/manifest.json
@@ -4,7 +4,7 @@
   "name": "LLOneBot",
   "slug": "LLOneBot",
   "description": "LiteLoaderQQNT的OneBotApi",
-  "version": "3.3.0",
+  "version": "3.3.1",
   "thumbnail": "./icon.png",
   "authors": [
     {
diff --git a/src/common/data.ts b/src/common/data.ts
index 0027f11..dcc559d 100644
--- a/src/common/data.ts
+++ b/src/common/data.ts
@@ -88,5 +88,5 @@ export function getStrangerByUin(uin: string) {
     }
 }
 
-export const version = "v3.3.0"
+export const version = "v3.3.1"
 export const heartInterval = 15000 // 毫秒
\ No newline at end of file
diff --git a/src/onebot11/constructor.ts b/src/onebot11/constructor.ts
index b404918..d06cbd3 100644
--- a/src/onebot11/constructor.ts
+++ b/src/onebot11/constructor.ts
@@ -183,7 +183,9 @@ export class OB11Constructor extends OB11EventConstructor{
     static group(group: Group): OB11Group {
         return {
             group_id: parseInt(group.groupCode),
-            group_name: group.groupName
+            group_name: group.groupName,
+            member_count: group.memberCount,
+            max_member_count: group.maxMember
         }
     }
 

From 9b0f2d098368ae258f018ed6e6e173d18d1a67ee Mon Sep 17 00:00:00 2001
From: Disy <disymayufei@yeah.net>
Date: Mon, 19 Feb 2024 13:31:57 +0800
Subject: [PATCH 14/16] chore: Conflict resolution

---
 manifest.json                                 |   2 +-
 src/common/config.ts                          |  20 +-
 src/common/data.ts                            |   5 +-
 src/common/utils.ts                           |   1 -
 src/global.d.ts                               |   2 +-
 src/main/ipcsend.ts                           |   2 +-
 src/main/main.ts                              |  46 +--
 src/ntqqapi/constructor.ts                    |   8 +-
 src/ntqqapi/hook.ts                           |  27 +-
 src/ntqqapi/ntcall.ts                         |  14 +-
 src/onebot11/ReconnectingWebsocket.ts         |   8 +-
 src/onebot11/actions/CanSendImage.ts          |   2 +-
 src/onebot11/actions/CanSendRecord.ts         |   2 +-
 src/onebot11/actions/DeleteMsg.ts             |   6 +-
 src/onebot11/actions/GetFriendList.ts         |   8 +-
 src/onebot11/actions/GetGroupInfo.ts          |   8 +-
 src/onebot11/actions/GetGroupList.ts          |   9 +-
 src/onebot11/actions/GetGroupMemberInfo.ts    |   8 +-
 src/onebot11/actions/GetGroupMemberList.ts    |  10 +-
 src/onebot11/actions/GetLoginInfo.ts          |   8 +-
 src/onebot11/actions/GetMsg.ts                |   8 +-
 src/onebot11/actions/GetStatus.ts             |   2 +-
 src/onebot11/actions/GetVersionInfo.ts        |   4 +-
 src/onebot11/actions/SendGroupMsg.ts          |   2 +-
 src/onebot11/actions/SendMsg.ts               |  16 +-
 src/onebot11/actions/SendPrivateMsg.ts        |   2 +-
 src/onebot11/actions/index.ts                 |   2 +-
 src/onebot11/actions/types.ts                 |   4 +-
 src/onebot11/actions/utils.ts                 |   3 +-
 src/onebot11/constructor.ts                   |  20 +-
 src/onebot11/event/BaseEvent.ts               |  10 -
 src/onebot11/event/BaseMessageEvent.ts        |   8 -
 src/onebot11/event/OB11BaseEvent.ts           |  15 +
 src/onebot11/event/manager.ts                 |  30 +-
 .../event/message/OB11BaseMessageEvent.ts     |   5 +
 src/onebot11/event/meta/OB11BaseMetaEvent.ts  |   6 +
 src/onebot11/event/meta/OB11HeartbeatEvent.ts |  21 ++
 src/onebot11/event/meta/OB11LifeCycleEvent.ts |  17 +
 .../event/notice/OB11BaseNoticeEvent.ts       |   5 +
 .../notice/OB11FriendRecallNoticeEvent.ts     |  13 +
 .../event/notice/OB11GroupAdminNoticeEvent.ts |   6 +
 .../OB11GroupDecreaseEvent.ts}                |  12 +-
 .../OB11GroupIncreaseEvent.ts}                |  15 +-
 .../event/notice/OB11GroupNoticeEvent.ts      |   6 +
 .../notice/OB11GroupRecallNoticeEvent.ts      |  16 +
 src/onebot11/events/constructor.ts            |  58 ---
 src/onebot11/events/types.ts                  |  67 ----
 src/onebot11/server.ts                        | 349 +++++++-----------
 src/onebot11/types.ts                         |  11 +-
 src/onebot11/utils.ts                         |   6 +-
 src/preload.ts                                |   6 +-
 51 files changed, 385 insertions(+), 556 deletions(-)
 delete mode 100644 src/onebot11/event/BaseEvent.ts
 delete mode 100644 src/onebot11/event/BaseMessageEvent.ts
 create mode 100644 src/onebot11/event/OB11BaseEvent.ts
 create mode 100644 src/onebot11/event/message/OB11BaseMessageEvent.ts
 create mode 100644 src/onebot11/event/meta/OB11BaseMetaEvent.ts
 create mode 100644 src/onebot11/event/meta/OB11HeartbeatEvent.ts
 create mode 100644 src/onebot11/event/meta/OB11LifeCycleEvent.ts
 create mode 100644 src/onebot11/event/notice/OB11BaseNoticeEvent.ts
 create mode 100644 src/onebot11/event/notice/OB11FriendRecallNoticeEvent.ts
 create mode 100644 src/onebot11/event/notice/OB11GroupAdminNoticeEvent.ts
 rename src/onebot11/event/{GroupDecreaseEvent.ts => notice/OB11GroupDecreaseEvent.ts} (54%)
 rename src/onebot11/event/{GroupIncreaseEvent.ts => notice/OB11GroupIncreaseEvent.ts} (53%)
 create mode 100644 src/onebot11/event/notice/OB11GroupNoticeEvent.ts
 create mode 100644 src/onebot11/event/notice/OB11GroupRecallNoticeEvent.ts
 delete mode 100644 src/onebot11/events/constructor.ts
 delete mode 100644 src/onebot11/events/types.ts

diff --git a/manifest.json b/manifest.json
index 4d0e65c..4ffec7f 100644
--- a/manifest.json
+++ b/manifest.json
@@ -4,7 +4,7 @@
   "name": "LLOneBot",
   "slug": "LLOneBot",
   "description": "LiteLoaderQQNT的OneBotApi",
-  "version": "3.3.1",
+  "version": "3.4.0",
   "thumbnail": "./icon.png",
   "authors": [
     {
diff --git a/src/common/config.ts b/src/common/config.ts
index 24d1033..762a04b 100644
--- a/src/common/config.ts
+++ b/src/common/config.ts
@@ -1,4 +1,4 @@
-import { Config } from "./types";
+import {Config} from "./types";
 
 const fs = require("fs");
 
@@ -14,7 +14,7 @@ export class ConfigUtil {
             httpPort: 3000,
             httpHosts: [],
             wsPort: 3001,
-            wsHosts: []
+            wsHosts: [],
             token: "",
             enableBase64: false,
             debug: false,
@@ -29,16 +29,20 @@ export class ConfigUtil {
             try {
                 jsonData = JSON.parse(data)
             }
-            catch (e){
-
+            catch (e) {}
+            if (!jsonData.httpHosts) {
+                jsonData.httpHosts = []
             }
-            if (!jsonData.hosts) {
-                jsonData.hosts = []
+            if (!jsonData.wsHosts) {
+                jsonData.wsHosts = []
             }
-            if (!jsonData.wsPort){
+            if (!jsonData.wsPort) {
                 jsonData.wsPort = 3001
             }
-            if (!jsonData.token){
+            if (!jsonData.httpPort) {
+                jsonData.httpPort = 3000
+            }
+            if (!jsonData.token) {
                 jsonData.token = ""
             }
             return jsonData;
diff --git a/src/common/data.ts b/src/common/data.ts
index dcc559d..60c930d 100644
--- a/src/common/data.ts
+++ b/src/common/data.ts
@@ -1,6 +1,5 @@
-import { NTQQApi } from '../ntqqapi/ntcall';
-import { Friend, Group, GroupMember, RawMessage, SelfInfo } from "../ntqqapi/types";
-import { log } from "./utils";
+import {NTQQApi} from '../ntqqapi/ntcall';
+import {Friend, Group, GroupMember, RawMessage, SelfInfo} from "../ntqqapi/types";
 
 export let groups: Group[] = []
 export let friends: Friend[] = []
diff --git a/src/common/utils.ts b/src/common/utils.ts
index d5ea957..2a1f67d 100644
--- a/src/common/utils.ts
+++ b/src/common/utils.ts
@@ -2,7 +2,6 @@ import * as path from "path";
 import {selfInfo} from "./data";
 import {ConfigUtil} from "./config";
 import util from "util";
-import { sendLog } from '../main/ipcsend';
 
 const fs = require('fs');
 
diff --git a/src/global.d.ts b/src/global.d.ts
index d410524..df0ba6e 100644
--- a/src/global.d.ts
+++ b/src/global.d.ts
@@ -1,4 +1,4 @@
-import { Config } from "./common/types";
+import {Config} from "./common/types";
 
 
 declare var llonebot: {
diff --git a/src/main/ipcsend.ts b/src/main/ipcsend.ts
index b40da4f..56eeb25 100644
--- a/src/main/ipcsend.ts
+++ b/src/main/ipcsend.ts
@@ -1,5 +1,5 @@
 import {webContents} from 'electron';
-import { CHANNEL_LOG } from '../common/channels';
+import {CHANNEL_LOG} from '../common/channels';
 
 
 function sendIPCMsg(channel: string, ...data: any) {
diff --git a/src/main/main.ts b/src/main/main.ts
index efcb816..009b7aa 100644
--- a/src/main/main.ts
+++ b/src/main/main.ts
@@ -1,16 +1,18 @@
 // 运行在 Electron 主进程 下的插件入口
 
-import { BrowserWindow, ipcMain } from 'electron';
+import {BrowserWindow, ipcMain} from 'electron';
 
-import { Config } from "../common/types";
-import {initWebsocket, postMsg, startExpress, startWebsocketServer} from "../onebot11/server";
-import { CHANNEL_GET_CONFIG, CHANNEL_LOG, CHANNEL_SET_CONFIG, } from "../common/channels";
-import { CONFIG_DIR, getConfigUtil, log } from "../common/utils";
-import { addHistoryMsg, getGroupMember, msgHistory, selfInfo, uidMaps } from "../common/data";
-import { hookNTQQApiReceive, ReceiveCmd, registerReceiveHook } from "../ntqqapi/hook";
-import { OB11Constructor } from "../onebot11/constructor";
-import { NTQQApi } from "../ntqqapi/ntcall";
-import { ChatType, RawMessage } from "../ntqqapi/types";
+import {Config} from "../common/types";
+import {postMsg, setToken, startHTTPServer, initWebsocket} from "../onebot11/server";
+import {CHANNEL_GET_CONFIG, CHANNEL_LOG, CHANNEL_SET_CONFIG,} from "../common/channels";
+import {CONFIG_DIR, getConfigUtil, log} from "../common/utils";
+import {addHistoryMsg, getGroupMember, msgHistory, selfInfo} from "../common/data";
+import {hookNTQQApiReceive, ReceiveCmd, registerReceiveHook} from "../ntqqapi/hook";
+import {OB11Constructor} from "../onebot11/constructor";
+import {NTQQApi} from "../ntqqapi/ntcall";
+import {ChatType, RawMessage} from "../ntqqapi/types";
+import {OB11FriendRecallNoticeEvent} from "../onebot11/event/notice/OB11FriendRecallNoticeEvent";
+import {OB11GroupRecallNoticeEvent} from "../onebot11/event/notice/OB11GroupRecallNoticeEvent";
 
 const fs = require('fs');
 
@@ -32,11 +34,11 @@ function onLoad() {
     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.httpPort != oldConfig.httpPort) {
+            startHTTPServer(arg.httpPort)
         }
         if (arg.wsPort != oldConfig.wsPort) {
-            startWebsocketServer(arg.wsPort)
+            initWebsocket(arg.wsPort)
         }
         if (arg.token != oldConfig.token) {
             setToken(arg.token);
@@ -87,8 +89,8 @@ function onLoad() {
                         continue
                     }
                     if (message.chatType == ChatType.friend) {
-                        const friendRecallEvent = OB11Constructor.friendRecallEvent(message.senderUin, oriMessage.msgShortId)
-                        postMsg(friendRecallEvent)
+                        const friendRecallEvent = new OB11FriendRecallNoticeEvent(parseInt(message.senderUin), oriMessage.msgShortId);
+                        postMsg(friendRecallEvent);
                     } else if (message.chatType == ChatType.group) {
                         let operatorId = message.senderUin
                         for (const element of message.elements) {
@@ -96,13 +98,14 @@ function onLoad() {
                             const operator = await getGroupMember(message.peerUin, null, operatorUid)
                             operatorId = operator.uin
                         }
-                        const groupRecallEvent = OB11Constructor.groupRecallEvent(
-                            message.peerUin,
-                            message.senderUin,
-                            operatorId,
+                        const groupRecallEvent = new OB11GroupRecallNoticeEvent(
+                            parseInt(message.peerUin),
+                            parseInt(message.senderUin),
+                            parseInt(operatorId),
                             oriMessage.msgShortId
                         )
-                        postMsg(groupRecallEvent)
+
+                        postMsg(groupRecallEvent);
                     }
                     continue
                 }
@@ -125,8 +128,7 @@ function onLoad() {
       
         const config = getConfigUtil().getConfig()
         startHTTPServer(config.httpPort)
-        startWebsocketServer(config.wsPort);
-        initWebsocket();
+        initWebsocket(config.wsPort);
         setToken(config.token)
         log("LLOneBot start")
     }
diff --git a/src/ntqqapi/constructor.ts b/src/ntqqapi/constructor.ts
index a966026..3267e27 100644
--- a/src/ntqqapi/constructor.ts
+++ b/src/ntqqapi/constructor.ts
@@ -1,13 +1,13 @@
 import {
+    AtType,
     ElementType,
+    SendFaceElement,
     SendPicElement,
     SendPttElement,
     SendReplyElement,
-    SendTextElement,
-    AtType,
-    SendFaceElement
+    SendTextElement
 } from "./types";
-import { NTQQApi } from "./ntcall";
+import {NTQQApi} from "./ntcall";
 
 
 export class SendMsgElementConstructor {
diff --git a/src/ntqqapi/hook.ts b/src/ntqqapi/hook.ts
index b3c5e4a..8f8b63b 100644
--- a/src/ntqqapi/hook.ts
+++ b/src/ntqqapi/hook.ts
@@ -1,15 +1,12 @@
 import {BrowserWindow} from 'electron';
 import {log, sleep} from "../common/utils";
 import {NTQQApi, NTQQApiClass, sendMessagePool} from "./ntcall";
-import {Group, GroupMember, RawMessage, User} from "./types";
+import {Group, RawMessage, User} from "./types";
 import {addHistoryMsg, friends, groups, msgHistory} from "../common/data";
 import {v4 as uuidv4} from 'uuid';
-import {callEvent, EventType} from "../onebot11/event/manager";
-import {OB11Message} from "../onebot11/types";
-import {OB11Constructor} from "../onebot11/constructor";
-import BaseMessageEvent from "../onebot11/event/BaseMessageEvent";
-import GroupDecreaseEvent from "../onebot11/event/GroupDecreaseEvent";
-import GroupIncreaseEvent from "../onebot11/event/GroupIncreaseEvent";
+import {OB11GroupDecreaseEvent} from "../onebot11/event/notice/OB11GroupDecreaseEvent";
+import {OB11GroupIncreaseEvent} from "../onebot11/event/notice/OB11GroupIncreaseEvent";
+import {postMsg} from "../onebot11/server";
 
 export let hookApiCallbacks: Record<string, (apiReturn: any) => void> = {}
 
@@ -140,7 +137,7 @@ async function processGroupEvent(payload) {
 
                     for (const member of oldMembers) {
                         if (!newMembersSet.has(member.uin)) {
-                            callEvent(new GroupDecreaseEvent(group.groupCode, parseInt(member.uin)));
+                            postMsg(new OB11GroupDecreaseEvent(group.groupCode, parseInt(member.uin)));
                             break;
                         }
                     }
@@ -159,7 +156,7 @@ async function processGroupEvent(payload) {
                     group.members = newMembers;
                     for (const member of newMembers) {
                         if (!oldMembersSet.has(member.uin)) {
-                            callEvent(new GroupIncreaseEvent(group.groupCode, parseInt(member.uin)));
+                            postMsg(new OB11GroupIncreaseEvent(group.groupCode, parseInt(member.uin)));
                             break;
                         }
                     }
@@ -211,22 +208,10 @@ registerReceiveHook<{
     }
 })
 
-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) {
         // log("收到新消息,push到历史记录", message)
         addHistoryMsg(message)
-
-        OB11Constructor.message(message).then(
-            function (message) {
-                callEvent<OB11Message>(new BaseMessageEvent(), message);
-            }
-        );
     }
     const msgIds = Object.keys(msgHistory);
     if (msgIds.length > 30000) {
diff --git a/src/ntqqapi/ntcall.ts b/src/ntqqapi/ntcall.ts
index 7d1e5ba..01de63d 100644
--- a/src/ntqqapi/ntcall.ts
+++ b/src/ntqqapi/ntcall.ts
@@ -1,12 +1,8 @@
-import { ipcMain } from "electron";
-import { v4 as uuidv4 } from "uuid";
-import { ReceiveCmd, hookApiCallbacks, registerReceiveHook, removeReceiveHook } from "./hook";
-import { log } from "../common/utils";
-import { ChatType, Friend, PicElement, SelfInfo, User } from "./types";
-import { Group } from "./types";
-import { GroupMember } from "./types";
-import { RawMessage } from "./types";
-import { SendMessageElement } from "./types";
+import {ipcMain} from "electron";
+import {v4 as uuidv4} from "uuid";
+import {hookApiCallbacks, ReceiveCmd, registerReceiveHook, removeReceiveHook} from "./hook";
+import {log} from "../common/utils";
+import {ChatType, Friend, Group, GroupMember, RawMessage, SelfInfo, SendMessageElement, User} from "./types";
 import * as fs from "fs";
 
 interface IPCReceiveEvent {
diff --git a/src/onebot11/ReconnectingWebsocket.ts b/src/onebot11/ReconnectingWebsocket.ts
index 032a5b9..5bb77e2 100644
--- a/src/onebot11/ReconnectingWebsocket.ts
+++ b/src/onebot11/ReconnectingWebsocket.ts
@@ -1,3 +1,5 @@
+import {log} from "../common/utils";
+
 const WebSocket = require("ws");
 
 class ReconnectingWebsocket {
@@ -35,12 +37,12 @@ class ReconnectingWebsocket {
             perMessageDeflate: false
         });
 
-        console.log("Trying to connect to the websocket server: " + this.url);
+        log("Trying to connect to the websocket server: " + this.url);
 
         const instance = this;
 
         this.websocket.on("open", function open() {
-            console.log("Connected to the websocket server: " + instance.url);
+            log("Connected to the websocket server: " + instance.url);
             instance.onopen();
         });
 
@@ -53,7 +55,7 @@ class ReconnectingWebsocket {
         this.websocket.on("ping", this.heartbeat);
 
         this.websocket.on("close", function close() {
-            console.log("The websocket connection: " + instance.url + " closed, trying reconnecting...");
+            log("The websocket connection: " + instance.url + " closed, trying reconnecting...");
             instance.onclose();
 
             setTimeout(instance.reconnect, 3000);
diff --git a/src/onebot11/actions/CanSendImage.ts b/src/onebot11/actions/CanSendImage.ts
index f8c6b2c..d018808 100644
--- a/src/onebot11/actions/CanSendImage.ts
+++ b/src/onebot11/actions/CanSendImage.ts
@@ -1,4 +1,4 @@
-import { ActionName } from "./types";
+import {ActionName} from "./types";
 import CanSendRecord from "./CanSendRecord";
 
 interface ReturnType{
diff --git a/src/onebot11/actions/CanSendRecord.ts b/src/onebot11/actions/CanSendRecord.ts
index 4d94d17..6019253 100644
--- a/src/onebot11/actions/CanSendRecord.ts
+++ b/src/onebot11/actions/CanSendRecord.ts
@@ -1,5 +1,5 @@
 import BaseAction from "./BaseAction";
-import { ActionName } from "./types";
+import {ActionName} from "./types";
 
 interface ReturnType{
     yes: boolean
diff --git a/src/onebot11/actions/DeleteMsg.ts b/src/onebot11/actions/DeleteMsg.ts
index 7c999c3..8ed6c1c 100644
--- a/src/onebot11/actions/DeleteMsg.ts
+++ b/src/onebot11/actions/DeleteMsg.ts
@@ -1,7 +1,7 @@
-import { ActionName } from "./types";
+import {ActionName} from "./types";
 import BaseAction from "./BaseAction";
-import { NTQQApi } from "../../ntqqapi/ntcall";
-import { getHistoryMsgByShortId, msgHistory } from "../../common/data";
+import {NTQQApi} from "../../ntqqapi/ntcall";
+import {getHistoryMsgByShortId} from "../../common/data";
 
 interface Payload {
     message_id: number
diff --git a/src/onebot11/actions/GetFriendList.ts b/src/onebot11/actions/GetFriendList.ts
index 6827728..4aff82a 100644
--- a/src/onebot11/actions/GetFriendList.ts
+++ b/src/onebot11/actions/GetFriendList.ts
@@ -1,8 +1,8 @@
-import { OB11User } from '../types';
-import { OB11Constructor } from "../constructor";
-import { friends } from "../../common/data";
+import {OB11User} from '../types';
+import {OB11Constructor} from "../constructor";
+import {friends} from "../../common/data";
 import BaseAction from "./BaseAction";
-import { ActionName } from "./types";
+import {ActionName} from "./types";
 
 
 class GetFriendList extends BaseAction<null, OB11User[]> {
diff --git a/src/onebot11/actions/GetGroupInfo.ts b/src/onebot11/actions/GetGroupInfo.ts
index 0ff09a1..952972c 100644
--- a/src/onebot11/actions/GetGroupInfo.ts
+++ b/src/onebot11/actions/GetGroupInfo.ts
@@ -1,8 +1,8 @@
-import { OB11Group } from '../types';
-import { getGroup } from "../../common/data";
-import { OB11Constructor } from "../constructor";
+import {OB11Group} from '../types';
+import {getGroup} from "../../common/data";
+import {OB11Constructor} from "../constructor";
 import BaseAction from "./BaseAction";
-import { ActionName } from "./types";
+import {ActionName} from "./types";
 
 interface PayloadType {
     group_id: number
diff --git a/src/onebot11/actions/GetGroupList.ts b/src/onebot11/actions/GetGroupList.ts
index d56cf12..bfbdc71 100644
--- a/src/onebot11/actions/GetGroupList.ts
+++ b/src/onebot11/actions/GetGroupList.ts
@@ -1,9 +1,8 @@
-import { OB11Group } from '../types';
-import { OB11Constructor } from "../constructor";
-import { groups } from "../../common/data";
+import {OB11Group} from '../types';
+import {OB11Constructor} from "../constructor";
+import {groups} from "../../common/data";
 import BaseAction from "./BaseAction";
-import { ActionName } from "./types";
-
+import {ActionName} from "./types";
 
 
 class GetGroupList extends BaseAction<null, OB11Group[]> {
diff --git a/src/onebot11/actions/GetGroupMemberInfo.ts b/src/onebot11/actions/GetGroupMemberInfo.ts
index 7010e9a..7546c1c 100644
--- a/src/onebot11/actions/GetGroupMemberInfo.ts
+++ b/src/onebot11/actions/GetGroupMemberInfo.ts
@@ -1,8 +1,8 @@
-import { OB11GroupMember } from '../types';
-import { getGroupMember } from "../../common/data";
-import { OB11Constructor } from "../constructor";
+import {OB11GroupMember} from '../types';
+import {getGroupMember} from "../../common/data";
+import {OB11Constructor} from "../constructor";
 import BaseAction from "./BaseAction";
-import { ActionName } from "./types";
+import {ActionName} from "./types";
 
 
 export interface PayloadType {
diff --git a/src/onebot11/actions/GetGroupMemberList.ts b/src/onebot11/actions/GetGroupMemberList.ts
index f9d9b55..0f97973 100644
--- a/src/onebot11/actions/GetGroupMemberList.ts
+++ b/src/onebot11/actions/GetGroupMemberList.ts
@@ -1,9 +1,9 @@
-import { OB11GroupMember } from '../types';
-import { getGroup } from "../../common/data";
-import { NTQQApi } from "../../ntqqapi/ntcall";
-import { OB11Constructor } from "../constructor";
+import {OB11GroupMember} from '../types';
+import {getGroup} from "../../common/data";
+import {NTQQApi} from "../../ntqqapi/ntcall";
+import {OB11Constructor} from "../constructor";
 import BaseAction from "./BaseAction";
-import { ActionName } from "./types";
+import {ActionName} from "./types";
 
 export interface PayloadType {
     group_id: number
diff --git a/src/onebot11/actions/GetLoginInfo.ts b/src/onebot11/actions/GetLoginInfo.ts
index 56dbfe1..ab8694f 100644
--- a/src/onebot11/actions/GetLoginInfo.ts
+++ b/src/onebot11/actions/GetLoginInfo.ts
@@ -1,8 +1,8 @@
-import { OB11User } from '../types';
-import { OB11Constructor } from "../constructor";
-import { selfInfo } from "../../common/data";
+import {OB11User} from '../types';
+import {OB11Constructor} from "../constructor";
+import {selfInfo} from "../../common/data";
 import BaseAction from "./BaseAction";
-import { ActionName } from "./types";
+import {ActionName} from "./types";
 
 
 class GetLoginInfo extends BaseAction<null, OB11User> {
diff --git a/src/onebot11/actions/GetMsg.ts b/src/onebot11/actions/GetMsg.ts
index e31c887..0be0ddf 100644
--- a/src/onebot11/actions/GetMsg.ts
+++ b/src/onebot11/actions/GetMsg.ts
@@ -1,8 +1,8 @@
-import { getHistoryMsgByShortId, msgHistory } from "../../common/data";
-import { OB11Message } from '../types';
-import { OB11Constructor } from "../constructor";
+import {getHistoryMsgByShortId} from "../../common/data";
+import {OB11Message} from '../types';
+import {OB11Constructor} from "../constructor";
 import BaseAction from "./BaseAction";
-import { ActionName } from "./types";
+import {ActionName} from "./types";
 
 
 export interface PayloadType {
diff --git a/src/onebot11/actions/GetStatus.ts b/src/onebot11/actions/GetStatus.ts
index 2df98db..c3f038c 100644
--- a/src/onebot11/actions/GetStatus.ts
+++ b/src/onebot11/actions/GetStatus.ts
@@ -1,6 +1,6 @@
 import BaseAction from "./BaseAction";
 import {OB11Status} from "../types";
-import { ActionName } from "./types";
+import {ActionName} from "./types";
 
 
 export default class GetStatus extends BaseAction<any, OB11Status> {
diff --git a/src/onebot11/actions/GetVersionInfo.ts b/src/onebot11/actions/GetVersionInfo.ts
index 5fadb22..83ba16b 100644
--- a/src/onebot11/actions/GetVersionInfo.ts
+++ b/src/onebot11/actions/GetVersionInfo.ts
@@ -1,7 +1,7 @@
 import BaseAction from "./BaseAction";
-import { OB11Version } from "../types";
+import {OB11Version} from "../types";
 import {version} from "../../common/data";
-import { ActionName } from "./types";
+import {ActionName} from "./types";
 
 export default class GetVersionInfo extends BaseAction<any, OB11Version>{
     actionName = ActionName.GetVersionInfo
diff --git a/src/onebot11/actions/SendGroupMsg.ts b/src/onebot11/actions/SendGroupMsg.ts
index 13df317..3bb09b5 100644
--- a/src/onebot11/actions/SendGroupMsg.ts
+++ b/src/onebot11/actions/SendGroupMsg.ts
@@ -1,5 +1,5 @@
 import SendMsg from "./SendMsg";
-import { ActionName } from "./types";
+import {ActionName} from "./types";
 
 
 class SendGroupMsg extends SendMsg{
diff --git a/src/onebot11/actions/SendMsg.ts b/src/onebot11/actions/SendMsg.ts
index e7b9700..cc84d23 100644
--- a/src/onebot11/actions/SendMsg.ts
+++ b/src/onebot11/actions/SendMsg.ts
@@ -1,12 +1,12 @@
-import { AtType, ChatType, Group, SendMessageElement } from "../../ntqqapi/types";
-import { addHistoryMsg, friends, getGroup, getHistoryMsgByShortId, getStrangerByUin, } from "../../common/data";
-import { OB11MessageData, OB11MessageDataType, OB11PostSendMsg } from '../types';
-import { NTQQApi, Peer } from "../../ntqqapi/ntcall";
-import { SendMsgElementConstructor } from "../../ntqqapi/constructor";
-import { uri2local } from "../utils";
-import { v4 as uuid4 } from 'uuid';
+import {AtType, ChatType, Group, SendMessageElement} from "../../ntqqapi/types";
+import {addHistoryMsg, friends, getGroup, getHistoryMsgByShortId, getStrangerByUin,} from "../../common/data";
+import {OB11MessageData, OB11MessageDataType, OB11PostSendMsg} from '../types';
+import {NTQQApi, Peer} from "../../ntqqapi/ntcall";
+import {SendMsgElementConstructor} from "../../ntqqapi/constructor";
+import {uri2local} from "../utils";
+import {v4 as uuid4} from 'uuid';
 import BaseAction from "./BaseAction";
-import { ActionName } from "./types";
+import {ActionName} from "./types";
 import * as fs from "fs";
 
 export interface ReturnDataType {
diff --git a/src/onebot11/actions/SendPrivateMsg.ts b/src/onebot11/actions/SendPrivateMsg.ts
index 26a8b22..5ce5b95 100644
--- a/src/onebot11/actions/SendPrivateMsg.ts
+++ b/src/onebot11/actions/SendPrivateMsg.ts
@@ -1,5 +1,5 @@
 import SendMsg from "./SendMsg";
-import { ActionName } from "./types";
+import {ActionName} from "./types";
 
 class SendPrivateMsg extends SendMsg {
     actionName = ActionName.SendPrivateMsg
diff --git a/src/onebot11/actions/index.ts b/src/onebot11/actions/index.ts
index a5c2e42..dfbfa90 100644
--- a/src/onebot11/actions/index.ts
+++ b/src/onebot11/actions/index.ts
@@ -21,7 +21,7 @@ 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(),
diff --git a/src/onebot11/actions/types.ts b/src/onebot11/actions/types.ts
index 39f0b25..1fc5145 100644
--- a/src/onebot11/actions/types.ts
+++ b/src/onebot11/actions/types.ts
@@ -1,5 +1,3 @@
-import GetVersionInfo from "./GetVersionInfo";
-
 export type BaseCheckResult = ValidCheckResult | InvalidCheckResult
 
 export interface ValidCheckResult {
@@ -13,7 +11,7 @@ export interface InvalidCheckResult {
     [k: string | number]: any
 }
 
-export enum ActionName{
+export enum ActionName {
     GetLoginInfo = "get_login_info",
     GetFriendList = "get_friend_list",
     GetGroupInfo = "get_group_info",
diff --git a/src/onebot11/actions/utils.ts b/src/onebot11/actions/utils.ts
index 3d432e1..e87b394 100644
--- a/src/onebot11/actions/utils.ts
+++ b/src/onebot11/actions/utils.ts
@@ -6,8 +6,7 @@ export class OB11Response {
             status: status,
             retcode: retcode,
             data: data,
-            message: message,
-            echo,
+            message: message
         }
     }
     static ok<T>(data: T) {
diff --git a/src/onebot11/constructor.ts b/src/onebot11/constructor.ts
index d06cbd3..2a2c59a 100644
--- a/src/onebot11/constructor.ts
+++ b/src/onebot11/constructor.ts
@@ -1,19 +1,11 @@
-import {
-    OB11MessageDataType,
-    OB11GroupMemberRole,
-    OB11Message,
-    OB11Group,
-    OB11GroupMember,
-    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";
+import {OB11Group, OB11GroupMember, OB11GroupMemberRole, OB11Message, OB11MessageDataType, OB11User} from "./types";
+import {AtType, ChatType, Group, GroupMember, IMAGE_HTTP_HOST, RawMessage, SelfInfo, User} from '../ntqqapi/types';
+import {getFriend, getGroupMember, getHistoryMsgBySeq, selfInfo} from '../common/data';
+import {file2base64, getConfigUtil, log} from "../common/utils";
+import {NTQQApi} from "../ntqqapi/ntcall";
 
 
-export class OB11Constructor extends OB11EventConstructor{
+export class OB11Constructor {
     static async message(msg: RawMessage): Promise<OB11Message> {
 
         const {enableBase64} = getConfigUtil().getConfig()
diff --git a/src/onebot11/event/BaseEvent.ts b/src/onebot11/event/BaseEvent.ts
deleted file mode 100644
index 39e8679..0000000
--- a/src/onebot11/event/BaseEvent.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import {selfInfo} from "../../common/data";
-import {EventType} from "./manager";
-
-class BaseEvent {
-    time = new Date().getTime();
-    self_id = selfInfo.uin;
-    post_type: EventType;
-}
-
-export default BaseEvent;
\ No newline at end of file
diff --git a/src/onebot11/event/BaseMessageEvent.ts b/src/onebot11/event/BaseMessageEvent.ts
deleted file mode 100644
index ba47aee..0000000
--- a/src/onebot11/event/BaseMessageEvent.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import BaseEvent from "./BaseEvent";
-import {EventType} from "./manager";
-
-class BaseMessageEvent extends BaseEvent {
-    post_type = EventType.MESSAGE;
-}
-
-export default BaseMessageEvent
\ No newline at end of file
diff --git a/src/onebot11/event/OB11BaseEvent.ts b/src/onebot11/event/OB11BaseEvent.ts
new file mode 100644
index 0000000..f45577f
--- /dev/null
+++ b/src/onebot11/event/OB11BaseEvent.ts
@@ -0,0 +1,15 @@
+import {selfInfo} from "../../common/data";
+
+export enum EventType {
+    META = "meta_event",
+    REQUEST = "request",
+    NOTICE = "notice",
+    MESSAGE = "message"
+}
+
+
+export abstract class OB11BaseEvent {
+    time = new Date().getTime();
+    self_id = selfInfo.uin;
+    post_type: EventType;
+}
\ No newline at end of file
diff --git a/src/onebot11/event/manager.ts b/src/onebot11/event/manager.ts
index 2bf0de2..931b53d 100644
--- a/src/onebot11/event/manager.ts
+++ b/src/onebot11/event/manager.ts
@@ -1,32 +1,24 @@
-import {selfInfo} from "../../common/data";
-import BaseEvent from "./BaseEvent";
+import * as websocket from "ws";
+import {PostMsgType, wsReply} from "../server";
+import ReconnectingWebsocket from "../ReconnectingWebsocket";
 
 const websocketList = [];
 
-export enum EventType {
-    META = "meta_event",
-    REQUEST = "request",
-    NOTICE = "notice",
-    MESSAGE = "message"
-}
-
-export function registerEventSender(ws) {
+export function registerEventSender(ws: websocket.WebSocket | ReconnectingWebsocket) {
     websocketList.push(ws);
 }
 
-export function unregisterEventSender(ws) {
+export function unregisterEventSender(ws: websocket.WebSocket | ReconnectingWebsocket) {
     let index = websocketList.indexOf(ws);
     if (index !== -1) {
         websocketList.splice(index, 1);
     }
 }
 
-export function callEvent<DataType>(event: BaseEvent, data: DataType = null) {
-
-    const assignedEvent = (data == null ? event : Object.assign(event, data));
-    for (const ws of websocketList) {
-        ws.send(
-            JSON.stringify(assignedEvent)
-        );
-    }
+export function callEvent(event: PostMsgType) {
+    new Promise(() => {
+        for (const ws of websocketList) {
+            wsReply(ws, event);
+        }
+    }).then()
 }
\ No newline at end of file
diff --git a/src/onebot11/event/message/OB11BaseMessageEvent.ts b/src/onebot11/event/message/OB11BaseMessageEvent.ts
new file mode 100644
index 0000000..6018aeb
--- /dev/null
+++ b/src/onebot11/event/message/OB11BaseMessageEvent.ts
@@ -0,0 +1,5 @@
+import {EventType, OB11BaseEvent} from "../OB11BaseEvent";
+
+export abstract class OB11BaseMessageEvent extends OB11BaseEvent {
+    post_type = EventType.MESSAGE;
+}
\ No newline at end of file
diff --git a/src/onebot11/event/meta/OB11BaseMetaEvent.ts b/src/onebot11/event/meta/OB11BaseMetaEvent.ts
new file mode 100644
index 0000000..d3946e6
--- /dev/null
+++ b/src/onebot11/event/meta/OB11BaseMetaEvent.ts
@@ -0,0 +1,6 @@
+import {EventType, OB11BaseEvent} from "../OB11BaseEvent";
+
+export abstract class OB11BaseMetaEvent extends OB11BaseEvent {
+    post_type = EventType.META;
+    meta_event_type: string;
+}
\ No newline at end of file
diff --git a/src/onebot11/event/meta/OB11HeartbeatEvent.ts b/src/onebot11/event/meta/OB11HeartbeatEvent.ts
new file mode 100644
index 0000000..42fa024
--- /dev/null
+++ b/src/onebot11/event/meta/OB11HeartbeatEvent.ts
@@ -0,0 +1,21 @@
+import {OB11BaseMetaEvent} from "./OB11BaseMetaEvent";
+
+interface HeartbeatStatus {
+    online: boolean | null,
+    good: boolean
+}
+
+export class OB11HeartbeatEvent extends OB11BaseMetaEvent {
+    meta_event_type = "heartbeat";
+    status: HeartbeatStatus;
+    interval: number;
+
+    public constructor(isOnline: boolean | null, isGood: boolean, interval: number) {
+        super();
+        this.interval = interval;
+        this.status = {
+            online: isOnline,
+            good: isGood
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/onebot11/event/meta/OB11LifeCycleEvent.ts b/src/onebot11/event/meta/OB11LifeCycleEvent.ts
new file mode 100644
index 0000000..e9243ca
--- /dev/null
+++ b/src/onebot11/event/meta/OB11LifeCycleEvent.ts
@@ -0,0 +1,17 @@
+import {OB11BaseMetaEvent} from "./OB11BaseMetaEvent";
+
+export enum LifeCycleSubType {
+    ENABLE = "enable",
+    DISABLE = "disable",
+    CONNECT = "connect"
+}
+
+export class OB11LifeCycleEvent extends OB11BaseMetaEvent {
+    meta_event_type = "lifecycle";
+    sub_type: LifeCycleSubType;
+
+    public constructor(subType: LifeCycleSubType) {
+        super();
+        this.sub_type = subType;
+    }
+}
\ No newline at end of file
diff --git a/src/onebot11/event/notice/OB11BaseNoticeEvent.ts b/src/onebot11/event/notice/OB11BaseNoticeEvent.ts
new file mode 100644
index 0000000..9429d2c
--- /dev/null
+++ b/src/onebot11/event/notice/OB11BaseNoticeEvent.ts
@@ -0,0 +1,5 @@
+import {EventType, OB11BaseEvent} from "../OB11BaseEvent";
+
+export abstract class OB11BaseNoticeEvent extends OB11BaseEvent {
+    post_type = EventType.NOTICE;
+}
\ No newline at end of file
diff --git a/src/onebot11/event/notice/OB11FriendRecallNoticeEvent.ts b/src/onebot11/event/notice/OB11FriendRecallNoticeEvent.ts
new file mode 100644
index 0000000..9344b44
--- /dev/null
+++ b/src/onebot11/event/notice/OB11FriendRecallNoticeEvent.ts
@@ -0,0 +1,13 @@
+import {OB11BaseNoticeEvent} from "./OB11BaseNoticeEvent";
+
+export class OB11FriendRecallNoticeEvent extends OB11BaseNoticeEvent {
+    notice_type = "friend_recall"
+    user_id: number
+    message_id: number
+
+    public constructor(userId: number, messageId: number) {
+        super();
+        this.user_id = userId;
+        this.message_id = messageId;
+    }
+}
\ No newline at end of file
diff --git a/src/onebot11/event/notice/OB11GroupAdminNoticeEvent.ts b/src/onebot11/event/notice/OB11GroupAdminNoticeEvent.ts
new file mode 100644
index 0000000..f30739e
--- /dev/null
+++ b/src/onebot11/event/notice/OB11GroupAdminNoticeEvent.ts
@@ -0,0 +1,6 @@
+import {OB11BaseNoticeEvent} from "./OB11BaseNoticeEvent";
+
+export class OB11GroupAdminNoticeEvent extends OB11BaseNoticeEvent {
+    notice_type = "group_admin"
+    sub_type: string  // "set" | "unset"
+}
\ No newline at end of file
diff --git a/src/onebot11/event/GroupDecreaseEvent.ts b/src/onebot11/event/notice/OB11GroupDecreaseEvent.ts
similarity index 54%
rename from src/onebot11/event/GroupDecreaseEvent.ts
rename to src/onebot11/event/notice/OB11GroupDecreaseEvent.ts
index b53c326..f9518d2 100644
--- a/src/onebot11/event/GroupDecreaseEvent.ts
+++ b/src/onebot11/event/notice/OB11GroupDecreaseEvent.ts
@@ -1,13 +1,9 @@
-import BaseEvent from "./BaseEvent";
-import {EventType} from "./manager";
+import {OB11GroupNoticeEvent} from "./OB11GroupNoticeEvent";
 
-class GroupDecreaseEvent extends BaseEvent {
-    post_type = EventType.NOTICE;
+export class OB11GroupDecreaseEvent extends OB11GroupNoticeEvent {
     notice_type = "group_decrease";
-    subtype = "leave";  // TODO: 实现其他几种子类型的识别
-    group_id: number;
+    sub_type = "leave";  // TODO: 实现其他几种子类型的识别 ("leave" | "kick" | "kick_me")
     operate_id: number;
-    user_id: number;
 
     constructor(groupId: number, userId: number) {
         super();
@@ -16,5 +12,3 @@ class GroupDecreaseEvent extends BaseEvent {
         this.user_id = userId;
     }
 }
-
-export default GroupDecreaseEvent
\ No newline at end of file
diff --git a/src/onebot11/event/GroupIncreaseEvent.ts b/src/onebot11/event/notice/OB11GroupIncreaseEvent.ts
similarity index 53%
rename from src/onebot11/event/GroupIncreaseEvent.ts
rename to src/onebot11/event/notice/OB11GroupIncreaseEvent.ts
index 9d75b1c..0b07394 100644
--- a/src/onebot11/event/GroupIncreaseEvent.ts
+++ b/src/onebot11/event/notice/OB11GroupIncreaseEvent.ts
@@ -1,13 +1,9 @@
-import BaseEvent from "./BaseEvent";
-import {EventType} from "./manager";
+import {OB11GroupNoticeEvent} from "./OB11GroupNoticeEvent";
 
-class GroupIncreaseEvent extends BaseEvent {
-    post_type = EventType.NOTICE;
+export class OB11GroupIncreaseEvent extends OB11GroupNoticeEvent {
     notice_type = "group_increase";
-    subtype = "approve";  // TODO: 实现其他几种子类型的识别
-    group_id: number;
+    sub_type = "approve";  // TODO: 实现其他几种子类型的识别 ("approve" | "invite")
     operate_id: number;
-    user_id: number;
 
     constructor(groupId: number, userId: number) {
         super();
@@ -15,7 +11,4 @@ class GroupIncreaseEvent extends BaseEvent {
         this.operate_id = userId;  // 实际上不应该这么实现,但是现在还没有办法识别用户是被邀请的,还是主动加入的
         this.user_id = userId;
     }
-}
-
-
-export default GroupIncreaseEvent
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/onebot11/event/notice/OB11GroupNoticeEvent.ts b/src/onebot11/event/notice/OB11GroupNoticeEvent.ts
new file mode 100644
index 0000000..31c1d6c
--- /dev/null
+++ b/src/onebot11/event/notice/OB11GroupNoticeEvent.ts
@@ -0,0 +1,6 @@
+import {OB11BaseNoticeEvent} from "./OB11BaseNoticeEvent";
+
+export abstract class OB11GroupNoticeEvent extends OB11BaseNoticeEvent {
+    group_id: number;
+    user_id: number;
+}
\ No newline at end of file
diff --git a/src/onebot11/event/notice/OB11GroupRecallNoticeEvent.ts b/src/onebot11/event/notice/OB11GroupRecallNoticeEvent.ts
new file mode 100644
index 0000000..83c31d5
--- /dev/null
+++ b/src/onebot11/event/notice/OB11GroupRecallNoticeEvent.ts
@@ -0,0 +1,16 @@
+import {OB11GroupNoticeEvent} from "./OB11GroupNoticeEvent";
+
+export class OB11GroupRecallNoticeEvent extends OB11GroupNoticeEvent {
+    notice_type = "group_recall"
+    operator_id: number
+    message_id: number
+
+    constructor(groupId: number, userId: number, operatorId: number, messageId: number) {
+        super();
+
+        this.group_id = groupId;
+        this.user_id = userId;
+        this.operator_id = operatorId;
+        this.message_id = messageId;
+    }
+}
\ No newline at end of file
diff --git a/src/onebot11/events/constructor.ts b/src/onebot11/events/constructor.ts
deleted file mode 100644
index 06cd850..0000000
--- a/src/onebot11/events/constructor.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import {
-    OB11EventBase,
-    OB11EventPostType, OB11FriendRecallNoticeEvent,
-    OB11GroupRecallNoticeEvent,
-    OB11HeartEvent,
-    OB11LifeCycleEvent, OB11MetaEvent, OB11NoticeEvent
-} 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 lifeCycleEvent(): OB11LifeCycleEvent {
-        return {
-            ...eventBase(OB11EventPostType.META) as OB11MetaEvent,
-            meta_event_type: "lifecycle",
-            sub_type: "connect"
-        }
-    }
-
-    static heartEvent(): OB11HeartEvent {
-        return {
-            ...eventBase(OB11EventPostType.META) as OB11MetaEvent,
-            meta_event_type: "heartbeat",
-            status: {
-                online: true,
-                good: true
-            },
-            interval: heartInterval
-        }
-    }
-
-    static groupRecallEvent(group_id: string, user_id: string, operator_id: string, message_id: number): OB11GroupRecallNoticeEvent {
-        return {
-            ...eventBase(OB11EventPostType.NOTICE) as OB11NoticeEvent,
-            notice_type: "group_recall",
-            group_id: parseInt(group_id),
-            user_id: parseInt(user_id),
-            operator_id: parseInt(operator_id),
-            message_id
-        }
-    }
-
-    static friendRecallEvent(user_id: string, message_id: number): OB11FriendRecallNoticeEvent {
-        return {
-            ...eventBase(OB11EventPostType.NOTICE) as OB11NoticeEvent,
-            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
deleted file mode 100644
index 5902114..0000000
--- a/src/onebot11/events/types.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-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 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 OB11NoticeEvent{
-    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 OB11NoticeEvent{
-    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 6ea5224..400b043 100644
--- a/src/onebot11/server.ts
+++ b/src/onebot11/server.ts
@@ -1,36 +1,25 @@
-import { getConfigUtil, log } from "../common/utils";
-
-const express = require("express");
-const expressWs = require("express-ws");
-
 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 { heartInterval, selfInfo } from "../common/data";
-import { OB11Message, OB11Return, OB11MessageData } from './types';
+import express, {Request, Response} from "express";
+import {getConfigUtil, log} from "../common/utils";
+import {heartInterval, selfInfo} from "../common/data";
+import {OB11Message, OB11MessageData, OB11Return} from './types';
 import {actionHandlers, actionMap} from "./actions";
 import {OB11Response, OB11WebsocketResponse} from "./actions/utils";
-import {registerEventSender, unregisterEventSender} from "./event/manager";
+import {callEvent, registerEventSender, unregisterEventSender} from "./event/manager";
 import ReconnectingWebsocket from "./ReconnectingWebsocket";
-import { actionHandlers } from "./actions";
-import { OB11Response } from "./actions/utils";
-import { ActionName } from "./actions/types";
+import {ActionName} from "./actions/types";
+import {OB11BaseMetaEvent} from "./event/meta/OB11BaseMetaEvent";
+import {OB11BaseNoticeEvent} from "./event/notice/OB11BaseNoticeEvent";
 import BaseAction from "./actions/BaseAction";
-import { OB11Constructor } from "./constructor";
-import { OB11EventBase, OB11LifeCycleEvent, OB11MetaEvent, OB11NoticeEvent } from "./events/types";
+import {LifeCycleSubType, OB11LifeCycleEvent} from "./event/meta/OB11LifeCycleEvent";
+import {OB11HeartbeatEvent} from "./event/meta/OB11HeartbeatEvent";
 
-let accessToken = ""
+let accessToken = "";
+let heartbeatRunning = false;
 
 // @SiberianHusky 2021-08-15
-enum WebsocketType {
-    API,
-    EVENT,
-    ALL
-}
 
 function checkSendMessage(sendMsgList: OB11MessageData[]) {
     function checkUri(uri: string): boolean {
@@ -72,11 +61,11 @@ function checkSendMessage(sendMsgList: OB11MessageData[]) {
 const JSONbig = require('json-bigint')({storeAsString: true});
 
 const expressAPP = express();
-let httpServer: http.Server = null;
 expressAPP.use(express.urlencoded({extended: true, limit: "500mb"}));
 
-const expressWsApp = express();
-const websocketClientConnections = [];
+let httpServer: http.Server = null;
+
+let websocketServer = null;
 
 expressAPP.use((req, res, next) => {
     let data = '';
@@ -139,74 +128,131 @@ export function startHTTPServer(port: number) {
     }
 }
 
-export function startWebsocketServer(port: number) {
-    const config = getConfigUtil().getConfig();
-    if (config.enableWs) {
-        try {
-            expressWs(expressWsApp)
-            expressWsApp.listen(getConfigUtil().getConfig().wsPort, function () {
-                console.log(`llonebot websocket service started 0.0.0.0:${port}`);
-            });
-        }
-        catch (e) {
-            console.log(e);
-        }
-    }
-}
+export function initWebsocket(port: number) {
+    if (!heartbeatRunning) {
+        setInterval(() => {
+            callEvent(new OB11HeartbeatEvent(true, true, heartInterval));
+        }, heartInterval);  // 心跳包
+
+        heartbeatRunning = true;
+    }
 
-export function initWebsocket() {
     if (getConfigUtil().getConfig().enableWs) {
-        expressWsApp.ws("/api", (ws, req) => {
-            initWebsocketServer(ws, req, WebsocketType.API);
-        });
-        expressWsApp.ws("/event", (ws, req) => {
-            initWebsocketServer(ws, req, WebsocketType.EVENT);
-        });
-        expressWsApp.ws("/", (ws, req) => {
-            initWebsocketServer(ws, req, WebsocketType.ALL);
-        });
+        if (websocketServer) {
+            websocketServer.close((err) => {
+                log("ws server close failed!", err)
+            })
+        }
+
+        websocketServer = new websocket.Server({port});
+        console.log(`llonebot websocket service started 0.0.0.0:${port}`);
+
+        websocketServer.on("connection", (ws, req) => {
+            const url = req.url.split("?").shift();
+            log("receive 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(req.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(OB11WebsocketResponse.res(null, "failed", 1403, "token验证失败")))
+                    return ws.close()
+                }
+            }
+
+            if (url == "/api" || url == "/api/" || url == "/") {
+                ws.on("message", async (msg) => {
+                    let receiveData: { action: ActionName, params: any, echo?: string } = {action: null, params: {}}
+                    let echo = ""
+                    log("收到正向Websocket消息", msg.toString())
+                    try {
+                        receiveData = JSON.parse(msg.toString())
+                        echo = receiveData.echo
+                    } catch (e) {
+                        return wsReply(ws, OB11WebsocketResponse.error("json解析失败,请检查数据格式", 1400, echo))
+                    }
+                    const action: BaseAction<any, any> = actionMap.get(receiveData.action);
+                    if (!action) {
+                        return wsReply(ws, OB11WebsocketResponse.error("不支持的api " + receiveData.action, 1404, echo))
+                    }
+                    try {
+                        let handleResult = await action.websocketHandle(receiveData.params, echo);
+                        wsReply(ws, handleResult)
+                    } catch (e) {
+                        wsReply(ws, OB11WebsocketResponse.error(`api处理出错:${e}`, 1200, echo))
+                    }
+                })
+            }
+            if (url == "/event" || url == "/event/" || url == "/") {
+                registerEventSender(ws);
+
+                log("event上报ws客户端已连接")
+
+                try {
+                    wsReply(ws, new OB11LifeCycleEvent(LifeCycleSubType.CONNECT))
+                } catch (e){
+                    log("发送生命周期失败", e)
+                }
+
+                ws.on("close", () => {
+                    log("event上报ws客户端已断开")
+                    unregisterEventSender(ws);
+                })
+            }
+        })
     }
 
-    initReverseWebsocket();
+    initReverseWebsocket().then();
 }
-
-function initReverseWebsocket() {
+async function initReverseWebsocket() {
     const config = getConfigUtil().getConfig();
     if (config.enableWsReverse) {
         for (const url of config.wsHosts) {
             try {
                 let wsClient = new ReconnectingWebsocket(url);
-                websocketClientConnections.push(wsClient);
                 registerEventSender(wsClient);
 
-                wsClient.onclose = function () {
-                    console.log("The websocket connection: " + url + " closed, trying reconnecting...");
-                    unregisterEventSender(wsClient);
-                    let index = websocketClientConnections.indexOf(wsClient);
-                    if (index !== -1) {
-                        websocketClientConnections.splice(index, 1);
-                    }
+                wsClient.onopen = function () {
+                    wsReply(wsClient, new OB11LifeCycleEvent(LifeCycleSubType.CONNECT));
                 }
 
-                wsClient.onmessage = async function (message) {
-                    console.log(message);
-                    if (typeof message === "string") {
-                        try {
-                            let recv = JSON.parse(message);
-                            let echo = recv.echo ?? "";
+                wsClient.onclose = function () {
+                    unregisterEventSender(wsClient);
+                }
 
-                            if (actionMap.has(recv.action)) {
-                                let action = actionMap.get(recv.action);
-                                const result = await action.websocketHandle(recv.params, echo);
-                                wsClient.send(JSON.stringify(result));
-                            }
-                            else {
-                                wsClient.send(JSON.stringify(OB11WebsocketResponse.error("Bad Request", 1400, echo)));
-                            }
-                        } catch (e) {
-                            log(e.stack);
-                            wsClient.send(JSON.stringify(OB11WebsocketResponse.error(e.stack.toString(), 1200)));
-                        }
+                wsClient.onmessage = async function (msg) {
+                    let receiveData: { action: ActionName, params: any, echo?: string } = {action: null, params: {}}
+                    let echo = ""
+                    log("收到正向Websocket消息", msg.toString())
+                    try {
+                        receiveData = JSON.parse(msg.toString())
+                        echo = receiveData.echo
+                    } catch (e) {
+                        return wsReply(wsClient, OB11WebsocketResponse.error("json解析失败,请检查数据格式", 1400, echo))
+                    }
+                    const action: BaseAction<any, any> = actionMap.get(receiveData.action);
+                    if (!action) {
+                        return wsReply(wsClient, OB11WebsocketResponse.error("不支持的api " + receiveData.action, 1404, echo))
+                    }
+                    try {
+                        let handleResult = await action.websocketHandle(receiveData.params, echo);
+                        wsReply(wsClient, handleResult)
+                    } catch (e) {
+                        wsReply(wsClient, OB11WebsocketResponse.error(`api处理出错:${e}`, 1200, echo))
                     }
                 }
             }
@@ -217,44 +263,7 @@ function initReverseWebsocket() {
     }
 }
 
-function initWebsocketServer(ws, req, type: WebsocketType) {
-    if (type == WebsocketType.EVENT || type == WebsocketType.ALL) {
-        registerEventSender(ws);
-    }
-
-    ws.on("message", async function (message) {
-        if (type == WebsocketType.API || type == WebsocketType.ALL) {
-            try {
-                let recv = JSON.parse(message);
-                let echo = recv.echo ?? "";
-
-                if (actionMap.has(recv.action)) {
-                    let action = actionMap.get(recv.action)
-                    const result = await action.websocketHandle(recv.params, echo);
-                    ws.send(JSON.stringify(result));
-                }
-                else {
-                    ws.send(JSON.stringify(OB11WebsocketResponse.error("Bad Request", 1400, echo)));
-                }
-            } catch (e) {
-                log(e.stack);
-                ws.send(JSON.stringify(OB11WebsocketResponse.error(e.stack.toString(), 1200)));
-            }
-        }
-    });
-
-    ws.on("close", function (ev) {
-        if (type == WebsocketType.EVENT || type == WebsocketType.ALL) {
-            unregisterEventSender(ws);
-        }
-    });
-}
-
-let wsEventClients: websocket.WebSocket[] = [];
-type RouterHandler = (payload: any) => Promise<OB11Return<any>>
-let routers: Record<string, RouterHandler> = {};
-
-function wsReply(wsClient: websocket.WebSocket, data: OB11Return<any> | PostMsgType) {
+export function wsReply(wsClient: websocket.WebSocket | ReconnectingWebsocket, data: OB11Return<any> | PostMsgType) {
     try {
         wsClient.send(JSON.stringify(data))
         log("ws 消息上报", data)
@@ -263,104 +272,17 @@ function wsReply(wsClient: websocket.WebSocket, data: OB11Return<any> | PostMsgT
     }
 }
 
-
-// TODO: 统一一下逻辑
-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.split("?").shift();
-        log("receive 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(req.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()
-            }
-        }
-        if (url == "/api" || url == "/api/" || url == "/") {
-            ws.on("message", async (msg) => {
-
-                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解析失败,请检查数据格式"), echo})
-                }
-                const handle: RouterHandler | undefined = routers[receiveData.action]
-                if (!handle) {
-                    let handleResult = OB11Response.error("不支持的api " + receiveData.action, 1404)
-                    handleResult.echo = echo
-                    return wsReply(ws, handleResult)
-                }
-                try {
-                    let handleResult = await handle(receiveData.params)
-                    if (echo){
-                        handleResult.echo = echo
-                    }
-                    wsReply(ws, handleResult)
-                } catch (e) {
-                    wsReply(ws, OB11Response.error(`api处理出错:${e}`))
-                }
-            })
-        }
-        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)
-            })
-        }
-    })
-}
-
-type PostMsgType = OB11Message | OB11MetaEvent | OB11NoticeEvent
+export type PostMsgType = OB11Message | OB11BaseMetaEvent | OB11BaseNoticeEvent
 
 export function postMsg(msg: PostMsgType) {
-    const {reportSelfMessage} = getConfigUtil().getConfig()
+    const config = getConfigUtil().getConfig();
     // 判断msg是否是event
-    if (!reportSelfMessage) {
+    if (!config.reportSelfMessage) {
         if ((msg as OB11Message).user_id.toString() == selfInfo.uin) {
             return
         }
     }
-    for (const host of getConfigUtil().getConfig().hosts) {
+    for (const host of config.httpHosts) {
         fetch(host, {
             method: "POST",
             headers: {
@@ -374,12 +296,10 @@ export function postMsg(msg: PostMsgType) {
             log(`新消息事件HTTP上报失败: ${host} ` + err + JSON.stringify(msg));
         });
     }
-    for (const wsClient of wsEventClients) {
-        log("新消息事件ws上报", msg)
-        new Promise((resolve, reject) => {
-            wsReply(wsClient, msg);
-        }).then();
-    }
+
+    log("新消息事件ws上报", msg);
+    console.log("新消息事件ws上报", msg);
+    callEvent(msg);
 }
 
 
@@ -406,7 +326,6 @@ function registerRouter(action: string, handle: (payload: any) => Promise<any>)
     expressAPP.get(url, expressAuthorize, (req: Request, res: Response) => {
         _handle(res, req.query as any || {}).then()
     });
-    routers[action] = handle
 }
 
 for (const action of actionHandlers) {
diff --git a/src/onebot11/types.ts b/src/onebot11/types.ts
index fceffc5..e7b43ad 100644
--- a/src/onebot11/types.ts
+++ b/src/onebot11/types.ts
@@ -1,4 +1,4 @@
-import { AtType, RawMessage } from "../ntqqapi/types";
+import {AtType, RawMessage} from "../ntqqapi/types";
 
 export interface OB11User {
     user_id: number;
@@ -89,19 +89,12 @@ export interface OB11Return<DataType> {
     retcode: number
     data: DataType
     message: string,
-    echo?: string
 }
 
-export interface OB11WebsocketReturn<DataType> {
-    status: string
-    retcode: number
-    data: DataType
+export interface OB11WebsocketReturn<DataType> extends OB11Return<DataType>{
     echo: string
-    message: string
 }
 
-export interface OB11SendMsgReturn extends OB11Return<{ message_id: string }> {}
-
 export enum OB11MessageDataType {
     text = "text",
     image = "image",
diff --git a/src/onebot11/utils.ts b/src/onebot11/utils.ts
index e6e1278..8050e68 100644
--- a/src/onebot11/utils.ts
+++ b/src/onebot11/utils.ts
@@ -1,7 +1,7 @@
-import { CONFIG_DIR, isGIF } from "../common/utils";
+import {CONFIG_DIR, isGIF} from "../common/utils";
 import * as path from 'path';
-import { NTQQApi } from '../ntqqapi/ntcall';
-import { OB11MessageData } from "./types";
+import {OB11MessageData} from "./types";
+
 const fs = require("fs").promises;
 
 export async function uri2local(fileName: string, uri: string){
diff --git a/src/preload.ts b/src/preload.ts
index afde46f..347f916 100644
--- a/src/preload.ts
+++ b/src/preload.ts
@@ -1,11 +1,7 @@
 // Electron 主进程 与 渲染进程 交互的桥梁
 
 import {Config} from "./common/types";
-import {
-    CHANNEL_GET_CONFIG,
-    CHANNEL_LOG,
-    CHANNEL_SET_CONFIG,
-} from "./common/channels";
+import {CHANNEL_GET_CONFIG, CHANNEL_LOG, CHANNEL_SET_CONFIG,} from "./common/channels";
 
 
 const {contextBridge} = require("electron");

From acb1ec387129c91888fce7118228078804a91c6f Mon Sep 17 00:00:00 2001
From: Disy <disymayufei@yeah.net>
Date: Mon, 19 Feb 2024 13:54:09 +0800
Subject: [PATCH 15/16] feat: Asynchronous connect reverse websocket

---
 src/onebot11/ReconnectingWebsocket.ts | 26 +++-------
 src/onebot11/event/manager.ts         |  2 +-
 src/onebot11/server.ts                | 74 ++++++++++++++-------------
 src/renderer.ts                       |  8 +--
 4 files changed, 52 insertions(+), 58 deletions(-)

diff --git a/src/onebot11/ReconnectingWebsocket.ts b/src/onebot11/ReconnectingWebsocket.ts
index 5bb77e2..ecf5c5e 100644
--- a/src/onebot11/ReconnectingWebsocket.ts
+++ b/src/onebot11/ReconnectingWebsocket.ts
@@ -2,7 +2,7 @@ import {log} from "../common/utils";
 
 const WebSocket = require("ws");
 
-class ReconnectingWebsocket {
+export class ReconnectingWebsocket {
     private websocket;
     private readonly url: string;
 
@@ -17,14 +17,6 @@ class ReconnectingWebsocket {
 
     public onclose = function () {}
 
-    private heartbeat() {
-        clearTimeout(this.websocket.pingTimeout);
-
-        this.websocket.pingTimeout = setTimeout(() => {
-            this.websocket.terminate();
-        }, 3000);
-    }
-
     public send(msg) {
         if (this.websocket && this.websocket.readyState == WebSocket.OPEN) {
             this.websocket.send(msg);
@@ -37,12 +29,12 @@ class ReconnectingWebsocket {
             perMessageDeflate: false
         });
 
-        log("Trying to connect to the websocket server: " + this.url);
+        console.log("Trying to connect to the websocket server: " + this.url);
 
         const instance = this;
 
         this.websocket.on("open", function open() {
-            log("Connected to the websocket server: " + instance.url);
+            console.log("Connected to the websocket server: " + instance.url);
             instance.onopen();
         });
 
@@ -52,15 +44,13 @@ class ReconnectingWebsocket {
 
         this.websocket.on("error", console.error);
 
-        this.websocket.on("ping", this.heartbeat);
-
         this.websocket.on("close", function close() {
-            log("The websocket connection: " + instance.url + " closed, trying reconnecting...");
+            console.log("The websocket connection: " + instance.url + " closed, trying reconnecting...");
             instance.onclose();
 
-            setTimeout(instance.reconnect, 3000);
+            setTimeout(() => {
+                instance.reconnect();
+            }, 3000);  // TODO: 重连间隔在配置文件中实现
         });
     }
-}
-
-export default ReconnectingWebsocket;
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/onebot11/event/manager.ts b/src/onebot11/event/manager.ts
index 931b53d..93ce191 100644
--- a/src/onebot11/event/manager.ts
+++ b/src/onebot11/event/manager.ts
@@ -1,6 +1,6 @@
 import * as websocket from "ws";
 import {PostMsgType, wsReply} from "../server";
-import ReconnectingWebsocket from "../ReconnectingWebsocket";
+import {ReconnectingWebsocket} from "../ReconnectingWebsocket";
 
 const websocketList = [];
 
diff --git a/src/onebot11/server.ts b/src/onebot11/server.ts
index 400b043..5ec0a83 100644
--- a/src/onebot11/server.ts
+++ b/src/onebot11/server.ts
@@ -8,7 +8,7 @@ import {OB11Message, OB11MessageData, OB11Return} from './types';
 import {actionHandlers, actionMap} from "./actions";
 import {OB11Response, OB11WebsocketResponse} from "./actions/utils";
 import {callEvent, registerEventSender, unregisterEventSender} from "./event/manager";
-import ReconnectingWebsocket from "./ReconnectingWebsocket";
+import {ReconnectingWebsocket} from "./ReconnectingWebsocket";
 import {ActionName} from "./actions/types";
 import {OB11BaseMetaEvent} from "./event/meta/OB11BaseMetaEvent";
 import {OB11BaseNoticeEvent} from "./event/notice/OB11BaseNoticeEvent";
@@ -216,49 +216,52 @@ export function initWebsocket(port: number) {
         })
     }
 
-    initReverseWebsocket().then();
+    initReverseWebsocket();
 }
-async function initReverseWebsocket() {
+function initReverseWebsocket() {
     const config = getConfigUtil().getConfig();
     if (config.enableWsReverse) {
+        console.log("Prepare to connect all reverse websockets...");
         for (const url of config.wsHosts) {
-            try {
-                let wsClient = new ReconnectingWebsocket(url);
-                registerEventSender(wsClient);
+            new Promise(() => {
+                try {
+                    let wsClient = new ReconnectingWebsocket(url);
+                    registerEventSender(wsClient);
 
-                wsClient.onopen = function () {
-                    wsReply(wsClient, new OB11LifeCycleEvent(LifeCycleSubType.CONNECT));
-                }
-
-                wsClient.onclose = function () {
-                    unregisterEventSender(wsClient);
-                }
-
-                wsClient.onmessage = async function (msg) {
-                    let receiveData: { action: ActionName, params: any, echo?: string } = {action: null, params: {}}
-                    let echo = ""
-                    log("收到正向Websocket消息", msg.toString())
-                    try {
-                        receiveData = JSON.parse(msg.toString())
-                        echo = receiveData.echo
-                    } catch (e) {
-                        return wsReply(wsClient, OB11WebsocketResponse.error("json解析失败,请检查数据格式", 1400, echo))
+                    wsClient.onopen = function () {
+                        wsReply(wsClient, new OB11LifeCycleEvent(LifeCycleSubType.CONNECT));
                     }
-                    const action: BaseAction<any, any> = actionMap.get(receiveData.action);
-                    if (!action) {
-                        return wsReply(wsClient, OB11WebsocketResponse.error("不支持的api " + receiveData.action, 1404, echo))
+
+                    wsClient.onclose = function () {
+                        unregisterEventSender(wsClient);
                     }
-                    try {
-                        let handleResult = await action.websocketHandle(receiveData.params, echo);
-                        wsReply(wsClient, handleResult)
-                    } catch (e) {
-                        wsReply(wsClient, OB11WebsocketResponse.error(`api处理出错:${e}`, 1200, echo))
+
+                    wsClient.onmessage = async function (msg) {
+                        let receiveData: { action: ActionName, params: any, echo?: string } = {action: null, params: {}}
+                        let echo = ""
+                        log("收到正向Websocket消息", msg.toString())
+                        try {
+                            receiveData = JSON.parse(msg.toString())
+                            echo = receiveData.echo
+                        } catch (e) {
+                            return wsReply(wsClient, OB11WebsocketResponse.error("json解析失败,请检查数据格式", 1400, echo))
+                        }
+                        const action: BaseAction<any, any> = actionMap.get(receiveData.action);
+                        if (!action) {
+                            return wsReply(wsClient, OB11WebsocketResponse.error("不支持的api " + receiveData.action, 1404, echo))
+                        }
+                        try {
+                            let handleResult = await action.websocketHandle(receiveData.params, echo);
+                            wsReply(wsClient, handleResult)
+                        } catch (e) {
+                            wsReply(wsClient, OB11WebsocketResponse.error(`api处理出错:${e}`, 1200, echo))
+                        }
                     }
                 }
-            }
-            catch (e) {
-                console.log(e);
-            }
+                catch (e) {
+                    log(e.stack);
+                }
+            }).then();
         }
     }
 }
@@ -298,7 +301,6 @@ export function postMsg(msg: PostMsgType) {
     }
 
     log("新消息事件ws上报", msg);
-    console.log("新消息事件ws上报", msg);
     callEvent(msg);
 }
 
diff --git a/src/renderer.ts b/src/renderer.ts
index fe2eee4..e673b44 100644
--- a/src/renderer.ts
+++ b/src/renderer.ts
@@ -155,13 +155,15 @@ async function onSettingWindowCreated(view: Element) {
 
 
     function addHostEle(type: string, initValue: string = "") {
-        let addressDoc = parser.parseFromString(createHttpHostEleStr(initValue), "text/html");
-        let addressEle = addressDoc.querySelector("setting-item")
-        let hostItemsEle;
+        let addressEle, hostItemsEle;
         if (type === "ws") {
+            let addressDoc = parser.parseFromString(createWsHostEleStr(initValue), "text/html");
+            addressEle = addressDoc.querySelector("setting-item")
             hostItemsEle = document.getElementById("wsHostItems");
         }
         else {
+            let addressDoc = parser.parseFromString(createHttpHostEleStr(initValue), "text/html");
+            addressEle = addressDoc.querySelector("setting-item")
             hostItemsEle = document.getElementById("httpHostItems");
         }
 

From 82e3ca113d5635b0d4b58b4db54303751aaaec69 Mon Sep 17 00:00:00 2001
From: Disy <disymayufei@yeah.net>
Date: Mon, 19 Feb 2024 18:31:10 +0800
Subject: [PATCH 16/16] chore: change app version

---
 src/common/data.ts     |  2 +-
 src/onebot11/server.ts | 11 +++++++++--
 2 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/src/common/data.ts b/src/common/data.ts
index 60c930d..ce94b06 100644
--- a/src/common/data.ts
+++ b/src/common/data.ts
@@ -87,5 +87,5 @@ export function getStrangerByUin(uin: string) {
     }
 }
 
-export const version = "v3.3.1"
+export const version = "v3.4.0"
 export const heartInterval = 15000 // 毫秒
\ No newline at end of file
diff --git a/src/onebot11/server.ts b/src/onebot11/server.ts
index 5ec0a83..b2a2a7c 100644
--- a/src/onebot11/server.ts
+++ b/src/onebot11/server.ts
@@ -266,9 +266,16 @@ function initReverseWebsocket() {
     }
 }
 
-export function wsReply(wsClient: websocket.WebSocket | ReconnectingWebsocket, data: OB11Return<any> | PostMsgType) {
+export function wsReply(wsClient: websocket.WebSocket | ReconnectingWebsocket, data: OB11WebsocketResponse | PostMsgType) {
     try {
-        wsClient.send(JSON.stringify(data))
+        let packet = Object.assign({
+            echo: ""
+        }, data);
+        if (!packet.echo) {
+            packet.echo = "";
+        }
+
+        wsClient.send(JSON.stringify(packet))
         log("ws 消息上报", data)
     } catch (e) {
         log("websocket 回复失败", e)