精华内容
下载资源
问答
  • Android 微信红包插件自动抢微信红包,安卓红包插件。 Android 微信红包插件截图
  • 为大家准备了Android微信红包插件,轻轻松松帮助大家实现微信红包功能,感兴趣的小伙伴们可以参考一下
  • 微信红包插件

    2017-04-27 15:22:25
    打开『微信红包』应用,开启插件。 做你想做的事。 坐等红包进账。 实现原理 请见技术文档,注意文档描述的是dev分支(已弃用)的具体实现,而不是stable分支。若有疑问,请在ISSUES中提出。 更新日志 完整的更新...
  • 微信红包插件.rar,太多无法一一验证是否可用,程序如果跑不起来需要自调,部分代码功能进行参考学习。
  • 这个是一个插件,根据引导安装打开相应的权限即可,只支持安卓手机,设置好建议开启悬浮模式,可以自动抢微信红包
  • 大神最新发布在网上的红包微信插件插件基于xposed的,还是开源的! 哇!非常好的实战分析apk,对练习手工samli还原和xposed应用编程都有很大帮助! 所有还原的java代码放置在reverse_src/目录,供学习参考!(站在...
  • 想了解下这个,企业微信红包是否有插件,不知道哪位大神能指点迷津
  • 微信红包插件这个Android插件可以帮助你在微信群聊抢红包时战无不胜。当检测到红包时,插件会自动点击屏幕,和人工点击的速度不可同日而语。你正在查看的是dev分支,这个分支仍在开发中,如果你希望有一个可以立即...

    微信抢红包插件

    这个Android插件可以帮助你在微信群聊抢红包时战无不胜。当检测到红包时,插件会自动点击屏幕,和人工点击的速度不可同日而语。

    你正在查看的是dev分支,这个分支仍在开发中,如果你希望有一个可以立即使用的插件请切换到stable分支。

    注: stable分支的插件只点击最新的红包,根据目前测试抢红包成功率100%。dev分支在stable分支的基础上尝试了大量修改和优化,能使用但无法保证稳定性。

    下面的文档仅针对dev分支。

    预期特性

    可以抢屏幕上显示的所有红包,同类插件往往只能获取最新的一个红包。

    智能跳过已经戳过的红包,避免频繁点击影响正常使用。

    红包日志功能,方便查看抢过的红包内容。

    性能优化,感受不到插件的存在,可一直后台开启,不影响日常聊天。

    由于这是一份教学代码,项目的文档和注释都比较完整,代码适合阅读。

    注: 其中一些功能还存在问题,仍在调试中。

    实现原理

    1. 抢红包流程的逻辑控制

    这个插件通过一个Stage类来记录当前对应的阶段。Stage类被设计成单例并惰性实例化,因为一个Service不需要也不应该处在不同的阶段。对外暴露阶段常量和entering和getCurrentStage两个方法,分别记录和获取当前的阶段。

    public class Stage {

    private static Stage stageInstance;

    public static final int FETCHING_STAGE = 0, OPENING_STAGE = 1, FETCHED_STAGE = 2, OPENED_STAGE = 3;

    private int currentStage = FETCHED_STAGE;

    private Stage() {}

    public static Stage getInstance() {

    if (stageInstance == null) stageInstance = new Stage();

    return stageInstance;

    }

    public void entering(int _stage) {

    stageInstance.currentStage = _stage;

    }

    public int getCurrentStage() {

    return stageInstance.currentStage;

    }

    }

    1.1 阶段说明

    阶段

    说明

    FETCHING_STAGE

    正在读取屏幕上的红包,此时不应有别的操作

    FETCHED_STAGE

    已经结束一个FETCH阶段,屏幕上的红包都已加入待抢队列

    OPENING_STAGE

    正在拆红包,此时不应有别的操作

    OPENED_STAGE

    红包成功抢到,进入红包详情页面

    程序以FETCHED_STAGE 开始,将屏幕上的红包加入待抢队列:

    --> FETCHED_STAGE --> FETCHING_STAGE --> FETCHED_STAGE -->

    处理待抢队列中的红包:

    --> [CLICK] --> OPENING_STAGE --> [CLICK] --> OPENED_STAGE --> [BACK] --> FETCHED_STAGE -->(抢到)

    --> [CLICK] --> OPENING_STAGE --> [BACK] --> FETCHED_STAGE -->(没抢到)

    不断重复流程1和2

    1.2 根据阶段选择不同的入口

    在每次窗体状态发生变化后,根据当前所在的阶段选择入口。

    switch (Stage.getInstance().getCurrentStage()) {

    case Stage.OPENING_STAGE:

    // .......

    Stage.getInstance().entering(Stage.FETCHED_STAGE);

    performGlobalAction(GLOBAL_ACTION_BACK);

    break;

    case Stage.OPENED_STAGE:

    Stage.getInstance().entering(Stage.FETCHED_STAGE);

    performGlobalAction(GLOBAL_ACTION_BACK);

    break;

    case Stage.FETCHED_STAGE:

    if (nodesToFetch.size() > 0) {

    AccessibilityNodeInfo node = nodesToFetch.remove(nodesToFetch.size() - 1);

    if (node.getParent() != null) {

    // .......

    Stage.getInstance().entering(Stage.OPENING_STAGE);

    node.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);

    }

    return;

    }

    Stage.getInstance().entering(Stage.FETCHING_STAGE);

    fetchHongbao(nodeInfo);

    Stage.getInstance().entering(Stage.FETCHED_STAGE);

    break;

    }

    2. 屏幕内容检测和自动化点击的实现

    和其他插件一样,这里使用的是Android API提供的AccessibilityService。这个类位于android.accessibilityservice包内,该包中的类用于开发无障碍服务,提供代替或增强的用户反馈。

    AccessibilityService 服务在后台运行,等待系统在发生 AccessibilityEvent 事件时回调。这些事件指的是用户界面上发生的状态变化, 比如焦点变更、按钮按下等等。服务可以请求“查询当前窗口中内容”的能力。 开发辅助服务需要继承该类并实现其抽象方法。

    2.1 配置AccessibilityService

    在这个例子中,我们需要监听的事件是当红包来或者滑动屏幕时引起的屏幕内容变化,和点开红包时窗体状态的变化,因此我们只需要在配置XML的accessibility-service标签中加入一条

    android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged"

    或在onAccessibilityEvent回调函数中对事件进行一次类型判断

    final int eventType = event.getEventType();

    if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED

    || eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) {

    // ...

    }

    除此之外,由于我们只监听微信,还需要指定微信的包名

    android:packageNames="com.tencent.mm"

    为了获取窗口内容,我们还需要指定

    android:canRetrieveWindowContent="true"

    其他配置请看代码。

    2.2 获取红包所在的节点

    首先,我们要获取当前屏幕的根节点,下面两种方式效果是相同的:

    AccessibilityNodeInfo nodeInfo = event.getSource();

    AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();

    这里返回的AccessibilityNodeInfo是窗体内容的节点,包含节点在屏幕上的位置、文本描述、子节点id、能否点击等信息。从AccessibilityService的角度来看,窗体上的内容表示为辅助节点树,虽然和视图的结构不一定一一对应。换句话说,自定义的视图可以自己描述上面的辅助节点信息。当辅助节点传递给AccessibilityService之后就不可更改了,如果强行调用引起状态变化的方法会报错。

    在聊天页面,每个红包上面都有“领取红包”这几个字,我们把它作为识别红包的依据。因此,如果你收到了这四个字的文本消息,那插件会做出误判,并没有什么办法可以解决。

    AccessibilityNodeInfo的API中有一个findAccessibilityNodeInfosByText方法允许我们通过文本来搜索界面中的节点。匹配是大小写敏感的,它会从遍历树的根节点开始查找。API文档中特别指出,为了防止创建大量实例,节点回收是调用者的责任,这一点会在接下来的部分中讲到。

    List node1 = nodeInfo.findAccessibilityNodeInfosByText("领取红包");

    2.3 对节点进行操作

    AccessibilityNodeInfo同样暴露了一个API——performAction来对节点进行点击或者其他操作。出于安全性考虑,只有这个操作来自AccessibilityService时才会被执行。

    nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);

    不过,我们在调试时发现"领取红包"的mClickable属性为false,说明点击的监听加在它父辈的节点上。通过getParent获取父节点,这个节点是可以点击的。

    我们还需要全局的返回操作,最方便的办法就是performGlobalAction,不过注意这个方法是API 16后才有的。

    performGlobalAction(GLOBAL_ACTION_BACK);

    3. 获取屏幕上的所有红包

    和其他插件最大的区别是,这个插件的逻辑是获取屏幕上所有的红包节点,去掉已经获取过的之后,将待抢红包加入队列,再将队列中的红包一个个打开。

    3.1 判断红包节点是否已被抢过

    实现这一点是编写时最大的障碍。对于一般的Java对象实例来说,除非被GC回收,实例的Id都不会变化。我最初的想法是通过正则表达式匹配下面的十六进制对象id来表示一个红包。

    android.view.accessibility.AccessibilityNodeInfo@2a5a7c; .......

    但在测试中,队列中的部分红包没有被戳开。进一步观察发现,新的红包节点和旧的红包节点id出现了重复,且出现概率较大。由于GC日志正常,我推测AccessibilityNode可能有一个实例池的设计。获取当前窗体节点树的时候,从一个可重用的实例池中获取一个辅助节点信息 (AccessibilityNodeInfo)实例。在接下来的获取时,仍然从实例池中获取节点实例,这时可能会重用之前的实例。这样的设计是有好处的,可以防止每次返回都创建大量的实例,影响性能。AccessibilityNodeProvider的源码表明了这样的设计。

    也就是说,为了标识一个唯一的红包,只用实例id是不充分的。这个插件采用的是红包内容+节点实例id的hash来标记。因为同一屏下,同一个节点树下的节点id是一定不会重复的,滑动屏幕后新红包的内容和节点id同时重复的概率已经大大减小。更改标识策略后,实测中几乎没有出现误判。

    3.2 将新出现的红包加入待抢队列

    我们维护了两个列表,分别记录待抢红包和抢过的红包标识。

    private List nodesToFetch = new ArrayList<>();

    private List fetchedIdentifiers = new ArrayList<>();

    在每次读取聊天屏幕的时候,会检查这个红包是否在fetchedIdentifiers队列中,如果没有,则加入nodesToFetch队列。

    for (AccessibilityNodeInfo cellNode : fetchNodes) {

    String id = getHongbaoHash(cellNode);

    /* 如果节点没有被回收且该红包没有抢过 */

    if (id != null && !fetchedIdentifiers.contains(id)) {

    nodesToFetch.add(cellNode);

    }

    }

    4. 打开队列中的红包

    通过红包打开后显示的文本判断这个红包是否可以抢,进行接下来的操作。

    4.1 判断红包节点是否被重用

    这也是实现时的一个坑。前面提到了实例池的设计,当我们把红包们加入待抢队列,戳完一个红包再回来时,队列中的其他红包节点可能已被回收重用,如果再去点击这个节点,显然没有什么卵用。

    为了解决这个问题,我们只能退而求其次,在点开前做一次检查。如果发现被重用了,就舍弃这个节点,在下一轮fetch的阶段重新加入待抢队列。确认没有重用立即打开,并把节点hash加入fetchedIdentifiers队列。这里如果node失效getHongbaoHash会返回null。

    AccessibilityNodeInfo node = nodesToFetch.remove(nodesToFetch.size() - 1);

    if (node.getParent() != null) {

    String id = getHongbaoHash(node);

    if (id == null) return;

    fetchedIdentifiers.add(id);

    Stage.getInstance().entering(Stage.OPENING_STAGE);

    node.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);

    }

    可以看出这并不是很有效率的解决方案。在实测中,有时队列中中红包失效后被舍弃,但没有新的AccessibilityEvent发生,接下来的操作都被挂起了。在戳过较多红包之后,这种情况表现得尤为明显,必须要显式地改变窗体内容才能解决。

    4.2 根据红包类型选择操作

    红包被戳开前会进行查重。戳开后如果界面上出现了“拆红包”几个字,说明红包还没有被别人抢走,立刻点击“拆红包”并将stage标记为OPENED_STAGE。

    此时,另三种情况表明抢红包失败了,直接返回,接下来状态会被标记为FETCHED_STAGE。

    “过期”,说明红包超过有效时间

    “手慢了”,说明红包发完但没抢到

    “红包详情”,说明你已经抢到过

    4.3 防止加载红包时返回

    戳开红包和红包加载完之间有一个“正在加载”的过渡动画,会触发onAccessibilityEvent回调方法。如果在加载完之前判断,上述文本还没出现,会被默认标记为FETCHED_STAGE并触发返回。因此,我们要在返回前特殊判定这种情形。

    我们引入了TTL来记录尝试次数,并返回错误值-1。如果到达MAX_TTL时红包还没有加载出来就舍弃这个红包。

    Stage.getInstance().entering(Stage.OPENING_STAGE);

    ttl += 1;

    return -1;

    版权说明

    本项目源自小米今年秋季发布会时演示的抢红包测试源码。stable分支基于此代码继续开发,dev分支重写了几乎所有的逻辑代码。应用的包名com.miui.hongbao未变。

    由于插件可能会改变自然的微信交互方式,这份代码仅可用于教学目的,不得更改后用于其他用途。

    项目使用MIT许可证。在上述前提下,你可以将代码用于任何用途。

    展开全文
  • weixin, 微信裂变红包&微信红包接口&企业付款
  • 微信红包插件

    2017-12-23 18:13:22
    安卓微信自动抢红包插件源码,简单实用,使用方便,清晰易懂。
  • 利用AccessibilityService实现“微信红包插件。 AccessibilityService是android的一个辅助服务抽象类,它是继承Service类,可以用于监听通知栏的变化、窗体改变、窗体内容改变、EditText光标移动、TextView内容...

    一、什么是AccessibilityService?

    AccessibilityService是android的一个辅助服务抽象类,它是继承Service类,可以用于监听通知栏的变化、窗体改变、窗体内容改变、EditText光标移动、TextView内容改变等事件。


    二、基本实现流程。

    如下图:



    三、主要代码段。

    1、RedPacket类继承AccessibilityService,实现如下:

    package com.nicksong.luckymoney;
    
    import java.io.IOException;
    import java.util.List;
    import android.accessibilityservice.AccessibilityService;
    import android.annotation.SuppressLint;
    import android.app.Notification;
    import android.app.PendingIntent;
    import android.app.PendingIntent.CanceledException;
    import android.content.res.AssetFileDescriptor;
    import android.content.res.AssetManager;
    import android.media.MediaPlayer;
    import android.util.Log;
    import android.view.accessibility.AccessibilityEvent;
    import android.view.accessibility.AccessibilityNodeInfo;
    
    public class RedPacket extends AccessibilityService {
    	
    	private MediaPlayer player;
    	private boolean isFirst = false;
    
    	@Override
    	public void onAccessibilityEvent(AccessibilityEvent event) {
    		int eventType = event.getEventType();
    		switch (eventType) {
    		//第一步:监听通知栏消息
    		case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:
    			List<CharSequence> texts = event.getText();
    			if (!texts.isEmpty()) {
    				for (CharSequence text : texts) {
    					String content = text.toString();
    					Log.i("demo", "text:"+content);
    					if (content.contains("[微信红包]")) {
    						isFirst = true;
    						playSound4LuckyMoneyComing();
    						//模拟打开通知栏消息
    						if (event.getParcelableData() != null && event.getParcelableData() instanceof Notification) {
    							Notification notification = (Notification) event.getParcelableData();
    							PendingIntent pendingIntent = notification.contentIntent;
    							try {
    								pendingIntent.send();
    							} catch (CanceledException e) {
    								e.printStackTrace();
    							}
    						}
    					}
    				}
    			}
    			break;
    		//第二步:监听是否进入微信红包消息界面
    		case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
    			String className = event.getClassName().toString();
    			if (className.equals("com.tencent.mm.ui.LauncherUI")) {
    				Log.i("demo", "开始抢红包");
    				//开始抢红包
    				getPacket();
    			} else if (className.equals("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI")) {
    				Log.i("demo", "开始拆红包");
    				isFirst = false;
    				//开始打开红包
    				openPacket();
    			}
    //			else if (className.equals("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI")) {
    //				Log.i("demo", "在红包详情直接返回");
    //				//模拟点击返回
    //				goBackFromLmUi();
    //			}
    			break;
    		}
    	}
    	
    	/**
    	 * 在红包详情界面模拟返回
    	 */
    	private void goBackFromLmUi() {
    		isFirst = false;
    		AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
    		if (nodeInfo != null) {
    			for (int i = 0; i < nodeInfo.getChildCount(); i++) {
    				Log.i("demo", "goBackFromLmUi i = " + i);
    				Log.i("demo", "goBackFromLmUi Click"+",isClickable:" + nodeInfo.getChild(i).isClickable());
    				Log.i("demo", "goBackFromLmUi View type = " + nodeInfo.getChild(i).getClassName());
    				if ("android.widget.LinearLayout".equals(nodeInfo.getChild(i).getClassName()) && nodeInfo.getChild(i).isClickable()) {
    					nodeInfo.getChild(i).performAction(AccessibilityNodeInfo.ACTION_CLICK);
    				}
    			}
    		}
    	}
    
    	/**
    	 * 查找到
    	 */
    	@SuppressLint("NewApi")
    	private void openPacket() {
    		AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
    		if (nodeInfo != null) {
    			for (int i = 0; i < nodeInfo.getChildCount(); i++) {
    				Log.i("demo", "openPacket i = " + i);
    				Log.i("demo", "Click"+",isClickable:" + nodeInfo.getChild(i).isClickable());
    				Log.i("demo", "View type = " + nodeInfo.getChild(i).getClassName());
    				if ("android.widget.Button".equals(nodeInfo.getChild(i).getClassName()) && nodeInfo.getChild(i).isClickable()) {
    					nodeInfo.getChild(i).performAction(AccessibilityNodeInfo.ACTION_CLICK);
    				}
    			}
    		}
    
    	}
    
    	@SuppressLint("NewApi")
    	private void getPacket() {
    		AccessibilityNodeInfo rootNode = getRootInActiveWindow();
    		recycle(rootNode);
    	}
    	
    	/**
    	 * 打印一个节点的结构
    	 * @param info
    	 */
    	@SuppressLint("NewApi")
    	public void recycle(AccessibilityNodeInfo info) {
            if (info.getChildCount() == 0) { 
            	if(info.getText() != null){
            		if("领取红包".equals(info.getText().toString())){
            			//这里有一个问题需要注意,就是需要找到一个可以点击的View
                    	Log.i("demo", "Click"+",isClick:"+info.isClickable());
                    	info.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                    	AccessibilityNodeInfo parent = info.getParent();
                    	while(parent != null){
                    		Log.i("demo", "parent isClick:"+parent.isClickable());
                    		if(parent.isClickable() && isFirst){ 
                    			parent.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                    			break;
                    		}
                    		parent = parent.getParent();
                    	}
                    	
                	}
            	}
            	
            } else {  
                for (int i = 0; i < info.getChildCount(); i++) {  
                    if(info.getChild(i)!=null){  
                        recycle(info.getChild(i));
                    }  
                }  
            }  
        }  
    
    	@Override
    	public void onInterrupt() {
    	}
    
    	private void playSound4LuckyMoneyComing() {
    		try {
    			AssetManager assetManager = this.getAssets();
    			AssetFileDescriptor aFileDescriptor = assetManager.openFd("luckymoney_sound.ogg");
    			player = new MediaPlayer();
    			player.setDataSource(aFileDescriptor.getFileDescriptor(), aFileDescriptor.getStartOffset(), aFileDescriptor.getLength());
    			player.prepare();
    			player.start();
    		} catch (IOException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    	
    }
    

    2、res/xml/accessibility.xml如下:

    <?xml version="1.0" encoding="utf-8"?>
    <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
        android:accessibilityEventTypes="typeNotificationStateChanged|typeWindowStateChanged"
        android:accessibilityFeedbackType="feedbackGeneric"
        android:accessibilityFlags="flagDefault"
        android:canRetrieveWindowContent="true"
        android:description="@string/desc"
        android:notificationTimeout="100"
        android:packageNames="com.tencent.mm" />


    3、manifest清单文件主要部分如下:

    <service
                android:name="com.nicksong.luckymoney.RedPacket"
                android:enabled="true"
                android:exported="true"
                android:label="@string/app_name"
                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/accessibility" />
    </service>


    四、“设置-辅助服务”打开“微信红包插件”服务。

    不同的手机,这个辅助服务的名称、位置可能都不一样,这个就要大家在自己手机里找一下,应该很快能找到。如,魅族的一款手机,这个菜单位置如下截图:

       

    五、源码。

    https://github.com/Ericsongyl/WechatRedPacket

    欢迎star、fork,并提出宝贵意见和建议。

    注意:Android系统4.1以上、微信版本6.3.28及以上。


    ---------------------------------------------------------------------------------------------------------------

    声明:请勿商用,否则后果由投入商用者本人承担!

    展开全文
  • 微信红包插件这个Android插件可以帮助你在微信群聊抢红包时战无不胜。当检测到红包时,插件会自动点击屏幕,人工点击的速度无法比拟。实现原理1. 抢红包流程的逻辑控制这个插件通过一个Stage类来记录当前对应的...

    微信抢红包插件

    这个Android插件可以帮助你在微信群聊抢红包时战无不胜。当检测到红包时,插件会自动点击屏幕,人工点击的速度无法比拟。

    实现原理

    1. 抢红包流程的逻辑控制

    这个插件通过一个Stage类来记录当前对应的阶段。Stage类被设计成单例并惰性实例化,因为一个Service不需要也不应该处在不同的阶段。对外暴露阶段常量和entering和getCurrentStage两个方法,分别记录和获取当前的阶段。

    public class Stage {

    private static Stage stageInstance;

    public static final int FETCHING\_STAGE \= 0, OPENING\_STAGE \= 1, FETCHED\_STAGE \= 2, OPENED\_STAGE \= 3;

    private int currentStage \= FETCHED\_STAGE;

    private Stage() {}

    public static Stage getInstance() {

    if (stageInstance \== null) stageInstance \= new Stage();

    return stageInstance;

    }

    public void entering(int \_stage) {

    stageInstance.currentStage \= \_stage;

    }

    public int getCurrentStage() {

    return stageInstance.currentStage;

    }

    }

    1.1 阶段说明

    阶段

    说明

    FETCHING_STAGE

    正在读取屏幕上的红包,此时不应有别的操作

    FETCHED_STAGE

    已经结束一个FETCH阶段,屏幕上的红包都已加入待抢队列

    OPENING_STAGE

    正在拆红包,此时不应有别的操作

    OPENED_STAGE

    红包成功抢到,进入红包详情页面

    1.程序以FETCHED_STAGE 开始,将屏幕上的红包加入待抢队列:

    --> FETCHED_STAGE --> FETCHING_STAGE --> FETCHED_STAGE -->

    2.处理待抢队列中的红包:

    --> [CLICK] --> OPENING_STAGE --> [CLICK] --> OPENED_STAGE --> [BACK] --> FETCHED_STAGE -->(抢到)

    --> [CLICK] --> OPENING_STAGE --> [BACK] --> FETCHED_STAGE -->(没抢到)

    3.不断重复流程1和2

    1.2 根据阶段选择不同的入口

    在每次窗体状态发生变化后,根据当前所在的阶段选择入口。

    switch (Stage.getInstance().getCurrentStage()) {

    case Stage.OPENING\_STAGE:

    // .......

    Stage.getInstance().entering(Stage.FETCHED\_STAGE);

    performGlobalAction(GLOBAL\_ACTION\_BACK);

    break;

    case Stage.OPENED\_STAGE:

    Stage.getInstance().entering(Stage.FETCHED\_STAGE);

    performGlobalAction(GLOBAL\_ACTION\_BACK);

    break;

    case Stage.FETCHED\_STAGE:

    if (nodesToFetch.size() \> 0) {

    AccessibilityNodeInfo node \= nodesToFetch.remove(nodesToFetch.size() \- 1);

    if (node.getParent() != null) {

    // .......

    Stage.getInstance().entering(Stage.OPENING\_STAGE);

    node.getParent().performAction(AccessibilityNodeInfo.ACTION\_CLICK);

    }

    return;

    }

    Stage.getInstance().entering(Stage.FETCHING\_STAGE);

    fetchHongbao(nodeInfo);

    Stage.getInstance().entering(Stage.FETCHED\_STAGE);

    break;

    }

    2. 屏幕内容检测和自动化点击的实现

    和其他插件一样,这里使用的是Android API提供的AccessibilityService。这个类位于android.accessibilityservice包内,该包中的类用于开发无障碍服务,提供代替或增强的用户反馈。

    AccessibilityService 服务在后台运行,等待系统在发生 AccessibilityEvent 事件时回调。这些事件指的是用户界面上发生的状态变化, 比如焦点变更、按钮按下等等。服务可以请求“查询当前窗口中内容”的能力。 开发辅助服务需要继承该类并实现其抽象方法。

    2.1 配置AccessibilityService

    在这个例子中,我们需要监听的事件是当红包来或者滑动屏幕时引起的屏幕内容变化,和点开红包时窗体状态的变化,因此我们只需要在配置XML的accessibility-service标签中加入一条

    android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged"

    或在onAccessibilityEvent回调函数中对事件进行一次类型判断

    final int eventType = event.getEventType();

    if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED

    || eventType \== AccessibilityEvent.TYPE\_WINDOW\_CONTENT\_CHANGED) {

    // ...

    }

    除此之外,由于我们只监听微信,还需要指定微信的包名

    android:packageNames="com.tencent.mm"

    为了获取窗口内容,我们还需要指定

    android:canRetrieveWindowContent="true"

    其他配置请看代码。

    2.2 获取红包所在的节点

    首先,我们要获取当前屏幕的根节点,下面两种方式效果是相同的:

    AccessibilityNodeInfo nodeInfo = event.getSource();

    AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();

    这里返回的AccessibilityNodeInfo是窗体内容的节点,包含节点在屏幕上的位置、文本描述、子节点id、能否点击等信息。从AccessibilityService的角度来看,窗体上的内容表示为辅助节点树,虽然和视图的结构不一定一一对应。换句话说,自定义的视图可以自己描述上面的辅助节点信息。当辅助节点传递给AccessibilityService之后就不可更改了,如果强行调用引起状态变化的方法会报错。

    在聊天页面,每个红包上面都有“领取红包”这几个字,我们把它作为识别红包的依据。如果你收到了这四个字的文本消息,可能其他的插件会做出误判。因为我们加入了阶段的概念,因此不会出现这个问题。

    AccessibilityNodeInfo的API中有一个findAccessibilityNodeInfosByText方法允许我们通过文本来搜索界面中的节点。匹配是大小写敏感的,它会从遍历树的根节点开始查找。API文档中特别指出,为了防止创建大量实例,节点回收是调用者的责任,这一点会在接下来的部分中讲到。

    List node1 = nodeInfo.findAccessibilityNodeInfosByText("领取红包");

    2.3 对节点进行操作

    AccessibilityNodeInfo同样暴露了一个API——performAction来对节点进行点击或者其他操作。出于安全性考虑,只有这个操作来自AccessibilityService时才会被执行。

    nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);

    不过,我们在调试时发现"领取红包"的mClickable属性为false,说明点击的监听加在它父辈的节点上。通过getParent获取父节点,这个节点是可以点击的。

    我们还需要全局的返回操作,最方便的办法就是performGlobalAction,不过注意这个方法是API 16后才有的。

    performGlobalAction(GLOBAL_ACTION_BACK);

    3. 获取屏幕上的所有红包

    和其他插件最大的区别是,这个插件的逻辑是获取屏幕上所有的红包节点,去掉已经获取过的之后,将待抢红包加入队列,再将队列中的红包一个个打开。

    3.1 判断红包节点是否已被抢过

    实现这一点是编写时最大的障碍。对于一般的Java对象实例来说,除非被GC回收,实例的Id都不会变化。我最初的想法是通过正则表达式匹配下面的十六进制对象id来表示一个红包。

    android.view.accessibility.AccessibilityNodeInfo@2a5a7c; .......

    但在测试中,队列中的部分红包没有被戳开。进一步观察发现,新的红包节点和旧的红包节点id出现了重复,且出现概率较大。由于GC日志正常,我推测AccessibilityNode可能有一个实例池的设计。获取当前窗体节点树的时候,从一个可重用的实例池中获取一个辅助节点信息 (AccessibilityNodeInfo)实例。在接下来的获取时,仍然从实例池中获取节点实例,这时可能会重用之前的实例。这样的设计是有好处的,可以防止每次返回都创建大量的实例,影响性能。AccessibilityNodeProvider的源码表明了这样的设计。

    也就是说,为了标识一个唯一的红包,只用实例id是不充分的。这个插件采用的是红包内容+节点实例id的hash来标记。因为同一屏下,同一个节点树下的节点id是一定不会重复的,滑动屏幕后新红包的内容和节点id同时重复的概率已经大大减小。更改标识策略后,实测中几乎没有出现误判。

    3.2 将新出现的红包加入待抢队列

    我们维护了两个列表,分别记录待抢红包和抢过的红包标识。

    private List nodesToFetch = new ArrayList<>();

    private List fetchedIdentifiers = new ArrayList<>();

    在每次读取聊天屏幕的时候,会检查这个红包是否在fetchedIdentifiers队列中,如果没有,则加入nodesToFetch队列。

    for (AccessibilityNodeInfo cellNode : fetchNodes) {

    String id \= getHongbaoHash(cellNode);

    /\* 如果节点没有被回收且该红包没有抢过 \*/

    if (id != null && !fetchedIdentifiers.contains(id)) {

    nodesToFetch.add(cellNode);

    }

    }

    4. 打开队列中的红包

    通过红包打开后显示的文本判断这个红包是否可以抢,进行接下来的操作。

    4.1 判断红包节点是否被重用

    这也是实现时的一个坑。前面提到了实例池的设计,当我们把红包们加入待抢队列,戳完一个红包再回来时,队列中的其他红包节点可能已被回收重用,如果再去点击这个节点,显然没有什么卵用。

    为了解决这个问题,我们只能退而求其次,在点开前做一次检查。如果发现被重用了,就舍弃这个节点,在下一轮fetch的阶段重新加入待抢队列。确认没有重用立即打开,并把节点hash加入fetchedIdentifiers队列。这里如果node失效getHongbaoHash会返回null。

    AccessibilityNodeInfo node = nodesToFetch.remove(nodesToFetch.size() - 1);

    if (node.getParent() != null) {

    String id \= getHongbaoHash(node);

    if (id \== null) return;

    fetchedIdentifiers.add(id);

    Stage.getInstance().entering(Stage.OPENING\_STAGE);

    node.getParent().performAction(AccessibilityNodeInfo.ACTION\_CLICK);

    }

    可以看出这并不是很有效率的解决方案。在实测中,有时队列中中红包失效后被舍弃,但没有新的AccessibilityEvent发生,接下来的操作都被挂起了。在戳过较多红包之后,这种情况表现得尤为明显,必须要显式地改变窗体内容才能解决。

    4.2 根据红包类型选择操作

    红包被戳开前会进行查重。戳开后如果界面上出现了“拆红包”几个字,说明红包还没有被别人抢走,立刻点击“拆红包”并将stage标记为OPENED_STAGE。

    此时,另三种情况表明抢红包失败了,直接返回,接下来状态会被标记为FETCHED_STAGE。

    “过期”,说明红包超过有效时间

    “手慢了”,说明红包发完但没抢到

    “红包详情”,说明你已经抢到过

    4.3 防止加载红包时返回

    戳开红包和红包加载完之间有一个“正在加载”的过渡动画,会触发onAccessibilityEvent回调方法。如果在加载完之前判断,上述文本还没出现,会被默认标记为FETCHED_STAGE并触发返回。因此,我们要在返回前特殊判定这种情形。

    我们引入了TTL来记录尝试次数,并返回错误值-1。如果到达MAX_TTL时红包还没有加载出来就舍弃这个红包。

    Stage.getInstance().entering(Stage.OPENING_STAGE);

    ttl += 1;

    return -1;

    注:基于https://github.com/geeeeeeeee... 开发,由于原作者已经两年没有维护了,本插件几乎重写了所有逻辑,以上技术分析只是大致实现思路,与本项目有部分出入。同时支持了企业微信红包。由于项目刚刚完成,还没有在github发布代码,后续会将项目开源。

    展开全文
  • 微信红包插件源码

    2018-11-30 09:39:47
    这是开源的微信红包插件的源码,来源于Xposed一位大神所写,欢迎各位大佬研究。
  • 微信红包插件
  • (转载) ...直接在github上开源的微信红包插件改的,红包插件项目和你需要了解的几篇文章在这里 https://github.com/geeeeeeeeek/WeChatLuckyMoney http://www.xuebuyuan.com/2061597.html ...

    (转载)http://www.cnblogs.com/matoo/p/5452553.html

    背景

    地址: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<--


    展开全文
  • HTML5微信红包抽奖红包雨特效动画js效果下载。一款非常实用的红包雨抽奖jQuery插件,背景红包雨动画非常炫酷,带抽奖弹窗提示。
  • 微信红包插件项目

    千次阅读 2016-08-04 10:43:01
    前言:转载请注明出处:http://blog.csdn.net/hejjunlin/article/details/52116904 微信红包插件,红包精录源码,AccessibilityService学习
  • 本文给大家介绍Android中微信红包插件原理解析及开发思路,对微信红包插件原理相关知识感兴趣的朋友一起学习吧
  • 主要介绍了Android AccessibilityService实现微信红包插件的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • iOS版微信红包插件

    2021-04-06 08:20:13
    目前市面上最稳定、功能最全的 iOS 版微信红包插件。 功能特色 完全原生体验 完美嵌入微信设置中,无需安装其它无关应用。开启插件后,所有抢红包工作都会默默进行,不会影响微信的正常使用流程。 可自由设置...
  • 微信红包插件与Android辅助功能

    千次阅读 2017-06-04 14:04:16
    逢年过节大家都少不了发微信红包,通过微信红包来表达祝福。同时,微信还有拼手气群红包。各种群好友群,亲戚群,工作群逢年过节常常会有红包可抢。...损失了一个亿啊有木有……自从有了微信红包插件,再也不用担

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,520
精华内容 1,008
关键字:

企业微信红包插件