阅读 171

Node.js结合wechaty实现个人微信机器人助手

需求

自己一直想做一个个人的微信机器人,曾经目睹一位大佬用自己个人微信实现语音控制机器人搭建后台系统,羡慕不已的同时,自己也暗自下定决心做一个自己的微信机器人,发现wechaty的时候,似乎看到了希望,并不是超越谁而是实现目前所可以做到的。

搭建自己的微信公众号,jd转链接,图文翻译,智能对话,个人收藏夹目前就是这几个,可是我发现对于小程序是不可以发送到自己的订阅号的,还有就是jd链接我每次都会重复的打开微信公众号,找到自己订阅号后发送似乎繁琐,所以我决定自己搞一个个人机器人解决以上的痛点。

惊喜!!!放在前面

在我发布文章这一天,下午打开wechaty博客的时候发现

重磅:绕过登录限制,wechaty免费版web协议重新荣光 这篇文章,可以完美实现无需token即可实现微信登录

效果及代码 你的微信会在桌面微信登录,完美绕过不可以web登录微信的痛点,也可以正常使用你的其他功能

const { Wechaty } = require("wechaty");
const Qrterminal = require("qrcode-terminal");

const name = "wechat-puppet-wechat";
let bot = "";
bot = new Wechaty({
  name, // generate xxxx.memory-card.json and save login data for the next login
});

//  二维码生成
function onScan(qrcode, status) {
  Qrterminal.generate(qrcode); // 在console端显示二维码
  const qrcodeImageUrl = [
    "https://wechaty.js.org/qrcode/",
    encodeURIComponent(qrcode),
  ].join("");
  console.log(qrcodeImageUrl);
}

// 登录
async function onLogin(user) {
  console.log(`贴心小助理${user}登录了`);
  if (config.AUTOREPLY) {
    console.log(`已开启机器人自动聊天模式`);
  }
}

//登出
function onLogout(user) {
  console.log(`小助手${user} 已经登出`);
}

bot.on("scan", onScan);
bot.on("login", onLogin);
bot.on("logout", onLogout);
bot.on("message", function(message){
    console.log(message);
}) // 消息监听
bot
  .start()
  .then(() => console.log("开始登陆微信"))
  .catch((e) => console.error(e));

复制代码

微信图片_20210414142032.png

介绍

Wechaty

Wechaty是一款适用于Chatbot Makers的现代会话 RPA SDK,可以用几行代码创建一个bot。

Wechaty提供了开箱即用的支持,可以将您的IM帐户转变为聊天机器人,从而为您提供期望的通用功能,开发人员可以轻松地对其进行自定义和扩展,以创建满足其确切需求的聊天机器人。

世界上最短的聊天机器人

我们可以使用Wechaty用最少6行代码构建一个聊天机器人

const { Wechaty } = require('wechaty')

async function main () {
  conswt bot = new Wechaty()
  bot
    .on('scan', (qrcode, status) => console.log(`Scan QR Code to login: ${status}\nhttps://wechaty.js.org/qrcode/${encodeURIComponent(qrcode)}`))
    .on('login',            user => console.log(`User ${user} logged in`))
    .on('message',       message => console.log(`Message: ${message}`))
  await bot.start()
}

main()
  .catch(console.error)
复制代码

运行代码之前

准备好可运行Wechaty的微信号,已经申请前缀为puppet_padlocal的token

  • wechaty-puppet-padlocal:wechaty的ipad协议实现

(每个人申请的token是不同的所以使用的协议也不同,需要在入口做处理)

目前实现功能

  • 自动通过好友验证
    • 当有人添加机器人时,判断验证消息关键字后通过或直接通过
    • 通过验证后自动回复并介绍机器人功能
  • 私聊关键字回复
    • 例如回复 加群 推送群聊邀请
    • 例如回复 群聊名称 自动拉群
  • 自动聊天
    • 群聊中配置和自己的机器人聊天
    • 私聊发送消息即可聊天
  • 解析小程序信息
  • 加入群聊自动欢迎
    • 当新的小伙伴加入群聊后自动 @[新的小伙伴] 发一个文字欢迎
    • 关键字触发,发送个人卡片链接
    • 群内发送小程序可获取小程序相关信息
    • 群内发送英文开启翻译功能,最多不可超多2000字

api接口

有道翻译 百度翻译

天行api

核心代码

入口文件

bot.js

const { Wechaty } = require("wechaty");
const { PuppetPadlocal } = require("wechaty-puppet-padlocal");  // 引入协议包
const QrcodeTerminal = require("qrcode-terminal");  // 控制台二维码
const onMessage = require("./onMessage"); // 消息监听回调
const config = require("./config"); // 配置文件


// 实例化机器人
const bot = new Wechaty({
  puppet: new PuppetPadlocal({
    token: config.token,
  }),
});

// 添加监听事件
bot
  // 扫码登录
  .on("scan", (qrcode, status) => {
    console.log(
      `Scan QR Code to login: ${status}\nhttps://api.qrserver.com/v1/create-qr-code/?data=${encodeURIComponent(
        qrcode
      )}`
    );
    QrcodeTerminal.generate(qrcode);
  })
  // 登录监听
  .on("login", (user) => {
    console.log(user, "logined");
  })
  // 退出监听
  .on("logout", (user) => {
    console.log(user, "logout");
  })
  // 消息监听
  .on("message", onMessage(bot))
  .start();

复制代码

onMessage.js

监听消息事件触发,这里只判断了接收的主要类型,文字type7,小程序type9,卡片链接type14,使用群聊@的时候你可能会遇到发送不成功,你可以使用另外的一种方法如果你也遇到room.say失效,然后我在获取文本信息处理的时候加了一层判断,因为消息监听默认也会监听到你所有群聊的消息,我测试的时候就遇到机器人主动和群里的人聊天,很尴尬所以拉了几个好友组了一个测试群方便测试

 // 获取@你的群友
 let member = msg.talker();
 // 群聊@群友回复
 let response = ‘回复给群友的内容’;
 msg.room().say(response,member);
复制代码
const { UrlLink } = require("wechaty");
const request = require("request");
const urlencode = require("urlencode");
const config = require("./config");
const miniProrReply = require("../utils/miniProReply");  // 小程序参数
const translate = require("../utils/translate"); // 百度翻译
const { rainbowFart, circle ,drugInstruction} = require("../utils/txAPI");  // 天行api
const name = config.name;
const roomList = config.room.roomList;

// 消息监听回调
module.exports = (bot) => {
  return async function onMessage(msg) {
    // 判断消息来自自己,直接return
    if (msg.self()) return;
    // 消息类型判断
    switch (msg.type()) {
      case 7:
        var reg = /^[\u4e00-\u9fa5]+$/; // 文字正则
        console.log("获取到文本");
        // 回复信息是关键字 “加群”     测试成功
        if (await isAddRoom(msg)) return;
        // 回复信息是所管理的群聊名    测试成功
        if (await isRoomName(bot, msg)) return;
          // 开启机器人
          if (msg.payload.roomId) {
            // 添加判断 不是指定群聊的信息不触发  [不加判断机器人,机器人会回复任意所在群聊内容。。。]
            console.log("获取到群聊消息");
            if (msg.payload.roomId === "20856899751@chatroom") {
            // 判断群内回复内容不为文字
              if(!reg.test(msg.text())){
                translate(msg);
                return;
              } else{
                roomMessageReply(msg);
                return;
              }
            }
          }
        break;
      case 9:
        console.log("获取到小程序"); // 测试成功
        miniProrReply(msg);
        break;
      case 14:
        console.log("获取到卡片链接"); // 测试成功
        break;
      default:
        console.log("暂时不支持该类型的接收!");
        break;
    }
  };
};

/**
 * @description 回复群聊信息 处理函数
 * @param {Object} msg 消息对象
 * @return {Promise} true-是 false-不是
 */
async function roomMessageReply(msg) {
  const room = msg.room();
  const text = msg.text();
  if (await msg.mentionSelf()) {
    // 获取消息内容,拿到整个消息文本,去掉 @名字
    const sendText = msg.text().replace("@蔚蓝", "");
    // 请求机器人接口回复
    let res = await requestRobot(sendText);
    // 返回消息,并@来自人 只可以padplus调用
    // room.say(res, msg.fromId());
    // 此处替换为群内回话
    // padlocal使用 msg函数@指定人
    // 获取@你的群友 
    // let member = msg.talker();
    // 群聊@群友回复
    // msg.room().say(response,member);
    room.say(res);  
  } else {
    let content = await requestRobot(msg.text());
    room.say(content);
    return 
  }

  // 指定关键字触发
  if (/互动/.test(text)) {
    room.say("互动测试");
    return 
  } else if(/彩虹屁/.test(text)){
    let reply = await rainbowFart(params);
    room.say(reply);
    return 
  } else if(/文案/.test(text)){
    let reply = await circle(params);
    room.say(reply);
    return 
  }else if (/蔚蓝工作室/.test(text)) {
    room.say(new UrlLink(config.personal.introUrl));
    return 
  } else if(/药品/.test(text)){
    params.word = text.substring(2);
    console.log(params);
    let reply = await drugInstruction(params);
    room.say(reply);
    return 
  }
}

/**
 * @description 回复信息是关键字 “加群” 处理函数
 * @param {Object} msg 消息对象
 * @return {Promise} true-是 false-不是
 */
async function isAddRoom(msg) {
  // 关键字 加群 处理
  if (msg.text() == "加群") {
    let roomListName = Object.keys(roomList);
    let info = `${name}当前管理群聊有${roomListName.length}个,回复群聊名即可加入哦\n\n`;
    roomListName.map((v) => {
      info += "【" + v + "】" + "\n";
    });
    msg.say(info);
    return true;
  }
  return false;
}

/**
 * @description 回复信息是所管理的群聊名 处理函数
 * @param {Object} bot 实例对象
 * @param {Object} msg 消息对象
 * @return {Promise} true-是群聊 false-不是群聊
 */
async function isRoomName(bot, msg) {
  // 回复信息为管理的群聊名
  if (Object.keys(roomList).some((v) => v == msg.text())) {
    // 通过群聊id获取到该群聊实例
    const room = await bot.Room.find({ id: roomList[msg.text()] });
    // 获取当前room信息
    console.log(room);
    // 判断是否在房间中 在-提示并结束
    if (await room.has(msg.from())) {
      await msg.say("您已经在房间中了");
      return true;
    }

    // 发送群邀请
    await room.add(msg.from());
    await msg.say("已发送群邀请");
    return true;
  }
  return false;
}

/**
 * @description 机器人请求接口 处理函数
 * @param {String} info 发送文字
 * @return {Promise} 相应内容
 */
function requestRobot(info) {
  return new Promise((resolve, reject) => {
    let url = `https://open.drea.cc/bbsapi/chat/get?keyWord=${urlencode(info)}`;
    request(url, (error, response, body) => {
      if (!error && response.statusCode == 200) {
        let res = JSON.parse(body);
        if (res.isSuccess) {
          let send = res.data.reply;
          // 免费的接口,所以需要把机器人名字替换成为自己设置的机器人名字
          send = send.replace(/Smile/g, name);
          resolve(send);
        } else {
          if (res.code == 1010) {
            resolve("没事别老艾特我,我还以为爱情来了");
          } else {
            resolve("你在说什么,我听不懂");
          }
        }
      } else {
        resolve("你在说什么,我脑子有点短路诶!");
      }
    });
  });
}


复制代码

utils文件夹

主要存放的是一些onmessage的方法,api请求以及小程序通过xml-js提取参数类的,这部分相对简单一些,此处不再赘述

百度翻译 使用请到utils-translate文件替换appid以及key

天行api 使用请到src-config文件更换key保证正常使用

效果

4de6c65e83c4624b6ea90fe791cd3b1.jpg

ad0cf4c464a0ea0e2cbe74c7d6fb1fe.jpg

8e14eaa78b5a28570231048aee0060a.jpg

274fb61aa0bf250fdac31b81acc6922.jpg

常见问题

github wechaty-puppet-padplus升级说明如下: 卸载wechaty-puppet-padplus,安装wechaty-puppet-hostie 代码中启动wechaty的wechaty-puppet-padplus更换成【wechaty-puppet-hostie】

npm官网 Wechaty-Puppet-Hostie模块已重命名为wechaty-puppet-service,请改用【wechaty-puppet-service】 使用wechaty-puppet-service代替Wechaty-Puppet-Hostie

协议使用服务兼容性

免费token

padlocal 7天免费(推荐)

最后

探索token和对应协议的过程有点痛苦,不过我已经搭建好了,我会给你提供最简单的搭建个人机器人的方式,一起交流学习

项目github

文章分类
后端
文章标签