ElementRaid Test
// ==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<HTMLElement>} - 包含 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<ElementRaid>} - 符合筛选条件的子节点列表
*/
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<ElementRaid>} - 符合筛选条件的节点列表
*/
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());
}
})();