refactor: add Stalker VOD and serial details interfaces with normalization functions

This commit is contained in:
4gray committed 2026-01-30 20:38:26 +01:00
1 parent 6c52ee349d
commit 522e18e13d
5 files changed
+323

No files matched your search

+8
View File
@@ -27,3 +27,11 @@ export * from './lib/xtream-serie-details.interface';
export * from './lib/xtream-serie-item.interface';
export * from './lib/xtream-vod-details.interface';
export * from './lib/xtream-vod-stream.interface';
// Stalker interfaces
export * from './lib/stalker-vod-details.interface';
export * from './lib/stalker-serial-details.interface';
// Unified VOD details (discriminated union + adapters)
export * from './lib/vod-details-item.interface';
export * from './lib/vod-details-adapters';
@@ -0,0 +1,28 @@
/**
* Stalker portal series/serial details response structure.
* Contains series metadata from Stalker/Ministra portals.
*/
export interface StalkerSerialDetails {
/** Series title */
name: string;
/** Plot description */
description: string;
/** Director name */
director: string;
/** Comma-separated list of actors */
actors: string;
/** Release year */
year: string;
/** Comma-separated genres */
genres_str: string;
/** Age rating */
age: string;
/** IMDB rating as string */
rating_imdb: string;
/** Kinopoisk rating as string */
rating_kinopoisk: string;
/** Screenshot/poster URL */
screenshot_uri: string;
/** Date added to portal */
added: string;
}
@@ -0,0 +1,33 @@
/**
* Stalker portal VOD details response structure.
* Contains movie metadata from Stalker/Ministra portals.
*/
export interface StalkerVodDetails {
/** Unique identifier for the VOD item */
id: string;
/** Command string used to play the stream (e.g., "/media/file_123.mpg") */
cmd: string;
/** VOD metadata */
info: StalkerVodInfo;
}
export interface StalkerVodInfo {
/** Poster/cover image URL */
movie_image: string;
/** Plot description */
description: string;
/** Movie title */
name: string;
/** Comma-separated list of actors */
actors: string;
/** Director name */
director: string;
/** Release date (format varies) */
releasedate: string;
/** Genre(s) */
genre: string;
/** IMDB rating as string */
rating_imdb: string;
/** Kinopoisk rating as string */
rating_kinopoisk: string;
}
@@ -0,0 +1,158 @@
import { XtreamVodDetails } from './xtream-vod-details.interface';
import { StalkerVodDetails } from './stalker-vod-details.interface';
import {
NormalizedVodMeta,
VodDetailsItem,
XtreamVodItem,
StalkerVodItem,
} from './vod-details-item.interface';
/**
* Normalizes Xtream VOD details into a display-friendly format.
* Handles the various field name variations in Xtream API responses.
*/
export function normalizeXtreamVod(item: XtreamVodDetails): NormalizedVodMeta {
const info = item?.info;
const movieData = item?.movie_data;
return {
title: info?.name || movieData?.name || 'Unknown',
description: info?.description || info?.plot,
posterUrl: info?.movie_image || info?.cover_big,
backdropUrl: info?.backdrop_path?.[0],
year: extractYear(info?.releasedate),
genre: info?.genre,
duration: info?.duration || formatDuration(info?.duration_secs),
country: info?.country,
director: info?.director,
actors: info?.actors || info?.cast,
ratingImdb: info?.rating_imdb,
ratingKinopoisk: info?.rating_kinopoisk,
youtubeTrailer: info?.youtube_trailer,
};
}
/**
* Normalizes Stalker VOD details into a display-friendly format.
* Stalker has fewer metadata fields than Xtream.
*/
export function normalizeStalkerVod(item: StalkerVodDetails): NormalizedVodMeta {
const info = item?.info;
return {
title: info?.name || 'Unknown',
description: info?.description,
posterUrl: info?.movie_image,
backdropUrl: undefined, // Stalker doesn't provide backdrop
year: extractYear(info?.releasedate),
genre: info?.genre,
duration: undefined, // Stalker doesn't provide duration
country: undefined, // Stalker doesn't provide country
director: info?.director,
actors: info?.actors,
ratingImdb: info?.rating_imdb,
ratingKinopoisk: info?.rating_kinopoisk,
youtubeTrailer: undefined, // Stalker doesn't provide trailers
};
}
/**
* Normalizes any VodDetailsItem to display-friendly format.
* Uses discriminated union for type-safe handling.
*/
export function normalizeVodDetails(item: VodDetailsItem): NormalizedVodMeta {
if (item.type === 'xtream') {
return normalizeXtreamVod(item.data);
}
return normalizeStalkerVod(item.data);
}
/**
* Creates an XtreamVodItem from raw data and context.
*/
export function createXtreamVodItem(
data: XtreamVodDetails,
playlistId: string,
vodId?: number
): XtreamVodItem {
return {
type: 'xtream',
data,
playlistId,
vodId: vodId ?? data.movie_data?.stream_id ?? 0,
};
}
/**
* Creates a StalkerVodItem from raw data and context.
*/
export function createStalkerVodItem(
data: StalkerVodDetails,
playlistId: string
): StalkerVodItem {
return {
type: 'stalker',
data,
playlistId,
cmd: data.cmd,
};
}
/**
* Gets the unique identifier for a VOD item.
* Returns vodId for Xtream, id for Stalker.
*/
export function getVodId(item: VodDetailsItem): number | string {
if (item.type === 'xtream') {
return item.vodId;
}
return item.data.id;
}
/**
* Gets the numeric ID for download/favorite tracking.
*/
export function getVodNumericId(item: VodDetailsItem): number {
if (item.type === 'xtream') {
return item.vodId;
}
return Number(item.data.id) || 0;
}
// ============ Helper Functions ============
/**
* Extracts 4-digit year from various date formats.
*/
function extractYear(dateString?: string): string | undefined {
if (!dateString) return undefined;
// Try to extract 4-digit year from beginning
const yearMatch = dateString.match(/^(\d{4})/);
if (yearMatch) {
return yearMatch[1];
}
// Try to parse as date and extract year
const date = new Date(dateString);
if (!isNaN(date.getTime())) {
return date.getFullYear().toString();
}
return dateString.slice(0, 4);
}
/**
* Formats duration from seconds to human-readable string.
*/
function formatDuration(seconds?: number): string | undefined {
if (!seconds || seconds <= 0) return undefined;
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
if (hours > 0) {
return `${hours}h ${minutes}m`;
}
return `${minutes}m`;
}
@@ -0,0 +1,96 @@
import { XtreamVodDetails } from './xtream-vod-details.interface';
import { StalkerVodDetails } from './stalker-vod-details.interface';
/**
* Discriminated union for VOD details across portal types.
* Enables type-safe handling without runtime type checks.
*
* @example
* ```typescript
* function handleVod(item: VodDetailsItem) {
* if (item.type === 'xtream') {
* // TypeScript knows item.data is XtreamVodDetails
* console.log(item.data.movie_data.stream_id);
* } else {
* // TypeScript knows item.data is StalkerVodDetails
* console.log(item.data.cmd);
* }
* }
* ```
*/
export type VodDetailsItem = XtreamVodItem | StalkerVodItem;
/**
* Xtream Codes VOD item wrapper with context.
*/
export interface XtreamVodItem {
/** Discriminator for Xtream Codes portals */
readonly type: 'xtream';
/** Full VOD details from Xtream API */
data: XtreamVodDetails;
/** Playlist/portal ID for favorites and downloads */
playlistId: string;
/** Stream ID from Xtream API */
vodId: number;
}
/**
* Stalker portal VOD item wrapper with context.
*/
export interface StalkerVodItem {
/** Discriminator for Stalker portals */
readonly type: 'stalker';
/** Full VOD details from Stalker API */
data: StalkerVodDetails;
/** Playlist/portal ID for favorites and downloads */
playlistId: string;
/** Command string for stream playback */
cmd: string;
}
/**
* Normalized metadata for display in templates.
* Provides a consistent interface regardless of portal type.
*/
export interface NormalizedVodMeta {
/** Movie title */
title: string;
/** Plot description */
description?: string;
/** Poster/cover image URL */
posterUrl?: string;
/** Backdrop image URL (Xtream only) */
backdropUrl?: string;
/** Release year (4 digits) */
year?: string;
/** Genre(s) */
genre?: string;
/** Duration string (e.g., "1h 30m") */
duration?: string;
/** Country of origin (Xtream only) */
country?: string;
/** Director name */
director?: string;
/** Actors/cast (comma-separated) */
actors?: string;
/** IMDB rating */
ratingImdb?: string;
/** Kinopoisk rating */
ratingKinopoisk?: string;
/** YouTube trailer ID (Xtream only) */
youtubeTrailer?: string;
}
/**
* Type guard to check if item is Xtream VOD.
*/
export function isXtreamVod(item: VodDetailsItem): item is XtreamVodItem {
return item.type === 'xtream';
}
/**
* Type guard to check if item is Stalker VOD.
*/
export function isStalkerVod(item: VodDetailsItem): item is StalkerVodItem {
return item.type === 'stalker';
}