mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-16 14:16:16 -08:00
Every dashboard title carried a "Xtream · Series" / "Stalker · Movie" subtitle. Provider kind and content kind are the app's own taxonomy, not a property of the title, and they are identical on every card in a rail — so the one line that could tell two cards apart said nothing. The hero now shows the source name alone, through `playlistDisplayLabel` (a stored playlist name is routinely the pasted URL with credentials, or a MAC). Continue Watching cards show what actually varies: the "S1·E5" chip plus "12 min left". Favorites keep the title alone, Recently Added keeps the source name, and a meta row with nothing in it is no longer rendered. Stalker shows filed under Movies had no badge, no progress and no resume. An embedded-VOD row announces its episodes through a `series[]` array and carries no `is_series` flag, so `extractStalkerItemType` reports `movie` on purpose — the item must keep routing to the VOD catalog — while its progress is a set of episode positions keyed by the parent id, which the dashboard was looking up as a single `vod` row and never finding. Split the two questions: `PortalActivityItem.watch_kind` records the progress model when it differs from the routing type, and every reader that has to choose goes through `resolvePortalActivityWatchKind` instead of `type`. That makes the resume handoff reachable for Stalker, so wire it through `STALKER_SERIES_RESUME_TARGET`: consumed once after the series positions are read, hydrating a lazy Ministra season first with a bounded two-attempt retry, and playing nothing at all when the position read failed rather than restarting the episode from zero. Also fixes the global-recent route template, which bound `[seriesResume]` only on its Xtream branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
93 lines
3.1 KiB
TypeScript
93 lines
3.1 KiB
TypeScript
import { buildPlaylistRecentItems } from './playlist-recently-viewed.utils';
|
|
import { resolvePortalActivityWatchKind } from './portal-activity-item.interface';
|
|
import type { PlaylistMeta } from './playlist-meta.type';
|
|
|
|
const labels = { stalker: 'Stalker', m3u: 'M3U' };
|
|
|
|
function stalkerPlaylist(recentlyViewed: unknown[]): PlaylistMeta {
|
|
return {
|
|
_id: 'stalker-1',
|
|
title: 'Portal',
|
|
count: 0,
|
|
importDate: '2026-01-01T00:00:00.000Z',
|
|
autoRefresh: false,
|
|
macAddress: '00:1A:79:00:00:01',
|
|
recentlyViewed,
|
|
} as unknown as PlaylistMeta;
|
|
}
|
|
|
|
describe('buildPlaylistRecentItems (Stalker watch kind)', () => {
|
|
it('keeps embedded-VOD shows routing as movies while tracking them as series', () => {
|
|
// Real stored shape: no `is_series`, numeric VOD category, and the
|
|
// `series[]` episode array the portal answered with.
|
|
const [item] = buildPlaylistRecentItems(
|
|
[
|
|
stalkerPlaylist([
|
|
{
|
|
id: '17572',
|
|
title: 'Fake (10 episodes)',
|
|
category_id: '7',
|
|
series: [1, 2, 3],
|
|
added_at: '2026-09-19T16:13:50.000Z',
|
|
},
|
|
]),
|
|
],
|
|
labels
|
|
);
|
|
|
|
expect(item.type).toBe('movie');
|
|
expect(item.watch_kind).toBe('series');
|
|
expect(resolvePortalActivityWatchKind(item)).toBe('series');
|
|
});
|
|
|
|
it('marks lazy Ministra is_series rows as series-tracked too', () => {
|
|
const [item] = buildPlaylistRecentItems(
|
|
[
|
|
stalkerPlaylist([
|
|
{
|
|
id: '50001',
|
|
title: 'Flagged',
|
|
category_id: 'vod',
|
|
is_series: '1',
|
|
added_at: '2026-09-19T16:13:50.000Z',
|
|
},
|
|
]),
|
|
],
|
|
labels
|
|
);
|
|
|
|
expect(item.type).toBe('series');
|
|
expect(item.watch_kind).toBe('series');
|
|
});
|
|
|
|
it('leaves plain movies and live channels without a watch kind override', () => {
|
|
const items = buildPlaylistRecentItems(
|
|
[
|
|
stalkerPlaylist([
|
|
{
|
|
id: '900',
|
|
title: 'A Movie',
|
|
category_id: '7',
|
|
series: [],
|
|
added_at: '2026-09-19T16:13:50.000Z',
|
|
},
|
|
{
|
|
id: '712',
|
|
title: 'Channel',
|
|
category_id: 'itv',
|
|
added_at: '2026-09-19T14:49:22.000Z',
|
|
},
|
|
]),
|
|
],
|
|
labels
|
|
);
|
|
|
|
expect(items.map((item) => item.watch_kind)).toEqual([
|
|
undefined,
|
|
undefined,
|
|
]);
|
|
expect(resolvePortalActivityWatchKind(items[0])).toBe('movie');
|
|
expect(resolvePortalActivityWatchKind(items[1])).toBeNull();
|
|
});
|
|
});
|