Global Text Remover (FR/EN)

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

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==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();
  }

})();