微信防撤回通知机制:WeChatTweak-macOS系统通知实现原理
一、痛点直击:撤回消息的"数字消失术"
你是否经历过这样的场景:刚看到微信消息预览,还没来得及细读,屏幕上却弹出"对方已撤回一条消息"的提示?这种"数字消失术"不仅打断阅读体验,更可能导致重要信息的永久丢失。WeChatTweak-macOS作为首款针对微信macOS客户端的功能增强工具,通过底层消息拦截与系统通知整合,彻底解决了这一痛点。本文将深入剖析其防撤回通知机制的实现原理,帮助开发者理解Objective-C运行时特性在实际项目中的应用。
二、技术原理概览:消息拦截的"三道防线"
WeChatTweak-macOS的防撤回通知功能基于三大核心技术构建:
flowchart TD
A[消息拦截层] -->|Method Swizzling| B[FFProcessReqsvrZZ类]
C[UI渲染层] -->|视图篡改| D[MMMessageCellView类]
E[通知分发层] -->|系统通知| F[NSUserNotificationCenter]
B --> G[修改消息状态]
G --> C
G --> E
2.1 关键技术组件对比
| 技术组件 | 作用范围 | 核心API | 风险等级 |
|---|---|---|---|
| Method Swizzling | 运行时方法替换 | jr_swizzleMethod:withMethod:error: | 高(需精确匹配选择器) |
| 消息数据篡改 | 消息状态修改 | ModifyMsgData:msgData: | 中(依赖内部数据结构) |
| 系统通知集成 | 用户提醒机制 | deliverNotification: | 低(系统标准API) |
三、深度解析:从消息拦截到通知展示的全流程
3.1 方法替换:防撤回的"第一道关卡"
WeChatTweak-macOS通过__attribute__((constructor))修饰的初始化函数,在程序启动时完成关键方法的替换:
static void __attribute__((constructor)) tweak(void) {
// 拦截消息撤回处理方法
[objc_getClass("FFProcessReqsvrZZ") jr_swizzleMethod:NSSelectorFromString(@"DelRevokedMsg:msgData:")
withMethod:@selector(tweak_DelRevokedMsg:msgData:)
error:nil];
// 拦截撤回提示消息添加方法
[objc_getClass("FFProcessReqsvrZZ") jr_swizzleMethod:NSSelectorFromString(@"notifyAddRevokePromptMsgOnMainThread:msgData:")
withMethod:@selector(tweak_notifyAddRevokePromptMsgOnMainThread:msgData:)
error:nil];
}
技术点睛:
FFProcessReqsvrZZ是微信内部负责消息处理的核心类,通过替换其DelRevokedMsg:msgData:方法,实现对撤回指令的拦截。使用NSSelectorFromString而非直接使用选择器,是为了规避编译时检查。
3.2 消息状态篡改:让撤回"失效"的核心逻辑
在替换后的tweak_DelRevokedMsg:msgData:方法中,通过修改消息ID实现撤回拦截:
- (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];
});
}
}
关键洞察:微信通过比较
mesSvrID(服务器ID)和mesLocalID(本地ID)来判断消息是否被撤回。将两者设为相等,使系统将撤回消息识别为正常消息,从而绕过删除逻辑。
3.3 系统通知构建:从消息数据到用户提醒
在tweak_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];
MMServiceCenter *serviceCenter = [objc_getClass("MMServiceCenter") defaultCenter];
// 判断是群聊还是单聊
if ([session rangeOfString:@"@chatroom"].location == NSNotFound) {
// 单聊通知处理
ContactStorage *contactStorage = [serviceCenter getService:objc_getClass("ContactStorage")];
WCContactData *contact = [contactStorage GetContact:session];
userNotification.informativeText = messageData.msgContent;
} else {
// 群聊通知处理
GroupStorage *groupStorage = [serviceCenter getService:objc_getClass("GroupStorage")];
WCContactData *groupContact = [groupStorage GetGroupContact:session];
NSString *groupName = groupContact.m_nsNickName.length ?
groupContact.m_nsNickName :
[NSBundle.tweakBundle localizedStringForKey:@"Tweak.Title.Group"];
userNotification.informativeText = [NSString stringWithFormat:@"%@: %@", groupName, messageData.msgContent];
}
// 主线程分发通知
dispatch_async(dispatch_get_main_queue(), ^{
WeChatTweakNotificationType notificationType = WeChatTweak.notificationType;
if (notificationType == WeChatTweakNotificationTypeReceiveAll ||
(notificationType == WeChatTweakNotificationTypeInherited && isChatStatusNotifyOpen)) {
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:userNotification];
}
});
}
}
3.4 UI层修改:消息气泡的视觉标记
通过替换MMMessageCellView的三个关键方法,实现撤回消息的特殊标记:
// 添加撤回标记文本框
- (instancetype)tweak_initWithFrame:(NSRect)arg1 {
MMMessageCellView *view = (MMMessageCellView *)[self tweak_initWithFrame:arg1];
NSTextField *revokeTextField = [[NSTextField alloc] init];
revokeTextField.hidden = YES;
revokeTextField.tag = 9527; // 使用特殊标记值
revokeTextField.stringValue = [NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.RecalledMark"];
revokeTextField.font = [NSFont systemFontOfSize:7.0];
revokeTextField.textColor = [NSColor lightGrayColor];
[view addSubview:revokeTextField];
return view;
}
// 根据消息状态显示标记
- (void)tweak_populateWithMessage:(MMMessageTableItem *)tableItem {
[self tweak_populateWithMessage:tableItem];
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) return;
*stop = YES;
view.hidden = !recalled;
}];
// 设置背景色标记
((MMMessageCellView *)self).layer.backgroundColor = recalled ? WeChatTweak.maskColor.CGColor : nil;
}
实现细节:通过
tag = 9527标记自定义文本框,避免与微信原有视图冲突。当检测到消息被篡改(mesSvrID == mesLocalID)时,显示"已撤回"标记并更改气泡背景色。
四、通知分发流程:从消息拦截到用户提醒的完整链条
sequenceDiagram
participant 微信服务器
participant 微信客户端
participant WeChatTweak
participant 系统通知中心
微信服务器->>微信客户端: 发送撤回指令(DelRevokedMsg)
微信客户端->>WeChatTweak: 调用tweak_DelRevokedMsg:msgData:
WeChatTweak->>WeChatTweak: 修改mesSvrID = mesLocalID
WeChatTweak->>微信客户端: 调用ModifyMsgData:msgData:
微信客户端->>WeChatTweak: 调用notifyAddRevokePromptMsgOnMainThread
WeChatTweak->>WeChatTweak: 构建NSUserNotification
WeChatTweak->>系统通知中心: deliverNotification:
系统通知中心->>用户: 显示撤回消息通知
五、实战应用:基于WeChatTweak的功能扩展思路
5.1 通知类型扩展
WeChatTweak当前支持两种通知模式,开发者可通过扩展WeChatTweakNotificationType枚举添加更多模式:
typedef NS_ENUM(NSUInteger, WeChatTweakNotificationType) {
WeChatTweakNotificationTypeReceiveAll, // 接收所有撤回通知
WeChatTweakNotificationTypeInherited, // 继承微信通知设置
WeChatTweakNotificationTypeOnlyImportant, // 仅重要联系人通知(扩展)
WeChatTweakNotificationTypeCustomFilter // 自定义过滤规则(扩展)
};
5.2 通知内容增强
可通过添加富文本支持和操作按钮增强通知功能:
userNotification.hasActionButton = YES;
userNotification.actionButtonTitle = @"查看详情";
userNotification.otherButtonTitle = @"忽略";
userNotification.contentImage = [NSImage imageNamed:@"revoke_icon"];
六、风险与应对:内部API依赖的稳定性挑战
| 潜在风险 | 影响范围 | 应对策略 |
|---|---|---|
| 微信版本更新导致类名变更 | 全部功能失效 | 实现动态类名探测机制 |
| 消息数据结构变化 | 防撤回功能失效 | 添加数据结构兼容性检查 |
| 方法签名变更 | 部分功能异常 | 实现方法参数动态适配 |
稳定性建议:通过
objc_getClass和NSSelectorFromString动态获取类和方法,避免直接依赖微信内部符号;添加运行时类型检查,确保在内部API变更时能够优雅降级。
七、总结与展望
WeChatTweak-macOS的防撤回通知机制通过Objective-C运行时特性,实现了对闭源应用的功能增强。其核心价值不仅在于解决用户痛点,更为macOS应用逆向开发提供了典范:
- 技术层面:展示了Method Swizzling在实际项目中的安全应用
- 架构层面:构建了"拦截-篡改-通知"的三层处理模型
- 用户体验:平衡了功能实现与原生应用体验的一致性
未来版本可考虑添加的功能方向:
- 撤回消息的历史记录管理
- 自定义通知音效与样式
- 基于NSPasteboard的快速回复功能
掌握这些技术不仅能帮助开发者构建类似工具,更能深入理解macOS应用的内部工作机制。建议开发者在使用此类技术时,始终遵守软件使用协议和相关法律法规,仅在个人学习研究范围内使用。
如果觉得本文对你有帮助,请点赞、收藏、关注三连,下期将带来"WeChatTweak多开功能的实现原理"深度解析!
GLM-5智谱 AI 正式发布 GLM-5,旨在应对复杂系统工程和长时域智能体任务。Jinja00
GLM-5-w4a8GLM-5-w4a8基于混合专家架构,专为复杂系统工程与长周期智能体任务设计。支持单/多节点部署,适配Atlas 800T A3,采用w4a8量化技术,结合vLLM推理优化,高效平衡性能与精度,助力智能应用开发Jinja00
请把这个活动推给顶尖程序员😎本次活动专为懂行的顶尖程序员量身打造,聚焦AtomGit首发开源模型的实际应用与深度测评,拒绝大众化浅层体验,邀请具备扎实技术功底、开源经验或模型测评能力的顶尖开发者,深度参与模型体验、性能测评,通过发布技术帖子、提交测评报告、上传实践项目成果等形式,挖掘模型核心价值,共建AtomGit开源模型生态,彰显顶尖程序员的技术洞察力与实践能力。00
Kimi-K2.5Kimi K2.5 是一款开源的原生多模态智能体模型,它在 Kimi-K2-Base 的基础上,通过对约 15 万亿混合视觉和文本 tokens 进行持续预训练构建而成。该模型将视觉与语言理解、高级智能体能力、即时模式与思考模式,以及对话式与智能体范式无缝融合。Python00
MiniMax-M2.5MiniMax-M2.5开源模型,经数十万复杂环境强化训练,在代码生成、工具调用、办公自动化等经济价值任务中表现卓越。SWE-Bench Verified得分80.2%,Multi-SWE-Bench达51.3%,BrowseComp获76.3%。推理速度比M2.1快37%,与Claude Opus 4.6相当,每小时仅需0.3-1美元,成本仅为同类模型1/10-1/20,为智能应用开发提供高效经济选择。【此简介由AI生成】Python00
Qwen3.5Qwen3.5 昇腾 vLLM 部署教程。Qwen3.5 是 Qwen 系列最新的旗舰多模态模型,采用 MoE(混合专家)架构,在保持强大模型能力的同时显著降低了推理成本。00- RRing-2.5-1TRing-2.5-1T:全球首个基于混合线性注意力架构的开源万亿参数思考模型。Python00