您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Professional panel to check WorldGuessr leaderboards (elo/xp, all-time/past-day) and individual player stats with friendly formatting. Minimize to top-left.
// ==UserScript== // @name WorldGuessr Checker (Leaderboard + Individual Elo/XP) — Pro UI // @namespace wg-helper // @version 1.1.0 // @description Professional panel to check WorldGuessr leaderboards (elo/xp, all-time/past-day) and individual player stats with friendly formatting. Minimize to top-left. // @author you // @match https://worldguessr.com/* // @match https://www.worldguessr.com/* // @grant GM_addStyle // @grant GM_xmlhttpRequest // @connect api.worldguessr.com // ==/UserScript== (function () { 'use strict'; GM_addStyle(` #wg-helper { position: fixed; right: 16px; bottom: 16px; width: 520px; max-height: 80vh; background: #0f172a; color: #e2e8f0; border: 1px solid rgba(148,163,184,.3); border-radius: 16px; box-shadow: 0 20px 40px rgba(0,0,0,.35); font-family: Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji"; overflow: hidden; z-index: 999999; } #wg-header { display: flex; align-items: center; justify-content: space-between; padding: 10px 12px; background: #111827; border-bottom: 1px solid rgba(148,163,184,.2); font-weight: 600; letter-spacing: .2px; } #wg-title { font-size: 14px; } #wg-actions { display:flex; gap:8px; } .wg-btn { cursor: pointer; border: 1px solid rgba(148,163,184,.3); background: #0b1220; color: #e2e8f0; padding: 6px 10px; border-radius: 10px; font-size: 12px; } .wg-btn:hover { background: #0f172a; } #wg-body { padding: 12px; display:flex; flex-direction: column; gap: 10px; } .wg-row { display:flex; gap: 8px; flex-wrap: wrap; align-items: center; } .wg-chip { padding: 6px 10px; border-radius: 999px; font-size: 12px; border: 1px solid rgba(148,163,184,.25); cursor: pointer; background:#0b1220; } .wg-chip.active { background: #1f2937; border-color: #60a5fa; } .wg-input { flex: 1; background: #0b1220; color: #e5e7eb; border: 1px solid rgba(148,163,184,.3); border-radius: 10px; padding: 8px 10px; font-size: 12px; } #wg-output { white-space: pre-wrap; background: #0b1220; border: 1px solid rgba(148,163,184,.25); border-radius: 12px; padding: 10px; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size: 12px; color: #d1d5db; overflow: auto; max-height: 54vh; } .wg-section-title { font-size: 12px; font-weight: 700; color: #93c5fd; margin-top: 2px; margin-bottom: 4px; } .wg-muted { color: #9ca3af; font-size: 12px; } .wg-sep { height: 1px; background: rgba(148,163,184,.2); margin: 6px 0; } #wg-footer { padding: 8px 12px; display:flex; gap:8px; justify-content:flex-end; border-top:1px solid rgba(148,163,184,.2); } .wg-hidden { display:none !important; } .wg-mini { width: 320px; } #wg-restore { position: fixed; top: 10px; left: 10px; z-index: 1000000; background: #111827; color: #e2e8f0; border: 1px solid rgba(148,163,184,.3); padding: 6px 8px; font-size: 12px; border-radius: 10px; cursor: pointer; box-shadow: 0 8px 20px rgba(0,0,0,.35); } `); const panel = document.createElement('div'); panel.id = 'wg-helper'; panel.innerHTML = ` <div id="wg-header"> <div id="wg-title">WorldGuessr Checker</div> <div id="wg-actions"> <button class="wg-btn" id="wg-copy">Copy</button> <button class="wg-btn" id="wg-clear">Clear</button> <button class="wg-btn" id="wg-size">Compact</button> <button class="wg-btn" id="wg-min">Minimize</button> <button class="wg-btn" id="wg-close">✕</button> </div> </div> <div id="wg-body"> <div class="wg-row"> <span class="wg-muted">View:</span> <span class="wg-chip active" data-view="leaderboard">Leaderboard</span> <span class="wg-chip" data-view="individual">Individual</span> </div> <div class="wg-row"> <span class="wg-muted">Mode:</span> <span class="wg-chip active" data-mode="elo">Elo</span> <span class="wg-chip" data-mode="xp">XP</span> <span class="wg-sep" style="flex:1;background:transparent"></span> <span class="wg-muted">Range:</span> <span class="wg-chip active" data-range="all">All-time</span> <span class="wg-chip" data-range="day">Past-day</span> </div> <div class="wg-row" id="wg-username-row"> <input id="wg-username" class="wg-input" placeholder="Enter username for Individual view…" /> </div> <div class="wg-row"> <button class="wg-btn" id="wg-run">Fetch</button> </div> <div> <div class="wg-section-title">Output</div> <div id="wg-output">(results will appear here)</div> </div> </div> <div id="wg-footer" class="wg-hidden"> <span class="wg-muted">Tip: Leaderboard = top 100; Individual = single player. Processing does not show URLs.</span> </div> `; document.body.appendChild(panel); const restoreBtn = document.createElement('button'); restoreBtn.id = 'wg-restore'; restoreBtn.textContent = 'WG'; restoreBtn.classList.add('wg-hidden'); document.body.appendChild(restoreBtn); const state = { view: 'leaderboard', mode: 'elo', range: 'all', username: '' }; const $ = (sel) => panel.querySelector(sel); const $all = (sel) => Array.from(panel.querySelectorAll(sel)); const output = $('#wg-output'); function setActive(groupSelector, attr, value) { $all(groupSelector).forEach(chip => { if (chip.getAttribute(attr) === value) chip.classList.add('active'); else chip.classList.remove('active'); }); } function showUsernameRow(show) { $('#wg-username-row').classList.toggle('wg-hidden', !show); } function setOutput(text) { output.textContent = text; } function appendOutput(text) { output.textContent += text; } function gmGet(url) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url, headers: { 'Accept': 'application/json' }, onload: (res) => { try { if (res.status < 200 || res.status >= 300) { return reject(new Error(`HTTP ${res.status}`)); } resolve(JSON.parse(res.responseText)); } catch (e) { reject(e); } }, onerror: () => reject(new Error(`Network error`)) }); }); } function pick(valA, valB) { return (valA !== undefined && valA !== null) ? valA : valB; } function formatPlayerLine(p, mode) { const createdAt = pick(p.createdAt, p.joinDate); const lines = [ `Username: ${p.username ?? '—'}`, `Total XP: ${p.totalXp ?? '—'}`, `createdAt: ${createdAt ?? '—'}`, `elo: ${p.elo ?? '—'}`, `rank: ${p.rank ?? '—'}` ]; if (mode === 'elo' && p.eloToday != null) { lines.push(`Elo Gained Today: ${p.eloToday}`); } else if (mode === 'xp' && (p.xpToday != null || p.totalXpToday != null)) { const xpGain = p.xpToday ?? p.totalXpToday; lines.push(`XP Gained Today: ${xpGain}`); } return lines.join('\n'); } function formatLeaderboard(list, mode) { if (!Array.isArray(list) || list.length === 0) { return '(no leaderboard data)'; } return list.map(p => formatPlayerLine(p, mode)).join('\n\n'); } function buildLeaderboardUrl(mode, range) { const base = `https://api.worldguessr.com/api/leaderboard`; const params = new URLSearchParams({ mode }); if (range === 'day') params.set('pastDay', 'true'); return `${base}?${params.toString()}`; } function buildIndividualUrls(username, mode, range) { const urls = {}; if (mode === 'elo') { urls.elo = `https://api.worldguessr.com/api/eloRank?username=${encodeURIComponent(username)}`; } else if (mode === 'xp') { const base = `https://api.worldguessr.com/api/leaderboard`; const params = new URLSearchParams({ username, mode: 'xp' }); if (range === 'day') params.set('pastDay', 'true'); urls.xp = `${base}?${params.toString()}`; } return urls; } function formatIndividualElo(e, usernameFromInput) { const uname = usernameFromInput || e.username || '—'; const leagueName = e?.league?.name || '—'; const emoji = e?.league?.emoji || ''; const winPct = (typeof e.win_rate === 'number') ? (e.win_rate * 100).toFixed(2) + '%' : (e.win_rate ?? '—'); const lines = [ `Username: ${uname}`, `Elo: ${e.elo ?? '—'}`, `Rank: ${e.rank ?? '—'}`, `Rank: ${leagueName} ${emoji}`.trim(), `Duel Wins: ${e.duels_wins ?? '—'}`, `Duel Losses: ${e.duels_losses ?? '—'}`, `Duels Tied: ${e.duels_tied ?? '—'}`, `Win Rate: ${winPct}` ]; return lines.join('\n'); } $all('.wg-chip[data-view]').forEach(chip => { chip.addEventListener('click', () => { state.view = chip.getAttribute('data-view'); setActive('.wg-chip[data-view]', 'data-view', state.view); showUsernameRow(state.view === 'individual'); }); }); $all('.wg-chip[data-mode]').forEach(chip => { chip.addEventListener('click', () => { state.mode = chip.getAttribute('data-mode'); setActive('.wg-chip[data-mode]', 'data-mode', state.mode); }); }); $all('.wg-chip[data-range]').forEach(chip => { chip.addEventListener('click', () => { state.range = chip.getAttribute('data-range'); setActive('.wg-chip[data-range]', 'data-range', state.range); }); }); $('#wg-username').addEventListener('input', (e) => { state.username = e.target.value.trim(); }); $('#wg-run').addEventListener('click', async () => { try { setOutput('Processing request...'); if (state.view === 'leaderboard') { const url = buildLeaderboardUrl(state.mode, state.range); const data = await gmGet(url); const list = data?.leaderboard ?? []; const pretty = formatLeaderboard(list, state.mode); setOutput(pretty); } else { const username = state.username || prompt('Enter username:') || ''; state.username = username.trim(); if (!state.username) { setOutput('(No username provided)'); return; } const urls = buildIndividualUrls(state.username, state.mode, state.range); const url = urls.elo || urls.xp; if (!url) { setOutput('(No URL for this combination)'); return; } const res = await gmGet(url); if (state.mode === 'elo') { setOutput(formatIndividualElo(res, state.username)); } else { let player = null; if (Array.isArray(res)) { player = res[0] || null; } else if (res && typeof res === 'object') { if (res.username) { player = res; } else if (res.leaderboard && Array.isArray(res.leaderboard)) { player = res.leaderboard.find(p => (p.username || '').toLowerCase() === state.username.toLowerCase()) || null; } else if (res.player) { player = res.player; } else { player = { username: state.username, totalXp: res.myXp ?? res.totalXp ?? undefined, elo: res.myElo ?? res.elo ?? undefined, rank: res.myRank ?? res.rank ?? undefined, createdAt: res.createdAt ?? res.joinDate ?? undefined, xpToday: res.xpToday ?? res.totalXpToday ?? undefined }; } } if (!player) { setOutput('(No player data returned)'); return; } setOutput(formatPlayerLine(player, 'xp')); } } } catch (err) { setOutput(`Error: ${err && err.message ? err.message : String(err)}`); } }); $('#wg-copy').addEventListener('click', async () => { try { await navigator.clipboard.writeText(output.textContent || ''); appendOutput(`\n\n(copied)`); } catch { appendOutput(`\n\n(copy failed)`); } }); $('#wg-clear').addEventListener('click', () => setOutput('')); let compact = false; $('#wg-size').addEventListener('click', () => { compact = !compact; panel.classList.toggle('wg-mini', compact); $('#wg-size').textContent = compact ? 'Expand' : 'Compact'; }); $('#wg-close').addEventListener('click', () => { panel.remove(); restoreBtn.remove(); }); $('#wg-min').addEventListener('click', () => { panel.classList.add('wg-hidden'); restoreBtn.classList.remove('wg-hidden'); }); restoreBtn.addEventListener('click', () => { restoreBtn.classList.add('wg-hidden'); panel.classList.remove('wg-hidden'); }); showUsernameRow(false); })();