@@ -1542,7 +1542,7 @@ async function streamHandler(req, res) {
15421542 if ( ! usingCachedSearchResults ) {
15431543 const searchPlans = [ ] ;
15441544 const seenPlans = new Set ( ) ;
1545- const addPlan = ( planType , { tokens = [ ] , rawQuery = null } = { } ) => {
1545+ const addPlan = ( planType , { tokens = [ ] , rawQuery = null , asciiTitle = null } = { } ) => {
15461546 const tokenList = [ ...tokens ] ;
15471547 if ( planType === 'tvsearch' ) {
15481548 if ( seasonToken ) tokenList . push ( seasonToken ) ;
@@ -1559,6 +1559,10 @@ async function streamHandler(req, res) {
15591559 }
15601560 seenPlans . add ( planKey ) ;
15611561 const planRecord = { type : planType , query, rawQuery : rawQuery ? rawQuery : null , tokens : normalizedTokens } ;
1562+ // Store asciiTitle for Newznab searches to avoid non-ASCII character issues
1563+ if ( asciiTitle ) {
1564+ planRecord . asciiTitle = asciiTitle ;
1565+ }
15621566 if ( strictTextMode && planType === 'search' && rawQuery ) {
15631567 const strictPhrase = sanitizeStrictSearchPhrase ( rawQuery ) ;
15641568 if ( strictPhrase ) {
@@ -1570,8 +1574,8 @@ async function streamHandler(req, res) {
15701574 return true ;
15711575 } ;
15721576
1573- // Add ID-based searches immediately (before waiting for TMDb/Cinemeta )
1574- // Note: movieTitle might not be available yet, will be added later if needed
1577+ // Add ID-based searches (will start after title is resolved )
1578+ // Note: We'll add the title to these plans before executing them
15751579 if ( type === 'series' && metaIds . tvdb ) {
15761580 addPlan ( 'tvsearch' , { tokens : [ `{TvdbId:${ metaIds . tvdb } }` ] } ) ;
15771581 }
@@ -1584,21 +1588,6 @@ async function streamHandler(req, res) {
15841588 addPlan ( searchType , { tokens : [ `{ImdbId:${ metaIds . imdb } }` ] } ) ;
15851589 }
15861590
1587- // Start ID-based searches immediately in background
1588- const idSearchPromises = [ ] ;
1589- const idSearchStartTs = Date . now ( ) ;
1590- if ( searchPlans . length > 0 ) {
1591- console . log ( `${ INDEXER_LOG_PREFIX } Starting ${ searchPlans . length } ID-based search(es) immediately` ) ;
1592- idSearchPromises . push ( ...searchPlans . map ( ( plan ) => {
1593- console . log ( `${ INDEXER_LOG_PREFIX } Dispatching early ID plan` , plan ) ;
1594- const planStartTs = Date . now ( ) ;
1595- return Promise . allSettled ( [
1596- executeManagerPlanWithBackoff ( plan ) ,
1597- executeNewznabPlan ( plan ) ,
1598- ] ) . then ( ( settled ) => ( { plan, settled, startTs : planStartTs , endTs : Date . now ( ) } ) ) ;
1599- } ) ) ;
1600- }
1601-
16021591 // Now wait for TMDb to get localized titles (if applicable)
16031592 const tmdbWaitStartTs = Date . now ( ) ;
16041593 if ( tmdbMetadataPromise ) {
@@ -1677,16 +1666,57 @@ async function streamHandler(req, res) {
16771666
16781667 // Update ID-based plans with title if it's now available
16791668 // This ensures NZBHydra searches include the title even when tokens are present
1669+ // For Newznab, prefer ASCII version if title contains non-ASCII characters
16801670 if ( movieTitle && movieTitle . trim ( ) ) {
1671+ const hasNonAscii = / [ ^ \x00 - \x7F ] / . test ( movieTitle ) ;
1672+ let titleToUse = movieTitle . trim ( ) ;
1673+ let asciiTitleToUse = null ;
1674+
1675+ // Check if we have ASCII version from TMDB
1676+ const tmdbTitles = metaSources . find ( s => s ?. _tmdbTitles ) ?. _tmdbTitles ;
1677+ if ( hasNonAscii && tmdbTitles ) {
1678+ // Find English title or ASCII version
1679+ const englishTitle = tmdbTitles . find ( t => t . language ?. startsWith ( 'en-' ) ) ;
1680+ if ( englishTitle ) {
1681+ titleToUse = englishTitle . title ;
1682+ asciiTitleToUse = englishTitle . asciiTitle || null ;
1683+ } else {
1684+ // Try to find any ASCII title
1685+ const asciiTitleObj = tmdbTitles . find ( t => t . asciiTitle && ! / [ ^ \x00 - \x7F ] / . test ( t . asciiTitle ) ) ;
1686+ if ( asciiTitleObj ) {
1687+ titleToUse = asciiTitleObj . asciiTitle ;
1688+ }
1689+ }
1690+ }
1691+
16811692 searchPlans . forEach ( ( plan ) => {
16821693 if ( plan . tokens && plan . tokens . length > 0 && ! plan . rawQuery ) {
16831694 // 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 } ) ;
1695+ plan . rawQuery = titleToUse ;
1696+ if ( asciiTitleToUse ) {
1697+ plan . asciiTitle = asciiTitleToUse ;
1698+ }
1699+ console . log ( `${ INDEXER_LOG_PREFIX } Updated ID plan with title: "${ titleToUse } "` , { type : plan . type , tokens : plan . tokens , hasNonAscii } ) ;
16861700 }
16871701 } ) ;
16881702 }
16891703
1704+ // Start ID-based searches now that we have the title
1705+ const idSearchPromises = [ ] ;
1706+ const idSearchStartTs = Date . now ( ) ;
1707+ const idPlansToExecute = searchPlans . filter ( p => p . tokens && p . tokens . length > 0 ) ;
1708+ if ( idPlansToExecute . length > 0 ) {
1709+ console . log ( `${ INDEXER_LOG_PREFIX } Starting ${ idPlansToExecute . length } ID-based search(es) with title` ) ;
1710+ idSearchPromises . push ( ...idPlansToExecute . map ( ( plan ) => {
1711+ console . log ( `${ INDEXER_LOG_PREFIX } Dispatching ID plan` , plan ) ;
1712+ const planStartTs = Date . now ( ) ;
1713+ return Promise . allSettled ( [
1714+ executeManagerPlanWithBackoff ( plan ) ,
1715+ executeNewznabPlan ( plan ) ,
1716+ ] ) . then ( ( settled ) => ( { plan, settled, startTs : planStartTs , endTs : Date . now ( ) } ) ) ;
1717+ } ) ) ;
1718+ }
1719+
16901720 // Continue with text-based searches using TMDb titles
16911721 const textQueryParts = [ ] ;
16921722 let tmdbLocalizedQuery = null ;
@@ -1718,9 +1748,21 @@ async function streamHandler(req, res) {
17181748 const fallbackIdentifier = hasTmdbTitles ? null : ( incomingImdbId || baseIdentifier ) ;
17191749 textQueryFallbackValue = ( textQueryCandidate || fallbackIdentifier || '' ) . trim ( ) ;
17201750 if ( textQueryFallbackValue ) {
1721- const addedTextPlan = addPlan ( 'search' , { rawQuery : textQueryFallbackValue } ) ;
1751+ // Check if we need ASCII version for Newznab
1752+ const hasNonAscii = / [ ^ \x00 - \x7F ] / . test ( textQueryFallbackValue ) ;
1753+ let asciiFallback = null ;
1754+ if ( hasNonAscii ) {
1755+ const tmdbTitles = metaSources . find ( s => s ?. _tmdbTitles ) ?. _tmdbTitles ;
1756+ if ( tmdbTitles ) {
1757+ const englishTitle = tmdbTitles . find ( t => t . language ?. startsWith ( 'en-' ) ) ;
1758+ if ( englishTitle ) {
1759+ asciiFallback = `${ englishTitle . asciiTitle || englishTitle . title } ${ textQueryFallbackValue . replace ( / ^ [ ^ \x00 - \x7F ] + / , '' ) . replace ( / [ ^ \x00 - \x7F ] + $ / , '' ) } ` . trim ( ) ;
1760+ }
1761+ }
1762+ }
1763+ const addedTextPlan = addPlan ( 'search' , { rawQuery : textQueryFallbackValue , asciiTitle : asciiFallback } ) ;
17221764 if ( addedTextPlan ) {
1723- console . log ( `${ INDEXER_LOG_PREFIX } Added text search plan` , { query : textQueryFallbackValue } ) ;
1765+ console . log ( `${ INDEXER_LOG_PREFIX } Added text search plan` , { query : textQueryFallbackValue , hasNonAscii , asciiFallback : asciiFallback || 'none' } ) ;
17241766 } else {
17251767 console . log ( `${ INDEXER_LOG_PREFIX } Text search plan already present` , { query : textQueryFallbackValue } ) ;
17261768 }
@@ -1734,28 +1776,36 @@ async function streamHandler(req, res) {
17341776 if ( tmdbTitles && tmdbTitles . length > 0 && ! isSpecialRequest ) {
17351777 console . log ( `[TMDB] Adding ${ tmdbTitles . length } language-specific search plans` ) ;
17361778 tmdbTitles . forEach ( ( titleObj ) => {
1737- let localizedQuery = titleObj . title ;
1779+ // For titles with non-ASCII characters, prefer ASCII version for Newznab
1780+ // Store both in the plan so Newznab can choose ASCII automatically
1781+ const hasNonAscii = / [ ^ \x00 - \x7F ] / . test ( titleObj . title ) ;
1782+ const queryTitle = hasNonAscii && titleObj . asciiTitle ? titleObj . asciiTitle : titleObj . title ;
1783+
1784+ let localizedQuery = queryTitle ;
17381785 if ( type === 'movie' && Number . isFinite ( releaseYear ) ) {
17391786 localizedQuery = `${ localizedQuery } ${ releaseYear } ` ;
17401787 } else if ( type === 'series' && Number . isFinite ( seasonNum ) && Number . isFinite ( episodeNum ) ) {
17411788 localizedQuery = `${ localizedQuery } S${ String ( seasonNum ) . padStart ( 2 , '0' ) } E${ String ( episodeNum ) . padStart ( 2 , '0' ) } ` ;
17421789 }
1743- const added = addPlan ( 'search' , { rawQuery : localizedQuery } ) ;
1744- if ( added ) {
1745- console . log ( `${ INDEXER_LOG_PREFIX } Added TMDb ${ titleObj . language } search plan` , { query : localizedQuery } ) ;
1746- }
1747-
1748- // Add ASCII fallback if different
1749- if ( titleObj . asciiTitle && titleObj . asciiTitle !== titleObj . title ) {
1750- let asciiQuery = titleObj . asciiTitle ;
1790+
1791+ // Include asciiTitle in plan for Newznab to use if needed
1792+ const asciiQueryTitle = titleObj . asciiTitle && titleObj . asciiTitle !== titleObj . title ? titleObj . asciiTitle : null ;
1793+ let asciiQuery = null ;
1794+ if ( asciiQueryTitle ) {
1795+ asciiQuery = asciiQueryTitle ;
17511796 if ( type === 'movie' && Number . isFinite ( releaseYear ) ) {
17521797 asciiQuery = `${ asciiQuery } ${ releaseYear } ` ;
17531798 } else if ( type === 'series' && Number . isFinite ( seasonNum ) && Number . isFinite ( episodeNum ) ) {
17541799 asciiQuery = `${ asciiQuery } S${ String ( seasonNum ) . padStart ( 2 , '0' ) } E${ String ( episodeNum ) . padStart ( 2 , '0' ) } ` ;
17551800 }
1756- const addedAscii = addPlan ( 'search' , { rawQuery : asciiQuery } ) ;
1757- if ( addedAscii ) {
1758- console . log ( `${ INDEXER_LOG_PREFIX } Added TMDb ${ titleObj . language } ASCII search plan` , { query : asciiQuery } ) ;
1801+ }
1802+
1803+ const added = addPlan ( 'search' , { rawQuery : localizedQuery , asciiTitle : asciiQuery } ) ;
1804+ if ( added ) {
1805+ if ( hasNonAscii && asciiQuery ) {
1806+ console . log ( `${ INDEXER_LOG_PREFIX } Added TMDb ${ titleObj . language } search plan (using ASCII for Newznab)` , { query : localizedQuery , asciiQuery } ) ;
1807+ } else {
1808+ console . log ( `${ INDEXER_LOG_PREFIX } Added TMDb ${ titleObj . language } search plan` , { query : localizedQuery } ) ;
17591809 }
17601810 }
17611811
@@ -1981,7 +2031,10 @@ async function streamHandler(req, res) {
19812031 }
19822032
19832033 // Now execute remaining text-based search plans (exclude already-processed ID plans)
1984- const remainingPlans = searchPlans . filter ( p => ! processedIdPlans . has ( `${ p . type } |${ p . query } ` ) ) ;
2034+ const remainingPlans = searchPlans . filter ( p => {
2035+ const planKey = `${ p . type } |${ p . query } ` ;
2036+ return ! processedIdPlans . has ( planKey ) && ( ! p . tokens || p . tokens . length === 0 ) ;
2037+ } ) ;
19852038 console . log ( `${ INDEXER_LOG_PREFIX } Executing ${ remainingPlans . length } text-based search plan(s)` ) ;
19862039 const textSearchStartTs = Date . now ( ) ;
19872040 const planExecutions = remainingPlans . map ( ( plan ) => {
0 commit comments