@@ -13,7 +13,7 @@ import { formatMediaFlowUrl } from './utils/mediaflow';
1313import { mergeDynamic , loadDynamicChannels , purgeOldDynamicEvents , invalidateDynamicChannels , getDynamicFilePath , getDynamicFileStats } from './utils/dynamicChannels' ;
1414import { resolveAnimeTitle , AnimeResolvedTitle } from './utils/animeTitleResolver' ;
1515// --- Lightweight declarations to avoid TS complaints if @types/node non installati ---
16- // (Non sostituiscono l'uso consigliato di @types/node, ma evitano errori bloccanti.)
16+ // (Non sostituiscono l'uso consigliato di @types/node, ma evitano errori bloccanti.)
1717// eslint-disable-next-line @typescript-eslint/no-explicit-any
1818declare const __dirname : string ;
1919// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -108,7 +108,12 @@ const VAVOO_LOG_SIG_FULL: boolean = (() => { try { const v = (process?.env?.VAVO
108108const VAVOO_WORKER_URLS = ( process . env . VAVOO_WORKER_URL || '' ) . split ( ',' ) . map ( ( u : string ) => u . trim ( ) ) . filter ( Boolean ) ;
109109const VAVOO_API_UA = 'electron-fetch/1.0 electron (+https://github.com/arantes555/electron-fetch)' ;
110110const VAVOO_TS_UA = 'VAVOO/2.6' ;
111+ // --- Env toggles to show/hide Vavoo stream types (default: all true) ---
112+ const VAVOO_CLEAN : boolean = ( ( ) => { try { const v = ( process ?. env ?. VAVOO_CLEAN || '' ) . toLowerCase ( ) ; if ( ! v ) return true ; return ! ( v === '0' || v === 'false' || v === 'off' ) ; } catch { return true ; } } ) ( ) ;
113+ const VAVOO_PROXY : boolean = ( ( ) => { try { const v = ( process ?. env ?. VAVOO_PROXY || '' ) . toLowerCase ( ) ; if ( ! v ) return true ; return ! ( v === '0' || v === 'false' || v === 'off' ) ; } catch { return true ; } } ) ( ) ;
114+ const VAVOO_DIRECT : boolean = ( ( ) => { try { const v = ( process ?. env ?. VAVOO_DIRECT || '' ) . toLowerCase ( ) ; if ( ! v ) return true ; return ! ( v === '0' || v === 'false' || v === 'off' ) ; } catch { return true ; } } ) ( ) ;
111115console . log ( `[VAVOO] Worker URLs loaded: ${ VAVOO_WORKER_URLS . length } ` ) ;
116+ console . log ( `[VAVOO] Stream toggles: CLEAN=${ VAVOO_CLEAN } PROXY=${ VAVOO_PROXY } DIRECT=${ VAVOO_DIRECT } ` ) ;
112117function maskSig ( sig : string , keepStart = 12 , keepEnd = 6 ) : string { try { if ( ! sig ) return '' ; const len = sig . length ; const head = sig . slice ( 0 , Math . min ( keepStart , len ) ) ; const tail = len > keepStart ? sig . slice ( Math . max ( len - keepEnd , keepStart ) ) : '' ; const hidden = Math . max ( 0 , len - head . length - tail . length ) ; const mask = hidden > 0 ? '*' . repeat ( Math . min ( hidden , 32 ) ) + ( hidden > 32 ? `(+${ hidden - 32 } )` : '' ) : '' ; return `${ head } ${ mask } ${ tail } ` ; } catch { return '' ; } }
113118
114119function getClientIpFromReq ( req : any ) : string | null {
@@ -484,6 +489,7 @@ const execFilePromise = util.promisify(execFile);
484489const dynamicStreamCache = new Map < string , { finalUrl : string ; ts : number } > ( ) ;
485490const DYNAMIC_STREAM_TTL_MS = 5 * 60 * 1000 ; // 5 minuti
486491
492+
487493async function resolveDynamicEventUrl ( dUrl : string , providerTitle : string , mfpUrl ?: string , mfpPsw ?: string ) : Promise < { url : string ; title : string } > {
488494 if ( ! mfpUrl ) return { url : dUrl , title : providerTitle } ;
489495 // Normalizza mfpUrl per evitare doppio slash
@@ -3416,6 +3422,7 @@ function createBuilder(initialConfig: AddonConfig = {}) {
34163422 const reqObj : any = ( global as any ) . lastExpressRequest ;
34173423 const clientIp = getClientIpFromReq ( reqObj ) ;
34183424 let vavooCleanResolved : { url : string ; headers : Record < string , string > } | null = null ;
3425+ if ( VAVOO_CLEAN ) {
34193426 try {
34203427 const clean = await resolveVavooCleanUrl ( vUrl , clientIp ) ;
34213428 if ( clean && clean . url ) {
@@ -3430,7 +3437,9 @@ function createBuilder(initialConfig: AddonConfig = {}) {
34303437 vdbg ( 'Alias clean resolve failed' , { alias, error : msg } ) ;
34313438 console . log ( '[VAVOO] Clean resolve skipped/failed:' , msg ) ;
34323439 }
3440+ } else { vdbg ( 'VAVOO_CLEAN=false, skip alias clean resolve' ) ; }
34333441 // Iniezione Vavoo/MFP: incapsula SEMPRE l'URL vavoo.to originale (come in Live TV), senza extractor
3442+ if ( VAVOO_PROXY ) {
34343443 try {
34353444 if ( mfpUrl ) {
34363445 const passwordParam = mfpPsw ? `&api_password=${ encodeURIComponent ( mfpPsw ) } ` : '' ;
@@ -3446,6 +3455,20 @@ function createBuilder(initialConfig: AddonConfig = {}) {
34463455 } catch ( e2 ) {
34473456 vdbg ( 'Vavoo/MFP injection error' , String ( ( e2 as any ) ?. message || e2 ) ) ;
34483457 }
3458+ } else { vdbg ( 'VAVOO_PROXY=false, skip alias MFP injection' ) ; }
3459+ // Iniezione Vavoo Direct (raw URL) per eventi dinamici
3460+ if ( VAVOO_DIRECT ) {
3461+ try {
3462+ const title4 = `🎯 ${ alias } (Vavoo Direct) [ITA]` ;
3463+ const directStream = { url : vUrl , title : title4 , behaviorHints : { notWebReady : true } as any } ;
3464+ let insertAt = 0 ;
3465+ try { if ( streams . length && / \( V a v o o \) / i. test ( streams [ 0 ] . title ) ) insertAt = 1 ; } catch { }
3466+ try { streams . splice ( insertAt , 0 , directStream ) ; } catch { streams . push ( directStream ) ; }
3467+ vdbg ( 'Alias Vavoo Direct injected' , { alias, url : vUrl . substring ( 0 , 140 ) } ) ;
3468+ } catch ( e3 ) {
3469+ vdbg ( 'Vavoo Direct injection error' , String ( ( e3 as any ) ?. message || e3 ) ) ;
3470+ }
3471+ } else { vdbg ( 'VAVOO_DIRECT=false, skip alias direct injection' ) ; }
34493472 console . log ( `✅ [VAVOO] Injected first stream from alias='${ alias } ' -> ${ vUrl . substring ( 0 , 60 ) } ...` ) ;
34503473 } else {
34513474 console . log ( `⚠️ [VAVOO] Alias trovato ma nessun URL in cache: '${ alias } '` ) ;
@@ -4903,18 +4926,19 @@ function createBuilder(initialConfig: AddonConfig = {}) {
49034926 if ( foundVavooLinks . length > 0 ) {
49044927 foundVavooLinks . forEach ( ( { url, key } , idx ) => {
49054928 const streamTitle = `[✌️ V-${ idx + 1 } ] ${ channel . name } [ITA]` ;
4906- if ( mfpUrl ) {
4929+ if ( VAVOO_PROXY && mfpUrl ) {
49074930 const passwordParam = mfpPsw ? `&api_password=${ encodeURIComponent ( mfpPsw ) } ` : '' ;
49084931 const vavooProxyUrl = `${ mfpUrl } /proxy/hls/manifest.m3u8?d=${ encodeURIComponent ( url ) } ${ passwordParam } ` ;
49094932 streams . push ( {
49104933 title : streamTitle ,
49114934 url : vavooProxyUrl
49124935 } ) ;
49134936 } else {
4914- // Richiesta: nascondere stream Vavoo direct senza MFP URL
4937+ // Richiesta: nascondere stream Vavoo direct senza MFP URL (o VAVOO_PROXY=false)
49154938 }
49164939 vavooFoundUrls . push ( url ) ;
49174940 // For each found link, also prepare a clean variant labeled per index (➡️ V-1, V-2, ...)
4941+ if ( VAVOO_CLEAN ) {
49184942 const reqObj : any = ( global as any ) . lastExpressRequest ;
49194943 let clientIpForClean = getClientIpFromReq ( reqObj ) ;
49204944 // Fallback: use cached IP from middleware (tvvoo approach)
@@ -4941,6 +4965,7 @@ function createBuilder(initialConfig: AddonConfig = {}) {
49414965 vdbg ( 'Variant clean failed' , { index : idx + 1 , error : ( err as any ) ?. message || err } ) ;
49424966 }
49434967 } ) ( ) ) ;
4968+ } // end VAVOO_CLEAN gate
49444969 } ) ;
49454970 console . log ( `[VAVOO] RISULTATO: trovati ${ foundVavooLinks . length } link, stream generati:` , streams . map ( s => s . title ) ) ;
49464971 } else {
@@ -4950,20 +4975,20 @@ function createBuilder(initialConfig: AddonConfig = {}) {
49504975 const links = Array . isArray ( exact ) ? exact : [ exact ] ;
49514976 links . forEach ( ( url , idx ) => {
49524977 const streamTitle = `[✌️ V-${ idx + 1 } ] ${ channel . name } [ITA]` ;
4953- // Fix: Do not add Proxy streams if Vavoo Clean mode is enabled (vavooNoMfpEnabled=true)
4954- // This prevents duplication since Clean streams are added separately via resolveVavooCleanUrl
4955- if ( mfpUrl && config . vavooNoMfpEnabled !== true ) {
4978+ // Gate: only add Proxy streams if VAVOO_PROXY=true and MFP configured
4979+ if ( VAVOO_PROXY && mfpUrl && config . vavooNoMfpEnabled !== true ) {
49564980 const passwordParam = mfpPsw ? `&api_password=${ encodeURIComponent ( mfpPsw ) } ` : '' ;
49574981 const vavooProxyUrl = `${ mfpUrl } /proxy/hls/manifest.m3u8?d=${ encodeURIComponent ( url ) } ${ passwordParam } ` ;
49584982 streams . push ( {
49594983 title : streamTitle ,
49604984 url : vavooProxyUrl
49614985 } ) ;
49624986 } else {
4963- // Richiesta: nascondere stream Vavoo direct senza MFP URL (o se siamo in Clean mode, li aggiungiamo dopo )
4987+ // Richiesta: nascondere stream Vavoo direct senza MFP URL (o VAVOO_PROXY=false, o Clean mode)
49644988 }
49654989 vavooFoundUrls . push ( url ) ;
49664990 // Prepare clean variant per index as well
4991+ if ( VAVOO_CLEAN ) {
49674992 const reqObj : any = ( global as any ) . lastExpressRequest ;
49684993 let clientIpForClean = getClientIpFromReq ( reqObj ) ;
49694994 // Fallback: use cached IP from middleware (tvvoo approach)
@@ -4987,6 +5012,7 @@ function createBuilder(initialConfig: AddonConfig = {}) {
49875012 vdbg ( 'Variant clean failed' , { index : idx + 1 , error : ( err as any ) ?. message || err } ) ;
49885013 }
49895014 } ) ( ) ) ;
5015+ } // end VAVOO_CLEAN gate
49905016 } ) ;
49915017 console . log ( `[VAVOO] RISULTATO: fallback chiave esatta, trovati ${ links . length } link, stream generati:` , streams . map ( s => s . title ) ) ;
49925018 } else {
@@ -5307,7 +5333,7 @@ function createBuilder(initialConfig: AddonConfig = {}) {
53075333 }
53085334
53095335
5310- const allowVavooClean = true ; // simplified: always allow clean Vavoo variant
5336+ const allowVavooClean = VAVOO_CLEAN ; // env-gated: VAVOO_CLEAN=false hides clean streams
53115337 for ( const s of streams ) {
53125338 // Support special marker '#headers#<b64json>' to attach headers properly
53135339 const marker = '#headers#' ;
@@ -5545,7 +5571,7 @@ function createBuilder(initialConfig: AddonConfig = {}) {
55455571 }
55465572 // Dopo aver popolato streams (nella logica TV):
55475573 for ( const s of streams ) {
5548- const allowVavooClean = config . vavooNoMfpEnabled === true ; // default false se non specificato
5574+ const allowVavooClean = VAVOO_CLEAN && config . vavooNoMfpEnabled === true ; // env-gated + user toggle
55495575 const marker = '#headers#' ;
55505576 if ( s . url . includes ( marker ) ) {
55515577 const [ pureUrl , b64 ] = s . url . split ( marker ) ;
@@ -5567,8 +5593,8 @@ function createBuilder(initialConfig: AddonConfig = {}) {
55675593 }
55685594 }
55695595
5570- // Direct vavoo streams (raw URL, VLC only) - only when Vavoo NO MFP 🔓 is enabled
5571- if ( config . vavooNoMfpEnabled === true && vavooFoundUrls . length > 0 ) {
5596+ // Direct vavoo streams (raw URL, VLC only) - env-gated by VAVOO_DIRECT + user toggle vavooNoMfpEnabled
5597+ if ( VAVOO_DIRECT && config . vavooNoMfpEnabled === true && vavooFoundUrls . length > 0 ) {
55725598 for ( let i = 0 ; i < vavooFoundUrls . length ; i ++ ) {
55735599 const directTitle = `[🎯 V-${ i + 1 } ] ${ channel . name } (VLC only) [ITA]` ;
55745600 allStreams . push ( { name : 'Direct' , title : directTitle , url : vavooFoundUrls [ i ] , behaviorHints : { notWebReady : true } as any } ) ;
@@ -6207,49 +6233,64 @@ function createBuilder(initialConfig: AddonConfig = {}) {
62076233
62086234 // ── Pre-risoluzione centralizzata titolo anime ──
62096235 // UNA SOLA catena di chiamate API per tutti e 3 i provider anime.
6210- // Il risultato (englishTitle, malId, tmdbId, startDate) viene condiviso.
6236+ // Il risultato (englishTitle, malId, tmdbId, kitsuId, titleHints, episodeMode, absoluteEpisode, startDate)
6237+ // viene condiviso tra AnimeUnity, AnimeSaturn e AnimeWorld.
6238+ // Copre: kitsu:, mal:, IMDB (tt...), TMDB (tmdb:)
62116239 const anyAnimeEnabled = animeUnityEnabled || animeSaturnEnabled || animeWorldEnabled ;
6212- const isAnimeId = id . startsWith ( 'kitsu:' ) || id . startsWith ( 'mal:' ) ;
6240+ const isKitsuMalId = id . startsWith ( 'kitsu:' ) || id . startsWith ( 'mal:' ) ;
6241+ const isImdbId = id . startsWith ( 'tt' ) ;
6242+ const isTmdbId = id . startsWith ( 'tmdb:' ) ;
62136243 let preResolved : AnimeResolvedTitle | null = null ;
6214- if ( anyAnimeEnabled && isAnimeId ) {
6244+ if ( anyAnimeEnabled ) {
62156245 const tmdbKey = config . tmdbApiKey || process . env . TMDB_API_KEY || '40a9faa1f6741afb2c0c40238d85f8d0' ;
6216- preResolved = await resolveAnimeTitle ( id , tmdbKey ) ;
6246+ if ( isKitsuMalId ) {
6247+ // Path kitsu/mal — invariato, passa season/episode per futura compatibilità
6248+ preResolved = await resolveAnimeTitle ( id , tmdbKey , seasonNumber ?? undefined , episodeNumber ?? undefined ) ;
6249+ } else if ( isImdbId ) {
6250+ // NUOVO: pre-resolution unificata per IMDB
6251+ const imdbIdOnly = id . split ( ':' ) [ 0 ] ; // tt0388629 (senza :season:episode)
6252+ preResolved = await resolveAnimeTitle ( `imdb:${ imdbIdOnly } ` , tmdbKey , seasonNumber ?? undefined , episodeNumber ?? undefined ) ;
6253+ } else if ( isTmdbId ) {
6254+ // NUOVO: pre-resolution unificata per TMDB
6255+ const tmdbIdOnly = id . replace ( 'tmdb:' , '' ) ;
6256+ preResolved = await resolveAnimeTitle ( `tmdb:${ tmdbIdOnly } ` , tmdbKey , seasonNumber ?? undefined , episodeNumber ?? undefined ) ;
6257+ }
62176258 }
62186259
62196260 // AnimeUnity
62206261 scheduleProviderRun ( 'AnimeUnity' , animeUnityEnabled , async ( ) => {
62216262 const animeUnityProvider = new AnimeUnityProvider ( animeUnityConfig ) ;
6222- // Per kitsu: /mal: usa il titolo pre-risolto (0 chiamate API aggiuntive)
6223- if ( isAnimeId && preResolved ) {
6263+ // Se pre-risolto ( kitsu/mal/imdb/tmdb) usa handlePreResolved (0 chiamate API aggiuntive)
6264+ if ( preResolved ) {
62246265 return animeUnityProvider . handlePreResolved ( preResolved , id ) ;
62256266 }
6226- // Per IMDB/TMDB: risoluzione propria (gate + title da IMDB/TMDB, non duplicabile )
6227- if ( id . startsWith ( 'tt' ) ) return animeUnityProvider . handleImdbRequest ( id , seasonNumber , episodeNumber , isMovie ) ;
6228- if ( id . startsWith ( 'tmdb:' ) ) return animeUnityProvider . handleTmdbRequest ( id . replace ( 'tmdb:' , '' ) , seasonNumber , episodeNumber , isMovie ) ;
6267+ // Fallback legacy: se preResolved è null (animemapping + Haglund + tutti i fallback hanno fallito )
6268+ if ( isImdbId ) return animeUnityProvider . handleImdbRequest ( id , seasonNumber , episodeNumber , isMovie ) ;
6269+ if ( isTmdbId ) return animeUnityProvider . handleTmdbRequest ( id . replace ( 'tmdb:' , '' ) , seasonNumber , episodeNumber , isMovie ) ;
62296270 return { streams : [ ] } ;
62306271 } , providerLabel ( 'animeunity' ) , false , 30000 ) ; // AnimeUnity: timeout 30s
62316272
62326273 // AnimeSaturn
62336274 scheduleProviderRun ( 'AnimeSaturn' , animeSaturnEnabled , async ( ) => {
62346275 const { AnimeSaturnProvider } = await import ( './providers/animesaturn-provider' ) ;
62356276 const animeSaturnProvider = new AnimeSaturnProvider ( animeSaturnConfig ) ;
6236- if ( isAnimeId && preResolved ) {
6277+ if ( preResolved ) {
62376278 return animeSaturnProvider . handlePreResolved ( preResolved , id ) ;
62386279 }
6239- if ( id . startsWith ( 'tt' ) ) return animeSaturnProvider . handleImdbRequest ( id , seasonNumber , episodeNumber , isMovie ) ;
6240- if ( id . startsWith ( 'tmdb:' ) ) return animeSaturnProvider . handleTmdbRequest ( id . replace ( 'tmdb:' , '' ) , seasonNumber , episodeNumber , isMovie ) ;
6280+ if ( isImdbId ) return animeSaturnProvider . handleImdbRequest ( id , seasonNumber , episodeNumber , isMovie ) ;
6281+ if ( isTmdbId ) return animeSaturnProvider . handleTmdbRequest ( id . replace ( 'tmdb:' , '' ) , seasonNumber , episodeNumber , isMovie ) ;
62416282 return { streams : [ ] } ;
62426283 } , providerLabel ( 'animesaturn' ) , false , 30000 ) ; // AnimeSaturn: timeout 30s
62436284
62446285 // AnimeWorld
62456286 scheduleProviderRun ( 'AnimeWorld' , animeWorldEnabled , async ( ) => {
62466287 const { AnimeWorldProvider } = await import ( './providers/animeworld-provider' ) ;
62476288 const animeWorldProvider = new AnimeWorldProvider ( animeWorldConfig ) ;
6248- if ( isAnimeId && preResolved ) {
6289+ if ( preResolved ) {
62496290 return animeWorldProvider . handlePreResolved ( preResolved , id ) ;
62506291 }
6251- if ( id . startsWith ( 'tt' ) ) return animeWorldProvider . handleImdbRequest ( id , seasonNumber , episodeNumber , isMovie ) ;
6252- if ( id . startsWith ( 'tmdb:' ) ) return animeWorldProvider . handleTmdbRequest ( id . replace ( 'tmdb:' , '' ) , seasonNumber , episodeNumber , isMovie ) ;
6292+ if ( isImdbId ) return animeWorldProvider . handleImdbRequest ( id , seasonNumber , episodeNumber , isMovie ) ;
6293+ if ( isTmdbId ) return animeWorldProvider . handleTmdbRequest ( id . replace ( 'tmdb:' , '' ) , seasonNumber , episodeNumber , isMovie ) ;
62536294 return { streams : [ ] } ;
62546295 } , providerLabel ( 'animeworld' ) , false , 30000 ) ; // AnimeWorld: timeout 30s
62556296
0 commit comments