import assert from 'node:assert/strict'; import { test } from 'node:test'; import { findAuthor, mediaFromAttachments, mediaFromNode, partsOfPost, postIdFrom, scrapeParts, } from '../src/platforms/facebook.ts'; import { fixture } from './helpers.ts'; // Real captures of what a logged-out Facebook page ships, trimmed of the DASH // manifests and tracking blobs the adapter never reads. Each is a different // shape: a reel arrives with the next reels of the feed attached, a photo post // keys itself by an opaque `pfbid` with no id in the address at all, and a // video post has its pieces scattered over four separate payload blocks. const REEL = 'https://www.facebook.com/reel/962551420197596/'; const PHOTO_POST = 'https://www.facebook.com/NASA/posts/pfbid02yzKA4Z5Wnep5xU3PLWyWgRuVebWg3UZoATsEH3wuZcKoZuiLSBL8rEkbe4qTZ65Jl'; const VIDEO_POST = 'https://www.facebook.com/NASA/videos/nancy-grace-roman-space-telescope-launch-trailer/1082585924135116/'; const reel = () => fixture('facebook/reel.json'); const photoPost = () => fixture('facebook/photo-post.json'); const videoPost = () => fixture('facebook/video-post.json'); const scrape = (payloads: unknown[], id: string | undefined, url: string) => scrapeParts(partsOfPost(payloads, id, url)); test('a reel is read as the reel that was linked, not the feed around it', () => { const post = scrape(reel(), '962551420197596', REEL); assert.equal(post.author?.name, 'SpeechProf'); assert.equal(post.text, 'I need answers.'); assert.equal(post.media.length, 1); assert.equal(post.media[0]?.kind, 'video'); }); test('the reels Facebook staples on are really in the payload', () => { // Without this the test above proves nothing: the whole point is that the // page carries other people's reels in the same shape as the linked one. const others = JSON.stringify(reel()).match(/facebook\.com\\?\/reel\\?\/(\d+)/g) ?? []; const ids = new Set(others.map((match) => /(\d+)/.exec(match)?.[1])); ids.delete('962551420197596'); assert.ok(ids.size > 0, 'the fixture should carry recommended reels too'); }); test('a portrait reel keeps its shape, its poster and its length', () => { const item = scrape(reel(), '962551420197596', REEL).media[0]; assert.equal(item?.width, 720); assert.equal(item?.height, 1280); assert.equal(item?.kind === 'video' ? item.durationSec : undefined, 15); assert.match( item?.kind === 'video' ? (item.poster?.url ?? '') : '', /scontent-.*fbcdn\.net/, ); }); test('a post whose address carries no id is found by its permalink', () => { const post = scrape(photoPost(), undefined, PHOTO_POST); assert.match(post.author?.name ?? '', /^NASA/); assert.match(post.text ?? '', /^25 years ago/); assert.equal(post.media[0]?.kind, 'image'); }); test('the client configuration shipped alongside the post is not read as the post', () => { // With neither an id nor a matching permalink there is nothing to anchor // on, and the page's own configuration — thousands of nodes carrying a // `name` — is what a plain search finds first. const post = scrape(photoPost(), undefined, 'https://www.facebook.com/somewhere/else'); assert.match(post.author?.name ?? '', /^NASA/); }); test('a video post is assembled from the several blocks it is split over', () => { const post = scrape(videoPost(), '1082585924135116', VIDEO_POST); // The name, the vanity address and the picture arrive in different blocks // from the file and the timestamp; all of it has to end up on one post. assert.match(post.author?.name ?? '', /^NASA/); assert.equal(post.author?.url, 'https://www.facebook.com/NASA'); assert.ok(post.author?.profile_picture?.uri, 'the avatar is in a block of its own'); assert.match(post.text ?? '', /Nancy Grace Roman/); assert.equal(post.postedAt, '2026-08-28T23:23:27.000Z'); assert.equal(post.media[0]?.kind, 'video'); }); test('someone commenting under the post is not mistaken for its author', () => { const payloads = videoPost(); assert.ok( JSON.stringify(payloads).includes('Michael Hall'), 'the fixture should carry the comments, which is what makes this a trap', ); const author = findAuthor(partsOfPost(payloads, '1082585924135116', VIDEO_POST)); assert.match(author?.name ?? '', /^NASA/); }); test('a page that answers to a different post is not read at all', () => { // Facebook serves a link to something it no longer has by quietly handing // back something else — `/watch/` for a video that is gone comes back // as the Watch home page, feed and all. A page that names neither the id // nor the address is a failure rather than whatever happened to be on it. assert.deepEqual( partsOfPost(reel(), '111111111111111', 'https://www.facebook.com/watch/111111111111111/'), [], ); }); test('an address with no id in it still falls back to the query results', () => { // The strictness above only applies where there was an id to check: a // `pfbid` permalink has none, and refusing those would refuse every post // shared from a page. assert.notDeepEqual(partsOfPost(photoPost(), undefined, 'https://www.facebook.com/elsewhere'), []); }); test('the id is taken from wherever the address keeps it', () => { const id = (url: string) => postIdFrom(new URL(url)); assert.equal(id('https://www.facebook.com/reel/962551420197596/'), '962551420197596'); assert.equal(id('https://www.facebook.com/photo/?fbid=1626865842142119&set=a.41'), '1626865842142119'); assert.equal(id('https://www.facebook.com/watch/?v=1082585924135116'), '1082585924135116'); assert.equal(id('https://www.facebook.com/NASA/videos/some-slug/1082585924135116/'), '1082585924135116'); assert.equal(id('https://www.facebook.com/groups/123456789/posts/987654321/'), '987654321'); assert.equal(id('https://www.facebook.com/NASA/posts/pfbid02yzKA4Z5'), undefined); }); test('`id` on a permalink is the page, not the post, so it is left alone', () => { assert.equal( postIdFrom(new URL('https://www.facebook.com/permalink.php?story_fbid=222222222&id=999999999')), '222222222', ); assert.equal(postIdFrom(new URL('https://www.facebook.com/profile.php?id=999999999')), undefined); }); test('the better of the two files Facebook offers is the one used', () => { const media = mediaFromNode({ videoDeliveryLegacyFields: { browser_native_sd_url: 'https://video.example/sd.mp4', browser_native_hd_url: 'https://video.example/hd.mp4', }, preferred_thumbnail: { image: { uri: 'https://image.example/poster.jpg' } }, }); assert.equal(media[0]?.url, 'https://video.example/hd.mp4'); assert.equal(media[0]?.kind === 'video' ? media[0].poster?.url : undefined, 'https://image.example/poster.jpg'); }); test('an empty `all_subattachments` is a single picture, not a carousel', () => { // Facebook ships the key on every post, so its presence says nothing. const media = mediaFromAttachments([ { all_subattachments: { nodes: [] }, styles: { attachment: { media: { photo_image: { uri: 'https://image.example/one.jpg' } } } }, }, ]); assert.deepEqual(media.map((item) => item.url), ['https://image.example/one.jpg']); }); test('a populated `all_subattachments` is every picture in the post', () => { const media = mediaFromAttachments([ { media: { photo_image: { uri: 'https://image.example/cover.jpg' } }, all_subattachments: { nodes: [ { media: { photo_image: { uri: 'https://image.example/1.jpg' } } }, { media: { photo_image: { uri: 'https://image.example/2.jpg', width: 8, height: 6 } } }, ], }, }, ]); assert.deepEqual(media.map((item) => item.url), [ 'https://image.example/1.jpg', 'https://image.example/2.jpg', ]); assert.equal(media[1]?.width, 8); }); test('every asset carries the headers the Facebook CDN is given', () => { const post = scrape(reel(), '962551420197596', REEL); const item = post.media[0]; assert.equal(item?.fetchHeaders?.['Referer'], 'https://www.facebook.com/'); assert.equal(item?.fetchHeaders?.['Origin'], 'https://www.facebook.com'); });