PlayerFinder[Evades.io]

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

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