引言:php
在上上週的週六和週日,我發了兩篇利用itchat實現微信機器人的文章(Python):html
經過把腳本掛到服務器上,自此告別手動擋,不用去手動轉發小宇宙, 不用手動加好友,而後把別人一個個拉到個人Py交易羣裏。正當我 暗自竊喜的時候,微信並無放過我這隻小貓咪。java
我還記得那天早上,我興高采烈早早來到公司,更新了一波代碼準備爲 個人機器人添磚加瓦的時候,當我關閉了阿里雲上的腳本,這時候意外來了, 個人機器人小號,再也沒法經過微信網頁端的接口登陸了!!! 掃描完二維碼,永遠提示的都是下面這樣一句話:node
<error><ret>1203</ret><message>當前登陸環境異常。爲了你的賬號安全,暫時不能登陸web微信。
你能夠經過Windows微信、Mac微信或者手機客戶端微信登陸。</message></error>
複製代碼
是的,就是這樣一句話,找不到申訴渠道,也不知道什麼時候纔可能會解封。(客戶端任可正常使用) 而如今另外新申請的微信小號是沒法登陸微信網頁端的,其實這是微信在慢慢關停網頁版登陸, 最主要的緣由就是機器人氾濫!python
沒有了網頁版微信,日子仍是要過的,難道只能迴歸手動檔麼?幾種解決方案:android
AccessibilityService其實不是一個新的東西了,老久以前就有了, 官方原意:優化殘障人士的使用體驗的,而在我大天朝:git
搶紅包,自動安裝,一鍵XXX等等,可謂欣欣向榮。github
使用AccessibilityService也很是Easy,核心要點就是:web
經過UI Automator找到節點,經過resource-id,text,content-desc等 惟一特徵定位到具體的節點,接着執行各類模擬操做,點,滾動,填充, 用法比較簡單的,大部分時間會花在試錯和邏輯調整上!安全
來一發經過AccessibilityService實現的自動加好友以及拉人進羣聊的Gif體驗下:
Gif加速了一點,不過完成加好友以及拉人總共也就耗時15s,是至關客觀的啦。 下面就來介紹下AccessibilityService這個玩意怎麼用吧~
如題,自定義一個AccessibilityService類,重寫兩個主要方法:
onInterrupt
( ):輔助功能中斷的回調,基本不用理,核心仍是 onAccessibilityEvent
(AccessibilityEvent event) 上。
當界面發生了什麼事情,好比頂部Notification,界面更新,內容變化等, 會觸發這個方法,你能夠根據不一樣的事件響應不一樣的操做,好比小豬這個 就是當頂部出現加好友的Notification的event時,跳轉到加好友頁。 點開AccessibilityEvent類能夠看到一堆的事件類型~
事件類型 | 描述 |
---|---|
TYPE_VIEW_CLICKED | View被點擊 |
TYPE_VIEW_LONG_CLICKED | View被長按 |
TYPE_VIEW_SELECTED | View被選中 |
TYPE_VIEW_FOCUSED | View得到焦點 |
TYPE_VIEW_TEXT_CHANGED | View文本變化 |
TYPE_WINDOW_STATE_CHANGED | 打開了一個PopupWindow,Menu或Dialog |
TYPE_NOTIFICATION_STATE_CHANGED | Notification變化 |
TYPE_VIEW_HOVER_ENTER | 一個View進入懸停 |
TYPE_VIEW_HOVER_EXIT | 一個View退出懸停 |
TYPE_TOUCH_EXPLORATION_GESTURE_START | 觸摸瀏覽事件開始 |
TYPE_TOUCH_EXPLORATION_GESTURE_END | 觸摸瀏覽事件完成 |
TYPE_WINDOW_CONTENT_CHANGED | 窗口的內容發生變化,或子樹根佈局發生變化 |
TYPE_VIEW_SCROLLED | View滾動 |
TYPE_VIEW_TEXT_SELECTION_CHANGED | Edittext文字選中發生改變事件 |
TYPE_ANNOUNCEMENT | 應用產生一個通知事件 |
TYPE_VIEW_ACCESSIBILITY_FOCUSED | 得到無障礙焦點事件 |
TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED | 無障礙焦點事件清除 |
TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY | 在給定的移動粒度下遍歷視圖文本的事件 |
TYPE_GESTURE_DETECTION_START | 開始手勢監測 |
TYPE_GESTURE_DETECTION_END | 結束手勢監測 |
TYPE_TOUCH_INTERACTION_START | 觸摸屏幕事件開始 |
TYPE_TOUCH_INTERACTION_END | 觸摸屏幕事件結束 |
TYPE_WINDOWS_CHANGED | 屏幕上的窗口變化事件,須要API 21+ |
TYPE_VIEW_CONTEXT_CLICKED | View中的上下文點擊事件 |
TYPE_ASSIST_READING_CONTEXT | 輔助用戶讀取當前屏幕事件 |
好吧,上面的表其實並沒什麼大用,我仍是習慣直接把event.toString()給打印出來, 而後自行去判斷~
如圖就能夠拿到event類型,以及產生對應事件的類名,核心是這兩個, 除此以外還有Text和ContentDescription等。
好比我那個監聽Notification跳轉到添加好友頁的:
這裏就是對事件類型作了下判斷,而後獲取contentIntent,跳轉而已。 簡單點講就是:
你在這個方法裏,去判斷一波事件類型和className, 而後再獲取控件,作一些點擊,滾動,填充文本等。
自定義完這個服務要想讓他啓用你還得執行下面的操做:
Step 1:在res文件夾下建立xml文件夾,新建一個配置的xml文件(名字本身定)
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:accessibilityEventTypes="typeNotificationStateChanged|typeWindowStateChanged|typeWindowContentChanged" android:accessibilityFeedbackType="feedbackGeneric" android:accessibilityFlags="flagDefault" android:canRetrieveWindowContent="true" android:notificationTimeout="100" android:packageNames="com.tencent.mm" android:settingsActivity="com.coderpig.wechathelper.MainActivity" />
複製代碼
屬性簡介以下:
Step 2:接着AndroidManifest.xml文件中對該Service進行配置
先是添加一個權限:
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
複製代碼
接着是Service的配置:
這裏是你那個配置文件xml文件的文件名,其餘照抄。
Step 3:安裝到手機後,須要在手機設置的無障礙處開啓服務
通常在設置的輔助功能處能找到:
若是Logcat那裏能看到打印的LOG,說明服務正常運行,接下來要找控件節點
這裏能夠用到神器UI Automator來查看佈局層次,打開Android Studio, Ctrl + alt + A,輸入 monitor
依次點擊:選中設備 -> Dump View Hierarchy for UI Automator
稍等一會,右側就會出現當前頁面的佈局層次圖,如圖隨手選中一個邀請的節點:
右側能夠拿到對應的信息,通常比較經常使用的是這幾個,有一點要注意!!! resource-id不必定是惟一的
得到控件基本都會經過下述這個方法:
getRootInActiveWindow
( ):獲取當前整個活動窗口的根節點 返回的是一個**AccessibilityNodeInfo
**類,表明View的狀態信息, 提供了下述幾個很是實用的方法:
後面的這兩個方法會返回一個AccessibilityNodeInfo列表,通常操做是 遍歷,而後篩選特定節點,好比我程序裏的,得到底部Tab節點爲"通信錄", 而後點擊,跳轉後遍歷,篩選"羣聊"的節點,點擊。
另外,UI Automator有時並不可靠(實時問題),我建議寫多一個遍歷節點 的方法,能夠更清楚裏面的控件狀況:
拿到控件,接着就到觸發事件了。
經過調用**performAction
**()傳入一個時間類型便可觸發相應時間,好比點擊,長按等 事件就多了,本身點開AccessibilityNodeInfo類查看吧,這裏介紹下最經常使用的幾個事件:
//點擊
performAction(AccessibilityNodeInfo.ACTION_CLICK);
//長按
performAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
//滾動
performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); //向下滾一下
performAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); //向上滾一下
//填充EditText(API版本須要>18可用方法1,API>21兩種方法均可以使用)
//方法1:
ClipboardManager clipboard = (ClipboardManager)this.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("text", "填充內容");
clipboard.setPrimaryClip(clip);
//得到焦點
info.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
////粘貼進入內容
info.performAction(AccessibilityNodeInfo.ACTION_PASTE);
//方法2:
Bundle arguments = new Bundle();
arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, "填充內容");
info.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
複製代碼
除了控件觸發事件外,AccessibilityService提供了一個**performGlobalAction
(),用於執行 一些通用的事件**:
GLOBAL_ACTION_BACK 點擊返回按鈕
GLOBAL_ACTION_HOME 點擊home
GLOBAL_ACTION_NOTIFICATIONS 打開通知
GLOBAL_ACTION_RECENTS 打開最近應用
GLOBAL_ACTION_QUICK_SETTINGS 打開快速設置
GLOBAL_ACTION_POWER_DIALOG 打開長按電源鍵的彈框
複製代碼
另外在實際開發中,直接調用這些全局方法又是並無生效, 我在調GLOBAL_ACTION_BACK的時候就發現有時不會回退, 我的的解決方案是使用**handler.postDelay()**延時執行:
除了這樣玩之外,我還利用時間差,串行去執行幾個任務,好比:
上面的步驟是:
進入羣聊聊天信息頁後,列表滾動兩次,接着依次:
就不用過於依賴onAccessibilityEvent方法,除了用handler.postDelay外, 還能夠用Thread.sleep(休眠時長),用到的點大概就這麼多,其他的自行探究吧。
本節講解一波如何經過AccessibilityService來實現自動加好友以及拉人進羣, 以前是打算用xposed來寫的,後面發現沒我想像中簡單,並且不少用安卓機的都 不會搞機(基),root也不會,後來仍是選擇了AccessibilityService,簡單易用, 固然後面仍是會研究一波xposed實現的,敬請期待~ 對了,還有,以前那個網頁端的機器人被封緣由估計是信息秒回,若是有還用 itchat那個作機器人的,建議回覆的時間能夠稍微延長些;
關於AccessibilityService更多內容可見:
附:關鍵代碼(均可以在:github.com/coder-pig/W… 找到): 代碼有Bug的話正常,後續會優化下邏輯,感受寫得有點雜~
package com.coderpig.wechathelper;
import android.accessibilityservice.AccessibilityService;
import android.app.Notification;
import android.app.PendingIntent;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import java.util.List;
/**
* 描述:微信監控服務類
*
* @author CoderPig on 2018/04/04 13:46.
*/
public class HelperService extends AccessibilityService {
private static final String TAG = "HelperService";
private Handler handler = new Handler();
private String userName = "123";
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
int eventType = event.getEventType();
CharSequence classNameChr = event.getClassName();
String className = classNameChr.toString();
Log.d(TAG, event.toString());
switch (eventType) {
case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:
if (event.getParcelableData() != null && event.getParcelableData() instanceof Notification) {
Notification notification = (Notification) event.getParcelableData();
String content = notification.tickerText.toString();
if (content.contains("請求添加你爲朋友")) {
PendingIntent pendingIntent = notification.contentIntent;
try {
pendingIntent.send();
} catch (PendingIntent.CanceledException e) {
e.printStackTrace();
}
}
}
break;
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
switch (className) {
case "com.tencent.mm.plugin.subapp.ui.friend.FMessageConversationUI":
addFriend();
break;
case "com.tencent.mm.plugin.profile.ui.SayHiWithSnsPermissionUI":
verifyFriend();
break;
case "com.tencent.mm.plugin.profile.ui.ContactInfoUI":
performBackClick();
break;
case "com.tencent.mm.ui.LauncherUI":
if (!userName.equals("123")) {
openGroup();
}
break;
case "com.tencent.mm.ui.contact.ChatroomContactUI":
if (!userName.equals("123")) {
inviteGroup();
}
break;
case "com.tencent.mm.ui.chatting.ChattingUI":
if (!userName.equals("123")) {
openGroupSetting();
}
break;
case "com.tencent.mm.plugin.chatroom.ui.ChatroomInfoUI":
if (userName.equals("123")) {
performBackClick();
} else {
addToGroup();
}
break;
case "com.tencent.mm.ui.base.i":
dialogClick();
break;
}
break;
case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
}
}
private void addFriend() {
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
if (nodeInfo != null) {
List<AccessibilityNodeInfo> list = nodeInfo
.findAccessibilityNodeInfosByText("接受");
if (list != null && list.size() > 0) {
for (AccessibilityNodeInfo n : list) {
n.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
} else {
performBackClick();
}
}
}
private void verifyFriend() {
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
//得到用戶名
if (nodeInfo != null) {
userName = nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/d0n").get(0).getText().toString();
AccessibilityNodeInfo finishNode = nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/hd").get(0);
finishNode.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
private void openGroup() {
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
if (nodeInfo != null) {
List<AccessibilityNodeInfo> nodes = nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/ca5");
for (AccessibilityNodeInfo info : nodes) {
if (info.getText().toString().equals("通信錄")) {
info.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);
handler.postDelayed(new Runnable() {
@Override
public void run() {
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
if (nodeInfo != null) {
List<AccessibilityNodeInfo> nodes = nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/j5");
for (AccessibilityNodeInfo info : nodes) {
if (info.getText().toString().equals("羣聊")) {
info.getParent().getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);
break;
}
}
}
}
}, 500L);
}
}
}
}
private void inviteGroup() {
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
if (nodeInfo != null) {
List<AccessibilityNodeInfo> nodes = nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/a9v");
for (AccessibilityNodeInfo info : nodes) {
if (info.getText().toString().equals("小豬的Python學習交流羣")) {
info.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);
break;
}
}
}
}
private void openGroupSetting() {
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
if (nodeInfo != null) {
nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/he").get(0).performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
private void addToGroup() {
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
if (nodeInfo != null) {
List<AccessibilityNodeInfo> listNodes = nodeInfo.findAccessibilityNodeInfosByViewId("android:id/list");
if(listNodes != null && listNodes.size() > 0) {
AccessibilityNodeInfo listNode = listNodes.get(0);
listNode.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
listNode.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
final AccessibilityNodeInfo scrollNodeInfo = getRootInActiveWindow();
if (scrollNodeInfo != null) {
handler.postDelayed(new Runnable() {
@Override
public void run() {
List<AccessibilityNodeInfo> nodes = scrollNodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/d0b");
for (AccessibilityNodeInfo info : nodes) {
if (info.getContentDescription().toString().equals("添加成員")) {
info.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);
break;
}
}
}
},1000L);
handler.postDelayed(new Runnable() {
@Override
public void run() {
List<AccessibilityNodeInfo> editNodes = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("com.tencent.mm:id/arz");
if(editNodes != null && editNodes.size() > 0) {
AccessibilityNodeInfo editNode = editNodes.get(0);
Bundle arguments = new Bundle();
arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, userName);
editNode.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
}
}
}, 2300L);
handler.postDelayed(new Runnable() {
@Override
public void run() {
List<AccessibilityNodeInfo> cbNodes = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("com.tencent.mm:id/kr");
if(cbNodes != null) {
AccessibilityNodeInfo cbNode = null;
if(cbNodes.size() == 1) {
cbNode = cbNodes.get(0);
} else if(cbNodes.size() == 2) {
cbNode = cbNodes.get(1);
}
if (cbNode != null) {
cbNode.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);
AccessibilityNodeInfo sureNode = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("com.tencent.mm:id/hd").get(0);
sureNode.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
}
}, 3000L);
}
}
}
}
private void dialogClick() {
AccessibilityNodeInfo inviteNode = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("com.tencent.mm:id/aln").get(0);
inviteNode.performAction(AccessibilityNodeInfo.ACTION_CLICK);
userName = "123";
handler.postDelayed(new Runnable() {
@Override
public void run() {
List<AccessibilityNodeInfo> sureNodes = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("com.tencent.mm:id/aln");
if(sureNodes != null && sureNodes.size() > 0) {
AccessibilityNodeInfo sureNode = sureNodes.get(0);
sureNode.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
},1000L);
}
private void performBackClick() {
handler.postDelayed(new Runnable() {
@Override
public void run() {
performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
}
}, 300L);
}
//遍歷控件的方法
public void recycle(AccessibilityNodeInfo info) {
if (info.getChildCount() == 0) {
Log.i(TAG, "child widget----------------------------" + info.getClassName().toString());
Log.i(TAG, "showDialog:" + info.canOpenPopup());
Log.i(TAG, "Text:" + info.getText());
Log.i(TAG, "windowId:" + info.getWindowId());
Log.i(TAG, "desc:" + info.getContentDescription());
} else {
for (int i = 0; i < info.getChildCount(); i++) {
if (info.getChild(i) != null) {
recycle(info.getChild(i));
}
}
}
}
@Override
public void onInterrupt() {
}
}
複製代碼
來啊,Py交易啊
想加羣一塊兒學習Py的能夠加下,智障機器人小Pig,驗證信息裏包含: Python,python,py,Py,加羣,交易,屁眼 中的一個關鍵詞便可經過;
驗證經過後回覆 加羣 便可得到加羣連接(不要把機器人玩壞了!!!)~~~ 歡迎各類像我同樣的Py初學者,Py大神加入,一塊兒愉快地交流學♂習,van♂轉py。