Greasy Fork

Greasy Fork is available in English.

页面资源监控

智能检测JS/CSS是否返回HTML错误页(基于内容分析),支持异常分级查看、自动刷新、日志归档,并智能忽略常见合法但非传统格式的资源(如CSS变量、Iconfont、Webpack/Vite打包JS、ICE资产清单等)。已修复正则错误并增强识别能力。

当前为 2026-02-05 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         页面资源监控
// @namespace    resourceMonitor
// @version      1.4.10
// @description  智能检测JS/CSS是否返回HTML错误页(基于内容分析),支持异常分级查看、自动刷新、日志归档,并智能忽略常见合法但非传统格式的资源(如CSS变量、Iconfont、Webpack/Vite打包JS、ICE资产清单等)。已修复正则错误并增强识别能力。
// @author       mozkoe
// @match        *://*/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=greasyfork.org
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    const DEFAULT_CONFIG = {
        enabled: false,
        refreshInterval: 3000,
        checkTimeout: 2500,
        maxRecordsPerSlot: 150,
        monitorJS: true,
        monitorCSS: true,
    };

    const CONFIG_KEY = 'resourceMonitorConfig_v1_1';
    const MAIN_LOG_KEY = 'resourceCheckLog_v1_1';
    const BACKUP_LOG_KEY = 'resourceCheckLog_v1_2';

    let CONFIG = { ...DEFAULT_CONFIG };
    let isMonitoring = false;

    // =============== 内容类型辅助判断 ===============
    function looksLikeHTML(content) {
        const sample = content.trim().substring(0, 500).toLowerCase();
        return /<!doctype|<html|<head|<body|<meta\s+|<title|<script\s+(?!type=['"]?module['"]?)|<link\s+rel=|<div\s+class=/.test(sample);
    }

    function looksLikeJS(content) {
        const trimmed = content.trim();
        if (!trimmed) return false;

        // 1. 忽略 Iconfont 格式的 SVG 字符串
        if (/window\._iconfont_svg_string_\d+\s*=\s*'<svg>/.test(trimmed)) {
            return true;
        }

        // 2. 新增:忽略 ICE / 微前端资产清单(如 window.__ICE_ASSETS_MANIFEST__ = { ... })
        if (/^\s*window\.__[A-Z_]+_MANIFEST__\s*=\s*\{/.test(trimmed)) {
            return true;
        }

        // 3. Webpack 风格:含模块ID或 e.exports
        if (
            /^\s*!(?:async\s+)?(?:function|\(\s*\(\s*\)\s*=>|\(\s*function\b)/.test(trimmed) &&
            (trimmed.includes('e.exports') || /\b\d+\s*:\s*function\s*\([^)]*\)\s*\{/.test(trimmed))
        ) {
            return true;
        }

        // 4. 纯 IIFE 打包 JS(Vite/Rollup 等,无 exports,但含 Object.defineProperty/create)
        if (
            /^\s*!(?:async\s+)?(?:function|\(\s*function\b)/.test(trimmed) &&
            (trimmed.includes('Object.defineProperty') || trimmed.includes('Object.create'))
        ) {
            return true;
        }

        // 5. 传统 JS 特征(变量、函数、ESM、严格模式等)
        const head = trimmed.substring(0, 300).replace(/\s+/g, ' ');
        if (/^(\s*\/\*|\s*\/\/|\s*var\s+|\s*let\s+|\s*const\s+|\s*function\s+|\s*import\s+|\s*export\s+|\s*\{|\s*\(|\s*"use strict")/.test(head)) {
            return true;
        }

        return false;
    }

    function looksLikeCSS(content) {
        const trimmed = content.trim();
        if (!trimmed) return false;

        // 忽略以 :root 或 CSS 变量 (--xxx) 开头的合法 CSS
        if (/:root\s*\{|--\w+\s*:/.test(trimmed)) {
            return true;
        }

        // 常规 CSS 规则匹配
        const sample = trimmed.substring(0, 300);
        return /[\w.#:\[][^{}]*\{[^{}]*\}|@media|@import|@keyframes|@charset|@font-face/.test(sample);
    }

    // =============== 配置管理 ===============
    function loadConfig() {
        try {
            const saved = localStorage.getItem(CONFIG_KEY);
            if (saved) {
                CONFIG = { ...DEFAULT_CONFIG, ...JSON.parse(saved) };
            } else {
                CONFIG = { ...DEFAULT_CONFIG };
            }
        } catch (e) {
            console.warn('[⚠️] 配置加载失败,使用默认配置');
            CONFIG = { ...DEFAULT_CONFIG };
            saveConfig();
        }
    }

    function saveConfig() {
        try {
            localStorage.setItem(CONFIG_KEY, JSON.stringify(CONFIG));
        } catch (e) {
            console.error('[💥] 保存配置失败:', e);
        }
    }

    // =============== 存储工具 ===============
    function getStorage(key) {
        try {
            return JSON.parse(localStorage.getItem(key) || '[]');
        } catch (e) {
            console.warn(`[⚠️] 解析 ${key} 失败,已清除`);
            localStorage.removeItem(key);
            return [];
        }
    }

    function safeSetItem(key, data) {
        try {
            localStorage.setItem(key, JSON.stringify(data));
            return true;
        } catch (e) {
            return e.name === 'QuotaExceededError';
        }
    }

    // =============== 强化日志存储 ===============
    function saveFullLog(logEntries) {
        const logRecord = {
            timestamp: Date.now(),
            timeStr: new Date().toLocaleString('zh-CN'),
            entries: logEntries
        };

        let mainData = getStorage(MAIN_LOG_KEY);
        let backupData = getStorage(BACKUP_LOG_KEY);

        mainData.unshift(logRecord);
        if (mainData.length > CONFIG.maxRecordsPerSlot) {
            mainData = mainData.slice(0, CONFIG.maxRecordsPerSlot);
        }

        if (safeSetItem(MAIN_LOG_KEY, mainData)) {
            return;
        }

        console.warn('[🔄] 主槽写入失败,切换至备用槽...');
        const merged = [logRecord, ...mainData, ...backupData];
        const totalLimit = CONFIG.maxRecordsPerSlot * 2;
        const finalData = merged.length > totalLimit ? merged.slice(0, totalLimit) : merged;

        localStorage.removeItem(MAIN_LOG_KEY);

        if (safeSetItem(BACKUP_LOG_KEY, finalData)) {
            console.log(`[✅] 已存入备用槽,共 ${finalData.length} 条记录`);
        } else {
            console.error('[💥] 备用槽也写入失败!仅保留最新记录');
            localStorage.setItem(BACKUP_LOG_KEY, JSON.stringify([logRecord]));
        }
    }

    // =============== 主检查逻辑(含内容校验)===============
    function runCheckWithOriginalStyle() {
        const startTime = Date.now();
        const jsResources = CONFIG.monitorJS
            ? Array.from(document.querySelectorAll('script[src]')).map(el => el.src).filter(Boolean)
            : [];
        const cssResources = CONFIG.monitorCSS
            ? Array.from(document.querySelectorAll('link[rel="stylesheet"][href]')).map(el => el.href).filter(Boolean)
            : [];
        const logEntries = [];

        console.log(`\n🔍 开始检查页面资源加载情况 (${new Date().toLocaleTimeString()})`);
        console.log(`════════════════════════════════════════════════════════════════════════════`);

        jsResources.forEach(src => {
            console.log(`[•] 发现JS资源: ${src}`);
            logEntries.push({ type: 'js', action: 'discovered', url: src });
        });
        cssResources.forEach(href => {
            console.log(`[•] 发现CSS资源: ${href}`);
            logEntries.push({ type: 'css', action: 'discovered', url: href });
        });

        const allResources = [
            ...jsResources.map(url => ({ url, expectedType: 'js' })),
            ...cssResources.map(url => ({ url, expectedType: 'css' }))
        ];

        const checkPromises = allResources.map(({ url, expectedType }) => {
            return fetch(url, { credentials: 'omit' })
                .then(response => {
                    const contentType = response.headers.get('Content-Type') || '';
                    return response.text().then(content => ({ contentType, content }));
                })
                .then(({ contentType, content }) => {
                    const isHTMLByHeader = contentType.includes('text/html');
                    const isHTMLByContent = looksLikeHTML(content);

                    let entry = {
                        url,
                        expectedType,
                        contentType,
                    };
                    logEntries.push(entry);

                    if (isHTMLByHeader || isHTMLByContent) {
                        console.error(`[❌] ${url} → 错误: 返回HTML内容`);
                        entry.result = 'html_error';
                    } else if (expectedType === 'js') {
                        if (looksLikeJS(content)) {
                            console.log(`[✅] ${url} → 正常: JS资源`);
                            entry.result = 'js_ok';
                        } else {
                            console.warn(`[⚠️] ${url} → 内容不像JS(但非HTML)`);
                            entry.result = 'js_suspicious';
                        }
                    } else if (expectedType === 'css') {
                        if (looksLikeCSS(content)) {
                            console.log(`[✅] ${url} → 正常: CSS资源`);
                            entry.result = 'css_ok';
                        } else {
                            console.warn(`[⚠️] ${url} → 内容不像CSS(但非HTML)`);
                            entry.result = 'css_suspicious';
                        }
                    } else {
                        console.warn(`[⚠️] ${url} → 未知类型,但非HTML`);
                        entry.result = 'unknown_non_html';
                    }
                })
                .catch(error => {
                    console.error(`[❌] ${url} → 请求失败: ${error.message}`);
                    logEntries.push({
                        url,
                        expectedType,
                        error: error.message,
                        result: 'fetch_error'
                    });
                });
        });

        Promise.race([
            Promise.allSettled(checkPromises),
            new Promise(r => setTimeout(r, CONFIG.checkTimeout))
        ]).finally(() => {
            saveFullLog(logEntries);

            if (isMonitoring) {
                setTimeout(() => {
                    console.log(`\n⏱️ ${CONFIG.refreshInterval}ms 计时结束,正在刷新页面...`);
                    window.location.reload();
                }, Math.max(100, CONFIG.refreshInterval - (Date.now() - startTime)));
            } else {
                console.log(`\n⏹️ 监控已停止,页面将保持静止。`);
            }
        });
    }

    // =============== 控制命令 ===============
    window.startMonitor = function (cmd) {
        if (cmd === 'Y' || cmd === 'y') {
            CONFIG.enabled = true;
            saveConfig();
            isMonitoring = true;
            console.log('%c🚀 监控已启用!配置已保存。', 'color:#4CAF50;font-weight:bold');
            runCheckWithOriginalStyle();
        } else if (cmd === 'Q' || cmd === 'q') {
            CONFIG.enabled = false;
            saveConfig();
            isMonitoring = false;
            console.log('%c⏹️ 监控已禁用。', 'color:#F44336;font-weight:bold');
        } else {
            console.log('❓ 请输入 Y/y 启动,Q/q 退出');
        }
    };

    // =============== 分级错误查看 ===============
    window.showErrors = function (level = 'error') {
        const validLevels = ['error', 'warning'];
        if (!validLevels.includes(level)) {
            console.error(`❌ showErrors() 参数无效。支持: ${validLevels.join(', ')}`);
            return;
        }

        const allRecords = [...getStorage(MAIN_LOG_KEY), ...getStorage(BACKUP_LOG_KEY)];
        if (allRecords.length === 0) {
            console.log('📭 无任何记录');
            return;
        }

        const ERROR_TYPES = ['html_error', 'fetch_error'];
        const WARNING_TYPES = ['js_suspicious', 'css_suspicious', 'unknown_non_html'];

        let targetTypes = ERROR_TYPES;
        if (level === 'warning') {
            targetTypes = [...ERROR_TYPES, ...WARNING_TYPES];
        }

        let hasIssue = false;
        const title = level === 'warning'
            ? '🚨⚠️ 所有异常与可疑资源(按时间倒序):'
            : '🚨 仅严重错误资源(按时间倒序):';

        console.log(`\n${title}`);
        console.log('='.repeat(60));

        for (const record of allRecords.sort((a, b) => b.timestamp - a.timestamp)) {
            const issuesInRecord = record.entries.filter(e =>
                e.result && targetTypes.includes(e.result)
            );
            if (issuesInRecord.length > 0) {
                hasIssue = true;
                console.group(`🕒 ${record.timeStr}`);
                issuesInRecord.forEach(e => {
                    const isWarning = WARNING_TYPES.includes(e.result);
                    const prefix = isWarning ? '[⚠️]' : '[❌]';
                    const style = isWarning
                        ? 'color:#FF9800;font-weight:bold'
                        : 'color:#F44336;font-weight:bold';

                    if (e.result === 'html_error') {
                        console.log(`%c${prefix} ${e.url} → 返回HTML内容`, style);
                    } else if (e.result === 'fetch_error') {
                        console.log(`%c${prefix} ${e.url} → 请求失败: ${e.error}`, style);
                    } else if (e.result === 'js_suspicious') {
                        console.log(`%c${prefix} ${e.url} → 内容不像JS(但非HTML)`, style);
                    } else if (e.result === 'css_suspicious') {
                        console.log(`%c${prefix} ${e.url} → 内容不像CSS(但非HTML)`, style);
                    } else if (e.result === 'unknown_non_html') {
                        console.log(`%c${prefix} ${e.url} → 未知非HTML类型`, style);
                    }
                });
                console.groupEnd();
            }
        }

        if (!hasIssue) {
            const msg = level === 'warning'
                ? '✅ 无异常或可疑资源'
                : '✅ 暂无严重错误资源';
            console.log(msg);
        }
        console.log('='.repeat(60));
    };

    // =============== 调试与维护 ===============
    window.jsGetConfig = () => {
        console.log('🔧 当前配置:', CONFIG);
        return CONFIG;
    };

    window.jsSetConfig = (newConfig) => {
        if (typeof newConfig !== 'object' || newConfig === null) {
            console.error('❌ 配置必须是对象');
            return;
        }
        CONFIG = { ...DEFAULT_CONFIG, ...CONFIG, ...newConfig };
        saveConfig();
        console.log('✅ 配置已更新:', CONFIG);
    };

    window.jsCheckHistory = function (n = 10) {
        const allRecords = [...getStorage(MAIN_LOG_KEY), ...getStorage(BACKUP_LOG_KEY)]
            .sort((a, b) => b.timestamp - a.timestamp)
            .slice(0, n);

        if (allRecords.length === 0) {
            console.log('📭 无历史记录');
            return;
        }

        allRecords.forEach((record, idx) => {
            console.groupCollapsed(`📊 记录 #${idx + 1} | ${record.timeStr}`);
            console.log(`\n🔍 检查时间: ${record.timeStr}`);
            console.log(`════════════════════════════════════════════════════════════════════════════`);

            record.entries.forEach(e => {
                if (e.action === 'discovered') {
                    const prefix = e.type === 'js' ? 'JS' : 'CSS';
                    console.log(`[•] 发现${prefix}资源: ${e.url}`);
                } else if (e.result) {
                    switch (e.result) {
                        case 'html_error':
                            console.error(`[❌] ${e.url} → 返回HTML内容`);
                            break;
                        case 'js_ok':
                            console.log(`[✅] ${e.url} → 正常: JS资源`);
                            break;
                        case 'css_ok':
                            console.log(`[✅] ${e.url} → 正常: CSS资源`);
                            break;
                        case 'js_suspicious':
                            console.warn(`[⚠️] ${e.url} → 内容不像JS`);
                            break;
                        case 'css_suspicious':
                            console.warn(`[⚠️] ${e.url} → 内容不像CSS`);
                            break;
                        case 'unknown_non_html':
                            console.warn(`[⚠️] ${e.url} → 未知非HTML类型`);
                            break;
                        case 'fetch_error':
                            console.error(`[❌] ${e.url} → 请求失败: ${e.error}`);
                            break;
                    }
                }
            });
            console.groupEnd();
        });
    };

    // =============== 存储清理命令 ===============
    window.clearMonitorStorage = function () {
        try {
            localStorage.removeItem(CONFIG_KEY);
            localStorage.removeItem(MAIN_LOG_KEY);
            localStorage.removeItem(BACKUP_LOG_KEY);
            console.log('%c✅ 已成功清除监控脚本的所有本地存储数据', 'color:#4CAF50;font-weight:bold');
            console.log('   - 配置已重置为默认值');
            console.log('   - 所有历史日志已删除');
        } catch (e) {
            console.error('[💥] 清除存储时发生错误:', e);
        }
    };

    // =============== 帮助命令 ===============
    window.monitorHelp = function () {
        console.log('%c🛠️ 页面资源监控 - 命令帮助', 'color:#9C27B0;font-weight:bold;font-size:14px;');
        console.log('──────────────────────────────────────');
        console.log('startMonitor("Y")     → 启用自动监控并保存配置');
        console.log('startMonitor("q")     → 禁用自动监控');
        console.log('showErrors([level])   → 查看异常资源(level: "error" 默认, "warning" 含可疑项)');
        console.log('jsGetConfig()         → 查看当前配置');
        console.log('jsSetConfig({...})    → 动态更新配置');
        console.table(DEFAULT_CONFIG);
        console.log('jsCheckHistory(n)     → 查看最近 n 次检查详情');
        console.log('clearMonitorStorage() → 清除所有监控相关本地存储(配置+日志)');
        console.log('monitorHelp()         → 重新显示本帮助信息');
        console.log('──────────────────────────────────────');
        console.log('💡 提示:监控默认关闭,需手动启用才生效。');
    };

    // =============== 初始化 ===============
    function init() {
        loadConfig();
        isMonitoring = CONFIG.enabled;

        console.log('%cℹ️ 页面资源监控 v1.4.10(定制初始化提示)已加载', 'color:#2196F3;font-weight:bold');

        if (CONFIG.enabled) {
            console.log('🔁 检测到已启用监控,自动启动中...');
            runCheckWithOriginalStyle();
        } else {
            // 👇 严格按照你提供的格式输出
            console.log('👉 在控制台输入以下命令开始使用:');
            console.log('');
            console.log('  startMonitor("Y") → 启用监控');
            console.log('  startMonitor("q") → 关闭监控');
            console.log('  showErrors([level])   → 查看异常资源(level: "error" 默认, "warning" 含可疑项)');
            console.log('  clearMonitorStorage() → 清除所有监控相关本地存储(配置+日志)');
            console.log('');
            console.log('  monitorHelp()     → 查看完整命令帮助');
        }
    }

    if (document.readyState === 'complete') {
        init();
    } else {
        window.addEventListener('load', init, { once: true });
    }
})();