From c875cfda15b3597941ee2ff0cd6f8a7900dd502e Mon Sep 17 00:00:00 2001
From: Disy <disymayufei@yeah.net>
Date: Wed, 14 Feb 2024 22:11:07 +0800
Subject: [PATCH 01/12] feat: add websocket support

---
 package-lock.json                  |  35 +++++++
 package.json                       |   3 +-
 src/common/config.ts               |  19 ++--
 src/common/types.ts                |  13 ++-
 src/main/main.ts                   |  57 +++++-----
 src/onebot11/actions/BaseAction.ts |  27 +++--
 src/onebot11/actions/index.ts      |  14 ++-
 src/onebot11/actions/utils.ts      |  30 ++++--
 src/onebot11/server.ts             | 163 ++++++++++++++++++++---------
 src/onebot11/types.ts              |  10 +-
 src/renderer.ts                    |   8 +-
 11 files changed, 273 insertions(+), 106 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 19da495..35582a0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,7 @@
       "license": "ISC",
       "dependencies": {
         "express": "^4.18.2",
+        "express-ws": "^5.0.2",
         "json-bigint": "^1.0.0",
         "uuid": "^9.0.1"
       },
@@ -3123,6 +3124,20 @@
         "node": ">= 0.10.0"
       }
     },
+    "node_modules/express-ws": {
+      "version": "5.0.2",
+      "resolved": "https://registry.npmmirror.com/express-ws/-/express-ws-5.0.2.tgz",
+      "integrity": "sha512-0uvmuk61O9HXgLhGl3QhNSEtRsQevtmbL94/eILaliEADZBHZOQUAiHFrGPrgsjikohyrmSG5g+sCfASTt0lkQ==",
+      "dependencies": {
+        "ws": "^7.4.6"
+      },
+      "engines": {
+        "node": ">=4.5.0"
+      },
+      "peerDependencies": {
+        "express": "^4.0.0 || ^5.0.0-alpha.1"
+      }
+    },
     "node_modules/fast-deep-equal": {
       "version": "3.1.3",
       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -4691,6 +4706,26 @@
       "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==",
       "dev": true
     },
+    "node_modules/ws": {
+      "version": "7.5.9",
+      "resolved": "https://registry.npmmirror.com/ws/-/ws-7.5.9.tgz",
+      "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
+      "engines": {
+        "node": ">=8.3.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..b5de92b 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,7 @@
   "main": "dist/main.js",
   "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1",
-    "postinstall": "ELECTRON_SKIP_BINARY_DOWNLOAD=1 npm install electron --no-save",
+    "postinstall": "set ELECTRON_SKIP_BINARY_DOWNLOAD=1 && npm install electron --no-save",
     "build": "npm run build-main && npm run build-preload && npm run build-renderer",
     "build-main": "webpack --config webpack.main.config.js",
     "build-preload": "webpack --config webpack.preload.config.js",
@@ -19,6 +19,7 @@
   "license": "ISC",
   "dependencies": {
     "express": "^4.18.2",
+    "express-ws": "^5.0.2",
     "json-bigint": "^1.0.0",
     "uuid": "^9.0.1"
   },
diff --git a/src/common/config.ts b/src/common/config.ts
index 44b73d5..e390a4b 100644
--- a/src/common/config.ts
+++ b/src/common/config.ts
@@ -1,6 +1,6 @@
 import {Config} from "./types";
 
-const fs = require("fs")
+const fs = require("fs");
 
 export class ConfigUtil{
     configPath: string;
@@ -9,20 +9,25 @@ export class ConfigUtil{
         this.configPath = configPath;
     }
 
-    getConfig(): Config{
+    getConfig(): Config {
         if (!fs.existsSync(this.configPath)) {
-            return {port:3000, hosts: ["http://192.168.1.2:5000/"]}
+            return {
+                httpPort: 3000,
+                httpHosts: ["http://127.0.0.1:5000/"],
+                wsPort: 3001,
+                wsHosts: ["ws://127.0.0.1:3002/"]
+            }
         } else {
             const data = fs.readFileSync(this.configPath, "utf-8");
-            let jsonData =JSON.parse(data);
-            if (!jsonData.hosts){
-                jsonData.hosts = []
+            let jsonData = JSON.parse(data);
+            if (!jsonData.hosts) {
+                jsonData.hosts = [];
             }
             return jsonData;
         }
     }
 
     setConfig(config: Config){
-        fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2), "utf-8")
+        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..54964dd 100644
--- a/src/common/types.ts
+++ b/src/common/types.ts
@@ -1,9 +1,14 @@
 export interface Config {
-    port: number
-    hosts: string[]
+    httpPort: number
+    httpHosts: string[]
+    wsPort: number
+    wsHosts: string[]
+    enableHttp?: boolean
+    enableHttpPost?: boolean
+    enableWs?: boolean
+    enableWsReverse?: boolean
     enableBase64?: boolean
     debug?: boolean
     reportSelfMessage?: boolean
     log?: boolean
-}
-
+}
\ No newline at end of file
diff --git a/src/main/main.ts b/src/main/main.ts
index 43b5400..c2ab87e 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 {initWebsocket, postMsg, startExpress, startWebsocketServer} 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";
@@ -29,56 +29,53 @@ function onLoad() {
 
     // const config_dir = browserWindow.LiteLoader.plugins["LLOneBot"].path.data;
 
-
     if (!fs.existsSync(CONFIG_DIR)) {
         fs.mkdirSync(CONFIG_DIR, {recursive: true});
     }
     ipcMain.handle(CHANNEL_GET_CONFIG, (event: any, arg: any) => {
-        return getConfigUtil().getConfig()
+        return getConfigUtil().getConfig();
     })
     ipcMain.on(CHANNEL_SET_CONFIG, (event: any, arg: Config) => {
-        getConfigUtil().setConfig(arg)
+        getConfigUtil().setConfig(arg);
     })
 
     ipcMain.on(CHANNEL_LOG, (event: any, arg: any) => {
-        log(arg)
+        log(arg);
     })
 
 
     function postRawMsg(msgList: RawMessage[]) {
         const {debug, reportSelfMessage} = getConfigUtil().getConfig();
         for (let message of msgList) {
-            message.msgShortId = msgHistory[message.msgId]?.msgShortId
+            message.msgShortId = msgHistory[message.msgId]?.msgShortId;
             if (!message.msgShortId) {
-                addHistoryMsg(message)
+                addHistoryMsg(message);
             }
             OB11Constructor.message(message).then((msg) => {
                 if (debug) {
                     msg.raw = message;
                 }
                 if (msg.user_id == selfInfo.uin && !reportSelfMessage) {
-                    return
+                    return;
                 }
                 postMsg(msg);
-                // log("post msg", msg)
             }).catch(e => log("constructMessage error: ", e.toString()));
         }
     }
 
 
     function start() {
-        log("llonebot start")
+        log("llonebot start");
         registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.NEW_MSG, (payload) => {
             try {
-                // log("received msg length", payload.msgList.length);
                 postRawMsg(payload.msgList);
             } catch (e) {
-                log("report message error: ", e.toString())
+                log("report message error: ", e.toString());
             }
         })
 
         registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmd.SELF_SEND_MSG, (payload) => {
-            const {reportSelfMessage} = getConfigUtil().getConfig()
+            const {reportSelfMessage} = getConfigUtil().getConfig();
             if (!reportSelfMessage) {
                 return
             }
@@ -86,42 +83,46 @@ function onLoad() {
             try {
                 postRawMsg([payload.msgRecord]);
             } catch (e) {
-                log("report self message error: ", e.toString())
+                log("report self message error: ", e.toString());
             }
         })
-        NTQQApi.getGroups(true).then()
-        startExpress(getConfigUtil().getConfig().port)
+        NTQQApi.getGroups(true).then();
+
+        const config = getConfigUtil().getConfig();
+        startExpress(config.httpPort);
+        startWebsocketServer(config.wsPort);
+        initWebsocket();
     }
 
     const init = async () => {
         try {
-            const _ = await NTQQApi.getSelfInfo()
-            Object.assign(selfInfo, _)
-            selfInfo.nick = selfInfo.uin
-            log("get self simple info", _)
+            const _ = await NTQQApi.getSelfInfo();
+            Object.assign(selfInfo, _);
+            selfInfo.nick = selfInfo.uin;
+            log("get self simple info", _);
         } catch (e) {
-            log("retry get self info")
+            log("retry get self info");
         }
         if (selfInfo.uin) {
             try {
-                const userInfo = (await NTQQApi.getUserInfo(selfInfo.uid))
+                const userInfo = (await NTQQApi.getUserInfo(selfInfo.uid));
                 log("self info", userInfo);
                 if (userInfo) {
-                    selfInfo.nick = userInfo.nick
+                    selfInfo.nick = userInfo.nick;
                 } else {
-                    return setTimeout(init, 1000)
+                    return setTimeout(init, 1000);
                 }
             } catch (e) {
-                log("get self nickname failed", e.toString())
-                return setTimeout(init, 1000)
+                log("get self nickname failed", e.toString());
+                return setTimeout(init, 1000);
             }
             start();
         }
         else{
-            setTimeout(init, 1000)
+            setTimeout(init, 1000);
         }
     }
-    setTimeout(init, 1000)
+    setTimeout(init, 1000);
 }
 
 
diff --git a/src/onebot11/actions/BaseAction.ts b/src/onebot11/actions/BaseAction.ts
index ab69555..9806ea0 100644
--- a/src/onebot11/actions/BaseAction.ts
+++ b/src/onebot11/actions/BaseAction.ts
@@ -1,6 +1,6 @@
 import {ActionName, BaseCheckResult} from "./types"
-import { OB11Response } from "./utils"
-import { OB11Return } from "../types";
+import {OB11Response, OB11WebsocketResponse} from "./utils"
+import {OB11Return, OB11WebsocketReturn} from "../types";
 
 class BaseAction<PayloadType, ReturnDataType> {
     actionName: ActionName
@@ -11,20 +11,33 @@ class BaseAction<PayloadType, ReturnDataType> {
     }
 
     public async handle(payload: PayloadType): Promise<OB11Return<ReturnDataType | null>> {
+        const result = await this.check(payload);
+        if (!result.valid) {
+            return OB11Response.error(result.message, 400);
+        }
+        try {
+            const resData = await this._handle(payload);
+            return OB11Response.ok(resData);
+        } catch (e) {
+            return OB11Response.error(e.toString(), 200);
+        }
+    }
+
+    public async websocketHandle(payload: PayloadType, echo: string): Promise<OB11WebsocketReturn<ReturnDataType | null>> {
         const result = await this.check(payload)
         if (!result.valid) {
-            return OB11Response.error(result.message)
+            return OB11WebsocketResponse.error(result.message, 1400)
         }
         try {
             const resData = await this._handle(payload)
-            return OB11Response.ok(resData)
-        }catch (e) {
-            return OB11Response.error(e.toString())
+            return OB11WebsocketResponse.ok(resData, echo);
+        } catch (e) {
+            return OB11WebsocketResponse.error(e.toString(), 1200)
         }
     }
 
     protected async _handle(payload: PayloadType): Promise<ReturnDataType> {
-        throw `pleas override ${this.actionName} _handle`
+        throw `pleas override ${this.actionName} _handle`;
     }
 }
 
diff --git a/src/onebot11/actions/index.ts b/src/onebot11/actions/index.ts
index 42fcda6..dfd90c5 100644
--- a/src/onebot11/actions/index.ts
+++ b/src/onebot11/actions/index.ts
@@ -9,6 +9,7 @@ import SendGroupMsg from './SendGroupMsg'
 import SendPrivateMsg from './SendPrivateMsg'
 import SendMsg from './SendMsg'
 import DeleteMsg from "./DeleteMsg";
+import BaseAction from "./BaseAction";
 
 export const actionHandlers = [
     new GetMsg(),
@@ -17,4 +18,15 @@ export const actionHandlers = [
     new GetGroupList(), new GetGroupInfo(), new GetGroupMemberList(), new GetGroupMemberInfo(),
     new SendGroupMsg(), new SendPrivateMsg(), new SendMsg(),
     new DeleteMsg()
-]
\ No newline at end of file
+]
+
+function initActionMap() {
+    const actionMap = new Map<string, BaseAction<any, any>>();
+    for (const action of actionHandlers) {
+        actionMap.set(action.actionName, action);
+    }
+
+    return actionMap
+}
+
+export const actionMap = initActionMap();
\ No newline at end of file
diff --git a/src/onebot11/actions/utils.ts b/src/onebot11/actions/utils.ts
index 8120d7f..e87b394 100644
--- a/src/onebot11/actions/utils.ts
+++ b/src/onebot11/actions/utils.ts
@@ -1,18 +1,36 @@
-import { OB11Return } from '../types';
+import {OB11Return, OB11WebsocketReturn} from '../types';
 
 export class OB11Response {
-    static res<T>(data: T, status: number = 0, message: string = ""): OB11Return<T> {
+    static res<T>(data: T, status: string, retcode: number, message: string = ""): OB11Return<T> {
         return {
             status: status,
-            retcode: status,
+            retcode: retcode,
             data: data,
             message: message
         }
     }
     static ok<T>(data: T) {
-        return OB11Response.res<T>(data)
+        return OB11Response.res<T>(data, "ok", 0)
     }
-    static error(err: string) {
-        return OB11Response.res(null, -1, err)
+    static error(err: string, retcode: number) {
+        return OB11Response.res(null, "failed", retcode, err)
+    }
+}
+
+export class OB11WebsocketResponse {
+    static res<T>(data: T, status: string, retcode: number, echo: string, message: string = ""): OB11WebsocketReturn<T> {
+        return {
+            status: status,
+            retcode: retcode,
+            data: data,
+            echo: echo,
+            message: message
+        }
+    }
+    static ok<T>(data: T, echo: string = "") {
+        return OB11WebsocketResponse.res<T>(data, "ok", 0, echo)
+    }
+    static error(err: string, retcode: number, echo: string = "") {
+        return OB11WebsocketResponse.res(null, "failed", retcode, echo, err)
     }
 }
diff --git a/src/onebot11/server.ts b/src/onebot11/server.ts
index 95b67f6..74e819a 100644
--- a/src/onebot11/server.ts
+++ b/src/onebot11/server.ts
@@ -1,13 +1,16 @@
 import { getConfigUtil, log } from "../common/utils";
 
 const express = require("express");
+const expressWs = require("express-ws");
+
 import { Request } from 'express';
 import { Response } from 'express';
 
 const JSONbig = require('json-bigint')({ storeAsString: true });
 import { selfInfo } from "../common/data";
 import { OB11Message, OB11Return, OB11MessageData } from './types';
-import { actionHandlers } from "./actions";
+import {actionHandlers, actionMap} from "./actions";
+import {OB11Response, OB11WebsocketResponse} from "./actions/utils";
 
 
 // @SiberianHusky 2021-08-15
@@ -48,27 +51,12 @@ function checkSendMessage(sendMsgList: OB11MessageData[]) {
 
 // ==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 expressAPP = express();
 expressAPP.use(express.urlencoded({ extended: true, limit: "500mb" }));
 
+const expressWsApp = express();
+const websocketClientConnections = [];
+
 expressAPP.use((req, res, next) => {
     let data = '';
     req.on('data', chunk => {
@@ -86,12 +74,6 @@ 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) {
 
@@ -99,33 +81,120 @@ export function startExpress(port: number) {
         res.send('llonebot已启动');
     })
 
-    expressAPP.listen(port, "0.0.0.0", () => {
-        console.log(`llonebot started 0.0.0.0:${port}`);
-    });
+    if (getConfigUtil().getConfig().enableHttp) {
+        expressAPP.listen(port, "0.0.0.0", () => {
+            console.log(`llonebot http service started 0.0.0.0:${port}`);
+        });
+    }
+}
+
+export function startWebsocketServer(port: number) {
+    const config = getConfigUtil().getConfig();
+    if (config.enableWs) {
+        expressWs(expressWsApp)
+        expressWsApp.listen(getConfigUtil().getConfig().wsPort, function () {
+            console.log(`llonebot websocket service started 0.0.0.0:${port}`);
+        });
+    }
+}
+
+export function initWebsocket() {
+    if (getConfigUtil().getConfig().enableWs) {
+        expressWsApp.ws("/api", onWebsocketMessage);
+        expressWsApp.ws("/", onWebsocketMessage);
+    }
+
+    initReverseWebsocket();
+}
+
+function initReverseWebsocket() {
+    const config = getConfigUtil().getConfig();
+    if (config.enableWsReverse) {
+        for (const url of config.wsHosts) {
+            try {
+                const wsClient = new WebSocket(url);
+                websocketClientConnections.push(wsClient);
+
+                wsClient.onclose = function (ev) {
+                    let index = websocketClientConnections.indexOf(wsClient);
+                    if (index !== -1) {
+                        websocketClientConnections.splice(index, 1);
+                    }
+                }
+
+                wsClient.onmessage = async function (ev) {
+                    let message = ev.data;
+                    if (typeof message === "string") {
+                        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);
+                                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)));
+                        }
+                    }
+                }
+            }
+            catch (e) {}
+        }
+    }
+}
+
+function onWebsocketMessage(ws, req) {
+    ws.on("message", async function (message) {
+        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)));
+        }
+    })
 }
 
 
 export function postMsg(msg: OB11Message) {
-    const { reportSelfMessage } = getConfigUtil().getConfig()
-    if (!reportSelfMessage) {
-        if (msg.user_id == selfInfo.uin) {
-            return
+    const config = getConfigUtil().getConfig();
+    if (config.enableHttpPost) {
+        if (!config.reportSelfMessage) {
+            if (msg.user_id == selfInfo.uin) {
+                return
+            }
+        }
+        for (const host of config.httpHosts) {
+            fetch(host, {
+                method: "POST",
+                headers: {
+                    "Content-Type": "application/json",
+                    "x-self-id": selfInfo.uin
+                },
+                body: JSON.stringify(msg)
+            }).then((res: any) => {
+                log(`新消息事件上报成功: ${host} ` + JSON.stringify(msg));
+            }, (err: any) => {
+                log(`新消息事件上报失败: ${host} ` + err + JSON.stringify(msg));
+            });
         }
     }
-    for (const host of getConfigUtil().getConfig().hosts) {
-        fetch(host, {
-            method: "POST",
-            headers: {
-                "Content-Type": "application/json",
-                "x-self-id": selfInfo.uin
-            },
-            body: JSON.stringify(msg)
-        }).then((res: any) => {
-            log(`新消息事件上报成功: ${host} ` + JSON.stringify(msg));
-        }, (err: any) => {
-            log(`新消息事件上报失败: ${host} ` + err + JSON.stringify(msg));
-        });
-    }
+
 }
 
 let routers: Record<string, (payload: any) => Promise<OB11Return<any>>> = {};
@@ -143,7 +212,7 @@ function registerRouter(action: string, handle: (payload: any) => Promise<any>)
         }
         catch (e) {
             log(e.stack);
-            res.send(OB11Response.error(e.stack.toString()))
+            res.send(OB11Response.error(e.stack.toString(), 200))
         }
     }
 
diff --git a/src/onebot11/types.ts b/src/onebot11/types.ts
index f07552f..f78f3ab 100644
--- a/src/onebot11/types.ts
+++ b/src/onebot11/types.ts
@@ -86,12 +86,20 @@ export type OB11ApiName =
     | "get_msg"
 
 export interface OB11Return<DataType> {
-    status: number
+    status: string
     retcode: number
     data: DataType
     message: string
 }
 
+export interface OB11WebsocketReturn<DataType> {
+    status: string
+    retcode: number
+    data: DataType
+    echo: string
+    message: string
+}
+
 export interface OB11SendMsgReturn extends OB11Return<{message_id: string}>{}
 
 export enum OB11MessageDataType {
diff --git a/src/renderer.ts b/src/renderer.ts
index 83651f1..cdb3fdd 100644
--- a/src/renderer.ts
+++ b/src/renderer.ts
@@ -20,7 +20,7 @@ async function onSettingWindowCreated(view: Element) {
     }
 
     let hostsEleStr = ""
-    for (const host of config.hosts) {
+    for (const host of config.httpHosts) {
         hostsEleStr += creatHostEleStr(host);
     }
     let html = `
@@ -30,7 +30,7 @@ async function onSettingWindowCreated(view: Element) {
                 <setting-list class="wrap">
                     <setting-item class="vertical-list-item" data-direction="row">
                         <setting-text>监听端口</setting-text>
-                        <input id="port" type="number" value="${config.port}"/>
+                        <input id="port" type="number" value="${config.httpPort}"/>
                     </setting-item>
                     <div>
                         <button id="addHost" class="q-button">添加上报地址</button>
@@ -135,8 +135,8 @@ async function onSettingWindowCreated(view: Element) {
                     hosts.push(hostEle.value);
                 }
             }
-            config.port = parseInt(port);
-            config.hosts = hosts;
+            config.httpPort = parseInt(port);
+            config.httpHosts = hosts;
             window.llonebot.setConfig(config);
             alert("保存成功");
         })

From 8f48d1d4ca0c710ac2ab2cd65f28df46ac848ff9 Mon Sep 17 00:00:00 2001
From: Disy <disymayufei@yeah.net>
Date: Thu, 15 Feb 2024 21:47:16 +0800
Subject: [PATCH 02/12] =?UTF-8?q?feat:=20=E9=A2=84=E6=B7=BB=E5=8A=A0?=
 =?UTF-8?q?=E7=BE=A4=E6=88=90=E5=91=98=E5=8F=98=E5=8A=A8=E4=BA=8B=E4=BB=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/ntqqapi/hook.ts            | 122 +++++++++++++++++++++++-------
 src/onebot11/actions/GetMsg.ts |   1 -
 src/onebot11/event.ts          |  38 ++++++++++
 src/onebot11/server.ts         |  17 ++++-
 src/onebot11/types.ts          |   8 +-
 src/renderer.ts                | 134 +++++++++++++++++++++++++++------
 6 files changed, 260 insertions(+), 60 deletions(-)
 create mode 100644 src/onebot11/event.ts

diff --git a/src/ntqqapi/hook.ts b/src/ntqqapi/hook.ts
index 3b281b3..5680381 100644
--- a/src/ntqqapi/hook.ts
+++ b/src/ntqqapi/hook.ts
@@ -1,10 +1,12 @@
-import { BrowserWindow } from 'electron';
-import { getConfigUtil, log } from "../common/utils";
-import { NTQQApi, NTQQApiClass, sendMessagePool } from "./ntcall";
-import { Group, User } from "./types";
-import { RawMessage } from "./types";
-import { addHistoryMsg, friends, groups, msgHistory } from "../common/data";
-import { v4 as uuidv4 } from 'uuid';
+import {BrowserWindow} from 'electron';
+import {log} from "../common/utils";
+import {NTQQApi, NTQQApiClass, sendMessagePool} from "./ntcall";
+import {Group, GroupMember, RawMessage, User} from "./types";
+import {addHistoryMsg, friends, groups, msgHistory} from "../common/data";
+import {v4 as uuidv4} from 'uuid';
+import {callEvent, EventType} from "../onebot11/event";
+import {OB11Message} from "../onebot11/types";
+import {OB11Constructor} from "../onebot11/constructor";
 
 export let hookApiCallbacks: Record<string, (apiReturn: any) => void> = {}
 
@@ -41,7 +43,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))
+        // console.log(`received ntqq api message: ${channel}`, JSON.stringify(args))
         if (args?.[1] instanceof Array) {
             for (let receiveData of args?.[1]) {
                 const ntQQApiMethodName = receiveData.cmdName;
@@ -90,25 +92,91 @@ export function removeReceiveHook(id: string) {
     receiveHooks.splice(index, 1);
 }
 
-async function updateGroups(_groups: Group[]) {
+async function updateGroups(_groups: Group[], needUpdate: boolean = true) {
     for (let group of _groups) {
-        let existGroup = groups.find(g => g.groupCode == group.groupCode)
-        if (!existGroup) {
-            NTQQApi.getGroupMembers(group.groupCode).then(members => {
-                if (members) {
-                    group.members = members
-                }
-            })
-            groups.push(group)
-            log("update group members", group.members)
-        } else {
-            Object.assign(existGroup, group)
+        let existGroup = groups.find(g => g.groupCode == group.groupCode);
+        if (existGroup) {
+            Object.assign(existGroup, group);
+        }
+        else {
+            groups.push(group);
+        }
+
+        if (needUpdate) {
+            const members = await NTQQApi.getGroupMembers(group.groupCode);
+
+            if (members) {
+                group.members = members;
+            }
         }
     }
 }
 
-registerReceiveHook<{ groupList: Group[] }>(ReceiveCmd.GROUPS, (payload) => updateGroups(payload.groupList).then())
-registerReceiveHook<{ groupList: Group[] }>(ReceiveCmd.GROUPS_UNIX, (payload) => updateGroups(payload.groupList).then())
+async function processGroupEvent(payload) {
+    const newGroupList = payload.groupList;
+    for (const group of newGroupList) {
+        let existGroup = groups.find(g => g.groupCode == group.groupCode);
+        console.log(existGroup.members);
+        if (existGroup) {
+            if (existGroup.memberCount > group.memberCount) {
+                console.log("群人数减少力!");
+                const oldMembers = existGroup.members;
+                const newMembers = await NTQQApi.getGroupMembers(group.groupCode);
+                group.members = newMembers;
+                const newMembersSet = new Set<string>();  // 建立索引降低时间复杂度
+
+                for (const member of newMembers) {
+                    newMembersSet.add(member.uin);
+                }
+
+                console.log(oldMembers);
+                for (const member of oldMembers) {
+                    if (!newMembersSet.has(member.uin)) {
+                        console.log("减少的群员是:" + member.uin);
+                        break;
+                    }
+                }
+
+            }
+            else if (existGroup.memberCount < group.memberCount) {
+                console.log("群人数增加力!");
+
+                const oldMembersSet = new Set<string>();
+                for (const member of existGroup.members) {
+                    oldMembersSet.add(member.uin);
+                }
+
+                const newMembers = await NTQQApi.getGroupMembers(group.groupCode);
+                group.members = newMembers;
+                for (const member of newMembers) {
+                    if (!oldMembersSet.has(member.uin)) {
+                        console.log("增加的群员是:" + member.uin);
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+    updateGroups(newGroupList, false).then();
+}
+
+registerReceiveHook<{ groupList: Group[], updateType: number }>(ReceiveCmd.GROUPS, (payload) => {
+    if (payload.updateType != 2) {
+        updateGroups(payload.groupList).then();
+    }
+    else {
+        processGroupEvent(payload).then();
+    }
+})
+registerReceiveHook<{ groupList: Group[], updateType: number }>(ReceiveCmd.GROUPS_UNIX, (payload) => {
+    if (payload.updateType != 2) {
+        updateGroups(payload.groupList).then();
+    }
+    else {
+        processGroupEvent(payload).then();
+    }
+})
 registerReceiveHook<{
     data: { categoryId: number, categroyName: string, categroyMbCount: number, buddyList: User[] }[]
 }>(ReceiveCmd.FRIENDS, payload => {
@@ -125,10 +193,6 @@ registerReceiveHook<{
     }
 })
 
-// registerReceiveHook<any>(ReceiveCmd.USER_INFO, (payload)=>{
-//     log("user info", payload);
-// })
-
 registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.UPDATE_MSG, (payload) => {
     for (const message of payload.msgList) {
         addHistoryMsg(message)
@@ -138,6 +202,12 @@ registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.UPDATE_MSG, (payl
 registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.NEW_MSG, (payload) => {
     for (const message of payload.msgList) {
         // log("收到新消息,push到历史记录", message)
+        OB11Constructor.message(message).then(
+            function (message) {
+                callEvent<OB11Message>(EventType.MESSAGE, message);
+            }
+        );
+
         addHistoryMsg(message)
     }
     const msgIds = Object.keys(msgHistory);
diff --git a/src/onebot11/actions/GetMsg.ts b/src/onebot11/actions/GetMsg.ts
index 538c2be..22de0cf 100644
--- a/src/onebot11/actions/GetMsg.ts
+++ b/src/onebot11/actions/GetMsg.ts
@@ -1,7 +1,6 @@
 import { getHistoryMsgByShortId, msgHistory } from "../../common/data";
 import { OB11Message } from '../types';
 import { OB11Constructor } from "../constructor";
-import { log } from "../../common/utils";
 import BaseAction from "./BaseAction";
 import { ActionName } from "./types";
 
diff --git a/src/onebot11/event.ts b/src/onebot11/event.ts
new file mode 100644
index 0000000..e334977
--- /dev/null
+++ b/src/onebot11/event.ts
@@ -0,0 +1,38 @@
+import {selfInfo} from "../common/data";
+
+const websocketList = [];
+
+export enum EventType {
+    META = "meta_event",
+    REQUEST = "request",
+    NOTICE = "notice",
+    MESSAGE = "message"
+}
+
+export function registerEventSender(ws) {
+    websocketList.push(ws);
+}
+
+export function unregisterEventSender(ws) {
+    let index = websocketList.indexOf(ws);
+    if (index !== -1) {
+        websocketList.splice(index, 1);
+    }
+}
+
+export function callEvent<DataType>(type: EventType, data: DataType) {
+    const basicEvent = {
+        time: new Date().getTime(),
+        self_id: selfInfo.uin,
+        post_type: type
+    }
+
+
+    for (const ws of websocketList) {
+        ws.send(
+            JSON.stringify(
+                Object.assign(basicEvent, data)
+            )
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/onebot11/server.ts b/src/onebot11/server.ts
index 74e819a..e46201b 100644
--- a/src/onebot11/server.ts
+++ b/src/onebot11/server.ts
@@ -11,6 +11,7 @@ import { selfInfo } from "../common/data";
 import { OB11Message, OB11Return, OB11MessageData } from './types';
 import {actionHandlers, actionMap} from "./actions";
 import {OB11Response, OB11WebsocketResponse} from "./actions/utils";
+import {registerEventSender, unregisterEventSender} from "./event";
 
 
 // @SiberianHusky 2021-08-15
@@ -100,8 +101,8 @@ export function startWebsocketServer(port: number) {
 
 export function initWebsocket() {
     if (getConfigUtil().getConfig().enableWs) {
-        expressWsApp.ws("/api", onWebsocketMessage);
-        expressWsApp.ws("/", onWebsocketMessage);
+        expressWsApp.ws("/api", initWebsocketServer);
+        expressWsApp.ws("/", initWebsocketServer);
     }
 
     initReverseWebsocket();
@@ -114,8 +115,10 @@ function initReverseWebsocket() {
             try {
                 const wsClient = new WebSocket(url);
                 websocketClientConnections.push(wsClient);
+                registerEventSender(wsClient);
 
                 wsClient.onclose = function (ev) {
+                    unregisterEventSender(wsClient);
                     let index = websocketClientConnections.indexOf(wsClient);
                     if (index !== -1) {
                         websocketClientConnections.splice(index, 1);
@@ -149,7 +152,9 @@ function initReverseWebsocket() {
     }
 }
 
-function onWebsocketMessage(ws, req) {
+function initWebsocketServer(ws, req) {
+    registerEventSender(ws);
+
     ws.on("message", async function (message) {
         try {
             let recv = JSON.parse(message);
@@ -167,7 +172,11 @@ function onWebsocketMessage(ws, req) {
             log(e.stack);
             ws.send(JSON.stringify(OB11WebsocketResponse.error(e.stack.toString(), 1200)));
         }
-    })
+    });
+
+    ws.on("close", function (ev) {
+        unregisterEventSender(ws);
+    });
 }
 
 
diff --git a/src/onebot11/types.ts b/src/onebot11/types.ts
index f78f3ab..fc2d900 100644
--- a/src/onebot11/types.ts
+++ b/src/onebot11/types.ts
@@ -1,19 +1,19 @@
 import { AtType } from "../ntqqapi/types";
 import { RawMessage } from "../ntqqapi/types";
 
-export interface OB11User{
+export interface OB11User {
     user_id: string;
     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",
@@ -33,7 +33,7 @@ export interface OB11GroupMember {
     title?: string
 }
 
-export interface OB11Group{
+export interface OB11Group {
     group_id: string
     group_name: string
     member_count?: number
diff --git a/src/renderer.ts b/src/renderer.ts
index cdb3fdd..03f5a34 100644
--- a/src/renderer.ts
+++ b/src/renderer.ts
@@ -6,11 +6,11 @@ async function onSettingWindowCreated(view: Element) {
     window.llonebot.log("setting window created");
     let config = await window.llonebot.getConfig()
 
-    function creatHostEleStr(host: string) {
+    function createHttpHostEleStr(host: string) {
         let eleStr = `
             <setting-item data-direction="row" class="hostItem vertical-list-item">
                 <h2>事件上报地址(http)</h2>
-                <input class="host input-text" type="text" value="${host}" 
+                <input class="httpHost input-text" type="text" value="${host}" 
                 style="width:60%;padding: 5px"
                 placeholder="如果localhost上报失败试试局域网ip"/>
             </setting-item>
@@ -19,29 +19,87 @@ async function onSettingWindowCreated(view: Element) {
         return eleStr
     }
 
-    let hostsEleStr = ""
-    for (const host of config.httpHosts) {
-        hostsEleStr += creatHostEleStr(host);
+    function createWsHostEleStr(host: string) {
+        let eleStr = `
+            <setting-item data-direction="row" class="hostItem vertical-list-item">
+                <h2>事件上报地址(反向websocket)</h2>
+                <input class="wsHost input-text" type="text" value="${host}" 
+                style="width:60%;padding: 5px"
+                placeholder="如果localhost上报失败试试局域网ip"/>
+            </setting-item>
+
+            `
+        return eleStr
     }
+
+    let httpHostsEleStr = ""
+    for (const host of config.httpHosts) {
+        httpHostsEleStr += createHttpHostEleStr(host);
+    }
+
+    let wsHostsEleStr = ""
+    for (const host of config.wsHosts) {
+        wsHostsEleStr += createWsHostEleStr(host);
+    }
+
     let html = `
     <div class="config_view llonebot">
         <setting-section>
             <setting-panel>
                 <setting-list class="wrap">
                     <setting-item class="vertical-list-item" data-direction="row">
-                        <setting-text>监听端口</setting-text>
-                        <input id="port" type="number" value="${config.httpPort}"/>
+                        <setting-text>HTTP监听端口</setting-text>
+                        <input id="httpPort" type="number" value="${config.httpPort}"/>
                     </setting-item>
                     <div>
-                        <button id="addHost" class="q-button">添加上报地址</button>
+                        <button id="addHttpHost" class="q-button">添加HTTP POST上报地址</button>
                     </div>
-                    <div id="hostItems">
-                        ${hostsEleStr}
+                    <div id="httpHostItems">
+                        ${httpHostsEleStr}
+                    </div>
+                    
+                    <setting-item class="vertical-list-item" data-direction="row">
+                        <setting-text>正向Websocket监听端口</setting-text>
+                        <input id="wsPort" type="number" value="${config.wsPort}"/>
+                    </setting-item>
+                    <div>
+                        <button id="addWsHost" class="q-button">添加反向Websocket上报地址</button>
+                    </div>
+                    <div id="wsHostItems">
+                        ${wsHostsEleStr}
                     </div>
                     <button id="save" class="q-button">保存(监听端口重启QQ后生效)</button>
                 </setting-list>
             </setting-panel>
             <setting-panel>
+                <setting-item data-direction="row" class="hostItem vertical-list-item">
+                    <div>
+                        <div>启用HTTP支持</div>
+                        <div class="tips">修改后须重启QQ生效</div>
+                    </div>
+                    <setting-switch id="http" ${config.enableHttp ? "is-active" : ""}></setting-switch>
+                </setting-item>
+                <setting-item data-direction="row" class="hostItem vertical-list-item">
+                    <div>
+                        <div>启用HTTP POST支持</div>
+                        <div class="tips">修改后须重启QQ生效</div>
+                    </div>
+                    <setting-switch id="httpPost" ${config.enableHttpPost ? "is-active" : ""}></setting-switch>
+                </setting-item>
+                <setting-item data-direction="row" class="hostItem vertical-list-item">
+                    <div>
+                        <div>启用正向Websocket支持</div>
+                        <div class="tips">修改后须重启QQ生效</div>
+                    </div>
+                    <setting-switch id="websocket" ${config.enableWs ? "is-active" : ""}></setting-switch>
+                </setting-item>
+                <setting-item data-direction="row" class="hostItem vertical-list-item">
+                    <div>
+                        <div>启用反向Websocket支持</div>
+                        <div class="tips">修改后须重启QQ生效</div>
+                    </div>
+                    <setting-switch id="websocketReverse" ${config.enableWsReverse ? "is-active" : ""}></setting-switch>
+                </setting-item>
                 <setting-item data-direction="row" class="hostItem vertical-list-item">
                     <div>
                         <div>上报文件进行base64编码</div>
@@ -92,15 +150,23 @@ async function onSettingWindowCreated(view: Element) {
     const doc = parser.parseFromString(html, "text/html");
 
 
-    function addHostEle(initValue: string = "") {
-        let addressDoc = parser.parseFromString(creatHostEleStr(initValue), "text/html");
+    function addHostEle(type: string, initValue: string = "") {
+        let addressDoc = parser.parseFromString(createHttpHostEleStr(initValue), "text/html");
         let addressEle = addressDoc.querySelector("setting-item")
-        let hostItemsEle = document.getElementById("hostItems");
+        let hostItemsEle;
+        if (type === "ws") {
+            hostItemsEle = document.getElementById("wsHostItems");
+        }
+        else {
+            hostItemsEle = document.getElementById("httpHostItems");
+        }
+
         hostItemsEle.appendChild(addressEle);
     }
 
 
-    doc.getElementById("addHost").addEventListener("click", () => addHostEle())
+    doc.getElementById("addHttpHost").addEventListener("click", () => addHostEle("http"))
+    doc.getElementById("addWsHost").addEventListener("click", () => addHostEle("ws"))
 
     function switchClick(eleId: string, configKey: string) {
         doc.getElementById(eleId)?.addEventListener("click", (e) => {
@@ -116,6 +182,10 @@ async function onSettingWindowCreated(view: Element) {
         })
     }
 
+    switchClick("http", "enableHttp");
+    switchClick("httpPost", "enableHttpPost");
+    switchClick("websocket", "enableWs");
+    switchClick("websocketReverse", "enableWsReverse");
     switchClick("debug", "debug");
     switchClick("switchBase64", "enableBase64");
     switchClick("reportSelfMessage", "reportSelfMessage");
@@ -123,20 +193,35 @@ async function onSettingWindowCreated(view: Element) {
 
     doc.getElementById("save")?.addEventListener("click",
         () => {
-            const portEle: HTMLInputElement = document.getElementById("port") 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
+            const httpPortEle: HTMLInputElement = document.getElementById("httpPort") as HTMLInputElement;
+            const httpHostEles: HTMLCollectionOf<HTMLInputElement> = document.getElementsByClassName("httpHost") as HTMLCollectionOf<HTMLInputElement>;
+            const wsPortEle: HTMLInputElement = document.getElementById("wsPort") as HTMLInputElement;
+            const wsHostEles: HTMLCollectionOf<HTMLInputElement> = document.getElementsByClassName("wsHost") as HTMLCollectionOf<HTMLInputElement>;
+
             // 获取端口和host
-            const port = portEle.value
-            let hosts: string[] = [];
-            for (const hostEle of hostEles) {
+            const httpPort = httpPortEle.value
+            let httpHosts: string[] = [];
+
+            for (const hostEle of httpHostEles) {
                 if (hostEle.value) {
-                    hosts.push(hostEle.value);
+                    httpHosts.push(hostEle.value);
                 }
             }
-            config.httpPort = parseInt(port);
-            config.httpHosts = hosts;
+
+            const wsPort = wsPortEle.value
+            let wsHosts: string[] = [];
+
+            for (const hostEle of wsHostEles) {
+                if (hostEle.value) {
+                    wsHosts.push(hostEle.value);
+                }
+            }
+
+
+            config.httpPort = parseInt(httpPort);
+            config.httpHosts = httpHosts;
+            config.wsPort = parseInt(wsPort);
+            config.wsHosts = wsHosts;
             window.llonebot.setConfig(config);
             alert("保存成功");
         })
@@ -146,7 +231,6 @@ async function onSettingWindowCreated(view: Element) {
         view.appendChild(node);
     });
 
-
 }
 
 

From 72b1c906f70a10c1974d8f5f3f92c63d0a93b23d Mon Sep 17 00:00:00 2001
From: Disy <disymayufei@yeah.net>
Date: Thu, 15 Feb 2024 22:26:53 +0800
Subject: [PATCH 03/12] fix: Notification event not effective

---
 src/ntqqapi/hook.ts | 107 ++++++++++++++++++++++++++++----------------
 1 file changed, 68 insertions(+), 39 deletions(-)

diff --git a/src/ntqqapi/hook.ts b/src/ntqqapi/hook.ts
index 5680381..d9050f6 100644
--- a/src/ntqqapi/hook.ts
+++ b/src/ntqqapi/hook.ts
@@ -100,65 +100,90 @@ async function updateGroups(_groups: Group[], needUpdate: boolean = true) {
         }
         else {
             groups.push(group);
+            existGroup = group;
         }
 
         if (needUpdate) {
             const members = await NTQQApi.getGroupMembers(group.groupCode);
 
             if (members) {
-                group.members = members;
+                existGroup.members = members;
             }
         }
     }
 }
 
 async function processGroupEvent(payload) {
-    const newGroupList = payload.groupList;
-    for (const group of newGroupList) {
-        let existGroup = groups.find(g => g.groupCode == group.groupCode);
-        console.log(existGroup.members);
-        if (existGroup) {
-            if (existGroup.memberCount > group.memberCount) {
-                console.log("群人数减少力!");
-                const oldMembers = existGroup.members;
-                const newMembers = await NTQQApi.getGroupMembers(group.groupCode);
-                group.members = newMembers;
-                const newMembersSet = new Set<string>();  // 建立索引降低时间复杂度
-
-                for (const member of newMembers) {
-                    newMembersSet.add(member.uin);
-                }
-
-                console.log(oldMembers);
-                for (const member of oldMembers) {
-                    if (!newMembersSet.has(member.uin)) {
-                        console.log("减少的群员是:" + member.uin);
-                        break;
+    try {
+        const newGroupList = payload.groupList;
+        for (const group of newGroupList) {
+            let existGroup = groups.find(g => g.groupCode == group.groupCode);
+            if (existGroup) {
+                if (existGroup.memberCount > group.memberCount) {
+                    console.log("群人数减少力!");
+                    const oldMembers = existGroup.members;
+                    console.log("旧群人员:");
+                    for (const member of oldMembers) {
+                        console.log(member.nick);
                     }
+
+                    const newMembers = await NTQQApi.getGroupMembers(group.groupCode);
+                    console.log("新群人员:");
+                    for (const member of newMembers) {
+                        console.log(member.nick);
+                    }
+
+                    group.members = newMembers;
+                    const newMembersSet = new Set<string>();  // 建立索引降低时间复杂度
+
+                    for (const member of newMembers) {
+                        newMembersSet.add(member.uin);
+                    }
+
+                    for (const member of oldMembers) {
+                        if (!newMembersSet.has(member.uin)) {
+                            console.log("减少的群员是:" + member.uin);
+                            break;
+                        }
+                    }
+
                 }
+                else if (existGroup.memberCount < group.memberCount) {
+                    console.log("群人数增加力!");
+                    console.log("旧群人员:");
+                    for (const member of existGroup.members) {
+                        console.log(member.nick);
+                    }
 
-            }
-            else if (existGroup.memberCount < group.memberCount) {
-                console.log("群人数增加力!");
+                    const oldMembersSet = new Set<string>();
+                    for (const member of existGroup.members) {
+                        oldMembersSet.add(member.uin);
+                    }
 
-                const oldMembersSet = new Set<string>();
-                for (const member of existGroup.members) {
-                    oldMembersSet.add(member.uin);
-                }
+                    const newMembers = await NTQQApi.getGroupMembers(group.groupCode);
 
-                const newMembers = await NTQQApi.getGroupMembers(group.groupCode);
-                group.members = newMembers;
-                for (const member of newMembers) {
-                    if (!oldMembersSet.has(member.uin)) {
-                        console.log("增加的群员是:" + member.uin);
-                        break;
+                    console.log("新群人员:");
+                    for (const member of newMembers) {
+                        console.log(member.nick);
+                    }
+
+                    group.members = newMembers;
+                    for (const member of newMembers) {
+                        if (!oldMembersSet.has(member.uin)) {
+                            console.log("增加的群员是:" + member.uin);
+                            break;
+                        }
                     }
                 }
             }
         }
-    }
 
-    updateGroups(newGroupList, false).then();
+        updateGroups(newGroupList, false).then();
+    }
+    catch (e) {
+        updateGroups(payload.groupList).then();
+        console.log(e);
+    }
 }
 
 registerReceiveHook<{ groupList: Group[], updateType: number }>(ReceiveCmd.GROUPS, (payload) => {
@@ -166,7 +191,9 @@ registerReceiveHook<{ groupList: Group[], updateType: number }>(ReceiveCmd.GROUP
         updateGroups(payload.groupList).then();
     }
     else {
-        processGroupEvent(payload).then();
+        if (process.platform == "win32") {
+            processGroupEvent(payload).then();
+        }
     }
 })
 registerReceiveHook<{ groupList: Group[], updateType: number }>(ReceiveCmd.GROUPS_UNIX, (payload) => {
@@ -174,7 +201,9 @@ registerReceiveHook<{ groupList: Group[], updateType: number }>(ReceiveCmd.GROUP
         updateGroups(payload.groupList).then();
     }
     else {
-        processGroupEvent(payload).then();
+        if (process.platform != "win32") {
+            processGroupEvent(payload).then();
+        }
     }
 })
 registerReceiveHook<{

From 018ec070827f9a3fd04d99d9e60923175ce52b36 Mon Sep 17 00:00:00 2001
From: Disy <disymayufei@yeah.net>
Date: Fri, 16 Feb 2024 22:34:12 +0800
Subject: [PATCH 04/12] feat: support reverse websocket

---
 package-lock.json                           | 43 +++++++++--
 package.json                                |  4 +-
 src/common/utils.ts                         |  4 +
 src/ntqqapi/hook.ts                         | 42 ++++-------
 src/onebot11/ReconnectingWebsocket.ts       | 64 ++++++++++++++++
 src/onebot11/actions/SendMsg.ts             |  1 -
 src/onebot11/event/BaseEvent.ts             | 10 +++
 src/onebot11/event/BaseMessageEvent.ts      |  8 ++
 src/onebot11/event/GroupDecreaseEvent.ts    | 20 +++++
 src/onebot11/event/GroupIncreaseEvent.ts    | 21 ++++++
 src/onebot11/{event.ts => event/manager.ts} | 16 ++--
 src/onebot11/server.ts                      | 84 ++++++++++++++-------
 12 files changed, 242 insertions(+), 75 deletions(-)
 create mode 100644 src/onebot11/ReconnectingWebsocket.ts
 create mode 100644 src/onebot11/event/BaseEvent.ts
 create mode 100644 src/onebot11/event/BaseMessageEvent.ts
 create mode 100644 src/onebot11/event/GroupDecreaseEvent.ts
 create mode 100644 src/onebot11/event/GroupIncreaseEvent.ts
 rename src/onebot11/{event.ts => event/manager.ts} (58%)

diff --git a/package-lock.json b/package-lock.json
index 35582a0..9c1e3a4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,12 +13,14 @@
         "express": "^4.18.2",
         "express-ws": "^5.0.2",
         "json-bigint": "^1.0.0",
-        "uuid": "^9.0.1"
+        "uuid": "^9.0.1",
+        "ws": "^8.16.0"
       },
       "devDependencies": {
         "@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",
@@ -2200,6 +2202,15 @@
       "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==",
       "dev": true
     },
+    "node_modules/@types/ws": {
+      "version": "8.5.10",
+      "resolved": "https://registry.npmmirror.com/@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",
@@ -3138,6 +3149,26 @@
         "express": "^4.0.0 || ^5.0.0-alpha.1"
       }
     },
+    "node_modules/express-ws/node_modules/ws": {
+      "version": "7.5.9",
+      "resolved": "https://registry.npmmirror.com/ws/-/ws-7.5.9.tgz",
+      "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
+      "engines": {
+        "node": ">=8.3.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": "^5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/fast-deep-equal": {
       "version": "3.1.3",
       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -4707,15 +4738,15 @@
       "dev": true
     },
     "node_modules/ws": {
-      "version": "7.5.9",
-      "resolved": "https://registry.npmmirror.com/ws/-/ws-7.5.9.tgz",
-      "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
+      "version": "8.16.0",
+      "resolved": "https://registry.npmmirror.com/ws/-/ws-8.16.0.tgz",
+      "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
       "engines": {
-        "node": ">=8.3.0"
+        "node": ">=10.0.0"
       },
       "peerDependencies": {
         "bufferutil": "^4.0.1",
-        "utf-8-validate": "^5.0.2"
+        "utf-8-validate": ">=5.0.2"
       },
       "peerDependenciesMeta": {
         "bufferutil": {
diff --git a/package.json b/package.json
index b5de92b..e7a043a 100644
--- a/package.json
+++ b/package.json
@@ -21,12 +21,14 @@
     "express": "^4.18.2",
     "express-ws": "^5.0.2",
     "json-bigint": "^1.0.0",
-    "uuid": "^9.0.1"
+    "uuid": "^9.0.1",
+    "ws": "^8.16.0"
   },
   "devDependencies": {
     "@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",
diff --git a/src/common/utils.ts b/src/common/utils.ts
index 78a52c2..233d2bd 100644
--- a/src/common/utils.ts
+++ b/src/common/utils.ts
@@ -49,6 +49,10 @@ export function isGIF(path: string) {
     return buffer.toString() === 'GIF8'
 }
 
+export function sleep(ms: number): Promise<void> {
+    return new Promise(resolve => setTimeout(resolve, ms));
+}
+
 
 // 定义一个异步函数来检查文件是否存在
 export function checkFileReceived(path: string, timeout: number=3000): Promise<void> {
diff --git a/src/ntqqapi/hook.ts b/src/ntqqapi/hook.ts
index d9050f6..c9800f0 100644
--- a/src/ntqqapi/hook.ts
+++ b/src/ntqqapi/hook.ts
@@ -1,12 +1,15 @@
 import {BrowserWindow} from 'electron';
-import {log} from "../common/utils";
+import {log, sleep} from "../common/utils";
 import {NTQQApi, NTQQApiClass, sendMessagePool} from "./ntcall";
 import {Group, GroupMember, RawMessage, User} from "./types";
 import {addHistoryMsg, friends, groups, msgHistory} from "../common/data";
 import {v4 as uuidv4} from 'uuid';
-import {callEvent, EventType} from "../onebot11/event";
+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";
 
 export let hookApiCallbacks: Record<string, (apiReturn: any) => void> = {}
 
@@ -120,18 +123,10 @@ async function processGroupEvent(payload) {
             let existGroup = groups.find(g => g.groupCode == group.groupCode);
             if (existGroup) {
                 if (existGroup.memberCount > group.memberCount) {
-                    console.log("群人数减少力!");
                     const oldMembers = existGroup.members;
-                    console.log("旧群人员:");
-                    for (const member of oldMembers) {
-                        console.log(member.nick);
-                    }
 
+                    await sleep(200);  // 如果请求QQ API的速度过快,通常无法正确拉取到最新的群信息,因此这里人为引入一个延时
                     const newMembers = await NTQQApi.getGroupMembers(group.groupCode);
-                    console.log("新群人员:");
-                    for (const member of newMembers) {
-                        console.log(member.nick);
-                    }
 
                     group.members = newMembers;
                     const newMembersSet = new Set<string>();  // 建立索引降低时间复杂度
@@ -142,35 +137,26 @@ async function processGroupEvent(payload) {
 
                     for (const member of oldMembers) {
                         if (!newMembersSet.has(member.uin)) {
-                            console.log("减少的群员是:" + member.uin);
+                            callEvent(new GroupDecreaseEvent(group.groupCode, parseInt(member.uin)));
                             break;
                         }
                     }
 
                 }
                 else if (existGroup.memberCount < group.memberCount) {
-                    console.log("群人数增加力!");
-                    console.log("旧群人员:");
-                    for (const member of existGroup.members) {
-                        console.log(member.nick);
-                    }
-
+                    const oldMembers = existGroup.members;
                     const oldMembersSet = new Set<string>();
-                    for (const member of existGroup.members) {
+                    for (const member of oldMembers) {
                         oldMembersSet.add(member.uin);
                     }
 
+                    await sleep(200);
                     const newMembers = await NTQQApi.getGroupMembers(group.groupCode);
 
-                    console.log("新群人员:");
-                    for (const member of newMembers) {
-                        console.log(member.nick);
-                    }
-
                     group.members = newMembers;
                     for (const member of newMembers) {
                         if (!oldMembersSet.has(member.uin)) {
-                            console.log("增加的群员是:" + member.uin);
+                            callEvent(new GroupIncreaseEvent(group.groupCode, parseInt(member.uin)));
                             break;
                         }
                     }
@@ -231,13 +217,13 @@ registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.UPDATE_MSG, (payl
 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>(EventType.MESSAGE, message);
+                callEvent<OB11Message>(new BaseMessageEvent(), message);
             }
         );
-
-        addHistoryMsg(message)
     }
     const msgIds = Object.keys(msgHistory);
     if (msgIds.length > 30000) {
diff --git a/src/onebot11/ReconnectingWebsocket.ts b/src/onebot11/ReconnectingWebsocket.ts
new file mode 100644
index 0000000..032a5b9
--- /dev/null
+++ b/src/onebot11/ReconnectingWebsocket.ts
@@ -0,0 +1,64 @@
+const WebSocket = require("ws");
+
+class ReconnectingWebsocket {
+    private websocket;
+    private readonly url: string;
+
+    public constructor(url: string) {
+        this.url = url;
+        this.reconnect()
+    }
+
+    public onopen = function (){}
+
+    public onmessage = function (msg){}
+
+    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);
+        }
+    }
+
+    private reconnect() {
+        this.websocket = new WebSocket(this.url, {
+            handshakeTimeout: 2000,
+            perMessageDeflate: false
+        });
+
+        console.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);
+            instance.onopen();
+        });
+
+        this.websocket.on("message", function message(data) {
+            instance.onmessage(data.toString());
+        });
+
+        this.websocket.on("error", console.error);
+
+        this.websocket.on("ping", this.heartbeat);
+
+        this.websocket.on("close", function close() {
+            console.log("The websocket connection: " + instance.url + " closed, trying reconnecting...");
+            instance.onclose();
+
+            setTimeout(instance.reconnect, 3000);
+        });
+    }
+}
+
+export default ReconnectingWebsocket;
\ No newline at end of file
diff --git a/src/onebot11/actions/SendMsg.ts b/src/onebot11/actions/SendMsg.ts
index 5f374f1..c8bb429 100644
--- a/src/onebot11/actions/SendMsg.ts
+++ b/src/onebot11/actions/SendMsg.ts
@@ -13,7 +13,6 @@ import { SendMessageElement } from "../../ntqqapi/types";
 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";
diff --git a/src/onebot11/event/BaseEvent.ts b/src/onebot11/event/BaseEvent.ts
new file mode 100644
index 0000000..39e8679
--- /dev/null
+++ b/src/onebot11/event/BaseEvent.ts
@@ -0,0 +1,10 @@
+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
new file mode 100644
index 0000000..ba47aee
--- /dev/null
+++ b/src/onebot11/event/BaseMessageEvent.ts
@@ -0,0 +1,8 @@
+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/GroupDecreaseEvent.ts b/src/onebot11/event/GroupDecreaseEvent.ts
new file mode 100644
index 0000000..b53c326
--- /dev/null
+++ b/src/onebot11/event/GroupDecreaseEvent.ts
@@ -0,0 +1,20 @@
+import BaseEvent from "./BaseEvent";
+import {EventType} from "./manager";
+
+class GroupDecreaseEvent extends BaseEvent {
+    post_type = EventType.NOTICE;
+    notice_type = "group_decrease";
+    subtype = "leave";  // TODO: 实现其他几种子类型的识别
+    group_id: number;
+    operate_id: number;
+    user_id: number;
+
+    constructor(groupId: number, userId: number) {
+        super();
+        this.group_id = groupId;
+        this.operate_id = userId;  // 实际上不应该这么实现,但是现在还没有办法识别用户是被踢出的,还是自己主动退出的
+        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/GroupIncreaseEvent.ts
new file mode 100644
index 0000000..9d75b1c
--- /dev/null
+++ b/src/onebot11/event/GroupIncreaseEvent.ts
@@ -0,0 +1,21 @@
+import BaseEvent from "./BaseEvent";
+import {EventType} from "./manager";
+
+class GroupIncreaseEvent extends BaseEvent {
+    post_type = EventType.NOTICE;
+    notice_type = "group_increase";
+    subtype = "approve";  // TODO: 实现其他几种子类型的识别
+    group_id: number;
+    operate_id: number;
+    user_id: number;
+
+    constructor(groupId: number, userId: number) {
+        super();
+        this.group_id = groupId;
+        this.operate_id = userId;  // 实际上不应该这么实现,但是现在还没有办法识别用户是被邀请的,还是主动加入的
+        this.user_id = userId;
+    }
+}
+
+
+export default GroupIncreaseEvent
\ No newline at end of file
diff --git a/src/onebot11/event.ts b/src/onebot11/event/manager.ts
similarity index 58%
rename from src/onebot11/event.ts
rename to src/onebot11/event/manager.ts
index e334977..2bf0de2 100644
--- a/src/onebot11/event.ts
+++ b/src/onebot11/event/manager.ts
@@ -1,4 +1,5 @@
-import {selfInfo} from "../common/data";
+import {selfInfo} from "../../common/data";
+import BaseEvent from "./BaseEvent";
 
 const websocketList = [];
 
@@ -20,19 +21,12 @@ export function unregisterEventSender(ws) {
     }
 }
 
-export function callEvent<DataType>(type: EventType, data: DataType) {
-    const basicEvent = {
-        time: new Date().getTime(),
-        self_id: selfInfo.uin,
-        post_type: type
-    }
-
+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(
-                Object.assign(basicEvent, data)
-            )
+            JSON.stringify(assignedEvent)
         );
     }
 }
\ No newline at end of file
diff --git a/src/onebot11/server.ts b/src/onebot11/server.ts
index e46201b..88ef669 100644
--- a/src/onebot11/server.ts
+++ b/src/onebot11/server.ts
@@ -11,10 +11,17 @@ import { selfInfo } from "../common/data";
 import { OB11Message, OB11Return, OB11MessageData } from './types';
 import {actionHandlers, actionMap} from "./actions";
 import {OB11Response, OB11WebsocketResponse} from "./actions/utils";
-import {registerEventSender, unregisterEventSender} from "./event";
+import {registerEventSender, unregisterEventSender} from "./event/manager";
+import ReconnectingWebsocket from "./ReconnectingWebsocket";
 
 
 // @SiberianHusky 2021-08-15
+enum WebsocketType {
+    API,
+    EVENT,
+    ALL
+}
+
 function checkSendMessage(sendMsgList: OB11MessageData[]) {
     function checkUri(uri: string): boolean {
         const pattern = /^(file:\/\/|http:\/\/|https:\/\/|base64:\/\/)/;
@@ -92,17 +99,29 @@ export function startExpress(port: number) {
 export function startWebsocketServer(port: number) {
     const config = getConfigUtil().getConfig();
     if (config.enableWs) {
-        expressWs(expressWsApp)
-        expressWsApp.listen(getConfigUtil().getConfig().wsPort, function () {
-            console.log(`llonebot websocket service started 0.0.0.0:${port}`);
-        });
+        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() {
     if (getConfigUtil().getConfig().enableWs) {
-        expressWsApp.ws("/api", initWebsocketServer);
-        expressWsApp.ws("/", initWebsocketServer);
+        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);
+        });
     }
 
     initReverseWebsocket();
@@ -113,11 +132,12 @@ function initReverseWebsocket() {
     if (config.enableWsReverse) {
         for (const url of config.wsHosts) {
             try {
-                const wsClient = new WebSocket(url);
+                let wsClient = new ReconnectingWebsocket(url);
                 websocketClientConnections.push(wsClient);
                 registerEventSender(wsClient);
 
-                wsClient.onclose = function (ev) {
+                wsClient.onclose = function () {
+                    console.log("The websocket connection: " + url + " closed, trying reconnecting...");
                     unregisterEventSender(wsClient);
                     let index = websocketClientConnections.indexOf(wsClient);
                     if (index !== -1) {
@@ -125,8 +145,8 @@ function initReverseWebsocket() {
                     }
                 }
 
-                wsClient.onmessage = async function (ev) {
-                    let message = ev.data;
+                wsClient.onmessage = async function (message) {
+                    console.log(message);
                     if (typeof message === "string") {
                         try {
                             let recv = JSON.parse(message);
@@ -147,35 +167,43 @@ function initReverseWebsocket() {
                     }
                 }
             }
-            catch (e) {}
+            catch (e) {
+                console.log(e);
+            }
         }
     }
 }
 
-function initWebsocketServer(ws, req) {
-    registerEventSender(ws);
+function initWebsocketServer(ws, req, type: WebsocketType) {
+    if (type == WebsocketType.EVENT || type == WebsocketType.ALL) {
+        registerEventSender(ws);
+    }
 
     ws.on("message", async function (message) {
-        try {
-            let recv = JSON.parse(message);
-            let echo = recv.echo ?? "";
+        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));
+                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)));
             }
-            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) {
-        unregisterEventSender(ws);
+        if (type == WebsocketType.EVENT || type == WebsocketType.ALL) {
+            unregisterEventSender(ws);
+        }
     });
 }
 

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 05/12] 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 06/12] 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 07/12] 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)

From 6e9704443751c7eebdeed0295120efc707c7f16e Mon Sep 17 00:00:00 2001
From: Disy <disymayufei@yeah.net>
Date: Mon, 19 Feb 2024 23:05:44 +0800
Subject: [PATCH 08/12] feat: cache config

---
 src/common/config.ts | 37 +++++++++++++++++++++++++------------
 1 file changed, 25 insertions(+), 12 deletions(-)

diff --git a/src/common/config.ts b/src/common/config.ts
index 762a04b..d595780 100644
--- a/src/common/config.ts
+++ b/src/common/config.ts
@@ -3,13 +3,27 @@ import {Config} from "./types";
 const fs = require("fs");
 
 export class ConfigUtil {
-    configPath: string;
+    private readonly configPath: string;
+    private config: Config | null = null;
 
     constructor(configPath: string) {
         this.configPath = configPath;
     }
 
     getConfig(): Config {
+        if (this.config) {
+            return this.config;
+        }
+
+        this.reloadConfig();
+    }
+  
+    setConfig(config: Config) {
+        this.config = config;
+        fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2), "utf-8");
+    }
+
+    reloadConfig() {
         let defaultConfig: Config = {
             httpPort: 3000,
             httpHosts: [],
@@ -20,36 +34,35 @@ export class ConfigUtil {
             debug: false,
             log: false,
             reportSelfMessage: false
-        }
+        };
+
         if (!fs.existsSync(this.configPath)) {
             return defaultConfig
         } else {
             const data = fs.readFileSync(this.configPath, "utf-8");
             let jsonData: Config = defaultConfig;
             try {
-                jsonData = JSON.parse(data)
+                jsonData = JSON.parse(data);
             }
             catch (e) {}
+
             if (!jsonData.httpHosts) {
-                jsonData.httpHosts = []
+                jsonData.httpHosts = [];
             }
             if (!jsonData.wsHosts) {
-                jsonData.wsHosts = []
+                jsonData.wsHosts = [];
             }
             if (!jsonData.wsPort) {
-                jsonData.wsPort = 3001
+                jsonData.wsPort = 3001;
             }
             if (!jsonData.httpPort) {
-                jsonData.httpPort = 3000
+                jsonData.httpPort = 3000;
             }
             if (!jsonData.token) {
-                jsonData.token = ""
+                jsonData.token = "";
             }
+
             return jsonData;
         }
     }
-  
-    setConfig(config: Config) {
-        fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2), "utf-8")
-    }
 }

From 20399dc36955479faf6175bfa4f6ee94d522a8cf Mon Sep 17 00:00:00 2001
From: Disy <disymayufei@yeah.net>
Date: Tue, 20 Feb 2024 00:10:46 +0800
Subject: [PATCH 09/12] fix: get config return null ref

---
 README.md            | 4 ++--
 src/common/config.ts | 6 ++++--
 2 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/README.md b/README.md
index 2c6c8fe..050e511 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@ LiteLoaderQQNT的OneBot11协议插件
 - [x] http调用api
 - [x] http事件上报
 - [x] 正向websocket
-- [ ] 反向websocket
+- [x] 反向websocket
 
 主要功能:
 - [x] 发送好友消息
@@ -108,7 +108,7 @@ LiteLoaderQQNT的OneBot11协议插件
 
 ## TODO
 - [x] 重构摆脱LLAPI,目前调用LLAPI只能在renderer进程调用,需重构成在main进程调用
-- [x] 支持正向websocket
+- [x] 支持正、反向websocket(感谢@disymayufei的PR)
 - [ ] 转发消息记录 
 - [ ] 好友点赞api
 
diff --git a/src/common/config.ts b/src/common/config.ts
index d595780..9ad0d98 100644
--- a/src/common/config.ts
+++ b/src/common/config.ts
@@ -16,6 +16,7 @@ export class ConfigUtil {
         }
 
         this.reloadConfig();
+        return this.config;
     }
   
     setConfig(config: Config) {
@@ -37,7 +38,8 @@ export class ConfigUtil {
         };
 
         if (!fs.existsSync(this.configPath)) {
-            return defaultConfig
+            this.config = defaultConfig;
+            return;
         } else {
             const data = fs.readFileSync(this.configPath, "utf-8");
             let jsonData: Config = defaultConfig;
@@ -62,7 +64,7 @@ export class ConfigUtil {
                 jsonData.token = "";
             }
 
-            return jsonData;
+            this.config = jsonData;
         }
     }
 }

From c1dd309b2106deaef106e105043a66c847b6ab55 Mon Sep 17 00:00:00 2001
From: linyuchen <lin.yu.chen@hotmail.com>
Date: Tue, 20 Feb 2024 03:25:16 +0800
Subject: [PATCH 10/12] refactor: base server & setting ui

---
 package.json                                  |   3 +-
 src/common/config.ts                          |  56 +++--
 src/common/data.ts                            |   1 -
 src/common/types.ts                           |   7 +-
 src/common/utils.ts                           |  19 ++
 src/main/main.ts                              |  34 ++--
 .../{actions => action}/BaseAction.ts         |   0
 .../{actions => action}/CanSendImage.ts       |   0
 .../{actions => action}/CanSendRecord.ts      |   0
 src/onebot11/{actions => action}/DeleteMsg.ts |   0
 .../{actions => action}/GetFriendList.ts      |   0
 .../{actions => action}/GetGroupInfo.ts       |   0
 .../{actions => action}/GetGroupList.ts       |   0
 .../{actions => action}/GetGroupMemberInfo.ts |   0
 .../{actions => action}/GetGroupMemberList.ts |   0
 .../{actions => action}/GetLoginInfo.ts       |   0
 src/onebot11/{actions => action}/GetMsg.ts    |   0
 src/onebot11/{actions => action}/GetStatus.ts |   0
 .../{actions => action}/GetVersionInfo.ts     |   0
 .../{actions => action}/SendGroupMsg.ts       |   0
 src/onebot11/{actions => action}/SendMsg.ts   |  35 ++++
 .../{actions => action}/SendPrivateMsg.ts     |   0
 src/onebot11/{actions => action}/index.ts     |   0
 src/onebot11/{actions => action}/types.ts     |   0
 src/onebot11/{actions => action}/utils.ts     |   0
 .../notice/OB11GroupRecallNoticeEvent.ts      |   1 -
 src/onebot11/server.ts                        | 191 +++---------------
 src/onebot11/server/http.ts                   |  26 +++
 src/renderer.ts                               | 164 ++++++++-------
 src/server/base.ts                            |   6 +
 src/server/http.ts                            | 106 ++++++++++
 31 files changed, 368 insertions(+), 281 deletions(-)
 rename src/onebot11/{actions => action}/BaseAction.ts (100%)
 rename src/onebot11/{actions => action}/CanSendImage.ts (100%)
 rename src/onebot11/{actions => action}/CanSendRecord.ts (100%)
 rename src/onebot11/{actions => action}/DeleteMsg.ts (100%)
 rename src/onebot11/{actions => action}/GetFriendList.ts (100%)
 rename src/onebot11/{actions => action}/GetGroupInfo.ts (100%)
 rename src/onebot11/{actions => action}/GetGroupList.ts (100%)
 rename src/onebot11/{actions => action}/GetGroupMemberInfo.ts (100%)
 rename src/onebot11/{actions => action}/GetGroupMemberList.ts (100%)
 rename src/onebot11/{actions => action}/GetLoginInfo.ts (100%)
 rename src/onebot11/{actions => action}/GetMsg.ts (100%)
 rename src/onebot11/{actions => action}/GetStatus.ts (100%)
 rename src/onebot11/{actions => action}/GetVersionInfo.ts (100%)
 rename src/onebot11/{actions => action}/SendGroupMsg.ts (100%)
 rename src/onebot11/{actions => action}/SendMsg.ts (84%)
 rename src/onebot11/{actions => action}/SendPrivateMsg.ts (100%)
 rename src/onebot11/{actions => action}/index.ts (100%)
 rename src/onebot11/{actions => action}/types.ts (100%)
 rename src/onebot11/{actions => action}/utils.ts (100%)
 create mode 100644 src/onebot11/server/http.ts
 create mode 100644 src/server/base.ts
 create mode 100644 src/server/http.ts

diff --git a/package.json b/package.json
index 10a0c76..667e78e 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,7 @@
   "main": "dist/main.js",
   "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1",
-    "postinstall": "set ELECTRON_SKIP_BINARY_DOWNLOAD=1 && npm install electron --no-save",
+    "postinstall": "ELECTRON_SKIP_BINARY_DOWNLOAD=1 && npm install electron --no-save",
     "build": "npm run build-main && npm run build-preload && npm run build-renderer",
     "build-main": "webpack --config webpack.main.config.js",
     "build-preload": "webpack --config webpack.preload.config.js",
@@ -19,7 +19,6 @@
   "license": "ISC",
   "dependencies": {
     "express": "^4.18.2",
-    "express-ws": "^5.0.2",
     "json-bigint": "^1.0.0",
     "uuid": "^9.0.1",
     "ws": "^8.16.0"
diff --git a/src/common/config.ts b/src/common/config.ts
index 762a04b..12050b7 100644
--- a/src/common/config.ts
+++ b/src/common/config.ts
@@ -1,4 +1,5 @@
-import {Config} from "./types";
+import {Config, OB11Config} from "./types";
+import {mergeNewProperties} from "./utils";
 
 const fs = require("fs");
 
@@ -8,13 +9,25 @@ export class ConfigUtil {
     constructor(configPath: string) {
         this.configPath = configPath;
     }
-
-    getConfig(): Config {
-        let defaultConfig: Config = {
+    getConfig(){
+        try {
+            return this._getConfig()
+        }catch (e) {
+            console.log("获取配置文件出错", e)
+        }
+    }
+    _getConfig(): Config {
+        let ob11Default: OB11Config = {
             httpPort: 3000,
             httpHosts: [],
             wsPort: 3001,
             wsHosts: [],
+            enableWs: true,
+            enableWsReverse: true
+        }
+        let defaultConfig: Config = {
+            ob11: ob11Default,
+            heartInterval: 5000,
             token: "",
             enableBase64: false,
             debug: false,
@@ -28,28 +41,29 @@ export class ConfigUtil {
             let jsonData: Config = defaultConfig;
             try {
                 jsonData = JSON.parse(data)
+            } catch (e) {
             }
-            catch (e) {}
-            if (!jsonData.httpHosts) {
-                jsonData.httpHosts = []
-            }
-            if (!jsonData.wsHosts) {
-                jsonData.wsHosts = []
-            }
-            if (!jsonData.wsPort) {
-                jsonData.wsPort = 3001
-            }
-            if (!jsonData.httpPort) {
-                jsonData.httpPort = 3000
-            }
-            if (!jsonData.token) {
-                jsonData.token = ""
-            }
+            mergeNewProperties(defaultConfig, jsonData);
+            this.checkOldConfig(jsonData.ob11, jsonData, "httpPort", "port");
+            this.checkOldConfig(jsonData.ob11, jsonData, "httpHosts", "hosts");
+            this.checkOldConfig(jsonData.ob11, jsonData, "wsPort", "wsPort");
+            console.log("get config", jsonData);
             return jsonData;
         }
     }
-  
+
     setConfig(config: Config) {
         fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2), "utf-8")
     }
+
+    private checkOldConfig(currentConfig: Config | OB11Config,
+                           oldConfig: Config | OB11Config,
+                           currentKey: string, oldKey: string) {
+        // 迁移旧的配置到新配置,避免用户重新填写配置
+        const oldValue = oldConfig[oldKey];
+        if (oldValue) {
+            currentConfig[currentKey] = oldValue;
+            delete oldConfig[oldKey];
+        }
+    }
 }
diff --git a/src/common/data.ts b/src/common/data.ts
index ce94b06..e070a41 100644
--- a/src/common/data.ts
+++ b/src/common/data.ts
@@ -88,4 +88,3 @@ export function getStrangerByUin(uin: string) {
 }
 
 export const version = "v3.4.0"
-export const heartInterval = 15000 // 毫秒
\ No newline at end of file
diff --git a/src/common/types.ts b/src/common/types.ts
index abc2b88..1952099 100644
--- a/src/common/types.ts
+++ b/src/common/types.ts
@@ -1,4 +1,4 @@
-export interface Config {
+export interface OB11Config {
     httpPort: number
     httpHosts: string[]
     wsPort: number
@@ -7,7 +7,12 @@ export interface Config {
     enableHttpPost?: boolean
     enableWs?: boolean
     enableWsReverse?: boolean
+}
+
+export interface Config {
+    ob11: OB11Config
     token?: string
+    heartInterval?: number  // ms
     enableBase64?: boolean
     debug?: boolean
     reportSelfMessage?: boolean
diff --git a/src/common/utils.ts b/src/common/utils.ts
index 2a1f67d..43c77fe 100644
--- a/src/common/utils.ts
+++ b/src/common/utils.ts
@@ -97,3 +97,22 @@ export async function file2base64(path: string){
     }
     return result;
 }
+
+
+// 在保证老对象已有的属性不变化的情况下将新对象的属性复制到老对象
+export function mergeNewProperties(newObj: any, oldObj: any) {
+    Object.keys(newObj).forEach(key => {
+        // 如果老对象不存在当前属性,则直接复制
+        if (!oldObj.hasOwnProperty(key)) {
+            oldObj[key] = newObj[key];
+        } else {
+            // 如果老对象和新对象的当前属性都是对象,则递归合并
+            if (typeof oldObj[key] === 'object' && typeof newObj[key] === 'object') {
+                mergeNewProperties(newObj[key], oldObj[key]);
+            } else if(typeof oldObj[key] === 'object' || typeof newObj[key] === 'object'){
+                // 属性冲突,有一方不是对象,直接覆盖
+                oldObj[key] = newObj[key];
+            }
+        }
+    });
+}
\ No newline at end of file
diff --git a/src/main/main.ts b/src/main/main.ts
index 009b7aa..ad8be04 100644
--- a/src/main/main.ts
+++ b/src/main/main.ts
@@ -3,7 +3,7 @@
 import {BrowserWindow, ipcMain} from 'electron';
 
 import {Config} from "../common/types";
-import {postMsg, setToken, startHTTPServer, initWebsocket} from "../onebot11/server";
+import {postMsg, 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";
@@ -13,6 +13,7 @@ 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";
+import {ob11HTTPServer} from "../onebot11/server/http";
 
 const fs = require('fs');
 
@@ -29,19 +30,26 @@ function onLoad() {
         fs.mkdirSync(CONFIG_DIR, {recursive: true});
     }
     ipcMain.handle(CHANNEL_GET_CONFIG, (event: any, arg: any) => {
-        return getConfigUtil().getConfig();
+        try {
+            return getConfigUtil().getConfig();
+        }catch (e) {
+            console.log("获取配置文件出错", e)
+        }
     })
     ipcMain.on(CHANNEL_SET_CONFIG, (event: any, arg: Config) => {
         let oldConfig = getConfigUtil().getConfig();
         getConfigUtil().setConfig(arg)
-        if (arg.httpPort != oldConfig.httpPort) {
-            startHTTPServer(arg.httpPort)
+        if (arg.ob11.httpPort != oldConfig.ob11.httpPort && arg.ob11.enableHttp) {
+            ob11HTTPServer.restart(arg.ob11.httpPort);
         }
-        if (arg.wsPort != oldConfig.wsPort) {
-            initWebsocket(arg.wsPort)
+        if (!arg.ob11.enableHttp){
+            ob11HTTPServer.stop()
         }
-        if (arg.token != oldConfig.token) {
-            setToken(arg.token);
+        else{
+            ob11HTTPServer.start(arg.ob11.httpPort);
+        }
+        if (arg.ob11.wsPort != oldConfig.ob11.wsPort) {
+            initWebsocket(arg.ob11.wsPort)
         }
     })
 
@@ -125,11 +133,13 @@ function onLoad() {
             }
         })
         NTQQApi.getGroups(true).then()
-      
         const config = getConfigUtil().getConfig()
-        startHTTPServer(config.httpPort)
-        initWebsocket(config.wsPort);
-        setToken(config.token)
+        try {
+            ob11HTTPServer.start(config.ob11.httpPort)
+            initWebsocket(config.ob11.wsPort);
+        }catch (e) {
+            console.log("start failed", e)
+        }
         log("LLOneBot start")
     }
 
diff --git a/src/onebot11/actions/BaseAction.ts b/src/onebot11/action/BaseAction.ts
similarity index 100%
rename from src/onebot11/actions/BaseAction.ts
rename to src/onebot11/action/BaseAction.ts
diff --git a/src/onebot11/actions/CanSendImage.ts b/src/onebot11/action/CanSendImage.ts
similarity index 100%
rename from src/onebot11/actions/CanSendImage.ts
rename to src/onebot11/action/CanSendImage.ts
diff --git a/src/onebot11/actions/CanSendRecord.ts b/src/onebot11/action/CanSendRecord.ts
similarity index 100%
rename from src/onebot11/actions/CanSendRecord.ts
rename to src/onebot11/action/CanSendRecord.ts
diff --git a/src/onebot11/actions/DeleteMsg.ts b/src/onebot11/action/DeleteMsg.ts
similarity index 100%
rename from src/onebot11/actions/DeleteMsg.ts
rename to src/onebot11/action/DeleteMsg.ts
diff --git a/src/onebot11/actions/GetFriendList.ts b/src/onebot11/action/GetFriendList.ts
similarity index 100%
rename from src/onebot11/actions/GetFriendList.ts
rename to src/onebot11/action/GetFriendList.ts
diff --git a/src/onebot11/actions/GetGroupInfo.ts b/src/onebot11/action/GetGroupInfo.ts
similarity index 100%
rename from src/onebot11/actions/GetGroupInfo.ts
rename to src/onebot11/action/GetGroupInfo.ts
diff --git a/src/onebot11/actions/GetGroupList.ts b/src/onebot11/action/GetGroupList.ts
similarity index 100%
rename from src/onebot11/actions/GetGroupList.ts
rename to src/onebot11/action/GetGroupList.ts
diff --git a/src/onebot11/actions/GetGroupMemberInfo.ts b/src/onebot11/action/GetGroupMemberInfo.ts
similarity index 100%
rename from src/onebot11/actions/GetGroupMemberInfo.ts
rename to src/onebot11/action/GetGroupMemberInfo.ts
diff --git a/src/onebot11/actions/GetGroupMemberList.ts b/src/onebot11/action/GetGroupMemberList.ts
similarity index 100%
rename from src/onebot11/actions/GetGroupMemberList.ts
rename to src/onebot11/action/GetGroupMemberList.ts
diff --git a/src/onebot11/actions/GetLoginInfo.ts b/src/onebot11/action/GetLoginInfo.ts
similarity index 100%
rename from src/onebot11/actions/GetLoginInfo.ts
rename to src/onebot11/action/GetLoginInfo.ts
diff --git a/src/onebot11/actions/GetMsg.ts b/src/onebot11/action/GetMsg.ts
similarity index 100%
rename from src/onebot11/actions/GetMsg.ts
rename to src/onebot11/action/GetMsg.ts
diff --git a/src/onebot11/actions/GetStatus.ts b/src/onebot11/action/GetStatus.ts
similarity index 100%
rename from src/onebot11/actions/GetStatus.ts
rename to src/onebot11/action/GetStatus.ts
diff --git a/src/onebot11/actions/GetVersionInfo.ts b/src/onebot11/action/GetVersionInfo.ts
similarity index 100%
rename from src/onebot11/actions/GetVersionInfo.ts
rename to src/onebot11/action/GetVersionInfo.ts
diff --git a/src/onebot11/actions/SendGroupMsg.ts b/src/onebot11/action/SendGroupMsg.ts
similarity index 100%
rename from src/onebot11/actions/SendGroupMsg.ts
rename to src/onebot11/action/SendGroupMsg.ts
diff --git a/src/onebot11/actions/SendMsg.ts b/src/onebot11/action/SendMsg.ts
similarity index 84%
rename from src/onebot11/actions/SendMsg.ts
rename to src/onebot11/action/SendMsg.ts
index cc84d23..e96ab0a 100644
--- a/src/onebot11/actions/SendMsg.ts
+++ b/src/onebot11/action/SendMsg.ts
@@ -9,6 +9,41 @@ import BaseAction from "./BaseAction";
 import {ActionName} from "./types";
 import * as fs from "fs";
 
+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;
+}
+
 export interface ReturnDataType {
     message_id: number
 }
diff --git a/src/onebot11/actions/SendPrivateMsg.ts b/src/onebot11/action/SendPrivateMsg.ts
similarity index 100%
rename from src/onebot11/actions/SendPrivateMsg.ts
rename to src/onebot11/action/SendPrivateMsg.ts
diff --git a/src/onebot11/actions/index.ts b/src/onebot11/action/index.ts
similarity index 100%
rename from src/onebot11/actions/index.ts
rename to src/onebot11/action/index.ts
diff --git a/src/onebot11/actions/types.ts b/src/onebot11/action/types.ts
similarity index 100%
rename from src/onebot11/actions/types.ts
rename to src/onebot11/action/types.ts
diff --git a/src/onebot11/actions/utils.ts b/src/onebot11/action/utils.ts
similarity index 100%
rename from src/onebot11/actions/utils.ts
rename to src/onebot11/action/utils.ts
diff --git a/src/onebot11/event/notice/OB11GroupRecallNoticeEvent.ts b/src/onebot11/event/notice/OB11GroupRecallNoticeEvent.ts
index 83c31d5..11c180a 100644
--- a/src/onebot11/event/notice/OB11GroupRecallNoticeEvent.ts
+++ b/src/onebot11/event/notice/OB11GroupRecallNoticeEvent.ts
@@ -7,7 +7,6 @@ export class OB11GroupRecallNoticeEvent extends OB11GroupNoticeEvent {
 
     constructor(groupId: number, userId: number, operatorId: number, messageId: number) {
         super();
-
         this.group_id = groupId;
         this.user_id = userId;
         this.operator_id = operatorId;
diff --git a/src/onebot11/server.ts b/src/onebot11/server.ts
index b2a2a7c..df87476 100644
--- a/src/onebot11/server.ts
+++ b/src/onebot11/server.ts
@@ -1,134 +1,24 @@
-import * as http from "http";
 import * as websocket from "ws";
 import urlParse from "url";
-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 {selfInfo} from "../common/data";
+import {OB11Message} from './types';
+import {actionMap} from "./action";
+import {OB11WebsocketResponse} from "./action/utils";
 import {callEvent, registerEventSender, unregisterEventSender} from "./event/manager";
 import {ReconnectingWebsocket} from "./ReconnectingWebsocket";
-import {ActionName} from "./actions/types";
+import {ActionName} from "./action/types";
 import {OB11BaseMetaEvent} from "./event/meta/OB11BaseMetaEvent";
 import {OB11BaseNoticeEvent} from "./event/notice/OB11BaseNoticeEvent";
-import BaseAction from "./actions/BaseAction";
+import BaseAction from "./action/BaseAction";
 import {LifeCycleSubType, OB11LifeCycleEvent} from "./event/meta/OB11LifeCycleEvent";
 import {OB11HeartbeatEvent} from "./event/meta/OB11HeartbeatEvent";
 
-let accessToken = "";
 let heartbeatRunning = false;
-
-// @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==
-
-const JSONbig = require('json-bigint')({storeAsString: true});
-
-const expressAPP = express();
-expressAPP.use(express.urlencoded({extended: true, limit: "500mb"}));
-
-let httpServer: http.Server = null;
-
 let websocketServer = null;
 
-expressAPP.use((req, res, next) => {
-    let data = '';
-    req.on('data', chunk => {
-        data += chunk.toString();
-    });
-    req.on('end', () => {
-        if (data) {
-            try {
-                // log("receive raw", data)
-                req.body = JSONbig.parse(data);
-            } catch (e) {
-                return next(e);
-            }
-        }
-        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) {
-        httpServer.close();
-    }
-    expressAPP.get('/', (req: Request, res: Response) => {
-        res.send('LLOneBot已启动');
-    })
-
-    if (getConfigUtil().getConfig().enableHttp) {
-        httpServer = expressAPP.listen(port, "0.0.0.0", () => {
-            console.log(`llonebot http service started 0.0.0.0:${port}`);
-        });
-    }
-}
-
 export function initWebsocket(port: number) {
+    const {heartInterval, ob11: {enableWs}, token} = getConfigUtil().getConfig()
     if (!heartbeatRunning) {
         setInterval(() => {
             callEvent(new OB11HeartbeatEvent(true, true, heartInterval));
@@ -136,8 +26,7 @@ export function initWebsocket(port: number) {
 
         heartbeatRunning = true;
     }
-
-    if (getConfigUtil().getConfig().enableWs) {
+    if (enableWs) {
         if (websocketServer) {
             websocketServer.close((err) => {
                 log("ws server close failed!", err)
@@ -150,28 +39,26 @@ export function initWebsocket(port: number) {
         websocketServer.on("connection", (ws, req) => {
             const url = req.url.split("?").shift();
             log("receive ws connect", url)
-            let token: string = ""
+            let clientToken: string = ""
             const authHeader = req.headers['authorization'];
             if (authHeader) {
-                token = authHeader.split("Bearer ").pop()
-                log("receive ws header token", token);
+                clientToken = authHeader.split("Bearer ").pop()
+                log("receive ws header token", clientToken);
             } else {
                 const parsedUrl = urlParse.parse(req.url, true);
                 const urlToken = parsedUrl.query.access_token;
                 if (urlToken) {
                     if (Array.isArray(urlToken)) {
-                        token = urlToken[0]
+                        clientToken = urlToken[0]
                     } else {
-                        token = urlToken
+                        clientToken = urlToken
                     }
-                    log("receive ws url token", token);
+                    log("receive ws url token", clientToken);
                 }
             }
-            if (accessToken) {
-                if (token != accessToken) {
-                    ws.send(JSON.stringify(OB11WebsocketResponse.res(null, "failed", 1403, "token验证失败")))
-                    return ws.close()
-                }
+            if (token && clientToken != token) {
+                ws.send(JSON.stringify(OB11WebsocketResponse.res(null, "failed", 1403, "token验证失败")))
+                return ws.close()
             }
 
             if (url == "/api" || url == "/api/" || url == "/") {
@@ -204,7 +91,7 @@ export function initWebsocket(port: number) {
 
                 try {
                     wsReply(ws, new OB11LifeCycleEvent(LifeCycleSubType.CONNECT))
-                } catch (e){
+                } catch (e) {
                     log("发送生命周期失败", e)
                 }
 
@@ -218,11 +105,12 @@ export function initWebsocket(port: number) {
 
     initReverseWebsocket();
 }
+
 function initReverseWebsocket() {
     const config = getConfigUtil().getConfig();
-    if (config.enableWsReverse) {
+    if (config.ob11.enableWsReverse) {
         console.log("Prepare to connect all reverse websockets...");
-        for (const url of config.wsHosts) {
+        for (const url of config.ob11.wsHosts) {
             new Promise(() => {
                 try {
                     let wsClient = new ReconnectingWebsocket(url);
@@ -239,7 +127,7 @@ function initReverseWebsocket() {
                     wsClient.onmessage = async function (msg) {
                         let receiveData: { action: ActionName, params: any, echo?: string } = {action: null, params: {}}
                         let echo = ""
-                        log("收到正向Websocket消息", msg.toString())
+                        log("收到反向Websocket消息", msg.toString())
                         try {
                             receiveData = JSON.parse(msg.toString())
                             echo = receiveData.echo
@@ -257,8 +145,7 @@ function initReverseWebsocket() {
                             wsReply(wsClient, OB11WebsocketResponse.error(`api处理出错:${e}`, 1200, echo))
                         }
                     }
-                }
-                catch (e) {
+                } catch (e) {
                     log(e.stack);
                 }
             }).then();
@@ -292,7 +179,7 @@ export function postMsg(msg: PostMsgType) {
             return
         }
     }
-    for (const host of config.httpHosts) {
+    for (const host of config.ob11.httpHosts) {
         fetch(host, {
             method: "POST",
             headers: {
@@ -310,33 +197,3 @@ export function postMsg(msg: PostMsgType) {
     log("新消息事件ws上报", msg);
     callEvent(msg);
 }
-
-
-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) {
-            log(e.stack);
-            res.send(OB11Response.error(e.stack.toString(), 200))
-        }
-    }
-
-    expressAPP.post(url, expressAuthorize, (req: Request, res: Response) => {
-        _handle(res, req.body || {}).then()
-    });
-    expressAPP.get(url, expressAuthorize, (req: Request, res: Response) => {
-        _handle(res, req.query as any || {}).then()
-    });
-}
-
-for (const action of actionHandlers) {
-    registerRouter(action.actionName, (payload) => action.handle(payload))
-}
\ No newline at end of file
diff --git a/src/onebot11/server/http.ts b/src/onebot11/server/http.ts
new file mode 100644
index 0000000..b7408a2
--- /dev/null
+++ b/src/onebot11/server/http.ts
@@ -0,0 +1,26 @@
+import {Response} from "express";
+import {getConfigUtil} from "../../common/utils";
+import {OB11Response} from "../action/utils";
+import {HttpServerBase} from "../../server/http";
+import {actionHandlers} from "../action";
+
+class OB11HTTPServer extends HttpServerBase {
+    name = "OneBot V11 server"
+    handleFailed(res: Response, payload: any, e: any) {
+        res.send(OB11Response.error(e.stack.toString(), 200))
+    }
+
+    protected listen(port: number) {
+        if (getConfigUtil().getConfig().ob11.enableHttp) {
+            super.listen(port);
+        }
+    }
+}
+
+export const ob11HTTPServer = new OB11HTTPServer();
+
+for (const action of actionHandlers) {
+    for(const method of ["post", "get"]){
+        ob11HTTPServer.registerRouter(method, action.actionName, (res, payload) => action.handle(payload))
+    }
+}
diff --git a/src/renderer.ts b/src/renderer.ts
index e673b44..aa70f15 100644
--- a/src/renderer.ts
+++ b/src/renderer.ts
@@ -5,40 +5,42 @@
 async function onSettingWindowCreated(view: Element) {
     window.llonebot.log("setting window created");
     let config = await window.llonebot.getConfig()
+    const httpClass = "http";
+    const httpPostClass = "http-post";
+    const wsClass = "ws";
+    const reverseWSClass = "reverse-ws";
 
     function createHttpHostEleStr(host: string) {
         let eleStr = `
-            <setting-item data-direction="row" class="hostItem vertical-list-item">
-                <h2>事件上报地址(http)</h2>
+            <setting-item data-direction="row" class="hostItem vertical-list-item ${httpPostClass}">
+                <h2>HTTP事件上报地址(http)</h2>
                 <input class="httpHost input-text" type="text" value="${host}" 
                 style="width:60%;padding: 5px"
                 placeholder="如果localhost上报失败试试局域网ip"/>
             </setting-item>
-
             `
         return eleStr
     }
 
     function createWsHostEleStr(host: string) {
         let eleStr = `
-            <setting-item data-direction="row" class="hostItem vertical-list-item">
+            <setting-item data-direction="row" class="hostItem vertical-list-item ${reverseWSClass}">
                 <h2>事件上报地址(反向websocket)</h2>
                 <input class="wsHost input-text" type="text" value="${host}" 
                 style="width:60%;padding: 5px"
                 placeholder="如果localhost上报失败试试局域网ip"/>
             </setting-item>
-
             `
         return eleStr
     }
 
     let httpHostsEleStr = ""
-    for (const host of config.httpHosts) {
+    for (const host of config.ob11.httpHosts) {
         httpHostsEleStr += createHttpHostEleStr(host);
     }
 
     let wsHostsEleStr = ""
-    for (const host of config.wsHosts) {
+    for (const host of config.ob11.wsHosts) {
         wsHostsEleStr += createWsHostEleStr(host);
     }
 
@@ -47,63 +49,63 @@ async function onSettingWindowCreated(view: Element) {
         <setting-section>
             <setting-panel>
                 <setting-list class="wrap">
-                    <setting-item class="vertical-list-item" data-direction="row">
+                    <setting-item data-direction="row" class="hostItem vertical-list-item">
+                        <div>
+                            <div>启用HTTP服务</div>
+                        </div>
+                        <setting-switch id="http" ${config.ob11.enableHttp ? "is-active" : ""}></setting-switch>
+                    </setting-item>
+                    <setting-item class="vertical-list-item ${httpClass}" data-direction="row" style="display: ${config.ob11.enableHttp ? '' : 'none'}">
                         <setting-text>HTTP监听端口</setting-text>
-                        <input id="httpPort" type="number" value="${config.httpPort}"/>
+                        <input id="httpPort" type="number" value="${config.ob11.httpPort}"/>
                     </setting-item>
-                    <div>
-                        <button id="addHttpHost" class="q-button">添加HTTP POST上报地址</button>
+                    <setting-item data-direction="row" class="hostItem vertical-list-item">
+                        <div>
+                            <div>启用HTTP事件上报</div>
+                        </div>
+                        <setting-switch id="httpPost" ${config.ob11.enableHttpPost ? "is-active" : ""}></setting-switch>
+                    </setting-item>
+                    <div class="${httpPostClass}" style="display: ${config.ob11.enableHttpPost ? '' : 'none'}">
+                        <div >
+                            <button id="addHttpHost" class="q-button">添加HTTP POST上报地址</button>
+                        </div>
+                        <div id="httpHostItems">
+                            ${httpHostsEleStr}
+                        </div>
                     </div>
-                    <div id="httpHostItems">
-                        ${httpHostsEleStr}
-                    </div>
-                    
-                    <setting-item class="vertical-list-item" data-direction="row">
+                    <setting-item data-direction="row" class="hostItem vertical-list-item">
+                        <div>
+                            <div>启用正向Websocket协议</div>
+                        </div>
+                        <setting-switch id="websocket" ${config.ob11.enableWs ? "is-active" : ""}></setting-switch>
+                    </setting-item>
+                    <setting-item class="vertical-list-item ${wsClass}" data-direction="row" style="display: ${config.ob11.enableWs ? '' : 'none'}">
                         <setting-text>正向Websocket监听端口</setting-text>
-                        <input id="wsPort" type="number" value="${config.wsPort}"/>
+                        <input id="wsPort" type="number" value="${config.ob11.wsPort}"/>
                     </setting-item>
+                    
+                    <setting-item data-direction="row" class="hostItem vertical-list-item">
+                        <div>
+                            <div>启用反向Websocket协议</div>
+                        </div>
+                        <setting-switch id="websocketReverse" ${config.ob11.enableWsReverse ? "is-active" : ""}></setting-switch>
+                    </setting-item>
+                    <div class="${reverseWSClass}" style="display: ${config.ob11.enableWsReverse ? '' : 'none'}">
+                        <div>
+                            <button id="addWsHost" class="q-button">添加反向Websocket上报地址</button>
+                        </div>
+                        <div id="wsHostItems">
+                            ${wsHostsEleStr}
+                        </div>
+                    </div>
                     <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="addWsHost" class="q-button">添加反向Websocket上报地址</button>
-                    </div>
-                    <div id="wsHostItems">
-                        ${wsHostsEleStr}
-                    </div>
                     <button id="save" class="q-button">保存</button>
                 </setting-list>
             </setting-panel>
             <setting-panel>
-                <setting-item data-direction="row" class="hostItem vertical-list-item">
-                    <div>
-                        <div>启用HTTP支持</div>
-                        <div class="tips">修改后须重启QQ生效</div>
-                    </div>
-                    <setting-switch id="http" ${config.enableHttp ? "is-active" : ""}></setting-switch>
-                </setting-item>
-                <setting-item data-direction="row" class="hostItem vertical-list-item">
-                    <div>
-                        <div>启用HTTP POST支持</div>
-                        <div class="tips">修改后须重启QQ生效</div>
-                    </div>
-                    <setting-switch id="httpPost" ${config.enableHttpPost ? "is-active" : ""}></setting-switch>
-                </setting-item>
-                <setting-item data-direction="row" class="hostItem vertical-list-item">
-                    <div>
-                        <div>启用正向Websocket支持</div>
-                        <div class="tips">修改后须重启QQ生效</div>
-                    </div>
-                    <setting-switch id="websocket" ${config.enableWs ? "is-active" : ""}></setting-switch>
-                </setting-item>
-                <setting-item data-direction="row" class="hostItem vertical-list-item">
-                    <div>
-                        <div>启用反向Websocket支持</div>
-                        <div class="tips">修改后须重启QQ生效</div>
-                    </div>
-                    <setting-switch id="websocketReverse" ${config.enableWsReverse ? "is-active" : ""}></setting-switch>
-                </setting-item>
                 <setting-item data-direction="row" class="hostItem vertical-list-item">
                     <div>
                         <div>上报文件进行base64编码</div>
@@ -128,7 +130,7 @@ async function onSettingWindowCreated(view: Element) {
                 <setting-item data-direction="row" class="hostItem vertical-list-item">
                     <div>
                         <div>日志</div>
-                        <div class="tips">日志目录:${window.LiteLoader.plugins["LLOneBot"].path.data}</div>
+                        <div class="tips">目录:${window.LiteLoader.plugins["LLOneBot"].path.data}</div>
                     </div>
                     <setting-switch id="log" ${config.log ? "is-active" : ""}></setting-switch>
                 </setting-item>
@@ -160,8 +162,7 @@ async function onSettingWindowCreated(view: Element) {
             let addressDoc = parser.parseFromString(createWsHostEleStr(initValue), "text/html");
             addressEle = addressDoc.querySelector("setting-item")
             hostItemsEle = document.getElementById("wsHostItems");
-        }
-        else {
+        } else {
             let addressDoc = parser.parseFromString(createHttpHostEleStr(initValue), "text/html");
             addressEle = addressDoc.querySelector("setting-item")
             hostItemsEle = document.getElementById("httpHostItems");
@@ -174,24 +175,38 @@ async function onSettingWindowCreated(view: Element) {
     doc.getElementById("addHttpHost").addEventListener("click", () => addHostEle("http"))
     doc.getElementById("addWsHost").addEventListener("click", () => addHostEle("ws"))
 
-    function switchClick(eleId: string, configKey: string) {
+    function switchClick(eleId: string, configKey: string, _config=null) {
+        if (!_config){
+            _config = config
+        }
         doc.getElementById(eleId)?.addEventListener("click", (e) => {
             const switchEle = e.target as HTMLInputElement
-            if (config[configKey]) {
-                config[configKey] = false
+            if (_config[configKey]) {
+                _config[configKey] = false
                 switchEle.removeAttribute("is-active")
             } else {
-                config[configKey] = true
+                _config[configKey] = true
                 switchEle.setAttribute("is-active", "")
             }
+            // 妈蛋,手动操作DOM越写越麻烦,要不用vue算了
+            const keyClassMap = {
+                "enableHttp": httpClass,
+                "enableHttpPost": httpPostClass,
+                "enableWs": wsClass,
+                "enableWsReverse": reverseWSClass,
+            }
+            for (let e of document.getElementsByClassName(keyClassMap[configKey])) {
+                e["style"].display = _config[configKey] ? "" : "none"
+            }
+
             window.llonebot.setConfig(config)
         })
     }
 
-    switchClick("http", "enableHttp");
-    switchClick("httpPost", "enableHttpPost");
-    switchClick("websocket", "enableWs");
-    switchClick("websocketReverse", "enableWsReverse");
+    switchClick("http", "enableHttp", config.ob11);
+    switchClick("httpPost", "enableHttpPost", config.ob11);
+    switchClick("websocket", "enableWs", config.ob11);
+    switchClick("websocketReverse", "enableWsReverse", config.ob11);
     switchClick("debug", "debug");
     switchClick("switchBase64", "enableBase64");
     switchClick("reportSelfMessage", "reportSelfMessage");
@@ -210,27 +225,24 @@ async function onSettingWindowCreated(view: Element) {
             let httpHosts: string[] = [];
 
             for (const hostEle of httpHostEles) {
-                if (hostEle.value) {
-                    httpHosts.push(hostEle.value);
-                }
+                const value = hostEle.value.trim();
+                value && httpHosts.push(value);
             }
 
-            const wsPort = wsPortEle.value
-            const token = tokenEle.value
+            const wsPort = wsPortEle.value;
+            const token = tokenEle.value.trim();
             let wsHosts: string[] = [];
 
             for (const hostEle of wsHostEles) {
-                if (hostEle.value) {
-                    wsHosts.push(hostEle.value);
-                }
+                const value = hostEle.value.trim();
+                value && wsHosts.push(value);
             }
 
-
-            config.httpPort = parseInt(httpPort);
-            config.httpHosts = httpHosts;
-            config.wsPort = parseInt(wsPort);
-            config.wsHosts = wsHosts;
-            config.token = token.trim();
+            config.ob11.httpPort = parseInt(httpPort);
+            config.ob11.httpHosts = httpHosts;
+            config.ob11.wsPort = parseInt(wsPort);
+            config.ob11.wsHosts = wsHosts;
+            config.token = token;
             window.llonebot.setConfig(config);
             alert("保存成功");
         })
diff --git a/src/server/base.ts b/src/server/base.ts
new file mode 100644
index 0000000..7291840
--- /dev/null
+++ b/src/server/base.ts
@@ -0,0 +1,6 @@
+
+
+export abstract class ServerBase{
+    abstract start: () => void
+    abstract restart: ()=>void
+}
\ No newline at end of file
diff --git a/src/server/http.ts b/src/server/http.ts
new file mode 100644
index 0000000..6163140
--- /dev/null
+++ b/src/server/http.ts
@@ -0,0 +1,106 @@
+import express, {Express, Request, Response} from "express";
+import {getConfigUtil, log} from "../common/utils";
+import http from "http";
+
+const JSONbig = require('json-bigint')({storeAsString: true});
+
+type RegisterHandler = (res: Response, payload: any) => Promise<any>
+
+export abstract class HttpServerBase {
+    name: string = "LLOneBot";
+    private readonly expressAPP: Express;
+    private server: http.Server = null;
+
+    constructor() {
+        this.expressAPP = express();
+        this.expressAPP.use(express.urlencoded({extended: true, limit: "500mb"}));
+        this.expressAPP.use((req, res, next) => {
+            let data = '';
+            req.on('data', chunk => {
+                data += chunk.toString();
+            });
+            req.on('end', () => {
+                if (data) {
+                    try {
+                        // log("receive raw", data)
+                        req.body = JSONbig.parse(data);
+                    } catch (e) {
+                        return next(e);
+                    }
+                }
+                next();
+            });
+        });
+    }
+
+    authorize(req: Request, res: Response, next: () => void) {
+        let serverToken = getConfigUtil().getConfig().token;
+        let clientToken = ""
+        const authHeader = req.get("authorization")
+        if (authHeader) {
+            clientToken = authHeader.split("Bearer ").pop()
+            log("receive http header token", clientToken)
+        } else if (req.query.access_token) {
+            if (Array.isArray(req.query.access_token)) {
+                clientToken = req.query.access_token[0].toString();
+            } else {
+                clientToken = req.query.access_token.toString();
+            }
+            log("receive http url token", clientToken)
+        }
+
+        if (serverToken && clientToken != serverToken) {
+            return res.status(403).send(JSON.stringify({message: 'token verify failed!'}));
+        }
+        next();
+    };
+
+    start(port: number) {
+        this.expressAPP.get('/', (req: Request, res: Response) => {
+            res.send(`${this.name}已启动`);
+        })
+        this.listen(port);
+    }
+
+    stop() {
+        if (this.server){
+            this.server.close()
+            this.server = null;
+        }
+    }
+
+    restart(port: number){
+        this.stop()
+        this.start(port)
+    }
+
+    abstract handleFailed(res: Response, payload: any, err: any): void
+
+    registerRouter(method: string, url: string, handler: RegisterHandler) {
+        if (!url.startsWith("/")) {
+            url = "/" + url
+        }
+        const methodFunc = this.expressAPP[method]
+        if (!methodFunc){
+            const err = `${this.name} register router failed,${method} not exist`;
+            log(err);
+            throw err;
+        }
+        this.expressAPP[method](url, this.authorize, async (req: Request, res: Response) => {
+            const payload = req.body || req.query || {}
+            try{
+                res.send(await handler(res, payload))
+            }catch (e) {
+                this.handleFailed(res, payload, e.stack.toString())
+            }
+        });
+    }
+
+    protected listen(port: number) {
+        this.server = this.expressAPP.listen(port, "0.0.0.0", () => {
+            const info = `${this.name} started 0.0.0.0:${port}`
+            console.log(info);
+            log(info);
+        });
+    }
+}
\ No newline at end of file

From f9b97543d9fefdf1d0be35941035532d5bd14072 Mon Sep 17 00:00:00 2001
From: linyuchen <lin.yu.chen@hotmail.com>
Date: Tue, 20 Feb 2024 03:29:28 +0800
Subject: [PATCH 11/12] refactor: default config

---
 src/common/config.ts | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/common/config.ts b/src/common/config.ts
index 12050b7..f19b864 100644
--- a/src/common/config.ts
+++ b/src/common/config.ts
@@ -22,8 +22,10 @@ export class ConfigUtil {
             httpHosts: [],
             wsPort: 3001,
             wsHosts: [],
+            enableHttp: true,
+            enableHttpPost: true,
             enableWs: true,
-            enableWsReverse: true
+            enableWsReverse: false
         }
         let defaultConfig: Config = {
             ob11: ob11Default,

From 0545bcfdab417d526412eff0b20583eba8eda93e Mon Sep 17 00:00:00 2001
From: linyuchen <lin.yu.chen@hotmail.com>
Date: Tue, 20 Feb 2024 15:51:55 +0800
Subject: [PATCH 12/12] refactor: function getConfig add cache param

---
 src/common/config.ts | 20 +++++++++++---------
 1 file changed, 11 insertions(+), 9 deletions(-)

diff --git a/src/common/config.ts b/src/common/config.ts
index e56af19..7988a8e 100644
--- a/src/common/config.ts
+++ b/src/common/config.ts
@@ -1,8 +1,7 @@
+import fs from "fs";
 import {Config, OB11Config} from "./types";
 import {mergeNewProperties} from "./utils";
 
-const fs = require("fs");
-
 export class ConfigUtil {
     private readonly configPath: string;
     private config: Config | null = null;
@@ -11,14 +10,14 @@ export class ConfigUtil {
         this.configPath = configPath;
     }
 
-    getConfig(): Config {
-        if (this.config) {
+    getConfig(cache=true) {
+        if (this.config && cache) {
             return this.config;
         }
 
-        this.reloadConfig();
-        return this.config;
+        return this.reloadConfig();
     }
+
     reloadConfig(): Config {
         let ob11Default: OB11Config = {
             httpPort: 3000,
@@ -42,20 +41,23 @@ export class ConfigUtil {
 
         if (!fs.existsSync(this.configPath)) {
             this.config = defaultConfig;
-            return;
+            return this.config;
         } else {
             const data = fs.readFileSync(this.configPath, "utf-8");
             let jsonData: Config = defaultConfig;
             try {
                 jsonData = JSON.parse(data)
             } catch (e) {
+                this.config = defaultConfig;
+                return this.config;
             }
             mergeNewProperties(defaultConfig, jsonData);
             this.checkOldConfig(jsonData.ob11, jsonData, "httpPort", "port");
             this.checkOldConfig(jsonData.ob11, jsonData, "httpHosts", "hosts");
             this.checkOldConfig(jsonData.ob11, jsonData, "wsPort", "wsPort");
-            console.log("get config", jsonData);
-            return jsonData;
+            // console.log("get config", jsonData);
+            this.config = jsonData;
+            return this.config;
         }
     }