AntiAds

Небольшое пользовательское расширение для lolz.live, которое скрывает рекламные аватарки и заменяет их на нейтральные.

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

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

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

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.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==UserScript==
// @name         AntiAds
// @namespace    tm_lzt_ads_replacer
// @description  Небольшое пользовательское расширение для lolz.live, которое скрывает рекламные аватарки и заменяет их на нейтральные.
// @version      1.1
// @license      MIT
// @match        https://lolz.live/*
// @match        https://lzt.market/*
// @run-at       document-end
// ==/UserScript==

(() => {
  "use strict";

  /* ================= CONFIG ================= */

  const CFGK="tm_ads_cfg", DBK="tm_ads_db", SINCEK="tm_ads_since",
        ADSUK="tm_ads_u", ADSHK="tm_ads_h", WLUK="tm_ads_wl_u";

  const DEF={
    api:"https://lolz-antiads.vercel.app",
    key:"dontbeafreak",
    sync:180000,
    tick:700,
    max:250,
    on:1,
    rep:"https://nztcdn.com/avatar/l/1766152242/6060330.webp"
  };

  const RX=/nztcdn\.com\/avatar\/([sml])\/(\d+)\/(\d+)\.[a-z0-9]+/i;

  /* ================= HELPERS ================= */

  const J=(k,d)=>{try{return JSON.parse(localStorage.getItem(k))??d}catch{return d}};
  const W=(k,v)=>{try{localStorage.setItem(k,JSON.stringify(v))}catch{}};

  let cfg=Object.assign({},DEF,J(CFGK,{})); W(CFGK,cfg);
  let db=J(DBK,{});
  let adsU=new Set((J(ADSUK,[])||[]).map(String));
  let adsH=new Set((J(ADSHK,[])||[]).map(String));
  let wlU =new Set((J(WLUK,[]) ||[]).map(String));
  let seen=new WeakSet();

  /* ================= PARSING ================= */

  const parseAny = (s) => {
    s = String(s || "");

    const mUrl = s.match(/url\((['"]?)(.*?)\1\)/i);
    if (mUrl) s = mUrl[2] || s;

    const m = s.match(RX);
    return m ? {
      sz: m[1],
      unix: m[2],
      uid: m[3],
      k: `${m[2]}/${m[3]}`
    } : null;
  };

  const bgUrl = (el) => {
    if (el.style?.backgroundImage && el.style.backgroundImage !== "none")
      return el.style.backgroundImage;

    const raw = el.getAttribute("style") || "";
    const m = raw.match(/background-image\s*:\s*([^;]+)/i);
    if (m) return m[1];

    try {
      const c = getComputedStyle(el).backgroundImage;
      if (c && c !== "none") return c;
    } catch {}

    return "";
  };

  const repURL = (sz) => {
    const p = parseAny(cfg.rep);
    return p
      ? `https://nztcdn.com/avatar/${sz}/${p.unix}/${p.uid}.webp`
      : cfg.rep;
  };

  /* ================= REPLACE ================= */

  const setImg=(img,on,sz)=>{
    const r=repURL(sz);
    if(on){
      if(img.dataset.tmR==="1") return;
      img.dataset.tmR="1";
      img.dataset.tmO=img.src||"";
      img.src=r;
      img.removeAttribute("srcset");
    } else {
      if(img.dataset.tmR!=="1") return;
      img.dataset.tmR="0";
      if(img.dataset.tmO) img.src=img.dataset.tmO;
    }
  };

  const setBg=(el,on,sz,rawBg)=>{
    const r=repURL(sz);
    if(on){
      if(el.dataset.tmRbg==="1") return;
      el.dataset.tmRbg="1";
      el.dataset.tmOBG = rawBg;
      el.style.backgroundImage = `url("${r}")`;
    } else {
      if(el.dataset.tmRbg!=="1") return;
      el.dataset.tmRbg="0";
      if(el.dataset.tmOBG) el.style.backgroundImage = el.dataset.tmOBG;
    }
  };

  /* ================= LOGIC ================= */

  const isAds = (p) => {
    if (wlU.has(String(p.uid))) return false;
    const h = db[p.k];
    return adsU.has(String(p.uid)) || (h && adsH.has(String(h)));
  };

  const handleImg = (img) => {
    const p = parseAny(img.src || img.getAttribute("data-src"));
    if(!p) return false;
    cfg.on && isAds(p) ? setImg(img,1,p.sz) : setImg(img,0,p.sz);
    if(!db[p.k]) enqueue(p.k);
    return true;
  };

  const handleBg = (el) => {
    if(!el.classList?.contains("img")) return false;
    const rawBg = bgUrl(el);
    const p = parseAny(rawBg);
    if(!p) return false;
    cfg.on && isAds(p) ? setBg(el,1,p.sz,rawBg) : setBg(el,0,p.sz,rawBg);
    if(!db[p.k]) enqueue(p.k);
    return true;
  };

  /* ================= RESOLVE ================= */

  const Q=[], QS=new Set(); let busy=0, last=0;
  const enqueue=k=>{ if(document.hidden||db[k]||QS.has(k)) return; QS.add(k); Q.push(k); };

  const pump=async ()=>{
    if(document.hidden||busy||!Q.length) return;
    busy=1;
    const wait=Math.max(0,350-(Date.now()-last));
    if(wait) await new Promise(r=>setTimeout(r,wait));
    const k=Q.shift(); QS.delete(k);

    if(!db[k]){
      const [unix,uid]=k.split("/");
      try{
        const r=await fetch(`${cfg.api}/api/resolve`,{
          method:"POST",
          headers:{ "Content-Type":"application/json","X-API-Key":cfg.key },
          body:JSON.stringify({unix:+unix,uid:+uid})
        });
        if(r.ok){
          const j=await r.json();
          if(j?.key&&j?.hash){ db[j.key]=j.hash; W(DBK,db); }
        }
      }catch{}
    }
    last=Date.now(); busy=0;
  };

  /* ================= SYNC ================= */

  const sync=async ()=>{
    if(document.hidden) return;
    const since=+localStorage.getItem(SINCEK)||0;
    try{
      const r=await fetch(`${cfg.api}/api/known?since=${since}`,{
        headers:{"X-API-Key":cfg.key}
      });
      if(!r.ok) return;
      const j=await r.json();

      let ch=0;
      for(const k in j.results||{}) if(!db[k]){ db[k]=j.results[k]; ch=1; }

      const u0=adsU.size,h0=adsH.size,w0=wlU.size;
      j.ads_users?.forEach(u=>adsU.add(String(u)));
      j.ads_hashes?.forEach(h=>adsH.add(String(h)));
      j.whitelist_users?.forEach(u=>wlU.add(String(u)));

      if(ch) W(DBK,db);
      if(adsU.size!==u0) W(ADSUK,[...adsU]);
      if(adsH.size!==h0) W(ADSHK,[...adsH]);
      if(wlU.size!==w0)  W(WLUK,[...wlU]);

      if(j.last_updated_at) localStorage.setItem(SINCEK,String(j.last_updated_at));
      if(ch||adsU.size!==u0||adsH.size!==h0||wlU.size!==w0) seen=new WeakSet();
    }catch{}
  };

  /* ================= SCAN ================= */

  const tick=()=>{
    if(document.hidden) return;
    const nodes=document.querySelectorAll("img,.img,[style*='nztcdn.com/avatar/']");
    let n=0;
    for(let i=0;i<nodes.length && n<cfg.max;i++){
      const el=nodes[i];
      if(seen.has(el)) continue;
      const ok = el.tagName==="IMG" ? handleImg(el) : handleBg(el);
      if(ok){ seen.add(el); n++; }
    }
    pump();
  };

  /* ================= VIEW FULL AVATAR ================= */

  document.addEventListener("click",e=>{
    const el=e.target.closest(".img, img");
    if(!el) return;

    const p=parseAny(el.src||bgUrl(el));
    if(!p) return;

    e.preventDefault();
    e.stopPropagation();

    const ov=document.createElement("div");
    ov.style.cssText="position:fixed;inset:0;background:rgba(0,0,0,.85);z-index:999999;display:flex;align-items:center;justify-content:center";
    const im=document.createElement("img");
    im.src=`https://nztcdn.com/avatar/l/${p.unix}/${p.uid}.webp`;
    im.style.cssText="max-width:90vw;max-height:90vh;object-fit:contain;border-radius:14px;background:#000";
    ov.appendChild(im);
    ov.onclick=()=>ov.remove();
    document.body.appendChild(ov);
  },true);

  /* ================= TIMERS ================= */

  let tTick=0,tSync=0;
  const restart=()=>{ clearInterval(tTick); clearInterval(tSync); tTick=setInterval(tick,cfg.tick); tSync=setInterval(sync,cfg.sync); };

  document.addEventListener("visibilitychange",()=>{
    if(document.hidden){ clearInterval(tTick); clearInterval(tSync); }
    else { restart(); sync(); }
  });

  restart(); sync(); tick();
})();