@@ -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 ) => {
0 commit comments