Android无障碍服务 x itchat 打造微信半自动机器人_移动开发_Coder-Pig的猪栏-CSDN博客

来源: Android无障碍服务 x itchat 打造微信半自动机器人_移动开发_Coder-Pig的猪栏-CSDN博客

em…是我,那个『敲最屌的码,输最多的钱』的傻雕开发仔,故事的最后:没有暴富,没有嫩模,也没有穴深妹…

再次奉劝各位一句:远离投机倒把,保持身心健康!(当然,后面如果学到机器学习的东西,可能会有续集 ~(╯▽╰ )真香~~)

好的,碎碎念的那么多,说回本节,写这一篇原因是,我的Py交易群里,童鞋问的最多的问题都是和机器人有关,基本都是下面这类问题:

1.群主,你的机器人怎么实现的?
2.群主,你的apk怎么运行了没反应?
3.群主,支持自动添加手机号为好友吗?
…等等
一开始我还是比较热衷帮忙解决问题的,但是耐心这种东西呢,是最容易被消磨殆尽的。而且学习Python之后,我变得越来越懒,一些繁琐重复的操作,我都会想办法自动化…当然,如果你是给我打钱或者是妹子,我也是很乐意的。

其实Android无障碍服务和itchat都是一些老生常谈的东西了,我也写过好几篇文章了:

妙用AccessibilityService黑科技实现微信自动加好友拉人进群聊
自动抢红包,点赞朋友圈,AccessibilityService解放你的双手
利用itchat搭建微信机器人详解(附三个实用示例)
小猪的Python学习之旅 —— 18.Python微信转发小宇宙早报
小猪的Python学习之旅 —— 19.Python微信自动好友验证,自动回复,发送群聊链接
东西就那些,套路也是那些,大部分时候,你只需要一点**『灵性』**!

就好像七巧板一样,只有七块,但是却能拼接出许多图形。要有灵性!本文先说下无障碍服务和itchat的一些核心要点,然后以读者编写的机器人脚本为例,教你实现一波,希望可以给你带来一些提示,然后去扩展自己想要的功能。

开源的微信个人号接口库——itchat
itchat库基于微信网页版,出了好几年的了,微信正在慢慢收窄网页端的功能(因为滥用的微信机器人),很多以前能用的接口慢慢都不能用了,比如:拉人进群,添加好友等,反正网页端没有的,现在都不能用。丢几个链接:

微信网页端登录:https://wx2.qq.com/
itchat的仓库地址:https://github.com/littlecodersh/ItChat
itchat官方文档:https://itchat.readthedocs.io/zh/latest/
itchat现在能做的:监听加好友的信息,监听聊天信息(包括群聊),发送信息。
基本上常用的而且可用的就这三个。当然如果你愿意掏钱的话,你不需要折腾那么多,网上有其他付费的途径:微友助手,王二狗机器人,还有xposed插件等。基于其他协议且可用的免费开源库目前还没见过,有知道的欢迎在评论区告知下!

使用要点提炼

如果可以,我希望你,尽可能的学会使用『正则表达式处理字符串』(基本功),丢个以前写过的文章:
小猪的Python学习之旅 —— 3.正则表达式,正则对于字符串匹配,提取非常实用!

1.监听并通过加好友的请求
@itchat.msg_register(itchat.content.FRIENDS)
def deal_with_friend(msg):
# 自动将新好友的消息录入,不需要重载通讯录
itchat.add_friend(**msg[‘Text’])
1
2
3
4
通过上面的add_friend函数,就可以完成添加好友的操作了。但是,有时我们可能需要做一些过滤,
不然乱七八糟的人都能加你了,是吧,比如对添加内容进行过滤,包含某些字眼才通过验证,或者
获取加你的人的相关信息,比如姓名,验证信息,个性签名,性别等。我们直接把上面的msg打印出来,
内容如下:

{‘MsgId’: ‘6930655840618917667’, ‘FromUserName’: ‘fmessage’, ‘ToUserName’: ‘@64fc6691440834f2dfba5489d652e5dbae06da4d57d550757403094424f7ec9c’, ‘MsgType’: 37, ‘Content’: ‘<msg fromusername=”wxid_gvr9a3le939h22″ encryptusername=”v1_6a48a18b8d6164b69dfdd3949311cd4bc58d96dfe00636d5e9814ff36b8d107164e768ccd9b2fb911ea9b87ccac42978@stranger” fromnickname=”Robot Pig” content=”我是Robot Pig” shortpy=”ROBOTPIG” imagestatus=”3″ scene=”30″ country=”AD” province=”” city=”” sign=”(´v`o)♡” percard=”1″ sex=”2″ alias=”” weibo=”” albumflag=”0″ albumstyle=”0″ albumbgimgid=”” snsflag=”1″ snsbgimgid=”http://mmsns.qpic.cn/mmsns/icDH6NcE3zNVBleeQZUzzlnhWk16tIfPKyvsmqWIpUwAxHkvricuNCL2RvGPjS3pVq7miaZQoju8TU/0″ snsbgobjectid=”12478275406675193980″ mhash=”197adbfd7de1668f30895d20dfb09b67″ mfullhash=”197adbfd7de1668f30895d20dfb09b67″ bigheadimgurl=”http://wx.qlogo.cn/mmhead/ver_1/jYcpsrqBVaxDuMbFgPZwH5F3mvcgKMNVrQXlmmbGWx5p9iaFrYALPyia1b7Izn4YCUYDRXz1jnfKw0eUXvEeEH5NouILblzLDjog40fqOjIjc/0″ smallheadimgurl=”http://wx.qlogo.cn/mmhead/ver_1/jYcpsrqBVaxDuMbFgPZwH5F3mvcgKMNVrQXlmmbGWx5p9iaFrYALPyia1b7Izn4YCUYDRXz1jnfKw0eUXvEeEH5NouILblzLDjog40fqOjIjc/96″ ticket=”v2_4ed4b6a5ac5ccf04c68c7bd64e4b543ba5babde23ce2985d4317bfc4bb62dcdfd78bb701551e1410fe64836f9bd147199de3e4493a031ea2daf52187816d9207@stranger” opcode=”2″ googlecontact=”” qrticket=”” chatroomusername=”” sourceusername=”” sourcenickname=””><brandlist count=”0″ ver=”688441058″></brandlist></msg>’, ‘Status’: 3, ‘ImgStatus’: 1, ‘CreateTime’: 1541558757, ‘VoiceLength’: 0, ‘PlayLength’: 0, ‘FileName’: ”, ‘FileSize’: ”, ‘MediaId’: ”, ‘Url’: ”, ‘AppMsgType’: 0, ‘StatusNotifyCode’: 0, ‘StatusNotifyUserName’: ”, ‘RecommendInfo’: {‘UserName’: ‘@156957cc475e1113c0a8fa340eae41ac1d92a12a523302a8eb06208616d656b3’, ‘NickName’: ‘Robot Pig’, ‘QQNum’: 0, ‘Province’: ”, ‘City’: ”, ‘Content’: ‘我是Robot Pig’, ‘Signature’: ‘(´v`o)♡’, ‘Alias’: ”, ‘Scene’: 30, ‘VerifyFlag’: 0, ‘AttrStatus’: 50467109, ‘Sex’: 2, ‘Ticket’: ‘v2_4ed4b6a5ac5ccf04c68c7bd64e4b543ba5babde23ce2985d4317bfc4bb62dcdfd78bb701551e1410fe64836f9bd147199de3e4493a031ea2daf52187816d9207@stranger’, ‘OpCode’: 2}, ‘ForwardFlag’: 0, ‘AppInfo’: {‘AppID’: ”, ‘Type’: 0}, ‘HasProductId’: 0, ‘Ticket’: ”, ‘ImgHeight’: 0, ‘ImgWidth’: 0, ‘SubMsgType’: 0, ‘NewMsgId’: 6930655840618917667, ‘OriContent’: ”, ‘EncryFileName’: ”, ‘User’: <User: {‘UserName’: ‘@156957cc475e1113c0a8fa340eae41ac1d92a12a523302a8eb06208616d656b3’, ‘MemberList’: <ContactList: []>}>, ‘Type’: ‘Friends’, ‘Text’: {‘status’: 3, ‘userName’: ‘@156957cc475e1113c0a8fa340eae41ac1d92a12a523302a8eb06208616d656b3’, ‘verifyContent’: ”, ‘autoUpdate’: {‘UserName’: ‘@156957cc475e1113c0a8fa340eae41ac1d92a12a523302a8eb06208616d656b3’, ‘NickName’: ‘Robot Pig’, ‘QQNum’: 0, ‘Province’: ”, ‘City’: ”, ‘Content’: ‘我是Robot Pig’, ‘Signature’: ‘(´v`o)♡’, ‘Alias’: ”, ‘Scene’: 30, ‘VerifyFlag’: 0, ‘AttrStatus’: 50467109, ‘Sex’: 2, ‘Ticket’: ‘v2_4ed4b6a5ac5ccf04c68c7bd64e4b543ba5babde23ce2985d4317bfc4bb62dcdfd78bb701551e1410fe64836f9bd147199de3e4493a031ea2daf52187816d9207@stranger’, ‘OpCode’: 2}}}
1
看上去和Json有点类似是吧,但是不是Json,你用Json格式化工具试试就知道了,而是一种类似于字典的东东
(跟下源码就知道了,msg的类:itchat.storage.messagequeue.Message)一大串有点乱,用PyCharm新建一个json文件,
复制粘贴格式化下:

因为类似于字典,你可以通过键的形式获取所需的值,比如打印下msg[‘Content’]:

<msg fromusername=”wxid_gvr9a3le939h22″ encryptusername=”v1_6a48a18b8d6164b69dfdd3949311cd4bc58d96dfe00636d5e9814ff36b8d107164e768ccd9b2fb911ea9b87ccac42978@stranger” fromnickname=”Robot Pig” content=”我是Robot Pig” shortpy=”ROBOTPIG” imagestatus=”3″ scene=”30″ country=”AD” province=”” city=”” sign=”(´v`o)♡” percard=”1″ sex=”2″ alias=”” weibo=”” albumflag=”0″ albumstyle=”0″ albumbgimgid=”” snsflag=”1″ snsbgimgid=”http://mmsns.qpic.cn/mmsns/icDH6NcE3zNVBleeQZUzzlnhWk16tIfPKyvsmqWIpUwAxHkvricuNCL2RvGPjS3pVq7miaZQoju8TU/0″ snsbgobjectid=”12478275406675193980″ mhash=”197adbfd7de1668f30895d20dfb09b67″ mfullhash=”197adbfd7de1668f30895d20dfb09b67″ bigheadimgurl=”http://wx.qlogo.cn/mmhead/ver_1/jYcpsrqBVaxDuMbFgPZwH5F3mvcgKMNVrQXlmmbGWx5p9iaFrYALPyia1b7Izn4YCUYDRXz1jnfKw0eUXvEeEH5NouILblzLDjog40fqOjIjc/0″ smallheadimgurl=”http://wx.qlogo.cn/mmhead/ver_1/jYcpsrqBVaxDuMbFgPZwH5F3mvcgKMNVrQXlmmbGWx5p9iaFrYALPyia1b7Izn4YCUYDRXz1jnfKw0eUXvEeEH5NouILblzLDjog40fqOjIjc/96″ ticket=”v2_4ed4b6a5ac5ccf04c68c7bd64e4b543b5c95f344b73e23e05f71c527683d8693969fcef8a893316431a660ac382032b022ec8af4d8ae5372e680931064da1ce3@stranger” opcode=”2″ googlecontact=”” qrticket=”” chatroomusername=”” sourceusername=”” sourcenickname=””><brandlist count=”0″ ver=”688441086″></brandlist></msg>
1
同样新建一个xml文件,复制粘贴格式化下:

里面有我们想要的信息,接着我们用正则来提取这些想要的数据:

msg_pattern = re.compile(‘<msg fromusername=”(.*?)”.*?fromnickname=”(.*?)” content=”(.*?)”.*?sign=”(.*?)”.*?sex=”(\d)”.*?bigheadimgurl=”(.*?)”‘,re.S)
1
接着修改一波代码:

@itchat.msg_register(itchat.content.FRIENDS)
def deal_with_friend(msg):
result = msg_pattern.search(msg[‘Content’])
if result is not None:
print(‘添加人微信id:’, result.group(1))
print(‘添加人用户名’, result.group(2))
print(‘验证内容’, result.group(3))
print(‘添加人个性签名’, result.group(4))
print(‘添加人性别’, result.group(5))
print(‘添加人头像大图’, result.group(6))
1
2
3
4
5
6
7
8
9
10
添加试试,打印结果如下:

行吧,什么加你人的信息都拿到了,你想干嘛就干嘛!

2.监听聊天信息
这个也很简单,你可以监听多种类型的信息,如下表所示:

信息类型 解释
itchat.content.TEXT 文本内容
itchat.content.MAP 位置文本
itchat.content.Card 名片
itchat.content.Note 通知文本
itchat.content.Sharing 分享名称
itchat.content.RECORDING 录音
itchat.PICTURE 图片/表情
itchat.content.VOICE 录音
itchat.content.ATTACHMENT 附件
itchat.content.VIDEO 短视频
itchat.content.FRIENDS 好友邀请
itchat.content.SYSTEM 系统信息
可注册多个信息监听,后注册的信息优先级高于先注册信息,带参数信息高于不带参数信息。
核心代码示例如下:

@itchat.msg_register(itchat.content.TEXT)
def reply_msg(msg):
if msg[‘Content’] == u’你好’:
itchat.send_msg(msg[‘User’][‘NickName’] + “你好啊!”, msg[‘FromUserName’])
1
2
3
4
和上面的监听并通过加好友的请求玩法一样,根据键拿值,或者正则提取需要的数据。这里提供几个常用的键:

msg[‘Content’] # 获取用户发送的内容,后面的匹配值建议加上u,代表Unicode编码
msg[‘User’][‘NickName’] # 发送信息的用户名
msg[‘FromUserName’] # 接收信息的用户名,这个不是直接的用户昵称或微信号!!!
1
2
3
3.发送信息
itchat支持下述几种类型的信息(不支持语音):

函数名 作用
send_msg() 发送文字信息
send_file() 发送文件
send_video() 发送视频
send_image() 发送图片
核心代码示例如下:

user_info = itchat.search_friends(name=’一朵死去的花’)
if len(user_info) > 0:
# 拿到用户名
user_name = user_info[0][‘UserName’]
# 发送文字信息
itchat.send_msg(‘培杰你好啊!’, user_name)
# 发送图片
time.sleep(10)
itchat.send_image(‘cat.jpg’, user_name)
# 发送文件
time.sleep(10)
itchat.send_file(’19_2.py’, user_name)
# 发送视频
time.sleep(10)
itchat.send_video(‘sport.mp4’, user_name)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
建议加入延时,避免信息发送过于频繁,导致账号被封!

4.获得群聊成员列表
核心代码示例如下:

@itchat.msg_register(itchat.content.TEXT, isGroupChat=True)
def reply_msg(msg):
print(“收到一条群信息:”, msg[‘ActualNickName’], msg[‘Content’])
1
2
3
玩法和之前的一样,另外,还可以调用**msg.isAt**判断是否有人@自己。

5.监控加群信息
核心代码示例如下:

@itchat.msg_register([NOTE], isGroupChat=True)
def revoke_msg(msg):
if ‘邀请’ in str(msg[‘Text’]):
# 进行相关操作
1
2
3
4
就是判断提示信息里是否有加群字眼,好吧,常用的大概就这些,其他的自行查阅文档。

Android无障碍服务——AccessibilityService
其实就是一个自动点点点的东西,没什么技术含量,真的!!!在开始讲解AccessibilityService之前,先要明确一点:

什么是自动化?

下面是我个人的理解:

把本该人做的,重复性高,单调,机械化的操作,交给程序去完成。

举个例子,小猪每天都要用微信拉人进群,所需的操作步骤如下所示:

是的,你拉一个人,需要20多秒,每次拉人的操作都是机械重复的。如果每天有30个人
进群,你需要花费:600s,10分钟我都够开一把王者荣耀的了,别人在上分,我还在拉人???

多捞哦,用AccessibilityService写个自动点点点的工具就可以把我从中解放出来。

1.自定义Service继承AccessibilityService
自定义一个AccessibilityService类,重写两个主要方法:onInterrupt( ):辅助功能中断的回调,基本不用理,
核心还是: onAccessibilityEvent(AccessibilityEvent event) 。

当界面发生改变,比如顶部Notification,界面更新,内容变化等,就会触发**onAccessibilityEvent方法。
点开AccessibilityEvent**类可以看到一堆的事件类型:

上面一大堆,其实并没有什么用,我一般是习惯直接把event.toString()给打印出来,然后自行判断:

代码示例如下:

这里做的事情就是,当无障碍相关的Event触发时,去判断Event类型以及触发事件的类名,再去执行相关操作:点击,滚动,填充文本等。

2.获取结点的几个方法
可以通过resource-id,text来定位到结点,如果可以,建议使用后者,因为一般APP更新后,这个id都会发生变化,(所以微信更新后都需要做适配,就是更新这个id)

1)通过UI Automator来查看布局层次

旧版的Android Studio,Ctrl + alt + A,输入 monitor 可以找到,新版的
Android Studio是找不到的,你需要来到**android-sdk/tools**目录下:

连接手机后,点击顶部的:

接着可以看到当前页面的层次结构图:

有一点务必注意: resource-id不一定是唯一!!!

getRootInActiveWindow( ):获取当前整个活动窗口的根节点,返回的是一个AccessibilityNodeInfo类,代表View的状态信息, 提供了下述几个非常实用的方法:

findAccessibilityNodeInfosByViewId:通过视图id查找节点元素。
findAccessibilityNodeInfosByText:通过字符串查找节点元素。
getParent:获取父节点。
getChild:获取子节点。
另外,找结点要注意判空,找不到对应结点直接调用其他方法是会空指针异常的!!!
找到结点然后就是一些动作了,常用的点击,长按,滚动和输入文字。代码示例如下:

/* 点击 */
node.performAction(AccessibilityNodeInfo.ACTION_CLICK)

/* 长按 */
node.performAction(AccessibilityNodeInfo.ACTION_LONG_CLICK)

/* 滚动 */
listNode.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) //向上滚动
listNode.performAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) //向下滚动

/* 输入文字 */
val arguments = Bundle()
arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,”xxx”)
editNode.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments)

/* 通过粘贴板输入文字 */
public static void sendTextForEditText(Context context, AccessibilityNodeInfo editNode, String text) {
if (editNode != null) {
ClipboardManager clipboard = (ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText(“text”, text);
clipboard.setPrimaryClip(clip);
//获得焦点
editNode.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
//粘贴内容
edittext.performAction(AccessibilityNodeInfo.ACTION_PASTE);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
除此之外,还有AccessibilityService本身特有的方法,如模拟回退键,Home键等。

performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK) //回退
performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME) //Home键
performGlobalAction(AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS) //点击notification
1
2
3
大概的玩法就这些,除了通过UI Automator获得id外,还可以通过其他几种方式来获取id。

2)开发者助手

如果你手机root了的话,可以安装一个**『开发者助手』**,点击当前界面分析,点击想查看的节点即可,如图所示。

3)通过adb命令

依次键入:

adb shell uiautomator dump /mnt/sdcard/window_dump.xml
adb pull /mnt/sdcard/window_dump.xml
1
2
运行结果如图所示:

接着可以把这个xml文件丢到as里,格式化下,折叠下一层层拆开,然后去找对应的结点:

这种方法是不怎么推荐的,除非这个结点很明显,比如文本啊,之类的,层级很多的时候,可能会找死你…

3.AccessibilityService注意事项
在使用AccessibilityService服务时,有几点要注意:

首先需要手动开启无障碍服务!!!程序转了跑,没反应,多半是因为没有开启无障碍服务!
无障碍服务一般在:辅助功能->无障碍,(不同的手机可能不同)找到自己的点点点APK,开启,如图所示:

另外,有一点要注意,有时可能因为异常导致程序意外终止了,你需要到无障碍中关掉对应的服务,然后重启。
还有一点最重要的无障碍服务的适用范围:

原生的Android APP!!! 是的原生!!!现在很多应用都是混合应用,对于H5的页面,无障碍服务是无能为力的!因为此时的控件点击事件不是通过onClick来产生的,而是直接判断TouchEvent。而Android的无障碍服务没有提供发送down,move,up事件的api。而替代方案只能使用root后的手机,向系统发送全局点击命令。

一般是拿到结点,然后获得结点所在的区域,然后执行相关的命令,比如点击,常用代码示例如下:

/* 执行Shell命令 */
public static void execShellCmd(String cmd) {
try {
// 申请获取root权限,这一步很重要,不然会没有作用
Process process = Runtime.getRuntime().exec(“su”);
// 获取输出流
OutputStream outputStream = process.getOutputStream();
DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
dataOutputStream.writeBytes(cmd);
dataOutputStream.flush();
dataOutputStream.close();
outputStream.close();
} catch (Throwable t) {
t.printStackTrace();
}
}

/* 点击某个结点 */
public static void perforGlobalClick(AccessibilityNodeInfo info) {
Rect rect = new Rect();
info.getBoundsInScreen(rect);
perforGlobalClick(rect.centerX(), rect.centerY());
}

/* 点击某个坐标点 */
public static void perforGlobalClick(int x, int y) {
execShellCmd(“input tap ” + x + ” ” + y);
}

/* 全局滑动 */
public static void perforGlobalSwipe(int x0, int y0, int x1, int y1) {
execShellCmd(“input swipe ” + x0 + ” ” + y0 + ” ” + x1 + ” ” + y1);
}

/* 全局返回 */
public static void perforGlobalHome(long delay) {
execShellCmd(“input keyevent ” + KeyEvent.KEYCODE_HOME);
}

/* 全局Home键 */
public static void perforGlobalHome(long delay) {
execShellCmd(“input keyevent ” + KeyEvent.KEYCODE_BACK);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
行吧,关于AccessibilityService的玩法大概就这些了,接着教大家撸一个我的半自动微信机器人。

3.动手撸一个自己的微信机器人
先罗列下我的需求:

1.自动通过别人加好友的验证,发送欢迎图和欢迎信息;
2.监听用户发送的信息,响应对应的信息
菜单:返回菜单回复词
1:加入「Python学习交流群」
2:加入「Android学习交流群」
3:加入「闲聊扯淡群」
4:加入「抠腚男孩的妙妙屋」
5:关注公众号「抠腚男孩」
6:小猪的「个人博客」
7:小猪的「Github」
8:给小猪「打赏」
9:小猪的「微信」(不闲聊!)
其他,默认回复黑人问号图。
好的,问题来了,itchat现在不支持拉人进群,怎么办?一个折中的方法,就是利用Android AccessibilityService,
完成自动拉人进群,我们可以采集发送进群的人的用户名,然后定时(比如两小时)发送一次到文件传输助手,然后
复制粘贴下用户名到我们编写的无障碍脚本里,完成自动拉人的操作,因为还要人去复制粘贴,所以只能算半自动!

接着第二个问题,数据的传输格式,如果只是一个群的话,最简单的,用户名拼接,回车作为分隔:

小A
小B
小C
1
2
3
这里的话,因为我有多个群,读者可以选择加入自己想加入的群,回车换行或者添加分隔符的方式显得有点low,
而且不方便扩展,这里,我决定使用Json字符串,存储加每个群的人的用户名,最后拼接成一个Json,示例如下:

{
“Python”: [],
“Python2”: [
“朱伟”,
“程命沆(hàng)”,
“双枪老汉”
]
“Speak”: [
“程命沆(hàng)”
],
“Android”: [
“朱伟”,
“程命沆(hàng)”
],
“Guy”: []
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PS:这里有Python2的原因是,一群满了,所以要做下判断,如果人数达到495的,把加群的人添加到二群。
所以还需要监控群群人员变化的信息,有新的人进群,获取一下一群当前的,加入到二群中。还有,要对
加群的人做下判断,如果已经在群里了,就不要添加到列表中。逻辑都弄清楚了,那就直接上代码吧:

# -*- coding:utf-8 -*-
# 微信小宇宙助手
import datetime
import re
import time
import random
import json
import itchat
from itchat.content import *
from apscheduler.schedulers.blocking import BlockingScheduler

# 群聊人员列表
member_python_list = []
member_python_list_2 = []
member_android_list = []
member_speak_list = []

# 加群人员的列表
group_python_list = [] # Python
group_python_list_2 = [] # Python 2群
group_android_list = [] # Android
group_speak_list = [] # 闲聊

# 获取群聊人员的列表的正则
nickname_compile = re.compile(r”\<ChatroomMember:.*?’NickName’: ‘(.*?)'”, re.S)

# 获取群聊名称的正则
group_name_compile = re.compile(“‘NickName’: ‘(.{1,40})’, ‘HeadImgUrl’:”, re.S)

# 添加好友通过欢迎词
welcome_words = ‘(˶ᵔᵕᵔ˶)嘤嘤嘤,???\n我是智障机器人小Pig,发送关键字:「菜单」 \n 查看更多小Pig的更多功能!’

# 菜单回复词
menu_answer = ‘(˶ᵔᵕᵔ˶)锵锵锵~???,\n’ \
‘可用关键词如下(输入对应数字,比如1):\n’ \
‘ ? 1.加入「Python学习交流群」\n’ \
‘ ? 2.加入「Android学习交流群」\n’ \
‘ ? 3.加入「闲聊扯淡群」\n’ \
‘ ? 4.关注公众号「抠腚男孩」\n’ \
‘ ? 5.小猪的「个人博客」\n’ \
‘ ? 6.小猪的「GitHub」\n’ \
‘ ? 7.给小猪「打赏」\n’ \
‘ ? 8.小猪「微信」(不闲聊哦~)\n’ \
‘注:请不要回复过于频繁,智障机器人不会聊天哦!?’

# 加群统一回复词
add_group_answer = ‘???FBI Warning!???\n(`・ω・´)ゞ非常抱歉的通知您:\n\n微信粑粑把拉人接口禁掉了,你的加群请求已收到,小猪童鞋会尽快把你拉到群中。\n\nヾノ≧∀≦)o 麻烦耐心等候哦!’

# 重复加群回复词
add_repeat_answer = ‘<(`^´)>哼,敲生气,你都在群里了,加什么群鸭!???’

# 捐献回复词
donate_answer = ‘(˶ᵔᵕᵔ˶)您的打赏,会让小猪更有动力肝♂出更Interesting的文章,谢谢支持~???’

# 小猪回复词
pig_answer = ‘(˶ᵔᵕᵔ˶)小猪童鞋不闲聊哦,有问题欢迎到群里讨论哦~’

# 404回复词
no_match_answer = ‘!!!非常抱歉,您输入的关键词粗错了,请发送「菜单」查看支持的数字关键字ヽ(・ω・´メ)’

msg_pattern = re.compile(
‘<msg fromusername=”(.*?)”.*?fromnickname=”(.*?)” content=”(.*?)”.*?sign=”(.*?)”.*?sex=”(\d)”.*?bigheadimgurl=”(.*?)”‘,
re.S)

# 自动通过加好友
@itchat.msg_register(itchat.content.FRIENDS)
def deal_with_friend(msg):
result = msg_pattern.search(msg[‘Content’])
if result is not None:
print(‘添加人微信id:’, result.group(1))
print(‘添加人用户名’, result.group(2))
print(‘验证内容’, result.group(3))
print(‘添加人个性签名’, result.group(4))
print(‘添加人性别’, result.group(5))
print(‘添加人头像大图’, result.group(6))

# itchat.add_friend(**msg[‘Text’]) # 自动将新好友的消息录入,不需要重载通讯录
# time.sleep(random.randint(1, 3))
# itchat.send_msg(welcome_words, msg[‘RecommendInfo’][‘UserName’])
# time.sleep(random.randint(1, 3))
# itchat.send_image(‘welcome.png’, msg[‘RecommendInfo’][‘UserName’])

# 自动回复配置
@itchat.msg_register([TEXT])
def deal_with_msg(msg):
text = msg[‘Content’]
if text == u’菜单’:
time.sleep(random.randint(1, 3))
itchat.send(menu_answer, msg[‘FromUserName’])
# 加入Python交流群
elif text == u’1′:
time.sleep(random.randint(1, 3))
nickname = msg[‘User’][‘NickName’]
if nickname not in member_python_list and nickname not in member_python_list_2:
itchat.send_msg(“【” + nickname + “】童鞋\n” + add_group_answer, msg[‘FromUserName’])
if nickname is not None:
# 人数超过阀值拉入二群
if len(member_python_list) >= 495:
if nickname not in group_python_list_2:
group_python_list_2.append(nickname)
else:
if nickname not in group_python_list:
group_python_list.append(nickname)
else:
itchat.send_msg(add_repeat_answer, msg[‘FromUserName’])
# 加入Android交流群
elif text == u’2′:
time.sleep(random.randint(1, 3))
nickname = msg[‘User’][‘NickName’]
if nickname not in member_android_list:
itchat.send_msg(“【” + nickname + “】童鞋\n” + add_group_answer, msg[‘FromUserName’])
if nickname is not None and nickname not in group_android_list:
group_android_list.append(nickname)
else:
itchat.send_msg(add_repeat_answer, msg[‘FromUserName’])
# 加入闲聊群
elif text == u’3′:
time.sleep(random.randint(1, 3))
nickname = msg[‘User’][‘NickName’]
if nickname not in member_speak_list:
itchat.send_msg(“【” + nickname + “】童鞋\n” + add_group_answer, msg[‘FromUserName’])
if nickname is not None and nickname not in group_speak_list:
group_speak_list.append(nickname)
else:
itchat.send_msg(add_repeat_answer, msg[‘FromUserName’])
# 公众号
elif text == u’4′:
time.sleep(random.randint(1, 3))
itchat.send_image(‘gzh.jpg’, msg[‘FromUserName’])
# 个人博客
elif text == u’5′:
time.sleep(random.randint(1, 3))
return ‘coder-pig的个人主页-掘金:https://juejin.im/user/570afb741ea493005de84da3′
# GitHub
elif text == u’6’:
time.sleep(random.randint(1, 3))
return ‘https://github.com/coder-pig’
# 打赏
elif text == u’7′:
time.sleep(random.randint(1, 3))
itchat.send_image(‘ds.gif’, msg[‘FromUserName’])
time.sleep(random.randint(1, 3))
itchat.send_msg(donate_answer, msg[‘FromUserName’])
time.sleep(random.randint(1, 3))
itchat.send_image(‘wxpay.png’, msg[‘FromUserName’])
# 小猪微信
elif text == u’8′:
time.sleep(random.randint(1, 3))
itchat.send_msg(pig_answer, msg[‘FromUserName’])
time.sleep(random.randint(1, 3))
itchat.send_image(‘scan_code.png’, msg[‘FromUserName’])
# 其他默认回复:
else:
time.sleep(random.randint(1, 3))
itchat.send_image(‘hrwh.png’, msg[‘FromUserName’])
time.sleep(random.randint(1, 3))
itchat.send_msg(no_match_answer, msg[‘FromUserName’])

@itchat.msg_register([NOTE], isGroupChat=True)
def revoke_msg(msg):
result = group_name_compile.search(str(msg))
if result is not None:
group_name = result.group(1)
if ‘邀请’ in str(msg[‘Text’]):
results = nickname_compile.findall(str(msg))
if group_name == ‘小猪的Python学习交流群’:
member_python_list.clear()
for result in results:
member_python_list.append(result)
elif group_name == ‘小猪的Android学习交流群’:
member_python_list.clear()
results = nickname_compile.findall(str(msg))
for result in results:
member_android_list.append(result)
elif group_name == ‘技♂术交流?’:
member_python_list.clear()
results = nickname_compile.findall(str(msg))
for result in results:
member_speak_list.append(result)

# 发送加群人信息列表
def send_friend_group():
friend_dict = {“Python”: [], “Android”: [], “Speak”: [], “Python2″: []}
for p in group_python_list:
friend_dict[‘Python’].append(p)
for a in group_android_list:
friend_dict[‘Android’].append(a)
for s in group_speak_list:
friend_dict[‘Speak’].append(s)
for p2 in group_python_list_2:
friend_dict[‘Python2’].append(p2)
if len(friend_dict[‘Python’]) > 0 or len(friend_dict[‘Android’]) > 0 or len(friend_dict[‘Speak’]) > 0 or len(
friend_dict[‘Python2’]) > 0:
itchat.send_msg(str(json.dumps(friend_dict, ensure_ascii=False, indent=4)), toUserName=”filehelper”)
group_python_list.clear()
group_python_list_2.clear()
group_android_list.clear()
group_speak_list.clear()

# 登陆成功后开启定时任务
def after_login():
sched.add_job(send_friend_group, ‘interval’, hours=2)
sched.start()

# 登陆时先获取群聊的UserName,获取群成员昵称会用到
def get_member_list():
python_chat_rooms = itchat.search_chatrooms(name=’小猪的Python学习交流1群’)
if len(python_chat_rooms) > 0:
group_username = python_chat_rooms[0][‘UserName’]
result = itchat.update_chatroom(group_username, detailedMember=True)
member_python_list.clear()
results = nickname_compile.findall(str(result))
for result in results:
member_python_list.append(result)
python_chat_rooms_2 = itchat.search_chatrooms(name=’小猪的Python学习交流2群’)
if len(python_chat_rooms_2) > 0:
group_username = python_chat_rooms_2[0][‘UserName’]
result = itchat.update_chatroom(group_username, detailedMember=True)
member_python_list_2.clear()
results = nickname_compile.findall(str(result))
for result in results:
python_chat_rooms_2.append(result)
android_chat_rooms = itchat.search_chatrooms(name=’小猪的Android学习交流群’)
if len(android_chat_rooms) > 0:
group_username = android_chat_rooms[0][‘UserName’]
result = itchat.update_chatroom(group_username, detailedMember=True)
member_android_list.clear()
results = nickname_compile.findall(str(result))
for result in results:
member_android_list.append(result)
speak_chat_rooms = itchat.search_chatrooms(name=’技♂术交流?’)
if len(android_chat_rooms) > 0:
group_username = speak_chat_rooms[0][‘UserName’]
result = itchat.update_chatroom(group_username, detailedMember=True)
member_speak_list.clear()
results = nickname_compile.findall(str(result))
for result in results:
member_speak_list.append(result)

if __name__ == ‘__main__’:
sched = BlockingScheduler()
itchat.auto_login(loginCallback=get_member_list, enableCmdQR=1)
itchat.run(blockThread=False)
after_login()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
运行后可以测试下我们的自动回复:

可以,自动回复的功能就做好了,接着是搭配着无障碍服务自动拉人。
先是五个群名称:

接着写一个Bean类,用来放Json数据。

接着就是无障碍服务类了,感觉没什么好讲的,直接上代码吧:

package com.coderpig.wechathelper

import android.accessibilityservice.AccessibilityService
import android.app.Notification
import android.app.PendingIntent
import android.os.Bundle
import android.os.Handler
import android.util.Log
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import com.orhanobut.hawk.Hawk

/**
* 描述:无障碍服务类
*
* @author CoderPig on 2018/04/12 13:47.
*/
class HelperService : AccessibilityService() {

private val TAG = “HelperService”
private val handler = Handler()
private var curGroup = “”
private var mMember = Member()

override fun onInterrupt() {}

override fun onAccessibilityEvent(event: AccessibilityEvent) {
val eventType = event.eventType
val classNameChr = event.className
val className = classNameChr.toString()
Log.d(TAG, event.toString())
when (eventType) {
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED -> {
if (Hawk.get(Constant.ADD_FRIENDS, false)) {
when (className) {
“com.tencent.mm.ui.LauncherUI” -> openGroup()
“com.tencent.mm.ui.contact.ChatroomContactUI” -> searchGroup()
“com.tencent.mm.ui.chatting.ChattingUI” -> openGroupSetting()
“com.tencent.mm.chatroom.ui.ChatroomInfoUI” -> openSelectContact()
“com.tencent.mm.ui.contact.SelectContactUI” -> addMembers()
}
}
if (className == “com.tencent.mm.ui.widget.a.c”) {
dialogClick()
}
}
}
}

//1.打开群聊
private fun openGroup() {
mMember = Hawk.get<Member>(Constant.MEMBER)
if(mMember.python_1.size != 0 || mMember.android.size != 0 || mMember.speak.size != 0
|| mMember.python_2.size != 0 || mMember.guy.size != 0) {
curGroup = when {
mMember.python_1.size > 0 -> Constant.GROUP_NAME_1
mMember.python_2.size > 0 -> Constant.GROUP_NAME_2
mMember.android.size > 0 -> Constant.GROUP_NAME_3
mMember.speak.size > 0 -> Constant.GROUP_NAME_4
mMember.guy.size > 0 -> Constant.GROUP_NAME_5
else -> “”
}
val nodeInfo = rootInActiveWindow
if (nodeInfo != null) {
val tabNodes = nodeInfo.findAccessibilityNodeInfosByViewId(“com.tencent.mm:id/cw2”)
for (tabNode in tabNodes) {
if (tabNode.text.toString() == “通讯录”) {
tabNode.parent.performAction(AccessibilityNodeInfo.ACTION_CLICK)
handler.postDelayed({
val newNodeInfo = rootInActiveWindow
if (newNodeInfo != null) {
val tagNodes = newNodeInfo.findAccessibilityNodeInfosByViewId(“com.tencent.mm:id/lv”)
for (tagNode in tagNodes) {
if (tagNode.text.toString() == “群聊”) {
tagNode.parent.parent.performAction(AccessibilityNodeInfo.ACTION_CLICK)
break
}
}
}
}, 500L)
}
}
}
}
}

//2.搜索群聊
private fun searchGroup() {
val nodeInfo = rootInActiveWindow
if (nodeInfo != null) {
val nodes = nodeInfo.findAccessibilityNodeInfosByViewId(“com.tencent.mm:id/m6”)
for (info in nodes) {
if (info.text.toString() == curGroup) {
info.parent.performAction(AccessibilityNodeInfo.ACTION_CLICK)
break
}
}
}
}

//3.打开群聊设置
private fun openGroupSetting() {
when (curGroup) {
Constant.GROUP_NAME_1 -> {
if(mMember.python_1.size > 0) {
val nodeInfo = rootInActiveWindow
if (nodeInfo != null) {
nodeInfo.findAccessibilityNodeInfosByViewId(“com.tencent.mm:id/j1”)[0].performAction(AccessibilityNodeInfo.ACTION_CLICK)
}
}
}
Constant.GROUP_NAME_2 -> {
if(mMember.python_2.size > 0) {
val nodeInfo = rootInActiveWindow
if (nodeInfo != null) {
nodeInfo.findAccessibilityNodeInfosByViewId(“com.tencent.mm:id/j1”)[0].performAction(AccessibilityNodeInfo.ACTION_CLICK)
}
}
}
Constant.GROUP_NAME_3 -> {
if(mMember.android.size > 0) {
val nodeInfo = rootInActiveWindow
if (nodeInfo != null) {
nodeInfo.findAccessibilityNodeInfosByViewId(“com.tencent.mm:id/j1”)[0].performAction(AccessibilityNodeInfo.ACTION_CLICK)
}
}
}
Constant.GROUP_NAME_4 -> {
if(mMember.speak.size > 0) {
val nodeInfo = rootInActiveWindow
if (nodeInfo != null) {
nodeInfo.findAccessibilityNodeInfosByViewId(“com.tencent.mm:id/j1”)[0].performAction(AccessibilityNodeInfo.ACTION_CLICK)
}
}
}
Constant.GROUP_NAME_5 -> {
if(mMember.guy.size > 0) {
val nodeInfo = rootInActiveWindow
if (nodeInfo != null) {
nodeInfo.findAccessibilityNodeInfosByViewId(“com.tencent.mm:id/j1”)[0].performAction(AccessibilityNodeInfo.ACTION_CLICK)
}
}
}
else -> {
performBackClick()
}
}
}

//4.滚动后点击添加按钮,打开添加成员页面
private fun openSelectContact() {

if(curGroup != “”) {
var members = arrayListOf<String>()
when (curGroup) {
Constant.GROUP_NAME_1 -> members = mMember.python_1
Constant.GROUP_NAME_2 -> members = mMember.python_2
Constant.GROUP_NAME_3 -> members = mMember.android
Constant.GROUP_NAME_4 -> members = mMember.speak
Constant.GROUP_NAME_5 -> members = mMember.guy
}
if (members.size > 0) {
val nodeInfo = rootInActiveWindow
if (nodeInfo != null) {
val numText = nodeInfo.findAccessibilityNodeInfosByViewId(“android:id/text1”)[0].text.toString()
val memberCount = numText.substring(numText.indexOf(“(“) + 1,numText.indexOf(“)”)).toInt()
val listNode = nodeInfo.findAccessibilityNodeInfosByViewId(“android:id/list”)[0]
if(memberCount > 100) {
listNode.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD)
listNode.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD)
}
val scrollNodeInfo = rootInActiveWindow
if (scrollNodeInfo != null) {
handler.postDelayed({
val nodes = scrollNodeInfo.findAccessibilityNodeInfosByViewId(“com.tencent.mm:id/dnm”)
for (info in nodes) {
if (info.contentDescription.toString() == “添加成员”) {
info.parent.performAction(AccessibilityNodeInfo.ACTION_CLICK)
break
}
}
}, 1000L)
}
}
}
} else {
performBackClick()
}

}

//5.添加成员
private fun addMembers() {
var members = arrayListOf<String>()
//最后一次的时候清空记录,并且点击顶部确定按钮
when (curGroup) {
Constant.GROUP_NAME_1 -> members = mMember.python_1
Constant.GROUP_NAME_2 -> members = mMember.python_2
Constant.GROUP_NAME_3 -> members = mMember.android
Constant.GROUP_NAME_4 -> members = mMember.speak
Constant.GROUP_NAME_5 -> members = mMember.guy
}
if (members.size > 0) {
for (i in 0 until members.size) {
handler.postDelayed({
val nodeInfo = rootInActiveWindow
if (nodeInfo != null) {
val editNodes = nodeInfo.findAccessibilityNodeInfosByViewId(“com.tencent.mm:id/b26”)
if (editNodes != null && editNodes.size > 0) {
val editNode = editNodes[0]
val arguments = Bundle()
arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, members[i])
editNode.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments)
}
}
}, 500L * (i + 1))
handler.postDelayed({
val cbNodes = rootInActiveWindow.findAccessibilityNodeInfosByViewId(“com.tencent.mm:id/om”)
if (cbNodes != null) {
val cbNode: AccessibilityNodeInfo?
if (cbNodes.size > 0) {
cbNode = cbNodes[0]
cbNode?.parent?.performAction(AccessibilityNodeInfo.ACTION_CLICK)
}
}
//最后一次的时候清空记录,并且点击顶部确定按钮
if (i == members.size – 1) {
val m = Hawk.get<Member>(Constant.MEMBER)
when (curGroup) {
Constant.GROUP_NAME_1 -> m.python_1 = arrayListOf()
Constant.GROUP_NAME_2 -> m.python_2 = arrayListOf()
Constant.GROUP_NAME_3 -> m.android = arrayListOf()
Constant.GROUP_NAME_4 -> m.speak = arrayListOf()
Constant.GROUP_NAME_5 -> m.guy = arrayListOf()
}
Hawk.put(Constant.MEMBER, m)
curGroup = “”
val sureNodes = rootInActiveWindow.findAccessibilityNodeInfosByViewId(“com.tencent.mm:id/j0”)
if (sureNodes != null && sureNodes.size > 0) {
sureNodes[0].performAction(AccessibilityNodeInfo.ACTION_CLICK)
}

}
}, 800L * (i + 1))
}
}
}

//对话框自动点击
private fun dialogClick() {
val inviteNode = rootInActiveWindow.findAccessibilityNodeInfosByViewId(“com.tencent.mm:id/au_”)[0]
inviteNode.performAction(AccessibilityNodeInfo.ACTION_CLICK)
}

private fun performBackClick() {
handler.postDelayed({ performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK) }, 1300L)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
接着复制itchat返回的加群人的数据,写入后,点击打开微信,接下来就是享受自动加群了,如动图所述(加速过…)

行吧,关于Android无障碍服务 X itchat打造微信半自动机器人,就说这么多,如果你看完
还不会,我是真的没办法了…无障碍服务不止可以应用于微信,其他原生APP也可以做,比如最常见的
自动打卡,自动签到等,读者学会了方法后,可以自行拓展~

4.仓库地址
ItChatWXHelper:配合无障碍服务器拉人用的基于itchat的机器人

WechatHelper:利用Android AccessibilityService 实现自动加好友,拉人进群聊
————————————————
版权声明:本文为CSDN博主「coder-pig」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/coder_pig/article/details/84667061

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏