librqbit replaces the vendored BitTorrent 4.2.1 tree. Torrents download in place because seeding serves the files it downloaded, so the planned stage-then-move would have broken it. The stall budget now also covers magnet metadata resolution, which otherwise never returns against a dead swarm and wedged the scan. Adds add/rm/import/export, tracing setup, systemd units and README. A successful swarm download is unverified: no reachable peers here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPyeapneuXrCdojsaiXGbe
ipodderx-rs
A headless podcatcher: scans RSS/Atom feeds, downloads enclosures (HTTP and BitTorrent), files them into per-feed folders, and reaps old episodes to stay under a disk quota. Runs as a one-shot CLI or as a daemon with a Unix-socket JSON event stream for a UI to attach to.
Lineage
This is a modern Rust rewrite of ipodderx-core, the Python 2 engine behind iPodderX (2004-2008, Ray Slakinski & August Trometer), which was open-sourced under the MIT License in 2010.
What carries over: the feed scan and TTL handling, GUID/URL dedupe, per-feed and per-date download folders, keyword filters, the explicit-content filter, torrent enclosures, and "SmartSpace" -- the oldest-first disk quota reaper.
What does not: iTunes and iPhoto export via AppleScript, text-to-speech enclosures, the Windows
WMP/COM paths, XML plists and Python pickles for state, the directory.iPodderX.com survey ping,
3DES-encrypted preferences, and the printMSG stdout protocol (replaced by a JSON-lines socket).
Quickstart
cargo install --path .
ipx add https://atp.fm/rss # names the feed from its own title
ipx list
ipx fetch # scan now
ipx daemon # or run continuously, honouring each feed's <ttl>
Config lives at ~/.config/ipx/config.toml (mode 0600, since it may hold feed passwords);
state at ~/.local/share/ipx/state.db. Override with IPX_CONFIG and IPX_DATA_DIR.
Set IPX_LOG=ipx=debug for verbose logging on stderr.
Commands
| command | what it does |
|---|---|
ipx add <url> [--folder X] [--keywords a,b] |
subscribe; the id comes from the feed title |
ipx rm <feed> |
unsubscribe; downloads and history are kept |
ipx list / ipx status |
subscriptions and their state |
ipx fetch [FEED] [--force] |
scan; --force ignores the TTL |
ipx reap [--dry-run] |
run retention now |
ipx import/export <file.opml> |
move subscriptions in or out |
ipx daemon |
scheduler plus the control socket |
Any command with a wire form probes the socket first: if a daemon is running it does the work,
and the CLI just renders the events it streams back. --local forces in-process execution.
Configuration
[general]
download_dir = "~/Podcasts"
socket = "/run/user/1000/ipx.sock" # default: $XDG_RUNTIME_DIR/ipx.sock
interval_mins = 60 # default poll; a feed's own <ttl> wins when longer
organize = "feed" # "feed" | "date"
max_total_gb = 50 # 0 = unlimited
max_age_days = 30 # 0 = keep forever
[torrent]
enabled = true
seed_ratio = 1.0 # stop seeding at this ratio ...
seed_time_mins = 60 # ... or after this long, whichever comes first
port_range = "6881-6889"
stall_mins = 30 # give up on a torrent making no progress
[feeds.atp]
url = "https://atp.fm/rss"
folder = "Accidental Tech Podcast" # default: the feed title
keywords = ["deep dive"] # OR across keywords, AND within one
allow_explicit = false
auto_download = true
max_new_per_check = 3 # the rest wait for the next scan
username = "ray" # optional HTTP basic auth
password_env = "IPX_ATP_PASS" # or a literal `password`
Retention keeps files that are flagged in the database, and deletes read episodes before unread
ones, oldest first.
Socket protocol
Newline-delimited JSON over a Unix socket, both directions.
$ printf '{"cmd":"fetch","force":true}\n' | socat - UNIX-CONNECT:$XDG_RUNTIME_DIR/ipx.sock
{"ev":"feed_start","feed":"atp"}
{"ev":"progress","feed":"atp","url":"...","file":"ep1.mp3","done":8192,"total":3000000}
{"ev":"download_done","feed":"atp","url":"...","path":"...","bytes":3000000}
{"ev":"feed_done","feed":"atp","new":1,"downloaded":1,"failed":0,"torrents":0}
{"ev":"scan_done","feeds":1}
Commands: fetch (optional feed, force), reap (optional dry_run), status.
Events: feed_start, feed_skip, feed_done, feed_error, progress, download_done,
download_error, torrent_deferred, reaped, reap_done, scan_done, status, error.
scan_done, reap_done and status are terminal -- a client that asked for work stops there.
Progress is throttled to whole percents. The stream is a broadcast, so a client attached to a busy daemon also sees that daemon's other work.
Running it as a service
contrib/ has a systemd user unit for the daemon, and a timer plus one-shot service if you would
rather run periodic scans with no daemon (in which case there is no socket for a UI to attach to).
License
MIT. See LICENSE.