选择、阅读、整理
- RSS / Atom / 播客预览与订阅
- 网站公开 Feed 自动发现
- 可选的 RSSHub + Radar 路由发现
- 十二个主流中文内容平台的链接模式
- 频道与具体内容在站内主区域加载
- 订阅基线、查看记录与准确的新增判定
- 在浏览器与平台允许时复用外部登录会话
- 筛选、搜索、稍后看与安静阅读
- 本地合集与人工精选发现
- JSON 导入导出、跨标签页同步
“自选”是一个本地优先的内容订阅与整理工具。今日页只放你主动订阅的来源,发现页则把探索放在一个边界清晰的独立空间。
试试“RSS”“导出”“API”或“测试”。
CAPABILITY MAP
当前版本专注于“本机上的可控阅读”,不会把尚未实现的云端能力包装成已有功能。
QUICK START
需要 Node.js 22.13.0 或更高版本。克隆仓库后安装依赖并启动开发服务器。
git clone https://github.com/BillShiyaoZhang/our-choice.git
cd our-choice
npm install
npm run dev
浏览器打开 http://localhost:3000。文档位于 /docs/。
npm run build
npm run startdocker compose up --build在兼容编辑器中打开仓库,依赖会自动安装、RSSHub sidecar 会一起启动,并转发应用的 3000 端口。
Mac 版用原生 Swift/WKWebView 包装同一套网站,并内置 Node 22、Vinext 生产产物和浏览器助手;运行应用不要求预装 Node.js 或 Docker。
npm run mac:build
open "build/macos/Our Choice.app"
应用只监听 loopback,并在退出时关闭自己启动的服务。它优先使用 3000;端口被 Docker 或 Dev Container 占用时自动尝试 3001...3031,浏览器助手会验证健康端点并自动记住实际地址,不需要用户同步端口。专项开发仍可用 OUR_CHOICE_PORT 显式覆盖。
DEVELOPER_DIR=/Applications/Xcode-beta.app/Contents/Developer npm run mac:package-local
open build/macos/Our-Choice-local-unsigned.pkg
产物为 build/macos/Our-Choice-local-unsigned.pkg。它是明确未签名、未公证且只支持当前 Mac 架构的测试包,会安装或覆盖 /Applications/Our Choice.app,不得上传或对外分发。
npm run mac:package
该命令需要完整 Xcode、Apple Developer Program、双架构 Node、Developer ID Application 与 Developer ID Installer 身份,以及 App Store Connect API key 或已保存的公证 profile。它把 Safari .appex 嵌入主应用,再签名、公证并生成安装到 /Applications 的 PKG;显式传入 --skip-notarization 时只生成固定名为 Our-Choice-signed-unnotarized.pkg 的本地预检包。
notarytool profile。macos-release environment,再手动触发正式 workflow。正式 workflow 默认使用 Chrome manual 模式,不需要 Chrome Web Store 账号;商店公开发布后才可选择 store 模式。
浏览器助手通过安装版 Mac 应用每次启动重新生成的短期会话保护本地持久队列,自动发现端口后把内容交给 Mac 应用;Docker / 网页模式仍可使用手工配对。这样避免 Chrome、Safari 与 WKWebView 各自隔离的 localStorage 造成错投。完整产品数据仍使用设置中的 JSON 导入导出显式迁移。架构、签名和双架构验收见 docs/spec/macos-local-app.md。
A CALM READING LOOP
直接粘贴主页、分享文案、RSS 或网站地址,系统会自动识别平台;一个页面有多种订阅范围时,可以同时勾选多项。
按文章、视频、播客或新增筛选;点击后在主显示区打开,并用固定返回按钮回到自选。
创建自己的主题合集,或从发现页明确订阅一个人工精选合集。
在设置中下载 JSON 备份,换浏览器或清理数据前可随时恢复。
在“订阅”中点击来源名称,会进入该来源的内容详情;视频、文章和播客分区展示,各分区都按发布时间从新到旧排列。列表中还可打开来源主页、单独刷新、暂停、设置或移除来源。
来源设置允许修改名称与说明;由同一链接发现多个范围时,还可重新识别并增加或移除范围。Bilibili 来源还能选择视频默认在站内查看,或在新窗口打开 Bilibili 网页以使用平台登录与大会员播放能力。
Bilibili、微信公众号、知乎、小红书、抖音、快手、微博、小宇宙、今日头条、百家号、豆瓣和喜马拉雅由同一个输入自动识别。部署者配置 RSSHub 后,自选会列出 Radar 候选供多选;同一链接的全部已选范围始终视为一个来源,内容也合并在该来源下。喜马拉雅专辑使用显式映射,微信公众号高级设置可填写优读 ID、Biz/HID 或 Wechat2RSS ID。无规则或转换失败时仍保留链接模式。
优先粘贴网站公开的 RSS/Atom 地址。普通网页只有声明了公开 Feed 时才能自动发现。
Chrome、Edge 与 Safari 共用同一套 Manifest V3 源码,可以在用户浏览时收藏当前内容、订阅当前来源,并把 B站关注页中的 UP 主批量送回自选确认。平台登录始终留在原网站,扩展不会读取或保存密码、Cookie 或访问令牌。
chrome://extensions 或 edge://extensions 启用开发者模式。/Applications/Our Choice.app/Contents/Resources/browser-extension/chrome;源码开发时先运行 npm run extensions:build,再指向 build/browser-extensions/chrome/。“稍后看”直接进入系统合集;“收藏到合集”会在自选中要求选择目标合集。扩展只保存标题、规范 URL、摘要、封面和用户主动选中的文字,不保存整页快照。
“订阅这个来源”优先使用页面声明的 RSS/Atom,否则交给现有来源预览接口识别网站、作者主页、RSSHub 或链接模式。首次导入建立当前基线,历史内容不会被标成新增。
可选范围包括:UP 主图文、投币视频、动态、粉丝、关注用户、点赞视频、用户追番列表、默认收藏夹和投稿;默认只选择投稿。普通添加订阅、浏览器助手单个来源、批量导入及来源设置都使用同一套多选项。确认批量导入后,任务会在独立的右上角进度窗口中继续,可收起、暂停、继续或取消;最多六个账号并行处理,并省去每个 B站账号重复发现内容范围的请求。粉丝与关注用户依赖登录 UID 及自建 RSSHub 的 B站 Cookie 配置;投币、点赞、追番与收藏夹也受目标用户公开设置影响。
扩展处理的数据、保存期限和权限用途见独立的自选助手隐私政策。
组合内容类型与新增筛选。点击内容会记录查看时间并在站内打开;全局搜索会检查标题、摘要与来源。
刚加入来源时看到的条目属于频道历史,不会全部变成新增;后续首次发现且尚未查看的内容才计入新增。
把当前不处理的条目加入稍后,再从合集区域集中回看。
创建本地合集并收录内容,也可以明确订阅发现页展示的精选合集。
“附近一步”“跨出一步”“随机看看”提供不同探索距离,并展示推荐原因。
订阅、查看时间、新增基线、合集和发现偏好保存在当前浏览器的 localStorage,键名为 our-choice:state:v1;旧版数据会自动迁移到当前数据模型。
our-choice:assistant:v1 只为 Docker / 网页模式保存本地配对码,不进入应用状态或 JSON 备份;扩展待处理队列保存在扩展自己的本地存储中。不会。来源请求或解析失败时会保留已有内容,并给出可重试提示。
先确认已经选择具体订阅范围。未配置 RSSHub、Radar 没有适用规则、源站触发反爬,或该路由需要部署者配置 Cookie/Token 时,会安全降级为公开链接并显示原因;网站自己的 Feed 和可用的 RSSHub 路由仍可正常预览。
自选会规范化末尾斜杠,并只跟随 B站、小红书、抖音等明确允许域名的有限次公开短链跳转;其他跳转继续按 SSRF 安全规则拒绝。分享文案中只应包含一个要订阅的公开 URL。
部分平台通过 X-Frame-Options 或 CSP 禁止任何第三方站点内嵌。此时可在当前页打开来源,使用浏览器返回后,查看记录仍会保留。
浏览器可能隔离或阻止了 iframe 的第三方 Cookie。自选已经为支持 Storage Access API 的来源开放所需权限,但该权限必须由 B站页面自己请求,父页面无法代为调用。请使用查看器中的“在当前页打开并登录 B站”进入第一方登录环境;浏览器返回后,本地阅读记录仍会保留。
不会。登录表单、Cookie 与会话都由来源平台和浏览器处理;自选只保存非敏感的平台地址与最近打开时间。
当前没有账号或云同步。请在原设备导出 JSON,再在新设备导入。
来源预览接口会拒绝 localhost、私网 IP、非标准端口和危险重定向,防止服务端请求伪造(SSRF)。
BUILD WITH INTENT
项目采用 React 19、Next.js 16 接口约定与 vinext 构建,使用 TypeScript。没有独立数据库,产品状态由浏览器本地存储承载。
| 命令 | 用途 |
|---|---|
npm run dev | 同步文档并启动开发服务器 |
npm run build | 同步文档并生成生产构建 |
npm test | 运行生产构建与 Node 测试 |
npm run lint | 执行 ESLint 静态检查 |
npm run docs:sync | 将 docs/ 复制到 public/docs/ |
npm run extensions:build | 从共用源码生成 Chrome 与 Safari WebExtension 资源 |
npm run extensions:package:chrome | 生成并复验 Chrome Web Store 版本化上传 ZIP |
npm run mac:build | 构建当前架构的本地 .app |
npm run mac:package-local | 生成仅供当前 Mac 测试的未签名 PKG |
npm run mac:verify-package-local | 从最终 Payload 独立复验本机 PKG |
npm run mac:package | 嵌入 Safari 扩展并生成签名、公证用 PKG |
our-choice/
├── app/
│ ├── api/source-preview/route.ts # 来源获取、发现与解析
│ ├── lib/model.ts # 产品类型和种子数据
│ ├── our-choice-app.tsx # 客户端状态与界面
│ └── globals.css # 产品视觉系统
├── docs/
│ ├── assets/ # 文档样式与渐进增强脚本
│ ├── spec/ # 文档驱动的功能规格
│ └── index.html # 文档站入口
├── browser-extension/ # Chrome / Safari 共用 WebExtension 源码
├── macos/ # Swift 外壳与本地 Node 代理
├── scripts/ # 文档、扩展、Mac App 与 PKG 构建
├── tests/ # 行为和渲染测试
└── .github/workflows/ # GitHub Pages 发布
public/docs/ 是构建前生成的副本,不应直接编辑或提交。
客户端拥有产品状态与来源基线;服务端路由只在预览和刷新时请求来源,不持久化用户订阅。可选浏览器助手在安装版中使用短期原生会话、在 Docker / 网页模式中使用手工配对,把本地扩展队列送入客户端确认;网页收藏直接写入本地合集,来源候选仍必须经过预览接口。配置 RSSHub 后,服务端通过 /api/radar/rules/{domain} 读取同源 Radar 规则并请求匹配的 Feed;实例地址和访问密钥不进入浏览器数据。外部频道和内容通过受限 iframe 在主区域加载,来源平台的会话仍由浏览器管理。响应设置 Cache-Control: no-store。
/api/source-preview{
"url": "https://example.com/feed.xml",
"limit": 12
}
mode: "live" 返回规范化的来源、条目与抓取时间。由 RSSHub 转换的来源还会返回 provider: "rsshub"、原始 refreshUrl 与不含实例地址或密钥的 rsshubRoute。未配置、无规则或转换失败的中文内容平台返回 mode: "link-only"、平台标识、原始链接、空条目列表和明确警告;Bilibili 保留兼容的 BILIBILI_LINK_ONLY 警告。
| 错误码 | 含义 | 可重试 |
|---|---|---|
INVALID_URL | 地址、协议、端口或网络范围不允许 | 否 |
UNSUPPORTED_FORMAT | 不是可识别的 Feed,且网页未声明 Feed | 否 |
UPSTREAM_TIMEOUT | 来源在 7 秒内没有响应 | 是 |
UPSTREAM_HTTP | 连接、状态码或重定向失败 | 视情况 |
FEED_TOO_LARGE | 响应超过 1 MB | 否 |
PARSE_FAILED | XML 声明或内容无法安全解析 | 否 |
Docker Compose 默认连接同编排中的独立 RSSHub 服务。直接运行应用时设置 RSSHUB_BASE_URL;实例启用访问控制时再设置仅服务端可见的 RSSHUB_ACCESS_KEY。自选不会默认使用公共实例,也不会执行远程 Radar JavaScript。
仓库遵循文档驱动开发和 Red–Green–Refactor:先在 docs/spec/ 记录可观察行为与边界,再添加会因缺失功能而失败的测试,最后实现并重构。
node --test tests/docs-site.test.mjs
npm run lint
npm test
新增测试必须确定、隔离且不依赖真实上游网络。API 测试应使用注入或模拟响应覆盖成功、超时、格式错误与 SSRF 防护。
文档由 GitHub Pages 托管。推送到 main 且改动涉及 docs/** 或工作流时,GitHub Actions 会直接发布 docs/。
docs/;如行为发生变化,同步更新对应规格和测试。文档站没有外部运行时依赖;.nojekyll 确保文件按原样发布。本地应用则由同步脚本在 /docs/ 提供同一份内容。