Rewrite a bit and provide patch wrapper. add patcher test plugin as example
This commit is contained in:
parent
86528a3335
commit
b5fc88bc8e
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
import { DOM, BdUI, Modals, Reflection } from 'ui';
|
import { DOM, BdUI, Modals, Reflection } from 'ui';
|
||||||
import BdCss from './styles/index.scss';
|
import BdCss from './styles/index.scss';
|
||||||
import { Patcher, Vendor, Events, CssEditor, Globals, ExtModuleManager, PluginManager, ThemeManager, ModuleManager, WebpackModules, Settings, Database, ReactComponents, ReactAutoPatcher, DiscordApi } from 'modules';
|
import { Patcher, MonkeyPatch, Vendor, Events, CssEditor, Globals, ExtModuleManager, PluginManager, ThemeManager, ModuleManager, WebpackModules, Settings, Database, ReactComponents, ReactAutoPatcher, DiscordApi } from 'modules';
|
||||||
import { ClientLogger as Logger, ClientIPC, Utils } from 'common';
|
import { ClientLogger as Logger, ClientIPC, Utils } from 'common';
|
||||||
import { EmoteModule } from 'builtin';
|
import { EmoteModule } from 'builtin';
|
||||||
const ignoreExternal = false;
|
const ignoreExternal = false;
|
||||||
|
@ -25,6 +25,7 @@ class BetterDiscord {
|
||||||
Modals,
|
Modals,
|
||||||
Reflection,
|
Reflection,
|
||||||
Patcher,
|
Patcher,
|
||||||
|
MonkeyPatch,
|
||||||
Vendor,
|
Vendor,
|
||||||
Events,
|
Events,
|
||||||
CssEditor,
|
CssEditor,
|
||||||
|
|
|
@ -15,5 +15,5 @@ export { default as Permissions } from './permissionmanager';
|
||||||
export { default as Database } from './database';
|
export { default as Database } from './database';
|
||||||
export { default as EventsWrapper } from './eventswrapper';
|
export { default as EventsWrapper } from './eventswrapper';
|
||||||
export { default as DiscordApi } from './discordapi';
|
export { default as DiscordApi } from './discordapi';
|
||||||
export { default as Patcher } from './patcher';
|
export * from './patcher';
|
||||||
export * from './reactcomponents';
|
export * from './reactcomponents';
|
||||||
|
|
|
@ -9,31 +9,63 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { WebpackModules } from './webpackmodules';
|
import { WebpackModules } from './webpackmodules';
|
||||||
import { ClientLogger as Logger } from 'common';
|
import { ClientLogger as Logger, Utils } from 'common';
|
||||||
|
|
||||||
export default class Patcher {
|
export class Patcher {
|
||||||
static get patches() { return this._patches || (this._patches = {}) }
|
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) {
|
||||||
|
for (const patch of patches) {
|
||||||
|
for (const child of patch.children) {
|
||||||
|
child.unpatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
static resolveModule(module) {
|
static resolveModule(module) {
|
||||||
if (module instanceof Function || (module instanceof Object && !(module instanceof Array))) return module;
|
if (module instanceof Function || (module instanceof Object && !(module instanceof Array))) return module;
|
||||||
if ('string' === typeof module) return WebpackModules.getModuleByName(module);
|
if ('string' === typeof module) return WebpackModules.getModuleByName(module);
|
||||||
if (module instanceof Array) return WebpackModules.getModuleByProps(module);
|
if (module instanceof Array) return WebpackModules.getModuleByProps(module);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static overrideFn(patch) {
|
static overrideFn(patch) {
|
||||||
return function () {
|
return function () {
|
||||||
for (const superPatch of patch.supers) {
|
let retVal = null;
|
||||||
|
if (!patch.children) return patch.originalFunction.apply(this, arguments);
|
||||||
|
for (const superPatch of patch.children.filter(c => c.type === 'before')) {
|
||||||
try {
|
try {
|
||||||
superPatch.callback.apply(this, arguments);
|
superPatch.callback(this, arguments);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Logger.err('Patcher', err);
|
Logger.err(`Patcher:${patch.id}`, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const retVal = patch.originalFunction.apply(this, arguments);
|
|
||||||
for (const slavePatch of patch.slaves) {
|
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 {
|
try {
|
||||||
slavePatch.callback.apply(this, [arguments, { patch, retVal }]);
|
slavePatch.callback(this, arguments, retVal);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Logger.err('Patcher', err);
|
Logger.err(`Patcher:${patch.id}`, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return retVal;
|
return retVal;
|
||||||
|
@ -44,61 +76,56 @@ export default class Patcher {
|
||||||
patch.proxyFunction = patch.module[patch.functionName] = this.overrideFn(patch);
|
patch.proxyFunction = patch.module[patch.functionName] = this.overrideFn(patch);
|
||||||
}
|
}
|
||||||
|
|
||||||
static pushPatch(id, module, functionName) {
|
static pushPatch(caller, id, module, functionName) {
|
||||||
const patch = {
|
const patch = {
|
||||||
|
caller,
|
||||||
|
id,
|
||||||
module,
|
module,
|
||||||
functionName,
|
functionName,
|
||||||
originalFunction: module[functionName],
|
originalFunction: module[functionName],
|
||||||
proxyFunction: null,
|
proxyFunction: null,
|
||||||
revert: () => {
|
revert: () => { // Calling revert will destroy any patches added to the same module after this
|
||||||
patch.module[patch.functionName] = patch.originalFunction;
|
patch.module[patch.functionName] = patch.originalFunction;
|
||||||
patch.proxyFunction = null;
|
patch.proxyFunction = null;
|
||||||
patch.slaves = patch.supers = [];
|
patch.slaves = patch.supers = [];
|
||||||
},
|
},
|
||||||
supers: [],
|
counter: 0,
|
||||||
slaves: []
|
children: []
|
||||||
};
|
};
|
||||||
patch.proxyFunction = module[functionName] = this.overrideFn(patch);
|
patch.proxyFunction = module[functionName] = this.overrideFn(patch);
|
||||||
return this.patches[id] = patch;
|
return this.patches[id] = patch;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get before() { return this.superpatch; }
|
static before() { return this.pushChildPatch(...arguments, 'before') }
|
||||||
static superpatch(unresolveModule, functionName, callback, displayName) {
|
static after() { return this.pushChildPatch(...arguments, 'after') }
|
||||||
const module = this.resolveModule(unresolveModule);
|
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;
|
if (!module || !module[functionName] || !(module[functionName] instanceof Function)) return null;
|
||||||
displayName = 'string' === typeof unresolveModule ? unresolveModule : displayName || module.displayName || module.name || module.constructor.displayName || module.constructor.name;
|
displayName = 'string' === typeof unresolvedModule ? unresolvedModule : displayName || module.displayName || module.name || module.constructor.displayName || module.constructor.name;
|
||||||
const patchId = `${displayName}:${functionName}`;
|
const patchId = `${displayName}:${functionName}:${caller}`;
|
||||||
|
|
||||||
const patch = this.patches[patchId] || this.pushPatch(patchId, module, functionName);
|
const patch = this.patches[patchId] || this.pushPatch(caller, patchId, module, functionName);
|
||||||
if (!patch.proxyFunction) this.rePatch(patch);
|
if (!patch.proxyFunction) this.rePatch(patch);
|
||||||
const id = patch.supers.length + 1;
|
const child = {
|
||||||
const superPatch = {
|
caller,
|
||||||
id,
|
type,
|
||||||
|
id: patch.counter,
|
||||||
callback,
|
callback,
|
||||||
unpactch: () => patch.slaves.splice(patch.slaves.findIndex(slave => slave.id === id), 1) // This doesn't actually work correctly not, fix in a moment
|
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.supers.push(superPatch);
|
patch.counter++;
|
||||||
return superPatch;
|
return child.unpatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get after() { return this.slavepatch; }
|
|
||||||
static slavepatch(unresolveModule, functionName, callback, displayName) {
|
|
||||||
const module = this.resolveModule(unresolveModule);
|
|
||||||
if (!module || !module[functionName] || !(module[functionName] instanceof Function)) return null;
|
|
||||||
displayName = 'string' === typeof unresolveModule ? unresolveModule : displayName || module.displayName || module.name || module.constructor.displayName || module.constructor.name;
|
|
||||||
const patchId = `${displayName}:${functionName}`;
|
|
||||||
|
|
||||||
const patch = this.patches[patchId] || this.pushPatch(patchId, module, functionName);
|
|
||||||
if (!patch.proxyFunction) this.rePatch(patch);
|
|
||||||
const id = patch.slaves.length + 1;
|
|
||||||
const slavePatch = {
|
|
||||||
id,
|
|
||||||
callback,
|
|
||||||
unpactch: () => patch.slaves.splice(patch.slaves.findIndex(slave => slave.id === id), 1) // This doesn't actually work correctly not, fix in a moment
|
|
||||||
};
|
|
||||||
|
|
||||||
patch.slaves.push(slavePatch);
|
|
||||||
return slavePatch;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
});
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { SettingsSet, SettingsCategory, Setting, SettingsScheme } from 'structs'
|
||||||
import { BdMenuItems, Modals, DOM, Reflection } from 'ui';
|
import { BdMenuItems, Modals, DOM, Reflection } from 'ui';
|
||||||
import DiscordApi from './discordapi';
|
import DiscordApi from './discordapi';
|
||||||
import { ReactComponents } from './reactcomponents';
|
import { ReactComponents } from './reactcomponents';
|
||||||
|
import { MonkeyPatch } from './patcher';
|
||||||
|
|
||||||
export default class PluginApi {
|
export default class PluginApi {
|
||||||
|
|
||||||
|
@ -39,6 +40,9 @@ export default class PluginApi {
|
||||||
get Reflection() {
|
get Reflection() {
|
||||||
return Reflection;
|
return Reflection;
|
||||||
}
|
}
|
||||||
|
get MonkeyPatch() {
|
||||||
|
return module => MonkeyPatch(this.pluginInfo.id, module);
|
||||||
|
}
|
||||||
get plugin() {
|
get plugin() {
|
||||||
return PluginManager.getPluginById(this.pluginInfo.id || this.pluginInfo.name.toLowerCase().replace(/[^a-zA-Z0-9-]/g, '-').replace(/--/g, '-'));
|
return PluginManager.getPluginById(this.pluginInfo.id || this.pluginInfo.name.toLowerCase().replace(/[^a-zA-Z0-9-]/g, '-').replace(/--/g, '-'));
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Patcher from './patcher';
|
import { MonkeyPatch, Patcher } from './patcher';
|
||||||
import { WebpackModules, Filters } from './webpackmodules';
|
import { WebpackModules, Filters } from './webpackmodules';
|
||||||
import DiscordApi from './discordapi';
|
import DiscordApi from './discordapi';
|
||||||
import { EmoteModule } from 'builtin';
|
import { EmoteModule } from 'builtin';
|
||||||
|
@ -42,7 +42,7 @@ class Helpers {
|
||||||
return this.recursiveArray(parent, key, count);
|
return this.recursiveArray(parent, key, count);
|
||||||
}
|
}
|
||||||
static get recursiveChildren() {
|
static get recursiveChildren() {
|
||||||
return function*(parent, key, index = 0, count = 1) {
|
return function* (parent, key, index = 0, count = 1) {
|
||||||
const item = parent[key];
|
const item = parent[key];
|
||||||
yield { item, parent, key, index, count };
|
yield { item, parent, key, index, count };
|
||||||
if (item && item.props && item.props.children) {
|
if (item && item.props && item.props.children) {
|
||||||
|
@ -132,6 +132,18 @@ class Helpers {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
static findProp(obj, what) {
|
||||||
|
if (obj.hasOwnProperty(what)) return obj[what];
|
||||||
|
if (obj.props && !obj.children) return this.findProp(obj.props, what);
|
||||||
|
if (!obj.children) return null;
|
||||||
|
if (!(obj.children instanceof Array)) return this.findProp(obj.children, what);
|
||||||
|
for (const child of obj.children) {
|
||||||
|
if (!child) continue;
|
||||||
|
const findInChild = this.findProp(child, what);
|
||||||
|
if (findInChild) return findInChild;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
static get ReactDOM() {
|
static get ReactDOM() {
|
||||||
return WebpackModules.getModuleByName('ReactDOM');
|
return WebpackModules.getModuleByName('ReactDOM');
|
||||||
}
|
}
|
||||||
|
@ -142,206 +154,23 @@ class ReactComponent {
|
||||||
this._id = id;
|
this._id = id;
|
||||||
this._component = component;
|
this._component = component;
|
||||||
this._retVal = retVal;
|
this._retVal = retVal;
|
||||||
const self = this;
|
|
||||||
Patcher.slavepatch(this.component.prototype, 'componentWillMount', function(args, parv) {
|
|
||||||
self.eventCallback('componentWillMount', {
|
|
||||||
component: this,
|
|
||||||
retVal: parv.retVal
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Patcher.slavepatch(this.component.prototype, 'render', function (args, parv) {
|
|
||||||
self.eventCallback('render', {
|
|
||||||
component: this,
|
|
||||||
retVal: parv.retVal
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Patcher.slavepatch(this.component.prototype, 'componentDidMount', function (args, parv) {
|
|
||||||
self.eventCallback('componentDidMount', {
|
|
||||||
component: this,
|
|
||||||
props: this.props,
|
|
||||||
state: this.state,
|
|
||||||
element: Helpers.ReactDOM.findDOMNode(this),
|
|
||||||
retVal: parv.retVal
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Patcher.slavepatch(this.component.prototype, 'componentWillReceiveProps', function (args, parv) {
|
|
||||||
const [nextProps] = args;
|
|
||||||
self.eventCallback('componentWillReceiveProps', {
|
|
||||||
component: this,
|
|
||||||
nextProps,
|
|
||||||
retVal: parv.retVal
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Patcher.slavepatch(this.component.prototype, 'shouldComponentUpdate', function (args, parv) {
|
|
||||||
const [nextProps, nextState] = args;
|
|
||||||
self.eventCallback('shouldComponentUpdate', {
|
|
||||||
component: this,
|
|
||||||
nextProps,
|
|
||||||
nextState,
|
|
||||||
retVal: parv.retVal
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Patcher.slavepatch(this.component.prototype, 'componentWillUpdate', function (args, parv) {
|
|
||||||
const [nextProps, nextState] = args;
|
|
||||||
self.eventCallback('componentWillUpdate', {
|
|
||||||
component: this,
|
|
||||||
nextProps,
|
|
||||||
nextState,
|
|
||||||
retVal: parv.retVal
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Patcher.slavepatch(this.component.prototype, 'componentDidUpdate', function(args, parv) {
|
|
||||||
const [prevProps, prevState] = args;
|
|
||||||
self.eventCallback('componentDidUpdate', {
|
|
||||||
component: this,
|
|
||||||
prevProps,
|
|
||||||
prevState,
|
|
||||||
props: this.props,
|
|
||||||
state: this.state,
|
|
||||||
element: Helpers.ReactDOM.findDOMNode(this),
|
|
||||||
retVal: parv.retVal
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Patcher.slavepatch(this.component.prototype, 'componentWillUnmount', function (args, parv) {
|
|
||||||
self.eventCallback('componentWillUnmount', {
|
|
||||||
component: this,
|
|
||||||
retVal: parv.retVal
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Patcher.slavepatch(this.component.prototype, 'componentDidCatch', function (args, parv) {
|
|
||||||
const [error, info] = args;
|
|
||||||
self.eventCallback('componentDidCatch', {
|
|
||||||
component: this,
|
|
||||||
error,
|
|
||||||
info,
|
|
||||||
retVal: parv.retVal
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
eventCallback(event, eventData) {
|
|
||||||
for (const listener of this.events.find(e => e.id === event).listeners) {
|
|
||||||
listener(eventData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get events() {
|
|
||||||
return this._events || (this._events = [
|
|
||||||
{ id: 'componentWillMount', listeners: [] },
|
|
||||||
{ id: 'render', listeners: [] },
|
|
||||||
{ id: 'componentDidMount', listeners: [] },
|
|
||||||
{ id: 'componentWillReceiveProps', listeners: [] },
|
|
||||||
{ id: 'shouldComponentUpdate', listeners: [] },
|
|
||||||
{ id: 'componentWillUpdate', listeners: [] },
|
|
||||||
{ id: 'componentDidUpdate', listeners: [] },
|
|
||||||
{ id: 'componentWillUnmount', listeners: [] },
|
|
||||||
{ id: 'componentDidCatch', listeners: [] }
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
on(event, callback) {
|
|
||||||
const have = this.events.find(e => e.id === event);
|
|
||||||
if (!have) return;
|
|
||||||
have.listeners.push(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
get id() {
|
get id() {
|
||||||
return this._id;
|
return this._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
get component() {
|
get component() {
|
||||||
return this._component;
|
return this._component;
|
||||||
}
|
}
|
||||||
|
|
||||||
get retVal() {
|
get retVal() {
|
||||||
return this._retVal;
|
return this._retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
forceUpdateOthers() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ReactAutoPatcher {
|
|
||||||
static async autoPatch() {
|
|
||||||
await this.ensureReact();
|
|
||||||
Patcher.superpatch('React', 'createElement', (component, retVal) => ReactComponents.push(component, retVal));
|
|
||||||
this.patchem();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
static async ensureReact() {
|
|
||||||
while (!window.webpackJsonp || !WebpackModules.getModuleByName('React')) await new Promise(resolve => setTimeout(resolve, 10));
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
static patchem() {
|
|
||||||
this.patchMessage();
|
|
||||||
this.patchMessageGroup();
|
|
||||||
this.patchChannelMember();
|
|
||||||
}
|
|
||||||
|
|
||||||
static async patchMessage() {
|
|
||||||
this.Message.component = await ReactComponents.getComponent('Message', true, { selector: '.message' });
|
|
||||||
this.Message.component.on('render', ({ component, retVal, p }) => {
|
|
||||||
const { message } = component.props;
|
|
||||||
const { id, colorString, bot, author, attachments, embeds } = message;
|
|
||||||
retVal.props['data-message-id'] = id;
|
|
||||||
retVal.props['data-colourstring'] = colorString;
|
|
||||||
if (author && author.id) retVal.props['data-user-id'] = author.id;
|
|
||||||
if (bot || (author && author.bot)) retVal.props.className += ' bd-isBot';
|
|
||||||
if (attachments && attachments.length) retVal.props.className += ' bd-hasAttachments';
|
|
||||||
if (embeds && embeds.length) retVal.props.className += ' bd-hasEmbeds';
|
|
||||||
if (author && author.id === DiscordApi.currentUser.id) retVal.props.className += ' bd-isCurrentUser';
|
|
||||||
try {
|
|
||||||
const markup = Helpers.findByProp(retVal, 'className', 'markup').children; // First child has all the actual text content, second is the edited timestamp
|
|
||||||
markup[0] = EmoteModule.processMarkup(markup[0]);
|
|
||||||
} catch (err) {
|
|
||||||
console.error('MARKUP PARSER ERROR', err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static async patchMessageGroup() {
|
|
||||||
ReactComponents.setName('MessageGroup', this.MessageGroup.filter);
|
|
||||||
this.MessageGroup.component = await ReactComponents.getComponent('MessageGroup', true, { selector: '.message-group' });
|
|
||||||
this.MessageGroup.component.on('render', ({ component, retVal, p }) => {
|
|
||||||
const authorid = component.props.messages[0].author.id;
|
|
||||||
retVal.props['data-author-id'] = authorid;
|
|
||||||
if (authorid === DiscordApi.currentUser.id) retVal.props.className += ' bd-isCurrentUser';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static async patchChannelMember() {
|
|
||||||
this.ChannelMember.component = await ReactComponents.getComponent('ChannelMember');
|
|
||||||
this.ChannelMember.component.on('render', ({ component, retVal, p }) => {
|
|
||||||
const { user, isOwner } = component.props;
|
|
||||||
retVal.props.children.props['data-member-id'] = user.id;
|
|
||||||
if (user.id === DiscordApi.currentUser.id) retVal.props.children.props.className += ' bd-isCurrentUser';
|
|
||||||
if (isOwner) retVal.props.children.props.className += ' bd-isOwner';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static get MessageGroup() {
|
|
||||||
return this._messageGroup || (
|
|
||||||
this._messageGroup = {
|
|
||||||
filter: Filters.byCode(/"message-group"[\s\S]*"has-divider"[\s\S]*"hide-overflow"[\s\S]*"is-local-bot-message"/, c => c.prototype && c.prototype.render)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static get Message() {
|
|
||||||
return this._message || (this._message = {});
|
|
||||||
}
|
|
||||||
|
|
||||||
static get ChannelMember() {
|
|
||||||
return this._channelMember || (
|
|
||||||
this._channelMember = {});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ReactComponents {
|
export class ReactComponents {
|
||||||
static get components() { return this._components || (this._components = []) }
|
static get components() { return this._components || (this._components = []) }
|
||||||
static get unknownComponents() { return this._unknownComponents || (this._unknownComponents = [])}
|
static get unknownComponents() { return this._unknownComponents || (this._unknownComponents = []) }
|
||||||
static get listeners() { return this._listeners || (this._listeners = []) }
|
static get listeners() { return this._listeners || (this._listeners = []) }
|
||||||
static get nameSetters() { return this._nameSetters || (this._nameSetters =[])}
|
static get nameSetters() { return this._nameSetters || (this._nameSetters = []) }
|
||||||
|
|
||||||
static push(component, retVal) {
|
static push(component, retVal) {
|
||||||
if (!(component instanceof Function)) return null;
|
if (!(component instanceof Function)) return null;
|
||||||
|
@ -362,7 +191,7 @@ export class ReactComponents {
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getComponent(name, important, importantArgs) {
|
static async getComponent(name, important) {
|
||||||
const have = this.components.find(c => c.id === name);
|
const have = this.components.find(c => c.id === name);
|
||||||
if (have) return have;
|
if (have) return have;
|
||||||
if (important) {
|
if (important) {
|
||||||
|
@ -372,11 +201,11 @@ export class ReactComponents {
|
||||||
clearInterval(importantInterval);
|
clearInterval(importantInterval);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const select = document.querySelector(importantArgs.selector);
|
const select = document.querySelector(important.selector);
|
||||||
if (!select) return;
|
if (!select) return;
|
||||||
const reflect = Reflection(select);
|
const reflect = Reflection(select);
|
||||||
if (!reflect.component) {
|
if (!reflect.component) {
|
||||||
clearInterval(important);
|
clearInterval(importantInterval);
|
||||||
console.error(`FAILED TO GET IMPORTANT COMPONENT ${name} WITH REFLECTION FROM`, select);
|
console.error(`FAILED TO GET IMPORTANT COMPONENT ${name} WITH REFLECTION FROM`, select);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -413,7 +242,8 @@ export class ReactComponents {
|
||||||
static processUnknown(component, retVal) {
|
static processUnknown(component, retVal) {
|
||||||
const have = this.unknownComponents.find(c => c.component === component);
|
const have = this.unknownComponents.find(c => c.component === component);
|
||||||
for (const [fi, filter] of this.nameSetters.entries()) {
|
for (const [fi, filter] of this.nameSetters.entries()) {
|
||||||
if (filter.filter(component)) {
|
if (filter.filter.filter(component)) {
|
||||||
|
console.log('filter match!');
|
||||||
component.displayName = filter.name;
|
component.displayName = filter.name;
|
||||||
this.nameSetters.splice(fi, 1);
|
this.nameSetters.splice(fi, 1);
|
||||||
return this.push(component, retVal);
|
return this.push(component, retVal);
|
||||||
|
@ -424,3 +254,61 @@ export class ReactComponents {
|
||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ReactAutoPatcher {
|
||||||
|
static async autoPatch() {
|
||||||
|
await this.ensureReact();
|
||||||
|
this.React = {};
|
||||||
|
this.React.unpatchCreateElement = MonkeyPatch('BD:ReactComponents:createElement', 'React').before('createElement', (component, args) => {
|
||||||
|
ReactComponents.push(args[0]);
|
||||||
|
});
|
||||||
|
this.patchComponents();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async ensureReact() {
|
||||||
|
while (!window.webpackJsonp || !WebpackModules.getModuleByName('React')) await new Promise(resolve => setTimeout(resolve, 10));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async patchComponents() {
|
||||||
|
this.patchMessage();
|
||||||
|
this.patchMessageGroup();
|
||||||
|
this.patchChannelMember();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async patchMessage() {
|
||||||
|
this.Message = await ReactComponents.getComponent('Message', { selector: '.message' });
|
||||||
|
this.unpatchMessageRender = MonkeyPatch('BD:ReactComponents', this.Message.component.prototype).after('render', (component, args, retVal) => {
|
||||||
|
const { message } = component.props;
|
||||||
|
const { id, colorString, bot, author, attachments, embeds } = message;
|
||||||
|
retVal.props['data-message-id'] = id;
|
||||||
|
retVal.props['data-colourstring'] = colorString;
|
||||||
|
if (author && author.id) retVal.props['data-user-id'] = author.id;
|
||||||
|
if (bot || (author && author.bot)) retVal.props.className += ' bd-isBot';
|
||||||
|
if (attachments && attachments.length) retVal.props.className += ' bd-hasAttachments';
|
||||||
|
if (embeds && embeds.length) retVal.props.className += ' bd-hasEmbeds';
|
||||||
|
if (author && author.id === DiscordApi.currentUser.id) retVal.props.className += ' bd-isCurrentUser';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async patchMessageGroup() {
|
||||||
|
this.MessageGroup = await ReactComponents.getComponent('MessageGroup', { selector: '.message-group' });
|
||||||
|
this.unpatchMessageGroupRender = MonkeyPatch('BD:ReactComponents', this.MessageGroup.component.prototype).after('render', (component, args, retVal) => {
|
||||||
|
const { author, type } = component.props.messages[0];
|
||||||
|
retVal.props['data-author-id'] = author.id;
|
||||||
|
if (author.id === DiscordApi.currentUser.id) retVal.props.className += ' bd-isCurrentUser';
|
||||||
|
if (type !== 0) retVal.props.className += ' bd-isSystemMessage';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async patchChannelMember() {
|
||||||
|
this.ChannelMember = await ReactComponents.getComponent('ChannelMember', { selector: '.member.member-status' });
|
||||||
|
this.unpatchChannelMemberRender = MonkeyPatch('BD:ReactComponents', this.ChannelMember.component.prototype).after('render', (component, args, retVal) => {
|
||||||
|
if (!retVal.props || !retVal.props.children || !retVal.props.children.length) return;
|
||||||
|
const user = Helpers.findProp(component, 'user');
|
||||||
|
if (!user) return;
|
||||||
|
retVal.props['data-user-id'] = user.id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -89,7 +89,20 @@ class Reflection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static getComponent(node) {
|
static getComponent(node, first = true) {
|
||||||
|
try {
|
||||||
|
return this.reactInternalInstance(node).return.type;
|
||||||
|
} catch (err) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!node) return null;
|
||||||
|
if (first) node = this.reactInternalInstance(node);
|
||||||
|
if (node.hasOwnProperty('return')) {
|
||||||
|
if (node.return.hasOwnProperty('return') && !node.return.type) return node.type;
|
||||||
|
return this.getComponent(node.return, false);
|
||||||
|
}
|
||||||
|
if (node.hasOwnProperty('type')) return node.type;
|
||||||
|
return null;
|
||||||
// IMPORTANT TODO Currently only checks the first found component. For example channel-member will not return the correct component
|
// IMPORTANT TODO Currently only checks the first found component. For example channel-member will not return the correct component
|
||||||
try {
|
try {
|
||||||
return this.reactInternalInstance(node).return.type;
|
return this.reactInternalInstance(node).return.type;
|
||||||
|
|
|
@ -18,6 +18,9 @@ import { Vendor } from 'modules';
|
||||||
import filetype from 'file-type';
|
import filetype from 'file-type';
|
||||||
|
|
||||||
export class Utils {
|
export class Utils {
|
||||||
|
static isArrowFunction(fn) {
|
||||||
|
return !fn.toString().startsWith('function');
|
||||||
|
}
|
||||||
static overload(fn, cb) {
|
static overload(fn, cb) {
|
||||||
const orig = fn;
|
const orig = fn;
|
||||||
return function (...args) {
|
return function (...args) {
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"info": {
|
||||||
|
"id": "patcher-test",
|
||||||
|
"name": "Patcher Test",
|
||||||
|
"authors": [ "Jiiks" ],
|
||||||
|
"version": 1.0,
|
||||||
|
"description": "Patcher Test Description"
|
||||||
|
},
|
||||||
|
"main": "index.js",
|
||||||
|
"type": "plugin",
|
||||||
|
"defaultConfig": []
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
module.exports = (Plugin, Api, Vendor) => {
|
||||||
|
|
||||||
|
const { ReactComponents } = Api;
|
||||||
|
|
||||||
|
return class extends Plugin {
|
||||||
|
test() {
|
||||||
|
|
||||||
|
}
|
||||||
|
onStart() {
|
||||||
|
this.patchMessage();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
async patchMessage() {
|
||||||
|
const Message = await ReactComponents.getComponent('Message');
|
||||||
|
this.unpatchTest = Api.MonkeyPatch(Message.component.prototype).after('render', () => {
|
||||||
|
console.log('MESSAGE RENDER!');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onStop() {
|
||||||
|
this.unpatchTest(); // The automatic unpatcher is not there yet
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue