ROBLOX: Global Title Filter

attempts Block bypassed variations using fuzzy matching

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         ROBLOX: Global Title Filter
// @description  attempts Block bypassed variations using fuzzy matching
// @match        *://*.roblox.com/*
// @run-at       document-start
// @grant        none
// @version      9.0.0
// @namespace https://greasyfork.org/users/1576301
// ==/UserScript==

(() => {
  "use strict";

  const BASE_WORDS = [
    "brainrot",
    "gen alpha",
    "garden",
    "farm",
    "garten",
    "grow a",
    "grow your poop",
    "steal",
    "steal a"
  ];

  function normalize(s) {
    return String(s || "")
      .toLowerCase()
      .replace(/@/g,"a")
      .replace(/\$/g,"s")
      .replace(/0/g,"o")
      .replace(/1/g,"i")
      .replace(/3/g,"e")
      .replace(/4/g,"a")
      .replace(/5/g,"s")
      .replace(/7/g,"t")
      .replace(/[^a-z0-9\s]/g,"")
      .replace(/\s+/g," ")
      .trim();
  }

  function levenshtein(a,b){
    if(a.length===0) return b.length;
    if(b.length===0) return a.length;
    const matrix=[];
    for(let i=0;i<=b.length;i++) matrix[i]=[i];
    for(let j=0;j<=a.length;j++) matrix[0][j]=j;
    for(let i=1;i<=b.length;i++){
      for(let j=1;j<=a.length;j++){
        if(b.charAt(i-1)===a.charAt(j-1)){
          matrix[i][j]=matrix[i-1][j-1];
        }else{
          matrix[i][j]=Math.min(
            matrix[i-1][j-1]+1,
            matrix[i][j-1]+1,
            matrix[i-1][j]+1
          );
        }
      }
    }
    return matrix[b.length][a.length];
  }

  function fuzzyMatch(title){
    const t = normalize(title);
    for(const base of BASE_WORDS){
      const b = normalize(base);
      if(t.includes(b)) return true;
      const words = t.split(" ");
      for(const w of words){
        if(levenshtein(w,b) <= 2) return true;
      }
    }
    return false;
  }

  const originalFetch = window.fetch;
  window.fetch = async function(...args){
    const res = await originalFetch.apply(this,args);
    try{
      const url = args[0]?.toString()||"";
      if(!/games|discover|charts/i.test(url)) return res;
      const clone = res.clone();
      const data = await clone.json();
      if(data?.data && Array.isArray(data.data)){
        data.data = data.data.filter(g=>!fuzzyMatch(g.name||g.title));
        return new Response(JSON.stringify(data),{
          status:res.status,
          statusText:res.statusText,
          headers:res.headers
        });
      }
    }catch{}
    return res;
  };

  const originalXHR = XMLHttpRequest.prototype.open;
  XMLHttpRequest.prototype.open = function(...args){
    this.addEventListener("readystatechange",function(){
      try{
        if(this.readyState===4 && this.responseText){
          if(!/games|discover|charts/i.test(args[1])) return;
          const json = JSON.parse(this.responseText);
          if(json?.data && Array.isArray(json.data)){
            json.data = json.data.filter(g=>!fuzzyMatch(g.name||g.title));
            Object.defineProperty(this,"responseText",{value:JSON.stringify(json)});
          }
        }
      }catch{}
    });
    return originalXHR.apply(this,args);
  };

  function hideDOM(){
    const links = Array.from(document.querySelectorAll('a[href*="/games/"]'));
    for(const a of links){
      const tile = a.closest('li,[role="row"],[role="listitem"],div');
      if(!tile) continue;
      if(tile.dataset.filtered==="1") continue;
      const text = tile.textContent||"";
      if(fuzzyMatch(text)){
        tile.dataset.filtered="1";
        tile.style.display="none";
      }
    }
  }

  const observer = new MutationObserver(()=>hideDOM());
  document.addEventListener("DOMContentLoaded",hideDOM);
  observer.observe(document.documentElement,{childList:true,subtree:true});
  setInterval(hideDOM,1000);

})();