Utility for intercepting and patching Webpack chunks in UserScripts
Tento skript by nemal byť nainštalovaný priamo. Je to knižnica pre ďalšie skripty, ktorú by mali používať cez meta príkaz // @require https://update.greasyfork.org/scripts/588910/1888149/Webpack%20Patcher%20Library.js
// ==UserScript==
// @name Webpack Patcher Library
// @namespace webasm
// @version 0.2
// @description Utility for intercepting and patching Webpack chunks in UserScripts
// @author webasm
// @grant unsafeWindow
// ==/UserScript==
(function() {
'use strict';
const targetWindow = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
if (targetWindow.WebpackPatcher) return;
class ModulePatch {
constructor(idOrKeyword, context = null) {
if (typeof idOrKeyword === 'number') {
this._id = idOrKeyword;
this._keyword = null;
} else {
this._id = null;
this._keyword = Array.isArray(idOrKeyword) ? idOrKeyword : [idOrKeyword];
}
this._context = context;
this._isPatched = false;
}
get id() { return this._id; }
get keyword() { return this._keyword; }
get context() { return this._context; }
get isPatched() { return this._isPatched; }
test(moduleId, moduleStr) {
if (this._id !== null) {
return String(moduleId) === String(this._id);
}
if (this._keyword !== null) {
return this._keyword.every(kw => {
if (typeof kw === 'string') return moduleStr.includes(kw);
if (kw instanceof RegExp) return kw.test(moduleStr);
return false;
});
}
return false;
}
transformBody(originalBody) {
return originalBody;
}
applyTo(originalModule, matchedId) {
const moduleStr = originalModule.toString();
const match = moduleStr.match(/^(?:async\s+)?(?:function\*?\s*(?:\w+\s*)?)?\(([^)]*)\)\s*(?:=>)?\s*\{([\s\S]*)\}$/);
if (!match) {
console.warn(`[Patch] Regex mismatch for module ${matchedId}. It might be an unexpected format. Skipping.`);
return originalModule;
}
const argsStr = match[1];
const funcBodyStr = match[2];
const args = argsStr.split(',').map(s => s.trim()).filter(Boolean);
const modifiedBody = this.transformBody(funcBodyStr);
const errorCatchStart = `try { \n`;
const errorCatchEnd = `\n} catch(err) { console.error("[Patch Error] Module ${matchedId}:", err); }`;
try {
const newFunc = new Function(...args, errorCatchStart + modifiedBody + errorCatchEnd);
this._isPatched = true;
return newFunc;
} catch (err) {
console.error(`[Patch] Syntax Error while building function for module ${matchedId}. Falling back to original.`, err);
return originalModule;
}
}
}
class ModulePatchManager {
constructor() {
this.patches = [];
}
register(patch) {
this.patches.push(patch);
}
unregister(patch) {
this.patches = this.patches.filter(p => p !== patch);
}
processModules(modules, currentContext = null) {
if (!modules) return;
const pendingPatches = [...this.patches];
for (const patch of pendingPatches) {
if (patch.isPatched) continue;
if (patch.context !== null && patch.context !== currentContext) continue;
for (const [moduleId, originalModule] of Object.entries(modules)) {
if (!originalModule) continue;
const moduleStr = originalModule.toString();
if (patch.test(moduleId, moduleStr)) {
try {
const patchedModule = patch.applyTo(originalModule, moduleId);
modules[moduleId] = patchedModule;
console.log(`[PatchManager] Successfully patched module: ${moduleId} (Context: ${currentContext})`);
if (patch.isPatched) {
this.unregister(patch);
}
} catch (error) {
console.error(`[PatchManager] Failed to patch module: ${moduleId}`, error);
}
break;
}
}
}
}
}
class WebpackChunkInterceptor {
constructor(patchManager, globalVarName, injectionPayload = null) {
this.patchManager = patchManager;
this.globalVarName = globalVarName;
this.injectionPayload = injectionPayload;
}
_getChunkIdentifier(chunk) {
try {
return chunk[0][0];
} catch (e) {
return "unknown";
}
}
attach() {
if (this.patchManager.patches.length === 0) return;
const self = this;
const globalName = this.globalVarName;
const hookArray = (arr) => {
if (!arr || arr.__isHooked) return;
Object.defineProperty(arr, '__isHooked', { value: true, enumerable: false, writable: true });
let originalPush = arr.push;
Object.defineProperty(arr, 'push', {
get() {
return function(...args) {
try {
args.forEach(chunk => {
if (chunk && chunk[1]) {
const chunkId = self._getChunkIdentifier(chunk);
self.patchManager.processModules(chunk[1], chunkId);
}
});
} catch (err) {
console.error("[Interceptor] Error inside push wrapper:", err);
}
return originalPush.apply(this, args);
};
},
set(newPush) {
originalPush = newPush;
},
configurable: true,
enumerable: true
});
try {
arr.forEach(chunk => {
if (chunk && chunk[1]) {
const chunkId = self._getChunkIdentifier(chunk);
self.patchManager.processModules(chunk[1], chunkId);
}
});
} catch (err) {
console.error("[Interceptor] Error processing existing chunks:", err);
}
if (self.injectionPayload) {
try {
originalPush.call(arr, self.injectionPayload);
} catch (err) {
console.error("[Interceptor] Error injecting payload:", err);
}
}
};
let existingVal = targetWindow[globalName];
if (existingVal && Array.isArray(existingVal)) {
hookArray(existingVal);
} else if (existingVal === undefined) {
existingVal = [];
hookArray(existingVal);
}
Object.defineProperty(targetWindow, globalName, {
get() { return existingVal; },
set(newVal) {
existingVal = newVal;
if (Array.isArray(existingVal)) {
hookArray(existingVal);
}
},
configurable: true
});
console.log(`[Interceptor] Attached webpackChunk Interceptor securely to ${globalName}`);
}
}
class WebpackInternalCallInterceptor {
constructor(patchManager, triggerModuleId, targetFilename) {
this.patchManager = patchManager;
this.triggerModuleId = triggerModuleId;
this.targetFilename = targetFilename;
}
attach() {
if(this.patchManager.patches.length === 0) return;
const self = this;
const origCall = Function.prototype.call;
Function.prototype.call = function(thisArg, ...args) {
const module = args[0];
if (module && String(module.id) === String(self.triggerModuleId)) {
const stack = new Error().stack || "";
if (stack.includes(self.targetFilename)) {
try {
const __webpack_require__ = args[2];
if (__webpack_require__ && __webpack_require__.m) {
self.patchManager.processModules(__webpack_require__.m, self.targetFilename);
}
} finally {
Function.prototype.call = origCall;
console.log(`[Interceptor] Unhooked Webpack Internal Call Interceptor for ${self.targetFilename}`);
}
}
}
return origCall.apply(this, [thisArg, ...args]);
};
console.log(`[Interceptor] Attached Webpack Internal Call Interceptor for ${this.targetFilename}`);
}
}
targetWindow.WebpackPatcher = {
ModulePatch,
ModulePatchManager,
WebpackChunkInterceptor,
WebpackInternalCallInterceptor
};
})();