mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
refactor: add xtream api services
This commit is contained in:
1 parent
97a678a98d
commit
aa27e6da0d
3 files changed
+336
No files matched your search
@@ -0,0 +1,2 @@
|
||||
export * from './xtream-api.service';
|
||||
export * from './xtream-url.service';
|
||||
@@ -0,0 +1,259 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { DataService } from 'services';
|
||||
import {
|
||||
EpgItem,
|
||||
XtreamCategory,
|
||||
XtreamCodeActions,
|
||||
XtreamLiveStream,
|
||||
XtreamSerieDetails,
|
||||
XtreamSerieItem,
|
||||
XtreamVodDetails,
|
||||
XtreamVodStream,
|
||||
} from 'shared-interfaces';
|
||||
import { XtreamAccountInfo } from '../account-info/account-info.interface';
|
||||
|
||||
/**
|
||||
* Xtream API credentials
|
||||
*/
|
||||
export interface XtreamCredentials {
|
||||
serverUrl: string;
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Category type mapping for API calls
|
||||
*/
|
||||
export type CategoryType = 'live' | 'vod' | 'series';
|
||||
|
||||
/**
|
||||
* Stream type mapping for API calls
|
||||
*/
|
||||
export type StreamType = 'live' | 'movie' | 'series';
|
||||
|
||||
/**
|
||||
* Raw EPG listing from API (before decoding)
|
||||
*/
|
||||
interface RawEpgListing {
|
||||
title: string;
|
||||
description: string;
|
||||
start: string;
|
||||
end: string;
|
||||
start_timestamp: string;
|
||||
stop_timestamp: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* EPG API response
|
||||
*/
|
||||
interface EpgResponse {
|
||||
epg_listings?: RawEpgListing[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Service for all Xtream Codes API interactions.
|
||||
* Extracts API communication logic from the XtreamStore.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class XtreamApiService {
|
||||
private readonly dataService = inject(DataService);
|
||||
|
||||
/**
|
||||
* Get account/portal status
|
||||
*/
|
||||
async getAccountInfo(
|
||||
credentials: XtreamCredentials
|
||||
): Promise<XtreamAccountInfo> {
|
||||
return this.dataService.fetchData(
|
||||
`${credentials.serverUrl}/player_api.php`,
|
||||
{
|
||||
username: credentials.username,
|
||||
password: credentials.password,
|
||||
action: 'get_account_info',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get categories by type
|
||||
*/
|
||||
async getCategories(
|
||||
credentials: XtreamCredentials,
|
||||
type: CategoryType
|
||||
): Promise<XtreamCategory[]> {
|
||||
const actionMap: Record<CategoryType, XtreamCodeActions> = {
|
||||
live: XtreamCodeActions.GetLiveCategories,
|
||||
vod: XtreamCodeActions.GetVodCategories,
|
||||
series: XtreamCodeActions.GetSeriesCategories,
|
||||
};
|
||||
|
||||
const response = await this.dataService.fetchData(
|
||||
`${credentials.serverUrl}/player_api.php`,
|
||||
{
|
||||
action: actionMap[type],
|
||||
username: credentials.username,
|
||||
password: credentials.password,
|
||||
}
|
||||
);
|
||||
|
||||
return Array.isArray(response) ? response : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get live streams
|
||||
*/
|
||||
async getLiveStreams(
|
||||
credentials: XtreamCredentials
|
||||
): Promise<XtreamLiveStream[]> {
|
||||
const response = await this.dataService.fetchData(
|
||||
`${credentials.serverUrl}/player_api.php`,
|
||||
{
|
||||
action: XtreamCodeActions.GetLiveStreams,
|
||||
username: credentials.username,
|
||||
password: credentials.password,
|
||||
}
|
||||
);
|
||||
|
||||
return Array.isArray(response) ? response : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get VOD streams
|
||||
*/
|
||||
async getVodStreams(
|
||||
credentials: XtreamCredentials
|
||||
): Promise<XtreamVodStream[]> {
|
||||
const response = await this.dataService.fetchData(
|
||||
`${credentials.serverUrl}/player_api.php`,
|
||||
{
|
||||
action: XtreamCodeActions.GetVodStreams,
|
||||
username: credentials.username,
|
||||
password: credentials.password,
|
||||
}
|
||||
);
|
||||
|
||||
return Array.isArray(response) ? response : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get series items
|
||||
*/
|
||||
async getSeriesStreams(
|
||||
credentials: XtreamCredentials
|
||||
): Promise<XtreamSerieItem[]> {
|
||||
const response = await this.dataService.fetchData(
|
||||
`${credentials.serverUrl}/player_api.php`,
|
||||
{
|
||||
action: XtreamCodeActions.GetSeries,
|
||||
username: credentials.username,
|
||||
password: credentials.password,
|
||||
}
|
||||
);
|
||||
|
||||
return Array.isArray(response) ? response : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get streams by type (unified method)
|
||||
*/
|
||||
async getStreams(
|
||||
credentials: XtreamCredentials,
|
||||
type: StreamType
|
||||
): Promise<XtreamLiveStream[] | XtreamVodStream[] | XtreamSerieItem[]> {
|
||||
switch (type) {
|
||||
case 'live':
|
||||
return this.getLiveStreams(credentials);
|
||||
case 'movie':
|
||||
return this.getVodStreams(credentials);
|
||||
case 'series':
|
||||
return this.getSeriesStreams(credentials);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get VOD details
|
||||
*/
|
||||
async getVodInfo(
|
||||
credentials: XtreamCredentials,
|
||||
vodId: string | number
|
||||
): Promise<XtreamVodDetails> {
|
||||
return this.dataService.fetchData(
|
||||
`${credentials.serverUrl}/player_api.php`,
|
||||
{
|
||||
action: XtreamCodeActions.GetVodInfo,
|
||||
username: credentials.username,
|
||||
password: credentials.password,
|
||||
vod_id: vodId,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get series details
|
||||
*/
|
||||
async getSeriesInfo(
|
||||
credentials: XtreamCredentials,
|
||||
seriesId: string | number
|
||||
): Promise<XtreamSerieDetails> {
|
||||
return this.dataService.fetchData(
|
||||
`${credentials.serverUrl}/player_api.php`,
|
||||
{
|
||||
action: XtreamCodeActions.GetSeriesInfo,
|
||||
username: credentials.username,
|
||||
password: credentials.password,
|
||||
series_id: seriesId,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short EPG for a stream
|
||||
* Returns decoded EPG items (title and description are base64 decoded)
|
||||
*/
|
||||
async getShortEpg(
|
||||
credentials: XtreamCredentials,
|
||||
streamId: number,
|
||||
limit: number = 10
|
||||
): Promise<EpgItem[]> {
|
||||
const response: EpgResponse = await this.dataService.fetchData(
|
||||
`${credentials.serverUrl}/player_api.php`,
|
||||
{
|
||||
action: XtreamCodeActions.GetShortEpg,
|
||||
username: credentials.username,
|
||||
password: credentials.password,
|
||||
stream_id: streamId,
|
||||
limit,
|
||||
}
|
||||
);
|
||||
|
||||
if (!response?.epg_listings || !Array.isArray(response.epg_listings)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return response.epg_listings.map((item) => ({
|
||||
...item,
|
||||
title: this.decodeBase64Unicode(item.title).trim(),
|
||||
description: this.decodeBase64Unicode(item.description).trim(),
|
||||
})) as EpgItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode base64 unicode string (used for EPG data)
|
||||
*/
|
||||
private decodeBase64Unicode(str: string): string {
|
||||
try {
|
||||
return decodeURIComponent(
|
||||
Array.prototype.map
|
||||
.call(atob(str), (c: string) => {
|
||||
return (
|
||||
'%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
|
||||
);
|
||||
})
|
||||
.join('')
|
||||
);
|
||||
} catch {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import {
|
||||
XtreamSerieEpisode,
|
||||
XtreamVodDetails,
|
||||
} from 'shared-interfaces';
|
||||
import { SettingsStore } from '../../services/settings-store.service';
|
||||
import { XtreamCredentials } from './xtream-api.service';
|
||||
|
||||
/**
|
||||
* Extended playlist with optional HTTP headers
|
||||
*/
|
||||
export interface XtreamPlaylistWithHeaders extends XtreamCredentials {
|
||||
id: string;
|
||||
name: string;
|
||||
type?: string;
|
||||
userAgent?: string;
|
||||
referrer?: string;
|
||||
origin?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Live stream item with xtream_id
|
||||
*/
|
||||
export interface LiveStreamItem {
|
||||
xtream_id: number;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service for constructing Xtream stream URLs.
|
||||
* Handles URL construction for live streams, VOD, and series episodes.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class XtreamUrlService {
|
||||
private readonly settingsStore = inject(SettingsStore);
|
||||
|
||||
/**
|
||||
* Construct live stream URL
|
||||
* Format: {serverUrl}/live/{username}/{password}/{streamId}.{format}
|
||||
*/
|
||||
constructLiveUrl(
|
||||
credentials: XtreamCredentials,
|
||||
xtreamId: number,
|
||||
format?: string
|
||||
): string {
|
||||
const streamFormat =
|
||||
format ?? this.settingsStore.streamFormat() ?? 'ts';
|
||||
return `${credentials.serverUrl}/live/${credentials.username}/${credentials.password}/${xtreamId}.${streamFormat}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct VOD stream URL
|
||||
* Format: {serverUrl}/movie/{username}/{password}/{streamId}.{extension}
|
||||
*/
|
||||
constructVodUrl(
|
||||
credentials: XtreamCredentials,
|
||||
vodItem: XtreamVodDetails
|
||||
): string {
|
||||
const streamId =
|
||||
vodItem.movie_data.stream_id ?? (vodItem as any).stream_id;
|
||||
const extension = vodItem.movie_data.container_extension;
|
||||
return `${credentials.serverUrl}/movie/${credentials.username}/${credentials.password}/${streamId}.${extension}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct series episode stream URL
|
||||
* Format: {serverUrl}/series/{username}/{password}/{episodeId}.{extension}
|
||||
*/
|
||||
constructEpisodeUrl(
|
||||
credentials: XtreamCredentials,
|
||||
episode: XtreamSerieEpisode
|
||||
): string {
|
||||
return `${credentials.serverUrl}/series/${credentials.username}/${credentials.password}/${episode.id}.${episode.container_extension}`;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user