/* eslint-disable no-unused-vars */ /** * * @module Webpack */ import { log, warn, error } from '../util/Logger'; import moduleFilters from './modules.json'; import { sleep } from '../util/Time'; /** @private */ const _module = 'Module'; const _submodule = 'Webpack'; const _log = (...message) => log({ labels: [ _module, _submodule ], message }); const _warn = (...message) => warn({ labels: [ _module, _submodule ], message }); const _error = (...message) => error({ labels: [ _module, _submodule ], message }); /** * * @param {*} filter * @param {*} all * @returns * @private */ const _getModules = (filter, all = false) => { if (!this.instance?.c) { return; } const moduleInstances = Object.values(this.instance.c).filter(m => m.exports); if (all) { const exports = moduleInstances.filter(m => filter(m.exports)).map(m => m.exports); const expDefault = moduleInstances.filter(m => m.exports.default && filter(m.exports.default)).map(m => m.exports.default); return exports.concat(expDefault); } const exports = moduleInstances.find(m => filter(m.exports)); if (exports) { return exports.exports; } const expDefault = moduleInstances.find(m => m.exports.default && filter(m.exports.default)); if (expDefault) { return expDefault.exports.default; } return null; }; /** * Grabs a module from the Webpack store * @param {Function|Array} filter Filter used to grab the module. Can be a function or an array of keys the object must have. * @param {boolean} retry Whether or not to retry fetching if the module is not found. Each try will be delayed by 100ms and max retries is 20. * @param {boolean} forever If Vizality should try to fetch the module forever. Should be used only if you're in early stages of startup. * @returns {Promise|object} The found module. A promise will always be returned, unless retry is false. * @private */ const _getModule = (filter, retry = false, forever = false) => { if (Array.isArray(filter)) { const keys = filter; filter = m => keys.every(key => m.hasOwnProperty(key) || (m.__proto__ && m.__proto__.hasOwnProperty(key))); } if (!retry) return _getModules(filter); return new Promise(async res => { let mdl; for (let i = 0; i < (forever ? 666 : 21); i++) { mdl = _getModules(filter); if (mdl) return res(mdl); await sleep(1); } res(mdl); }); }; /** * Initializes the injection into webpack. * @returns {Promise} */ export const initialize = async () => { /** * If 'window.webpackChunkdiscord_app' doesn't exist create it * (Discord loads modules that existed window object exists) */ if (!window.webpackChunkdiscord_app) window.webpackChunkdiscord_app = [] window.webpackChunkdiscord_app.push([ [ Symbol('vizality') ], {}, (exports) => this.instance = exports ]) for (const mdl in moduleFilters) { this[mdl] = await _getModule(moduleFilters[mdl], true); } }; export const findComponent = (keyword, exact = false) => { if (!keyword) { return _warn('First argument provided must be a string.'); } let byDisplayName, byDefault, byType; const results = {}; if (exact) { byDisplayName = this.getModuleByDisplayName(keyword); byDefault = this.getModules(m => m.default && m.default.displayName === keyword); byType = this.getModules(m => m.type && m.type.displayName === keyword); } else { keyword = keyword.toLowerCase(); byDisplayName = this.getModules(m => m.displayName && m.displayName.toLowerCase().indexOf(keyword) > -1); byDefault = this.getModules(m => m.default && m.default.displayName && m.default.displayName.toLowerCase().indexOf(keyword) > -1); byType = this.getModules(m => m.type && m.type.displayName && m.type.displayName.toLowerCase().indexOf(keyword) > -1); } if (byDisplayName && byDisplayName.length) { Object.assign(results, { displayName: { matches: byDisplayName } }); } if (byDefault && byDefault.length) { Object.assign(results, { default: { matches: byDefault } }); } if (byType && byType.length) { Object.assign(results, { type: { matches: byType } }); } if (!results || !Object.keys(results).length) { return _warn(`No results found for components ${exact ? 'matching' : 'containing'} '${keyword}'`); } let count = 0; const resultsText = count === 1 ? 'result' : 'results'; Object.keys(results).forEach(key => count += results[key].matches.length); _log(`${count} ${resultsText} found for components ${exact ? 'matching' : 'containing'} '${keyword}':\n`); return results; }; /** * Gets all cached Webpack modules. * @returns {object[]} Cached Webpack modules */ export const getAllModules = () => { return this.getModules(m => m); }; /** * * @param {...any} filter * @returns */ export const getModule = (...filter) => { let retry = false; let forever = false; if (typeof filter[filter.length - 1] === 'boolean') { forever = filter.pop(); if (typeof filter[filter.length - 1] === 'boolean') { retry = filter.pop(); } else { retry = forever; forever = false; } } if (typeof filter[0] === 'function') { [ filter ] = filter; } return _getModule(filter, retry, forever); }; /** * Grabs a React component by its display name * @param {string} displayName Component's display name. * @param {boolean} retry Whether or not to retry fetching if the module is not found. Each try will be delayed by 100ms and max retries is 20. * @param {boolean} forever If Vizality should try to fetch the module forever. Should be used only if you're in early stages of startup. * @returns {Promise|object} The component. A promise will always be returned, unless retry is false. */ export const getModuleByDisplayName = (displayName, retry = false, forever = false) => { return _getModule(m => m?.displayName && m?.displayName?.toLowerCase() === displayName?.toLowerCase(), retry, forever); }; /** * Grabs a Webpack module by its ID. * @param {string} id Webpack module's ID * @param {boolean} retry Whether or not to retry fetching if the module is not found. Each try will be delayed by 100ms and max retries is 20. * @param {boolean} forever If Vizality should try to fetch the module forever. Should be used only if you're in early stages of startup. * @returns {Promise|object} The component. A promise will always be returned, unless retry is false. */ export const getModuleById = (id, retry = false, forever = false) => { return _getModule(m => m?._dispatchToken === `ID_${id}`, retry, forever); }; /* * @todo: Make this work like getModule, where it accepts the argument as strings... i.e. * getModuleByPrototypes('_log') instead of getModuleByPrototypes([ '_log' ]) */ /** * Grabs a module using properties on its prototype. * @param {string} filter Properties to use to filter modules. * @param {boolean} retry Whether to retry fetching if the module is not found. Each try will be delayed by 100ms and max retries is 20. * @param {boolean} forever Whether to try to fetch the module forever. Should be used only if you're in early stages of startup. * @returns {WebpackModule|Promise} The found module. A promise will only be returned if `retry` is true. */ export const getModuleByPrototypes = (filter, retry = false, forever = false) => { return _getModule(m => m.prototype && filter.every(prop => m.prototype[prop]), retry, forever); }; /** * Grabs all found modules from the webpack store. * @param {Function|Array} filter Filter used to grab the module * @returns {Array|undefined} The found modules */ export const getModules = (filter) => { if (Array.isArray(filter)) { const keys = filter; filter = m => keys.every(key => m.hasOwnProperty(key) || (m.__proto__ && m.__proto__.hasOwnProperty(key))); } return _getModules(filter, true); }; export const getModulesByKeyword = (keyword, exact = false) => { return this.getModules(module => { const modules = [ ...Object.keys(module), ...Object.keys(module.__proto__) ]; for (const mdl of modules) { if (exact) { if (mdl === keyword) { return true; } } else { if (mdl.toLowerCase().indexOf(keyword.toLowerCase()) > -1) { return true; } } } return false; }); }; export default this;