Add monkeypatch utility function
This commit is contained in:
parent
2bf1709dba
commit
88b063ca8e
|
@ -14,6 +14,7 @@ import ExtModuleManager from './extmodulemanager';
|
|||
import PluginManager from './pluginmanager';
|
||||
import ThemeManager from './thememanager';
|
||||
import Events from './events';
|
||||
import { SettingsSet, SettingsCategory, Setting, SettingsScheme } from 'structs';
|
||||
import { Modals, DOM } from 'ui';
|
||||
import SettingsModal from '../ui/components/bd/modals/SettingsModal.vue';
|
||||
|
||||
|
@ -80,6 +81,9 @@ export default class PluginApi {
|
|||
get Utils() {
|
||||
return {
|
||||
overload: () => Utils.overload.apply(Utils, arguments),
|
||||
monkeyPatch: () => Utils.monkeyPatch.apply(Utils, arguments),
|
||||
monkeyPatchOnce: () => Utils.monkeyPatchOnce.apply(Utils, arguments),
|
||||
compatibleMonkeyPatch: () => Utils.monkeyPatchOnce.apply(Utils, arguments),
|
||||
tryParseJson: () => Utils.tryParseJson.apply(Utils, arguments),
|
||||
toCamelCase: () => Utils.toCamelCase.apply(Utils, arguments),
|
||||
compare: () => Utils.compare.apply(Utils, arguments),
|
||||
|
@ -88,6 +92,27 @@ export default class PluginApi {
|
|||
};
|
||||
}
|
||||
|
||||
createSettingsSet(args, ...merge) {
|
||||
return new SettingsSet(args, ...merge);
|
||||
}
|
||||
createSettingsCategory(args, ...merge) {
|
||||
return new SettingsCategory(args, ...merge);
|
||||
}
|
||||
createSetting(args, ...merge) {
|
||||
return new Setting(args, ...merge);
|
||||
}
|
||||
createSettingsScheme(args) {
|
||||
return new SettingsScheme(args);
|
||||
}
|
||||
get Settings() {
|
||||
return {
|
||||
createSet: this.createSet.bind(this),
|
||||
createCategory: this.createSettingsCategory.bind(this),
|
||||
createSetting: this.createSetting.bind(this),
|
||||
createScheme: this.createSettingsScheme.bind(this)
|
||||
};
|
||||
}
|
||||
|
||||
getInternalSetting(set, category, setting) {
|
||||
return Settings.get(set, category, setting);
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ export default class SettingsCategory {
|
|||
|
||||
/**
|
||||
* Returns the first setting where calling {function} returns true.
|
||||
* @param {Function} function A function to call to filter setting
|
||||
* @param {Function} function A function to call to filter settings
|
||||
* @return {Setting}
|
||||
*/
|
||||
find(f) {
|
||||
|
@ -112,7 +112,7 @@ export default class SettingsCategory {
|
|||
|
||||
/**
|
||||
* Merges a category into this category without emitting events (and therefore synchronously).
|
||||
* This only exists for use by SettingsSet.
|
||||
* This only exists for use by the constructor and SettingsSet.
|
||||
*/
|
||||
_merge(newCategory) {
|
||||
let updatedSettings = [];
|
||||
|
@ -151,7 +151,7 @@ export default class SettingsCategory {
|
|||
continue;
|
||||
}
|
||||
|
||||
const updatedSetting = await setting._merge(newSetting, false);
|
||||
const updatedSetting = await setting.merge(newSetting, false);
|
||||
if (!updatedSetting) continue;
|
||||
updatedSettings = updatedSettings.concat(updatedSetting.map(({ setting, value, old_value }) => ({
|
||||
category: this, category_id: this.id,
|
||||
|
|
|
@ -191,7 +191,7 @@ export default class SettingsSet {
|
|||
* Returns the value of the setting with the ID {id}.
|
||||
* @param {String} categoryid The ID of the category to look in (optional)
|
||||
* @param {String} id The ID of the setting to look for
|
||||
* @return {SettingsCategory}
|
||||
* @return {Any}
|
||||
*/
|
||||
get(cid, sid) {
|
||||
const setting = this.getSetting(cid, sid);
|
||||
|
|
|
@ -103,7 +103,7 @@ export default class Setting {
|
|||
|
||||
/**
|
||||
* Merges a setting into this setting without emitting events (and therefore synchronously).
|
||||
* This only exists for use by SettingsCategory.
|
||||
* This only exists for use by the constructor and SettingsCategory.
|
||||
*/
|
||||
_merge(newSetting) {
|
||||
const value = newSetting.args ? newSetting.args.value : newSetting.value;
|
||||
|
|
|
@ -21,12 +21,11 @@ export default class {
|
|||
const defer = setInterval(() => {
|
||||
if (!this.profilePopupModule) return;
|
||||
clearInterval(defer);
|
||||
this.profilePopupModule.open = Utils.overload(this.profilePopupModule.open, userid => {
|
||||
Events.emit('ui-event', {
|
||||
event: 'profile-popup-open',
|
||||
data: { userid }
|
||||
});
|
||||
});
|
||||
|
||||
Utils.monkeyPatch(this.profilePopupModule, 'open', 'after', (data, userid) => Events.emit('ui-event', {
|
||||
event: 'profile-popup-open',
|
||||
data: { userid }
|
||||
}));
|
||||
}, 100);
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ export default class extends EventListener {
|
|||
|
||||
setTimeout(() => {
|
||||
let hasBadges = false;
|
||||
let root = document.querySelector('[class*=profileBadges]');
|
||||
let root = document.querySelector('[class*="profileBadges"]');
|
||||
if (root) {
|
||||
hasBadges = true;
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
/**
|
||||
* BetterDiscord Monkeypatch
|
||||
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
|
||||
* All rights reserved.
|
||||
* 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 { ClientLogger as Logger } from './logger';
|
||||
|
||||
export class PatchedFunction {
|
||||
constructor(object, methodName, replaceOriginal = true) {
|
||||
if (object[methodName].__monkeyPatch)
|
||||
return object[methodName].__monkeyPatch;
|
||||
|
||||
this.object = object;
|
||||
this.methodName = methodName;
|
||||
this.patches = [];
|
||||
this.originalMethod = object[methodName];
|
||||
this.replaced = false;
|
||||
|
||||
const patchedFunction = this;
|
||||
this.replace = function(...args) {
|
||||
patchedFunction.call(this, arguments);
|
||||
};
|
||||
this.replace.__monkeyPatch = this;
|
||||
|
||||
if (replaceOriginal)
|
||||
this.replaceOriginal();
|
||||
}
|
||||
|
||||
addPatch(patch) {
|
||||
if (!this.patches.includes(patch))
|
||||
this.patches.push(patch);
|
||||
}
|
||||
|
||||
removePatch(patch, restoreOriginal = true) {
|
||||
let i = 0;
|
||||
while (this.patches[i]) {
|
||||
if (this.patches[i] !== patch) i++;
|
||||
else this.patches.splice(i, 1);
|
||||
}
|
||||
|
||||
if (!this.patches.length && restoreOriginal)
|
||||
this.restoreOriginal();
|
||||
}
|
||||
|
||||
replaceOriginal() {
|
||||
if (this.replaced) return;
|
||||
this.object[this.methodName] = Object.assign(this.replace, this.object[this.methodName]);
|
||||
this.replaced = true;
|
||||
}
|
||||
|
||||
restoreOriginal() {
|
||||
if (!this.replaced) return;
|
||||
this.object[this.methodName] = Object.assign(this.object[this.methodName], this.replace);
|
||||
this.replaced = false;
|
||||
}
|
||||
|
||||
call(_this, args) {
|
||||
const data = {
|
||||
this: _this,
|
||||
arguments: args,
|
||||
return: undefined,
|
||||
originalMethod: this.originalMethod,
|
||||
callOriginalMethod: () => {
|
||||
Logger.log('MonkeyPatch', [`Calling original method`, this, data]);
|
||||
data.return = this.originalMethod.apply(data.this, data.arguments);
|
||||
}
|
||||
};
|
||||
|
||||
// Work through the patches calling each patch's hooks as if each patch had overridden the previous patch
|
||||
for (let patch of this.patches) {
|
||||
let callOriginalMethod = data.callOriginalMethod;
|
||||
data.callOriginalMethod = () => {
|
||||
const patch_data = Object.assign({}, data, {
|
||||
callOriginalMethod, patch
|
||||
});
|
||||
patch.call(patch_data);
|
||||
data.arguments = patch_data.arguments;
|
||||
data.return = patch_data.return;
|
||||
};
|
||||
}
|
||||
|
||||
data.callOriginalMethod();
|
||||
return data.return;
|
||||
}
|
||||
}
|
||||
|
||||
export class Patch {
|
||||
constructor(patchedFunction, options, f) {
|
||||
this.patchedFunction = patchedFunction;
|
||||
|
||||
if (options instanceof Function) {
|
||||
f = options;
|
||||
options = {
|
||||
instead: data => {
|
||||
f.call(this, data, ...data.arguments);
|
||||
}
|
||||
};
|
||||
} else if (options === 'before') {
|
||||
options = {
|
||||
before: data => {
|
||||
f.call(this, data, ...data.arguments);
|
||||
}
|
||||
};
|
||||
} else if (options === 'after') {
|
||||
options = {
|
||||
after: data => {
|
||||
f.call(this, data, ...data.arguments);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.before = options.before || undefined;
|
||||
this.instead = options.instead || undefined;
|
||||
this.after = options.after || undefined;
|
||||
this.once = options.once || false;
|
||||
this.silent = options.silent || false;
|
||||
this.suppressErrors = typeof options.suppressErrors === 'boolean' ? options.suppressErrors : true;
|
||||
}
|
||||
|
||||
call(data) {
|
||||
if (this.once)
|
||||
this.cancel();
|
||||
|
||||
this.callBefore(data);
|
||||
this.callInstead(data);
|
||||
this.callAfter(data);
|
||||
}
|
||||
|
||||
callBefore(data) {
|
||||
if (this.before)
|
||||
this.callHook('before', this.before, data);
|
||||
}
|
||||
|
||||
callInstead(data) {
|
||||
if (this.instead)
|
||||
this.callHook('instead', this.instead, data);
|
||||
else data.callOriginalMethod();
|
||||
}
|
||||
|
||||
callAfter(data) {
|
||||
if (this.after)
|
||||
this.callHook('after', this.after, data);
|
||||
}
|
||||
|
||||
callHook(hook, f, data) {
|
||||
try {
|
||||
f.call(this, data, ...data.arguments);
|
||||
} catch (err) {
|
||||
Logger.log('MonkeyPatch', [`Error thrown in ${hook} hook of`, this, '- :', err]);
|
||||
if (!this.suppressErrors) throw err;
|
||||
}
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.patchedFunction.removePatch(this);
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@ const
|
|||
fs = require('fs'),
|
||||
_ = require('lodash');
|
||||
|
||||
import { PatchedFunction, Patch } from './monkeypatch';
|
||||
import { Vendor } from 'modules';
|
||||
import filetype from 'file-type';
|
||||
|
||||
|
@ -25,6 +26,67 @@ export class Utils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Monkey-patches an object's method.
|
||||
*/
|
||||
static monkeyPatch(object, methodName, options, f) {
|
||||
const patchedFunction = new PatchedFunction(object, methodName);
|
||||
const patch = new Patch(patchedFunction, options, f);
|
||||
patchedFunction.addPatch(patch);
|
||||
return patch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Monkey-patches an object's method and returns a promise that will be resolved with the data object when the method is called.
|
||||
* You will have to call data.callOriginalMethod() if it wants the original method to be called.
|
||||
*/
|
||||
static monkeyPatchOnce(object, methodName) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.monkeyPatch(object, methodName, data => {
|
||||
data.patch.cancel();
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Monkeypatch function that is compatible with samogot's Lib Discord Internals.
|
||||
* Don't use this for writing new plugins as it will eventually be removed!
|
||||
*/
|
||||
static compatibleMonkeyPatch(what, methodName, options) {
|
||||
const { before, instead, after, once = false, silent = false } = options;
|
||||
const cancelPatch = () => patch.cancel();
|
||||
|
||||
const compatible_function = _function => data => {
|
||||
const compatible_data = {
|
||||
thisObject: data.this,
|
||||
methodArguments: data.arguments,
|
||||
returnValue: data.return,
|
||||
cancelPatch,
|
||||
originalMethod: data.originalMethod,
|
||||
callOriginalMethod: () => data.callOriginalMethod()
|
||||
};
|
||||
try {
|
||||
_function(compatible_data);
|
||||
data.arguments = compatible_data.methodArguments;
|
||||
data.return = compatible_data.returnValue;
|
||||
} catch (err) {
|
||||
data.arguments = compatible_data.methodArguments;
|
||||
data.return = compatible_data.returnValue;
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const patch = this.monkeyPatch(what, methodName, {
|
||||
before: before ? compatible_function(before) : undefined,
|
||||
instead: instead ? compatible_function(instead) : undefined,
|
||||
after: after ? compatible_function(after) : undefined,
|
||||
once
|
||||
});
|
||||
|
||||
return cancelPatch;
|
||||
}
|
||||
|
||||
static async tryParseJson(jsonString) {
|
||||
try {
|
||||
return JSON.parse(jsonString);
|
||||
|
|
Loading…
Reference in New Issue