// detectPlatform — which platform a URL is on, by host. // // Plain JS (with JSDoc types) rather than TS for ONE reason: umtool's // report-to-video scripts are `.mjs` run by bare `node` (20 in the runtime // image — no type stripping), and they spawn yt-dlp too, so they need the same // answer the editor gets. `lib/platform.ts` re-exports this; every TS caller // keeps importing it from there. Do not grow a second host table anywhere. /** @typedef {import("./platform").Platform} Platform */ // Forums known to run XenForo, by host. Any other XenForo forum is recognised // by its thread URL (THREAD_PATH_RE) instead — a host table is a convenience, // not the rule. export const XENFORO_HOSTS = ["kiwifarms.st", "kiwifarms.net"]; // Hosts that serve ONE forum under several domains (a page saved from one is a // page of the same thread on the other). Compared by `sameXenforoForum`. export const XENFORO_MIRRORS = [["kiwifarms.st", "kiwifarms.net"]]; /** * Whether two hosts are the same forum: equal, or mirrors of one another. * @param {string} a * @param {string} b * @returns {boolean} */ export function sameXenforoForum(a, b) { const x = a.toLowerCase().replace(/^www\./, ""); const y = b.toLowerCase().replace(/^www\./, ""); if (x === y) return true; return XENFORO_MIRRORS.some((group) => group.includes(x) && group.includes(y)); } // A XenForo thread path: /threads/./ or /threads//, optionally // under a prefix (/community/threads/…) or behind index.php? (non-friendly // URLs). export const XENFORO_THREAD_PATH_RE = /(?:^|\/|\?)threads\/(?:[^/?#]*\.)?(\d+)(?:\/|$)/; /** * @param {string | undefined | null} url * @returns {Platform | null} */ export function detectPlatform(url) { if (!url) return null; try { const host = new URL(url).hostname.toLowerCase(); if (host.endsWith("youtube.com") || host === "youtu.be") return "youtube"; if (host.endsWith("rumble.com")) return "rumble"; if (host.endsWith("odysee.com")) return "odysee"; if (host.endsWith("twitch.tv")) return "twitch"; if (host.endsWith("kick.com")) return "kick"; if (host === "x.com" || host.endsWith(".x.com")) return "twitter"; if (host.endsWith("twitter.com")) return "twitter"; if (host === "bsky.app" || host.endsWith(".bsky.app")) return "bluesky"; // archive.org ITEMS only. Not web.archive.org (the Wayback Machine's page // captures are a different kind of record) — see lib/archiveOrgId.ts. if (host === "archive.org" || host === "www.archive.org") return "archiveorg"; // bitchute.com, www. and old. (the pages), api. and the seedNNN. media // hosts — every host BitChute serves from is the one platform's. if (host === "bitchute.com" || host.endsWith(".bitchute.com")) return "bitchute"; if (XENFORO_HOSTS.some((h) => host === h || host.endsWith(`.${h}`))) { return "xenforo"; } const u = new URL(url); if (XENFORO_THREAD_PATH_RE.test(u.pathname + u.search)) return "xenforo"; } catch { /* fall through */ } return null; }