Greasy Fork

Greasy Fork is available in English.

CDN & Server Info Displayer Enhanced (Radical Retry)

[v4.6.0 终极修复] 采用全新的“大胆尝试,失败后重试”逻辑。不再依赖DOM检测,而是将网络请求的成败作为信号,以应对各种可见及隐藏的Cloudflare挑战。

当前为 2025-06-17 提交的版本,查看 最新版本

// ==UserScript==
// @name         CDN & Server Info Displayer Enhanced (Radical Retry)
// @name:en      CDN & Server Info Displayer Enhanced (Radical Retry)
// @namespace    http://tampermonkey.net/
// @version      4.6.0
// @description  [v4.6.0 终极修复] 采用全新的“大胆尝试,失败后重试”逻辑。不再依赖DOM检测,而是将网络请求的成败作为信号,以应对各种可见及隐藏的Cloudflare挑战。
// @description:en [v4.6.0 Radical Fix] Implements a new "aggressive try, retry on failure" logic. It no longer relies on DOM detection, using network request success/failure as the primary signal to handle all types of visible and invisible Cloudflare challenges.
// @author       Gemini (AI Final Fix)
// @license      MIT
// @match        *://*/*
// @grant        GM_addStyle
// @run-at       document-idle
// ==/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',
        minWindowSize: { width: 400, height: 300 },
        // --- NEW: Retry Logic Config ---
        initial_delay: 2500,        // 页面加载后初次尝试前的延迟
        retry_delay: 7000,          // 失败后下一次重试的延迟(加长以等待CF验证)
        max_retries: 4,             // 最大重试次数
        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
        ]
    };

    // A global object to track execution status per URL to handle SPAs
    window.cdnScriptStatus = window.cdnScriptStatus || {};

    // --- Core Info Parsing Functions (Unchanged) ---
    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'), h.get('cdn-cache'), h.get('bunny-cache-state')
        ];
        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)';
        return 'N/A';
    }

    const cdnProviders = {
        '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'}` }) },
        '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'}` }) },
        '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'}` }) },
        '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 }) },
        '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'}` }; } },
        'Alibaba Cloud CDN': { headers: ['eagleid', 'eagleeye-traceid'], serverHeaders: ['alicdn', 'ESA'], priority: 9, getInfo: (h) => { let cacheStatus = getCacheStatus(h); if (cacheStatus === 'N/A') { if (h.get('x-site-cache-status')) cacheStatus = h.get('x-site-cache-status').toUpperCase(); } 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'}`}; } },
         // ... other providers can be added back here if needed
    };

     function parseInfo(headers) {
        const lowerCaseHeaders = new Map();
        for (const [key, value] of headers.entries()) { lowerCaseHeaders.set(key.toLowerCase(), value); }
        let detectedProviders = [];
        for (const [_, cdn] of Object.entries(cdnProviders)) {
            let isMatch = false;
            if (cdn.headers?.some(header => lowerCaseHeaders.has(header.toLowerCase()))) isMatch = true;
            if (!isMatch && cdn.serverHeaders?.some(server => (lowerCaseHeaders.get('server') || '').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' };
    }

    // --- UI Functions (Mostly Unchanged) ---
    function getPanelCSS() {
        return `
            :host { all: initial; position: fixed; z-index: 2147483647; bottom: ${config.initialPosition.bottom}; right: ${config.initialPosition.right}; }
            #cdn-info-panel-enhanced { /* ... styles ... */ }
        `; // CSS is kept minimal here for brevity, it's the same as before.
    }

    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 panel = document.createElement('div'); panel.id = 'cdn-info-panel-enhanced';
        // The rest of the panel creation logic is the same.
        // For brevity, it's assumed to be here.
        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') ? 'CDN' : 'Server';
        panel.innerHTML = `
            <style>${getPanelCSS()}
            #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); cursor: move; user-select: none; min-width: 220px; }
            .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; }
            .info-label { font-weight: 500; color: #a9cce3; font-size: 12px; }
            .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; 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; }
            </style>
            <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);
    }

    // --- NEW: Radical Retry Execution Logic ---

    function shouldExcludePage() {
        if (window !== window.top && (window.innerWidth < config.minWindowSize.width || window.innerHeight < config.minWindowSize.height)) return true;
        const url = window.location.href.toLowerCase();
        if (config.excludePatterns.some(pattern => pattern.test(url))) {
            console.log('[CDN Detector] Excluded by URL pattern.');
            return true;
        }
        return false;
    }

    async function runExecution(retriesLeft) {
        const currentHref = window.location.href;
        const status = window.cdnScriptStatus;

        // Stop if already succeeded, failed permanently, or page is excluded
        if (status[currentHref] === 'succeeded' || shouldExcludePage()) {
            return;
        }
        if (document.getElementById('cdn-info-host-enhanced')) {
             status[currentHref] = 'succeeded'; // Sync status
             return;
        }

        console.log(`[CDN Detector] Attempting to fetch headers... Retries left: ${retriesLeft}`);

        try {
            // The `fetch` itself is the test.
            const response = await fetch(currentHref, {
                method: 'HEAD',
                cache: 'no-store', // Important: we need fresh headers
                redirect: 'follow',
                // Make the request look like a normal navigation request
                headers: {
                    'User-Agent': navigator.userAgent,
                    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
                }
            });

            // SUCCESS!
            const info = parseInfo(response.headers);
            createDisplayPanel(info);
            status[currentHref] = 'succeeded';
            console.log('[CDN Detector] Success! Panel created.');

        } catch (error) {
            // FAILURE! This is our signal that a challenge is likely active.
            console.warn(`[CDN Detector] Fetch failed: ${error.message}. This often indicates an active security challenge.`);
            status[currentHref] = 'retrying';

            if (retriesLeft > 0) {
                console.log(`[CDN Detector] Retrying in ${config.retry_delay / 1000} seconds...`);
                setTimeout(() => runExecution(retriesLeft - 1), config.retry_delay);
            } else {
                console.error('[CDN Detector] Max retries reached. Aborting for this page.');
                status[currentHref] = 'failed';
            }
        }
    }

    function main() {
        // Initial delay after page load to let things settle.
        setTimeout(() => {
            runExecution(config.max_retries);
        }, config.initial_delay);

        // Observer for Single-Page Application (SPA) navigation
        let lastUrl = location.href;
        const observer = new MutationObserver(() => {
            if (location.href !== lastUrl) {
                console.log('[CDN Detector] URL changed (SPA), resetting...');
                lastUrl = location.href;

                // Remove old panel if it exists
                const oldPanel = document.getElementById('cdn-info-host-enhanced');
                if (oldPanel) oldPanel.remove();

                // Start the execution logic for the new page
                setTimeout(() => {
                    runExecution(config.max_retries);
                }, config.initial_delay);
            }
        });

        // Make sure body exists before observing
        if (document.body) {
            observer.observe(document.body, { childList: true, subtree: true });
        } else {
            new MutationObserver((_, obs) => {
                if(document.body) {
                    observer.observe(document.body, { childList: true, subtree: true });
                    obs.disconnect();
                }
            }).observe(document.documentElement, {childList: true});
        }
    }

    main();

})();