BetterDiscordApp-v2/client/src/modules/patcher.js

141 lines
5.3 KiB
JavaScript

/**
* BetterDiscord Component Patcher
* Copyright (c) 2015-present JsSucks - https://github.com/JsSucks
* All rights reserved.
* https://github.com/JsSucks - https://betterdiscord.net
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { WebpackModules } from './webpackmodules';
import { ClientLogger as Logger, Utils } from 'common';
export class Patcher {
static get patches() { return this._patches || (this._patches = {}) }
static getPatchesByCaller(id) {
const patches = [];
for (const patch in this.patches) {
if (this.patches.hasOwnProperty(patch)) {
if (this.patches[patch].caller === id) patches.push(this.patches[patch]);
}
}
return patches;
}
static unpatchAll(patches) {
if (typeof patches === 'string')
patches = this.getPatchesByCaller(patches);
for (const patch of patches) {
for (const child of patch.children) {
child.unpatch();
}
}
}
static resolveModule(module) {
if (module instanceof Function || (module instanceof Object && !(module instanceof Array))) return module;
if (typeof module === 'string') return WebpackModules.getModuleByName(module);
if (module instanceof Array) return WebpackModules.getModuleByProps(module);
return null;
}
static overrideFn(patch) {
return function () {
let retVal = null;
if (!patch.children) return patch.originalFunction.apply(this, arguments);
for (const superPatch of patch.children.filter(c => c.type === 'before')) {
try {
superPatch.callback(this, arguments);
} catch (err) {
Logger.err(`Patcher:${patch.id}`, err);
}
}
const insteads = patch.children.filter(c => c.type === 'instead');
if (!insteads.length) {
retVal = patch.originalFunction.apply(this, arguments);
} else {
for (const insteadPatch of insteads) {
try {
retVal = insteadPatch.callback(this, arguments);
} catch (err) {
Logger.err(`Patcher:${patch.id}`, err);
}
}
}
for (const slavePatch of patch.children.filter(c => c.type === 'after')) {
try {
slavePatch.callback(this, arguments, retVal);
} catch (err) {
Logger.err(`Patcher:${patch.id}`, err);
}
}
return retVal;
}
}
static rePatch(patch) {
patch.proxyFunction = patch.module[patch.functionName] = this.overrideFn(patch);
}
static pushPatch(caller, id, module, functionName) {
const patch = {
caller,
id,
module,
functionName,
originalFunction: module[functionName],
proxyFunction: null,
revert: () => { // Calling revert will destroy any patches added to the same module after this
patch.module[patch.functionName] = patch.originalFunction;
patch.proxyFunction = null;
patch.slaves = patch.supers = [];
},
counter: 0,
children: []
};
patch.proxyFunction = module[functionName] = this.overrideFn(patch);
return this.patches[id] = patch;
}
static before() { return this.pushChildPatch(...arguments, 'before') }
static after() { return this.pushChildPatch(...arguments, 'after') }
static instead() { return this.pushChildPatch(...arguments, 'instead') }
static pushChildPatch(caller, unresolvedModule, functionName, callback, displayName, type = 'after') {
const module = this.resolveModule(unresolvedModule);
if (!module || !module[functionName] || !(module[functionName] instanceof Function)) return null;
displayName = typeof unresolvedModule === 'string' ? unresolvedModule :
displayName || module.displayName || module.name || module.constructor.displayName || module.constructor.name;
const patchId = `${displayName}:${functionName}:${caller}`;
const patch = this.patches[patchId] || this.pushPatch(caller, patchId, module, functionName);
if (!patch.proxyFunction) this.rePatch(patch);
const child = {
caller,
type,
id: patch.counter,
callback,
unpatch: () => {
patch.children.splice(patch.children.findIndex(cpatch => cpatch.id === child.id && cpatch.type === type), 1);
if (patch.children.length <= 0) delete this.patches[patchId];
}
};
patch.children.push(child);
patch.counter++;
return child.unpatch;
}
}
export const MonkeyPatch = (caller, module, displayName) => ({
before: (functionName, callBack) => Patcher.before(caller, module, functionName, callBack, displayName),
after: (functionName, callBack) => Patcher.after(caller, module, functionName, callBack, displayName),
instead: (functionName, callBack) => Patcher.instead(caller, module, functionName, callBack, displayName)
});