From 1ca02d70cf2bc9850cd26ceca633e5bfc7e78117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pr=C3=A9nom=20Nom?= <adresse@mail.com> Date: Sun, 16 Feb 2025 05:49:41 +0100 Subject: [PATCH] correction + affichage stats --- src/background/background.js | 156 +++++++++----- src/popup/popup.js | 80 ++++--- src/utils/stats.js | 386 ++++++++++++++++++++++++---------- src/workers/pyodide_worker.js | 72 ++++++- 4 files changed, 485 insertions(+), 209 deletions(-) diff --git a/src/background/background.js b/src/background/background.js index 178b3bf..ff331a8 100644 --- a/src/background/background.js +++ b/src/background/background.js @@ -296,7 +296,6 @@ function showInstructionPopup(details) { }); } - // ───────────────────────────────────────────────────────────────────────────── // Initialisation du WebWorker // ───────────────────────────────────────────────────────────────────────────── @@ -344,7 +343,7 @@ browser.runtime.onMessage.addListener(async (message, sender, sendResponse) => { if (!isActive) { await browser.storage.local.set({ autoAdd: false }); //console.log("[Background] autoAdd désactivé car les statistiques ont été désactivées."); - //Vérifier et activer le tracking + //Vérifier et activer le tracking des stats checkAndUpdateTracking(); } } @@ -354,27 +353,42 @@ browser.runtime.onMessage.addListener(async (message, sender, sendResponse) => { worker.postMessage({ command: "pyodide-simplemma" }); } - if (message.command === "process-text") { - console.log("[Background] Envoi du texte au Worker pour traitement :", message.text); - - // // Récupérer les préférences utilisateur depuis le stockage local - // const { accessToken, trackedLanguages, threshold, autoAdd } = await browser.storage.local.get([ - // "accessToken", "trackedLanguages", "threshold", "autoAdd" - // ]) || { trackedLanguages: [], threshold: 10, autoAdd: false }; - - // // Envoyer les données au Worker - // worker.postMessage({ - // type: "process-text", - // text: message.text, - // accessToken, - // trackedLanguages, - // threshold, - // autoAdd - // }); - } + // if (message.command === "process-text") { + // console.log("[Background] Envoi du texte au Worker pour traitement :", message.text); + // worker.postMessage({ command: "process-text", text: message.text }); + + // // // Récupérer les préférences utilisateur depuis le stockage local + // // const { accessToken, trackedLanguages, threshold, autoAdd } = await browser.storage.local.get([ + // // "accessToken", "trackedLanguages", "threshold", "autoAdd" + // // ]) || { trackedLanguages: [], threshold: 10, autoAdd: false }; + + // // // Envoyer les données au Worker + // // worker.postMessage({ + // // type: "process-text", + // // text: message.text, + // // accessToken, + // // trackedLanguages, + // // threshold, + // // autoAdd + // // }); + // } return true; }); +// Charger les fréquences stockées au démarrage +async function loadStoredFrequencies() { + const { storedFrequencies } = await browser.storage.local.get("storedFrequencies"); + return storedFrequencies || {}; +} + +// Initialiser le stockage au démarrage +let storedFrequencies = {}; + +loadStoredFrequencies().then(frequencies => { + storedFrequencies = frequencies; + console.log("[Background] Fréquences initialisées :", storedFrequencies); +}); + // ───────────────────────────────────────────────────────────────────────────── // Écouter les réponses du WebWorker // ───────────────────────────────────────────────────────────────────────────── @@ -384,12 +398,29 @@ worker.onmessage = async (event) => { //Si Pyodide et Simplemma sont prêts : AJOUTER UNE INFO VISUELLE if (event.data.type === "pyodide-simplemma" && event.data.status === "success") { console.log("[Background] Pyodide et Simplemma prêts. Mise à jour de l'état."); - // Stocker l'information dans le localStorage await browser.storage.local.set({ pyodideSimplemmaReady: true }); - + //Activer le tracking des stats checkAndUpdateTracking(); } + + // if (event.data.type === "update-frequencies") { + // console.log("test") + // const { lang, frequencies } = data; + // console.log(`[Background] TEST LANGUE DETECTEE : ${lang}`); + // } + + //Stockage des fréquences + // if (event.data.type === "save-frequencies") { + // cconsole.log("[Background] Sauvegarde des fréquences :", event.data.frequencies); + // storedFrequencies = event.data.frequencies; + // try { + // await browser.storage.local.set({ storedFrequencies }); + // console.log("[Background] Fréquences sauvegardées avec succès."); + // } catch (error) { + // console.error("[Background] Erreur lors de la sauvegarde des fréquences :", error); + // } + // } }; // ───────────────────────────────────────────────────────────────────────────── @@ -414,48 +445,69 @@ async function checkAndUpdateTracking() { async function notifyAllTabs(message) { browser.tabs.query({}).then((tabs) => { tabs.forEach((tab) => { + //console.log(`[Background] Tentative d'envoi à l'onglet ${tab.id}`); browser.tabs.sendMessage(tab.id, message) .catch((error) => console.warn(`[Background] Impossible d'envoyer un message à l'onglet ${tab.id} : ${error}`)); }); }); } -// Gestion des nouveaux onglets -browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { - if (changeInfo.status === "complete") { - browser.storage.local.get(["isTrackingActive", "pyodideSimplemmaReady"]).then(({ isTrackingActive, pyodideSimplemmaReady }) => { - if (isTrackingActive && pyodideSimplemmaReady) { - browser.tabs.executeScript(tabId, { file: "src/utils/stats.js" }) - .then(() => console.log(`[Background] stats.js injecté dans le nouvel onglet ${tabId}`)) - .catch((error) => console.warn(`[Background] Impossible d'injecter stats.js dans ${tabId} : ${error}`)); - - setTimeout(() => { - browser.tabs.sendMessage(tabId, { command: "activate-stats" }) - .catch((error) => console.warn(`[Background] Impossible d'envoyer 'activate-stats' à ${tabId} : ${error}`)); - }, 1000); - } - }); +// ───────────────────────────────────────────────────────────────────────────── +// Statistiques : Écoute des modifications du stockage et mise à jour du tracking +// ───────────────────────────────────────────────────────────────────────────── +browser.storage.onChanged.addListener(async (changes, area) => { + if (area === "local" && (changes.isTrackingActive || changes.pyodideSimplemmaReady)) { + checkAndUpdateTracking(); } }); -// Gestion du tracking pour les nouveaux onglets -browser.tabs.onCreated.addListener((tab) => { - setTimeout(() => { - browser.storage.local.get(["isTrackingActive", "pyodideSimplemmaReady"]).then(({ isTrackingActive, pyodideSimplemmaReady }) => { - if (isTrackingActive && pyodideSimplemmaReady) { - console.log(`[Background] Nouveau onglet détecté (${tab.id}), activation du tracking.`); - browser.tabs.sendMessage(tab.id, { command: "activate-stats" }) - .catch((error) => console.warn(`[Background] Impossible d'activer le tracking sur ${tab.id} : ${error}`)); - } - }); - }, 1000); +// ───────────────────────────────────────────────────────────────────────────── +// Écoute l'inscription de stats.js +// ───────────────────────────────────────────────────────────────────────────── +browser.runtime.onMessage.addListener(async (message, sender) => { + if (message.command === "register-stats-script") { + console.log("[Background] stats.js s'est enregistré."); + const { isTrackingActive, pyodideSimplemmaReady } = await browser.storage.local.get(["isTrackingActive", "pyodideSimplemmaReady"]); + if (isTrackingActive && pyodideSimplemmaReady) { + browser.tabs.sendMessage(sender.tab.id, { command: "activate-stats" }) + .catch((error) => console.warn(`[Background] Impossible d'envoyer activate-stats à ${sender.tab.id} : ${error}`)); + } + } }); // ───────────────────────────────────────────────────────────────────────────── -// Écoute des modifications du stockage et mise à jour du tracking +// Statistiques : Créer une liaison entre stats.js et le Worker // ───────────────────────────────────────────────────────────────────────────── -browser.storage.onChanged.addListener(async (changes, area) => { - if (area === "local" && (changes.isTrackingActive || changes.pyodideSimplemmaReady)) { - checkAndUpdateTracking(); +// Gestion des connexions des scripts (ex: stats.js) +browser.runtime.onConnect.addListener((port) => { + console.log("[Background] Connexion établie :", port.name); + + if (port.name === "stats-worker-port") { + // Redirige les messages de stats.js vers pyodide-worker.js + port.onMessage.addListener((message) => { + console.log("[Background] Message reçu de stats.js :", message); + worker.postMessage(message); + }); + + //A DEPLACER ? + // Gérer les messages en retour du Worker vers stats.js + worker.onmessage = async (event) => { + console.log("[Background] Message du Worker :", event.data); + + //Mà j des fréquences + if (event.data.type === "update-frequencies") { + // const { lang, frequencies } = event.data; + const frequencies = event.data.frequencies; + //console.log("[Background] Mise à jour des fréquences :", frequencies); + //console.log(`[Background] Mise à jour des fréquences pour la langue : ${lang}`); + notifyAllTabs({ command: "update-frequencies", frequencies: event.data.frequencies }); + port.postMessage({ command: "update-frequencies", frequencies: event.data.frequencies }); + + // Enregistrer les fréquences dans le storage + browser.storage.local.set({ lemmaFrequencies: event.data.frequencies }) + + } + }; } }); + diff --git a/src/popup/popup.js b/src/popup/popup.js index ce1ec4d..5a480f5 100644 --- a/src/popup/popup.js +++ b/src/popup/popup.js @@ -54,8 +54,7 @@ document.getElementById("toggleStatsBtn").addEventListener("click", async () => document.getElementById("stats-options").classList.toggle("hidden", !newState); // Mettre à jour l'affichage des options utilisateur await updateOptionsUI() - // Envoi des messages au background script - //toggle-stats + // Envoi du message au background script : toggle-stats browser.runtime.sendMessage({ command: "toggle-stats", isActive: newState }); //pyodide-simplemma : récupère pyodide et simplemma si besoin if (newState) { @@ -63,6 +62,41 @@ document.getElementById("toggleStatsBtn").addEventListener("click", async () => browser.runtime.sendMessage({ command: "pyodide-simplemma" }); } }); +// === Cocherajout automatique === +document.getElementById("auto-add").addEventListener("change", async () => { + const isChecked = document.getElementById("auto-add").checked; + // Affichage dynamique + document.getElementById("auto-add-options").classList.toggle("hidden", !isChecked); + document.getElementById("save-options").classList.toggle("hidden", !isChecked); + if (!isChecked) { + await browser.storage.local.set({ autoAdd: false }); + } + +}); + +// === Sauvegarder les options === +document.getElementById("save-options").addEventListener("click", async () => { + const autoAdd = document.getElementById("auto-add").checked; + const threshold = parseInt(document.getElementById("threshold").value, 10); + const selectedLanguages = Array.from(document.querySelectorAll("#language-selection .lang-option.selected")) + .map(option => option.dataset.value); + //Message d'erreur si une langue n'est pas au moins sélectionée + const errorMessage = document.getElementById("error-message"); + // Vérifier si au moins une langue est sélectionnée + if (autoAdd && selectedLanguages.length === 0) { + errorMessage.classList.remove("hidden"); // Afficher le message d'erreur + return; + } + // Masquer l'erreur si elle était affichée + errorMessage.classList.add("hidden"); + //Enregistrer les préférences + await browser.storage.local.set({ + autoAdd, + threshold, + trackedLanguages: selectedLanguages + }); + console.log("Options sauvegardées :", { autoAdd, threshold, trackedLanguages: selectedLanguages }); +}); // ───────────────────────────────────────────────────────────────────────────── // Fonctions d'affichage et gestion des boutons pour les statistiques @@ -76,9 +110,6 @@ async function updateStatsBtn() { toggleStatsBtn.textContent = isTrackingActive ? "Désactiver les statistiques" : "Activer les statistiques"; } - -// === Affichage des langues de l'utilisateur === -// === Affichage et gestion de la sélection des langues de l'utilisateur === // === Affichage et gestion de la sélection des langues de l'utilisateur === async function updateLanguageSelection() { const languageSelection = document.getElementById("language-selection"); @@ -120,9 +151,6 @@ async function updateLanguageSelection() { console.log("Sélection des langues :", userLanguages); } - - - // === Options des statistiques === async function updateOptionsUI() { const { accessToken } = await browser.storage.local.get("accessToken"); @@ -157,42 +185,6 @@ async function updateOptionsUI() { } } - -// === Gestion de l'affichage dynamique des options Auto-Add === -document.getElementById("auto-add").addEventListener("change", async () => { - const isChecked = document.getElementById("auto-add").checked; - // Affichage dynamique - document.getElementById("auto-add-options").classList.toggle("hidden", !isChecked); - document.getElementById("save-options").classList.toggle("hidden", !isChecked); - if (!isChecked) { - await browser.storage.local.set({ autoAdd: false }); - } -}); - -// === Sauvegarde des options === -document.getElementById("save-options").addEventListener("click", async () => { - const autoAdd = document.getElementById("auto-add").checked; - const threshold = parseInt(document.getElementById("threshold").value, 10); - const selectedLanguages = Array.from(document.querySelectorAll("#language-selection .lang-option.selected")) - .map(option => option.dataset.value); - //Message d'erreur si une langue n'est pas au moins sélectionée - const errorMessage = document.getElementById("error-message"); - // Vérifier si au moins une langue est sélectionnée - if (autoAdd && selectedLanguages.length === 0) { - errorMessage.classList.remove("hidden"); // Afficher le message d'erreur - return; - } - // Masquer l'erreur si elle était affichée - errorMessage.classList.add("hidden"); - //Enregistrer les préférences - await browser.storage.local.set({ - autoAdd, - threshold, - trackedLanguages: selectedLanguages - }); - console.log("Options sauvegardées :", { autoAdd, threshold, trackedLanguages: selectedLanguages }); -}); - // ───────────────────────────────────────────────────────────────────────────── // Mise à jour des boutons dans le menu d'extension // ───────────────────────────────────────────────────────────────────────────── diff --git a/src/utils/stats.js b/src/utils/stats.js index 404ae6e..d650b82 100644 --- a/src/utils/stats.js +++ b/src/utils/stats.js @@ -3,138 +3,304 @@ return; } window.hasRun = true; + let workerPort = null; // Port unique vers le WebWorker + let statsWidget = null; - let isTrackingActive = false; - let scrollListenerAttached = false; - const seenContent = new Set(); - - /** - * Fonction pour extraire le texte visible et l'envoyer au background script. - */ - function trackVisibleContent() { - let selectors = "p, h1, h2, h3, h4, h5, h6, ul, ol, li, table, tr, td, th, blockquote"; - - // Sélecteurs spécifiques à exclure sur Wikipedia et d'autres sites - let excludeSelectors = [ - "#p-lang", // Liste des langues sur Wikipedia - "#footer", // Pied de page - ".navbox", // Boîtes de navigation - ".infobox", // Infobox - ".sidebar", // Sidebars en général - "script", // Scripts JS - "style" // Styles CSS - ]; - - document.querySelectorAll(selectors).forEach((element) => { - if (excludeSelectors.some(sel => element.closest(sel))) { - return; // On ignore cet élément - } - - const rect = element.getBoundingClientRect(); - if (rect.top >= 0 && rect.bottom <= window.innerHeight) { - let text = element.innerText - text = cleanText(text); // nettoyage du texte avec cleanText - if (text.length > 10 && !seenContent.has(text)) { - seenContent.add(text); - console.log("[Stats] Envoi du texte filtré au background.js :", text); - browser.runtime.sendMessage({ command: "process-text", text: text }); - } - } - }); + function createStatsWidget() { + console.log("[Stats] Création du widget..."); + if (document.getElementById("stats-widget")) return; // Empêche la duplication + + statsWidget = document.createElement("div"); + statsWidget.id = "stats-widget"; + statsWidget.style.position = "fixed"; + statsWidget.style.bottom = "10px"; + statsWidget.style.right = "10px"; + statsWidget.style.background = "rgba(0, 0, 0, 0.8)"; + statsWidget.style.color = "white"; + statsWidget.style.padding = "10px"; + statsWidget.style.borderRadius = "8px"; + statsWidget.style.fontSize = "14px"; + statsWidget.style.zIndex = "1000000"; + statsWidget.style.maxWidth = "300px"; + statsWidget.style.boxShadow = "2px 2px 10px rgba(0, 0, 0, 0.5)"; + statsWidget.style.overflow = "hidden"; + statsWidget.style.whiteSpace = "nowrap"; + statsWidget.style.textOverflow = "ellipsis"; + statsWidget.innerHTML = "<strong>📊 Statistiques</strong><br>Analyse en cours..."; + document.body.appendChild(statsWidget); } - function cleanText(text) { - text = text.replace(/[\u2022\u00b7•·■◆▪▸▹▶►▻⇨]/g, " "); // Supprime puces et flèches - text = text.replace(/[\t\n\r]+/g, " "); // Supprime les sauts de ligne inutiles - text = text.replace(/\s{2,}/g, " "); // Remplace plusieurs espaces par un seul - text = text.replace(/(\||\t)+/g, " "); // Remplace les séparateurs de tableau par un espace - text = text.replace(/(\s*-+\s*)+/g, " "); // Supprime les lignes de séparation des tableaux - text = text.trim(); - return text; - } - - // STATISTIQUES ------------------------------------------------------------------------------------------ - async function initializeTrackingState() { - const { isTrackingActive: storedState } = await browser.storage.local.get("isTrackingActive"); - isTrackingActive = storedState ?? false; - console.log("[Stats] État initial récupéré :", isTrackingActive); - if (isTrackingActive) { - startTracking(); - } else { - stopTracking(); + function updateStatsWidget(stats) { + if (!statsWidget) { + createStatsWidget(); + } + console.log("[Stats] Mise à jour du widget avec :", stats); + + let content = "<strong>📊 Statistiques</strong><br>"; + content += `Mots totaux : ${stats.totalWords}<br>`; + content += `Mots uniques : ${stats.uniqueWords}<br>`; + content += "<hr>"; + + for (const lang of Object.keys(stats.languages)) { + let langData = stats.languages[lang]; + content += `<strong>${lang.toUpperCase()}</strong> - ${langData.wordCount} mots<br>`; + content += `Mot le plus lu : <strong>${langData.mostFrequentWord || "Aucun"}</strong> (${langData.mostFrequentCount} fois)<br><hr>`; } + + statsWidget.innerHTML = content; } - function startTracking() { - console.log("[Stats] Suivi des statistiques activé."); - addViewportBorder(); - attachScrollListener(); +// ───────────────────────────────────────────────────────────────────────────── +// Connexion/Transmission des données avec le WebWorker +// ───────────────────────────────────────────────────────────────────────────── +//Connexion au port +function connectToWorker() { + if (!workerPort) { + // console.log("[Stats] Connexion au WebWorker..."); + workerPort = browser.runtime.connect({ name: "stats-worker-port" }); + workerPort.onMessage.addListener((message) => { + console.log("[Stats] Message reçu du Worker :", message); + if (message.command === "update-frequencies") { + console.log("[Stats] Fréquences mises à jour :", message.frequencies); + checkThreshold(message.frequencies); + + let totalWords = 0; + let uniqueWordsSet = new Set(); + let languagesData = {}; + + for (const lang in message.frequencies) { + let wordCount = 0; + let mostFrequentWord = ""; + let mostFrequentCount = 0; + + for (const [word, count] of Object.entries(message.frequencies[lang])) { + totalWords += count; + wordCount += count; + uniqueWordsSet.add(word); + + if (count > mostFrequentCount) { + mostFrequentWord = word; + mostFrequentCount = count; + } + } + + languagesData[lang] = { + wordCount, + mostFrequentWord, + mostFrequentCount + }; + } + + updateStatsWidget({ + totalWords, + uniqueWords: uniqueWordsSet.size, + languages: languagesData + }); + } + }); + workerPort.onDisconnect.addListener(() => { + // console.log("[Stats] Déconnexion du WebWorker."); + workerPort = null; + }); + } +} +//Fonction pour envoyer le texte directement au Worker +function sendTextToWorker(text) { + if (!workerPort) { + connectToWorker(); } + if (workerPort) { + console.log("[Stats] Envoi du texte au Worker :", text); + workerPort.postMessage({ command: "process-text", text: text }); + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// Vérification du seuil +// ───────────────────────────────────────────────────────────────────────────── +// Stockage des mots déjà signalés (évite d'afficher plusieurs fois la même alerte) +let notifiedWords = {}; - function stopTracking() { - console.log("[Stats] Suivi des statistiques désactivé."); - removeViewportBorder(); - detachScrollListener(); +// Fonction de vérification du seuil +async function checkThreshold(frequencies) { + // Récupérer les préférences de l'utilisateur + const { threshold, trackedLanguages } = await browser.storage.local.get(["threshold", "trackedLanguages"]) || { threshold: 10, trackedLanguages: [] }; + + if (!threshold || !trackedLanguages.length) { + console.log("[Stats] Aucun seuil défini ou aucune langue suivie."); + return; } - function attachScrollListener() { - if (!scrollListenerAttached) { - window.addEventListener("scroll", trackVisibleContent); - scrollListenerAttached = true; - console.log("[Stats] Écouteur de défilement attaché."); + console.log(`[Stats] Seuil défini à : ${threshold}`); + console.log(`[Stats] Langues suivies : ${trackedLanguages.join(", ")}`); + + let wordsAboveThreshold = {}; + + // Vérifier chaque langue suivie + for (const lang of trackedLanguages) { + if (frequencies[lang]) { + console.log(`[Stats] Vérification des fréquences pour la langue ${lang}...`); + + // Vérifier quels mots dépassent le seuil et qui n'ont pas encore été notifiés + const exceededWords = Object.entries(frequencies[lang]) + .filter(([word, count]) => count >= threshold && !(notifiedWords[lang] && notifiedWords[lang].includes(word))) + .map(([word]) => word); + + if (exceededWords.length > 0) { + // Ajouter ces mots aux notifications pour éviter les doublons + if (!notifiedWords[lang]) { + notifiedWords[lang] = []; + } + notifiedWords[lang].push(...exceededWords); + + wordsAboveThreshold[lang] = exceededWords; + } } } - function detachScrollListener() { - if (scrollListenerAttached) { - window.removeEventListener("scroll", trackVisibleContent); - scrollListenerAttached = false; - console.log("[Stats] Écouteur de défilement détaché."); + // Si des mots dépassent le seuil pour la première fois, afficher une alerte + if (Object.keys(wordsAboveThreshold).length > 0) { + console.log("[Stats] âš ï¸ Nouveaux mots dépassant le seuil :", wordsAboveThreshold); + + let alertMessage = "📌 Nouveaux mots dépassant le seuil :\n"; + for (const [lang, words] of Object.entries(wordsAboveThreshold)) { + alertMessage += `\n🔹 ${lang.toUpperCase()} : ${words.join(", ")}`; } + alert(alertMessage); + } else { + console.log("[Stats] ✅ Aucun nouveau mot n'a dépassé le seuil."); } +} - // BORDURE ------------------------------------------------------------------------------------------ - function addViewportBorder() { - const existingBorder = document.getElementById("viewport-border"); - if (existingBorder) { - existingBorder.remove(); - } - const border = document.createElement("div"); - border.id = "viewport-border"; - border.style.position = "fixed"; - border.style.top = "0"; - border.style.left = "0"; - border.style.width = "100vw"; - border.style.height = "100vh"; - border.style.boxSizing = "border-box"; - border.style.border = "8px solid red"; - border.style.pointerEvents = "none"; - border.style.zIndex = "999999"; - document.body.appendChild(border); - } - - function removeViewportBorder() { - const border = document.getElementById("viewport-border"); - if (border) { - border.remove(); - } + + + +// ───────────────────────────────────────────────────────────────────────────── +// Gestion des messages envoyés depuis le background +// ───────────────────────────────────────────────────────────────────────────── +browser.runtime.onMessage.addListener((message) => { + console.log("[Stats] Message reçu :", message); + if (message.command === "activate-stats") { + startTracking(); } + if (message.command === "deactivate-stats") { + stopTracking(); + } +}); + +//Enregistrement manuel auprès du background pour écouter activate-stats +browser.runtime.sendMessage({ command: "register-stats-script" }); + +// ───────────────────────────────────────────────────────────────────────────── +// Extraction du texte sur les pages +// ───────────────────────────────────────────────────────────────────────────── +let scrollListenerAttached = false; +const seenContent = new Set(); + +/** + * Fonction pour extraire le texte visible + */ +function trackVisibleContent() { + let selectors = "p, h1, h2, h3, h4, h5, h6, ul, ol, li, table, tr, td, th, blockquote"; + + // Sélecteurs spécifiques à exclure sur Wikipedia et d'autres sites + let excludeSelectors = [ + "#p-lang", // Liste des langues sur Wikipedia + "#footer", // Pied de page + ".navbox", // Boîtes de navigation + ".infobox", // Infobox + ".sidebar", // Sidebars en général + "script", // Scripts JS + "style" // Styles CSS + ]; - // GESTION DES MESSAGES envoyé depuis le background ----------------------------------------------------------------------------------------- - browser.runtime.onMessage.addListener((message) => { - if (message.command === "activate-stats") { - console.log("[Stats] Activation demandée par le background script."); - startTracking(); + document.querySelectorAll(selectors).forEach((element) => { + if (excludeSelectors.some(sel => element.closest(sel))) { + return; // On ignore cet élément } - if (message.command === "deactivate-stats") { - console.log("[Stats] Désactivation du tracking."); - stopTracking(); + + const rect = element.getBoundingClientRect(); + if (rect.top >= 0 && rect.bottom <= window.innerHeight) { + let text = element.innerText + text = cleanText(text); // nettoyage du texte avec cleanText + if (!seenContent.has(text)) { + seenContent.add(text); + //Envoyer le texte au WebWorker + console.log("[Stats] Envoi du texte filtré au Worker:", text); + sendTextToWorker(text); + } } }); +} + +/** + * Fonction pour nettoyer le texte + */ +function cleanText(text) { + text = text.replace(/[\u2022\u00b7•·■◆▪▸▹▶►▻⇨]/g, " "); // Supprime puces et flèches + text = text.replace(/[\t\n\r]+/g, " "); // Supprime les sauts de ligne inutiles + text = text.replace(/\s{2,}/g, " "); // Remplace plusieurs espaces par un seul + text = text.replace(/(\||\t)+/g, " "); // Remplace les séparateurs de tableau par un espace + text = text.replace(/(\s*-+\s*)+/g, " "); // Supprime les lignes de séparation des tableaux + text = text.trim(); + return text; +} + +// ───────────────────────────────────────────────────────────────────────────── +// Gestion de l'activation/désactivation des statistiques +// ───────────────────────────────────────────────────────────────────────────── +function startTracking() { + console.log("[Stats] Suivi des statistiques activé."); + addViewportBorder(); + attachScrollListener(); + } + +function stopTracking() { + console.log("[Stats] Suivi des statistiques désactivé."); + removeViewportBorder(); + detachScrollListener(); +} - // Initialisation - // initializeTrackingState(); +function attachScrollListener() { + if (!scrollListenerAttached) { + window.addEventListener("scroll", trackVisibleContent); + scrollListenerAttached = true; + console.log("[Stats] Écouteur de défilement attaché."); + } +} - // Envoyer les données au WebWorker pour qu'il traite le texte scrappé +function detachScrollListener() { + if (scrollListenerAttached) { + window.removeEventListener("scroll", trackVisibleContent); + scrollListenerAttached = false; + console.log("[Stats] Écouteur de défilement détaché."); + } +} +// Indice visuel des statistiques actives +function addViewportBorder() { + const existingBorder = document.getElementById("viewport-border"); + if (existingBorder) { + existingBorder.remove(); + } + const border = document.createElement("div"); + border.id = "viewport-border"; + border.style.position = "fixed"; + border.style.top = "0"; + border.style.left = "0"; + border.style.width = "100vw"; + border.style.height = "100vh"; + border.style.boxSizing = "border-box"; + border.style.border = "8px solid red"; + border.style.pointerEvents = "none"; + border.style.zIndex = "999999"; + document.body.appendChild(border); +} + +function removeViewportBorder() { + const border = document.getElementById("viewport-border"); + if (border) { + border.remove(); + } +} })(); diff --git a/src/workers/pyodide_worker.js b/src/workers/pyodide_worker.js index aac6fc7..2c7abc1 100644 --- a/src/workers/pyodide_worker.js +++ b/src/workers/pyodide_worker.js @@ -4,6 +4,7 @@ const LATEST_BASE_URL = "https://cdn.jsdelivr.net/pyodide/v0.27.2/full/"; let pyodide = null; let pyodideLoaded = false; // Variable indiquant si Pyodide est en mémoire let simplemmaLoaded = false; // Variable indiquant si simplemma est déjà installé +let storedFrequencies = {}; // Stockage des fréquences accumulées // Écouteur des messages reçus du background script self.onmessage = async (event) => { @@ -78,8 +79,73 @@ self.onmessage = async (event) => { } // Traitement du texte (envoyé par stats.js) - if (data.command === "toggle-stats") { - console.log(`[WebWorker] Statistiques ${data.isActive ? "activées" : "désactivées"}`); - return; + if (data.command === "process-text") { + console.log("[Worker] Texte reçu pour analyse :", data.text); + try { + const result = await pyodide.runPythonAsync(` + import json + import re + import simplemma + from simplemma import langdetect + + def detect_language(text): + lang_scores = simplemma.langdetect(text, lang=("fr", "en", "es", "de", "it", "pt")) + return lang_scores[0][0] if lang_scores else "unk" + + def preprocess(text): + contractions = ["l'", "d'", "j'", "c'", "s'", "t'", "qu'", "m'", "n'"] + for contraction in contractions: + text = re.sub(fr"{contraction}", f"{contraction} ", text) + return text + + def tokenize(text): + return re.findall(r"\\b[a-zA-ZÀ-ÿ'-]+\\b", text.lower()) + + text = """${data.text.replace(/\"/g, '\\"')}""" + detected_lang = detect_language(text) + + if detected_lang == "unk": + detected_lang = "other" + + processed_text = preprocess(text) + tokens = tokenize(processed_text) + lemmatized_tokens = [simplemma.lemmatize(token, lang=detected_lang) for token in tokens] + + freq = {} + for token in lemmatized_tokens: + freq[token] = freq.get(token, 0) + 1 + + json.dumps({"lang": detected_lang, "frequencies": freq}, ensure_ascii=False) + `); + + const parsedResult = JSON.parse(result); + const detectedLang = parsedResult.lang; + //console.log("[Worker] Résultat du traitement :", parsedResult); + + // Vérifier si la langue existe dans storedFrequencies, sinon l'initialiser + if (!storedFrequencies[detectedLang]) { + storedFrequencies[detectedLang] = {}; + } + + // Mise à jour des fréquences stockées + for (const [word, count] of Object.entries(parsedResult.frequencies)) { + storedFrequencies[detectedLang][word] = (storedFrequencies[detectedLang][word] || 0) + count; + } + + // self.postMessage({ + // type: "update-frequencies", + // lang: detectedLang, + // frequencies: storedFrequencies[detectedLang] + // }); + + // Envoyer le message update-frequencies => mà j des fréquences et sauvegarde dans le local storage + self.postMessage({ + type: "update-frequencies", + frequencies: storedFrequencies + }); + + } catch (error) { + console.error("[Worker] Erreur dans l'analyse du texte :", error); + } } }; -- GitLab