// ==UserScript== // @name 网页调试 // @namespace https://github.com/WhiteSevs/TamperMonkeyScript // @version 2025.7.11 // @author WhiteSevs // @description 内置多种网页调试工具,包括:Eruda、vConsole、PageSpy、Chii,可在设置菜单中进行详细配置 // @license GPL-3.0-only // @icon data:image/png;base64,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 // @supportURL https://github.com/WhiteSevs/TamperMonkeyScript/issues // @match *://*/* // @require https://fastly.jsdelivr.net/gh/WhiteSevs/TamperMonkeyScript@86be74b83fca4fa47521cded28377b35e1d7d2ac/lib/CoverUMD/index.js // @require https://fastly.jsdelivr.net/gh/WhiteSevs/TamperMonkeyScript@734ba267afee2a5995d15dc419e754a19532cbf4/lib/Eruda/index.js // @require https://fastly.jsdelivr.net/gh/WhiteSevs/TamperMonkeyScript@9f63667d501ec8df5bdb4af680f37793f393754f/lib/VConsole/index.js // @require https://fastly.jsdelivr.net/gh/WhiteSevs/TamperMonkeyScript@85cf0165a124dc4672a939ab9b6d707850d58b25/lib/PageSpy/index.js // @require https://fastly.jsdelivr.net/npm/@whitesev/utils@2.7.0/dist/index.umd.js // @require https://fastly.jsdelivr.net/npm/@whitesev/domutils@1.5.11/dist/index.umd.js // @require https://fastly.jsdelivr.net/npm/@whitesev/pops@2.1.12/dist/index.umd.js // @require https://fastly.jsdelivr.net/npm/qmsg@1.3.8/dist/index.umd.js // @resource Resource_erudaBenchmark https://fastly.jsdelivr.net/npm/eruda-benchmark@2.0.1 // @resource Resource_erudaCode https://fastly.jsdelivr.net/npm/eruda-code@2.2.0 // @resource Resource_erudaFeatures https://fastly.jsdelivr.net/npm/eruda-features@2.1.0 // @resource Resource_erudaGeolocation https://fastly.jsdelivr.net/gh/WhiteSevs/eruda-geolocation@38b60386bcb6280de4cccac7b31169a2abdb2edf/eruda-geolocation.js // @resource Resource_erudaMonitor https://fastly.jsdelivr.net/npm/eruda-monitor@1.1.2 // @resource Resource_erudaOrientation https://fastly.jsdelivr.net/npm/eruda-orientation@2.1.1 // @resource Resource_erudaOutlinePlugin https://fastly.jsdelivr.net/npm/eruda-outline-plugin@0.0.5 // @resource Resource_erudaPixel https://fastly.jsdelivr.net/npm/eruda-pixel@1.0.13 // @resource Resource_erudaTiming https://fastly.jsdelivr.net/npm/eruda-timing@2.0.1 // @resource Resource_erudaTouches https://fastly.jsdelivr.net/npm/eruda-touches@2.1.0 // @resource Resource_erudaVue https://fastly.jsdelivr.net/npm/eruda-vue@1.1.1 // @resource Resource_vConsoleVueDevtools https://fastly.jsdelivr.net/npm/vue-vconsole-devtools@1.0.9 // @connect * // @grant GM_deleteValue // @grant GM_getResourceText // @grant GM_getValue // @grant GM_info // @grant GM_registerMenuCommand // @grant GM_setClipboard // @grant GM_setValue // @grant GM_unregisterMenuCommand // @grant GM_xmlhttpRequest // @grant unsafeWindow // @run-at document-start // @downloadURL none // ==/UserScript== (function (Qmsg, DOMUtils, Utils, pops) { 'use strict'; var _GM_deleteValue = /* @__PURE__ */ (() => typeof GM_deleteValue != "undefined" ? GM_deleteValue : void 0)(); var _GM_getResourceText = /* @__PURE__ */ (() => typeof GM_getResourceText != "undefined" ? GM_getResourceText : void 0)(); var _GM_getValue = /* @__PURE__ */ (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)(); var _GM_info = /* @__PURE__ */ (() => typeof GM_info != "undefined" ? GM_info : void 0)(); var _GM_registerMenuCommand = /* @__PURE__ */ (() => typeof GM_registerMenuCommand != "undefined" ? GM_registerMenuCommand : void 0)(); var _GM_setClipboard = /* @__PURE__ */ (() => typeof GM_setClipboard != "undefined" ? GM_setClipboard : void 0)(); var _GM_setValue = /* @__PURE__ */ (() => typeof GM_setValue != "undefined" ? GM_setValue : void 0)(); var _GM_unregisterMenuCommand = /* @__PURE__ */ (() => typeof GM_unregisterMenuCommand != "undefined" ? GM_unregisterMenuCommand : void 0)(); var _GM_xmlhttpRequest = /* @__PURE__ */ (() => typeof GM_xmlhttpRequest != "undefined" ? GM_xmlhttpRequest : void 0)(); var _unsafeWindow = /* @__PURE__ */ (() => typeof unsafeWindow != "undefined" ? unsafeWindow : void 0)(); var _monkeyWindow = /* @__PURE__ */ (() => window)(); const PanelSettingConfig = { /** Toast位置 */ qmsg_config_position: { key: "qmsg-config-position", defaultValue: "bottom" }, /** 最多显示的数量 */ qmsg_config_maxnums: { key: "qmsg-config-maxnums", defaultValue: 3 }, /** 逆序弹出 */ qmsg_config_showreverse: { key: "qmsg-config-showreverse", defaultValue: false } }; const utils = Utils.noConflict(); DOMUtils.noConflict(); const __pops = pops; const log = new utils.Log( _GM_info, _unsafeWindow.console || _monkeyWindow.console ); let SCRIPT_NAME = _GM_info?.script?.name || void 0; pops.config.Utils.AnyTouch(); const DEBUG = false; log.config({ debug: DEBUG, logMaxCount: 1e3, autoClearConsole: true, tag: true }); Qmsg.config( Object.defineProperties( { html: true, autoClose: true, showClose: false }, { position: { get() { return Panel.getValue( PanelSettingConfig.qmsg_config_position.key, PanelSettingConfig.qmsg_config_position.defaultValue ); } }, maxNums: { get() { return Panel.getValue( PanelSettingConfig.qmsg_config_maxnums.key, PanelSettingConfig.qmsg_config_maxnums.defaultValue ); } }, showReverse: { get() { return Panel.getValue( PanelSettingConfig.qmsg_config_showreverse.key, PanelSettingConfig.qmsg_config_showreverse.defaultValue ); } }, zIndex: { get() { let maxZIndex = Utils.getMaxZIndex(); let popsMaxZIndex = pops.config.InstanceUtils.getPopsMaxZIndex().zIndex; return Utils.getMaxValue(maxZIndex, popsMaxZIndex) + 100; } } } ) ); __pops.GlobalConfig.setGlobalConfig({ zIndex: () => { let maxZIndex = Utils.getMaxZIndex(void 0, void 0, ($ele) => { if ($ele?.classList?.contains("qmsg-shadow-container")) { return false; } if ($ele?.closest("qmsg") && $ele.getRootNode() instanceof ShadowRoot) { return false; } }); let popsMaxZIndex = pops.config.InstanceUtils.getPopsMaxZIndex().zIndex; return Utils.getMaxValue(maxZIndex, popsMaxZIndex) + 100; }, mask: { // 开启遮罩层 enable: true, // 取消点击遮罩层的事件 clickEvent: { toClose: false, toHide: false } } }); const GM_Menu = new utils.GM_Menu({ GM_getValue: _GM_getValue, GM_setValue: _GM_setValue, GM_registerMenuCommand: _GM_registerMenuCommand, GM_unregisterMenuCommand: _GM_unregisterMenuCommand }); const httpx = new utils.Httpx({ xmlHttpRequest: _GM_xmlhttpRequest, logDetails: DEBUG }); httpx.interceptors.request.use((data) => { return data; }); httpx.interceptors.response.use(void 0, (data) => { log.error("拦截器-请求错误", data); if (data.type === "onabort") { Qmsg.warning("请求取消", { consoleLogContent: true }); } else if (data.type === "onerror") { Qmsg.error("请求异常", { consoleLogContent: true }); } else if (data.type === "ontimeout") { Qmsg.error("请求超时", { consoleLogContent: true }); } else { Qmsg.error("其它错误", { consoleLogContent: true }); } return data; }); ({ Object: { defineProperty: _unsafeWindow.Object.defineProperty }, Function: { apply: _unsafeWindow.Function.prototype.apply, call: _unsafeWindow.Function.prototype.call }, Element: { appendChild: _unsafeWindow.Element.prototype.appendChild }, setTimeout: _unsafeWindow.setTimeout }); utils.addStyle.bind(utils); document.querySelector.bind(document); document.querySelectorAll.bind(document); new utils.GM_Cookie(); const KEY = "GM_Panel"; const ATTRIBUTE_INIT = "data-init"; const ATTRIBUTE_KEY = "data-key"; const ATTRIBUTE_DEFAULT_VALUE = "data-default-value"; const ATTRIBUTE_INIT_MORE_VALUE = "data-init-more-value"; const PROPS_STORAGE_API = "data-storage-api"; const PanelUISize = { /** * 一般设置界面的尺寸 */ setting: { get width() { if (window.innerWidth < 550) { return "88vw"; } else if (window.innerWidth < 700) { return "550px"; } else { return "700px"; } }, get height() { if (window.innerHeight < 450) { return "70vh"; } else if (window.innerHeight < 550) { return "450px"; } else { return "550px"; } } }, /** * 信息界面,一般用于提示信息之类 */ info: { get width() { return window.innerWidth < 350 ? "350px" : "350px"; }, get height() { return window.innerHeight < 250 ? "250px" : "250px"; } } }; class StorageUtils { /** 存储的键名 */ storageKey; listenerData; /** * 存储的键名,可以是多层的,如:a.b.c * * 那就是 * { * "a": { * "b": { * "c": { * ...你的数据 * } * } * } * } * @param key */ constructor(key) { if (typeof key === "string") { let trimKey = key.trim(); if (trimKey == "") { throw new Error("key参数不能为空字符串"); } this.storageKey = trimKey; } else { throw new Error("key参数类型错误,必须是字符串"); } this.listenerData = new Utils.Dictionary(); } /** * 获取本地值 */ getLocalValue() { let localValue = _GM_getValue(this.storageKey); if (localValue == null) { localValue = {}; this.setLocalValue(localValue); } return localValue; } /** * 设置本地值 * @param value */ setLocalValue(value) { _GM_setValue(this.storageKey, value); } /** * 设置值 * @param key 键 * @param value 值 */ set(key, value) { let oldValue = this.get(key); let localValue = this.getLocalValue(); Reflect.set(localValue, key, value); this.setLocalValue(localValue); this.triggerValueChangeListener(key, oldValue, value); } /** * 获取值 * @param key 键 * @param defaultValue 默认值 */ get(key, defaultValue) { let localValue = this.getLocalValue(); return Reflect.get(localValue, key) ?? defaultValue; } /** * 获取所有值 */ getAll() { let localValue = this.getLocalValue(); return localValue; } /** * 删除值 * @param key 键 */ delete(key) { let oldValue = this.get(key); let localValue = this.getLocalValue(); Reflect.deleteProperty(localValue, key); this.setLocalValue(localValue); this.triggerValueChangeListener(key, oldValue, void 0); } /** * 判断是否存在该值 */ has(key) { let localValue = this.getLocalValue(); return Reflect.has(localValue, key); } /** * 获取所有键 */ keys() { let localValue = this.getLocalValue(); return Reflect.ownKeys(localValue); } /** * 获取所有值 */ values() { let localValue = this.getLocalValue(); return Reflect.ownKeys(localValue).map( (key) => Reflect.get(localValue, key) ); } /** * 清空所有值 */ clear() { _GM_deleteValue(this.storageKey); } /** * 监听值改变 * + .set * + .delete * @param key 监听的键 * @param callback 值改变的回调函数 */ addValueChangeListener(key, callback) { let listenerId = Math.random(); let listenerData = this.listenerData.get(key) || []; listenerData.push({ id: listenerId, key, callback }); this.listenerData.set(key, listenerData); return listenerId; } /** * 移除监听 * @param listenerId 监听的id或键名 */ removeValueChangeListener(listenerId) { let flag = false; for (const [key, listenerData] of this.listenerData.entries()) { for (let index = 0; index < listenerData.length; index++) { const value = listenerData[index]; if (typeof listenerId === "string" && value.key === listenerId || typeof listenerId === "number" && value.id === listenerId) { listenerData.splice(index, 1); index--; flag = true; } } this.listenerData.set(key, listenerData); } return flag; } /** * 主动触发监听器 * @param key 键 * @param oldValue (可选)旧值 * @param newValue (可选)新值 */ triggerValueChangeListener(key, oldValue, newValue) { if (!this.listenerData.has(key)) { return; } let listenerData = this.listenerData.get(key); for (let index = 0; index < listenerData.length; index++) { const data = listenerData[index]; if (typeof data.callback === "function") { let value = this.get(key); let __newValue; let __oldValue; if (typeof oldValue !== "undefined" && arguments.length >= 2) { __oldValue = oldValue; } else { __oldValue = value; } if (typeof newValue !== "undefined" && arguments.length > 2) { __newValue = newValue; } else { __newValue = value; } data.callback(key, __oldValue, __newValue); } } } } const PopsPanelStorageApi = new StorageUtils(KEY); const PanelContent = { $data: { /** * @private */ __contentConfig: null, get contentConfig() { if (this.__contentConfig == null) { this.__contentConfig = new utils.Dictionary(); } return this.__contentConfig; } }, /** * 设置所有配置项,用于初始化默认的值 * * 如果是第一组添加的话,那么它默认就是设置菜单打开的配置 * @param configList 配置项 */ addContentConfig(configList) { if (!Array.isArray(configList)) { configList = [configList]; } let index = this.$data.contentConfig.keys().length; this.$data.contentConfig.set(index, configList); }, /** * 获取所有的配置内容,用于初始化默认的值 */ getAllContentConfig() { return this.$data.contentConfig.values().flat(); }, /** * 获取配置内容 * @param index 配置索引 */ getConfig(index = 0) { return this.$data.contentConfig.get(index) ?? []; }, /** * 获取默认左侧底部的配置项 */ getDefaultBottomContentConfig() { return [ { id: "script-version", title: `版本:${_GM_info?.script?.version || "未知"}`, isBottom: true, forms: [], clickFirstCallback(event, rightHeaderElement, rightContainerElement) { let supportURL = _GM_info?.script?.supportURL || _GM_info?.script?.namespace; if (typeof supportURL === "string" && utils.isNotNull(supportURL)) { window.open(supportURL, "_blank"); } return false; } } ]; } }; const PanelMenu = { $data: { __menuOption: [ { key: "show_pops_panel_setting", text: "⚙ 设置", autoReload: false, isStoreValue: false, showText(text) { return text; }, callback: () => { Panel.showPanel(PanelContent.getConfig(0)); } } ], get menuOption() { return this.__menuOption; } }, init() { this.initExtensionsMenu(); }, /** * 初始化菜单项 */ initExtensionsMenu() { if (!Panel.isTopWindow()) { return; } GM_Menu.add(this.$data.menuOption); }, /** * 添加菜单项 * @param option 菜单配置 */ addMenuOption(option) { if (!Array.isArray(option)) { option = [option]; } this.$data.menuOption.push(...option); }, /** * 更新菜单项 * @param option 菜单配置 */ updateMenuOption(option) { if (!Array.isArray(option)) { option = [option]; } option.forEach((optionItem) => { let findIndex = this.$data.menuOption.findIndex((it) => { return it.key === optionItem.key; }); if (findIndex !== -1) { this.$data.menuOption[findIndex] = optionItem; } }); }, /** * 获取菜单项 * @param [index=0] 索引 */ getMenuOption(index = 0) { return this.$data.menuOption[index]; }, /** * 删除菜单项 * @param [index=0] 索引 */ deleteMenuOption(index = 0) { this.$data.menuOption.splice(index, 1); } }; const Panel = { /** 数据 */ $data: { /** * @private */ __configDefaultValueData: null, /** * @private */ __onceExecMenuData: null, /** * @private */ __onceExecData: null, /** * @private */ __panelConfig: {}, $panel: null, /** * 菜单项的默认值 */ get configDefaultValueData() { if (this.__configDefaultValueData == null) { this.__configDefaultValueData = new utils.Dictionary(); } return this.__configDefaultValueData; }, /** * 成功只执行了一次的项 */ get onceExecMenuData() { if (this.__onceExecMenuData == null) { this.__onceExecMenuData = new utils.Dictionary(); } return this.__onceExecMenuData; }, /** * 成功只执行了一次的项 */ get onceExecData() { if (this.__onceExecData == null) { this.__onceExecData = new utils.Dictionary(); } return this.__onceExecData; }, /** 脚本名,一般用在设置的标题上 */ get scriptName() { return SCRIPT_NAME; }, /** * pops.panel的默认配置 */ get panelConfig() { return this.__panelConfig; }, set panelConfig(value) { this.__panelConfig = value; }, /** 菜单项的总值在本地数据配置的键名 */ key: KEY, /** 菜单项在attributes上配置的菜单键 */ attributeKeyName: ATTRIBUTE_KEY, /** 菜单项在attributes上配置的菜单默认值 */ attributeDefaultValueName: ATTRIBUTE_DEFAULT_VALUE }, init() { this.initContentDefaultValue(); PanelMenu.init(); }, /** 判断是否是顶层窗口 */ isTopWindow() { return _unsafeWindow.top === _unsafeWindow.self; }, /** 初始化菜单项的默认值保存到本地数据中 */ initContentDefaultValue() { const initDefaultValue = (config) => { if (!config.attributes) { return; } if (config.type === "button" || config.type === "forms" || config.type === "deepMenu") { return; } let needInitConfig = {}; let key = config.attributes[ATTRIBUTE_KEY]; if (key != null) { needInitConfig[key] = config.attributes[ATTRIBUTE_DEFAULT_VALUE]; } let __attr_init__ = config.attributes[ATTRIBUTE_INIT]; if (typeof __attr_init__ === "function") { let __attr_result__ = __attr_init__(); if (typeof __attr_result__ === "boolean" && !__attr_result__) { return; } } let initMoreValue = config.attributes[ATTRIBUTE_INIT_MORE_VALUE]; if (initMoreValue && typeof initMoreValue === "object") { Object.assign(needInitConfig, initMoreValue); } let needInitConfigList = Object.keys(needInitConfig); if (!needInitConfigList.length) { log.warn(["请先配置键", config]); return; } needInitConfigList.forEach((__key) => { let __defaultValue = needInitConfig[__key]; this.setDefaultValue(__key, __defaultValue); }); }; const loopInitDefaultValue = (configList) => { for (let index = 0; index < configList.length; index++) { let configItem = configList[index]; initDefaultValue(configItem); let childForms = configItem.forms; if (childForms && Array.isArray(childForms)) { loopInitDefaultValue(childForms); } } }; const contentConfigList = [...PanelContent.getAllContentConfig()]; for (let index = 0; index < contentConfigList.length; index++) { let leftContentConfigItem = contentConfigList[index]; if (!leftContentConfigItem.forms) { continue; } const rightContentConfigList = leftContentConfigItem.forms; if (rightContentConfigList && Array.isArray(rightContentConfigList)) { loopInitDefaultValue(rightContentConfigList); } } }, /** * 设置初始化使用的默认值 */ setDefaultValue(key, defaultValue) { if (this.$data.configDefaultValueData.has(key)) { log.warn("请检查该key(已存在): " + key); } this.$data.configDefaultValueData.set(key, defaultValue); }, /** * 设置值 * @param key 键 * @param value 值 */ setValue(key, value) { PopsPanelStorageApi.set(key, value); }, /** * 获取值 * @param key 键 * @param defaultValue 默认值 */ getValue(key, defaultValue) { let localValue = PopsPanelStorageApi.get(key); if (localValue == null) { if (this.$data.configDefaultValueData.has(key)) { return this.$data.configDefaultValueData.get(key); } return defaultValue; } return localValue; }, /** * 删除值 * @param key 键 */ deleteValue(key) { PopsPanelStorageApi.delete(key); }, /** * 判断该键是否存在 * @param key 键 */ hasKey(key) { return PopsPanelStorageApi.has(key); }, /** * 监听调用setValue、deleteValue * @param key 需要监听的键 * @param callback */ addValueChangeListener(key, callback) { let listenerId = PopsPanelStorageApi.addValueChangeListener( key, (__key, __newValue, __oldValue) => { callback(key, __oldValue, __newValue); } ); return listenerId; }, /** * 移除监听 * @param listenerId 监听的id */ removeValueChangeListener(listenerId) { PopsPanelStorageApi.removeValueChangeListener(listenerId); }, /** * 主动触发菜单值改变的回调 * @param key 菜单键 * @param newValue 想要触发的新值,默认使用当前值 * @param oldValue 想要触发的旧值,默认使用当前值 */ triggerMenuValueChange(key, newValue, oldValue) { PopsPanelStorageApi.triggerValueChangeListener(key, oldValue, newValue); }, /** * 移除已执行的仅执行一次的菜单 * @param key 键 */ deleteExecMenuOnce(key) { this.$data.onceExecMenuData.delete(key); let flag = PopsPanelStorageApi.removeValueChangeListener(key); return flag; }, /** * 移除已执行的仅执行一次的菜单 * @param key 键 */ deleteOnceExec(key) { this.$data.onceExecData.delete(key); }, /** * 执行菜单 * * @param queryKey 键|键数组 * @param callback 执行的回调函数 * @param checkExec 判断是否执行回调 * * (默认)如果想要每个菜单是`与`关系,即每个菜单都判断为开启,那么就判断它们的值&就行 * * 如果想要任意菜单存在true再执行,那么判断它们的值|就行 * * + 返回值都为`true`,执行回调,如果回调返回了