玩转钉钉机器人

写在前面#

高效的团队往往会充分利用不同的工具来提升自己的工作效率,譬如通过钉钉进行沟通协同,使用 Trello 进行任务管理,使用 GitLab 或者 GitHub 来进行代码管理,使用 JIRA 来进行项目与事务跟踪等等。不同的工具分工合作,把团队的事务数字化管理流转起来,并在一定程度上实现了流程的自动化,将大家从一些繁琐的事务中解放出来,有效地提升了大家的协同能力和工作质量。

本文来讨论一下利用 node 接入机器人来实现一些日常办公小功能的自动化。

什么是群机器人?群机器人是钉钉群的高级扩展功能。群机器人可以将第三方服务的信息聚合到群聊中,实现自动化的信息同步。

首先来看一下案例:

博客机器人,cicd 触发时将新文章推送到群
博客机器人

bug 提醒,每天定时推送现有 bug 到群
bug机器人

如何接入自己的钉钉机器人?#

首先打开钉钉群右上角,点群设置。找到智能群助手,点击添加机器人。
添加机器人

这里要选择自定义机器人,安全策略要选择加签。
安全策略加签

这里我们拿到密钥,点击继续,拿到机器人的 webhook。
拿到webhook

关于签名的算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const crypto = require('crypto');

// webhook中access_token
const token = 'e5f004c907173f8188eca8696f6fa3d3a841e4174733bae334057e3e769e32ae';

// 时间戳
const timestamp = +new Date();

// 上一步拿到的密钥
const secret = 'SEC32f1a057885884cadf5eea01e713e54d4b97bccd85bcc86187ac735dba26b7c7';
const stringToSign = timestamp + '\n' + secret;

// 用 SHA256 算法,用密钥生成
const hmac = crypto.createHmac('sha256', secret);

// 用时间戳加密钥生成签名
hmac.update(stringToSign, 'utf-8');

// 用 base64 编码再 encoding
const sign = encodeURIComponent(hmac.digest('base64'));

// 将得到的签名,时间戳放进query部分
const url = `https://oapi.dingtalk.com/robot/send?access_token=${token}&timestamp=${timestamp}&sign=${sign}`;

本质上是我们向钉钉的 openAPI 发送一个 post 请求。只要我们拿到正确的 url 和请求格式,就可以向机器人发送消息。这样就完成了钉钉机器人的接入。
消息类型及数据格式

1
2
3
4
5
6
7
8
9
10
11
12
const axios = require('axios');

axios.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8';
try {
axios.post(url, msg).then(function (response) {
console.log(response.data);
console.log(response.status);
console.log(response.statusText);
});
} catch (error) {
console.error(error);
}

Example#

blog 机器人#

在 cicd 中配置在build的时候执行脚本,找出新增文章推送到群。
主要问题在于如何找出新的文章。
在构建时,会临时生成 db.json 文件,这个文件不会被push,部署时会被删除。我们可以每次构建时保存一份文章列表,一起推送到git上面去。这样我们每次构建完成后,部署之前,先读取旧的文章名单,然后再读取 db.json,对比找出新的文章。
每篇文章我们要生成摘要,方法是:找到第一个 markdown## 标记,将标题后面的文章的第一句话当作摘要。所以希望大家尽量要写 ## 标记。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 旧的文章list
const onlineList = JSON.parse(JSON.stringify(oldList)).map(value => value = value.title);

// 生成文章list
const list = [];
const reg = new RegExp(`((#)?(##)?(###)? ([\u4e00-\u9fa5A-Za-z0-9^%&',;=??$\/x22。,,.()()、]{0,})?\\n)(\\n)?([\u4e00-\u9fa5A-Za-z0-9^%&',;=?$\/x22,,.()()、 ]{0,})`);
fs.readFile('./db.json', (err, data) => {
if (err) throw err;
JSON.parse(data).models.Post.forEach(element => {
let abstract = reg.exec(element._content)[0];
abstract = abstract.split(`\n`);
abstract = abstract[abstract.length - 1] ? `${abstract.pop()}...` : '暂无摘要';
let url = element.source;
url = url.replace('_posts','')
list.push({
title: element.title,
abstract: abstract,
})
});

fs.writeFile('./article.json', JSON.stringify(list), () => { });

// 对比找出新增文章
let newArticle;
for (i of list) {
if (!onlineList.includes(i.title)) {
newArticle = i;
break;
}
}
}

禅道bug机器人#

目的是实现从禅道拉去 bug 清单,过滤出特定人员,发送到指定群。未来todo要实现能单独@机器人,他会把属于你的bug发给你。
难点在于如何从禅道拉取 bug 清单。禅道不是 Restful 的,他是用后端模板渲染的 html 返回的方式。要解决的问题是,模拟登录和拉取数据。
这里采用的方案是使用 puppeteer 包,作用是生成一个无头的浏览器,这样就可以操作DOM元素了。
puppeteer文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  // 模拟登录禅道
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://zentao.jifenn.com/pro/bug-browse-10--unclosed-0--130-500-1.html');

await new Promise(resolve => setTimeout(resolve, 800));

await page.waitForSelector('#account');
await page.waitForSelector('#submit');

await (await page.$('#account')).type(config.username);
await (await page.$("input[name='password']")).type(config.password);
await (await page.$('#submit')).click();

await browser.close();
```

需要注意的是,代码执行的时候,dom树可能还没渲染,所以要记得await某个元素。
编辑消息的时候,为了简化操作,可以利用 `ejs` 配置模板。有点像 `jsp` 的语法

```jsp
![screenshot](http://enbrands-2.oss-cn-shanghai.aliyuncs.com/user/lALPDefR0Z0O7TnM1c0CuQ_697_2134.png)

当前bug数量: **`<%= ourBugs.length %>`**

<% for(let i of ourBugs ){ %> 1. [[<%=i.id %>] <%= i.title %>](<%='https://zentao.jifenn.com'+i.url %>) - <%=i.assignedTo %>
<% } %>

最后#

希望这篇文章能给大家带来启发,不仅仅是机器人,大家都能搭建适合自己的自动化脚本。社会发展就是机器取代人,简单重复的工作被取代。而我们人最重要的一点,就是机器没有的思维。发动我们聪明绝顶的脑袋想一想,集思广益,助飞效能提升。