feat: reader-compat batch — JSON Feed, OPML export, conditional GET, dedup
Batch of four reader-facing improvements (TODO "Compat lecteurs + dedup"): - JSON Feed at /json/:feedId (feed lib .json1()); all formats cross-link - OPML export at /admin/opml (admin-protected; the registry lists every feed URL, so it must not be public) - Conditional GET on /rss + /atom: strong ETag + Last-Modified, 304 on If-None-Match/If-Modified-Since, validators shared via http-cache.ts - Duplicate-send dedup in ingestion: match by Message-ID, fall back to a SHA-256 of normalized subject+content; a duplicate is a no-op and bumps the new emails_deduplicated counter (status page + /api/v1/stats) 429 tests green, tsc clean, build dry-run OK. Docs (README/CLAUDE/TODO + landing cards) updated. Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
1 parent
334713fbd9
commit
0abd5f306c
23 files changed
+1015
-11
No files matched your search
@@ -22,6 +22,10 @@ kill-the-news keeps the same workflow while avoiding shared domains and shared d
|
||||
- Optional per-feed sender allowlist (`[email protected]` or `domain.com`)
|
||||
- RSS generation on demand (`/rss/:feedId`)
|
||||
- Atom feed at `/atom/:feedId`
|
||||
- JSON Feed at `/json/:feedId` (natively consumed by NetNewsWire, Reeder, NewsBlur, Feedly)
|
||||
- Bandwidth-friendly polling: RSS/Atom send a strong `ETag` + `Last-Modified` and answer `304 Not Modified` on conditional requests
|
||||
- Duplicate-send dedup: a newsletter delivered twice (matched by `Message-ID`, then by a content hash) is stored once
|
||||
- OPML export of all feeds at `/admin/opml` (admin-protected) for one-click bulk import into any reader
|
||||
- Reader-friendly output: relative links/images absolutized to the sender's site, lazy-loaded images promoted (`data-src` → `src`), plain-text feed titles, and XML-illegal control characters stripped so feeds parse in strict readers
|
||||
- Per-feed favicon derived from the last sender's domain (`/favicon/:feedId`), cached and shown in feeds + admin
|
||||
- Automatic RFC 8058 one-click unsubscribe when a feed is deleted — stops newsletters from mailing the now-dead address
|
||||
@@ -51,8 +55,10 @@ Main routes:
|
||||
|
||||
- `src/lib/cloudflare-email.ts`: Cloudflare Email Workers ingestion
|
||||
- `src/routes/inbound.ts`: ForwardEmail webhook ingestion
|
||||
- `src/routes/rss.ts`: RSS rendering
|
||||
- `src/routes/atom.ts`: Atom feed rendering
|
||||
- `src/routes/rss.ts`: RSS rendering (with conditional-GET / ETag support)
|
||||
- `src/routes/atom.ts`: Atom feed rendering (with conditional-GET / ETag support)
|
||||
- `src/routes/json.ts`: JSON Feed rendering
|
||||
- `src/routes/opml.ts`: OPML export of all feeds (admin-protected, mounted at `/admin/opml`)
|
||||
- `src/routes/files.ts`: attachment file serving from R2
|
||||
- `src/routes/admin.tsx`: admin UI + feed CRUD
|
||||
- `src/routes/api/`: versioned REST API + OpenAPI spec/docs (`/api/v1/*`, `/api/openapi.json`, `/api/docs`)
|
||||
|
||||
Reference in new issue
Block a user