背景
因为我们的用户喜欢通过微信群讨论来反馈产品问题,这无疑会对日常在线问题处理的效率产生很大的影响。曾有人尝试引导用户习惯填写在线方法,但均以失败告终。没办法,只能看看微信群监控机器人是否可行。
在之前的公司,我用python拿到了一个通过itchat广播BI数据的机器人,但是因为itcaht使用的是微信web协议,所以微信监控很严格,很多账号都用不了。,非常不稳定。所以,这一次肯定是无法通过web协议获取的。于是带着一点期待,我找到了微信,一个支持微信ipad协议的SDK。
微信官方定义:微信是一个开源的个人账号微信机器人界面,使用Typescript构建的Node.js应用。支持多种微信接入方案,包括网页、ipad、ios、windows、android等。同时支持Linux、Windows、Darwin(OSX/Mac)、Docker多平台。
这里必须提一下,Token是微信开源项目设计并支持的一种认证技术。是句子互动公司基于微信Puppet实现插件的云服务API授权账号。这也意味着,在使用微信开发基于 ipad 协议的机器人之前,您必须获得一个可用的代币。您可以从微信社区申请一个 15 天的有效试用令牌。试用期结束后微信群积分统计机器人,您可以选择付费(200RMB/月)或尝试获取长期免费代币,如下:Wechaty Token申请及使用文档及常见问题
微信目前支持Java、Python、Go、PHP等语言,但SDK原生使用TypeScript编写,github上大量的demo和开源项目都是用node.js编写的,加上微信声称可以实现一个机器人有 6 行代码微信群积分统计机器人,所以我最终决定用我之前的 JavaScript 前端开发经验来拥抱 node.js!
参考:
经过短暂的学习和实验,我发现几乎所有微信机器人常用的功能都可以直接从这些开源项目中获取,然后可以根据自己的需要进行修改。开发确实很方便。
在开发之前,首先要明确本次的功能需求:
项目github地址:
一、项目结构
|-- src/
|---- index.js # 入口文件
|---- config.js # 配置文件
|---- onScan.js # 机器人需要扫描二维码时监听回调
|---- onRoomJoin.js # 进入房间监听回调
|---- onMessage.js # 消息监听回调
|---- onFriendShip.js # 好友添加监听回调
|---- onDatabaseOperation.js # MySQL数据库操作回调
|---- onEnterpriseWechatBot.js # 企业微信群消息发送回调
|---- onFileIO.js # 文件读取回调
|-- package.json
二、核心包:三、下面介绍几个核心代码文件
1、配置文件(src/config.js):
module.exports = {
// puppet_padplus Token
token: "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_key
webhook_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") // 配置文件
//初始化bot
const 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)
当机器人下线时,很多开源项目都会生成二维码到程序日志中进行扫描。但是,一般来说,当机器人放在服务器上时,扫描二维码会很不方便。因此,结合企业微信群机器人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_key
module.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
}}}