AntiAds

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

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

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