// ==UserScript== // @name ElementRaid Test // @namespace https://bbs.tampermonkey.net.cn/ // @version 0.0.5 // @description ElementRaid Test // @author EmpyrealTear // @match .*://.* // @require https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js // @require https://scriptcat.org/lib/513/2.0.0/ElementGetter.js // require https://scriptcat.org/lib/2628/6.2.0.1/moduleRaid.js // @grant unsafeWindow // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @run-at document-start // ==/UserScript== // const xhrOpen = XMLHttpRequest.prototype.open; // XMLHttpRequest.prototype.open = function () { // const xhr = this // // 是否显示默认被隐藏的卖价 // if (/logan[^\/]+\.js/.test(arguments[1])) { // console.log('-------------------------SUCCESS') // const getter = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, "responseText").get // Object.defineProperty(xhr, "responseText", { // get: () => { // let result = getter.call(xhr) // console.log(result) // result = result.replace('window.webpackJpLogan20=window.webpackJpLogan20||[]', 'window.webpackJpLogan20=window.webpackJpLogan20||[], debugger') // debugger // return result // }, // }) // } // return xhrOpen.apply(xhr, arguments) // } // 监听所有网络请求 // const observer = new PerformanceObserver((list) => { // list.getEntries().forEach((entry) => { // if (entry.initiatorType === 'script' && /logan_[^\/]+\.js/.test(entry.name)) { // console.log('Script loaded:', entry.name); // // 检查加载状态 // if (entry.duration > 0) { // console.log('Script loaded successfully:', entry.name); // } else { // console.error('Script failed to load:', entry.name); // } // } // }); // }); // observer.observe({ entryTypes: ['resource'] }); // if ('serviceWorker' in navigator) { // navigator.serviceWorker.register('/logan_2.1.5.js') // .then((registration) => { // console.log('Service Worker 注册成功,作用范围:', registration.scope); // }) // .catch((error) => { // console.error('Service Worker 注册失败:', error); // }); // } // 开始观察网络请求 // const observer = new MutationObserver((mutationsList) => { // for (const mutation of mutationsList) { // if (mutation.type === 'childList') { // mutation.addedNodes.forEach((node) => { // if (node.tagName === 'SCRIPT' && /logan_[^\/]+\.js/.test(node.src)) { // console.log('Detected script tag with src:', node.src); // // 阻止原始脚本加载 // node.remove(); // // 使用 Fetch API 加载脚本 // fetch(node.src) // .then((response) => response.text()) // .then((scriptContent) => { // // console.log('Script loaded:', scriptContent); // // 动态执行脚本 // const newScript = document.createElement('script'); // newScript.textContent = scriptContent // .replace('window.webpackJpLogan20=window.webpackJpLogan20||[]', 'window.webpackJpLogan20=window.webpackJpLogan20||[],window.webpackJpLogan20.require=o'); // document.head.appendChild(newScript); // }) // .catch((error) => { // console.error('Failed to load script:', error); // }); // } // }); // } // } // }); // observer.observe(document.documentElement, { // childList: true, // subtree: true // }); // 劫持 document.createElement // const originalCreateElement = document.createElement; // document.createElement = function (tagName) { // if (tagName.toLowerCase() === 'script') { // const script = originalCreateElement.call(document, tagName); // // 劫持 src 属性 // let src = ''; // Object.defineProperty(script, 'src', { // get() { // return src; // }, // set(value) { // // 检查 src 是否以目标后缀结尾 // if (/logan_[^\/]+\.js/.test(value)) { // console.log('Detected script tag with src:', value); // // 使用 Fetch API 加载脚本 // fetch(value) // .then((response) => response.text()) // .then((scriptContent) => { // // console.log('Script loaded:', scriptContent); // // 动态执行脚本 // const newScript = document.createElement('script'); // newScript.textContent = scriptContent // .replace('window.webpackJpLogan20=window.webpackJpLogan20||[]', 'window.webpackJpLogan20=window.webpackJpLogan20||[],window.webpackJpLogan20.require=o'); // document.head.appendChild(newScript); // }) // .catch((error) => { // console.error('Failed to load script:', error); // }); // this.remove() // } else { // src = value // } // return true; // } // }); // return script; // } // return originalCreateElement.call(document, tagName); // }; /** * Main moduleRaid class */ class ModuleRaid { /** * moduleRaid constructor * * @example * Constructing an instance without any arguments: * ```ts * const mR = new ModuleRaid() * ``` * * Constructing an instance with the optional `opts` object: * ```ts * const mR = new ModuleRaid({ entrypoint: 'webpackChunk_custom_name' }) * ``` * * @param opts a object containing options to initialize moduleRaid with * - **opts:** * - _target_: the window object being searched for * - _entrypoint_: the Webpack entrypoint present on the global window object * - _debug_: whether debug mode is enabled or not * - _strict_: whether strict mode is enabled or not */ constructor(opts) { /** * A random generated module ID we use for injecting into Webpack */ this.moduleID = Math.random().toString(36).substring(7); /** * An array containing different argument injection methods for * Webpack (before version 4), and subsequently pulling out methods and modules * @internal */ this.functionArguments = [ [ [0], [ (_e, _t, i) => { this.modules = i.c; this.constructors = i.m; this.get = i; }, ], ], [ [1e3], { [this.moduleID]: (_e, _t, i) => { this.modules = i.c; this.constructors = i.m; this.get = i; }, }, [[this.moduleID]], ], ]; /** * An array containing different argument injection methods for * Webpack (after version 4), and subsequently pulling out methods and modules * @internal */ this.arrayArguments = [ // webpackJsonpCallback(chunkIds, executeModules, moreModules) this.functionArguments[1], // webpackJsonpCallback(chunkIds, moreModules, executeModules) [ [this.moduleID], {}, (e) => { const mCac = e.m; Object.keys(mCac).forEach((mod) => { try { this.modules[mod] = e(mod); } catch (err) { this.log(`[arrayArguments/1] Failed to require(${mod}) with error:\n${err}\n${err.stack}`); } }); this.get = e; }, ], ]; /** * Storage for the modules we extracted from Webpack */ this.modules = {}; /** * Storage for the constructors we extracted from Webpack */ this.constructors = []; let options = { target: window, entrypoint: 'webpackJsonp', debug: false, strict: false, }; if (typeof opts === 'object') { options = Object.assign(Object.assign({}, options), opts); } this.target = options.target this.entrypoint = options.entrypoint; this.debug = options.debug; this.strict = options.strict; this.detectEntrypoint(); this.fillModules(); this.replaceGet(); this.setupPushEvent(); } /** * Debug logging method, outputs to the console when {@link ModuleRaid.debug} is true * * @param {*} message The message to be logged * @internal */ log(message) { if (this.debug) { console.warn(`[moduleRaid] ${message}`); } } /** * Method to set an alternative getter if we weren't able to extract __webpack_require__ * from Webpack * @internal */ replaceGet() { if (this.get === null) { this.get = (key) => this.modules[key]; } } /** * Method that will try to inject a module into Webpack or get modules * depending on it's success it might be more or less brute about it * @internal */ fillModules() { if (typeof this.target[this.entrypoint] === 'function') { this.functionArguments.forEach((argument, index) => { try { if (this.modules && Object.keys(this.modules).length > 0) return; this.target[this.entrypoint](...argument); } catch (err) { this.log(`moduleRaid.functionArguments[${index}] failed:\n${err}\n${err.stack}`); } }); } else { this.arrayArguments.forEach((argument, index) => { try { if (this.modules && Object.keys(this.modules).length > 0) return; this.target[this.entrypoint].push(argument); } catch (err) { this.log(`Pushing moduleRaid.arrayArguments[${index}] into ${this.entrypoint} failed:\n${err}\n${err.stack}`); } }); } if (this.modules && Object.keys(this.modules).length == 0) { let moduleEnd = false; let moduleIterator = 0; if (typeof this.target[this.entrypoint] != 'function' || !this.target[this.entrypoint]([], [], [moduleIterator])) { throw Error('Unknown Webpack structure'); } while (!moduleEnd) { try { this.modules[moduleIterator] = this.target[this.entrypoint]([], [], [moduleIterator]); moduleIterator++; } catch (err) { moduleEnd = true; } } } } /** * Method to hook into `this.target[this.entrypoint].push` adding a listener for new * chunks being pushed into Webpack * * @example * You can listen for newly pushed packages using the `moduleraid:webpack-push` event * on `document` * * ```ts * document.addEventListener('moduleraid:webpack-push', (e) => { * // e.detail contains the arguments push() was called with * console.log(e.detail) * }) * ``` * @internal */ setupPushEvent() { const originalPush = this.target[this.entrypoint].push; this.target[this.entrypoint].push = (...args) => { const result = Reflect.apply(originalPush, this.target[this.entrypoint], args); document.dispatchEvent(new CustomEvent('moduleraid:webpack-push', { detail: args })); return result; }; } /** * Method to try autodetecting a Webpack JSONP entrypoint based on common naming * * If the default entrypoint, or the entrypoint that's passed to the moduleRaid constructor * already matches, the method exits early * * If `options.strict` has been set in the constructor and the initial entrypoint cannot * be found, this method will error, demanding a strictly set entrypoint * @internal */ detectEntrypoint() { if (this.target[this.entrypoint] != undefined) { return; } if (this.strict) { throw Error(`Strict mode is enabled and entrypoint at window.${this.entrypoint} couldn't be found. Please specify the correct one!`); } let windowObjects = Object.keys(this.target); windowObjects = windowObjects .filter((object) => object.toLowerCase().includes('chunk') || object.toLowerCase().includes('webpack')) .filter((object) => typeof this.target[object] === 'function' || Array.isArray(this.target[object])); if (windowObjects.length > 1) { throw Error(`Multiple possible endpoints have been detected, please create a new moduleRaid instance with a specific one:\n${windowObjects.join(', ')}`); } if (windowObjects.length === 0) { throw Error('No Webpack JSONP entrypoints could be detected'); } this.log(`Entrypoint has been detected at window.${windowObjects[0]} and set for injection`); this.entrypoint = windowObjects[0]; } /** * Recursive object-search function for modules * * @param object the object to search through * @param query the query the object keys/values are searched for * @returns boolean state of `object` containing `query` somewhere in it * @internal */ searchObject(object, query) { for (const key in object) { const value = object[key]; const lowerCaseQuery = query.toLowerCase(); if (typeof value != 'object') { const lowerCaseKey = key.toString().toLowerCase(); if (lowerCaseKey.includes(lowerCaseQuery)) return true; if (typeof value != 'object') { const lowerCaseValue = value.toString().toLowerCase(); if (lowerCaseValue.includes(lowerCaseQuery)) return true; } else { if (this.searchObject(value, query)) return true; } } } return false; } /** * Method to search through the module object, searching for the fitting content * if a string is supplied * * If query is supplied as a function, everything that returns true when passed * to the query function will be returned * * @example * With a string as query argument: * ```ts * const results = mR.findModule('feature') * // => Array of module results * ``` * * With a function as query argument: * ```ts * const results = mR.findModule((module) => { typeof module === 'function' }) * // => Array of module results * ``` * * @param query query to search the module list for * @return a list of modules fitting the query */ findModule(query) { const results = []; const modules = Object.keys(this.modules); if (modules.length === 0) { throw new Error('There are no modules to search through!'); } modules.forEach((key) => { const module = this.modules[key.toString()]; if (module === undefined) return; try { if (typeof query === 'string') { query = query.toLowerCase(); switch (typeof module) { case 'string': if (module.toLowerCase().includes(query)) results.push(module); break; case 'function': if (module.toString().toLowerCase().includes(query)) results.push(module); break; case 'object': if (this.searchObject(module, query)) results.push(module); break; } } else if (typeof query === 'function') { if (query(module)) results.push(module); } else { throw new TypeError(`findModule can only find via string and function, ${typeof query} was passed`); } } catch (err) { this.log(`There was an error while searching through module '${key}':\n${err}\n${err.stack}`); } }); return results; } /** * Method to search through the constructor array, searching for the fitting content * if a string is supplied * * If query is supplied as a function, everything that returns true when passed * to the query function will be returned * * @example * With a string as query argument: * ```ts * const results = mR.findConstructor('feature') * // => Array of constructor/module tuples * ``` * * With a function as query argument: * ```ts * const results = mR.findConstructor((constructor) => { constructor.prototype.value !== undefined }) * // => Array of constructor/module tuples * ``` * * Accessing the resulting data: * ```ts * // With array destructuring (ES6) * const [constructor, module] = results[0] * * // ...or... * * // regular access * const constructor = results[0][0] * const module = results[0][1] * ``` * * @param query query to search the constructor list for * @returns a list of constructor/module tuples fitting the query */ findConstructor(query) { const results = []; const constructors = Object.keys(this.constructors); if (constructors.length === 0) { throw new Error('There are no constructors to search through!'); } constructors.forEach((key) => { const constructor = this.constructors[key]; try { if (typeof query === 'string') { query = query.toLowerCase(); if (constructor.toString().toLowerCase().includes(query)) results.push([this.constructors[key], this.modules[key]]); } else if (typeof query === 'function') { if (query(constructor)) results.push([this.constructors[key], this.modules[key]]); } } catch (err) { this.log(`There was an error while searching through constructor '${key}':\n${err}\n${err.stack}`); } }); return results; } } class ElementRaid { constructor(opts) { let options = { target: document.body, // 目标 DOM 节点 entrypoint: '_', // DOM 节点内部实例的关键字 children: [], // 子节点 }; // 合并用户配置 if (typeof opts === 'object') { options = { ...options, ...opts }; } // 初始化属性 this.target = options.target; this.entrypoint = options.entrypoint; this.children = [...options.children]; } /** * 查找目标节点及其子节点中的 React 实例 * @param {HTMLElement} target - 目标 DOM 节点 * @returns {Array} - 包含 DOM 节点的列表 */ closest(target) { let queue = [target]; let children = []; while (queue.length > 0) { let cur = queue.pop(); let elmObjects = Object.keys(cur); elmObjects = elmObjects.filter((v) => v.includes(this.entrypoint)); if (elmObjects.length > 0) { children.push(cur); } else { queue.push(...cur.children); } } return children; } /** * 遍历 children 并返回符合筛选条件的子节点 * @param {Object|Function} filter - 筛选条件,可以是对象或函数 * @returns {Array} - 符合筛选条件的子节点列表 */ filterChildren(obj, filter) { // 如果 filter 是函数,则直接使用该函数进行筛选 if (typeof filter === 'function') { return obj.children.filter(filter); } // 如果 filter 是对象,则将其作为属性匹配条件 if (typeof filter === 'object' && filter !== null) { return obj.children.filter((child) => { // 检查 child 的属性是否与 filter 对象中的属性匹配 return Object.keys(filter).every((key) => { // 如果 filter 中的属性值是函数,则调用该函数进行匹配 if (typeof filter[key] === 'function') { return filter[key](child[key]); } // 否则直接比较属性值 return child[key] === filter[key]; }); }); } // 如果 filter 不是函数也不是对象,则返回空数组 return []; } /** * 遍历所有子节点并返回符合筛选条件的节点 * @param {Object|Function} filter - 筛选条件,可以是对象或函数 * @param {boolean} deep - 是否深度遍历所有子节点,默认为 false * @returns {Array} - 符合筛选条件的节点列表 */ filterAllChildren(filter, deep = false) { let queue = [...this.children] let matched = [] while (queue.length > 0) { if (matched.length > 0 && !deep) break let obj = queue.pop() let res = this.filterChildren(obj, filter) if (res.length > 0) { matched.push(...res) } if (obj.children.length > 0) { queue.push(...obj.children) } } return matched; } } class ReactRaid extends ElementRaid { constructor(opts) { let options = { target: document.body, // 目标 DOM 节点 entrypoint: 'react', // React 内部实例的关键字 children: [], // 子节点 }; // 合并用户配置 if (typeof opts === 'object') { options = { ...options, ...opts }; } super(options) // 如果未提供子节点,则从目标节点的子节点中查找 if (this.target.children.length > 0 && this.children.length == 0) { Array.from(this.target.children).forEach((v) => { this.children.push(...this.closest(v).map(v => new ReactRaid({ target: v }))); }); } // 获取 React 实例和父组件 this.instance = this.getInstance(this.target); this.parentComponent = this.findParent(this.instance); this.props = this.getProperties(this.target); this.events = this.getEvents(this.instance); } /** * 获取目标节点的 React 属性 * @param {HTMLElement} elm - 目标 DOM 节点 * @returns {Object} - React 属性 */ getProperties(elm) { let props = {}; // 遍历节点的属性,提取 React 相关属性 Object.keys(elm).forEach((key) => { if (key.includes(this.entrypoint)) { const propName = key .replace(/\$.+$/, '') // 去除后缀 .replace(/Listening.*$/, 'Listening'); // 处理事件监听器 props[propName] = elm[key]; } }); // 提取嵌套属性(如 props.children) if (elm._reactProps) { Object.assign(props, elm._reactProps); } return props; } /** * 获取目标节点的 React 实例 * @param {HTMLElement} elm - 目标 DOM 节点 * @returns {Object|null} - React 实例 */ getInstance(elm) { // 查找 React 15 或 React 16+ 的内部实例 const key = Object.keys(elm).find( (key) => key.startsWith('__reactInternalInstance$') || // React 15 key.startsWith('__reactFiber$') // React 16+ ); return key ? elm[key] : null; } /** * 获取 React 实例的父组件 * @param {Object} internalInstance - React 实例 * @returns {Object|null} - 父组件 */ findParent(internalInstance) { if (internalInstance == null) return null; // 兼容不同版本的 React return ( internalInstance._debugOwner ?? // React 16+ 开发模式 internalInstance.return ?? // React 16+ internalInstance._currentElement?._owner // React 15 ); } /** * 获取 React 实例的事件监听器 * @param {Object} internalInstance - React 实例 * @returns {Object|null} - 事件监听器 */ getEvents(internalInstance) { if (internalInstance == null) return null; // 提取事件监听器 const events = {}; Object.keys(internalInstance).forEach((key) => { if (key.includes('Listener') || key.includes('Handler')) { events[key] = internalInstance[key]; } }); // 提取嵌套事件(如 props.onClick) if (internalInstance.memoizedProps) { Object.keys(internalInstance.memoizedProps).forEach((key) => { if (key.startsWith('on') && typeof internalInstance.memoizedProps[key] === 'function') { events[key] = internalInstance.memoizedProps[key]; } }); } return events; } /** * 获取 React 组件的类型 * @param {Object} internalInstance - React 实例 * @returns {string|null} - 组件类型 */ getComponentType(internalInstance) { if (internalInstance == null) return null; // 从 Fiber 节点中提取组件类型 const type = internalInstance.type ?? internalInstance.elementType; if (typeof type === 'string') { // 宿主组件(如 div、span) return type; } else if (typeof type === 'function') { // 函数组件或类组件 return type.name || 'AnonymousComponent'; } else if (type?.$$typeof === Symbol.for('react.memo')) { // React.memo 组件 return type.type.name || 'MemoComponent'; } else if (type?.$$typeof === Symbol.for('react.forward_ref')) { // React.forwardRef 组件 return type.render.name || 'ForwardRefComponent'; } else { // 其他类型 return 'UnknownComponent'; } } } (function () { 'use strict'; unsafeWindow.jQuery = $; unsafeWindow.ModuleRaid = ModuleRaid; unsafeWindow.ReactRaid = ReactRaid; document.addEventListener("DOMContentLoaded", DOM_ContentReady); window.addEventListener("load", pageFullyLoaded); function DOM_ContentReady() { // 2ND PART OF SCRIPT RUN GOES HERE. // This is the equivalent of @run-at document-end console.log("==> 2nd part of script run.", new Date()); } function pageFullyLoaded() { console.log("==> Page is fully loaded, including images.", new Date()); } })();