An RSS-to-Mastodon bridge. Each RSS feed gets its own bot account on the fediverse, discoverable via WebFinger (e.g. @hackernews@robot.villas). Fediverse users can follow any bot from Mastodon or other ActivityPub-compatible platforms and receive new posts in their timeline.
Bots, follows, and relays are configured in feeds.yml:
bots:
hackernews:
feed_url: "https://news.ycombinator.com/rss"
display_name: "Hacker News"
summary: "Top stories from Hacker News"
profile_photo: "https://news.ycombinator.com/y18.svg"
default_hashtags:
- Tech
- News
follows:
- "@someone@mastodon.social"
relays:
- https://relay.toot.io/actorEach key under bots becomes the bot's fediverse username. Usernames must be lowercase alphanumeric or underscores (validated at startup via Zod).
| Field | Required | Description |
|---|---|---|
feed_url |
Yes | URL of the RSS/Atom feed |
display_name |
Yes | Display name shown on the profile (max 100 chars) |
summary |
Yes | Short bio/description (max 500 chars) |
profile_photo |
No | URL to an avatar image |
default_hashtags |
No | Up to 3 default hashtags (no leading #) |
The follows list contains fediverse handles (@user@instance) that every bot will send a Follow to on startup.
The relays list contains ActivityPub relay actor URLs. The server subscribes to these on startup so posts reach a wider audience.
| Variable | Description | Default |
|---|---|---|
DATABASE_URL |
PostgreSQL connection string | (required) |
DOMAIN |
Public domain for ActivityPub IDs and WebFinger | (required) |
PORT |
HTTP server port | 3000 |
POLL_INTERVAL_MS |
Milliseconds between RSS poll cycles | 300000 (5 min) |
BLOCKED_INSTANCES |
Comma-separated hostnames to reject Follows from | (none) |
GEMINI_API_KEY |
Google Gemini API key for AI hashtag suggestions | (none) |
GEMINI_PROJECT |
GCP project for Vertex AI (alternative to key) | (none) |
GEMINI_LOCATION |
GCP region for Vertex AI | us-central1 |
GEMINI_MODEL |
Gemini model name | gemini-2.5-flash |
nvm use
yarn install
export DATABASE_URL="postgres://user:password@localhost:5432/robot_villas"
export DOMAIN="robot.villas"
yarn devA running PostgreSQL instance is required. Migrations are applied automatically on startup.
| Component | Technology |
|---|---|
| Runtime | TypeScript 6 / Node.js 24+ |
| Web Framework | Next.js 15 (App Router, standalone output) |
| UI | React 19, Tailwind CSS 4, DaisyUI 5 |
| ActivityPub | Fedify v2 (@fedify/fedify, @fedify/vocab, @fedify/next) |
| KV & Message Queue | @fedify/postgres |
| Database | PostgreSQL via Drizzle ORM |
| RSS Parsing | rss-parser |
| AI Hashtags | Google Gemini via @google/genai (optional) |
| Config Validation | Zod v4 |
| Testing | Vitest v4 |
src/
app/ Next.js App Router pages and API routes
page.tsx Homepage listing all bots
bot/[username]/ Bot profile, followers, and following pages
stats/ Global statistics dashboard
status/ System status page
healthcheck/ Health check endpoint
nodeinfo/ NodeInfo protocol endpoint
users/ WebFinger endpoint
components/ Shared React components
lib/
config.ts Zod-validated feeds.yml parser
schema.ts Drizzle ORM table definitions
db.ts Typed data access functions
globals.ts Singleton initialization (DB, federation, config)
rss.ts RSS/Atom feed fetcher and normalizer
federation.ts Fedify federation, actor dispatchers, inbox listeners
publisher.ts Dedup + publish new entries as Create(Note) activities
poller.ts Interval-based polling loop
hashtags.ts Hashtag extraction, normalization, optional Gemini AI tagging
logging.ts LogTape logging configuration
__tests__/ Unit and integration tests
middleware.ts Fedify/ActivityPub request routing
instrumentation.ts Server startup: migrations, queue, poller
drizzle/ Generated SQL migration files (committed to git)
feeds.yml Bot, follow, and relay configuration
The Dockerfile uses a multi-stage build: Next.js is compiled in the builder stage, then .next/standalone and .next/static are copied into the final node:25-slim image along with Drizzle migrations. feeds.yml is baked into the image — mount it as a volume to override.
MIT