Android無障礙服務 x itchat 打造微信半自動機器人

coder-pig發表於2018-11-07

em…是我,那個『敲最屌的碼,輸最多的錢』的傻雕開發仔,故事的最後:沒有暴富,沒有嫩模,也沒有穴深妹…

Android無障礙服務 x itchat 打造微信半自動機器人

再次奉勸各位一句:遠離投機倒把,保持身心健康!(當然,後面如果學到機器學習的東西,可能會有續集 ~(╯▽╰ )真香~~)

Android無障礙服務 x itchat 打造微信半自動機器人

好的,碎碎唸的那麼多,說回本節,寫這一篇原因是,我的Py交易群裡,童鞋問的最多的問題都是和機器人有關,基本都是下面這類問題:

  • 1.群主,你的機器人怎麼實現的?
  • 2.群主,你的apk怎麼執行了沒反應?
  • 3.群主,支援自動新增手機號為好友嗎?
  • …等等

一開始我還是比較熱衷幫忙解決問題的,但是耐心這種東西呢,是最容易被消磨殆盡的。而且學習Python之後,我變得越來越懶,一些繁瑣重複的操作,我都會想辦法自動化….當然,如果你是給我打錢或者是妹子,我也是很樂意的。

Android無障礙服務 x itchat 打造微信半自動機器人

其實Android無障礙服務和itchat都是一些老生常談的東西了,我也寫過好幾篇文章了:

東西就那些,套路也是那些,大部分時候,你只需要一點 『靈性』

Android無障礙服務 x itchat 打造微信半自動機器人

就好像七巧板一樣,只有七塊,但是卻能拼接出許多圖形。要有靈性!本文先說下無障礙服務和itchat的一些核心要點,然後以讀者編寫的機器人指令碼為例,教你實現一波,希望可以給你帶來一些提示,然後去擴充套件自己想要的功能。


開源的微信個人號介面庫——itchat

itchat庫基於微信網頁版,出了好幾年的了,微信正在慢慢收窄網頁端的功能(因為濫用的微信機器人),很多以前能用的介面慢慢都不能用了,比如:拉人進群,新增好友等,反正網頁端沒有的,現在都不能用。丟幾個連結:

itchat現在能做的:監聽加好友的資訊,監聽聊天資訊(包括群聊),傳送資訊。基本上常用的而且可用的就這三個。當然如果你願意掏錢的話,你不需要折騰那麼多,網上有其他付費的途徑:微友助手,王二狗機器人,還有xposed外掛等。基於其他協議且可用的免費開源庫目前還沒見過,有知道的歡迎在評論區告知下!

使用要點提煉

如果可以,我希望你,儘可能的學會使用『正規表示式處理字串』(基本功),丟個以前寫過的文章:小豬的Python學習之旅 —— 3.正規表示式,正則對於字串匹配,提取非常實用!

1.監聽並通過加好友的請求

@itchat.msg_register(itchat.content.FRIENDS)def deal_with_friend(msg):    # 自動將新好友的訊息錄入,不需要過載通訊錄    itchat.add_friend(**msg['Text']) 複製程式碼

通過上面的add_friend函式,就可以完成新增好友的操作了。但是,有時我們可能需要做一些過濾,不然亂七八糟的人都能加你了,是吧,比如對新增內容進行過濾,包含某些字眼才通過驗證,或者獲取加你的人的相關資訊,比如姓名,驗證資訊,個性簽名,性別等。我們直接把上面的msg列印出來,內容如下:

{'MsgId': '6930655840618917667', 'FromUserName': 'fmessage', 'ToUserName': '@64fc6691440834f2dfba5489d652e5dbae06da4d57d550757403094424f7ec9c', 'MsgType': 37, 'Content': '<
msg fromusername="wxid_gvr9a3le939h22" encryptusername="v1_6a48a18b8d6164b69dfdd3949311cd4bc58d96dfe00636d5e9814ff36b8d107164e768ccd9b2fb911ea9b87ccac42978@stranger" fromnickname="Robot Pig" content="我是Robot Pig" shortpy="ROBOTPIG" imagestatus="3" scene="30" country="AD" province="" city="" sign="(´v`o)♡" percard="1" sex="2" alias="" weibo="" albumflag="0" albumstyle="0" albumbgimgid="" snsflag="1" snsbgimgid="http://mmsns.qpic.cn/mmsns/icDH6NcE3zNVBleeQZUzzlnhWk16tIfPKyvsmqWIpUwAxHkvricuNCL2RvGPjS3pVq7miaZQoju8TU/0" snsbgobjectid="12478275406675193980" mhash="197adbfd7de1668f30895d20dfb09b67" mfullhash="197adbfd7de1668f30895d20dfb09b67" bigheadimgurl="http://wx.qlogo.cn/mmhead/ver_1/jYcpsrqBVaxDuMbFgPZwH5F3mvcgKMNVrQXlmmbGWx5p9iaFrYALPyia1b7Izn4YCUYDRXz1jnfKw0eUXvEeEH5NouILblzLDjog40fqOjIjc/0" smallheadimgurl="http://wx.qlogo.cn/mmhead/ver_1/jYcpsrqBVaxDuMbFgPZwH5F3mvcgKMNVrQXlmmbGWx5p9iaFrYALPyia1b7Izn4YCUYDRXz1jnfKw0eUXvEeEH5NouILblzLDjog40fqOjIjc/96" ticket="v2_4ed4b6a5ac5ccf04c68c7bd64e4b543ba5babde23ce2985d4317bfc4bb62dcdfd78bb701551e1410fe64836f9bd147199de3e4493a031ea2daf52187816d9207@stranger" opcode="2" googlecontact="" qrticket="" chatroomusername="" sourceusername="" sourcenickname="">
<
brandlist count="0" ver="688441058">
<
/brandlist>
<
/msg>
'
, 'Status': 3, 'ImgStatus': 1, 'CreateTime': 1541558757, 'VoiceLength': 0, 'PlayLength': 0, 'FileName': '', 'FileSize': '', 'MediaId': '', 'Url': '', 'AppMsgType': 0, 'StatusNotifyCode': 0, 'StatusNotifyUserName': '', 'RecommendInfo': {'UserName': '@156957cc475e1113c0a8fa340eae41ac1d92a12a523302a8eb06208616d656b3', 'NickName': 'Robot Pig', 'QQNum': 0, 'Province': '', 'City': '', 'Content': '我是Robot Pig', 'Signature': '(´v`o)♡', 'Alias': '', 'Scene': 30, 'VerifyFlag': 0, 'AttrStatus': 50467109, 'Sex': 2, 'Ticket': 'v2_4ed4b6a5ac5ccf04c68c7bd64e4b543ba5babde23ce2985d4317bfc4bb62dcdfd78bb701551e1410fe64836f9bd147199de3e4493a031ea2daf52187816d9207@stranger', 'OpCode': 2
}, 'ForwardFlag': 0, 'AppInfo': {'AppID': '', 'Type': 0
}, 'HasProductId': 0, 'Ticket': '', 'ImgHeight': 0, 'ImgWidth': 0, 'SubMsgType': 0, 'NewMsgId': 6930655840618917667, 'OriContent': '', 'EncryFileName': '', 'User': <
User: {'UserName': '@156957cc475e1113c0a8fa340eae41ac1d92a12a523302a8eb06208616d656b3', 'MemberList': <
ContactList: []>

}>
, 'Type': 'Friends', 'Text': {'status': 3, 'userName': '@156957cc475e1113c0a8fa340eae41ac1d92a12a523302a8eb06208616d656b3', 'verifyContent': '', 'autoUpdate': {'UserName': '@156957cc475e1113c0a8fa340eae41ac1d92a12a523302a8eb06208616d656b3', 'NickName': 'Robot Pig', 'QQNum': 0, 'Province': '', 'City': '', 'Content': '我是Robot Pig', 'Signature': '(´v`o)♡', 'Alias': '', 'Scene': 30, 'VerifyFlag': 0, 'AttrStatus': 50467109, 'Sex': 2, 'Ticket': 'v2_4ed4b6a5ac5ccf04c68c7bd64e4b543ba5babde23ce2985d4317bfc4bb62dcdfd78bb701551e1410fe64836f9bd147199de3e4493a031ea2daf52187816d9207@stranger', 'OpCode': 2
}
}
}複製程式碼

看上去和Json有點類似是吧,但是不是Json,你用Json格式化工具試試就知道了,而是一種類似於字典的東東(跟下原始碼就知道了,msg的類:itchat.storage.messagequeue.Message)一大串有點亂,用PyCharm新建一個json檔案,複製貼上格式化下:

Android無障礙服務 x itchat 打造微信半自動機器人

因為類似於字典,你可以通過鍵的形式獲取所需的值,比如列印下msg[‘Content’]:

<
msg fromusername="wxid_gvr9a3le939h22" encryptusername="v1_6a48a18b8d6164b69dfdd3949311cd4bc58d96dfe00636d5e9814ff36b8d107164e768ccd9b2fb911ea9b87ccac42978@stranger" fromnickname="Robot Pig" content="我是Robot Pig" shortpy="ROBOTPIG" imagestatus="3" scene="30" country="AD" province="" city="" sign="(´v`o)♡" percard="1" sex="2" alias="" weibo="" albumflag="0" albumstyle="0" albumbgimgid="" snsflag="1" snsbgimgid="http://mmsns.qpic.cn/mmsns/icDH6NcE3zNVBleeQZUzzlnhWk16tIfPKyvsmqWIpUwAxHkvricuNCL2RvGPjS3pVq7miaZQoju8TU/0" snsbgobjectid="12478275406675193980" mhash="197adbfd7de1668f30895d20dfb09b67" mfullhash="197adbfd7de1668f30895d20dfb09b67" bigheadimgurl="http://wx.qlogo.cn/mmhead/ver_1/jYcpsrqBVaxDuMbFgPZwH5F3mvcgKMNVrQXlmmbGWx5p9iaFrYALPyia1b7Izn4YCUYDRXz1jnfKw0eUXvEeEH5NouILblzLDjog40fqOjIjc/0" smallheadimgurl="http://wx.qlogo.cn/mmhead/ver_1/jYcpsrqBVaxDuMbFgPZwH5F3mvcgKMNVrQXlmmbGWx5p9iaFrYALPyia1b7Izn4YCUYDRXz1jnfKw0eUXvEeEH5NouILblzLDjog40fqOjIjc/96" ticket="v2_4ed4b6a5ac5ccf04c68c7bd64e4b543b5c95f344b73e23e05f71c527683d8693969fcef8a893316431a660ac382032b022ec8af4d8ae5372e680931064da1ce3@stranger" opcode="2" googlecontact="" qrticket="" chatroomusername="" sourceusername="" sourcenickname="">
<
brandlist count="0" ver="688441086">
<
/brandlist>
<
/msg>
複製程式碼

同樣新建一個xml檔案,複製貼上格式化下:

Android無障礙服務 x itchat 打造微信半自動機器人

裡面有我們想要的資訊,接著我們用正則來提取這些想要的資料:

msg_pattern = re.compile('<
msg fromusername="(.*?)".*?fromnickname="(.*?)" content="(.*?)".*?sign="(.*?)".*?sex="(\d)".*?bigheadimgurl="(.*?)"'
,re.S)複製程式碼

接著修改一波程式碼:

@itchat.msg_register(itchat.content.FRIENDS)def deal_with_friend(msg):    result = msg_pattern.search(msg['Content'])    if result is not None:        print('新增人微信id:', result.group(1))        print('新增人使用者名稱', result.group(2))        print('驗證內容', result.group(3))        print('新增人個性簽名', result.group(4))        print('新增人性別', result.group(5))        print('新增人頭像大圖', result.group(6))複製程式碼

新增試試,列印結果如下:

Android無障礙服務 x itchat 打造微信半自動機器人

行吧,什麼加你人的資訊都拿到了,你想幹嘛就幹嘛!

2.監聽聊天資訊

這個也很簡單,你可以監聽多種型別的資訊,如下表所示:

資訊型別 解釋
itchat.content.TEXT 文字內容
itchat.content.MAP 位置文字
itchat.content.Card 名片
itchat.content.Note 通知文字
itchat.content.Sharing 分享名稱
itchat.content.RECORDING 錄音
itchat.PICTURE 圖片/表情
itchat.content.VOICE 錄音
itchat.content.ATTACHMENT 附件
itchat.content.VIDEO 短視訊
itchat.content.FRIENDS 好友邀請
itchat.content.SYSTEM 系統資訊

可註冊多個資訊監聽,後註冊的資訊優先順序高於先註冊資訊,帶引數資訊高於不帶引數資訊。核心程式碼示例如下:

@itchat.msg_register(itchat.content.TEXT)def reply_msg(msg):    if msg['Content'] == u'你好':        itchat.send_msg(msg['User']['NickName'] + "你好啊!", msg['FromUserName'])複製程式碼

和上面的監聽並通過加好友的請求玩法一樣,根據鍵拿值,或者正則提取需要的資料。這裡提供幾個常用的鍵:

msg['Content']  # 獲取使用者傳送的內容,後面的匹配值建議加上u,代表Unicode編碼msg['User']['NickName'] # 傳送資訊的使用者名稱msg['FromUserName'] # 接收資訊的使用者名稱,這個不是直接的使用者暱稱或微訊號!!!複製程式碼

3.傳送資訊

itchat支援下述幾種型別的資訊(不支援語音):

函式名 作用
send_msg() 傳送文字資訊
send_file() 傳送檔案
send_video() 傳送視訊
send_image() 傳送圖片

核心程式碼示例如下:

user_info = itchat.search_friends(name='一朵死去的花')if len(user_info) >
0: # 拿到使用者名稱 user_name = user_info[0]['UserName'] # 傳送文字資訊 itchat.send_msg('培傑你好啊!', user_name) # 傳送圖片 time.sleep(10) itchat.send_image('cat.jpg', user_name) # 傳送檔案 time.sleep(10) itchat.send_file('19_2.py', user_name) # 傳送視訊 time.sleep(10) itchat.send_video('sport.mp4', user_name)複製程式碼

建議加入延時,避免資訊傳送過於頻繁,導致賬號被封!

4.獲得群聊成員列表

核心程式碼示例如下:

@itchat.msg_register(itchat.content.TEXT, isGroupChat=True)def reply_msg(msg):    print("收到一條群資訊:", msg['ActualNickName'], msg['Content'])複製程式碼

玩法和之前的一樣,另外,還可以呼叫**msg.isAt**判斷是否有人@自己。

5.監控加群資訊

核心程式碼示例如下:

@itchat.msg_register([NOTE], isGroupChat=True)def revoke_msg(msg):    if '邀請' in str(msg['Text']):        # 進行相關操作複製程式碼

就是判斷提示資訊裡是否有加群字眼,好吧,常用的大概就這些,其他的自行查閱文件。


Android無障礙服務——AccessibilityService

其實就是一個自動點點點的東西,沒什麼技術含量,真的!!!在開始講解AccessibilityService之前,先要明確一點:

什麼是自動化?

下面是我個人的理解:

把本該人做的,重複性高,單調,機械化的操作,交給程式去完成。

舉個例子,小豬每天都要用微信拉人進群,所需的操作步驟如下所示:

Android無障礙服務 x itchat 打造微信半自動機器人

是的,你拉一個人,需要20多秒,每次拉人的操作都是機械重複的。如果每天有30個人進群,你需要花費:600s,10分鐘我都夠開一把王者榮耀的了,別人在上分,我還在拉人???

Android無障礙服務 x itchat 打造微信半自動機器人

多撈哦,用AccessibilityService寫個自動點點點的工具就可以把我從中解放出來。

1.自定義Service繼承AccessibilityService

自定義一個AccessibilityService類,重寫兩個主要方法:onInterrupt( ):輔助功能中斷的回撥,基本不用理,核心還是: onAccessibilityEvent(AccessibilityEvent event) 。

當介面發生改變,比如頂部Notification,介面更新,內容變化等,就會觸發**onAccessibilityEvent方法。點開AccessibilityEvent**類可以看到一堆的事件型別:

Android無障礙服務 x itchat 打造微信半自動機器人

上面一大堆,其實並沒有什麼用,我一般是習慣直接把event.toString()給列印出來,然後自行判斷:

Android無障礙服務 x itchat 打造微信半自動機器人

程式碼示例如下:

Android無障礙服務 x itchat 打造微信半自動機器人

這裡做的事情就是,當無障礙相關的Event觸發時,去判斷Event型別以及觸發事件的類名,再去執行相關操作:點選,滾動,填充文字等。

2.獲取結點的幾個方法

可以通過resource-id,text來定位到結點,如果可以,建議使用後者,因為一般APP更新後,這個id都會發生變化,(所以微信更新後都需要做適配,就是更新這個id)

1)通過UI Automator來檢視佈局層次

舊版的Android Studio,Ctrl + alt + A,輸入 monitor 可以找到,新版的Android Studio是找不到的,你需要來到**android-sdk/tools**目錄下:

Android無障礙服務 x itchat 打造微信半自動機器人

連線手機後,點選頂部的:

Android無障礙服務 x itchat 打造微信半自動機器人

接著可以看到當前頁面的層次結構圖:

Android無障礙服務 x itchat 打造微信半自動機器人

有一點務必注意: resource-id不一定是唯一!!!

getRootInActiveWindow( ):獲取當前整個活動視窗的根節點,返回的是一個AccessibilityNodeInfo類,代表View的狀態資訊, 提供了下述幾個非常實用的方法:

  • findAccessibilityNodeInfosByViewId:通過檢視id查詢節點元素。
  • findAccessibilityNodeInfosByText:通過字串查詢節點元素。
  • getParent:獲取父節點。
  • getChild:獲取子節點。

另外,找結點要注意判空,找不到對應結點直接呼叫其他方法是會空指標異常的!!!找到結點然後就是一些動作了,常用的點選,長按,滾動和輸入文字。程式碼示例如下:

/* 點選 */node.performAction(AccessibilityNodeInfo.ACTION_CLICK)/* 長按 */node.performAction(AccessibilityNodeInfo.ACTION_LONG_CLICK)/* 滾動 */listNode.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) //向上滾動listNode.performAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) //向下滾動/* 輸入文字 */val arguments = Bundle()arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,"xxx")editNode.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments)/* 通過貼上板輸入文字 */public static void sendTextForEditText(Context context, AccessibilityNodeInfo editNode, String text) { 
if (editNode != null) {
ClipboardManager clipboard = (ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("text", text);
clipboard.setPrimaryClip(clip);
//獲得焦點 editNode.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
//貼上內容 edittext.performAction(AccessibilityNodeInfo.ACTION_PASTE);

}
}複製程式碼

除此之外,還有AccessibilityService本身特有的方法,如模擬回退鍵,Home鍵等。

performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK)    //回退performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME)    //Home鍵performGlobalAction(AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS)    //點選notification複製程式碼

大概的玩法就這些,除了通過UI Automator獲得id外,還可以通過其他幾種方式來獲取id。

2)開發者助手

如果你手機root了的話,可以安裝一個**『開發者助手』**,點選當前介面分析,點選想檢視的節點即可,如圖所示。

Android無障礙服務 x itchat 打造微信半自動機器人

3)通過adb命令

依次鍵入:

adb shell uiautomator dump /mnt/sdcard/window_dump.xmladb pull /mnt/sdcard/window_dump.xml複製程式碼

執行結果如圖所示:

Android無障礙服務 x itchat 打造微信半自動機器人

接著可以把這個xml檔案丟到as裡,格式化下,摺疊下一層層拆開,然後去找對應的結點:

Android無障礙服務 x itchat 打造微信半自動機器人

這種方法是不怎麼推薦的,除非這個結點很明顯,比如文字啊,之類的,層級很多的時候,可能會找死你…


3.AccessibilityService注意事項

在使用AccessibilityService服務時,有幾點要注意:

首先需要手動開啟無障礙服務!!!程式轉了跑,沒反應,多半是因為沒有開啟無障礙服務!無障礙服務一般在:輔助功能->
無障礙
,(不同的手機可能不同)找到自己的點點點APK,開啟,如圖所示:

Android無障礙服務 x itchat 打造微信半自動機器人

另外,有一點要注意,有時可能因為異常導致程式意外終止了,你需要到無障礙中關掉對應的服務,然後重啟。還有一點最重要的無障礙服務的適用範圍:

原生的Android APP!!! 是的原生!!!現在很多應用都是混合應用,對於H5的頁面,無障礙服務是無能為力的!因為此時的控制元件點選事件不是通過onClick來產生的,而是直接判斷TouchEvent。而Android的無障礙服務沒有提供傳送down,move,up事件的api。而替代方案只能使用root後的手機,向系統傳送全域性點選命令。

一般是拿到結點,然後獲得結點所在的區域,然後執行相關的命令,比如點選,常用程式碼示例如下:

/* 執行Shell命令 */public static void execShellCmd(String cmd) { 
try {
// 申請獲取root許可權,這一步很重要,不然會沒有作用 Process process = Runtime.getRuntime().exec("su");
// 獲取輸出流 OutputStream outputStream = process.getOutputStream();
DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
dataOutputStream.writeBytes(cmd);
dataOutputStream.flush();
dataOutputStream.close();
outputStream.close();

} catch (Throwable t) {
t.printStackTrace();

}
}/* 點選某個結點 */public static void perforGlobalClick(AccessibilityNodeInfo info) {
Rect rect = new Rect();
info.getBoundsInScreen(rect);
perforGlobalClick(rect.centerX(), rect.centerY());

}/* 點選某個座標點 */public static void perforGlobalClick(int x, int y) {
execShellCmd("input tap " + x + " " + y);

}/* 全域性滑動 */public static void perforGlobalSwipe(int x0, int y0, int x1, int y1) {
execShellCmd("input swipe " + x0 + " " + y0 + " " + x1 + " " + y1);

}/* 全域性返回 */public static void perforGlobalHome(long delay) {
execShellCmd("input keyevent " + KeyEvent.KEYCODE_HOME);

}/* 全域性Home鍵 */public static void perforGlobalHome(long delay) {
execShellCmd("input keyevent " + KeyEvent.KEYCODE_BACK);

}複製程式碼

行吧,關於AccessibilityService的玩法大概就這些了,接著教大家擼一個我的半自動微信機器人。


3.動手擼一個自己的微信機器人

先羅列下我的需求:

  • 1.自動通過別人加好友的驗證,傳送歡迎圖和歡迎資訊;
  • 2.監聽使用者傳送的資訊,響應對應的資訊
  • 選單:返回選單回覆詞
  • 1:加入「Python學習交流群」
  • 2:加入「Android學習交流群」
  • 3:加入「閒聊扯淡群」
  • 4:加入「摳腚男孩的妙妙屋」
  • 5:關注公眾號「摳腚男孩」
  • 6:小豬的「個人部落格」
  • 7:小豬的「Github」
  • 8:給小豬「打賞」
  • 9:小豬的「微信」(不閒聊!)
  • 其他,預設回覆黑人問號圖。

好的,問題來了,itchat現在不支援拉人進群,怎麼辦?一個折中的方法,就是利用Android AccessibilityService,完成自動拉人進群,我們可以採集傳送進群的人的使用者名稱,然後定時(比如兩小時)傳送一次到檔案傳輸助手,然後複製貼上下使用者名稱到我們編寫的無障礙指令碼里,完成自動拉人的操作,因為還要人去複製貼上,所以只能算半自動!

接著第二個問題,資料的傳輸格式,如果只是一個群的話,最簡單的,使用者名稱拼接,回車作為分隔:

小A小B小C複製程式碼

這裡的話,因為我有多個群,讀者可以選擇加入自己想加入的群,回車換行或者新增分隔符的方式顯得有點low,而且不方便擴充套件,這裡,我決定使用Json字串,儲存加每個群的人的使用者名稱,最後拼接成一個Json,示例如下:

{ 
"Python": [], "Python2": [ "朱偉", "程命沆(hàng)", "雙槍老漢" ] "Speak": [ "程命沆(hàng)" ], "Android": [ "朱偉", "程命沆(hàng)" ], "Guy": []
}複製程式碼

PS:這裡有Python2的原因是,一群滿了,所以要做下判斷,如果人數達到495的,把加群的人新增到二群。所以還需要監控群群人員變化的資訊,有新的人進群,獲取一下一群當前的,加入到二群中。還有,要對加群的人做下判斷,如果已經在群裡了,就不要新增到列表中。邏輯都弄清楚了,那就直接上程式碼吧:

# -*- coding:utf-8 -*-# 微信小宇宙助手import datetimeimport reimport timeimport randomimport jsonimport itchatfrom itchat.content import *from apscheduler.schedulers.blocking import BlockingScheduler# 群聊人員列表member_python_list = []member_python_list_2 = []member_android_list = []member_speak_list = []# 加群人員的列表group_python_list = []  # Pythongroup_python_list_2 = []  # Python 2群group_android_list = []  # Androidgroup_speak_list = []  # 閒聊# 獲取群聊人員的列表的正則nickname_compile = re.compile(r"\<
ChatroomMember:.*?'NickName': '(.*?)'"
, re.S)# 獲取群聊名稱的正則group_name_compile = re.compile("'NickName': '(.{1,40
})', 'HeadImgUrl':"
, re.S)# 新增好友通過歡迎詞welcome_words = '(˶ᵔᵕᵔ˶)嚶嚶嚶,???\n我是智障機器人小Pig,傳送關鍵字:「選單」 \n 檢視更多小Pig的更多功能!'# 選單回覆詞menu_answer = '(˶ᵔᵕᵔ˶)鏘鏘鏘~???,\n' \ '可用關鍵詞如下(輸入對應數字,比如1):\n' \ ' ? 1.加入「Python學習交流群」\n' \ ' ? 2.加入「Android學習交流群」\n' \ ' ? 3.加入「閒聊扯淡群」\n' \ ' ? 4.關注公眾號「摳腚男孩」\n' \ ' ? 5.小豬的「個人部落格」\n' \ ' ? 6.小豬的「GitHub」\n' \ ' ? 7.給小豬「打賞」\n' \ ' ? 8.小豬「微信」(不閒聊哦~)\n' \ '注:請不要回復過於頻繁,智障機器人不會聊天哦!?'# 加群統一回複詞add_group_answer = '???FBI Warning!???\n(`・ω・´)ゞ非常抱歉的通知您:\n\n微信粑粑把拉人介面禁掉了,你的加群請求已收到,小豬童鞋會盡快把你拉到群中。\n\nヾノ≧∀≦)o 麻煩耐心等候哦!'# 重複加群回覆詞add_repeat_answer = '<
(`^´)>
哼,敲生氣,你都在群裡了,加什麼群鴨!???'
# 捐獻回覆詞donate_answer = '(˶ᵔᵕᵔ˶)您的打賞,會讓小豬更有動力肝♂出更Interesting的文章,謝謝支援~???'# 小豬回覆詞pig_answer = '(˶ᵔᵕᵔ˶)小豬童鞋不閒聊哦,有問題歡迎到群裡討論哦~'# 404回覆詞no_match_answer = '!!!非常抱歉,您輸入的關鍵詞粗錯了,請傳送「選單」檢視支援的數字關鍵字ヽ(・ω・´メ)'msg_pattern = re.compile( '<
msg fromusername="(.*?)".*?fromnickname="(.*?)" content="(.*?)".*?sign="(.*?)".*?sex="(\d)".*?bigheadimgurl="(.*?)"'
, re.S)# 自動通過加好友@itchat.msg_register(itchat.content.FRIENDS)def deal_with_friend(msg): result = msg_pattern.search(msg['Content']) if result is not None: print('新增人微信id:', result.group(1)) print('新增人使用者名稱', result.group(2)) print('驗證內容', result.group(3)) print('新增人個性簽名', result.group(4)) print('新增人性別', result.group(5)) print('新增人頭像大圖', result.group(6)) # itchat.add_friend(**msg['Text']) # 自動將新好友的訊息錄入,不需要過載通訊錄 # time.sleep(random.randint(1, 3)) # itchat.send_msg(welcome_words, msg['RecommendInfo']['UserName']) # time.sleep(random.randint(1, 3)) # itchat.send_image('welcome.png', msg['RecommendInfo']['UserName'])# 自動回覆配置@itchat.msg_register([TEXT])def deal_with_msg(msg): text = msg['Content'] if text == u'選單': time.sleep(random.randint(1, 3)) itchat.send(menu_answer, msg['FromUserName']) # 加入Python交流群 elif text == u'1': time.sleep(random.randint(1, 3)) nickname = msg['User']['NickName'] if nickname not in member_python_list and nickname not in member_python_list_2: itchat.send_msg("【" + nickname + "】童鞋\n" + add_group_answer, msg['FromUserName']) if nickname is not None: # 人數超過閥值拉入二群 if len(member_python_list) >
= 495: if nickname not in group_python_list_2: group_python_list_2.append(nickname) else: if nickname not in group_python_list: group_python_list.append(nickname) else: itchat.send_msg(add_repeat_answer, msg['FromUserName']) # 加入Android交流群 elif text == u'2': time.sleep(random.randint(1, 3)) nickname = msg['User']['NickName'] if nickname not in member_android_list: itchat.send_msg("【" + nickname + "】童鞋\n" + add_group_answer, msg['FromUserName']) if nickname is not None and nickname not in group_android_list: group_android_list.append(nickname) else: itchat.send_msg(add_repeat_answer, msg['FromUserName']) # 加入閒聊群 elif text == u'3': time.sleep(random.randint(1, 3)) nickname = msg['User']['NickName'] if nickname not in member_speak_list: itchat.send_msg("【" + nickname + "】童鞋\n" + add_group_answer, msg['FromUserName']) if nickname is not None and nickname not in group_speak_list: group_speak_list.append(nickname) else: itchat.send_msg(add_repeat_answer, msg['FromUserName']) # 公眾號 elif text == u'4': time.sleep(random.randint(1, 3)) itchat.send_image('gzh.jpg', msg['FromUserName']) # 個人部落格 elif text == u'5': time.sleep(random.randint(1, 3)) return 'coder-pig的個人主頁-掘金:https://juejin.im/user/570afb741ea493005de84da3' # GitHub elif text == u'6': time.sleep(random.randint(1, 3)) return 'https://github.com/coder-pig' # 打賞 elif text == u'7': time.sleep(random.randint(1, 3)) itchat.send_image('ds.gif', msg['FromUserName']) time.sleep(random.randint(1, 3)) itchat.send_msg(donate_answer, msg['FromUserName']) time.sleep(random.randint(1, 3)) itchat.send_image('wxpay.png', msg['FromUserName']) # 小豬微信 elif text == u'8': time.sleep(random.randint(1, 3)) itchat.send_msg(pig_answer, msg['FromUserName']) time.sleep(random.randint(1, 3)) itchat.send_image('scan_code.png', msg['FromUserName']) # 其他預設回覆: else: time.sleep(random.randint(1, 3)) itchat.send_image('hrwh.png', msg['FromUserName']) time.sleep(random.randint(1, 3)) itchat.send_msg(no_match_answer, msg['FromUserName'])@itchat.msg_register([NOTE], isGroupChat=True)def revoke_msg(msg): result = group_name_compile.search(str(msg)) if result is not None: group_name = result.group(1) if '邀請' in str(msg['Text']): results = nickname_compile.findall(str(msg)) if group_name == '小豬的Python學習交流群': member_python_list.clear() for result in results: member_python_list.append(result) elif group_name == '小豬的Android學習交流群': member_python_list.clear() results = nickname_compile.findall(str(msg)) for result in results: member_android_list.append(result) elif group_name == '技♂術交流?': member_python_list.clear() results = nickname_compile.findall(str(msg)) for result in results: member_speak_list.append(result)# 傳送加群人資訊列表def send_friend_group(): friend_dict = {"Python": [], "Android": [], "Speak": [], "Python2": []
} for p in group_python_list: friend_dict['Python'].append(p) for a in group_android_list: friend_dict['Android'].append(a) for s in group_speak_list: friend_dict['Speak'].append(s) for p2 in group_python_list_2: friend_dict['Python2'].append(p2) if len(friend_dict['Python']) >
0 or len(friend_dict['Android']) >
0 or len(friend_dict['Speak']) >
0 or len( friend_dict['Python2']) >
0: itchat.send_msg(str(json.dumps(friend_dict, ensure_ascii=False, indent=4)), toUserName="filehelper") group_python_list.clear() group_python_list_2.clear() group_android_list.clear() group_speak_list.clear()# 登陸成功後開啟定時任務def after_login(): sched.add_job(send_friend_group, 'interval', hours=2) sched.start()# 登陸時先獲取群聊的UserName,獲取群成員暱稱會用到def get_member_list(): python_chat_rooms = itchat.search_chatrooms(name='小豬的Python學習交流1群') if len(python_chat_rooms) >
0: group_username = python_chat_rooms[0]['UserName'] result = itchat.update_chatroom(group_username, detailedMember=True) member_python_list.clear() results = nickname_compile.findall(str(result)) for result in results: member_python_list.append(result) python_chat_rooms_2 = itchat.search_chatrooms(name='小豬的Python學習交流2群') if len(python_chat_rooms_2) >
0: group_username = python_chat_rooms_2[0]['UserName'] result = itchat.update_chatroom(group_username, detailedMember=True) member_python_list_2.clear() results = nickname_compile.findall(str(result)) for result in results: python_chat_rooms_2.append(result) android_chat_rooms = itchat.search_chatrooms(name='小豬的Android學習交流群') if len(android_chat_rooms) >
0: group_username = android_chat_rooms[0]['UserName'] result = itchat.update_chatroom(group_username, detailedMember=True) member_android_list.clear() results = nickname_compile.findall(str(result)) for result in results: member_android_list.append(result) speak_chat_rooms = itchat.search_chatrooms(name='技♂術交流?') if len(android_chat_rooms) >
0: group_username = speak_chat_rooms[0]['UserName'] result = itchat.update_chatroom(group_username, detailedMember=True) member_speak_list.clear() results = nickname_compile.findall(str(result)) for result in results: member_speak_list.append(result)if __name__ == '__main__': sched = BlockingScheduler() itchat.auto_login(loginCallback=get_member_list, enableCmdQR=1) itchat.run(blockThread=False) after_login()複製程式碼

執行後可以測試下我們的自動回覆:

Android無障礙服務 x itchat 打造微信半自動機器人

可以,自動回覆的功能就做好了,接著是搭配著無障礙服務自動拉人。先是五個群名稱:

Android無障礙服務 x itchat 打造微信半自動機器人

接著寫一個Bean類,用來放Json資料。

Android無障礙服務 x itchat 打造微信半自動機器人

接著就是無障礙服務類了,感覺沒什麼好講的,直接上程式碼吧:

package com.coderpig.wechathelperimport android.accessibilityservice.AccessibilityServiceimport android.app.Notificationimport android.app.PendingIntentimport android.os.Bundleimport android.os.Handlerimport android.util.Logimport android.view.accessibility.AccessibilityEventimport android.view.accessibility.AccessibilityNodeInfoimport com.orhanobut.hawk.Hawk/** * 描述:無障礙服務類 * * @author CoderPig on 2018/04/12 13:47. */class HelperService : AccessibilityService() { 
private val TAG = "HelperService" private val handler = Handler() private var curGroup = "" private var mMember = Member() override fun onInterrupt() {
} override fun onAccessibilityEvent(event: AccessibilityEvent) {
val eventType = event.eventType val classNameChr = event.className val className = classNameChr.toString() Log.d(TAG, event.toString()) when (eventType) {
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED ->
{
if (Hawk.get(Constant.ADD_FRIENDS, false)) {
when (className) {
"com.tencent.mm.ui.LauncherUI" ->
openGroup() "com.tencent.mm.ui.contact.ChatroomContactUI" ->
searchGroup() "com.tencent.mm.ui.chatting.ChattingUI" ->
openGroupSetting() "com.tencent.mm.chatroom.ui.ChatroomInfoUI" ->
openSelectContact() "com.tencent.mm.ui.contact.SelectContactUI" ->
addMembers()
}
} if (className == "com.tencent.mm.ui.widget.a.c") {
dialogClick()
}
}
}
} //1.開啟群聊 private fun openGroup() {
mMember = Hawk.get<
Member>
(Constant.MEMBER) if(mMember.python_1.size != 0 || mMember.android.size != 0 || mMember.speak.size != 0 || mMember.python_2.size != 0 || mMember.guy.size != 0) {
curGroup = when {
mMember.python_1.size >
0 ->
Constant.GROUP_NAME_1 mMember.python_2.size >
0 ->
Constant.GROUP_NAME_2 mMember.android.size >
0 ->
Constant.GROUP_NAME_3 mMember.speak.size >
0 ->
Constant.GROUP_NAME_4 mMember.guy.size >
0 ->
Constant.GROUP_NAME_5 else ->
""
} val nodeInfo = rootInActiveWindow if (nodeInfo != null) {
val tabNodes = nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/cw2") for (tabNode in tabNodes) {
if (tabNode.text.toString() == "通訊錄") {
tabNode.parent.performAction(AccessibilityNodeInfo.ACTION_CLICK) handler.postDelayed({
val newNodeInfo = rootInActiveWindow if (newNodeInfo != null) {
val tagNodes = newNodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/lv") for (tagNode in tagNodes) {
if (tagNode.text.toString() == "群聊") {
tagNode.parent.parent.performAction(AccessibilityNodeInfo.ACTION_CLICK) break
}
}
}
}, 500L)
}
}
}
}
} //2.搜尋群聊 private fun searchGroup() {
val nodeInfo = rootInActiveWindow if (nodeInfo != null) {
val nodes = nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/m6") for (info in nodes) {
if (info.text.toString() == curGroup) {
info.parent.performAction(AccessibilityNodeInfo.ACTION_CLICK) break
}
}
}
} //3.開啟群聊設定 private fun openGroupSetting() {
when (curGroup) {
Constant.GROUP_NAME_1 ->
{
if(mMember.python_1.size >
0) {
val nodeInfo = rootInActiveWindow if (nodeInfo != null) {
nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/j1")[0].performAction(AccessibilityNodeInfo.ACTION_CLICK)
}
}
} Constant.GROUP_NAME_2 ->
{
if(mMember.python_2.size >
0) {
val nodeInfo = rootInActiveWindow if (nodeInfo != null) {
nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/j1")[0].performAction(AccessibilityNodeInfo.ACTION_CLICK)
}
}
} Constant.GROUP_NAME_3 ->
{
if(mMember.android.size >
0) {
val nodeInfo = rootInActiveWindow if (nodeInfo != null) {
nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/j1")[0].performAction(AccessibilityNodeInfo.ACTION_CLICK)
}
}
} Constant.GROUP_NAME_4 ->
{
if(mMember.speak.size >
0) {
val nodeInfo = rootInActiveWindow if (nodeInfo != null) {
nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/j1")[0].performAction(AccessibilityNodeInfo.ACTION_CLICK)
}
}
} Constant.GROUP_NAME_5 ->
{
if(mMember.guy.size >
0) {
val nodeInfo = rootInActiveWindow if (nodeInfo != null) {
nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/j1")[0].performAction(AccessibilityNodeInfo.ACTION_CLICK)
}
}
} else ->
{
performBackClick()
}
}
} //4.滾動後點選新增按鈕,開啟新增成員頁面 private fun openSelectContact() {
if(curGroup != "") {
var members = arrayListOf<
String>
() when (curGroup) {
Constant.GROUP_NAME_1 ->
members = mMember.python_1 Constant.GROUP_NAME_2 ->
members = mMember.python_2 Constant.GROUP_NAME_3 ->
members = mMember.android Constant.GROUP_NAME_4 ->
members = mMember.speak Constant.GROUP_NAME_5 ->
members = mMember.guy
} if (members.size >
0) {
val nodeInfo = rootInActiveWindow if (nodeInfo != null) {
val numText = nodeInfo.findAccessibilityNodeInfosByViewId("android:id/text1")[0].text.toString() val memberCount = numText.substring(numText.indexOf("(") + 1,numText.indexOf(")")).toInt() val listNode = nodeInfo.findAccessibilityNodeInfosByViewId("android:id/list")[0] if(memberCount >
100) {
listNode.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) listNode.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD)
} val scrollNodeInfo = rootInActiveWindow if (scrollNodeInfo != null) {
handler.postDelayed({
val nodes = scrollNodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/dnm") for (info in nodes) {
if (info.contentDescription.toString() == "新增成員") {
info.parent.performAction(AccessibilityNodeInfo.ACTION_CLICK) break
}
}
}, 1000L)
}
}
}
} else {
performBackClick()
}
} //5.新增成員 private fun addMembers() {
var members = arrayListOf<
String>
() //最後一次的時候清空記錄,並且點選頂部確定按鈕 when (curGroup) {
Constant.GROUP_NAME_1 ->
members = mMember.python_1 Constant.GROUP_NAME_2 ->
members = mMember.python_2 Constant.GROUP_NAME_3 ->
members = mMember.android Constant.GROUP_NAME_4 ->
members = mMember.speak Constant.GROUP_NAME_5 ->
members = mMember.guy
} if (members.size >
0) {
for (i in 0 until members.size) {
handler.postDelayed({
val nodeInfo = rootInActiveWindow if (nodeInfo != null) {
val editNodes = nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/b26") if (editNodes != null &
&
editNodes.size >
0) {
val editNode = editNodes[0] val arguments = Bundle() arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, members[i]) editNode.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments)
}
}
}, 500L * (i + 1)) handler.postDelayed({
val cbNodes = rootInActiveWindow.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/om") if (cbNodes != null) {
val cbNode: AccessibilityNodeInfo? if (cbNodes.size >
0) {
cbNode = cbNodes[0] cbNode?.parent?.performAction(AccessibilityNodeInfo.ACTION_CLICK)
}
} //最後一次的時候清空記錄,並且點選頂部確定按鈕 if (i == members.size - 1) {
val m = Hawk.get<
Member>
(Constant.MEMBER) when (curGroup) {
Constant.GROUP_NAME_1 ->
m.python_1 = arrayListOf() Constant.GROUP_NAME_2 ->
m.python_2 = arrayListOf() Constant.GROUP_NAME_3 ->
m.android = arrayListOf() Constant.GROUP_NAME_4 ->
m.speak = arrayListOf() Constant.GROUP_NAME_5 ->
m.guy = arrayListOf()
} Hawk.put(Constant.MEMBER, m) curGroup = "" val sureNodes = rootInActiveWindow.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/j0") if (sureNodes != null &
&
sureNodes.size >
0) {
sureNodes[0].performAction(AccessibilityNodeInfo.ACTION_CLICK)
}
}
}, 800L * (i + 1))
}
}
} //對話方塊自動點選 private fun dialogClick() {
val inviteNode = rootInActiveWindow.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/au_")[0] inviteNode.performAction(AccessibilityNodeInfo.ACTION_CLICK)
} private fun performBackClick() {
handler.postDelayed({
performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK)
}, 1300L)
}
}複製程式碼

接著複製itchat返回的加群人的資料,寫入後,點選開啟微信,接下來就是享受自動加群了,如動圖所述(加速過…)

Android無障礙服務 x itchat 打造微信半自動機器人

行吧,關於Android無障礙服務 X itchat打造微信半自動機器人,就說這麼多,如果你看完還不會,我是真的沒辦法了…無障礙服務不止可以應用於微信,其他原生APP也可以做,比如最常見的自動打卡,自動簽到等,讀者學會了方法後,可以自行擴充~

4.倉庫地址

  • ItChatWXHelper:配合無障礙伺服器拉人用的基於itchat的機器人

  • WechatHelper:利用Android AccessibilityService 實現自動加好友,拉人進群聊

來源:https://juejin.im/post/5be2ef3551882516e46b0af5#comment

相關文章