# RSS Reader

ANetBBS ships a built-in RSS / Atom feed reader. Users browse it from
the web (`Tools → RSS Reader`) or the BBS terminal (`Main BBS Menu → R`).
Sysops manage feeds at `Admin → Subsystems → RSS Feeds`.

A default feed (X-News at `https://x-bit.org/rss/rss.xml`) is seeded
on first run so a fresh install ships with at least one feed populated.

## How users see it

### Web
- **Tools → RSS Reader** lists every active feed with per-user unread
  counts.
- **All Feeds (River)** is a combined newest-first stream across every
  feed.
- Click a feed → paginated item list. New items show in **bold** with
  a **NEW** badge.
- Click an item → full content view. Marks as read.
- "Mark all read" button per-feed and global.

### Terminal (telnet/SSH)
- Press `R` from the Main BBS Menu.
- Feed list shows item count and unread count per feed.
- `A` = combined river of all feeds.
- Numeric choice = open that feed.
- Inside a feed: numeric = read item, `N`/`P` paginate, `Q` returns.
- `*` marker = unread; reading clears it.

## How the poller works

Background daemon thread (`anetbbs.rss.poller`) wakes on
`RSS_POLL_INTERVAL` (default 1800s = 30 min) and refreshes every active
feed via Python's `feedparser`. Items are deduped per-feed by GUID
(falls back to link or title if the feed doesn't supply a GUID).
Failed fetches set `feed.last_error` so the sysop sees red badges in
the admin list.

To refresh manually, hit the circular-arrow icon next to a feed in
`/admin/rss/`.

## How sysops add a feed

`Admin → Subsystems → RSS Feeds → Add Feed`:

| Field          | Notes |
|----------------|-------|
| **Name**       | Display name shown to users (e.g., `X-News`) |
| **Category**   | Free text — `news`, `tech`, `scene`, `general`. Used for grouping in future versions; currently informational. |
| **Feed URL**   | RSS 2.0 / Atom / RSS 1.0 — feedparser handles all of them. |
| **Site URL**   | Optional human-facing homepage of the feed source. |
| **Description**| One-liner shown on the feed picker. |
| **Sort order** | Lower = appears first. |
| **Active**     | Disable to hide a feed without deleting its history. |

Saving a new feed triggers an immediate fetch so users see items right
away (no waiting for the next 30-min poll tick).

Deleting a feed cascades — all its items and per-user read state are
removed.

## Tunables

- `RSS_POLL_INTERVAL` env var — seconds between polls (default 1800,
  minimum 60). Set tighter on a fast-moving feed; relax on stable
  feeds to be a polite citizen.

## Storage

| Table | What it holds |
|-------|---------------|
| `rss_feeds` | Sysop-configured feeds (URL, name, category, last fetch state). |
| `rss_items` | Article/post records, deduped per-feed by GUID. |
| `rss_read_status` | Per-user read markers (presence = read, absence = unread). |

Auto-created on app startup via the lightweight migration sweep.
No manual SQL needed.

## Future / open questions

- **Web autorefresh** — the user has to reload the page to see new
  items between poll ticks. A SocketIO push of "new item arrived"
  would be a nice touch.
- **Feed groupings by category** — currently the category field is
  stored but not surfaced in the UI.
- **Per-user feed subscriptions** — right now every user sees every
  active feed. A future version could let users hide/show specific
  feeds.
