diff --git a/manifest.json b/manifest.json
index 749de07..b9fccf0 100644
--- a/manifest.json
+++ b/manifest.json
@@ -4,7 +4,7 @@
   "name": "LLOneBot",
   "slug": "LLOneBot",
   "description": "实现 OneBot 11 协议,用以 QQ 机器人开发",
-  "version": "3.29.1",
+  "version": "3.29.2",
   "icon": "./icon.webp",
   "authors": [
     {
diff --git a/src/common/config.ts b/src/common/config.ts
index 3ef929c..ce2250f 100644
--- a/src/common/config.ts
+++ b/src/common/config.ts
@@ -52,6 +52,7 @@ export class ConfigUtil {
       autoDeleteFile: false,
       autoDeleteFileSecond: 60,
       musicSignUrl: '',
+      msgCacheExpire: 120
     }
 
     if (!fs.existsSync(this.configPath)) {
diff --git a/src/common/data.ts b/src/common/data.ts
index ccd36cc..a875fc4 100644
--- a/src/common/data.ts
+++ b/src/common/data.ts
@@ -9,6 +9,8 @@ import { NTQQGroupApi } from '../ntqqapi/api/group'
 import { log } from './utils/log'
 import { isNumeric } from './utils/helper'
 import { NTQQFriendApi, NTQQUserApi } from '../ntqqapi/api'
+import { RawMessage } from '../ntqqapi/types'
+import { getConfigUtil } from './config'
 
 export let groups: Group[] = []
 export let friends: Friend[] = []
@@ -128,3 +130,24 @@ export function getSelfUid() {
 export function getSelfUin() {
   return selfInfo['uin']
 }
+
+const messages: Map<string, RawMessage> = new Map()
+let expire: number
+
+/** 缓存近期消息内容 */
+export async function addMsgCache(msg: RawMessage) {
+  expire ??= getConfigUtil().getConfig().msgCacheExpire! * 1000
+  if (expire === 0) {
+    return
+  }
+  const id = msg.msgId
+  messages.set(id, msg)
+  setTimeout(() => {
+    messages.delete(id)
+  }, expire)
+}
+
+/** 获取近期消息内容 */
+export function getMsgCache(msgId: string) {
+  return messages.get(msgId)
+}
\ No newline at end of file
diff --git a/src/common/types.ts b/src/common/types.ts
index d4c6986..e7612b4 100644
--- a/src/common/types.ts
+++ b/src/common/types.ts
@@ -30,6 +30,8 @@ export interface Config {
   ffmpeg?: string // ffmpeg路径
   musicSignUrl?: string
   ignoreBeforeLoginMsg?: boolean
+  /** 单位为秒 */
+  msgCacheExpire?: number
 }
 
 export interface LLOneBotError {
diff --git a/src/main/main.ts b/src/main/main.ts
index 5ba38f5..7d758be 100644
--- a/src/main/main.ts
+++ b/src/main/main.ts
@@ -21,7 +21,8 @@ import {
   setSelfInfo,
   getSelfInfo,
   getSelfUid,
-  getSelfUin
+  getSelfUin,
+  addMsgCache
 } from '../common/data'
 import { hookNTQQApiCall, hookNTQQApiReceive, ReceiveCmdS, registerReceiveHook, startHook } from '../ntqqapi/hook'
 import { OB11Constructor } from '../onebot11/constructor'
@@ -95,7 +96,7 @@ function onLoad() {
   }
   ipcMain.handle(CHANNEL_ERROR, async (event, arg) => {
     const ffmpegOk = await checkFfmpeg(getConfigUtil().getConfig().ffmpeg)
-    llonebotError.ffmpegError = ffmpegOk ? '' : '没有找到ffmpeg,音频只能发送wav和silk,视频尺寸可能异常'
+    llonebotError.ffmpegError = ffmpegOk ? '' : '没有找到 FFmpeg, 音频只能发送 WAV 和 SILK, 视频尺寸可能异常'
     let { httpServerError, wsServerError, otherError, ffmpegError } = llonebotError
     let error = `${otherError}\n${httpServerError}\n${wsServerError}\n${ffmpegError}`
     error = error.replace('\n\n', '\n')
@@ -159,6 +160,7 @@ function onLoad() {
         peerUid: message.peerUid
       }
       message.msgShortId = MessageUnique.createMsg(peer, message.msgId)
+      addMsgCache(message)
 
       OB11Constructor.message(message)
         .then((msg) => {
@@ -183,7 +185,7 @@ function onLoad() {
         }
       })
       OB11Constructor.PrivateEvent(message).then((privateEvent) => {
-        log(message)
+        //log(message)
         if (privateEvent) {
           // log("post private event", privateEvent);
           postOb11Event(privateEvent)
diff --git a/src/onebot11/action/msg/GetMsg.ts b/src/onebot11/action/msg/GetMsg.ts
index 5f53294..e3a0e2b 100644
--- a/src/onebot11/action/msg/GetMsg.ts
+++ b/src/onebot11/action/msg/GetMsg.ts
@@ -4,6 +4,7 @@ import BaseAction from '../BaseAction'
 import { ActionName } from '../types'
 import { NTQQMsgApi } from '@/ntqqapi/api'
 import { MessageUnique } from '@/common/utils/MessageUnique'
+import { getMsgCache } from '@/common/data'
 
 export interface PayloadType {
   message_id: number | string
@@ -29,12 +30,9 @@ class GetMsg extends BaseAction<PayloadType, OB11Message> {
       peerUid: msgIdWithPeer.Peer.peerUid,
       chatType: msgIdWithPeer.Peer.chatType
     }
-    const msg = await NTQQMsgApi.getMsgsByMsgId(
-      peer,
-      [msgIdWithPeer?.MsgId || payload.message_id.toString()]
-    )
-    const retMsg = await OB11Constructor.message(msg.msgList[0])
-    retMsg.message_id = MessageUnique.createMsg(peer, msg.msgList[0].msgId)!
+    const msg = getMsgCache(msgIdWithPeer.MsgId) ?? (await NTQQMsgApi.getMsgsByMsgId(peer, [msgIdWithPeer.MsgId])).msgList[0]
+    const retMsg = await OB11Constructor.message(msg)
+    retMsg.message_id = MessageUnique.createMsg(peer, msg.msgId)!
     retMsg.message_seq = retMsg.message_id
     retMsg.real_id = retMsg.message_id
     return retMsg
diff --git a/src/onebot11/action/msg/SendMsg.ts b/src/onebot11/action/msg/SendMsg.ts
index cae7707..a08c3d5 100644
--- a/src/onebot11/action/msg/SendMsg.ts
+++ b/src/onebot11/action/msg/SendMsg.ts
@@ -119,9 +119,8 @@ export async function createSendElements(
         if (!peer) {
           continue
         }
-        let atQQ = sendMsg.data?.qq
-        if (atQQ) {
-          atQQ = atQQ.toString()
+        if (sendMsg.data?.qq) {
+          const atQQ = String(sendMsg.data.qq)
           if (atQQ === 'all') {
             // todo:查询剩余的at全体次数
             const groupCode = peer.peerUid
@@ -161,7 +160,7 @@ export async function createSendElements(
       }
         break
       case OB11MessageDataType.reply: {
-        if (sendMsg.data.id) {
+        if (sendMsg.data?.id) {
           const replyMsgId = await MessageUnique.getMsgIdAndPeerByShortId(+sendMsg.data.id)
           if (!replyMsgId) {
             log('回复消息不存在', replyMsgId)
diff --git a/src/onebot11/constructor.ts b/src/onebot11/constructor.ts
index 2bc85a8..c94335f 100644
--- a/src/onebot11/constructor.ts
+++ b/src/onebot11/constructor.ts
@@ -590,21 +590,19 @@ export class OB11Constructor {
     msg: RawMessage,
     shortId: number
   ): Promise<OB11FriendRecallNoticeEvent | OB11GroupRecallNoticeEvent | undefined> {
-    let msgElement = msg.elements.find(
+    const msgElement = msg.elements.find(
       (element) => element.grayTipElement?.subElementType === GrayTipElementSubType.RECALL,
     )
     if (!msgElement) {
       return
     }
-    const isGroup = msg.chatType === ChatType.group
     const revokeElement = msgElement.grayTipElement.revokeElement
-    if (isGroup) {
+    if (msg.chatType === ChatType.group) {
       const operator = await getGroupMember(msg.peerUid, revokeElement.operatorUid)
-      const sender = await getGroupMember(msg.peerUid, revokeElement.origMsgSenderUid!)
       return new OB11GroupRecallNoticeEvent(
         parseInt(msg.peerUid),
-        parseInt(sender?.uin!),
-        parseInt(operator?.uin!),
+        parseInt(msg.senderUin!),
+        parseInt(operator?.uin || msg.senderUin!),
         shortId,
       )
     }
diff --git a/src/renderer/index.ts b/src/renderer/index.ts
index 70d9083..fa88e6a 100644
--- a/src/renderer/index.ts
+++ b/src/renderer/index.ts
@@ -219,6 +219,11 @@ async function onSettingWindowCreated(view: Element) {
           `${window.LiteLoader.plugins['LLOneBot'].path.data}/logs`,
           SettingButton('打开', 'config-open-log-path'),
         ),
+        SettingItem(
+          '消息内容缓存时长',
+          '单位为秒,可用于获取撤回的消息',
+          `<div class="q-input"><input class="q-input__inner" data-config-key="msgCacheExpire" type="number" min="1" value="${config.msgCacheExpire}" placeholder="${config.msgCacheExpire}" /></div>`,
+        ),
       ]),
       SettingList([
         SettingItem('GitHub 仓库', `https://github.com/LLOneBot/LLOneBot`, SettingButton('点个星星', 'open-github')),
diff --git a/src/version.ts b/src/version.ts
index b7db38f..d453805 100644
--- a/src/version.ts
+++ b/src/version.ts
@@ -1 +1 @@
-export const version = '3.29.1'
+export const version = '3.29.2'