mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-14 13:16:16 -08:00
feat(global-widgets): add M3U recently-viewed and favs support
- Introduced M3uRecentlyViewedItem interface to define structure for recently viewed items. - Implemented utility functions to handle mapping and building of recently viewed items from playlists. - Updated Playlist interface to use PlaylistRecentlyViewedItem type for recently viewed items. - Enhanced PortalActivityItem interface to include 'm3u' as a possible source. - Added UI components for displaying recently viewed channels, including clear functionality. - Integrated recent view functionality into the channel list container and sidebar components. - Updated dashboard data service to manage M3U favorites and recently viewed items. - Added tests to ensure correct behavior of recently viewed items in the dashboard data service.
This commit is contained in:
1 parent
188ebb6fd9
commit
1e9f5d9d05
27 files changed
+1286
-227
No files matched your search
@@ -12,6 +12,8 @@ export * from './lib/language.enum';
|
||||
export * from './lib/parsed-playlist.interface';
|
||||
export * from './lib/playback-position.interface';
|
||||
export * from './lib/playlist-meta.type';
|
||||
export * from './lib/playlist-recently-viewed.interface';
|
||||
export * from './lib/playlist-recently-viewed.utils';
|
||||
export * from './lib/playlist.interface';
|
||||
export * from './lib/portal-activity-item.interface';
|
||||
export * from './lib/portal-debug.interface';
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { StalkerPortalItem } from './stalker-portal-item.interface';
|
||||
|
||||
export interface M3uRecentlyViewedItem {
|
||||
source: 'm3u';
|
||||
id: string;
|
||||
url: string;
|
||||
title: string;
|
||||
channel_id?: string;
|
||||
poster_url?: string;
|
||||
tvg_id?: string;
|
||||
tvg_name?: string;
|
||||
group_title?: string;
|
||||
category_id: 'live';
|
||||
added_at: string | number;
|
||||
}
|
||||
|
||||
export type PlaylistRecentlyViewedItem =
|
||||
| M3uRecentlyViewedItem
|
||||
| StalkerPortalItem;
|
||||
|
||||
export function isM3uRecentlyViewedItem(
|
||||
item: unknown
|
||||
): item is M3uRecentlyViewedItem {
|
||||
if (!item || typeof item !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const candidate = item as Record<string, unknown>;
|
||||
return (
|
||||
candidate['source'] === 'm3u' &&
|
||||
typeof candidate['url'] === 'string' &&
|
||||
candidate['url'].trim().length > 0
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import { PlaylistMeta } from './playlist-meta.type';
|
||||
import { PortalRecentItem } from './portal-activity-item.interface';
|
||||
import {
|
||||
extractStalkerItemId,
|
||||
extractStalkerItemPoster,
|
||||
extractStalkerItemTitle,
|
||||
extractStalkerItemType,
|
||||
normalizeStalkerDate,
|
||||
} from './stalker-item.normalizer';
|
||||
import {
|
||||
PlaylistRecentlyViewedItem,
|
||||
isM3uRecentlyViewedItem,
|
||||
} from './playlist-recently-viewed.interface';
|
||||
|
||||
interface PlaylistRecentLabels {
|
||||
stalker: string;
|
||||
m3u: string;
|
||||
}
|
||||
|
||||
function getPlaylistRecentItems(
|
||||
playlist: PlaylistMeta
|
||||
): PlaylistRecentlyViewedItem[] {
|
||||
return Array.isArray(playlist.recentlyViewed)
|
||||
? (playlist.recentlyViewed as PlaylistRecentlyViewedItem[])
|
||||
: [];
|
||||
}
|
||||
|
||||
function mapStalkerPlaylistRecentItems(
|
||||
playlist: PlaylistMeta,
|
||||
defaultPlaylistName: string
|
||||
): PortalRecentItem[] {
|
||||
return getPlaylistRecentItems(playlist).reduce<PortalRecentItem[]>(
|
||||
(acc, rawItem, index) => {
|
||||
if (isM3uRecentlyViewedItem(rawItem)) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
const item = (rawItem ?? {}) as Record<string, unknown>;
|
||||
const id = extractStalkerItemId(item, playlist._id, index);
|
||||
|
||||
acc.push({
|
||||
id,
|
||||
title: extractStalkerItemTitle(item),
|
||||
type: extractStalkerItemType(item),
|
||||
playlist_id: playlist._id,
|
||||
playlist_name: playlist.title || defaultPlaylistName,
|
||||
viewed_at: normalizeStalkerDate(item['added_at']),
|
||||
category_id: String(item['category_id'] ?? ''),
|
||||
xtream_id: id,
|
||||
poster_url: extractStalkerItemPoster(item),
|
||||
source: 'stalker',
|
||||
stalker_item: rawItem,
|
||||
});
|
||||
|
||||
return acc;
|
||||
},
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
function mapM3uPlaylistRecentItems(
|
||||
playlist: PlaylistMeta,
|
||||
defaultPlaylistName: string
|
||||
): PortalRecentItem[] {
|
||||
return getPlaylistRecentItems(playlist).reduce<PortalRecentItem[]>(
|
||||
(acc, rawItem) => {
|
||||
if (!isM3uRecentlyViewedItem(rawItem)) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
const channelUrl = rawItem.url.trim();
|
||||
if (!channelUrl) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
acc.push({
|
||||
id: rawItem.id || channelUrl,
|
||||
title:
|
||||
rawItem.title?.trim() ||
|
||||
rawItem.tvg_name?.trim() ||
|
||||
rawItem.channel_id ||
|
||||
channelUrl,
|
||||
type: 'live',
|
||||
playlist_id: playlist._id,
|
||||
playlist_name: playlist.title || defaultPlaylistName,
|
||||
viewed_at: normalizeStalkerDate(rawItem.added_at),
|
||||
category_id: rawItem.category_id || 'live',
|
||||
xtream_id: channelUrl,
|
||||
poster_url: rawItem.poster_url || undefined,
|
||||
source: 'm3u',
|
||||
});
|
||||
|
||||
return acc;
|
||||
},
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
export function buildPlaylistRecentItems(
|
||||
playlists: PlaylistMeta[],
|
||||
labels: PlaylistRecentLabels
|
||||
): PortalRecentItem[] {
|
||||
return playlists.reduce<PortalRecentItem[]>((acc, playlist) => {
|
||||
if (playlist.macAddress) {
|
||||
acc.push(
|
||||
...mapStalkerPlaylistRecentItems(playlist, labels.stalker)
|
||||
);
|
||||
return acc;
|
||||
}
|
||||
|
||||
if (!playlist.serverUrl) {
|
||||
acc.push(...mapM3uPlaylistRecentItems(playlist, labels.m3u));
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PlaylistRecentlyViewedItem } from './playlist-recently-viewed.interface';
|
||||
import { StalkerPortalItem } from './stalker-portal-item.interface';
|
||||
|
||||
/**
|
||||
@@ -42,7 +43,7 @@ export interface Playlist {
|
||||
password?: string;
|
||||
macAddress?: string;
|
||||
portalUrl?: string;
|
||||
recentlyViewed?: any[];
|
||||
recentlyViewed?: PlaylistRecentlyViewedItem[];
|
||||
/** Indicates if this is a full stalker portal URL (e.g., /stalker_portal/c) requiring handshake authentication */
|
||||
isFullStalkerPortal?: boolean;
|
||||
/** Session token for full stalker portal authentication - persisted for session */
|
||||
|
||||
@@ -16,7 +16,7 @@ export interface PortalActivityItem {
|
||||
category_id: string | number;
|
||||
xtream_id: string | number;
|
||||
poster_url?: string;
|
||||
source?: 'xtream' | 'stalker';
|
||||
source?: 'xtream' | 'stalker' | 'm3u';
|
||||
/** Original stalker item for navigation state; undefined for xtream. */
|
||||
stalker_item?: StalkerPortalItem;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user