Fix everything broken by recent Discord update (#3177)

Co-authored-by: sadan <117494111+sadan4@users.noreply.github.com>
Co-authored-by: Vendicated <vendicated@riseup.net>
This commit is contained in:
Nuckyz 2025-01-29 01:04:36 -03:00 committed by GitHub
parent cdc756193e
commit 33d4f13a24
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 389 additions and 244 deletions

View file

@ -9,7 +9,7 @@ import "./ChatButton.css";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { waitFor } from "@webpack"; import { waitFor } from "@webpack";
import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common"; import { Button, ButtonWrapperClasses, Tooltip } from "@webpack/common";
import { Channel } from "discord-types/general"; import { Channel } from "discord-types/general";
import { HTMLProps, JSX, MouseEventHandler, ReactNode } from "react"; import { HTMLProps, JSX, MouseEventHandler, ReactNode } from "react";
@ -110,7 +110,7 @@ export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => {
<Button <Button
aria-label={props.tooltip} aria-label={props.tooltip}
size="" size=""
look={ButtonLooks.BLANK} look={Button.Looks.BLANK}
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
innerClassName={`${ButtonWrapperClasses.button} ${ChannelTextAreaClasses?.button}`} innerClassName={`${ButtonWrapperClasses.button} ${ChannelTextAreaClasses?.button}`}

View file

@ -122,7 +122,7 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra
} }
interface ContextMenuProps { interface ContextMenuProps {
contextMenuApiArguments?: Array<any>; contextMenuAPIArguments?: Array<any>;
navId: string; navId: string;
children: Array<ReactElement<any> | null>; children: Array<ReactElement<any> | null>;
"aria-label": string; "aria-label": string;
@ -136,7 +136,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
children: cloneMenuChildren(props.children), children: cloneMenuChildren(props.children),
}; };
props.contextMenuApiArguments ??= []; props.contextMenuAPIArguments ??= [];
const contextMenuPatches = navPatches.get(props.navId); const contextMenuPatches = navPatches.get(props.navId);
if (!Array.isArray(props.children)) props.children = [props.children]; if (!Array.isArray(props.children)) props.children = [props.children];
@ -144,7 +144,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
if (contextMenuPatches) { if (contextMenuPatches) {
for (const patch of contextMenuPatches) { for (const patch of contextMenuPatches) {
try { try {
patch(props.children, ...props.contextMenuApiArguments); patch(props.children, ...props.contextMenuAPIArguments);
} catch (err) { } catch (err) {
ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err); ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err);
} }
@ -153,7 +153,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
for (const patch of globalPatches) { for (const patch of globalPatches) {
try { try {
patch(props.navId, props.children, ...props.contextMenuApiArguments); patch(props.navId, props.children, ...props.contextMenuAPIArguments);
} catch (err) { } catch (err) {
ContextMenuLogger.error("Global patch errored,", err); ContextMenuLogger.error("Global patch errored,", err);
} }

View file

@ -221,15 +221,13 @@ export function migratePluginSettings(name: string, ...oldNames: string[]) {
} }
export function migratePluginSetting(pluginName: string, oldSetting: string, newSetting: string) { export function migratePluginSetting(pluginName: string, oldSetting: string, newSetting: string) {
const { plugins } = SettingsStore.plain; const settings = SettingsStore.plain.plugins[pluginName];
if (!settings) return;
if ( if (!Object.hasOwn(settings, oldSetting) || Object.hasOwn(settings, newSetting)) return;
plugins?.[pluginName]?.[oldSetting] == null ||
plugins[pluginName][newSetting] != null
) return;
plugins[pluginName][newSetting] = plugins[pluginName][oldSetting]; settings[newSetting] = settings[oldSetting];
delete plugins[pluginName][oldSetting]; delete settings[oldSetting];
SettingsStore.markAsChanged(); SettingsStore.markAsChanged();
} }

View file

@ -62,14 +62,21 @@ async function runReporter() {
if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw new Error("Webpack Find Fail"); if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw new Error("Webpack Find Fail");
} catch (e) { } catch (e) {
let logMessage = searchType; let logMessage = searchType;
if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") logMessage += `(${args[0].toString().slice(0, 147)}...)`; if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") {
else if (method === "extractAndLoadChunks") logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`; if (args[0].$$vencordProps != null) {
else if (method === "mapMangledModule") { logMessage += `(${args[0].$$vencordProps.map(arg => `"${arg}"`).join(", ")})`;
} else {
logMessage += `(${args[0].toString().slice(0, 147)}...)`;
}
} else if (method === "extractAndLoadChunks") {
logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`;
} else if (method === "mapMangledModule") {
const failedMappings = Object.keys(args[1]).filter(key => result?.[key] == null); const failedMappings = Object.keys(args[1]).filter(key => result?.[key] == null);
logMessage += `("${args[0]}", {\n${failedMappings.map(mapping => `\t${mapping}: ${args[1][mapping].toString().slice(0, 147)}...`).join(",\n")}\n})`; logMessage += `("${args[0]}", {\n${failedMappings.map(mapping => `\t${mapping}: ${args[1][mapping].toString().slice(0, 147)}...`).join(",\n")}\n})`;
} else {
logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`;
} }
else logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`;
ReporterLogger.log("Webpack Find Fail:", logMessage); ReporterLogger.log("Webpack Find Fail:", logMessage);
} }

View file

@ -28,7 +28,7 @@ import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { isPluginDev } from "@utils/misc"; import { isPluginDev } from "@utils/misc";
import { closeModal, Modals, openModal } from "@utils/modal"; import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { Forms, Toasts, UserStore } from "@webpack/common"; import { Forms, Toasts, UserStore } from "@webpack/common";
import { User } from "discord-types/general"; import { User } from "discord-types/general";
@ -144,8 +144,8 @@ export default definePlugin({
closeModal(modalKey); closeModal(modalKey);
VencordNative.native.openExternal("https://github.com/sponsors/Vendicated"); VencordNative.native.openExternal("https://github.com/sponsors/Vendicated");
}}> }}>
<Modals.ModalRoot {...props}> <ModalRoot {...props}>
<Modals.ModalHeader> <ModalHeader>
<Flex style={{ width: "100%", justifyContent: "center" }}> <Flex style={{ width: "100%", justifyContent: "center" }}>
<Forms.FormTitle <Forms.FormTitle
tag="h2" tag="h2"
@ -159,8 +159,8 @@ export default definePlugin({
Vencord Donor Vencord Donor
</Forms.FormTitle> </Forms.FormTitle>
</Flex> </Flex>
</Modals.ModalHeader> </ModalHeader>
<Modals.ModalContent> <ModalContent>
<Flex> <Flex>
<img <img
role="presentation" role="presentation"
@ -183,13 +183,13 @@ export default definePlugin({
Please consider supporting the development of Vencord by becoming a donor. It would mean a lot!! Please consider supporting the development of Vencord by becoming a donor. It would mean a lot!!
</Forms.FormText> </Forms.FormText>
</div> </div>
</Modals.ModalContent> </ModalContent>
<Modals.ModalFooter> <ModalFooter>
<Flex style={{ width: "100%", justifyContent: "center" }}> <Flex style={{ width: "100%", justifyContent: "center" }}>
<DonateButton /> <DonateButton />
</Flex> </Flex>
</Modals.ModalFooter> </ModalFooter>
</Modals.ModalRoot> </ModalRoot>
</ErrorBoundary> </ErrorBoundary>
)); ));
}, },

View file

@ -12,11 +12,15 @@ export default definePlugin({
description: "API to add buttons to the chat input", description: "API to add buttons to the chat input",
authors: [Devs.Ven], authors: [Devs.Ven],
patches: [{ patches: [
{
find: '"sticker")', find: '"sticker")',
replacement: { replacement: {
match: /return\(!\i\.\i&&(?=\(\i\.isDM.+?(\i)\.push\(.{0,50}"gift")/, match: /return\((!)?\i\.\i(?:\|\||&&)(?=\(\i\.isDM.+?(\i)\.push)/,
replace: "$&(Vencord.Api.ChatButtons._injectButtons($1,arguments[0]),true)&&" replace: (m, not, children) => not
? `${m}(Vencord.Api.ChatButtons._injectButtons(${children},arguments[0]),true)&&`
: `${m}(Vencord.Api.ChatButtons._injectButtons(${children},arguments[0]),false)||`
} }
}] }
]
}); });

View file

@ -34,12 +34,22 @@ export default definePlugin({
} }
}, },
{ {
find: ".Menu,{", find: "navId:",
all: true, all: true,
replacement: { noWarn: true,
match: /Menu,{(?<=\.jsxs?\)\(\i\.Menu,{)/g, replacement: [
replace: "$&contextMenuApiArguments:typeof arguments!=='undefined'?arguments:[]," {
match: /navId:(?=.+?([,}].*?\)))/g,
replace: (m, rest) => {
// Check if this navId: match is a destructuring statement, ignore it if it is
const destructuringMatch = rest.match(/}=.+/);
if (destructuringMatch == null) {
return `contextMenuAPIArguments:typeof arguments!=='undefined'?arguments:[],${m}`;
}
return m;
} }
} }
] ]
}
]
}); });

View file

@ -0,0 +1,68 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Devs } from "@utils/constants";
import { canonicalizeMatch } from "@utils/patches";
import definePlugin from "@utils/types";
// duplicate values have multiple branches with different types. Just include all to be safe
const nameMap = {
radio: "MenuRadioItem",
separator: "MenuSeparator",
checkbox: "MenuCheckboxItem",
groupstart: "MenuGroup",
control: "MenuControlItem",
compositecontrol: "MenuControlItem",
item: "MenuItem",
customitem: "MenuItem",
};
export default definePlugin({
name: "MenuItemDemanglerAPI",
description: "Demangles Discord's Menu Item module",
authors: [Devs.Ven],
required: true,
patches: [
{
find: '"Menu API',
replacement: {
match: /function.{0,80}type===(\i\.\i)\).{0,50}navigable:.+?Menu API/s,
replace: (m, mod) => {
const nameAssignments = [] as string[];
// if (t.type === m.MenuItem)
const typeCheckRe = canonicalizeMatch(/\(\i\.type===(\i\.\i)\)/g);
// push({type:"item"})
const pushTypeRe = /type:"(\w+)"/g;
let typeMatch: RegExpExecArray | null;
// for each if (t.type === ...)
while ((typeMatch = typeCheckRe.exec(m)) !== null) {
// extract the current menu item
const item = typeMatch[1];
// Set the starting index of the second regex to that of the first to start
// matching from after the if
pushTypeRe.lastIndex = typeCheckRe.lastIndex;
// extract the first type: "..."
const type = pushTypeRe.exec(m)?.[1];
if (type && type in nameMap) {
const name = nameMap[type];
nameAssignments.push(`Object.defineProperty(${item},"name",{value:"${name}"})`);
}
}
if (nameAssignments.length < 6) {
console.warn("[MenuItemDemanglerAPI] Expected to at least remap 6 items, only remapped", nameAssignments.length);
}
// Merge all our redefines with the actual module
return `${nameAssignments.join(";")};${m}`;
},
},
},
],
});

View file

@ -65,7 +65,7 @@ export default definePlugin({
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}` replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
}, },
{ {
match: /({(?=.+?function (\i).{0,160}(\i)=\i\.useMemo.{0,140}return \i\.useMemo\(\(\)=>\i\(\3).+?function\(\){return )\2(?=})/, match: /({(?=.+?function (\i).{0,160}(\i)=\i\.useMemo.{0,140}return \i\.useMemo\(\(\)=>\i\(\3).+?(?:function\(\){return |\(\)=>))\2/,
replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})` replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})`
} }
] ]

View file

@ -173,8 +173,8 @@ export default definePlugin({
// Disable expanding and collapsing folders transition in the normal GuildsBar sidebar // Disable expanding and collapsing folders transition in the normal GuildsBar sidebar
{ {
predicate: () => !settings.store.keepIcons, predicate: () => !settings.store.keepIcons,
match: /(?<=#{intl::SERVER_FOLDER_PLACEHOLDER}.+?useTransition\)\()/, match: /(?=,\{from:\{height)/,
replace: "$self.shouldShowTransition(arguments[0])&&" replace: "&&$self.shouldShowTransition(arguments[0])"
}, },
// If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded // If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded
{ {

View file

@ -21,7 +21,7 @@ import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findExportedComponentLazy, findStoreLazy } from "@webpack"; import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
import { Constants, React, RestAPI, Tooltip } from "@webpack/common"; import { Constants, React, RestAPI, Tooltip } from "@webpack/common";
import { RenameButton } from "./components/RenameButton"; import { RenameButton } from "./components/RenameButton";
@ -34,7 +34,7 @@ const UserSettingsModal = findByPropsLazy("saveAccountChanges", "open");
const TimestampClasses = findByPropsLazy("timestampTooltip", "blockquoteContainer"); const TimestampClasses = findByPropsLazy("timestampTooltip", "blockquoteContainer");
const SessionIconClasses = findByPropsLazy("sessionIcon"); const SessionIconClasses = findByPropsLazy("sessionIcon");
const BlobMask = findExportedComponentLazy("BlobMask"); const BlobMask = findComponentByCodeLazy("!1,lowerBadgeSize:");
const settings = definePluginSettings({ const settings = definePluginSettings({
backgroundCheck: { backgroundCheck: {

View file

@ -101,8 +101,8 @@ export default definePlugin({
find: 'minimal:"contentColumnMinimal"', find: 'minimal:"contentColumnMinimal"',
replacement: [ replacement: [
{ {
match: /\(0,\i\.useTransition\)\((\i)/, match: /(?=\(0,\i\.\i\)\((\i),\{from:\{position:"absolute")/,
replace: "(_cb=>_cb(void 0,$1))||$&" replace: "(_cb=>_cb(void 0,$1))||"
}, },
{ {
match: /\i\.animated\.div/, match: /\i\.animated\.div/,

View file

@ -69,8 +69,8 @@ export default definePlugin({
{ {
find: "https://github.com/highlightjs/highlight.js/issues/2277", find: "https://github.com/highlightjs/highlight.js/issues/2277",
replacement: { replacement: {
match: /(?<=&&\()console.log\(`Deprecated.+?`\),/, match: /\(console.log\(`Deprecated.+?`\),/,
replace: "" replace: "("
} }
}, },
{ {

View file

@ -63,7 +63,7 @@ function makeShortcuts() {
default: default:
const uniqueMatches = [...new Set(matches)]; const uniqueMatches = [...new Set(matches)];
if (uniqueMatches.length > 1) if (uniqueMatches.length > 1)
console.warn(`Warning: This filter matches ${matches.length} modules. Make it more specific!\n`, uniqueMatches); console.warn(`Warning: This filter matches ${uniqueMatches.length} exports. Make it more specific!\n`, uniqueMatches);
return matches[0]; return matches[0];
} }
@ -165,11 +165,38 @@ function loadAndCacheShortcut(key: string, val: any, forceLoad: boolean) {
const currentVal = val.getter(); const currentVal = val.getter();
if (!currentVal || val.preload === false) return currentVal; if (!currentVal || val.preload === false) return currentVal;
const value = currentVal[SYM_LAZY_GET] function unwrapProxy(value: any) {
? forceLoad ? currentVal[SYM_LAZY_GET]() : currentVal[SYM_LAZY_CACHED] if (value[SYM_LAZY_GET]) {
: currentVal; forceLoad ? currentVal[SYM_LAZY_GET]() : currentVal[SYM_LAZY_CACHED];
} else if (value.$$vencordInternal) {
return forceLoad ? value.$$vencordInternal() : value;
}
if (value) define(window.shortcutList, key, { value }); return value;
}
const value = unwrapProxy(currentVal);
if (typeof value === "object" && value !== null) {
const descriptors = Object.getOwnPropertyDescriptors(value);
for (const propKey in descriptors) {
if (value[propKey] == null) continue;
const descriptor = descriptors[propKey];
if (descriptor.writable === true || descriptor.set != null) {
const currentValue = value[propKey];
const newValue = unwrapProxy(currentValue);
if (newValue != null && currentValue !== newValue) {
value[propKey] = newValue;
}
}
}
}
if (value != null) {
define(window.shortcutList, key, { value });
define(window, key, { value });
}
return value; return value;
} }

View file

@ -42,10 +42,10 @@ export default definePlugin({
// Only one of the two patches will be at effect; Discord often updates to switch between them. // Only one of the two patches will be at effect; Discord often updates to switch between them.
// See: https://discord.com/channels/1015060230222131221/1032770730703716362/1261398512017477673 // See: https://discord.com/channels/1015060230222131221/1032770730703716362/1261398512017477673
{ {
find: ".ENTER&&(!", find: ".selectPreviousCommandOption(",
replacement: { replacement: {
match: /(?<=(\i)\.which===\i\.\i.ENTER&&).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=&&\(\i\.preventDefault)/, match: /(?<=(\i)\.which(?:!==|===)\i\.\i.ENTER(\|\||&&)).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=(?:\|\||&&)\(\i\.preventDefault)/,
replace: "$self.shouldSubmit($1, $2)" replace: (_, event, condition, codeblock) => `${condition === "||" ? "!" : ""}$self.shouldSubmit(${event},${codeblock})`
} }
}, },
{ {

View file

@ -235,7 +235,7 @@ export default definePlugin({
} }
}, },
{ {
find: ".PREMIUM_LOCKED;", find: ".GUILD_SUBSCRIPTION_UNAVAILABLE;",
group: true, group: true,
predicate: () => settings.store.enableEmojiBypass, predicate: () => settings.store.enableEmojiBypass,
replacement: [ replacement: [
@ -256,8 +256,10 @@ export default definePlugin({
}, },
{ {
// Disallow the emoji for premium locked if the intention doesn't allow it // Disallow the emoji for premium locked if the intention doesn't allow it
match: /!\i\.\i\.canUseEmojisEverywhere\(\i\)/, match: /(!)?(\i\.\i\.canUseEmojisEverywhere\(\i\))/,
replace: m => `(${m}&&!${IS_BYPASSEABLE_INTENTION})` replace: (m, not) => not
? `(${m}&&!${IS_BYPASSEABLE_INTENTION})`
: `(${m}||${IS_BYPASSEABLE_INTENTION})`
}, },
{ {
// Allow animated emojis to be used if the intention allows it // Allow animated emojis to be used if the intention allows it

View file

@ -22,11 +22,11 @@ import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord"; import { getIntlMessage } from "@utils/discord";
import { NoopComponent } from "@utils/react"; import { NoopComponent } from "@utils/react";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { filters, findByPropsLazy, waitFor } from "@webpack"; import { filters, findByCodeLazy, waitFor } from "@webpack";
import { ChannelStore, ContextMenuApi, UserStore } from "@webpack/common"; import { ChannelStore, ContextMenuApi, UserStore } from "@webpack/common";
import { Message } from "discord-types/general"; import { Message } from "discord-types/general";
const { useMessageMenu } = findByPropsLazy("useMessageMenu"); const useMessageMenu = findByCodeLazy(".MESSAGE,commandTargetId:");
interface CopyIdMenuItemProps { interface CopyIdMenuItemProps {
id: string; id: string;

View file

@ -26,7 +26,7 @@ import { findComponentByCodeLazy } from "@webpack";
import style from "./style.css?managed"; import style from "./style.css?managed";
const Button = findComponentByCodeLazy("Button.Sizes.NONE,disabled:"); const Button = findComponentByCodeLazy(".NONE,disabled:", ".PANEL_BUTTON");
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!; const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;

View file

@ -27,7 +27,7 @@ export default definePlugin({
{ {
find: "hasFlag:{writable", find: "hasFlag:{writable",
replacement: { replacement: {
match: /if\((\i)<=(?:1<<30|1073741824)\)return/, match: /if\((\i)<=(?:0x40000000|(?:1<<30|1073741824))\)return/,
replace: "if($1===(1<<20))return false;$&", replace: "if($1===(1<<20))return false;$&",
}, },
}, },

View file

@ -243,7 +243,7 @@ export default definePlugin({
find: '"LocalActivityStore"', find: '"LocalActivityStore"',
replacement: [ replacement: [
{ {
match: /HANG_STATUS.+?(?=!\i\(\)\(\i,\i\)&&)(?<=(\i)\.push.+?)/, match: /HANG_STATUS.+?(?=!?\i\(\)\(\i,\i\))(?<=(\i)\.push.+?)/,
replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);` replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);`
} }
] ]

View file

@ -50,7 +50,7 @@ export default definePlugin({
{ {
find: "#{intl::FRIENDS_SECTION_ONLINE}", find: "#{intl::FRIENDS_SECTION_ONLINE}",
replacement: { replacement: {
match: /(\(0,\i\.jsx\)\(\i\.TabBar\.Item,\{id:\i\.\i)\.BLOCKED,className:([^\s]+?)\.item,children:\i\.\i\.string\(\i\.\i#{intl::BLOCKED}\)\}\)/, match: /(\(0,\i\.jsx\)\(\i\.\i\.Item,\{id:\i\.\i)\.BLOCKED,className:([^\s]+?)\.item,children:\i\.\i\.string\(\i\.\i#{intl::BLOCKED}\)\}\)/,
replace: "$1.IMPLICIT,className:$2.item,children:\"Implicit\"}),$&" replace: "$1.IMPLICIT,className:$2.item,children:\"Implicit\"}),$&"
}, },
}, },

View file

@ -57,7 +57,7 @@ export default definePlugin({
{ {
find: ".ROLE_MENTION)", find: ".ROLE_MENTION)",
replacement: { replacement: {
match: /children:\[\i&&.{0,50}\.RoleDot.{0,300},\i(?=\])/, match: /children:\[\i&&.{0,100}className:\i.roleDot,.{0,200},\i(?=\])/,
replace: "$&,$self.renderRoleIcon(arguments[0])" replace: "$&,$self.renderRoleIcon(arguments[0])"
} }
}], }],

View file

@ -9,7 +9,7 @@ import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { isNonNullish } from "@utils/guards"; import { isNonNullish } from "@utils/guards";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findExportedComponentLazy } from "@webpack"; import { findComponentByCodeLazy } from "@webpack";
import { SnowflakeUtils, Tooltip } from "@webpack/common"; import { SnowflakeUtils, Tooltip } from "@webpack/common";
import { Message } from "discord-types/general"; import { Message } from "discord-types/general";
@ -26,7 +26,7 @@ interface Diff {
} }
const DISCORD_KT_DELAY = 1471228928; const DISCORD_KT_DELAY = 1471228928;
const HiddenVisually = findExportedComponentLazy("HiddenVisually"); const HiddenVisually = findComponentByCodeLazy(".hiddenVisually]:");
export default definePlugin({ export default definePlugin({
name: "MessageLatency", name: "MessageLatency",

View file

@ -501,7 +501,7 @@ export default definePlugin({
{ {
// Message context base menu // Message context base menu
find: "useMessageMenu:", find: ".MESSAGE,commandTargetId:",
replacement: [ replacement: [
{ {
// Remove the first section if message is deleted // Remove the first section if message is deleted

View file

@ -100,8 +100,8 @@ export default definePlugin({
replace: "true" replace: "true"
}, },
{ {
match: /!\(0,\i\.isDesktop\)\(\)/, match: /(!)?\(0,\i\.isDesktop\)\(\)/,
replace: "false" replace: (_, not) => not ? "false" : "true"
} }
] ]
}, },

View file

@ -46,8 +46,8 @@ export default definePlugin({
find: "#{intl::ONBOARDING_CHANNEL_THRESHOLD_WARNING}", find: "#{intl::ONBOARDING_CHANNEL_THRESHOLD_WARNING}",
replacement: [ replacement: [
{ {
match: /{(\i:function\(\){return \i},?){2}}/, match: /{(?:\i:(?:function\(\){return |\(\)=>)\i}?,?){2}}/,
replace: m => m.replaceAll(canonicalizeMatch(/return \i/g), "return ()=>Promise.resolve(true)") replace: m => m.replaceAll(canonicalizeMatch(/(function\(\){return |\(\)=>)\i/g), "$1()=>Promise.resolve(true)")
} }
], ],
predicate: () => settings.store.onboarding predicate: () => settings.store.onboarding

View file

@ -6,7 +6,7 @@
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModalLazy } from "@utils/modal"; import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModalLazy } from "@utils/modal";
import { extractAndLoadChunksLazy, findComponentByCodeLazy, findExportedComponentLazy } from "@webpack"; import { extractAndLoadChunksLazy, findComponentByCodeLazy } from "@webpack";
import { Button, Forms, Text, TextInput, Toasts, useMemo, useState } from "@webpack/common"; import { Button, Forms, Text, TextInput, Toasts, useMemo, useState } from "@webpack/common";
import { DEFAULT_COLOR, SWATCHES } from "../constants"; import { DEFAULT_COLOR, SWATCHES } from "../constants";
@ -30,7 +30,7 @@ interface ColorPickerWithSwatchesProps {
} }
const ColorPicker = findComponentByCodeLazy<ColorPickerProps>("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)"); const ColorPicker = findComponentByCodeLazy<ColorPickerProps>("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)");
const ColorPickerWithSwatches = findExportedComponentLazy<ColorPickerWithSwatchesProps>("ColorPicker", "CustomColorPicker"); const ColorPickerWithSwatches = findComponentByCodeLazy<ColorPickerWithSwatchesProps>('id:"color-picker"');
export const requireSettingsMenu = extractAndLoadChunksLazy(['name:"UserSettings"'], /createPromise:.{0,20}(\i\.\i\("?.+?"?\).*?).then\(\i\.bind\(\i,"?(.+?)"?\)\).{0,50}"UserSettings"/); export const requireSettingsMenu = extractAndLoadChunksLazy(['name:"UserSettings"'], /createPromise:.{0,20}(\i\.\i\("?.+?"?\).*?).then\(\i\.bind\(\i,"?(.+?)"?\)\).{0,50}"UserSettings"/);

View file

@ -25,7 +25,7 @@ import { Settings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findStoreLazy } from "@webpack"; import { filters, findStoreLazy, mapMangledModuleLazy } from "@webpack";
import { PresenceStore, Tooltip, UserStore } from "@webpack/common"; import { PresenceStore, Tooltip, UserStore } from "@webpack/common";
import { User } from "discord-types/general"; import { User } from "discord-types/general";
@ -70,7 +70,9 @@ const Icons = {
}; };
type Platform = keyof typeof Icons; type Platform = keyof typeof Icons;
const StatusUtils = findByPropsLazy("useStatusFillColor", "StatusTypes"); const { useStatusFillColor } = mapMangledModuleLazy(".concat(.5625*", {
useStatusFillColor: filters.byCode(".hex")
});
const PlatformIcon = ({ platform, status, small }: { platform: Platform, status: string; small: boolean; }) => { const PlatformIcon = ({ platform, status, small }: { platform: Platform, status: string; small: boolean; }) => {
const tooltip = platform === "embedded" const tooltip = platform === "embedded"
@ -79,7 +81,7 @@ const PlatformIcon = ({ platform, status, small }: { platform: Platform, status:
const Icon = Icons[platform] ?? Icons.desktop; const Icon = Icons[platform] ?? Icons.desktop;
return <Icon color={StatusUtils.useStatusFillColor(status)} tooltip={tooltip} small={small} />; return <Icon color={useStatusFillColor(status)} tooltip={tooltip} small={small} />;
}; };
function ensureOwnStatus(user: User) { function ensureOwnStatus(user: User) {

View file

@ -108,8 +108,10 @@ export default definePlugin({
}, },
{ {
// Prevent Discord from trying to connect to hidden voice channels // Prevent Discord from trying to connect to hidden voice channels
match: /(?=&&\i\.\i\.selectVoiceChannel\((\i)\.id\))/, match: /(?=(\|\||&&)\i\.\i\.selectVoiceChannel\((\i)\.id\))/,
replace: (_, channel) => `&&!$self.isHiddenChannel(${channel})` replace: (_, condition, channel) => condition === "||"
? `||$self.isHiddenChannel(${channel})`
: `&&!$self.isHiddenChannel(${channel})`
}, },
{ {
// Make Discord show inside the channel if clicking on a hidden or locked channel // Make Discord show inside the channel if clicking on a hidden or locked channel
@ -122,8 +124,10 @@ export default definePlugin({
{ {
find: ".AUDIENCE),{isSubscriptionGated", find: ".AUDIENCE),{isSubscriptionGated",
replacement: { replacement: {
match: /!(\i)\.isRoleSubscriptionTemplatePreviewChannel\(\)/, match: /(!)?(\i)\.isRoleSubscriptionTemplatePreviewChannel\(\)/,
replace: (m, channel) => `${m}&&!$self.isHiddenChannel(${channel})` replace: (m, not, channel) => not
? `${m}&&!$self.isHiddenChannel(${channel})`
: `${m}||$self.isHiddenChannel(${channel})`
} }
}, },
{ {
@ -173,8 +177,10 @@ export default definePlugin({
}, },
// Make voice channels also appear as muted if they are muted // Make voice channels also appear as muted if they are muted
{ {
match: /(?<=\.wrapper:\i\.notInteractive,)(.+?)if\((\i)\)return (\i\.MUTED);/, match: /(?<=\.wrapper:\i\.notInteractive,)(.+?)(if\()?(\i)(?:\)return |\?)(\i\.MUTED)/,
replace: (_, otherClasses, isMuted, mutedClassExpression) => `${isMuted}?${mutedClassExpression}:"",${otherClasses}if(${isMuted})return "";` replace: (_, otherClasses, isIf, isMuted, mutedClassExpression) => isIf
? `${isMuted}?${mutedClassExpression}:"",${otherClasses}if(${isMuted})return ""`
: `${isMuted}?${mutedClassExpression}:"",${otherClasses}${isMuted}?""`
} }
] ]
}, },
@ -184,8 +190,8 @@ export default definePlugin({
{ {
// Make muted channels also appear as unread if hide unreads is false, using the HiddenIconWithMutedStyle and the channel is hidden // Make muted channels also appear as unread if hide unreads is false, using the HiddenIconWithMutedStyle and the channel is hidden
predicate: () => settings.store.hideUnreads === false && settings.store.showMode === ShowMode.HiddenIconWithMutedStyle, predicate: () => settings.store.hideUnreads === false && settings.store.showMode === ShowMode.HiddenIconWithMutedStyle,
match: /\.LOCKED;if\((?<={channel:(\i).+?)/, match: /(?<=\.LOCKED(?:;if\(|:))(?<={channel:(\i).+?)/,
replace: (m, channel) => `${m}!$self.isHiddenChannel(${channel})&&` replace: (_, channel) => `!$self.isHiddenChannel(${channel})&&`
}, },
{ {
// Hide unreads // Hide unreads

View file

@ -76,8 +76,8 @@ export default definePlugin({
find: "#{intl::GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}", find: "#{intl::GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}",
replacement: [ replacement: [
{ {
match: /(\i)\.Tooltip,{(text:.{0,30}#{intl::GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}\))/, match: /\i\.\i,{(text:.{0,30}#{intl::GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}\))/,
replace: "$self.TooltipWrapper,{message:arguments[0].message,$2" replace: "$self.TooltipWrapper,{message:arguments[0].message,$1"
} }
] ]
} }

View file

@ -23,12 +23,12 @@ import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord"; import { getIntlMessage } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findComponentByCodeLazy, findExportedComponentLazy, findStoreLazy } from "@webpack"; import { findComponentByCodeLazy, findStoreLazy } from "@webpack";
import { GuildMemberStore, RelationshipStore, SelectedChannelStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common"; import { GuildMemberStore, RelationshipStore, SelectedChannelStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common";
import { buildSeveralUsers } from "../typingTweaks"; import { buildSeveralUsers } from "../typingTweaks";
const ThreeDots = findExportedComponentLazy("Dots", "AnimatedDots"); const ThreeDots = findComponentByCodeLazy(".dots,", "dotRadius:");
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers"); const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
const TypingStore = findStoreLazy("TypingStore"); const TypingStore = findStoreLazy("TypingStore");

View file

@ -22,7 +22,7 @@ const VoiceStateStore = findStoreLazy("VoiceStateStore");
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers"); const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
const Avatar = findComponentByCodeLazy(".status)/2):0"); const Avatar = findComponentByCodeLazy(".status)/2):0");
const GroupDMAvatars = findComponentByCodeLazy(".AvatarSizeSpecs[", "getAvatarURL"); const GroupDMAvatars = findComponentByCodeLazy("frontSrc:", "getAvatarURL");
const ActionButtonClasses = findByPropsLazy("actionButton", "highlight"); const ActionButtonClasses = findByPropsLazy("actionButton", "highlight");

View file

@ -23,11 +23,11 @@ import { Settings, useSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findExportedComponentLazy } from "@webpack"; import { findComponentByCodeLazy } from "@webpack";
import { Menu, Popout, useState } from "@webpack/common"; import { Menu, Popout, useState } from "@webpack/common";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
const HeaderBarIcon = findExportedComponentLazy("Icon", "Divider"); const HeaderBarIcon = findComponentByCodeLazy(".HEADER_BAR_BADGE_TOP:", '.iconBadge,"top"');
function VencordPopout(onClose: () => void) { function VencordPopout(onClose: () => void) {
const { useQuickCss } = useSettings(["useQuickCss"]); const { useQuickCss } = useSettings(["useQuickCss"]);

View file

@ -220,16 +220,16 @@ export default definePlugin({
{ {
find: ".cursorPointer:null,children", find: ".cursorPointer:null,children",
replacement: { replacement: {
match: /.Avatar,.+?src:(.+?\))(?=[,}])/, match: /(?=,src:(\i.getAvatarURL\(.+?[)]))/,
replace: (m, avatarUrl) => `${m},onClick:()=>$self.openAvatar(${avatarUrl})` replace: (_, avatarUrl) => `,onClick:()=>$self.openAvatar(${avatarUrl})`
} }
}, },
// User Dms top large icon // User Dms top large icon
{ {
find: 'experimentLocation:"empty_messages"', find: 'experimentLocation:"empty_messages"',
replacement: { replacement: {
match: /.Avatar,.+?src:(.+?\))(?=[,}])/, match: /(?<=SIZE_80,)(?=src:(.+?\))[,}])/,
replace: (m, avatarUrl) => `${m},onClick:()=>$self.openAvatar(${avatarUrl})` replace: (_, avatarUrl) => `onClick:()=>$self.openAvatar(${avatarUrl}),`
} }
} }
] ]

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { findByPropsLazy, findModuleId, proxyLazyWebpack, wreq } from "@webpack"; import { filters, findModuleId, mapMangledModuleLazy, proxyLazyWebpack, wreq } from "@webpack";
import type { ComponentType, PropsWithChildren, ReactNode, Ref } from "react"; import type { ComponentType, PropsWithChildren, ReactNode, Ref } from "react";
import { LazyComponent } from "./react"; import { LazyComponent } from "./react";
@ -49,7 +49,7 @@ export interface ModalOptions {
type RenderFunction = (props: ModalProps) => ReactNode | Promise<ReactNode>; type RenderFunction = (props: ModalProps) => ReactNode | Promise<ReactNode>;
export const Modals = findByPropsLazy("ModalRoot", "ModalCloseButton") as { interface Modals {
ModalRoot: ComponentType<PropsWithChildren<{ ModalRoot: ComponentType<PropsWithChildren<{
transitionState: ModalTransitionState; transitionState: ModalTransitionState;
size?: ModalSize; size?: ModalSize;
@ -99,7 +99,21 @@ export const Modals = findByPropsLazy("ModalRoot", "ModalCloseButton") as {
hideOnFullscreen?: boolean; hideOnFullscreen?: boolean;
className?: string; className?: string;
}>; }>;
}; }
const Modals: Modals = mapMangledModuleLazy(':"thin")', {
ModalRoot: filters.componentByCode('.MODAL,"aria-labelledby":'),
ModalHeader: filters.componentByCode(",id:"),
ModalContent: filters.componentByCode(".content,"),
ModalFooter: filters.componentByCode(".footer,"),
ModalCloseButton: filters.componentByCode(".close]:")
});
export const ModalRoot = LazyComponent(() => Modals.ModalRoot);
export const ModalHeader = LazyComponent(() => Modals.ModalHeader);
export const ModalContent = LazyComponent(() => Modals.ModalContent);
export const ModalFooter = LazyComponent(() => Modals.ModalFooter);
export const ModalCloseButton = LazyComponent(() => Modals.ModalCloseButton);
export type MediaModalItem = { export type MediaModalItem = {
url: string; url: string;
@ -135,38 +149,33 @@ export const openMediaModal: (props: MediaModalProps) => void = proxyLazyWebpack
return Object.values<any>(openMediaModalModule).find(v => String(v).includes("modalKey:")); return Object.values<any>(openMediaModalModule).find(v => String(v).includes("modalKey:"));
}); });
export const ModalRoot = LazyComponent(() => Modals.ModalRoot); interface ModalAPI {
export const ModalHeader = LazyComponent(() => Modals.ModalHeader); /**
export const ModalContent = LazyComponent(() => Modals.ModalContent);
export const ModalFooter = LazyComponent(() => Modals.ModalFooter);
export const ModalCloseButton = LazyComponent(() => Modals.ModalCloseButton);
export const ModalAPI = findByPropsLazy("openModalLazy");
/**
* Wait for the render promise to resolve, then open a modal with it. * Wait for the render promise to resolve, then open a modal with it.
* This is equivalent to render().then(openModal) * This is equivalent to render().then(openModal)
* You should use the Modal components exported by this file * You should use the Modal components exported by this file
*/ */
export const openModalLazy: (render: () => Promise<RenderFunction>, options?: ModalOptions & { contextKey?: string; }) => Promise<string> openModalLazy: (render: () => Promise<RenderFunction>, options?: ModalOptions & { contextKey?: string; }) => Promise<string>;
= proxyLazyWebpack(() => ModalAPI.openModalLazy); /**
/**
* Open a Modal with the given render function. * Open a Modal with the given render function.
* You should use the Modal components exported by this file * You should use the Modal components exported by this file
*/ */
export const openModal: (render: RenderFunction, options?: ModalOptions, contextKey?: string) => string openModal: (render: RenderFunction, options?: ModalOptions, contextKey?: string) => string;
= proxyLazyWebpack(() => ModalAPI.openModal); /**
/**
* Close a modal by its key * Close a modal by its key
*/ */
export const closeModal: (modalKey: string, contextKey?: string) => void closeModal: (modalKey: string, contextKey?: string) => void;
= proxyLazyWebpack(() => ModalAPI.closeModal); /**
/**
* Close all open modals * Close all open modals
*/ */
export const closeAllModals: () => void closeAllModals: () => void;
= proxyLazyWebpack(() => ModalAPI.closeAllModals); }
export const ModalAPI: ModalAPI = mapMangledModuleLazy(".modalKey?", {
openModalLazy: filters.byCode(".modalKey?"),
openModal: filters.byCode(",instant:"),
closeModal: filters.byCode(".onCloseCallback()"),
closeAllModals: filters.byCode(".getState();for")
});
export const { openModalLazy, openModal, closeModal, closeAllModals } = ModalAPI;

View file

@ -16,76 +16,82 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { filters, findByPropsLazy, waitFor } from "@webpack"; import { LazyComponent } from "@utils/lazyReact";
import { filters, mapMangledModuleLazy, waitFor } from "@webpack";
import { waitForComponent } from "./internal"; import { waitForComponent } from "./internal";
import * as t from "./types/components"; import * as t from "./types/components";
export let Forms = {} as {
FormTitle: t.FormTitle, const FormTitle = waitForComponent<t.FormTitle>("FormTitle", filters.componentByCode('["defaultMargin".concat', '="h5"'));
FormSection: t.FormSection, const FormText = waitForComponent<t.FormText>("FormText", filters.componentByCode(".SELECTABLE),", ".DISABLED:"));
FormDivider: t.FormDivider, const FormSection = waitForComponent<t.FormSection>("FormSection", filters.componentByCode(".titleId)&&"));
FormText: t.FormText, const FormDivider = waitForComponent<t.FormDivider>("FormDivider", filters.componentByCode(".divider,", ",style:", '"div"', /\.divider,\i\),style:/));
export const Forms = {
FormTitle,
FormText,
FormSection,
FormDivider
}; };
export let Icons = {} as t.Icons; export const Card = waitForComponent<t.Card>("Card", filters.componentByCode(".editable),", ".outline:"));
export const Button = waitForComponent<t.Button>("Button", filters.componentByCode("#{intl::A11Y_LOADING_STARTED}))),!1"));
export const Switch = waitForComponent<t.Switch>("Switch", filters.componentByCode(".labelRow,ref:", ".disabledText"));
const Tooltips = mapMangledModuleLazy(".tooltipTop,bottom:", {
Tooltip: filters.componentByCode("this.renderTooltip()]"),
TooltipContainer: filters.componentByCode('="div",')
}) as {
Tooltip: t.Tooltip,
TooltipContainer: t.TooltipContainer;
};
export const Tooltip = LazyComponent(() => Tooltips.Tooltip);
export const TooltipContainer = LazyComponent(() => Tooltips.TooltipContainer);
export const TextInput = waitForComponent<t.TextInput>("TextInput", filters.componentByCode(".error]:this.hasError()"));
export const TextArea = waitForComponent<t.TextArea>("TextArea", filters.componentByCode("this.getPaddingRight()},id:"));
export const Text = waitForComponent<t.Text>("Text", filters.componentByCode('case"always-white"'));
export const Heading = waitForComponent<t.Heading>("Heading", filters.componentByCode(">6?{", "variant:"));
export const Select = waitForComponent<t.Select>("Select", filters.componentByCode('.selectPositionTop]:"top"===', '"Escape"==='));
export const SearchableSelect = waitForComponent<t.SearchableSelect>("SearchableSelect", filters.componentByCode('.selectPositionTop]:"top"===', ".multi]:"));
export const Slider = waitForComponent<t.Slider>("Slider", filters.componentByCode('"markDash".concat('));
export const Popout = waitForComponent<t.Popout>("Popout", filters.componentByCode("ref:this.ref,preload:"));
export const Dialog = waitForComponent<t.Dialog>("Dialog", filters.componentByCode('role:"dialog",tabIndex:-1'));
export const TabBar = waitForComponent("TabBar", filters.componentByCode("ref:this.tabBarRef,className:"));
export const Paginator = waitForComponent<t.Paginator>("Paginator", filters.componentByCode('rel:"prev",children:'));
export const Clickable = waitForComponent<t.Clickable>("Clickable", filters.componentByCode("this.context?this.renderNonInteractive():"));
export const Avatar = waitForComponent<t.Avatar>("Avatar", filters.componentByCode(".size-1.375*"));
export let createScroller: (scrollbarClassName: string, fadeClassName: string, customThemeClassName: string) => t.ScrollerThin;
export let scrollerClasses: Record<string, string>;
waitFor(filters.byCode('="ltr",orientation:', "customTheme:", "forwardRef"), m => createScroller = m);
waitFor(["thin", "auto", "customTheme"], m => scrollerClasses = m);
export const ScrollerNone = LazyComponent(() => createScroller(scrollerClasses.none, scrollerClasses.fade, scrollerClasses.customTheme));
export const ScrollerThin = LazyComponent(() => createScroller(scrollerClasses.thin, scrollerClasses.fade, scrollerClasses.customTheme));
export const ScrollerAuto = LazyComponent(() => createScroller(scrollerClasses.auto, scrollerClasses.fade, scrollerClasses.customTheme));
const { FocusLock_ } = mapMangledModuleLazy("attachTo:null!==", {
FocusLock_: filters.componentByCode(".containerRef")
}) as {
FocusLock_: t.FocusLock;
};
export const FocusLock = LazyComponent(() => FocusLock_);
export let Card: t.Card;
export let Button: t.Button;
export let Switch: t.Switch;
export let Tooltip: t.Tooltip;
export let TooltipContainer: t.TooltipContainer;
export let TextInput: t.TextInput;
export let TextArea: t.TextArea;
export let Text: t.Text;
export let Heading: t.Heading;
export let Select: t.Select;
export let SearchableSelect: t.SearchableSelect;
export let Slider: t.Slider;
export let ButtonLooks: t.ButtonLooks;
export let Popout: t.Popout;
export let Dialog: t.Dialog;
export let TabBar: any;
export let Paginator: t.Paginator;
export let ScrollerThin: t.ScrollerThin;
export let Clickable: t.Clickable;
export let Avatar: t.Avatar;
export let FocusLock: t.FocusLock;
// token lagger real
/** css colour resolver stuff, no clue what exactly this does, just copied usage from Discord */
export let useToken: t.useToken; export let useToken: t.useToken;
waitFor(m => {
if (typeof m !== "function") {
return false;
}
const str = String(m);
return str.includes(".resolve({theme:null") && !str.includes("useMemo");
}, m => useToken = m);
export const MaskedLink = waitForComponent<t.MaskedLink>("MaskedLink", filters.componentByCode("MASKED_LINK)")); export const MaskedLink = waitForComponent<t.MaskedLink>("MaskedLink", filters.componentByCode("MASKED_LINK)"));
export const Timestamp = waitForComponent<t.Timestamp>("Timestamp", filters.byCode("#{intl::MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL}")); export const Timestamp = waitForComponent<t.Timestamp>("Timestamp", filters.componentByCode("#{intl::MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL}"));
export const Flex = waitForComponent<t.Flex>("Flex", ["Justify", "Align", "Wrap"]); export const Flex = waitForComponent<t.Flex>("Flex", ["Justify", "Align", "Wrap"]);
export const OAuth2AuthorizeModal = waitForComponent("OAuth2AuthorizeModal", filters.componentByCode(".authorize),children:", ".contentBackground"));
export const { OAuth2AuthorizeModal } = findByPropsLazy("OAuth2AuthorizeModal");
waitFor(["FormItem", "Button"], m => {
({
useToken,
Card,
Button,
FormSwitch: Switch,
Tooltip,
TooltipContainer,
TextInput,
TextArea,
Text,
Select,
SearchableSelect,
Slider,
ButtonLooks,
TabBar,
Popout,
Dialog,
Paginator,
ScrollerThin,
Clickable,
Avatar,
FocusLock,
Heading
} = m);
Forms = m;
Icons = m;
});

View file

@ -17,16 +17,28 @@
*/ */
// eslint-disable-next-line path-alias/no-relative // eslint-disable-next-line path-alias/no-relative
import { filters, mapMangledModuleLazy, waitFor } from "../webpack"; import { filters, mapMangledModuleLazy, waitFor, wreq } from "../webpack";
import type * as t from "./types/menu"; import type * as t from "./types/menu";
export let Menu = {} as t.Menu; export const Menu = {} as t.Menu;
waitFor(["MenuItem", "MenuSliderControl"], m => Menu = m); // Relies on .name properties added by the MenuItemDemanglerAPI
waitFor(m => m.name === "MenuCheckboxItem", (_, id) => {
// we have to do this manual require by ID because m is in this case the MenuCheckBoxItem instead of the entire module
const module = wreq(id as any);
for (const e of Object.values(module)) {
if (typeof e === "function" && e.name.startsWith("Menu")) {
Menu[e.name] = e;
}
}
});
waitFor(filters.componentByCode('path:["empty"]'), m => Menu.Menu = m);
waitFor(filters.componentByCode("sliderContainer", "slider", "handleSize:16", "=100"), m => Menu.MenuSliderControl = m);
export const ContextMenuApi: t.ContextMenuApi = mapMangledModuleLazy('type:"CONTEXT_MENU_OPEN', { export const ContextMenuApi: t.ContextMenuApi = mapMangledModuleLazy('type:"CONTEXT_MENU_OPEN', {
closeContextMenu: filters.byCode("CONTEXT_MENU_CLOSE"), closeContextMenu: filters.byCode("CONTEXT_MENU_CLOSE"),
openContextMenu: filters.byCode("renderLazy:"), openContextMenu: filters.byCode("renderLazy:"),
openContextMenuLazy: e => typeof e === "function" && e.toString().length < 100 openContextMenuLazy: e => typeof e === "function" && e.toString().length < 100
}); });

View file

@ -18,14 +18,12 @@
import type { ComponentPropsWithRef, ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, JSX, KeyboardEvent, MouseEvent, PropsWithChildren, PropsWithRef, ReactNode, Ref } from "react"; import type { ComponentPropsWithRef, ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, JSX, KeyboardEvent, MouseEvent, PropsWithChildren, PropsWithRef, ReactNode, Ref } from "react";
import { IconNames } from "./iconNames";
export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code"; export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code";
export type FormTextTypes = Record<"DEFAULT" | "INPUT_PLACEHOLDER" | "DESCRIPTION" | "LABEL_BOLD" | "LABEL_SELECTED" | "LABEL_DESCRIPTOR" | "ERROR" | "SUCCESS", string>; export type FormTextTypes = Record<"DEFAULT" | "INPUT_PLACEHOLDER" | "DESCRIPTION" | "LABEL_BOLD" | "LABEL_SELECTED" | "LABEL_DESCRIPTOR" | "ERROR" | "SUCCESS", string>;
export type HeadingTag = `h${1 | 2 | 3 | 4 | 5 | 6}`; export type HeadingTag = `h${1 | 2 | 3 | 4 | 5 | 6}`;
export type Margins = Record<"marginTop16" | "marginTop8" | "marginBottom8" | "marginTop20" | "marginBottom20", string>; export type Margins = Record<"marginTop16" | "marginTop8" | "marginBottom8" | "marginTop20" | "marginBottom20", string>;
export type ButtonLooks = Record<"FILLED" | "INVERTED" | "OUTLINED" | "LINK" | "BLANK", string>;
export type TextProps = PropsWithChildren<HtmlHTMLAttributes<HTMLDivElement> & { export type TextProps = PropsWithChildren<HtmlHTMLAttributes<HTMLDivElement> & {
variant?: TextVariant; variant?: TextVariant;
@ -504,4 +502,3 @@ export type Icon = ComponentType<JSX.IntrinsicElements["svg"] & {
colorClass?: string; colorClass?: string;
} & Record<string, any>>; } & Record<string, any>>;
export type Icons = Record<IconNames, Icon>;

File diff suppressed because one or more lines are too long

View file

@ -172,26 +172,36 @@ function patchFactories(factories: Record<string, (module: any, exports: any, re
if (!exports) return; if (!exports) return;
// There are (at the time of writing) 11 modules exporting the window
// Make these non enumerable to improve webpack search performance
if (require.c) { if (require.c) {
let foundWindow = false; let shouldMakeNonEnumerable = false;
if (exports === window) { nonEnumerableChecking: {
foundWindow = true; // There are (at the time of writing) 11 modules exporting the window,
} else if (typeof exports === "object") { // and also modules exporting DOMTokenList, which breaks webpack finding
if (exports?.default === window) { // Make these non enumerable to improve search performance and avoid erros
foundWindow = true; if (exports === window || exports[Symbol.toStringTag] === "DOMTokenList") {
} else { shouldMakeNonEnumerable = true;
for (const nested in exports) if (nested.length <= 3) { break nonEnumerableChecking;
if (exports[nested] === window) {
foundWindow = true;
} }
if (typeof exports !== "object") {
break nonEnumerableChecking;
}
if (exports.default === window || exports.default?.[Symbol.toStringTag] === "DOMTokenList") {
shouldMakeNonEnumerable = true;
break nonEnumerableChecking;
}
for (const nested in exports) {
if (exports[nested] === window || exports[nested]?.[Symbol.toStringTag] === "DOMTokenList") {
shouldMakeNonEnumerable = true;
break nonEnumerableChecking;
} }
} }
} }
if (foundWindow) { if (shouldMakeNonEnumerable) {
Object.defineProperty(require.c, id, { Object.defineProperty(require.c, id, {
value: require.c[id], value: require.c[id],
enumerable: false, enumerable: false,
@ -221,7 +231,7 @@ function patchFactories(factories: Record<string, (module: any, exports: any, re
subscriptions.delete(filter); subscriptions.delete(filter);
callback(exports.default, id); callback(exports.default, id);
} else { } else {
for (const nested in exports) if (nested.length <= 3) { for (const nested in exports) {
if (exports[nested] && filter(exports[nested])) { if (exports[nested] && filter(exports[nested])) {
subscriptions.delete(filter); subscriptions.delete(filter);
callback(exports[nested], id); callback(exports[nested], id);

View file

@ -56,11 +56,14 @@ export const filters = {
: m => props.every(p => m[p] !== void 0), : m => props.every(p => m[p] !== void 0),
byCode: (...code: CodeFilter): FilterFn => { byCode: (...code: CodeFilter): FilterFn => {
code = code.map(canonicalizeMatch); const parsedCode = code.map(canonicalizeMatch);
return m => { const filter = m => {
if (typeof m !== "function") return false; if (typeof m !== "function") return false;
return stringMatches(Function.prototype.toString.call(m), code); return stringMatches(Function.prototype.toString.call(m), parsedCode);
}; };
filter.$$vencordProps = [...code];
return filter;
}, },
byStoreName: (name: StoreNameFilter): FilterFn => m => byStoreName: (name: StoreNameFilter): FilterFn => m =>
m.constructor?.displayName === name, m.constructor?.displayName === name,
@ -131,8 +134,7 @@ export const find = traceFunction("find", function find(filter: FilterFn, { isIn
return isWaitFor ? [found, key] : found; return isWaitFor ? [found, key] : found;
} }
// the length check makes search about 20% faster for (const nestedMod in mod.exports) {
for (const nestedMod in mod.exports) if (nestedMod.length <= 3) {
const nested = mod.exports[nestedMod]; const nested = mod.exports[nestedMod];
if (nested && filter(nested)) { if (nested && filter(nested)) {
return isWaitFor ? [nested, key] : nested; return isWaitFor ? [nested, key] : nested;
@ -163,7 +165,7 @@ export function findAll(filter: FilterFn) {
if (mod.exports.default && filter(mod.exports.default)) if (mod.exports.default && filter(mod.exports.default))
ret.push(mod.exports.default); ret.push(mod.exports.default);
else for (const nestedMod in mod.exports) if (nestedMod.length <= 3) { else for (const nestedMod in mod.exports) {
const nested = mod.exports[nestedMod]; const nested = mod.exports[nestedMod];
if (nested && filter(nested)) ret.push(nested); if (nested && filter(nested)) ret.push(nested);
} }
@ -226,8 +228,7 @@ export const findBulk = traceFunction("findBulk", function findBulk(...filterFns
break; break;
} }
for (const nestedMod in mod.exports) for (const nestedMod in mod.exports) {
if (nestedMod.length <= 3) {
const nested = mod.exports[nestedMod]; const nested = mod.exports[nestedMod];
if (nested && filter(nested)) { if (nested && filter(nested)) {
results[j] = nested; results[j] = nested;