Reported from a real subscription: an episode failed to play with "the format of the data has not been recognized" on a cdn.netzpolitik.org/….jpg URL, 272428 bytes — exactly the length the feed declared for a JPEG. audio_url preferred an audio-typed enclosure but then fell back to *any* media url, and a WordPress blog feed attaches each post's featured image as an <enclosure>, structurally identical to a podcast enclosure apart from type="image/jpeg". https://netzpolitik.org/feed/ is 25 items, 25 JPEG enclosures, and no audio reference of any kind, so every post became an episode that could not play. An enclosure is now accepted when its type says audio, when the feed omits the type (plenty of hand-rolled feeds do), or when a generic application/octet-stream is backed by an audio file extension — and rejected otherwise, so images and video are skipped. Audio-typed still wins, so a show that publishes both plays as audio. A feed with entries and no audio now says so ("this looks like a blog feed rather than a podcast feed"): an empty listing explains nothing on its own. No URL in the message — a feed URL is a credential. Also raises DEFAULT_MAX_FEED_BYTES from 8 to 32 MiB. Logbuch:Netzpolitik, 559 episodes in, is a healthy 6.8 MiB: feeds carry their whole back catalogue with full show notes, so the first cap would have started refusing real feeds within a year or two. Still bounded, still enforced while reading rather than after, still lowerable via max_feed_bytes. Verified: 30 rssdy tests (5 new, over feed-rs's real entry shapes: image-only, mixed image+audio, untyped, generic-with-extension, generic-without, video), and both live feeds — netzpolitik.org/feed/ now yields 0 episodes with the warning, logbuch-netzpolitik.de/feed/mp3 (-> feeds.metaebene.me/lnp/mp3) yields 559 audio/mpeg episodes whose HH:MM:SS durations parse. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| src | ||
| Cargo.toml | ||
| README.md | ||
README.md
rssdy — the RSS podcast provider
Mounts the podcast feeds you subscribe to at /rss in the crabidy
library. Works with plain public feeds and with premium per-subscriber
URLs, and always shows the newest episodes.
Logging in
There is no login. A feed is either public or its URL is the credential — a paid podcast gives you a URL with a token in it:
https://feeds.economist.com/v1/rss/the-economist-podcasts/f74365b0-…
Anyone holding that URL has your subscription, so the provider treats it as a
secret: it is redacted from Debug, never logged, and never put in a
library path. Paths carry a slug of the subscription name and a hash of the
episode id instead — paths are displayed, logged, and persisted into saved
queues and bookmarks, so a URL in one would leak everywhere.
Keep rss.toml private; it holds the URLs in cleartext, like every other
provider credential.
Subscribing
Either edit the config (below), or from a client: press % on /rss, paste
the feed URL, and the provider fetches it once, names the subscription from
the feed's own title, and writes it into rss.toml. e renames a
subscription (its path changes with the name), d unsubscribes — that only
removes the config entry, never audio you captured from it.
How it works
/rss
├── <subscription> one node per feed, newest episodes first
│ └── <episode> a track; audio is the feed's enclosure URL
└── …
A subscription is queueable and downloadable, so you can queue or W-capture
a whole feed. Episodes stream directly from the enclosure URL — no sidecar,
no helper binary.
Nothing is cached. Every visit to a subscription fetches the feed, so an episode published a minute ago is there. One memo exists purely so that listing a feed and then queueing its 40 episodes costs one fetch rather than 41: it is written by listings and read only when resolving a track, so it can never make a listing stale.
Episode paths are blake3(guid)[..16], which is stable as long as the
publisher keeps its guids stable. Two consequences worth knowing:
- A bookmark (
w) to an episode that has since aged out of the feed cannot resolve — there is nothing left to look up. Capture (W) what you want to keep. - A publisher that regenerates guids on every fetch invalidates bookmarks. Nothing can be done about that from this side.
Configuration — ~/.config/crabidy/rss.toml
# One table per subscription. `name` is yours and decides the path slug;
# duplicates get a numeric suffix.
[[feeds]]
name = "The Economist Podcasts"
url = "https://feeds.economist.com/v1/rss/…"
[[feeds]]
name = "Cautionary Tales"
url = "https://feeds.example.org/cautionary-tales"
# Optional, defaults shown.
# episodes_per_feed = 200 # episodes listed per feed
# call_timeout_secs = 30 # per-request timeout
# max_feed_bytes = 33554432 # 32 MiB cap on a feed body
A feed entry with no url is skipped with a warning. No feeds at all is fine:
/rss mounts empty and you can % into it.
Notes
- Feeds are read as RSS 2.0/1.0/0.x, Atom, or JSON Feed via
feed-rs. itunes:durationis parsed here rather than taken fromfeed-rs, which reads it as NPT — a format with noMM:SSform, so53:25came back as 53 seconds.S,MM:SSandHH:MM:SSall work now.- Build the server without the
rsscargo feature to leave this provider out of the binary entirely.