当我需要一个通知机器人时,我的选型过程以及最终方案。

需求

我的私人服务器有一些定时任务,任务内容是去一些站点爬取内容,当有我需要的内容时,发消息给我。这个消息很短并且有时效性,所以通知既需要保证时效性,同时对我的侵入不能太强。

针对通知,我知道的方案有邮箱、微信机器人、企业通讯工具机器人(企业微信、钉钉),经朋友提醒,还有 Server 酱,如果还有其他的方案,欢迎反馈,下面针对当前的需求分析这几种方案。

方案 实现方式 限制 通知形式 时效性 通知侵入
邮件 nodemailer 邮箱 邮件
企业微信机器人 webhook 需要一个公司 & 群 企业微信消息 一般
钉钉机器人 webhook 钉钉消息 一般
telegram Telegram Bot 国内某些区域需要科学上网 telegram 消息 网络问题导致较弱的延时 一般
微信机器人 wechaty 等微信协议 新注册微信无法使用 web 协议
非 web 协议实现成本较高且要求实体机
微信消息
Server 酱 服务号通知 关注服务号 微信消息

邮件并不适合这种即时消息通知场景,并且有种杀鸡用牛刀的意思,我用了一段时间后,果断开始找替代品。

之前了解过微信机器人,看了文档和社区之后意识到实现成本略高,首先需要一个可以 web 端登录的微信号,其次你得有两个微信号(因为微信限制多端登录),同时还得承受号被封的危险……当然我直接被卡在了第一步,虽然小号注册很早,但依然不能登录 web 端,有些帖子说号需要”养“一段时间,客户端登录,多发朋友圈,多分享,多加好友,这样的操作也透着玄学的意思,最终放弃。

快速简单的方案不可行之后,开始考虑从其他 IM 工具入手,比如企业微信和钉钉原生支持机器人,相交于微信需要通过公司的方式进入,钉钉则简单很多,建一个群(拉一个人进来,创建一个机器人,再把刚拉进来的人踢掉),创建机器人之后,机器人对应一个 hook 地址,带上消息请求这个地址就直接发消息,简单粗暴,方便。虽然需要多开一个钉钉,但因为除了这个机器人,没有其他的消息通知内容,几乎无感,可以忍受。到这里,需求已经满足了。

在知道 Server 酱之后,我觉得 Server 酱的模式要优于钉钉,直接在微信看消息,后续如果有新的通知场景,我会试试,它的逻辑是将微信号绑定在 Server 酱这个服务号下,调用接口把要发送的消息发给 Server 酱的服务器,让服务器调微信发送服务号通知,思路取巧。

正文已经结束,下面说下对应几个方案的代码实现

邮箱 & nodemailer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const mailInfo = {
user: 'bot@foxmail.com',
pass: 'XXXXXXX'
}

async function main(title, htmlInfo) {
let transporter = nodemailer.createTransport({
service: "QQ",
auth: {
user: mailInfo.user,
pass: mailInfo.pass
}
})
let info = await transporter.sendMail({
from: '"Leeon_Bot 🤖" <bot@foxmail.com>',
to: "leeon@gmail.com",
subject: title,
html: htmlInfo
})
}

微信机器人 & wechaty

wechaty 的封装十分强大,上手也极其简单,具体可以参考 wechaty 文档,这里不再赘述

Linux 环境中我遇到了依赖问题,通过安装以下依赖解决:
yum install pango.x86_64 libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 libXext.x86_64 libXi.x86_64 libXtst.x86_64 cups-libs.x86_64 libXScrnSaver.x86_64 libXrandr.x86_64 GConf2.x86_64 alsa-lib.x86_64 atk.x86_64 gtk3.x86_64 -y

钉钉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 这里用 dingtalk-robot 这个封装好的包演示一下
const robot = require('dingtalk-robot')(xxxxx)
const robotShot = data => {
robot.send({
msgtype: 'markdown',
'markdown': {
"title":"New Msg",
"text": data
}
}, function(err) {
if (err) {
log('Error ==>>', err)
}
log('Success', new Date())
})
}