微信聊天机器人,本质就是一个应用程序,启动之后,扫码登录你自己的微信号,就可以实时动态地接收你微信中群聊或私聊中的消息,并且可以根据消息内容进行回复。

遗憾地是微信官方并不提供这样的服务,于是开源技术社区里的同学群策群力,搞出了不少解决方案。目前看用的比较多的就是 Wechaty,只需要少量的代码就可以实现一个聊天机器人,除微信之外,还支持其它很多种聊天类的应用。

一路折腾下来之后,发现 Wechaty 的官网有点儿陈旧,给出的指南也很不是很严谨,新上手的同学很容易走弯路,这里记录一下我自己的实践过程,有兴趣的同学可以参考一下。

开发环境

Wechaty 的运行环境依赖于 Node.js v16+。不同的操作系统(或版本)安装不同版本的 Node.js 可能会遇到一些问题,为了描述方便,这里使用 Docker。

拉取镜像

docker pull centos:centos7.9.2009

启动容器

docker run -dit --name wechaty centos:centos7.9.2009 /bin/bash

进入容器

docker exec -it wechaty /bin/bash

以下操作在容器里进行。

安装工具

yum install -y wget
yum install -y sudo
yum install -y vim
yum install -y git

下载 Node.js 安装包

wget https://nodejs.org/download/release/v16.19.0/node-v16.19.0-linux-x64.tar.xz

安装 Node.js

VERSION=v16.19.0
DISTRO=linux-x64
sudo mkdir -p /usr/local/lib/nodejs
sudo tar -xJvf node-$VERSION-$DISTRO.tar.xz -C /usr/local/lib/nodejs

删除 Node.js 安装包

rm -f node-v16.19.0-linux-x64.tar.xz

配置 Node.js 环境变量

创建且编辑文件 /etc/profile.d/nodejs.sh,写入下述内容:

VERSION=v16.19.0
DISTRO=linux-x64
export PATH=/usr/local/lib/nodejs/node-$VERSION-$DISTRO/bin:$PATH

保存文件,执行命令:

source /etc/profile

测试 Node.js

执行命令,如果输出相似内容,表示 Node.js 安装且配置成功。

node -v

v16.19.0
npm version

{
  npm: '8.19.3',
  node: '16.19.0',
  v8: '9.4.146.26-node.24',
  uv: '1.43.0',
  zlib: '1.2.11',
  brotli: '1.0.9',
  ares: '1.18.1',
  modules: '93',
  nghttp2: '1.47.0',
  napi: '8',
  llhttp: '6.0.10',
  openssl: '1.1.1s+quic',
  cldr: '41.0',
  icu: '71.1',
  tz: '2022f',
  unicode: '14.0',
  ngtcp2: '0.8.1',
  nghttp3: '0.7.0'
}
npx -v

8.19.3

入门示例

创建并进入项目目录 ding-dong-bot

mkdir ding-dong-bot
cd ding-dong-bot

初始化项目

npm init -y

编辑文件 package.json

添加 "type": "module",如下:

{
  "name": "ding-dong-bot",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "type": "module"
}

创建文件 ding-dong-bot.js,复制/粘贴代码

代码可以直接从官方示例项目中获取:ding-dong-bot.js,如下:

/**
 *   Wechaty - https://github.com/wechaty/wechaty
 *
 *   @copyright 2016-now Huan LI <zixia@zixia.net>
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 */
 import 'dotenv/config.js'

 import {
  WechatyBuilder,
  ScanStatus,
  log,
}                     from 'wechaty'
import qrcodeTerminal from 'qrcode-terminal'

function onScan (qrcode, status) {
  if (status === ScanStatus.Waiting || status === ScanStatus.Timeout) {
    qrcodeTerminal.generate(qrcode, { small: true })  // show qrcode on console

    const qrcodeImageUrl = [
      'https://wechaty.js.org/qrcode/',
      encodeURIComponent(qrcode),
    ].join('')

    log.info('StarterBot', 'onScan: %s(%s) - %s', ScanStatus[status], status, qrcodeImageUrl)

  } else {
    log.info('StarterBot', 'onScan: %s(%s)', ScanStatus[status], status)
  }
}

function onLogin (user) {
  log.info('StarterBot', '%s login', user)
}

function onLogout (user) {
  log.info('StarterBot', '%s logout', user)
}

async function onMessage (msg) {
  log.info('StarterBot', msg.toString())

  if (msg.text() === 'ding') {
    await msg.say('dong')
  }
}

const bot = WechatyBuilder.build({
  name: 'ding-dong-bot',
  /**
   * How to set Wechaty Puppet Provider:
   *
   *  1. Specify a `puppet` option when instantiating Wechaty. (like `{ puppet: 'wechaty-puppet-padlocal' }`, see below)
   *  1. Set the `WECHATY_PUPPET` environment variable to the puppet NPM module name. (like `wechaty-puppet-padlocal`)
   *
   * You can use the following providers:
   *  - wechaty-puppet-wechat (no token required)
   *  - wechaty-puppet-padlocal (token required)
   *  - wechaty-puppet-service (token required, see: <https://wechaty.js.org/docs/puppet-services>)
   *  - etc. see: <https://github.com/wechaty/wechaty-puppet/wiki/Directory>
   */
  // puppet: 'wechaty-puppet-wechat',
})

bot.on('scan',    onScan)
bot.on('login',   onLogin)
bot.on('logout',  onLogout)
bot.on('message', onMessage)

bot.start()
  .then(() => log.info('StarterBot', 'Starter Bot Started.'))
  .catch(e => log.error('StarterBot', e))

保存文件。

这是 JavaScript 版本的代码示例,其它语言的参考官方文档。

安装依赖

npm install dotenv
npm install wechaty
npm install qrcode-terminal

运行

node ding-dong-bot.js

如果可以看到类似输出:

则表示运行成功。

扫码登录

打开手机微信扫码登录,看到如下信息:

10:46:24 INFO StarterBot onScan: Scanned(3)
10:46:39 INFO StarterBot Contact<渊深海阔> login
10:46:41 INFO StarterBot Message#Unknown[🗣Contact<渊深海阔>]
10:47:07 INFO StarterBot Message#Text[🗣Contact<萌猫他爸>]    ding

表示登录成功。使用另外一个微信号给自己的微信号(刚刚扫码登录的微信号)发送一个消息:ding,会收到一个回复消息:dong。

实现原理

参考上述代码中的方法 onMessage 实现:

if (msg.text() === 'ding') {
  await msg.say('dong')
}

逻辑很简单,不再赘述。

增强版示例

微信聊天机器人收到消息内容之后,可以

  • 判断消息是自己发送给自己的,还是其他人发送给自己的
  • 判断消息是否来自群聊,以及获取群聊名称
  • 判断消息是否来自私聊,以及获取发送人名称
  • 获取消息类型
  • 获取消息内容
  • 其他等等。
import { WechatyBuilder, ScanStatus, log } from "wechaty";
import qrcodeTerminal from "qrcode-terminal";

function onScan(qrcode, status) {
  if (status === ScanStatus.Waiting || status === ScanStatus.Timeout) {
    qrcodeTerminal.generate(qrcode, { small: true }); // show qrcode on console

    const qrcodeImageUrl = [
      "https://wechaty.js.org/qrcode/",
      encodeURIComponent(qrcode),
    ].join("");

    log.info(
      "ChatBot",
      "onScan: %s(%s) - %s",
      ScanStatus[status],
      status,
      qrcodeImageUrl
    );
  } else {
    log.info("ChatBot", "onScan: %s(%s)", ScanStatus[status], status);
  }
}

function onLogin(user) {
  log.info("ChatBot", "%s login", user);
}

function onLogout(user) {
  log.info("ChatBot", "%s logout", user);
}

async function onMessage(msg) {
  const self = msg.self();
  if (self) {
    // 自己发送的消息,不处理
    return;
  }

  // 消息群聊
  const room = msg.room();
  // 消息发送者
  const talker = msg.talker();
  // 消息类型
  const type = msg.type();
  // 消息文本
  let text = msg.text().trim();
  if (!text) {
    // 消息文本为空,不处理
    return;
  }

  log.info(
    "ChatBot",
    "message from room: %s, user: %s, type: %s, text: %s",
    room ? await room.topic() : "{}",
    talker ? talker.name() : "{}",
    type,
    text
  );
}

function onError(error) {
  log.info("ChatBot", "run error: %s", error);
}

const bot = WechatyBuilder.build({
  name: "ChatBot",
});

bot.on("scan", onScan);
bot.on("login", onLogin);
bot.on("logout", onLogout);
bot.on("message", onMessage);
bot.on("error", onError);

bot
  .start()
  .then(() => log.info("ChatBot", "started."))
  .catch((e) => log.error("ChatBot", "start error: %s", e));

增强版相比于入门版示例,代码整体结构没有发生太大变化,注意两个地方就可以。

error

代码行数:75,增加错误处理器,防止运行时错误导致程序异常终止。

onMessage

代码行数:33,收到消息之后,使用日志的形式输出消息的群聊名称、发送者名称、消息类型和消息文本内容。有两种特殊情况:自己发送的消息和消息文本内容为空,不会有输出。

结语

可以看到,使用 Wechaty 实现微信聊天机器人,确实很简单。我们只需要根据接收到的消息内容,实现更加贴合业务场景的消息回复即可。

results matching ""

    No results matching ""