/** * BetterDiscord Reflection Module * 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 { Filters, ClientLogger as Logger } from 'common'; import { ReactComponents } from 'modules'; class Reflection { static reactInternalInstance(node) { if (!node) return null; if (!Object.keys(node) || !Object.keys(node).length) return null; const riiKey = Object.keys(node).find(k => k.startsWith('__reactInternalInstance')); return riiKey ? node[riiKey] : null; } static findProp(node, prop) { const ii = this.reactInternalInstance(node); if (!ii) return null; const fir = this.findInReturn(ii, prop); if (fir) return fir; const fim = this.findInChildProps(ii, prop); if (fim) return fim; return null; } static findInReturn(internalInstance, prop) { const r = internalInstance.return; if (!r) return null; let find = this.findMemoizedProp(r, prop); if (find) return find; find = this.findMemoizedState(r, prop); if (find) return find; return this.findInReturn(r, prop); } static findMemoizedProp(obj, prop) { if (!obj.hasOwnProperty('memoizedProps')) return null; obj = obj.memoizedProps; return this.findPropIn(obj, prop); } static findMemoizedState(obj, prop) { if (!obj.hasOwnProperty('memoizedState')) return null; obj = obj.memoizedState; return this.findPropIn(obj, prop); } static findInChildProps(obj, prop) { try { const f = obj.children || obj.memoizedProps.children; if (!f.props) return null; if (!f.props.hasOwnProperty(prop)) return null; return f.props[prop]; } catch (err) { return null; } } static findPropIn(obj, prop) { if (obj && !(obj instanceof Array) && obj instanceof Object && obj.hasOwnProperty(prop)) return obj[prop]; if (obj && obj instanceof Array) { const found = obj.find(mp => { if (mp.props && mp.props.hasOwnProperty(prop)) return true; }); if (found) return found; } return null; } static propIterator(obj, propNames) { if (obj === null || obj === undefined) return null; const curPropName = propNames.shift(1); if (!obj.hasOwnProperty(curPropName)) return null; const curProp = obj[curPropName]; if (propNames.length === 0) { return curProp; } return this.propIterator(curProp, propNames); } static getState(node) { const stateNode = this.getStateNode(node); if (stateNode) return stateNode.state; } static getStateNode(node) { return this.getStateNodes(node)[0]; } static getStateNodes(node) { const instance = this.reactInternalInstance(node); const stateNodes = []; let lastInstance = instance; do { if (lastInstance.return.stateNode instanceof HTMLElement) break; if (lastInstance.return.stateNode) stateNodes.push(lastInstance.return.stateNode); lastInstance = lastInstance.return; } while (lastInstance.return); return stateNodes; } static getComponentStateNode(node, component) { if (component instanceof ReactComponents.ReactComponent) component = component.component; for (const stateNode of this.getStateNodes(node)) { if (stateNode instanceof component) return stateNode; } } static findStateNode(node, filter, first = true) { return this.getStateNodes(node)[first ? 'find' : 'filter'](filter); } static getComponent(node) { return this.getComponents(node)[0]; } static getComponents(node) { const instance = this.reactInternalInstance(node); const components = []; let lastInstance = instance; do { if (typeof lastInstance.return.type === 'string') break; if (lastInstance.return.type) components.push(lastInstance.return.type); lastInstance = lastInstance.return; } while (lastInstance.return); return components; } static findComponent(node, filter, first = true) { return this.getComponents(node)[first ? 'find' : 'filter'](filter); } } const propsProxyHandler = { get(node, prop) { return Reflection.findProp(node, prop); } }; export default function (node) { return new class ReflectionInstance { constructor(node) { if (typeof node === 'string') node = document.querySelector(node); this.node = /* this.el = this.element = */ node; } get el() { return this.node } get element() { return this.node } get reactInternalInstance() { return Reflection.reactInternalInstance(this.node); } get props() { return new Proxy(this.node, propsProxyHandler); } get state() { return Reflection.getState(this.node); } get stateNode() { return Reflection.getStateNode(this.node); } get stateNodes() { return Reflection.getStateNodes(this.node); } getComponentStateNode(component) { return Reflection.getComponentStateNode(this.node, component); } findStateNode(filter) { if (typeof filter === 'function') return Reflection.findStateNode(this.node, filter); if (filter) return Reflection.getComponentStateNode(this.node, filter); return Reflection.getStateNode(this.node); } get component() { return Reflection.getComponent(this.node); } get components() { return Reflection.getComponents(this.node); } getComponentByProps(props, selector) { return Reflection.findComponent(this.node, Filters.byProperties(props, selector)); } getComponentByPrototypes(props, selector) { return Reflection.findComponent(this.node, Filters.byPrototypeFields(props, selector)); } getComponentByRegex(regex, selector) { return Reflection.findComponent(this.node, Filters.byCode(regex, selector)); } getComponentByDisplayName(name) { return Reflection.findComponent(this.node, Filters.byDisplayName(name)); } forceUpdate(filter) { try { const stateNode = this.findStateNode(filter); if (!stateNode || !stateNode.forceUpdate) return; stateNode.forceUpdate(); } catch (err) { Logger.err('Reflection', err); } } prop(propName) { const split = propName.split('.'); const first = Reflection.findProp(this.node, split[0]); if (split.length === 1) return first; return Reflection.propIterator(first, split.slice(1)); } }(node); }