RSS-to-ActivityPub 代理服务 — 从 Mastodon 关注任意 RSS 源。
用户只需在 Mastodon 中搜索 @example.com@your-keyfeeder-domain.com,即可关注对应网站的 RSS 更新。
- Mastodon 通过 WebFinger 查询用户
- KeyFeeder 自动发现该域名的 RSS 源
- 每天通过 Cron Job 拉取 RSS 更新(Vercel Hobby 限制,可以使用诸如 Github Action / Bot 绕过)
- 新内容通过 ActivityPub 协议推送给所有关注者
在 Mastodon 中搜索 @<用户名>@your-keyfeeder-domain.com,用户名即为目标网站的地址。
.只用于域名部分(子域名、域名、TLD)_代表/(路径分隔符)
| 用户名 | 解析结果 | RSS 发现地址 |
|---|---|---|
xkcd.com |
xkcd.com |
https://xkcd.com/ |
blog.example.com |
blog.example.com |
https://blog.example.com/ |
example.com_blog |
example.com/blog |
https://example.com/blog |
example.com_blog_feed |
example.com/blog/feed |
https://example.com/blog/feed |
KeyFeeder 会依次尝试直接访问、/rss.xml、/feed.xml、.rss 后缀、.xml 后缀、HTML 中的 <link rel="alternate"> 标签,以及 WordPress 的 /feed/ 路径来自动发现 RSS 源。如果根路径找不到,还会尝试 /blog 子路径。请注意,后几者会更加耗时,因此最好直接使用对应地址。
- 框架: Next.js (App Router)
- 数据库: Neon Postgres + Drizzle ORM
- 部署: Vercel (Serverless)
- 定时任务: Vercel Cron Jobs (每小时)
- 协议: ActivityPub + HTTP Signatures
生成 RSA 密钥对(用于 ActivityPub HTTP Signature 签名):
openssl genpkey -algorithm RSA -out private.pem
openssl rsa -pubout -in private.pem -out public.pemnpm i -g vercel
vercel link通过 Vercel Marketplace 安装 Neon,会自动注入 DATABASE_URL 环境变量:
vercel integration add neon# 服务域名(不含 https://)
vercel env add SERVER_HOSTNAME
# 输入: keyfeeder.yourdomain.com
# RSA 私钥(Base64 编码)
echo "$(base64 -w 0 private.pem)" | vercel env add --sensitive RSA_PRIVATE_KEY_BASE64 production
# RSA 公钥(Base64 编码)
echo "$(base64 -w 0 public.pem)" | vercel env add RSA_PUBLIC_KEY_BASE64 production
echo "$(base64 -w 0 public.pem)" | vercel env add RSA_PUBLIC_KEY_BASE64 development
echo "$(base64 -w 0 public.pem)" | vercel env add RSA_PRIVATE_KEY_BASE64 preview
# Cron 请求验证密钥(随机字符串)
vercel env add CRON_SECRET# 先拉取环境变量到本地
vercel env pull .env.local
# 推送表结构到 Neon
pnpm exec dotenv -e .env.local -- pnpm exec drizzle-kit pushvercel deploy --prodvercel domains add keyfeeder.yourdomain.com| 变量 | 说明 | 来源 |
|---|---|---|
DATABASE_URL |
Neon Postgres 连接字符串 | Marketplace 自动注入 |
SERVER_HOSTNAME |
服务域名 | 手动设置 |
RSA_PUBLIC_KEY_BASE64 |
RSA 公钥 PEM(Base64 编码) | 手动设置 |
RSA_PRIVATE_KEY_BASE64 |
RSA 私钥 PEM(Base64 编码) | 手动设置 |
CRON_SECRET |
Cron 验证密钥 | 手动设置 |
pnpm install确保已经完成了上面「部署到 Vercel」的步骤 1-4,然后:
vercel env pull .env.local这会把 Vercel 上的环境变量(包括 DATABASE_URL)拉到本地 .env.local 文件。
pnpm exec dotenv -e .env.local -- pnpm exec drizzle-kit pushpnpm dev服务启动在 http://localhost:3000。
WebFinger 查询:
curl "http://localhost:3000/.well-known/webfinger?resource=acct:xkcd.com@localhost:3000"ActivityPub 用户档案:
curl -H "Accept: application/activity+json" http://localhost:3000/xkcd.com手动触发 Cron(拉取 RSS 并推送):
curl -H "Authorization: Bearer YOUR_CRON_SECRET" http://localhost:3000/api/cron/fetch-feeds# 查看数据库结构
pnpm exec dotenv -e .env.local -- pnpm exec drizzle-kit studio
# 生成迁移文件
pnpm exec dotenv -e .env.local -- pnpm exec drizzle-kit generate
# 应用迁移
pnpm exec dotenv -e .env.local -- pnpm exec drizzle-kit migratesrc/
├── app/
│ ├── .well-known/webfinger/route.ts # WebFinger 端点
│ ├── [hostname]/route.ts # ActivityPub Person 档案
│ ├── [hostname]/inbox/route.ts # Follow/Unfollow 处理
│ ├── api/cron/fetch-feeds/route.ts # 定时 RSS 拉取
│ ├── layout.tsx
│ └── page.tsx # 着陆页
├── lib/
│ ├── db.ts # Neon + Drizzle 客户端
│ ├── env.ts # 环境变量
│ ├── activitypub/
│ │ ├── types.ts # Zod schemas
│ │ ├── send.ts # HTTP Signature 签名
│ │ └── accept.ts # Accept activity
│ ├── feed/
│ │ ├── fetch-feed.ts # RSS 解析
│ │ ├── fetch-url-info.ts # RSS URL 发现 + 缓存
│ │ └── fetch-and-send.ts # Cron 核心逻辑
│ ├── xml-utils.ts # XML 工具
│ └── parse-domain.ts # 用户名 → 域名/路径 解析
├── drizzle/
│ └── schema.ts # 数据库 Schema
MIT