为什么我们需要 RSS

支持 RSS 是一种美德。

RSS(全称:RDF Site Summary;Really Simple Syndication),中文译作简易信息聚合,也称聚合内容,是一种消息来源格式规范,用以聚合经常发布更新资料的网站,例如博客文章、新闻、音频或视频的网摘。RSS 文件(或称做摘要、网络摘要、或频更新,提供到频道)包含全文或是节录的文字,再加上发布者所订阅之网摘资料和授权的元数据。简单来说 RSS 能够让用户订阅个人网站个人博客,当订阅的网站有新文章是能够获得通知。

以上内容转自维基百科

自从迈入移动互联网时代,原来开放的网络再度走向封闭,巨头们纷纷搞起了“圈地运动”,妄图把用户都留在自家的 App 上,成为自己独享的韭菜。各种推送层出不穷,如果不主动关闭手机基本上 24 小时都会有通知消息,人们越来越习惯于接收信息而不是去主动获取,被层层包裹在由精确算法编织出的信息茧房中(虽然说我觉得 B 站的推送算法可能是程序员拿脚写的,我越不想看什么它就越给我推送什么)。这其中受害最深的莫过于抖音和微博用户了:前者主打下沉市场,让从未接触过互联网的低文化群体沉迷短视频;后者作为臭名昭著的“人才孵化基地”,让无数网友深刻体会到了“网民本科率不到 10%”这个事实。当然其他平台也好不到哪去,只不过这两家的信息茧房效应较为显著罢了。

为了摆脱这种近乎强奸式的大数据信息轰炸,不少人重新拾起了“老旧”的 RSS,这其中也包括我。

开端

用 RSSHub 统合信息渠道,这并不是我一时的想法。早在 2020 年初,我了解到了 RSSHub 这个开源项目之后,这枚种子就一直扎根在我的心底,奈何那时手头上唯一的服务器是 Windows Server 2012 的系统,再加上 1M 带宽小水管,本身就挂载着一些乱七八糟的网站,再挂载其他服务不免有些吃力(今年不打算续费这台破机子了,就算是续费肯定要格式化换成其他系统)。更何况我(至今)一直没有搞懂如何用 npm 安装 RSSHub,所以就只能暂时搁置。

在去年的十月左右,我找到了一个开放使用的 RSSHub 服务,便暂时用这家网站代替(其实一开始用官方的 Demo 也行,但是这个 Demo 太多人用,很多路由早就无法正常获取了)。不过用别人的 RSSHub 还是有诸多不便,例如无法订阅一些隐私性较强的内容(这里的隐私性指的是需要用到我社交软件账号的 cookie,如果把它交给别人就相当于是交出了自己账号的绝大部分权限),所以算不上什么全面的信息统合,如 B 站动态、微博关注、知乎时间线这些东西还是只能在它们各家的 App 上浏览。

今年一月,我购入了一台 CentOS(现已换成 Debian)系统的轻量应用服务器,虽然有着每月 1000G 的流量限制,但我估计撑死也用不到那么多(到目前为止也就挂着 QQ 机器人,自动健康打卡脚本,还有本文提到的 RSSHub 而已)。前天晚上睡不着觉,又在 GItHub 上重新看到了 RSSHub 的项目仓库,脑袋一热就用手机的终端连上服务器开干了。

部署 RSSHub

官方文档上有多种部署方法,都十分简单,这里我选择的是较为熟悉的 Docker 部署(说是最熟悉其实只会用 pull run stop rm 等最基础的命令)

首先自然是安装 Docker,参考菜鸟教程上的使用官方安装脚本自动安装,安装命令如下:

curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun  

接着便是拉取 RSSHub 镜像,这里如果觉得下载速度慢的可以换其他源试试。

docker pull diygod/rsshub

再来就是运行 RSSHub,这里的 -p 1200:1200 即是把本地的 1200 端口映射到容器内部的 1200 端口上,而 -e DEBUG_INFO=false 则是设置容器的环境变量,这在之后会经常用到。

docker run -d --name rsshub -p 1200:1200 -e DEBUG_INFO=false diygod/rsshub

这样我们在服务器本地访问 http://127.0.0.1:1200/ 便可以看到 RSSHub 的主页了,当然在服务器外应该访问 <http://服务器 IP 地址:1200/>。

image-20210316212011523

如果想要更新镜像或者更改环境变量,可以输入下面的指令停止并删除正在运行的容器,然后从拉取镜像那一步重新来过。

docker stop rsshub
docker rm rsshub

Nginx 反向代理

虽然我本来也没有打算把 RSSHub 服务开放给其他人使用,但是一直使用 ip 地址访问不免有些麻烦,只好想办法搞个域名了。自然是继续用 maddestroyer.xyz 的二级域名,首先用 Cloudfare 的 DNS 解析到服务器 ip,接下来就是服务器的事情了。

服务器这里需要用 Nginx 进行反向代理。说来惭愧,虽然本人旗下有多个网站,但都是通过宝塔面板进行的傻瓜式部署,所以我从来没有自己安装过 Nginx。我百度到的安装方法一个比一个离谱(点名批评菜鸟教程),说要先安装编译工具和库文件,然后编译安装 PCRE,最后才能编译安装 Nginx。最后我发现 debian 上只需要跟平常安装软件包一样 sudo apt-get install nginx 就行了,白忙活了半个多小时。

安装好了之后输入 nginx -v 看一下版本,检查有没有正常安装。

nginx -v
nginx version: nginx/1.14.2

接着就是添加对应的配置文件,使 Nginx 能正确地将 rsshub.maddestroyer.xyz 域名解析到本地的 1200 端口。这里百度到的方法也都是坑,都让我修改 /etc/nginx/conf.d/*.conf ,但最后我发现直接新建 /etc/nginx/conf.d/rsshub.conf 比这个好使多了(所以说一定要去看官方文档,哪怕是英文的官方文档也比这些不知道从哪蹦出来的文章要靠谱得多)。

server {
    server_name rsshub.maddestroyer.xyz;
    location / {
        proxy_pass http://localhost:1200;
        proxy_redirect off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
}

将配置文件修改为上之后,再重启一下 Nginx 服务,我们便可以通过 rsshub.maddestroyer.xyz 域名访问部署的 RSSHub 了。

nginx -s reload

SSL 证书

Certbot,照着这个网站上的教程做就行了。

我这里演示下自己服务器上的安装流程。

sudo apt update
sudo apt install snapd
sudo snap install core; sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
sudo certbot --nginx

一些 RSSHub 路由的配置

既然自建了 RSSHub,我自然要享受一下非自建不能享受的功能,尤其是这个 B 站关注动态的功能我眼馋好久了。

首先要确保你已经登陆了 B 站账号,然后访问 https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_new?uid=0&type=8

按 F12 打开浏览器的控制台,在网络找到 dynamic_new 请求,找到 Cookie,从SESSDATA=开始复制直到出现分号。

-e BILIBILI_COOKIE_{uid}=SESSDATA=dsadsadasdssadasdadsdsadasd

{uid} 就是你 B 站账号的 uid,SESSDATA= 则是刚才复制到的 Cookie。

获取 Pixiv Refresh Token

方法来源:https://gist.github.com/ZipFile/c9ebedb224406f4f11845ab700124362

首先,你需要有一个 Pixiv(废话),和一台能登录 Pixiv 的电脑(还是废话)

然后复制粘贴以下代码到pixiv_auth.py

#!/usr/bin/env python

from argparse import ArgumentParser
from base64 import urlsafe_b64encode
from hashlib import sha256
from pprint import pprint
from secrets import token_urlsafe
from sys import exit
from urllib.parse import urlencode
from webbrowser import open as open_url

import requests

# Latest app version can be found using GET /v1/application-info/android
USER_AGENT = "PixivAndroidApp/5.0.234 (Android 11; Pixel 5)"
REDIRECT_URI = "https://app-api.pixiv.net/web/v1/users/auth/pixiv/callback"
LOGIN_URL = "https://app-api.pixiv.net/web/v1/login"
AUTH_TOKEN_URL = "https://oauth.secure.pixiv.net/auth/token"
CLIENT_ID = "MOBrBDS8blbauoSck0ZfDbtuzpyT"
CLIENT_SECRET = "lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj"


def s256(data):
    """S256 transformation method."""

    return urlsafe_b64encode(sha256(data).digest()).rstrip(b"=").decode("ascii")


def oauth_pkce(transform):
    """Proof Key for Code Exchange by OAuth Public Clients (RFC7636)."""

    code_verifier = token_urlsafe(32)
    code_challenge = transform(code_verifier.encode("ascii"))

    return code_verifier, code_challenge


def print_auth_token_response(response):
    data = response.json()

    try:
        access_token = data["access_token"]
        refresh_token = data["refresh_token"]
    except KeyError:
        print("error:")
        pprint(data)
        exit(1)

    print("access_token:", access_token)
    print("refresh_token:", refresh_token)
    print("expires_in:", data.get("expires_in", 0))


def login():
    code_verifier, code_challenge = oauth_pkce(s256)
    login_params = {
        "code_challenge": code_challenge,
        "code_challenge_method": "S256",
        "client": "pixiv-android",
    }

    open_url(f"{LOGIN_URL}?{urlencode(login_params)}")

    try:
        code = input("code: ").strip()
    except (EOFError, KeyboardInterrupt):
        return

    response = requests.post(
        AUTH_TOKEN_URL,
        data={
            "client_id": CLIENT_ID,
            "client_secret": CLIENT_SECRET,
            "code": code,
            "code_verifier": code_verifier,
            "grant_type": "authorization_code",
            "include_policy": "true",
            "redirect_uri": REDIRECT_URI,
        },
        headers={"User-Agent": USER_AGENT},
    )

    print_auth_token_response(response)


def refresh(refresh_token):
    response = requests.post(
        AUTH_TOKEN_URL,
        data={
            "client_id": CLIENT_ID,
            "client_secret": CLIENT_SECRET,
            "grant_type": "refresh_token",
            "include_policy": "true",
            "refresh_token": refresh_token,
        },
        headers={"User-Agent": USER_AGENT},
    )
    print_auth_token_response(response)


def main():
    parser = ArgumentParser()
    subparsers = parser.add_subparsers()
    parser.set_defaults(func=lambda _: parser.print_usage())
    login_parser = subparsers.add_parser("login")
    login_parser.set_defaults(func=lambda _: login())
    refresh_parser = subparsers.add_parser("refresh")
    refresh_parser.add_argument("refresh_token")
    refresh_parser.set_defaults(func=lambda ns: refresh(ns.refresh_token))
    args = parser.parse_args()
    args.func(args)


if __name__ == "__main__":
    main()

运行脚本

python pixiv_auth.py login

此时会自动打开一个浏览器窗口,先别急着登录。

按下 F12,切换到 Network,在筛选器输入callback?,勾选Preserve log,再进行一次正常的 Pixiv 帐号登录。

在抓取到的网络包中找到https://app-api.pixiv.net/web/v1/users/auth/pixiv/callback?state=...&code=...,复制code=后面的内容,粘贴回终端。

现在你应该能够看到脚本输出了 refresh_token。

-e PIXIV_REFRESHTOKEN=sdsdsdsdsdsd

鉴于 Pixiv 在中国大陆感人的访问情况,你可能还要给 RSSHub 配置代理。

配置邮箱账号密码

如果是 QQ 邮箱,可以在设置->账户->POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV 服务一栏找到 IMAP 服务的开关,打开该服务之后点击下面的生成授权码获取我们需要的密码。

-e EMAIL_CONFIG_xxx_qq_com="password=123456&host=imap.qq.com&port=993"

这里把 xxx 改成你的 QQ 号,password 则是前面获取的授权码,host 和 port 都不需要改动。

值得注意的是,官方文档上告诉我给 RSSHub 的 docker 配置环境变量时不应该带引号,但经过我实测仍然得带引号。

部署 Tiny TIny RSS

Tiny Tiny RSS 是一款基于 PHP 的免费开源 RSS 聚合网页阅读器,同时也可以给你的阅读器提供 API。我看中的自然是 API 服务,使得手机端和电脑端之间的数据能够同步。

部署 TTRSS 我推荐使用 docker-compose,所以让我们先装上再说。

sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

新建一个ttrss文件夹,在其中新建docker-compose.yml,复制粘贴以下内容。

version: "3"
services:
  service.rss:
    image: wangqiru/ttrss:latest
    container_name: ttrss
    ports:
      - 181:80
    environment:
      - SELF_URL_PATH=http://localhost:181/ # please change to your own domain
      - DB_PASS=ttrss # use the same password defined in `database.postgres`
      - PUID=1000
      - PGID=1000
    volumes:
      - feed-icons:/var/www/feed-icons/
    networks:
      - public_access
      - service_only
      - database_only
    stdin_open: true
    tty: true
    restart: always

  # 可删除
  service.mercury: # set Mercury Parser API endpoint to `service.mercury:3000` on TTRSS plugin setting page
    image: wangqiru/mercury-parser-api:latest
    container_name: mercury
    networks:
      - public_access
      - service_only
    restart: always

  # 可删除
  service.opencc: # set OpenCC API endpoint to `service.opencc:3000` on TTRSS plugin setting page
    image: wangqiru/opencc-api-server:latest
    container_name: opencc
    environment:
      - NODE_ENV=production
    networks:
      - service_only
    restart: always

  database.postgres:
    image: postgres:13-alpine
    container_name: postgres
    environment:
      - POSTGRES_PASSWORD=ttrss # feel free to change the password
    volumes:
      - ~/postgres/data/:/var/lib/postgresql/data # persist postgres data to ~/postgres/data/ on the host
    networks:
      - database_only
    restart: always

volumes:
  feed-icons:

networks:
  public_access: # Provide the access for ttrss UI
  service_only: # Provide the communication network between services only
    internal: true
  database_only: # Provide the communication between ttrss and database only
    internal: true

上面是 Awesome TTRSS 提供的 docker-compose.yml,如果你懒得复制粘贴也可以

wget https://raw.githubusercontent.com/HenryQW/Awesome-TTRSS/main/docker-compose.yml

这个文件实际上部署了四个镜像,第一是 TTRSS 本体,第四是数据库,第二和第三分别是全文获取服务和简繁转换服务,如果没有需求其实也可以删掉后面两个服务。

之后便可以启动了。

docker-compose up -d

至于配置反向代理,其实和前面讲过的一样,只需要在 /etc/nginx/conf.d/ 新建一个 ttrss.conf 的文件,粘贴上文的配置文件,将其中的 rsshub 改成 ttrss,端口改成 181 就行了。

获取 SSL 证书的话,再运行一次 certbot,选择对应的选项即可,此处不再多提。

TTRSS 的默认帐号和密码分别是 admin 和 password,登录后别忘了改密码。

阅读器的选择

我在 Windows 上使用的的阅读器是云之幻的 RSS 追踪(Microsoft Store 14 块)FluentReader,手机端则是其对应的 Fluent Reader Lite