Wechaty 实现微信聊天机器人的思路

·  阅读 1244

“我正在参加「掘金·启航计划」”

概述

之前写了一篇介绍 Wechaty 实现微信聊天机器人的文章,只对如何使用 Wechaty 做了简单介绍,本文则对它的实现原理做一些简单介绍。可以点击查看演示视频

Wechaty 经过有五、六年的开发和迭代,功能相对来说已经比较稳定,但是也存在因微信升级导致不可用的风险。截止本文写作时(22 年 9 月),Wechaty 依然是可用的,如果您对此有任何疑问,请自己动手安装试用一下。

Wechaty 是一个比较受欢迎的微信机器人工具,该项目在 npm 下载量、github stars 数量如下所示:

Downloads GitHub stars

github stars 的趋势:

Star History Chart

简介

Webchat 是一个构建聊天机器人的通用 SDK,需要通过不同的 Puppet Provider 来实现各种 IM 聊天。Webchat 支持的聊天工具包括 WhatsApp、WeChat、 WeCom、Gitter 以及 Lark,只需要使用相应的 Puppet Provider 即可。本文重点介绍微信(WeChat)机器人,它的 Provider 比较多, 这里只对 PuppetPuppeteer (wechaty-puppet-puppeteer) 展开介绍。

ProtocolPuppet ProviderEnvironment Variable
WebPuppetPuppeteerexport WECHATY_PUPPET=wechaty-puppet-puppeteer
WindowsPuppetWxworkexport WECHATY_PUPPET=wechaty-puppet-service
MockPuppetMockexport WECHATY_PUPPET=wechaty-puppet-mock
WebPuppetWechat4uexport WECHATY_PUPPET=wechaty-puppet-wechat4u
iPadPuppetRockexport WECHATY_PUPPET=wechaty-puppet-service
iPadPuppetPadLocalexport WECHATY_PUPPET=wechaty-puppet-service
WindowsPuppetDonutexport WECHATY_PUPPET=wechaty-puppet-service
iPadPuppetPadpro DEPRECATEDexport WECHATY_PUPPET=wechaty-puppet-padpro
iPadPuppetPadchat DEPRECATEDexport WECHATY_PUPPET=wechaty-puppet-padchat
iPadPuppetPadplus DEPRECATEDexport WECHATY_PUPPET=wechaty-puppet-padplus
MacPuppetMacpro DEPRECATEDexport WECHATY_PUPPET=wechaty-puppet-macpro

实现原理

突破网页版限制

顾名思义,PuppetPuppeteer 使用了 Puppeteer,这里使用了微信的网页版。但是我们知道,微信网页版已经被限制使用,当我们扫码登陆成功之后,会显示如下文案:

为了保障你的帐号安全,暂不支持使用网页版微信。你可以前往微信官网 weixin.qq.com/ 下载客户端登录。

目前,这个问题是通过开启 uos 协议登录来解决的,具体细节可以阅读 「免费 UOS 协议快速接入可视化配置面板」 这篇文档。具体到实现上,是通过 Puppeteer 拦截 /cgi-bin/mmwebwx-bin/webwxnewloginpage 请求,并在该请求的 Header 上添加 client-versionextspam 两个字段,具体源码如下所示:

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 可关注公众号。

分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改