Skip to content

Commit ede615c

Browse files
fix: addon
1 parent 2d7bf66 commit ede615c

File tree

1 file changed

+67
-26
lines changed

1 file changed

+67
-26
lines changed

src/addon.ts

Lines changed: 67 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { formatMediaFlowUrl } from './utils/mediaflow';
1313
import { mergeDynamic, loadDynamicChannels, purgeOldDynamicEvents, invalidateDynamicChannels, getDynamicFilePath, getDynamicFileStats } from './utils/dynamicChannels';
1414
import { 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
1818
declare 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
108108
const VAVOO_WORKER_URLS = (process.env.VAVOO_WORKER_URL || '').split(',').map((u: string) => u.trim()).filter(Boolean);
109109
const VAVOO_API_UA = 'electron-fetch/1.0 electron (+https://github.com/arantes555/electron-fetch)';
110110
const 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; } })();
111115
console.log(`[VAVOO] Worker URLs loaded: ${VAVOO_WORKER_URLS.length}`);
116+
console.log(`[VAVOO] Stream toggles: CLEAN=${VAVOO_CLEAN} PROXY=${VAVOO_PROXY} DIRECT=${VAVOO_DIRECT}`);
112117
function 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

114119
function getClientIpFromReq(req: any): string | null {
@@ -484,6 +489,7 @@ const execFilePromise = util.promisify(execFile);
484489
const dynamicStreamCache = new Map<string, { finalUrl: string; ts: number }>();
485490
const DYNAMIC_STREAM_TTL_MS = 5 * 60 * 1000; // 5 minuti
486491

492+
487493
async 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 && /\(Vavoo\)/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

Comments
 (0)