0.14.0-3 Analyze fix

This commit is contained in:
2026-04-21 08:20:40 +00:00
parent 720a6f5609
commit caf6a638f7
8 changed files with 516 additions and 60 deletions
+78 -18
View File
@@ -5,9 +5,58 @@ const logger = require('./logger').child('TMDB');
const TMDB_BASE_URL = 'https://api.themoviedb.org/3';
const TMDB_IMAGE_BASE_URL = 'https://image.tmdb.org/t/p';
const TMDB_TIMEOUT_MS = 10000;
const TMDB_TIMEOUT_MS = 15000;
class TmdbService {
isAbortError(error) {
const name = String(error?.name || '').trim().toLowerCase();
const message = String(error?.message || '').trim().toLowerCase();
return name === 'aborterror' || message.includes('aborted');
}
classifyRequestError(error) {
if (this.isAbortError(error)) {
return 'timeout';
}
const statusCode = Number(error?.statusCode || 0) || null;
if (statusCode === 401 || statusCode === 403) {
return 'auth';
}
if (statusCode >= 500) {
return 'upstream';
}
if (statusCode >= 400) {
return 'request_failed';
}
return 'network';
}
readFailureCode(rows) {
const value = rows && typeof rows.tmdbFailureCode === 'string'
? rows.tmdbFailureCode
: '';
const normalized = String(value || '').trim().toLowerCase();
return normalized || null;
}
attachFailureCode(rows, failureCode = null) {
const output = Array.isArray(rows) ? rows : [];
const normalized = String(failureCode || '').trim().toLowerCase();
if (!normalized) {
return output;
}
try {
Object.defineProperty(output, 'tmdbFailureCode', {
value: normalized,
enumerable: false,
configurable: true
});
} catch (_error) {
output.tmdbFailureCode = normalized;
}
return output;
}
normalizeNameList(values = [], options = {}) {
const maxItems = Math.max(1, Number(options.maxItems || 10));
const source = Array.isArray(values) ? values : [];
@@ -130,24 +179,30 @@ class TmdbService {
}
const language = await this.resolveLanguage(options.language);
const data = await this.request('/search/tv', {
query: {
query: normalizedQuery,
first_air_date_year: options.year || undefined,
language,
page: options.page || 1,
include_adult: false
}
}).catch((error) => {
let data = null;
try {
data = await this.request('/search/tv', {
query: {
query: normalizedQuery,
first_air_date_year: options.year || undefined,
language,
page: options.page || 1,
include_adult: false
}
});
} catch (error) {
const failureCode = this.classifyRequestError(error);
logger.warn('search:failed', {
query: normalizedQuery,
error: error?.message || String(error)
error: error?.message || String(error),
failureCode,
statusCode: Number(error?.statusCode || 0) || null
});
return null;
});
return this.attachFailureCode([], failureCode);
}
const rows = Array.isArray(data?.results) ? data.results : [];
return rows
const normalizedRows = rows
.map((row) => ({
id: Number(row?.id || 0) || null,
title: String(row?.name || row?.original_name || '').trim() || null,
@@ -162,6 +217,7 @@ class TmdbService {
popularity: Number(row?.popularity || 0) || 0
}))
.filter((row) => row.id && row.title);
return this.attachFailureCode(normalizedRows, null);
}
async getSeriesDetails(seriesId, options = {}) {
@@ -366,8 +422,9 @@ class TmdbService {
async searchSeriesWithSeasons(query, options = {}) {
const candidates = await this.searchSeries(query, options);
const failureCode = this.readFailureCode(candidates);
if (candidates.length === 0) {
return [];
return this.attachFailureCode([], failureCode);
}
const limit = Math.max(1, Math.min(10, Number(options.limit || 5)));
@@ -387,7 +444,8 @@ class TmdbService {
}));
}));
return expanded.flat().filter((item) => item?.tmdbId && item?.title);
const normalized = expanded.flat().filter((item) => item?.tmdbId && item?.title);
return this.attachFailureCode(normalized, failureCode);
}
async searchSeriesWithSeason(query, seasonNumber, options = {}) {
@@ -397,8 +455,9 @@ class TmdbService {
}
const candidates = await this.searchSeries(query, options);
const failureCode = this.readFailureCode(candidates);
if (candidates.length === 0) {
return [];
return this.attachFailureCode([], failureCode);
}
const limit = Math.max(1, Math.min(10, Number(options.limit || 5)));
@@ -417,11 +476,12 @@ class TmdbService {
};
}));
return withSeasons
const normalized = withSeasons
.filter((candidate) => candidate.season && candidate.season.episodeCount > 0)
.map((candidate) => this.buildSeriesMetadataCandidate(candidate, {
seasonNumber: normalizedSeason
}));
return this.attachFailureCode(normalized, failureCode);
}
}