Share the link-preview fallback between the Meta adapters
Instagram and Threads both ended with the same twelve lines: read the Open Graph tags, make og:video a video with og:image as its poster, otherwise make og:image an image, and attach the referrer headers the CDN wants. Facebook needs the same floor for the same reason, and three copies of a thing is where it stops being a coincidence. meta-media.ts already exists because these platforms are one product underneath. The media schema it normalizes is Instagram's and Threads'; Facebook's is its own, but the headers and the link preview are shared with both, so that is where this goes. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
import { ResolveError, oneSegment, type Media, type Post } from '../types.ts';
|
||||
import { readOpenGraph } from '../browser/capture.ts';
|
||||
import { findCaption, findMetaMedia, mediaFromMetaNode, metaHeaders } from './meta-media.ts';
|
||||
import {
|
||||
findCaption,
|
||||
findMetaMedia,
|
||||
mediaFromMetaNode,
|
||||
mediaFromOpenGraph,
|
||||
metaHeaders,
|
||||
} from './meta-media.ts';
|
||||
import { extractJsonObjectAfterKey } from './scan.ts';
|
||||
import { hostMatcher } from './url.ts';
|
||||
import type { PlatformSpec, ResolveContext } from './types.ts';
|
||||
@@ -202,22 +208,8 @@ function fromPayloads(texts: string[]): Scraped | undefined {
|
||||
/** Absolute floor: whatever the page offers a link preview. */
|
||||
async function fromOpenGraph(ctx: ResolveContext): Promise<Scraped> {
|
||||
const og = await readOpenGraph(ctx.page);
|
||||
const fetchHeaders = metaHeaders(REFERER);
|
||||
const media: Media[] = [];
|
||||
|
||||
if (og['og:video']) {
|
||||
media.push({
|
||||
kind: 'video',
|
||||
url: og['og:video'],
|
||||
fetchHeaders,
|
||||
...(og['og:image'] ? { poster: { url: og['og:image'], fetchHeaders } } : {}),
|
||||
});
|
||||
} else if (og['og:image']) {
|
||||
media.push({ kind: 'image', url: og['og:image'], fetchHeaders });
|
||||
}
|
||||
|
||||
return {
|
||||
media,
|
||||
media: mediaFromOpenGraph(og, REFERER),
|
||||
...(og['og:description'] ? { text: og['og:description'] } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { Media } from '../types.ts';
|
||||
|
||||
/**
|
||||
* Instagram and Threads are the same product underneath and serve the same
|
||||
* media schema, so both adapters normalize through here.
|
||||
* Instagram, Threads and Facebook are the same product underneath. The first
|
||||
* two serve the same media schema, so both normalize through here; Facebook's
|
||||
* is its own, but the headers its CDN wants and the link preview it falls
|
||||
* back to are shared with them.
|
||||
*/
|
||||
|
||||
type Candidate = { url?: string; width?: number; height?: number };
|
||||
@@ -66,6 +68,27 @@ export function mediaFromMetaNode(node: MetaMediaNode, referer: string): Media[]
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* The link preview as media: the floor on all three, and all that is left
|
||||
* when a post is refused to a logged-out reader.
|
||||
*/
|
||||
export function mediaFromOpenGraph(og: Record<string, string>, referer: string): Media[] {
|
||||
const fetchHeaders = metaHeaders(referer);
|
||||
|
||||
if (og['og:video']) {
|
||||
return [{
|
||||
kind: 'video',
|
||||
url: og['og:video'],
|
||||
fetchHeaders,
|
||||
...(og['og:image'] ? { poster: { url: og['og:image'], fetchHeaders } } : {}),
|
||||
}];
|
||||
}
|
||||
if (og['og:image']) {
|
||||
return [{ kind: 'image', url: og['og:image'], fetchHeaders }];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function nonEmptyArray(value: unknown): boolean {
|
||||
return Array.isArray(value) && value.length > 0;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { ResolveError, type Media, type Post, type Segment } from '../types.ts';
|
||||
import { ResolveError, type Post, type Segment } from '../types.ts';
|
||||
import { readOpenGraph } from '../browser/capture.ts';
|
||||
import { findCaption, findMetaMedia, mediaFromMetaNode, metaHeaders, type MetaMediaNode } from './meta-media.ts';
|
||||
import {
|
||||
findCaption,
|
||||
findMetaMedia,
|
||||
mediaFromMetaNode,
|
||||
mediaFromOpenGraph,
|
||||
type MetaMediaNode,
|
||||
} from './meta-media.ts';
|
||||
import { hostMatcher } from './url.ts';
|
||||
import type { PlatformSpec, ResolveContext } from './types.ts';
|
||||
|
||||
@@ -129,19 +135,7 @@ function segmentOf(post: ThreadsPost, isAnchor: boolean): Segment {
|
||||
/** The floor, when the structured payload is not there to be read. */
|
||||
async function fromOpenGraph(ctx: ResolveContext): Promise<Segment | undefined> {
|
||||
const og = await readOpenGraph(ctx.page);
|
||||
const fetchHeaders = metaHeaders(REFERER);
|
||||
const media: Media[] = [];
|
||||
|
||||
if (og['og:video']) {
|
||||
media.push({
|
||||
kind: 'video',
|
||||
url: og['og:video'],
|
||||
fetchHeaders,
|
||||
...(og['og:image'] ? { poster: { url: og['og:image'], fetchHeaders } } : {}),
|
||||
});
|
||||
} else if (og['og:image']) {
|
||||
media.push({ kind: 'image', url: og['og:image'], fetchHeaders });
|
||||
}
|
||||
const media = mediaFromOpenGraph(og, REFERER);
|
||||
|
||||
const text = og['og:description'];
|
||||
if (media.length === 0 && !text) return undefined;
|
||||
|
||||
Reference in New Issue
Block a user