Greasy Fork is available in English.
智能检测JS/CSS是否返回HTML错误页(基于内容分析),支持异常分级查看、自动刷新、日志归档,并智能忽略常见合法但非传统格式的资源(如CSS变量、Iconfont、Webpack/Vite打包JS、ICE资产清单等)。已修复正则错误并增强识别能力。
当前为
// ==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 });
}
})();