Android无障碍服务 x itchat 打造微信半自动机器人

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

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

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

  • 1.群主,你的机器人怎么实现的?
  • 2.群主,你的apk怎么运行了没反应?
  • 3.群主,支持自动添加手机号为好友吗?
  • ...等等

一开始我仍是比较热衷帮忙解决问题的,可是耐心这种东西呢,是最容易被消磨殆尽的。并且学习Python以后,我变得愈来愈懒,一些繁琐重复的操做,我都会想办法自动化....固然,若是你是给我打钱或者是妹子,我也是很乐意的。git

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

东西就那些,套路也是那些,大部分时候,你只须要一点 『灵性』正则表达式

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


开源的微信我的号接口库——itchat

itchat库基于微信网页版,出了好几年的了,微信正在慢慢收窄网页端的功能(由于滥用的微信机器人),不少之前能用的接口慢慢都不能用了,好比:拉人进群,添加好友等,反正网页端没有的,如今都不能用。丢几个连接:json

itchat如今能作的:监听加好友的信息,监听聊天信息(包括群聊),发送信息。 基本上经常使用的并且可用的就这三个。固然若是你愿意掏钱的话,你不须要折腾那么多,网上有其余付费的途径:微友助手,王二狗机器人,还有xposed插件等。基于其余协议且可用的免费开源库目前还没见过,有知道的欢迎在评论区告知下!api

使用要点提炼bash

若是能够,我但愿你,尽量的学会使用『正则表达式处理字符串』(基本功),丢个之前写过的文章: 小猪的Python学习之旅 —— 3.正则表达式,正则对于字符串匹配,提取很是实用!

1.监听并经过加好友的请求

@itchat.msg_register(itchat.content.FRIENDS)
def deal_with_friend(msg):
    # 自动将新好友的消息录入,不须要重载通信录
    itchat.add_friend(**msg['Text']) 
复制代码

经过上面的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}}}
复制代码

看上去和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>
复制代码

一样新建一个xml文件,复制粘贴格式化下:

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

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))
复制代码

添加试试,打印结果以下:

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

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'])
复制代码

和上面的监听并经过加好友的请求玩法同样,根据键拿值,或者正则提取须要的数据。这里提供几个经常使用的键:

msg['Content']  # 获取用户发送的内容,后面的匹配值建议加上u,表明Unicode编码
msg['User']['NickName'] # 发送信息的用户名
msg['FromUserName'] # 接收信息的用户名,这个不是直接的用户昵称或微信号!!!
复制代码

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)
复制代码

建议加入延时,避免信息发送过于频繁,致使帐号被封!

4.得到群聊成员列表

核心代码示例以下:

@itchat.msg_register(itchat.content.TEXT, isGroupChat=True)
def reply_msg(msg):
    print("收到一条群信息:", msg['ActualNickName'], msg['Content'])
复制代码

玩法和以前的同样,另外,还能够调用**msg.isAt**判断是否有人@本身。

5.监控加群信息

核心代码示例以下:

@itchat.msg_register([NOTE], isGroupChat=True)
def revoke_msg(msg):
    if '邀请' in str(msg['Text']):
        # 进行相关操做
复制代码

就是判断提示信息里是否有加群字眼,好吧,经常使用的大概就这些,其余的自行查阅文档。


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);
    }
}

复制代码

除此以外,还有AccessibilityService自己特有的方法,如模拟回退键,Home键等。

performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK)    //回退
performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME)    //Home键
performGlobalAction(AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS)    //点击notification
复制代码

大概的玩法就这些,除了经过UI Automator得到id外,还能够经过其余几种方式来获取id。

2)开发者助手

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

3)经过adb命令

依次键入:

adb shell uiautomator dump /mnt/sdcard/window_dump.xml
adb pull /mnt/sdcard/window_dump.xml
复制代码

运行结果如图所示:

接着能够把这个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);
}
复制代码

行吧,关于AccessibilityService的玩法大概就这些了,接着教你们撸一个个人半自动微信机器人。


3.动手撸一个本身的微信机器人

先罗列下个人需求:

  • 1.自动经过别人加好友的验证,发送欢迎图和欢迎信息;
  • 2.监听用户发送的信息,响应对应的信息
  • 菜单:返回菜单回复词
  • 1:加入「Python学习交流群」
  • 2:加入「Android学习交流群」
  • 3:加入「闲聊扯淡群」
  • 4:加入「抠腚男孩的妙妙屋」
  • 5:关注公众号「抠腚男孩」
  • 6:小猪的「我的博客」
  • 7:小猪的「Github」
  • 8:给小猪「打赏」
  • 9:小猪的「微信」(不闲聊!)
  • 其余,默认回复黑人问号图。

好的,问题来了,itchat如今不支持拉人进群,怎么办?一个折中的方法,就是利用Android AccessibilityService, 完成自动拉人进群,咱们能够采集发送进群的人的用户名,而后定时(好比两小时)发送一次到文件传输助手,而后 复制粘贴下用户名到咱们编写的无障碍脚本里,完成自动拉人的操做,由于还要人去复制粘贴,因此只能算半自动!

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

小A
小B
小C
复制代码

这里的话,由于我有多个群,读者能够选择加入本身想加入的群,回车换行或者添加分隔符的方式显得有点low, 并且不方便扩展,这里,我决定使用Json字符串,存储加每一个群的人的用户名,最后拼接成一个Json,示例以下:

{
    "Python": [],
     "Python2": [
        "朱伟",
        "程命沆(hàng)",
        "双枪老汉"
    ]
    "Speak": [
        "程命沆(hàng)"
    ],
    "Android": [
        "朱伟",
        "程命沆(hàng)"
    ],
    "Guy": []
}
复制代码

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()
复制代码

运行后能够测试下咱们的自动回复:

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

接着写一个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)
    }
}
复制代码

接着复制itchat返回的加群人的数据,写入后,点击打开微信,接下来就是享受自动加群了,如动图所述(加速过...)

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

4.仓库地址

  • ItChatWXHelper:配合无障碍服务器拉人用的基于itchat的机器人

  • WechatHelper:利用Android AccessibilityService 实现自动加好友,拉人进群聊

相关文章
相关标签/搜索