前言
Just do it.
本文主要描写这两个功能的开发过程中,自己都经历过哪些阶段,以及如何一步步从一开始都不会做MacOS逆向开发,到了解别人的项目,再到实现自己想要的功能。再加上当时正在看的一本书中,描述的内容在这次开发中得到了体现,于是想记录下来,给这段难得的经历留个纪念,也为那些在踏出第一步前迷茫的各位,点亮一盏烛火。Just do it。
插件功能基于WeChatPlugin-MacOS,作者TK。
主要经历
无从下手
在想要实现该功能时,TK已经移除了该项目,然后发现MustangYM在继续维护这个项目。这个时候的我以前翻过两页《Objective-C基础教程》,以及有一些Java和前端开发的相关经验,但是对于MacOS逆向,以及如何写OBJC的代码完全没有头绪,找到MustangYM的时候,我还给他发了一封邮件,询问是否能够提供一些指导方向,那个时候是7月16号。
柳暗花明
在给MustangYM发了邮件一段时间以后,并没有得到回信。有一天跟同事说到了这个东西,在帮他弄的过程中,突然发现了TK的项目文件中有写如何进行编译,然后根据描述试着去跑起来项目,中间在pod install也因为各种情况遇到了一些问题,所幸最后是将项目启动了起来,并且能够进入断点进行调试。
功能开发
在可以进行调试以后,又过去了那么一段时间,这段时间苦于看不懂OBJC的代码,也不会用Xcode,然后我又搁置了一段时间。在给自己开发另外一个项目的时候,需要对接印象笔记,但是JavaScript SDK一直401,在头疼SDK对接不成功的过程中,发现开发文档中有Mac本地API,打开发现使用的是AppleScript脚本。
在使用WeChatPlugin这个插件时,就很好奇为什么他能通过给自己发消息实现远程控制。在这个时候有扒过源码,了解到是通过AppleScript来控制Mac上的应用的,并且使用AppleScript做了一个简单的控制网易云播放和切歌的Workflow,那么看到印象笔记支持AppleScript时,就来了兴趣,于是就写了脚本实现了自己项目的一个功能。
功能完成以后,就开始捣鼓AppleScript,发现微信并没有提供字典,我就尝试着进行了几个测试,看是否能通过特定的键盘操作实现找到好友,并给好友发送消息,接着尝试使用脚本来控制微信发消息,结果是成功的,相关脚本如下。
-- 搜索好友,并发送指定消息
tell application "WeChat" to activate
tell application "System Events"
key code 3 using {command down}
keystroke "会话名称"
delay 1
key code 36
key code 49 using {control down}
delay 2
keystroke "消息"
key code 49
key code 36
end tell
联想到远程控制的实现方式,我就想是不是也能通过触发脚本的方式来实现给指定好友发消息的功能,于是就开始了新一轮的折腾。
开发消息转发功能
在一步步查看远程控制的代码的过程中,发现都会经过下面这个方法,然后我看懂了autoReplyWithMsg,通过调试发现接收消息是都会经过消息同步,并且在自动回复中有一个replyWithMsg方法,看作用应该是发送消息用的,于是我就忘记了远程控制,开始了折腾收到消息时,转发给好友里的某个用户。
/**
hook 微信消息同步
*/
- (void)hook_OnSyncBatchAddMsgs:(NSArray *)msgs isFirstSync:(BOOL)arg2 {
// 其他一些代码
[self autoReplyWithMsg:addMsg];
if ([addMsg.fromUserName.string isEqualToString:currentUserName]
&& [addMsg.toUserName.string isEqualToString:currentUserName]) {
[self remoteControlWithMsg:addMsg];
[self replySelfWithMsg:addMsg];
}
}
然后分析autoReplyWithMsg这个方法,一眼望过去根本不想去了解每一行都是干嘛的,就觉得“我看不懂啊,这都是什么玩意,My god,救救我吧”。
/**
自动回复
@param addMsg 接收的消息
*/
- (void)autoReplyWithMsg:(AddMsg *)addMsg {
// addMsg.msgType != 49
if (![[TKWeChatPluginConfig sharedConfig] autoReplyEnable]) return;
if (addMsg.msgType != 1 && addMsg.msgType != 3) return;
NSString *userName = addMsg.fromUserName.string;
MMSessionMgr *sessionMgr = [[objc_getClass("MMServiceCenter") defaultCenter] getService:objc_getClass("MMSessionMgr")];
WCContactData *msgContact = [sessionMgr getContact:userName];
if ([msgContact isBrandContact] || [msgContact isSelf]) {
// 该消息为公众号或者本人发送的消息
return;
}
NSArray *autoReplyModels = [[TKWeChatPluginConfig sharedConfig] autoReplyModels];
[autoReplyModels enumerateObjectsUsingBlock:^(TKAutoReplyModel *model, NSUInteger idx, BOOL * _Nonnull stop) {
if (!model.enable) return;
if (!model.replyContent || model.replyContent.length == 0) return;
if (model.enableSpecificReply) {
if ([model.specificContacts containsObject:userName]) {
[self replyWithMsg:addMsg model:model];
}
return;
}
if ([addMsg.fromUserName.string containsString:@"@chatroom"] && !model.enableGroupReply) return;
if (![addMsg.fromUserName.string containsString:@"@chatroom"] && !model.enableSingleReply) return;
[self replyWithMsg:addMsg model:model];
}];
}
可是在看消息同步的方法时,留下了一个想法,那就是看不懂某个编程语言时,看if准没错,于是我就去看了最后几行if以及里面的相关代码,不知道model是干嘛的,一番折腾后发现是引入的一个文件,看到下面这些文件里的内容,打开自动回复的设置界面对比了一下,想着“嗯,这应该就是自动回复的相关配置了。”
#import "TKBaseModel.h"
@interface TKAutoReplyModel : TKBaseModel
@property (nonatomic, assign) BOOL enable; /**< 是否开启自动回复 */
@property (nonatomic, copy) NSString *keyword; /**< 自动回复关键字 */
@property (nonatomic, copy) NSString *replyContent; /**< 自动回复的内容 */
@property (nonatomic, assign) BOOL enableGroupReply; /**< 是否开启群聊自动回复 */
@property (nonatomic, assign) BOOL enableSingleReply; /**< 是否开启私聊自动回复 */
@property (nonatomic, assign) BOOL enableRegex; /**< 是否开启正则匹配 */
@property (nonatomic, assign) BOOL enableDelay; /**< 是否开启延迟回复 */
@property (nonatomic, assign) NSInteger delayTime; /**< 延迟时间 */
@property (nonatomic, assign) BOOL enableSpecificReply; /**< 是否开启特定回复 */
@property (nonatomic, strong) NSArray *specificContacts; /**< 特定回复的联系人 */
- (BOOL)hasEmptyKeywordOrReplyContent;
@end
明白了作用,再加上代码的自描述,知道这一段应该就是跟特定联系人回复的相关代码,下一步->添加特定联系人,开始测试。(其实我到现在都不记得下面这几行代码写了哪几个字母🙄,在里面加代码就完事了️)
if (model.enableSpecificReply) {
if ([model.specificContacts containsObject:userName]) {
[self replyWithMsg:addMsg model:model];
}
return;
}
后面的过程就是扒replyWithMsg这个方法,找到[[TKMessageManager shareManager] sendTextMessage:randomReplyContent toUsrName:addMsg.fromUserName.string delay:delayTime];这行代码是发送消息的,有样学样,写一个转发的方法出来。字符串怎么拼接?不会,谷歌“obejctive-c 字符串拼接”。用户名怎么取?不会,找个相同的代码,抄过来。完成,转发消息的方法就出来了。
- (void)replyToMySelf:(AddMsg *)addMsg model:(TKAutoReplyModel *)model {
NSString* msgContent;
NSString* str1;
MMSessionMgr *sessionMgr = [[objc_getClass("MMServiceCenter") defaultCenter] getService:objc_getClass("MMSessionMgr")];
WCContactData *msgContact = [sessionMgr getContact:addMsg.fromUserName.string];
// 格式化消息,来源用户,分隔符,消息内容
str1 = [msgContact.m_nsNickName stringByAppendingString:@"\n---\n"];
msgContent = [str1 stringByAppendingString:addMsg.content.string];
NSInteger delayTime = 0;
// 将格式化好的消息转发给username这个微信号
// 我的本地代码中,写死了一个微信账号,这里使用username替代了
[[TKMessageManager shareManager] sendTextMessage:msgContent toUsrName:@"username" delay:delayTime];
// 将来信用户的微信号发送给username,用于给指定好友发消息的时候使用
[[TKMessageManager shareManager] sendTextMessage:addMsg.fromUserName.string toUsrName:@"username" delay:delayTime];
}
开发给指定好友发消息的功能
经过这么一番折腾,WeChatPlugin的代码,开始眼熟了起来。在上面消息同步的方法中,加入下面代码,OK,完成。
if ([addMsg.fromUserName.string isEqualToString:@"username"]
&& [addMsg.toUserName.string isEqualToString:currentUserName]) {
NSString *content = addMsg.content.string;
if ([content hasPrefix:@"Reply:"]) {
NSArray *list = [content componentsSeparatedByString: @":"];
if ([list count] == 3) {
NSString *toUserName = list[1];
NSString *toContent = list[2];
[[TKMessageManager shareManager] sendTextMessage:toContent toUsrName:toUserName delay:0];
}
} else {
[self remoteControlWithMsg:addMsg];
}
}
结语
在实现这两个功能的过程中,感受到生活中的每个点,都是如何相互影响的,也体会到书中的内容,又是如何在自己身上获得印证的,也收获了用自己所学实现自己想法的喜悦。感谢TK大佬的封装,让我能这么简单便实现我想要的功能。代码并不完美,但能满足我现在的需求,并且经过这次开发,在自己捣鼓软件上,又前进了一步,不完美的代码可以在以后不断完善。
太过追求完美,会阻止你迈出前进的步伐。
不苛求完美,会阻止你不断成长的步伐。
共勉。