mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 18:36:15 -08:00
fix(xtream): drop URL-only season overviews and fall back to TMDB (#1382)
Xtream panels routinely fill get_series_info seasons[].overview with a
bare cover-image URL, which rendered verbatim under the season tabs.
URL-only overviews are now treated as absent (sanitizeProviderOverview),
and the lazy season enrichment stores the TMDB season overview on the
selection (tmdb_season_overviews) as the fallback description - same
cached /tv/{id}/season/{n} payload, so no extra requests. Provider text
keeps priority when it is real prose.
The enrichment write is also convergent now: the serial detail re-fires
season enrichment after every selection write, and the previous
unconditional rewrite scheduled the next cache-served run indefinitely.
A repeat run that changes nothing no longer writes.
buildSeasonDescriptions is extracted from SerialDetailsComponent, which
would otherwise cross the 400-line max-lines limit.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7103f7e734
commit
92be39ef66
13 files changed
+411
-43
No files matched your search
@@ -35,6 +35,7 @@ export * from './lib/portal-activity-item.interface';
|
||||
export * from './lib/portal-debug.interface';
|
||||
export * from './lib/playlist-display-label.util';
|
||||
export * from './lib/portal-playback.interface';
|
||||
export * from './lib/provider-overview.util';
|
||||
export * from './lib/random-id.util';
|
||||
export * from './lib/security-policy-error.utils';
|
||||
export * from './lib/settings.interface';
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { sanitizeProviderOverview } from './provider-overview.util';
|
||||
|
||||
describe('sanitizeProviderOverview', () => {
|
||||
it('keeps real description text', () => {
|
||||
expect(sanitizeProviderOverview('A tense hijack drama.')).toBe(
|
||||
'A tense hijack drama.'
|
||||
);
|
||||
});
|
||||
|
||||
it('trims surrounding whitespace', () => {
|
||||
expect(sanitizeProviderOverview(' Season text ')).toBe('Season text');
|
||||
});
|
||||
|
||||
it('returns null for empty and missing values', () => {
|
||||
expect(sanitizeProviderOverview('')).toBeNull();
|
||||
expect(sanitizeProviderOverview(' ')).toBeNull();
|
||||
expect(sanitizeProviderOverview(null)).toBeNull();
|
||||
expect(sanitizeProviderOverview(undefined)).toBeNull();
|
||||
});
|
||||
|
||||
it('drops a bare http image URL', () => {
|
||||
expect(
|
||||
sanitizeProviderOverview(
|
||||
'http://line.example.net:80/images/series/cover_small.jpg'
|
||||
)
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('drops a bare https URL with a query string', () => {
|
||||
expect(
|
||||
sanitizeProviderOverview('https://cdn.example.com/p.jpg?w=300')
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('drops a bare URL padded with whitespace', () => {
|
||||
expect(
|
||||
sanitizeProviderOverview(' http://cdn.example.com/cover.png ')
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('drops a protocol-relative URL', () => {
|
||||
expect(
|
||||
sanitizeProviderOverview('//cdn.example.com/cover.jpg')
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('keeps prose that merely contains a URL', () => {
|
||||
const text = 'More info at http://example.com/season1';
|
||||
expect(sanitizeProviderOverview(text)).toBe(text);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Xtream panels routinely fill editorial text fields with junk — most
|
||||
* commonly a season `overview` that holds a bare cover-image URL instead
|
||||
* of a description. Rendering that verbatim puts a raw URL on screen, so
|
||||
* a value that is nothing but a URL is treated as absent.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Trimmed overview text, or `null` when the value is empty or a bare URL.
|
||||
* Prose that merely contains a URL is kept — only a single URL token with
|
||||
* no surrounding text is junk.
|
||||
*/
|
||||
export function sanitizeProviderOverview(
|
||||
text: string | null | undefined
|
||||
): string | null {
|
||||
const trimmed = text?.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
return isBareUrl(trimmed) ? null : trimmed;
|
||||
}
|
||||
|
||||
/** A single absolute or protocol-relative URL token (`http://…/x.jpg`). */
|
||||
function isBareUrl(text: string): boolean {
|
||||
return /^(?:https?:)?\/\/\S+$/i.test(text);
|
||||
}
|
||||
@@ -8,6 +8,13 @@ export interface XtreamSerieDetails {
|
||||
seasons: XtreamSerieSeason[];
|
||||
info: XtreamSerieInfo;
|
||||
episodes: Record<string, XtreamSerieEpisode[]>;
|
||||
/**
|
||||
* Populated by lazy TMDB season enrichment; absent in raw provider
|
||||
* responses. Keyed by the provider season key (the `episodes` record
|
||||
* key). Detail views use it as the season-description fallback when
|
||||
* the provider's `seasons[].overview` is empty or URL-only junk.
|
||||
*/
|
||||
tmdb_season_overviews?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface XtreamSerieInfo {
|
||||
|
||||
Reference in new issue
Block a user