一个微信机器人

793 阅读9分钟

背景


春节期间,看到别人开发了一个基于网页版协议的微信机器人 , 用来推送疫情相关的一些消息 , 感觉很酷 , 想着自己也做一个 , 但无奈于网页版的封号比较严重 , 于是这个想法也就搁置了

发现wechaty-puppet-padplus


想法虽然搁置了 , 但还是会搜索一些相关内容 , 后来看到说有iPad协议的微信机器人 , 搜索了几个也是差强人意 , 有的甚至需要付费来获得他们的服务 , 后来发现了Wechaty|NodeJS基于iPad协议手撸一个简单的微信机器人助手这篇

关于wechaty-puppet-padplus


wechaty-puppet-padplus不同于以往的基于微信网页版协议 , 它是基于iPad协议的

这是它的GitHub地址wechaty-puppet-padplus

使用说明:Support-Developers

我们的使命:为开发人员轻松构建微信聊天机器人。

我们使用iPad 协议为具有强烈意愿和能力为用户构建有价值的聊天机器人的开发人员提供免费访问。

任何开发者都可以添加JuziBOT公司的员工(**微信编号:botorange_yeah)**作为微信的朋友。添加后您将收到审核表单。如果你通过审查,并愿意写一个博客在Wechaty,你可以使用我们的iPad协议是免费的!

意思就是 : 添加微信后需要填写审查表 , 通过审查会给你一个为期15天的免费Token , 利用这个Token来进行开发 , 愿意参与开源激励计划 ,15天后需要提交一个MVP(最小可行化产品)的GitHub仓库 , 通过微信发送GitHub地址 , 然后他们会将其fork到Wechaty社区并且你还需要在Wechaty社区内撰写一篇博客写一下在开发过程中的心得及遇到的问题等等 , 这样就会提供长期的免费Token , 反之 , 可能需要继续协商讨论后续的合作形式

前期准备


需要在电脑上提前安装好Node , 关于Node怎么安装这里不再细讲

新建一个空文件夹

npm init -y

安装wechaty

npm install --save wechaty

安装wechaty-puppet-padplus

npm install --save wechaty-puppet-padplus

安装qrcode-terminal在终端输出二维码 , 便于扫码登录

npm install --save qrcode-terminal

这样就可以进行开发了

我的项目


项目地址

Github

使用库

目前实现的功能

  • 通过关键词自动通过好友的请求
  • 私聊关键字进行回复相关内容
    • 例如回复加群推送群聊邀请
    • 例如回复疫情获取全国当前关于疫情的一些内容
  • 自动聊天
    • 群聊中可以通过@机器人来进行聊天
    • 群聊中可以通过@机器人+疫情来获取全国疫情的一些相关内容
    • 私聊发送消息即可进行聊天
  • 加入群聊
    • 当有新的朋友加入机器人所在群聊会@新朋友并且发送一段欢迎文字
  • 踢出群聊
    • 当机器人把某个人踢出之后会发出xxx已被移出群聊的内容
  • 定时发送消息给指定联系人
    • 设置一个时间 , 指定一个联系人 , 会在指定时间发送一些内容给联系人内容有日期 , 天气 , 鸡汤文

代码说明


目录结构

|-- README.md                //项目说明
|-- config
|   |-- formatDate.js       //获取时间
|   |-- getOne.js           //一些模块 , 包含 : 每日一句 获取全国疫情 获取墨迹天气 天行机器人配置 加群关键字
|   |-- schedule.js         //定时任务的配置
|   `-- superagent.js       //superagent的配置
|-- index.js                //文件入口及一些实现功能模块
|-- package.json            //配置文件

核心代码index.js

这里面包含了初始化 , 扫码登陆 , 登出 , 消息处理 , 定时任务 , 进入群聊 , 踢出群聊的一些模块 , 接下来我们掰开了揉碎了说

首先引入包

const { Wechaty, Friendship } = require('wechaty');
const qrcodeTerminal = require('qrcode-terminal');

Friendship是验证好友请求时需要用到的

注意 : 这里并没有引入PuppetPadplus

wechaty-puppet-padplus给出的例子是

Snipaste_2020-03-14_19-48-47.png
我没有使用这种做法 , 用的是

const { Wechaty, Friendship } = require('wechaty');
const qrcodeTerminal = require('qrcode-terminal');

const token  = 'your-token'
const puppet = 'wechaty-puppet-padplus'
const name   = 'your-bot-name'

const bot = new Wechaty({
  name,
  puppet,
  puppetOptions: { token },
})
bot
  .on('scan', (qrcode, status) => {
    QrcodeTerminal.generate(qrcode, {
      small: true
    })
  })
  .on('message', msg => {
    console.log(`msg : ${msg}`)
  })
  .start()

关于这个可以查看这里

name你可以随便取 , 高兴就好

  • 二维码生成

    这个没什么好说的只是简单的一句

    function onScan(qrcode, status) {
      qrcodeTerminal.generate(qrcode, { small: true });
    }
    

    qrcode就是二维码 , status是状态 , 借助qrcodeTerminal.generate把二维码输出到终端 , small是把二维码变得小一些

  • 登录

    async function onLogin(user) {
      console.log(`小机器人${user.name()} 登陆啦!!!`);
      //const roomList = await bot.Room.findAll();
      //console.log(roomList); //输出房间列表 , 在登陆之后
      await bot.say('Hello! , Robot has login'); //跟自己发条消息
    
      //登陆后创建定时任务
      await initDay();
    }
    

    登录成功之后会在终端输出一句 小机器人XXX登陆啦!!! , 这里的user.name()就是刚才设置的name的名字

    bot.say()就是登录之后 , 自己给自己发送一条消息

    定时任务这个在后面会讲到

  • 根据关键词自动通过好友请求

    async function onFriendShip(friendship) {
      let logMsg;
      try {
        logMsg = '添加好友' + friendship.contact().name();
        console.log(logMsg);
        switch (friendship.type()) {
          /**
           * 1.新的好友请求
           * 设置请求后,我们可以从'request.hello()'获取验证消息,并
           * 通过'request.accept()'接受此请求
           */
          case Friendship.Type.Receive:
            let addFriendKeywords = ['Hello', 'robot', '你好'];
            if (addFriendKeywords.some(str => str === friendship.hello())) {
              logMsg = `自动添加好友成功,因为验证消息是"${friendship.hello()}"`;
              //通过验证
              await friendship.accept();
            } else {
              logMsg = `没有通过验证:因为"${friendship.hello()}"不匹配`;
            }
            break;
          //友谊的小船确认
          case Friendship.Type.Confirm:
            logMsg = `${friendship.contact().name()}已经上了你的贼船`;
            //跟自己发个提示XX已经加了好友
            bot.say(`${friendship.contact().name()}添加了你为好友`);
            break;
        }
      } catch (e) {
        logMsg = e.message;
      }
      console.log(logMsg);
    }
    

    friendship.contact().name()就是请求添加好友的名字

    我们以朋友关系的状态friendship.type()进行判断 , 如果他发来了一个请求Friendship.Type.Receive就好比有人跟你说我们作朋友吧 , 你是不是还要思考 , 验证一番呢?addFriendKeywords就是添加好友时填写的验证信息 , 比如有人跟你说跟他做朋友然后你的吃住他都包了 , 那你肯定跟他做朋友是吧 , 这里用了一个数组 , 如果验证消息包含里面的内容那么就会自动通过friendship.accept()就是通过验证 , Friendship.Type.Confirm就是表示已经确认了友谊 , 就是说你的吃住以后不用愁了

  • 进入群聊

    async function onRoomJoin(room, inviteeList, inviter) {
      if (roomId !== room.id) return;
      //如果bot在房间且已经设置新进入房间的人就会@并发送一条消息
      inviteeList.map(c => {
        room.say(
          '\n你好,欢迎你的加入 , 请自觉遵守群规则 , 文明交流 ,你可以通过@我进行对话 , 先介绍一下自己😄',
          c
        );
      });
    }
    

    room群聊实例 , inviteeList 受邀者 inviter邀请者

    这里使用了roomId来作为判断依据是不是机器人所管理的群聊 , 并没有用room.topic()是因为群聊名字可能会改变 , 而roomId是不变的 , 如何获取可以查看登录那里的两行注释 , 解开跑一下就行 roomId是一串数字+@chatroom , 如果群聊太多不知道哪个是哪个 , 可以看topic字段后面的就是群聊名字 , say 方法第一个参数就是要发送的消息,第二个参数就是为了@此人一下

  • 踢出房间

    async function onRoomLeave(room, leaverList) {
      if (roomId !== room.id) return;
      leaverList.map(c => {
        room.say(`「${c.name()}」被移除群聊`);
      });
    }
    

    这个只限于机器人踢出人才会触发 , 如果群聊里的人自己主动退出并不会触发 , 我尝试在有人加入时获取群聊联系人列表 , 然后在有人主动退出之后使用room.sync() , 然后再获取群聊联系人 , 发现还是那些人 , 这个可能是我使用有问题 , 还需要再研究一下

  • 监听对话

    async function onMessage(msg) {
      const contact = msg.from(); //发消息人
      const content = msg.text(); //消息内容
      const room = msg.room(); //是否是群消息
      //消息来自自己 , 直接return
      if (msg.self()) return;
    
      //只处理文本消息
      if (msg.type() === bot.Message.Type.Text) {
        //消息是否来自群聊
        if (room) {
          //如果消息来自群聊
          const topic = await room.topic(); //获取群聊名字
          //console.log(`群名: ${topic} 发消息人 :${contact.name()}内容:${content}`);
          //在群聊回复 疫情 会发送全国疫情消息
          if (content === '疫情') await msg.say(await superagent.getnCov());
          //收到消息并@了自己
          if (await msg.mentionSelf()) {
            //获取提到自己的名字
            let self = await msg.to();
            self = '@' + self.name();
            //获取消息内容 , 拿到整个消息文本 , 去掉@+名字 , 注意名字跟消息之间有空格
            let sendText = content.replace(self, '').trim();
            //如果发送 了 @疫情 , 那就会发送疫情消息 , 并return,为了不触发机器人自动对话
            if (sendText === '疫情') {
              await msg.say(await superagent.getnCov());
              return;
            }
            console.log(`${contact.name()}说:`, sendText);
            //请求机器人回复
            let res = await superagent.getReply(sendText);
            console.log('天行机器人回复:', res);
            //返回消息 , 并@来自人
            room.say(res, msg.from());
            return;
          }
        } else {
          //如果不是群消息 , 是 个人一对一
          //发送消息是 加群 不触发机器人
          if (await superagent.addRoom(this, msg)) return;
          if (content === '疫情') {
            await msg.say(await superagent.getnCov());
            return;
          }
          //请求机器人聊天
          console.log(`${contact.name()}:`, content);
          let res = await superagent.getReply(content);
          console.log('天行机器人:', res);
          //返回内容
          try {
            await contact.say(res);
          } catch (e) {
            console.log(e.message);
          }
        }
      } else {
        console.log('不是文本消息!');
      }
    }
    

    这个里面都有注释 , 说的也还算清楚 , 就不详细说明了 , 说一下遇到的问题

    里面有用到天行机器人 , 这个可以去天行机器人官网注册一下获得APIKEY 然后再申请需要的接口就行了

    我要说的是 , 比如跟微信机器人私聊 北京天气 , 然后再说个别的地方的天气 , 天行机器人就会返回两个不一样 , 就没有问题 , 但是在群聊@机器人 , (群聊是通过@自动对话的)就总会返回同样的天气 , 起初还以为是天行机器人是智障呢(也确实是智障) , 后来发现是在处理@的时候出现的问题 , 群聊中@人的话会在名字与消息之间存在一个空格 , 没有处理 , 就导致了这样 , 后来trim()了一下 , 就好了

    还有就是对关键字疫情的处理不是太好 , 还没有想到更好的解决办法

    if (await superagent.addRoom(this, msg)) return;这一句放在这里也是处理的不是很好 , 偶尔会失效

    如果你有好的办法 欢迎PR

  • initDay

    async function initDay() {
      console.log('已经设定每日说任务');
      //每天早上7:30发送
      schedule.setSchedule('00 30 07 * * *', async () => {
        console.log('你的贴心小助理开始工作啦');
        let logMsg;
        //发送个指定联系人或者他的备注名字
        let contact =
          (await bot.Contact.find({ name: '小梦大半' })) ||
          (await bot.Contact.find({ alias: '小梦大半' }));
        let one = await superagent.getOne(); //获取每日一句
        let weather = await superagent.getWether(); //获取天气信息
        let today = await formatDate.formatDate(new Date()); //获取今天的日期
        let str = `${today}\n元气满满的一天开始啦 , 要开心😄\n\n${weather.todayWeather}\nTips:${weather.weatherTips}\n\n每日鸡汤\n${one}`;
        try {
          logMsg = str;
          await contact.say(str); //发送消息
        } catch (e) {
          logMsg = e.message;
        }
        console.log(logMsg);
      });
    }
    

    设定一个时间点来给指的联系人发送消息 , 用到了node-schedule这个包 , 定时设置在schedule.js这个文件里面,里面也有一些说明 , 其他的都引用了getOne.js这个文件的里的内容 , 在开始登陆的时候我们就让他执行起来

其他

config里面的一些文件

getOne.js这里主要写获取每日一句 , 天行机器人 , 加群 , 疫情 和获取墨迹天气的一些操作

基本都用到了superagent这个包 , 疫情用的是这个接口 , 加群主要是根据加群关键字先判断是否在群里 , 是就发送一句话 , 不是就发送群链接

其他3个文件就是一些配置

可能遇到的问题


Windows下执行npm install --save wechaty-puppet-padplus安装失败

因为用到了其他语言实作的依赖 , 需要build tools

可以参考 github.com/wechaty/wec…

github.com/nodejs/node…

github.com/felixrieseb…

最后


做完这个还是有所收获的 , 尽管有一些代码是借鉴别人的 , 但是却是自己一个个敲得 , 没有粘贴复制 , 而且遇到不懂的也反复的参考官方文档 , 我始终相信技术改变世界 PEACE&LOVE