Global Text Remover (FR/EN)

Removes specified words/phrases from all sites (case-insensitive, persistent globally, bilingual UI).

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Global Text Remover (FR/EN)
// @name:fr      Masqueur global de texte (FR/EN)
// @namespace    https://example.com
// @version      3.5
// @description  Removes specified words/phrases from all sites (case-insensitive, persistent globally, bilingual UI).
// @description:fr  Script ChatGPT qui enlève des mots/phrases de tous les sites (ne respecte pas la casse, persiste).
// @match        *://*/*
// @grant        GM_getValue
// @license MIT
// @grant        GM_setValue
// ==/UserScript==

(function() {
  "use strict";

  /** 🌐 Language auto-detect **/
  const lang = navigator.language.startsWith("fr") ? "fr" : "en";

  const T = {
    addPlaceholder: lang === "fr" ? "Mot ou phrase à supprimer" : "Word or phrase to remove",
    addButton: lang === "fr" ? "Ajouter" : "Add",
    panelTitle: lang === "fr" ? "Liste de mots masqués" : "Hidden words list",
    deleteTip: lang === "fr" ? "Supprimer ce mot" : "Delete this word",
    hideButton: lang === "fr" ? "Cacher le bouton" : "Hide button"
  };

  const STORAGE_KEY = "masqueur_global_list";
  const HIDE_KEY = "masqueur_toggle_hidden";

  let filtres = GM_getValue(STORAGE_KEY, []);
  let toggleHidden = GM_getValue(HIDE_KEY, false);

  /** Utils **/
  const escapeRegex = s => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");

  const isIgnored = el => {
    if (!el) return true;
    if (el.closest("#masqueur-panel") || el.closest("#masqueur-toggle")) return true;
    const tag = el.tagName ? el.tagName.toLowerCase() : "";
    return ["input","textarea","select","script","style","noscript"].includes(tag);
  };

  /** Masquage **/
  function maskNodeText(node) {
    if (node.nodeType !== Node.TEXT_NODE) return;
    if (isIgnored(node.parentElement)) return;

    const txt = node.nodeValue;
    if (!txt || !txt.trim()) return;

    let newTxt = txt;

    for (const f of filtres) {
      if (!f) continue;
      const regex = new RegExp(escapeRegex(f), "gi");
      newTxt = newTxt.replace(regex, "");
    }

    if (newTxt !== txt) node.nodeValue = newTxt;
  }

  function maskAll(root = document.body) {

    if (!filtres.length || !root) return;

    const walker = document.createTreeWalker(
      root,
      NodeFilter.SHOW_TEXT,
      null,
      false
    );

    let n;

    while ((n = walker.nextNode())) maskNodeText(n);
  }

  /** Observer **/
  const observer = new MutationObserver(mutations => {

    for (const m of mutations) {

      for (const n of m.addedNodes) {

        if (n.nodeType === Node.TEXT_NODE)
          maskNodeText(n);

        else if (n.nodeType === Node.ELEMENT_NODE && !isIgnored(n))
          maskAll(n);
      }
    }
  });

  /** Interface **/

  // --- Bouton rond ---
  const toggle = document.createElement("div");

  toggle.id = "masqueur-toggle";
  toggle.textContent = "🕵️";

  Object.assign(toggle.style,{
    position:"fixed",
    bottom:"14px",
    left:"14px",
    width:"32px",
    height:"32px",
    lineHeight:"28px",
    textAlign:"center",
    background:"rgba(0,0,0,0.7)",
    color:"white",
    borderRadius:"50%",
    cursor:"pointer",
    zIndex:"999999999",
    fontSize:"18px",
    boxShadow:"0 2px 6px rgba(0,0,0,0.4)",
    transition:"background 0.2s ease",
    userSelect:"none"
  });

  toggle.onmouseenter = () => toggle.style.background="rgba(0,0,0,0.9)";
  toggle.onmouseleave = () => toggle.style.background="rgba(0,0,0,0.7)";

  // --- Panneau ---
  const panel = document.createElement("div");

  panel.id="masqueur-panel";

  Object.assign(panel.style,{
    position:"fixed",
    bottom:"60px",
    left:"10px",
    background:"rgba(0,0,0,0.85)",
    color:"#fff",
    padding:"10px",
    borderRadius:"10px",
    fontFamily:"sans-serif",
    zIndex:"999999998",
    width:"260px",
    boxShadow:"0 2px 6px rgba(0,0,0,0.35)",
    display:"none"
  });

  const title=document.createElement("div");

  title.textContent=T.panelTitle;

  Object.assign(title.style,{
    fontWeight:"bold",
    fontSize:"13px",
    marginBottom:"6px",
    textAlign:"center"
  });

  const input=document.createElement("input");

  Object.assign(input,{
    type:"text",
    placeholder:T.addPlaceholder
  });

  Object.assign(input.style,{
    width:"100%",
    padding:"6px",
    borderRadius:"4px",
    border:"none",
    marginBottom:"6px",
    fontSize:"12px",
    boxSizing:"border-box"
  });

  const addBtn=document.createElement("button");

  addBtn.textContent=T.addButton;

  Object.assign(addBtn.style,{
    width:"100%",
    padding:"6px",
    background:"#2e8b57",
    color:"#fff",
    border:"none",
    borderRadius:"4px",
    cursor:"pointer",
    fontSize:"12px",
    marginBottom:"6px"
  });

  const hideBtn=document.createElement("button");

  hideBtn.textContent=T.hideButton;

  Object.assign(hideBtn.style,{
    width:"100%",
    padding:"6px",
    background:"#444",
    color:"#fff",
    border:"none",
    borderRadius:"4px",
    cursor:"pointer",
    fontSize:"12px",
    marginBottom:"6px"
  });

  const list=document.createElement("ul");

  Object.assign(list.style,{
    listStyle:"none",
    padding:0,
    margin:0,
    maxHeight:"150px",
    overflowY:"auto",
    fontSize:"12px"
  });

  /** Flèche de réapparition **/

  const revealZone=document.createElement("div");

  Object.assign(revealZone.style,{
    position:"fixed",
    bottom:"0",
    left:"0",
    width:"40px",
    height:"40px",
    zIndex:"999999997"
  });

  const revealArrow=document.createElement("div");

  revealArrow.textContent="➜";

  Object.assign(revealArrow.style,{
    position:"fixed",
    bottom:"14px",
    left:"10px",
    fontSize:"18px",
    color:"white",
    background:"rgba(0,0,0,0.7)",
    borderRadius:"6px",
    padding:"2px 6px",
    cursor:"pointer",
    display:"none",
    zIndex:"999999998"
  });

  revealZone.onmouseenter=()=>{
    if(toggleHidden) revealArrow.style.display="block";
  };

  revealZone.onmouseleave=()=>{
    revealArrow.style.display="none";
  };

  revealArrow.onclick=()=>{
    toggleHidden=false;
    GM_setValue(HIDE_KEY,false);
    toggle.style.display="block";
    revealArrow.style.display="none";
  };

  /** Liste **/

  function renderList(){

    list.innerHTML="";

    filtres.forEach((f,i)=>{

      const li=document.createElement("li");

      Object.assign(li.style,{
        display:"flex",
        justifyContent:"space-between",
        alignItems:"center",
        padding:"3px 0",
        borderBottom:"1px solid rgba(255,255,255,0.1)"
      });

      const span=document.createElement("span");
      span.textContent=f;

      const del=document.createElement("button");

      del.textContent="✖";
      del.title=T.deleteTip;

      Object.assign(del.style,{
        background:"none",
        border:"none",
        color:"#ff6666",
        cursor:"pointer",
        fontSize:"12px"
      });

      del.onclick=()=>{

        filtres.splice(i,1);

        GM_setValue(STORAGE_KEY,filtres);

        maskAll();

        renderList();
      };

      li.appendChild(span);
      li.appendChild(del);

      list.appendChild(li);
    });
  }

  addBtn.onclick=()=>{

    const val=input.value.trim();

    if(!val) return;

    if(!filtres.includes(val)){

      filtres.push(val);

      GM_setValue(STORAGE_KEY,filtres);

      maskAll();

      renderList();
    }

    input.value="";
  };

  hideBtn.onclick=()=>{

    toggleHidden=true;

    GM_setValue(HIDE_KEY,true);

    toggle.style.display="none";

    panel.style.display="none";
  };

  toggle.onclick=()=>{
    panel.style.display=panel.style.display==="none"?"block":"none";
  };

  panel.appendChild(title);
  panel.appendChild(input);
  panel.appendChild(addBtn);
  panel.appendChild(hideBtn);
  panel.appendChild(list);

  document.body.appendChild(panel);
  document.body.appendChild(toggle);
  document.body.appendChild(revealZone);
  document.body.appendChild(revealArrow);

  /** Initialisation **/

  function init(){

    renderList();

    maskAll();

    observer.observe(document.body,{
      childList:true,
      subtree:true
    });

    if(toggleHidden){
      toggle.style.display="none";
    }
  }

  if(document.readyState==="loading"){
    document.addEventListener("DOMContentLoaded",init);
  } else {
    init();
  }

})();