Instagram at /ig/, Facebook at /fb/, and one shared failure rule - #159
Merged
Conversation
Two more namespaces, and they cost very different amounts — which is the useful part of this change rather than an aside. Instagram is X's shape: no feeds, collected through the RSSHub daemon already running in the poller, mirrored here. It was a canonical.js, a fetch.js and a route, because the namespace machinery already existed. Facebook is not. Three doors, measured rather than assumed: - facebook.com/feeds/page.php?format=rss20 answers 404 (removed) - mbasic.facebook.com/<page> answers 200 with a login wall - RSSHub carries a thousand namespaces and no Facebook one The only remaining route is Meta's Graph API, which returns a Page's posts only to somebody who ADMINISTERS that Page — reading a stranger's public Page needs a permission Meta grants rarely and only after review. So /fb/ takes no open submissions: a Page appears once its operator connects it via FB_PAGE_TOKENS, and an unconnected Page is told plainly that it is not collectable rather than shown a button that does nothing. Nothing here drives a logged-in session against that login wall. The rule from #157 is now shared rather than per-platform. src/failure.js owns "only a source that does not exist may count against the source", and X was refactored onto it rather than Instagram and Facebook being written against a copy. Its 60 tests passed unchanged, which is the evidence the refactor was behaviour-identical. Two bugs the tests caught, both mine: - identify.js said in a comment that X is tried before Instagram and had Instagram first, so a bare @handle resolved to Instagram - the Facebook collector took its display name from the ref, which is lowercased because it is an identity — so every item title read "somepage:" instead of "SomePage:". Display now comes from feed_url, which kept the original casing; identity still comes from the ref Also: an uncrawled social row now renders its canonical name instead of whatever the OPML import called it. 50,026 subreddits are imported and a few hundred crawled, so /r/programming was headed "reddit.com". A render decision, not a rewrite of stored titles — the crawler owns those. 1,345 tests across 12 packages, all green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q6QEgpuS4MLamogXtr2ZX6
ralyodio
added a commit
that referenced
this pull request
Aug 29, 2026
#159 gave a social row its canonical name in the RSS channel title and in the page metadata, and left the thing a reader actually looks at — the <h1> — saying "reddit.com". The cause was one line away from the change: `FEED_COLS` does not select `social_ref`, so `feedBySlug` has never returned it and the feed page could not tell a social row from any other. The RSS path worked only because it goes through `feedBySocialRef`, which is `select *`. So the columns join FEED_COLS, and the page uses them. Both addresses for a row now agree — /r/programming and /r-programming show the same name — which they did not before, since only one of them knew what it was holding. Verified against a seeded database rather than by reading: a row titled "reddit.com", exactly as the OPML import leaves them, now renders "r/programming" in the heading, the <title> and the feed. Claude-Session: https://claude.ai/code/session_01Q6QEgpuS4MLamogXtr2ZX6 Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two more namespaces, and they cost very different amounts — which is the useful part of this change rather than an aside.
Instagram was cheap
X's shape: no feeds, collected through the RSSHub daemon already running inside the poller, mirrored here. A
canonical.js, afetch.jsand a route, because the namespace machinery already existed./ig/<handle>and/ig/tag/<tag>, four formats each.It needs far less than X did: X needed a normaliser because a repost has no text of its own and a quote is two posts in one item. An Instagram post is a caption and some pictures, so RSSHub's rendering is the item — the only thing changed is the identity, re-keyed from the bridge's guid onto the post shortcode so swapping bridges doesn't mark every account unread.
Facebook is not possible the way you'd want
Three doors, measured on 2026-08-29 rather than assumed:
facebook.com/feeds/page.php?format=rss20mbasic.facebook.com/<page>unauthenticatedinstagramexists)The only remaining route is Meta's Graph API, and it returns a Page's posts only to somebody who administers that Page. Reading a stranger's public Page needs
Page Public Content Access— App Review plus business verification, granted rarely.So
/fb/takes no open submissions. A Page appears when its operator connects it viaFB_PAGE_TOKENS, and an unconnected Page is told plainly that it is not collectable rather than shown an add button that would quietly do nothing. Nothing here drives a logged-in session against that login wall — it breaks constantly, it is against Meta's terms, and it would risk an account to serve a directory nobody pays for.The #157 rule is now shared, not per-platform
src/failure.jsowns only a source that does not exist may count against the source. X was refactored onto it rather than the new platforms being written against a copy — and its 60 existing tests passed unchanged, which is the evidence the refactor was behaviour-identical.Two bugs the tests caught, both mine
identify.jsclaimed in a comment that X is tried before Instagram, and had Instagram first — so a bare@handleresolved to Instagram. Exactly the comment-says-one-thing-code-does-another class I flagged in Only a missing account may count against an X source's health #157.somepage:instead ofSomePage:. Display now comes fromfeed_url, which kept the original casing; identity still comes from the ref.Also: the title polish
An uncrawled social row now renders its canonical name instead of whatever the OPML import called it. 50,026 subreddits are imported and a few hundred crawled, so
/r/programmingwas headed "reddit.com". A render decision, not a rewrite of stored titles — the crawler owns those, and overwriting would fightmarkCrawlSuccesson every subsequent crawl.1,345 tests across 12 packages, all green. Build clean, all four namespaces registering.
Neither new platform collects anything until credentials exist (
IG_COOKIE,FB_PAGE_TOKENS) — same as X today.🤖 Generated with Claude Code
https://claude.ai/code/session_01Q6QEgpuS4MLamogXtr2ZX6