372 lines
16 KiB
JavaScript
372 lines
16 KiB
JavaScript
const DAZN_API_PLAYBACK = 'https://api.playback.indazn.com/v5/Playback';
|
|
const DAZN_API_REFRESH_TOKEN = 'https://ott-authz-bff-prod.ar.indazn.com/v5/RefreshAccessToken';
|
|
const DAZN_API_RAIL = 'https://rail-router.discovery.indazn.com/eu/v7/Rail';
|
|
const DAZN_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';
|
|
let daznTokenPromiseResolver = null;
|
|
|
|
function decodeJwtPayload(token) {
|
|
if (!token || typeof token !== 'string') return null;
|
|
try {
|
|
const parts = token.split('.');
|
|
if (parts.length !== 3) return null;
|
|
const payloadBase64 = parts[1];
|
|
if (!payloadBase64) return null;
|
|
const decoded = atob(payloadBase64.replace(/-/g, '+').replace(/_/g, '/'));
|
|
return JSON.parse(decoded);
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function showDaznTokenModal() {
|
|
return new Promise((resolve) => {
|
|
daznTokenPromiseResolver = resolve;
|
|
try {
|
|
const daznModalEl = document.getElementById('daznTokenModal');
|
|
if (!daznModalEl) {
|
|
resolve({ token: null, remember: false });
|
|
daznTokenPromiseResolver = null;
|
|
return;
|
|
}
|
|
const daznModal = bootstrap.Modal.getOrCreateInstance(daznModalEl, { backdrop: 'static', keyboard: false });
|
|
$('#daznTokenModalInput').val('');
|
|
$('#daznRememberTokenCheck').prop('checked', true);
|
|
daznModal.show();
|
|
} catch (e) {
|
|
resolve({ token: null, remember: false });
|
|
daznTokenPromiseResolver = null;
|
|
}
|
|
});
|
|
}
|
|
|
|
$(document).on('click', '#submitDaznTokenBtn', async () => {
|
|
const tokenInput = $('#daznTokenModalInput').val();
|
|
const rememberToken = $('#daznRememberTokenCheck').is(':checked');
|
|
const currentResolver = daznTokenPromiseResolver;
|
|
daznTokenPromiseResolver = null;
|
|
if (currentResolver) {
|
|
currentResolver({ token: tokenInput, remember: rememberToken });
|
|
}
|
|
const daznModalEl = document.getElementById('daznTokenModal');
|
|
if (daznModalEl) {
|
|
const daznModalInstance = bootstrap.Modal.getInstance(daznModalEl);
|
|
if (daznModalInstance) {
|
|
daznModalInstance.hide();
|
|
}
|
|
}
|
|
});
|
|
|
|
$(document).on('click', '#cancelDaznTokenBtn', () => {
|
|
const currentResolver = daznTokenPromiseResolver;
|
|
daznTokenPromiseResolver = null;
|
|
if (currentResolver) {
|
|
currentResolver({ token: null, remember: false });
|
|
}
|
|
const daznModalEl = document.getElementById('daznTokenModal');
|
|
if (daznModalEl) {
|
|
const daznModalInstance = bootstrap.Modal.getInstance(daznModalEl);
|
|
if (daznModalInstance) {
|
|
daznModalInstance.hide();
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
async function getDaznTokenFromUserInputOrSettings() {
|
|
let token = daznAuthTokenState;
|
|
if (!token) {
|
|
if (typeof showLoading === 'function') showLoading(true, 'Esperando token DAZN...');
|
|
const modalResult = await showDaznTokenModal();
|
|
if (typeof showLoading === 'function') showLoading(false);
|
|
|
|
const userInputToken = modalResult.token;
|
|
const remember = modalResult.remember;
|
|
|
|
if (userInputToken && userInputToken.trim() !== '') {
|
|
daznAuthTokenState = userInputToken.trim();
|
|
if (remember && typeof saveAppConfigValue === 'function' && typeof DAZN_TOKEN_DB_KEY !== 'undefined') {
|
|
try {
|
|
await saveAppConfigValue(DAZN_TOKEN_DB_KEY, daznAuthTokenState);
|
|
if (typeof showNotification === 'function') showNotification('DAZN: Token guardado.', 'success');
|
|
if (typeof populateUserSettingsForm === 'function') populateUserSettingsForm();
|
|
} catch (e) {
|
|
if(typeof showNotification === 'function') showNotification('DAZN: Error al guardar token en BD.', 'error');
|
|
}
|
|
} else if (!remember && typeof deleteAppConfigValue === 'function' && typeof DAZN_TOKEN_DB_KEY !== 'undefined') {
|
|
try {
|
|
const existingToken = await getAppConfigValue(DAZN_TOKEN_DB_KEY);
|
|
if (existingToken) {
|
|
await deleteAppConfigValue(DAZN_TOKEN_DB_KEY);
|
|
}
|
|
} catch (e) { console.warn("DAZN: Error revisando/borrando token de BD al no recordar", e); }
|
|
if (typeof populateUserSettingsForm === 'function') populateUserSettingsForm();
|
|
}
|
|
return daznAuthTokenState;
|
|
}
|
|
if (typeof showNotification === 'function' && !userInputToken) showNotification('DAZN: Operación de token cancelada o token vacío.', 'info');
|
|
return null;
|
|
}
|
|
return token;
|
|
}
|
|
|
|
async function refreshDaznToken(currentToken, specificUserAgent = null) {
|
|
if (!currentToken) return null;
|
|
if (typeof showLoading === 'function') showLoading(true, 'Refrescando token DAZN...');
|
|
const userAgentToUse = specificUserAgent || DAZN_USER_AGENT;
|
|
|
|
try {
|
|
const decoded = decodeJwtPayload(currentToken);
|
|
if (!decoded || !decoded.deviceId) {
|
|
if (typeof showNotification === 'function') showNotification('DAZN: Token inválido o no se pudo decodificar deviceId para refrescar.', 'error');
|
|
daznAuthTokenState = null;
|
|
if (typeof deleteAppConfigValue === 'function' && typeof DAZN_TOKEN_DB_KEY !== 'undefined') {
|
|
try { await deleteAppConfigValue(DAZN_TOKEN_DB_KEY); } catch (e) { }
|
|
}
|
|
if (typeof populateUserSettingsForm === 'function') populateUserSettingsForm();
|
|
return null;
|
|
}
|
|
|
|
const device_id_suffix = decoded.deviceId.split('-').pop();
|
|
if (!device_id_suffix) {
|
|
if (typeof showNotification === 'function') showNotification('DAZN: DeviceId con formato inesperado.', 'error');
|
|
return null;
|
|
}
|
|
|
|
const response = await fetch(DAZN_API_REFRESH_TOKEN, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Accept': 'application/json, text/plain, */*',
|
|
'Authorization': `Bearer ${currentToken}`,
|
|
'Content-Type': 'application/json',
|
|
'User-Agent': userAgentToUse
|
|
},
|
|
body: JSON.stringify({ DeviceId: device_id_suffix })
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorText = await response.text().catch(() => `HTTP ${response.status}`);
|
|
if (response.status === 401 || response.status === 403) {
|
|
daznAuthTokenState = null;
|
|
if (typeof deleteAppConfigValue === 'function' && typeof DAZN_TOKEN_DB_KEY !== 'undefined') {
|
|
try { await deleteAppConfigValue(DAZN_TOKEN_DB_KEY); } catch (e) { }
|
|
}
|
|
if (typeof populateUserSettingsForm === 'function') populateUserSettingsForm();
|
|
if (typeof showNotification === 'function') showNotification('DAZN: Token inválido/expirado. Se solicitará uno nuevo al intentar la operación.', 'warning');
|
|
return null;
|
|
}
|
|
throw new Error(`Error ${response.status} al refrescar token: ${errorText.substring(0,100)}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
const newToken = data?.AuthToken?.Token;
|
|
|
|
if (newToken) {
|
|
daznAuthTokenState = newToken;
|
|
const storedTokenShouldBeRemembered = await getAppConfigValue(DAZN_TOKEN_DB_KEY);
|
|
|
|
if (storedTokenShouldBeRemembered && typeof saveAppConfigValue === 'function' && typeof DAZN_TOKEN_DB_KEY !== 'undefined') {
|
|
try {
|
|
await saveAppConfigValue(DAZN_TOKEN_DB_KEY, daznAuthTokenState);
|
|
if (typeof showNotification === 'function') showNotification('DAZN: Token refrescado y guardado exitosamente.', 'success');
|
|
} catch (e) {
|
|
if (typeof showNotification === 'function') showNotification('DAZN: Token refrescado, pero error al guardar en BD.', 'warning');
|
|
}
|
|
} else if (storedTokenShouldBeRemembered === null) {
|
|
if (typeof showNotification === 'function') showNotification('DAZN: Token refrescado (sesión actual).', 'info');
|
|
}
|
|
if (typeof populateUserSettingsForm === 'function') populateUserSettingsForm();
|
|
return newToken;
|
|
} else {
|
|
throw new Error('Respuesta de refresco de token no contenía un nuevo token.');
|
|
}
|
|
} catch (error) {
|
|
if (typeof showNotification === 'function') showNotification(`DAZN: Error refrescando token: ${error.message}`, 'error');
|
|
return null;
|
|
} finally {
|
|
if (typeof showLoading === 'function') showLoading(false);
|
|
}
|
|
}
|
|
|
|
async function getActiveDaznToken(forceUserInputIfInvalid = false, specificUserAgent = null) {
|
|
let currentToken = daznAuthTokenState;
|
|
|
|
if (!currentToken) {
|
|
currentToken = await getDaznTokenFromUserInputOrSettings();
|
|
if (!currentToken) {
|
|
return null;
|
|
}
|
|
return currentToken;
|
|
}
|
|
|
|
const refreshedToken = await refreshDaznToken(currentToken, specificUserAgent);
|
|
if (refreshedToken) {
|
|
return refreshedToken;
|
|
} else {
|
|
if (forceUserInputIfInvalid) {
|
|
if (typeof showNotification === 'function') showNotification('DAZN: El token actual no pudo ser refrescado. Se solicitará uno nuevo.', 'warning');
|
|
daznAuthTokenState = null;
|
|
currentToken = await getDaznTokenFromUserInputOrSettings();
|
|
if (!currentToken) {
|
|
return null;
|
|
}
|
|
return currentToken;
|
|
} else {
|
|
if (typeof showNotification === 'function') showNotification('DAZN: El token actual no pudo ser refrescado y no se forzó la entrada de uno nuevo. Usando token anterior si existe.', 'warning');
|
|
return currentToken;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
async function fetchSingleDaznAssetDetails(authToken, assetId, specificUserAgent = null) {
|
|
const decodedToken = decodeJwtPayload(authToken);
|
|
if (!decodedToken || !decodedToken.deviceId) {
|
|
if (typeof showNotification === 'function') showNotification('DAZN: No se pudo decodificar deviceId del token activo.', 'error');
|
|
return null;
|
|
}
|
|
|
|
const userAgentToUse = specificUserAgent || DAZN_USER_AGENT;
|
|
|
|
const params = new URLSearchParams({
|
|
'AppVersion': '0.60.0',
|
|
'DrmType': 'WIDEVINE',
|
|
'Format': 'MPEG-DASH',
|
|
'PlayerId': '@dazn/peng-html5-core/web/web',
|
|
'Platform': 'web',
|
|
'LanguageCode': 'es',
|
|
'Model': 'unknown',
|
|
'Secure': 'true',
|
|
'Manufacturer': 'microsoft',
|
|
'PlayReadyInitiator': 'false',
|
|
'Capabilities': 'mta',
|
|
'AssetId': assetId
|
|
});
|
|
|
|
try {
|
|
const response = await fetch(`${DAZN_API_PLAYBACK}?${params.toString()}`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${authToken}`,
|
|
'User-Agent': userAgentToUse,
|
|
'x-dazn-device': decodedToken.deviceId
|
|
}
|
|
});
|
|
if (!response.ok) {
|
|
const errorText = await response.text().catch(() => `HTTP ${response.status}`);
|
|
throw new Error(`HTTP ${response.status} para AssetId ${assetId}`);
|
|
}
|
|
const data = await response.json();
|
|
|
|
const playbackDetails = data?.PlaybackDetails?.[0];
|
|
if (!playbackDetails || !playbackDetails.ManifestUrl || !playbackDetails.CdnToken) {
|
|
return null;
|
|
}
|
|
|
|
const manifestUrl = playbackDetails.ManifestUrl;
|
|
const cdnToken = playbackDetails.CdnToken;
|
|
|
|
const linearIdMatch = manifestUrl.match(/dazn-linear-(\d+)/);
|
|
const daznLinearId = linearIdMatch ? linearIdMatch[1] : null;
|
|
|
|
return {
|
|
assetId: assetId,
|
|
title: null,
|
|
baseUrl: manifestUrl,
|
|
cdnTokenName: cdnToken.Name,
|
|
cdnTokenValue: cdnToken.Value,
|
|
daznLinearId: daznLinearId,
|
|
streamUserAgent: userAgentToUse
|
|
};
|
|
|
|
} catch (error) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function fetchDaznRailDataAndChannelDetails(authToken, specificUserAgent = null) {
|
|
if (typeof showLoading === 'function') showLoading(true, 'Obteniendo canales DAZN...');
|
|
const userAgentToUse = specificUserAgent || DAZN_USER_AGENT;
|
|
const params = new URLSearchParams({
|
|
'id': 'LiveAndNextNew',
|
|
'params': 'PageType:Home;ContentType:None',
|
|
'languageCode': 'es',
|
|
'country': 'es',
|
|
'platform': 'web'
|
|
});
|
|
|
|
try {
|
|
const response = await fetch(`${DAZN_API_RAIL}?${params.toString()}`, {
|
|
headers: { 'User-Agent': userAgentToUse }
|
|
});
|
|
if (!response.ok) {
|
|
const errorText = await response.text().catch(() => `HTTP ${response.status}`);
|
|
throw new Error(`HTTP ${response.status} al obtener Rail data`);
|
|
}
|
|
const railData = await response.json();
|
|
|
|
const daznChannelsInfo = [];
|
|
const assetPromises = [];
|
|
|
|
if (railData && railData.Tiles) {
|
|
for (const tile of railData.Tiles) {
|
|
if (tile.Type === 'Live' && tile.Label === 'Canal en directo' && tile.AssetId) {
|
|
assetPromises.push(
|
|
fetchSingleDaznAssetDetails(authToken, tile.AssetId, userAgentToUse)
|
|
.then(details => {
|
|
if (details) {
|
|
details.title = tile.Title;
|
|
return details;
|
|
}
|
|
return null;
|
|
})
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
const resolvedAssets = await Promise.all(assetPromises);
|
|
resolvedAssets.forEach(asset => {
|
|
if (asset) {
|
|
daznChannelsInfo.push(asset);
|
|
}
|
|
});
|
|
|
|
if (daznChannelsInfo.length === 0 && typeof showNotification === 'function') {
|
|
showNotification('DAZN: No se encontraron canales en directo desde la API.', 'warning');
|
|
}
|
|
return daznChannelsInfo;
|
|
|
|
} catch (error) {
|
|
if (typeof showNotification === 'function') showNotification(`DAZN: Error obteniendo lista de canales: ${error.message}`, 'error');
|
|
return [];
|
|
} finally {
|
|
if (typeof showLoading === 'function') showLoading(false);
|
|
}
|
|
}
|
|
|
|
async function orchestrateDaznUpdate(specificUserAgent = null) {
|
|
if (typeof showLoading === 'function') showLoading(true, 'Iniciando actualización DAZN...');
|
|
try {
|
|
const authToken = await getActiveDaznToken(true, specificUserAgent);
|
|
|
|
if (!authToken) {
|
|
if (typeof populateUserSettingsForm === 'function') populateUserSettingsForm();
|
|
return;
|
|
}
|
|
|
|
const daznChannelDetailsList = await fetchDaznRailDataAndChannelDetails(authToken, specificUserAgent);
|
|
|
|
if (daznChannelDetailsList && daznChannelDetailsList.length > 0) {
|
|
if (typeof window.updateM3UWithDaznData === 'function') {
|
|
window.updateM3UWithDaznData(daznChannelDetailsList);
|
|
} else {
|
|
if (typeof showNotification === 'function') showNotification('DAZN: Error interno, no se pudo actualizar M3U.', 'error');
|
|
}
|
|
} else if (daznChannelDetailsList) {
|
|
if (typeof showNotification === 'function') showNotification('DAZN: No se obtuvieron detalles de canales para actualizar.', 'info');
|
|
}
|
|
} catch (error) {
|
|
if (typeof showNotification === 'function') showNotification(`DAZN: Error general durante la actualización: ${error.message}`, 'error');
|
|
} finally {
|
|
if (typeof showLoading === 'function') showLoading(false);
|
|
}
|
|
} |