Merge pull request #174 from JsSucks/patcher
Patcher changes and emote module fixes
This commit is contained in:
commit
ffea5bd3ae
|
@ -7,8 +7,8 @@
|
|||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
import { FileUtils } from 'common';
|
||||
import { Events, Globals, WebpackModules, ReactComponents } from 'modules';
|
||||
import { FileUtils, ClientLogger as Logger } from 'common';
|
||||
import { Events, Globals, WebpackModules, ReactComponents, MonkeyPatch } from 'modules';
|
||||
import { DOM, VueInjector } from 'ui';
|
||||
import EmoteComponent from './EmoteComponent.vue';
|
||||
let emotes = null;
|
||||
|
@ -24,6 +24,9 @@ export default class {
|
|||
static get React() {
|
||||
return WebpackModules.getModuleByName('React');
|
||||
}
|
||||
static get ReactDOM() {
|
||||
return WebpackModules.getModuleByName('ReactDOM');
|
||||
}
|
||||
static processMarkup(markup) {
|
||||
if (!emotesEnabled) return markup; // TODO Get it from setttings
|
||||
const newMarkup = [];
|
||||
|
@ -76,22 +79,52 @@ export default class {
|
|||
}
|
||||
}
|
||||
|
||||
static findByProp(obj, what, value) {
|
||||
if (obj.hasOwnProperty(what) && obj[what] === value) return obj;
|
||||
if (obj.props && !obj.children) return this.findByProp(obj.props, what, value);
|
||||
if (!obj.children || !obj.children.length) return null;
|
||||
for (const child of obj.children) {
|
||||
if (!child) continue;
|
||||
const findInChild = this.findByProp(child, what, value);
|
||||
if (findInChild) return findInChild;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static async observe() {
|
||||
const dataPath = Globals.getObject('paths').find(path => path.id === 'data').path;
|
||||
try {
|
||||
emotes = await FileUtils.readJsonFromFile(dataPath + '/emotes.json');
|
||||
const Message = await ReactComponents.getComponent('Message');
|
||||
Message.on('componentDidMount', ({ element }) => this.injectEmotes(element));
|
||||
Message.on('componentDidUpdate', ({ state, element }) => {
|
||||
if (!state.isEditing) this.injectEmotes(element);
|
||||
this.unpatchRender = MonkeyPatch('BD:EmoteModule', Message.component.prototype).after('render', (component, args, retVal) => {
|
||||
try {
|
||||
const markup = this.findByProp(retVal, 'className', 'markup'); // First child has all the actual text content, second is the edited timestamp
|
||||
if (!markup) return;
|
||||
markup.children[0] = this.processMarkup(markup.children[0]);
|
||||
} catch (err) {
|
||||
Logger.err('EmoteModule', err);
|
||||
}
|
||||
});
|
||||
this.unpatchMount = MonkeyPatch('BD:EmoteModule', Message.component.prototype).after('componentDidMount', (component, args) => {
|
||||
const element = this.ReactDOM.findDOMNode(component);
|
||||
if (!element) return;
|
||||
this.injectEmotes(element);
|
||||
});
|
||||
this.unpatchUpdate = MonkeyPatch('BD:EmoteModule', Message.component.prototype).after('componentDidUpdate', (component, args) => {
|
||||
const element = this.ReactDOM.findDOMNode(component);
|
||||
if (!element) return;
|
||||
this.injectEmotes(element);
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
Logger.err('EmoteModule', err);
|
||||
}
|
||||
}
|
||||
|
||||
static injectEmote(root) {
|
||||
if (!emotesEnabled) return;
|
||||
while (root.firstChild) {
|
||||
root.removeChild(root.firstChild);
|
||||
}
|
||||
const { bdemoteName, bdemoteSrc } = root.dataset;
|
||||
if (!bdemoteName || !bdemoteSrc) return;
|
||||
VueInjector.inject(
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
import { DOM, BdUI, Modals, Reflection } from 'ui';
|
||||
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 { EmoteModule } from 'builtin';
|
||||
const ignoreExternal = false;
|
||||
|
@ -25,6 +25,7 @@ class BetterDiscord {
|
|||
Modals,
|
||||
Reflection,
|
||||
Patcher,
|
||||
MonkeyPatch,
|
||||
Vendor,
|
||||
Events,
|
||||
CssEditor,
|
||||
|
|
|
@ -15,5 +15,5 @@ export { default as Permissions } from './permissionmanager';
|
|||
export { default as Database } from './database';
|
||||
export { default as EventsWrapper } from './eventswrapper';
|
||||
export { default as DiscordApi } from './discordapi';
|
||||
export { default as Patcher } from './patcher';
|
||||
export * from './patcher';
|
||||
export * from './reactcomponents';
|
||||
|
|
|
@ -9,31 +9,63 @@
|
|||
*/
|
||||
|
||||
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 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) {
|
||||
if (module instanceof Function || (module instanceof Object && !(module instanceof Array))) return module;
|
||||
if ('string' === typeof module) return WebpackModules.getModuleByName(module);
|
||||
if (module instanceof Array) return WebpackModules.getModuleByProps(module);
|
||||
return null;
|
||||
}
|
||||
|
||||
static overrideFn(patch) {
|
||||
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 {
|
||||
superPatch.callback.apply(this, arguments);
|
||||
superPatch.callback(this, arguments);
|
||||
} 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 {
|
||||
slavePatch.callback.apply(this, [arguments, { patch, retVal }]);
|
||||
slavePatch.callback(this, arguments, retVal);
|
||||
} catch (err) {
|
||||
Logger.err('Patcher', err);
|
||||
Logger.err(`Patcher:${patch.id}`, err);
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
|
@ -44,59 +76,56 @@ export default class Patcher {
|
|||
patch.proxyFunction = patch.module[patch.functionName] = this.overrideFn(patch);
|
||||
}
|
||||
|
||||
static pushPatch(id, module, functionName) {
|
||||
static pushPatch(caller, id, module, functionName) {
|
||||
const patch = {
|
||||
caller,
|
||||
id,
|
||||
module,
|
||||
functionName,
|
||||
originalFunction: module[functionName],
|
||||
proxyFunction: null,
|
||||
revert: () => {
|
||||
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 = [];
|
||||
},
|
||||
supers: [],
|
||||
slaves: []
|
||||
counter: 0,
|
||||
children: []
|
||||
};
|
||||
patch.proxyFunction = module[functionName] = this.overrideFn(patch);
|
||||
return this.patches[id] = patch;
|
||||
}
|
||||
|
||||
static superpatch(unresolveModule, functionName, callback, displayName) {
|
||||
const module = this.resolveModule(unresolveModule);
|
||||
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 = 'string' === typeof unresolveModule ? unresolveModule : displayName || module.displayName || module.name || module.constructor.displayName || module.constructor.name;
|
||||
const patchId = `${displayName}:${functionName}`;
|
||||
displayName = 'string' === typeof unresolvedModule ? unresolvedModule : displayName || module.displayName || module.name || module.constructor.displayName || module.constructor.name;
|
||||
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);
|
||||
const id = patch.supers.length + 1;
|
||||
const superPatch = {
|
||||
id,
|
||||
const child = {
|
||||
caller,
|
||||
type,
|
||||
id: patch.counter,
|
||||
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.supers.push(superPatch);
|
||||
return superPatch;
|
||||
patch.children.push(child);
|
||||
patch.counter++;
|
||||
return child.unpatch;
|
||||
}
|
||||
|
||||
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 DiscordApi from './discordapi';
|
||||
import { ReactComponents } from './reactcomponents';
|
||||
import { MonkeyPatch } from './patcher';
|
||||
|
||||
export default class PluginApi {
|
||||
|
||||
|
@ -39,6 +40,9 @@ export default class PluginApi {
|
|||
get Reflection() {
|
||||
return Reflection;
|
||||
}
|
||||
get MonkeyPatch() {
|
||||
return module => MonkeyPatch(this.pluginInfo.id, module);
|
||||
}
|
||||
get plugin() {
|
||||
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.
|
||||
*/
|
||||
|
||||
import Patcher from './patcher';
|
||||
import { MonkeyPatch, Patcher } from './patcher';
|
||||
import { WebpackModules, Filters } from './webpackmodules';
|
||||
import DiscordApi from './discordapi';
|
||||
import { EmoteModule } from 'builtin';
|
||||
|
@ -42,7 +42,7 @@ class Helpers {
|
|||
return this.recursiveArray(parent, key, count);
|
||||
}
|
||||
static get recursiveChildren() {
|
||||
return function*(parent, key, index = 0, count = 1) {
|
||||
return function* (parent, key, index = 0, count = 1) {
|
||||
const item = parent[key];
|
||||
yield { item, parent, key, index, count };
|
||||
if (item && item.props && item.props.children) {
|
||||
|
@ -132,6 +132,18 @@ class Helpers {
|
|||
}
|
||||
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() {
|
||||
return WebpackModules.getModuleByName('ReactDOM');
|
||||
}
|
||||
|
@ -142,206 +154,23 @@ class ReactComponent {
|
|||
this._id = id;
|
||||
this._component = component;
|
||||
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() {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
get component() {
|
||||
return this._component;
|
||||
}
|
||||
|
||||
get 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 {
|
||||
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 nameSetters() { return this._nameSetters || (this._nameSetters =[])}
|
||||
static get nameSetters() { return this._nameSetters || (this._nameSetters = []) }
|
||||
|
||||
static push(component, retVal) {
|
||||
if (!(component instanceof Function)) return null;
|
||||
|
@ -362,7 +191,7 @@ export class ReactComponents {
|
|||
return c;
|
||||
}
|
||||
|
||||
static async getComponent(name, important, importantArgs) {
|
||||
static async getComponent(name, important) {
|
||||
const have = this.components.find(c => c.id === name);
|
||||
if (have) return have;
|
||||
if (important) {
|
||||
|
@ -372,11 +201,11 @@ export class ReactComponents {
|
|||
clearInterval(importantInterval);
|
||||
return;
|
||||
}
|
||||
const select = document.querySelector(importantArgs.selector);
|
||||
const select = document.querySelector(important.selector);
|
||||
if (!select) return;
|
||||
const reflect = Reflection(select);
|
||||
if (!reflect.component) {
|
||||
clearInterval(important);
|
||||
clearInterval(importantInterval);
|
||||
console.error(`FAILED TO GET IMPORTANT COMPONENT ${name} WITH REFLECTION FROM`, select);
|
||||
return;
|
||||
}
|
||||
|
@ -413,7 +242,8 @@ export class ReactComponents {
|
|||
static processUnknown(component, retVal) {
|
||||
const have = this.unknownComponents.find(c => c.component === component);
|
||||
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;
|
||||
this.nameSetters.splice(fi, 1);
|
||||
return this.push(component, retVal);
|
||||
|
@ -424,3 +254,61 @@ export class ReactComponents {
|
|||
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,13 +89,23 @@ class Reflection {
|
|||
}
|
||||
}
|
||||
|
||||
static getComponent(node) {
|
||||
static getComponent(node, first = true) {
|
||||
// IMPORTANT TODO Currently only checks the first found component. For example channel-member will not return the correct component
|
||||
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;
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,9 @@ import { Vendor } from 'modules';
|
|||
import filetype from 'file-type';
|
||||
|
||||
export class Utils {
|
||||
static isArrowFunction(fn) {
|
||||
return !fn.toString().startsWith('function');
|
||||
}
|
||||
static overload(fn, cb) {
|
||||
const orig = fn;
|
||||
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