Fix anti-revoke message cell mask style issue

This commit is contained in:
Sunnyyoung 2021-05-09 19:12:03 +08:00
parent 7754b578ab
commit b802de5e87
10 changed files with 281 additions and 289 deletions

View File

@ -5,7 +5,6 @@ target 'WeChatTweak' do
pod 'JRSwizzle'
pod 'GCDWebServer'
pod 'YYModel'
pod 'MMKV'
end
post_install do |installer|

View File

@ -3,28 +3,24 @@ PODS:
- GCDWebServer/Core (= 3.5.4)
- GCDWebServer/Core (3.5.4)
- JRSwizzle (1.0)
- MMKV (1.0.23)
- YYModel (1.0.4)
DEPENDENCIES:
- GCDWebServer
- JRSwizzle
- MMKV
- YYModel
SPEC REPOS:
trunk:
- GCDWebServer
- JRSwizzle
- MMKV
- YYModel
SPEC CHECKSUMS:
GCDWebServer: 2c156a56c8226e2d5c0c3f208a3621ccffbe3ce4
JRSwizzle: dd5ead5d913a0f29e7f558200165849f006bb1e3
MMKV: 031ae51aedc0af1b250922f7ed8a6b6ee2f94c4c
YYModel: 2a7fdd96aaa4b86a824e26d0c517de8928c04b30
PODFILE CHECKSUM: ae0e3cae9898443a58d6ff3b8332b51018abc753
PODFILE CHECKSUM: 2d627cca174f5b93185d7b4603c19b06c0bd86a0
COCOAPODS: 1.10.1

View File

@ -11,6 +11,8 @@
7D14E5A51F6447DB00D75132 /* AlfredManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D14E5A31F6447DB00D75132 /* AlfredManager.m */; };
7D2194C32646E62E0068F4CC /* AutoAuth.h in Headers */ = {isa = PBXBuildFile; fileRef = 7D2194C12646E62E0068F4CC /* AutoAuth.h */; };
7D2194C42646E62E0068F4CC /* AutoAuth.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D2194C22646E62E0068F4CC /* AutoAuth.m */; };
7D2194CB264701950068F4CC /* AntiRevoke.h in Headers */ = {isa = PBXBuildFile; fileRef = 7D2194C9264701950068F4CC /* AntiRevoke.h */; };
7D2194CC264701950068F4CC /* AntiRevoke.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D2194CA264701950068F4CC /* AntiRevoke.m */; };
7D54A05C20E74D9400CB5306 /* TweakPreferencesController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7D54A05E20E74D9400CB5306 /* TweakPreferencesController.xib */; };
7D54A06A20E74FE500CB5306 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7D54A06C20E74FE500CB5306 /* Localizable.strings */; };
7D9049F51F82A41A004E6370 /* fishhook.c in Sources */ = {isa = PBXBuildFile; fileRef = 7D9049F31F82A415004E6370 /* fishhook.c */; };
@ -19,8 +21,6 @@
7D9049FA1F82B708004E6370 /* NSString+WeChatTweak.h in Headers */ = {isa = PBXBuildFile; fileRef = 7D9049F71F82B6FB004E6370 /* NSString+WeChatTweak.h */; };
7DB8AFBE206211D900E683AE /* WTConfigManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 7DB8AFBC206211D900E683AE /* WTConfigManager.h */; };
7DB8AFBF206211D900E683AE /* WTConfigManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DB8AFBD206211D900E683AE /* WTConfigManager.m */; };
7DBA4467231812AB004CE2DB /* RecallCacheManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 7DBA4465231812AB004CE2DB /* RecallCacheManager.h */; };
7DBA4468231812AB004CE2DB /* RecallCacheManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DBA4466231812AB004CE2DB /* RecallCacheManager.m */; };
7DF8422C1F40583F00D42D79 /* WeChatTweak.h in Headers */ = {isa = PBXBuildFile; fileRef = 7DF8422A1F40583F00D42D79 /* WeChatTweak.h */; settings = {ATTRIBUTES = (Public, ); }; };
7DF842341F4058AB00D42D79 /* WeChatTweak.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DF842331F4058AB00D42D79 /* WeChatTweak.m */; };
7DF842521F4058C600D42D79 /* TweakPreferencesController.h in Headers */ = {isa = PBXBuildFile; fileRef = 7DF8424F1F4058C600D42D79 /* TweakPreferencesController.h */; };
@ -38,6 +38,8 @@
7D14E5A31F6447DB00D75132 /* AlfredManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AlfredManager.m; sourceTree = "<group>"; };
7D2194C12646E62E0068F4CC /* AutoAuth.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AutoAuth.h; sourceTree = "<group>"; };
7D2194C22646E62E0068F4CC /* AutoAuth.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AutoAuth.m; sourceTree = "<group>"; };
7D2194C9264701950068F4CC /* AntiRevoke.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AntiRevoke.h; sourceTree = "<group>"; };
7D2194CA264701950068F4CC /* AntiRevoke.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AntiRevoke.m; sourceTree = "<group>"; };
7D54A05F20E74E4600CB5306 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/TweakPreferencesController.xib; sourceTree = "<group>"; };
7D54A07020E74FFD00CB5306 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
7D54A07120E7535F00CB5306 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
@ -48,8 +50,6 @@
7D9049F81F82B6FB004E6370 /* NSString+WeChatTweak.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSString+WeChatTweak.m"; sourceTree = "<group>"; };
7DB8AFBC206211D900E683AE /* WTConfigManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WTConfigManager.h; sourceTree = "<group>"; };
7DB8AFBD206211D900E683AE /* WTConfigManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WTConfigManager.m; sourceTree = "<group>"; };
7DBA4465231812AB004CE2DB /* RecallCacheManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RecallCacheManager.h; sourceTree = "<group>"; };
7DBA4466231812AB004CE2DB /* RecallCacheManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RecallCacheManager.m; sourceTree = "<group>"; };
7DF7065D23A8B69000DEA8F6 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/TweakPreferencesController.xib; sourceTree = "<group>"; };
7DF7065E23A8B69A00DEA8F6 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = "zh-Hans"; path = "zh-Hans.lproj/TweakPreferencesController.xib"; sourceTree = "<group>"; };
7DF7065F23A8B6A400DEA8F6 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = "zh-Hant"; path = "zh-Hant.lproj/TweakPreferencesController.xib"; sourceTree = "<group>"; };
@ -94,8 +94,6 @@
7D14E5A31F6447DB00D75132 /* AlfredManager.m */,
7DB8AFBC206211D900E683AE /* WTConfigManager.h */,
7DB8AFBD206211D900E683AE /* WTConfigManager.m */,
7DBA4465231812AB004CE2DB /* RecallCacheManager.h */,
7DBA4466231812AB004CE2DB /* RecallCacheManager.m */,
);
path = Manager;
sourceTree = "<group>";
@ -165,6 +163,8 @@
7DF842331F4058AB00D42D79 /* WeChatTweak.m */,
7D2194C12646E62E0068F4CC /* AutoAuth.h */,
7D2194C22646E62E0068F4CC /* AutoAuth.m */,
7D2194C9264701950068F4CC /* AntiRevoke.h */,
7D2194CA264701950068F4CC /* AntiRevoke.m */,
7D5AAF3720DF4BB300860EEE /* Vendor */,
7D5AAF3620DF4BA400860EEE /* Category */,
7D5AAF3520DF4B9700860EEE /* Controller */,
@ -199,13 +199,13 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
7DBA4467231812AB004CE2DB /* RecallCacheManager.h in Headers */,
7D9049FA1F82B708004E6370 /* NSString+WeChatTweak.h in Headers */,
7DF8422C1F40583F00D42D79 /* WeChatTweak.h in Headers */,
7DF8425B1F4058DD00D42D79 /* NSBundle+WeChatTweak.h in Headers */,
7DB8AFBE206211D900E683AE /* WTConfigManager.h in Headers */,
7D9049F61F82A41A004E6370 /* fishhook.h in Headers */,
7D14E5A41F6447DB00D75132 /* AlfredManager.h in Headers */,
7D2194CB264701950068F4CC /* AntiRevoke.h in Headers */,
7DF842601F40590500D42D79 /* WeChatTweakHeaders.h in Headers */,
7DF842521F4058C600D42D79 /* TweakPreferencesController.h in Headers */,
7D2194C32646E62E0068F4CC /* AutoAuth.h in Headers */,
@ -330,7 +330,7 @@
7D14E5A51F6447DB00D75132 /* AlfredManager.m in Sources */,
7DF842531F4058C600D42D79 /* TweakPreferencesController.m in Sources */,
7D9049F91F82B6FB004E6370 /* NSString+WeChatTweak.m in Sources */,
7DBA4468231812AB004CE2DB /* RecallCacheManager.m in Sources */,
7D2194CC264701950068F4CC /* AntiRevoke.m in Sources */,
7DF842341F4058AB00D42D79 /* WeChatTweak.m in Sources */,
7DB8AFBF206211D900E683AE /* WTConfigManager.m in Sources */,
7D9049F51F82A41A004E6370 /* fishhook.c in Sources */,

20
WeChatTweak/AntiRevoke.h Normal file
View File

@ -0,0 +1,20 @@
//
// AntiRevoke.h
// WeChatTweak
//
// Created by Sunny Young on 2021/5/9.
// Copyright © 2021 Sunnyyoung. All rights reserved.
//
#import <AppKit/AppKit.h>
#import <objc/runtime.h>
#import <objc/message.h>
#import <JRSwizzle/JRSwizzle.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (AntiRevoke)
@end
NS_ASSUME_NONNULL_END

230
WeChatTweak/AntiRevoke.m Normal file
View File

@ -0,0 +1,230 @@
//
// AntiRevoke.m
// WeChatTweak
//
// Created by Sunny Young on 2021/5/9.
// Copyright © 2021 Sunnyyoung. All rights reserved.
//
#import "AntiRevoke.h"
#import "WeChatTweakHeaders.h"
#import "WTConfigManager.h"
#import "NSString+WeChatTweak.h"
#import "NSBundle+WeChatTweak.h"
@implementation NSObject (AntiRevoke)
static void __attribute__((constructor)) tweak(void) {
[objc_getClass("FFProcessReqsvrZZ") jr_swizzleMethod:NSSelectorFromString(@"FFToNameFavChatZZ:sessionMsgList:") withMethod:@selector(tweak_FFToNameFavChatZZ:sessionMsgList:) error:nil];
[objc_getClass("MMMessageCellView") jr_swizzleMethod:NSSelectorFromString(@"initWithFrame:") withMethod:@selector(tweak_initWithFrame:) error:nil];
[objc_getClass("MMMessageCellView") jr_swizzleMethod:NSSelectorFromString(@"populateWithMessage:") withMethod:@selector(tweak_populateWithMessage:) error:nil];
[objc_getClass("MMMessageCellView") jr_swizzleMethod:NSSelectorFromString(@"layout") withMethod:@selector(tweak_layout) error:nil];
}
- (void)tweak_FFToNameFavChatZZ:(MessageData *)message sessionMsgList:(nullable id)sessionMsgList {
// - (id)GetMsgData:(id)arg1 svrId:(unsigned long long)arg2;
SEL GetMsgDataSelector = NSSelectorFromString(@"GetMsgData:svrId:");
if (![self respondsToSelector:GetMsgDataSelector]) {
// Fallback to origin method
return [self tweak_FFToNameFavChatZZ:message sessionMsgList:sessionMsgList];
}
// Decode message
NSString *session = [message.msgContent tweak_subStringFrom:@"<session>" to:@"</session>"];
NSUInteger newMessageID = [message.msgContent tweak_subStringFrom:@"<newmsgid>" to:@"</newmsgid>"].longLongValue;
NSString *replaceMessage = [message.msgContent tweak_subStringFrom:@"<replacemsg><![CDATA[" to:@"]]></replacemsg>"];
// Get message data
MessageData *messageData = ((id (*)(id, SEL, id, unsigned long long))objc_msgSend)(self, GetMsgDataSelector, session, newMessageID);
if (messageData.isSendFromSelf) {
// Fallback to origin method
[self tweak_FFToNameFavChatZZ:message sessionMsgList:sessionMsgList];
} else {
switch (WTConfigManager.sharedInstance.revokedMessageStyle) {
case WTRevokedMessageStyleClassic:
[self handleRevokedMessageIntoClassicStyleWithSession:session messageData:messageData replaceMessage:replaceMessage];
break;
case WTRevokedMessageStyleMask:
[self handleRevokedMessageIntoMaskStyleWithSession:session messageData:messageData replaceMessage:replaceMessage];
break;
default:
break;
}
}
}
- (void)handleRevokedMessageIntoClassicStyleWithSession:(NSString *)session messageData:(MessageData *)messageData replaceMessage:(NSString *)replaceMessage {
// Prepare message data
MessageData *localMessageData = messageData;
MessageData *promptMessageData = ({
MessageData *data = [[objc_getClass("MessageData") alloc] initWithMsgType:10000];
data.msgStatus = 4;
data.toUsrName = localMessageData.toUsrName;
data.fromUsrName = localMessageData.fromUsrName;
data.mesSvrID = localMessageData.mesSvrID;
data.mesLocalID = localMessageData.mesLocalID;
data.msgCreateTime = localMessageData.msgCreateTime;
data.msgContent = ({
NSString *fromUserName = [replaceMessage componentsSeparatedByString:@" "].firstObject;
NSString *userRevoke = [NSString stringWithFormat:@"%@ %@ ", fromUserName, [NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.Recalled"]];
NSString *tips = [NSString stringWithFormat:[NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.InterceptedARecalledMessage"], userRevoke];
NSMutableString *msgContent = [NSMutableString stringWithString:tips];
switch (localMessageData.messageType) {
case MessageDataTypeText: {
if (localMessageData.msgContent.length) {
if ([session rangeOfString:@"@chatroom"].location == NSNotFound) {
[msgContent appendFormat:@"\"%@\"", localMessageData.msgContent];
} else {
[msgContent appendFormat:@"\"%@\"", [localMessageData.msgContent componentsSeparatedByString:@":\n"].lastObject];
}
} else {
[msgContent appendString:[NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.AMessage"]];
}
break;
}
case MessageDataTypeImage:
[msgContent appendFormat:@"<%@>", [NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.Image"]]; break;
case MessageDataTypeVoice:
[msgContent appendFormat:@"<%@>", [NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.Voice"]]; break;
case MessageDataTypeVideo:
[msgContent appendFormat:@"<%@>", [NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.Video"]]; break;
case MessageDataTypeSticker:
[msgContent appendFormat:@"<%@>", [NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.Sticker"]]; break;
case MessageDataTypeAppUrl:
[msgContent appendFormat:@"<%@>", [NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.Link"]]; break;
default:
[msgContent appendString:[NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.AMessage"]]; break;
}
msgContent.copy;
});
data;
});
// Prepare notification information
MMServiceCenter *serviceCenter = [objc_getClass("MMServiceCenter") defaultCenter];
NSUserNotification *userNotification = [[NSUserNotification alloc] init];
BOOL isChatStatusNotifyOpen = YES;
if ([session rangeOfString:@"@chatroom"].location == NSNotFound) {
ContactStorage *contactStorage = [serviceCenter getService:objc_getClass("ContactStorage")];
WCContactData *contact = [contactStorage GetContact:session];
isChatStatusNotifyOpen = [contact isChatStatusNotifyOpen];
userNotification.informativeText = replaceMessage;
} else {
GroupStorage *groupStorage = [serviceCenter getService:objc_getClass("GroupStorage")];
WCContactData *groupContact = [groupStorage GetGroupContact:session];
isChatStatusNotifyOpen = [groupContact isChatStatusNotifyOpen];
NSString *groupName = groupContact.m_nsNickName.length ? groupContact.m_nsNickName : [NSBundle.tweakBundle localizedStringForKey:@"Tweak.Title.Group"];
userNotification.informativeText = [NSString stringWithFormat:@"%@: %@", groupName, replaceMessage];
}
// - (void)AddLocalMsg:(id)arg1 msgData:(id)arg2;
SEL addMsgSelector = NSSelectorFromString(@"AddLocalMsg:msgData:");
if ([self respondsToSelector:addMsgSelector]) {
((void (*)(id, SEL, id, id))objc_msgSend)(self, addMsgSelector, session, promptMessageData);
}
// Dispatch notification
dispatch_async(dispatch_get_main_queue(), ^{
// Deliver notification
RevokeNotificationType notificationType = [[NSUserDefaults standardUserDefaults] integerForKey:WeChatTweakPreferenceRevokeNotificationTypeKey];
if (notificationType == RevokeNotificationTypeReceiveAll || (notificationType == RevokeNotificationTypeFollow && isChatStatusNotifyOpen)) {
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:userNotification];
}
});
}
- (void)handleRevokedMessageIntoMaskStyleWithSession:(NSString *)session messageData:(MessageData *)messageData replaceMessage:(NSString *)replaceMessage {
MMRevokeMsgService *service = [[objc_getClass("MMServiceCenter") defaultCenter] getService:objc_getClass("MMRevokeMsgService")];
[service.db insertRevokeMsg:({
RevokeMsgItem *item = [[objc_getClass("RevokeMsgItem") alloc] init];
item.svrId = @(messageData.mesSvrID).stringValue;
item.createTime = UINT32_MAX;
item;
})];
WeChat *wechat = [objc_getClass("WeChat") sharedInstance];
if ([wechat respondsToSelector:NSSelectorFromString(@"chatsViewController")]) {
id chatsViewController = [wechat valueForKey:@"chatsViewController"];
if ([chatsViewController respondsToSelector:NSSelectorFromString(@"chatDetailSplitViewController")]) {
id chatDetailSplitViewController = [chatsViewController valueForKey:@"chatDetailSplitViewController"];
if ([chatDetailSplitViewController respondsToSelector:NSSelectorFromString(@"chatMessageViewController")]) {
id chatMessageViewController = [chatDetailSplitViewController valueForKey:@"chatMessageViewController"];
if ([chatMessageViewController respondsToSelector:NSSelectorFromString(@"reloadTableView")]) {
dispatch_async(dispatch_get_main_queue(), ^{
((void (*)(id, SEL))objc_msgSend)(chatMessageViewController, NSSelectorFromString(@"reloadTableView"));
});
}
}
}
}
// Prepare notification information
MMServiceCenter *serviceCenter = [objc_getClass("MMServiceCenter") defaultCenter];
NSUserNotification *userNotification = [[NSUserNotification alloc] init];
BOOL isChatStatusNotifyOpen = YES;
if ([session rangeOfString:@"@chatroom"].location == NSNotFound) {
ContactStorage *contactStorage = [serviceCenter getService:objc_getClass("ContactStorage")];
WCContactData *contact = [contactStorage GetContact:session];
isChatStatusNotifyOpen = [contact isChatStatusNotifyOpen];
userNotification.informativeText = replaceMessage;
} else {
GroupStorage *groupStorage = [serviceCenter getService:objc_getClass("GroupStorage")];
WCContactData *groupContact = [groupStorage GetGroupContact:session];
isChatStatusNotifyOpen = [groupContact isChatStatusNotifyOpen];
NSString *groupName = groupContact.m_nsNickName.length ? groupContact.m_nsNickName : [NSBundle.tweakBundle localizedStringForKey:@"Tweak.Title.Group"];
userNotification.informativeText = [NSString stringWithFormat:@"%@: %@", groupName, replaceMessage];
}
// Dispatch notification
dispatch_async(dispatch_get_main_queue(), ^{
// Deliver notification
RevokeNotificationType notificationType = [[NSUserDefaults standardUserDefaults] integerForKey:WeChatTweakPreferenceRevokeNotificationTypeKey];
if (notificationType == RevokeNotificationTypeReceiveAll || (notificationType == RevokeNotificationTypeFollow && isChatStatusNotifyOpen)) {
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:userNotification];
}
});
}
- (instancetype)tweak_initWithFrame:(NSRect)arg1 {
MMMessageCellView *view = (MMMessageCellView *)[self tweak_initWithFrame:arg1];
NSTextField *revokeTextField = [[NSTextField alloc] init];
revokeTextField.hidden = YES;
revokeTextField.editable = NO;
revokeTextField.selectable = NO;
revokeTextField.bordered = NO;
revokeTextField.drawsBackground = NO;
revokeTextField.usesSingleLineMode = YES;
revokeTextField.tag = 9527;
revokeTextField.stringValue = @"[已撤回]";
revokeTextField.font = [NSFont systemFontOfSize:10];
revokeTextField.textColor = [NSColor lightGrayColor];
[revokeTextField sizeToFit];
[view addSubview:revokeTextField];
return view;
}
- (void)tweak_populateWithMessage:(MMMessageTableItem *)tableItem {
[self tweak_populateWithMessage:tableItem];
MMRevokeMsgService *service = [[objc_getClass("MMServiceCenter") defaultCenter] getService:objc_getClass("MMRevokeMsgService")];
BOOL style = tableItem.message.messageType != MessageDataTypePrompt && [service.db getRevokeMsg:@(tableItem.message.mesSvrID).stringValue] != NULL;
[((MMMessageCellView *)self).subviews enumerateObjectsUsingBlock:^(__kindof NSView * _Nonnull view, NSUInteger index, BOOL * _Nonnull stop) {
if (view.tag != 9527) {
return ;
}
*stop = YES;
view.hidden = !style;
}];
((MMMessageCellView *)self).layer.backgroundColor = style ? [NSColor.systemYellowColor colorWithAlphaComponent:0.3].CGColor : ((MMMessageCellView *)self).layer.backgroundColor;
}
- (void)tweak_layout {
[self tweak_layout];
[((MMMessageCellView *)self).subviews enumerateObjectsUsingBlock:^(__kindof NSView * _Nonnull view, NSUInteger index, BOOL * _Nonnull stop) {
if (view.tag != 9527) {
return ;
}
*stop = YES;
view.frame = ({
NSView *avatarView = ((MMMessageCellView *)self).avatarImgView;
CGFloat x = CGRectGetMidX(avatarView.frame) - CGRectGetWidth(view.frame) / 2.0;
CGFloat y = CGRectGetMinY(avatarView.frame) - CGRectGetHeight(view.frame);
NSRect frame = NSMakeRect(x, y, CGRectGetWidth(view.frame), CGRectGetHeight(view.frame));
frame;
});
}];
}
@end

View File

@ -1,25 +0,0 @@
//
// RecallCacheManager.h
// WeChatTweak
//
// Created by Sunny Young on 2019/8/29.
// Copyright © 2019 Sunnyyoung. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <MMKV/MMKV.h>
@class MessageData;
NS_ASSUME_NONNULL_BEGIN
@interface RecallCacheManager : NSObject
+ (instancetype)sharedInstance;
+ (void)insertRevokedMessage:(MessageData *)message;
+ (BOOL)containsRevokedMessage:(MessageData *)message;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,47 +0,0 @@
//
// RecallCacheManager.m
// WeChatTweak
//
// Created by Sunny Young on 2019/8/29.
// Copyright © 2019 Sunnyyoung. All rights reserved.
//
#import "RecallCacheManager.h"
#import "WeChatTweakHeaders.h"
@interface RecallCacheManager()
@property (nonatomic, strong) MMKV *kv;
@end
@implementation RecallCacheManager
- (instancetype)init {
if (self = [super init]) {
[MMKV setLogLevel:MMKVLogNone];
_kv = [MMKV mmkvWithID:@"Recall.cache"];
}
return self;
}
+ (instancetype)sharedInstance {
static RecallCacheManager *shared;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
shared = [[RecallCacheManager alloc] init];
});
return shared;
}
+ (void)insertRevokedMessage:(MessageData *)message {
NSString *identifer = [NSString stringWithFormat:@"%ud-%lld-%ud", message.mesLocalID, message.mesSvrID, message.msgCreateTime];
[RecallCacheManager.sharedInstance.kv setBool:YES forKey:identifer];
}
+ (BOOL)containsRevokedMessage:(MessageData *)message {
NSString *identifer = [NSString stringWithFormat:@"%ud-%lld-%ud", message.mesLocalID, message.mesSvrID, message.msgCreateTime];
return [RecallCacheManager.sharedInstance.kv containsKey:identifer];
}
@end

View File

@ -11,7 +11,7 @@
#import <objc/message.h>
typedef NS_ENUM(NSUInteger, WTRevokedMessageStyle) {
WTRevokedMessageStylePlain = 0,
WTRevokedMessageStyleClassic = 0,
WTRevokedMessageStyleMask
};

View File

@ -135,6 +135,27 @@ static NSString * const WeChatTweakPreferenceRevokeNotificationTypeKey = @"WeCha
@end
@interface RevokeMsgItem : NSObject
@property (nonatomic, assign) unsigned int createTime;
@property (nonatomic, retain) NSString *svrId;
@property (nonatomic, retain) NSString *content;
@end
@interface MMRevokeMsgDB : NSObject
- (BOOL)insertRevokeMsg:(id)msg;
- (id)getRevokeMsg:(NSString *)svrId;
@end
@interface MMRevokeMsgService : NSObject
@property (nonatomic, strong) MMRevokeMsgDB *db;
@end
@protocol MASPreferencesViewController <NSObject>
@property(readonly, nonatomic) NSString *toolbarItemLabel;

View File

@ -14,7 +14,6 @@
#import "TweakPreferencesController.h"
#import "AlfredManager.h"
#import "WTConfigManager.h"
#import "RecallCacheManager.h"
// Global Function
static NSString *(*original_NSHomeDirectory)(void);
@ -53,19 +52,12 @@ static void __attribute__((constructor)) tweak(void) {
// Method Swizzling
class_addMethod(objc_getClass("AppDelegate"), @selector(applicationDockMenu:), method_getImplementation(class_getInstanceMethod(objc_getClass("AppDelegate"), @selector(tweak_applicationDockMenu:))), "@:@");
[objc_getClass("AppDelegate") jr_swizzleMethod:NSSelectorFromString(@"applicationDidFinishLaunching:") withMethod:@selector(tweak_applicationDidFinishLaunching:) error:nil];
[objc_getClass("MessageService") jr_swizzleMethod:NSSelectorFromString(@"onRevokeMsg:") withMethod:@selector(tweak_onRevokeMsg:) error:nil];
[objc_getClass("MessageService") jr_swizzleMethod:NSSelectorFromString(@"FFToNameFavChatZZ:") withMethod:@selector(tweak_onRevokeMsg:) error:nil];
[objc_getClass("MessageService") jr_swizzleMethod:NSSelectorFromString(@"FFToNameFavChatZZ:sessionMsgList:") withMethod:@selector(tweak_onRevokeMsg:sessionMessageList:) error:nil];
[objc_getClass("FFProcessReqsvrZZ") jr_swizzleMethod:NSSelectorFromString(@"FFToNameFavChatZZ:sessionMsgList:") withMethod:@selector(tweak_onRevokeMsg:sessionMessageList:) error:nil];
[objc_getClass("CUtility") jr_swizzleClassMethod:NSSelectorFromString(@"HasWechatInstance") withClassMethod:@selector(tweak_HasWechatInstance) error:nil];
[objc_getClass("CUtility") jr_swizzleClassMethod:NSSelectorFromString(@"FFSvrChatInfoMsgWithImgZZ") withClassMethod:@selector(tweak_HasWechatInstance) error:nil];
[objc_getClass("NSRunningApplication") jr_swizzleClassMethod:NSSelectorFromString(@"runningApplicationsWithBundleIdentifier:") withClassMethod:@selector(tweak_runningApplicationsWithBundleIdentifier:) error:nil];
[objc_getClass("MASPreferencesWindowController") jr_swizzleMethod:NSSelectorFromString(@"initWithViewControllers:") withMethod:@selector(tweak_initWithViewControllers:) error:nil];
[objc_getClass("MMMessageCellView") jr_swizzleMethod:NSSelectorFromString(@"contextMenu") withMethod:@selector(tweak_contextMenu) error:nil];
[objc_getClass("MMMessageCellView") jr_swizzleMethod:NSSelectorFromString(@"initWithFrame:") withMethod:@selector(tweak_initWithFrame:) error:nil];
[objc_getClass("MMMessageCellView") jr_swizzleMethod:NSSelectorFromString(@"populateWithMessage:") withMethod:@selector(tweak_populateWithMessage:) error:nil];
[objc_getClass("MMMessageCellView") jr_swizzleMethod:NSSelectorFromString(@"layout") withMethod:@selector(tweak_layout) error:nil];
objc_property_attribute_t type = { "T", "@\"NSString\"" }; // NSString
objc_property_attribute_t atom = { "N", "" }; // nonatomic
@ -78,200 +70,6 @@ static void __attribute__((constructor)) tweak(void) {
class_addMethod(objc_getClass("WCContactData"), @selector(modelPropertyWhitelist), method_getImplementation(class_getClassMethod(objc_getClass("WCContactData"), @selector(modelPropertyWhitelist))), "v@:");
}
- (instancetype)tweak_initWithFrame:(NSRect)arg1 {
MMMessageCellView *view = (MMMessageCellView *)[self tweak_initWithFrame:arg1];
NSTextField *revokeTextField = [[NSTextField alloc] init];
revokeTextField.hidden = YES;
revokeTextField.editable = NO;
revokeTextField.selectable = NO;
revokeTextField.bordered = NO;
revokeTextField.drawsBackground = NO;
revokeTextField.usesSingleLineMode = YES;
revokeTextField.tag = 9527;
revokeTextField.stringValue = @"[已撤回]";
revokeTextField.font = [NSFont systemFontOfSize:10];
revokeTextField.textColor = [NSColor lightGrayColor];
[view addSubview:revokeTextField];
return view;
}
- (void)tweak_populateWithMessage:(MMMessageTableItem *)tableItem {
[self tweak_populateWithMessage:tableItem];
BOOL style = [RecallCacheManager containsRevokedMessage:tableItem.message] && tableItem.message.messageType != MessageDataTypePrompt;
[((MMMessageCellView *)self).subviews enumerateObjectsUsingBlock:^(__kindof NSView * _Nonnull view, NSUInteger index, BOOL * _Nonnull stop) {
if (view.tag != 9527) {
return ;
}
*stop = YES;
view.hidden = !style;
}];
((MMMessageCellView *)self).layer.backgroundColor = style ? [NSColor.yellowColor colorWithAlphaComponent:0.3].CGColor : ((MMMessageCellView *)self).layer.backgroundColor;
}
- (void)tweak_layout {
[self tweak_layout];
__block NSTextField *label = nil;
[((MMMessageCellView *)self).subviews enumerateObjectsUsingBlock:^(__kindof NSView * _Nonnull view, NSUInteger index, BOOL * _Nonnull stop) {
if (view.tag != 9527) {
return ;
}
*stop = YES;
label = view;
}];
if (label == nil) {
return;
}
label.frame = ({
NSView *avatarView = ((MMMessageCellView *)self).avatarImgView;
CGFloat x = CGRectGetMidX(avatarView.frame) - CGRectGetWidth(label.frame) / 2.0;
CGFloat y = CGRectGetMinY(avatarView.frame) - CGRectGetHeight(label.frame);
NSRect fuck = [label.stringValue boundingRectWithSize:NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX) options:kNilOptions attributes:nil];
NSRect frame = NSMakeRect(x, y, CGRectGetWidth(fuck), CGRectGetHeight(fuck));
frame;
});
}
#pragma mark - No Revoke Message
- (void)tweak_onRevokeMsg:(MessageData *)message {
[self tweak_onRevokeMsg:message sessionMessageList:nil];
}
- (void)tweak_onRevokeMsg:(MessageData *)message sessionMessageList:(nullable id)sessionMessageList {
// - (id)GetMsgData:(id)arg1 svrId:(unsigned long long)arg2;
SEL GetMsgDataSelector = NSSelectorFromString(@"GetMsgData:svrId:");
if (![self respondsToSelector:GetMsgDataSelector]) {
// Fallback to origin method
return [self tweak_onRevokeMsg:message sessionMessageList:sessionMessageList];
}
// Decode message
NSString *session = [message.msgContent tweak_subStringFrom:@"<session>" to:@"</session>"];
NSUInteger newMessageID = [message.msgContent tweak_subStringFrom:@"<newmsgid>" to:@"</newmsgid>"].longLongValue;
NSString *replaceMessage = [message.msgContent tweak_subStringFrom:@"<replacemsg><![CDATA[" to:@"]]></replacemsg>"];
// Get message data
MessageData *messageData = ((id (*)(id, SEL, id, unsigned long long))objc_msgSend)(self, GetMsgDataSelector, session, newMessageID);
if (messageData.isSendFromSelf) {
// Fallback to origin method
[self tweak_onRevokeMsg:message sessionMessageList:sessionMessageList];
} else {
switch (WTConfigManager.sharedInstance.revokedMessageStyle) {
case WTRevokedMessageStylePlain:
[self handleRevokedMessageIntoClassicStyleWithSession:session messageData:messageData replaceMessage:replaceMessage];
break;
case WTRevokedMessageStyleMask:
[self handleRevokedMessageIntoMaskStyleWithSession:session messageData:messageData replaceMessage:replaceMessage];
break;
default:
break;
}
}
}
- (void)handleRevokedMessageIntoClassicStyleWithSession:(NSString *)session messageData:(MessageData *)messageData replaceMessage:(NSString *)replaceMessage {
// Prepare message data
MessageData *localMessageData = messageData;
MessageData *promptMessageData = ({
MessageData *data = [[objc_getClass("MessageData") alloc] initWithMsgType:10000];
data.msgStatus = 4;
data.toUsrName = localMessageData.toUsrName;
data.fromUsrName = localMessageData.fromUsrName;
data.mesSvrID = localMessageData.mesSvrID;
data.mesLocalID = localMessageData.mesLocalID;
data.msgCreateTime = localMessageData.msgCreateTime;
data.msgContent = ({
NSString *fromUserName = [replaceMessage componentsSeparatedByString:@" "].firstObject;
NSString *userRevoke = [NSString stringWithFormat:@"%@ %@ ", fromUserName, [NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.Recalled"]];
NSString *tips = [NSString stringWithFormat:[NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.InterceptedARecalledMessage"], userRevoke];
NSMutableString *msgContent = [NSMutableString stringWithString:tips];
switch (localMessageData.messageType) {
case MessageDataTypeText: {
if (localMessageData.msgContent.length) {
if ([session rangeOfString:@"@chatroom"].location == NSNotFound) {
[msgContent appendFormat:@"\"%@\"", localMessageData.msgContent];
} else {
[msgContent appendFormat:@"\"%@\"", [localMessageData.msgContent componentsSeparatedByString:@":\n"].lastObject];
}
} else {
[msgContent appendString:[NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.AMessage"]];
}
break;
}
case MessageDataTypeImage:
[msgContent appendFormat:@"<%@>", [NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.Image"]]; break;
case MessageDataTypeVoice:
[msgContent appendFormat:@"<%@>", [NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.Voice"]]; break;
case MessageDataTypeVideo:
[msgContent appendFormat:@"<%@>", [NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.Video"]]; break;
case MessageDataTypeSticker:
[msgContent appendFormat:@"<%@>", [NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.Sticker"]]; break;
case MessageDataTypeAppUrl:
[msgContent appendFormat:@"<%@>", [NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.Link"]]; break;
default:
[msgContent appendString:[NSBundle.tweakBundle localizedStringForKey:@"Tweak.Message.AMessage"]]; break;
}
msgContent.copy;
});
data;
});
// Prepare notification information
MMServiceCenter *serviceCenter = [objc_getClass("MMServiceCenter") defaultCenter];
NSUserNotification *userNotification = [[NSUserNotification alloc] init];
BOOL isChatStatusNotifyOpen = YES;
if ([session rangeOfString:@"@chatroom"].location == NSNotFound) {
ContactStorage *contactStorage = [serviceCenter getService:objc_getClass("ContactStorage")];
WCContactData *contact = [contactStorage GetContact:session];
isChatStatusNotifyOpen = [contact isChatStatusNotifyOpen];
userNotification.informativeText = replaceMessage;
} else {
GroupStorage *groupStorage = [serviceCenter getService:objc_getClass("GroupStorage")];
WCContactData *groupContact = [groupStorage GetGroupContact:session];
isChatStatusNotifyOpen = [groupContact isChatStatusNotifyOpen];
NSString *groupName = groupContact.m_nsNickName.length ? groupContact.m_nsNickName : [NSBundle.tweakBundle localizedStringForKey:@"Tweak.Title.Group"];
userNotification.informativeText = [NSString stringWithFormat:@"%@: %@", groupName, replaceMessage];
}
// - (void)AddLocalMsg:(id)arg1 msgData:(id)arg2;
SEL addMsgSelector = NSSelectorFromString(@"AddLocalMsg:msgData:");
if ([self respondsToSelector:addMsgSelector]) {
((void (*)(id, SEL, id, id))objc_msgSend)(self, addMsgSelector, session, promptMessageData);
}
// Dispatch notification
dispatch_async(dispatch_get_main_queue(), ^{
// Deliver notification
RevokeNotificationType notificationType = [[NSUserDefaults standardUserDefaults] integerForKey:WeChatTweakPreferenceRevokeNotificationTypeKey];
if (notificationType == RevokeNotificationTypeReceiveAll || (notificationType == RevokeNotificationTypeFollow && isChatStatusNotifyOpen)) {
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:userNotification];
}
});
}
- (void)handleRevokedMessageIntoMaskStyleWithSession:(NSString *)session messageData:(MessageData *)messageData replaceMessage:(NSString *)replaceMessage {
[RecallCacheManager insertRevokedMessage:messageData];
// Prepare notification information
MMServiceCenter *serviceCenter = [objc_getClass("MMServiceCenter") defaultCenter];
NSUserNotification *userNotification = [[NSUserNotification alloc] init];
BOOL isChatStatusNotifyOpen = YES;
if ([session rangeOfString:@"@chatroom"].location == NSNotFound) {
ContactStorage *contactStorage = [serviceCenter getService:objc_getClass("ContactStorage")];
WCContactData *contact = [contactStorage GetContact:session];
isChatStatusNotifyOpen = [contact isChatStatusNotifyOpen];
userNotification.informativeText = replaceMessage;
} else {
GroupStorage *groupStorage = [serviceCenter getService:objc_getClass("GroupStorage")];
WCContactData *groupContact = [groupStorage GetGroupContact:session];
isChatStatusNotifyOpen = [groupContact isChatStatusNotifyOpen];
NSString *groupName = groupContact.m_nsNickName.length ? groupContact.m_nsNickName : [NSBundle.tweakBundle localizedStringForKey:@"Tweak.Title.Group"];
userNotification.informativeText = [NSString stringWithFormat:@"%@: %@", groupName, replaceMessage];
}
// Dispatch notification
dispatch_async(dispatch_get_main_queue(), ^{
// Deliver notification
RevokeNotificationType notificationType = [[NSUserDefaults standardUserDefaults] integerForKey:WeChatTweakPreferenceRevokeNotificationTypeKey];
if (notificationType == RevokeNotificationTypeReceiveAll || (notificationType == RevokeNotificationTypeFollow && isChatStatusNotifyOpen)) {
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:userNotification];
}
});
}
#pragma mark - AppUrlMessageMenu
- (id)tweak_contextMenu {