image
GitHub - huginn/huginn: Create agents that monitor and act on your behalf. Your agents are standing

介绍

在北欧神话中,奥丁的肩膀上坐着两只乌鸦,一只名叫 Huginn,一只名叫 Muninn。这两只乌鸦告诉奥丁他们的所见所闻,毫无遗漏。奥丁在黎明时派出它们,它们飞遍全世界然后在晚餐之前回来汇报,因此,奥丁能知晓很多事情。在Huginn的项目主页上,作者对它有详细的介绍。我们同样可以通过Huginn创建不同的代理,通过这些代理发送HTTP请求获得相关数据,然后将获取到的数据进行处理,就可以在互联网上面收集到各类我们需要的信息了。通过Huginn我们可以比较方便的实现如下功能:

  • 监控你关心的事项例如知乎、微博、贴吧等平台指定的信息,一旦监控到信息,邮件通知你。

  • 监控各大购物平台商品信息,一旦发现折扣信息,邮件通知你。 支持各种形式的发送和接收 WebHooks。

  • 抓取网页内容并且在它们发生变化时发送邮件给你。 将获得的数据进行相应的格式处理并输出,例如RSS。

  • 跟踪天气的变化,如果监测到明天要下雨或下雪,就会发邮件提醒你。

总得来说,Huginn可以帮助我们做好两件事情,一是定制化推送或提醒,二是聚合数据。

目前我的用途:

  • 监控 Github Release 和 Commits,推送消息

  • 监控地震台网,推送消息

  • 监控Dz论坛帖子,推送消息

中间踩坑还是不少的

安装 Huginn

使用 Docker 安装:

  1. 拉取镜像 hub.docker.com

  2. 启动镜像 docker run -it -p 3000:3000 -v /home/huginn/mysql-data:/var/lib/mysql huginn/huginn

支持外部 MySQL 数据库,但是作为个人使用的话只需要导出内部数据库文件做持久化就好了。
我在群晖里遇到目录权限问题,最后手动把对应目录权限设置为everyone解决的

  1. 初始用户名:admin 密码:password

概念讲解

Agent

Agent 就是代理,是执行具体操作的角色。如采集网页、运行js代码、发送 webhook 等具体操作。

Scenario

Scenario 就是集合,等于是文件夹,用来整理归类 Agent 方便识别。所以不用 Scenario 归类也可以。

Event

Event 就是事件,Agent 之前依靠 Event 传递消息和数据。有些 Agent 可以自己产生数据(如爬取网页或解析RSS),有些 Agent 拿到传递的 Event 解析后再发出下一个 Event。

实践

RSS订阅源

首先当然是去找现成的 RSS ,这样就不用自己写网页采集了。

RSSHub订阅服务 - 胜之不易

有现成的我捡来用了

Shopify Liquid

Liquid 是一种字符串模版,用于提取 Agent 传递的 Event 中的数据。

简单点来说 如果上一个 Agent 传递的 Event 是这样的

1
2
3
4
5
{
"abc": {
"123": "true"
}
}

那么 {{abc.123}} 的结果就是 true

更多的姿势可以看 Liquid 文档和 Huginn Wiki

使用 Liquid · 设置事件格式huginn/huginn Wiki (github.com)

简介 – Liquid 模板语言中文文档 | Liquid 中文网 (bootcss.com)

消息推送相关

钉钉机器人

消息链接说明 - 钉钉开放平台 (dingtalk.com)

以下是 POST Agent 的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"post_url": "https://oapi.dingtalk.com/robot/send?access_token=10086",
"expected_receive_period_in_days": "1",
"content_type": "json", // json xml 和正常的 HTTP Header 头
"method": "post", // get, post, put, patch, and delete
"payload": { // 可以是 json,也可以是字符串(字符串看 Bark 例子)。
"msgtype": "actionCard",
"actionCard": {
"title": "{{title}}",
"text": "{{content}}",
"btnOrientation": "1",
"btns": [
{
"title": "有好康的",
"actionURL": "dingtalk://dingtalkclient/page/link?url={{encodeUrl}}&pc_slide=false"
}
]
}
},
"headers": {},
"emit_events": "false", // 设置成 true 可以看到输出,用来调试 webhook
"no_merge": "false",
"output_mode": "clean"
}

飞书机器人

自定义机器人使用指南 - 开发指南 - 开发文档 - 飞书开放平台

以下是 POST Agent 的内容

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
32
33
34
{
"post_url": "https://open.feishu.cn/open-apis/bot/v2/hook/xxx",
"expected_receive_period_in_days": "0",
"content_type": "json",
"method": "post",
"payload": {
"msg_type": "post",
"content": {
"post": {
"zh_cn": {
"title": "{{title}}",
"content": [
[
{
"tag": "text",
"text": "{{content}}"
},
{
"tag": "a",
"text": "\n\n> 查看详情",
"href": "{{url}}"
}
]
]
}
}
}
},
"headers": {
},
"emit_events": "false",
"no_merge": "true",
"output_mode": "clean"
}

iOS Bark

使用教程

以下是 POST Agent 的内容

建议使用正常的 POST,Json POST 好像有问题

1
2
3
4
5
6
7
8
9
10
{
"post_url": "https://api.day.app/ping",
"expected_receive_period_in_days": "1",
"content_type": "application/x-www-form-urlencoded", // json xml 和正常的 HTTP Header 头
"method": "post",
"payload": "title={{title}}&body={{body}}&group={{group}}&icon={{icon}}&isArchive={{isArchive}}&url={{url}}&sound={{sound}}", // 可以是 json,也可以是字符串
"emit_events": "false",
"no_merge": "true", // 如果 payload 是字符串,必须为 true
"output_mode": "clean"
}

Github RSS 订阅、处理和传递

RSS 订阅

GitHub 官方也提供了一些 RSS:

仓库 releases: https://github.com/:owner/:repo/releases.atom
仓库 commits: https://github.com/:owner/:repo/commits.atom
用户动态: https://github.com/:user.atom
专属动态: https://github.com/:user.private.atom?token=:secret (登录后在仪表盘页面 (opens new window)找到 Subscribe to your news feed 字样即可)
Wiki 历史: https://github.com/:owner/:repo/wiki.atom

使用 Rss Agent,名为 Github Repo Release Atom

1
2
3
4
5
{
"expected_update_period_in_days": "5",
"clean": "false",
"url": "https://github.com/huginn/huginn/commits/master.atom" # url 支持数组,可以填入多个 RSS 源。
}

检查频率(Schedule)是 5 个小时(Every 5h),Event 传递给 Github Repo Release Atom Parser

数据处理

如果想从 RSS 中解析内容到 飞书机器人,还需要自行处理数据。

因为涉及到时间转换和正则匹配,所以我使用了 JavaScript Agent,名为Github Repo Release Atom Parser

JS 也提供了 Huginn Event 相关的 API,在初始化的时候会有示范代码,右侧也有相应说明。

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
function formatDate(time, format) {
if (time == null || time == undefined || time == "") {
return "";
}

var t = new Date(time); // 容器内时区为-8
var len = t.getTime(); // 到现在的秒数
var offset = t.getTimezoneOffset() * 60000; // 和utc差多少秒
var utc = len + offset; // utc时间
var t1 = new Date(utc + 3600000 * 8); // +8

var tf = function (i) {
return (i < 10 ? '0' : '') + i
};
return format.replace(/yyyy|MM|dd|HH|mm|ss/g, function (a) {
switch (a) {
case 'yyyy':
return tf(t1.getFullYear());
break;
case 'MM':
return tf(t1.getMonth() + 1);
break;
case 'mm':
return tf(t1.getMinutes());
break;
case 'dd':
return tf(t1.getDate());
break;
case 'HH':
return tf(t1.getHours());
break;
case 'ss':
return tf(t1.getSeconds());
break;
}
})
}

Agent.receive = function () {
var events = this.incomingEvents();
for (var i = 0; i < events.length; i++) {
var url = events[i].payload.url;

var regex = /^https:\/\/github\.com\/([a-zA-z]+\/[a-zA-z-_\.]+)\/.+$/;
var res = regex.exec(url);

var dateTime = formatDate(new Date(events[i].payload.last_updated), 'yyyy-MM-dd HH:mm:ss');
var content = events[i].payload.content.replace(/<.*?>/ig, "").replace(/\n\n/ig, "\n");

this.createEvent({
'title': 'Github ' + res[1] + ' Release',
'content': content,
'url': events[i].payload.url
});
}
}

通过 this.createEvent 创建 Json,传递给飞书机器人 Agent,最后通过 Liquid 提取对应字段就完成了。

参考

TODO

Agent介绍

discuz论坛的通用RSS订阅办法