給微信聊天記錄添加截圖功能

有時候,知識小集羣裏討論的技術問題,比較有價值,咱們會把有價值的內容整理出來供你們查閱。但爲了保護羣友隱私,須要把暱稱和頭像都打碼,若是碰到幾百條聊天記錄,這樣作簡直要吐血。並且也不能截一張長圖,只能一張一張截取,而後拼接起來。羣聊記錄只能在微信內分享,這也限制了傳播的渠道。爲了提升小集成員工做效率,想着能不能給微信作個插件,解決這些問題。咱們一直在追求如何更有效率開展咱們的工做,好比使用腳本自動整理每週小集內容,使用微信小程序給讀者更好閱讀體驗。(呀,還有腳本,若是你還不知道,那確定沒有點 star 吧,傳送門node

提出問題

經過以上痛點,能夠肯定咱們要解決的問題主要有:git

  • 截圖:把全部的聊天記錄截一張長圖並保存到相冊;
  • 截圖(馬賽克):把全部的聊天記錄截一張長圖並保存到相冊,須要把羣友的頭像和暱稱打碼;
  • 預覽:預覽打碼後的效果。

如何解決

下面這張圖是聊天記錄頁面,點擊導航右邊按鈕,會彈出 ActionSheet。從圖中能夠看出,添加截圖功能,在 ActionSheet 上添加是不錯的選擇。github

61522546968_.pic.jpg

咱們下面主要的工做是:小程序

  • 1.拿到 ActionSheet 並添加3個菜單(截圖,截圖(馬賽克),預覽)
  • 2.找到聊天記錄所在的頁面,找到展現消息的視圖;
  • 3.獲取 ActionSheet 點擊菜單對應的事件;
  • 4.實現截取長圖並保存到相冊功能;
  • 5.對頭像和暱稱打馬賽克;
  • 6.添加版權信息。

實現

本文使用 MonkeyDev 工具開發(無需越獄),重點是教你如何開發一個微信插件,並不打算介紹工具。關注咱們的朋友應該都知道,以往的 #iOS知識小集 中我已經介紹了三個工具的使用。這三個工具在下面會用到。微信小程序

給 ActionSheet 添加3個菜單

使用 Reveal 工具查看聊天記錄頁面對應的 VC,ActionSheet 對應的類名。如何使用 Reveal 調試第三方 APP,網上有不少教程。使用MonkeyDev無需越獄。bash

reveal.png

經過上圖能夠看到聊天記錄所對應的VC是 MsgRecordDetailViewController,使用 MMTableView 展現聊天內容。彈出的 ActionSheet 對應的類爲 WCActionSheet, 能夠發現它是一個 UIWindow 。 那麼咱們看看這幾個類中的內容吧。微信

使用 class-dump 查看第三方 APP 的頭文件。在 #iOS知識小集 中已經介紹過這個工具的使用。app

MsgRecordDetailViewController的頭文件中發現有一個 WCActionSheet *favImgLongPressAction; 咱們能夠判定出 WCActionSheet 就是咱們要找的 ActionSheet。好了,接下來主要就是看 WCActionSheet 的頭文件,挖掘有用的信息。工具

WCActionSheet頭文件ui

@property(strong, nonatomic) NSMutableArray *buttonTitleList;
- (void)showInView:(id)arg1;
- (long long)addButtonWithItem:(id)arg1 atIndex:(unsigned long long)arg2;
- (long long)addButtonWithTitle:(id)arg1 atIndex:(unsigned long long)arg2;
- (long long)addButtonWithTitle:(id)arg1;
複製代碼

咱們的目標是給 WCActionSheet 添加3個菜單。下面這些方法彷佛對咱們有用。目前想到有兩種方案:

1.在 buttonTitleList 中添加一個對象

咱們所關心的最主要的問題是 buttonTitleList 中存放的的對象是什麼?須要使用Cycript工具,這個工具在以往的 #iOS知識小集 介紹過,想了解的朋友能夠在知識小集小程序中搜索 Cycript調試第三方APP

cycript.png

經過 Cycript 能夠看到 buttonTitleList 中存放的對象是 WCActionSheetItem。咱們看看 WCActionSheetItem 的頭文件,發現其實就是一個 Model 對象,用來表示菜單的標題,顏色等等。

@interface WCActionSheetItem : NSObject 
@property(copy, nonatomic) NSString *titleColor;
@property(copy, nonatomic) NSString *title; 
- (id)initWithTitle:(id)arg1 fontSize:(long long)arg2 fontColor:(id)arg3 WithDesc:(id)arg4 descFontSize:(long long)arg5 descFontColor:(id)arg6 enable:(_Bool)arg7;
- (id)initWithTitle:(id)arg1;
複製代碼

看到這裏,咱們能夠直接在 buttonTitleList 中添加 WCActionSheetItem 實例便可。

2.直接調用 addButtonWithTitle: 方法

從上圖能夠看出直接調用 addButtonWithTitle: 這個方法,返回一個 Index 爲 3,說明能夠直接調用這個方法。

接下來主要的問題是,找到添加菜單的時機。第一想到的是在 WCActionSheetDelegate 的代理中添加菜單,果斷在 MsgRecordDetailViewController 中 Hook 下面這3個代理方法,可是經過實驗證實,發現最後兩個方法並無被調用,由於 MsgRecordDetailViewController 並無實現這兩個代理,只好放棄了這種思路。

- (void)actionSheet:(id)arg1 clickedButtonAtIndex:(long long)arg2;
- (void)didPresentActionSheet:(WCActionSheet *)arg1;
- (void)willPresentActionSheet:(WCActionSheet *)arg1;
複製代碼

無奈之下看到 WCActionSheet 中有個showInView:方法, 能夠直接 Hook 這個方法。但這樣致使全部的 WCActionSheet 都會被添加了額外的菜單。而咱們的目的只是在聊天記錄頁中的 WCActionSheet 顯示截圖菜單。因此用 [WeChatSaveData defaultSaveData].isNeedAddMenu 加了一個判斷,isNeedAddMenu 在 MsgRecordDetailViewController 頁面將要出現的時候,設置成 YES,在頁面將要消失的時候,設置成 NO。因此須要 Hook MsgRecordDetailViewControllerviewWillAppear:viewWillDisappear:方法。

CHOptimizedMethod1(self, void, WCActionSheet, showInView, UIView *, view){
    if ([WeChatSaveData defaultSaveData].isNeedAddMenu) {
        // 方案一
        [self addButtonWithTitle:@""]; // 填坑
        [self addButtonWithTitle:kScreenshotTitle];
        [self addButtonWithTitle:kScreenshotTitleMask];
        
        // 方案二
       WCActionSheetItem *shotItem = [[objc_getClass("WCActionSheetItem") alloc] initWithTitle:kScreenshotTitle];
        WCActionSheetItem *shotItem2 = [[objc_getClass("WCActionSheetItem") alloc] initWithTitle:kScreenshotTitleMask];
        [self.buttonTitleList addObject:shotItem];
        [self.buttonTitleList addObject:shotItem2];
    }
    CHSuper1(WCActionSheet, showInView, view);
}
複製代碼

執行結果以下圖:

21523364398_.pic_hd.jpg

找到聊天記錄頁面和展現消息的視圖

MsgRecordDetailViewController頭文件

@interface MsgRecordDetailViewController: UIViewController
{
    MMTableView *m_tableView;
}
- (void)viewWillAppear:(_Bool)arg1;
- (void)viewWillDisappear:(_Bool)arg1;
- (void)actionSheet:(id)arg1 clickedButtonAtIndex:(long long)arg2;
- (UITableViewCell *)tableView:(id)arg1 cellForRowAtIndexPath:(id)arg2;
@end
複製代碼

經過對 MsgRecordDetailViewController頭文件分析,能夠達到截圖功能,只須要截取 TableView 爲一張長圖便可。

獲取到 MsgRecordDetailViewController 實例,使用 KVC 的方式便可獲取到 MMTableView

MMTableView *tableView = [viewController valueForKeyPath:@"m_tableView"];

實現截取長圖的功能,保存到相冊

首先須要 Hook actionSheet: clickedButtonAtIndex: 捕獲菜單的點擊事件,作截圖功能。

CHOptimizedMethod2(self, void, MsgRecordDetailViewController, actionSheet, WCActionSheet*, sheet, clickedButtonAtIndex, int, index){
    CHSuper2(MsgRecordDetailViewController, actionSheet, sheet, clickedButtonAtIndex, index);

    [WeChatCapture saveCaptureImageWithSheet:sheet index:index viewController:self];
}
複製代碼

saveCaptureImageWithSheet 這個方法中主要獲取到 MMTableView 並截圖保存到相冊。有興趣能夠看源碼。

對頭像和暱稱打馬賽克

爲了保護用戶的隱私,須要對用戶的頭像和暱稱作保護,那麼咱們能夠在 TableView 的代理中獲取頭像和暱稱對應的 View,而後替換 View 的內容便可。須要 Hook cellForRowAtIndexPath 這個方法。

CHOptimizedMethod2(self, UITableViewCell *, MsgRecordDetailViewController, tableView, MMTableView *, tableViewArg, cellForRowAtIndexPath, NSIndexPath, *indexPath){
    UITableViewCell *cell = CHSuper2(MsgRecordDetailViewController, tableView, tableViewArg, cellForRowAtIndexPath, indexPath);
    [WeChatCapture updateCellDataWithCell:cell indexPath:indexPath];
    return cell;
}
複製代碼

獲取到 Cell 若是當前是要打碼截圖,須要對頭像和暱稱的內容作處理。這裏作一個特殊的處理,頭像和暱稱咱們換成三國人物的頭像的名字。

+ (void)updateCellDataWithCell:(UITableViewCell *)cell indexPath:(NSIndexPath *)indexPath
{
    if ([WeChatSaveData defaultSaveData].maskType == WeChatSaveDataMaskTypeMast || [WeChatSaveData defaultSaveData].maskType == WeChatSaveDataMaskTypePreview) {
        NSArray *subviews = [cell.contentView subviews];
        FavRecordBaseNodeView *nodeView = [subviews lastObject];
        if ([NSStringFromClass([nodeView class]) hasSuffix:@"NodeView"]) {
            UILabel *nickNameLabel = [nodeView valueForKey:@"m_srcTitleLabel"];
            if (nickNameLabel) {
                CGRect tempFrame = nickNameLabel.frame;
                tempFrame.size.width = 120;
                nickNameLabel.frame = tempFrame;
            }
            
            MMHeadImageView *imageView = [nodeView valueForKeyPath:@"m_headImg"];
            NSString *nickName = [imageView valueForKey:@"_nsUsrName"];
            WeChatUser *aUser = [[WeChatSaveData defaultSaveData].userNameDict objectForKey:nickName?:@""];
            if (!aUser) {
                aUser = [WeChatUser user];
                [[WeChatSaveData defaultSaveData].userNameDict setObject:aUser forKey:nickName?:@""];
            }
            nickNameLabel.text = aUser.nickname ?: @"";
            if (imageView) {
                [imageView updateUsrName:aUser.nickname withHeadImgUrl:aUser.icon];
            }
        }
    }
}
複製代碼

添加版權信息

給繪製的長圖添加一個小集的版權 by公衆號 知識小集

最終效果(伴隨着咔嚓一聲,一張被打碼的照片保存到了相冊,你能夠在任意的渠道分享了):

11523363542_.pic_hd.jpg

踩坑

WCActionSheet的代理方法Index不對

添加額外的菜單後,WCActionSheet 的代理方法 actionSheet: clickedButtonAtIndex: 中的 Index 點擊取消或空白區域總爲 2,也就是我添加菜單第一個的 Index。致使每次點擊取消或空白區域時都會聽到咔嚓一聲截圖。解決方法,就是加一個沒有標題的菜單,而且高度爲 0。

收藏的聊天記錄也須要有截圖功能

按着這個思路給收藏中的聊天記錄添加截圖功能,這就是你爲何會在源碼中看到 FavRecordDetailViewController 的 Hook。

總結

逆向的整個流程能夠歸結爲:

  • 第一,靈感,有一個好的想法,而後去實現它;
  • 第二,工具,作逆向必須藉助強大的工具;
  • 第三,查找,找出你所關心的視圖,導出頭文件,看看從頭文件中能獲得什麼;
  • 第四,思路,想一想實現思路是什麼,從什麼地方入手比較好;
  • 最後,實現,全部的思路想好後,開始編寫代碼。

而本文也是按這幾個步驟逐漸實現的:

  • 第一,靈感,咱們遇到的痛點;
  • 第二,工具,作這個時 Lefe_x 已經有一些逆向基礎,會基本的工具使用;
  • 第三,查找,經過 Revel 和 Cycript 工具找出所關注的視圖,着手查看頭文件;
  • 第四,思路,梳理思路,Hook 哪些方法;
  • 最後,實現,編碼,容許,查看效果。

本插件以開源 前往查看 ,若是想安裝在本身的手機上,須要安裝 MonkeyDev,把圖中的文件導入便可

demo.png

知識小集公衆號

知識小集是一個團隊公衆號,每週都會有原創文章分享,咱們的文章都會在公衆號首發。知識小集微信羣,短短几周時間,目前羣友已經300+人,很快就要達到上限(抓住機會哦),關注公衆號獲取加羣方式。

相關文章
相關標籤/搜索