Add Facebook
CI / Typecheck, test, build (pull_request) Successful in 29s

A shared Facebook link lands on a page that carries the whole post logged
out -- the caption, the author, the files, the dimensions -- in ScheduledServerJS
payloads that are plain JSON in ordinary script tags. What it does not carry
is only that post. A reel arrives with the next five reels of the feed
attached under viewer.lasso_blue_feed, a video with its related videos, and
every one of them has the same fields in the same shape as the real one.
Reading the first node with media on it gets a stranger's reel under someone
else's name, which is the same false attribution a lifted quote-post picture
used to be.

So nothing is read until the post has been picked out. partsOfPost matches the
id in the address against every node that names one; an address with no id in
it -- a pfbid permalink -- is matched on permalink_url instead. Only with
nothing to match on at all does it fall back to the route's query results,
which is still narrower than the whole payload: the page ships its entire
client configuration alongside the post, thousands of nodes carrying a name or
an id, and a plain search finds a video player setting long before it finds the
author.

An id that matches nothing is a failure rather than a best guess. Facebook
answers a link to something it no longer has by quietly serving something else
-- /watch/<id> for a video that is gone comes back as the Watch home page, feed
and all -- so the error card, which still carries the link and the copy button,
is the honest answer.

One post's pieces are spread across several payload blocks: a video post keeps
its files in one, its caption in another, its author's avatar in a third and
its timestamp in a fourth. Every claiming node is collected, not just the
first, and the author's gaps are filled only from nodes carrying the same id.
The author is whatever the payload calls the owner -- actors, owner,
video_owner, owner_as_page. `author` on a Facebook page means the author of a
comment, which sits right there in the same shape with a name and a picture of
its own.

Media is videoDeliveryLegacyFields.browser_native_hd_url with
preferred_thumbnail as its poster, photo_image or image for a picture, and
all_subattachments.nodes for a post of several -- which Facebook ships empty on
every single-picture post, so only a populated one is a carousel. The CDN is
signed and serves Range without asking for a referrer, but the assets are
proxied like everything else.

/share/{r,v,p,g} links are stubs, so the adapter follows one and hands back
where it landed: the share code says nothing about what it opens, and rdid,
share_url and fs are what the redirect leaves behind. m.facebook.com is a login
wall logged out, so the rewrite rule rebuilds on www.

Fixtures are real captures of a reel, a photo post and a video post, trimmed of
the DASH manifests and tracking blobs. The reel keeps its recommendations and
the video post keeps its comments, because those are the two things that have
to survive being read past.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-12 11:35:32 -03:00
co-authored by Claude Opus 5
parent fa07b43c1c
commit ab23c2b9fe
12 changed files with 8681 additions and 9 deletions
+19 -6
View File
@@ -2,11 +2,12 @@
Reads social posts back to you without the app.
Links to X, Threads, Instagram, TikTok, Bluesky and Reddit get shared constantly, and
opening one means an app interstitial, a login wall, a feed you didn't ask for, and a
pile of tracking. antisocial is the other half of a StopTheMadness rewrite rule: the link gets
redirected here, and you get the post — the media and the words — plus a badge saying
where it came from and a button to copy the original URL if you do want to go there.
Links to X, Threads, Instagram, Facebook, TikTok, Bluesky and Reddit get shared
constantly, and opening one means an app interstitial, a login wall, a feed you didn't
ask for, and a pile of tracking. antisocial is the other half of a StopTheMadness rewrite
rule: the link gets redirected here, and you get the post — the media and the words —
plus a badge saying where it came from and a button to copy the original URL if you do
want to go there.
Built for one person, on a private network. There is **no authentication of any kind**
anything that can reach it can drive a browser through it, so put it somewhere only you
@@ -37,6 +38,10 @@ https://antisocial.example.com/threads/$1
/^https:\/\/(?:www\.)?instagram\.com\/(.*)$/
https://antisocial.example.com/ig/$1
# Facebook
/^https:\/\/(?:www\.|m\.|web\.)?facebook\.com\/(.*)$/
https://antisocial.example.com/fb/$1
# TikTok
/^https:\/\/(?:www\.|vm\.|vt\.)?tiktok\.com\/(.*)$/
https://antisocial.example.com/tiktok/$1
@@ -68,7 +73,9 @@ share codes lose their subdomain in the rewrite; a single opaque path segment is
recognised as a share code and rebuilt as `vm.tiktok.com/<code>/`, or `redd.it/<code>`
where it came from Reddit. A Reddit `/r/<sub>/s/<code>` share link is followed to the
post it points at, and that permalink — not the opaque share code — is what the copy
button hands back.
button hands back. A Facebook `/share/r/<code>` link is followed the same way, and
`m.facebook.com` is rebuilt on `www.` because the mobile site answers a logged-out
reader with a login page and nothing else.
`/` serves these rules with the live hostname already filled in, if you'd rather copy
them from there.
@@ -122,6 +129,7 @@ Each adapter layers its extraction, most structured first:
| Bluesky | the public AT Protocol API | `getPostThread`; falls back to the post page |
| X | `platform.twitter.com` embed | the `cdn.syndication.twimg.com/tweet-result` response |
| Instagram | `/embed/captioned/` | `shortcode_media`, then the rendered `<video>`/`<img>` |
| Facebook | the post page | the Relay payloads, narrowed to the nodes naming that post |
| TikTok | the post page | `__UNIVERSAL_DATA_FOR_REHYDRATION__` |
| Threads | the post page | the Relay payloads in `<script type="application/json">` |
| Reddit | the post's own `.json` | the post and the first page of comments; falls back to the page |
@@ -206,6 +214,11 @@ Known rough edges:
- **Instagram is the least reliable.** It only ships the structured payload some of the
time. When it doesn't, the rendered DOM carries single images and reels fine, but a
carousel will come back as its first image only.
- **Facebook ships the post surrounded by other people's posts.** A reel arrives with
the next five reels of the feed attached, in exactly the same shape. Only the nodes
naming the post in the address are read, so a link to something Facebook no longer has
— which it answers by quietly serving the Watch home page — fails rather than showing
a stranger's video under your link.
- **TikTok sometimes answers with a slider puzzle** instead of the post. You get handed
the puzzle rather than an error — see below.
- **Reddit refuses `.json` to a browser it has never seen.** The first request of a cold