Global Text Remover (FR/EN)

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

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

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

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

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

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==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();
  }

})();