diff --git a/README-Chinese.md b/README-Chinese.md index aa99594..8c4e06e 100644 --- a/README-Chinese.md +++ b/README-Chinese.md @@ -10,25 +10,29 @@ ![](Screenshot/0x01.png) ![](Screenshot/0x02.png) +![](Screenshot/0x03.png) ## 功能 - 阻止消息撤回 + - 消息列表通知 + - 系统通知 + - 正常撤回自己发出的消息 - 客户端无限多开 - 右键 Dock Icon 登录新的微信账号 - 命令行执行:`open -n /Applications/WeChat.app` -- 阻止退出登录(重新打开应用无需手机认证) +- 重新打开应用无需手机认证 ## 使用 -- `sudo make install` 安装动态库 +- `sudo make install` 安装动态库 - `sudo make uninstall` 卸载动态库 ## 开发调试 **Requirement: Command Line Tools** -运行命令:`xcode-select --install` 安装 Command Line Tools。 +运行命令:`xcode-select --install` 安装 Command Line Tools - `make build` 编译 dylib 动态库到当前目录下 - `make debug` 编译 dylib 动态库并临时注入微信 macOS 客户端 diff --git a/README.md b/README.md index 8c32e56..ee708e3 100644 --- a/README.md +++ b/README.md @@ -10,25 +10,29 @@ A dynamic library tweak for WeChat macOS. ![](Screenshot/0x01.png) ![](Screenshot/0x02.png) +![](Screenshot/0x03.png) ## Feature - Prevent message revoked + - Message list notification + - System notification + - Revoke message you sent - Multiple WeChat Instance - Right click on the Dock icon to login another WeChat account - Run command: `open -n /Applications/WeChat.app` -- Prevent logout (Auto login without authority) +- Auto login without authentication ## Usage -- `sudo make install` Inject the dylib to `WeChat` -- `sudo make uninstall` Uninstall the injection +- `sudo make install` Inject the dylib +- `sudo make uninstall` Uninstall the dylib ## Development **Requirement: Command Line Tools** -Run `xcode-select --install` to install Command Line Tools. +Run `xcode-select --install` to install Command Line Tools - `make build` Build the dylib file to the same dicrectory - `make debug` Build the dylib file and run `WeChat` with dynamic injection diff --git a/Screenshot/0x01.png b/Screenshot/0x01.png index e40a0b0..02d823e 100644 Binary files a/Screenshot/0x01.png and b/Screenshot/0x01.png differ diff --git a/Screenshot/0x02.png b/Screenshot/0x02.png index 2369c46..552d84c 100644 Binary files a/Screenshot/0x02.png and b/Screenshot/0x02.png differ diff --git a/Screenshot/0x03.png b/Screenshot/0x03.png new file mode 100644 index 0000000..c650cfa Binary files /dev/null and b/Screenshot/0x03.png differ diff --git a/WeChatTweak.dylib b/WeChatTweak.dylib index 8c0b7fa..0bac113 100755 Binary files a/WeChatTweak.dylib and b/WeChatTweak.dylib differ diff --git a/WeChatTweak.m b/WeChatTweak.m index 12bae71..a25ce5d 100755 --- a/WeChatTweak.m +++ b/WeChatTweak.m @@ -3,30 +3,94 @@ #import #import #import "JRSwizzle.h" +#import "WeChatTweakHeaders.h" + +@implementation NSString (WeChatTweak) + +- (NSString *)tweak_sessionFromMessage { + NSRange begin = [self rangeOfString:@""]; + NSRange end = [self rangeOfString:@""]; + NSRange range = NSMakeRange(begin.location + begin.length,end.location - begin.location - begin.length); + return [self substringWithRange:range]; +} + +- (NSUInteger)tweak_newMessageIDFromMessage { + NSRange begin = [self rangeOfString:@""]; + NSRange end = [self rangeOfString:@""]; + NSRange range = NSMakeRange(begin.location + begin.length,end.location - begin.location - begin.length); + return [[self substringWithRange:range] longLongValue]; +} + +- (NSString *)tweak_replaceMessageFromMessage { + NSRange begin = [self rangeOfString:@""]; + NSRange range = NSMakeRange(begin.location + begin.length,end.location - begin.location - begin.length); + return [self substringWithRange:range]; +} + +@end @implementation NSObject (WeChatTweak) #pragma mark - Constructor static void __attribute__((constructor)) tweak(void) { - [objc_getClass("MessageService") jr_swizzleMethod:NSSelectorFromString(@"onRevokeMsg:") withMethod:@selector(tweak_onRevokeMsg:) error:nil]; - [objc_getClass("CUtility") jr_swizzleClassMethod:NSSelectorFromString(@"HasWechatInstance") withClassMethod:@selector(tweak_HasWechatInstance) error:nil]; - class_addMethod(objc_getClass("AppDelegate"), @selector(applicationDockMenu:), method_getImplementation(class_getInstanceMethod(objc_getClass("AppDelegate"), @selector(tweak_applicationDockMenu:))), "@:@"); + 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("AppDelegate") jr_swizzleMethod:NSSelectorFromString(@"applicationShouldTerminate:") withMethod:@selector(tweak_applicationShouldTerminate:) error:nil]; + [objc_getClass("MessageService") jr_swizzleMethod:NSSelectorFromString(@"onRevokeMsg:") withMethod:@selector(tweak_onRevokeMsg:) error:nil]; + [objc_getClass("CUtility") jr_swizzleClassMethod:NSSelectorFromString(@"HasWechatInstance") withClassMethod:@selector(tweak_HasWechatInstance) error:nil]; } #pragma mark - No Revoke Message - (void)tweak_onRevokeMsg:(NSString *)message { - NSRange begin = [message rangeOfString:@""]; - NSRange subRange = NSMakeRange(begin.location + begin.length,end.location - begin.location - begin.length); - NSString *informativeText = [message substringWithRange:subRange]; + // Decode message + NSString *session = [message tweak_sessionFromMessage]; + NSUInteger newMessageID = [message tweak_newMessageIDFromMessage]; + NSString *replaceMessage = [message tweak_replaceMessageFromMessage]; + + // Prepare message data + MessageData *localMessageData = [((MessageService *)self) GetMsgData:session svrId:newMessageID]; + MessageData *promptMessageData = ({ + MessageData *data = [[objc_getClass("MessageData") alloc] init]; + data.messageType = 10000; + data.msgStatus = 4; + data.toUsrName = localMessageData.toUsrName; + data.fromUsrName = localMessageData.fromUsrName; + data.msgCreateTime = localMessageData.msgCreateTime; + if ([localMessageData isSendFromSelf]) { + data.mesLocalID = localMessageData.mesLocalID; + data.msgContent = replaceMessage; + } else { + data.mesLocalID = localMessageData.mesLocalID + 1; + data.msgContent = [NSString stringWithFormat:@"[已拦截]\n%@", replaceMessage]; + } + data; + }); + + // Prepare notification information + NSUserNotification *userNotification = [[NSUserNotification alloc] init]; + if ([session rangeOfString:@"@chatroom"].location == NSNotFound) { + userNotification.informativeText = replaceMessage; + } else { + GroupStorage *groupStorage = [[objc_getClass("MMServiceCenter") defaultCenter] getService:objc_getClass("GroupStorage")]; + WCContactData *groupContact = [groupStorage GetGroupContact: session]; + NSString *groupName = groupContact.m_nsNickName.length ? groupContact.m_nsNickName : @"群组"; + userNotification.informativeText = [NSString stringWithFormat:@"%@: %@", groupName, replaceMessage]; + } + + // Dispatch notification dispatch_async(dispatch_get_main_queue(), ^{ - NSUserNotification *userNotification = [[NSUserNotification alloc] init]; - userNotification.informativeText = informativeText; - [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:userNotification]; + // Delete message if is revoke from myself + if ([localMessageData isSendFromSelf]) { + [((MessageService *)self) DelMsg:session msgList:@[localMessageData] isDelAll:NO isManual:YES]; + [((MessageService *)self) AddRevokePromptMsg:session msgData: promptMessageData]; + } else { + [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:userNotification]; + [((MessageService *)self) AddRevokePromptMsg:session msgData: promptMessageData]; + [((MessageService *)self) notifyAddMsgOnMainThread:session msgData: promptMessageData]; + } }); } @@ -51,7 +115,7 @@ static void __attribute__((constructor)) tweak(void) { [task launch]; } -#pragma mark - No Logout +#pragma mark - Auto auth - (void)tweak_applicationDidFinishLaunching:(NSNotification *)notification { [self tweak_applicationDidFinishLaunching:notification]; diff --git a/WeChatTweakHeaders.h b/WeChatTweakHeaders.h new file mode 100644 index 0000000..ef987db --- /dev/null +++ b/WeChatTweakHeaders.h @@ -0,0 +1,44 @@ +#import +#import + +@interface MessageData: NSObject + +@property(nonatomic) unsigned int messageType; +@property(nonatomic) unsigned int msgStatus; +@property(retain, nonatomic) NSString *toUsrName; +@property(retain, nonatomic) NSString *fromUsrName; +@property(retain, nonatomic) NSString *msgContent; +@property(nonatomic) unsigned int msgCreateTime; +@property(nonatomic) unsigned int mesLocalID; + +- (BOOL)isSendFromSelf; + +@end + +@interface WCContactData : NSObject + +@property(retain, nonatomic) NSString *m_nsNickName; + +@end + +@interface GroupStorage: NSObject + +- (id)GetGroupContact:(id)arg1; + +@end + +@interface MMServiceCenter : NSObject + ++ (id)defaultCenter; +- (id)getService:(Class)arg1; + +@end + +@interface MessageService: NSObject + +- (id)GetMsgData:(id)arg1 svrId:(unsigned long long)arg2; +- (void)DelMsg:(id)arg1 msgList:(id)arg2 isDelAll:(BOOL)arg3 isManual:(BOOL)arg4; +- (void)AddRevokePromptMsg:(id)arg1 msgData:(id)arg2; +- (void)notifyAddMsgOnMainThread:(id)arg1 msgData:(id)arg2; + +@end