diff --git a/Podfile b/Podfile index 61136f4..96728b8 100644 --- a/Podfile +++ b/Podfile @@ -5,4 +5,5 @@ target 'WeChatTweak' do pod 'JRSwizzle' pod 'GCDWebServer' pod 'YYModel' + pod 'MMKV' end diff --git a/Podfile.lock b/Podfile.lock index 0567d3f..852a460 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -3,24 +3,28 @@ PODS: - GCDWebServer/Core (= 3.5.3) - GCDWebServer/Core (3.5.3) - JRSwizzle (1.0) + - MMKV (1.0.22) - YYModel (1.0.4) DEPENDENCIES: - GCDWebServer - JRSwizzle + - MMKV - YYModel SPEC REPOS: https://github.com/cocoapods/specs.git: - GCDWebServer - JRSwizzle + - MMKV - YYModel SPEC CHECKSUMS: GCDWebServer: c0ab22c73e1b84f358d1e2f74bf6afd1c60829f2 JRSwizzle: dd5ead5d913a0f29e7f558200165849f006bb1e3 + MMKV: 99b05c376c0eb412b468ba1d3e6813502edeaa24 YYModel: 2a7fdd96aaa4b86a824e26d0c517de8928c04b30 -PODFILE CHECKSUM: af44d62b300e2c55cb63386ec4be3227b93c7761 +PODFILE CHECKSUM: fb1b1e412c5f88813595570bd6f951a741cda575 COCOAPODS: 1.7.5 diff --git a/WeChatTweak.xcodeproj/project.pbxproj b/WeChatTweak.xcodeproj/project.pbxproj index 8f089c3..7c6a994 100644 --- a/WeChatTweak.xcodeproj/project.pbxproj +++ b/WeChatTweak.xcodeproj/project.pbxproj @@ -17,6 +17,8 @@ 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 */; }; @@ -45,6 +47,8 @@ 7D9049F81F82B6FB004E6370 /* NSString+WeChatTweak.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSString+WeChatTweak.m"; sourceTree = ""; }; 7DB8AFBC206211D900E683AE /* WTConfigManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WTConfigManager.h; sourceTree = ""; }; 7DB8AFBD206211D900E683AE /* WTConfigManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WTConfigManager.m; sourceTree = ""; }; + 7DBA4465231812AB004CE2DB /* RecallCacheManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RecallCacheManager.h; sourceTree = ""; }; + 7DBA4466231812AB004CE2DB /* RecallCacheManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RecallCacheManager.m; sourceTree = ""; }; 7DF842271F40583F00D42D79 /* WeChatTweak.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = WeChatTweak.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7DF8422A1F40583F00D42D79 /* WeChatTweak.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WeChatTweak.h; sourceTree = ""; }; 7DF8422B1F40583F00D42D79 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -86,6 +90,8 @@ 7D14E5A31F6447DB00D75132 /* AlfredManager.m */, 7DB8AFBC206211D900E683AE /* WTConfigManager.h */, 7DB8AFBD206211D900E683AE /* WTConfigManager.m */, + 7DBA4465231812AB004CE2DB /* RecallCacheManager.h */, + 7DBA4466231812AB004CE2DB /* RecallCacheManager.m */, ); path = Manager; sourceTree = ""; @@ -187,6 +193,7 @@ 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 */, @@ -241,6 +248,7 @@ developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( + English, en, Base, "zh-Hans", @@ -316,6 +324,7 @@ 7D14E5A51F6447DB00D75132 /* AlfredManager.m in Sources */, 7DF842531F4058C600D42D79 /* TweakPreferencesController.m in Sources */, 7D9049F91F82B6FB004E6370 /* NSString+WeChatTweak.m in Sources */, + 7DBA4468231812AB004CE2DB /* RecallCacheManager.m in Sources */, 7DF842341F4058AB00D42D79 /* WeChatTweak.m in Sources */, 7DB8AFBF206211D900E683AE /* WTConfigManager.m in Sources */, 7D9049F51F82A41A004E6370 /* fishhook.c in Sources */, diff --git a/WeChatTweak/Manager/RecallCacheManager.h b/WeChatTweak/Manager/RecallCacheManager.h new file mode 100644 index 0000000..e023e93 --- /dev/null +++ b/WeChatTweak/Manager/RecallCacheManager.h @@ -0,0 +1,23 @@ +// +// RecallCacheManager.h +// WeChatTweak +// +// Created by Sunny Young on 2019/8/29. +// Copyright © 2019 Sunnyyoung. All rights reserved. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RecallCacheManager : NSObject + ++ (instancetype)sharedInstance; + ++ (void)insertRevokedMessageID:(long long)messageID; ++ (BOOL)containsRevokedMessageID:(long long)messageID; + +@end + +NS_ASSUME_NONNULL_END diff --git a/WeChatTweak/Manager/RecallCacheManager.m b/WeChatTweak/Manager/RecallCacheManager.m new file mode 100644 index 0000000..4c435a7 --- /dev/null +++ b/WeChatTweak/Manager/RecallCacheManager.m @@ -0,0 +1,44 @@ +// +// RecallCacheManager.m +// WeChatTweak +// +// Created by Sunny Young on 2019/8/29. +// Copyright © 2019 Sunnyyoung. All rights reserved. +// + +#import "RecallCacheManager.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)insertRevokedMessageID:(long long)messageID { + [RecallCacheManager.sharedInstance.kv setBool:YES forKey:@(messageID).stringValue]; +} + ++ (BOOL)containsRevokedMessageID:(long long)messageID { + return [RecallCacheManager.sharedInstance.kv containsKey:@(messageID).stringValue]; +} + +@end diff --git a/WeChatTweak/Supporting Files/WeChatTweakHeaders.h b/WeChatTweak/Supporting Files/WeChatTweakHeaders.h index 32b89c9..a5cfbab 100644 --- a/WeChatTweak/Supporting Files/WeChatTweakHeaders.h +++ b/WeChatTweak/Supporting Files/WeChatTweakHeaders.h @@ -64,6 +64,7 @@ typedef NS_ENUM(unsigned int, MessageDataType) { - (instancetype)initWithMsgType:(long long)arg1; - (BOOL)isSendFromSelf; +- (id)getChatNameForCurMsg; @end @@ -111,7 +112,8 @@ typedef NS_ENUM(unsigned int, MessageDataType) { - (id)GetMsgData:(id)arg1 svrId:(unsigned long long)arg2; - (void)DelMsg:(id)arg1 msgList:(id)arg2 isDelAll:(BOOL)arg3 isManual:(BOOL)arg4; - (void)AddLocalMsg:(id)arg1 msgData:(id)arg2; -- (void)notifyAddMsgOnMainThread:(id)arg1 msgData:(id)arg2; +- (void)notifyDelMsgOnMainThread:(id)arg1 msgData:(id)arg2; +- (void)notifyAddRevokePromptMsgOnMainThread:(id)arg1 msgData:(id)arg2; @end diff --git a/WeChatTweak/WeChatTweak.m b/WeChatTweak/WeChatTweak.m index 6b3b6a9..6d67c6d 100755 --- a/WeChatTweak/WeChatTweak.m +++ b/WeChatTweak/WeChatTweak.m @@ -14,6 +14,7 @@ #import "TweakPreferencesController.h" #import "AlfredManager.h" #import "WTConfigManager.h" +#import "RecallCacheManager.h" // Global Function static NSString *(*original_NSHomeDirectory)(void); @@ -63,8 +64,12 @@ static void __attribute__((constructor)) tweak(void) { [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 objc_property_attribute_t ownership = { "&", "" }; // C = copy & = strong @@ -76,6 +81,59 @@ 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 containsRevokedMessageID:tableItem.message.mesSvrID] && 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 { @@ -83,54 +141,10 @@ static void __attribute__((constructor)) tweak(void) { NSString *session = [message.msgContent tweak_subStringFrom:@"" to:@""]; NSUInteger newMessageID = [message.msgContent tweak_subStringFrom:@"" to:@""].longLongValue; NSString *replaceMessage = [message.msgContent tweak_subStringFrom:@""]; - - // Prepare message data - MessageData *localMessageData = [((MessageService *)self) GetMsgData:session svrId:newMessageID]; - 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; - if ([localMessageData isSendFromSelf]) { - data.msgContent = replaceMessage; - } else { - 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; - } - data.msgContent = msgContent; - } - data; - }); + + // Get message data + MessageData *messageData = [((MessageService *)self) GetMsgData:session svrId:newMessageID]; + [RecallCacheManager insertRevokedMessageID:messageData.mesSvrID]; // Prepare notification information MMServiceCenter *serviceCenter = [objc_getClass("MMServiceCenter") defaultCenter]; @@ -149,21 +163,31 @@ static void __attribute__((constructor)) tweak(void) { userNotification.informativeText = [NSString stringWithFormat:@"%@: %@", groupName, replaceMessage]; } - // Delete message if it is revoke from myself - if ([localMessageData isSendFromSelf]) { - [((MessageService *)self) DelMsg:session msgList:@[localMessageData] isDelAll:NO isManual:YES]; + if ([messageData isSendFromSelf]) { + MessageData *promptMessageData = ({ + MessageData *data = [[objc_getClass("MessageData") alloc] initWithMsgType:MessageDataTypePrompt]; + data.msgStatus = 4; + data.toUsrName = messageData.toUsrName; + data.fromUsrName = messageData.fromUsrName; + data.mesSvrID = messageData.mesSvrID; + data.mesLocalID = messageData.mesLocalID; + data.msgCreateTime = messageData.msgCreateTime; + data.msgContent = replaceMessage; + data; + }); + // Delete message if it is revoke from myself + [((MessageService *)self) DelMsg:session msgList:@[messageData] isDelAll:NO isManual:YES]; [((MessageService *)self) AddLocalMsg:session msgData:promptMessageData]; } else { - if (localMessageData.messageType == MessageDataTypeText) { - [((MessageService *)self) DelMsg:session msgList:@[localMessageData] isDelAll:NO isManual:YES]; - } - [((MessageService *)self) AddLocalMsg:session msgData:promptMessageData]; + // Invoke message reloading + [((MessageService *)self) notifyDelMsgOnMainThread:messageData.getChatNameForCurMsg msgData:messageData]; + [((MessageService *)self) notifyAddRevokePromptMsgOnMainThread:messageData.getChatNameForCurMsg msgData:messageData]; } - + // Dispatch notification dispatch_async(dispatch_get_main_queue(), ^{ // Deliver notification - if (![localMessageData isSendFromSelf]) { + if (![messageData isSendFromSelf]) { RevokeNotificationType notificationType = [[NSUserDefaults standardUserDefaults] integerForKey:WeChatTweakPreferenceRevokeNotificationTypeKey]; if (notificationType == RevokeNotificationTypeReceiveAll || (notificationType == RevokeNotificationTypeFollow && isChatStatusNotifyOpen)) { [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:userNotification];