微信防撤回功能深度解析:WeChatTweak-macOS通知机制实现
一、引言:被撤回消息的痛点与解决方案
你是否曾遇到过这样的场景:重要工作消息被对方撤回却来不及查看?朋友发来的内容转瞬即逝让你错失关键信息?作为 macOS 平台首款实现微信消息防撤回的动态库(Dynamic Library),WeChatTweak-macOS 通过底层方法拦截与消息重定向技术,为用户提供了完整的撤回消息保留方案。本文将从技术实现角度,深入剖析其核心的通知拦截机制,带您了解如何通过 Objective-C 运行时(Runtime)特性与方法交换(Method Swizzling)技术,实现对微信撤回消息的捕获、处理与展示。
读完本文,您将掌握:
- 微信 macOS 客户端消息撤回的底层通信流程
- Method Swizzling 在拦截系统方法中的实战应用
- 消息数据结构(MessageData)的关键字段解析
- 防撤回通知机制的完整实现逻辑
- 自定义 UI 展示撤回消息的技术细节
二、技术原理:微信撤回机制与拦截方案
2.1 微信撤回流程原理解析
微信 macOS 客户端的消息撤回功能通过以下关键组件实现:
sequenceDiagram
participant 客户端 as 微信客户端
participant 服务器 as 微信服务器
participant 数据库 as 本地消息数据库
participant UI as 用户界面
服务器->>客户端: 推送撤回指令(DelRevokedMsg)
客户端->>数据库: 请求删除消息(DelRevokedMsg:msgData:)
数据库->>客户端: 返回操作结果
客户端->>UI: 发送撤回通知(notifyAddRevokePromptMsgOnMainThread)
UI->>客户端: 更新界面显示"消息已撤回"
WeChatTweak-macOS 通过拦截两个核心方法实现防撤回功能:
DelRevokedMsg:msgData::服务器通知客户端删除消息的核心接口notifyAddRevokePromptMsgOnMainThread:msgData::触发UI显示撤回提示的通知方法
2.2 方法交换技术基础
项目采用 Objective-C 的 Method Swizzling 技术,在运行时替换微信客户端的原始方法。其核心实现位于 AntiRevoke.m 的构造函数中:
static void __attribute__((constructor)) tweak(void) {
// 拦截消息删除方法
[objc_getClass("FFProcessReqsvrZZ") jr_swizzleMethod:NSSelectorFromString(@"DelRevokedMsg:msgData:")
withMethod:@selector(tweak_DelRevokedMsg:msgData:)
error:nil];
// 拦截撤回通知方法
[objc_getClass("MMMessageCellView") jr_swizzleMethod:NSSelectorFromString(@"populateWithMessage:")
withMethod:@selector(tweak_populateWithMessage:)
error:nil];
}
技术点睛:
__attribute__((constructor))修饰的函数会在动态库加载时自动执行,确保在微信主程序初始化前完成方法替换。jr_swizzleMethod是封装的方法交换工具,内部调用了 Objective-C Runtime 的method_exchangeImplementations函数。
三、核心实现:防撤回通知机制的关键步骤
3.1 消息拦截与重定向
DelRevokedMsg:msgData: 方法的拦截实现是防撤回功能的核心,其关键代码如下:
- (void)tweak_DelRevokedMsg:(NSString *)session msgData:(MessageData *)messageData {
if (messageData.isSendFromSelf) {
// 自己发送的消息允许撤回
[self tweak_DelRevokedMsg:session msgData:messageData];
} else {
// 篡改消息ID,使系统无法找到原始消息进行删除
messageData.mesSvrID = messageData.mesLocalID;
// 修改消息数据
[((FFProcessReqsvrZZ *)self) ModifyMsgData:session msgData:messageData];
// 异步更新UI
dispatch_async(dispatch_get_main_queue(), ^{
[((FFProcessReqsvrZZ *)self) notifyDelMsgOnMainThread:session msgData:messageData isRevoke:YES];
[((FFProcessReqsvrZZ *)self) notifyAddMsgOnMainThread:session msgData:messageData];
});
}
}
关键技术点:
- 通过
messageData.isSendFromSelf判断消息方向,仅拦截他人发送的消息 - 核心篡改逻辑:
messageData.mesSvrID = messageData.mesLocalID,使系统无法匹配到待删除消息 - 使用
dispatch_async(dispatch_get_main_queue())确保UI更新操作在主线程执行
3.2 撤回通知的拦截与自定义处理
notifyAddRevokePromptMsgOnMainThread:msgData: 方法负责拦截系统的撤回通知并生成自定义通知:
- (void)tweak_notifyAddRevokePromptMsgOnMainThread:(NSString *)session msgData:(MessageData *)messageData {
MessageData *localMessage = [((FFProcessReqsvrZZ *)self) GetMsgData:session localId:messageData.mesLocalID];
if (!localMessage || localMessage.mesSvrID != messageData.mesLocalID) {
[self tweak_notifyAddRevokePromptMsgOnMainThread:session msgData:messageData];
} else {
// 创建系统通知
NSUserNotification *userNotification = [[NSUserNotification alloc] init];
userNotification.title = [NSBundle.tweakBundle localizedStringForKey:@"Tweak.Title.RevokedMessage"];
userNotification.informativeText = messageData.msgContent;
// 根据会话类型设置通知内容
if ([session rangeOfString:@"@chatroom"].location != NSNotFound) {
GroupStorage *groupStorage = [serviceCenter getService:objc_getClass("GroupStorage")];
WCContactData *groupContact = [groupStorage GetGroupContact:session];
userNotification.subtitle = [NSString stringWithFormat:@"[%@] 撤回了一条消息", groupContact.m_nsNickName];
}
// 分发通知
dispatch_async(dispatch_get_main_queue(), ^{
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:userNotification];
});
}
}
3.3 消息数据结构解析
MessageData 是微信客户端存储消息信息的核心数据结构,其关键字段包括:
| 字段名 | 类型 | 描述 | 防撤回关键作用 |
|---|---|---|---|
| mesSvrID | NSInteger | 服务器消息ID | 通过修改此字段使系统无法定位删除对象 |
| mesLocalID | NSInteger | 本地消息ID | 作为替换mesSvrID的关键值 |
| msgContent | NSString | 消息内容 | 存储原始消息文本,用于通知展示 |
| isSendFromSelf | BOOL | 是否自己发送 | 判断是否需要拦截撤回操作 |
| createTime | NSTimeInterval | 消息创建时间 | 用于消息排序与时间戳展示 |
四、UI展示:撤回消息的视觉提示实现
4.1 自定义撤回标记视图
WeChatTweak 在消息单元格(MMMessageCellView)中添加了自定义标记视图,用于标识被撤回的消息:
- (instancetype)tweak_initWithFrame:(NSRect)arg1 {
MMMessageCellView *view = (MMMessageCellView *)[self tweak_initWithFrame:arg1];
// 创建撤回标记文本框
NSTextField *revokeTextField = [[NSTextField alloc] init];
revokeTextField.hidden = YES;
revokeTextField.stringValue = [NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.RecalledMark"];
revokeTextField.font = [NSFont systemFontOfSize:7.0];
revokeTextField.textColor = [NSColor lightGrayColor];
revokeTextField.tag = 9527; // 唯一标识
[view addSubview:revokeTextField];
return view;
}
4.2 撤回状态判断与UI更新
通过重写 populateWithMessage: 方法,根据消息状态控制UI展示:
- (void)tweak_populateWithMessage:(MMMessageTableItem *)tableItem {
[self tweak_populateWithMessage:tableItem];
// 判断消息是否为撤回状态(mesSvrID == mesLocalID表示已被篡改)
BOOL recalled = tableItem.message.mesSvrID &&
tableItem.message.mesSvrID == tableItem.message.mesLocalID;
// 更新撤回标记显示状态
[((MMMessageCellView *)self).subviews enumerateObjectsUsingBlock:^(__kindof NSView * _Nonnull view, NSUInteger index, BOOL * _Nonnull stop) {
if (view.tag == 9527) { // 找到自定义标记视图
view.hidden = !recalled;
*stop = YES;
}
}];
// 为撤回消息添加背景色标记
((MMMessageCellView *)self).layer.backgroundColor = recalled ?
WeChatTweak.maskColor.CGColor : nil;
}
4.3 自定义标记布局
通过重写 layout 方法确保标记视图正确定位:
- (void)tweak_layout {
[self tweak_layout];
[((MMMessageCellView *)self).subviews enumerateObjectsUsingBlock:^(__kindof NSView * _Nonnull view, NSUInteger index, BOOL * _Nonnull stop) {
if (view.tag != 9527) return;
// 定位到头像视图中间偏上位置
NSView *avatarView = ((MMMessageCellView *)self).avatarImgView;
CGFloat x = CGRectGetMidX(avatarView.frame) - CGRectGetWidth(view.frame) / 2.0;
CGFloat y = CGRectGetMinY(avatarView.frame) - CGRectGetHeight(view.frame);
view.frame = NSMakeRect(x, y, CGRectGetWidth(view.frame), CGRectGetHeight(view.frame));
*stop = YES;
}];
}
五、通知偏好设置:用户可控的通知机制
5.1 通知类型枚举定义
在 WeChatTweak.h 中定义了三种通知类型,满足不同用户需求:
typedef NS_ENUM(NSUInteger, WeChatTweakNotificationType) {
WeChatTweakNotificationTypeInherited = 0, // 继承系统通知设置
WeChatTweakNotificationTypeReceiveAll, // 接收所有撤回通知
WeChatTweakNotificationTypeDisable // 禁用撤回通知
};
5.2 用户偏好存储实现
通过 NSUserDefaults 实现通知设置的持久化存储:
+ (WeChatTweakNotificationType)notificationType {
return [NSUserDefaults.standardUserDefaults integerForKey:@"WeChatTweakPreferenceRevokeNotificationTypeKey"];
}
+ (void)setNotificationType:(WeChatTweakNotificationType)notificationType {
[NSUserDefaults.standardUserDefaults setInteger:notificationType
forKey:@"WeChatTweakPreferenceRevokeNotificationTypeKey"];
}
5.3 通知分发逻辑
根据用户设置决定是否发送通知:
// 根据通知类型判断是否发送通知
WeChatTweakNotificationType notificationType = WeChatTweak.notificationType;
if (notificationType == WeChatTweakNotificationTypeReceiveAll ||
(notificationType == WeChatTweakNotificationTypeInherited && isChatStatusNotifyOpen)) {
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:userNotification];
}
六、总结与展望
WeChatTweak-macOS 的防撤回通知机制通过三个关键步骤实现了完整的撤回消息拦截功能:
flowchart TD
A[方法交换] -->|替换| B[DelRevokedMsg:msgData:]
A -->|替换| C[notifyAddRevokePromptMsgOnMainThread:msgData:]
B --> D[篡改消息ID]
D --> E[阻止消息删除]
C --> F[提取原始消息内容]
F --> G[生成系统通知]
G --> H[展示撤回内容]
E --> I[修改UI显示状态]
该实现方案具有以下技术亮点:
- 无侵入式设计:通过动态库注入和方法交换,无需修改微信客户端原始代码
- 高效拦截机制:在消息处理的关键节点进行拦截,确保数据完整性
- 用户体验优化:通过自定义UI标记和系统通知,提供清晰的撤回提示
- 灵活的偏好设置:允许用户根据需求定制通知行为和视觉效果
未来可能的改进方向包括:
- 支持富媒体消息(图片、文件)的防撤回功能
- 实现撤回消息的历史记录管理
- 增加消息内容的加密存储选项
- 提供更丰富的UI展示样式选择
七、扩展学习:Method Swizzling 技术实践
方法交换是 Objective-C 运行时编程的强大技术,其基本实现模板如下:
#import <objc/runtime.h>
@implementation NSObject (SwizzlingExample)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(originalMethod);
SEL swizzledSelector = @selector(swizzledMethod);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// 检查原始方法是否存在
BOOL didAddMethod = class_addMethod(class, originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)swizzledMethod {
// 前置处理逻辑
[self swizzledMethod]; // 调用原始方法实现
// 后置处理逻辑
}
@end
注意事项:在使用 Method Swizzling 时,应注意线程安全(使用 dispatch_once)、方法存在性检查以及避免递归调用等问题。
通过本文的技术解析,相信您已深入了解 WeChatTweak-macOS 防撤回功能的实现原理。该项目不仅解决了实际使用痛点,更为 macOS 应用逆向工程和动态库开发提供了宝贵的技术参考。如需进一步探索,可以查看项目的完整源代码,或尝试扩展其功能实现更多个性化需求。
GLM-5.1GLM-5.1是智谱迄今最智能的旗舰模型,也是目前全球最强的开源模型。GLM-5.1大大提高了代码能力,在完成长程任务方面提升尤为显著。和此前分钟级交互的模型不同,它能够在一次任务中独立、持续工作超过8小时,期间自主规划、执行、自我进化,最终交付完整的工程级成果。Jinja00
MiniMax-M2.7MiniMax-M2.7 是我们首个深度参与自身进化过程的模型。M2.7 具备构建复杂智能体应用框架的能力,能够借助智能体团队、复杂技能以及动态工具搜索,完成高度精细的生产力任务。Python00- QQwen3.5-397B-A17BQwen3.5 实现了重大飞跃,整合了多模态学习、架构效率、强化学习规模以及全球可访问性等方面的突破性进展,旨在为开发者和企业赋予前所未有的能力与效率。Jinja00
HY-Embodied-0.5这是一套专为现实世界具身智能打造的基础模型。该系列模型采用创新的混合Transformer(Mixture-of-Transformers, MoT) 架构,通过潜在令牌实现模态特异性计算,显著提升了细粒度感知能力。Jinja00
LongCat-AudioDiT-1BLongCat-AudioDiT 是一款基于扩散模型的文本转语音(TTS)模型,代表了当前该领域的最高水平(SOTA),它直接在波形潜空间中进行操作。00
ERNIE-ImageERNIE-Image 是由百度 ERNIE-Image 团队开发的开源文本到图像生成模型。它基于单流扩散 Transformer(DiT)构建,并配备了轻量级的提示增强器,可将用户的简短输入扩展为更丰富的结构化描述。凭借仅 80 亿的 DiT 参数,它在开源文本到图像模型中达到了最先进的性能。该模型的设计不仅追求强大的视觉质量,还注重实际生成场景中的可控性,在这些场景中,准确的内容呈现与美观同等重要。特别是,ERNIE-Image 在复杂指令遵循、文本渲染和结构化图像生成方面表现出色,使其非常适合商业海报、漫画、多格布局以及其他需要兼具视觉质量和精确控制的内容创作任务。它还支持广泛的视觉风格,包括写实摄影、设计导向图像以及更多风格化的美学输出。Jinja00