笔记

FreshRSS 到公开 Reader 的数据桥接

如何从 FreshRSS 的 Google Reader API 获取数据,筛选后生成公开的阅读精选页面。

公开 Reader 的数据来源是 FreshRSS,但不是直接暴露 FreshRSS 的所有数据。中间有一层数据桥接,负责获取、筛选和转换。

数据源

FreshRSS 兼容 Google Reader API,这是一个被广泛支持的 RSS 阅读器 API 标准。通过这个 API 可以:

  • 获取订阅源列表。
  • 获取文章列表。
  • 获取文章详情。
  • 标记文章已读/未读。

API 配置

数据桥接使用 FreshRSS 的 /reader/app/api/query.php 端点:

const READER_QUERY_PATH = '/reader/app/api/query.php';
const READER_QUERY_PARAMETERS = new Set(['user', 't', 'f']);

查询参数:

  • user:用户名(不包含密码)。
  • t:API token。
  • f:固定值 greader(Google Reader 兼容模式)。

安全验证

在使用 API 配置之前,进行严格的安全验证:

  1. HTTPS 强制:只接受 HTTPS 协议的 URL。
  2. 同源验证:API URL 的 origin 必须与站点 URL 一致。
  3. 参数验证:只接受预定义的参数名和数量。
  4. 凭据检查:URL 中不能包含用户名或密码。
  5. 路径验证:必须是指定的 API 路径。

任何验证失败都会抛出错误,阻止不安全的配置。

数据获取

使用 bounded fetch 获取 API 响应:

const response = await fetcher(source.sourceUrl, {
  credentials: 'omit',
  redirect: 'error',
  headers: { Accept: 'application/json' },
  signal: controller.signal,
});

关键安全措施:

  • 不发送凭据:credentials: 'omit' 确保不发送 cookie。
  • 不跟随重定向:redirect: 'error' 防止被重定向到其他地址。
  • 超时控制:10 秒超时,防止长时间挂起。
  • 大小限制:响应体不能超过 1MB,防止内存溢出。

数据转换

从 Google Reader API 获取的原始数据需要转换为站点使用的格式:

function adaptGReaderPayload(payload: unknown, now: Date): ReaderResult {
  // 转换逻辑
}

转换过程:

  1. 解析 JSON 响应。
  2. 提取文章列表。
  3. 筛选适合公开的内容。
  4. 转换为标准化的 ReaderItem 格式。

隐私筛选

数据桥接的一个关键职责是确保只有适合公开的内容进入公开 Reader:

公开字段

  • 标题
  • 链接
  • 来源名称
  • 摘要
  • 发布日期

不公开字段

  • 订阅源 URL
  • 阅读状态
  • 分类和标签
  • 账户信息
  • 管理接口信息

构建时执行

数据获取在构建时执行,不是在客户端:

  • 构建时获取:RSS 数据在 astro build 时从 FreshRSS 获取。
  • 静态输出:获取的数据被嵌入到静态 HTML 中。
  • 客户端无感知:访客看到的是静态页面,不涉及 API 调用。

这意味着:

  • FreshRSS 服务器只在构建时被访问,不是每个访客请求都会触发。
  • 如果 FreshRSS 不可用,构建会失败,但已部署的站点不受影响。
  • 访客的隐私不受影响,因为所有数据都是预渲染的。

错误处理

数据获取可能因为多种原因失败:

  • FreshRSS 服务器不可用。
  • API token 过期。
  • 网络超时。
  • 响应格式异常。

失败时的处理策略:

  • 构建中断,不生成不完整的 Reader 页面。
  • 已部署的上一个版本保持不变。
  • 需要手动检查问题并重新构建。

本笔记的状态

这是一个 growing 状态的笔记。随着 Reader 功能的演进,会补充关于增量同步、缓存策略和错误恢复的细节。

评论