// ==UserScript==
// @name CDN & Server Info Displayer Enhanced (增强版CDN及服务器信息显示)
// @name:en CDN & Server Info Displayer Enhanced
// @namespace http://tampermonkey.net/
// @version 4.1.0
// @description [v4.1.0] The definitive stable version. Using Shadow DOM for complete style isolation and a massively expanded CDN detection library.
// @description:en [v4.1.0] The definitive stable version. Using Shadow DOM for complete style isolation and a massively expanded CDN detection library.
// @author Gemini
// @license MIT
// @match *://*/*
// @grant GM_addStyle
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
// --- Configuration ---
const config = {
initialPosition: { bottom: '15px', right: '15px' },
panelBgColor: 'rgba(44, 62, 80, 0.95)',
panelTextColor: '#ffffff',
borderColor: '#3498db',
opacity: '0.9',
animationDuration: '0.3s',
minWindowSize: { width: 400, height: 300 },
excludePatterns: [
/(\/|&)pay(pal|ment)/i,
/\/checkout|\/billing/i,
/\/login|\/signin|\/auth/i,
/\/phpmyadmin/i,
/(\/ads\/|ad_id=|advertisement)/i,
/doubleclick\.net/i,
/facebook\.com\/plugins/i,
/twitter\.com\/widgets/i,
/\/popup|\/modal/i
]
};
// --- Helper function to get cache status ---
function getCacheStatus(h) {
const headersToCheck = [
h.get('cache-status'), h.get('x-cache-status'), h.get('x-cache'),
h.get('x-edge-cache-status'), h.get('x-sucuri-cache'), h.get('x-vercel-cache'),
h.get('x-qc-cache'), h.get('cf-cache-status'), h.get('x-response-cache'),
h.get('x-bdcdn-cache-status')
];
for (const value of headersToCheck) {
if (!value) continue;
const upperVal = value.toUpperCase();
if (upperVal.includes('HIT')) return 'HIT';
if (upperVal.includes('MISS')) return 'MISS';
if (upperVal.includes('BYPASS')) return 'BYPASS';
if (upperVal.includes('DYNAMIC')) return 'DYNAMIC';
if (upperVal.includes('UPDATING')) return 'UPDATING';
if (upperVal.includes('STALE')) return 'STALE';
if (upperVal.includes('EXPIRED')) return 'EXPIRED';
}
if (parseInt(h.get('age'), 10) > 0) return 'HIT (inferred)';
let hitCount = 0;
for (const value of h.values()) {
if (value.toLowerCase() === 'hit') hitCount++;
}
if (hitCount >= 3) return 'HIT (inferred)';
return 'N/A';
}
// --- CDN Provider Detection Rules ---
const cdnProviders = {
// --- Highest Priority ---
'ByteDance Edge': {
headers: ['x-tt-trace-tag', 'x-bytefaas-request-id'], priority: 11,
getInfo: (h) => {
let cache = 'N/A';
const traceTag = h.get('x-tt-trace-tag') || '';
const match = traceTag.match(/cdn-cache=([^;]+)/);
if (match && match[1]) {
cache = match[1].toUpperCase();
} else {
cache = getCacheStatus(h);
}
return { provider: 'ByteDance Edge', cache: cache, pop: 'N/A', extra: `Trace ID: ${h.get('x-tt-logid') || 'N/A'}` };
}
},
'Baidu Cloud CDN': {
headers: ['x-bdcdn-cache-status'], priority: 10,
getInfo: (h) => ({
provider: 'Baidu Cloud CDN',
cache: getCacheStatus(h),
pop: 'N/A',
extra: `Req ID: ${h.get('x-request-id') || 'N/A'}`
})
},
'Cloudflare': {
headers: ['cf-ray'], serverHeaders: ['cloudflare'], priority: 10,
getInfo: (h) => ({ provider: 'Cloudflare', cache: h.get('cf-cache-status')?.toUpperCase() || 'N/A', pop: h.get('cf-ray')?.slice(-3).toUpperCase() || 'N/A', extra: `Ray ID: ${h.get('cf-ray') || 'N/A'}` })
},
'Tencent EdgeOne': {
serverHeaders: ['TencentEdgeOne'], headers: ['eo-cache-status'], priority: 10,
getInfo: (h) => ({ provider: 'Tencent EdgeOne', cache: h.get('eo-cache-status')?.toUpperCase() || 'N/A', pop: 'N/A', extra: `Log-UUID: ${h.get('eo-log-uuid') || 'N/A'}` })
},
'Imperva': {
headers: ['incap-proxy-id'], priority: 10,
getInfo: (h) => ({ provider: 'Imperva', cache: h.get('x-iinfo') ? 'HIT' : getCacheStatus(h), pop: h.get('incap-origin-pop') || h.get('incap-pop') || 'N/A', extra: `Proxy ID: ${h.get('incap-proxy-id')?.split(' ')[0] || 'N/A'}` })
},
'Sucuri': {
headers: ['x-sucuri-id'], priority: 10,
getInfo: (h) => ({ provider: 'Sucuri', cache: getCacheStatus(h), pop: 'N/A', extra: `Firewall ID: ${h.get('x-sucuri-id') || 'N/A'}` })
},
'Netlify': {
headers: ['x-nf-request-id'], priority: 10,
getInfo: (h) => ({ provider: 'Netlify', cache: getCacheStatus(h), pop: 'N/A', extra: `Request ID: ${h.get('x-nf-request-id') || 'N/A'}` })
},
'QUIC.cloud': {
headers: ['x-qc-pop'], priority: 10,
getInfo: (h) => {
let pop = h.get('x-qc-pop') || 'N/A';
const match = pop.match(/[A-Z]{3}/);
pop = match ? match[0] : pop;
return { provider: 'QUIC.cloud', cache: getCacheStatus(h), pop: pop, extra: null };
}
},
'Vercel': {
headers: ['x-vercel-id'], priority: 10,
getInfo: (h) => {
let pop = 'N/A';
const vercelId = h.get('x-vercel-id');
if (vercelId) {
const regionPart = vercelId.split('::')[0];
const match = regionPart.match(/^[a-zA-Z]+/);
if (match) pop = match[0].toUpperCase();
}
return { provider: 'Vercel', cache: getCacheStatus(h), pop: pop, extra: `ID: ${h.get('x-vercel-id') || 'N/A'}` };
}
},
// --- High Priority ---
'Fastly': {
headers: ['x-fastly-request-id', 'x-served-by'], priority: 9,
getInfo: (h) => ({ provider: 'Fastly', cache: getCacheStatus(h), pop: h.get('x-served-by')?.split('-').pop() || 'N/A', extra: `ReqID: ${h.get('x-fastly-request-id') || 'N/A'}` })
},
'AWS CloudFront': {
headers: ['x-amz-cf-pop', 'x-amz-cf-id'], priority: 9,
getInfo: (h) => ({ provider: 'AWS CloudFront', cache: getCacheStatus(h), pop: (h.get('x-amz-cf-pop') || 'N/A').substring(0,3), extra: `CF ID: ${h.get('x-amz-cf-id') || 'N/A'}` })
},
'Qiniu CDN': {
headers: ['x-qiniu-zone', 'x-qnm-cache'], priority: 9,
getInfo: (h) => ({ provider: 'Qiniu CDN', cache: h.get('x-qnm-cache')?.toUpperCase() || getCacheStatus(h), pop: `Zone ${h.get('x-qiniu-zone') || 'N/A'}`, extra: `ReqID: ${h.get('x-reqid') || 'N/A'}` })
},
'Alibaba Cloud CDN': {
headers: ['eagleid', 'eagleeye-traceid'], serverHeaders: ['alicdn', 'ESA'], priority: 9,
getInfo: (h) => {
let cacheStatus = getCacheStatus(h);
if (cacheStatus === 'N/A' || cacheStatus.includes('(inferred)')) {
const siteCacheStatus = h.get('x-site-cache-status')?.toUpperCase();
const serverTiming = h.get('server-timing') || '';
const via = h.get('via') || '';
if (siteCacheStatus) cacheStatus = siteCacheStatus;
else if (serverTiming.includes('fetch-origin')) cacheStatus = 'MISS (inferred)';
else if (via.includes('[') && via.includes(',0]')) cacheStatus = 'MISS (inferred)';
}
const traceId = h.get('eagleid')?.split(',')[0] || h.get('eagleeye-traceid');
return { provider: 'Alibaba Cloud CDN', cache: cacheStatus, pop: 'N/A', extra: `TraceID: ${traceId || 'N/A'}` };
}
},
'Tencent Cloud CDN': {
headers: ['x-cache-lookup', 'x-nws-log-uuid'], serverHeaders: ['nws'], priority: 9,
getInfo: (h) => {
let cache = (h.get('x-cache-lookup') || '').split(',')[0].toUpperCase();
cache = cache ? cache.replace('CACHE ', '') : getCacheStatus(h);
return { provider: 'Tencent Cloud CDN', cache: cache, pop: 'N/A', extra: `Log-UUID: ${h.get('x-nws-log-uuid') || 'N/A'}`};
}
},
'Akamai': {
headers: ['x-akamai-transformed', 'x-check-cacheable'], serverHeaders:['AkamaiGHost'], priority: 9,
getInfo: (h) => ({ provider: 'Akamai', cache: (h.get('x-check-cacheable') || getCacheStatus(h))?.toUpperCase() || 'N/A', pop: 'N/A', extra: h.get('x-akamai-transformed') ? 'Content transformed' : null })
},
'Azure CDN': {
headers: ['x-azure-ref'], serverHeaders:['ECAcc', 'ECS'], priority: 9,
getInfo: (h) => ({ provider: 'Azure CDN', cache: getCacheStatus(h), pop: 'N/A', extra: `Ref: ${h.get('x-azure-ref') || 'N/A'}` })
},
'CDN77': {
headers: ['x-77-cache'], priority: 9,
getInfo: (h) => ({ provider: 'CDN77', cache: h.get('x-77-cache')?.toUpperCase() || 'N/A', pop: h.get('x-77-pop')?.toUpperCase() || 'N/A', extra: `Ray: ${h.get('x-77-nzt-ray') || 'N/A'}` })
},
'BunnyCDN': {
serverHeaders: ['BunnyCDN'], headers: ['bunny-cdn-pullzone', 'bunny-pop'], priority: 9,
getInfo: (h) => ({ provider: 'BunnyCDN', cache: h.get('bunny-cache-state')?.toUpperCase() || 'N/A', pop: h.get('bunny-pop') || 'N/A', extra: `Pullzone: ${h.get('bunny-cdn-pullzone') || 'N/A'}` })
},
'KeyCDN': {
serverHeaders: ['keycdn'], priority: 8,
getInfo: (h) => ({ provider: 'KeyCDN', cache: getCacheStatus(h), pop: h.get('x-edge-location')?.toUpperCase() || 'N/A', extra: null })
},
// --- Fallback / Generic ---
'Varnish': {
headers: ['x-varnish'], priority: 5,
getInfo: (h) => ({ provider: 'Varnish (Cache)', cache: h.get('x-varnish-cache')?.toUpperCase() || getCacheStatus(h), pop: 'N/A', extra: `ID: ${h.get('x-varnish')?.split(' ')[0] || 'N/A'}` })
},
'LiteSpeed': {
serverHeaders: ['litespeed'], priority: 1,
getInfo: (h) => ({ provider: 'LiteSpeed', cache: getCacheStatus(h), pop: 'N/A', extra: 'No CDN detected' })
}
};
function parseInfo(headers) { const lowerCaseHeaders = new Map(); for (const [key, value] of headers.entries()) { lowerCaseHeaders.set(key.toLowerCase(), value); } let detectedProviders = []; for (const [cdnName, cdn] of Object.entries(cdnProviders)) { let isMatch = false; if (cdn.headers?.some(header => lowerCaseHeaders.has(header.toLowerCase()))) { isMatch = true; } if (!isMatch && cdn.serverHeaders) { const serverHeader = lowerCaseHeaders.get('server') || ''; if (cdn.serverHeaders.some(server => serverHeader.toLowerCase().includes(server.toLowerCase()))) isMatch = true; } if (isMatch) { detectedProviders.push({ ...cdn.getInfo(lowerCaseHeaders), priority: cdn.priority || 5 }); } } if (detectedProviders.length > 0) { detectedProviders.sort((a, b) => b.priority - a.priority); return detectedProviders[0]; } const server = lowerCaseHeaders.get('server'); if (server) { return { provider: server, cache: getCacheStatus(lowerCaseHeaders), pop: 'N/A', extra: 'No CDN detected' }; } return { provider: 'Unknown', cache: 'N/A', pop: 'N/A', extra: 'No CDN or Server info' }; }
function getPanelCSS() {
return `
:host { all: initial; position: fixed; z-index: 99999; bottom: ${config.initialPosition.bottom}; right: ${config.initialPosition.right}; }
#cdn-info-panel-enhanced { padding: 12px 16px; border-radius: 12px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 13px; color: ${config.panelTextColor}; background: ${config.panelBgColor}; backdrop-filter: blur(10px); border: 1px solid ${config.borderColor}; box-shadow: 0 8px 32px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.1); cursor: move; user-select: none; transition: opacity ${config.animationDuration}, transform ${config.animationDuration}; opacity: ${config.opacity}; transform: scale(1); min-width: 220px; }
#cdn-info-panel-enhanced:hover { opacity: 1; transform: scale(1.02); }
.panel-header { font-weight: 600; font-size: 14px; margin-bottom: 8px; color: ${config.borderColor}; text-align: center; border-bottom: 1px solid rgba(52, 152, 219, 0.3); padding-bottom: 6px; }
.info-line { display: flex; justify-content: space-between; align-items: center; margin: 6px 0; padding: 2px 0; }
.info-label { font-weight: 500; color: #a9cce3; font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px; }
.info-value { font-weight: 600; max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: right; }
.extra-info { font-size: 11px; color: #95a5a6; font-style: italic; text-align: center; margin-top: 8px; padding-top: 6px; border-top: 1px solid rgba(149, 165, 166, 0.2); word-break: break-all; }
.cache-HIT { color: #2ecc71 !important; }
.cache-MISS, .cache-BYPASS { color: #e74c3c !important; }
.close-btn { position: absolute; top: -8px; right: -8px; width: 20px; height: 20px; border-radius: 50%; background: #e74c3c; color: white; border: none; cursor: pointer; font-size: 12px; display: none; align-items: center; justify-content: center; transition: all 0.2s; }
#cdn-info-panel-enhanced:hover .close-btn { display: flex; }
.close-btn:hover { background: #c0392b; transform: scale(1.1); }
`;
}
function createDisplayPanel(info) {
if (!info || document.getElementById('cdn-info-host-enhanced')) return;
const host = document.createElement('div');
host.id = 'cdn-info-host-enhanced';
document.body.appendChild(host);
const shadowRoot = host.attachShadow({ mode: 'open' });
const styleEl = document.createElement('style');
styleEl.textContent = getPanelCSS();
shadowRoot.appendChild(styleEl);
const panel = document.createElement('div');
panel.id = 'cdn-info-panel-enhanced';
const cacheStatus = info.cache.toUpperCase();
let cacheClass = '';
if (cacheStatus.includes('HIT')) cacheClass = 'cache-HIT';
else if (cacheStatus.includes('MISS') || cacheStatus.includes('BYPASS')) cacheClass = 'cache-MISS';
const providerLabel = info.provider.includes('CDN') || info.provider.includes('Cloud') || info.provider.includes('Edge') || info.provider.includes('(inferred)') || info.provider.includes('(Cache)') ? 'CDN' : 'Server';
panel.innerHTML = `
<button class="close-btn" title="Close">×</button>
<div class="panel-header">CDN & Server Info</div>
<div class="info-line"><span class="info-label">${providerLabel}</span><span class="info-value" title="${info.provider}">${info.provider}</span></div>
<div class="info-line"><span class="info-label">Cache</span><span class="info-value ${cacheClass}">${info.cache}</span></div>
<div class="info-line"><span class="info-label">POP</span><span class="info-value" title="${info.pop}">${info.pop}</span></div>
${info.extra ? `<div class="extra-info" title="${info.extra}">${info.extra}</div>` : ''}
`;
shadowRoot.appendChild(panel);
shadowRoot.querySelector('.close-btn').addEventListener('click', () => host.remove());
makeDraggable(host);
}
function makeDraggable(element) {
let isDragging = false, startX = 0, startY = 0, elementX = 0, elementY = 0;
element.shadowRoot.addEventListener('mousedown', (e) => {
if (e.target.classList.contains('close-btn')) return;
isDragging = true; startX = e.clientX; startY = e.clientY;
const rect = element.getBoundingClientRect(); elementX = rect.left; elementY = rect.top;
document.addEventListener('mousemove', drag); document.addEventListener('mouseup', dragEnd);
});
function drag(e) {
if (!isDragging) return; e.preventDefault();
const newX = elementX + e.clientX - startX; const newY = elementY + e.clientY - startY;
const maxX = window.innerWidth - element.offsetWidth; const maxY = window.innerHeight - element.offsetHeight;
element.style.left = `${Math.max(0, Math.min(newX, maxX))}px`; element.style.top = `${Math.max(0, Math.min(newY, maxY))}px`;
element.style.right = 'auto'; element.style.bottom = 'auto';
}
function dragEnd() { isDragging = false; document.removeEventListener('mousemove', drag); document.removeEventListener('mouseup', dragEnd); }
}
function isElementVisible(el) { if (!el) return false; const rect = el.getBoundingClientRect(); return (rect.width > 1 && rect.height > 1 && getComputedStyle(el).visibility !== 'hidden' && getComputedStyle(el).display !== 'none'); }
function shouldShowPanel() {
if (window !== window.top && (window.innerWidth < config.minWindowSize.width || window.innerHeight < config.minWindowSize.height)) return false;
const url = window.location.href.toLowerCase();
if (config.excludePatterns.some(pattern => pattern.test(url))) {
console.log(`[CDN Detector] URL (${url}) matches exclude pattern. Skipping.`); return false;
}
const captchaSelectors = {
strict: [/challenge/i, /verify/i, /security check/i, /just a moment/i],
lenient: [
'[id*="recaptcha"]', '[class*="recaptcha"]',
'[id*="hcaptcha"]', '[class*="hcaptcha"]',
'[class*="cf-turnstile"]', '[id*="turnstile"]',
'[id*="cf-challenge"]', '#cf-wrapper'
]
};
if (captchaSelectors.strict.some(pattern => pattern.test(document.title.toLowerCase()))) {
console.log('[CDN Detector] Page title indicates a challenge. Skipping.'); return false;
}
if (captchaSelectors.lenient.some(selector => { const el = document.querySelector(selector); if (el && isElementVisible(el)) { console.log(`[CDN Detector] Found a VISIBLE captcha element: ${selector}. Skipping.`); return true; } return false; })) {
return false;
}
const excludeDomains = ['accounts.google.com', 'login.microsoftonline.com', 'challenges.cloudflare.com'];
if (excludeDomains.some(domain => window.location.hostname.toLowerCase().includes(domain))) {
console.log(`[CDN Detector] Excluded domain. Skipping.`); return false;
}
return true;
}
function detectFromDOM() { try { const html = document.documentElement.innerHTML.toLowerCase(); if (html.includes('/cdn-cgi/') || html.includes('cloudflare.com/privacypolicy')) { createDisplayPanel({ provider: 'Cloudflare (inferred)', cache: 'N/A', pop: 'N/A', extra: 'From page content' }); return true; } const vercelLink = Array.from(document.querySelectorAll('a[href="https://vercel.com"]')).find(a => a.innerText.toLowerCase().includes('powered by')); if (vercelLink) { createDisplayPanel({ provider: 'Vercel (inferred)', cache: 'N/A', pop: 'N/A', extra: 'From page footer' }); return true; } } catch (e) { console.error('[CDN Detector] DOM detection failed:', e); } return false; }
async function main() {
if (document.readyState !== "complete") {
await new Promise(resolve => window.addEventListener("load", resolve, { once: true }));
}
if (!shouldShowPanel()) {
console.log('[CDN Detector] Page does not meet display criteria. Script will not run.'); return;
}
try {
const response = await fetch(window.location.href, { method: 'HEAD', cache: 'no-cache', redirect: 'follow' });
const info = parseInfo(response.headers);
createDisplayPanel(info);
} catch (error) {
console.warn('[CDN Detector] HEAD request failed, trying DOM-based fallback.', error.message);
if (!detectFromDOM()) {
createDisplayPanel({ provider: 'Detection Limited', cache: 'N/A', pop: 'N/A', extra: 'CORS policy may have blocked analysis' });
}
}
}
main().catch(error => { console.error('[CDN Detector] Initialization failed:', error); });
})();