// === GLOBAL VARIABLES ===
let psshs = chrome.extension.getBackgroundPage().psshs;
let requests = chrome.extension.getBackgroundPage().requests;
let pageURL = chrome.extension.getBackgroundPage().pageURL;
let targetIds = chrome.extension.getBackgroundPage().targetIds;
let clearkey = chrome.extension.getBackgroundPage().clearkey;
let userInputs = {}; // IMPORTANT: Cette variable était manquante!
// === WIDEVINE KEY EXTRACTION ===
class WidevineExtractor {
static async extractKeys() {
const guessButton = document.getElementById("guess");
const resultTextarea = document.getElementById('result');
try {
// Vérifier que userInputs.license est défini
if (userInputs.license === undefined || !requests[userInputs.license]) {
throw new Error('License not selected. Please wait for auto-selection or select manually.');
}
// UI feedback
UIHelpers.setLoadingState(guessButton, true);
document.body.style.cursor = "wait";
// Initialize Pyodide
const pyodide = await loadPyodide();
await pyodide.loadPackage([
"certifi-2024.2.2-py3-none-any.whl",
"charset_normalizer-3.3.2-py3-none-any.whl",
"construct-2.8.8-py2.py3-none-any.whl",
"idna-3.6-py3-none-any.whl",
"packaging-23.2-py3-none-any.whl",
"protobuf-4.24.4-cp312-cp312-emscripten_3_1_52_wasm32.whl",
"pycryptodome-3.20.0-cp35-abi3-emscripten_3_1_52_wasm32.whl",
"pymp4-1.4.0-py3-none-any.whl",
"pyodide_http-0.2.1-py3-none-any.whl",
"pywidevine-1.8.0-py3-none-any.whl",
"requests-2.31.0-py3-none-any.whl",
"urllib3-2.2.1-py3-none-any.whl"
].map(e => "/libs/wheels/" + e));
// Configure Guesser
pyodide.globals.set("pssh", document.getElementById('pssh').value);
pyodide.globals.set("licUrl", requests[userInputs['license']]['url']);
pyodide.globals.set("licHeaders", requests[userInputs['license']]['headers']);
pyodide.globals.set("licBody", requests[userInputs['license']]['body']);
// Load Python scripts
const [pre, after, scheme] = await Promise.all([
fetch('/python/pre.py').then(res => res.text()),
fetch('/python/after.py').then(res => res.text()),
Promise.resolve(document.getElementById("schemeCode").value)
]);
// Execute Python script
const result = await pyodide.runPythonAsync([pre, scheme, after].join("\n"));
resultTextarea.value = result;
// Save to history
this.saveToHistory(result);
// Auto-update CrawlFlix integration
CrawlFlixIntegration.updateAfterKeyExtraction();
StatusManager.show('Keys extracted successfully!', 'success');
} catch (error) {
console.error('Key extraction failed:', error);
StatusManager.show(`Key extraction failed: ${error.message}`, 'error');
} finally {
// Reset UI
UIHelpers.setLoadingState(guessButton, false);
document.body.style.cursor = "auto";
}
}
static saveToHistory(result) {
const historyData = {
PSSH: document.getElementById('pssh').value,
KEYS: result.split("\n").slice(0, -1)
};
chrome.storage.local.set({[pageURL]: historyData}, null);
}
static async autoSelect() {
userInputs["license"] = 0;
document.getElementById("license").value = requests[0]['url'];
document.getElementById('pssh').value = psshs[0];
try {
const selectRules = await fetch("/selectRules.conf").then(r => r.text());
const rules = selectRules
.replace(/\n^\s*$|\s*\/\/.*|\s*$/gm, "")
.split("\n")
.map(row => row.split("$$"));
for (const item of rules) {
const search = requests.map(r => r['url']).findIndex(e => e.includes(item[0]));
if (search >= 0) {
if (item[1]) document.getElementById("schemeSelect").value = item[1];
userInputs["license"] = search;
document.getElementById("license").value = requests[search]['url'];
break;
}
}
document.getElementById("schemeSelect").dispatchEvent(new Event("input"));
} catch (error) {
console.error('Auto-select failed:', error);
}
}
}
class MPDCache {
constructor() {
this.cachedMPDs = new Map();
this.validationPromises = new Map();
}
hasChanged(newMPDs) {
const currentUrls = Array.from(this.cachedMPDs.keys()).sort();
const newUrls = newMPDs.sort();
return JSON.stringify(currentUrls) !== JSON.stringify(newUrls);
}
setCachedMPD(url, data) {
this.cachedMPDs.set(url, {
...data,
lastValidated: Date.now()
});
}
// Récupère un MPD du cache
getCachedMPD(url) {
return this.cachedMPDs.get(url);
}
// Supprime un MPD invalide du cache
removeMPD(url) {
this.cachedMPDs.delete(url);
this.validationPromises.delete(url);
}
// Récupère tous les MPDs valides triés par score/durée
getValidMPDs() {
return Array.from(this.cachedMPDs.entries())
.map(([url, data]) => ({ url, ...data }))
.sort((a, b) => {
// Prioriser d'abord par durée si disponible
if (a.duration && b.duration) {
return b.duration - a.duration;
}
// Sinon par score
return b.score - a.score;
});
}
cleanup(maxAge = 5 * 60 * 1000) {
const now = Date.now();
for (const [url, data] of this.cachedMPDs.entries()) {
if (now - data.lastValidated > maxAge) {
this.removeMPD(url);
}
}
}
}
class MPDValidator {
static async validateMPD(url) {
const API_URL = 'http://192.168.1.230:6080/api/processMpd';
try {
const response = await fetch(API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
mp4Filename: "validation_test",
mpdUrl: url
})
});
if (!response.ok) {
throw new Error(`API returned ${response.status}: ${response.statusText}`);
}
const result = await response.json();
// Valider la structure de la réponse
if (!result.videoTracks || !Array.isArray(result.videoTracks)) {
throw new Error('Invalid response structure: missing videoTracks array');
}
// Calculer la durée maximale parmi les tracks
const maxDuration = result.videoTracks.reduce((max, track) => {
const duration = track.duration || 0;
return Math.max(max, duration);
}, 0);
return {
valid: true,
duration: maxDuration,
videoTracks: result.videoTracks,
raw: result
};
} catch (error) {
console.error(`MPD validation failed for ${url}:`, error);
return {
valid: false,
error: error.message,
duration: 0
};
}
}
static async validateMPDWithCache(url, cache) {
// Vérifier si on a déjà une promesse en cours pour cette URL
if (cache.validationPromises.has(url)) {
return await cache.validationPromises.get(url);
}
// Créer une nouvelle promesse de validation
const validationPromise = this.validateMPD(url);
cache.validationPromises.set(url, validationPromise);
try {
const result = await validationPromise;
return result;
} finally {
// Nettoyer la promesse une fois terminée
cache.validationPromises.delete(url);
}
}
}
class SmartMPDSelector {
static scoreAndRankMPDs(mpdUrls) {
const scoredMPDs = mpdUrls.map(url => ({
url,
score: this.calculateMPDScore(url),
reason: this.getSelectionReason(url)
}));
// Trier par score décroissant
return scoredMPDs.sort((a, b) => b.score - a.score);
}
static calculateMPDScore(url) {
let score = 0;
const urlLower = url.toLowerCase();
// === CRITÈRES NÉGATIFS (à éviter) ===
// Éliminer les URLs non-MPD
if (urlLower.includes('github.com') || urlLower.includes('manifest.json')) {
return -1000; // Score très négatif
}
// Éviter les URLs avec trop de redirections/proxies
if (urlLower.includes('routemeup') || urlLower.includes('route=')) {
score -= 200;
}
// Éviter les URLs avec double-encoding ou trop complexes
if (url.includes('__token__') && url.includes('%')) {
score -= 100;
}
// === CRITÈRES POSITIFS (à privilégier) ===
// Privilégier les URLs directes CDN
if (urlLower.includes('cdn.net') || urlLower.includes('vod-')) {
score += 300;
}
// Privilégier les URLs avec .mpd explicite
if (url.endsWith('.mpd') || urlLower.includes('.mpd?')) {
score += 200;
}
// Privilégier les URLs courtes (moins de proxies)
if (url.length < 300) {
score += 100;
} else if (url.length > 500) {
score -= 50;
}
// Privilégier les tokens JWT simples vs double tokens
const tokenCount = (url.match(/token=/g) || []).length;
if (tokenCount === 1) {
score += 50;
} else if (tokenCount > 1) {
score -= 100;
}
// Privilégier les URLs sans encoding excessif
const encodingScore = url.length - decodeURIComponent(url).length;
if (encodingScore < 50) {
score += 50;
} else {
score -= encodingScore;
}
// === CRITÈRES SPÉCIFIQUES CANAL+ ===
// Détecter le pattern Canal+/Paramount+
if (urlLower.includes('paramountplus') || urlLower.includes('viacom')) {
score += 100;
// Privilégier les edge servers directs
if (urlLower.includes('p-cdnvod-edge') && !urlLower.includes('routemeup')) {
score += 150;
}
}
return score;
}
static getSelectionReason(url) {
const urlLower = url.toLowerCase();
if (urlLower.includes('github.com')) {
return 'Excluded: Not a media manifest';
}
if (urlLower.includes('routemeup')) {
return 'Router/proxy URL (lower priority)';
}
if (urlLower.includes('p-cdnvod-edge') && !urlLower.includes('routemeup')) {
return 'Direct CDN edge server (optimal)';
}
if (url.includes('__token__')) {
return 'Tokenized CDN URL';
}
return 'Standard MPD URL';
}
static getBestMPD(mpdUrls) {
if (!mpdUrls || mpdUrls.length === 0) return null;
if (mpdUrls.length === 1) return mpdUrls[0];
const rankedMPDs = this.scoreAndRankMPDs(mpdUrls);
const best = rankedMPDs[0];
console.log('MPD Selection Results:', rankedMPDs);
// Ne retourner que si le score est positif
return best.score > 0 ? best.url : null;
}
static analyzeAndDisplayMPDs(mpdUrls) {
const rankedMPDs = this.scoreAndRankMPDs(mpdUrls);
// Afficher l'analyse dans la console pour debug
console.table(rankedMPDs.map(mpd => ({
URL: this.truncateUrl(mpd.url),
Score: mpd.score,
Reason: mpd.reason
})));
return rankedMPDs;
}
static truncateUrl(url, maxLength = 60) {
if (url.length <= maxLength) return url;
try {
const urlObj = new URL(url);
const domain = urlObj.hostname;
const filename = urlObj.pathname.split('/').pop();
return `${domain}/.../${filename}`;
} catch {
return url.substring(0, maxLength) + '...';
}
}
// Fonction pour mettre à jour l'UI avec le meilleur MPD
static updateUIWithBestMPD(mpdUrls) {
const bestMPD = this.getBestMPD(mpdUrls);
if (bestMPD) {
// Auto-sélectionner le meilleur MPD
const selects = ['mpdSelect', 'mpdSelectCK'];
const inputs = ['mpdUrl', 'mpdUrlCK'];
selects.forEach((selectId, index) => {
const select = document.getElementById(selectId);
const input = document.getElementById(inputs[index]);
if (select && input) {
select.value = bestMPD;
input.value = bestMPD;
// Ajouter une classe pour indiquer la sélection auto
select.classList.add('border-success');
setTimeout(() => select.classList.remove('border-success'), 3000);
}
});
const analysis = this.analyzeAndDisplayMPDs(mpdUrls);
const bestAnalysis = analysis[0];
StatusManager.show(
`Auto-selected best MPD: ${bestAnalysis.reason}`,
'success'
);
}
}
}
class MPDDetector {
constructor() {
this.cache = new MPDCache();
this.isUpdating = false;
}
static detectMPDUrls() {
const backgroundPage = chrome.extension.getBackgroundPage();
const detectedMPDs = backgroundPage.detectedMPDs || [];
console.log('Raw detected MPDs:', detectedMPDs);
return detectedMPDs
.filter((url, index, arr) => arr.indexOf(url) === index) // Remove duplicates
.filter(url => url && url.length > 0) // Remove empty URLs
.filter(url => !url.includes('/chunk-') && !url.includes('/segment-')); // Filter segments
}
async updateDropdowns() {
// Éviter les mises à jour simultanées
if (this.isUpdating) {
console.log('MPD update already in progress, skipping...');
return;
}
this.isUpdating = true;
try {
const rawMPDs = MPDDetector.detectMPDUrls();
// Vérifier si la liste a changé
if (!this.cache.hasChanged(rawMPDs)) {
console.log('MPD list unchanged, using cache');
this.renderDropdowns();
return;
}
console.log('MPD list changed, updating cache...');
// Nettoyer le cache des URLs qui ne sont plus détectées
const currentCachedUrls = Array.from(this.cache.cachedMPDs.keys());
currentCachedUrls.forEach(url => {
if (!rawMPDs.includes(url)) {
this.cache.removeMPD(url);
}
});
// Traiter les nouvelles URLs
const newUrls = rawMPDs.filter(url => !this.cache.getCachedMPD(url));
if (newUrls.length > 0) {
StatusManager.show(
`Validating ${newUrls.length} new MPD(s)...`,
'info'
);
await this.processNewMPDs(newUrls);
}
// Rendre les dropdowns
this.renderDropdowns();
// Nettoyer le cache périodiquement
this.cache.cleanup();
} catch (error) {
console.error('MPD update failed:', error);
StatusManager.show(`MPD update failed: ${error.message}`, 'error');
} finally {
this.isUpdating = false;
}
}
async processNewMPDs(urls) {
const validationPromises = urls.map(async (url) => {
try {
// Calculer le score initial (logique existante)
const initialScore = SmartMPDSelector.calculateMPDScore(url);
const reason = SmartMPDSelector.getSelectionReason(url);
// Valider via API
const validationResult = await MPDValidator.validateMPDWithCache(url, this.cache);
if (validationResult.valid) {
// Ajuster le score basé sur la durée
let finalScore = initialScore;
if (validationResult.duration > 0) {
// Bonus pour les durées plus longues
finalScore += Math.min(validationResult.duration / 10, 500); // Max +500 points
}
this.cache.setCachedMPD(url, {
score: finalScore,
reason: `${reason} | Duration: ${validationResult.duration}s`,
duration: validationResult.duration,
valid: true,
videoTracks: validationResult.videoTracks
});
console.log(`✅ MPD validated: ${url} (duration: ${validationResult.duration}s)`);
} else {
// MPD invalide, ne pas l'ajouter au cache
console.log(`❌ MPD invalid: ${url} (${validationResult.error})`);
}
} catch (error) {
console.error(`Error processing MPD ${url}:`, error);
}
});
await Promise.allSettled(validationPromises);
}
renderDropdowns() {
const validMPDs = this.cache.getValidMPDs();
const selects = ['mpdSelect', 'mpdSelectCK'];
selects.forEach(selectId => {
const select = document.getElementById(selectId);
if (!select) return;
select.innerHTML = '';
validMPDs.forEach((mpdInfo, index) => {
const option = document.createElement('option');
option.value = mpdInfo.url;
// Indicateurs visuels améliorés
let prefix = '';
if (index === 0) {
prefix = `🏆 [${mpdInfo.duration}s] `;
} else if (mpdInfo.duration > 0) {
prefix = `⏱️ [${mpdInfo.duration}s] `;
} else if (mpdInfo.score > 0) {
prefix = '✅ ';
} else {
prefix = '⚠️ ';
}
option.textContent = prefix + this.formatUrlForDisplay(mpdInfo.url);
option.title = `Score: ${mpdInfo.score} | ${mpdInfo.reason}`;
// Styling
if (index === 0) {
option.style.fontWeight = 'bold';
option.style.color = '#28a745';
} else if (mpdInfo.duration > 1000) { // Plus de 16 minutes
option.style.color = '#17a2b8';
} else if (mpdInfo.score <= 0) {
option.style.color = '#dc3545';
}
select.appendChild(option);
});
});
// Auto-sélection du meilleur
if (validMPDs.length > 0) {
this.selectBestMPD(validMPDs[0]);
}
// Mettre à jour le status
const totalDetected = MPDDetector.detectMPDUrls().length;
const validCount = validMPDs.length;
const invalidCount = totalDetected - validCount;
let statusMessage = `Found ${validCount} valid MPD(s)`;
if (invalidCount > 0) {
statusMessage += ` (${invalidCount} filtered out)`;
}
StatusManager.show(statusMessage, validCount > 0 ? 'success' : 'warning');
}
selectBestMPD(bestMPD) {
const selects = ['mpdSelect', 'mpdSelectCK'];
const inputs = ['mpdUrl', 'mpdUrlCK'];
selects.forEach((selectId, index) => {
const select = document.getElementById(selectId);
const input = document.getElementById(inputs[index]);
if (select && input) {
select.value = bestMPD.url;
input.value = bestMPD.url;
// Effet visuel
select.classList.add('border-success');
setTimeout(() => select.classList.remove('border-success'), 3000);
}
});
}
formatUrlForDisplay(url) {
return SmartMPDSelector.truncateUrl(url, 80);
}
// Méthode publique pour forcer la mise à jour
async forceUpdate() {
this.cache = new MPDCache(); // Reset du cache
await this.updateDropdowns();
}
}
const mpdDetectorInstance = new MPDDetector();
MPDDetector.updateDropdowns = () => mpdDetectorInstance.updateDropdowns();
MPDDetector.forceUpdate = () => mpdDetectorInstance.forceUpdate();
// === CRAWLFLIX INTEGRATION ===
class CrawlFlixIntegration {
static async sendToCrawlFlix(isClearchey = false) {
const suffix = isClearchey ? 'CK' : '';
const crawlFlixUrl = document.getElementById(`crawlFlixUrl${suffix}`).value || 'http://localhost:4200';
const mpdUrl = document.getElementById(`mpdUrl${suffix}`).value;
const resultTextarea = document.getElementById(isClearchey ? 'ckResult' : 'result');
// Validation
if (!mpdUrl.trim()) {
StatusManager.show('Please enter or select an MPD URL', 'error', suffix);
return;
}
if (!resultTextarea.value.trim()) {
StatusManager.show('No keys available to send', 'error', suffix);
return;
}
try {
StatusManager.show('Opening CrawlFlix with preloaded data...', 'info', suffix);
// Parse et valide les clés pour information
const keys = this.parseKeys(resultTextarea.value);
if (keys.length === 0) {
StatusManager.show('No valid keys found in the format key:value', 'error', suffix);
return;
}
// Ouvrir CrawlFlix directement avec les paramètres
this.openCrawlFlixTab(crawlFlixUrl, mpdUrl, resultTextarea.value);
// Message de succès
const contentType = isClearchey ? 'ClearKey' : 'Widevine';
StatusManager.show(`${contentType} data sent to CrawlFlix! (${keys.length} keys, MPD URL)`, 'success', suffix);
} catch (error) {
console.error('CrawlFlix send error:', error);
StatusManager.show(`Failed to send: ${error.message}`, 'error', suffix);
}
}
static parseKeys(keysText) {
return keysText
.split('\n')
.map(line => line.trim())
.filter(line => line && line.includes(':'))
.map(line => {
const [key, value] = line.split(':');
return {
key: key?.trim(),
value: value?.trim()
};
})
.filter(k => k.key && k.value && k.key.length > 0 && k.value.length > 0);
}
static async processMPD(crawlFlixUrl, mpdUrl) {
const response = await fetch(`${crawlFlixUrl}/processMPD`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ mpdUrl })
});
if (!response.ok) {
const errorText = await response.text().catch(() => 'Unknown error');
throw new Error(`MPD processing failed (${response.status}): ${errorText}`);
}
return await response.json();
}
static openCrawlFlixTab(crawlFlixUrl, mpdUrl, keysText) {
// Encoder les données pour l'URL
const params = new URLSearchParams({
mpdUrl: mpdUrl,
keys: keysText,
autoLoad: 'true'
});
// Ouvrir CrawlFlix avec les paramètres pré-remplis
const crawlFlixTab = `${crawlFlixUrl}?${params.toString()}`;
chrome.tabs.create({ url: crawlFlixTab });
console.log('Opening CrawlFlix with preloaded data:', crawlFlixTab);
}
static copyKeys(resultTextareaId) {
const textarea = document.getElementById(resultTextareaId);
if (!textarea || !textarea.value.trim()) {
StatusManager.show('No keys to copy', 'error');
return;
}
navigator.clipboard.writeText(textarea.value).then(() => {
StatusManager.show('Keys copied to clipboard!', 'success');
}).catch(err => {
console.error('Copy failed:', err);
StatusManager.show('Failed to copy keys', 'error');
});
}
static updateAfterKeyExtraction() {
// Auto-refresh MPD detection after key extraction
setTimeout(() => {
MPDDetector.updateDropdowns();
}, 1000);
}
static loadSavedSettings() {
chrome.storage.local.get(['crawlFlixUrl'], (result) => {
if (result.crawlFlixUrl) {
const inputs = ['crawlFlixUrl', 'crawlFlixUrlCK'];
inputs.forEach(id => {
const input = document.getElementById(id);
if (input && !input.value) {
input.value = result.crawlFlixUrl;
}
});
}
});
}
static saveSettings() {
const inputs = ['crawlFlixUrl', 'crawlFlixUrlCK'];
inputs.forEach(id => {
const input = document.getElementById(id);
if (input) {
input.addEventListener('change', (e) => {
chrome.storage.local.set({ crawlFlixUrl: e.target.value });
});
}
});
}
}
// === STATUS MANAGER ===
class StatusManager {
static show(message, type, suffix = '') {
const statusDiv = document.getElementById(`crawlFlixStatus${suffix}`);
if (!statusDiv) return;
// Clear any existing content
statusDiv.innerHTML = '';
// Create alert element
const alert = document.createElement('div');
alert.className = `alert alert-${this.getBootstrapClass(type)} alert-dismissible fade show`;
alert.innerHTML = `
${message.replace(/\n/g, '
')}
`;
statusDiv.appendChild(alert);
// Auto-dismiss after delay
if (type === 'success' || type === 'info') {
setTimeout(() => {
if (alert.parentNode) {
alert.classList.remove('show');
setTimeout(() => alert.remove(), 300);
}
}, 5000);
}
}
static getBootstrapClass(type) {
const mapping = {
success: 'success',
error: 'danger',
info: 'info',
warning: 'warning'
};
return mapping[type] || 'secondary';
}
static getIcon(type) {
const mapping = {
success: 'fa-check-circle',
error: 'fa-exclamation-triangle',
info: 'fa-info-circle',
warning: 'fa-exclamation-triangle'
};
return mapping[type] || 'fa-info';
}
}
// === UI HELPERS ===
class UIHelpers {
static setLoadingState(button, isLoading) {
if (!button) return;
if (isLoading) {
button.disabled = true;
const originalText = button.innerHTML;
button.dataset.originalText = originalText;
button.innerHTML = 'Extracting Keys...';
} else {
button.disabled = false;
button.innerHTML = ' Extract Widevine Keys';
}
}
static copyToClipboard(text) {
const textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
}
}
// === EVENT LISTENERS ===
class EventManager {
static init() {
this.setupWidevineListeners();
this.setupCrawlFlixListeners();
this.setupUIListeners();
}
static setupWidevineListeners() {
const guessButton = document.getElementById('guess');
const resultTextarea = document.getElementById('result');
if (guessButton) {
guessButton.addEventListener('click', () => WidevineExtractor.extractKeys());
}
if (resultTextarea) {
resultTextarea.addEventListener('click', function() {
this.select();
navigator.clipboard.writeText(this.value);
});
}
}
static setupCrawlFlixListeners() {
// Dropdown selections
['mpdSelect', 'mpdSelectCK'].forEach(selectId => {
const select = document.getElementById(selectId);
const urlInput = document.getElementById(selectId.replace('Select', 'Url'));
if (select && urlInput) {
select.addEventListener('change', (e) => {
if (e.target.value) {
urlInput.value = e.target.value;
}
});
}
});
// Refresh buttons
['refreshMPDs', 'refreshMPDsCK'].forEach(buttonId => {
const button = document.getElementById(buttonId);
if (button) {
button.addEventListener('click', async () => {
button.disabled = true;
button.innerHTML = ' Refreshing...';
try {
await MPDDetector.forceUpdate();
} finally {
button.disabled = false;
button.innerHTML = ' Refresh';
}
});
}
});
// Send buttons
const sendButton = document.getElementById('sendToCrawlFlix');
if (sendButton) {
sendButton.addEventListener('click', () => CrawlFlixIntegration.sendToCrawlFlix(false));
}
const sendButtonCK = document.getElementById('sendToCrawlFlixCK');
if (sendButtonCK) {
sendButtonCK.addEventListener('click', () => CrawlFlixIntegration.sendToCrawlFlix(true));
}
// Copy buttons
const copyButton = document.getElementById('copyKeys');
if (copyButton) {
copyButton.addEventListener('click', () => CrawlFlixIntegration.copyKeys('result'));
}
const copyButtonCK = document.getElementById('copyKeysCK');
if (copyButtonCK) {
copyButtonCK.addEventListener('click', () => CrawlFlixIntegration.copyKeys('ckResult'));
}
}
static setupUIListeners() {
// ClearKey result click handler
const ckResult = document.getElementById('ckResult');
if (ckResult) {
ckResult.addEventListener('click', function() {
this.select();
navigator.clipboard.writeText(this.value);
});
}
}
}
// === CORS FETCH HELPER ===
window.corsFetch = (u, m, h, b) => {
return new Promise((resolve, reject) => {
chrome.tabs.sendMessage(targetIds[0], {
type: "FETCH",
u: u,
m: m,
h: h,
b: b
}, {frameId: targetIds[1]}, res => {
resolve(res);
});
});
};
// === INITIALIZATION ===
document.addEventListener('DOMContentLoaded', () => {
EventManager.init();
CrawlFlixIntegration.loadSavedSettings();
CrawlFlixIntegration.saveSettings();
});
// Main initialization logic
if (clearkey) {
// ClearKey detected
document.getElementById('noEME').style.display = 'none';
document.getElementById('ckHome').style.display = 'block';
document.getElementById('ckResult').value = clearkey;
MPDDetector.updateDropdowns();
StatusManager.show('ClearKey content detected', 'success');
} else if (psshs.length) {
// Widevine detected
document.getElementById('noEME').style.display = 'none';
document.getElementById('home').style.display = 'block';
WidevineExtractor.autoSelect().then(async () => {
EventManager.init();
await MPDDetector.updateDropdowns();
StatusManager.show('Widevine content detected and configured', 'success');
});
// Réduire la fréquence de refresh automatique
setInterval(() => {
MPDDetector.updateDropdowns(); // Ne se met à jour que si nécessaire
}, 60000); // Augmenté à 10 secondes au lieu de 3
}