zoukankan      html  css  js  c++  java
  • 基于微信红包插件的原理实现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<--

  • 相关阅读:
    Vue 2.x windows环境下安装
    VSCODE官网下载缓慢或下载失败 解决办法
    angular cli 降级
    Win10 VS2019 设置 以管理员身份运行
    XSHELL 连接 阿里云ECS实例
    Chrome浏览器跨域设置
    DBeaver 执行 mysql 多条语句报错
    DBeaver 连接MySql 8.0 报错 Public Key Retrieval is not allowed
    DBeaver 连接MySql 8.0报错 Unable to load authentication plugin 'caching_sha2_password'
    Linux系统分区
  • 原文地址:https://www.cnblogs.com/matoo/p/5452553.html
Copyright © 2011-2022 走看看