You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
vizality/renderer/src/modules/webpack/index.js

260 lines
8.3 KiB

/* 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>|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<void>}
*/
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>|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>|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<WebpackModule>} 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<WebpackModule>|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;