一开始的选择(需要登陆)中纠结使用 Planet 还是博客的时候就已经在关注 Planet 这个东西。

非常奇特,本身并不存储什么数据,但是用这样一个简单的方式在极低的成本下将大量的用户和内容结合到了一起。

终于决定要建立一个社区星球的时候,我才发现它连主题模板都不支持响应式才不是我想写个好看点的主题结果折腾一天无果(ノ=Д=)ノ┻━┻

总之,使用的是 Ubuntu 仓库内的 planet-venus 包,遇到了各种问题…

  • 莫名其妙无法获取 feed,浏览器访问正常,用 curl 正常
  • 莫名其妙无法读取 feed 内容,其他 feedparser 均正常
  • 对文章内使用相对链接的内容无能为力
  • 模板语言落后,不要跟我说 old fashion
  • 输出路径一直是 cwd,不知道是不是 feature。但是这导致我的 home 下面到处都是 output 目录

强迫症犯了。遂决定自己实现一个因为逻辑很简单啊不过就是把 RSS 抓下来排个序再丢出去么

但是到具体的细节,还是有不少需要考量的东西。

压缩和编码

有些网站打开了 gzip 压缩,有些网站使用了非 UTF-8 编码…只是暴力读取的结果就是页面上一半正常一半乱码。

好在 feedparser 给出了使用 zlibiconv代码样例,这个问题就迎刃而解啦。

相对路径和内容安全

Planet Venus 似乎是会解析处理文章内不带有协议的链接和图片,以使这些资源能够在 planet 的页面中直接通过原地址访问。但是问题在于,planet venus 似乎只是暴力给所有非完整 URL 形式的资源地址都强行加上协议和主机名。于是就出现了这种情况——

  • 原文地址 http://www.example.com/2017/01/21/example.html
  • 原文中引用的资源 images/1234.jpg
  • 看起来没毛病,但是 Planet Venus 解析过来就是 http://www.example.com/images/1234.jpg
  • 这似乎看起来也没毛病… 然!而!正确的地址是 http://www.example.com/2017/01/21/images/1234.jpg

…呵呵哒。

先不管为什么会有这么奇怪的资源路径,但是 Feedparser 却可以正确解析到带有跟路径的地址。也就是上面的资源地址,Feedparser 解析完之后就是正确的 /2017/01/21/images/1234.jpg

但是只有正确的相对路径还不够,因为 planet 是单独的站点,直接把 HTML 往页面中插,结果就是浏览器会去请求 planet/2017/01/21/images/1234.jpg 然而 planet 的服务器上并不会有这个资源。

于是这个问题先放着,来看另一个问题。

聚合的一个特点是,几乎无法控制来源内容的安全性——因为其他的网站服务器都不是自己维护的,其他人能否保证自己的网站不被入侵、被跨站…都是未知数。如果有订阅的网站被入侵挂了马,阅读/订阅聚合 planet 的用户也会中招。

解决这个问题,常见的办法就是过滤掉不安全的 HTML tag。于是这里引入了 sanitize-html

其实我一开始打算用 regex 直接切掉不过还是不够 robust 所以乖乖去找包了… 但是惊喜的是这个包居然可以实现按规则替换!这就顺利解决了之前的问题,可以将 Feedparser 解析得到的路径加上正确的协议和主机地址。

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
sanitizeHtml(html, {
transformTags: {
// restore origin links
'img': function (tagName, attribs) {
if (attribs && attribs.src) {
if (attribs.src.startsWith('/') && attribs.src.charAt(1) !== '/') {
// root-relative path, add corresponding host before root
return {
tagName: tagName,
attribs: {
src: host + attribs.src
}
};
} else if (attribs.src.startsWith('//') ||
attribs.src.startsWith('http://') ||
attribs.src.startsWith('https://')) {
// absolute path, do nothing
return {
tagName: tagName,
attribs: attribs
};
} else {
// Feedparser may not correctly recognize this path, try with host/path
return {
tagName: tagName,
attribs: {
src: host + '/' + attribs.src
}
};
}
}
// don't miss the default action or it throws exception.
return {
tagName: tagName,
attribs: attribs
};
}
}
});

解决了文章内容相对路径的问题,要过滤特定的标签或者标签属性则是这个包本来就要做的事情了,小菜。

处理完文章对象之后,用 Array.prototype.sort 带一个 compare function 通过更新日期排序,接下来就可以简单渲染页面和 RSS 文件啦。

其他

一些不太常见的功能,例如 http proxy 的支持(在特殊的网络环境下可能用到),长文章仅展示 summary 并提示继续阅读,avatar 的支持,模板和输出目录保持和配置文件的相对路径,等。

然后作为深有体会——

1
2
3
CSS
IS
AWESOME

因此打死不写前端的家伙选择直接套用了 YUI Library 的 purecss 框架做一个还算看得过去的模板。至少… 几番折腾之后把响应式和 pre 等难缠的宽度搞定了顺便玩了下 media query

于是代码 -> GitHub

以及已经在使用的 Planet NyaaCat

从开坑到基本完善大概花了 15 个小时… 果然长时间不写代码手生才不是冻得手打字都变慢了呢