Quote:

On Saturday and Sunday of last week, I posted two articles (Python) about implementing wechat robot using Itchat:

  • Pig’s Journey to Learn Python — 18
  • 19. Automatically verify friends on Python wechat, automatically reply, and send group chat links

By attaching the script to the server, I’m done with manual transmission, no need to manually forward the universe, no need to manually add friends, and then pull people into my Py trading group one by one. When I secretly happy, wechat did not let me this little cat.

I still remember that morning, I came to the company happily early, updated a wave of code to prepare for my robot, when I closed the ali Cloud script, this time an accident, my robot trumpet, can no longer through the interface of the wechat web terminal login!! Scan the QR code, always prompt is the following sentence:

<error><ret>1203</ret><message>The current login environment is abnormal. For the security of your account, you cannot log in to the Web wechat for the time being. You can log in via Windows wechat, Mac wechat or mobile wechat.</message></error>
Copy the code

Yes, is such a sentence, can not find the appeal channel, do not know when it may be unsealed. (the client can be used normally) and now another new application micro channel small is unable to log in micro channel web terminal, in fact, this is micro channel in slowly shut down the web version login, the main reason is the flood of robots!

Without the web version of wechat, life is still to live, can only return to manual gear? Several solutions:

  • 1. Study the client protocol (this is expensive, and the authorities change things slightly, you’ll cry)
  • 2.APP reverse, the use of Xposed framework, hook related methods, but also some research costs;
  • 3. Use something like a keystroke Sprite and write a script that lets it automatically click, automate testing tools, or the AccessibilityService described in this section — AccessibilityService

AccessibilityService (AccessibilityService) is not a new thing. It has been around for a long time.

Grab red envelopes, automatic installation, a key XXX and so on, can be described as thriving.

Using AccessibilityService is also very Easy. The key points are:

Find the node through UI Automator, locate the specific node through resource-id, text, content-desc and other unique features, and then perform various simulation operations, dot, scroll, fill, the usage is relatively simple, most of the time will be spent in trial and error and logic adjustment!

Use AccessibilityService to automatically add friends and pull people into a group chat Gif experience:

Gif is a bit faster, but it takes a total of 15s to add and pull, which is pretty objective. AccessibilityService (AccessibilityService


This section describes the usage of AccessibilityService

1. User-defined Service inherits AccessibilityService

For example, you can define a custom AccessibilityService class that overrides the two main methods:

OnInterrupt () : Callback to an auxiliary interrupt, which is largely ignored. The core is onAccessibilityEvent(AccessibilityEvent Event).

When something happens to the interface, such as Notification at the top, interface update, content change, etc., this method is triggered. You can respond to different actions according to different events. Open the AccessibilityEvent class to see a list of event types

The event type describe
TYPE_VIEW_CLICKED View by clicking
TYPE_VIEW_LONG_CLICKED The View was long press
TYPE_VIEW_SELECTED The View is selected
TYPE_VIEW_FOCUSED View gets focus
TYPE_VIEW_TEXT_CHANGED View text change
TYPE_WINDOW_STATE_CHANGED A PopupWindow, Menu, or Dialog is opened
TYPE_NOTIFICATION_STATE_CHANGED Change Notification
TYPE_VIEW_HOVER_ENTER A View enters the hover
TYPE_VIEW_HOVER_EXIT A View exits the hover
TYPE_TOUCH_EXPLORATION_GESTURE_START The touch browse event begins
TYPE_TOUCH_EXPLORATION_GESTURE_END Touch browse event completed
TYPE_WINDOW_CONTENT_CHANGED The content of the window changes, or the layout of subroots changes
TYPE_VIEW_SCROLLED The View to scroll
TYPE_VIEW_TEXT_SELECTION_CHANGED Edittext Text selection changes events
TYPE_ANNOUNCEMENT The application generates a notification event
TYPE_VIEW_ACCESSIBILITY_FOCUSED Get a barrier-free focus event
TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED Frictionless focus event cleared
TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY An event that traverses view text at the given movement granularity
TYPE_GESTURE_DETECTION_START Start gesture monitoring
TYPE_GESTURE_DETECTION_END End gesture monitoring
TYPE_TOUCH_INTERACTION_START The Touch screen event starts
TYPE_TOUCH_INTERACTION_END The touch screen event ends
TYPE_WINDOWS_CHANGED Window change events on the screen require API 21+
TYPE_VIEW_CONTEXT_CLICKED Context click event in View
TYPE_ASSIST_READING_CONTEXT Assist the user to read the current screen event

Ok, the above table is not very useful, I would like to print out event.toString() and decide for myself

You can get the type of event and the name of the class that generated the corresponding event. The core is these two, in addition to Text and ContentDescription.

For example, I listen to Notification and jump to the Add Friends page:

So what we’re doing here is we’re determining the event type, and we’re getting contentIntent, just jumping. To put it simply:

In this method, you determine a wave of event types and classnames, then get the control, do some clicking, scrolling, filling in the text, and so on.


2. Configure services

After customizing the service, you need to do the following to enable it:

Step 1: Create an XML folder under the res folder and create a new configuration XML file (name your own).

<?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" />
Copy the code

The attributes are described as follows:

  • AccessibilityEventTypes: set up to monitor the event types, separated with a |, all can use typeAllMask listening;
  • AccessibilityFeedbackType: services provide feedback type, feedbackGeneric general feedback.
  • AccessibilityFlags: Additional flags for accessibility, flagDefault default configuration
  • CanRetrieveWindowContent: Whether the accessibility service can retrieve the properties of the content of the active window
  • NotificationTimeout: response time
  • PackageNames: Specifies the name of the application package to listen to
  • SettingsActivity: The name of the activity class that allows the user to change accessibility

Step 2: Then configure the Service in the Androidmanifest.xml file

First add a permission:

android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"  
Copy the code

Next is the configuration of the Service:

Here is the name of your configuration file XML file, copy the rest.

Step 3: After installing the mobile phone, enable the service in the barrier-free area set on the mobile phone

You can usually find it in the accessibility section of the Settings:

If you can see the printed LOG in Logcat, the service is running properly. Next, look for the control node


3. To find the control

To view the layout hierarchy, use UI Automator, open Android Studio, Ctrl + Alt + A, and type Monitor

Go to Device -> Dump View Hierarchy for UI Automator

Wait for a while, the layout hierarchy diagram of the current page will appear on the right side, as shown in the figure, randomly select an invited node:

The right side can get the corresponding information, generally more commonly used are these several, there is a point to pay attention to!! Resource-id does not have to be unique

Control is basically obtained by the following method:

GetRootInActiveWindow () : Getting the root node of the current active window returns an AccessibilityNodeInfo class that represents the state of the View and provides several useful methods:

  • GetParent: Obtains the parent node.
  • GetChild: Gets the child node.
  • PerformAction: Performs an action on a node.
  • FindAccessibilityNodeInfosByText: node element via the string.
  • FindAccessibilityNodeInfosByViewId: node element via the view id.

These last two methods will return a list of AccessibilityNodeInfo, so what you do is you iterate over it, and then you filter specific nodes, like in my program, you get the bottom Tab that’s “address book,” and then you click, and then you go through it, and then you filter the group chat nodes, and then you click.

In addition, UI Automator can be unreliable (real-time issues), so I recommend writing an extra method to iterate over the nodes to get a better idea of what’s going on inside:

Get the control, and then it’s time to fire the event.


4. The event is triggered

Call **performAction**() and pass in a time type to trigger the corresponding time, such as click, long press and other events, you click on AccessibilityNodeInfo class to see, here are the most commonly used events:

/ / click
performAction(AccessibilityNodeInfo.ACTION_CLICK);

/ / long press
performAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);

/ / rolling
performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); // Roll down
performAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); // Roll up

// Fill in the EditText(API version requires >18 for method 1, API>21 for both)

// Method 1:
ClipboardManager clipboard = (ClipboardManager)this.getSystemService(Context.CLIPBOARD_SERVICE);  
ClipData clip = ClipData.newPlainText("text"."Fill in the content");  
clipboard.setPrimaryClip(clip);  
// Get the focus
info.performAction(AccessibilityNodeInfo.ACTION_FOCUS);  
//// Paste the entry content
info.performAction(AccessibilityNodeInfo.ACTION_PASTE);  

// Method 2:
Bundle arguments = new Bundle();  
arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, "Fill in the content");  
info.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);  
Copy the code

In addition to the control firing events, the AccessibilityService provides a **performGlobalAction() that performs some common events ** :

GLOBAL_ACTION_BACK Click the Back button GLOBAL_ACTION_HOME click home GLOBAL_ACTION_NOTIFICATIONS to open the latest app GLOBAL_ACTION_RECENTS GLOBAL_ACTION_QUICK_SETTINGS Enables quick Settings. GLOBAL_ACTION_POWER_DIALOG Displays the popup when you hold down the power buttonCopy the code

** handler.postdelay ()** delay ()** delay ()** delay ()** delay ()** delay ()

In addition to playing this way, I also use the time difference to perform several tasks in succession, such as:

The steps above are:

After entering the group chat information page, the list scrolls twice, then successively:

  • 1. After a delay of 1 second, find the add Member button and click;
  • 2. After a delay of 2.3s, fill the name in the EditText
  • 3. After a delay of 3s, click OK

Instead of relying too much on the onAccessibilityEvent method, you can use thread. sleep in addition to handle. postDelay.


summary

This section describes a wave of how to achieve automatic add friends and pull people into the group through AccessibilityService, before is going to use Xposed to write, found no simple in the back I imagine, and many android machine will not do machine (base), root will not, Later or choose AccessibilityService, easy to use, of course, after will study a wave of Xposed implementation, please look forward to ~ right, and, before the web end of the robot is estimated to be the reason for the information second back, if there is also using itchat that do the robot, A slightly longer response time is recommended;

More about AccessibilityService:

  • Android accessibility: blog.csdn.net/qq_24800377…
  • Building the org.eclipse.swt.accessibility Services:developer.android.com/guide/topic…
  • Developing an the org.eclipse.swt.accessibility Service:developer.android.com/training/ac…

Attached: Key code (all available at: github.com/coder-pig/W… Find) : the code has a Bug, then normal, will be optimized under the logic, feel a little miscellaneous ~

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; /** * Description: * * @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("Request to add you as a friend")) { 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("Accept");
            if(list ! = null && list.size() > 0) {for(AccessibilityNodeInfo n : list) { n.performAction(AccessibilityNodeInfo.ACTION_CLICK); }}else {
                performBackClick();
            }
        }
    }

    private void verifyFriend() { AccessibilityNodeInfo nodeInfo = getRootInActiveWindow(); // Get the user nameif(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("Address book")) {
                    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("Group")) {
                                        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("Piggy's Python Learning Exchange")) {
                    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(newRunnable() {
                        @Override
                        public void run() {
                            List<AccessibilityNodeInfo> nodes = scrollNodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/d0b");
                            for (AccessibilityNodeInfo info : nodes) {
                                if (info.getContentDescription().toString().equals("Add member")) {
                                    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(newRunnable() {
                        @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 voidperformBackClick() {
        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 voidonInterrupt() {}}Copy the code

Come on, Py deal

If you want to learn Py together, you can add “Pig”, and verify that the information contains: Python, Python, Py, Py, plus group, trade, butthar.

After verification passed reply add group can get add group link (don’t play the robot bad!! ~ ~ ~ welcome all kinds of Py beginners like me, Py big god to join us, and have fun learning and learning together, van came and turned Py.