Skip to content

Commit 6746354

Browse files
author
strxno
committed
fix issue with nzbhydra
1 parent 2774e94 commit 6746354

2 files changed

Lines changed: 173 additions & 32 deletions

File tree

server.js

Lines changed: 159 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1487,13 +1487,20 @@ async function streamHandler(req, res) {
14871487
return Number.isFinite(parsed) ? parsed : null;
14881488
};
14891489

1490-
// Prioritize originalTitle to preserve original language names
1490+
// Prefer English/title first, fallback to originalTitle if no results found later
14911491
let movieTitle = pickFirstDefined(
14921492
...collectValues(
1493-
(src) => src?.originalTitle,
1494-
(src) => src?.original_title,
14951493
(src) => src?.name,
1496-
(src) => src?.title
1494+
(src) => src?.title,
1495+
(src) => src?.originalTitle,
1496+
(src) => src?.original_title
1497+
)
1498+
);
1499+
// Store original title separately for fallback
1500+
let originalTitle = pickFirstDefined(
1501+
...collectValues(
1502+
(src) => src?.originalTitle,
1503+
(src) => src?.original_title
14971504
)
14981505
);
14991506

@@ -1564,6 +1571,7 @@ async function streamHandler(req, res) {
15641571
};
15651572

15661573
// Add ID-based searches immediately (before waiting for TMDb/Cinemeta)
1574+
// Note: movieTitle might not be available yet, will be added later if needed
15671575
if (type === 'series' && metaIds.tvdb) {
15681576
addPlan('tvsearch', { tokens: [`{TvdbId:${metaIds.tvdb}}`] });
15691577
}
@@ -1605,13 +1613,15 @@ async function streamHandler(req, res) {
16051613
}
16061614
}
16071615
// Create a metadata object compatible with existing code
1608-
// Prioritize originalTitle from TMDB to preserve original language names
1616+
// Prefer English title first, but keep originalTitle available for fallback
1617+
const tmdbEnglishTitle = tmdbMetadata.titles?.find(t => t.language?.startsWith('en-'))?.title;
1618+
const tmdbPrimaryTitle = tmdbEnglishTitle || tmdbMetadata.originalTitle;
16091619
metaSources.push({
16101620
imdb_id: incomingImdbId,
16111621
tmdb_id: String(tmdbMetadata.tmdbId),
1612-
title: tmdbMetadata.originalTitle, // Use originalTitle as primary title
1613-
originalTitle: tmdbMetadata.originalTitle, // Explicitly set originalTitle
1614-
name: tmdbMetadata.originalTitle, // Also set name for compatibility
1622+
title: tmdbPrimaryTitle, // Use English title if available, otherwise original
1623+
originalTitle: tmdbMetadata.originalTitle, // Keep originalTitle for fallback
1624+
name: tmdbPrimaryTitle, // Also set name for compatibility
16151625
year: tmdbMetadata.year,
16161626
_tmdbTitles: tmdbMetadata.titles, // Store for later use
16171627
});
@@ -1630,13 +1640,22 @@ async function streamHandler(req, res) {
16301640
}
16311641

16321642
if (!movieTitle) {
1633-
// Prioritize originalTitle to preserve original language names
1643+
// Prefer English/title first, fallback to originalTitle if no results found later
16341644
movieTitle = pickFirstDefined(
16351645
...collectValues(
1636-
(src) => src?.originalTitle,
1637-
(src) => src?.original_title,
16381646
(src) => src?.name,
1639-
(src) => src?.title
1647+
(src) => src?.title,
1648+
(src) => src?.originalTitle,
1649+
(src) => src?.original_title
1650+
)
1651+
);
1652+
}
1653+
// Store original title separately for fallback if not already set
1654+
if (!originalTitle) {
1655+
originalTitle = pickFirstDefined(
1656+
...collectValues(
1657+
(src) => src?.originalTitle,
1658+
(src) => src?.original_title
16401659
)
16411660
);
16421661
}
@@ -1656,6 +1675,18 @@ async function streamHandler(req, res) {
16561675

16571676
console.log('[REQUEST] Resolved title/year', { movieTitle, releaseYear, elapsedMs: Date.now() - requestStartTs });
16581677

1678+
// Update ID-based plans with title if it's now available
1679+
// This ensures NZBHydra searches include the title even when tokens are present
1680+
if (movieTitle && movieTitle.trim()) {
1681+
searchPlans.forEach((plan) => {
1682+
if (plan.tokens && plan.tokens.length > 0 && !plan.rawQuery) {
1683+
// Add title to rawQuery so it's included in the search
1684+
plan.rawQuery = movieTitle.trim();
1685+
console.log(`${INDEXER_LOG_PREFIX} Updated ID plan with title: "${movieTitle}"`, { type: plan.type, tokens: plan.tokens });
1686+
}
1687+
});
1688+
}
1689+
16591690
// Continue with text-based searches using TMDb titles
16601691
const textQueryParts = [];
16611692
let tmdbLocalizedQuery = null;
@@ -1752,34 +1783,31 @@ async function streamHandler(req, res) {
17521783
// Check if we have TMDb titles - prefer original title for Easynews to match original language releases
17531784
const tmdbTitles = metaSources.find(s => s?._tmdbTitles)?._tmdbTitles;
17541785
if (tmdbTitles && tmdbTitles.length > 0) {
1755-
// Prefer original title from TMDB (first title is usually the original language)
1756-
// This helps find releases with original language names like "Kompani Lauritzen"
1757-
const originalTitle = tmdbTitles.find(t => {
1758-
// Find the title that matches the original language (usually first in array)
1759-
// or fall back to first non-English title
1760-
return t.language && !t.language.startsWith('en-');
1761-
}) || tmdbTitles[0]; // Fallback to first title if no non-English found
1762-
1763-
if (originalTitle) {
1764-
// Use ASCII version if available (better for Easynews search), otherwise use original
1765-
easynewsRawQuery = originalTitle.asciiTitle || originalTitle.title;
1786+
// Prefer English title first for Easynews (better search coverage)
1787+
// Will fallback to original title if no results found
1788+
const englishTitle = tmdbTitles.find(t => t.language && t.language.startsWith('en-'));
1789+
if (englishTitle) {
1790+
easynewsRawQuery = englishTitle.asciiTitle || englishTitle.title;
17661791
if (type === 'movie' && Number.isFinite(releaseYear)) {
17671792
easynewsRawQuery = `${easynewsRawQuery} ${releaseYear}`;
17681793
} else if (type === 'series' && Number.isFinite(seasonNum) && Number.isFinite(episodeNum)) {
17691794
easynewsRawQuery = `${easynewsRawQuery} S${String(seasonNum).padStart(2, '0')}E${String(episodeNum).padStart(2, '0')}`;
17701795
}
1771-
console.log('[EASYNEWS] Using original title from TMDb:', easynewsRawQuery, `[${originalTitle.language}]`);
1796+
console.log('[EASYNEWS] Using English title from TMDb:', easynewsRawQuery);
17721797
} else {
1773-
// Fallback to English title if no original found
1774-
const englishTitle = tmdbTitles.find(t => t.language && t.language.startsWith('en-'));
1775-
if (englishTitle) {
1776-
easynewsRawQuery = englishTitle.asciiTitle || englishTitle.title;
1798+
// Fallback to original title if no English found
1799+
const originalTitleObj = tmdbTitles.find(t => {
1800+
return t.language && !t.language.startsWith('en-');
1801+
}) || tmdbTitles[0];
1802+
1803+
if (originalTitleObj) {
1804+
easynewsRawQuery = originalTitleObj.asciiTitle || originalTitleObj.title;
17771805
if (type === 'movie' && Number.isFinite(releaseYear)) {
17781806
easynewsRawQuery = `${easynewsRawQuery} ${releaseYear}`;
17791807
} else if (type === 'series' && Number.isFinite(seasonNum) && Number.isFinite(episodeNum)) {
17801808
easynewsRawQuery = `${easynewsRawQuery} S${String(seasonNum).padStart(2, '0')}E${String(episodeNum).padStart(2, '0')}`;
17811809
}
1782-
console.log('[EASYNEWS] Using English title from TMDb (fallback):', easynewsRawQuery);
1810+
console.log('[EASYNEWS] Using original title from TMDb (fallback):', easynewsRawQuery, `[${originalTitleObj.language}]`);
17831811
}
17841812
}
17851813
}
@@ -2078,7 +2106,80 @@ async function streamHandler(req, res) {
20782106
}
20792107

20802108
const aggregationCount = usingStrictIdMatching ? aggregatedResults.length : resultsByKey.size;
2081-
if (aggregationCount === 0) {
2109+
2110+
// If no results and we have an original title different from current title, retry with original
2111+
if (aggregationCount === 0 && originalTitle && originalTitle !== movieTitle && originalTitle.trim()) {
2112+
console.log(`${INDEXER_LOG_PREFIX} No results with "${movieTitle}", retrying with original title "${originalTitle}"`);
2113+
2114+
// Build new search plans with original title
2115+
const originalTextQueryParts = [originalTitle];
2116+
if (type === 'movie' && Number.isFinite(releaseYear)) {
2117+
originalTextQueryParts.push(String(releaseYear));
2118+
} else if (type === 'series' && Number.isFinite(seasonNum) && Number.isFinite(episodeNum)) {
2119+
originalTextQueryParts.push(`S${String(seasonNum).padStart(2, '0')}E${String(episodeNum).padStart(2, '0')}`);
2120+
}
2121+
const originalTextQuery = originalTextQueryParts.join(' ').trim();
2122+
2123+
if (originalTextQuery) {
2124+
const originalPlan = {
2125+
type: 'search',
2126+
query: originalTextQuery,
2127+
rawQuery: originalTextQuery,
2128+
tokens: [],
2129+
strictMatch: shouldAddTextSearch && !isSpecialRequest,
2130+
strictPhrase: shouldAddTextSearch && !isSpecialRequest ? originalTextQuery.toLowerCase().trim() : null
2131+
};
2132+
2133+
console.log(`${INDEXER_LOG_PREFIX} Retrying with original title plan`, originalPlan);
2134+
const originalPlanStartTs = Date.now();
2135+
2136+
const originalPlanSettled = await Promise.allSettled([
2137+
executeManagerPlanWithBackoff(originalPlan),
2138+
executeNewznabPlan(originalPlan),
2139+
]);
2140+
2141+
const originalManagerSet = originalPlanSettled[0];
2142+
const originalNewznabSet = originalPlanSettled[1];
2143+
const originalManagerResults = originalManagerSet.status === 'fulfilled' ? (Array.isArray(originalManagerSet.value) ? originalManagerSet.value : []) : [];
2144+
const originalNewznabResults = originalNewznabSet.status === 'fulfilled' ? (Array.isArray(originalNewznabSet.value?.results) ? originalNewznabSet.value.results : []) : [];
2145+
const originalCombinedResults = [...originalManagerResults, ...originalNewznabResults];
2146+
2147+
console.log(`${INDEXER_LOG_PREFIX} Original title search returned ${originalCombinedResults.length} results in ${Date.now() - originalPlanStartTs} ms`);
2148+
2149+
if (originalCombinedResults.length > 0) {
2150+
const originalFiltered = originalCombinedResults.filter((item) => {
2151+
if (!item || typeof item !== 'object' || !item.downloadUrl) return false;
2152+
return resultMatchesStrictPlan(originalPlan, item);
2153+
});
2154+
2155+
originalFiltered.forEach((item) => rawAggregatedResults.push({ result: item, planType: originalPlan.type }));
2156+
2157+
if (usingStrictIdMatching) {
2158+
aggregatedResults.push(...originalFiltered.map((item) => ({ result: item, planType: originalPlan.type })));
2159+
} else {
2160+
for (const item of originalFiltered) {
2161+
const key = deriveResultKey(item);
2162+
if (key && !resultsByKey.has(key)) {
2163+
resultsByKey.set(key, { result: item, planType: originalPlan.type });
2164+
}
2165+
}
2166+
}
2167+
2168+
planSummaries.push({
2169+
planType: originalPlan.type,
2170+
query: originalPlan.query,
2171+
total: originalCombinedResults.length,
2172+
filtered: originalFiltered.length,
2173+
uniqueAdded: originalFiltered.length,
2174+
managerCount: originalManagerResults.length,
2175+
newznabCount: originalNewznabResults.length,
2176+
});
2177+
}
2178+
}
2179+
}
2180+
2181+
const finalAggregationCount = usingStrictIdMatching ? aggregatedResults.length : resultsByKey.size;
2182+
if (finalAggregationCount === 0) {
20822183
console.warn(`${INDEXER_LOG_PREFIX} ⚠ All ${searchPlans.length} search plans returned no NZB results`);
20832184
} else if (usingStrictIdMatching) {
20842185
console.log(`${INDEXER_LOG_PREFIX} ✅ Aggregated NZB results with strict ID matching`, {
@@ -2138,6 +2239,34 @@ async function streamHandler(req, res) {
21382239
console.warn('[EASYNEWS] Search timed out or failed', err?.message || err);
21392240
}
21402241
console.log(`[EASYNEWS] Easynews search completed in ${Date.now() - easynewsWaitStartTs} ms`);
2242+
2243+
// If Easynews returned 0 results and we have original title, retry with original
2244+
if (easynewsResults.length === 0 && originalTitle && originalTitle !== movieTitle && originalTitle.trim() && easynewsSearchParams) {
2245+
console.log(`[EASYNEWS] No results with "${movieTitle}", retrying with original title "${originalTitle}"`);
2246+
const originalEasynewsQueryParts = [originalTitle];
2247+
if (type === 'movie' && Number.isFinite(releaseYear)) {
2248+
originalEasynewsQueryParts.push(String(releaseYear));
2249+
} else if (type === 'series' && Number.isFinite(seasonNum) && Number.isFinite(episodeNum)) {
2250+
originalEasynewsQueryParts.push(`S${String(seasonNum).padStart(2, '0')}E${String(episodeNum).padStart(2, '0')}`);
2251+
}
2252+
const originalEasynewsQuery = originalEasynewsQueryParts.join(' ').trim();
2253+
2254+
if (originalEasynewsQuery) {
2255+
try {
2256+
const originalEasynewsResults = await easynewsService.searchEasynews({
2257+
...easynewsSearchParams,
2258+
rawQuery: originalEasynewsQuery,
2259+
});
2260+
if (Array.isArray(originalEasynewsResults) && originalEasynewsResults.length > 0) {
2261+
console.log('[EASYNEWS] Original title search returned results', { count: originalEasynewsResults.length });
2262+
easynewsResults = originalEasynewsResults;
2263+
}
2264+
} catch (err) {
2265+
console.warn('[EASYNEWS] Original title search failed', err?.message || err);
2266+
}
2267+
}
2268+
}
2269+
21412270
if (Array.isArray(easynewsResults) && easynewsResults.length > 0) {
21422271
console.log('[EASYNEWS] Adding results to final list', { count: easynewsResults.length });
21432272
easynewsResults.forEach((item) => {

src/services/indexer.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,22 @@ function buildHydraSearchParams(plan) {
161161
plan.tokens.forEach((token) => applyTokenToHydraParams(token, params));
162162
}
163163

164+
// Include title in query parameter even when tokens are present
165+
// NZBHydra can use both structured params (season/ep) and text query together
164166
if (plan.rawQuery) {
165167
params.q = plan.rawQuery;
166-
} else if ((!plan.tokens || plan.tokens.length === 0) && plan.query) {
167-
params.q = plan.query;
168+
} else if (plan.query) {
169+
// Extract title from query if it contains tokens
170+
// If query is just tokens (like "{ImdbId:tt123} {Season:1} {Episode:1}"),
171+
// we need to get the title from elsewhere or use the query as-is
172+
// For now, use the query which may contain the title if it was included
173+
const queryText = plan.query.trim();
174+
// Only set q if it's not just tokens (tokens are already in structured params)
175+
// Check if query looks like it contains actual text, not just token patterns
176+
const isJustTokens = /^\{[^}]+\}(\s+\{[^}]+\})*$/.test(queryText);
177+
if (!isJustTokens || !plan.tokens || plan.tokens.length === 0) {
178+
params.q = queryText;
179+
}
168180
}
169181

170182
return params;

0 commit comments

Comments
 (0)