“我正在参加「掘金·启航计划」”
概述
之前写了一篇介绍 Wechaty 实现微信聊天机器人的文章,只对如何使用 Wechaty 做了简单介绍,本文则对它的实现原理做一些简单介绍。可以点击查看演示视频。
Wechaty 经过有五、六年的开发和迭代,功能相对来说已经比较稳定,但是也存在因微信升级导致不可用的风险。截止本文写作时(22 年 9 月),Wechaty 依然是可用的,如果您对此有任何疑问,请自己动手安装试用一下。
Wechaty 是一个比较受欢迎的微信机器人工具,该项目在 npm 下载量、github stars 数量如下所示:
github stars 的趋势:
简介
Webchat 是一个构建聊天机器人的通用 SDK,需要通过不同的 Puppet Provider 来实现各种 IM 聊天。Webchat 支持的聊天工具包括 WhatsApp、WeChat、 WeCom、Gitter 以及 Lark,只需要使用相应的 Puppet Provider 即可。本文重点介绍微信(WeChat)机器人,它的 Provider 比较多, 这里只对 PuppetPuppeteer (wechaty-puppet-puppeteer) 展开介绍。
| Protocol | Puppet Provider | Environment Variable |
|---|---|---|
| Web | PuppetPuppeteer | export WECHATY_PUPPET=wechaty-puppet-puppeteer |
| Windows | PuppetWxwork | export WECHATY_PUPPET=wechaty-puppet-service |
| Mock | PuppetMock | export WECHATY_PUPPET=wechaty-puppet-mock |
| Web | PuppetWechat4u | export WECHATY_PUPPET=wechaty-puppet-wechat4u |
| iPad | PuppetRock | export WECHATY_PUPPET=wechaty-puppet-service |
| iPad | PuppetPadLocal | export WECHATY_PUPPET=wechaty-puppet-service |
| Windows | PuppetDonut | export WECHATY_PUPPET=wechaty-puppet-service |
| iPad | export WECHATY_PUPPET=wechaty-puppet-padpro | |
| iPad | export WECHATY_PUPPET=wechaty-puppet-padchat | |
| iPad | export WECHATY_PUPPET=wechaty-puppet-padplus | |
| Mac | export WECHATY_PUPPET=wechaty-puppet-macpro |
实现原理
突破网页版限制
顾名思义,PuppetPuppeteer 使用了 Puppeteer,这里使用了微信的网页版。但是我们知道,微信网页版已经被限制使用,当我们扫码登陆成功之后,会显示如下文案:
为了保障你的帐号安全,暂不支持使用网页版微信。你可以前往微信官网 weixin.qq.com/ 下载客户端登录。
目前,这个问题是通过开启 uos 协议登录来解决的,具体细节可以阅读 「免费 UOS 协议快速接入可视化配置面板」 这篇文档。具体到实现上,是通过 Puppeteer 拦截 /cgi-bin/mmwebwx-bin/webwxnewloginpage 请求,并在该请求的 Header 上添加 client-version 和 extspam 两个字段,具体源码如下所示:
const uosHeaders = {
'client-version' : UOS_PATCH_CLIENT_VERSION,
extspam : UOS_PATCH_EXTSPAM,
}
page.on('request', (req) => {
const url = new URL(req.url())
if (url.pathname === '/cgi-bin/mmwebwx-bin/webwxnewloginpage') {
const override = {
headers: {
...req.headers(),
...uosHeaders,
},
}
this.wrapAsync(req.continue(override))
}
})
这样,扫码登陆之后便可进入到聊天页面,突破了网页版的限制。使用下面的代码启动 Wechaty 机器人,可以看到效果:
const bot = WechatyBuilder.build({
name: 'wechat-bot',
puppetOptions: {
+ head: true, // 关闭无头模式
+ uos: true, // 开启 uos 协议
},
puppet: 'wechaty-puppet-wechat',
})
注入交互脚本
进入到聊天页面之后,PuppetPuppeteer 会在浏览器环境中注入脚本,通过注入的脚本来实现发送消息、创建群组等操作。实现注入脚本的代码如下:
const WECHATY_BRO_JS_FILE = path.join(
codeRoot,
'src',
'wechaty-bro.js',
)
const sourceCode = fs.readFileSync(WECHATY_BRO_JS_FILE)
.toString()
let retObj = await page.evaluate(sourceCode) as undefined | InjectResult
在端到端 (E2E) 自动化测试中,基本上都是使用脚本来模拟用户的交互行为,以操控 DOM 元素的方式,执行某些操作或流程。但是 PuppetPuppeteer 的实现方式与此完全不同,wechaty-bro.js 的主要功能是实现和微信网页版的交互,但并没有操作 DOM。
与微信网页版的交互
PuppetPuppeteer 对微信网页版有比较深入的理解,并没有通过操作 DOM 来实现发送消息、创建群组等功能。
微信网页版使用了 Angular,在微信的 JavaScript 中,对每种功能都封装成了单独的模块。比如下面的群组模块,具体见代码:
// https://res.wx.qq.com/t/wx_fed/webwx/res/static/js/index_fbe050f.js
angular.module('Services')
.factory('chatroomFactory', [
// ... ...
])
而上面的模块可以通过下面的代码获取到:
angular.element(document).injector().get('chatroomFactory');
// {
// addMember: ...,
// create: ...,
// // ...
// }
也就是说,只需在注入的脚本中,拿到微信网页版的封装的函数方法,便可以实现发送消息、创建群组等一系列功能。wechaty-bro.js 中的代码如下:
const injector = angular.element
const accountFactory = injector.get('accountFactory')
const chatroomFactory = injector.get('chatroomFactory')
const chatFactory = injector.get('chatFactory')
const contactFactory = injector.get('contactFactory')
...
WechatyBro.glue = {
accountFactory,
chatFactory,
chatroomFactory,
contactFactory,
...
}
在 Puppeteer 后台要对群组进行操作时,只需要执行以下代码即可:
this.page.evaluate(`
WechatyBro
.glue
.chatroomFactory
.addMember
.apply(
undefined,
{ ... },
)`
)
总结
以上就是 Wechaty 的 PuppetPuppeteer 实现微信机器人的一些实现方案,其中比较关键的两点:一个是 uos 登陆协议,另一个是对微信网页版中代码的使用。需要注意的是,一旦微信网页版的实现方式有调整时,聊天机器人存在失效的风险。
本文由 「KooFE前端团队」发布,搜索 ikoofe 可关注公众号。