Greasy Fork is available in English.
Affiche une carte interactive de tous les serveurs/ressources chargΓ©s par la page avec leurs IPs et localisations
// ==UserScript==
// @name π Server Map Tracker
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Affiche une carte interactive de tous les serveurs/ressources chargΓ©s par la page avec leurs IPs et localisations
// @author ServerMapTracker
// @match *://*/*
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @connect ip-api.com
// @run-at document-idle
// ==/UserScript==
(function () {
'use strict';
// ββ ΓTAT ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
const servers = new Map(); // host β { ip, country, countryCode, city, lat, lon, org, type, count }
let mapVisible = false;
let map = null;
let markers = [];
let pendingQueue = [];
let isProcessing = false;
const MAX_ENTRIES = 80; // reset auto au-delΓ
// ββ STYLES ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
GM_addStyle(`
#smt-btn {
position: fixed;
bottom: 24px;
right: 24px;
z-index: 2147483646;
background: #0f172a;
border: 1.5px solid #38bdf8;
color: #38bdf8;
font-family: 'Courier New', monospace;
font-size: 12px;
padding: 8px 14px;
border-radius: 6px;
cursor: pointer;
box-shadow: 0 0 12px #38bdf840;
transition: all .2s;
display: flex;
align-items: center;
gap: 6px;
}
#smt-btn:hover { background: #38bdf8; color: #0f172a; box-shadow: 0 0 20px #38bdf870; }
#smt-badge {
background: #f43f5e;
color: #fff;
border-radius: 10px;
padding: 1px 6px;
font-size: 10px;
font-weight: bold;
}
#smt-panel {
position: fixed;
bottom: 72px;
right: 24px;
width: 620px;
height: 500px;
z-index: 2147483647;
background: #0f172a;
border: 1.5px solid #334155;
border-radius: 12px;
box-shadow: 0 8px 40px #00000080;
display: flex;
flex-direction: column;
overflow: hidden;
font-family: 'Courier New', monospace;
}
#smt-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 16px;
background: #1e293b;
border-bottom: 1px solid #334155;
flex-shrink: 0;
}
#smt-title { color: #38bdf8; font-size: 13px; font-weight: bold; letter-spacing: 1px; }
#smt-controls { display: flex; gap: 8px; }
.smt-ctrl-btn {
background: #0f172a;
border: 1px solid #475569;
color: #94a3b8;
font-size: 10px;
padding: 4px 10px;
border-radius: 4px;
cursor: pointer;
transition: all .15s;
}
.smt-ctrl-btn:hover { border-color: #38bdf8; color: #38bdf8; }
.smt-ctrl-btn.active { border-color: #38bdf8; color: #38bdf8; background: #0e3a52; }
#smt-map {
flex: 1;
min-height: 0;
}
#smt-list {
flex: 1;
min-height: 0;
overflow-y: auto;
display: none;
}
#smt-list::-webkit-scrollbar { width: 4px; }
#smt-list::-webkit-scrollbar-track { background: #0f172a; }
#smt-list::-webkit-scrollbar-thumb { background: #334155; border-radius: 2px; }
.smt-row {
display: grid;
grid-template-columns: 24px 1fr 90px 80px 60px;
gap: 8px;
padding: 6px 14px;
border-bottom: 1px solid #1e293b;
align-items: center;
font-size: 11px;
transition: background .1s;
}
.smt-row:hover { background: #1e293b; }
.smt-flag { font-size: 16px; text-align: center; }
.smt-host { color: #e2e8f0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.smt-ip { color: #38bdf8; font-size: 10px; }
.smt-org { color: #64748b; font-size: 10px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.smt-type { font-size: 9px; padding: 2px 5px; border-radius: 3px; text-align: center; font-weight: bold; }
.type-js { background: #f59e0b20; color: #f59e0b; }
.type-css { background: #8b5cf620; color: #8b5cf6; }
.type-img { background: #10b98120; color: #10b981; }
.type-api { background: #f43f5e20; color: #f43f5e; }
.type-font { background: #06b6d420; color: #06b6d4; }
.type-other { background: #47556920; color: #475569; }
.smt-copy-ip {
background: none;
border: 1px solid #334155;
color: #475569;
font-size: 9px;
padding: 2px 5px;
border-radius: 3px;
cursor: pointer;
transition: all .15s;
font-family: 'Courier New', monospace;
}
.smt-copy-ip:hover { border-color: #38bdf8; color: #38bdf8; }
.smt-copy-ip.copied { border-color: #10b981; color: #10b981; }
#smt-footer {
padding: 6px 14px;
background: #1e293b;
border-top: 1px solid #334155;
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
}
#smt-stats { color: #475569; font-size: 10px; }
#smt-reset-btn {
background: #f43f5e20;
border: 1px solid #f43f5e40;
color: #f43f5e;
font-size: 10px;
padding: 3px 10px;
border-radius: 4px;
cursor: pointer;
transition: all .15s;
}
#smt-reset-btn:hover { background: #f43f5e; color: #fff; }
.leaflet-container { background: #0f172a !important; }
.smt-marker-popup {
font-family: 'Courier New', monospace;
font-size: 11px;
line-height: 1.6;
}
.smt-marker-popup .host { color: #38bdf8; font-weight: bold; }
.smt-marker-popup .ip-line { display: flex; align-items: center; gap: 6px; margin-top: 4px; }
.smt-popup-copy {
background: #0f172a;
border: 1px solid #38bdf8;
color: #38bdf8;
font-size: 9px;
padding: 1px 6px;
border-radius: 3px;
cursor: pointer;
}
`);
// ββ DOM ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
const btn = document.createElement('div');
btn.id = 'smt-btn';
btn.innerHTML = `π Serveurs <span id="smt-badge">0</span>`;
document.body.appendChild(btn);
const panel = document.createElement('div');
panel.id = 'smt-panel';
panel.style.display = 'none';
panel.innerHTML = `
<div id="smt-header">
<span id="smt-title">π SERVER MAP TRACKER</span>
<div id="smt-controls">
<button class="smt-ctrl-btn active" id="smt-tab-map">CARTE</button>
<button class="smt-ctrl-btn" id="smt-tab-list">LISTE</button>
</div>
</div>
<div id="smt-map"></div>
<div id="smt-list"></div>
<div id="smt-footer">
<span id="smt-stats">0 serveurs β’ 0 pays</span>
<button id="smt-reset-btn">β³ RΓ©initialiser</button>
</div>
`;
document.body.appendChild(panel);
// ββ LEAFLET ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
function loadLeaflet(cb) {
if (window.L) { cb(); return; }
const css = document.createElement('link');
css.rel = 'stylesheet';
css.href = 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css';
document.head.appendChild(css);
const js = document.createElement('script');
js.src = 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js';
js.onload = cb;
document.head.appendChild(js);
}
function initMap() {
if (map) return;
map = window.L.map('smt-map', {
center: [20, 0],
zoom: 2,
zoomControl: true,
attributionControl: false
});
window.L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
maxZoom: 18
}).addTo(map);
refreshMarkers();
}
function refreshMarkers() {
if (!map) return;
markers.forEach(m => m.remove());
markers = [];
servers.forEach((data, host) => {
if (!data.lat || !data.lon) return;
const color = typeColor(data.type);
const icon = window.L.divIcon({
html: `<div style="width:10px;height:10px;border-radius:50%;background:${color};border:2px solid #fff;box-shadow:0 0 6px ${color}80;"></div>`,
className: '',
iconSize: [10, 10]
});
const marker = window.L.marker([data.lat, data.lon], { icon })
.addTo(map)
.bindPopup(`
<div class="smt-marker-popup">
<div class="host">${host}</div>
<div>${flagEmoji(data.countryCode)} ${data.city || ''} ${data.country || ''}</div>
<div class="ip-line">
<span>${data.ip || '?'}</span>
<button class="smt-popup-copy" onclick="navigator.clipboard.writeText('${data.ip}');this.textContent='β'">COPIER</button>
</div>
<div style="color:#64748b;font-size:10px;margin-top:2px;">${data.org || ''}</div>
<div style="color:#64748b;font-size:10px;">${data.count} ressource(s)</div>
</div>
`);
markers.push(marker);
});
}
// ββ UTILITAIRES ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
function typeColor(t) {
return { js: '#f59e0b', css: '#8b5cf6', img: '#10b981', api: '#f43f5e', font: '#06b6d4' }[t] || '#475569';
}
function getType(url) {
if (/\.(js|mjs)(\?|$)/.test(url)) return 'js';
if (/\.css(\?|$)/.test(url)) return 'css';
if (/\.(png|jpe?g|gif|svg|webp|ico)(\?|$)/.test(url)) return 'img';
if (/\.(woff2?|ttf|eot)(\?|$)/.test(url)) return 'font';
if (/api|graphql|rest|json/.test(url)) return 'api';
return 'other';
}
function flagEmoji(code) {
if (!code || code.length !== 2) return 'π³οΈ';
return String.fromCodePoint(...[...code.toUpperCase()].map(c => 0x1F1E6 - 65 + c.charCodeAt(0)));
}
function updateBadge() {
const badge = document.getElementById('smt-badge');
if (badge) badge.textContent = servers.size;
const stats = document.getElementById('smt-stats');
if (stats) {
const countries = new Set([...servers.values()].map(s => s.countryCode).filter(Boolean));
stats.textContent = `${servers.size} serveurs β’ ${countries.size} pays`;
}
}
function autoReset() {
if (servers.size >= MAX_ENTRIES) {
doReset();
}
}
function doReset() {
servers.clear();
pendingQueue = [];
markers.forEach(m => m.remove());
markers = [];
updateBadge();
refreshList();
if (map) map.setView([20, 0], 2);
}
// ββ LISTE ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
function refreshList() {
const list = document.getElementById('smt-list');
if (!list) return;
list.innerHTML = '';
[...servers.entries()]
.sort((a, b) => b[1].count - a[1].count)
.forEach(([host, data]) => {
const row = document.createElement('div');
row.className = 'smt-row';
row.innerHTML = `
<div class="smt-flag">${flagEmoji(data.countryCode)}</div>
<div>
<div class="smt-host" title="${host}">${host}</div>
<div class="smt-org">${data.org || 'β'}</div>
</div>
<div class="smt-ip">${data.ip || '...'}</div>
<div class="smt-type type-${data.type}">${data.type.toUpperCase()}</div>
<button class="smt-copy-ip" data-ip="${data.ip}">COPIER IP</button>
`;
const copyBtn = row.querySelector('.smt-copy-ip');
copyBtn.addEventListener('click', () => {
navigator.clipboard.writeText(data.ip || '').then(() => {
copyBtn.textContent = 'β COPIΓ';
copyBtn.classList.add('copied');
setTimeout(() => { copyBtn.textContent = 'COPIER IP'; copyBtn.classList.remove('copied'); }, 1500);
});
});
list.appendChild(row);
});
}
// ββ GΓOLOCALISATION IP βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
function processQueue() {
if (isProcessing || pendingQueue.length === 0) return;
isProcessing = true;
const host = pendingQueue.shift();
if (!host || servers.has(host)) { isProcessing = false; processQueue(); return; }
servers.set(host, { ip: null, country: null, countryCode: null, city: null, lat: null, lon: null, org: null, type: 'other', count: 1 });
updateBadge();
GM_xmlhttpRequest({
method: 'GET',
url: `http://ip-api.com/json/${host}?fields=status,country,countryCode,city,lat,lon,org,query`,
onload(res) {
try {
const d = JSON.parse(res.responseText);
if (d.status === 'success') {
const existing = servers.get(host) || {};
servers.set(host, { ...existing, ip: d.query, country: d.country, countryCode: d.countryCode, city: d.city, lat: d.lat, lon: d.lon, org: d.org });
updateBadge();
if (mapVisible) { refreshMarkers(); refreshList(); }
}
} catch (e) {}
isProcessing = false;
setTimeout(processQueue, 120); // limite rate
},
onerror() { isProcessing = false; setTimeout(processQueue, 120); }
});
}
function trackHost(url, type) {
try {
const host = new URL(url).hostname;
if (!host || host === location.hostname) return;
autoReset();
if (servers.has(host)) {
servers.get(host).count++;
return;
}
servers.set(host, { ip: null, country: null, countryCode: null, city: null, lat: null, lon: null, org: null, type, count: 1 });
pendingQueue.push(host);
processQueue();
updateBadge();
} catch (e) {}
}
// ββ INTERCEPTION DES RESSOURCES ββββββββββββββββββββββββββββββββββββββββββββββ
// 1. PerformanceObserver (toutes ressources rΓ©seau)
const observer = new PerformanceObserver(list => {
list.getEntries().forEach(entry => {
if (entry.initiatorType && entry.name.startsWith('http')) {
trackHost(entry.name, getType(entry.name));
}
});
});
observer.observe({ type: 'resource', buffered: true });
// 2. Fetch intercept
const origFetch = window.fetch;
window.fetch = function (...args) {
const url = typeof args[0] === 'string' ? args[0] : (args[0] instanceof Request ? args[0].url : '');
if (url) trackHost(url, 'api');
return origFetch.apply(this, args);
};
// 3. XHR intercept
const origOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (method, url) {
if (url) trackHost(url, 'api');
return origOpen.apply(this, arguments);
};
// ββ TOGGLE PANEL βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
btn.addEventListener('click', () => {
mapVisible = !mapVisible;
panel.style.display = mapVisible ? 'flex' : 'none';
if (mapVisible) {
loadLeaflet(() => {
setTimeout(() => { initMap(); refreshList(); }, 100);
});
}
});
document.getElementById('smt-tab-map').addEventListener('click', () => {
document.getElementById('smt-map').style.display = 'block';
document.getElementById('smt-list').style.display = 'none';
document.getElementById('smt-tab-map').classList.add('active');
document.getElementById('smt-tab-list').classList.remove('active');
if (map) setTimeout(() => map.invalidateSize(), 50);
});
document.getElementById('smt-tab-list').addEventListener('click', () => {
document.getElementById('smt-map').style.display = 'none';
document.getElementById('smt-list').style.display = 'block';
document.getElementById('smt-tab-list').classList.add('active');
document.getElementById('smt-tab-map').classList.remove('active');
refreshList();
});
document.getElementById('smt-reset-btn').addEventListener('click', doReset);
})();