fix(tmdb): address PR review — best-effort fallback fetch, /embed/ scope, names-only trigger

- A failing original-language fallback fetch no longer costs the
  already-fetched primary details payload (Codex P2) — it is wrapped
  best-effort and the primary payload is returned on error.
- The YouTube Referer shim now scopes to /embed/ paths, so regular
  www.youtube.com requests keep their real (missing) Referer (greptile).
- seasonNeedsTextFallback also triggers when episode overviews exist but
  ALL episode names are empty (partially translated seasons; greptile).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Fable 5 committed 2026-07-05 11:53:25 +02:00
1 parent 28177c391b
commit 7d868d77fe
5 files changed
+64 -14

No files matched your search

@@ -281,6 +281,21 @@ describe('request header overrides', () => {
expect(headers['Referer']).toBeUndefined();
});
it('does not inject the Referer for non-embed YouTube paths', async () => {
const { registerStaticHeaderShims } = await import(
'./request-header-overrides.service'
);
registerStaticHeaderShims();
const listener = mockOnBeforeSendHeaders.mock.calls[0][1];
const headers = runHeaderListener(
listener,
'https://www.youtube.com/watch?v=abc123'
);
expect(headers['Referer']).toBeUndefined();
});
it('does not inject the embed Referer for non-YouTube hosts', async () => {
const { registerStaticHeaderShims } = await import(
'./request-header-overrides.service'
@@ -77,13 +77,18 @@ function applyYoutubeEmbedRefererShim(
url: string,
requestHeaders: Record<string, string>
): void {
let host: string;
let parsed: URL;
try {
host = new URL(url).hostname;
parsed = new URL(url);
} catch {
return;
}
if (!YOUTUBE_EMBED_HOSTS.has(host)) {
// Scope strictly to embed player requests — www.youtube.com also
// serves regular pages that must keep their real (missing) Referer
if (
!YOUTUBE_EMBED_HOSTS.has(parsed.hostname) ||
!parsed.pathname.startsWith('/embed/')
) {
return;
}
@@ -222,12 +222,19 @@ export class TmdbEnrichmentService {
if (!fallbackLanguage) {
return details;
}
const fallback = await this.fetchDetails(
mediaType,
tmdbId,
fallbackLanguage
);
return fillDetailsFromFallback(details, fallback);
// Best-effort: a failing fallback fetch must never cost the
// already-fetched primary payload (cast, artwork, trailer, ...)
try {
const fallback = await this.fetchDetails(
mediaType,
tmdbId,
fallbackLanguage
);
return fillDetailsFromFallback(details, fallback);
} catch (error) {
console.warn('TMDB fallback-language fetch failed:', error);
return details;
}
}
private async fetchDetails(
@@ -83,11 +83,25 @@ describe('season fallback', () => {
expect(
seasonNeedsTextFallback({
overview: '',
episodes: [{ episode_number: 1, overview: 'Text' }],
episodes: [
{ episode_number: 1, name: 'Ep 1', overview: 'Text' },
],
})
).toBe(false);
});
it('triggers when episode overviews exist but ALL names are empty', () => {
expect(
seasonNeedsTextFallback({
overview: 'Season text',
episodes: [
{ episode_number: 1, name: '', overview: 'Text 1' },
{ episode_number: 2, name: ' ', overview: 'Text 2' },
],
})
).toBe(true);
});
it('fills missing season overview and episode texts by number', () => {
const merged = fillSeasonFromFallback(emptySeason, russianSeason);
expect(merged.overview).toBe('Описание сезона');
@@ -42,14 +42,23 @@ export function fillDetailsFromFallback(
: primary;
}
/** True when the season payload carries no usable text at all */
/**
* True when the season payload carries no usable text: no overviews at
* all, or episodes whose names are ALL empty (partially translated
* seasons keep localized overviews but lose the episode names).
*/
export function seasonNeedsTextFallback(season: TmdbSeasonDetails): boolean {
const episodes = season.episodes ?? [];
if (
episodes.length > 0 &&
!episodes.some((episode) => hasText(episode.name))
) {
return true;
}
if (hasText(season.overview)) {
return false;
}
return !(season.episodes ?? []).some((episode) =>
hasText(episode.overview)
);
return !episodes.some((episode) => hasText(episode.overview));
}
/**