diff --git a/src/background/background.js b/src/background/background.js index ff331a88dc99a673fa2a9ddde9f6e1ecaff8d17e..d9a7d650c7f9a32519ffca04feddc0ff01b67fd5 100644 --- a/src/background/background.js +++ b/src/background/background.js @@ -353,6 +353,7 @@ 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); // worker.postMessage({ command: "process-text", text: message.text }); @@ -403,6 +404,7 @@ worker.onmessage = async (event) => { //Activer le tracking des stats checkAndUpdateTracking(); } + // if (event.data.type === "update-frequencies") { // console.log("test") @@ -459,8 +461,37 @@ browser.storage.onChanged.addListener(async (changes, area) => { if (area === "local" && (changes.isTrackingActive || changes.pyodideSimplemmaReady)) { checkAndUpdateTracking(); } + + if (area === "local" && (changes.accessToken || changes.threshold || changes.trackedLanguages || changes.autoAdd)) { + console.log("[Background] Mise à jour des préférences détectée."); + + // Récupérer les nouvelles valeurs depuis le local storage + const { accessToken, trackedLanguages, threshold, autoAdd } = await browser.storage.local.get([ + "accessToken", "trackedLanguages", "threshold", "autoAdd" + ]); + + // Vérifier si on est bien connecté + const isAuthenticated = !!accessToken; + + // console.log("[Background] Envoi des préférences au Worker :", { + // isAuthenticated, + // trackedLanguages, + // threshold, + // autoAdd + // }); + + // Envoyer les nouvelles préférences au Worker + worker.postMessage({ + command: "update-preferences", + isAuthenticated, + trackedLanguages: trackedLanguages || [], + threshold: threshold || 10, + autoAdd: autoAdd || false + }); + } }); + // ───────────────────────────────────────────────────────────────────────────── // Écoute l'inscription de stats.js // ───────────────────────────────────────────────────────────────────────────── @@ -487,9 +518,9 @@ browser.runtime.onConnect.addListener((port) => { 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); @@ -505,9 +536,54 @@ browser.runtime.onConnect.addListener((port) => { // Enregistrer les fréquences dans le storage browser.storage.local.set({ lemmaFrequencies: event.data.frequencies }) - } - }; + + if (event.data.type === "threshold-exceeded") { + const wordsAboveThreshold = event.data.wordsAboveThreshold; + if (wordsAboveThreshold && Object.keys(wordsAboveThreshold).length > 0) { + console.log("[Background] Mots dépassant le seuil :", wordsAboveThreshold); + + // Envoyer ces mots à stats.js pour affichage ou autre action + notifyAllTabs({ + command: "threshold-exceeded", + wordsAboveThreshold: wordsAboveThreshold + }); + //Envoi à stats.js via le port + port.postMessage({ command: "threshold-exceeded", wordsAboveThreshold: wordsAboveThreshold }); + + } + } } +} }); +// ───────────────────────────────────────────────────────────────────────────── +// Stoplists (à modifier) +// ───────────────────────────────────────────────────────────────────────────── +// Charger la stoplist au démarrage +let stoplistFr = []; + +function loadStoplist() { + fetch(browser.runtime.getURL("stoplist_fr.txt")) + .then(response => response.text()) + .then(text => { + stoplistFr = text.split("\n").map(word => word.trim()); // Supprimer les espaces et \r éventuels + console.log("[Background] Stoplist chargée :", stoplistFr); + + // Envoyer la stoplist au worker dès son chargement + sendStoplistToWorker(); + }) + .catch(error => console.error("[Background] Erreur lors du chargement de la stoplist :", error)); +} + +// Envoyer la stoplist au worker +function sendStoplistToWorker() { + console.log("[Background] Envoi de la stoplist au Worker..."); + worker.postMessage({ command: "update-stoplist", stoplist: stoplistFr }); + +} + +// Charger la stoplist au démarrage de l'extension +browser.runtime.onStartup.addListener(loadStoplist); +browser.runtime.onInstalled.addListener(loadStoplist); + diff --git a/src/utils/definitions.js b/src/utils/definitions.js index d29516074843c3e9fbe364ad685dbf2e303aa7b5..ff1a246a0fccd788ea7e4bd945550872571febc9 100644 --- a/src/utils/definitions.js +++ b/src/utils/definitions.js @@ -126,46 +126,151 @@ async function fetchLexiconDefinitions(word) { * Récupère la définition d'un mot depuis le Wiktionnaire (fr). * Retourne un tableau d'objets : [{ source: 'Wiktionnaire', text: '...' }] */ -async function fetchWiktionaryDefinition(word) { +// async function fetchWiktionaryDefinition(word) { +// try { +// console.log(`🔠Requête Wiktionnaire pour "${word}"...`); +// if (!word || word.trim() === "") { +// throw new Error("âš ï¸ Mot vide, impossible d'envoyer la requête."); +// } +// const wiktionaryURL = `https://fr.wiktionary.org/w/api.php?action=query&format=json&origin=*&prop=extracts&explaintext=true&redirects=1&titles=${encodeURIComponent(word)}`; +// const response = await fetch(wiktionaryURL); +// if (!response.ok) { +// throw new Error(`⌠Erreur API Wiktionnaire: ${response.statusText}`); +// } +// const data = await response.json(); +// console.log("📖 Réponse API (Wiktionnaire) :", data); + +// const pages = data.query?.pages; +// const page = pages ? Object.values(pages)[0] : null; + +// const definitionText = page && page.extract +// ? page.extract.trim() +// : "âš ï¸ Aucune définition trouvée sur le Wiktionnaire."; + +// console.log("🌠Définition Wiktionnaire extraite :", definitionText); + +// return [ +// { +// source: "Wiktionnaire", +// text: definitionText +// } +// ]; +// } catch (error) { +// console.error("⌠Erreur Wiktionnaire :", error); +// return [ +// { +// source: "Wiktionnaire", +// text: "âš ï¸ Erreur lors de la récupération sur le Wiktionnaire." +// } +// ]; +// } +// } + +async function wikiApiResponse(word) { + const result = await browser.storage.local.get("accessToken"); + authToken = result.accessToken; + // Construire l'URL de l'API avec le mot sélectionné + const wiktionaryApiUrl = `https://babalex.lezinter.net/api/wiktionary/search?graphy=${encodeURIComponent(word)}&language=fr`; + try { - console.log(`🔠Requête Wiktionnaire pour "${word}"...`); - if (!word || word.trim() === "") { - throw new Error("âš ï¸ Mot vide, impossible d'envoyer la requête."); - } - const wiktionaryURL = `https://fr.wiktionary.org/w/api.php?action=query&format=json&origin=*&prop=extracts&explaintext=true&redirects=1&titles=${encodeURIComponent(word)}`; - const response = await fetch(wiktionaryURL); + const response = await fetch(wiktionaryApiUrl, { + method: 'GET', + headers: { + Authorization: `Bearer ${authToken}`, + 'Content-Type': 'application/json', + }, + }); + if (!response.ok) { - throw new Error(`⌠Erreur API Wiktionnaire: ${response.statusText}`); + throw new Error(`Erreur lors de la récupération de la définition depuis le Wiktionnaire : ${response.statusText}`); } - const data = await response.json(); - console.log("📖 Réponse API (Wiktionnaire) :", data); - const pages = data.query?.pages; - const page = pages ? Object.values(pages)[0] : null; - const definitionText = page && page.extract - ? page.extract.trim() - : "âš ï¸ Aucune définition trouvée sur le Wiktionnaire."; + const data = await response.json(); + console.log(`Résultats du Wiktionnaire pour le mot "${word}" :`, data); + return data; + } catch (error) { + console.error('Erreur lors de la récupération de la définition depuis le Wiktionnaire :', error); + throw error; + } +} + +function formatDefinitionData(apiResponse) { + let formattedData = { + word: apiResponse[0]?.id.split("-").slice(2).join("-") || "", + pronunciations: [], + definitions: [], + examples: [], + translations: {}, + }; + + apiResponse.forEach(entry => { + const wordData = entry[entry.id.split(".").slice(-1)[0]]; // Accéder aux données via la clé dynamique + + // Ajout des prononciations + if (wordData.pronunciations) { + formattedData.pronunciations.push(...wordData.pronunciations); + } - console.log("🌠Définition Wiktionnaire extraite :", definitionText); + // Ajout des définitions + if (wordData.senses) { + for (let senseKey in wordData.senses) { + let sense = wordData.senses[senseKey]; + if (sense.Definitions) { + formattedData.definitions.push(...sense.Definitions.map(d => d.definition)); + } + if (sense.Examples) { + formattedData.examples.push(...sense.Examples.map(e => e.example)); + } + } + } - return [ - { - source: "Wiktionnaire", - text: definitionText + // Ajout des traductions + if (entry.translations) { + entry.translations.forEach(translation => { + if (!formattedData.translations[translation.lang_name]) { + formattedData.translations[translation.lang_name] = []; + } + formattedData.translations[translation.lang_name].push(translation.sense); + }); } - ]; - } catch (error) { - console.error("⌠Erreur Wiktionnaire :", error); - return [ - { - source: "Wiktionnaire", - text: "âš ï¸ Erreur lors de la récupération sur le Wiktionnaire." + }); + + return formattedData; +} + +async function fetchWiktionaryDefinition(word) { + try { + console.log(` Recherche de la définition pour : ${word}`); + + // Récupération des données depuis l'API + const apiResponse = await wikiApiResponse(word); + console.log("Réponse brute de l'API :", apiResponse); + + if (!Array.isArray(apiResponse) || apiResponse.length === 0) { + console.warn(`Aucune définition trouvée pour "${word}"`); + return []; // Retourne un tableau vide si aucune définition } - ]; + + // Formatage des données + const formattedData = formatDefinitionData(apiResponse); + console.log("Données formatées :", formattedData); + + return [ + { + source: "Wiktionnaire", + text: formattedData.definitions.length > 0 ? formattedData.definitions.join(" | ") : "âš ï¸ Aucune définition disponible." + } + ]; + } catch (error) { + console.error("Erreur lors de la récupération de la définition :", error); + return [{ source: "Wiktionnaire", text: "Erreur lors de la récupération sur le Wiktionnaire." }]; } } + + + // ───────────────────────────────────────────────────────────────────────────── // â–Œ Affichage des définitions dans la barre latérale // ───────────────────────────────────────────────────────────────────────────── @@ -326,6 +431,10 @@ async function combineDefinitions(word) { const wiktionaryDefinitions = results[1].status === "fulfilled" ? results[1].value : []; + // Assurer que c'est bien un tableau + if (!Array.isArray(lexiconDefinitions)) lexiconDefinitions = []; + if (!Array.isArray(wiktionaryDefinitions)) wiktionaryDefinitions = []; + const allDefinitions = [...lexiconDefinitions, ...wiktionaryDefinitions]; console.log("📚 [combineDefinitions] Résultat fusionné :", allDefinitions); @@ -333,6 +442,7 @@ async function combineDefinitions(word) { return allDefinitions; } + /** * Récupère et affiche toutes les définitions (lexiques + Wiktionnaire). */ diff --git a/src/utils/stats.js b/src/utils/stats.js index d650b8225712664397c2e40bcc48b950a73f8c48..788f32cfd16adb801db9243cd66417e68adba474 100644 --- a/src/utils/stats.js +++ b/src/utils/stats.js @@ -4,303 +4,273 @@ } window.hasRun = true; let workerPort = null; // Port unique vers le WebWorker - let statsWidget = null; - - 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); + // ───────────────────────────────────────────────────────────────────────────── + // 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); + } + if (message.command === "threshold-exceeded") { + console.log("[Stats] Mots dépassant le seuil :", message.wordsAboveThreshold); + let alertMessage = "Nouveaux mots dépassant le seuil :\n"; + for (const [lang, words] of Object.entries(message.wordsAboveThreshold)) { + alertMessage += `\n🔹 ${lang.toUpperCase()} : ${words.join(", ")}`; + } + alert(alertMessage); + } + }); + workerPort.onDisconnect.addListener(() => { + // console.log("[Stats] Déconnexion du WebWorker."); + workerPort = null; + }); + } } - - function updateStatsWidget(stats) { - if (!statsWidget) { - createStatsWidget(); + //Fonction pour envoyer le texte directement au Worker + function sendTextToWorker(text) { + if (!workerPort) { + connectToWorker(); } - 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>`; + if (workerPort) { + console.log("[Stats] Envoi du texte au Worker :", text); + workerPort.postMessage({ command: "process-text", text: text }); } - - statsWidget.innerHTML = content; } -// ───────────────────────────────────────────────────────────────────────────── -// 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 = {}; + // ───────────────────────────────────────────────────────────────────────────── + // 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 READING_SPEED_MS_PER_WORD = 250; // Seuil de lecture + // Stockage des chronos et des éléments lus + const readingTimers = new Map(); + const readContent = new Set(); + let userIsActive = false; // Indique si l'utilisateur est actif sur la page + + // Détecte l'activité utilisateur pour éviter les fausses lectures + document.addEventListener("mousemove", () => userIsActive = true); + document.addEventListener("keydown", () => userIsActive = true); + setInterval(() => userIsActive = false, 60000); // Reset toutes les minutes + + // Arrête tous les chronomètres lorsque l'utilisateur change d'onglet + document.addEventListener("visibilitychange", () => { + if (document.hidden) { + // console.log("[Stats] Changement d'onglet détecté"); + resetAllTimers(); + } + }); - for (const lang in message.frequencies) { - let wordCount = 0; - let mostFrequentWord = ""; - let mostFrequentCount = 0; + /** + * 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, span, b"; - for (const [word, count] of Object.entries(message.frequencies[lang])) { - totalWords += count; - wordCount += count; - uniqueWordsSet.add(word); + // Sélecteurs spécifiques à exclure sur Wikipedia et d'autres sites + let excludeSelectors = [ + "#p-lang", "#footer", ".navbox", ".infobox", ".sidebar", "script", "style", ".interlanguage-link" + ]; - if (count > mostFrequentCount) { - mostFrequentWord = word; - mostFrequentCount = count; - } - } - languagesData[lang] = { - wordCount, - mostFrequentWord, - mostFrequentCount - }; + document.querySelectorAll(selectors).forEach((element) => { + if (excludeSelectors.some(sel => element.closest(sel))) { + return; // Ignore les éléments exclus + } + + const rect = element.getBoundingClientRect(); + const isVisible = rect.top >= 0 && rect.bottom <= window.innerHeight; + let text = cleanText(element.innerText); + let wordCount = text.split(/\s+/).length; + let minReadTime = wordCount * READING_SPEED_MS_PER_WORD; + + if (text.length < 3) return; // Ignore les petits éléments + + if (isVisible) { + if (!readContent.has(text)) { + startReadingTimer(element, text, minReadTime); } - - updateStatsWidget({ - totalWords, - uniqueWords: uniqueWordsSet.size, - languages: languagesData - }); + highlightElement(element, true); + } else { + stopReadingTimer(element, text); + highlightElement(element, false); } }); - 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 = {}; - -// 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; + + /** + * Ajoute une bordure rouge autour de l'élément + */ + function highlightElement(element, shouldHighlight) { + if (shouldHighlight) { + element.style.outline = "3px solid red"; + } else { + element.style.outline = ""; + removeReadingIndicator(element); + } } - - 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] = []; + + /** + * Démarre un chronomètre pour vérifier si l'élément est lu + */ + function startReadingTimer(element, text, minReadTime) { + if (!readingTimers.has(element)) { + let elapsedTime = 0; + let counter = document.createElement("div"); + counter.classList.add("reading-counter"); + counter.style.position = "absolute"; + counter.style.background = "black"; + counter.style.color = "white"; + counter.style.padding = "4px 6px"; + counter.style.borderRadius = "5px"; + counter.style.fontSize = "12px"; + counter.style.zIndex = "9999"; + + document.body.appendChild(counter); + + let interval = setInterval(() => { + elapsedTime += 1000; + + // Vérifie si l'utilisateur est actif et si le temps minimum est atteint + if (userIsActive && elapsedTime >= minReadTime) { + console.log(`[Stats] Élément lu : ${text}`); + readContent.add(text); + sendTextToWorker(text); + stopReadingTimer(element, text); } - notifiedWords[lang].push(...exceededWords); - - wordsAboveThreshold[lang] = exceededWords; - } + + // Mise à jour de la position du compteur + let rect = element.getBoundingClientRect(); + counter.style.top = `${rect.top + window.scrollY - 20}px`; + counter.style.left = `${rect.left + window.scrollX + rect.width + 10}px`; + counter.innerText = `â³ ${Math.floor(elapsedTime / 1000)}s`; + + }, 1000); + + readingTimers.set(element, { interval, counter, elapsedTime }); } } - - // 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(", ")}`; + + /** + * Arrête le chronomètre et supprime l'affichage du temps de lecture + */ + function stopReadingTimer(element, text) { + if (readingTimers.has(element)) { + let { interval, counter } = readingTimers.get(element); + clearInterval(interval); + counter.remove(); + readingTimers.delete(element); } - alert(alertMessage); - } else { - console.log("[Stats] ✅ Aucun nouveau mot n'a dépassé le seuil."); } -} - - - - -// ───────────────────────────────────────────────────────────────────────────── -// 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(); + /** + * Réinitialise tous les chronomètres lors du changement d'onglet + */ + function resetAllTimers() { + for (let [element, { interval, counter }] of readingTimers) { + clearInterval(interval); + counter.remove(); + } + readingTimers.clear(); } -}); -//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 - ]; - - document.querySelectorAll(selectors).forEach((element) => { - if (excludeSelectors.some(sel => element.closest(sel))) { - return; // On ignore cet élément + /** + * Supprime l'indicateur de lecture + */ + function removeReadingIndicator(element) { + if (readingTimers.has(element)) { + let { counter } = readingTimers.get(element); + counter.remove(); } + } + /** + * 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; + } - 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); - } + // ───────────────────────────────────────────────────────────────────────────── + // Gestion de l'activation/désactivation des statistiques + // ───────────────────────────────────────────────────────────────────────────── + function startTracking() { + console.log("[Stats] Suivi des statistiques activé."); + addViewportBorder(); + attachScrollListener(); } - }); -} - -/** - * 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(); } -function stopTracking() { - console.log("[Stats] Suivi des statistiques désactivé."); - removeViewportBorder(); - detachScrollListener(); -} - -function attachScrollListener() { - if (!scrollListenerAttached) { - window.addEventListener("scroll", trackVisibleContent); - scrollListenerAttached = true; - console.log("[Stats] Écouteur de défilement attaché."); + function attachScrollListener() { + if (!scrollListenerAttached) { + window.addEventListener("scroll", trackVisibleContent); + scrollListenerAttached = true; + console.log("[Stats] Écouteur de défilement attaché."); + } } -} -function detachScrollListener() { - if (scrollListenerAttached) { - window.removeEventListener("scroll", trackVisibleContent); - scrollListenerAttached = false; - console.log("[Stats] Écouteur de défilement détaché."); + 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(); + // 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); } - 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(); + 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 2c7abc11b7c5adc064a2e4402c093b1f3249e267..a85f4088bb3a0ef4b40aac32b37b1d7490ce778d 100644 --- a/src/workers/pyodide_worker.js +++ b/src/workers/pyodide_worker.js @@ -6,6 +6,15 @@ 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 + +let autoAddEnabled = false; // Ajout automatique désactivé par défaut +let isAuthenticated = false; // Non connecté par défaut +let userThreshold = 10; // Seuil par défaut +let trackedLanguages = []; // Aucune langue suivie par défaut +// Stockage des mots déjà signalés (évite d'afficher plusieurs fois la même alerte) +let notifiedWords = {}; // +let stoplistFr = new Set(); // Stockage optimisé de la stoplist + // Écouteur des messages reçus du background script self.onmessage = async (event) => { const data = event.data; @@ -68,7 +77,6 @@ self.onmessage = async (event) => { simplemmaLoaded = true; console.log("[Worker] Simplemma installé avec succès !"); } - // Envoyer confirmation au background script self.postMessage({ type: "pyodide-simplemma", status: "success", message: "Pyodide et Simplemma chargés" }); @@ -81,7 +89,10 @@ self.onmessage = async (event) => { // Traitement du texte (envoyé par stats.js) if (data.command === "process-text") { console.log("[Worker] Texte reçu pour analyse :", data.text); + try { + const stoplistArray = Array.from(stoplistFr); + console.log(stoplistArray) const result = await pyodide.runPythonAsync(` import json import re @@ -100,21 +111,35 @@ self.onmessage = async (event) => { def tokenize(text): return re.findall(r"\\b[a-zA-ZÀ-ÿ'-]+\\b", text.lower()) - + + # Chargement de la stoplist pour le français + stoplist = set(json.loads('${JSON.stringify(stoplistArray)}')) + #print("Stoplist utilisée pour le filtrage :", stoplist) + + 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) + + tokens = tokenize(text) + + if detected_lang == "fr": + filtered_tokens = [token.lower().strip() for token in tokens if token.lower().strip() not in stoplist] + #print(f"Tokens avant filtrage ({len(tokens)}) :", tokens) # Debug + #print(f"Tokens après filtrage ({len(filtered_tokens)}) :", filtered_tokens) # Debug + tokens = filtered_tokens # Appliquer le filtrage stoplist + + 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 - + if token not in stoplist: # Exclure les mots de la stoplist avant comptage + freq[token] = freq.get(token, 0) + 1 + + json.dumps({"lang": detected_lang, "frequencies": freq}, ensure_ascii=False) `); @@ -132,20 +157,83 @@ self.onmessage = async (event) => { 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 }); + + //Vérifier le seuil uniquement si l'utilisateur a coché l'option + if (autoAddEnabled) { + checkThreshold(detectedLang) + } } catch (error) { console.error("[Worker] Erreur dans l'analyse du texte :", error); } } + + if (data.command === "update-preferences") { + userThreshold = data.threshold; + trackedLanguages = data.trackedLanguages; + autoAddEnabled = data.autoAdd; + isAuthenticated = data.isAuthenticated; + + console.log("[Worker] Mise à jour des préférences :", { + userThreshold, trackedLanguages, autoAddEnabled, isAuthenticated + }); + } + + if (data.command === "update-stoplist") { + stoplistFr = new Set(data.stoplist.map(word => word.toLowerCase().trim())); + console.log("[Worker] Stoplist FR mise à jour :", stoplistFr); + } + +}; +// ───────────────────────────────────────────────────────────────────────────── +// Vérification du seuil et notification (sans duplication) +// ───────────────────────────────────────────────────────────────────────────── +function checkThreshold(lang) { + if (!autoAddEnabled || !isAuthenticated) { + console.log("[Worker] Auto-Add désactivé ou utilisateur non connecté. Aucune vérification de seuil."); + return; + } + + if (!trackedLanguages.includes(lang)) { + console.log(`[Worker] La langue ${lang} n'est pas suivie.`); + return; + } + + console.log(`[Worker] Vérification des fréquences pour la langue ${lang}...`); + + let wordsAboveThreshold = {}; + + // Vérifier chaque mot de la langue sélectionnée + if (storedFrequencies[lang]) { + const exceededWords = Object.entries(storedFrequencies[lang]) + .filter(([word, count]) => count >= userThreshold && !(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; + } + } + + // Si des mots dépassent le seuil, notifier stats.js + if (Object.keys(wordsAboveThreshold).length > 0) { + console.log("[Worker] Nouveaux mots dépassant le seuil :", wordsAboveThreshold); + + self.postMessage({ + type: "threshold-exceeded", + wordsAboveThreshold: wordsAboveThreshold + }); + } else { + console.log("[Worker] Aucun nouveau mot n'a dépassé le seuil."); + } }; diff --git a/stoplist_fr.txt b/stoplist_fr.txt new file mode 100644 index 0000000000000000000000000000000000000000..36fa27bf0ff4d49b4beb3c965986e4770af75be8 --- /dev/null +++ b/stoplist_fr.txt @@ -0,0 +1,116 @@ +alors +au +aucuns +aussi +autre +avant +avec +avoir +bon +car +ce +cela +ces +ceux +chaque +ci +comme +comment +dans +des +du +dedans +dehors +depuis +devrait +doit +donc +dos +début +elle +elles +en +encore +essai +est +et +eu +fait +faites +fois +font +hors +ici +il +ils +je +juste +la +le +les +leur +là +ma +maintenant +mais +mes +mien +moins +mon +mot +même +ni +nommés +notre +nous +ou +où +par +parce +pas +peut +peu +plupart +pour +pourquoi +quand +que +quel +quelle +quelles +quels +qui +sa +sans +ses +seulement +si +sien +son +sont +sous +soyez +sujet +sur +ta +tandis +tellement +tels +tes +ton +tous +tout +trop +très +tu +voient +vont +votre +vous +vu +ça +étaient +état +étions +été +être \ No newline at end of file