日志系统 + 页面错误拦截 — JM Shelf 推荐脚本的模块库,通过 @require 被主脚本引用。
Skrip ini tidak untuk dipasang secara langsung. Ini adalah pustaka skrip lain untuk disertakan dengan direktif meta // @require https://update.sleazyfork.org/scripts/581101/1842600/JM%20Shelf%20-%20Logger.js
// ==UserScript==
// @name JM Shelf - Logger
// @namespace jmshelf-lib
// @version 1.0.0
// @author Kesdi
// @description 日志系统 + 页面错误拦截 — JM Shelf 推荐脚本的模块库,通过 @require 被主脚本引用。
// @license MIT
// ==/UserScript==
//
// 此文件是 GreasyFork 库(library),不直接安装。
// 请安装主脚本: JM Shelf 给杂鱼的个性化推荐
//
// ═══ [4] LOGGER ═══ — 同时输出控制台 + 持久化log (便于排查)
// ============================================================
const LOG = {
_prefix: '[JM Shelf]',
_buffer: [],
_maxBuffer: 500,
_write(level, args) {
const msg = args.map(a => (typeof a === 'object' ? JSON.stringify(a) : String(a))).join(' ');
const entry = { t: Date.now(), l: level, m: msg };
this._buffer.push(entry);
if (this._buffer.length > this._maxBuffer) this._buffer.shift();
this._flush(); // 每次写入立即持久化
},
/** 从localStorage恢复历史日志 */
_restore() {
try {
const saved = localStorage.getItem('jms_log');
if (saved) { const arr = JSON.parse(saved); if (Array.isArray(arr)) this._buffer = arr; }
} catch(e) {}
},
_flush() {
try { localStorage.setItem('jms_log', JSON.stringify(this._buffer.slice(-300))); } catch(e) {}
},
info(...args) { console.log(this._prefix, ...args); this._write('I', args); },
warn(...args) { console.warn(this._prefix, ...args); this._write('W', args); },
error(...args) { console.error(this._prefix, ...args); this._write('E', args); },
debug(...args) { console.debug(this._prefix, ...args); this._write('D', args); },
/** 获取所有日志 (供面板显示) */
getLogs() {
try {
const saved = localStorage.getItem('jms_log');
const savedLogs = saved ? JSON.parse(saved) : [];
// 合并内存和持久化, 去重
const all = [...savedLogs, ...this._buffer];
const seen = new Set();
return all.filter(e => { const k = e.t + e.m; if (seen.has(k)) return false; seen.add(k); return true; }).slice(-200);
} catch(e) { return this._buffer.slice(-200); }
},
/** 清空日志 */
clearLogs() { this._buffer = []; try { localStorage.removeItem('jms_log'); } catch(e) {} },
};
// 拦截页面全局错误和console.error (捕获18comic自身的JS报错)
(function setupErrorCapture() {
// 拦截 uncaught errors
window.addEventListener('error', (e) => {
const msg = e.message || e.error?.message || 'Unknown error';
const src = e.filename || e.target?.src || '';
// 静默站点自身DOM错误
if (/ReferenceError|is not defined|Can not detect|innerHTML|addZone|MutationObserver.*Node|indexOf is not/i.test(msg)) return;
if (src.includes('18comic') || src.includes('airav') || src.includes('owl.carousel')) {
LOG._write('X', [`[页面错误] ${msg.substring(0, 150)} ${src.split('/').pop()}`]);
}
});
// 拦截 unhandled rejections
window.addEventListener('unhandledrejection', (e) => {
const msg = e.reason?.message || String(e.reason).substring(0, 150);
LOG._write('X', [`[Promise错误] ${msg}`]);
});
// 拦截 console.error (站点自身的错误输出)
const origError = console.error.bind(console);
console.error = function(...args) {
origError(...args);
const msg = args.map(a => (a?.stack || a?.message || String(a)).substring(0, 200)).join(' | ');
// 过滤: 只记录18comic相关, 跳过JM Shelf自己的
// 静默站点广告脚本错误 (zbafsnu, p2inl81, vi0ygz7 等随机变量名)
if (msg.match(/ReferenceError|is not defined|Can not detect viewport|owl/i) && !msg.includes('[JM Shelf]')) {
return; // 完全静默, 不记录
}
if (!msg.includes('[JM Shelf]') && (msg.includes('18comic') || msg.includes('owl') || msg.includes('MutationObserver') || msg.includes('Deferred'))) {
LOG._write('X', [`[站点错误] ${msg.substring(0, 180)}`]);
}
};
})();