mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-11 19:46:16 -08:00
feat(portals): season descriptions under the season tabs
Xtream reads season overviews from get_series_info; Stalker exposes the TMDB season overview through TmdbEnrichmentService.getSeason (same cache rows as the episode enrichment) and keys it per tmdbId so views reused across detail navigations cannot leak descriptions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
191abe16eb
commit
3bd0f3121c
6 files changed
+62
-8
No files matched your search
+1
@@ -149,6 +149,7 @@
|
||||
[playingEpisodeId]="
|
||||
inlinePlayback()?.contentInfo?.contentXtreamId ?? null
|
||||
"
|
||||
[seasonDescriptions]="seasonDescriptions()"
|
||||
[isLoading]="
|
||||
isVodSeriesSeasonsLoading() ||
|
||||
isCurrentSeasonLoading(seasonContainer?.selectedSeason())
|
||||
|
||||
+33
-3
@@ -135,8 +135,28 @@ export class StalkerSeriesViewComponent implements OnDestroy {
|
||||
private readonly tmdbSeasonEpisodes = signal<
|
||||
ReadonlyMap<string, TmdbEpisode[]>
|
||||
>(new Map());
|
||||
/** TMDB season overviews, keyed like tmdbSeasonEpisodes. */
|
||||
private readonly tmdbSeasonOverviews = signal<
|
||||
ReadonlyMap<string, string>
|
||||
>(new Map());
|
||||
private readonly tmdbEnrichment = inject(TmdbEnrichmentService);
|
||||
|
||||
/** Season descriptions for the season tabs (TMDB overview per season). */
|
||||
readonly seasonDescriptions = computed<Record<string, string>>(() => {
|
||||
const tmdbId = this.displayItem()?.info?.tmdb_id;
|
||||
if (!tmdbId) {
|
||||
return {};
|
||||
}
|
||||
const prefix = `${tmdbId}|`;
|
||||
const descriptions: Record<string, string> = {};
|
||||
for (const [mapKey, overview] of this.tmdbSeasonOverviews()) {
|
||||
if (mapKey.startsWith(prefix)) {
|
||||
descriptions[mapKey.slice(prefix.length)] = overview;
|
||||
}
|
||||
}
|
||||
return descriptions;
|
||||
});
|
||||
|
||||
/**
|
||||
* Track VOD series seasons with their loaded episodes
|
||||
*/
|
||||
@@ -380,16 +400,26 @@ export class StalkerSeriesViewComponent implements OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
const tmdbEpisodes = await this.tmdbEnrichment.getSeasonEpisodes(
|
||||
const season = await this.tmdbEnrichment.getSeason(
|
||||
tmdbId,
|
||||
seasonNumber
|
||||
);
|
||||
if (!tmdbEpisodes?.length) {
|
||||
if (!season) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (season.overview) {
|
||||
const overviews = new Map(this.tmdbSeasonOverviews());
|
||||
overviews.set(mapKey, season.overview);
|
||||
this.tmdbSeasonOverviews.set(overviews);
|
||||
}
|
||||
|
||||
if (!season.episodes?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const next = new Map(this.tmdbSeasonEpisodes());
|
||||
next.set(mapKey, tmdbEpisodes);
|
||||
next.set(mapKey, season.episodes);
|
||||
this.tmdbSeasonEpisodes.set(next);
|
||||
}
|
||||
|
||||
|
||||
@@ -157,6 +157,7 @@
|
||||
[playingEpisodeId]="
|
||||
inlinePlayback()?.contentInfo?.contentXtreamId ?? null
|
||||
"
|
||||
[seasonDescriptions]="seasonDescriptions()"
|
||||
(episodeClicked)="playEpisode($event)"
|
||||
(playbackToggleRequested)="handlePlaybackToggleRequested($event)"
|
||||
(seasonSelected)="onSeasonSelected($event)"
|
||||
|
||||
@@ -109,6 +109,17 @@ export class SerialDetailsComponent implements OnInit, OnDestroy {
|
||||
readonly inlineEpisodeMetadata = this.playback.inlineEpisodeMetadata;
|
||||
readonly inlineSeriesNavigation = this.playback.inlineSeriesNavigation;
|
||||
|
||||
/** Season overviews from get_series_info, keyed by season key. */
|
||||
readonly seasonDescriptions = computed<Record<string, string>>(() => {
|
||||
const descriptions: Record<string, string> = {};
|
||||
for (const season of this.selectedItem()?.seasons ?? []) {
|
||||
if (season?.overview && season.season_number !== undefined) {
|
||||
descriptions[String(season.season_number)] = season.overview;
|
||||
}
|
||||
}
|
||||
return descriptions;
|
||||
});
|
||||
|
||||
/** TMDB recommendations matched against the loaded series catalog */
|
||||
readonly similarItems = computed<SimilarCatalogItem[]>(() => {
|
||||
const item = this.selectedItem();
|
||||
|
||||
@@ -65,6 +65,19 @@ export class TmdbEnrichmentService {
|
||||
tmdbId: number,
|
||||
seasonNumber: number
|
||||
): Promise<TmdbEpisode[] | null> {
|
||||
const season = await this.getSeason(tmdbId, seasonNumber);
|
||||
return season ? (season.episodes ?? []) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Full season payload (overview + episodes) in the app language, using
|
||||
* the same cache rows as `getSeasonEpisodes`. Returns `null` when
|
||||
* enrichment is off or the request fails.
|
||||
*/
|
||||
async getSeason(
|
||||
tmdbId: number,
|
||||
seasonNumber: number
|
||||
): Promise<TmdbSeasonDetails | null> {
|
||||
if (!this.isEnabled()) {
|
||||
return null;
|
||||
}
|
||||
@@ -79,10 +92,7 @@ export class TmdbEnrichmentService {
|
||||
cached?.payload
|
||||
) {
|
||||
try {
|
||||
const season = JSON.parse(
|
||||
cached.payload
|
||||
) as TmdbSeasonDetails;
|
||||
return season.episodes ?? [];
|
||||
return JSON.parse(cached.payload) as TmdbSeasonDetails;
|
||||
} catch {
|
||||
// Corrupt cache row — fall through to a fresh fetch
|
||||
}
|
||||
@@ -103,7 +113,7 @@ export class TmdbEnrichmentService {
|
||||
payload: JSON.stringify(season),
|
||||
});
|
||||
|
||||
return season.episodes ?? [];
|
||||
return season;
|
||||
} catch (error) {
|
||||
console.warn('TMDB season enrichment failed:', error);
|
||||
return null;
|
||||
|
||||
@@ -99,6 +99,7 @@ export interface TmdbEpisode {
|
||||
|
||||
export interface TmdbSeasonDetails {
|
||||
season_number?: number;
|
||||
overview?: string;
|
||||
episodes?: TmdbEpisode[];
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user