From 8adf7ca155a7f2b9d59ecfcc98ca47d123a26f59 Mon Sep 17 00:00:00 2001 From: Ven Date: Sun, 30 Oct 2022 20:45:18 +0100 Subject: [PATCH] Webpack Warnings & Errors (#178) * dev: Useful strict Warnings & Errors * Always log error * Ignore pending patches with all or whose predicate = false * Error -> Warn --- src/Vencord.ts | 15 ++- src/api/Commands/commandHelpers.ts | 2 +- src/components/PluginSettings/PluginModal.tsx | 2 +- src/components/PluginSettings/index.tsx | 2 +- src/plugins/experiments.tsx | 2 +- src/plugins/ignoreActivities.ts | 2 +- src/plugins/index.ts | 1 + src/plugins/interactionKeybinds.ts | 2 +- src/plugins/petpet.ts | 6 +- .../components/PronounsChatComponent.tsx | 2 +- src/plugins/reverseImageSearch.tsx | 2 +- src/plugins/sendify.ts | 6 +- src/utils/logger.ts | 2 +- src/webpack/common.tsx | 2 +- src/webpack/patchWebpack.ts | 2 +- src/webpack/webpack.ts | 95 +++++++++++-------- 16 files changed, 90 insertions(+), 55 deletions(-) diff --git a/src/Vencord.ts b/src/Vencord.ts index 4d18cb23..041335f5 100644 --- a/src/Vencord.ts +++ b/src/Vencord.ts @@ -25,7 +25,7 @@ export * as Webpack from "./webpack"; import { popNotice, showNotice } from "./api/Notices"; import { PlainSettings, Settings } from "./api/settings"; -import { startAllPlugins } from "./plugins"; +import { patches, PMLogger, startAllPlugins } from "./plugins"; export { PlainSettings, Settings }; @@ -61,6 +61,19 @@ async function init() { UpdateLogger.error("Failed to check for updates", err); } } + + if (IS_DEV) { + const pendingPatches = patches.filter(p => !p.all && p.predicate?.() !== false); + if (pendingPatches.length) + PMLogger.warn( + "Webpack has finished initialising, but some patches haven't been applied yet.", + "This might be expected since some Modules are lazy loaded, but please verify", + "that all plugins are working as intended.", + "You are seeing this warning because this is a Development build of Vencord.", + "\nThe following patches have not been applied:", + "\n\n" + pendingPatches.map(p => `${p.plugin}: ${p.find}`).join("\n") + ); + } } init(); diff --git a/src/api/Commands/commandHelpers.ts b/src/api/Commands/commandHelpers.ts index 90b89ed1..8986248d 100644 --- a/src/api/Commands/commandHelpers.ts +++ b/src/api/Commands/commandHelpers.ts @@ -24,7 +24,7 @@ import { filters, waitFor } from "../../webpack"; import { Argument } from "./types"; const createBotMessage = lazyWebpack(filters.byCode('username:"Clyde"')); -const MessageSender = lazyWebpack(filters.byProps(["receiveMessage"])); +const MessageSender = lazyWebpack(filters.byProps("receiveMessage")); let SnowflakeUtils: any; waitFor("fromTimestamp", m => SnowflakeUtils = m); diff --git a/src/components/PluginSettings/PluginModal.tsx b/src/components/PluginSettings/PluginModal.tsx index 9a47c322..4c14d61d 100644 --- a/src/components/PluginSettings/PluginModal.tsx +++ b/src/components/PluginSettings/PluginModal.tsx @@ -38,7 +38,7 @@ import { } from "./components"; const UserSummaryItem = lazyWebpack(filters.byCode("defaultRenderUser", "showDefaultAvatarsForNullUsers")); -const AvatarStyles = lazyWebpack(filters.byProps(["moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"])); +const AvatarStyles = lazyWebpack(filters.byProps("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar")); const UserRecord: Constructor> = proxyLazy(() => UserStore.getCurrentUser().constructor) as any; interface PluginModalProps extends ModalProps { diff --git a/src/components/PluginSettings/index.tsx b/src/components/PluginSettings/index.tsx index 1a2e78f3..9ab1396f 100644 --- a/src/components/PluginSettings/index.tsx +++ b/src/components/PluginSettings/index.tsx @@ -36,7 +36,7 @@ import * as styles from "./styles"; const logger = new Logger("PluginSettings", "#a6d189"); const Select = lazyWebpack(filters.byCode("optionClassName", "popoutPosition", "autoFocus", "maxVisibleItems")); -const InputStyles = lazyWebpack(filters.byProps(["inputDefault", "inputWrapper"])); +const InputStyles = lazyWebpack(filters.byProps("inputDefault", "inputWrapper")); const CogWheel = lazyWebpack(filters.byCode("18.564C15.797 19.099 14.932 19.498 14 19.738V22H10V19.738C9.069")); const InfoIcon = lazyWebpack(filters.byCode("4.4408921e-16 C4.4771525,-1.77635684e-15 4.4408921e-16")); diff --git a/src/plugins/experiments.tsx b/src/plugins/experiments.tsx index c6303f3b..eac5b48d 100644 --- a/src/plugins/experiments.tsx +++ b/src/plugins/experiments.tsx @@ -23,7 +23,7 @@ import { Settings } from "../Vencord"; import { filters } from "../webpack"; import { Forms, React } from "../webpack/common"; -const KbdStyles = lazyWebpack(filters.byProps(["key", "removeBuildOverride"])); +const KbdStyles = lazyWebpack(filters.byProps("key", "removeBuildOverride")); export default definePlugin({ name: "Experiments", diff --git a/src/plugins/ignoreActivities.ts b/src/plugins/ignoreActivities.ts index a4291365..5c1ddcc9 100644 --- a/src/plugins/ignoreActivities.ts +++ b/src/plugins/ignoreActivities.ts @@ -28,7 +28,7 @@ interface MatchAndReplace { } /** Used to re-render the Registered Games tab to update how our button looks like */ -const RunningGameStoreModule = lazyWebpack(filters.byProps(["IgnoreActivities_reRenderGames"])); +const RunningGameStoreModule = lazyWebpack(filters.byProps("IgnoreActivities_reRenderGames")); let ignoredActivitiesCache: string[] = []; diff --git a/src/plugins/index.ts b/src/plugins/index.ts index be6fae34..86d44b4f 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -25,6 +25,7 @@ import { Patch, Plugin } from "../utils/types"; const logger = new Logger("PluginManager", "#a6d189"); +export const PMLogger = logger; export const plugins = Plugins; export const patches = [] as Patch[]; diff --git a/src/plugins/interactionKeybinds.ts b/src/plugins/interactionKeybinds.ts index 1d28847f..76a25bb5 100644 --- a/src/plugins/interactionKeybinds.ts +++ b/src/plugins/interactionKeybinds.ts @@ -24,7 +24,7 @@ import definePlugin from "../utils/types"; import { filters } from "../webpack"; import { ChannelStore, FluxDispatcher as Dispatcher, SelectedChannelStore, UserStore } from "../webpack/common"; -const MessageStore = lazyWebpack(filters.byProps(["getRawMessages"])); +const MessageStore = lazyWebpack(filters.byProps("getRawMessages")); const isMac = navigator.platform.includes("Mac"); // bruh let replyIdx = -1; diff --git a/src/plugins/petpet.ts b/src/plugins/petpet.ts index 8f2462df..093acbab 100644 --- a/src/plugins/petpet.ts +++ b/src/plugins/petpet.ts @@ -16,9 +16,9 @@ * along with this program. If not, see . */ -import { ApplicationCommandInputType, ApplicationCommandOptionType, Argument, CommandContext,findOption } from "../api/Commands"; +import { ApplicationCommandInputType, ApplicationCommandOptionType, Argument, CommandContext, findOption } from "../api/Commands"; import { Devs } from "../utils/constants"; -import { lazyWebpack,makeLazy } from "../utils/misc"; +import { lazyWebpack, makeLazy } from "../utils/misc"; import definePlugin from "../utils/types"; import { filters } from "../webpack"; @@ -41,7 +41,7 @@ const getFrames = makeLazy(() => Promise.all( const fetchUser = lazyWebpack(filters.byCode(".USER(")); const promptToUpload = lazyWebpack(filters.byCode("UPLOAD_FILE_LIMIT_ERROR")); -const UploadStore = lazyWebpack(filters.byProps(["getUploads"])); +const UploadStore = lazyWebpack(filters.byProps("getUploads")); function loadImage(source: File | string) { const isFile = source instanceof File; diff --git a/src/plugins/pronoundb/components/PronounsChatComponent.tsx b/src/plugins/pronoundb/components/PronounsChatComponent.tsx index ec4de98a..2d204615 100644 --- a/src/plugins/pronoundb/components/PronounsChatComponent.tsx +++ b/src/plugins/pronoundb/components/PronounsChatComponent.tsx @@ -25,7 +25,7 @@ import { UserStore } from "../../../webpack/common"; import { PronounMapping } from "../types"; import { fetchPronouns, formatPronouns } from "../utils"; -const styles: Record = lazyWebpack(filters.byProps(["timestampInline"])); +const styles: Record = lazyWebpack(filters.byProps("timestampInline")); export default function PronounsChatComponent({ message }: { message: Message; }) { // Don't bother fetching bot or system users diff --git a/src/plugins/reverseImageSearch.tsx b/src/plugins/reverseImageSearch.tsx index 9f618de9..da7d8441 100644 --- a/src/plugins/reverseImageSearch.tsx +++ b/src/plugins/reverseImageSearch.tsx @@ -29,7 +29,7 @@ const Engines = { TinEye: "https://www.tineye.com/search?url=" }; -const Menu = lazyWebpack(filters.byProps(["MenuItem"])); +const Menu = lazyWebpack(filters.byProps("MenuItem")); export default definePlugin({ diff --git a/src/plugins/sendify.ts b/src/plugins/sendify.ts index 436a9287..40836ad8 100644 --- a/src/plugins/sendify.ts +++ b/src/plugins/sendify.ts @@ -53,9 +53,9 @@ interface Track { name: string; } -const Spotify = lazyWebpack(filters.byProps(["getPlayerState"])); -const MessageCreator = lazyWebpack(filters.byProps(["getSendMessageOptionsForReply", "sendMessage"])); -const PendingReplyStore = lazyWebpack(filters.byProps(["getPendingReply"])); +const Spotify = lazyWebpack(filters.byProps("getPlayerState")); +const MessageCreator = lazyWebpack(filters.byProps("getSendMessageOptionsForReply", "sendMessage")); +const PendingReplyStore = lazyWebpack(filters.byProps("getPendingReply")); function sendMessage(channelId, message) { message = { diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 24cd2c8f..88ebb43b 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -29,7 +29,7 @@ export default class Logger { return ["%c %c %s ", "", `background: ${color}; color: black; font-weight: bold; border-radius: 5px;`, title]; } - constructor(public name: string, public color: string) { } + constructor(public name: string, public color: string = "white") { } private _log(level: "log" | "error" | "warn" | "info" | "debug", levelColor: string, args: any[], customFmt = "") { console[level]( diff --git a/src/webpack/common.tsx b/src/webpack/common.tsx index f5b2401a..4c591029 100644 --- a/src/webpack/common.tsx +++ b/src/webpack/common.tsx @@ -23,7 +23,7 @@ import type Stores from "discord-types/stores"; import { lazyWebpack } from "../utils/misc"; import { _resolveReady, filters, mapMangledModuleLazy, waitFor } from "./webpack"; -export const Margins = lazyWebpack(filters.byProps(["marginTop20"])); +export const Margins = lazyWebpack(filters.byProps("marginTop20")); export let FluxDispatcher: Other.FluxDispatcher; export let React: typeof import("react"); diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts index 55967306..b3cfd703 100644 --- a/src/webpack/patchWebpack.ts +++ b/src/webpack/patchWebpack.ts @@ -102,7 +102,7 @@ function patchPush() { callback(exports.default); } - for (const nested in exports) if (nested.length < 3) { + for (const nested in exports) if (nested.length <= 3) { if (exports[nested] && filter(exports[nested])) { subscriptions.delete(filter); callback(exports[nested]); diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index 7604266f..3db4dd2d 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -18,6 +18,7 @@ import type { WebpackInstance } from "discord-types/other"; +import Logger from "../utils/logger"; import { proxyLazy } from "../utils/proxyLazy"; export let _resolveReady: () => void; @@ -33,11 +34,13 @@ export let cache: WebpackInstance["c"]; export type FilterFn = (mod: any) => boolean; export const filters = { - byProps: (props: string[]): FilterFn => + byProps: (...props: string[]): FilterFn => props.length === 1 ? m => m[props[0]] !== void 0 : m => props.every(p => m[p] !== void 0), + byDisplayName: (deezNuts: string): FilterFn => m => m.default?.displayName === deezNuts, + byCode: (...code: string[]): FilterFn => m => { if (typeof m !== "function") return false; const s = Function.prototype.toString.call(m); @@ -48,6 +51,7 @@ export const filters = { }, }; +const logger = new Logger("Webpack"); export const subscriptions = new Map(); export const listeners = new Set(); @@ -56,12 +60,12 @@ export type CallbackFn = (mod: any) => void; export function _initWebpack(instance: typeof window.webpackChunkdiscord_app) { if (cache !== void 0) throw "no."; - wreq = instance.push([[Symbol()], {}, r => r]); + wreq = instance.push([[Symbol("Vencord")], {}, r => r]); cache = wreq.c; instance.pop(); } -export function find(filter: FilterFn, getDefault = true) { +export function find(filter: FilterFn, getDefault = true, isWaitFor = false) { if (typeof filter !== "function") throw new Error("Invalid filter. Expected a function got " + typeof filter); @@ -77,7 +81,6 @@ export function find(filter: FilterFn, getDefault = true) { if (mod.exports.default && filter(mod.exports.default)) return getDefault ? mod.exports.default : mod.exports; - // is 3 is the longest obfuscated export? // the length check makes search about 20% faster for (const nestedMod in mod.exports) if (nestedMod.length <= 3) { const nested = mod.exports[nestedMod]; @@ -85,11 +88,21 @@ export function find(filter: FilterFn, getDefault = true) { } } + if (!isWaitFor) { + const err = new Error("Didn't find module matching this filter"); + if (IS_DEV) { + // Strict behaviour in DevBuilds to fail early and make sure the issue is found + throw err; + } + logger.warn(err); + } + return null; } export function findAll(filter: FilterFn, getDefault = true) { - if (typeof filter !== "function") throw new Error("Invalid filter. Expected a function got " + typeof filter); + if (typeof filter !== "function") + throw new Error("Invalid filter. Expected a function got " + typeof filter); const ret = [] as any[]; for (const key in cache) { @@ -113,17 +126,17 @@ export function findAll(filter: FilterFn, getDefault = true) { } /** - * Finds a mangled module by the provided code "code" (must be unique and can be anywhere in the module) - * then maps it into an easily usable module via the specified mappers - * @param code Code snippet - * @param mappers Mappers to create the non mangled exports - * @returns Unmangled exports as specified in mappers - * - * @example mapMangledModule("headerIdIsManaged:", { - * openModal: filters.byCode("headerIdIsManaged:"), - * closeModal: filters.byCode("key==") - * }) - */ + * Finds a mangled module by the provided code "code" (must be unique and can be anywhere in the module) + * then maps it into an easily usable module via the specified mappers + * @param code Code snippet + * @param mappers Mappers to create the non mangled exports + * @returns Unmangled exports as specified in mappers + * + * @example mapMangledModule("headerIdIsManaged:", { + * openModal: filters.byCode("headerIdIsManaged:"), + * closeModal: filters.byCode("key==") + * }) + */ export function mapMangledModule(code: string, mappers: Record): Record { const exports = {} as Record; @@ -143,26 +156,31 @@ export function mapMangledModule(code: string, mappers: Record } } } - break; + return exports; } } + const err = new Error("Didn't find module matching this code:\n" + code); + if (IS_DEV) + throw err; + + logger.warn(err); return exports; } /** - * Same as {@link mapMangledModule} but lazy - */ + * Same as {@link mapMangledModule} but lazy + */ export function mapMangledModuleLazy(code: string, mappers: Record): Record { return proxyLazy(() => mapMangledModule(code, mappers)); } export function findByProps(...props: string[]) { - return find(filters.byProps(props)); + return find(filters.byProps(...props)); } export function findAllByProps(...props: string[]) { - return findAll(filters.byProps(props)); + return findAll(filters.byProps(...props)); } export function findByDisplayName(deezNuts: string) { @@ -170,11 +188,14 @@ export function findByDisplayName(deezNuts: string) { } export function waitFor(filter: string | string[] | FilterFn, callback: CallbackFn) { - if (typeof filter === "string") filter = filters.byProps([filter]); - else if (Array.isArray(filter)) filter = filters.byProps(filter); - else if (typeof filter !== "function") throw new Error("filter must be a string, string[] or function, got " + typeof filter); + if (typeof filter === "string") + filter = filters.byProps(filter); + else if (Array.isArray(filter)) + filter = filters.byProps(...filter); + else if (typeof filter !== "function") + throw new Error("filter must be a string, string[] or function, got " + typeof filter); - const existing = find(filter!); + const existing = find(filter!, true, true); if (existing) return void callback(existing); subscriptions.set(filter, callback); @@ -189,11 +210,11 @@ export function removeListener(callback: CallbackFn) { } /** - * Search modules by keyword. This searches the factory methods, - * meaning you can search all sorts of things, displayName, methodName, strings somewhere in the code, etc - * @param filters One or more strings or regexes - * @returns Mapping of found modules - */ + * Search modules by keyword. This searches the factory methods, + * meaning you can search all sorts of things, displayName, methodName, strings somewhere in the code, etc + * @param filters One or more strings or regexes + * @returns Mapping of found modules + */ export function search(...filters: Array) { const results = {} as Record; const factories = wreq.m; @@ -212,13 +233,13 @@ export function search(...filters: Array) { } /** - * Extract a specific module by id into its own Source File. This has no effect on - * the code, it is only useful to be able to look at a specific module without having - * to view a massive file. extract then returns the extracted module so you can jump to it. - * As mentioned above, note that this extracted module is not actually used, - * so putting breakpoints or similar will have no effect. - * @param id The id of the module to extract - */ + * Extract a specific module by id into its own Source File. This has no effect on + * the code, it is only useful to be able to look at a specific module without having + * to view a massive file. extract then returns the extracted module so you can jump to it. + * As mentioned above, note that this extracted module is not actually used, + * so putting breakpoints or similar will have no effect. + * @param id The id of the module to extract + */ export function extract(id: number) { const mod = wreq.m[id] as Function; if (!mod) return null;