PlayerFinder[Evades.io]

// ==UserScript==
// @name        PlayerFinder[Evades.io]
// @namespace   Violentmonkey Scripts
// @match       *://evades.io/*
// @grant       GM_xmlhttpRequest
// @version     1.0
// @author      Drik
// @description:en Find players by nickname on all Evades.io servers. "Chip" enables auto-tracking in real time
// @description:ru Поиск игроков по нику на всех серверах Evades.io. "Чип" включает авто-отслеживание в реальном времени
// @connect      evades.io
// @license MIT
// ==/UserScript==



(function () {
  const root = document.createElement('div');
  root.id = 'pf-root';
  root.innerHTML = `
    <div id="pf-card">
      <div id="pf-h">
        <svg viewBox="0 0 24 24" id="pf-icon" aria-hidden="true"><path fill="currentColor" d="M12 2a7 7 0 0 1 7 7c0 5-7 13-7 13S5 14 5 9a7 7 0 0 1 7-7zm0 9.5A2.5 2.5 0 1 0 12 6a2.5 2.5 0 0 0 0 5.5z"></path></svg>
        <input id="pf-input" placeholder="nick" />
        <button id="pf-find">Find</button>
        <button id="pf-clear">✕</button>
      </div>
      <div id="pf-controls">
        <label class="pf-chip"><input type="checkbox" id="pf-chip">Чип</label>
      </div>
      <div id="pf-res"></div>
    </div>
  `;
  document.body.appendChild(root);

  const css = `
    #pf-root {position: fixed; left: 12px; top: 12px; z-index: 2147483647; font-family: Inter, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;}
    #pf-card {width:260px;padding:12px;border-radius:14px;background:linear-gradient(135deg, rgba(255,255,255,0.04), rgba(255,255,255,0.02));box-shadow:0 8px 30px rgba(0,0,0,0.5);backdrop-filter: blur(6px);color:#eaeef6;border:1px solid rgba(255,255,255,0.04)}
    #pf-h{display:flex;align-items:center;gap:8px}
    #pf-icon{width:18px;height:18px;opacity:0.9;color:#9be7ff}
    #pf-input{flex:1;background:transparent;border:none;padding:8px 10px;border-radius:10px;color:inherit;outline:none;font-size:13px}
    #pf-input::placeholder{color:rgba(255,255,255,0.35)}
    #pf-find{background:linear-gradient(180deg,#00c2ff,#0077b6);border:none;padding:6px 10px;border-radius:10px;color:#022;cursor:pointer;font-weight:700}
    #pf-find:active{transform:translateY(1px)}
    #pf-clear{background:transparent;border:none;color:rgba(255,255,255,0.6);cursor:pointer;padding:6px;border-radius:8px;font-size:13px}
    #pf-clear:hover{color:white;background:rgba(255,255,255,0.02)}
    #pf-controls{margin-top:8px;display:flex;justify-content:flex-start;align-items:center;gap:8px}
    .pf-chip{display:inline-flex;align-items:center;gap:6px;background:rgba(255,255,255,0.03);padding:6px 8px;border-radius:12px;font-size:12px}
    .pf-chip input{width:14px;height:14px;margin:0}
    #pf-res{margin-top:10px;padding:10px;border-radius:10px;background:linear-gradient(180deg, rgba(0,0,0,0.06), rgba(255,255,255,0.01));font-weight:700;text-align:center;font-size:14px;min-height:24px;display:flex;align-items:center;justify-content:center}
  `;
  const s = document.createElement('style');
  s.textContent = css;
  document.head.appendChild(s);

  const input = document.getElementById('pf-input');
  const findBtn = document.getElementById('pf-find');
  const clearBtn = document.getElementById('pf-clear');
  const resBox = document.getElementById('pf-res');
  const chip = document.getElementById('pf-chip');

  let currentReq = null;
  let lastTime = 0;
  const MIN_MS = 200;
  let rafId = null;
  let lastNick = '';

  function render(text) {
    resBox.textContent = text;
  }

  function parseAndFind(data, nick) {
    const q = nick.toLowerCase();
    if (!data || !data.servers) return null;
    const regs = Object.keys(data.servers);
    for (let r = 0; r < regs.length; r++) {
      const region = regs[r];
      const regionObj = data.servers[region];
      if (!regionObj) continue;
      const shards = Object.keys(regionObj);
      for (let s = 0; s < shards.length; s++) {
        const shard = shards[s];
        const server = regionObj[shard];
        if (!server || !Array.isArray(server.online)) continue;
        for (let i = 0; i < server.online.length; i++) {
          if (String(server.online[i]).toLowerCase() === q) {
            return { region, shard, server };
          }
        }
      }
    }
    return null;
  }

  function doRequest(nick) {
    if (!nick) return;
    lastNick = nick;
    if (currentReq && currentReq.abort) try { currentReq.abort(); } catch (e) {}
    currentReq = GM_xmlhttpRequest({
      method: 'GET',
      url: 'https://evades.io/api/game/list',
      onload(resq) {
        currentReq = null;
        try {
          const data = JSON.parse(resq.responseText);
          const found = parseAndFind(data, nick);
          if (found) {
            const shardNum = parseInt(found.shard, 10);
            const shardDisp = isNaN(shardNum) ? found.shard : String(shardNum + 1);
            const connected = (found.server && (found.server.connected || found.server.connected === 0)) ? found.server.connected : (found.server && found.server.players ? found.server.players.length : '?');
            const capacity = (found.server && (found.server.capacity || found.server.capacity === 0)) ? found.server.capacity : '?';
            render(`${nick} — ${found.region} ${shardDisp} / ${connected}/${capacity}`);
          } else {
            render(`${nick} — offline`);
          }
        } catch (e) {
          render('err');
        }
      },
      onerror() {
        currentReq = null;
        render('err');
      },
      ontimeout() {
        currentReq = null;
        render('timeout');
      }
    });
  }

  findBtn.addEventListener('click', function () {
    doRequest(input.value.trim());
  });

  input.addEventListener('keydown', function (e) {
    if (e.key === 'Enter') doRequest(input.value.trim());
  });

  clearBtn.addEventListener('click', function () {
    input.value = '';
    render('');
    if (currentReq && currentReq.abort) try { currentReq.abort(); } catch (e) {}
  });

  function loop() {
    rafId = requestAnimationFrame(loop);
    if (!chip.checked) return;
    const nick = input.value.trim();
    if (!nick) return;
    if (performance.now() - lastTime < MIN_MS) return;
    lastTime = performance.now();
    if (currentReq && currentReq.abort) try { currentReq.abort(); } catch (e) {}
    doRequest(nick);
  }

  rafId = requestAnimationFrame(loop);
})();