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

mikel阅读(1284)

来源: 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

小猪的Python学习之旅 —— 18.Python微信转发小宇宙早报 - 掘金

mikel阅读(1008)

来源: 小猪的Python学习之旅 —— 18.Python微信转发小宇宙早报 – 掘金

小猪的Python学习之旅 —— 18.Python微信转发小宇宙早报

一句话概括本文

这篇非常简单好玩,利用itchat,监控特定信息,转发到特定微信群~


引言

上一篇 小猪的Python学习之旅 —— 17.Python数据分析:我主良缘交友了解下 貌似反响不错,一堆微信加群的,后面有个小伙伴说看到我的文章,是被推荐到了 掘金Python群的早报,掘金竟然还有官方Py群,妥妥地让他拉我进群啊,然后呢, 里面的掘金之饼赵小饼是个机器人,会拉人,发表之类的,觉得有些意思,自己 也搜了一下相关的库Star了,以后怕是会用到。这不,今天就搞了个简单的东西 玩玩,场景是这样的:

在干货群里有个蘑菇云大佬天天发小宇宙早报,比如今天的:

#每天清晨花60秒了解昨夜今晨的世界大事#

小宇宙整理第663天早安快报,3月30日,星期五,农历二月十四!

1、交通部:全国190个城市实现交通一卡通互联互通;

2、霍金墓地将与牛顿达尔文为邻 葬礼只邀请家人朋友参加;

3、央行货币金银工作会议:开展对各类虚拟货币的整顿清理;

4、三大运营商年报对比:赚钱方面,联通+电信=0.18个移动;

5、爱奇艺 正式登陆纳斯达克 成为目前国内视频平台中唯一的上市公司;

6、6秒42!国际田联正式官宣:中国选手苏炳添以6秒42的成绩创造了新的亚洲纪录!

7、WiFi万能钥匙回应“窃取用户密码”:原理是热点共享 不是破解;

8、动车降价!28条城际铁路动车票价最大折扣将提高至20%;

9、脸书丑闻发酵,剑桥分析母公司被指研究印度选民数据,花花公子宣布删除Facebook官方主页;

10、提议乐视破产重整或退市,孙宏斌或涉嫌”操纵市场”;

【早安微语】Time goes by so fast, people go in and out of your life. 如梭时光,生命中人来人往

然后呢,之前自己发到过几个群里,都很喜欢看这个,然后天天早上@我发, 然而我也是转发别人的,别人没发我怎么发?我跟你讲我就是这个表情:

然后呢,每次我都要翻一堆聊天记录,找到小宇宙日报,然后转发, 有时蘑菇云大佬有事,或者我开会忙之类的,往往早报变成了午报。

说有没有公号之类的,就死了这条心吧,目前是没有的,专门发新闻那个 群是要付费的,已经问过小宇宙本人了,只是他偶尔会在一些群里发。

作为一个Py玩家,肯定要想办法解放自己的双手啊,让自动来,套路也不复杂:

监测到小宇宙或者蘑菇云发送的今天的小宇宙,就转发到特定的群。

怎么监测?

  • 1.抓包?naive,微信用的自己的协议,研究有得你研究。
  • 2.写个xposed模块,类似于自动抢红包的套路?监听接收消息的函数, 遇到是小宇宙的信息,直接把信息发送到特定的群?投入时间成本比较 高,有得整,最主要我知道些,并不熟悉…
  • 3.打开浏览器登录微信网页端,自己处理网页结构之类的?放弃

最后想起之前star的:itchatgithub.com/littlecoder…

啧啧,有点意思,反正写着基于微信网页端,网页端能干的,这个都能干。


1.用itchat写个小宇宙早报转发脚本

显示命令行走一波pip装下库:

pip install itchat
复制代码

接着分析下我们的流程:

  • 1.监听群聊信息
  • 2.正则过滤当天的小宇宙早报
  • 3.如果过滤到小宇宙早报,判断是否为小宇宙本人发的(避免有些人调戏机器人)
  • 4.把新闻转发到某些特定的群
  • 5.本地测试没问题后,把脚本丢到腾讯云服务器上跑

另外要注意:

移动端要保证微信号在线,不然网页端会断开链接,具体网页端时效性有多久, 还不知道,另外感觉长时间不发信息会被下线,故加个定时器,每隔半个小时 往一个没用的群里发送一条信息。

大概实现流程如上所述,接着开始撸代码

过滤小宇宙早报的正则

对接收到的信息做正则,小宇宙的,而且是特定人发的, 遍历群发的组,调用send方法发送信息。

接着main方法调用下,会弹出一个二维码页面让你扫:

接着就可以试试发下早报了,复制了今天的小宇宙,另外建了三个群聊, 接着随便往其中的一个发小宇宙~

到此相信就没什么疑问了吧,非常简单,接着Timer写个定时器, 每个半个小时往探挽懒月群里发一条广告词

代码如下

先把时间改成10s看下结果:

可以,接下来就是把脚本丢服务器上执行了。


2.把脚本丢到服务器上运行(利用Screen命令)

不知道你还记不记得ssh连服务器执行py脚本,终端关闭后,脚本也会停止, 如果不记得可以看回之前写的:8.爬虫实战:刷某博客站点的访问量 那里调用的是nohup命令,以后台程序的方式执行我们的脚本。

评论区那里有个掘金大佬评论说可以试试screen(虚拟终端),这里就来试试~

基本用法如下

  • 1.新建Screenscreen -S screen名字
  • 2.Screen里新建虚拟终端:组合键:ctrl + a + c
  • 3.在多个虚拟终端间切换:组合键:ctrl + a + p (上一个), ctrl + a + n (后一个),ctrl + a + shirt + " (终端列表之间切换)
  • 4.关闭一个虚拟终端ctrl + a + k 或者键入 exit
  • 5.挂起当前screen,回到之前的shell:ctrl + a + d
  • 6.重新连接screen:键入**screen -ls列出所有screen,键入screen - r xxx**重新连接;

接着依旧复制下小宇宙发到群里,看是否能正常转发~

到此就完了,脚本挂着,手机微信也需要登着,坐等明天小宇宙发早报 来检验效果啦~


小结

本节试了下itchat,写了个监控小宇宙发的日报,并转发到某特定群 的小脚本,挺有意思的,当然你可以自行扩展做个机器人,比如每天 定时发车,每日一女优介绍,每日一车牌号?或者一些其他的信息, 结合后台玩起来更嗨,当然这就是后面的事了~

最后祝周末愉快~


附:最终代码(都可以在:github.com/coder-pig/R… 找到):

# 微信小宇宙助手
import itchat
from itchat.content import *
import datetime
import re
import time
from threading import Timer

xyz_compile = re.compile(r'.*?小宇宙整理.*?%d月%d日.*'
                         % (datetime.datetime.now().month, datetime.datetime.now().day), re.S)


# 小宇宙日报抓取
@itchat.msg_register([TEXT], isGroupChat=True)
def xyz_reply(msg):
    group_list = [u'我是渣渣辉', u'我是轱天乐', u'探挽懒月']
    group_name = []
    for group in group_list:
        chat = itchat.search_chatrooms(name=group)
        if len(chat) > 0:
            group_name.append(chat[0]['UserName'])
    # 过滤小宇宙新闻
    result = xyz_compile.search(msg['Content'])

    if result is not None:
        if result.group() is not None and msg['ActualNickName'] == '十二':
            for group in group_name:
                itchat.send('%s' % (msg['Content']), toUserName=group)


# 发信息
def send_msg():
    sched_time = datetime.datetime(2018, 3, 30, 16, 30, 0)
    flag = 0
    while True:
        now = datetime.datetime.now()
        if now == sched_time < now < (sched_time + datetime.timedelta(seconds=10)):
            flag = 1
            time.sleep(1)
        else:
            if flag == 1:
                itchat.send('123', toUserName=u'探挽懒月')
                flag = 0


# 每个半个小时发依次信息貌似能防止掉线
def loop_send():
    global count
    itchat.send('大扎好,我系轱天乐,我四渣嘎辉,探挽懒月,介四里没有挽过的船新版本,'
                '挤需体验三番钟,里造会干我一样,爱像借款游戏。'
                , toUserName=itchat.search_chatrooms(name=u'探挽懒月')[0]['UserName'])
    count += 1
    if count < 10000:
        Timer(1800, loop_send).start()


if __name__ == '__main__':
    count = 0
    Timer(1800, loop_send).start()
    itchat.auto_login(enableCmdQR=2, hotReload=True)
    itchat.run()
复制代码

来啊,Py交易啊

想加群一起学习Py的可以加下,智障机器人小Pig,验证信息里包含: PythonpythonpyPy加群交易屁眼 中的一个关键词即可通过;

验证通过后回复 加群 即可获得加群链接(不要把机器人玩坏了!!!)~~~ 欢迎各种像我一样的Py初学者,Py大神加入,一起愉快地交流学♂习,van♂转py。

当前不会命中断点。源代码与原始版本不同 (VS2012) - _Ong - 博客园

mikel阅读(989)

来源: 当前不会命中断点。源代码与原始版本不同 (VS2012) – _Ong – 博客园

遇到“当前不会命中断点。源代码与原始版本不同”的问题。 在网上查的类似:
一般studio会提示将“工具”,“选项”,“调试”,“要求源文件与原始版本完成匹配”去掉勾。
但是这个配置去掉治标不治本,错误是不会提示了,但是依旧没有执行最新的程序。
我在google搜了一把,有以下几种解决方法:
1.清理解决方案,重新生成
2.格式化,重新生成(ctrl_A,ctrl_F)
3.将出问题的文件用notepad打开,然后另存为Unicode编码
4.删掉临时文件夹:C:/windows/microsoft.net/Frameword/2.0.50727/Temporary ASP.NET Files/
删除了下相关目录删除.这里要提示一下删除此文件夹内容时需要关掉vs.
再打开vs,打开解决方案,执行,依旧无法命中断点。
这时候需要做的是:把出问题的文件内容copy出来,然后删除此文件,编译(这里一定要编译!我试过不编译是不行的),当然肯定是不通过的。
添加新项,加一个新的文件,名称起刚才删除文件的名字,再把刚才的内容copy进来(这里等于是重新创建了一个同样的文件),再编译,肯定通过的了。
这时候执行出来的程序就是可以命中断点了!
总结一下:
第一步:复制出问题文件内容并删除此文件
第二步:分步编译!!!不通过
第三步:新建相同的文件名并将刚才的复制内容past进来
第四部:编译通过!!!ok!

 

试了1,3 不行, 于是试试4,

打开文件夹:C:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files

删除 root子文件夹,好多提示 权限问题, 于是以管理员身份运行cmd,直接敲命令:

rd /s /q “C:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\root”

重新 打开vs2012, OK……      不明白MS为啥一直让这个问题存在???

在C#代码中应用Log4Net(四)在Winform和Web中捕获全局异常 - 陈哈哈 - 博客园

mikel阅读(711)

来源: 在C#代码中应用Log4Net(四)在Winform和Web中捕获全局异常 – 陈哈哈 – 博客园

毕竟人不是神,谁写的程序都会有bug,有了bug不可怕,可怕的是出错了,你却不知道错误在哪里。所以我们需要将应用程序中抛出的所有异常都记录起来,不然出了错,找问题就能要了你的命。下面我们主要讨论的是如何捕捉全局的异常。基本上在winform或web中捕获全局异常的思路都是一样的,在全局的应用程序对象中添加异常捕获的代码,并写入日志文件中。

一.在Winform程序中捕获全局异常

在winfrom中我们需要了解Application对象中的两个事件

Application.ThreadException 事件–UI线程中某个异常未被捕获时出现。

AppDomain.UnhandledException 事件–非UI线程中某个异常未被捕获时出现。

我们需要在Program.cs中设置异常的捕捉代码(如下图所示)。LogHelper类是自定义的日志帮助类,在前面的几篇文章中已经有涉及到。

image

需要在Program.cs中添加的代码如下所示

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Text;
using Common;

namespace testLog4N
{
    static class Program
    {
        /// <summary>
        /// 应用程序的主入口点。
        /// </summary>
        [STAThread]
        static void Main()
        {
            BindExceptionHandler();//绑定程序中的异常处理
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
        /// <summary>
        /// 绑定程序中的异常处理
        /// </summary>
        private static void BindExceptionHandler()
        {
            //设置应用程序处理异常方式:ThreadException处理
            Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
            //处理UI线程异常
            Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);
            //处理未捕获的异常
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
        }
        /// <summary>
        /// 处理UI线程异常
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
        {
            LogHelper.ErrorLog(null, e.Exception as Exception);
        }
        /// <summary>
        /// 处理未捕获的异常
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            LogHelper.ErrorLog(null, e.ExceptionObject as Exception);
        }
    }
}
复制代码

示例性代码下载

二、在Web中捕获全局异常

我们只需要在Global.asax文件中添加异常捕获的代码即可。

image

完整Global.asax代码如下所示

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.SessionState;
using Common;

namespace WebApplication_testLog4Net
{
    public class Global : System.Web.HttpApplication
    {

        void Application_Start(object sender, EventArgs e)
        {
            // 在应用程序启动时运行的代码

        }

        void Application_End(object sender, EventArgs e)
        {
            //  在应用程序关闭时运行的代码

        }

        void Application_Error(object sender, EventArgs e)
        {
            // 在出现未处理的错误时运行的代码
            Exception objExp = HttpContext.Current.Server.GetLastError();
            LogHelper.ErrorLog("<br/><strong>客户机IP</strong>:" + Request.UserHostAddress + "<br /><strong>错误地址</strong>:" + Request.Url , objExp);
        }

        void Session_Start(object sender, EventArgs e)
        {
            // 在新会话启动时运行的代码
           }

        void Session_End(object sender, EventArgs e)
        {
            // 在会话结束时运行的代码。 
            // 注意: 只有在 Web.config 文件中的 sessionstate 模式设置为
            // InProc 时,才会引发 Session_End 事件。如果会话模式设置为 StateServer 
            // 或 SQLServer,则不会引发该事件。

        }

    }
}
复制代码

示例程序下载

三、在WPF中捕获全局异常

我们只需要在App.xaml文件中添加异常捕获的代码即可。

image

在WPF中捕获全局异常主要涉及到以下两个事件

1、AppDomain.UnhandledException 事件–当某个异常未被捕获时出现。主要指的是非UI线程。

2、Application.DispatcherUnhandledException 事件–如果异常是由应用程序引发,但未处理,发生。主要指的是UI线程。

完整的App.xaml文件如下所示

复制代码
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Windows;
using Common;

namespace WpfApplication1
{
    /// <summary>
    /// App.xaml 的交互逻辑
    /// </summary>
    public partial class App : Application
    {
        public App()
        {
            this.DispatcherUnhandledException += new System.Windows.Threading.DispatcherUnhandledExceptionEventHandler(App_DispatcherUnhandledException);
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
        }
        
        void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            if (e.ExceptionObject is System.Exception)
            {
                LogHelper.ErrorLog(null, (System.Exception)e.ExceptionObject);
            }
        }

        public static void HandleException(Exception ex)
        {
            LogHelper.ErrorLog(null,ex);
        }

        void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
        {
            e.Handled = true;
            LogHelper.ErrorLog(null, e.Exception);
        }
    
    }
}
复制代码

示例代码下载

作者:kissazi2
出处:http://www.cnblogs.com/kissazi2/
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

c# winform 应用程序根据条件阻止窗口关闭 - 李不爽 - 博客园

mikel阅读(1068)

来源: c# winform 应用程序根据条件阻止窗口关闭 – 李不爽 – 博客园

/添加窗口关闭事件委托

在窗口关闭事件中处理

private void TestForm_FormClosing(object sender, FormClosingEventArgs e)
{
switch (e.CloseReason)
{
//应用程序要求关闭窗口
case CloseReason.ApplicationExitCall:
e.Cancel = false; //不拦截,响应操作
break;
//自身窗口上的关闭按钮
case CloseReason.FormOwnerClosing:
e.Cancel = true;//拦截,不响应操作
break;
//MDI窗体关闭事件
case CloseReason.MdiFormClosing:
e.Cancel = true;//拦截,不响应操作
break;
//不明原因的关闭
case CloseReason.None:
break;
//任务管理器关闭进程
case CloseReason.TaskManagerClosing:
e.Cancel = false;//不拦截,响应操作
break;
//用户通过UI关闭窗口或者通过Alt+F4关闭窗口
case CloseReason.UserClosing:
e.Cancel = true;//拦截,不响应操作
break;
//操作系统准备关机
case CloseReason.WindowsShutDown:
e.Cancel = false;//不拦截,响应操作
break;
default:
break;
}

//if(e.Cancel == false)
// base.OnFormClosing(e);
}

错误信息:Microsoft 分布式事务处理协调器(MS DTC)已取消此分布式事务_数据库_少年休闲海-CSDN博客

mikel阅读(1314)

来源: 错误信息:Microsoft 分布式事务处理协调器(MS DTC)已取消此分布式事务_数据库_少年休闲海-CSDN博客

在联机文档中是这样描述MS DTC的:
Microsoft 分布式事务处理协调器 (MS DTC) 是一个事务管理器,它允许客户端应用程序在一个事务中包含多个不同的数据源。MS DTC 协调在所有已在事务中登记的服务器间提交分布式事务。
Microsoft® SQL Server™ 安装可通过下列方法参与分布式事务:
1,调用运行 SQL Server 的远程服务器上的存储过程。
2,自动或显式地将本地事务提升为一个分布式事务并在该事务中登记远程服务器。
3, 执行分布式更新以更新多个 OLE DB 数据源上的数据。如果这些 OLE DB 数据源支持 OLE DB 分布式事务接口,SQL Server 还可以将它们登记在分布式事务中。 MS DTC 服务协调正确完成分布式事务,以确保所有服务器上的全部更为永久性的,或在发错误时删除所有更新。
SQL->CREATE TABLE test1 ([id] int ,email varchar(50))
表test1在A服务器中,表email在B服务器中,其表结构和test1一样。在A服务器中运行
SQL->EXEC sp_addlinkedserver ‘B’,’SQL Server’
SQL->GO
SQL->EXEC sp_addlinkedsrvlogin ‘B’,’false’,’sa’,’sa’,’123456′
同样在B服务器中运行以上代码,不同的是服务器名称的变化。
在B服务器中执行如下语句:
SQL->CREATE PROCEDURE dbo.usp_test
SQL->AS
SQL->SELECT id,email FROM email
SQL->GO
这样就把为AB服务器互相添加了Linked Server,并在AB服务器中启动MSDTC服务。在A服务器中运行如下语句:
SQL->BEGIN TRANSACTION Ta
SQL->INSERT INTO test (id,email)
SQL->EXEC a.test.dbo.usp_test
SQL->COMMIT TRANSACTION Ta
运行出错,错误如下:
服务器: 消息 7391,级别 16,状态 1,行 1
该操作未能执行,因为 OLE DB 提供程序 ‘SQLOLEDB’ 无法启动分布式事务。 [OLE/DB provider returned message: 新事务不能登记到指定的事务处理器中。 ] OLE DB 错误跟踪[OLE/DB Provider ‘SQLOLEDB’ ITransactionJoin::JoinTransaction returned 0x8004d00a]。<br>估计是MSDTC的相关配置有问题。检查MSDTC配置。似乎都满足先制条件。苦思,终不得解,去网上找相关资料。终于不负有心人,找到了解决方法。继续……
解决方法:
组件服务=WIN+R调出运行框,输入dcomcnfg

组件服务–> a.展开”组件服务”树,然后展开”我的电脑”。b.右键单击”我的电脑”,然后选择”属性”。C.在 MSDTC 选项卡中‘安全配置’按钮,确保选中了下列选项:网络 DTC 访问。网络事务 XA 事务 d.另外,”DTC 登录帐户”一定要设置为”NT Authority\NetworkService”。e单击”确定”,重新启动MSDTC。
再次运行以上语句,结果成功。

xposed框架 微信群发源码_qiaoxin6666的博客-CSDN博客

mikel阅读(1316)

来源: xposed框架 微信群发源码_qiaoxin6666的博客-CSDN博客

基于xposed框架的微信二次开发模块,方便开发者用微信做一些扩展功能(如微信机器人、微信群发、多群转播(直播)等。。。)
目前支持功能:
发文本消息
发图片消息
发语音消息
获取微信好友列表
群列表
支持群发消息
[注:本模块为开发版,是用于开发者做二次开发的,所提到的功能只是提供接口,并不是安装模块就可以使用这些功能,开发者可以使用这些接口来开发相应功能的软件,如果你不是开发者,请下载提供的示例软件测试相关功能]
更多功能更新中。。。

微信模块 http://repo.xposed.info/module/com.easy.wtool

微信机器人demo下载地址:https://github.com/weechatfly/wtoolsdkrobot/raw/master/%E5%BE%AE%E4%BF%A1%E6%9C%BA%E5%99%A8%E4%BA%BA-wtoolsdkDemo.apk

加QQ群:153770839 讨论群
————————————————
版权声明:本文为CSDN博主「qiaoxin6666」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qiaoxin6666/article/details/55211861

Xposed框架的使用--简单入门_xingkong_hdc的博客-CSDN博客

mikel阅读(1341)

来源: Xposed框架的使用–简单入门_xingkong_hdc的博客-CSDN博客

##Xposed框架的使用

###1.xposed是什么?
框架是一款开源框架,其功能是可以在不修改APK的情况下影响程序运行(修改系统)的框架服务,基于它可以制作出许多功能强大的模块,且在功能不冲突的情况下同时运作。Xposed 就好比是 Google 模块化手机的主体,只是一个框架的存在,在添加其他功能模块(Modules)之前,发挥不了什么作用,但是没了它也不行。也正因为如此,Xposed 具有比较高的可定制化程度。Moto X 用户可定制手机的外观、壁纸、开机动画等,Xposed 则允许用户自选模块对手机功能进行自定义扩充。

###2.xposed框架的使用
xposed的主要作用是hook应用方法,动态劫持方法的运行。xposed的使用需要xposed框架.apk和XposedBridgeApi-54.jar配合使用 。而xposed框架apk可以看成是个Xposed 框架的管理工具,在这里可以安装、更新Xposed 框架,也可以卸载Xposed 框架,查看安装日志。也可设置Xposed 框架安装器是否联网更新框架及模块。
xposed框架.apk需要root权限,所以需要一台root后的手机或者模拟器,这里推荐使用模拟器即可。首先下载一个模拟器,常用的模拟器有bluestacks蓝叠,夜神模拟器,genemotion等,由于genemotion的cpu框架是x86的,很多应用无法运行,所以选择了夜神模拟器。下载地址:https://www.yeshen.com/。下载安装之后如下界面:

模拟器是自带root权限的,下面下载xposed框架.apk
链接: https://pan.baidu.com/s/1QjQ2CG1Br2SUoGbgHoRfTA 密码: imt4!

下载完成后直接拖到模拟器上安装,安装之后打开apk,点击框架,进去点击安装更新:

授予root权限,然后重启模拟器。xposed框架apk可以工作了,但是现在只是安装好了xposed框架apk,并没有任何hook模块工作。hook模块可以自己去下载,也可以自己编写模块。
###3.xposed模块的编写
下面通过简单的demo演示如何编写一个xposed模块,来hook住我们想要hook的方法。这里就hook自己的app的一个加载广告的代码,来动态拦截广告的加载。

1.使用AS新建一个项目XposedDemo:

2.在MainActivity 模拟加载广告的代码,正常点击加载广告按钮,会加载广告,但是使用xposed对该方法进行hook之后,可以改变这个方法的执行。:

public class MainActivity extends AppCompatActivity {

private TextView tv_ad;
private Button btn_load_ad;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_ad = findViewById(R.id.tv_ad);
btn_load_ad = findViewById(R.id.btn_load_ad);

btn_load_ad.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
tv_ad.setText(getTTAd());
}
});
}

public String getTTAd(){
return “广告加载成功”;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
在MainActivity 的布局文件如下:

<?xml version=”1.0″ encoding=”utf-8″?>
<Android.support.constraint.ConstraintLayout xmlns:Android=”http://schemas.Android.com/apk/res/android”
xmlns:app=”http://schemas.android.com/apk/res-auto”
xmlns:tools=”http://schemas.android.com/tools”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
tools:context=”.MainActivity”>

<TextView
android:id=”@+id/tv_ad”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”等待广告加载”
app:layout_constraintBottom_toBottomOf=”parent”
app:layout_constraintLeft_toLeftOf=”parent”
app:layout_constraintRight_toRightOf=”parent”
app:layout_constraintTop_toTopOf=”parent” />

<Button
android:id=”@+id/btn_load_ad”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:text=”加载广告”/>
</android.support.constraint.ConstraintLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2.下载XposedBridgeApi-54.jar的依赖包,地址
https://pan.baidu.com/s/1HfDapCF0rLxKa_HIZ9orew
密码:iy2p 下载完成后在app目录下创建文件夹mylib(名字随意),并把XposedBridgeApi-54.jar复制到mylib下面,注意不能直接放到lib里面,然后再app的build.gradle的dependencies加上provided fileTree(dir: ‘mylib’, include: [’.jar’]),或者compileOnly fileTree(dir: ‘mylib’, include: [’.jar’]),根据你的gradle版本选择。

dependencies {
implementation fileTree(dir: ‘libs’, include: [‘*.jar’])
implementation ‘com.android.support:appcompat-v7:27.1.1’
implementation ‘com.android.support.constraint:constraint-layout:1.1.3’
testImplementation ‘junit:junit:4.12’
androidTestImplementation ‘com.android.support.test:runner:1.0.2’
androidTestImplementation ‘com.android.support.test.espresso:espresso-core:3.0.2’

compileOnly fileTree(dir: ‘mylib’, include: [‘*.jar’])

}
1
2
3
4
5
6
7
8
9
10
11
3.修改AndroidManifest.xml文件,在applicatio标签下面加入以下标签:

<application
android:allowBackup=”true”
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”
android:roundIcon=”@mipmap/ic_launcher_round”
android:supportsRtl=”true”
android:theme=”@style/AppTheme”>
<activity android:name=”.MainActivity”>
<intent-filter>
<action android:name=”android.intent.action.MAIN” />

<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
</activity>

<meta-data
android:name=”xposedmodule”
android:value=”true”/>
<meta-data
android:name=”xposeddescription”
android:value=”这是一个xposed应用”/>
<meta-data
android:name=”xposedminversion”
android:value=”54″/>
</application>
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
注意这里面的三个meta-data标签的name不能错误,不然xposed框架apk无法识别自定义编写的xposed模块。

4.编写hook工具类XposedHookUtil对getTTAd方法进行拦截替换,XposedHookUtil实现IXposedHookLoadPackage接口,复写handleLoadPackage方法,并替换原有的getTTAd方法,来进行拦截。

public class XposedHookUtil implements IXposedHookLoadPackage {
String class_name = “com.hdc.xposeddemo.MainActivity”;
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {

Class clazz = loadPackageParam.classLoader.loadClass(class_name);
XposedHelpers.findAndHookMethod(clazz, “getTTAd”, new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable {
return “广告被拦截了”;
}
});
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
5.在main文件夹下创建文件夹assets,并在assets下面创建xposed_init文本文件,注意这里文件名必须是xposed_init。并在xposed_init里面添加hook工具类的完整包名路径:com.hdc.xposeddemo.xposed.XposedHookUtil

6.运行apk
点击run app按钮运行apk,如果android studio 没有找到夜神模拟器,可能是模拟器还没有关联起来。关联方法:cmd 进入命令窗口,执行
cd C:\Program Files\Nox\bin,注意cd到你的夜神模拟器的安装路径,之后执行:nox_adb.exe connect 127.0.0.1:62001
然后AS可以关联成功,运行apk。

点击加载广告,这时候显示广告加载成功,因为还有使这个xposed模块工作。

7.安装xposed模块
打开Xposed框架apk,

点击模块,看到里面有刚刚编写的模块,勾选之后重启:

重启之后,xposed模块生效,点击加载广告,显示广告被拦截了:

###总结
总的来说此demo也借鉴了许多前人的经验,至此xposed框架的基本使用已经完成。如有错误,欢迎指正。
————————————————
版权声明:本文为CSDN博主「大神温酒煮咖啡」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xingkong_hdc/article/details/82531505

重磅!VirtualXposed,让你无需Root也能使用Xposed框架! - Xposed框架中文站

mikel阅读(2468)

来源: 重磅!VirtualXposed,让你无需Root也能使用Xposed框架! – Xposed框架中文站

VirtualXposed 是基于VirtualApp 和 epic 在非ROOT环境下运行Xposed模块的实现(支持5.0~8.1)。一直以来Xposed框架最大的入门难度就在于设备需要Root,然后还要Recovery,还有承担变砖的各种搞基风险,现在这一切都不用再担心了!感谢Xposed作者rov89,感谢VirtualApp作者asLody@github!目前来看VirtualXposed的稳定性已经相当出色了!

v 0.17.3版本更新内容:

  • 修复Google服务无法在VirtualXposed中使用的问题
  • 修复相同Apps启动失败的问题
  • 适配 Android Q

VirtualXposed v0.17.3版本直接下载:

重磅!VirtualXposed,让你无需Root也能使用Xposed框架!-Xposed框架中文站此处内容已经被作者隐藏,请输入验证码查看内容

验证码:

请关注本站微信公众号,回复“验证码”,获取验证码。在微信里搜索“Appfound”或者“Manyapps”或者微信扫描右侧二维码都可以关注本站微信公众号。

v0.9.9版本更新内容:
Xposed底层调整,提升对插件的兼容性。
保守模式默认开启,如果你使用没问题;需要主动前往高级设置关闭。
新增常用模块的功能,推荐一些VXP使用稳定的模块。
QQ群/微信群反馈隐藏,请使用邮件反馈。
修复若干Xposed相关的Bug。

v0.9.8版本更新该内容:
提高 Android 7.0/7.1系统上的稳定性。
解决某些应用界面跳转时闪动的问题。
兼容全面屏(支持18:9屏幕)
新增“保守模式”(设置-高级设置)

v0.9.0版本修复三星8.0上无法使用的问题。改善后台接受消息的问题。修复闪退,提升稳定性。特别对于三星设备应该有不少体验的提升。

重磅!VirtualXposed,让你无需Root也能使用Xposed框架!-Xposed框架中文站

VirtualXposed

源码地址:https://github.com/Android-hacker/VirtualXposed,欢迎大家去围观,去贡献!

敬告大家,VirtualXposed还处于初始的阶段,如果不能够像原生的Xposed那般好使也请多多包容。Xposed本身就挑机器,挑ROM,VirtualXposed可能更是如此,但好处是即便是无法工作也不会造成变砖之类的,所以能用就用吧,不能用就卸载了!放平心态,祝大家搞基愉快。

VirtualXposed使用准备

首先在 VirtualXposed发布页面 下载最新的VAExposed安装包安装到手机。

VirtualXposed安装模块

打开 VirtualXposed,在里面安装要使用的APP,以及相应的Xposed模块即可。

注意:所有的工作(安装Xposed模块,安装APP)必须在 VirtualXposed中进行,否则Xposed模块不会有任何作用!比如,将微信直接安装在系统上(而非VirtualXposed中),防撤回安装在VirtualXposed中;或者把微信安装在VirtualXposed上,防撤回插件直接安装在系统上;或者两者都直接安装在系统上,均不会起任何作用。

重磅!VirtualXposed,让你无需Root也能使用Xposed框架!-Xposed框架中文站

VirtualXposed

在VirtualXposed中安装App的方式

直接复制已经在系统中安装好的APP,比如如果你系统中装了微信,那么可以直接复制一份。
通过外置存储直接安装APK文件;点主界面的➕,然后选择后面两个TAB即可。
在VirtualXposed中安装Xposed模块,可以跟安装正常的APK一样,以上两种安装App的方式也适用于安装Xposed模块。不过,你也可以通过VirtualXposed中内置的XposedInstaller来安装和管理模块,跟通常的XposedInstaller使用方式一样;去下载页面,下载安装即可。

VirtualXposed框架已经支持的模块

微X模块
微信巫师
MDWechat
应用变量
冲顶助手
情迁抢红包
步数修改器
指纹支付

VirtualXposed已知问题

由于暂不支持资源HOOK,因此资源钩子不会起任何作用;使用资源HOOK的模块,相应的功能不会生效。部分插件的兼容性有问题,比如QX模块。

支持和加入
目前VirtualXposed 还不完善,如果你对非ROOT下实现Xposed感兴趣;欢迎加入!你可以通过如下方式来支持:

直接贡献代码,提供Feature,修复BUG!
使用你拥有的手机,安装你常用的Xposed模块,反馈不可用情况;协助帮忙解决兼容性问题!
提出体验上,功能上的建议,帮助完善VirtualXposed!

下载地址

  • 名称:VirtualXposed框架
  • 格式:.apk
  • 版本:0.16.1
  • 大小:7.15M
  • 点击下载

基于动态代理的WebAPI/RPC/webSocket框架,一套接口定义,多个通讯方式 - hubro - 博客园

mikel阅读(1260)

来源: 基于动态代理的WebAPI/RPC/webSocket框架,一套接口定义,多个通讯方式 – hubro – 博客园

API/RPC/webSocket三个看起来好像没啥相同的地方,在开发时,服务端,客户端实现代码也大不一样

最近整理了一下,通过动态代理的形式,整合了这些开发,都通过统一的接口约束,服务端实现和客户端调用

基于这样的形式,WebAPI/RPC/webSocket只需要定义一套接口,就能达到通用的效果

 

示例接口约束

复制代码
    public class TestObj
    {
        public string Name { get; set; }
    }
    public interface ITestService
    {
        void Login();
        bool Test1(int a,int? b,out string error);
        TestObj Test2(TestObj obj);
    }
    public class TestService : AbsService, ITestService
    {
        [LoginPoint]
        public void Login()
        {
            SaveSession("hubro", "7777777777", "test");
        }

        public bool Test1(int a, int? b, out string error)
        {
            var user = CurrentUserName;
            var tag = CurrentUserTag;

            error = "out error";
            Console.WriteLine(a);
            Console.WriteLine(b);
            return true;
        }

        public TestObj Test2(TestObj obj)
        {
            Console.WriteLine(obj.ToJson());
            return obj;
        }
    }
复制代码

上面是一个标准接口和接口实现,并继承了AbsService,

Login方法标注了LoginPoint特性,表示登录切入点,调用此接口时首先得调用此方法,方法内SaveSession存储登录状态

Test1方法内使用了CurrentUserName,以获取登录方法保存的Session

特点:

  • 通过接口约束服务端和客户端调用,参数定义透明化,out也支持
  • 真正的方法远程调用,任意参数类型,个数
  • 集成登录认证逻辑,自定义登录认证过程,也可以自定义Session实现
  • 集成简单数据签名,不用费心数据安全问题

技术实现:

  • Dynamitey实现接口类型代理
  • DotNetty实现TCP通讯
  • 对象二进制序列化

RPC调用

服务端

复制代码
var server = new ServerCreater().CreatetRPC(805);
            server.CheckSign();
            server.SetSessionManage(new SessionManage());
            server.Register<ITestService, TestService>();
            server.Start();
复制代码

CreateRPC是一个扩展方法,引用CRL.RPC获取

SetSessionManage,自定义Session存储,默认是内存里

CheckSign 处理请求时,进行参数签名验证

客户端接口调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var clientConnect = new RPCClientConnect("127.0.0.1", 805);
            clientConnect.UseSign();
            var service = clientConnect.GetClient<ITestService>();
        label1:
            service.Login();
            Console.WriteLine("loginOk");
            int? a = 1;
            string error;
            service.Test1(1, a, out error);
            Console.WriteLine("error:" + error);
            var obj2 = service.Test2(new TestObj() { Name = "test" });
            Console.WriteLine("obj2:" + obj2.ToJson());
            Console.ReadLine();
            goto label1;

 

客户端先调用login方法进行登录,并记录服务端返回的token

Test1方法将token回传给服务器以验证登录状态,并进行远程调用

当调用了UseSign方法,就会对提交的参数进行签名,签名KEY为登录后服务端返回的TOKEN,服务端同样按此对签名进行比较

 

动态webApi

同样基于上文结构,接口定义就不粘贴了

服务端定义

1
2
3
4
5
6
var server = new ServerCreater().CreatetApi();
            server.CheckSign();
            server.SetSessionManage(new SessionManage());
            server.Register<ITestService, TestService>();
            var listener = new ServerListener();
            //listener.Start("http://localhost:809/");//自定义监听

 

如果宿主是.NET网站 在web.config增加处理module

1
2
3
4
5
<system.webServer>
    <modules>
      <add name="DynamicModule" type="CRL.DynamicWebApi.DynamicModule" />
    </modules>
  </system.webServer>

 

如果是单独程序,启动ServerListener即可

客户端调用

复制代码
var clientConnect = new CRL.DynamicWebApi.ApiClientConnect("http://localhost:53065");
            //var clientConnect = new CRL.DynamicWebApi.ApiClientConnect("http://localhost:8022");
            clientConnect.UseSign();
            var service = clientConnect.GetClient<ITestService>();
        
        label1:
            service.Login();
            Console.WriteLine("loginOk");
            int? a = 1;
            string error;
            service.Test1(1, a, out error);
            Console.WriteLine("error:" + error);
            var obj2 = service.Test2(new TestObj() { Name = "test" });
            Console.WriteLine("obj2:" + obj2.ToJson());
            Console.ReadLine();
            goto label1;
复制代码

 

WebSocket

WebSocket是一个比较特殊的方式,常用来做双工通讯的方式,客户端能往服务端发送数,服务端也能往客户端发送据

除去服务端往客户端发数据,也可以采用接口调用的形式实现,在这里,服务端往客户端发送数据,客户端采用了订阅的方式

服务端实现

1
2
3
4
5
6
7
8
9
10
11
12
var server = new ServerCreater().CreatetWebSocket(8015);
            server.CheckSign();
            server.SetSessionManage(new SessionManage());
            server.Register<ITestService, TestService>();
            server.Start();
            new CRL.Core.ThreadWork().Start("send", () =>
            {
                var socket = server.GetServer() as CRL.WebSocket.WebSocketServer;
                socket.SendMessage("hubro"new socketMsg() { name = DateTime.Now.ToString() }, out string error);
                Console.WriteLine("send msg");
                return true;
            }, 10);

 

上面演示代码,服务端开启了一个线程,定时往客户端”hubro”发送数据 socket.SendMessage

客户端实现

复制代码
var clientConnect = new CRL.WebSocket.WebSocketClientConnect("127.0.0.1", 8015);
            clientConnect.UseSign();
            clientConnect.SubscribeMessage<socketMsg>((obj) =>
            {
                Console.WriteLine("OnMessage:" + obj.ToJson());
            });
            clientConnect.StartPing();
            var service = clientConnect.GetClient<ITestService>();
        label1:

            service.Login();
            Console.WriteLine("loginOk");
            int? a = 1;
            string error;
            service.Test1(1, a, out error);
            Console.WriteLine("error:" + error);
            var obj2 = service.Test2(new TestObj() { Name = "test" });
            Console.WriteLine("obj2:" + obj2.ToJson());
            Console.ReadLine();
            goto label1;
复制代码

clientConnect.SubscribeMessage就是订阅消息了,通过订阅的方式,处理服务端发送的数据

 

可以看到以上各种形式,服务端实现和客户端调用基本相同,定义的接口能重复使用,做接口通讯效果杠杠的

具体实现方式和细节功能参见源码和demo,已经开源,请自行下载

源码地址:

CRL:

https://github.com/hubro-xx/CRL5

RCP:

https://github.com/hubro-xx/CRL5/tree/master/RPC

WebAPI:

https://github.com/hubro-xx/CRL5/tree/master/DynamicWebApi

WebSocket:

https://github.com/hubro-xx/CRL5/tree/master/WebSocket