// ==UserScript== // @name js-script-hook // @namespace https://github.com/JSREI/js-script-hook.git // @version 0.3 // @description 用来给script类型的请求打断点 // @document https://github.com/JSREI/js-script-hook.git // @author CC11001100 // @match *://*/* // @run-at document-start // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @require https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.js // @downloadURL https://update.greasyfork.icu/scripts/419533/js-script-hook.user.js // @updateURL https://update.greasyfork.icu/scripts/419533/js-script-hook.meta.js // ==/UserScript== /******/ (() => { // webpackBootstrap /******/ var __webpack_modules__ = ([ /* 0 */, /* 1 */ /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const {registerMenu, show} = __webpack_require__(2); const {getUnsafeWindow} = __webpack_require__(14); const {DocumentHook} = __webpack_require__(15); const {initConfig, getGlobalConfig} = __webpack_require__(5); const {ConfigurationComponent} = __webpack_require__(3); /** * 初始化整个脚本 */ function init() { // 加载配置 initConfig(); // 增加可视化的配置 registerMenu(); // 为document增加hook点 new DocumentHook(getUnsafeWindow().document).addHook(); if (GM_getValue("js-script-hook-open-configuration")) { GM_setValue("js-script-hook-open-configuration", false); show(); } } module.exports = { init } /***/ }), /* 2 */ /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const {ConfigurationComponent} = __webpack_require__(3); const {appendScriptHookStyleToCurrentPage} = __webpack_require__(13); const {getGlobalConfig} = __webpack_require__(5); function registerMenu() { let id = GM_registerMenuCommand( "Configuration", function () { if (getGlobalConfig().autoJumpProjectSiteOnConfiguraion) { // 重定向到项目的主页,要不然怕有样式不兼容 GM_setValue("js-script-hook-open-configuration", true); if (!currentInProjectRepo()) { window.location.href = "https://github.com/JSREI/js-script-hook"; } else { if (!GM_getValue("js-script-hook-open-configuration")) { return; } GM_setValue("js-script-hook-open-configuration", false); show(); } } else { show(); } } ); } function show() { // 只在需要的时候才追加样式表,尽可能的避免样式污染 appendScriptHookStyleToCurrentPage(); const configurationComponent = new ConfigurationComponent(); configurationComponent.show(); } function currentInProjectRepo() { return window.location.href.toLowerCase().startsWith("https://github.com/jsrei/js-script-hook"); } module.exports = { registerMenu, show } /***/ }), /* 3 */ /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const {GlobalOptionsComponent} = __webpack_require__(4); const {DebuggerManagerComponent} = __webpack_require__(9); const {getGlobalConfig} = __webpack_require__(5); const {getLanguage} = __webpack_require__(12); /** * 配置组件 */ class ConfigurationComponent { constructor() { this.modalHTML = `
`; } /** * 展示配置界面 */ show() { // i18n配置语言 let language = getLanguage(getGlobalConfig().language); // 将模态框添加到body元素中 $(document.body).append($(this.modalHTML)); // 全局配置参数 const globalOptionsComponent = new GlobalOptionsComponent(); $("#js-script-hook-configuration-content").append(globalOptionsComponent.render(language, getGlobalConfig())); // 断点参数 const debuggerManager = new DebuggerManagerComponent(); $("#js-script-hook-configuration-content").append(debuggerManager.render(language, getGlobalConfig().debuggers)); // 关闭按钮事件处理 document.getElementById("jsrei-js-script-hook-configuration-close-btn").addEventListener('click', this.closeModalWindow); document.getElementById("jsrei-js-script-hook-configuration-modal-window").style.display = 'flex'; } /** * 隐藏模态框的函数 */ closeModalWindow() { const element = document.getElementById("jsrei-js-script-hook-configuration-modal-window"); if (element) { element.parentNode.removeChild(element); } } } module.exports = { ConfigurationComponent } /***/ }), /* 4 */ /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const {getGlobalConfig} = __webpack_require__(5); const {renderSwitchComponent} = __webpack_require__(8); /** * 全局配置参数 */ class GlobalOptionsComponent { /** * 渲染模板 * * @param oldConfig * @param language * @return {string} */ template(oldConfig, language) { return `
${language.global_settings.title}
?
${language.global_settings.languageTips}
${language.global_settings.language}
?
${language.global_settings.responseDebuggerHookTypeTips}
${language.global_settings.responseDebuggerHookType}
?
${language.global_settings.flagPrefixTips}
${language.global_settings.flagPrefix}
?
${language.global_settings.isIgnoreJsSuffixRequestTips}
${language.global_settings.isIgnoreJsSuffixRequest}
?
${language.global_settings.isIgnoreNotJsonpRequestTips}
${language.global_settings.isIgnoreNotJsonpRequest}
?
${language.global_settings.autoJumpProjectSiteOnConfiguraionTips}
${language.global_settings.autoJumpProjectSiteOnConfiguraion}
`; } /** * * 渲染全局配置 * * @param language * @param oldConfig {Config} */ render(language, oldConfig) { const component = $(this.template(oldConfig, language)); if (oldConfig.hookType) { component.find(`#js-script-hook-global-config-hook-type`).val(oldConfig.hookType); } component.find("#js-script-hook-global-config-hook-type").change(function () { getGlobalConfig().hookType = $(this).val(); getGlobalConfig().persist(); }); // 切换语言选择 component.find("#js-script-hook-global-config-language").change(function () { getGlobalConfig().language = $(this).val(); getGlobalConfig().persist(); }); // 全局标志的前缀 component.find("#js-script-hook-global-config-flag-prefix").on("input", function () { getGlobalConfig().prefix = this.value; getGlobalConfig().persist(); }); // 是否忽略所有的js文件的请求 component.find("#js-script-hook-global-config-isIgnoreJsSuffixRequest").on("change", function () { getGlobalConfig().isIgnoreJsSuffixRequest = $(this).is(':checked'); getGlobalConfig().persist(); }); // 是否忽略所有的非jsonp的请求 component.find("#js-script-hook-global-config-isIgnoreNotJsonpRequest").on("change", function () { getGlobalConfig().isIgnoreNotJsonpRequest = $(this).is(':checked'); getGlobalConfig().persist(); }); // 在打开配置页面的时候自动跳转项目主页 component.find("#js-script-hook-global-config-autoJumpProjectSiteOnConfiguraion").on("change", function () { getGlobalConfig().autoJumpProjectSiteOnConfiguraion = $(this).is(':checked'); getGlobalConfig().persist(); }); return component; } } module.exports = { GlobalOptionsComponent } /***/ }), /* 5 */ /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const {DebuggerTester} = __webpack_require__(6); const {Debugger} = __webpack_require__(7); const GM_config_name = "js-script-hook-config-name"; /** * 支持的相关配置 */ class Config { constructor() { // 默认为英文的操作界面 this.language = "english"; // 让用户能够自己指定前缀,也许会有一些拥有感?之前ast hook好像就有个哥们喜欢这样干... this.prefix = "JSREI"; this.hookType = "use-proxy-function"; // 是否忽略.js后缀的请求 this.isIgnoreJsSuffixRequest = false; // 是否忽略不是jsonp的请求 this.isIgnoreNotJsonpRequest = false; // 在打开配置页面的时候自动跳转到项目主页 this.autoJumpProjectSiteOnConfiguraion = true; // 所有的断点 this.debuggers = []; } findDebuggerById(id) { for (let debuggerInformation of this.debuggers) { if (debuggerInformation.id === id) { return debuggerInformation; } } return null; } addDebugger(debuggerInformation) { // TODO 2024-12-22 05:06:15 断点的有效性校验 this.debuggers.push(debuggerInformation); } removeDebuggerById(id) { const newDebuggers = []; for (let debuggerInformation of this.debuggers) { if (debuggerInformation.id !== id) { newDebuggers.push(debuggerInformation); } } this.debuggers = newDebuggers; } load() { const configJsonString = GM_getValue(GM_config_name); if (!configJsonString) { return this; } const o = JSON.parse(configJsonString); this.language = o.language; this.prefix = o.prefix; this.hookType = o.hookType; this.isIgnoreJsSuffixRequest = o.isIgnoreJsSuffixRequest; this.isIgnoreNotJsonpRequest = o.isIgnoreNotJsonpRequest; this.autoJumpProjectSiteOnConfiguraion = o.autoJumpProjectSiteOnConfiguraion; this.debuggers = []; for (let debuggerInformationObject of o.debuggers) { const debuggerInformation = new Debugger(); debuggerInformation.createTime = debuggerInformationObject.createTime; debuggerInformation.updateTime = debuggerInformationObject.updateTime; debuggerInformation.id = debuggerInformationObject.id; debuggerInformation.enable = debuggerInformationObject.enable; debuggerInformation.urlPattern = debuggerInformationObject.urlPattern; debuggerInformation.urlPatternType = debuggerInformationObject.urlPatternType; debuggerInformation.enableRequestDebugger = debuggerInformationObject.enableRequestDebugger; debuggerInformation.enableResponseDebugger = debuggerInformationObject.enableResponseDebugger; debuggerInformation.callbackFunctionParamName = debuggerInformationObject.callbackFunctionParamName; debuggerInformation.comment = debuggerInformationObject.comment; this.debuggers.push(debuggerInformation); } return this; } persist() { const configJsonString = JSON.stringify(this); GM_setValue(GM_config_name, configJsonString); } /** * 执行测试所有断点,看看是否有条件命中啥的 * * @param scriptContext {ScriptContext} * @return {Array} */ testAll(scriptContext) { const hitDebuggers = []; for (let jsonpDebugger of this.debuggers) { if (jsonpDebugger.enable && new DebuggerTester().test(this, jsonpDebugger, scriptContext)) { hitDebuggers.push(jsonpDebugger); } } return hitDebuggers; } /** * 测试是否能够命中响应内容 * * @param scriptContext * @return {*[]} */ testAllForResponse(scriptContext) { const hitDebuggers = []; for (let debuggerConfig of this.debuggers) { if (debuggerConfig.enable && new DebuggerTester().testForResponse(this, debuggerConfig, scriptContext)) { hitDebuggers.push(debuggerConfig); } } return hitDebuggers; } } let globalConfig = new Config(); function initConfig() { globalConfig.load(); } function getGlobalConfig() { return globalConfig; } module.exports = { Config, initConfig, getGlobalConfig } /***/ }), /* 6 */ /***/ ((module) => { /** * 用于测试是否命中断点的工具类,支持对请求和响应的断点测试。 */ class DebuggerTester { /** * 测试是否命中断点。 * * @param {Object} globalConfig - 全局配置对象,包含断点的全局设置。 * @param {Object} debuggerConfig - 断点配置对象,包含断点的具体设置。 * @param {ScriptContext} scriptContext - 脚本上下文对象,包含请求和响应的详细信息。 * @return {boolean} - 如果命中断点则返回 true,否则返回 false。 */ test(globalConfig, debuggerConfig, scriptContext) { // 首先 URL 要能够匹配得上 if (!this.testUrlPattern(debuggerConfig.urlPatternType, debuggerConfig.urlPattern, scriptContext.url)) { return false; } // 支持忽略 .js 文件请求 if (globalConfig.isIgnoreJsSuffixRequest && scriptContext.isJsSuffixRequest()) { return false; } // 忽略不是 JSONP 的请求 if (globalConfig.isIgnoreNotJsonpRequest && !scriptContext.isJsonp()) { return false; } // 请求断点 if (debuggerConfig.enableRequestDebugger) { // 把一些相关的上下文赋值到变量方便断点命中这里的时候观察 // _scriptContext 中存放的是与当前的 script 请求相关的一些上下文信息 const _scriptContext = scriptContext; const humanReadableScriptInformation = scriptContext.toHumanReadable(); debugger; // 断点调试 } return true; } // --------------------------------------------------------------------------------------------------------------------- /** * 测试请求的 URL 是否匹配断点的 URL 模式。 * * @param {string} urlPatternType - URL 匹配模式类型(例如:"equals-string"、"contains-string" 等)。 * @param {string | RegExp} urlPattern - URL 匹配模式的值。 * @param {string} url - 要测试匹配的 URL。 * @return {boolean} - 如果 URL 匹配模式则返回 true,否则返回 false。 */ testUrlPattern(urlPatternType, urlPattern, url) { if (!url) { return false; } if (!urlPattern) { return true; } switch (urlPatternType) { case "equals-string": return this.testUrlPatternForEquals(urlPattern, url); case "contains-string": return this.testUrlPatternForContains(urlPattern, url); case "match-regexp": return this.testUrlPatternForMatchRegexp(urlPattern, url); case "match-all": return this.testUrlPatternForMatchAll(urlPattern, url); default: return false; } } /** * 测试 URL 是否完全匹配给定的模式。 * * @param {string} urlPattern - 要匹配的 URL 模式。 * @param {string} url - 要测试的 URL。 * @return {boolean} - 如果 URL 完全匹配模式则返回 true,否则返回 false。 */ testUrlPatternForEquals(urlPattern, url) { return url === urlPattern; } /** * 测试 URL 是否包含给定的关键字。 * * @param {string} urlPattern - 要匹配的关键字。 * @param {string} url - 要测试的 URL。 * @return {boolean} - 如果 URL 包含关键字则返回 true,否则返回 false。 */ testUrlPatternForContains(urlPattern, url) { return url.indexOf(urlPattern) !== -1; } /** * 测试 URL 是否匹配给定的正则表达式。 * * @param {string} urlPattern - 要匹配的正则表达式。 * @param {string} url - 要测试的 URL。 * @return {boolean} - 如果 URL 匹配正则表达式则返回 true,否则返回 false。 */ testUrlPatternForMatchRegexp(urlPattern, url) { try { return new RegExp(urlPattern).test(url); } catch (e) { console.error(e); return false; } } /** * 匹配所有 URL(始终返回 true)。 * * @param {string} urlPattern - 忽略此参数。 * @param {string} url - 忽略此参数。 * @return {boolean} - 始终返回 true。 */ testUrlPatternForMatchAll(urlPattern, url) { return true; } // --------------------------------------------------------------------------------------------------------------------- /** * 测试响应是否命中断点。 * * @param {Object} globalConfig - 全局配置对象,包含断点的全局设置。 * @param {Object} debuggerConfig - 断点配置对象,包含断点的具体设置。 * @param {ScriptContext} scriptContext - 脚本上下文对象,包含请求和响应的详细信息。 * @return {boolean} - 如果命中断点则返回 true,否则返回 false。 */ testForResponse(globalConfig, debuggerConfig, scriptContext) { // 首先 URL 要能够匹配得上 if (!this.testUrlPattern(debuggerConfig.urlPatternType, debuggerConfig.urlPattern, scriptContext.url)) { return false; } // 支持忽略 .js 文件请求 if (globalConfig.isIgnoreJsSuffixRequest && scriptContext.isJsSuffixRequest()) { return false; } // 忽略不是 JSONP 的请求 if (globalConfig.isIgnoreNotJsonpRequest && !scriptContext.isJsonp()) { return false; } // 响应断点是否开启 return debuggerConfig.enableResponseDebugger; } /** * 判断是否需要将信息打印到控制台。 * * @param {Object} globalConfig - 全局配置对象,包含断点的全局设置。 * @param {ScriptContext} scriptContext - 脚本上下文对象,包含请求和响应的详细信息。 * @return {boolean} - 如果需要打印到控制台则返回 true,否则返回 false。 */ isNeedPrintToConsole(globalConfig, scriptContext) { // 忽略 .js 文件请求 if (globalConfig.isIgnoreJsSuffixRequest && scriptContext.isJsSuffixRequest()) { return false; } // 忽略不是 JSONP 的请求 if (globalConfig.isIgnoreNotJsonpRequest && !scriptContext.isJsonp()) { return false; } return true; } } module.exports = { DebuggerTester }; /***/ }), /* 7 */ /***/ ((module) => { /** * 表示一个 JSONP 的条件断点,用于在请求或响应处理时中断执行。 */ class Debugger { /** * 构造函数,创建一个 Debugger 实例。 * * @param {string} id - 断点的唯一标识。 * @param {boolean} enable - 此断点是否处于启用状态。 * @param {string} urlPatternType - URL 匹配模式类型(例如:字符串或正则表达式)。 * @param {string | RegExp} urlPattern - 用于与 script 类型请求的 URL 做匹配,只有这个是必须指定的。 * @param {boolean} [enableRequestDebugger=true] - 是否开启请求断点。开启后会在请求发送之前进入断点。如果不指定,默认开启。 * @param {boolean} [enableResponseDebugger=true] - 是否开启响应断点。开启后会在响应处理之前进入断点。如果不指定,默认开启。 * @param {string | null} [callbackFunctionParamName=null] - 传递 JSONP 回调函数名称的参数(例如:"callback")。如果不指定,会自动推测。 * @param {string | null} [comment=null] - 断点的注释或描述信息。 */ constructor( id, enable, urlPatternType, urlPattern, enableRequestDebugger = true, enableResponseDebugger = true, callbackFunctionParamName = null, comment = null ) { this.createTime = new Date().getTime(); this.updateTime = new Date().getTime(); this.id = id; this.enable = enable; this.urlPatternType = urlPatternType; this.urlPattern = urlPattern; this.enableRequestDebugger = enableRequestDebugger; this.enableResponseDebugger = enableResponseDebugger; this.callbackFunctionParamName = callbackFunctionParamName; this.comment = comment; } } module.exports = { Debugger }; /***/ }), /* 8 */ /***/ ((module) => { class SwitchComponent { render(id, initValue) { const checkboxComponent = $(this.template(id, initValue)); checkboxComponent.find(`#${id}-span`).css("before", { "position": "absolute", "content": "", "height": "26px", "width": "26px", "left": "4px", "bottom": "4px", "background-color": "white", "-webkit-transition": ".4s", "transition": ".4s", "border-radius": "50%", }); checkboxComponent.find(`#${id}`).on("change", function () { }); return checkboxComponent.clone().html(); } template(id, initValue) { return `
`; } } function renderSwitchComponent(id, initValue) { return new SwitchComponent().render(id, initValue); } module.exports = { SwitchComponent, renderSwitchComponent } /***/ }), /* 9 */ /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const {DebuggerComponent} = __webpack_require__(10); const {Debugger} = __webpack_require__(7); const {randomId} = __webpack_require__(11); const {getGlobalConfig} = __webpack_require__(5); class DebuggerManagerComponent { constructor() { this.html = `
`; } /** * * @param language * @param debuggers {Array} */ render(language, debuggers) { // 按照最后修改时间排序 debuggers.sort((a, b) => { const t1 = parseInt(a.updateTime || 0); const t2 = parseInt(b.updateTime || 0); return t2 - t1; }); const debuggerManager = $(this.html); // 渲染已经存在的断点配置信息 for (let debuggerInformation of debuggers) { const debuggerComponent = new DebuggerComponent(); debuggerManager.find("#js-script-hook-debugger-list").append(debuggerComponent.render(language, debuggerInformation)); } // 增加断点配置 debuggerManager.find("#js-script-hook-add-debugger-btn").click(() => { const debuggerComponent = new DebuggerComponent(); const newDebuggerConfig = new Debugger(); newDebuggerConfig.id = randomId(); newDebuggerConfig.enable = true; newDebuggerConfig.urlPatternType = "match-all"; newDebuggerConfig.urlPattern = ""; newDebuggerConfig.enableRequestDebugger = true; newDebuggerConfig.enableResponseDebugger = true; newDebuggerConfig.callbackFunctionParamName = ""; newDebuggerConfig.comment = ""; debuggerManager.find("#js-script-hook-debugger-list").append(debuggerComponent.render(language, newDebuggerConfig)); getGlobalConfig().addDebugger(newDebuggerConfig); getGlobalConfig().persist(); }); return debuggerManager; } } module.exports = { DebuggerManagerComponent } /***/ }), /* 10 */ /***/ ((module, __unused_webpack_exports, __webpack_require__) => { const {getGlobalConfig} = __webpack_require__(5); const {DebuggerTester} = __webpack_require__(6); /** * 用于表示一个断点配置 */ class DebuggerComponent { /** * 构造初始的模板 * * @param language * @param debuggerConfig * @return {string} */ template(language, debuggerConfig) { return `
${language.debugger_config.debuggerTitle}-${debuggerConfig.id}
X
?
${language.debugger_config.enableTips}
${language.debugger_config.enable}
?
${language.debugger_config.urlPatternTips}
${language.debugger_config.urlPattern}
?
${language.debugger_config.urlPatternTypeTips}
?
${language.debugger_config.urlPatternTextTips}
?
${language.debugger_config.urlPatternTestTips}
?
${language.debugger_config.enableRequestDebuggerTips}
${language.debugger_config.enableRequestDebugger}
?
${language.debugger_config.enableResponseDebuggerTips}
${language.debugger_config.enableResponseDebugger}
?
${language.debugger_config.callbackFunctionParamNameTips}
${language.debugger_config.callbackFunctionParamName}
?
${language.debugger_config.commentTips}
${language.debugger_config.comment}
`; } /** * 渲染一条断点规则 * * @param language * @param debuggerInformation * @return {*|jQuery|HTMLElement} */ render(language, debuggerInformation) { const debuggerElt = $(this.template(language, debuggerInformation)); // 设置匹配类型 if (debuggerInformation.urlPatternType) { debuggerElt.find(`#${debuggerInformation.id}-url-pattern`).val(debuggerInformation.urlPatternType); } // 断点是否开启 debuggerElt.find(`#${debuggerInformation.id}-enable-checkbox`).on('change', function () { const localDebuggerInformation = getGlobalConfig().findDebuggerById(debuggerInformation.id); localDebuggerInformation.enable = $(this).is(':checked'); localDebuggerInformation.updateTime = new Date().getTime(); getGlobalConfig().persist(); }); // URL匹配类型 debuggerElt.find(`#${debuggerInformation.id}-url-pattern`).change(function () { const localDebuggerInformation = getGlobalConfig().findDebuggerById(debuggerInformation.id); localDebuggerInformation.urlPatternType = $(this).val(); localDebuggerInformation.updateTime = new Date().getTime(); getGlobalConfig().persist(); }); // URL匹配值 debuggerElt.find(`#${debuggerInformation.id}-url-pattern-text`).on('input', function () { const localDebuggerInformation = getGlobalConfig().findDebuggerById(debuggerInformation.id); localDebuggerInformation.urlPattern = this.value; localDebuggerInformation.updateTime = new Date().getTime(); getGlobalConfig().persist(); }); // URL匹配测试 debuggerElt.find(`#${debuggerInformation.id}-url-pattern-test`).on('click', function () { let urlForTest = prompt(language.debugger_config.urlPatternTestPrompt, ""); const debuggerConfig = getGlobalConfig().findDebuggerById(debuggerInformation.id); const result = new DebuggerTester().testUrlPattern(debuggerConfig.urlPatternType, debuggerConfig.urlPattern, urlForTest); alert(language.debugger_config.urlPatternTestResult + result); }); // enableRequestDebugger debuggerElt.find(`#${debuggerInformation.id}-enableRequestDebugger-checkbox`).on('change', function () { const localDebuggerInformation = getGlobalConfig().findDebuggerById(debuggerInformation.id); localDebuggerInformation.enableRequestDebugger = $(this).is(':checked'); localDebuggerInformation.updateTime = new Date().getTime(); getGlobalConfig().persist(); }); // enableResponseDebugger debuggerElt.find(`#${debuggerInformation.id}-enableResponseDebugger-checkbox`).on('change', function () { const localDebuggerInformation = getGlobalConfig().findDebuggerById(debuggerInformation.id); localDebuggerInformation.enableResponseDebugger = $(this).is(':checked'); localDebuggerInformation.updateTime = new Date().getTime(); getGlobalConfig().persist(); }); // ${debuggerConfig.id}-hook-type debuggerElt.find(`#${debuggerInformation.id}-hook-type`).change(function () { const localDebuggerInformation = getGlobalConfig().findDebuggerById(debuggerInformation.id); localDebuggerInformation.hookType = $(this).val(); localDebuggerInformation.updateTime = new Date().getTime(); getGlobalConfig().persist(); }); // callbackFunctionParamName debuggerElt.find(`#${debuggerInformation.id}-callbackFunctionParamName-text`).on('input', function () { const localDebuggerInformation = getGlobalConfig().findDebuggerById(debuggerInformation.id); localDebuggerInformation.callbackFunctionParamName = this.value; localDebuggerInformation.updateTime = new Date().getTime(); getGlobalConfig().persist(); }); // 注释 debuggerElt.find(`#${debuggerInformation.id}-comment-text`).on('input', function () { const localDebuggerInformation = getGlobalConfig().findDebuggerById(debuggerInformation.id); localDebuggerInformation.comment = this.value; localDebuggerInformation.updateTime = new Date().getTime(); getGlobalConfig().persist(); }); // 删除按钮 debuggerElt.find(`#${debuggerInformation.id}-remove-btn`).click(function () { $(`#${debuggerInformation.id}`).remove(); getGlobalConfig().removeDebuggerById(debuggerInformation.id); getGlobalConfig().persist(); }); return debuggerElt; } // // 鼠标划过与移走 // manifestElt.onmouseover = function () { // // // 获取元素的边界矩形 // const rect = manifestElt.getBoundingClientRect(); // // // 获取页面的滚动偏移量 // const scrollTop = window.pageYOffset || document.documentElement.scrollTop; // const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft; // // // 计算绝对位置 // const absoluteTop = rect.top + scrollTop; // const absoluteLeft = rect.left + scrollLeft; // // tips.style.display = 'block'; // tips.style.position = 'absolute'; // tips.style.left = `${absoluteLeft}px`; // tips.style.top = `${absoluteTop - tips.offsetHeight - 20}px`; // }; // manifestElt.onmouseout = function () { // tips.style.display = 'none'; // }; } module.exports = { DebuggerComponent } /***/ }), /* 11 */ /***/ ((module) => { /** * 生成一个随机 ID,格式类似于 UUID。 * * @returns {string} - 返回一个随机生成的 ID,格式为 "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"。 */ function randomId() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { // 生成一个 0 到 15 的随机整数 const r = Math.random() * 16 | 0; // 如果字符是 'x',则直接使用随机数;如果是 'y',则根据规则生成特定值 const v = c === 'x' ? r : (r & 0x3 | 0x8); // 将结果转换为十六进制字符串 return v.toString(16); }); } module.exports = { randomId }; /***/ }), /* 12 */ /***/ ((module, __unused_webpack_exports, __webpack_require__) => { // 中文菜单 const {getGlobalConfig} = __webpack_require__(5); const chinese = { global_settings: { title: "全局设置", language: "界面语言:", languageTips: "你可以修改此配置界面的语言,修改后下次进入生效!
You can modify the language of this configuration interface, and the changes will take effect the next time you enter!", flagPrefix: "Hook Flag前缀:", flagPrefixTips: "在Hook的时候会设置一些全局唯一的标志位,你可以个性化修改为自定义的前缀", flagPrefixPlaceholder: "可自定义全局前缀,未设置默认为 JSREI_js_script_hook", responseDebuggerHookType: "响应断点Hook方式:", responseDebuggerHookTypeTips: "当Hook jsonp的callback函数的时候,有两种方式实现Hook:

一种是替换掉callback方法的引用,相当于是一个代理函数,这种需要在命中断点后再点一下跟进去callback函数的实现,这种方式兼容性比较好,绝大多数网站都可以丝滑兼容;

还有一种方式是直接改写callback函数的函数体,相当于是对函数的代码实现进行编辑后重新声明,这样子可以直接把断点打在callback函数体中,但此种方式可能会有一些作用域的兼容性问题,如有遇到报错,请调整为代理方式实现Hook;

注意,此选项修改后刷新页面后生效", responseDebuggerHookTypeUseProxyFunction: "使用代理函数实现Hook", responseDebuggerHookTypeUseRedeclareFunction: "直接修改网站callback函数体(注意可能会有兼容性问题)", isIgnoreJsSuffixRequest: "是否忽略.js后缀的请求:", isIgnoreJsSuffixRequestTips: "大多数时候.js后缀的请求都是单纯的加载JavaScript资源文件,可以选择忽略掉这类请求,当勾选的时候,控制台上也不会再打印.js请求", isIgnoreNotJsonpRequest: "是否忽略不是jsonp的请求:", isIgnoreNotJsonpRequestTips: "如果只关注jsonp类型的请求,可以选择忽略掉其它请求,当勾选的时候,控制台上也不会再打印非jsonp请求", autoJumpProjectSiteOnConfiguraion: "跳转到项目主页打开此界面以防样式错乱:", autoJumpProjectSiteOnConfiguraionTips: "油猴脚本注入的界面可能会跟网页中原有的样式发生冲突或者污染,从而导致样式错乱,跳转到经过测试的项目主页打开设置界面可以有效防止布局错乱,推荐勾选此选项", }, debugger_config: { debuggerTitle: "断点配置", enable: "是否启用此断点:", enableTips: "是否启用此断点,仅当断点处于启用状态的时候才会生效,取消勾选可以暂时禁用断点而无需删除。", urlPattern: "URL匹配方式:", urlPatternTips: "URL匹配方式用于指定当Script的URL符合什么条件时命中此断点", urlPatternTypeTips: "指定以什么方式匹配Script URL:", urlPatternType_EqualsThisString: "Script URL需要完全匹配给定的字符串", urlPatternType_ContainsThisString: "Script URL包含给定的字符串", urlPatternType_MatchThisRegexp: "Script URL匹配给定的正则表达式", urlPatternType_MatchALL: "直接匹配所有Script URL", urlPatternTextTips: "输入关键字或者表达式", urlPatternTextPlaceholder: "输入关键字或者表达式", urlPatternTest: "测试", urlPatternTestTips: "你可以输入一个script url测试此断点对其命中情况", urlPatternTestPrompt: "请输入要测试的URL:", urlPatternTestResult: "测试结果:", enableRequestDebugger: "是否开启请求断点:", enableRequestDebuggerTips: "启用请求断点后,在script请求发出之前进入断点", enableResponseDebugger: "是否开启响应断点:", enableResponseDebuggerTips: "启用响应断点之后,在jsonp请求的回调函数中命中断点", callbackFunctionParamName: "jsonp回调函数参数名称:", callbackFunctionParamNameTips: "不指定的话会使用内置引擎自动推测jsonp参数名称,推测失败的话可以手动指定", callbackFunctionParamNamePlaceholder: "不指定的话会使用内置引擎自动推测jsonp参数名称", comment: "备注:", commentTips: "你可以输入一些备注,或者相关信息的一些上下文,以防止时间长了之后忘记。", commentPlaceholder: "好记性不如烂笔头", }, console: { tableKey: "键", tableValue: "值", tableComment: "备注", titleRequest: "Script Hook 捕捉到请求", titleResponse: "Script Hook 捕捉到响应", time: "时间", requestId: "请求ID", isJsonpRequest: "是否是jsonp请求", hostname: "请求域名", path: "请求路径", param: "请求参数", hash: "请求#hash", paramName: "参数名称", paramValue: "参数值", isJsonpCallback: "是否是jsonp回调函数", codeLocation: "代码位置", } }; // 英文菜单 const english = { "global_settings": { "title": "Global Settings", "language": "Interface Language:", "languageTips": "你可以修改此配置界面的语言,修改后下次进入生效!
You can modify the language of this configuration interface, and the changes will take effect the next time you enter!", "flagPrefix": "Hook Flag Prefix:", "flagPrefixTips": "When hooking, some globally unique flags will be set. You can customize the prefix.", "flagPrefixPlaceholder": "You can customize the global prefix. If not set, the default is JSREI_js_script_hook.", "responseDebuggerHookType": "Response Breakpoint Hook Method:", "responseDebuggerHookTypeTips": "When hooking the callback function of JSONP, there are two ways to implement the hook:

One is to replace the reference of the callback function, which acts as a proxy function. This requires stepping into the callback function implementation after hitting the breakpoint. This method has better compatibility and works smoothly on most websites.

The other method is to directly rewrite the function body of the callback function, which is equivalent to editing and redeclaring the function's code. This allows you to place the breakpoint directly in the callback function body, but there may be some scope compatibility issues. If you encounter errors, please switch to the proxy method.

Note: This option takes effect after refreshing the page.", "responseDebuggerHookTypeUseProxyFunction": "Use Proxy Function to Implement Hook", "responseDebuggerHookTypeUseRedeclareFunction": "Directly Modify the Callback Function Body (Note: There May Be Compatibility Issues)", "isIgnoreJsSuffixRequest": "Ignore .js Suffix Requests:", "isIgnoreJsSuffixRequestTips": "Most of the time, requests with a .js suffix are simply loading JavaScript resource files. You can choose to ignore such requests. When checked, .js requests will not be printed on the console.", "isIgnoreNotJsonpRequest": "Ignore Non-JSONP Requests:", "isIgnoreNotJsonpRequestTips": "If you are only concerned with JSONP-type requests, you can choose to ignore other requests. When checked, non-JSONP requests will not be printed on the console.", "autoJumpProjectSiteOnConfiguraion": "Jump to the Project Homepage to Open This Interface to Prevent Style Issues:", "autoJumpProjectSiteOnConfiguraionTips": "The interface injected by the Tampermonkey script may conflict with or pollute the original styles of the webpage, causing style issues. Jumping to the tested project homepage to open the settings interface can effectively prevent layout issues. It is recommended to check this option." }, "debugger_config": { "debuggerTitle": "Breakpoint Configuration", "enable": "Enable This Breakpoint:", "enableTips": "Whether to enable this breakpoint. It will only take effect when the breakpoint is enabled. Unchecking it can temporarily disable the breakpoint without deleting it.", "urlPattern": "URL Matching Method:", "urlPatternTips": "The URL matching method is used to specify when the script's URL meets certain conditions to hit this breakpoint.", "urlPatternTypeTips": "Specify how to match the Script URL:", "urlPatternType_EqualsThisString": "The Script URL must exactly match the given string.", "urlPatternType_ContainsThisString": "The Script URL contains the given string.", "urlPatternType_MatchThisRegexp": "The Script URL matches the given regular expression.", "urlPatternType_MatchALL": "Directly match all Script URLs.", "urlPatternTextTips": "Enter a keyword or expression.", "urlPatternTextPlaceholder": "Enter a keyword or expression.", "urlPatternTest": "Test", "urlPatternTestTips": "You can enter a script URL to test whether this breakpoint hits it.", "urlPatternTestPrompt": "Please enter the URL to test:", "urlPatternTestResult": "Test Result:", "enableRequestDebugger": "Enable Request Breakpoint:", "enableRequestDebuggerTips": "After enabling the request breakpoint, the breakpoint will be triggered before the script request is sent.", "enableResponseDebugger": "Enable Response Breakpoint:", "enableResponseDebuggerTips": "After enabling the response breakpoint, the breakpoint will be triggered in the callback function of the JSONP request.", "callbackFunctionParamName": "JSONP Callback Function Parameter Name:", "callbackFunctionParamNameTips": "If not specified, the built-in engine will automatically infer the JSONP parameter name. If the inference fails, you can manually specify it.", "callbackFunctionParamNamePlaceholder": "If not specified, the built-in engine will automatically infer the JSONP parameter name.", "comment": "Comment:", "commentTips": "You can enter some comments or contextual information to avoid forgetting it over time.", "commentPlaceholder": "A good memory is not as good as a written record." }, "console": { tableKey: "key", tableValue: "value", tableComment: "comment", "titleRequest": "Script Hook Captured Request", "titleResponse": "Script Hook Captured Response", "time": "Time", "requestId": "Request ID", "isJsonpRequest": "Is JSONP Request", "hostname": "Request Hostname", "path": "Request Path", "param": "Request Parameters", "hash": "Request #hash", "paramName": "Parameter Name", "paramValue": "Parameter Value", "isJsonpCallback": "Is JSONP Callback Function", "codeLocation": "Code Location" } }; /** * * @return {{debugger_config: {urlPatternTest: string, urlPatternTestTips: string, enableTips: string, commentPlaceholder: string, debuggerTitle: string, enableRequestDebuggerTips: string, enableResponseDebugger: string, enableRequestDebugger: string, callbackFunctionParamName: string, urlPatternTypeTips: string, urlPatternTips: string, urlPatternType_MatchThisRegexp: string, urlPatternType_MatchALL: string, urlPatternType_EqualsThisString: string, urlPatternTextPlaceholder: string, enable: string, commentTips: string, urlPatternTextTips: string, enableResponseDebuggerTips: string, callbackFunctionParamNameTips: string, callbackFunctionParamNamePlaceholder: string, comment: string, urlPattern: string, urlPatternType_ContainsThisString: string}, global_settings: {languageTips: string, autoJumpProjectSiteOnConfiguraionTips: string, flagPrefix: string, flagPrefixTips: string, isIgnoreJsSuffixRequestTips: string, autoJumpProjectSiteOnConfiguraion: string, isIgnoreJsSuffixRequest: string, language: string, isIgnoreNotJsonpRequestTips: string, title: string, flagPrefixPlaceholder: string, isIgnoreNotJsonpRequest: string}}|{debugger_config: {urlPatternTest: string, urlPatternTestTips: string, enableTips: string, commentPlaceholder: string, enableRequestDebuggerTips: string, enableResponseDebugger: string, enableRequestDebugger: string, callbackFunctionParamName: string, urlPatternTypeTips: string, urlPatternTips: string, urlPatternType_MatchThisRegexp: string, urlPatternType_MatchALL: string, urlPatternType_EqualsThisString: string, urlPatternTextPlaceholder: string, enable: string, commentTips: string, urlPatternTextTips: string, enableResponseDebuggerTips: string, callbackFunctionParamNameTips: string, callbackFunctionParamNamePlaceholder: string, comment: string, urlPattern: string, urlPatternType_ContainsThisString: string}, global_settings: {languageTips: string, autoJumpProjectSiteOnConfiguraionTips: string, flagPrefix: string, flagPrefixTips: string, isIgnoreJsSuffixRequestTips: string, autoJumpProjectSiteOnConfiguraion: string, isIgnoreJsSuffixRequest: string, language: string, isIgnoreNotJsonpRequestTips: string, title: string, flagPrefixPlaceholder: string, isIgnoreNotJsonpRequest: string}}} */ function getLanguageByGlobalConfig() { return getLanguage(getGlobalConfig().language); } /** * * @param language * @return {{debugger_config: {urlPatternTest: string, urlPatternTestTips: string, enableTips: string, commentPlaceholder: string, debuggerTitle: string, enableRequestDebuggerTips: string, enableResponseDebugger: string, enableRequestDebugger: string, callbackFunctionParamName: string, urlPatternTypeTips: string, urlPatternTips: string, urlPatternType_MatchThisRegexp: string, urlPatternType_MatchALL: string, urlPatternType_EqualsThisString: string, urlPatternTextPlaceholder: string, enable: string, commentTips: string, urlPatternTextTips: string, enableResponseDebuggerTips: string, callbackFunctionParamNameTips: string, callbackFunctionParamNamePlaceholder: string, comment: string, urlPattern: string, urlPatternType_ContainsThisString: string}, global_settings: {languageTips: string, autoJumpProjectSiteOnConfiguraionTips: string, flagPrefix: string, flagPrefixTips: string, isIgnoreJsSuffixRequestTips: string, autoJumpProjectSiteOnConfiguraion: string, isIgnoreJsSuffixRequest: string, language: string, isIgnoreNotJsonpRequestTips: string, title: string, flagPrefixPlaceholder: string, isIgnoreNotJsonpRequest: string}}|{debugger_config: {urlPatternTest: string, urlPatternTestTips: string, enableTips: string, commentPlaceholder: string, enableRequestDebuggerTips: string, enableResponseDebugger: string, enableRequestDebugger: string, callbackFunctionParamName: string, urlPatternTypeTips: string, urlPatternTips: string, urlPatternType_MatchThisRegexp: string, urlPatternType_MatchALL: string, urlPatternType_EqualsThisString: string, urlPatternTextPlaceholder: string, enable: string, commentTips: string, urlPatternTextTips: string, enableResponseDebuggerTips: string, callbackFunctionParamNameTips: string, callbackFunctionParamNamePlaceholder: string, comment: string, urlPattern: string, urlPatternType_ContainsThisString: string}, global_settings: {languageTips: string, autoJumpProjectSiteOnConfiguraionTips: string, flagPrefix: string, flagPrefixTips: string, isIgnoreJsSuffixRequestTips: string, autoJumpProjectSiteOnConfiguraion: string, isIgnoreJsSuffixRequest: string, language: string, isIgnoreNotJsonpRequestTips: string, title: string, flagPrefixPlaceholder: string, isIgnoreNotJsonpRequest: string}}} */ function getLanguage(language) { switch (language) { case "chinese": return chinese; case "english": return english; default: return english; } } module.exports = { getLanguage, getLanguageByGlobalConfig, chinese, english } /***/ }), /* 13 */ /***/ ((module) => { // 定义要添加的 CSS 规则 const css = ` /* ================================== 最外边的窗口 ================================== */ /* 容器 div */ .js-script-hook-scrollable-div { max-height: 100vh; /* 最大高度等于视口高度 */ overflow-y: auto; /* 超出时显示垂直滚动条 */ border: 2px solid #4CAF50; padding: 20px; box-sizing: border-box; /* 确保 padding 和 border 不增加总高度 */ } /* ================================== 输入框 ================================== */ /* 输入框样式 */ .js-script-hook-input[type="text"] { flex: 1; width: 100%; padding: 5px; margin-bottom: 15px; border: 2px solid #ddd; border-radius: 5px; font-size: 15px; transition: border-color 0.3s ease, box-shadow 0.3s ease; outline: none; } /* 输入框聚焦效果 */ .js-script-hook-input[type="text"]:focus { border-color: #4CAF50; box-shadow: 0 0 8px rgba(76, 175, 80, 0.3); } /* ================================== 按钮 ================================== */ /* 按钮样式 */ .js-script-hook-button { background-color: #4CAF50; color: white; padding: 3px 12px; border: none; border-radius: 5px; font-size: 12px; font-weight: bold; cursor: pointer !important; transition: background-color 0.3s ease, transform 0.2s ease; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } /* 按钮悬停效果 */ .js-script-hook-button:hover { background-color: #45a049; transform: translateY(-2px); box-shadow: 0 6px 8px rgba(0, 0, 0, 0.15); } /* 按钮点击效果 */ js-script-hook-button:active { background-color: #3d8b40; transform: translateY(0); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } /* 禁用按钮样式 */ .js-script-hook-button:disabled { background-color: #cccccc; cursor: not-allowed; opacity: 0.7; } /* ================================== 复选框 ================================== */ /* 隐藏原生复选框 */ .js-script-hook-input[type="checkbox"] { display: none; } /* 自定义复选框的容器 */ .js-script-hook-checkbox-container { display: flex; align-items: center; margin-bottom: 10px; cursor: pointer; } /* 自定义复选框的外观 */ .js-script-hook-checkbox-container .js-script-hook-custom-checkbox { width: 20px; height: 20px; border: 2px solid #ccc; border-radius: 4px; margin-right: 10px; position: relative; transition: background-color 0.3s ease, border-color 0.3s ease; } /* 复选框选中时的样式 */ .js-script-hook-input[type="checkbox"]:checked + .js-script-hook-custom-checkbox { background-color: #4CAF50; border-color: #4CAF50; } /* 复选框选中时的对勾图标 */ .js-script-hook-input[type="checkbox"]:checked + .js-script-hook-custom-checkbox::after { content: ''; position: absolute; left: 6px; top: 2px; width: 5px; height: 10px; border: solid white; border-width: 0 2px 2px 0; transform: rotate(45deg); } /* 悬停效果 */ .js-script-hook-checkbox-container:hover .js-script-hook-custom-checkbox { border-color: #4CAF50; } /* 禁用状态 */ .js-script-hook-input[type="checkbox"]:disabled + .js-script-hook-custom-checkbox { background-color: #f0f0f0; border-color: #ccc; cursor: not-allowed; } .js-script-hook-input[type="checkbox"]:disabled:checked + .js-script-hook-custom-checkbox { background-color: #ccc; } /* ================================== 下拉框 ================================== */ /* 下拉框容器 */ .js-script-hook-select-container { position: relative; width: 200px; } /* 美化下拉框 */ .js-script-hook-select-container select { width: 100%; padding: 5px; font-size: 15px; border: 2px solid #ccc; border-radius: 5px; background-color: white; appearance: none; /* 隐藏默认箭头 */ -webkit-appearance: none; /* 兼容 Safari */ -moz-appearance: none; /* 兼容 Firefox */ cursor: pointer; transition: border-color 0.3s ease, box-shadow 0.3s ease; } /* 自定义下拉箭头 */ .js-script-hook-select-container::after { content: '▼'; position: absolute; top: 50%; right: 10px; transform: translateY(-50%); pointer-events: none; /* 防止点击箭头时触发下拉框 */ color: #666; font-size: 12px; } /* 悬停效果 */ .js-script-hook-select-container select:hover { border-color: #4CAF50; } /* 聚焦效果 */ .js-script-hook-select-container select:focus { border-color: #4CAF50; box-shadow: 0 0 8px rgba(76, 175, 80, 0.3); outline: none; } /* 禁用状态 */ .js-script-hook-select-container select:disabled { background-color: #f0f0f0; cursor: not-allowed; opacity: 0.7; } /* ================================== 问号提示 ================================== */ /* 问号图标 */ .js-script-hook-tips-icon { display: inline-block; width: 20px; height: 20px; line-height: 20px; text-align: center; background-color: #DDD; color: white; border-radius: 50%; font-size: 16px; font-weight: bold; cursor: pointer; position: relative; } /* 提示信息 */ .js-script-hook-tips-icon .js-script-hook-tooltip { visibility: hidden; opacity: 0; width: 500px; background-color: #333; color: white; text-align: left; border-radius: 5px; padding: 10px; position: absolute; z-index: 2147483647; bottom: 50%; /* 提示信息垂直居中 */ left: 125%; /* 提示信息位于问号右侧 */ transform: translateY(50%); /* 垂直居中 */ transition: opacity 0.3s ease, visibility 0.3s ease; } /* 提示信息箭头 */ .js-script-hook-tips-icon .js-script-hook-tooltip::after { content: ''; position: absolute; top: 50%; left: -5px; /* 箭头位于提示信息的左侧 */ margin-top: -5px; /* 垂直居中 */ border-width: 5px; border-style: solid; border-color: transparent #333 transparent transparent; /* 箭头指向左侧 */ } /* 鼠标悬停时显示提示信息 */ .js-script-hook-tips-icon:hover .js-script-hook-tooltip { visibility: visible; opacity: 1; } /* ================================== 多行文本框 ================================== */ /* 美化 Textarea */ .js-script-hook-textarea { width: 100%; height: 150px; padding: 12px; font-size: 16px; border: 2px solid #ccc; border-radius: 8px; background-color: #f9f9f9; resize: vertical; /* 允许垂直调整大小 */ outline: none; transition: border-color 0.3s ease, box-shadow 0.3s ease; } /* 悬停效果 */ .js-script-hook-textarea:hover { border-color: #4CAF50; } /* 聚焦效果 */ .js-script-hook-textarea:focus { border-color: #4CAF50; box-shadow: 0 0 8px rgba(76, 175, 80, 0.3); } /* 禁用状态 */ .js-script-hook-textarea:disabled { background-color: #f0f0f0; cursor: not-allowed; opacity: 0.7; } `; /** * 把当前hook需要用到的样式表追加到页面中 */ function appendScriptHookStyleToCurrentPage() { // 避免重复插入 if ($("#js-script-hook-style").length > 0) { return; } // 创建一个