为自己搭建 RSS 订阅提醒服务

为自己搭建 RSS 订阅提醒服务

缘由

自己一直苦于关注的信息源太杂, 每次想要查看新的信息, 都得打开一堆网站和 APP (如别人的博客, Github, 微博, 知乎, B站之类的). 为了改善自己的订阅体验, 在 @Mexii 的帮助下, 我开始研究如何给自己搭建一个体验良好的 RSS 订阅提醒服务.

最终的解决方案如下:

  • Inkrss (开源项目, RSS 订阅提醒的核心, 使用免费的 Cloudflare Workers)
  • Vercel (免费的 Serverless 云函数服务, 能够免费托管 NodeJS, Python 代码)
  • 企业微信 Server 酱 (免费开通企业微信, 然后使用 Server 酱进行信息提醒)
  • RssHub (为了更好的体验, 需要托管在自建的服务器中, 需要服务器)

大致效果如下:

RssHub

介绍

RssHub 是一个开源、简单易用、易于扩展的 RSS 生成器,可以给任何奇奇怪怪的内容生成 RSS 订阅源。比如微博, 知乎, B站, Lofter, 番剧更新网站等等.

使用 Docker 部署起来非常简单.

安装

运行下面的命令下载 RSSHub 镜像.

1
docker pull diygod/rsshub

然后运行 RSSHub 即可.

1
docker run -d --name rsshub -p 1200:1200 diygod/rsshub

在浏览器中打开 http://localhost:1200/.

您可以使用下面的命令来关闭 RSSHub:

1
docker stop rsshub

更新

删除旧容器:

1
2
docker stop rsshub
docker rm rsshub

然后重复安装步骤.

添加配置

配置运行在 docker 中的 RSSHub, 最便利的方法是使用 docker 环境变量:

以设置缓存时间为 5 分钟举例, 只需要在运行时增加参数: -e CACHE_EXPIRE=300

1
docker run -d --name rsshub -p 1200:1200 -e CACHE_EXPIRE=300 -e GITHUB_ACCESS_TOKEN=example diygod/rsshub

该部署方式不包括 puppeteer 和 redis 依赖, 如有需要请改用 Docker Compose 部署方式或自行部署外部依赖.

Inkrss

Inkrss 的介绍在这里: https://github.com/pureink/inkrss

它支持通过微信或者 Telegram 进行提醒. 按照它文档的描述, 如果要用微信进行提醒, 我们要:

  1. 开通企业微信, 然后使用 Server 酱进行提醒.
  2. 在 Vercel 挂载一个 xml2json 的服务 (由于 Cloudflare 的限制, 基本上大部分 NPM 包都是不可用的)
  3. 通过 Cloudflare Workers 挂载 Inkrss.

其中它的文档描述已经很详细了, 在此就不多赘述. 但是其中有几个坑, 需要注意一下.

不支持使用 IP 地址直接访问 RSS 订阅源

因为我使用的是自己搭建的 RssHub, 我的服务器在国内而且又没有备案, 所以导致只能通过 IP 地址直接访问 Rss 订阅源.

最后我采用的方法是, 魔改 Inkrss 和 Vercel 上的云函数服务.

我在 Vercel 上的云函数服务的 /app 目录下加入了一个叫 xml2xml.js, 看起来有点多此一举的文件. 内容如下:

1
2
3
4
5
6
7
import axios from 'axios'

module.exports = async (req, res) => {
const { body } = req
const { data } = await axios.get(body.url)
res.send(data)
}

然后在 inkrss 的 schedule.js 文件中修改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let resp;
if (sub[i].url.startsWith("http://")) {
resp = await fetch(`${config.PARSE_URL}/api/xml2xml`, {
method: "POST",
headers: {
"Content-Type": "application/json; charset=utf-8",
},
body: JSON.stringify({
url: sub[i].url,
}),
});
} else {
resp = await fetch(sub[i].url);
}
const text = await resp.text();

我默认如果是 HTTP 请求, 不是 HTTPS 请求, 就通过 Vercel 间接获取.

未对无时间属性的订阅源特殊处理

如果对一些没有时间属性的订阅源进行订阅, 它将永远不会提醒, 例如 RssHub 中的番剧更新订阅源 “agefans”. 所以我们要进行一些处理: 如果不存在时间属性, 那么只要第一个 item 的标题改变了, 就进行提醒.

修改后的代码如下 (在 schedule.js 文件下):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for (let j = 0; j < data.items.length && i < config.maxItemsCount; j++) {
if (data.items[j].pubDate) {
if (new Date(data.items[j].pubDate) > new Date(sub[i].lastUpdateTime)) {
if (new Date(data.items[0].pubDate) > new Date(lastUpdateTime)) {
lastUpdateTime = data.items[0].pubDate;
}
lastUpdateTime = data.items[0].pubDate;
await reply(sub[i], data.items[j]);
}
} else {
sub[i].lastUpdateTime = new Date().toLocaleString();
await reply(sub[i], data.items[j]);
break;
}
}

微信相关的提醒服务的优化

Inkrss 的作者本身并不用微信来提醒, 所以对微信的提醒服务相应的处理不够全面. 例如, 没有进行 URL 转义, 发送消息可能出错; 提醒时只有标题和链接, 没有内容的问题.

notifications/wechat.js 文件下, 原来的代码是:

1
await fetch(`${config.WECHAT_URL}&msg_type=text&msg=${feed.title}\n${item.title}\n${item.link}`)

只有标题和链接 没有内容 item.contentSnippet.

我修改之后的代码为:

1
2
3
4
5
if (item.contentSnippet) {
await fetch(`${config.WECHAT_URL}&msg_type=text&msg=${encodeURI(feed.title)}\n---------------------------------------\n${encodeURI(item.title)}\n\n${encodeURI(item.contentSnippet)}\n\nLink: ${encodeURI(item.link)}`);
} else {
await fetch(`${config.WECHAT_URL}&msg_type=text&msg=${encodeURI(feed.title)}\n---------------------------------------\n${encodeURI(item.title)}\n\nLink: ${encodeURI(item.link)}`);
}
使用搜索:谷歌必应百度