feat(epg): manual EPG mapping for Stalker portals (#1173)

* feat(epg): manual EPG mapping for Stalker portals

Extends the manual EPG-to-channel mapping (PR #1165) to Stalker:

- shared key helper buildStalkerEpgMappingKey — playlist-scoped
  stalker:{playlistId}:{channelId} keys, mirroring the Xtream scheme
- ITV sidebar: right-click context menu with "Map EPG channel"; after
  the dialog closes with a change, the bulk EPG cache is rebuilt so the
  panel and row previews reflect the new mapping immediately
- withStalkerEpg().applyMappedItvEpg(): batch-resolves mappings for
  rendered channels (one getEpgMappingsBatch IPC per new id set) and
  overlays uploaded-XMLTV programs onto bulkItvEpgByChannel; overrides
  survive ensureBulkItvEpg reloads
- stream-resolver: mapping check in loadStalkerEpgItems (detail) and
  batched prefetch in loadStalkerEpgBatch (previews);
  mappingCandidateKeys/prefetchEpgMappings generalized beyond Xtream
- global favorites: stalker branch for the Map-EPG menu entry (item id
  extracted from the stalker::{playlistId}::{id} uid)
- docs: manual-mapping section in docs/architecture/stalker-epg.md and
  a CLAUDE.md EPG bullet covering the whole mapping feature
- tests: applyMappedItvEpg suite, stalker preview mapping in the
  stream-resolver spec, key-builder specs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(epg): harden Stalker mapping edges found in adversarial review

- stream-resolver: stalker items resolve only the playlist-scoped
  mapping key — bare tvgId/name candidates (tvgId mirrors the raw
  provider id) could only produce false matches against unrelated M3U
  mappings, and previews would disagree with the detail path
- applyMappedItvEpg: staleness guard after every await so an in-flight
  call cannot write portal A's mapped EPG into portal B's state after
  a playlist switch (the store is a root singleton)
- applyMappedItvEpg: ids are marked checked only after a successful
  lookup — a transient IPC failure no longer suppresses the mapping
  for the rest of the session
- live layout: post-dialog refresh re-applies overrides for the
  unfiltered channel list, so an active search cannot drop the playing
  channel's mapping
- global favorites: new epgMappingChanged output emitted when the
  dialog actually changed a mapping; unified live tab reloads its EPG
  previews in response

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Fable 5 authored and GitHub committed 2026-07-19 18:29:23 +02:00
1 parent b1fc23abbb
commit c707af1334
15 files changed
+624 -13

No files matched your search

+23
View File
@@ -263,6 +263,29 @@ advantage of the richer bulk API when it is available. Row previews do not
fallback to per-channel requests in this mode; they remain empty until bulk EPG
is available.
## Manual EPG Mapping
Stalker channels carry no XMLTV identifier, so when the portal's own EPG is
missing or wrong the only uploaded-EPG entry point is a **manual mapping**:
right-click a channel in the ITV sidebar (or in global favorites) and pick
"Map EPG channel" to attach it to a channel from an uploaded XMLTV guide.
- Mappings are stored in the shared `epg_channel_mappings` table under the
playlist-scoped key `stalker:{playlistId}:{channelId}`
(`buildStalkerEpgMappingKey` in
`libs/shared/interfaces/src/lib/epg-mapping-key.util.ts`).
- `withStalkerEpg().applyMappedItvEpg(channelIds)` batch-resolves mappings
(one `getEpgMappingsBatch` IPC per new id set) and overlays the mapped
XMLTV programs onto `bulkItvEpgByChannel`, so both the active panel and
the row previews pick them up with no template changes. Overrides are
re-merged whenever `ensureBulkItvEpg` replaces the bulk record and are
re-checked after the mapping dialog closes with a change.
- The collection views (global favorites/recent) resolve the same keys in
`StreamResolverService` (`loadStalkerEpgItems` for the detail panel,
`loadStalkerEpgBatch` + `prefetchEpgMappings` for row previews).
- Everything is gated behind `supportsEpgMapping`, so the PWA never shows
the menu entry.
## Future Enhancements
- add cache refresh / invalidation for long-running live sessions