diff --git a/README.md b/README.md
index 810f7d9..2c6c8fe 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,11 @@ LiteLoaderQQNT的OneBot11协议插件
 
 ## 支持的API
 
-目前只支持http协议,不支持websocket,事件上报也是http协议
+目前支持的协议
+- [x] http调用api
+- [x] http事件上报
+- [x] 正向websocket
+- [ ] 反向websocket
 
 主要功能:
 - [x] 发送好友消息
@@ -29,9 +33,11 @@ LiteLoaderQQNT的OneBot11协议插件
 - [x] 撤回消息
 - [x] 上报好友消息
 - [x] 上报群消息
+- [x] 上报好友、群消息撤回
 
 消息格式支持:
 - [x] 文字
+- [x] 表情
 - [x] 图片
 - [x] 引用消息
 - [x] @群成员
@@ -53,6 +59,10 @@ LiteLoaderQQNT的OneBot11协议插件
 - [x] get_group_member_info
 - [x] get_friend_list
 - [x] get_msg
+- [x] get_version_info
+- [x] get_status
+- [x] can_send_image
+- [x] can_send_record
 
 ## 示例
 
@@ -98,9 +108,9 @@ LiteLoaderQQNT的OneBot11协议插件
 
 ## TODO
 - [x] 重构摆脱LLAPI,目前调用LLAPI只能在renderer进程调用,需重构成在main进程调用
+- [x] 支持正向websocket
 - [ ] 转发消息记录 
 - [ ] 好友点赞api
-- [ ] 支持websocket,等个有缘人提PR实现
 
 ## onebot11文档
 <https://11.onebot.dev/>
diff --git a/manifest.json b/manifest.json
index 3574dc6..521ef5a 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,31 +1,33 @@
 {
-    "manifest_version": 4,
-    "type": "extension",
-    "name": "LLOneBot",
-    "slug": "LLOneBot",
-    "description": "LiteLoaderQQNT的OneBotApi",
-    "version": "3.0.8",
-    "thumbnail": "./icon.png",
-    "authors": [{
-        "name": "linyuchen",
-        "link": "https://github.com/linyuchen"
-    }],
-    "repository": {
+  "manifest_version": 4,
+  "type": "extension",
+  "name": "LLOneBot",
+  "slug": "LLOneBot",
+  "description": "LiteLoaderQQNT的OneBotApi",
+  "version": "3.3.0",
+  "thumbnail": "./icon.png",
+  "authors": [
+    {
+      "name": "linyuchen",
+      "link": "https://github.com/linyuchen"
+    }
+  ],
+  "repository": {
     "repo": "linyuchen/LiteLoaderQQNT-OneBotApi",
     "branch": "main",
     "release": {
-        "tag": "latest",
-        "name": "LLOneBot.zip"
-    }
-},
-    "platform": [
-        "win32",
-        "linux",
-        "darwin"
-    ],
-    "injects": {
-        "renderer": "./renderer.js",
-        "main": "./main.js",
-        "preload": "./preload.js"
+      "tag": "latest",
+      "name": "LLOneBot.zip"
     }
+  },
+  "platform": [
+    "win32",
+    "linux",
+    "darwin"
+  ],
+  "injects": {
+    "renderer": "./renderer.js",
+    "main": "./main.js",
+    "preload": "./preload.js"
+  }
 }
diff --git a/package-lock.json b/package-lock.json
index 9c1e3a4..4bc8916 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25,7 +25,8 @@
         "ts-loader": "^9.5.0",
         "typescript": "^5.2.2",
         "webpack": "^5.89.0",
-        "webpack-cli": "^5.1.4"
+        "webpack-cli": "^5.1.4",
+        "ws": "^8.16.0"
       }
     },
     "node_modules/@ampproject/remapping": {
@@ -2204,7 +2205,7 @@
     },
     "node_modules/@types/ws": {
       "version": "8.5.10",
-      "resolved": "https://registry.npmmirror.com/@types/ws/-/ws-8.5.10.tgz",
+      "resolved": "https://mirrors.cloud.tencent.com/npm/@types/ws/-/ws-8.5.10.tgz",
       "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==",
       "dev": true,
       "dependencies": {
@@ -4739,8 +4740,9 @@
     },
     "node_modules/ws": {
       "version": "8.16.0",
-      "resolved": "https://registry.npmmirror.com/ws/-/ws-8.16.0.tgz",
+      "resolved": "https://mirrors.cloud.tencent.com/npm/ws/-/ws-8.16.0.tgz",
       "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
+      "dev": true,
       "engines": {
         "node": ">=10.0.0"
       },
diff --git a/package.json b/package.json
index e7a043a..10a0c76 100644
--- a/package.json
+++ b/package.json
@@ -33,6 +33,7 @@
     "ts-loader": "^9.5.0",
     "typescript": "^5.2.2",
     "webpack": "^5.89.0",
-    "webpack-cli": "^5.1.4"
+    "webpack-cli": "^5.1.4",
+    "ws": "^8.16.0"
   }
 }
diff --git a/src/common/config.ts b/src/common/config.ts
index e390a4b..24d1033 100644
--- a/src/common/config.ts
+++ b/src/common/config.ts
@@ -1,8 +1,8 @@
-import {Config} from "./types";
+import { Config } from "./types";
 
 const fs = require("fs");
 
-export class ConfigUtil{
+export class ConfigUtil {
     configPath: string;
 
     constructor(configPath: string) {
@@ -10,24 +10,42 @@ export class ConfigUtil{
     }
 
     getConfig(): Config {
+        let defaultConfig: Config = {
+            httpPort: 3000,
+            httpHosts: [],
+            wsPort: 3001,
+            wsHosts: []
+            token: "",
+            enableBase64: false,
+            debug: false,
+            log: false,
+            reportSelfMessage: false
+        }
         if (!fs.existsSync(this.configPath)) {
-            return {
-                httpPort: 3000,
-                httpHosts: ["http://127.0.0.1:5000/"],
-                wsPort: 3001,
-                wsHosts: ["ws://127.0.0.1:3002/"]
-            }
+            return defaultConfig
         } else {
             const data = fs.readFileSync(this.configPath, "utf-8");
-            let jsonData = JSON.parse(data);
+            let jsonData: Config = defaultConfig;
+            try {
+                jsonData = JSON.parse(data)
+            }
+            catch (e){
+
+            }
             if (!jsonData.hosts) {
-                jsonData.hosts = [];
+                jsonData.hosts = []
+            }
+            if (!jsonData.wsPort){
+                jsonData.wsPort = 3001
+            }
+            if (!jsonData.token){
+                jsonData.token = ""
             }
             return jsonData;
         }
     }
-
-    setConfig(config: Config){
-        fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2), "utf-8");
+  
+    setConfig(config: Config) {
+        fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2), "utf-8")
     }
 }
diff --git a/src/common/data.ts b/src/common/data.ts
index bbd23e1..0027f11 100644
--- a/src/common/data.ts
+++ b/src/common/data.ts
@@ -86,4 +86,7 @@ export function getStrangerByUin(uin: string) {
             return uidMaps[key];
         }
     }
-}
\ No newline at end of file
+}
+
+export const version = "v3.3.0"
+export const heartInterval = 15000 // 毫秒
\ No newline at end of file
diff --git a/src/common/types.ts b/src/common/types.ts
index 54964dd..abc2b88 100644
--- a/src/common/types.ts
+++ b/src/common/types.ts
@@ -7,6 +7,7 @@ export interface Config {
     enableHttpPost?: boolean
     enableWs?: boolean
     enableWsReverse?: boolean
+    token?: string
     enableBase64?: boolean
     debug?: boolean
     reportSelfMessage?: boolean
diff --git a/src/common/utils.ts b/src/common/utils.ts
index 233d2bd..d5ea957 100644
--- a/src/common/utils.ts
+++ b/src/common/utils.ts
@@ -33,7 +33,7 @@ export function log(...msg: any[]) {
         }
         logMsg += msgItem + " ";
     }
-    logMsg = `${currentDateTime} ${userInfo}: ${logMsg}\n`
+    logMsg = `${currentDateTime} ${userInfo}: ${logMsg}\n\n`
     // sendLog(...msg);
     // console.log(msg)
     fs.appendFile(path.join(CONFIG_DIR , `llonebot-${currentDate}.log`), logMsg, (err: any) => {
diff --git a/src/main/main.ts b/src/main/main.ts
index c2ab87e..efcb816 100644
--- a/src/main/main.ts
+++ b/src/main/main.ts
@@ -1,22 +1,16 @@
 // 运行在 Electron 主进程 下的插件入口
 
-import * as path from "path";
 import { BrowserWindow, ipcMain } from 'electron';
-import * as util from 'util';
 
 import { Config } from "../common/types";
-import {
-    CHANNEL_GET_CONFIG,
-    CHANNEL_LOG,
-    CHANNEL_SET_CONFIG,
-} from "../common/channels";
 import {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, msgHistory, selfInfo } from "../common/data";
+import { addHistoryMsg, getGroupMember, msgHistory, selfInfo, uidMaps } from "../common/data";
 import { hookNTQQApiReceive, ReceiveCmd, registerReceiveHook } from "../ntqqapi/hook";
 import { OB11Constructor } from "../onebot11/constructor";
 import { NTQQApi } from "../ntqqapi/ntcall";
-import { Group, RawMessage, SelfInfo } from "../ntqqapi/types";
+import { ChatType, RawMessage } from "../ntqqapi/types";
 
 const fs = require('fs');
 
@@ -36,7 +30,17 @@ function onLoad() {
         return getConfigUtil().getConfig();
     })
     ipcMain.on(CHANNEL_SET_CONFIG, (event: any, arg: Config) => {
-        getConfigUtil().setConfig(arg);
+        let oldConfig = getConfigUtil().getConfig();
+        getConfigUtil().setConfig(arg)
+        if (arg.port != oldConfig.port) {
+            startHTTPServer(arg.port)
+        }
+        if (arg.wsPort != oldConfig.wsPort) {
+            startWebsocketServer(arg.wsPort)
+        }
+        if (arg.token != oldConfig.token) {
+            setToken(arg.token);
+        }
     })
 
     ipcMain.on(CHANNEL_LOG, (event: any, arg: any) => {
@@ -47,7 +51,8 @@ function onLoad() {
     function postRawMsg(msgList: RawMessage[]) {
         const {debug, reportSelfMessage} = getConfigUtil().getConfig();
         for (let message of msgList) {
-            message.msgShortId = msgHistory[message.msgId]?.msgShortId;
+            log("收到新消息", message)
+            message.msgShortId = msgHistory[message.msgId]?.msgShortId
             if (!message.msgShortId) {
                 addHistoryMsg(message);
             }
@@ -55,8 +60,8 @@ function onLoad() {
                 if (debug) {
                     msg.raw = message;
                 }
-                if (msg.user_id == selfInfo.uin && !reportSelfMessage) {
-                    return;
+                if (msg.user_id.toString() == selfInfo.uin && !reportSelfMessage) {
+                    return
                 }
                 postMsg(msg);
             }).catch(e => log("constructMessage error: ", e.toString()));
@@ -64,8 +69,7 @@ function onLoad() {
     }
 
 
-    function start() {
-        log("llonebot start");
+    async function start() {
         registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.NEW_MSG, (payload) => {
             try {
                 postRawMsg(payload.msgList);
@@ -73,7 +77,38 @@ function onLoad() {
                 log("report message error: ", e.toString());
             }
         })
-
+        registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.UPDATE_MSG, async (payload) => {
+            for (const message of payload.msgList) {
+                // log("message update", message, message.sendStatus)
+                if (message.sendStatus === 2) {
+                    // 撤回消息上报
+                    const oriMessage = msgHistory[message.msgId]
+                    if (!oriMessage) {
+                        continue
+                    }
+                    if (message.chatType == ChatType.friend) {
+                        const friendRecallEvent = OB11Constructor.friendRecallEvent(message.senderUin, oriMessage.msgShortId)
+                        postMsg(friendRecallEvent)
+                    } else if (message.chatType == ChatType.group) {
+                        let operatorId = message.senderUin
+                        for (const element of message.elements) {
+                            const operatorUid = element.grayTipElement?.revokeElement.operatorUid
+                            const operator = await getGroupMember(message.peerUin, null, operatorUid)
+                            operatorId = operator.uin
+                        }
+                        const groupRecallEvent = OB11Constructor.groupRecallEvent(
+                            message.peerUin,
+                            message.senderUin,
+                            operatorId,
+                            oriMessage.msgShortId
+                        )
+                        postMsg(groupRecallEvent)
+                    }
+                    continue
+                }
+                addHistoryMsg(message)
+            }
+        })
         registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmd.SELF_SEND_MSG, (payload) => {
             const {reportSelfMessage} = getConfigUtil().getConfig();
             if (!reportSelfMessage) {
@@ -86,12 +121,14 @@ function onLoad() {
                 log("report self message error: ", e.toString());
             }
         })
-        NTQQApi.getGroups(true).then();
-
-        const config = getConfigUtil().getConfig();
-        startExpress(config.httpPort);
+        NTQQApi.getGroups(true).then()
+      
+        const config = getConfigUtil().getConfig()
+        startHTTPServer(config.httpPort)
         startWebsocketServer(config.wsPort);
         initWebsocket();
+        setToken(config.token)
+        log("LLOneBot start")
     }
 
     const init = async () => {
@@ -116,10 +153,9 @@ function onLoad() {
                 log("get self nickname failed", e.toString());
                 return setTimeout(init, 1000);
             }
-            start();
-        }
-        else{
-            setTimeout(init, 1000);
+            start().then();
+        } else {
+            setTimeout(init, 1000)
         }
     }
     setTimeout(init, 1000);
@@ -131,7 +167,7 @@ function onBrowserWindowCreated(window: BrowserWindow) {
     try {
         hookNTQQApiReceive(window);
     } catch (e) {
-        log("llonebot hook error: ", e.toString())
+        log("LLOneBot hook error: ", e.toString())
     }
 }
 
diff --git a/src/ntqqapi/constructor.ts b/src/ntqqapi/constructor.ts
index 76a38b0..a966026 100644
--- a/src/ntqqapi/constructor.ts
+++ b/src/ntqqapi/constructor.ts
@@ -1,5 +1,13 @@
-import {ElementType, SendPicElement, SendPttElement, SendReplyElement, SendTextElement, AtType} from "./types";
-import {NTQQApi} from "./ntcall";
+import {
+    ElementType,
+    SendPicElement,
+    SendPttElement,
+    SendReplyElement,
+    SendTextElement,
+    AtType,
+    SendFaceElement
+} from "./types";
+import { NTQQApi } from "./ntcall";
 
 
 export class SendMsgElementConstructor {
@@ -44,7 +52,7 @@ export class SendMsgElementConstructor {
         }
     }
 
-    static async pic(picPath: string): Promise<SendPicElement>{
+    static async pic(picPath: string): Promise<SendPicElement> {
         const {md5, fileName, path, fileSize} = await NTQQApi.uploadFile(picPath);
         const imageSize = await NTQQApi.getImageSize(picPath);
         const picElement = {
@@ -70,7 +78,7 @@ export class SendMsgElementConstructor {
         };
     }
 
-    static async ptt(pttPath: string):Promise<SendPttElement> {
+    static async ptt(pttPath: string): Promise<SendPttElement> {
         const {md5, fileName, path, fileSize} = await NTQQApi.uploadFile(pttPath);
         return {
             elementType: ElementType.PTT,
@@ -94,4 +102,15 @@ export class SendMsgElementConstructor {
             }
         };
     }
+
+    static face(faceId: number): SendFaceElement {
+        return {
+            elementType: ElementType.FACE,
+            elementId: "",
+            faceElement: {
+                faceIndex: faceId,
+                faceType: 1
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/ntqqapi/hook.ts b/src/ntqqapi/hook.ts
index c9800f0..b3c5e4a 100644
--- a/src/ntqqapi/hook.ts
+++ b/src/ntqqapi/hook.ts
@@ -39,7 +39,7 @@ interface NTQQApiReturnData<PayloadType = unknown> extends Array<any> {
 
 let receiveHooks: Array<{
     method: ReceiveCmd,
-    hookFunc: (payload: any) => void,
+    hookFunc: ((payload: any) => void | Promise<void>)
     id: string
 }> = []
 
@@ -55,7 +55,10 @@ export function hookNTQQApiReceive(window: BrowserWindow) {
                     if (hook.method === ntQQApiMethodName) {
                         new Promise((resolve, reject) => {
                             try {
-                                hook.hookFunc(receiveData.payload);
+                                let _ = hook.hookFunc(receiveData.payload)
+                                if (hook.hookFunc.constructor.name === "AsyncFunction"){
+                                    (_ as Promise<void>).then()
+                                }
                             } catch (e) {
                                 log("hook error", e, receiveData.payload)
                             }
diff --git a/src/ntqqapi/types.ts b/src/ntqqapi/types.ts
index da86cf8..da4ff74 100644
--- a/src/ntqqapi/types.ts
+++ b/src/ntqqapi/types.ts
@@ -7,13 +7,14 @@ export interface User {
     remark?: string
 }
 
-export interface SelfInfo extends User{
+export interface SelfInfo extends User {
 
 }
 
-export interface Friend extends User{}
+export interface Friend extends User {
+}
 
-export interface Group{
+export interface Group {
     groupCode: string,
     maxMember: number,
     memberCount: number,
@@ -62,6 +63,7 @@ export enum ElementType {
     TEXT = 1,
     PIC = 2,
     PTT = 4,
+    FACE = 6,
     REPLY = 7,
 }
 
@@ -76,6 +78,7 @@ export interface SendTextElement {
         atNtUid: string,
     }
 }
+
 export interface SendPttElement {
     elementType: ElementType.PTT,
     elementId: "",
@@ -127,7 +130,13 @@ export interface SendReplyElement {
     }
 }
 
-export type SendMessageElement = SendTextElement | SendPttElement | SendPicElement | SendReplyElement
+export interface SendFaceElement {
+    elementType: ElementType.FACE,
+    elementId: "",
+    faceElement: FaceElement
+}
+
+export type SendMessageElement = SendTextElement | SendPttElement | SendPicElement | SendReplyElement | SendFaceElement
 
 export enum AtType {
     notAt = 0,
@@ -140,6 +149,7 @@ export enum ChatType {
     group = 2,
     temp = 100
 }
+
 export interface PttElement {
     canConvert2Text: boolean;
     duration: number; // 秒数
@@ -180,6 +190,22 @@ export interface PicElement {
     fileUuid: string;
 }
 
+export interface GrayTipElement {
+    revokeElement: {
+        operatorRole: string;
+        operatorUid: string;
+        operatorNick: string;
+        operatorRemark: string;
+        operatorMemRemark?: string;
+        wording: string;  // 自定义的撤回提示语
+    }
+}
+
+export interface FaceElement {
+    faceIndex: number,
+    faceType: 1
+}
+
 export interface RawMessage {
     msgId: string;
     msgShortId?: number;  // 自己维护的消息id
@@ -191,6 +217,7 @@ export interface RawMessage {
     sendNickName: string;
     sendMemberName?: string; // 发送者群名片
     chatType: ChatType;
+    sendStatus?: number;  // 消息状态,2是已撤回
     elements: {
         elementId: string,
         replyElement: {
@@ -208,17 +235,7 @@ export interface RawMessage {
         picElement: PicElement;
         pttElement: PttElement;
         arkElement: ArkElement;
+        grayTipElement: GrayTipElement;
+        faceElement: FaceElement;
     }[];
 }
-
-export interface MessageElement {
-    raw: RawMessage;
-    peer: any;
-    sender: {
-        uid: string; // 一串加密的字符串
-        memberName: string;
-        nickname: string;
-    };
-}
-
-
diff --git a/src/onebot11/actions/CanSendImage.ts b/src/onebot11/actions/CanSendImage.ts
new file mode 100644
index 0000000..f8c6b2c
--- /dev/null
+++ b/src/onebot11/actions/CanSendImage.ts
@@ -0,0 +1,10 @@
+import { ActionName } from "./types";
+import CanSendRecord from "./CanSendRecord";
+
+interface ReturnType{
+    yes: boolean
+}
+
+export default class CanSendImage extends CanSendRecord{
+    actionName = ActionName.CanSendImage
+}
\ No newline at end of file
diff --git a/src/onebot11/actions/CanSendRecord.ts b/src/onebot11/actions/CanSendRecord.ts
new file mode 100644
index 0000000..4d94d17
--- /dev/null
+++ b/src/onebot11/actions/CanSendRecord.ts
@@ -0,0 +1,16 @@
+import BaseAction from "./BaseAction";
+import { ActionName } from "./types";
+
+interface ReturnType{
+    yes: boolean
+}
+
+export default class CanSendRecord extends BaseAction<any, ReturnType>{
+    actionName = ActionName.CanSendRecord
+
+    protected async _handle(payload): Promise<ReturnType>{
+        return {
+            yes: true
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/onebot11/actions/GetGroupInfo.ts b/src/onebot11/actions/GetGroupInfo.ts
index af59af1..0ff09a1 100644
--- a/src/onebot11/actions/GetGroupInfo.ts
+++ b/src/onebot11/actions/GetGroupInfo.ts
@@ -1,5 +1,5 @@
 import { OB11Group } from '../types';
-import { getGroup, groups } from "../../common/data";
+import { getGroup } from "../../common/data";
 import { OB11Constructor } from "../constructor";
 import BaseAction from "./BaseAction";
 import { ActionName } from "./types";
@@ -8,17 +8,17 @@ interface PayloadType {
     group_id: number
 }
 
-class GetGroupInfo extends BaseAction<PayloadType, OB11Group[]> {
+class GetGroupInfo extends BaseAction<PayloadType, OB11Group> {
     actionName = ActionName.GetGroupInfo
 
     protected async _handle(payload: PayloadType) {
         const group = await getGroup(payload.group_id.toString())
         if (group) {
-            return OB11Constructor.groups(groups)
+            return OB11Constructor.group(group)
         } else {
             throw `群${payload.group_id}不存在`
         }
     }
 }
 
-export default GetGroupInfo
\ No newline at end of file
+export default GetGroupInfo
diff --git a/src/onebot11/actions/GetMsg.ts b/src/onebot11/actions/GetMsg.ts
index 22de0cf..e31c887 100644
--- a/src/onebot11/actions/GetMsg.ts
+++ b/src/onebot11/actions/GetMsg.ts
@@ -16,6 +16,9 @@ class GetMsg extends BaseAction<PayloadType, OB11Message> {
 
     protected async _handle(payload: PayloadType){
         // log("history msg ids", Object.keys(msgHistory));
+        if (!payload.message_id){
+            throw("参数message_id不能为空")
+        }
         const msg = getHistoryMsgByShortId(payload.message_id)
         if (msg) {
             const msgData = await OB11Constructor.message(msg);
diff --git a/src/onebot11/actions/GetStatus.ts b/src/onebot11/actions/GetStatus.ts
new file mode 100644
index 0000000..2df98db
--- /dev/null
+++ b/src/onebot11/actions/GetStatus.ts
@@ -0,0 +1,14 @@
+import BaseAction from "./BaseAction";
+import {OB11Status} from "../types";
+import { ActionName } from "./types";
+
+
+export default class GetStatus extends BaseAction<any, OB11Status> {
+    actionName = ActionName.GetStatus
+    protected async _handle(payload: any): Promise<OB11Status> {
+        return {
+            online: null,
+            good: true
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/onebot11/actions/GetVersionInfo.ts b/src/onebot11/actions/GetVersionInfo.ts
new file mode 100644
index 0000000..5fadb22
--- /dev/null
+++ b/src/onebot11/actions/GetVersionInfo.ts
@@ -0,0 +1,15 @@
+import BaseAction from "./BaseAction";
+import { OB11Version } from "../types";
+import {version} from "../../common/data";
+import { ActionName } from "./types";
+
+export default class GetVersionInfo extends BaseAction<any, OB11Version>{
+    actionName = ActionName.GetVersionInfo
+    protected async _handle(payload: any): Promise<OB11Version> {
+        return {
+            app_name: "LLOneBot",
+            protocol_version: "v11",
+            app_version: version
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/onebot11/actions/SendMsg.ts b/src/onebot11/actions/SendMsg.ts
index c8bb429..e7b9700 100644
--- a/src/onebot11/actions/SendMsg.ts
+++ b/src/onebot11/actions/SendMsg.ts
@@ -1,15 +1,7 @@
-import { AtType, ChatType, Group } from "../../ntqqapi/types";
-import {
-    addHistoryMsg,
-    friends,
-    getGroup,
-    getHistoryMsgByShortId,
-    getStrangerByUin,
-} from "../../common/data";
+import { AtType, ChatType, Group, SendMessageElement } from "../../ntqqapi/types";
+import { addHistoryMsg, friends, getGroup, getHistoryMsgByShortId, getStrangerByUin, } from "../../common/data";
 import { OB11MessageData, OB11MessageDataType, OB11PostSendMsg } from '../types';
-import { NTQQApi } from "../../ntqqapi/ntcall";
-import { Peer } from "../../ntqqapi/ntcall";
-import { SendMessageElement } from "../../ntqqapi/types";
+import { NTQQApi, Peer } from "../../ntqqapi/ntcall";
 import { SendMsgElementConstructor } from "../../ntqqapi/constructor";
 import { uri2local } from "../utils";
 import { v4 as uuid4 } from 'uuid';
@@ -24,7 +16,7 @@ export interface ReturnDataType {
 class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
     actionName = ActionName.SendMsg
 
-    protected async _handle(payload: OB11PostSendMsg){
+    protected async _handle(payload: OB11PostSendMsg) {
         const peer: Peer = {
             chatType: ChatType.friend,
             peerUid: ""
@@ -39,18 +31,16 @@ class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
             peer.chatType = ChatType.group
             // peer.name = group.name
             peer.peerUid = group.groupCode
-        }
-        else if (payload?.user_id) {
+        } else if (payload?.user_id) {
             const friend = friends.find(f => f.uin == payload.user_id.toString())
             if (friend) {
                 // peer.name = friend.nickName
                 peer.peerUid = friend.uid
-            }
-            else {
+            } else {
                 peer.chatType = ChatType.temp
                 const tempUser = getStrangerByUin(payload.user_id.toString())
                 if (!tempUser) {
-                    throw(`找不到私聊对象${payload.user_id}`)
+                    throw (`找不到私聊对象${payload.user_id}`)
                 }
                 // peer.name = tempUser.nickName
                 peer.peerUid = tempUser.uid
@@ -63,8 +53,7 @@ class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
                     text: payload.message
                 }
             }] as OB11MessageData[]
-        }
-        else if (!Array.isArray(payload.message)) {
+        } else if (!Array.isArray(payload.message)) {
             payload.message = [payload.message]
         }
         const sendElements: SendMessageElement[] = []
@@ -75,22 +64,23 @@ class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
                     if (text) {
                         sendElements.push(SendMsgElementConstructor.text(sendMsg.data!.text))
                     }
-                } break;
+                }
+                    break;
                 case OB11MessageDataType.at: {
                     let atQQ = sendMsg.data?.qq;
                     if (atQQ) {
                         atQQ = atQQ.toString()
                         if (atQQ === "all") {
                             sendElements.push(SendMsgElementConstructor.at(atQQ, atQQ, AtType.atAll, "全体成员"))
-                        }
-                        else {
+                        } else {
                             const atMember = group?.members.find(m => m.uin == atQQ)
                             if (atMember) {
                                 sendElements.push(SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick))
                             }
                         }
                     }
-                } break;
+                }
+                    break;
                 case OB11MessageDataType.reply: {
                     let replyMsgId = sendMsg.data.id;
                     if (replyMsgId) {
@@ -100,20 +90,27 @@ class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
                             sendElements.push(SendMsgElementConstructor.reply(replyMsg.msgSeq, replyMsg.msgId, replyMsg.senderUin, replyMsg.senderUin))
                         }
                     }
-                } break;
+                }
+                    break;
+                case OB11MessageDataType.face: {
+                    const faceId = sendMsg.data?.id
+                    if (faceId) {
+                        sendElements.push(SendMsgElementConstructor.face(parseInt(faceId)))
+                    }
+                }
+                    break;
                 case OB11MessageDataType.image:
                 case OB11MessageDataType.voice: {
                     const file = sendMsg.data?.file
                     if (file) {
                         const {path, isLocal} = (await uri2local(uuid4(), file))
                         if (path) {
-                            if (!isLocal){ // 只删除http和base64转过来的文件
+                            if (!isLocal) { // 只删除http和base64转过来的文件
                                 deleteAfterSentFiles.push(path)
                             }
-                            if (sendMsg.type === OB11MessageDataType.image){
+                            if (sendMsg.type === OB11MessageDataType.image) {
                                 sendElements.push(await SendMsgElementConstructor.pic(path))
-                            }
-                            else {
+                            } else {
                                 sendElements.push(await SendMsgElementConstructor.ptt(path))
                             }
                         }
@@ -125,10 +122,11 @@ class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
         try {
             const returnMsg = await NTQQApi.sendMsg(peer, sendElements)
             addHistoryMsg(returnMsg)
-            deleteAfterSentFiles.map(f=>fs.unlink(f, ()=>{}))
-            return { message_id: returnMsg.msgShortId }
+            deleteAfterSentFiles.map(f => fs.unlink(f, () => {
+            }))
+            return {message_id: returnMsg.msgShortId}
         } catch (e) {
-            throw(e.toString())
+            throw (e.toString())
         }
     }
 }
diff --git a/src/onebot11/actions/index.ts b/src/onebot11/actions/index.ts
index dfd90c5..a5c2e42 100644
--- a/src/onebot11/actions/index.ts
+++ b/src/onebot11/actions/index.ts
@@ -10,6 +10,10 @@ import SendPrivateMsg from './SendPrivateMsg'
 import SendMsg from './SendMsg'
 import DeleteMsg from "./DeleteMsg";
 import BaseAction from "./BaseAction";
+import GetVersionInfo from "./GetVersionInfo";
+import CanSendRecord from "./CanSendRecord";
+import CanSendImage from "./CanSendImage";
+import GetStatus from "./GetStatus";
 
 export const actionHandlers = [
     new GetMsg(),
@@ -17,7 +21,11 @@ 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(),
+    new GetStatus()
 ]
 
 function initActionMap() {
@@ -29,4 +37,4 @@ function initActionMap() {
     return actionMap
 }
 
-export const actionMap = initActionMap();
\ No newline at end of file
+export const actionMap = initActionMap();
diff --git a/src/onebot11/actions/types.ts b/src/onebot11/actions/types.ts
index fdfeeb5..39f0b25 100644
--- a/src/onebot11/actions/types.ts
+++ b/src/onebot11/actions/types.ts
@@ -1,3 +1,5 @@
+import GetVersionInfo from "./GetVersionInfo";
+
 export type BaseCheckResult = ValidCheckResult | InvalidCheckResult
 
 export interface ValidCheckResult {
@@ -22,5 +24,9 @@ export enum ActionName{
     SendMsg = "send_msg",
     SendGroupMsg = "send_group_msg",
     SendPrivateMsg = "send_private_msg",
-    DeleteMsg = "delete_msg"
+    DeleteMsg = "delete_msg",
+    GetVersionInfo = "get_version_info",
+    GetStatus = "get_status",
+    CanSendRecord = "can_send_record",
+    CanSendImage = "can_send_image",
 }
\ No newline at end of file
diff --git a/src/onebot11/actions/utils.ts b/src/onebot11/actions/utils.ts
index e87b394..3d432e1 100644
--- a/src/onebot11/actions/utils.ts
+++ b/src/onebot11/actions/utils.ts
@@ -6,7 +6,8 @@ export class OB11Response {
             status: status,
             retcode: retcode,
             data: data,
-            message: message
+            message: message,
+            echo,
         }
     }
     static ok<T>(data: T) {
diff --git a/src/onebot11/constructor.ts b/src/onebot11/constructor.ts
index bbe7c92..b404918 100644
--- a/src/onebot11/constructor.ts
+++ b/src/onebot11/constructor.ts
@@ -7,25 +7,26 @@ import {
     OB11User
 } from "./types";
 import { AtType, ChatType, Group, GroupMember, IMAGE_HTTP_HOST, RawMessage, SelfInfo, User } from '../ntqqapi/types';
-import { getFriend, getGroupMember, getHistoryMsgBySeq, msgHistory, selfInfo } from '../common/data';
+import { getFriend, getGroupMember, getHistoryMsgBySeq, heartInterval, msgHistory, selfInfo } from '../common/data';
 import { file2base64, getConfigUtil, log } from "../common/utils";
 import { NTQQApi } from "../ntqqapi/ntcall";
+import {OB11EventConstructor} from "./events/constructor";
 
 
-export class OB11Constructor {
+export class OB11Constructor extends OB11EventConstructor{
     static async message(msg: RawMessage): Promise<OB11Message> {
 
         const {enableBase64} = getConfigUtil().getConfig()
         const message_type = msg.chatType == ChatType.group ? "group" : "private";
         const resMsg: OB11Message = {
-            self_id: selfInfo.uin,
-            user_id: msg.senderUin,
+            self_id: parseInt(selfInfo.uin),
+            user_id: parseInt(msg.senderUin),
             time: parseInt(msg.msgTime) || 0,
             message_id: msg.msgShortId,
             real_id: msg.msgId,
             message_type: msg.chatType == ChatType.group ? "group" : "private",
             sender: {
-                user_id: msg.senderUin,
+                user_id: parseInt(msg.senderUin),
                 nickname: msg.sendNickName,
                 card: msg.sendMemberName || "",
             },
@@ -37,10 +38,11 @@ export class OB11Constructor {
         }
         if (msg.chatType == ChatType.group) {
             resMsg.sub_type = "normal"
-            resMsg.group_id = msg.peerUin
+            resMsg.group_id = parseInt(msg.peerUin)
             const member = await getGroupMember(msg.peerUin, msg.senderUin);
             if (member) {
                 resMsg.sender.role = OB11Constructor.groupMemberRole(member.role);
+                resMsg.sender.nickname = member.nick
             }
         } else if (msg.chatType == ChatType.friend) {
             resMsg.sub_type = "friend"
@@ -111,6 +113,9 @@ export class OB11Constructor {
             } else if (element.arkElement) {
                 message_data["type"] = OB11MessageDataType.json;
                 message_data["data"]["data"] = element.arkElement.bytesData;
+            } else if (element.faceElement){
+                message_data["type"] = OB11MessageDataType.face;
+                message_data["data"]["id"] = element.faceElement.faceIndex.toString();
             }
             if (message_data.data.http_file) {
                 message_data.data.file = message_data.data.http_file
@@ -135,7 +140,7 @@ export class OB11Constructor {
 
     static friend(friend: User): OB11User {
         return {
-            user_id: friend.uin,
+            user_id: parseInt(friend.uin),
             nickname: friend.nick,
             remark: friend.remark
         }
@@ -144,7 +149,7 @@ export class OB11Constructor {
 
     static selfInfo(selfInfo: SelfInfo): OB11User {
         return {
-            user_id: selfInfo.uin,
+            user_id: parseInt(selfInfo.uin),
             nickname: selfInfo.nick
         }
     }
@@ -163,8 +168,8 @@ export class OB11Constructor {
 
     static groupMember(group_id: string, member: GroupMember): OB11GroupMember {
         return {
-            group_id,
-            user_id: member.uin,
+            group_id: parseInt(group_id),
+            user_id: parseInt(member.uin),
             nickname: member.nick,
             card: member.cardName
         }
@@ -177,7 +182,7 @@ export class OB11Constructor {
 
     static group(group: Group): OB11Group {
         return {
-            group_id: group.groupCode,
+            group_id: parseInt(group.groupCode),
             group_name: group.groupName
         }
     }
diff --git a/src/onebot11/events/constructor.ts b/src/onebot11/events/constructor.ts
new file mode 100644
index 0000000..06cd850
--- /dev/null
+++ b/src/onebot11/events/constructor.ts
@@ -0,0 +1,58 @@
+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
new file mode 100644
index 0000000..5902114
--- /dev/null
+++ b/src/onebot11/events/types.ts
@@ -0,0 +1,67 @@
+import { OB11Status } from "../types";
+
+export enum OB11EventPostType{
+    META = "meta_event",
+    NOTICE = "notice"
+}
+
+export interface OB11EventBase {
+    time: number
+    self_id: number
+    post_type: OB11EventPostType
+}
+
+export interface OB11MetaEvent extends OB11EventBase{
+    post_type: OB11EventPostType.META
+    meta_event_type: "lifecycle" | "heartbeat"
+}
+
+export interface 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 88ef669..6ea5224 100644
--- a/src/onebot11/server.ts
+++ b/src/onebot11/server.ts
@@ -3,17 +3,27 @@ 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';
-
-const JSONbig = require('json-bigint')({ storeAsString: true });
-import { selfInfo } from "../common/data";
+import { getConfigUtil, log } from "../common/utils";
+import { heartInterval, 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/manager";
 import ReconnectingWebsocket from "./ReconnectingWebsocket";
+import { actionHandlers } from "./actions";
+import { OB11Response } from "./actions/utils";
+import { ActionName } from "./actions/types";
+import BaseAction from "./actions/BaseAction";
+import { OB11Constructor } from "./constructor";
+import { OB11EventBase, OB11LifeCycleEvent, OB11MetaEvent, OB11NoticeEvent } from "./events/types";
 
+let accessToken = ""
 
 // @SiberianHusky 2021-08-15
 enum WebsocketType {
@@ -59,8 +69,11 @@ function checkSendMessage(sendMsgList: OB11MessageData[]) {
 
 // ==end==
 
+const JSONbig = require('json-bigint')({storeAsString: true});
+
 const expressAPP = express();
-expressAPP.use(express.urlencoded({ extended: true, limit: "500mb" }));
+let httpServer: http.Server = null;
+expressAPP.use(express.urlencoded({extended: true, limit: "500mb"}));
 
 const expressWsApp = express();
 const websocketClientConnections = [];
@@ -83,14 +96,44 @@ expressAPP.use((req, res, next) => {
     });
 });
 
-export function startExpress(port: number) {
+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已启动');
+        res.send('LLOneBot已启动');
     })
 
     if (getConfigUtil().getConfig().enableHttp) {
-        expressAPP.listen(port, "0.0.0.0", () => {
+        httpServer = expressAPP.listen(port, "0.0.0.0", () => {
             console.log(`llonebot http service started 0.0.0.0:${port}`);
         });
     }
@@ -207,61 +250,165 @@ function initWebsocketServer(ws, req, type: WebsocketType) {
     });
 }
 
+let wsEventClients: websocket.WebSocket[] = [];
+type RouterHandler = (payload: any) => Promise<OB11Return<any>>
+let routers: Record<string, RouterHandler> = {};
 
-export function postMsg(msg: OB11Message) {
-    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));
-            });
-        }
+function wsReply(wsClient: websocket.WebSocket, data: OB11Return<any> | PostMsgType) {
+    try {
+        wsClient.send(JSON.stringify(data))
+        log("ws 消息上报", data)
+    } catch (e) {
+        log("websocket 回复失败", e)
+    }
+}
+
+
+// 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 function postMsg(msg: PostMsgType) {
+    const {reportSelfMessage} = getConfigUtil().getConfig()
+    // 判断msg是否是event
+    if (!reportSelfMessage) {
+        if ((msg as OB11Message).user_id.toString() == selfInfo.uin) {
+            return
+        }
+    }
+    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(`新消息事件HTTP上报成功: ${host} ` + JSON.stringify(msg));
+        }, (err: any) => {
+            log(`新消息事件HTTP上报失败: ${host} ` + err + JSON.stringify(msg));
+        });
+    }
+    for (const wsClient of wsEventClients) {
+        log("新消息事件ws上报", msg)
+        new Promise((resolve, reject) => {
+            wsReply(wsClient, msg);
+        }).then();
     }
-
 }
 
-let routers: Record<string, (payload: any) => Promise<OB11Return<any>>> = {};
 
 function registerRouter(action: string, handle: (payload: any) => Promise<any>) {
     let url = action.toString()
     if (!action.startsWith("/")) {
         url = "/" + action
     }
+
     async function _handle(res: Response, payload: any) {
         log("receive post data", url, payload)
         try {
             const result = await handle(payload)
             res.send(result)
-        }
-        catch (e) {
+        } catch (e) {
             log(e.stack);
             res.send(OB11Response.error(e.stack.toString(), 200))
         }
     }
 
-    expressAPP.post(url, (req: Request, res: Response) => {
-        _handle(res, req.body).then()
+    expressAPP.post(url, expressAuthorize, (req: Request, res: Response) => {
+        _handle(res, req.body || {}).then()
     });
-    expressAPP.get(url, (req: Request, res: Response) => {
-        _handle(res, req.query as any).then()
+    expressAPP.get(url, expressAuthorize, (req: Request, res: Response) => {
+        _handle(res, req.query as any || {}).then()
     });
-    routers[url] = handle
+    routers[action] = handle
 }
 
-for (const action  of actionHandlers) {
+for (const action of actionHandlers) {
     registerRouter(action.actionName, (payload) => action.handle(payload))
 }
\ No newline at end of file
diff --git a/src/onebot11/types.ts b/src/onebot11/types.ts
index fc2d900..fceffc5 100644
--- a/src/onebot11/types.ts
+++ b/src/onebot11/types.ts
@@ -1,8 +1,7 @@
-import { AtType } from "../ntqqapi/types";
-import { RawMessage } from "../ntqqapi/types";
+import { AtType, RawMessage } from "../ntqqapi/types";
 
 export interface OB11User {
-    user_id: string;
+    user_id: number;
     nickname: string;
     remark?: string
 }
@@ -20,8 +19,8 @@ export enum OB11GroupMemberRole {
 }
 
 export interface OB11GroupMember {
-    group_id: string
-    user_id: string
+    group_id: number
+    user_id: number
     nickname: string
     card?: string
     sex?: OB11UserSex
@@ -34,14 +33,14 @@ export interface OB11GroupMember {
 }
 
 export interface OB11Group {
-    group_id: string
+    group_id: number
     group_name: string
     member_count?: number
     max_member_count?: number
 }
 
 interface OB11Sender {
-    user_id: string,
+    user_id: number,
     nickname: string,
     sex?: OB11UserSex,
     age?: number,
@@ -56,12 +55,12 @@ export enum OB11MessageType {
 }
 
 export interface OB11Message {
-    self_id?: string,
+    self_id?: number,
     time: number,
     message_id: number,
     real_id: string,
-    user_id: string,
-    group_id?: string,
+    user_id: number,
+    group_id?: number,
     message_type: "private" | "group",
     sub_type?: "friend" | "group" | "normal",
     sender: OB11Sender,
@@ -89,7 +88,8 @@ export interface OB11Return<DataType> {
     status: string
     retcode: number
     data: DataType
-    message: string
+    message: string,
+    echo?: string
 }
 
 export interface OB11WebsocketReturn<DataType> {
@@ -100,7 +100,7 @@ export interface OB11WebsocketReturn<DataType> {
     message: string
 }
 
-export interface OB11SendMsgReturn extends OB11Return<{message_id: string}>{}
+export interface OB11SendMsgReturn extends OB11Return<{ message_id: string }> {}
 
 export enum OB11MessageDataType {
     text = "text",
@@ -108,7 +108,8 @@ export enum OB11MessageDataType {
     voice = "record",
     at = "at",
     reply = "reply",
-    json = "json"
+    json = "json",
+    face = "face"
 }
 
 export type OB11MessageData = {
@@ -140,6 +141,11 @@ export type OB11MessageData = {
     data: {
         id: string,
     }
+} | {
+    type: OB11MessageDataType.face,
+    data: {
+        id: string
+    }
 }
 
 export interface OB11PostSendMsg {
@@ -147,4 +153,17 @@ export interface OB11PostSendMsg {
     user_id: string,
     group_id?: string,
     message: OB11MessageData[] | string | OB11MessageData;
-}
\ No newline at end of file
+}
+
+export interface OB11Version {
+    app_name: "LLOneBot"
+    app_version: string
+    protocol_version: "v11"
+}
+
+
+export interface OB11Status {
+    online: boolean | null,
+    good: boolean
+}
+
diff --git a/src/onebot11/utils.ts b/src/onebot11/utils.ts
index d1cdfdc..e6e1278 100644
--- a/src/onebot11/utils.ts
+++ b/src/onebot11/utils.ts
@@ -1,6 +1,7 @@
 import { CONFIG_DIR, isGIF } from "../common/utils";
 import * as path from 'path';
 import { NTQQApi } from '../ntqqapi/ntcall';
+import { OB11MessageData } from "./types";
 const fs = require("fs").promises;
 
 export async function uri2local(fileName: string, uri: string){
@@ -59,4 +60,40 @@ export async function uri2local(fileName: string, uri: string){
     res.success = true
     res.path = filePath
     return res
-}
\ No newline at end of file
+}
+
+
+function checkSendMessage(sendMsgList: OB11MessageData[]) {
+    function checkUri(uri: string): boolean {
+        const pattern = /^(file:\/\/|http:\/\/|https:\/\/|base64:\/\/)/;
+        return pattern.test(uri);
+    }
+
+    for (let msg of sendMsgList) {
+        if (msg["type"] && msg["data"]) {
+            let type = msg["type"];
+            let data = msg["data"];
+            if (type === "text" && !data["text"]) {
+                return 400;
+            } else if (["image", "voice", "record"].includes(type)) {
+                if (!data["file"]) {
+                    return 400;
+                } else {
+                    if (checkUri(data["file"])) {
+                        return 200;
+                    } else {
+                        return 400;
+                    }
+                }
+
+            } else if (type === "at" && !data["qq"]) {
+                return 400;
+            } else if (type === "reply" && !data["id"]) {
+                return 400;
+            }
+        } else {
+            return 400
+        }
+    }
+    return 200;
+}
diff --git a/src/renderer.ts b/src/renderer.ts
index 03f5a34..fe2eee4 100644
--- a/src/renderer.ts
+++ b/src/renderer.ts
@@ -62,13 +62,17 @@ async function onSettingWindowCreated(view: Element) {
                         <setting-text>正向Websocket监听端口</setting-text>
                         <input id="wsPort" type="number" value="${config.wsPort}"/>
                     </setting-item>
+                    <setting-item class="vertical-list-item" data-direction="row">
+                        <setting-text>Access Token</setting-text>
+                        <input id="token" type="text" placeholder="可为空" value="${config.token}"/>
+                    </setting-item>
                     <div>
                         <button id="addWsHost" class="q-button">添加反向Websocket上报地址</button>
                     </div>
                     <div id="wsHostItems">
                         ${wsHostsEleStr}
                     </div>
-                    <button id="save" class="q-button">保存(监听端口重启QQ后生效)</button>
+                    <button id="save" class="q-button">保存</button>
                 </setting-list>
             </setting-panel>
             <setting-panel>
@@ -197,6 +201,7 @@ async function onSettingWindowCreated(view: Element) {
             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>;
+            const tokenEle = document.getElementById("token") as HTMLInputElement;
 
             // 获取端口和host
             const httpPort = httpPortEle.value
@@ -209,6 +214,7 @@ async function onSettingWindowCreated(view: Element) {
             }
 
             const wsPort = wsPortEle.value
+            const token = tokenEle.value
             let wsHosts: string[] = [];
 
             for (const hostEle of wsHostEles) {
@@ -222,6 +228,7 @@ async function onSettingWindowCreated(view: Element) {
             config.httpHosts = httpHosts;
             config.wsPort = parseInt(wsPort);
             config.wsHosts = wsHosts;
+            config.token = token.trim();
             window.llonebot.setConfig(config);
             alert("保存成功");
         })