Merge pull request #1514 from BetterDiscord/feat/webpack-aliases

Add webpack aliases.
This commit is contained in:
Zerebos 2023-06-14 20:12:29 -05:00 committed by GitHub
commit 9bfd078695
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 220 additions and 13 deletions

View File

@ -1,6 +1,18 @@
import Logger from "common/logger";
import WebpackModules, {Filters} from "../webpackmodules";
const getOptions = (args, defaultOptions = {}) => {
if (args.length > 1 &&
typeof(args[args.length - 1]) === "object" &&
!Array.isArray(args[args.length - 1]) &&
args[args.length - 1] !== null
) {
Object.assign(defaultOptions, args.pop());
}
return defaultOptions;
};
/**
* `Webpack` is a utility class for getting internal webpack modules. Instance is accessible through the {@link BdApi}.
* This is extremely useful for interacting with the internals of Discord.
@ -9,6 +21,10 @@ import WebpackModules, {Filters} from "../webpackmodules";
* @name Webpack
*/
const Webpack = {
/**
* A Proxy that returns the module source by ID.
*/
modules: WebpackModules.modules,
/**
* Series of {@link Filters} to be used for finding webpack modules.
@ -16,19 +32,29 @@ const Webpack = {
* @memberof Webpack
*/
Filters: {
/**
* @deprecated
*/
byProps(...props) {return Filters.byKeys(props);},
/**
* Generates a function that filters by a set of properties.
* @param {...string} props List of property names
* @param {...string} keys List of property names
* @returns {function} A filter that checks for a set of properties
*/
byProps(...props) {return Filters.byProps(props);},
byKeys(...keys) {return Filters.byKeys(keys);},
/**
* @deprecated
*/
byPrototypeFields(...props) {return Filters.byPrototypeKeys(props);},
/**
* Generates a function that filters by a set of properties on the object's prototype.
* @param {...string} props List of property names
* @returns {function} A filter that checks for a set of properties on the object's prototype.
*/
byPrototypeFields(...props) {return Filters.byPrototypeFields(props);},
byPrototypeKeys(...props) {return Filters.byPrototypeKeys(props);},
/**
* Generates a function that filters by a regex.
@ -52,6 +78,13 @@ const Webpack = {
*/
byDisplayName(name) {return Filters.byDisplayName(name);},
/**
* Generates a function that filters by a specific internal Store name.
* @param {string} name Name the store should have
* @returns {function} A filter that checks for a Store name match
*/
byStoreName(name) {return Filters.byStoreName(name);},
/**
* Generates a combined function from a list of filters.
* @param {...function} filters A list of filters
@ -60,6 +93,22 @@ const Webpack = {
combine(...filters) {return Filters.combine(...filters);},
},
/**
* Searches for a module by value, returns module & matched key. Useful in combination with the Patcher.
* @param {(value: any, index: number, array: any[]) => boolean} filter A function to use to filter the module
* @param {object} [options] Set of options to customize the search
* @param {any} [options.target=null] Optional module target to look inside.
* @param {Boolean} [options.defaultExport=true] Whether to return default export when matching the default export
* @param {Boolean} [options.searchExports=false] Whether to execute the filter on webpack export getters.
* @return {[Any, string]}
*/
getWithKey(filter, options = {}) {
if (("first" in options)) return Logger.error("BdApi.Webpack~getWithKey", "Unsupported option first.");
if (("defaultExport" in options) && typeof(options.defaultExport) !== "boolean") return Logger.error("BdApi.Webpack~getWithKey", "Unsupported type used for options.defaultExport", options.defaultExport, "boolean expected.");
if (("searchExports" in options) && typeof(options.searchExports) !== "boolean") return Logger.error("BdApi.Webpack~getWithKey", "Unsupported type used for options.searchExports", options.searchExports, "boolean expected.");
return WebpackModules.getWithKey(filter, options);
},
/**
* Finds a module using a filter function.
* @memberof Webpack
@ -71,12 +120,26 @@ const Webpack = {
* @return {any}
*/
getModule(filter, options = {}) {
if (("first" in options) && typeof(options.first) !== "boolean") return Logger.error("BdApi.Webpack~getModule", "Unsupported type used for options.first", options.first, "boolean expected.");
if (("first" in options) && typeof(options.first) !== "boolean") return Logger.error("BdApi.Webpack~get", "Unsupported type used for options.first", options.first, "boolean expected.");
if (("defaultExport" in options) && typeof(options.defaultExport) !== "boolean") return Logger.error("BdApi.Webpack~getModule", "Unsupported type used for options.defaultExport", options.defaultExport, "boolean expected.");
if (("searchExports" in options) && typeof(options.searchExports) !== "boolean") return Logger.error("BdApi.Webpack~getModule", "Unsupported type used for options.searchExports", options.searchExports, "boolean expected.");
return WebpackModules.getModule(filter, options);
},
/**
* Finds all modules matching a filter function.
* @param {Function} filter A function to use to filter modules
* @param {object} [options] Options to configure the search
* @param {Boolean} [options.defaultExport=true] Whether to return default export when matching the default export
* @param {Boolean} [options.searchExports=false] Whether to execute the filter on webpack exports
* @return {any[]}
*/
getModules(filter, options = {}) {
if (("defaultExport" in options) && typeof(options.defaultExport) !== "boolean") return Logger.error("BdApi.Webpack~getModules", "Unsupported type used for options.defaultExport", options.defaultExport, "boolean expected.");
if (("searchExports" in options) && typeof(options.searchExports) !== "boolean") return Logger.error("BdApi.Webpack~getModules", "Unsupported type used for options.searchExports", options.searchExports, "boolean expected.");
return WebpackModules.getModule(filter, Object.assign(options, {first: false}));
},
/**
* Finds multiple modules using multiple filters.
* @memberof Webpack
@ -102,9 +165,106 @@ const Webpack = {
waitForModule(filter, options = {}) {
if (("defaultExport" in options) && typeof(options.defaultExport) !== "boolean") return Logger.error("BdApi.Webpack~waitForModule", "Unsupported type used for options.defaultExport", options.defaultExport, "boolean expected.");
if (("signal" in options) && !(options.signal instanceof AbortSignal)) return Logger.error("BdApi.Webpack~waitForModule", "Unsupported type used for options.signal", options.signal, "AbortSignal expected.");
if (("searchExports" in options) && typeof(options.searchExports) !== "boolean") return Logger.error("BdApi.Webpack~getModule", "Unsupported type used for options.searchExports", options.searchExports, "boolean expected.");
if (("searchExports" in options) && typeof(options.searchExports) !== "boolean") return Logger.error("BdApi.Webpack~waitForModule", "Unsupported type used for options.searchExports", options.searchExports, "boolean expected.");
return WebpackModules.getLazy(filter, options);
},
/**
* Finds a module using its code.
* @param {RegEx} regex A regular expression to use to filter modules
* @param {object} [options] Options to configure the search
* @param {Boolean} [options.defaultExport=true] Whether to return default export when matching the default export
* @param {Boolean} [options.searchExports=false] Whether to execute the filter on webpack exports
* @return {Any}
*/
getByRegex(regex, options = {}) {
return WebpackModules.getModule(Filters.byRegex(regex), options);
},
/**
* Finds all modules using its code.
* @param {RegEx} regex A regular expression to use to filter modules
* @param {object} [options] Options to configure the search
* @param {Boolean} [options.defaultExport=true] Whether to return default export when matching the default export
* @param {Boolean} [options.searchExports=false] Whether to execute the filter on webpack exports
* @return {Any[]}
*/
getAllByRegex(regex, options = {}) {
return WebpackModules.getModule(Filters.byRegex(regex), Object.assign({}, options, {first: true}));
},
/**
* Finds a single module using properties on its prototype.
* @param {...string} prototypes Properties to use to filter modules
* @return {Any}
*/
getByPrototypeKeys(...prototypes) {
const options = getOptions(prototypes);
return WebpackModules.getModule(Filters.byPrototypeKeys(prototypes), options);
},
/**
* Finds all modules with a set of properties of its prototype.
* @param {...string} prototypes Properties to use to filter modules
* @return {Any[]}
*/
getAllByPrototypeKeys(...prototypes) {
const options = getOptions(prototypes, {first: false});
return WebpackModules.getModule(Filters.byPrototypeKeys(prototypes), options);
},
/**
* Finds a single module using its own properties.
* @param {...string} props Properties to use to filter modules
* @return {Any}
*/
getByKeys(...props) {
const options = getOptions(props);
return WebpackModules.getModule(Filters.byKeys(props), options);
},
/**
* Finds all modules with a set of properties.
* @param {...string} props Properties to use to filter modules
* @return {Any[]}
*/
getAllByKeys(...props) {
const options = getOptions(props, {first: false});
return WebpackModules.getModule(Filters.byKeys(props), options);
},
/**
* Finds a single module using a set of strings.
* @param {...String} props Strings to use to filter modules
* @return {Any}
*/
getByStrings(...strings) {
const options = getOptions(strings);
return WebpackModules.getModule(Filters.byStrings(...strings), options);
},
/**
* Finds all modules with a set of strings.
* @param {...String} strings Strings to use to filter modules
* @return {Any[]}
*/
getAllByStrings(...strings) {
const options = getOptions(strings, {first: false});
return WebpackModules.getModule(Filters.byStrings(...strings), options);
},
/**
* Finds an internal Store module using the name.
* @param {String} name Name of the store to find (usually includes "Store")
* @return {Any}
*/
getStore(name) {return WebpackModules.getModule(Filters.byStoreName(name));},
};
Object.freeze(Webpack);

View File

@ -22,7 +22,7 @@ export class Filters {
* @param {module:WebpackModules.Filters~filter} filter - Additional filter
* @returns {module:WebpackModules.Filters~filter} - A filter that checks for a set of properties
*/
static byProps(props, filter = m => m) {
static byKeys(props, filter = m => m) {
return module => {
if (!module) return false;
if (typeof(module) !== "object" && typeof(module) !== "function") return false;
@ -41,7 +41,7 @@ export class Filters {
* @param {module:WebpackModules.Filters~filter} filter - Additional filter
* @returns {module:WebpackModules.Filters~filter} - A filter that checks for a set of properties on the object's prototype
*/
static byPrototypeFields(fields, filter = m => m) {
static byPrototypeKeys(fields, filter = m => m) {
return module => {
if (!module) return false;
if (typeof(module) !== "object" && typeof(module) !== "function") return false;
@ -94,7 +94,6 @@ export class Filters {
/**
* Generates a {@link module:WebpackModules.Filters~filter} that filters by a set of properties.
* @param {string} name - Name the module should have
* @param {module:WebpackModules.Filters~filter} filter - Additional filter
* @returns {module:WebpackModules.Filters~filter} - A filter that checks for a set of properties
*/
static byDisplayName(name) {
@ -103,6 +102,17 @@ export class Filters {
};
}
/**
* Generates a {@link module:WebpackModules.Filters~filter} that filters by a set of properties.
* @param {string} name - Name the store should have (usually includes the word Store)
* @returns {module:WebpackModules.Filters~filter} - A filter that checks for a set of properties
*/
static byStoreName(name) {
return module => {
return module?._dispatchToken && module?.getName?.() === name;
};
}
/**
* Generates a combined {@link module:WebpackModules.Filters~filter} from a list of filters.
* @param {...module:WebpackModules.Filters~filter} filters - A list of filters
@ -140,6 +150,25 @@ export default class WebpackModules {
static findByUniqueProperties(props, first = true) {return first ? this.getByProps(...props) : this.getAllByProps(...props);}
static findByDisplayName(name) {return this.getByDisplayName(name);}
/**
* A Proxy that returns the module source by ID.
*/
static modules = new Proxy({}, {
ownKeys() {return Object.keys(WebpackModules.require.m);},
getOwnPropertyDescriptor() {
return {
enumerable: true,
configurable: true, // Not actually
};
},
get(_, k) {
return WebpackModules.require.m[k];
},
set() {
throw new Error("[WebpackModules~modules] Setting modules is not allowed.");
}
});
/**
* Finds a module using a filter function.
* @param {function} filter A function to use to filter modules
@ -252,6 +281,24 @@ export default class WebpackModules {
return returnedModules;
}
/**
* Searches for a module by value, returns module & matched key. Useful in combination with the Patcher.
* @param {(value: any, index: number, array: any[]) => boolean} filter A function to use to filter the module
* @param {object} [options] Set of options to customize the search
* @param {any} [options.target=null] Optional module target to look inside.
* @param {Boolean} [options.defaultExport=true] Whether to return default export when matching the default export
* @param {Boolean} [options.searchExports=false] Whether to execute the filter on webpack export getters.
* @return {[Any, string]}
*/
static *getWithKey(filter, {target = null, ...rest} = {}) {
yield target ??= this.getModule(exports =>
Object.values(exports).some(filter),
rest
);
yield target && Object.keys(target).find(k => filter(target[k]));
}
/**
* Finds all modules matching a filter function.
* @param {Function} filter A function to use to filter modules
@ -283,7 +330,7 @@ export default class WebpackModules {
* @return {Any}
*/
static getByPrototypes(...prototypes) {
return this.getModule(Filters.byPrototypeFields(prototypes));
return this.getModule(Filters.byPrototypeKeys(prototypes));
}
/**
@ -292,7 +339,7 @@ export default class WebpackModules {
* @return {Any}
*/
static getAllByPrototypes(...prototypes) {
return this.getModule(Filters.byPrototypeFields(prototypes), {first: false});
return this.getModule(Filters.byPrototypeKeys(prototypes), {first: false});
}
/**
@ -301,7 +348,7 @@ export default class WebpackModules {
* @return {Any}
*/
static getByProps(...props) {
return this.getModule(Filters.byProps(props));
return this.getModule(Filters.byKeys(props));
}
/**
@ -310,7 +357,7 @@ export default class WebpackModules {
* @return {Any}
*/
static getAllByProps(...props) {
return this.getModule(Filters.byProps(props), {first: false});
return this.getModule(Filters.byKeys(props), {first: false});
}
/**

View File

@ -62,7 +62,7 @@ export default new class SettingsRenderer {
}
async patchSections() {
const UserSettings = await WebpackModules.getLazy(Filters.byPrototypeFields(["getPredicateSections"]));
const UserSettings = await WebpackModules.getLazy(Filters.byPrototypeKeys(["getPredicateSections"]));
Patcher.after("SettingsManager", UserSettings.prototype, "getPredicateSections", (thisObject, args, returnValue) => {
let location = returnValue.findIndex(s => s.section.toLowerCase() == "changelog") - 1;