/** * Previews Bandcamp links from their page, for `rich-content-service.ts`. * * @module */ import type { RichContent } from "../../../shared/dump.ts"; import type { RichContentProvider } from "./provider.ts"; import { parsePage } from "../html.ts"; import { fetchPage } from "../fetch.ts"; import { BANDCAMP_HOST_RE } from "../bandcamp-tralbum.ts"; import { genericPreview } from "./generic.ts"; /** * Returns the embed player's URL a page's `og:video` names, over https; only * a player on bandcamp.com counts, since the frontend frames it. */ function embeddedPlayerUrl(raw: string | undefined): string | undefined { if (!raw) return undefined; try { const url = new URL(raw); if ( (url.protocol !== "https:" && url.protocol !== "http:") || url.hostname !== "bandcamp.com" || !url.pathname.startsWith("/EmbeddedPlayer/") ) return undefined; url.protocol = "https:"; return url.href; } catch { return undefined; } } /** * Claims bandcamp.com and its subdomains, where artists have their pages, and * takes the embed player's URL from the page's `og:video`. The native player * fetches the page's streams itself at play time, through * `GET /api/remote/bandcamp`. */ export const bandcampProvider: RichContentProvider = { name: "bandcamp", matches(url: string): boolean { try { return BANDCAMP_HOST_RE.test(new URL(url).hostname); } catch { return false; } }, async fetch(url: string): Promise { const fetched = await fetchPage(url); if (fetched.html === undefined) { return { type: "bandcamp", siteName: "Bandcamp", url }; } const page = parsePage(fetched.html, fetched.url); return { ...genericPreview(page, url, fetched.url), type: "bandcamp", siteName: "Bandcamp", embedUrl: embeddedPlayerUrl(page.meta("og:video")), }; }, };