mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-09 17:36:15 -08:00
fix(downloads): preserve Xtream episode headers
This commit is contained in:
1 parent
3ccc13f852
commit
45d9dcebd5
5 files changed
+63
-8
No files matched your search
@@ -88,6 +88,9 @@ describe('SerialDetailsComponent', () => {
|
||||
serverUrl: 'http://xtream.example',
|
||||
username: 'user',
|
||||
password: 'pass',
|
||||
userAgent: 'ProtectedProvider/2.0',
|
||||
referrer: 'https://referrer.example/series',
|
||||
origin: 'https://origin.example',
|
||||
});
|
||||
const fetchSerialDetailsWithMetadata = jest.fn();
|
||||
const cancelDetailsRequest = jest.fn();
|
||||
@@ -384,6 +387,11 @@ describe('SerialDetailsComponent', () => {
|
||||
seriesXtreamId: 103,
|
||||
seasonNumber: 1,
|
||||
episodeNumber: 1,
|
||||
headers: {
|
||||
userAgent: 'ProtectedProvider/2.0',
|
||||
referer: 'https://referrer.example/series',
|
||||
origin: 'https://origin.example',
|
||||
},
|
||||
metadataSnapshot: {
|
||||
version: 1,
|
||||
language: 'en',
|
||||
|
||||
@@ -117,6 +117,9 @@ export class SerialDetailsComponent implements OnInit, OnDestroy {
|
||||
serverUrl: playlist.serverUrl,
|
||||
username: playlist.username,
|
||||
password: playlist.password,
|
||||
userAgent: playlist.userAgent,
|
||||
referrer: playlist.referrer,
|
||||
origin: playlist.origin,
|
||||
metadataContext: createXtreamSeriesDownloadMetadataContext(
|
||||
item.info,
|
||||
this.translateService.currentLang ||
|
||||
|
||||
+33
-1
@@ -1,4 +1,7 @@
|
||||
import type { XtreamSerieEpisode } from '@iptvnator/shared/interfaces';
|
||||
import {
|
||||
XTREAM_CLIENT_USER_AGENT,
|
||||
type XtreamSerieEpisode,
|
||||
} from '@iptvnator/shared/interfaces';
|
||||
import { createXtreamSeriesDownloadAdapter } from './xtream-series-download.adapter';
|
||||
|
||||
const POSTER_URL = 'https://images.test/stills/the-one.jpg';
|
||||
@@ -10,6 +13,9 @@ const OPTIONS = {
|
||||
serverUrl: 'http://host/',
|
||||
username: 'user',
|
||||
password: 'pass',
|
||||
userAgent: 'ProtectedProvider/1.0',
|
||||
referrer: 'https://referrer.test/series',
|
||||
origin: 'https://origin.test',
|
||||
metadataContext: {
|
||||
language: 'en',
|
||||
title: 'Signal House',
|
||||
@@ -67,6 +73,11 @@ describe('createXtreamSeriesDownloadAdapter', () => {
|
||||
seriesXtreamId: 900,
|
||||
seasonNumber: 2,
|
||||
episodeNumber: 3,
|
||||
headers: {
|
||||
userAgent: 'ProtectedProvider/1.0',
|
||||
referer: 'https://referrer.test/series',
|
||||
origin: 'https://origin.test',
|
||||
},
|
||||
});
|
||||
expect(metadataSnapshot).toEqual({
|
||||
version: 1,
|
||||
@@ -85,6 +96,27 @@ describe('createXtreamSeriesDownloadAdapter', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps the Xtream client user-agent fallback for episode downloads', async () => {
|
||||
const adapter = createXtreamSeriesDownloadAdapter({
|
||||
...OPTIONS,
|
||||
userAgent: ' ',
|
||||
referrer: undefined,
|
||||
origin: undefined,
|
||||
});
|
||||
|
||||
const candidate = adapter.createCandidate(episode(), '1');
|
||||
|
||||
await expect(candidate?.prepare()).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
headers: {
|
||||
userAgent: XTREAM_CLIENT_USER_AGENT,
|
||||
referer: undefined,
|
||||
origin: undefined,
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('preserves the existing season and episode fallback rules', async () => {
|
||||
const adapter = createXtreamSeriesDownloadAdapter(OPTIONS);
|
||||
const fromSeasonKey = adapter.createCandidate(
|
||||
|
||||
+14
-3
@@ -3,9 +3,10 @@ import {
|
||||
createSeriesEpisodeDownloadSnapshot,
|
||||
type DownloadMovieSnapshotInput,
|
||||
} from '@iptvnator/portal/shared/util';
|
||||
import type {
|
||||
XtreamSerieEpisode,
|
||||
XtreamSerieEpisodeInfo,
|
||||
import {
|
||||
XTREAM_CLIENT_USER_AGENT,
|
||||
type XtreamSerieEpisode,
|
||||
type XtreamSerieEpisodeInfo,
|
||||
} from '@iptvnator/shared/interfaces';
|
||||
|
||||
export interface XtreamSeriesDownloadAdapterOptions {
|
||||
@@ -15,6 +16,9 @@ export interface XtreamSeriesDownloadAdapterOptions {
|
||||
readonly serverUrl?: string;
|
||||
readonly username?: string;
|
||||
readonly password?: string;
|
||||
readonly userAgent?: string;
|
||||
readonly referrer?: string;
|
||||
readonly origin?: string;
|
||||
readonly metadataContext: DownloadMovieSnapshotInput;
|
||||
}
|
||||
|
||||
@@ -88,6 +92,13 @@ export function createXtreamSeriesDownloadAdapter(
|
||||
title: `${options.title || 'Series'} - ${episodeCode} - ${episode.title}`,
|
||||
url: `${normalizedServerUrl}/series/${username}/${password}/${episode.id}.${extension}`,
|
||||
posterUrl: info?.movie_image,
|
||||
headers: {
|
||||
userAgent:
|
||||
options.userAgent?.trim() ||
|
||||
XTREAM_CLIENT_USER_AGENT,
|
||||
referer: options.referrer,
|
||||
origin: options.origin,
|
||||
},
|
||||
metadataSnapshot: createSeriesEpisodeDownloadSnapshot({
|
||||
...options.metadataContext,
|
||||
episode: {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
export type XtreamPortalStatusType =
|
||||
| 'active'
|
||||
| 'inactive'
|
||||
| 'expired'
|
||||
| 'unavailable';
|
||||
'active' | 'inactive' | 'expired' | 'unavailable';
|
||||
|
||||
export interface XtreamPortalStatusResponseLike {
|
||||
user_info?: {
|
||||
@@ -19,6 +16,10 @@ export interface XtreamCredentialsFromUrl {
|
||||
|
||||
const XTREAM_API_ENDPOINT_PATTERN = /\/(?:get|player_api)\.php$/i;
|
||||
|
||||
// Some Xtream panels challenge generic Node HTTP clients while allowing
|
||||
// established IPTV players. Keep API, probe, and download requests aligned.
|
||||
export const XTREAM_CLIENT_USER_AGENT = 'VLC/3.0.18 LibVLC/3.0.18';
|
||||
|
||||
export function normalizeXtreamServerUrl(value: string): string {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) {
|
||||
|
||||
Reference in new issue
Block a user