背景

因为咱们的用户都喜爱通过微信群探讨的形式进行产品问题反馈,这无疑给日常的线上问题解决的效率带来极大的影响。已经尝试对用户习惯进行线上填写形式的疏导,但最终以失败告终。无奈下看看弄一个微信群监控机器人是否可行。

在之前公司我已经用python通过itchat弄过一个群播报BI数据的机器人,但因为itcaht采纳的是微信web协定,微信监控特地严,很多号都不能应用,即便登录下来了还会常常莫名掉线,极不稳固。因而这回必定不能再通过web协定的形式来弄了。于是带着一点点期盼发现了Wechaty这个反对微信ipad协定的SDK。

Wechaty官网定义:

Wechaty是一个开源的的集体号微信机器人接口,应用Typescript构建的Node.js利用。反对多种微信接入计划,包含网页,ipad,ios,windows,android 等。同时反对 Linux, Windows, Darwin(OSX/Mac) 和 Docker 多个平台。

这里必须要重点提一下,Token 是 Wechaty 凋谢源代码我的项目中所设计和反对的一种认证技术,是句子互动公司基于 Wechaty 的 Puppet 实现插件对云服务 API 的受权账号。这也就意味着你在应用Wechaty开发基于ipad协定的机器人之前必须要先拿到可用的token。你能够从Wechaty社区申请到一个15天无效的试用Token,过了试用期后能够抉择付费购买(200RMB/月)或者依照如下介绍尝试获取长期收费的Token:Wechaty Token 申请及应用文档和常见问题

Wechaty目前曾经反对了JavaPythonGoPHP等多种语言,然而该SDK原生是用TypeScript编写的,并且github上大量的demo和开源我的项目都是用node.js写的,再加上Wechaty声称能够通过6行代码就能够实现一个机器人,于是最终决定用之前一点稚嫩的JavaScript前端开发教训拥抱node.js吧!

参考资料:

  • 官网 API文档
  • 官网demo:wechaty-getting-started
  • wechaty-puppet-padplus 示例 。
  • Wechaty社区 开源我的项目

通过短时间的学习和尝试后,发现根本微信机器人罕用的性能实现简直都能从这些开源的我的项目中间接拿到,而后再联合本人的需要再进行改装就能够了,的确开发起来挺不便的。

开发之前,首先要明确一下此次的性能需要:

  • 主动聊天:群聊中通过 @[机器人]xxx, 机器人回复问题反馈模版信息 (已实现)
  • 退出群聊主动欢送:当新的小伙伴退出群聊后主动 @[新的小伙伴] 发一个文字欢送 (已实现)
  • 推送机器人登陆二维码到企业微信:机器人掉线后,主动将二维码信息推送给指定企业微信群(已实现)
  • 监控群聊信息:实时将聊天记录入库 (已实现)
  • 自动识别问题反馈信息:自动识别判断群聊中问题反馈类信息,并收纳入问题库 (开发中)
  • 群播报功:每天上班前播报问题收纳和未敞开问题状况 (未开始)

我的项目github地址: https://github.com/tomallv/wechat-group-chat-monitoring-robot

一、我的项目构造

|-- src/  |---- index.js                   # 入口文件  |---- config.js                  # 配置文件  |---- onScan.js                  # 机器人须要扫描二维码时监听回调  |---- onRoomJoin.js              # 进入房间监听回调  |---- onMessage.js               # 音讯监听回调  |---- onFriendShip.js            # 好友增加监听回调  |---- onDatabaseOperation.js     # MySQL数据库操作回调  |---- onEnterpriseWechatBot.js   # 企业微信群音讯发送回调  |---- onFileIO.js                # 文件读取回调  |-- package.json

二、外围包:

  • Wechaty外围包npm install --save wechaty
  • padplus协定包npm install --save wechaty-puppet-padplus
  • 生成二维码npm install --save qrcode-terminal

三、接下来介绍几个外围代码文件

1、配置文件src/config.js):

module.exports = {// puppet_padplus Tokentoken: "xxxxxxxxxx",// 机器人名字name: "xxxxxxxxxx",// 房间/群聊room: {     // 退出房间回复     roomJoinReply: `\n您好,欢迎您的退出,请自觉遵守群规定,文化交换! ????\n\n如您须要反馈问题,请依照如下模版进行拷贝填写,谢谢:\n问题反馈\n[1-问题形容]:\n[2-截图信息]:\n[3-账号信息]: \n[4-操作系统]:\n[5-浏览器]:\n[6-屏幕分辨率]:\n[7-挪动设施型号(APP、小程序相干问题)]:\n[8-App、小程序版本信息(APP、小程序相干问题)]:\n[9-模块信息]: A-官网前台、B-小程序、C-APP、D-句芒后盾、D-学习核心、F-CRM、G-H5网页、H-老后盾、I-其余`},// 私人personal: {     // 好友验证主动通过关键字     addFriendKeywords: ["xxxxxx", "xxxxxxx"],     // 是否开启加群     addRoom: false},// mysql数据库配置信息mysql_db: {    host: 'xxx.xxx.xxx.xxxx',    port: '3306',    user: 'xxxxxxxxxx',    password: 'xxxxxxx',    database: 'xxxxxxx',    charset : 'xxxxxxx'},// 要推送机器人二维码登陆信息的切页微信群webhook_keywebhook_key: "xxxxxxxxxxxxxxxxxxxxxxxxxxx",// 机器人登陆二维码图片文件名称qrcode_png: "xxxxxxx.png"}

2、入口文件( src/index.js):

const { Wechaty } = require("wechaty") // Wechaty外围包  const { PuppetPadplus } = require("wechaty-puppet-padplus") // padplus协定包  const config = require("./config") // 配置文件//初始化botconst bot = new Wechaty({   puppet: new PuppetPadplus({   token: config.token   }),   name: config.name  })//调用,监听,启动const onScan = require("./onScan")  const onRoomJoin = require("./onRoomJoin")  const onMessage = require("./onMessage")  const onFriendShip = require("./onFriendShip")  bot   .on("scan", onScan) // 机器人须要扫描二维码时监听   .on("room-join", onRoomJoin) // 退出房间监听   .on("message", onMessage(bot)) // 音讯监听   .on("friendship", onFriendShip) // 好友增加监听   .start()

3、机器人掉线监听回调(src/onScan.js

当机器人掉线的时候,很多开源我的项目都是将二维码生成到程序log中,供扫描应用。然而个别状况当机器人放到服务器的时候,扫描二维码就会变得十分不不便,因而这里联合企业微信群机器人API实现了一旦掉线就把登陆二维码推送到企业微信群中,这样随时随地都能够进行扫描登陆操作了。同时也思考基本上机器人都是后半夜会产生掉线状况,因而这里设置了无效推送时间段,以避免烦扰失常劳动。

const Qrterminal = require("qrcode-terminal");const qrimage = require('qr-image')const fs = require('fs')const wechat_bot = require('./onEnterpriseWechatBot') // 企业微信机器人群发const config = require("./config")const path =require('path');const defpath=path.join(__dirname,'../');const qrcode_png_path = path.join(defpath,config.qrcode_png)const weboot_key = config.webhook_keymodule.exports = function onScan(qrcode, status) {    Qrterminal.generate(qrcode, { small: true })    const myDate = new Date()    const current_hour = myDate.getHours();    console.log("以后小时数: " + current_hour);    console.log("状态码: " + status);    // 设置早上8点到早晨24点之间才推送掉线二维码    if (current_hour >= 8 && current_hour <=23) {          let link = ""          if (status == 2){                console.log("机器人曾经下线,请从新扫描二维码登陆: " + qrcode);                const temp_qrcode = qrimage.image(qrcode, {size :6, margin: 4}) // 生成机器人登陆二维码图片                temp_qrcode.pipe(require('fs').createWriteStream(qrcode_png_path).on('finish', function() {console.log('write finished')}))                link = '机器人掉线了,请点击如下链接查看登陆二维码:\n https://wechaty.js.org/qrcode/'+ qrcode          } else if (status == 3) {                link = "已扫码,请在手机端确认登陆..."          } else if (status == 4) {                link = "已确认,登陆胜利!"          } else if (status == 5) {                link = "二维码已过期!"          } else {               link = '机器人掉线了,请点击如下链接查看登陆二维码:\n https://wechaty.js.org/qrcode/'+ qrcode          }          wechat_bot.send_text(link,weboot_key)}}

性能实现截图:

4、音讯监听回调(src/onMessage.js

次要实现对群音讯进行监听,将监听到聊天音讯写入mysql中。

const { Message } = require("wechaty")const config = require("./config") // 配置文件const name = config.name // 机器人名字const mysqldb = require("./onDatabaseOperation") // 连贯MySQL数据库// 音讯监听回调module.exports = bot => {         return async function onMessage(msg) {             // 判断音讯来自本人,间接return             if (msg.self()) return             console.log("=============================")             console.log(`msg : ${msg}`)             console.log(`from: ${msg.from() ? msg.from().name() : null}: ${msg.from() ? msg.from().id : null} `)             console.log(`to: ${msg.to()}`)             console.log(`send_time: ${msg.date()}`)             console.log(`text: ${msg.text()} `)             console.log(`isRoom: ${msg.room()} : ${msg.room() ? msg.room().id : null}`)             console.log("=============================")             // 判断此音讯类型是否为群音讯             if (msg.room()) {                  const room = await msg.room() // 获取群聊                  const room_name = `${room} ` // 获取群名称                  console.log(`群名称:` + room_name.substring(5,room_name.length-2))                  const room_id = room.id // 获取群ID                  console.log(`群id :` + room_id)                  let sender_alias = await room.alias(msg.from()) //获取发信人群昵称                  console.log(`发信人的群昵称:` + sender_alias)             if (sender_alias == null){                  sender_alias = ""             }             console.log(`发信人的群昵称:` + sender_alias)             const sender_name = msg.from().name() //获取发信人微信名称             console.log(`发信人的微信名称:` + sender_name)             const msg_date = msg.date() // 获取音讯发送工夫             console.log(`音讯发送工夫: ${msg.date()}`)             const msg_type = msg.type() // 获取音讯类型             console.log(`音讯类型:` + msg_type)             var msg_content = "" // 获取音讯内容             if (msg_type == Message.Type.Text || msg_type == Message.Type.Url){                  msg_content = msg.text()             } else if (msg_type == Message.Type.Attachment){                  msg_content = "音讯内容类型为附件"             } else if (msg_type == Message.Type.Audio){                  msg_content = "音讯内容类型为音频"             } else if (msg_type == Message.Type.Contact){                  msg_content = "音讯内容类型为联系人"             } else if (msg_type == Message.Type.Emoticon){                  msg_content = "音讯内容类型为表情包"             } else if (msg_type == Message.Type.Image){                  msg_content = "音讯内容类型为图片"             } else if (msg_type == Message.Type.Video){                  msg_content = "音讯内容类型为视频"             } else {                  msg_content = "音讯内容类型为未知"             }             console.log(`音讯内容:` + msg_content)             // 音讯入库sql             var sql = "insert into wechat_room_chat_record(id,room_name,room_id,sender_name,sender_alias,msg_content,msg_type,issue_flag,msg_date) values(?,?,?,?,?,?,?,?,?)"             // 入库sql音讯变量             var sqlParams = [process.hrtime.bigint(),room_name.substring(5,room_name.length-2),room_id,sender_name,sender_alias,msg_content,msg_type,0,msg_date]             mysqldb.InsertData(sql,sqlParams)             console.log(`入库工夫戳:` + process.hrtime.bigint())             // 收到音讯,提到本人             if (await msg.mentionSelf()) {                   // 获取提到本人的名字                   let self = await msg.to()                   self_format = "@" + self.name()                   const self_name = self.name() //获取机器人本人的微信名称                   console.log("本人的微信名称:" + self_name)                   const self_alias = await room.alias(msg.to()) //获取机器人本人的群昵称                   console.log("本人的群昵称:" + self_alias)                   // 获取音讯内容,拿到整个音讯文本,去掉 @+名字                   let sendText = msg.text().replace(self_format, "").substring(1,)                   // 规定回复问题反馈模版                   var report_template = "如您须要反馈问题,请依照如下模版进行拷贝填写,谢谢:\n问题反馈\n[1-问题形容]:\n[2-截图信息]:\n[3-账号信息]: \n[4-操作系统]:\n[5-浏览器]:\n[6-屏幕分辨率]:\n[7-挪动设施型号(APP、小程序相干问题)]:\n[8-App、小程序版本信息(APP、小程序相干问题)]:\n[9-模块信息]: A-官网前台、B-小程序、C-APP、D-句芒后盾、D-学习核心、F-CRM、G-H5网页、H-老后盾、I-其余"                   console.log("主动回复内容:" + report_template)                   // 返回音讯,并@来自人                   var Datetemp1= new Date();                   room.say(report_template, msg.from())                   const sql = "insert into wechat_room_chat_record(id,room_name,room_id,sender_name,sender_alias,msg_content,msg_type,issue_flag,msg_date) values(?,?,?,?,?,?,?,?,?)"                   const sqlParams = [process.hrtime.bigint(),room_name.substring(5,room_name.length-2), room_id,self_name,self_alias,report_template,Message.Type.Text,0,Datetemp1]                   mysqldb.InsertData(sql,sqlParams)                   return             }         } else{         // 非群聊不做任何解决        return}}}

当在群里@机器人的时候,机器人会主动回复问题反馈的模版信息:

这里因为工夫问题,做的绝对简略。如果工夫充沛齐全能够做一个微服务,撑持机器人更好在群里与别人互动。

音讯入库示例:

这块目前只是实现了音讯入库,然而对聊天中的图片、视频和音频文件的保留的性能局部还没有整合进去,相干局部还处于本地调试过程中。后续会在github上更新此局部代码。

对于自动识别判断聊天信息是否为问题反馈点的性能局部。目前应用Python利用Jieba的分词计划联合人工前期统计的热词字典,曾经达到辨认正确率>90%,误识别率<10%的成果。这块也正处于本地测试中,稍后会把这块性能移植到node.js,并集成到该我的项目中。

至于最初两个环节,一个是将问题反馈的录入问题零碎以及每天机器人群内播报当天问题状况的性能,也会在稍后进行开发。并集成到该我的项目中。