sifa-rss-import #
Import a site's articles (via its RSS/Atom feeds) into the Publications section of a Sifa ID profile, with deduplication of what already exists.
Under the hood: Sifa is an AppView that reads the records from a user's
atproto PDS. This tool therefore writes id.sifa.profile.publication records
straight into your repo — they then show up automatically on your Sifa profile.
Warning
Records written directly to your PDS may not be indexed by the Sifa AppView
yet. Sifa indexes via the firehose and has a known firehose-gap with no
reconcile backstop in place, so a bulk import can land valid records in your
PDS that never surface on your profile (getProfileView returns
publications: 0). This is a Sifa-side limitation, not a bug in this tool —
your data is correctly stored and owned by you. Tracking issue:
singi-labs/sifa-workspace#399.
How it works #
- Discovery — reads the
<link rel="alternate" type="application/rss+xml">tags from the site (fallback:/index.xml,/feed, … for Hugo & co). - Parsing — RSS 2.0 + Atom (
rss-parser). - Deduplication — lists the publications already present
(
com.atproto.repo.listRecords) and compares by normalized URL. - Writing — creates only the missing ones
(
com.atproto.repo.createRecord), with a concurrency limit and retries.
RSS → id.sifa.profile.publication mapping:
| RSS/Atom | Sifa field |
|---|---|
<title> |
title |
<link> |
url |
| feed title | publisher |
<description> |
description |
<pubDate> |
publishedAt (YYYY-MM-DD) |
| — | createdAt (now) |
Requirements #
- Node.js ≥ 20
- An account on an atproto PDS (your Sifa/Bluesky handle)
Install #
npm install
Authentication (two modes) #
Dev loopback mode — default, no hosting required. The OAuth client_id is a
loopback client (http://localhost?redirect_uri=...&scope=...): the
authorization server recognizes it without fetching any URL. This is the ideal
mode for personal use from your machine — nothing to drop on your site.
Hosted mode — for automation. atproto then requires the client_id to be a
public HTTPS URL serving client-metadata.json. See
Automation below.
1. Authorize (once) #
npm run login -- une-tasse-de.cafe
A browser opens for authorization; the callback comes back to
http://127.0.0.1:8765/callback. The session (refresh token + DPoP key) is
persisted in ~/.config/sifa-rss-import/sessions.json — subsequent runs refresh
without a browser. The command prints your DID to reuse.
2. Import #
# Preview without writing anything:
npm run import -- https://une-tasse-de.cafe --handle did:plc:hqnyog7skad6m4aejb2yujxy --dry-run
# For real:
npm run import -- https://une-tasse-de.cafe --handle did:plc:hqnyog7skad6m4aejb2yujxy
Options: --dry-run (writes nothing), --limit <n> (caps the number of
creations), --include <s> / --exclude <s> (repeatable URL filters, see
below). Re-running is idempotent: only new entries are created.
Filtering out non-article pages #
A site's root feed often aggregates standalone pages (an "about" page, a CV, an
archive index…) alongside real articles. Restrict the import with --include
(keep only URLs containing the substring) and/or --exclude (drop matching
URLs); both are repeatable and match anywhere in the URL:
# Keep only /blog/ and /expresso/ articles:
npm run import -- https://une-tasse-de.cafe --handle did:plc:hqnyog7skad6m4aejb2yujxy \
--include /blog/ --include /expresso/
# Or drop specific pages:
npm run import -- https://une-tasse-de.cafe --handle did:plc:hqnyog7skad6m4aejb2yujxy \
--exclude /cv/ --exclude /talks/
Scope #
The default requested scope is atproto transition:generic (works everywhere,
covers repo writes). The clean, granular scope is the id.sifa.authProfile
permission set (it grants exactly id.sifa.profile.* + id.sifa.graph.follow);
its support on atproto authorization servers is still rolling out. To try it:
SIFA_OAUTH_SCOPE="atproto include:id.sifa.authProfile" npm run login -- une-tasse-de.cafe
Automation (Tangled) #
For a recurring, non-interactive import, switch to hosted mode:
- Host
client-metadata.jsonat a public HTTPS URL — on your Hugo site, drop it intostatic/and deploy (→https://une-tasse-de.cafe/client-metadata.json). The file'sclient_idmust match that URL exactly. - Set
SIFA_CLIENT_ID=https://une-tasse-de.cafe/client-metadata.json: this enables hosted mode (loadsclient-metadata.json). - Run
loginonce in this mode to obtain a hosted session.
Since the refresh token is persisted, subsequent runs work without a browser. On
Tangled, use a pipeline (not a GitHub Action): inject
the contents of sessions.json as a secret (with SIFA_DATA_DIR pointing to a
directory rebuilt at run time) and run
npm run import -- <site> --handle <did>.
License #
MIT