背景

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

  1. package com.huijimuhe.pman.services;
  2.  
  3. import android.accessibilityservice.AccessibilityService;
  4. import android.content.ComponentName;
  5. import android.content.SharedPreferences;
  6. import android.content.pm.PackageManager;
  7. import android.os.Bundle;
  8. import android.os.Handler;
  9. import android.os.Message;
  10. import android.preference.PreferenceManager;
  11. import android.util.Log;
  12. import android.view.accessibility.AccessibilityEvent;
  13. import android.view.accessibility.AccessibilityNodeInfo;
  14.  
  15. import com.huijimuhe.pman.utils.PowerUtil;
  16.  
  17. import java.util.ArrayList;
  18. import java.util.List;
  19.  
  20. public class PostService extends AccessibilityService implements SharedPreferences.OnSharedPreferenceChangeListener {
  21.  
  22. private static final String TAG = "PostService";
  23.  
  24. private static final String MAIN_ACT = "MainActivity";
  25. private static final String DETAIL_ACT = "NewsPageActivity";
  26. private static final String BASE_ACT = "BaseActivity";
  27.  
  28. private static final int MSG_BACK = 159;
  29. private static final int MSG_REFRESH_NEW_LIST = 707;
  30. private static final int MSG_READ_NEWS = 19;
  31. private static final int MSG_POST_COMMENT = 211;
  32. private static final int MSG_REFRESH_COMPLETE = 22;
  33. private static final int MSG_FINISH_COMMENT = 59;
  34.  
  35. private String currentActivityName = MAIN_ACT;
  36. private HandlerEx mHandler = new HandlerEx();
  37.  
  38. private boolean mIsMutex = false;
  39. private int mReadCount = 0;
  40. private List<String> readedNews = new ArrayList<>();
  41. private PowerUtil powerUtil;
  42. private SharedPreferences sharedPreferences;
  43.  
  44. /**
  45. * AccessibilityEvent
  46. *
  47. * @param event 事件
  48. */
  49. @Override
  50. public void onAccessibilityEvent(AccessibilityEvent event) {
  51.  
  52. if (sharedPreferences == null) return;
  53.  
  54. setCurrentActivityName(event);
  55. watchMain(event);
  56. watchBasic(event);
  57. watchDetail(event);
  58. }
  59.  
  60. private void watchMain(AccessibilityEvent event) {
  61. //新闻列表
  62. if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && currentActivityName.contains(MAIN_ACT)) {
  63. if (mReadCount > 4) {
  64. //如果读取完了都没有新的就刷新
  65. Log.d(TAG, "新闻已读取完,需要刷新列表");
  66. //需要刷新列表了
  67. mHandler.sendEmptyMessage(MSG_REFRESH_NEW_LIST);
  68. } else {
  69. mHandler.sendEmptyMessage(MSG_READ_NEWS);
  70. }
  71. }
  72. }
  73.  
  74. private void watchDetail(AccessibilityEvent event) {
  75. if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && currentActivityName.contains(DETAIL_ACT)) {
  76. //添加评论
  77. mHandler.sendEmptyMessage(MSG_POST_COMMENT);
  78. }
  79. }
  80.  
  81. private void watchBasic(AccessibilityEvent event) {
  82. if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && currentActivityName.contains(BASE_ACT)) {
  83. Log.d(TAG, "进入非新闻页,即将退出");
  84. mHandler.sendEmptyMessage(MSG_BACK);
  85. mHandler.sendEmptyMessage(MSG_BACK);
  86. }
  87. }
  88.  
  89. private void refreshList() {
  90. List<AccessibilityNodeInfo> nodes = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("android:id/list");
  91. for (AccessibilityNodeInfo node : nodes) {
  92. //页面是否加载完成
  93. if (node == null) return;
  94. //执行刷新
  95. node.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
  96. }
  97. //重新开始读取新闻
  98. mHandler.sendEmptyMessage(MSG_REFRESH_COMPLETE);
  99. }
  100.  
  101. private void enterDetailAct() {
  102.  
  103. //获取列表items
  104. List<AccessibilityNodeInfo> nodes = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("com.netease.newsreader.activity:id/perfect_item");
  105.  
  106. for (AccessibilityNodeInfo node : nodes) {
  107. //页面是否加载完成
  108. if (node == null) return;
  109.  
  110. //获取列表item的标题
  111. List<AccessibilityNodeInfo> titles = node.findAccessibilityNodeInfosByViewId("com.netease.newsreader.activity:id/title");
  112.  
  113. for (AccessibilityNodeInfo title : titles) {
  114.  
  115. //检查是否已读取
  116. if (!readedNews.contains(title.getText().toString())) {
  117. //点击读取该新闻
  118. readedNews.add(title.getText().toString());
  119. node.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);
  120. Log.d(TAG, "进入新闻:" + title.getText().toString());
  121. mReadCount++;
  122. //进入一个就停止
  123. return;
  124. }
  125. }
  126. }
  127. }
  128.  
  129. private void postComment() {
  130. //激活输入框
  131. List<AccessibilityNodeInfo> nodes = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("com.netease.newsreader.activity:id/mock_reply_edit");
  132. for (AccessibilityNodeInfo node : nodes) {
  133.  
  134. //页面是否加载完成
  135. if (node == null) return;
  136.  
  137. node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
  138. }
  139.  
  140. //输入内容
  141. List<AccessibilityNodeInfo> editNodes = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("com.netease.newsreader.activity:id/reply_edit");
  142. for (AccessibilityNodeInfo node : editNodes) {
  143.  
  144. //页面是否加载完成
  145. if (node == null) return;
  146.  
  147. Bundle arguments = new Bundle();
  148. arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, "抽烟的人最讨厌了");
  149. node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
  150. }
  151.  
  152. // //回复按钮
  153. // List<AccessibilityNodeInfo> postNodes = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("com.netease.newsreader.activity:id/reply");
  154. // for (AccessibilityNodeInfo node : postNodes) {
  155. // //页面是否加载完成
  156. // if (node == null) return;
  157. // node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
  158. // }
  159.  
  160. //退出
  161. mHandler.sendEmptyMessage(MSG_FINISH_COMMENT);
  162.  
  163. Log.d(TAG, "评论已发表");
  164. }
  165.  
  166. /**
  167. * 设置当前页面名称
  168. *
  169. * @param event
  170. */
  171. private void setCurrentActivityName(AccessibilityEvent event) {
  172.  
  173. if (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
  174. return;
  175. }
  176.  
  177. try {
  178. ComponentName componentName = new ComponentName(event.getPackageName().toString(), event.getClassName().toString());
  179.  
  180. getPackageManager().getActivityInfo(componentName, 0);
  181. currentActivityName = componentName.flattenToShortString();
  182. Log.d(TAG, "<--pkgName-->" + event.getPackageName().toString());
  183. Log.d(TAG, "<--className-->" + event.getClassName().toString());
  184. Log.d(TAG, "<--currentActivityName-->" + currentActivityName);
  185. } catch (PackageManager.NameNotFoundException e) {
  186. currentActivityName = MAIN_ACT;
  187. }
  188. }
  189.  
  190. @Override
  191. public void onDestroy() {
  192. this.powerUtil.handleWakeLock(false);
  193. super.onDestroy();
  194. }
  195.  
  196. @Override
  197. public void onInterrupt() {
  198.  
  199. }
  200.  
  201. @Override
  202. public void onServiceConnected() {
  203. super.onServiceConnected();
  204. this.watchFlagsFromPreference();
  205. }
  206.  
  207. /**
  208. * 屏幕是否常亮
  209. */
  210. private void watchFlagsFromPreference() {
  211. sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
  212. sharedPreferences.registerOnSharedPreferenceChangeListener(this);
  213.  
  214. this.powerUtil = new PowerUtil(this);
  215. Boolean watchOnLockFlag = sharedPreferences.getBoolean("pref_watch_on_lock", false);
  216. this.powerUtil.handleWakeLock(watchOnLockFlag);
  217. }
  218.  
  219. @Override
  220. public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
  221. if (key.equals("pref_watch_on_lock")) {
  222. Boolean changedValue = sharedPreferences.getBoolean(key, false);
  223. this.powerUtil.handleWakeLock(changedValue);
  224. }
  225. }
  226.  
  227. /**
  228. * 处理机
  229. */
  230. private class HandlerEx extends Handler {
  231. @Override
  232. public void handleMessage(Message msg) {
  233. super.handleMessage(msg);
  234. switch (msg.what) {
  235. //后退
  236. case MSG_BACK:
  237. new Handler().postDelayed(new Runnable() {
  238. @Override
  239. public void run() {
  240. performGlobalAction(GLOBAL_ACTION_BACK);
  241. }
  242. }, 1000);
  243. break;
  244. //结束评论
  245. case MSG_FINISH_COMMENT:
  246. for (int i = 0; i < 4; i++) {
  247. new Handler().postDelayed(new Runnable() {
  248. @Override
  249. public void run() {
  250. performGlobalAction(GLOBAL_ACTION_BACK);
  251. }
  252. }, 2000 +i*500);
  253. }
  254. break;
  255. //刷新列表
  256. case MSG_REFRESH_NEW_LIST:
  257. new Handler().postDelayed(new Runnable() {
  258. @Override
  259. public void run() {
  260. refreshList();
  261. }
  262. }, 3000);
  263. break;
  264. //结束刷新
  265. case MSG_REFRESH_COMPLETE:
  266. new Handler().postDelayed(new Runnable() {
  267. @Override
  268. public void run() {
  269. mReadCount = 0;
  270. enterDetailAct();
  271. }
  272. }, 3000);
  273. break;
  274. //进入新闻页
  275. case MSG_READ_NEWS:
  276. new Handler().postDelayed(new Runnable() {
  277. @Override
  278. public void run() {
  279. enterDetailAct();
  280. }
  281. }, 3000);
  282. break;
  283. //发送评论
  284. case MSG_POST_COMMENT:
  285. new Handler().postDelayed(new Runnable() {
  286. @Override
  287. public void run() {
  288. postComment();
  289. }
  290. }, 3000);
  291. break;
  292. }
  293. }
  294. }
  295. }

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

粗略实现步骤

1.manifest中申明服务

  1. <service
  2. android:name=".services.PostService"
  3. android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
  4. <intent-filter>
  5. <action android:name="android.accessibilityservice.AccessibilityService"/>
  6. </intent-filter>
  7. <meta-data android:name="android.accessibilityservice"
  8. android:resource="@xml/accessible_service_config"/>
  9. </service>

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

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

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

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

  1. @Override
  2. public void onAccessibilityEvent(AccessibilityEvent event) {
  3.  
  4. if (sharedPreferences == null) return;
  5.  
  6. setCurrentActivityName(event);
  7. watchMain(event);
  8. watchBasic(event);
  9. watchDetail(event);
  10. }
  11. /**
  12. * 设置当前页面名称
  13. *
  14. * @param event
  15. */
  16. private void setCurrentActivityName(AccessibilityEvent event) {
  17.  
  18. if (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
  19. return;
  20. }
  21.  
  22. try {
  23. ComponentName componentName = new ComponentName(event.getPackageName().toString(), event.getClassName().toString());
  24.  
  25. getPackageManager().getActivityInfo(componentName, 0);
  26. currentActivityName = componentName.flattenToShortString();
  27. Log.d(TAG, "<--pkgName-->" + event.getPackageName().toString());
  28. Log.d(TAG, "<--className-->" + event.getClassName().toString());
  29. Log.d(TAG, "<--currentActivityName-->" + currentActivityName);
  30. } catch (PackageManager.NameNotFoundException e) {
  31. currentActivityName = MAIN_ACT;
  32. }
  33. }

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

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

5.监控是否是新闻详情

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

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

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

7.回复评论

  1. private void postComment() {
  2. //激活输入框
  3. List<AccessibilityNodeInfo> nodes = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("com.netease.newsreader.activity:id/mock_reply_edit");
  4. for (AccessibilityNodeInfo node : nodes) {
  5.  
  6. //页面是否加载完成
  7. if (node == null) return;
  8.  
  9. node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
  10. }
  11.  
  12. //输入内容
  13. List<AccessibilityNodeInfo> editNodes = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("com.netease.newsreader.activity:id/reply_edit");
  14. for (AccessibilityNodeInfo node : editNodes) {
  15.  
  16. //页面是否加载完成
  17. if (node == null) return;
  18.  
  19. Bundle arguments = new Bundle();
  20. arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, "抽烟的人最讨厌了");
  21. node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
  22. }
  23.  
  24. //退出
  25. mHandler.sendEmptyMessage(MSG_FINISH_COMMENT);
  26.  
  27. Log.d(TAG, "评论已发表");
  28. }

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

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

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

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

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

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

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

App独立开发群533838427

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

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

  1. 仿各种APP将文章DOM转JSON并在APP中以列表显示(android、ios、php已开源)

    背景 一直以来都想实现类似新闻客户端.鲜城等文章型app的正文显示,即在web editor下编辑后存为json,在app中解析json并显示正文. 网上搜过,没找到轮子.都是给的思路,然后告知是公司 ...

  2. 发布了Android的App,我要开源几个组件!

    做了一款App,本来是毕业设计但是毕业的时候还没有做完,因为大部分时间都改论文去了,你们都懂的.现在毕业了在工作之余把App基本上做完了.为什么说基本上呢,因为我觉得还有很多功能还没实现,还要很多bu ...

  3. android黑科技系列——微信抢红包插件原理解析和开发实现

    一.前言 自从几年前微信添加抢红包的功能,微信的电商之旅算是正式开始正式火爆起来.但是作为Android开发者来说,我们在抢红包的同时意识到了很多问题,就是手动去抢红包的速度慢了,当然这些有很多原因导 ...

  4. Android通过辅助功能实现抢微信红包原理简单介绍

    简书文章:https://www.jianshu.com/p/e1099a94b979 附抢红包开源项目地址,代码已全改为Kotlin了,已适配到最新微信7.0.5版本,如果对你有所帮助赏个star吧 ...

  5. Android中微信抢红包插件原理解析和开发实现

    一.前言 自从去年中微信添加抢红包的功能,微信的电商之旅算是正式开始正式火爆起来.但是作为Android开发者来说,我们在抢红包的同时意识到了很多问题,就是手动去抢红包的速度慢了,当然这些有很多原因导 ...

  6. [Android进阶]学习AccessibilityService实现微信抢红包插件

    在你的手机更多设置或者高级设置中,我们会发现有个无障碍的功能,很多人不知道这个功能具体是干嘛的,其实这个功能是为了增强用户界面以帮助残障人士,或者可能暂时无法与设备充分交互的人们 它的具体实现是通过A ...

  7. Android进阶——学习AccessibilityService实现微信抢红包插件

    在你的手机更多设置或者高级设置中,我们会发现有个无障碍的功能,很多人不知道这个功能具体是干嘛的,其实这个功能是为了增强用户界面以帮助残障人士,或者可能暂时无法与设备充分交互的人们 它的具体实现是通过A ...

  8. Android基于代理的插件化思路分析

    前言 正常的App开发流程基本上是这样的:开发功能-->测试--->上线,上线后发现有大bug,紧急修复---->发新版本---->用户更新----->bug修复.从发现 ...

  9. Python自动抢红包,超详细教程,再也不会错过微信红包了!

    目录: 0 引言 1 环境 2 需求分析 3 前置准备 4 抢红包流程回顾 5 代码梳理 6 后记 0 引言 提到抢红包,就不得不提Xposed框架,它简直是个抢红包的神器,但使用Xposed框架有一 ...

随机推荐

  1. GDI学习之俄罗斯方块续

    当前方块对象 #region 定义砖块int[i,j,y,x] Tricks:i为那块砖,j为状态,y为列,x为行 private int[, , ,] Tricks = {{ { {,,,}, {, ...

  2. C++基础——子类转父类转子类 (派生类转基类转派生类)

    ==================================声明================================== 本文原创,转载在正文中显要的注明作者和出处,并保证文章的完 ...

  3. java Timer(定时调用、实现固定时间执行)

    最近需要用到定时调用的功能.可以通过java的Timer类来进行定时调用,下面是有关Timer的一些相关知识. 其实就Timer来讲就是一个调度器,而TimerTask呢只是一个实现了run方法的一个 ...

  4. poj 2391 Ombrophobic Bovines(最大流+floyd+二分)

    Ombrophobic Bovines Time Limit: 1000MSMemory Limit: 65536K Total Submissions: 14519Accepted: 3170 De ...

  5. POJ 2299 Ultra-QuickSort(线段树入门)

    Ultra-QuickSort Time Limit: 7000MS Memory Limit: 65536K Description In this problem, you have to ana ...

  6. Redhat6.5 安装64位oracle11.2.0.1

    系统架构 [root@localhost ~]# uname -a Linux db 2.6.32-431.el6.x86_64 #1 SMP Sun Nov 10 22:19:54 EST 2013 ...

  7. Java Consumer and Producer demo

    import java.util.Random; import java.util.concurrent.LinkedBlockingQueue; class producer {     Rando ...

  8. AC日记——石子归并 codevs 1048

    1048 石子归并  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题解  查看运行结果     题目描述 Description 有n堆石子排成一列,每堆石子 ...

  9. NGUI 3.x 练习

    一.常用快捷键 Alt+Shitf+W 创建一个新的 Widget Alt+Shift+S 创建一个新的 Sprite Alt+Shift+L 创建一个新的 Label Alt+Shift+T 创建一 ...

  10. Apache Shiro 开源权限框架

    在 Web 项目中应用 Apache Shiro 开源权限框架 Apache Shiro 是功能强大并且容易集成的开源权限框架,它能够完成认证.授权.加密.会话管理等功能.认证和授权为权限控制的核心, ...