Download lib/scraper/insertunit.ts from mwask/miruro: direct link, hf CLI and curl.
- Browser
- Download file 6.76 kB
-
https://huggingface.co/spaces/mwask/miruro/resolve/main/lib/scraper/insertunit.ts
- Command line
-
hf download hf://spaces/mwask/miruro/lib/scraper/insertunit.ts
-
curl -L -o insertunit.ts https://huggingface.co/spaces/mwask/miruro/resolve/main/lib/scraper/insertunit.ts
6.76 kB
| // Server-only Insertunit source resolver (api.insertunit.ws). Resolves a TMDB | |
| // id to an IMDB id, scrapes the embed HTML, and extracts the dash/hls streams, | |
| // audio-track names and cc (subtitle) list. Streams live on interkh.com and | |
| // require the insertunit Referer, so they must be proxied. | |
| export interface InsertunitResult { | |
| dash: string | null; | |
| dasha: string | null; | |
| hls: string | null; | |
| audio: { names: string[]; order: number[] } | null; | |
| cc: { url: string; name: string }[] | null; | |
| } | |
| const HEADERS: Record<string, string> = { | |
| accept: | |
| "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8", | |
| "accept-language": "en-GB,en;q=0.9", | |
| "cache-control": "max-age=0", | |
| "sec-ch-ua": | |
| '"Chromium";v="148", "Brave";v="148", "Not/A)Brand";v="99"', | |
| "sec-ch-ua-mobile": "?0", | |
| "sec-ch-ua-platform": '"macOS"', | |
| "sec-fetch-dest": "document", | |
| "sec-fetch-mode": "navigate", | |
| "sec-fetch-site": "none", | |
| "sec-fetch-user": "?1", | |
| "upgrade-insecure-requests": "1", | |
| }; | |
| export const INSERTUNIT_REFERER = "https://api.insertunit.ws/"; | |
| function safeJsonLoad<T = any>(s: string): T | null { | |
| try { | |
| return JSON.parse(s) as T; | |
| } catch { | |
| // strip trailing commas from sloppy JS objects, retry | |
| const clean = s.replace(/,\s*}/g, "}").replace(/,\s*\]/g, "]"); | |
| try { | |
| return JSON.parse(clean) as T; | |
| } catch { | |
| return null; | |
| } | |
| } | |
| } | |
| export class InsertunitScraper { | |
| private tmdbKey = | |
| process.env.TMDB_API_KEY || ""; | |
| private async getImdbId(tmdbId: number, isTv: boolean): Promise<string | null> { | |
| const mt = isTv ? "tv" : "movie"; | |
| try { | |
| const r = await fetch( | |
| `https://api.themoviedb.org/3/${mt}/${tmdbId}/external_ids?api_key=${this.tmdbKey}` | |
| ); | |
| if (!r.ok) return null; | |
| const d = await r.json(); | |
| return d.imdb_id || null; | |
| } catch { | |
| return null; | |
| } | |
| } | |
| // Finds the dynamic token suffix appended to media URLs to bypass 403s. | |
| private extractDynamicToken(html: string): string { | |
| const varMatch = html.match( | |
| /o\[k\]\s*\+=\s*'&'\s*\+\s*([a-zA-Z0-9_]+);/ | |
| ); | |
| if (!varMatch) return ""; | |
| const name = varMatch[1]; | |
| const valMatch = html.match( | |
| new RegExp(`${name}\\s*=\\s*"([^"]+)"`) | |
| ); | |
| if (!valMatch) return ""; | |
| return `&${valMatch[1]}`; | |
| } | |
| async fetchSources( | |
| tmdbId: number, | |
| season = 0, | |
| episode = 0 | |
| ): Promise<InsertunitResult | null> { | |
| const isTv = !!(season && episode); | |
| const imdb = await this.getImdbId(tmdbId, isTv); | |
| if (!imdb) return null; | |
| let html: string; | |
| try { | |
| const r = await fetch(`https://api.insertunit.ws/embed/imdb/${imdb}`, { | |
| headers: HEADERS, | |
| cache: "no-store", // fresh tokens each resolve | |
| }); | |
| if (!r.ok) return null; | |
| html = await r.text(); | |
| } catch { | |
| return null; | |
| } | |
| return isTv | |
| ? this.parseTv(html, season, episode) | |
| : this.parseMovie(html); | |
| } | |
| private parseMovie(html: string): InsertunitResult | null { | |
| const m = html.match(/source:\s*({[\s\S]*?})/); | |
| if (!m) return null; | |
| const src = m[1]; | |
| const tok = this.extractDynamicToken(html); | |
| const dash = src.match(/dash:\s*"(.*?)"/); | |
| const dasha = src.match(/dasha:\s*"(.*?)"/); | |
| const hls = src.match(/hls:\s*"(.*?)"/); | |
| const audio = src.match(/audio:\s*({[\s\S]*?})/); | |
| const cc = src.match(/cc:\s*(\[[\s\S]*?\])/); | |
| return { | |
| dash: dash ? dash[1] + tok : null, | |
| dasha: dasha ? dasha[1] + tok : null, | |
| hls: hls ? hls[1] + tok : null, | |
| audio: audio ? safeJsonLoad(audio[1]) : null, | |
| cc: cc ? safeJsonLoad(cc[1]) : null, | |
| }; | |
| } | |
| private parseTv( | |
| html: string, | |
| season: number, | |
| episode: number | |
| ): InsertunitResult | null { | |
| // Isolate the season block to avoid cross-season episode mismatches. | |
| const seasonRe = new RegExp( | |
| `"season":\\s*${season}\\s*,\\s*"blocked"[\\s\\S]*?(?="season":\\s*\\d+\\s*,|$)` | |
| ); | |
| const sm = html.match(seasonRe); | |
| if (!sm) return null; | |
| const seasonHtml = sm[0]; | |
| const epRe = new RegExp( | |
| `"episode":\\s*"${episode}"[\\s\\S]*?(?="episode":\\s*"\\d+"|$)` | |
| ); | |
| const em = seasonHtml.match(epRe); | |
| if (!em) return null; | |
| const eh = em[0]; | |
| const tok = this.extractDynamicToken(html); | |
| const dash = eh.match(/"dash":\s*"(.*?)"/); | |
| const dasha = eh.match(/"dasha":\s*"(.*?)"/); | |
| const hls = eh.match(/"hls":\s*"(.*?)"/); | |
| const audio = eh.match(/"audio":\s*({[\s\S]*?})/); | |
| const cc = eh.match(/"cc":\s*(\[[\s\S]*?\])/); | |
| return { | |
| dash: dash ? dash[1] + tok : null, | |
| dasha: dasha ? dasha[1] + tok : null, | |
| hls: hls ? hls[1] + tok : null, | |
| audio: audio ? safeJsonLoad(audio[1]) : null, | |
| cc: cc ? safeJsonLoad(cc[1]) : null, | |
| }; | |
| } | |
| } | |
| import type { SourceModule } from "./types"; | |
| // Best-effort language code from an Insertunit cc track name. | |
| function guessLang(name: string): string { | |
| const n = (name || "").toLowerCase(); | |
| if (n.includes("eng") || n.includes("english")) return "en"; | |
| if (n.includes("рус") || n.includes("rus")) return "ru"; | |
| if (n.includes("укр") || n.includes("ukr")) return "uk"; | |
| if (n.includes("span") || n.includes("esp")) return "es"; | |
| if (n.includes("fr")) return "fr"; | |
| if (n.includes("de") || n.includes("ger")) return "de"; | |
| return ""; | |
| } | |
| const _insertunit = new InsertunitScraper(); | |
| // interkh streams need the insertunit Referer, so both streams and cc subs are | |
| // proxied. Prefer DASH (its <BaseURL>[0] is a real CDN host; the HLS variant | |
| // uses an "x-bc" placeholder host that 410s). | |
| export const insertunit: SourceModule = { | |
| id: "insertunit", | |
| name: "Ari", | |
| label: "Multi-audio · built-in subs", | |
| active: true, | |
| rank: 2, | |
| async fetch(ctx) { | |
| const r = await _insertunit | |
| .fetchSources(ctx.tmdbId, ctx.season, ctx.episode) | |
| .catch(() => null); | |
| if (!r) return null; | |
| const ref = INSERTUNIT_REFERER; | |
| const streams = []; | |
| if (r.dash) { | |
| streams.push({ | |
| file: ctx.proxyStream(r.dash, ref), | |
| label: "auto", | |
| type: "dash" as const, | |
| }); | |
| } else if (r.hls) { | |
| streams.push({ | |
| file: ctx.proxyStream(r.hls, ref), | |
| label: "auto", | |
| type: "hls" as const, | |
| }); | |
| } | |
| if (!streams.length) return null; | |
| const subtitles = (r.cc || []).map((c) => ({ | |
| url: ctx.proxySub(c.url, ref), | |
| display: c.name, | |
| language: guessLang(c.name), | |
| source: "Ari", | |
| })); | |
| return { streams, subtitles }; | |
| }, | |
| }; | |