基于微信红包插件的原理实现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
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;
}
}
}
}
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;
            }
        }
    }
}
在开始写代码前,你应该至少阅读了之前几篇文章和微信红包插件的代码,然后还应该掌握用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来实现操作的间隔,其他的请自己阅读代码,我只测试了下思路是否可行就没有继续延伸下去了。
 
                    
                
 
                
            
         
 浙公网安备 33010602011771号
浙公网安备 33010602011771号