const ORANGE_IDENTITY_KEY = "orangeTvIdentityCookie"; const URL_BASE_API_MOB_JS = 'https://android.orangetv.orange.es/mob/api/rtv/v1'; const URL_BASE_API_PC_JS = 'https://pc.orangetv.orange.es/pc/api/rtv/v1'; const URL_BASE_IMAGES_PC_JS = `${URL_BASE_API_PC_JS}/images`; const MODEL_EXTERNAL_IDS_FOR_TERMINALS_JS = ['AKS19', 'HUM18', 'SAG22']; const BOUQUET_ID_FOR_CHANNELS_PC_JS = '1'; const MODEL_ID_FOR_CHANNELS_PC_JS = 'PC'; const MAX_WORKERS_CHANNELS_JS = 10; const ORANGE_TV_API_HOST_MOB = "android.orangetv.orange.es"; const ORANGE_TV_API_HOST_PC = "pc.orangetv.orange.es"; const CHANNEL_SPECIFIC_CLEARKEYS_JS = { "r11_la1": {"kid": "9nV6vfcUs6Qjf/R3niFDXA", "k": "+Hf9yEn6OF2o4ornglSp0g"}, "r11_la2": {"kid": "9nV6vfcUs6Qjf/R3niFDXA", "k": "+Hf9yEn6OF2o4ornglSp0g"}, "r11_antena3": {"kid": "9nV6vfcUs6Qjf/R3niFDXA", "k": "+Hf9yEn6OF2o4ornglSp0g"}, "r11_cuatro": {"kid": "9nV6vfcUs6Qjf/R3niFDXA", "k": "+Hf9yEn6OF2o4ornglSp0g"}, "r11_telecinco": {"kid": "9nV6vfcUs6Qjf/R3niFDXA", "k": "+Hf9yEn6OF2o4ornglSp0g"}, "r11_lasexta": {"kid": "9nV6vfcUs6Qjf/R3niFDXA", "k": "+Hf9yEn6OF2o4ornglSp0g"}, "r13_selekt": {"kid": "27o7zoN8t7LH7Jq5VycTMg", "k": "+fxpwwObxHptQxQms4C+LA"}, "r11_starchannel": {"kid": "MD7N7urJAQtF5oTryeF1lA", "k": "BxIdiu1Rx7vYBGGUUXw/Eg"}, "r11_amc": {"kid": "27o7zoN8t7LH7Jq5VycTMg", "k": "+fxpwwObxHptQxQms4C+LA"}, "r11_tnt": {"kid": "VypQBxqrMWM4PmRW+hcBdQ", "k": "hj7H6C6K0HjZ4ICxwLpi0g"}, "r11_axn": {"kid": "DhwwYB1i4nchlODT7uz1Xw", "k": "D314t8hAHWaGkMSTCwhh+Q"}, "r11_comedy": {"kid": "Z8AqZy5+h+KXz6dQPeew4g", "k": "YK2xLXIvkk7cGhow+MNJ1Q"}, "r11_calle13": {"kid": "MW3cMBCG06kSHBrb1nIXyA", "k": "J6EXwkkHdU+L1+iTe3IQxg"}, "r11_xtrm": {"kid": "27o7zoN8t7LH7Jq5VycTMg", "k": "+fxpwwObxHptQxQms4C+LA"}, "r11_scifi": {"kid": "MW3cMBCG06kSHBrb1nIXyA", "k": "J6EXwkkHdU+L1+iTe3IQxg"}, "r11_cosmo": {"kid": "6XvuzxKLh3MNzpOFl2+PAQ", "k": "loe9Tcqr+hlepdH88g7nKg"}, "r13_enfamilia": {"kid": "F2gY9e6GDV4KP0kErIgBKg", "k": "BZEt/FNCoTx8YSZS72Tgig"}, "r13_fdf": {"kid": "9nV6vfcUs6Qjf/R3niFDXA", "k": "+Hf9yEn6OF2o4ornglSp0g"}, "r11_neox": {"kid": "9nV6vfcUs6Qjf/R3niFDXA", "k": "+Hf9yEn6OF2o4ornglSp0g"}, "r11_energy": {"kid": "9nV6vfcUs6Qjf/R3niFDXA", "k": "+Hf9yEn6OF2o4ornglSp0g"}, "r11_atreseries": {"kid": "9nV6vfcUs6Qjf/R3niFDXA", "k": "+Hf9yEn6OF2o4ornglSp0g"}, "r13_divinity": {"kid": "9nV6vfcUs6Qjf/R3niFDXA", "k": "+Hf9yEn6OF2o4ornglSp0g"}, "r11_nova": {"kid": "9nV6vfcUs6Qjf/R3niFDXA", "k": "+Hf9yEn6OF2o4ornglSp0g"}, "r11_hollywood": {"kid": "27o7zoN8t7LH7Jq5VycTMg", "k": "+fxpwwObxHptQxQms4C+LA"}, "r11_axnwhite": {"kid": "DhwwYB1i4nchlODT7uz1Xw", "k": "D314t8hAHWaGkMSTCwhh+Q"}, "r11_somos": {"kid": "27o7zoN8t7LH7Jq5VycTMg", "k": "+fxpwwObxHptQxQms4C+LA"}, "r13_bomcine": {"kid": "WwxMD+1K/NItX7qazKZ4ig", "k": "NmFTcN1vRo26EkWPoOOq/Q"}, "r13_squirrel": {"kid": "WwxMD+1K/NItX7qazKZ4ig", "k": "NmFTcN1vRo26EkWPoOOq/Q"}, "r11_tcm": {"kid": "VypQBxqrMWM4PmRW+hcBdQ", "k": "hj7H6C6K0HjZ4ICxwLpi0g"}, "r11_sundance": {"kid": "27o7zoN8t7LH7Jq5VycTMg", "k": "+fxpwwObxHptQxQms4C+LA"}, "r11_dark": {"kid": "27o7zoN8t7LH7Jq5VycTMg", "k": "+fxpwwObxHptQxQms4C+LA"}, "r11_paramount": {"kid": "IsjmUI4McB6wmNGYoR8rEA", "k": "Mzk+KGJVxi5ho9Xtk40vHg"}, "r11_bemad": {"kid": "9nV6vfcUs6Qjf/R3niFDXA", "k": "+Hf9yEn6OF2o4ornglSp0g"}, "r11_historia": {"kid": "27o7zoN8t7LH7Jq5VycTMg", "k": "+fxpwwObxHptQxQms4C+LA"}, "r11_nat_geo": {"kid": "MD7N7urJAQtF5oTryeF1lA", "k": "BxIdiu1Rx7vYBGGUUXw/Eg"}, "r11_blaze": {"kid": "27o7zoN8t7LH7Jq5VycTMg", "k": "+fxpwwObxHptQxQms4C+LA"}, "r11_odisea": {"kid": "27o7zoN8t7LH7Jq5VycTMg", "k": "+fxpwwObxHptQxQms4C+LA"}, "r11_discovery": {"kid": "vB3XOgolNBAnPnx3RaJhCQ", "k": "jkxJcB1cWDLDKMmdIoLdqQ"}, "r11_natgeowild": {"kid": "MD7N7urJAQtF5oTryeF1lA", "k": "BxIdiu1Rx7vYBGGUUXw/Eg"}, "r11_crimeninvestigacion": {"kid": "27o7zoN8t7LH7Jq5VycTMg", "k": "+fxpwwObxHptQxQms4C+LA"}, "r11_cocina": {"kid": "27o7zoN8t7LH7Jq5VycTMg", "k": "+fxpwwObxHptQxQms4C+LA"}, "r11_decasahd": {"kid": "27o7zoN8t7LH7Jq5VycTMg", "k": "+fxpwwObxHptQxQms4C+LA"}, "r13_solmusica": {"kid": "27o7zoN8t7LH7Jq5VycTMg", "k": "+fxpwwObxHptQxQms4C+LA"}, "r11_mega": {"kid": "9nV6vfcUs6Qjf/R3niFDXA", "k": "+Hf9yEn6OF2o4ornglSp0g"}, "r13_dmax": {"kid": "9nV6vfcUs6Qjf/R3niFDXA", "k": "+Hf9yEn6OF2o4ornglSp0g"}, "r13_ten": {"kid": "9nV6vfcUs6Qjf/R3niFDXA", "k": "+Hf9yEn6OF2o4ornglSp0g"}, "r11_disneychan": {"kid": "j1CZ9MDi/2+moKofzwo2TA", "k": "ImOtSX+6rDn7Ca1RSCS3GA"}, "r11_disney_jr": {"kid": "j1CZ9MDi/2+moKofzwo2TA", "k": "ImOtSX+6rDn7Ca1RSCS3GA"}, "r11_nick": {"kid": "Z8AqZy5+h+KXz6dQPeew4g", "k": "YK2xLXIvkk7cGhow+MNJ1Q"}, "r11_nickjr": {"kid": "Z8AqZy5+h+KXz6dQPeew4g", "k": "YK2xLXIvkk7cGhow+MNJ1Q"}, "r11_dreamworks": {"kid": "MW3cMBCG06kSHBrb1nIXyA", "k": "J6EXwkkHdU+L1+iTe3IQxg"}, "r11_boing": {"kid": "9nV6vfcUs6Qjf/R3niFDXA", "k": "+Hf9yEn6OF2o4ornglSp0g"}, "r11_clanhd": {"kid": "IsjmUI4McB6wmNGYoR8rEA", "k": "Mzk+KGJVxi5ho9Xtk40vHg"}, "r12_eurosport": {"kid": "vB3XOgolNBAnPnx3RaJhCQ", "k": "jkxJcB1cWDLDKMmdIoLdqQ"}, "r12_eurosport2": {"kid": "vB3XOgolNBAnPnx3RaJhCQ", "k": "jkxJcB1cWDLDKMmdIoLdqQ"}, "r11_tdphd": {"kid": "IsjmUI4McB6wmNGYoR8rEA", "k": "Mzk+KGJVxi5ho9Xtk40vHg"}, "r12_daznlaliga": {"kid": "Yemhb9f6RnnLUcgfyqhynw", "k": "kpmfl/9O5uxSpg1JD7PxTA"}, "r14ll_mlaliga": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r12_mlaliga": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r12_daznlaliga2": {"kid": "Yemhb9f6RnnLUcgfyqhynw", "k": "kpmfl/9O5uxSpg1JD7PxTA"}, "r12_mlaliga2": {"kid": "Yemhb9f6RnnLUcgfyqhynw", "k": "kpmfl/9O5uxSpg1JD7PxTA"}, "r12_mlaliga3": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r12_mlaliga4": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r12_mcampeones7": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r12_mlaliga6": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r12_mcampeones5": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r12_mcampeones6": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r12_mcampeones4": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r14ll_mcampeones": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r12_mcampeones-hdr": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r12_mcampeones": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r12_mcampeones2-hdr": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r12_mcampeones2": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r12_mcampeones3": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r12_laligasmartbank": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r12_laligasmartbank2": {"kid": "AiSxNfL5UCr/+cszVRwIgQ", "k": "URcFiCvQispOGhKKfZuoEw"}, "r12_laligasmartbank3": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r12_laligaplus": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r13_nautical": {"kid": "WwxMD+1K/NItX7qazKZ4ig", "k": "NmFTcN1vRo26EkWPoOOq/Q"}, "r12_gol": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r13_realmadridconti": {"kid": "9nV6vfcUs6Qjf/R3niFDXA", "k": "+Hf9yEn6OF2o4ornglSp0g"}, "r12_betis": {"kid": "9nV6vfcUs6Qjf/R3niFDXA", "k": "+Hf9yEn6OF2o4ornglSp0g"}, "r13_motoadv": {"kid": "iukRwhaDykixDta5JRJyGA", "k": "IPzNiyCJIrIMclkEWZCKVg"}, "r11_mtv": {"kid": "Z8AqZy5+h+KXz6dQPeew4g", "k": "YK2xLXIvkk7cGhow+MNJ1Q"}, "r13_ubeat": {"kid": "JjJYKDacD2UbHQKAMWcWeA", "k": "2oVeLpuI43GyrGj4W5VgaQ"}, "r13_gametoon": {"kid": "JjJYKDacD2UbHQKAMWcWeA", "k": "2oVeLpuI43GyrGj4W5VgaQ"}, "r13_dkiss": {"kid": "9nV6vfcUs6Qjf/R3niFDXA", "k": "+Hf9yEn6OF2o4ornglSp0g"}, "r13_myzen": {"kid": "3J1au8Je3Q/LRcXw3k/p5A", "k": "tVbv93kAFfDl+F8zk+zOqg"}, "r13_outtv": {"kid": "3J1au8Je3Q/LRcXw3k/p5A", "k": "tVbv93kAFfDl+F8zk+zOqg"}, "r11_mtvlive": {"kid": "Z8AqZy5+h+KXz6dQPeew4g", "k": "YK2xLXIvkk7cGhow+MNJ1Q"}, "r11_vh1": {"kid": "Z8AqZy5+h+KXz6dQPeew4g", "k": "YK2xLXIvkk7cGhow+MNJ1Q"}, "r13_mezzo": {"kid": "6di3sjutuhXFL8S3Uqiw8Q", "k": "1sFtN5OtzTjtxLuRRc4gIA"}, "r13_tr3ce": {"kid": "9nV6vfcUs6Qjf/R3niFDXA", "k": "+Hf9yEn6OF2o4ornglSp0g"}, "r13_intereconomia": {"kid": "IsjmUI4McB6wmNGYoR8rEA", "k": "Mzk+KGJVxi5ho9Xtk40vHg"}, "r13_ewtn": {"kid": "WwxMD+1K/NItX7qazKZ4ig", "k": "NmFTcN1vRo26EkWPoOOq/Q"}, "r13_andalucia": {"kid": "IsjmUI4McB6wmNGYoR8rEA", "k": "Mzk+KGJVxi5ho9Xtk40vHg"}, "r12_realmadrid": {"kid": "9nV6vfcUs6Qjf/R3niFDXA", "k": "+Hf9yEn6OF2o4ornglSp0g"}, "r13_tv3i": {"kid": "IsjmUI4McB6wmNGYoR8rEA", "k": "Mzk+KGJVxi5ho9Xtk40vHg"}, "r13_tvgi": {"kid": "IsjmUI4McB6wmNGYoR8rEA", "k": "Mzk+KGJVxi5ho9Xtk40vHg"}, "r13_eitb": {"kid": "IsjmUI4McB6wmNGYoR8rEA", "k": "Mzk+KGJVxi5ho9Xtk40vHg"}, "r11_24h": {"kid": "9nV6vfcUs6Qjf/R3niFDXA", "k": "+Hf9yEn6OF2o4ornglSp0g"}, "r13_euronews": {"kid": "WwxMD+1K/NItX7qazKZ4ig", "k": "NmFTcN1vRo26EkWPoOOq/Q"}, "r13_bbc": {"kid": "WwxMD+1K/NItX7qazKZ4ig", "k": "NmFTcN1vRo26EkWPoOOq/Q"}, "r13_11internacional": {"kid": "WwxMD+1K/NItX7qazKZ4ig", "k": "NmFTcN1vRo26EkWPoOOq/Q"}, "r13_aljazeera": {"kid": "WwxMD+1K/NItX7qazKZ4ig", "k": "NmFTcN1vRo26EkWPoOOq/Q"}, "r13_caracoltv": {"kid": "WwxMD+1K/NItX7qazKZ4ig", "k": "NmFTcN1vRo26EkWPoOOq/Q"}, "r13_protv": {"kid": "WwxMD+1K/NItX7qazKZ4ig", "k": "NmFTcN1vRo26EkWPoOOq/Q"}, "r13_tv5": {"kid": "WwxMD+1K/NItX7qazKZ4ig", "k": "NmFTcN1vRo26EkWPoOOq/Q"}, "r12_daznlaliga3": {"kid": "Yemhb9f6RnnLUcgfyqhynw", "k": "kpmfl/9O5uxSpg1JD7PxTA"}, "r12_mcampeones8": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r12_mcampeones9": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r12_mcampeones10": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r12_mcampeones11": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r12_mcampeones12": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r12_mcampeones13": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r12_mcampeones14": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r12_mcampeones15": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r12_mcampeones16": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r12_mcampeones17": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r12_mcampeones18": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"}, "r12_mcampeones19": {"kid": "hQwaQwOWzkJV85Ar8fTolw", "k": "KVsduXf5Tfu6ASBzkBoBmw"} }; class NotAuthenticatedError extends Error { constructor(message) { super(message); this.name = "NotAuthenticatedError"; } } async function setDynamicHeaders(headersArray, targetHost = null) { if (!chrome.runtime?.id) { return; } try { const message = { cmd: "updateHeadersRules", requestHeaders: headersArray }; if (targetHost) { message.urlFilter = `*://${targetHost}/*`; } else { message.urlFilter = `*://${ORANGE_TV_API_HOST_MOB}/*,*://${ORANGE_TV_API_HOST_PC}/*`; } const response = await chrome.runtime.sendMessage(message); if (!response || !response.success) { console.error("Error al configurar cabeceras dinámicas:", response?.error || "Respuesta no exitosa."); showNotification("Error crítico configurando cabeceras de red.", "error"); } } catch (e) { console.error("Excepción al enviar mensaje para configurar cabeceras dinámicas:", e); showNotification("Excepción configurando cabeceras de red.", "error"); } } async function clearAllDynamicHeaders() { if (!chrome.runtime?.id) return; try { await chrome.runtime.sendMessage({ cmd: "clearAllDnrHeaders" }); } catch (e) { console.error("Excepción al limpiar cabeceras dinámicas:", e); } } async function loginOrangeMob() { console.log("Paso 1: Intentando iniciar sesión (Mob API)..."); showNotification("Iniciando sesión en OrangeTV (Mob API)...", "info"); await clearAllDynamicHeaders(); const orangeUsername = userSettings.orangeTvUsername; const orangePassword = userSettings.orangeTvPassword; const fetchHeaders = { 'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': 'okhttp/4.10.0' }; const loginUrl = `${URL_BASE_API_MOB_JS}/Login?username=${orangeUsername}`; const loginDataStr = `client=json&username=${orangeUsername}&password=${orangePassword}`; try { const response = await fetch(loginUrl, { method: 'POST', headers: fetchHeaders, body: loginDataStr, credentials: 'omit' }); if (!response.ok) { const errorText = await response.text(); console.error(`Paso 1: HTTP error en Login (Mob API): ${response.status}`, errorText.substring(0,500)); showNotification(`Error en login (Mob): ${response.status}`, "error"); return null; } const responseJson = await response.json(); if (responseJson?.response?.status === 'SUCCESS' && responseJson?.response?.message?.startsWith('identity=')) { const identityCookieStr = responseJson.response.message; console.log(`Paso 1: ¡Login (Mob API) exitoso! Cookie: ${identityCookieStr.substring(0, 20)}...`); showNotification("Login (Mob API) exitoso.", "success"); await saveAppConfigValue(ORANGE_IDENTITY_KEY, identityCookieStr); return identityCookieStr; } else { console.error("Paso 1: Login (Mob API) fallido o formato inesperado.", responseJson); showNotification("Login (Mob API) fallido. Revisa las credenciales.", "error"); return null; } } catch (e) { console.error("Error de red o JSON en Login (Mob API):", e); showNotification("Error de red en login (Mob).", "error"); return null; } } async function loadIdentityFromDB() { try { const identityStr = await getAppConfigValue(ORANGE_IDENTITY_KEY); if (identityStr && identityStr.startsWith("identity=")) { console.log(`Cookie '${identityStr.substring(0,20)}...' cargada desde IndexedDB.`); return identityStr; } } catch (e) { console.error("Error al cargar cookie desde IndexedDB:", e); } return null; } async function getIdentityCookie() { let identity = await loadIdentityFromDB(); if (identity) { return identity; } console.warn("Cookie no válida/inexistente. Iniciando sesión (Mob API)..."); showNotification("Cookie de OrangeTV no encontrada, intentando nuevo login...", "info"); return await loginOrangeMob(); } async function apiRequestMob(method, endpoint, identityCookieStr, params = null, bodyData = null, includeHouseholdId = false) { let url = `${URL_BASE_API_MOB_JS}${endpoint}`; if (params) { url += `?${new URLSearchParams(params).toString()}`; } const dnrHeadersToSet = [ { header: 'User-Agent', value: 'okhttp/4.10.0' }, { header: 'Cookie', value: identityCookieStr } ]; if (includeHouseholdId) { dnrHeadersToSet.push({ header: 'HouseholdID', value: '1' }); } await setDynamicHeaders(dnrHeadersToSet, ORANGE_TV_API_HOST_MOB); const fetchOptions = { method: method, headers: {}, credentials: 'omit' }; if (bodyData && (method === 'POST' || method === 'PUT')) { fetchOptions.body = bodyData; if (typeof bodyData === 'string' && bodyData.includes('=')) { fetchOptions.headers['Content-Type'] = 'application/x-www-form-urlencoded'; } } try { await new Promise(resolve => setTimeout(resolve, 150)); const response = await fetch(url, fetchOptions); if (response.status === 401) { throw new NotAuthenticatedError("401 Auth Error (Mob API). Cookie expirada?"); } if (!response.ok) { const errorText = await response.text(); console.error(`Error en API Mob (${endpoint}): ${response.status}`, errorText.substring(0,200)); return null; } if (response.status === 204 || response.headers.get("content-length") === "0") { return { success_no_content: true, status: response.status }; } const contentType = response.headers.get("content-type"); if (contentType && contentType.includes("application/json")) { return await response.json(); } else { console.warn(`Respuesta de API Mob (${endpoint}) no es JSON. Tipo: ${contentType}`); return { raw_text: await response.text(), status_code: response.status }; } } catch (e) { if (e instanceof NotAuthenticatedError) throw e; console.error(`Excepción en API Mob (${endpoint}):`, e); return null; } } async function getSerialAndModelMob(identityCookieStr) { if (!identityCookieStr) return { serial: null, model: null }; console.log("Paso 2: Obteniendo terminales (Mob API)..."); showNotification("Obteniendo información de terminales (Mob)...", "info"); const responseData = await apiRequestMob('GET', '/GetTerminalList?client=json', identityCookieStr, null, null, false); if (responseData?.response?.terminals) { const terminals = responseData.response.terminals; if (terminals && terminals.length > 0) { for (const modelIdFilter of MODEL_EXTERNAL_IDS_FOR_TERMINALS_JS) { for (const t of terminals) { if (t?.model?.externalId === modelIdFilter) { const serial = t.serialNumber; console.log(`Paso 2: ¡Terminal encontrado! Modelo: ${modelIdFilter}, Serial: ${serial}`); showNotification("Terminal (Mob) encontrado.", "success"); return { serial: serial, model: modelIdFilter }; } } } console.warn(`Paso 2: No se encontró descodificador con modelos: ${MODEL_EXTERNAL_IDS_FOR_TERMINALS_JS.join(', ')}`); showNotification("No se encontró terminal compatible (Mob).", "warning"); } else { console.warn("Paso 2: No se encontraron terminales."); showNotification("No hay terminales registrados (Mob).", "warning"); } } else { console.error("Paso 2: Fallo al obtener terminales (Mob API) o formato inesperado."); if (responseData) console.error("Respuesta GetTerminalList:", responseData); showNotification("Error obteniendo terminales (Mob).", "error"); } return { serial: null, model: null }; } async function getChannelListPc(identityCookieMobStr) { if (!identityCookieMobStr) return null; console.log(`Paso 3.1: Obteniendo canales (PC API) para modelo ${MODEL_ID_FOR_CHANNELS_PC_JS}...`); showNotification("Obteniendo lista de canales (PC API)...", "info"); const dnrHeadersForPc = [ { header: 'User-Agent', value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36' }, { header: 'Cookie', value: identityCookieMobStr }, { header: 'Accept', value: 'application/json, text/plain, */*'} ]; await setDynamicHeaders(dnrHeadersForPc, ORANGE_TV_API_HOST_PC); const params = { 'bouquet_id': BOUQUET_ID_FOR_CHANNELS_PC_JS, 'model_external_id': MODEL_ID_FOR_CHANNELS_PC_JS, 'filter_unsupported_channels': 'false', 'client': 'json' }; const urlPcChannelList = `${URL_BASE_API_PC_JS}/GetChannelList?${new URLSearchParams(params).toString()}`; try { await new Promise(resolve => setTimeout(resolve, 150)); const response = await fetch(urlPcChannelList, { method: 'GET', headers: {}, credentials: 'omit' }); if (response.status === 401) { console.error(`Paso 3.1: Error de autenticación (401) en GetChannelList (PC API).`); showNotification("Autenticación fallida para PC API (canales).", "error"); return null; } if (!response.ok) { const errorText = await response.text(); console.error(`Paso 3.1: HTTP error en GetChannelList (PC API): ${response.status}`, errorText.substring(0,500)); showNotification(`Error obteniendo canales (PC API): ${response.status}`, "error"); return null; } const responseJson = await response.json(); if (responseJson && Array.isArray(responseJson.response)) { const channelsData = responseJson.response; console.log(`Paso 3.1: ¡${channelsData.length} canales (PC API) obtenidos!`); showNotification(`${channelsData.length} canales (PC API) obtenidos.`, "success"); return channelsData; } else { console.error("Paso 3.1: Fallo al obtener canales (PC API) o formato inesperado.", responseJson); showNotification("Formato de respuesta de canales (PC API) inesperado.", "error"); return null; } } catch (e) { console.error("Error de red o JSON en GetChannelList (PC API):", e); showNotification("Error de red obteniendo canales (PC API).", "error"); return null; } } async function getLivePlayingInfoMob(identityCookieStr, serialNumber, channelExternalId) { if (!identityCookieStr || !serialNumber || !channelExternalId) return null; const params = { 'client': 'json', 'serial_number': serialNumber, 'include_cas_token': 'true', 'channel_external_id': channelExternalId }; const responseData = await apiRequestMob('GET', '/GetLivePlayingInfo', identityCookieStr, params, null, true); if (responseData?.response?.playingUrl) { return responseData.response; } console.warn(`No se obtuvo playingUrl para ${channelExternalId}. Respuesta:`, responseData); return null; } function extractStreamIdentifier(mpdUrl) { if (!mpdUrl || typeof mpdUrl !== 'string') return null; const regex = /\/([a-zA-Z0-9_.-]+)\/dash_(?:high|medium|low)\.mpd/i; let match = mpdUrl.match(regex); if (match && match[1]) { const candidate = match[1]; if (/^r\d{1,2}_/i.test(candidate)) return candidate; } const pathParts = mpdUrl.split('/'); for (let i = pathParts.length - 2; i >= 0; i--) { const part = pathParts[i]; if (part.toLowerCase() === 'cmaf' || part.toLowerCase() === 'std' || part.includes('.')) continue; if (part && /^r\d{1,2}_/i.test(part)) return part; } if (match && match[1] && (match[1].toLowerCase() !== 'cmaf' && match[1].toLowerCase() !== 'std')) return match[1]; return null; } async function processSingleChannel(channelDataPc, serialNumberMob, identityCookieMob) { const name = channelDataPc.name || 'Nombre Desconocido'; const externalId = channelDataPc.externalChannelId; const category = channelDataPc.category || 'Desconocido'; const number = channelDataPc.number || ''; const attachments = channelDataPc.attachments || []; const encodingType = channelDataPc.encoding; const sourceType = channelDataPc.sourceType; const channelUrlField = channelDataPc.url; if (!externalId) { console.warn("Canal sin externalChannelId:", channelDataPc); return null; } if (userSettings.orangeTvSelectedGroups && userSettings.orangeTvSelectedGroups.length > 0) { const normalizedCategoryApi = category.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase(); const selectedGroupsNormalized = userSettings.orangeTvSelectedGroups.map(g => g.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase()); let groupMatch = selectedGroupsNormalized.includes(normalizedCategoryApi); if (!groupMatch) { if ((normalizedCategoryApi === "general" || normalizedCategoryApi === "generalistas") && selectedGroupsNormalized.includes("generalista")) { groupMatch = true; } else if ((normalizedCategoryApi === "noticias") && selectedGroupsNormalized.includes("informacion")) { groupMatch = true; } else if ((normalizedCategoryApi === "infanti") && selectedGroupsNormalized.includes("infantil")) { groupMatch = true; } } if (!groupMatch) { return null; } } let streamUrl = null; let isExternalHls = false; let kodiPropsArray = []; if (encodingType === "EXTERNAL" && sourceType === "HLS" && channelUrlField === "externalURL") { const extrafields = channelDataPc.extrafields || []; const extStreamField = extrafields.find(ef => ef.name === "externalStreamingUrl"); if (extStreamField && extStreamField.value) { try { const externalStreamingData = JSON.parse(extStreamField.value); const hlsUrl = externalStreamingData.externalURL; if (hlsUrl) { streamUrl = hlsUrl; isExternalHls = true; } } catch (e) { console.warn(`Error parseando externalStreamingUrl para ${name}: ${e}`); } } if (!streamUrl) isExternalHls = false; } if (!isExternalHls) { const playingInfoMob = await getLivePlayingInfoMob(identityCookieMob, serialNumberMob, externalId); if (playingInfoMob && playingInfoMob.playingUrl) { let tempUrl = playingInfoMob.playingUrl; if (!tempUrl.endsWith('/externalURL')) { streamUrl = tempUrl; if (streamUrl.toLowerCase().endsWith(".mpd")) { streamUrl = streamUrl.replace(/dash_medium\.mpd/i, "dash_high.mpd").replace(/dash_low\.mpd/i, "dash_high.mpd"); kodiPropsArray.push("inputstream.adaptive.manifest_type=mpd"); const streamIdForClearkey = extractStreamIdentifier(streamUrl); if (streamIdForClearkey && CHANNEL_SPECIFIC_CLEARKEYS_JS[streamIdForClearkey]) { const keys = CHANNEL_SPECIFIC_CLEARKEYS_JS[streamIdForClearkey]; if (keys.k && keys.kid) { const licenseKeyJsonObj = { keys: [{ kty: "oct", k: keys.k, kid: keys.kid }], type: "temporary" }; const licenseKeyJsonStr = JSON.stringify(licenseKeyJsonObj); kodiPropsArray.push(`inputstream.adaptive.license_type=clearkey`); kodiPropsArray.push(`inputstream.adaptive.license_key=${licenseKeyJsonStr}`); } } } } } } if (streamUrl) { let m3uEntry = ""; const logoAttachment = attachments.find(att => att.name === "LOGO" && att.value); const logoPath = logoAttachment ? `${URL_BASE_IMAGES_PC_JS}${logoAttachment.value}` : ""; m3uEntry += `#EXTINF:-1 tvg-id="${externalId}" ch-number="${number}" tvg-name="${name}" tvg-logo="${logoPath}" group-title="OrangeTV | ${category}",${name}\n`; kodiPropsArray.forEach(prop => { m3uEntry += `#KODIPROP:${prop}\n`; }); m3uEntry += `${streamUrl}\n`; return m3uEntry; } return null; } async function generateM3uOrangeTv() { showLoading(true, "Iniciando proceso OrangeTV..."); console.log("--- Iniciando generación M3U OrangeTV (JS) ---"); const orangeTvSourceName = "OrangeTV"; let identityCookieMob = null; let serialNumberMob = null; try { identityCookieMob = await getIdentityCookie(); if (!identityCookieMob) { throw new Error("CRÍTICO: No se pudo obtener cookie (Mob API)."); } const terminalInfo = await getSerialAndModelMob(identityCookieMob); serialNumberMob = terminalInfo.serial; if (!serialNumberMob) { console.warn("Fallo al obtener terminales (Mob API). La cookie podría haber expirado. Re-intentando login..."); showNotification("Información de terminal no obtenida, reintentando login...", "warning"); await deleteAppConfigValue(ORANGE_IDENTITY_KEY); identityCookieMob = await loginOrangeMob(); if (!identityCookieMob) { throw new Error("CRÍTICO: No se pudo obtener cookie (Mob API) tras re-login."); } const newTerminalInfo = await getSerialAndModelMob(identityCookieMob); serialNumberMob = newTerminalInfo.serial; if (!serialNumberMob) { throw new Error("CRÍTICO: No se pudo obtener serial (Mob API) tras re-login."); } } const listaCanalesPcApi = await getChannelListPc(identityCookieMob); if (!listaCanalesPcApi || listaCanalesPcApi.length === 0) { throw new Error("CRÍTICO: No se pudo obtener la lista de canales (PC API)."); } showNotification(`Procesando ${listaCanalesPcApi.length} canales... Esto puede tardar.`, "info", 10000); let m3uLinesForFile = ["#EXTM3U"]; let canalesExitosos = 0; let canalesConError = 0; const resultsInOrder = new Array(listaCanalesPcApi.length).fill(null); let processedCount = 0; for (let i = 0; i < listaCanalesPcApi.length; i += MAX_WORKERS_CHANNELS_JS) { const batch = listaCanalesPcApi.slice(i, i + MAX_WORKERS_CHANNELS_JS); const promises = batch.map((channelPc, indexInBatch) => processSingleChannel(channelPc, serialNumberMob, identityCookieMob) .then(result => ({ status: 'fulfilled', value: result, originalIndex: i + indexInBatch })) .catch(error => ({ status: 'rejected', reason: error, originalIndex: i + indexInBatch })) ); const settledResults = await Promise.all(promises); for (const result of settledResults) { if (result.status === 'fulfilled' && result.value) { resultsInOrder[result.originalIndex] = result.value; canalesExitosos++; } else { canalesConError++; if (result.status === 'rejected') { const channelNameForError = listaCanalesPcApi[result.originalIndex]?.name || `Índice ${result.originalIndex}`; console.error(`Error procesando canal '${channelNameForError}':`, result.reason); } } processedCount++; if (processedCount % 10 === 0 || processedCount === listaCanalesPcApi.length) { showNotification(`Procesados ${processedCount}/${listaCanalesPcApi.length} canales...`, "info", 3000); } } if (i + MAX_WORKERS_CHANNELS_JS < listaCanalesPcApi.length) { await new Promise(resolve => setTimeout(resolve, 200)); } } resultsInOrder.forEach(entry => { if (entry) { m3uLinesForFile.push(entry); } }); console.log("Proceso de URLs (concurrente) finalizado."); console.log(`Canales con URL exitosa: ${canalesExitosos}`); console.log(`Canales con error/omitidos: ${canalesConError}`); showNotification(`Proceso completado. Éxito: ${canalesExitosos}, Fallos/Omitidos: ${canalesConError}`, "info"); if (canalesExitosos > 0) { let finalM3uContent = m3uLinesForFile.join("\n"); if (!finalM3uContent.endsWith("\n\n") && finalM3uContent.split('\n').length > 1) { finalM3uContent += "\n"; } console.log("M3U Generado (primeros 500 caracteres):", finalM3uContent.substring(0,500)); if (typeof removeChannelsBySourceOrigin === 'function') { removeChannelsBySourceOrigin(orangeTvSourceName); } if (typeof appendM3UContent === 'function') { appendM3UContent(finalM3uContent, orangeTvSourceName); } else { console.error("appendM3UContent no disponible. Usando fallback processM3UContent."); processM3UContent(finalM3uContent, orangeTvSourceName, channels.length === 0); } return finalM3uContent; } else { showNotification("No se generó M3U (no se obtuvieron URLs o no coincidieron con grupos seleccionados).", "warning"); return "#EXTM3U\n#EXTINF:-1,No se pudieron obtener canales\nerror.ts"; } } catch (e) { console.error("Error en generateM3uOrangeTv:", e.message, e); if (e instanceof NotAuthenticatedError || e.message.toLowerCase().includes("cookie")) { showNotification("Error de autenticación con OrangeTV. Intenta de nuevo.", "error"); await deleteAppConfigValue(ORANGE_IDENTITY_KEY); } else { showNotification(`Error generando lista OrangeTV: ${e.message.substring(0,100)}`, "error"); } return "#EXTM3U\n#EXTINF:-1,Error general en el proceso\nerror.ts"; } finally { showLoading(false); await clearAllDynamicHeaders(); console.log("--- Proceso OrangeTV (JS) Finalizado ---"); } }