Advanced_DRM_Player/orange_tv_client.js

675 lines
34 KiB
JavaScript

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 ---");
}
}