• 基于微信红包插件的原理实现android任何APP自动发送评论(已开源)


    背景

    地址:https://github.com/huijimuhe/postman

    核心就是android的AccessibilityService,回复功能api需要23以上版本才行。

    其实很像在做单元测试。你可以有n种方式实现发帖功能,这只是一个比较邪火的方式,亲测过一次,可行。这里我以网易新闻客户端举例。

    模拟你在手机端的物理动作:选择新闻-》回复-》退回新闻列表-》进入下一个新闻-》回复-》退回新闻列表刷新-》进入-》回复....

    做的不精细,只是探究到底可不可行。你可以用在任何APP中自动发消息,只要没有验证码。

    你要拿来玩,请抱着一颗开心的心情。

    原理

    直接在github上开源的微信红包插件改的,红包插件项目和你需要了解的几篇文章在这里

    https://github.com/geeeeeeeeek/WeChatLuckyMoney

    http://www.xuebuyuan.com/2061597.html

    http://www.xuebuyuan.com/2061595.html

    http://developer.android.com/training/accessibility/service.html

    package com.huijimuhe.pman.services;
    
    import android.accessibilityservice.AccessibilityService;
    import android.content.ComponentName;
    import android.content.SharedPreferences;
    import android.content.pm.PackageManager;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    import android.preference.PreferenceManager;
    import android.util.Log;
    import android.view.accessibility.AccessibilityEvent;
    import android.view.accessibility.AccessibilityNodeInfo;
    
    import com.huijimuhe.pman.utils.PowerUtil;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class PostService extends AccessibilityService implements SharedPreferences.OnSharedPreferenceChangeListener {
    
        private static final String TAG = "PostService";
    
        private static final String MAIN_ACT = "MainActivity";
        private static final String DETAIL_ACT = "NewsPageActivity";
        private static final String BASE_ACT = "BaseActivity";
    
        private static final int MSG_BACK = 159;
        private static final int MSG_REFRESH_NEW_LIST = 707;
        private static final int MSG_READ_NEWS = 19;
        private static final int MSG_POST_COMMENT = 211;
        private static final int MSG_REFRESH_COMPLETE = 22;
        private static final int MSG_FINISH_COMMENT = 59;
    
        private String currentActivityName = MAIN_ACT;
        private HandlerEx mHandler = new HandlerEx();
    
        private boolean mIsMutex = false;
        private int mReadCount = 0;
        private List<String> readedNews = new ArrayList<>();
        private PowerUtil powerUtil;
        private SharedPreferences sharedPreferences;
    
        /**
         * AccessibilityEvent
         *
         * @param event 事件
         */
        @Override
        public void onAccessibilityEvent(AccessibilityEvent event) {
    
            if (sharedPreferences == null) return;
    
            setCurrentActivityName(event);
            watchMain(event);
            watchBasic(event);
            watchDetail(event);
        }
    
        private void watchMain(AccessibilityEvent event) {
            //新闻列表
            if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && currentActivityName.contains(MAIN_ACT)) {
                if (mReadCount > 4) {
                    //如果读取完了都没有新的就刷新
                    Log.d(TAG, "新闻已读取完,需要刷新列表");
                    //需要刷新列表了
                    mHandler.sendEmptyMessage(MSG_REFRESH_NEW_LIST);
                } else {
                    mHandler.sendEmptyMessage(MSG_READ_NEWS);
                }
            }
        }
    
        private void watchDetail(AccessibilityEvent event) {
            if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && currentActivityName.contains(DETAIL_ACT)) {
                //添加评论
                mHandler.sendEmptyMessage(MSG_POST_COMMENT);
            }
        }
    
        private void watchBasic(AccessibilityEvent event) {
            if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && currentActivityName.contains(BASE_ACT)) {
                Log.d(TAG, "进入非新闻页,即将退出");
                mHandler.sendEmptyMessage(MSG_BACK);
                mHandler.sendEmptyMessage(MSG_BACK);
            }
        }
    
        private void refreshList() {
            List<AccessibilityNodeInfo> nodes = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("android:id/list");
            for (AccessibilityNodeInfo node : nodes) {
                //页面是否加载完成
                if (node == null) return;
                //执行刷新
                node.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
            }
            //重新开始读取新闻
            mHandler.sendEmptyMessage(MSG_REFRESH_COMPLETE);
        }
    
        private void enterDetailAct() {
    
            //获取列表items
            List<AccessibilityNodeInfo> nodes = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("com.netease.newsreader.activity:id/perfect_item");
    
            for (AccessibilityNodeInfo node : nodes) {
                //页面是否加载完成
                if (node == null) return;
    
                //获取列表item的标题
                List<AccessibilityNodeInfo> titles = node.findAccessibilityNodeInfosByViewId("com.netease.newsreader.activity:id/title");
    
                for (AccessibilityNodeInfo title : titles) {
    
                    //检查是否已读取
                    if (!readedNews.contains(title.getText().toString())) {
                        //点击读取该新闻
                        readedNews.add(title.getText().toString());
                        node.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);
                        Log.d(TAG, "进入新闻:" + title.getText().toString());
                        mReadCount++;
                        //进入一个就停止
                        return;
                    }
                }
            }
        }
    
        private void postComment() {
            //激活输入框
            List<AccessibilityNodeInfo> nodes = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("com.netease.newsreader.activity:id/mock_reply_edit");
            for (AccessibilityNodeInfo node : nodes) {
    
                //页面是否加载完成
                if (node == null) return;
    
                node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
            }
    
            //输入内容
            List<AccessibilityNodeInfo> editNodes = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("com.netease.newsreader.activity:id/reply_edit");
            for (AccessibilityNodeInfo node : editNodes) {
    
                //页面是否加载完成
                if (node == null) return;
    
                Bundle arguments = new Bundle();
                arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, "抽烟的人最讨厌了");
                node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
            }
    
    //        //回复按钮
    //        List<AccessibilityNodeInfo> postNodes = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("com.netease.newsreader.activity:id/reply");
    //        for (AccessibilityNodeInfo node : postNodes) {
    //           //页面是否加载完成
    //           if (node == null) return;
    //           node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
    //        }
    
            //退出
            mHandler.sendEmptyMessage(MSG_FINISH_COMMENT);
    
            Log.d(TAG, "评论已发表");
        }
    
        /**
         * 设置当前页面名称
         *
         * @param event
         */
        private void setCurrentActivityName(AccessibilityEvent event) {
    
            if (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
                return;
            }
    
            try {
                ComponentName componentName = new ComponentName(event.getPackageName().toString(), event.getClassName().toString());
    
                getPackageManager().getActivityInfo(componentName, 0);
                currentActivityName = componentName.flattenToShortString();
                Log.d(TAG, "<--pkgName-->" + event.getPackageName().toString());
                Log.d(TAG, "<--className-->" + event.getClassName().toString());
                Log.d(TAG, "<--currentActivityName-->" + currentActivityName);
            } catch (PackageManager.NameNotFoundException e) {
                currentActivityName = MAIN_ACT;
            }
        }
    
        @Override
        public void onDestroy() {
            this.powerUtil.handleWakeLock(false);
            super.onDestroy();
        }
    
        @Override
        public void onInterrupt() {
    
        }
    
        @Override
        public void onServiceConnected() {
            super.onServiceConnected();
            this.watchFlagsFromPreference();
        }
    
        /**
         * 屏幕是否常亮
         */
        private void watchFlagsFromPreference() {
            sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
            sharedPreferences.registerOnSharedPreferenceChangeListener(this);
    
            this.powerUtil = new PowerUtil(this);
            Boolean watchOnLockFlag = sharedPreferences.getBoolean("pref_watch_on_lock", false);
            this.powerUtil.handleWakeLock(watchOnLockFlag);
        }
    
        @Override
        public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
            if (key.equals("pref_watch_on_lock")) {
                Boolean changedValue = sharedPreferences.getBoolean(key, false);
                this.powerUtil.handleWakeLock(changedValue);
            }
        }
    
        /**
         * 处理机
         */
        private class HandlerEx extends Handler {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    //后退
                    case MSG_BACK:
                        new Handler().postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                performGlobalAction(GLOBAL_ACTION_BACK);
                            }
                        }, 1000);
                        break;
                    //结束评论
                    case MSG_FINISH_COMMENT:
                        for (int i = 0; i < 4; i++) {
                            new Handler().postDelayed(new Runnable() {
                                @Override
                                public void run() {
                                    performGlobalAction(GLOBAL_ACTION_BACK);
                                }
                            }, 2000 +i*500);
                        }
                        break;
                    //刷新列表
                    case MSG_REFRESH_NEW_LIST:
                        new Handler().postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                refreshList();
                            }
                        }, 3000);
                        break;
                    //结束刷新
                    case MSG_REFRESH_COMPLETE:
                        new Handler().postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                mReadCount = 0;
                                enterDetailAct();
                            }
                        }, 3000);
                        break;
                    //进入新闻页
                    case MSG_READ_NEWS:
                        new Handler().postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                enterDetailAct();
                            }
                        }, 3000);
                        break;
                    //发送评论
                    case MSG_POST_COMMENT:
                        new Handler().postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                postComment();
                            }
                        }, 3000);
                        break;
                }
            }
        }
    }
    View Code

    在开始写代码前,你应该至少阅读了之前几篇文章和微信红包插件的代码,然后还应该掌握用Android Device Monitor查看UI树的工具使用。(最近开始研究iOS逆向,这个确实比reveal和cycript方便太多)

    粗略实现步骤

    1.manifest中申明服务

     <service
                    android:name=".services.PostService"
                    android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
                <intent-filter>
                    <action android:name="android.accessibilityservice.AccessibilityService"/>
                </intent-filter>
                <meta-data android:name="android.accessibilityservice"
                           android:resource="@xml/accessible_service_config"/>
            </service>

     2.设定你需要监控的app包名来过滤,在/res/xml/accessible_service_config.xml中

    <accessibility-service
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:description="@string/app_description"
        android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged"
        android:accessibilityFeedbackType="feedbackAllMask"
        android:packageNames="com.netease.newsreader.activity"
        android:notificationTimeout="10"
        android:settingsActivity="com.huijimuhe.pman.activities.SettingsActivity"
        android:accessibilityFlags="flagIncludeNotImportantViews|flagDefault"
        android:canRetrieveWindowContent="true"/>

    比如网易的,android:packageNames="com.netease.newsreader.activity"

    3.在AccessibleService中实现对事件的监听 

      @Override
        public void onAccessibilityEvent(AccessibilityEvent event) {
    
            if (sharedPreferences == null) return;
    
            setCurrentActivityName(event);
            watchMain(event);
            watchBasic(event);
            watchDetail(event);
        }
    /**
     * 设置当前页面名称
     *
     * @param event
     */
    private void setCurrentActivityName(AccessibilityEvent event) {
    
        if (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
            return;
        }
    
        try {
            ComponentName componentName = new ComponentName(event.getPackageName().toString(), event.getClassName().toString());
    
            getPackageManager().getActivityInfo(componentName, 0);
            currentActivityName = componentName.flattenToShortString();
            Log.d(TAG, "<--pkgName-->" + event.getPackageName().toString());
            Log.d(TAG, "<--className-->" + event.getClassName().toString());
            Log.d(TAG, "<--currentActivityName-->" + currentActivityName);
        } catch (PackageManager.NameNotFoundException e) {
            currentActivityName = MAIN_ACT;
        }
    }

    4.监控是否是新闻列表,可以设定个页面刷新阀值

      //新闻列表
            if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && currentActivityName.contains(MAIN_ACT)) {
                if (mReadCount > 4) {
                    //如果读取完了都没有新的就刷新
                    Log.d(TAG, "新闻已读取完,需要刷新列表");
                    //需要刷新列表了
                    mHandler.sendEmptyMessage(MSG_REFRESH_NEW_LIST);
                } else {
                    mHandler.sendEmptyMessage(MSG_READ_NEWS);
                }
            }

    5.监控是否是新闻详情

        private void watchDetail(AccessibilityEvent event) {
            if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && currentActivityName.contains(DETAIL_ACT)) {
                //添加评论
                mHandler.sendEmptyMessage(MSG_POST_COMMENT);
            }
        }

    6监控是否广告或其他专题,不做操作

        private void watchBasic(AccessibilityEvent event) {
            if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && currentActivityName.contains(BASE_ACT)) {
                Log.d(TAG, "进入非新闻页,即将退出");
                mHandler.sendEmptyMessage(MSG_BACK);
                mHandler.sendEmptyMessage(MSG_BACK);
            }
        }

    7.回复评论

        private void postComment() {
            //激活输入框
            List<AccessibilityNodeInfo> nodes = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("com.netease.newsreader.activity:id/mock_reply_edit");
            for (AccessibilityNodeInfo node : nodes) {
    
                //页面是否加载完成
                if (node == null) return;
    
                node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
            }
    
            //输入内容
            List<AccessibilityNodeInfo> editNodes = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("com.netease.newsreader.activity:id/reply_edit");
            for (AccessibilityNodeInfo node : editNodes) {
    
                //页面是否加载完成
                if (node == null) return;
    
                Bundle arguments = new Bundle();
                arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, "抽烟的人最讨厌了");
                node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
            }
    
            //退出
            mHandler.sendEmptyMessage(MSG_FINISH_COMMENT);
    
            Log.d(TAG, "评论已发表");
        }

    总体思路是通过postDelay来实现操作的间隔,其他的请自己阅读代码,我只测试了下思路是否可行就没有继续延伸下去了。

    大家不要留言说我简单事情做那么复杂。用物理方式(现在回头看倒觉得很像单元测试)实现回复,真实性是100%,发贴机你要倒腾一个别人家服务器看不出作弊的,估计更费劲吧。

    如果你觉得python写脚本很酷或者直接用fiddler抓包然后写个发帖器都行。我这还有个用Tesseract-OCR做验证码识别的winform。

    做这个只是当时觉得红包插件原理很酷,可以有点其他玩法,我也确实倒腾了一个,也开源了https://github.com/huijimuhe/focus

    要是开开脑洞,比如不停的微信给欠债老板发消息让还钱啥的,这种插件倒是很能气死他,哈哈哈哈。

    要搞什么推广(尤其是卖面膜的)应该靠金主,而不是这个,哈哈哈哈。

    P.S. 
    自己在做独立开发,希望广结英豪,尤其是像我一样脑子短路不用react硬拼anroid、ios原生想干点什么的朋友。

    App独立开发群533838427

    微信公众号『懒文』-->lanwenapp<--

  • 相关阅读:
    页面显示This is the initial start page for the WebDriver server.的解决办法
    Robot Framework + Selenium library + IEDriver环境搭建
    selenium之 chromedriver与chrome版本映射表(更新至v2.38)
    Python 各种测试框架简介(三):nose
    Python 各种测试框架简介
    python 几种常用测试框架
    一步一步教你搭建和使用FitNesse
    xss 学习记录
    android root 原理
    rtd1296 mtd 设备驱动分析
  • 原文地址:https://www.cnblogs.com/matoo/p/5452553.html
Copyright © 2020-2023  润新知