mirror of https://github.com/GooseMod/GooseMod
GooseMod v5.0.0: React settings sidebar, native / included patcher and context menu, separate version info, minor optimizations and fixes
parent
6fd54b3f7e
commit
71113fd576
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,95 @@
|
||||
const generateIdSegment = () => Math.random().toString(36).replace(/[^a-z0-9]+/g, ''); // Random 12 char string
|
||||
|
||||
const generateId = (segments = 3) => new Array(segments).fill(0).map(() => generateIdSegment()).join(''); // Chain random 12 char strings together X times
|
||||
|
||||
// Based on Powercord's Injector - <3
|
||||
// https://github.com/powercord-org/powercord/blob/v2/src/fake_node_modules/powercord/injector/index.js
|
||||
|
||||
let injectionIndex = [];
|
||||
|
||||
export const inject = (injectionId, mod, funcName, patch, pre = false) => {
|
||||
if (!mod) {
|
||||
return console.error(`Tried to patch undefined (Injection ID "${injectionId}")`);
|
||||
}
|
||||
|
||||
if (injectionIndex.find(i => i.id === injectionId)) {
|
||||
return console.error(`Injection ID "${injectionId}" is already used!`);
|
||||
}
|
||||
|
||||
if (!mod.__goosemodInjectionId || !mod.__goosemodInjectionId[funcName]) { // First injection for the targetted function
|
||||
const id = generateId(); // Random ID to identify function
|
||||
|
||||
mod.__goosemodInjectionId = Object.assign((mod.__goosemodInjectionId || {}), { [funcName]: id });
|
||||
|
||||
mod[funcName] = (_oldMethod => function (...args) { // Override the function to do run injections pre and after
|
||||
const finalArgs = _runPreInjections(id, args, this);
|
||||
|
||||
if (finalArgs !== false && Array.isArray(finalArgs)) {
|
||||
const returned = _oldMethod ? _oldMethod.call(this, ...finalArgs) : void 0;
|
||||
return _runInjections(id, finalArgs, returned, this);
|
||||
}
|
||||
})(mod[funcName]);
|
||||
|
||||
injectionIndex[id] = [];
|
||||
}
|
||||
|
||||
injectionIndex.push({ // Add injection to index to keep track
|
||||
module: mod.__goosemodInjectionId[funcName],
|
||||
id: injectionId,
|
||||
method: patch,
|
||||
pre
|
||||
});
|
||||
};
|
||||
|
||||
export const uninject = (injectionId) => { // Remove injection from index (if there)
|
||||
injectionIndex = injectionIndex.filter(i => i.id !== injectionId);
|
||||
};
|
||||
|
||||
export const isInjected = (injectionId) => injectionIndex.some(i => i.id === injectionId); // Check if the given id is in the index
|
||||
|
||||
const _runPreInjections = (modId, originArgs, _this) => { // Run pre injections (wrapper)
|
||||
const injections = injectionIndex.filter(i => i.module === modId && i.pre);
|
||||
|
||||
if (injections.length === 0) {
|
||||
return originArgs;
|
||||
}
|
||||
|
||||
return _runPreInjectionsRecursive(injections, originArgs, _this);
|
||||
};
|
||||
|
||||
const _runPreInjectionsRecursive = (injections, originalArgs, _this) => { // Run pre injections (actual)
|
||||
const injection = injections.pop();
|
||||
|
||||
let args = injection.method.call(_this, originalArgs);
|
||||
if (args === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Array.isArray(args)) {
|
||||
console.error(`Pre-injection ${injection.id} returned something invalid. Injection will be ignored.`);
|
||||
|
||||
args = originalArgs;
|
||||
}
|
||||
|
||||
if (injections.length > 0) {
|
||||
return _runPreInjectionsRecursive(injections, args, _this);
|
||||
}
|
||||
|
||||
return args;
|
||||
};
|
||||
|
||||
const _runInjections = (modId, originArgs, originReturn, _this) => { // Run post injections
|
||||
let finalReturn = originReturn;
|
||||
|
||||
const injections = injectionIndex.filter(i => i.module === modId && !i.pre);
|
||||
|
||||
injections.forEach(i => {
|
||||
try {
|
||||
finalReturn = i.method.call(_this, originArgs, finalReturn);
|
||||
} catch (e) {
|
||||
console.error(`Failed to run injection "${i.id}"`, e);
|
||||
}
|
||||
});
|
||||
|
||||
return finalReturn;
|
||||
};
|
@ -0,0 +1,98 @@
|
||||
import { inject, uninject } from './base';
|
||||
import { getOwnerInstance, findInReactTree } from '../react';
|
||||
|
||||
let goosemodScope = {};
|
||||
|
||||
export const setThisScope = (scope) => {
|
||||
goosemodScope = scope;
|
||||
};
|
||||
|
||||
export const labelToId = (label) => label.toLowerCase().replace(/ /g, '-');
|
||||
|
||||
export const getInjectId = (id) => `gm-cm-${id}`;
|
||||
|
||||
export const patchTypeToNavId = (type) => {
|
||||
switch (type) {
|
||||
case 'user':
|
||||
return 'user-context';
|
||||
|
||||
case 'message':
|
||||
return 'message';
|
||||
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
};
|
||||
|
||||
export const getExtraInfo = (type) => {
|
||||
try {
|
||||
switch (type) {
|
||||
case 'message':
|
||||
return getOwnerInstance(document.getElementById('message'))._reactInternalFiber.child.memoizedProps.children.props.children.props;
|
||||
|
||||
case 'user':
|
||||
return getOwnerInstance(document.querySelector('#user-context'))._reactInternalFiber.return.memoizedProps;
|
||||
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
} catch (e) { return undefined; }
|
||||
};
|
||||
|
||||
export const add = (type, itemProps) => {
|
||||
const { React } = goosemodScope.webpackModules.common;
|
||||
const Menu = goosemodScope.webpackModules.findByProps('MenuItem');
|
||||
|
||||
const wantedNavId = patchTypeToNavId(type);
|
||||
const id = itemProps.id || labelToId(itemProps.label);
|
||||
|
||||
if (!itemProps.id) {
|
||||
itemProps.id = id;
|
||||
}
|
||||
|
||||
const origAction = itemProps.action;
|
||||
|
||||
itemProps.action = function() {
|
||||
return origAction(arguments, getExtraInfo(type));
|
||||
};
|
||||
|
||||
inject(getInjectId(id), Menu, 'default', (args) => {
|
||||
const [ { navId, children } ] = args;
|
||||
if (navId !== wantedNavId) {
|
||||
return args;
|
||||
}
|
||||
|
||||
console.log('inj');
|
||||
|
||||
const alreadyHasItem = findInReactTree(children, child => child && child.props && child.props.id === itemProps.id);
|
||||
if (alreadyHasItem) return args;
|
||||
|
||||
const item = React.createElement(Menu.MenuItem, itemProps);
|
||||
|
||||
console.log(item);
|
||||
|
||||
let goosemodGroup = findInReactTree(children, child => child && child.props && child.props.goosemod === true);
|
||||
|
||||
console.log(goosemodGroup);
|
||||
|
||||
if (!goosemodGroup) {
|
||||
goosemodGroup = React.createElement(Menu.MenuGroup, { goosemod: true }, item);
|
||||
|
||||
console.log('a', goosemodGroup);
|
||||
|
||||
children.push([ React.createElement(Menu.MenuSeparator), goosemodGroup ]);
|
||||
} else {
|
||||
console.log('b', goosemodGroup);
|
||||
|
||||
if (!Array.isArray(goosemodGroup.props.children)) {
|
||||
goosemodGroup.props.children = [ goosemodGroup.props.children ];
|
||||
}
|
||||
|
||||
goosemodGroup.props.children.push(item);
|
||||
}
|
||||
|
||||
return args;
|
||||
}, true);
|
||||
};
|
||||
|
||||
export const remove = (label) => uninject(getInjectId(labelToId(label)));
|
@ -0,0 +1,9 @@
|
||||
export { inject, uninject, isInjected } from './base';
|
||||
|
||||
import * as _contextMenu from './contextMenu';
|
||||
|
||||
export const contextMenu = _contextMenu;
|
||||
|
||||
export const setThisScope = (scope) => {
|
||||
_contextMenu.setThisScope(scope);
|
||||
};
|
@ -0,0 +1,86 @@
|
||||
// https://gist.github.com/noodlebox/047a9f57a8a714d88ca4a60672a22c81
|
||||
export function getReactInstance (e) { return e[Object.keys(e).find(k => k.startsWith("__reactInternalInstance"))]; }
|
||||
|
||||
// https://rauenzi.github.io/BDPluginLibrary/docs/modules_reacttools.js.html
|
||||
export function getOwnerInstance(node, {include, exclude = ["Popout", "Tooltip", "Scroller", "BackgroundFlash"], filter = _ => _} = {}) {
|
||||
if (node === undefined) return undefined;
|
||||
|
||||
const excluding = include === undefined;
|
||||
const nameFilter = excluding ? exclude : include;
|
||||
|
||||
function getDisplayName(owner) {
|
||||
const type = owner.type;
|
||||
if (!type) return null;
|
||||
return type.displayName || type.name || null;
|
||||
}
|
||||
|
||||
function classFilter(owner) {
|
||||
const name = getDisplayName(owner);
|
||||
return (name !== null && !!(nameFilter.includes(name) ^ excluding));
|
||||
}
|
||||
|
||||
let curr = getReactInstance(node);
|
||||
|
||||
for (curr = curr && curr.return; curr !== null; curr = curr.return) {
|
||||
if (curr === null) continue;
|
||||
|
||||
const owner = curr.stateNode;
|
||||
if (owner !== null && !(owner instanceof HTMLElement) && classFilter(curr) && filter(owner)) return owner;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function findInReactTree(tree, searchFilter) {
|
||||
return findInTree(tree, searchFilter, {walkable: ["props", "children", "child", "sibling"]});
|
||||
}
|
||||
|
||||
export function findInTree(tree, filter, {walkable = null, ignore = []} = {}) {
|
||||
if (!tree || typeof tree !== 'object') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof filter === 'string') {
|
||||
if (tree.hasOwnProperty(filter)) {
|
||||
return tree[filter];
|
||||
}
|
||||
|
||||
return;
|
||||
} else if (filter(tree)) {
|
||||
return tree;
|
||||
}
|
||||
|
||||
let returnValue = null;
|
||||
|
||||
if (Array.isArray(tree)) {
|
||||
for (const value of tree) {
|
||||
returnValue = findInTree(value, filter, {
|
||||
walkable,
|
||||
ignore
|
||||
});
|
||||
|
||||
if (returnValue) {
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const walkables = !walkable ? Object.keys(tree) : walkable;
|
||||
|
||||
for (const key of walkables) {
|
||||
if (!tree.hasOwnProperty(key) || ignore.includes(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
returnValue = findInTree(tree[key], filter, {
|
||||
walkable,
|
||||
ignore
|
||||
});
|
||||
|
||||
if (returnValue) {
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
Loading…
Reference in new issue