Patreon creators split into their shows; filters follow settings
A Patreon token pasted into Add feed, or a creator link without &show=, becomes a folder of that creator's shows, found through Patreon's web API and kept in step like a subscribed OPML (sync_group, split out of sync_opml). A creator already read as one feed is split too: each show takes over the files and read state it held (Db::adopt). A creator with one show stays a plain feed. Filter verdicts are judged again every scan, so turning on Allow explicit brings skipped items back. Add feed has an explicit box. Feeds in a group follow your settings on the group, as its dialog said. A new feed no longer takes the id of a removed one at a different URL and shows its old items. See CHANGELOG.md [Unreleased] and docs/history.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wi22VSVrkAvqNj61eqHsm9
This commit is contained in:
@@ -28,12 +28,15 @@ The page is compiled in, so **editing `web/index.html` needs a rebuild**.
|
||||
1. Skip the feed unless `last_checked + max(schedule, ttl)` has passed (`--force` ignores this).
|
||||
2. Conditional GET with the stored `ETag` / `Last-Modified`. `304` ends it there.
|
||||
3. Sniff the body: RSS, then Atom, then OPML. An OPML is a live subscription — its feeds are
|
||||
re-derived into the database each scan, never written to config.toml.
|
||||
re-derived into the database each scan, never written to config.toml. A Patreon creator link
|
||||
(a token, no `show=`) with more than one show is treated the same way, before any fetch: its
|
||||
shows come from Patreon's web API and each becomes a derived feed.
|
||||
4. Record entries. A changed title or description flips the item back to unread.
|
||||
5. Record enclosures. `enclosures.url` is `UNIQUE`, which is the dedupe key and subsumes the
|
||||
original's `history.dat` pickle: a reaped file keeps its row so it is never fetched twice.
|
||||
6. Apply the merged policy (see [users.md](users.md)) and mark anything rejected as `skipped` with
|
||||
a reason.
|
||||
a reason. What a filter skipped is judged again every scan, so a change of settings brings it
|
||||
back. A feed in a group takes your settings on the group for anything you have not set on it.
|
||||
7. Download what is still pending, newest first, up to the per-scan cap. A `.torrent` body goes to
|
||||
the torrent path whatever its advertised type; an HTML body is a failed download — a login wall
|
||||
or an error page — and is deleted.
|
||||
|
||||
@@ -6,6 +6,68 @@ reasoning lives. New write-ups go at the top.
|
||||
|
||||
See [README.md](../README.md) for what the thing is.
|
||||
|
||||
## 2026-09-11 — A Patreon creator is a list of shows
|
||||
|
||||
Ray asked whether ipx could sync with Patreon. Not in full. The documented API (v2, the
|
||||
`identity.memberships` scope) lists the creators you back and whether each has a feed (`has_rss`),
|
||||
but no resource carries the `auth` token that makes a feed URL work. That token only comes from the
|
||||
creator's page. It is also one per membership, not one per account: techpod's differs from Glass
|
||||
Cannon's, so no single token finds everything you back.
|
||||
|
||||
What does work is one creator at a time, which is what Ray wanted for Glass Cannon and its 33 shows:
|
||||
|
||||
- `patreon.com/rss?auth=<token>`, with no creator named, returns that token's creator. Its self link,
|
||||
about 660 bytes in, gives the campaign by number (`/rss/369921`). Patreon ignores `Range` here, so
|
||||
ipx reads the stream until the number appears and hangs up, instead of taking all 2.8 MB.
|
||||
- A show's `show=` number is a Patreon collection. Asked anonymously, the collection listing
|
||||
(`/api/collection?filter[campaign_id]=`) and a post's `collections` both hide patron-only ones: you
|
||||
get "FAQ". `/api/campaigns/<id>?include=shows` lists every show, anonymously, in one response.
|
||||
- Every spelling works: `rss/glasscannon?auth=…&show=N`, `rss/369921?…` and `rss?auth=…&show=N` all
|
||||
serve the same 131 items. Enclosure URLs are the same in the creator feed and the show feed, and
|
||||
stable between fetches.
|
||||
|
||||
That last point shaped the design. `enclosures.url` is unique, so whichever feed is scanned first owns
|
||||
the file. The first cut only asked a creator for its shows while it had no entries of its own, so that a
|
||||
creator already read as a plain feed, holding every show's episodes, would never be split into shows
|
||||
that came up empty. Within the hour that was the wrong call: Glass Cannon had gone into production on
|
||||
the build before this one, been read as one feed of 2,385 items, and the rule kept it that way. Finding
|
||||
anything in that heap was the problem Ray wanted solved.
|
||||
|
||||
So a creator with more than one show is always a group, run through the same sync as an OPML
|
||||
(`sync_group`, split out of `sync_opml`). When it becomes one, its items are cleared and each show
|
||||
takes over the enclosures the creator holds as the show lists them (`Db::adopt`), downloaded files and
|
||||
everyone's read state included. One show leaves it a plain feed, which is what techpod already was. If
|
||||
the shows cannot be listed, a creator already split fails the scan rather than being read as one heap;
|
||||
one that never was is read as one feed until they can be. An answer without a `shows` list is an error,
|
||||
not "no shows".
|
||||
|
||||
**Filter verdicts follow the settings.** Ray also reported that turning on Allow explicit and
|
||||
rescanning brought nothing back. An item was judged once, when first seen, and `skipped` was final. The
|
||||
2026-09-10 entry below saw it coming ("worth a `ipx retry <feed>` command if this bites"). It bit:
|
||||
2,166 Glass Cannon items and all 88 of Shadowdark's sat at `skipped: explicit` with the setting on.
|
||||
Every scan now runs the filters again over what they skipped (not over `torrents disabled`, which is not
|
||||
a filter's call) and requeues what they now let through. Only that direction: a queued item is never
|
||||
pulled back, because Download latest and a manual download both work by queueing.
|
||||
|
||||
**Two gaps beside it.** Add feed had no explicit box, so every new feed's first scan skipped all its
|
||||
explicit items; it has one now, stored on your subscription like the feed dialog's. And a feed inside a
|
||||
group ignored your settings on the group, though the group's dialog said they were inherited: settings
|
||||
live on each person's subscription, and nothing read the group's. `Db::subscribers` now fills what you
|
||||
have not set on the feed from your subscription to the group, and the feed list shows the same.
|
||||
|
||||
**A name that was already used.** Replaying the split on a copy of the production database left one
|
||||
show with a Supercast episode in it. "Glass Cannon Live! Ascension | Pathfinder 2E" slugs to
|
||||
`glass-cannon-live-ascension-pathfinder-2`, the id of a Supercast feed of the same show that had been
|
||||
removed. Removing a feed keeps its rows on purpose, so that re-adding it does not fetch the back
|
||||
catalogue again, but choosing a new id only checked config.toml and derived feeds. The Patreon show took
|
||||
the old id and everything still filed under it. An id is now also taken when the database has a feed by
|
||||
that id at a different URL; the same URL may still have it back, which is the re-add case.
|
||||
|
||||
Shows already added by hand are matched by token and show number, not by exact URL (`same_feed`), so a
|
||||
bare token does not add Get in the Trunk and Shadowdark a second time under another spelling.
|
||||
|
||||
The show listing is Patreon's own undocumented web API. If it changes, only finding new shows stops.
|
||||
|
||||
## 2026-09-11 — One meaning per icon, sortable columns, and one player
|
||||
|
||||
Ray asked for a pass over the whole UI: consistent icons, and buttons placed next to what they act
|
||||
|
||||
Reference in New Issue
Block a user