mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-11 02:46:16 -08:00
* feat(tmdb): opt-in TMDB metadata enrichment for Xtream and Stalker portals Adds an opt-in TMDB integration (Settings > Metadata) that enriches detail views with a field-level merge — the provider stays authoritative for stream data, TMDB fills editorial fields when the match is confident. Enrichment: - Movie/series details: plot, cast (avatar chips), director, genres, rating, poster/backdrop, official YouTube trailers - Confidence-gated matching: provider tmdb_id trusted; otherwise normalized-title search with year gate (±1; series accept earlier premieres), season-suffix stripping, Cyrillic search-language override, and language-prefix fallback variants - Lazy season/episode enrichment: real episode names, overviews, stills - "Similar" rail (Xtream): TMDB recommendations matched to the catalog - Actor pages per portal with full filmography, availability filter and an Electron-only "All portals" scope backed by a batched DB_MATCH_TITLES worker op over the trigram FTS index Infrastructure: - SQLite cache table tmdb_metadata (details, search verdicts, seasons, persons; per-language, TTL-guarded), in-memory fallback for the PWA - Settings: enable toggle, own-API-key override with a live "check key" button; TMDB attribution in Settings and About - Embedded key stays an empty placeholder; CI injects TMDB_API_KEY via tools/tmdb/inject-tmdb-key.mjs when the secret is configured - normalizeTitle shared between renderer and DB worker - CSP: allow YouTube embeds (frame-src was 'none'; trailers never worked) Fixes and refactors along the way: - fix(stalker): Advanced Search sent bare get_ordered_list requests and skipped the auth handshake when isFullStalkerPortal was missing on the active-playlist meta — full portals answered "Authorization failed." and search looked empty; now mirrors the catalog request shape and routes through makeAuthenticatedRequest with URL-based detection - fix(stalker): TMDB fields survive info re-normalization; detail views prefer the store copy patched by async enrichment over stale snapshots - refactor(xtream): split oversized vod/serial detail components into component-scoped playback services; detail routes re-initialize on route param changes (router reuses them for detail-to-detail nav) - i18n: all new keys translated across the 18 locales Docs: docs/architecture/tmdb-metadata-enrichment.md + CLAUDE.md updates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(tmdb): provide route params observable to inline collection details, linearize regexes The global-collection inline detail host builds a fake ActivatedRoute for VodDetailsRouteComponent/SerialDetailsComponent with only snapshot.params. Since the detail components now read route.params via toSignal() (detail-> detail re-init), the missing observable crashed component construction and the content hero never rendered — broke dashboard-activation, favorites and recent Electron E2E on all platforms. Provide the params observable alongside the snapshot and assert it in the component spec. Also resolves both CodeQL js/polynomial-redos alerts: bracket-stripping in normalizeTitle now excludes opening delimiters inside the classes, and youtubeEmbedUrl extracts watch?v= ids with a linear two-pass match instead of "watch\?.*v=". Combining-diacritics range rewritten as explicit \u escapes (greptile note). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(tmdb): surface TMDB-only VOD score in the rating badge, drop youtube.com from CSP Review follow-ups on PR #1123: the Xtream VOD detail badge renders rating_imdb, but the merge wrote the TMDB score only into `rating`, so a TMDB-only score was never displayed (Codex P2) — fill rating_imdb when the provider left it empty, mirroring the Stalker merge. All trailer iframes are normalized to youtube-nocookie.com, so the extra youtube.com frame-src allowance was dead surface (greptile) — removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(tmdb): resolve confirmed review findings — matching correctness, race guards, cache schema Fixes the confirmed findings from the PR #1123 code review: - Stalker search: setSelectedContentType now runs BEFORE setSelectedItem, so the TMDB enrichment gate in the selection hook no longer sees the content type of the previously open tab (wrong/no enrichment after ITV -> search -> movie). - Title normalization is now two-tier (normalizeTitleKeys): the exact normalized form keeps a trailing year, the base form strips it and remembers the tag. Year stripping is anchored to the end of the title ("2001: A Space Odyssey" keeps its year) and language-prefix stripping is UPPERCASE-only ("It: Chapter Two" is no longer amputated). - All catalog matching (similar rail, actor pages, DB worker DB_MATCH_TITLES) compares exact forms first and only accepts year-stripped matches when the stripped tag is year-compatible (+-1) with the TMDB year — "Blade Runner" (1982) can no longer claim a catalog "Blade Runner 2049". CatalogTitleMatch carries the stripped trailingYear so the renderer can apply the guard to worker matches. - mergedBackdrops tolerates a plain-string backdrop_path; enrichment merge+patch blocks are wrapped in try/catch so a malformed provider payload can no longer become an unhandled rejection. - loadGlobalMatches (both actor routes) guards against actor->actor navigation races — a slow match for the previous person no longer overwrites the current one's results. - tmdb_metadata media_type CHECK widened to ('movie','tv','person') and person rows now use the honest 'person' type (TmdbCacheMediaType). Pre-release dev DBs with the narrow CHECK are rebuilt in place — the table is a pure cache, so the migration is a self-healing drop-and-recreate keyed off sqlite_master. Docs updated (tmdb-metadata-enrichment.md, CLAUDE.md). New regression coverage: title-normalization.util.spec.ts, two-tier cases in tmdb-similar.util.spec.ts and title-match.operations.spec.ts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
83 lines
3.5 KiB
HTML
83 lines
3.5 KiB
HTML
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<title>IPTVnator</title>
|
|
<base href="/" />
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<meta
|
|
http-equiv="Content-Security-Policy"
|
|
content="default-src 'self'; base-uri 'self'; object-src 'none'; frame-src https://www.youtube-nocookie.com; form-action 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' http: https: file: data: blob:; font-src 'self' data:; media-src 'self' http: https: file: data: blob:; connect-src 'self' http: https: ws: wss: blob: data:; worker-src 'self' blob:"
|
|
/>
|
|
<meta property="og:type" content="website" />
|
|
<meta property="og:title" content="IPTVnator" />
|
|
<meta property="og:url" content="https://iptvnator.vercel.app" />
|
|
<meta
|
|
property="og:image"
|
|
content="https://raw.githubusercontent.com/4gray/iptvnator/electron/iptv-dark-theme.png"
|
|
/>
|
|
<meta
|
|
property="og:description"
|
|
content="IPTVnator is a video player application that provides support for the playback of IPTV playlists (m3u, m3u8)"
|
|
/>
|
|
<link rel="manifest" href="manifest.webmanifest" />
|
|
<link rel="apple-touch-icon" href="assets/icons/apple-touch-icon.png" />
|
|
<link rel="icon" type="image/x-icon" href="assets/icons/favicon.ico" />
|
|
<script src="assets/app-config.js" defer></script>
|
|
<style>
|
|
/* Inline splash — paints immediately while the JS bundle and the
|
|
styles.css (~300KB) are still downloading and Angular is
|
|
bootstrapping. Removed from the DOM by main.ts after
|
|
bootstrapApplication() resolves. No assets, zero extra HTTP. */
|
|
#initial-splash {
|
|
position: fixed;
|
|
inset: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 18px;
|
|
background: #1f1f23;
|
|
color: #f5f5f7;
|
|
font-family:
|
|
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
|
'Helvetica Neue', Arial, sans-serif;
|
|
z-index: 99999;
|
|
}
|
|
#initial-splash .splash-mark {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
letter-spacing: 0.04em;
|
|
opacity: 0.85;
|
|
}
|
|
#initial-splash .splash-spinner {
|
|
width: 28px;
|
|
height: 28px;
|
|
border: 3px solid rgba(255, 255, 255, 0.12);
|
|
border-top-color: rgba(255, 255, 255, 0.7);
|
|
border-radius: 50%;
|
|
animation: initial-splash-spin 0.9s linear infinite;
|
|
}
|
|
@keyframes initial-splash-spin {
|
|
to {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
@media (prefers-reduced-motion: reduce) {
|
|
#initial-splash .splash-spinner {
|
|
animation: none;
|
|
opacity: 0.6;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="mat-app-background">
|
|
<div id="initial-splash" role="status" aria-label="Loading IPTVnator">
|
|
<span class="splash-mark">IPTVnator</span>
|
|
<span class="splash-spinner" aria-hidden="true"></span>
|
|
</div>
|
|
<app-root></app-root>
|
|
</body>
|
|
</html>
|