diff --git a/src/common/config.ts b/src/common/config.ts
index 600d20e..6ae2723 100644
--- a/src/common/config.ts
+++ b/src/common/config.ts
@@ -39,6 +39,7 @@ export class ConfigUtil {
             enableWs: true,
             enableWsReverse: false,
             messagePostFormat: "array",
+            enableHttpHeart: false
         }
         let defaultConfig: Config = {
             ob11: ob11Default,
diff --git a/src/common/data.ts b/src/common/data.ts
index a6b3c44..982c980 100644
--- a/src/common/data.ts
+++ b/src/common/data.ts
@@ -58,6 +58,15 @@ export async function getGroup(qq: string): Promise<Group | undefined> {
     return group
 }
 
+export function deleteGroup(groupCode: string) {
+    const groupIndex = groups.findIndex(group => group.groupCode === groupCode.toString())
+    // log(groups, groupCode, groupIndex);
+    if (groupIndex !== -1) {
+        log("删除群", groupCode);
+        groups.splice(groupIndex, 1)
+    }
+}
+
 export async function getGroupMember(groupQQ: string | number, memberUinOrUid: string | number) {
     groupQQ = groupQQ.toString()
     memberUinOrUid = memberUinOrUid.toString()
diff --git a/src/common/server/http.ts b/src/common/server/http.ts
index ccaf49f..e32472c 100644
--- a/src/common/server/http.ts
+++ b/src/common/server/http.ts
@@ -95,6 +95,9 @@ export abstract class HttpServerBase {
             if (method == "get") {
                 payload = req.query
             }
+            else if (req.query){
+                payload = {...req.query, ...req.body}
+            }
             log("收到http请求", url, payload);
             try {
                 res.send(await handler(res, payload))
diff --git a/src/common/types.ts b/src/common/types.ts
index 7a89537..7cad1df 100644
--- a/src/common/types.ts
+++ b/src/common/types.ts
@@ -9,6 +9,7 @@ export interface OB11Config {
   enableWs?: boolean
   enableWsReverse?: boolean
   messagePostFormat?: 'array' | 'string'
+  enableHttpHeart?: boolean
 }
 export interface CheckVersion {
   result: boolean,
diff --git a/src/main/main.ts b/src/main/main.ts
index 005aa58..e7c6d2e 100644
--- a/src/main/main.ts
+++ b/src/main/main.ts
@@ -34,7 +34,7 @@ import {
     GroupNotifyTypes,
     RawMessage
 } from "../ntqqapi/types";
-import {ob11HTTPServer} from "../onebot11/server/http";
+import {httpHeart, ob11HTTPServer} from "../onebot11/server/http";
 import {OB11FriendRecallNoticeEvent} from "../onebot11/event/notice/OB11FriendRecallNoticeEvent";
 import {OB11GroupRecallNoticeEvent} from "../onebot11/event/notice/OB11GroupRecallNoticeEvent";
 import {postOB11Event} from "../onebot11/server/postOB11Event";
@@ -148,7 +148,10 @@ function onLoad() {
     async function postReceiveMsg(msgList: RawMessage[]) {
         const {debug, reportSelfMessage} = getConfigUtil().getConfig();
         for (let message of msgList) {
-
+            // 过滤启动之前的消息
+            if (parseInt(message.msgTime) < startTime / 1000) {
+                continue;
+            }
             // log("收到新消息", message.msgId, message.msgSeq)
             // if (message.senderUin !== selfInfo.uin){
             message.msgShortId = await dbUtil.addMsg(message);
@@ -379,7 +382,7 @@ function onLoad() {
         })
     }
 
-    let startTime = 0;
+    let startTime = 0;  // 毫秒
 
     async function start() {
         log("llonebot pid", process.pid)
@@ -402,6 +405,9 @@ function onLoad() {
         if (config.ob11.enableWsReverse) {
             ob11ReverseWebsockets.start();
         }
+        if (config.ob11.enableHttpHeart){
+            httpHeart.start();
+        }
 
         log("LLOneBot start")
     }
diff --git a/src/main/setConfig.ts b/src/main/setConfig.ts
index 7759f56..423ee80 100644
--- a/src/main/setConfig.ts
+++ b/src/main/setConfig.ts
@@ -1,5 +1,5 @@
 import {Config} from "../common/types";
-import {ob11HTTPServer} from "../onebot11/server/http";
+import {httpHeart, ob11HTTPServer} from "../onebot11/server/http";
 import {ob11WebsocketServer} from "../onebot11/server/ws/WebsocketServer";
 import {ob11ReverseWebsockets} from "../onebot11/server/ws/ReverseWebsocket";
 import {llonebotError} from "../common/data";
@@ -54,6 +54,14 @@ export async function setConfig(config: Config) {
             }
         }
     }
+    if (config.ob11.enableHttpHeart){
+        // 启动http心跳
+        httpHeart.start();
+    }
+    else{
+        // 关闭http心跳
+        httpHeart.stop();
+    }
     log("old config", oldConfig)
     log("配置已更新", config)
     checkFfmpeg(config.ffmpeg).then()
diff --git a/src/ntqqapi/api/group.ts b/src/ntqqapi/api/group.ts
index 8e49db5..8730a58 100644
--- a/src/ntqqapi/api/group.ts
+++ b/src/ntqqapi/api/group.ts
@@ -1,7 +1,7 @@
 import {ReceiveCmdS} from "../hook";
 import {Group, GroupMember, GroupMemberRole, GroupNotifies, GroupNotify, GroupRequestOperateTypes} from "../types";
 import {callNTQQApi, GeneralCallResult, NTQQApiClass, NTQQApiMethod} from "../ntcall";
-import {uidMaps} from "../../common/data";
+import {deleteGroup, uidMaps} from "../../common/data";
 import {dbUtil} from "../../common/db";
 import {log} from "../../common/utils/log";
 import {NTQQWindowApi, NTQQWindows} from "./window";
@@ -102,13 +102,17 @@ export class NTQQGroupApi{
         });
     }
     static async quitGroup(groupQQ: string) {
-        await callNTQQApi<GeneralCallResult>({
+        const result = await callNTQQApi<GeneralCallResult>({
             methodName: NTQQApiMethod.QUIT_GROUP,
             args: [
                 {"groupCode": groupQQ},
                 null
             ]
         })
+        if (result.result === 0){
+            deleteGroup(groupQQ);
+        }
+        return result;
     }
     static async kickMember(groupQQ: string, kickUids: string[], refuseForever: boolean = false, kickReason: string = '') {
         return await callNTQQApi<GeneralCallResult>(
diff --git a/src/ntqqapi/hook.ts b/src/ntqqapi/hook.ts
index 6cd3f2f..fe78af7 100644
--- a/src/ntqqapi/hook.ts
+++ b/src/ntqqapi/hook.ts
@@ -2,7 +2,16 @@ import {BrowserWindow} from 'electron';
 import {NTQQApiClass, NTQQApiMethod} from "./ntcall";
 import {NTQQMsgApi, sendMessagePool} from "./api/msg"
 import {ChatType, Group, GroupMember, GroupMemberRole, RawMessage, User} from "./types";
-import {friends, getFriend, getGroupMember, groups, selfInfo, tempGroupCodeMap, uidMaps} from "../common/data";
+import {
+  deleteGroup,
+  friends,
+  getFriend,
+  getGroupMember,
+  groups,
+  selfInfo,
+  tempGroupCodeMap,
+  uidMaps
+} from "../common/data";
 import {OB11GroupDecreaseEvent} from "../onebot11/event/notice/OB11GroupDecreaseEvent";
 import {v4 as uuidv4} from "uuid"
 import {postOB11Event} from "../onebot11/server/postOB11Event";
@@ -233,6 +242,11 @@ let activatedGroups: string[] = [];
 
 async function updateGroups(_groups: Group[], needUpdate: boolean = true) {
   for (let group of _groups) {
+    log("update group", group);
+    if (group.privilegeFlag === 0){
+      deleteGroup(group.groupCode);
+      continue;
+    }
     log("update group", group)
     // if (!activatedGroups.includes(group.groupCode)) {
     NTQQMsgApi.activateChat({peerUid: group.groupCode, chatType: ChatType.group}).then((r) => {
@@ -294,7 +308,9 @@ async function processGroupEvent(payload: { groupList: Group[] }) {
             }
           }
         }
-
+        if (group.privilegeFlag === 0){
+          deleteGroup(group.groupCode);
+        }
       }
     }
 
diff --git a/src/onebot11/action/group/GetGroupList.ts b/src/onebot11/action/group/GetGroupList.ts
index f48aff2..69cdbd3 100644
--- a/src/onebot11/action/group/GetGroupList.ts
+++ b/src/onebot11/action/group/GetGroupList.ts
@@ -6,17 +6,27 @@ import {ActionName} from "../types";
 import {NTQQGroupApi} from "../../../ntqqapi/api";
 import {log} from "../../../common/utils";
 
+interface Payload {
+  no_cache: boolean
+}
 
-class GetGroupList extends BaseAction<null, OB11Group[]> {
-    actionName = ActionName.GetGroupList
+class GetGroupList extends BaseAction<Payload, OB11Group[]> {
+  actionName = ActionName.GetGroupList
 
-    protected async _handle(payload: null) {
-        // if (groups.length === 0) {
-        //     const groups = await NTQQGroupApi.getGroups(true)
-        //     log("get groups", groups)
-        // }
+  protected async _handle(payload: Payload) {
+    if (groups.length === 0
+      // || payload.no_cache === true
+    ) {
+      try {
+        const groups = await NTQQGroupApi.getGroups(true)
+        // log("get groups", groups)
         return OB11Constructor.groups(groups);
+      } catch (e) {
+
+      }
     }
+    return OB11Constructor.groups(groups);
+  }
 }
 
 export default GetGroupList
\ No newline at end of file
diff --git a/src/onebot11/action/index.ts b/src/onebot11/action/index.ts
index 7702be9..3e760a7 100644
--- a/src/onebot11/action/index.ts
+++ b/src/onebot11/action/index.ts
@@ -91,6 +91,8 @@ function initActionMap() {
     const actionMap = new Map<string, BaseAction<any, any>>();
     for (const action of actionHandlers) {
         actionMap.set(action.actionName, action);
+        actionMap.set(action.actionName + '_async', action);
+        actionMap.set(action.actionName + '_rate_limited', action);
     }
 
     return actionMap
diff --git a/src/onebot11/action/msg/SendMsg.ts b/src/onebot11/action/msg/SendMsg.ts
index cb03f8a..5ab9fe1 100644
--- a/src/onebot11/action/msg/SendMsg.ts
+++ b/src/onebot11/action/msg/SendMsg.ts
@@ -74,15 +74,15 @@ export interface ReturnDataType {
 
 export function convertMessage2List(message: OB11MessageMixType, autoEscape = false) {
     if (typeof message === "string") {
-        if (!autoEscape) {
-            message = decodeCQCode(message.toString())
-        } else {
+        if (autoEscape === true) {
             message = [{
                 type: OB11MessageDataType.text,
                 data: {
                     text: message
                 }
             }]
+        } else {
+            message = decodeCQCode(message.toString())
         }
     } else if (!Array.isArray(message)) {
         message = [message]
@@ -329,7 +329,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
         } else {
             throw ("发送消息参数错误, 请指定group_id或user_id")
         }
-        const messages = convertMessage2List(payload.message, !!payload.auto_escape);
+        const messages = convertMessage2List(payload.message, payload.auto_escape);
         if (this.getSpecialMsgNum(payload, OB11MessageDataType.node)) {
             try {
                 const returnMsg = await this.handleForwardNode(peer, messages as OB11MessageNode[], group)
@@ -508,6 +508,9 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
         // nodeIds.push(nodeMsg.msgId)
         // await sleep(500);
         // 开发转发
+        if (nodeMsgIds.length === 0) {
+            throw Error("转发消息失败,节点为空")
+        }
         try {
             log("开发转发", nodeMsgIds)
             return await NTQQMsgApi.multiForwardMsg(srcPeer, destPeer, nodeMsgIds)
diff --git a/src/onebot11/constructor.ts b/src/onebot11/constructor.ts
index 4416003..89310a7 100644
--- a/src/onebot11/constructor.ts
+++ b/src/onebot11/constructor.ts
@@ -1,28 +1,28 @@
 import {
-    OB11Group,
-    OB11GroupMember,
-    OB11GroupMemberRole,
-    OB11Message,
-    OB11MessageData,
-    OB11MessageDataType,
-    OB11User,
-    OB11UserSex
+  OB11Group,
+  OB11GroupMember,
+  OB11GroupMemberRole,
+  OB11Message,
+  OB11MessageData,
+  OB11MessageDataType,
+  OB11User,
+  OB11UserSex
 } from "./types";
 import {
-    AtType,
-    ChatType, FaceIndex,
-    GrayTipElementSubType,
-    Group,
-    GroupMember,
-    IMAGE_HTTP_HOST, IMAGE_HTTP_HOST_NT,
-    RawMessage,
-    SelfInfo,
-    Sex,
-    TipGroupElementType,
-    User,
-    VideoElement
+  AtType,
+  ChatType, FaceIndex,
+  GrayTipElementSubType,
+  Group,
+  GroupMember,
+  IMAGE_HTTP_HOST, IMAGE_HTTP_HOST_NT,
+  RawMessage,
+  SelfInfo,
+  Sex,
+  TipGroupElementType,
+  User,
+  VideoElement
 } from '../ntqqapi/types';
-import {getFriend, getGroupMember, selfInfo, tempGroupCodeMap} from '../common/data';
+import {deleteGroup, getFriend, getGroupMember, groups, selfInfo, tempGroupCodeMap} from '../common/data';
 import {EventType} from "./event/OB11BaseEvent";
 import {encodeCQCode} from "./cqcode";
 import {dbUtil} from "../common/db";
@@ -39,447 +39,450 @@ import {getConfigUtil} from "../common/config";
 import {OB11GroupTitleEvent} from "./event/notice/OB11GroupTitleEvent";
 import {OB11GroupCardEvent} from "./event/notice/OB11GroupCardEvent";
 import {OB11GroupDecreaseEvent} from "./event/notice/OB11GroupDecreaseEvent";
+import {NTQQGroupApi} from "../ntqqapi/api";
 
 let lastRKeyUpdateTime = 0;
 
 export class OB11Constructor {
-    static async message(msg: RawMessage): Promise<OB11Message> {
-        let config = getConfigUtil().getConfig();
-        const {enableLocalFile2Url, ob11: {messagePostFormat}} = config;
-        const message_type = msg.chatType == ChatType.group ? "group" : "private";
-        const resMsg: OB11Message = {
-            self_id: parseInt(selfInfo.uin),
-            user_id: parseInt(msg.senderUin),
-            time: parseInt(msg.msgTime) || Date.now(),
-            message_id: msg.msgShortId,
-            real_id: msg.msgShortId,
-            message_type: msg.chatType == ChatType.group ? "group" : "private",
-            sender: {
-                user_id: parseInt(msg.senderUin),
-                nickname: msg.sendNickName,
-                card: msg.sendMemberName || "",
-            },
-            raw_message: "",
-            font: 14,
-            sub_type: "friend",
-            message: messagePostFormat === 'string' ? '' : [],
-            message_format: messagePostFormat === 'string' ? 'string' : 'array',
-            post_type: selfInfo.uin == msg.senderUin ? EventType.MESSAGE_SENT : EventType.MESSAGE,
-        }
-        if (msg.chatType == ChatType.group) {
-            resMsg.sub_type = "normal" // 这里go-cqhttp是group,而onebot11标准是normal, 蛋疼
-            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
+  static async message(msg: RawMessage): Promise<OB11Message> {
+    let config = getConfigUtil().getConfig();
+    const {enableLocalFile2Url, ob11: {messagePostFormat}} = config;
+    const message_type = msg.chatType == ChatType.group ? "group" : "private";
+    const resMsg: OB11Message = {
+      self_id: parseInt(selfInfo.uin),
+      user_id: parseInt(msg.senderUin),
+      time: parseInt(msg.msgTime) || Date.now(),
+      message_id: msg.msgShortId,
+      real_id: msg.msgShortId,
+      message_type: msg.chatType == ChatType.group ? "group" : "private",
+      sender: {
+        user_id: parseInt(msg.senderUin),
+        nickname: msg.sendNickName,
+        card: msg.sendMemberName || "",
+      },
+      raw_message: "",
+      font: 14,
+      sub_type: "friend",
+      message: messagePostFormat === 'string' ? '' : [],
+      message_format: messagePostFormat === 'string' ? 'string' : 'array',
+      post_type: selfInfo.uin == msg.senderUin ? EventType.MESSAGE_SENT : EventType.MESSAGE,
+    }
+    if (msg.chatType == ChatType.group) {
+      resMsg.sub_type = "normal" // 这里go-cqhttp是group,而onebot11标准是normal, 蛋疼
+      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"
+      const friend = await getFriend(msg.senderUin);
+      if (friend) {
+        resMsg.sender.nickname = friend.nick;
+      }
+    } else if (msg.chatType == ChatType.temp) {
+      resMsg.sub_type = "group"
+      const tempGroupCode = tempGroupCodeMap[msg.peerUin]
+      if (tempGroupCode) {
+        resMsg.group_id = parseInt(tempGroupCode)
+      }
+    }
+
+    for (let element of msg.elements) {
+      let message_data: OB11MessageData | any = {
+        data: {},
+        type: "unknown"
+      }
+      if (element.textElement && element.textElement?.atType !== AtType.notAt) {
+        message_data["type"] = OB11MessageDataType.at
+        if (element.textElement.atType == AtType.atAll) {
+          // message_data["data"]["mention"] = "all"
+          message_data["data"]["qq"] = "all"
+        } else {
+          let atUid = element.textElement.atNtUid
+          let atQQ = element.textElement.atUid
+          if (!atQQ || atQQ === "0") {
+            const atMember = await getGroupMember(msg.peerUin, atUid)
+            if (atMember) {
+              atQQ = atMember.uin
             }
-        } else if (msg.chatType == ChatType.friend) {
-            resMsg.sub_type = "friend"
-            const friend = await getFriend(msg.senderUin);
-            if (friend) {
-                resMsg.sender.nickname = friend.nick;
+          }
+          if (atQQ) {
+            // message_data["data"]["mention"] = atQQ
+            message_data["data"]["qq"] = atQQ
+          }
+        }
+      } else if (element.textElement) {
+        message_data["type"] = "text"
+        let text = element.textElement.content
+        if (!text.trim()) {
+          continue;
+        }
+        message_data["data"]["text"] = text
+      } else if (element.replyElement) {
+        message_data["type"] = "reply"
+        // log("收到回复消息", element.replyElement.replayMsgSeq)
+        try {
+          const replyMsg = await dbUtil.getMsgBySeqId(element.replyElement.replayMsgSeq)
+          // log("找到回复消息", replyMsg.msgShortId, replyMsg.msgId)
+          if (replyMsg) {
+            message_data["data"]["id"] = replyMsg.msgShortId.toString()
+          } else {
+            continue
+          }
+        } catch (e) {
+          log("获取不到引用的消息", e.stack, element.replyElement.replayMsgSeq)
+        }
+
+      } else if (element.picElement) {
+        message_data["type"] = "image"
+        // message_data["data"]["file"] = element.picElement.sourcePath
+        message_data["data"]["file"] = element.picElement.fileName
+        // message_data["data"]["path"] = element.picElement.sourcePath
+        const url = element.picElement.originImageUrl
+        const fileMd5 = element.picElement.md5HexStr
+        const fileUuid = element.picElement.fileUuid
+        // let currentRKey = config.imageRKey || "CAQSKAB6JWENi5LMk0kc62l8Pm3Jn1dsLZHyRLAnNmHGoZ3y_gDZPqZt-64"
+        let currentRKey = "CAQSKAB6JWENi5LMk0kc62l8Pm3Jn1dsLZHyRLAnNmHGoZ3y_gDZPqZt-64"
+        if (url) {
+          if (url.startsWith("/download")) {
+            if (url.includes("&rkey=")) {
+              // 正则提取rkey
+              // const rkey = url.match(/&rkey=([^&]+)/)[1]
+              // // log("图片url已有rkey", rkey)
+              // if (rkey != currentRKey){
+              //     config.imageRKey = rkey
+              //     if (Date.now() - lastRKeyUpdateTime > 1000 * 60) {
+              //         lastRKeyUpdateTime = Date.now()
+              //         getConfigUtil().setConfig(config)
+              //     }
+              // }
+              message_data["data"]["url"] = IMAGE_HTTP_HOST + url
+            } else {
+              // 有可能会碰到appid为1406的,这个不能使用新的NT域名,并且需要把appid改为1407才可访问
+              message_data["data"]["url"] = `${IMAGE_HTTP_HOST}/download?appid=1407&fileid=${fileUuid}&rkey=${currentRKey}&spec=0`
             }
-        } else if (msg.chatType == ChatType.temp) {
-            resMsg.sub_type = "group"
-            const tempGroupCode = tempGroupCodeMap[msg.peerUin]
-            if (tempGroupCode) {
-                resMsg.group_id = parseInt(tempGroupCode)
+          } else {
+            message_data["data"]["url"] = IMAGE_HTTP_HOST + url
+          }
+        } else if (fileMd5) {
+          message_data["data"]["url"] = `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${fileMd5.toUpperCase()}/0`
+        }
+        // message_data["data"]["file_id"] = element.picElement.fileUuid
+        message_data["data"]["file_size"] = element.picElement.fileSize
+        dbUtil.addFileCache(element.picElement.fileName, {
+          fileName: element.picElement.fileName,
+          filePath: element.picElement.sourcePath,
+          fileSize: element.picElement.fileSize.toString(),
+          url: message_data["data"]["url"],
+          downloadFunc: async () => {
+            await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid,
+              element.elementId, element.picElement.thumbPath?.get(0) || "", element.picElement.sourcePath)
+          }
+        }).then()
+        // 不在自动下载图片
+
+      } else if (element.videoElement || element.fileElement) {
+        const videoOrFileElement = element.videoElement || element.fileElement
+        const ob11MessageDataType = element.videoElement ? OB11MessageDataType.video : OB11MessageDataType.file
+        message_data["type"] = ob11MessageDataType;
+        message_data["data"]["file"] = videoOrFileElement.fileName
+        message_data["data"]["path"] = videoOrFileElement.filePath
+        message_data["data"]["file_id"] = videoOrFileElement.fileUuid
+        message_data["data"]["file_size"] = videoOrFileElement.fileSize
+        dbUtil.addFileCache(videoOrFileElement.fileUuid, {
+          msgId: msg.msgId,
+          fileName: videoOrFileElement.fileName,
+          filePath: videoOrFileElement.filePath,
+          fileSize: videoOrFileElement.fileSize,
+          downloadFunc: async () => {
+            await NTQQFileApi.downloadMedia(
+              msg.msgId, msg.chatType, msg.peerUid,
+              element.elementId,
+              ob11MessageDataType == OB11MessageDataType.video ? (videoOrFileElement as VideoElement).thumbPath.get(0) : null,
+              videoOrFileElement.filePath)
+          }
+        }).then()
+        // 怎么拿到url呢
+      } else if (element.pttElement) {
+        message_data["type"] = OB11MessageDataType.voice;
+        message_data["data"]["file"] = element.pttElement.fileName
+        message_data["data"]["path"] = element.pttElement.filePath
+        // message_data["data"]["file_id"] = element.pttElement.fileUuid
+        message_data["data"]["file_size"] = element.pttElement.fileSize
+        dbUtil.addFileCache(element.pttElement.fileName, {
+          fileName: element.pttElement.fileName,
+          filePath: element.pttElement.filePath,
+          fileSize: element.pttElement.fileSize,
+        }).then()
+
+        // log("收到语音消息", msg)
+        // window.LLAPI.Ptt2Text(message.raw.msgId, message.peer, messages).then(text => {
+        //     console.log("语音转文字结果", text);
+        // }).catch(err => {
+        //     console.log("语音转文字失败", err);
+        // })
+      } else if (element.arkElement) {
+        message_data["type"] = OB11MessageDataType.json;
+        message_data["data"]["data"] = element.arkElement.bytesData;
+      } else if (element.faceElement) {
+        const faceId = element.faceElement.faceIndex;
+        if (faceId === FaceIndex.dice) {
+          message_data["type"] = OB11MessageDataType.dice
+          message_data["data"]["result"] = element.faceElement.resultId;
+        } else if (faceId === FaceIndex.RPS) {
+          message_data["type"] = OB11MessageDataType.RPS
+          message_data["data"]["result"] = element.faceElement.resultId;
+        } else {
+          message_data["type"] = OB11MessageDataType.face;
+          message_data["data"]["id"] = element.faceElement.faceIndex.toString();
+        }
+      } else if (element.marketFaceElement) {
+        message_data["type"] = OB11MessageDataType.mface;
+        message_data["data"]["text"] = element.marketFaceElement.faceName;
+      } else if (element.markdownElement) {
+        message_data["type"] = OB11MessageDataType.markdown;
+        message_data["data"]["data"] = element.markdownElement.content;
+      } else if (element.multiForwardMsgElement) {
+        message_data["type"] = OB11MessageDataType.forward;
+        message_data["data"]["id"] = msg.msgId
+      }
+      if (message_data.type !== "unknown" && message_data.data) {
+        const cqCode = encodeCQCode(message_data);
+        if (messagePostFormat === 'string') {
+          (resMsg.message as string) += cqCode;
+        } else (resMsg.message as OB11MessageData[]).push(message_data);
+
+        resMsg.raw_message += cqCode;
+      }
+    }
+    resMsg.raw_message = resMsg.raw_message.trim();
+    return resMsg;
+  }
+
+  static async GroupEvent(msg: RawMessage): Promise<OB11GroupNoticeEvent> {
+    if (msg.chatType !== ChatType.group) {
+      return;
+    }
+    if (msg.senderUin) {
+      let member = await getGroupMember(msg.peerUid, msg.senderUin);
+      if (member && member.cardName !== msg.sendMemberName) {
+        const event = new OB11GroupCardEvent(parseInt(msg.peerUid), parseInt(msg.senderUin), msg.sendMemberName, member.cardName)
+        member.cardName = msg.sendMemberName;
+        return event
+      }
+    }
+    // log("group msg", msg);
+    for (let element of msg.elements) {
+      const grayTipElement = element.grayTipElement
+      const groupElement = grayTipElement?.groupElement
+      if (groupElement) {
+        // log("收到群提示消息", groupElement)
+        if (groupElement.type == TipGroupElementType.memberIncrease) {
+          log("收到群成员增加消息", groupElement)
+          await sleep(1000);
+          const member = await getGroupMember(msg.peerUid, groupElement.memberUid);
+          let memberUin = member?.uin;
+          if (!memberUin) {
+            memberUin = (await NTQQUserApi.getUserDetailInfo(groupElement.memberUid)).uin
+          }
+          // log("获取新群成员QQ", memberUin)
+          const adminMember = await getGroupMember(msg.peerUid, groupElement.adminUid);
+          // log("获取同意新成员入群的管理员", adminMember)
+          if (memberUin) {
+            const operatorUin = adminMember?.uin || memberUin
+            let event = new OB11GroupIncreaseEvent(parseInt(msg.peerUid), parseInt(memberUin), parseInt(operatorUin));
+            // log("构造群增加事件", event)
+            return event;
+          }
+        } else if (groupElement.type === TipGroupElementType.ban) {
+          log("收到群群员禁言提示", groupElement)
+          const memberUid = groupElement.shutUp.member.uid
+          const adminUid = groupElement.shutUp.admin.uid
+          let memberUin: string = ""
+          let duration = parseInt(groupElement.shutUp.duration)
+          let sub_type: "ban" | "lift_ban" = duration > 0 ? "ban" : "lift_ban"
+          if (memberUid) {
+            memberUin = (await getGroupMember(msg.peerUid, memberUid))?.uin || (await NTQQUserApi.getUserDetailInfo(memberUid))?.uin
+          } else {
+            memberUin = "0";  // 0表示全员禁言
+            if (duration > 0) {
+              duration = -1
             }
-        }
-
-        for (let element of msg.elements) {
-            let message_data: OB11MessageData | any = {
-                data: {},
-                type: "unknown"
+          }
+          const adminUin = (await getGroupMember(msg.peerUid, adminUid))?.uin || (await NTQQUserApi.getUserDetailInfo(adminUid))?.uin
+          if (memberUin && adminUin) {
+            return new OB11GroupBanEvent(parseInt(msg.peerUid), parseInt(memberUin), parseInt(adminUin), duration, sub_type);
+          }
+        } else if (groupElement.type == TipGroupElementType.kicked) {
+          log(`收到我被踢出或退群提示, 群${msg.peerUid}`, groupElement)
+          deleteGroup(msg.peerUid);
+          NTQQGroupApi.quitGroup(msg.peerUid).then()
+          try {
+            const adminUin = (await getGroupMember(msg.peerUid, groupElement.adminUid))?.uin || (await NTQQUserApi.getUserDetailInfo(groupElement.adminUid))?.uin
+            if (adminUin) {
+              return new OB11GroupDecreaseEvent(parseInt(msg.peerUid), parseInt(selfInfo.uin), parseInt(adminUin), "kick_me");
             }
-            if (element.textElement && element.textElement?.atType !== AtType.notAt) {
-                message_data["type"] = OB11MessageDataType.at
-                if (element.textElement.atType == AtType.atAll) {
-                    // message_data["data"]["mention"] = "all"
-                    message_data["data"]["qq"] = "all"
-                } else {
-                    let atUid = element.textElement.atNtUid
-                    let atQQ = element.textElement.atUid
-                    if (!atQQ || atQQ === "0") {
-                        const atMember = await getGroupMember(msg.peerUin, atUid)
-                        if (atMember) {
-                            atQQ = atMember.uin
-                        }
-                    }
-                    if (atQQ) {
-                        // message_data["data"]["mention"] = atQQ
-                        message_data["data"]["qq"] = atQQ
-                    }
-                }
-            } else if (element.textElement) {
-                message_data["type"] = "text"
-                let text = element.textElement.content
-                if (!text.trim()) {
-                    continue;
-                }
-                message_data["data"]["text"] = text
-            } else if (element.replyElement) {
-                message_data["type"] = "reply"
-                // log("收到回复消息", element.replyElement.replayMsgSeq)
-                try {
-                    const replyMsg = await dbUtil.getMsgBySeqId(element.replyElement.replayMsgSeq)
-                    // log("找到回复消息", replyMsg.msgShortId, replyMsg.msgId)
-                    if (replyMsg) {
-                        message_data["data"]["id"] = replyMsg.msgShortId.toString()
-                    } else {
-                        continue
-                    }
-                } catch (e) {
-                    log("获取不到引用的消息", e.stack, element.replyElement.replayMsgSeq)
-                }
+          } catch (e) {
+            return new OB11GroupDecreaseEvent(parseInt(msg.peerUid), parseInt(selfInfo.uin), 0, "leave");
+          }
+        }
+      } else if (element.fileElement) {
+        return new OB11GroupUploadNoticeEvent(parseInt(msg.peerUid), parseInt(msg.senderUin), {
+          id: element.fileElement.fileUuid,
+          name: element.fileElement.fileName,
+          size: parseInt(element.fileElement.fileSize),
+          busid: element.fileElement.fileBizId || 0
+        })
+      }
 
-            } else if (element.picElement) {
-                message_data["type"] = "image"
-                // message_data["data"]["file"] = element.picElement.sourcePath
-                message_data["data"]["file"] = element.picElement.fileName
-                // message_data["data"]["path"] = element.picElement.sourcePath
-                const url = element.picElement.originImageUrl
-                const fileMd5 = element.picElement.md5HexStr
-                const fileUuid = element.picElement.fileUuid
-                // let currentRKey = config.imageRKey || "CAQSKAB6JWENi5LMk0kc62l8Pm3Jn1dsLZHyRLAnNmHGoZ3y_gDZPqZt-64"
-                let currentRKey = "CAQSKAB6JWENi5LMk0kc62l8Pm3Jn1dsLZHyRLAnNmHGoZ3y_gDZPqZt-64"
-                if (url) {
-                    if (url.startsWith("/download")) {
-                        if (url.includes("&rkey=")) {
-                            // 正则提取rkey
-                            // const rkey = url.match(/&rkey=([^&]+)/)[1]
-                            // // log("图片url已有rkey", rkey)
-                            // if (rkey != currentRKey){
-                            //     config.imageRKey = rkey
-                            //     if (Date.now() - lastRKeyUpdateTime > 1000 * 60) {
-                            //         lastRKeyUpdateTime = Date.now()
-                            //         getConfigUtil().setConfig(config)
-                            //     }
-                            // }
-                            message_data["data"]["url"] = IMAGE_HTTP_HOST + url
-                        }
-                        else{
-                            // 有可能会碰到appid为1406的,这个不能使用新的NT域名,并且需要把appid改为1407才可访问
-                            message_data["data"]["url"] = `${IMAGE_HTTP_HOST}/download?appid=1407&fileid=${fileUuid}&rkey=${currentRKey}&spec=0`
-                        }
-                    } else {
-                        message_data["data"]["url"] = IMAGE_HTTP_HOST + url
-                    }
-                } else if (fileMd5) {
-                    message_data["data"]["url"] = `${IMAGE_HTTP_HOST}/gchatpic_new/0/0-0-${fileMd5.toUpperCase()}/0`
-                }
-                // message_data["data"]["file_id"] = element.picElement.fileUuid
-                message_data["data"]["file_size"] = element.picElement.fileSize
-                dbUtil.addFileCache(element.picElement.fileName, {
-                    fileName: element.picElement.fileName,
-                    filePath: element.picElement.sourcePath,
-                    fileSize: element.picElement.fileSize.toString(),
-                    url: message_data["data"]["url"],
-                    downloadFunc: async () => {
-                        await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid,
-                            element.elementId, element.picElement.thumbPath?.get(0) || "", element.picElement.sourcePath)
-                    }
-                }).then()
-                // 不在自动下载图片
+      if (grayTipElement) {
+        if (grayTipElement.subElementType == GrayTipElementSubType.INVITE_NEW_MEMBER) {
+          log("收到新人被邀请进群消息", grayTipElement)
+          const xmlElement = grayTipElement.xmlElement
+          if (xmlElement?.content) {
+            const regex = /jp="(\d+)"/g;
 
-            } else if (element.videoElement || element.fileElement) {
-                const videoOrFileElement = element.videoElement || element.fileElement
-                const ob11MessageDataType = element.videoElement ? OB11MessageDataType.video : OB11MessageDataType.file
-                message_data["type"] = ob11MessageDataType;
-                message_data["data"]["file"] = videoOrFileElement.fileName
-                message_data["data"]["path"] = videoOrFileElement.filePath
-                message_data["data"]["file_id"] = videoOrFileElement.fileUuid
-                message_data["data"]["file_size"] = videoOrFileElement.fileSize
-                dbUtil.addFileCache(videoOrFileElement.fileUuid, {
-                    msgId: msg.msgId,
-                    fileName: videoOrFileElement.fileName,
-                    filePath: videoOrFileElement.filePath,
-                    fileSize: videoOrFileElement.fileSize,
-                    downloadFunc: async () => {
-                        await NTQQFileApi.downloadMedia(
-                            msg.msgId, msg.chatType, msg.peerUid,
-                            element.elementId,
-                            ob11MessageDataType == OB11MessageDataType.video ? (videoOrFileElement as VideoElement).thumbPath.get(0) : null,
-                            videoOrFileElement.filePath)
-                    }
-                }).then()
-                // 怎么拿到url呢
-            } else if (element.pttElement) {
-                message_data["type"] = OB11MessageDataType.voice;
-                message_data["data"]["file"] = element.pttElement.fileName
-                message_data["data"]["path"] = element.pttElement.filePath
-                // message_data["data"]["file_id"] = element.pttElement.fileUuid
-                message_data["data"]["file_size"] = element.pttElement.fileSize
-                dbUtil.addFileCache(element.pttElement.fileName, {
-                    fileName: element.pttElement.fileName,
-                    filePath: element.pttElement.filePath,
-                    fileSize: element.pttElement.fileSize,
-                }).then()
+            let matches = [];
+            let match = null
 
-                // log("收到语音消息", msg)
-                // window.LLAPI.Ptt2Text(message.raw.msgId, message.peer, messages).then(text => {
-                //     console.log("语音转文字结果", text);
-                // }).catch(err => {
-                //     console.log("语音转文字失败", err);
-                // })
-            } else if (element.arkElement) {
-                message_data["type"] = OB11MessageDataType.json;
-                message_data["data"]["data"] = element.arkElement.bytesData;
-            } else if (element.faceElement) {
-                const faceId = element.faceElement.faceIndex;
-                if (faceId === FaceIndex.dice){
-                    message_data["type"] = OB11MessageDataType.dice
-                    message_data["data"]["result"] = element.faceElement.resultId;
-                }
-                else if (faceId === FaceIndex.RPS){
-                    message_data["type"] = OB11MessageDataType.RPS
-                    message_data["data"]["result"] = element.faceElement.resultId;
-                }
-                else{
-                    message_data["type"] = OB11MessageDataType.face;
-                    message_data["data"]["id"] = element.faceElement.faceIndex.toString();
-                }
-            } else if (element.marketFaceElement) {
-                message_data["type"] = OB11MessageDataType.mface;
-                message_data["data"]["text"] = element.marketFaceElement.faceName;
-            } else if (element.markdownElement){
-                message_data["type"] = OB11MessageDataType.markdown;
-                message_data["data"]["data"] = element.markdownElement.content;
-            } else if (element.multiForwardMsgElement){
-                message_data["type"] = OB11MessageDataType.forward;
-                message_data["data"]["id"] = msg.msgId
+            while ((match = regex.exec(xmlElement.content)) !== null) {
+              matches.push(match[1]);
             }
-            if (message_data.type !== "unknown" && message_data.data) {
-                const cqCode = encodeCQCode(message_data);
-                if (messagePostFormat === 'string') {
-                    (resMsg.message as string) += cqCode;
-                } else (resMsg.message as OB11MessageData[]).push(message_data);
-
-                resMsg.raw_message += cqCode;
+            // log("新人进群匹配到的QQ号", matches)
+            if (matches.length === 2) {
+              const [inviter, invitee] = matches;
+              return new OB11GroupIncreaseEvent(parseInt(msg.peerUid), parseInt(invitee), parseInt(inviter), "invite");
             }
+          }
+        } else if (grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE) {
+          const json = JSON.parse(grayTipElement.jsonGrayTipElement.jsonStr)
+          /*
+          {
+            align: 'center',
+            items: [
+              { txt: '恭喜', type: 'nor' },
+              {
+                col: '3',
+                jp: '5',
+                param: ["QQ号"],
+                txt: '林雨辰',
+                type: 'url'
+              },
+              { txt: '获得群主授予的', type: 'nor' },
+              {
+                col: '3',
+                jp: '',
+                txt: '好好好',
+                type: 'url'
+              },
+              { txt: '头衔', type: 'nor' }
+            ]
+          }
+
+          * */
+          const memberUin = json.items[1].param[0]
+          const title = json.items[3].txt
+          log("收到群成员新头衔消息", json)
+          getGroupMember(msg.peerUid, memberUin).then(member => {
+            member.memberSpecialTitle = title
+          })
+          return new OB11GroupTitleEvent(parseInt(msg.peerUid), parseInt(memberUin), title)
         }
-        resMsg.raw_message = resMsg.raw_message.trim();
-        return resMsg;
+      }
     }
+  }
 
-    static async GroupEvent(msg: RawMessage): Promise<OB11GroupNoticeEvent> {
-        if (msg.chatType !== ChatType.group) {
-            return;
-        }
-        if (msg.senderUin){
-            let member = await getGroupMember(msg.peerUid, msg.senderUin);
-            if (member && member.cardName !== msg.sendMemberName) {
-                const event = new OB11GroupCardEvent(parseInt(msg.peerUid), parseInt(msg.senderUin), msg.sendMemberName, member.cardName)
-                member.cardName = msg.sendMemberName;
-                return event
-            }
-        }
-        // log("group msg", msg);
-        for (let element of msg.elements) {
-            const grayTipElement = element.grayTipElement
-            const groupElement = grayTipElement?.groupElement
-            if (groupElement) {
-                // log("收到群提示消息", groupElement)
-                if (groupElement.type == TipGroupElementType.memberIncrease) {
-                    log("收到群成员增加消息", groupElement)
-                    await sleep(1000);
-                    const member = await getGroupMember(msg.peerUid, groupElement.memberUid);
-                    let memberUin = member?.uin;
-                    if (!memberUin) {
-                        memberUin = (await NTQQUserApi.getUserDetailInfo(groupElement.memberUid)).uin
-                    }
-                    // log("获取新群成员QQ", memberUin)
-                    const adminMember = await getGroupMember(msg.peerUid, groupElement.adminUid);
-                    // log("获取同意新成员入群的管理员", adminMember)
-                    if (memberUin) {
-                        const operatorUin = adminMember?.uin || memberUin
-                        let event = new OB11GroupIncreaseEvent(parseInt(msg.peerUid), parseInt(memberUin), parseInt(operatorUin));
-                        // log("构造群增加事件", event)
-                        return event;
-                    }
-                } else if (groupElement.type === TipGroupElementType.ban) {
-                    log("收到群群员禁言提示", groupElement)
-                    const memberUid = groupElement.shutUp.member.uid
-                    const adminUid = groupElement.shutUp.admin.uid
-                    let memberUin: string = ""
-                    let duration = parseInt(groupElement.shutUp.duration)
-                    let sub_type: "ban" | "lift_ban" = duration > 0 ? "ban" : "lift_ban"
-                    if (memberUid) {
-                        memberUin = (await getGroupMember(msg.peerUid, memberUid))?.uin || (await NTQQUserApi.getUserDetailInfo(memberUid))?.uin
-                    } else {
-                        memberUin = "0";  // 0表示全员禁言
-                        if (duration > 0) {
-                            duration = -1
-                        }
-                    }
-                    const adminUin = (await getGroupMember(msg.peerUid, adminUid))?.uin || (await NTQQUserApi.getUserDetailInfo(adminUid))?.uin
-                    if (memberUin && adminUin) {
-                        return new OB11GroupBanEvent(parseInt(msg.peerUid), parseInt(memberUin), parseInt(adminUin), duration, sub_type);
-                    }
-                }
-                else if (groupElement.type == TipGroupElementType.kicked){
-                    log("收到我被踢出提示", groupElement)
-                    const adminUin = (await getGroupMember(msg.peerUid, groupElement.adminUid))?.uin || (await NTQQUserApi.getUserDetailInfo(groupElement.adminUid))?.uin
-                    if (adminUin) {
-                        return new OB11GroupDecreaseEvent(parseInt(msg.peerUid), parseInt(selfInfo.uin), parseInt(adminUin), "kick_me");
-                    }
-                }
-            } else if (element.fileElement) {
-                return new OB11GroupUploadNoticeEvent(parseInt(msg.peerUid), parseInt(msg.senderUin), {
-                    id: element.fileElement.fileUuid,
-                    name: element.fileElement.fileName,
-                    size: parseInt(element.fileElement.fileSize),
-                    busid: element.fileElement.fileBizId || 0
-                })
-            }
-
-            if (grayTipElement) {
-                if (grayTipElement.subElementType == GrayTipElementSubType.INVITE_NEW_MEMBER) {
-                    log("收到新人被邀请进群消息", grayTipElement)
-                    const xmlElement = grayTipElement.xmlElement
-                    if (xmlElement?.content) {
-                        const regex = /jp="(\d+)"/g;
-
-                        let matches = [];
-                        let match = null
-
-                        while ((match = regex.exec(xmlElement.content)) !== null) {
-                            matches.push(match[1]);
-                        }
-                        // log("新人进群匹配到的QQ号", matches)
-                        if (matches.length === 2) {
-                            const [inviter, invitee] = matches;
-                            return new OB11GroupIncreaseEvent(parseInt(msg.peerUid), parseInt(invitee), parseInt(inviter), "invite");
-                        }
-                    }
-                } else if (grayTipElement.subElementType == GrayTipElementSubType.MEMBER_NEW_TITLE) {
-                    const json = JSON.parse(grayTipElement.jsonGrayTipElement.jsonStr)
-                    /*
-                    {
-                      align: 'center',
-                      items: [
-                        { txt: '恭喜', type: 'nor' },
-                        {
-                          col: '3',
-                          jp: '5',
-                          param: ["QQ号"],
-                          txt: '林雨辰',
-                          type: 'url'
-                        },
-                        { txt: '获得群主授予的', type: 'nor' },
-                        {
-                          col: '3',
-                          jp: '',
-                          txt: '好好好',
-                          type: 'url'
-                        },
-                        { txt: '头衔', type: 'nor' }
-                      ]
-                    }
-
-                    * */
-                    const memberUin = json.items[1].param[0]
-                    const title = json.items[3].txt
-                    log("收到群成员新头衔消息", json)
-                    getGroupMember(msg.peerUid, memberUin).then(member => {
-                        member.memberSpecialTitle = title
-                    })
-                    return new OB11GroupTitleEvent(parseInt(msg.peerUid), parseInt(memberUin), title)
-                }
-            }
-        }
+  static friend(friend: User): OB11User {
+    return {
+      user_id: parseInt(friend.uin),
+      nickname: friend.nick,
+      remark: friend.remark,
+      sex: OB11Constructor.sex(friend.sex),
+      level: friend.qqLevel && calcQQLevel(friend.qqLevel) || 0
     }
+  }
 
-    static friend(friend: User): OB11User {
-        return {
-            user_id: parseInt(friend.uin),
-            nickname: friend.nick,
-            remark: friend.remark,
-            sex: OB11Constructor.sex(friend.sex),
-            level: friend.qqLevel && calcQQLevel(friend.qqLevel) || 0
-        }
+  static selfInfo(selfInfo: SelfInfo): OB11User {
+    return {
+      user_id: parseInt(selfInfo.uin),
+      nickname: selfInfo.nick,
     }
+  }
 
-    static selfInfo(selfInfo: SelfInfo): OB11User {
-        return {
-            user_id: parseInt(selfInfo.uin),
-            nickname: selfInfo.nick,
-        }
-    }
+  static friends(friends: User[]): OB11User[] {
+    return friends.map(OB11Constructor.friend)
+  }
 
-    static friends(friends: User[]): OB11User[] {
-        return friends.map(OB11Constructor.friend)
-    }
+  static groupMemberRole(role: number): OB11GroupMemberRole | undefined {
+    return {
+      4: OB11GroupMemberRole.owner,
+      3: OB11GroupMemberRole.admin,
+      2: OB11GroupMemberRole.member
+    }[role]
+  }
 
-    static groupMemberRole(role: number): OB11GroupMemberRole | undefined {
-        return {
-            4: OB11GroupMemberRole.owner,
-            3: OB11GroupMemberRole.admin,
-            2: OB11GroupMemberRole.member
-        }[role]
+  static sex(sex: Sex): OB11UserSex {
+    const sexMap = {
+      [Sex.male]: OB11UserSex.male,
+      [Sex.female]: OB11UserSex.female,
+      [Sex.unknown]: OB11UserSex.unknown
     }
+    return sexMap[sex] || OB11UserSex.unknown
+  }
 
-    static sex(sex: Sex): OB11UserSex {
-        const sexMap = {
-            [Sex.male]: OB11UserSex.male,
-            [Sex.female]: OB11UserSex.female,
-            [Sex.unknown]: OB11UserSex.unknown
-        }
-        return sexMap[sex] || OB11UserSex.unknown
+  static groupMember(group_id: string, member: GroupMember): OB11GroupMember {
+    return {
+      group_id: parseInt(group_id),
+      user_id: parseInt(member.uin),
+      nickname: member.nick,
+      card: member.cardName,
+      sex: OB11Constructor.sex(member.sex),
+      age: 0,
+      area: "",
+      level: 0,
+      qq_level: member.qqLevel && calcQQLevel(member.qqLevel) || 0,
+      join_time: 0,  // 暂时没法获取
+      last_sent_time: 0,  // 暂时没法获取
+      title_expire_time: 0,
+      unfriendly: false,
+      card_changeable: true,
+      is_robot: member.isRobot,
+      shut_up_timestamp: member.shutUpTime,
+      role: OB11Constructor.groupMemberRole(member.role),
+      title: member.memberSpecialTitle || "",
     }
+  }
 
-    static groupMember(group_id: string, member: GroupMember): OB11GroupMember {
-        return {
-            group_id: parseInt(group_id),
-            user_id: parseInt(member.uin),
-            nickname: member.nick,
-            card: member.cardName,
-            sex: OB11Constructor.sex(member.sex),
-            age: 0,
-            area: "",
-            level: 0,
-            qq_level: member.qqLevel && calcQQLevel(member.qqLevel) || 0,
-            join_time: 0,  // 暂时没法获取
-            last_sent_time: 0,  // 暂时没法获取
-            title_expire_time: 0,
-            unfriendly: false,
-            card_changeable: true,
-            is_robot: member.isRobot,
-            shut_up_timestamp: member.shutUpTime,
-            role: OB11Constructor.groupMemberRole(member.role),
-            title: member.memberSpecialTitle || "",
-        }
+  static stranger(user: User): OB11User {
+    return {
+      ...user,
+      user_id: parseInt(user.uin),
+      nickname: user.nick,
+      sex: OB11Constructor.sex(user.sex),
+      age: 0,
+      qid: user.qid,
+      login_days: 0,
+      level: user.qqLevel && calcQQLevel(user.qqLevel) || 0,
     }
+  }
 
-    static stranger(user: User): OB11User {
-        return {
-            ...user,
-            user_id: parseInt(user.uin),
-            nickname: user.nick,
-            sex: OB11Constructor.sex(user.sex),
-            age: 0,
-            qid: user.qid,
-            login_days: 0,
-            level: user.qqLevel && calcQQLevel(user.qqLevel) || 0,
-        }
-    }
+  static groupMembers(group: Group): OB11GroupMember[] {
+    log("construct ob11 group members", group)
+    return group.members.map(m => OB11Constructor.groupMember(group.groupCode, m))
+  }
 
-    static groupMembers(group: Group): OB11GroupMember[] {
-        log("construct ob11 group members", group)
-        return group.members.map(m => OB11Constructor.groupMember(group.groupCode, m))
+  static group(group: Group): OB11Group {
+    return {
+      group_id: parseInt(group.groupCode),
+      group_name: group.groupName,
+      member_count: group.memberCount,
+      max_member_count: group.maxMember
     }
+  }
 
-    static group(group: Group): OB11Group {
-        return {
-            group_id: parseInt(group.groupCode),
-            group_name: group.groupName,
-            member_count: group.memberCount,
-            max_member_count: group.maxMember
-        }
-    }
-
-    static groups(groups: Group[]): OB11Group[] {
-        return groups.map(OB11Constructor.group)
-    }
+  static groups(groups: Group[]): OB11Group[] {
+    return groups.map(OB11Constructor.group)
+  }
 }
diff --git a/src/onebot11/server/http.ts b/src/onebot11/server/http.ts
index ac7a95b..e614f66 100644
--- a/src/onebot11/server/http.ts
+++ b/src/onebot11/server/http.ts
@@ -1,8 +1,11 @@
 import {Response} from "express";
 import {OB11Response} from "../action/OB11Response";
 import {HttpServerBase} from "../../common/server/http";
-import {actionHandlers} from "../action";
+import {actionHandlers, actionMap} from "../action";
 import {getConfigUtil} from "../../common/config";
+import {postOB11Event} from "./postOB11Event";
+import {OB11HeartbeatEvent} from "../event/meta/OB11HeartbeatEvent";
+import {selfInfo} from "../../common/data";
 
 class OB11HTTPServer extends HttpServerBase {
     name = "OneBot V11 server"
@@ -21,9 +24,32 @@ class OB11HTTPServer extends HttpServerBase {
 export const ob11HTTPServer = new OB11HTTPServer();
 
 setTimeout(() => {
-    for (const action of actionHandlers) {
+    for (const [actionName, action] of actionMap) {
         for (const method of ["post", "get"]) {
-            ob11HTTPServer.registerRouter(method, action.actionName, (res, payload) => action.handle(payload))
+            ob11HTTPServer.registerRouter(method, actionName, (res, payload) => action.handle(payload))
         }
     }
-}, 0)
\ No newline at end of file
+}, 0)
+
+
+class HTTPHeart{
+    intervalId: NodeJS.Timeout | null = null
+    start(){
+        const {heartInterval} = getConfigUtil().getConfig();
+        if (this.intervalId) {
+            clearInterval(this.intervalId);
+        }
+        this.intervalId = setInterval(() => {
+            // ws的心跳是ws自己维护的
+            postOB11Event(new OB11HeartbeatEvent(selfInfo.online, true, heartInterval), false, false)
+        }, heartInterval)
+    }
+
+    stop(){
+        if (this.intervalId){
+            clearInterval(this.intervalId)
+        }
+    }
+}
+
+export const httpHeart = new HTTPHeart();
\ No newline at end of file
diff --git a/src/onebot11/server/postOB11Event.ts b/src/onebot11/server/postOB11Event.ts
index 876ea1d..4c22920 100644
--- a/src/onebot11/server/postOB11Event.ts
+++ b/src/onebot11/server/postOB11Event.ts
@@ -69,7 +69,7 @@ export function postWsEvent(event: PostEventType) {
     }
 }
 
-export function postOB11Event(msg: PostEventType, reportSelf = false) {
+export function postOB11Event(msg: PostEventType, reportSelf = false, postWs = true) {
     const config = getConfigUtil().getConfig();
     // 判断msg是否是event
     if (!config.reportSelfMessage && !reportSelf) {
@@ -172,5 +172,7 @@ export function postOB11Event(msg: PostEventType, reportSelf = false) {
             });
         }
     }
-    postWsEvent(msg);
+    if (postWs){
+        postWsEvent(msg);
+    }
 }
\ No newline at end of file
diff --git a/src/renderer/index.ts b/src/renderer/index.ts
index 28308ad..d370d76 100644
--- a/src/renderer/index.ts
+++ b/src/renderer/index.ts
@@ -68,6 +68,9 @@ async function onSettingWindowCreated(view: Element) {
                 `<div class="q-input"><input class="q-input__inner" data-config-key="ob11.httpPort" type="number" min="1" max="65534" value="${config.ob11.httpPort}" placeholder="${config.ob11.httpPort}" /></div>`,
                 'config-ob11-httpPort', config.ob11.enableHttp
             ),
+            SettingItem('启用 HTTP 心跳', null,
+              SettingSwitch('ob11.enableHttpHeart', config.ob11.enableHttpHeart, {'control-display-id': 'config-ob11-enableHttpHeart'}),
+            ),
             SettingItem('启用 HTTP 事件上报', null,
                 SettingSwitch('ob11.enableHttpPost', config.ob11.enableHttpPost, {'control-display-id': 'config-ob11-httpHosts'}),
             ),