微信防撤回功能深度解析:WeChatTweak-macOS通知机制实现

微信防撤回功能深度解析:WeChatTweak-macOS通知机制实现

【免费下载链接】WeChatTweak-macOS A dynamic library tweak for WeChat macOS - 首款微信 macOS 客户端撤回拦截与多开 🔨 项目地址: https://gitcode.***/gh_mirrors/we/WeChatTweak-macOS

一、引言:被撤回消息的痛点与解决方案

你是否曾遇到过这样的场景:重要工作消息被对方撤回却来不及查看?朋友发来的内容转瞬即逝让你错失关键信息?作为 macOS 平台首款实现微信消息防撤回的动态库(Dynamic Library),WeChatTweak-macOS 通过底层方法拦截与消息重定向技术,为用户提供了完整的撤回消息保留方案。本文将从技术实现角度,深入剖析其核心的通知拦截机制,带您了解如何通过 Objective-C 运行时(Runtime)特性与方法交换(Method Swizzling)技术,实现对微信撤回消息的捕获、处理与展示。

读完本文,您将掌握:

  • 微信 macOS 客户端消息撤回的底层通信流程
  • Method Swizzling 在拦截系统方法中的实战应用
  • 消息数据结构(MessageData)的关键字段解析
  • 防撤回通知机制的完整实现逻辑
  • 自定义 UI 展示撤回消息的技术细节

二、技术原理:微信撤回机制与拦截方案

2.1 微信撤回流程原理解析

微信 macOS 客户端的消息撤回功能通过以下关键组件实现:

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")];
            W***ontactData *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 的防撤回通知机制通过三个关键步骤实现了完整的撤回消息拦截功能:

该实现方案具有以下技术亮点:

  1. 无侵入式设计:通过动态库注入和方法交换,无需修改微信客户端原始代码
  2. 高效拦截机制:在消息处理的关键节点进行拦截,确保数据完整性
  3. 用户体验优化:通过自定义UI标记和系统通知,提供清晰的撤回提示
  4. 灵活的偏好设置:允许用户根据需求定制通知行为和视觉效果

未来可能的改进方向包括:

  • 支持富媒体消息(图片、文件)的防撤回功能
  • 实现撤回消息的历史记录管理
  • 增加消息内容的加密存储选项
  • 提供更丰富的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 应用逆向工程和动态库开发提供了宝贵的技术参考。如需进一步探索,可以查看项目的完整源代码,或尝试扩展其功能实现更多个性化需求。

【免费下载链接】WeChatTweak-macOS A dynamic library tweak for WeChat macOS - 首款微信 macOS 客户端撤回拦截与多开 🔨 项目地址: https://gitcode.***/gh_mirrors/we/WeChatTweak-macOS

转载请说明出处内容投诉
CSS教程网 » 微信防撤回功能深度解析:WeChatTweak-macOS通知机制实现

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买