因为itdog目前不太好用,所以目前重新用上ping.pe了,但是由于节点众多且无分类,所以用vibe了一个脚本出来方便展示,地图支持缩放拖拽,可以直接点击国家,右边会显示该国家的节点,点击节点可以跳转到对应节点并自动展开mtr结果
效果如图

脚本如下
// ==UserScript==
// @name Looking Glass Map for ping.pe
// @namespace https://ping.pe/
// @version 1.1.0
// @description 将 ping.pe 的全球 Ping/MTR 结果可视化为可缩放区域地图,并支持点击城市跳转 MTR 明细。小地区(香港、新加坡等)额外渲染散点标记。
// @author SmileQWQ
// @match https://ping.pe/*
// @match https://ping6.ping.pe/*
// @match https://chart.ping.pe/*
// @match https://chart6.ping.pe/*
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js
// @resource worldGeoJson https://cdn.jsdelivr.net/npm/[email protected]/map/json/world.json
// @grant GM_addStyle
// @grant GM_getResourceText
// @run-at document-idle
// ==/UserScript==
(function () {
'use strict';
const PANEL_ID = 'pingpe-global-region-map';
const UPDATE_INTERVAL_MS = 4000;
const WORLD_MAP = 'pingpe-world';
const BUCKETS = [
{ key: 'timeout', label: '超时', color: '#e51b1b' },
{ key: 'gt250', label: '>250ms', color: '#ff9829' },
{ key: '201-250', label: '201ms-250ms', color: '#f1e62b' },
{ key: '101-200', label: '101ms-200ms', color: '#b8f052' },
{ key: '51-100', label: '51ms-100ms', color: '#35d144' },
{ key: 'le50', label: '<=50ms', color: '#18a81f' },
{ key: 'pending', label: '等待中', color: '#6b7280' },
];
const COUNTRY_ALIAS = {
USA: 'United States',
UK: 'United Kingdom',
UAE: 'United Arab Emirates',
Kyiv: 'Ukraine',
Kosovo: 'Kosovo',
Taiwan: 'Taiwan',
'Hong Kong': 'Hong Kong',
};
// 小地区坐标表:地图面积太小、难以点中的地区
// 坐标为 [经度, 纬度]
const SMALL_REGION_COORDS = {
'Hong Kong': [114.17, 22.32],
'Singapore': [103.82, 1.35],
'Taiwan': [120.97, 23.70],
'Macau': [113.55, 22.20],
'Luxembourg': [ 6.13, 49.61],
'Bahrain': [ 50.55, 26.07],
'Maldives': [ 73.22, 3.20],
'Malta': [ 14.51, 35.90],
'Liechtenstein': [ 9.52, 47.14],
'Monaco': [ 7.42, 43.73],
'San Marino': [ 12.46, 43.94],
'Andorra': [ 1.52, 42.51],
'Vatican City': [ 12.45, 41.90],
'Cyprus': [ 33.43, 35.13],
'Iceland': [-19.02, 64.96],
'Trinidad and Tobago':[-61.22, 10.65],
'Jamaica': [-77.30, 18.11],
'Mauritius': [ 57.55, -20.28],
'Reunion': [ 55.54, -21.13],
'Djibouti': [ 43.14, 11.83],
};
// 需要用散点标记的地区集合(面积小 or 地图上看不清楚)
const SMALL_REGION_SET = new Set(Object.keys(SMALL_REGION_COORDS));
let chart = null;
let panelEl = null;
let chartEl = null;
let lastMetric = 'avg';
let lastSignature = '';
let renderQueued = false;
let currentZoom = 1.18;
let currentCenter = null;
let lastGroups = new Map();
function boot() {
if (document.getElementById(PANEL_ID)) return;
const table = document.querySelector('table.pingtable');
if (!table || !document.querySelector('tr.ping-result-row')) {
window.setTimeout(boot, 500);
return;
}
init(table);
}
function init(table) {
if (typeof echarts === 'undefined') {
insertError(table, 'ECharts 加载失败:请检查 Tampermonkey 是否允许访问 cdn.jsdelivr.net。');
return;
}
try {
echarts.registerMap(WORLD_MAP, JSON.parse(GM_getResourceText('worldGeoJson')));
} catch (error) {
insertError(table, `世界地图 GeoJSON 加载失败:${error && error.message ? error.message : error}`);
return;
}
GM_addStyle(`
#${PANEL_ID} {
margin: 14px 0 18px;
border: 1px solid #155015;
border-radius: 10px;
overflow: hidden;
background: #050805;
box-shadow: 0 0 0 1px rgba(48,224,80,.12), 0 12px 30px rgba(0,0,0,.35);
}
#${PANEL_ID} .pgr-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 9px 11px;
color: #e6ffe6;
background: linear-gradient(90deg, #001c00, #071407);
border-bottom: 1px solid #155015;
flex-wrap: wrap;
}
#${PANEL_ID} .pgr-title { color: #30e050; font-weight: 700; }
#${PANEL_ID} .pgr-status { color: #cdeccd; opacity: .9; }
#${PANEL_ID} .pgr-controls {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
#${PANEL_ID} select,
#${PANEL_ID} button {
color: #e6ffe6;
background: #002000;
border: 1px solid #238023;
border-radius: 5px;
padding: 4px 8px;
font: inherit;
cursor: pointer;
}
#${PANEL_ID} .pgr-layout {
display: grid;
grid-template-columns: minmax(0, 1fr) 330px;
background: #050805;
}
#${PANEL_ID} .pgr-chart {
height: min(68vh, 650px);
min-height: 430px;
background: #f7f7f2;
border-right: 1px solid #155015;
}
#${PANEL_ID} .pgr-detail {
height: min(68vh, 650px);
min-height: 430px;
overflow: auto;
color: #e6ffe6;
background: #020802;
}
#${PANEL_ID} .pgr-detail-head {
position: sticky;
top: 0;
z-index: 1;
padding: 10px 12px;
background: #002000;
border-bottom: 1px solid #155015;
}
#${PANEL_ID} .pgr-detail-title {
color: #30e050;
font-weight: 700;
margin-bottom: 4px;
}
#${PANEL_ID} .pgr-detail-table {
width: 100%;
border-collapse: collapse;
font-size: 12px;
line-height: 1.45;
}
#${PANEL_ID} .pgr-detail-table th,
#${PANEL_ID} .pgr-detail-table td {
padding: 5px 7px;
border-bottom: 1px solid rgba(48,224,80,.13);
text-align: left;
white-space: nowrap;
}
#${PANEL_ID} .pgr-detail-table tbody tr {
cursor: pointer;
}
#${PANEL_ID} .pgr-detail-table tbody tr:hover {
background: rgba(48,224,80,.16);
}
#${PANEL_ID} .pgr-detail-table th {
color: #001800;
background: #30e050;
position: sticky;
top: 59px;
z-index: 1;
}
#${PANEL_ID} .pgr-detail-empty {
padding: 14px 12px;
color: #bcdabc;
}
#${PANEL_ID}.pgr-fullscreen {
position: fixed;
z-index: 999999;
inset: 10px;
margin: 0;
}
#${PANEL_ID}.pgr-fullscreen .pgr-chart,
#${PANEL_ID}.pgr-fullscreen .pgr-detail {
height: calc(100vh - 78px);
max-height: none;
}
#${PANEL_ID} .pgr-legend {
display: flex;
gap: 10px 14px;
align-items: center;
flex-wrap: wrap;
padding: 8px 11px;
color: #d7ead7;
background: #020702;
border-top: 1px solid #155015;
}
#${PANEL_ID} .pgr-legend-item {
display: inline-flex;
align-items: center;
gap: 5px;
white-space: nowrap;
}
#${PANEL_ID} .pgr-swatch {
width: 14px;
height: 14px;
border-radius: 4px;
display: inline-block;
}
.pgr-tooltip {
min-width: 220px;
max-width: 360px;
color: #fff;
background: rgba(38, 48, 38, .86);
border-radius: 4px;
box-shadow: 0 12px 28px rgba(0,0,0,.35);
overflow: hidden;
font-family: Roboto Mono, Consolas, "Courier New", monospace;
}
.pgr-tooltip-title {
padding: 8px 12px;
text-align: center;
background: #4384f5;
color: #fff;
font-size: 16px;
line-height: 1.2;
}
.pgr-tooltip-body { padding: 12px 18px 14px; font-size: 14px; line-height: 1.65; }
.pgr-tooltip-line { display: flex; align-items: baseline; gap: 4px; white-space: nowrap; }
.pgr-tooltip-isp { color: #7de354; }
.pgr-tooltip-value { margin-left: auto; }
.pgr-tooltip-hint { margin-top: 3px; text-align: center; color: #fff; opacity: .92; }
tr.pgr-jump-highlight > td {
outline: 1px solid #30e050;
box-shadow: inset 0 0 0 9999px rgba(48,224,80,.12);
}
@media (max-width: 980px) {
#${PANEL_ID} .pgr-layout { grid-template-columns: 1fr; }
#${PANEL_ID} .pgr-chart { border-right: 0; border-bottom: 1px solid #155015; }
#${PANEL_ID} .pgr-detail { min-height: 260px; height: 330px; }
}
`);
panelEl = document.createElement('section');
panelEl.id = PANEL_ID;
panelEl.innerHTML = `
<div class="pgr-toolbar">
<div>
<span class="pgr-title">Looking Glass Map</span>
<span class="pgr-status" data-role="status">waiting...</span>
</div>
<div class="pgr-controls">
<label>指标
<select data-role="metric">
<option value="avg" selected>Avg</option>
<option value="last">Last</option>
<option value="best">Best</option>
<option value="worst">Worst</option>
</select>
</label>
<button type="button" data-role="zoom-in">+</button>
<button type="button" data-role="zoom-out">-</button>
<button type="button" data-role="reset">重置视图</button>
<button type="button" data-role="fullscreen">全屏</button>
</div>
</div>
<div class="pgr-layout">
<div class="pgr-chart" data-role="chart"></div>
<aside class="pgr-detail" data-role="detail"></aside>
</div>
<div class="pgr-legend">
${BUCKETS.map(b => `<span class="pgr-legend-item"><i class="pgr-swatch" style="background:${b.color}"></i>${b.label}</span>`).join('')}
<span class="pgr-legend-item"><i class="pgr-swatch" style="background:#fff;border:2px solid #30e050;border-radius:50%;box-sizing:border-box"></i>小地区标记(可点击)</span>
</div>
`;
table.parentNode.insertBefore(panelEl, table);
chartEl = panelEl.querySelector('[data-role="chart"]');
chart = echarts.init(chartEl, null, { renderer: 'canvas', useDirtyRect: true });
panelEl.querySelector('[data-role="metric"]').addEventListener('change', ev => {
lastMetric = ev.target.value;
lastSignature = '';
scheduleRender(true);
});
panelEl.querySelector('[data-role="zoom-in"]').addEventListener('click', () => zoomBy(1.28));
panelEl.querySelector('[data-role="zoom-out"]').addEventListener('click', () => zoomBy(0.78));
panelEl.querySelector('[data-role="reset"]').addEventListener('click', resetView);
panelEl.querySelector('[data-role="fullscreen"]').addEventListener('click', () => {
panelEl.classList.toggle('pgr-fullscreen');
window.setTimeout(() => chart.resize(), 120);
});
panelEl.querySelector('[data-role="detail"]').addEventListener('click', ev => {
const tr = ev.target.closest('tr[data-pinger-id]');
if (!tr) return;
jumpToMtr(tr.getAttribute('data-pinger-id'));
});
chartEl.addEventListener('wheel', ev => ev.preventDefault(), { passive: false });
window.addEventListener('resize', () => chart && chart.resize());
chart.on('georoam', () => {
const option = chart.getOption();
// geo 组件和 map series 都会触发 georoam,优先从 geo 读,回退到 map series
const geoComp = option.geo && option.geo[0];
const mapSeries = option.series && option.series[0];
const src = geoComp || mapSeries;
if (src) {
currentZoom = Array.isArray(src.zoom) ? src.zoom[0] : (src.zoom || currentZoom);
const rawCenter = Array.isArray(src.center) ? src.center : null;
if (rawCenter) currentCenter = Array.isArray(rawCenter[0]) ? rawCenter[0] : rawCenter;
}
});
// geo 区域点击(国家/地区)
chart.on('click', { geoIndex: 0 }, params => {
if (params && params.name) {
renderDetail(params.name);
}
});
// 散点标记点击(小地区圆点)
chart.on('click', { seriesId: 'pingpe-dot-series' }, params => {
if (params && params.data && params.data.country) {
renderDetail(params.data.country);
}
});
const observer = new MutationObserver(() => scheduleRender(false));
observer.observe(table, {
subtree: true,
attributes: true,
attributeFilter: ['data-last', 'data-avg', 'data-best', 'data-worst', 'data-loss', 'data-sent'],
});
scheduleRender(true);
window.setInterval(() => scheduleRender(false), UPDATE_INTERVAL_MS);
}
function insertError(table, message) {
const div = document.createElement('div');
div.id = PANEL_ID;
div.style.cssText = 'margin:12px 0;padding:10px;border:1px solid #803030;background:#300;color:#fdd;border-radius:6px';
div.textContent = message;
table.parentNode.insertBefore(div, table);
}
function scheduleRender(force) {
if (renderQueued) return;
renderQueued = true;
window.requestAnimationFrame(() => {
renderQueued = false;
render(force);
});
}
function render(force) {
if (!chart) return;
const rows = Array.from(document.querySelectorAll('tr.ping-result-row'));
const groups = collectCountryGroups(rows, lastMetric);
const signature = buildSignature(groups, rows.length);
if (!force && signature === lastSignature) return;
lastSignature = signature;
lastGroups = groups;
// ── 散点标记数据(仅有数据的小地区)──
const dotData = [];
for (const [country, group] of groups) {
if (!SMALL_REGION_SET.has(country)) continue;
const coords = SMALL_REGION_COORDS[country];
if (!coords) continue;
dotData.push({
name: country,
value: [...coords, group.value], // [lng, lat, metricValue]
country,
group,
itemStyle: {
color: group.bucket.color,
borderColor: '#ffffff',
borderWidth: 1.5,
shadowBlur: 6,
shadowColor: 'rgba(0,0,0,0.5)',
},
emphasis: {
itemStyle: {
color: group.bucket.color,
borderColor: '#30e050',
borderWidth: 2.5,
shadowBlur: 14,
shadowColor: group.bucket.color,
},
},
});
}
const mappedRows = Array.from(groups.values()).reduce((sum, group) => sum + group.rows.length, 0);
const timeoutRows = rows.filter(row => {
const value = parseLatency(row.dataset[lastMetric]);
const sent = parseNumber(row.dataset.sent);
const loss = parseNumber(row.dataset.loss);
return classify(value, sent, loss).key === 'timeout';
}).length;
updateStatus(rows.length, mappedRows, rows.length - mappedRows, timeoutRows);
const tooltipFormatter = params => {
const g = params.data && params.data.group;
return buildTooltip(g, params.name);
};
// geo regions:把每个国家的颜色注入到 geo 组件的 regions 数组
// 只渲染一次地图,scatter 也挂在同一个 geo 上,彻底避免双重渲染
const geoRegions = Array.from(groups.values()).map(group => ({
name: group.country,
itemStyle: { areaColor: group.bucket.color },
emphasis: {
itemStyle: {
areaColor: group.bucket.color,
borderColor: '#ffffff',
borderWidth: 1.5,
},
label: { show: true, color: '#111', fontWeight: 'bold' },
},
// 附上 group 以供 tooltip/click 使用
_group: group,
}));
chart.setOption({
animation: false,
backgroundColor: '#f7f7f2',
tooltip: {
trigger: 'item',
borderWidth: 0,
padding: 0,
transitionDuration: 0,
confine: true,
extraCssText: 'box-shadow:none;background:transparent;',
formatter: params => {
// geo 区域点击时 params.data 是 region 对象(含 _group),scatter 时是 dotData 项
const g = (params.data && params.data._group)
|| (params.data && params.data.group)
|| null;
return buildTooltip(g, params.name);
},
},
geo: [{
id: 'pingpe-geo',
map: WORLD_MAP,
roam: true,
zoom: currentZoom,
center: currentCenter || undefined,
scaleLimit: { min: 0.9, max: 30 },
selectedMode: false,
nameProperty: 'name',
itemStyle: {
areaColor: '#e9e7df',
borderColor: '#cfcfc6',
borderWidth: 0.6,
},
emphasis: {
itemStyle: { borderColor: '#ffffff', borderWidth: 1 },
label: { show: true, color: '#111', fontWeight: 'bold' },
},
label: { show: false },
regions: geoRegions,
}],
series: [{
id: 'pingpe-dot-series',
type: 'scatter',
coordinateSystem: 'geo',
geoIndex: 0,
data: dotData,
symbol: 'circle',
symbolSize: 10,
zlevel: 2,
label: {
show: true,
position: 'right',
formatter: params => params.name,
fontSize: 10,
color: '#1a1a1a',
textBorderColor: '#fff',
textBorderWidth: 2,
},
emphasis: {
label: {
show: true,
fontSize: 11,
fontWeight: 'bold',
color: '#001800',
textBorderColor: '#30e050',
textBorderWidth: 2,
},
scale: 1.5,
},
}],
}, {
notMerge: false,
lazyUpdate: true,
replaceMerge: ['series'],
});
const selected = panelEl?.querySelector('[data-role="detail"]')?.dataset.country;
if (selected && groups.has(selected)) {
renderDetail(selected);
} else {
renderDetail(pickDefaultCountry(groups));
}
}
function collectCountryGroups(rows, metric) {
const groups = new Map();
for (const row of rows) {
const location = clean(row.dataset.location || row.querySelector('.td-location')?.textContent || '');
if (!location) continue;
const country = toMapCountry(location);
if (!country) continue;
const item = {
id: row.dataset.pingerId || '',
location,
city: shortLocationTitle(location),
provider: clean(row.dataset.provider || row.querySelector('.td-provider')?.textContent || ''),
value: parseLatency(row.dataset[metric]),
last: parseLatency(row.dataset.last),
avg: parseLatency(row.dataset.avg),
best: parseLatency(row.dataset.best),
worst: parseLatency(row.dataset.worst),
loss: parseNumber(row.dataset.loss),
sent: parseNumber(row.dataset.sent),
};
item.bucket = classify(item.value, item.sent, item.loss);
if (!groups.has(country)) {
groups.set(country, { country, rows: [], value: NaN, bucket: BUCKETS[6] });
}
const group = groups.get(country);
group.rows.push(item);
if (normalizedLatency(item.value) < normalizedLatency(group.value)) {
group.value = item.value;
group.bucket = item.bucket;
} else if (!Number.isFinite(group.value) && item.bucket.key === 'timeout') {
group.bucket = item.bucket;
}
}
return groups;
}
function renderDetail(country) {
const detail = panelEl && panelEl.querySelector('[data-role="detail"]');
if (!detail) return;
const group = lastGroups.get(country);
if (!group) {
detail.dataset.country = '';
detail.innerHTML = `<div class="pgr-detail-empty">点击地图上的国家/地区查看城市节点明细。</div>`;
return;
}
detail.dataset.country = country;
const rows = group.rows.slice().sort((a, b) => normalizedLatency(a.value) - normalizedLatency(b.value));
const fastest = rows.find(item => Number.isFinite(item.value));
detail.innerHTML = `
<div class="pgr-detail-head">
<div class="pgr-detail-title">${escapeHtml(country)}</div>
<div>最快:${fastest ? escapeHtml(formatValue(fastest.value)) : '等待中/超时'} · 节点:${rows.length} · 指标:${escapeHtml(lastMetric.toUpperCase())}</div>
</div>
<table class="pgr-detail-table">
<thead>
<tr>
<th>城市</th>
<th>ISP</th>
<th>${escapeHtml(lastMetric.toUpperCase())}</th>
<th>Loss</th>
<th>Sent</th>
</tr>
</thead>
<tbody>
${rows.map(item => `
<tr data-pinger-id="${escapeHtml(item.id)}" title="点击展开并跳转到该节点 MTR 结果">
<td>${escapeHtml(item.city)}</td>
<td title="${escapeHtml(item.provider)}">${escapeHtml(shortProvider(item.provider || item.id))}</td>
<td style="background:${classify(item.value, item.sent, item.loss).color};color:#001800">${escapeHtml(formatValue(item.value))}</td>
<td>${Number.isFinite(item.loss) ? `${item.loss}%` : '-'}</td>
<td>${Number.isFinite(item.sent) ? item.sent : '-'}</td>
</tr>
`).join('')}
</tbody>
</table>
`;
}
function jumpToMtr(pingerId) {
if (!pingerId) return;
const pingRow = document.getElementById(`ping-${pingerId}-tr`);
const showButton = document.getElementById(`td-${pingerId}-mtr-report-show`)
|| document.querySelector(`.mtr-toggle[data-pinger-id="${cssAttrEscape(pingerId)}"]`);
if (pingRow && pingRow.getAttribute('data-mtr-visible') !== '1' && showButton) {
showButton.click();
}
window.setTimeout(() => {
const target = document.getElementById(`tr-${pingerId}-mtr-report`)
|| document.getElementById(`ping-${pingerId}-tr`);
if (!target) return;
target.scrollIntoView({ behavior: 'smooth', block: 'center' });
highlightRows(pingerId);
}, 180);
}
function highlightRows(pingerId) {
const rows = [
document.getElementById(`ping-${pingerId}-tr`),
document.getElementById(`tr-${pingerId}-mtr-report`),
].filter(Boolean);
rows.forEach(row => row.classList.add('pgr-jump-highlight'));
window.setTimeout(() => rows.forEach(row => row.classList.remove('pgr-jump-highlight')), 2200);
}
function buildTooltip(group, fallbackName) {
if (!group) {
return `
<div class="pgr-tooltip">
<div class="pgr-tooltip-title">${escapeHtml(fallbackName || '-')}</div>
<div class="pgr-tooltip-body">暂无 ping.pe 节点数据<br>点击其它染色区域查看明细</div>
</div>
`;
}
const rows = group.rows.slice().sort((a, b) => normalizedLatency(a.value) - normalizedLatency(b.value));
const fastest = rows.find(item => Number.isFinite(item.value));
const maxLines = 8;
return `
<div class="pgr-tooltip">
<div class="pgr-tooltip-title">${escapeHtml(group.country)}</div>
<div class="pgr-tooltip-body">
<div>最快响应:${fastest ? escapeHtml(formatValue(fastest.value)) : '等待中/超时'}</div>
<div>节点数量:${rows.length}</div>
${rows.slice(0, maxLines).map(item => `
<div class="pgr-tooltip-line">
<span class="pgr-tooltip-isp">[${escapeHtml(shortProvider(item.provider || item.id))}]</span>
<span>${escapeHtml(item.city)}</span>
<span class="pgr-tooltip-value">${escapeHtml(formatValue(item.value))}</span>
</div>
`).join('')}
${rows.length > maxLines ? `<div>还有 ${rows.length - maxLines} 条线路,点击国家看完整明细...</div>` : ''}
<div class="pgr-tooltip-hint">--- 滚轮缩放,拖动平移,点击看城市 ---</div>
</div>
</div>
`;
}
function zoomBy(factor) {
currentZoom = Math.max(0.9, Math.min(30, currentZoom * factor));
chart.setOption({
geo: [{ id: 'pingpe-geo', zoom: currentZoom, center: currentCenter || undefined }],
}, { lazyUpdate: true });
}
function resetView() {
currentZoom = 1.18;
currentCenter = null;
chart.setOption({
geo: [{ id: 'pingpe-geo', zoom: currentZoom, center: undefined }],
}, { lazyUpdate: true });
}
function buildSignature(groups, totalRows) {
const parts = [lastMetric, totalRows];
Array.from(groups.keys()).sort().forEach(country => {
const group = groups.get(country);
parts.push(country, Math.round((Number.isFinite(group.value) ? group.value : -1) * 100), group.rows.length, group.bucket.key);
});
return parts.join('|');
}
function updateStatus(total, mapped, skipped, timeoutRows) {
const status = document.querySelector(`#${PANEL_ID} [data-role="status"]`);
if (!status) return;
const fastRows = Array.from(document.querySelectorAll('tr.ping-result-row')).filter(row => {
const value = parseLatency(row.dataset[lastMetric]);
return Number.isFinite(value) && value <= 50;
}).length;
status.textContent = ` · mapped ${mapped}/${total}, skipped ${skipped}, timeout ${timeoutRows}, <=50ms ${fastRows}`;
}
function pickDefaultCountry(groups) {
if (groups.has('China')) return 'China';
if (groups.has('United States')) return 'United States';
return groups.keys().next().value || '';
}
function toMapCountry(location) {
const first = clean(location).split(',')[0].trim();
return COUNTRY_ALIAS[first] || first;
}
function classify(value, sent, loss) {
if ((!Number.isFinite(value) && sent > 0) || loss >= 100) return BUCKETS[0];
if (!Number.isFinite(value)) return BUCKETS[6];
if (value > 250) return BUCKETS[1];
if (value > 200) return BUCKETS[2];
if (value > 100) return BUCKETS[3];
if (value > 50) return BUCKETS[4];
return BUCKETS[5];
}
function parseLatency(value) {
if (value == null) return NaN;
const text = String(value).replace(/–|–|—/g, '').replace(/[^\d.-]/g, '').trim();
if (!text) return NaN;
const number = Number.parseFloat(text);
return Number.isFinite(number) ? number : NaN;
}
function parseNumber(value) {
const number = parseLatency(value);
return Number.isFinite(number) ? number : NaN;
}
function normalizedLatency(value) {
return Number.isFinite(value) ? value : Number.POSITIVE_INFINITY;
}
function formatValue(value) {
return Number.isFinite(value) ? `${Math.round(value * 100) / 100} ms` : 'timeout/pending';
}
function shortLocationTitle(location) {
const parts = String(location).split(',').map(s => s.trim()).filter(Boolean);
if (!parts.length) return location;
if ((parts[0] === 'USA' || parts[0] === 'Canada') && parts.length >= 3) return `${parts[1]} ${parts[2]}`;
return parts[parts.length - 1] || parts[0];
}
function shortProvider(provider) {
const text = String(provider || '').trim();
if (text.length <= 14) return text;
return `${text.slice(0, 13)}…`;
}
function clean(value) {
return String(value || '').replace(/\s+/g, ' ').trim();
}
function cssAttrEscape(value) {
if (window.CSS && typeof window.CSS.escape === 'function') {
return window.CSS.escape(String(value));
}
return String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
}
function escapeHtml(value) {
return String(value)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
boot();
})();
更新
1.0.0版本发现香港台湾等地区无法点击,现在加上了小圆点便于点击了
牛的很
好东西,直观很多
鸡腿奉上!
很好 给个鸡腿
Cool
感谢,加鸡腿
1.0.0版本发现香港台湾等地区无法点击,现在加上了小圆点便于点击了,需要的重新复制脚本就好了哦