Compare commits

..

No commits in common. "c7489bfa88420a9bd61e3705b504e1c14cc09b5f" and "351fa9ff8612569897736ce38b129a38f6838c8b" have entirely different histories.

32 changed files with 476 additions and 186 deletions

View file

@ -1,7 +1,7 @@
{ {
"name": "vencord", "name": "vencord",
"private": "true", "private": "true",
"version": "1.9.8", "version": "1.9.7",
"description": "The cutest Discord client mod", "description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme", "homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": { "bugs": {

View file

@ -134,7 +134,7 @@ export async function loadLazyChunks() {
const allChunks = [] as number[]; const allChunks = [] as number[];
// Matches "id" or id: // Matches "id" or id:
for (const currentMatch of wreq!.u.toString().matchAll(/(?:"([\deE]+?)"(?![,}]))|(?:([\deE]+?):)/g)) { for (const currentMatch of wreq!.u.toString().matchAll(/(?:"([\deE]+?)")|(?:([\deE]+?):)/g)) {
const id = currentMatch[1] ?? currentMatch[2]; const id = currentMatch[1] ?? currentMatch[2];
if (id == null) continue; if (id == null) continue;

View file

@ -62,6 +62,34 @@ export default definePlugin({
authors: [Devs.Megu, Devs.Ven, Devs.TheSun], authors: [Devs.Megu, Devs.Ven, Devs.TheSun],
required: true, required: true,
patches: [ patches: [
/* Patch the badge list component on user profiles */
{
find: 'id:"premium",',
replacement: [
{
match: /&&(\i)\.push\(\{id:"premium".+?\}\);/,
replace: "$&$1.unshift(...$self.getBadges(arguments[0]));",
},
{
// alt: "", aria-hidden: false, src: originalSrc
match: /alt:" ","aria-hidden":!0,src:(?=(\i)\.src)/,
// ...badge.props, ..., src: badge.image ?? ...
replace: "...$1.props,$& $1.image??"
},
// replace their component with ours if applicable
{
match: /(?<=text:(\i)\.description,spacing:12,.{0,50})children:/,
replace: "children:$1.component ? () => $self.renderBadgeComponent($1) :"
},
// conditionally override their onClick with badge.onClick if it exists
{
match: /href:(\i)\.link/,
replace: "...($1.onClick && { onClick: vcE => $1.onClick(vcE, $1) }),$&"
}
]
},
/* new profiles */
{ {
find: ".FULL_SIZE]:26", find: ".FULL_SIZE]:26",
replacement: { replacement: {

View file

@ -0,0 +1,5 @@
# AutomodContext
Allows you to jump to the messages surrounding an automod hit
![Visualization](https://github.com/Vendicated/Vencord/assets/61953774/d13740c8-2062-4553-b975-82fd3d6cc08b)

View file

@ -0,0 +1,73 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { Button, ChannelStore, Text } from "@webpack/common";
const { selectChannel } = findByPropsLazy("selectChannel", "selectVoiceChannel");
function jumpToMessage(channelId: string, messageId: string) {
const guildId = ChannelStore.getChannel(channelId)?.guild_id;
selectChannel({
guildId,
channelId,
messageId,
jumpType: "INSTANT"
});
}
function findChannelId(message: any): string | null {
const { embeds: [embed] } = message;
const channelField = embed.fields.find(({ rawName }) => rawName === "channel_id");
if (!channelField) {
return null;
}
return channelField.rawValue;
}
export default definePlugin({
name: "AutomodContext",
description: "Allows you to jump to the messages surrounding an automod hit.",
authors: [Devs.JohnyTheCarrot],
patches: [
{
find: ".Messages.GUILD_AUTOMOD_REPORT_ISSUES",
replacement: {
match: /\.Messages\.ACTIONS.+?}\)(?=,(\(0.{0,40}\.dot.*?}\)),)/,
replace: (m, dot) => `${m},${dot},$self.renderJumpButton({message:arguments[0].message})`
}
}
],
renderJumpButton: ErrorBoundary.wrap(({ message }: { message: any; }) => {
const channelId = findChannelId(message);
if (!channelId) {
return null;
}
return (
<Button
style={{ padding: "2px 8px" }}
look={Button.Looks.LINK}
size={Button.Sizes.SMALL}
color={Button.Colors.LINK}
onClick={() => jumpToMessage(channelId, message.id)}
>
<Text color="text-link" variant="text-xs/normal">
Jump to Surrounding
</Text>
</Button>
);
}, { noop: true })
});

View file

@ -17,9 +17,13 @@
*/ */
import { definePluginSettings, Settings } from "@api/Settings"; import { definePluginSettings, Settings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { canonicalizeMatch } from "@utils/patches"; import { canonicalizeMatch } from "@utils/patches";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack";
const UserPopoutSectionCssClasses = findByPropsLazy("section", "lastSection");
const settings = definePluginSettings({ const settings = definePluginSettings({
hide: { hide: {
@ -68,9 +72,23 @@ export default definePlugin({
match: /\.NOTE_PLACEHOLDER,/, match: /\.NOTE_PLACEHOLDER,/,
replace: "$&spellCheck:!$self.noSpellCheck," replace: "$&spellCheck:!$self.noSpellCheck,"
} }
},
{
find: ".popularApplicationCommandIds,",
replacement: {
match: /lastSection:(!?\i)}\),/,
replace: "$&$self.patchPadding({lastSection:$1}),"
}
} }
], ],
patchPadding: ErrorBoundary.wrap(({ lastSection }) => {
if (!lastSection) return null;
return (
<div className={UserPopoutSectionCssClasses.lastSection} ></div>
);
}),
get noSpellCheck() { get noSpellCheck() {
return settings.store.noSpellCheck; return settings.store.noSpellCheck;
} }

View file

@ -121,7 +121,7 @@ export default definePlugin({
{ {
find: "UserProfileStore", find: "UserProfileStore",
replacement: { replacement: {
match: /(?<=getUserProfile\(\i\){return )(.+?)(?=})/, match: /(?<=getUserProfile\(\i\){return )(\i\[\i\])/,
replace: "$self.colorDecodeHook($1)" replace: "$self.colorDecodeHook($1)"
} }
}, },

View file

@ -16,6 +16,7 @@ const containerWrapper = findByPropsLazy("memberSinceWrapper");
const container = findByPropsLazy("memberSince"); const container = findByPropsLazy("memberSince");
const getCreatedAtDate = findByCodeLazy('month:"short",day:"numeric"'); const getCreatedAtDate = findByCodeLazy('month:"short",day:"numeric"');
const locale = findByPropsLazy("getLocale"); const locale = findByPropsLazy("getLocale");
const lastSection = findByPropsLazy("lastSection");
const section = findLazy((m: any) => m.section !== void 0 && m.heading !== void 0 && Object.values(m).length === 2); const section = findLazy((m: any) => m.section !== void 0 && m.heading !== void 0 && Object.values(m).length === 2);
export default definePlugin({ export default definePlugin({
@ -23,7 +24,31 @@ export default definePlugin({
description: "Shows when you became friends with someone in the user popout", description: "Shows when you became friends with someone in the user popout",
authors: [Devs.Elvyra, Devs.Antti], authors: [Devs.Elvyra, Devs.Antti],
patches: [ patches: [
// DM User Sidebar // User popup - old layout
{
find: ".USER_PROFILE}};return",
replacement: {
match: /,{userId:(\i.id).{0,30}}\)/,
replace: "$&,$self.friendsSinceOld({ userId: $1 })"
}
},
// DM User Sidebar - old layout
{
find: ".PROFILE_PANEL,",
replacement: {
match: /,{userId:([^,]+?)}\)/,
replace: "$&,$self.friendsSinceOld({ userId: $1 })"
}
},
// User Profile Modal - old layout
{
find: ".userInfoSectionHeader,",
replacement: {
match: /(\.Messages\.USER_PROFILE_MEMBER_SINCE.+?userId:(.+?),textClassName:)(\i\.userInfoText)}\)/,
replace: (_, rest, userId, textClassName) => `${rest}!$self.getFriendSince(${userId}) ? ${textClassName} : void 0 }), $self.friendsSinceOld({ userId: ${userId}, textClassName: ${textClassName} })`
}
},
// DM User Sidebar - new layout
{ {
find: ".PANEL}),nicknameIcons", find: ".PANEL}),nicknameIcons",
replacement: { replacement: {
@ -31,7 +56,7 @@ export default definePlugin({
replace: "$&,$self.friendsSinceNew({userId:$1,isSidebar:true})" replace: "$&,$self.friendsSinceNew({userId:$1,isSidebar:true})"
} }
}, },
// User Profile Modal // User Profile Modal - new layout
{ {
find: "action:\"PRESS_APP_CONNECTION\"", find: "action:\"PRESS_APP_CONNECTION\"",
replacement: { replacement: {
@ -52,6 +77,39 @@ export default definePlugin({
} }
}, },
friendsSinceOld: ErrorBoundary.wrap(({ userId, textClassName }: { userId: string; textClassName?: string; }) => {
if (!RelationshipStore.isFriend(userId)) return null;
const friendsSince = RelationshipStore.getSince(userId);
if (!friendsSince) return null;
return (
<div className={lastSection.section}>
<Heading variant="eyebrow">
Friends Since
</Heading>
<div className={containerWrapper.memberSinceWrapper}>
{!!getCurrentChannel()?.guild_id && (
<svg
aria-hidden="true"
width="16"
height="16"
viewBox="0 0 24 24"
fill="var(--interactive-normal)"
>
<path d="M13 10a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z" />
<path d="M3 5v-.75C3 3.56 3.56 3 4.25 3s1.24.56 1.33 1.25C6.12 8.65 9.46 12 13 12h1a8 8 0 0 1 8 8 2 2 0 0 1-2 2 .21.21 0 0 1-.2-.15 7.65 7.65 0 0 0-1.32-2.3c-.15-.2-.42-.06-.39.17l.25 2c.02.15-.1.28-.25.28H9a2 2 0 0 1-2-2v-2.22c0-1.57-.67-3.05-1.53-4.37A15.85 15.85 0 0 1 3 5Z" />
</svg>
)}
<Text variant="text-sm/normal" className={textClassName}>
{getCreatedAtDate(friendsSince, locale.getLocale())}
</Text>
</div>
</div>
);
}, { noop: true }),
friendsSinceNew: ErrorBoundary.wrap(({ userId, isSidebar }: { userId: string; isSidebar: boolean; }) => { friendsSinceNew: ErrorBoundary.wrap(({ userId, isSidebar }: { userId: string; isSidebar: boolean; }) => {
if (!RelationshipStore.isFriend(userId)) return null; if (!RelationshipStore.isFriend(userId)) return null;

View file

@ -5,10 +5,9 @@
*/ */
import { getCurrentChannel } from "@utils/discord"; import { getCurrentChannel } from "@utils/discord";
import { isObjectEmpty } from "@utils/misc";
import { SelectedChannelStore, Tooltip, useEffect, useStateFromStores } from "@webpack/common"; import { SelectedChannelStore, Tooltip, useEffect, useStateFromStores } from "@webpack/common";
import { ChannelMemberStore, cl, GuildMemberCountStore, numberFormat, ThreadMemberListStore } from "."; import { ChannelMemberStore, cl, GuildMemberCountStore, numberFormat } from ".";
import { OnlineMemberCountStore } from "./OnlineMemberCountStore"; import { OnlineMemberCountStore } from "./OnlineMemberCountStore";
export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; tooltipGuildId?: string; }) { export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; tooltipGuildId?: string; }) {
@ -31,19 +30,10 @@ export function MemberCount({ isTooltip, tooltipGuildId }: { isTooltip?: true; t
() => ChannelMemberStore.getProps(guildId, currentChannel?.id) () => ChannelMemberStore.getProps(guildId, currentChannel?.id)
); );
const threadGroups = useStateFromStores(
[ThreadMemberListStore],
() => ThreadMemberListStore.getMemberListSections(currentChannel.id)
);
if (!isTooltip && (groups.length >= 1 || groups[0].id !== "unknown")) { if (!isTooltip && (groups.length >= 1 || groups[0].id !== "unknown")) {
onlineCount = groups.reduce((total, curr) => total + (curr.id === "offline" ? 0 : curr.count), 0); onlineCount = groups.reduce((total, curr) => total + (curr.id === "offline" ? 0 : curr.count), 0);
} }
if (!isTooltip && threadGroups && !isObjectEmpty(threadGroups)) {
onlineCount = Object.values(threadGroups).reduce((total, curr) => total + (curr.sectionId === "offline" ? 0 : curr.userIds.length), 0);
}
useEffect(() => { useEffect(() => {
OnlineMemberCountStore.ensureCount(guildId); OnlineMemberCountStore.ensureCount(guildId);
}, [guildId]); }, [guildId]);

View file

@ -32,10 +32,6 @@ export const GuildMemberCountStore = findStoreLazy("GuildMemberCountStore") as F
export const ChannelMemberStore = findStoreLazy("ChannelMemberStore") as FluxStore & { export const ChannelMemberStore = findStoreLazy("ChannelMemberStore") as FluxStore & {
getProps(guildId: string, channelId: string): { groups: { count: number; id: string; }[]; }; getProps(guildId: string, channelId: string): { groups: { count: number; id: string; }[]; };
}; };
export const ThreadMemberListStore = findStoreLazy("ThreadMemberListStore") as FluxStore & {
getMemberListSections(channelId: string): { [sectionId: string]: { sectionId: string; userIds: string[]; }; };
};
const settings = definePluginSettings({ const settings = definePluginSettings({
toolTip: { toolTip: {

View file

@ -247,9 +247,9 @@ export default definePlugin({
} }
}, },
{ {
find: ".Messages.USER_PROFILE_PRONOUNS", find: 'copyMetaData:"User Tag"',
replacement: { replacement: {
match: /(?=,hideBotTag:!0)/, match: /(?=,botClass:)/,
replace: ",moreTags_channelId:arguments[0].moreTags_channelId" replace: ",moreTags_channelId:arguments[0].moreTags_channelId"
} }
}, },

View file

@ -58,6 +58,20 @@ export default definePlugin({
authors: [Devs.amia], authors: [Devs.amia],
patches: [ patches: [
{
find: ".Messages.MUTUAL_GUILDS_WITH_END_COUNT", // Note: the module is lazy-loaded
replacement: {
match: /(?<=\.tabBarItem.{0,50}MUTUAL_GUILDS.+?}\),)(?=.+?(\(0,\i\.jsxs?\)\(.{0,100}id:))/,
replace: '$self.isBotOrSelf(arguments[0].user)?null:$1"MUTUAL_GDMS",children:$self.getMutualGDMCountText(arguments[0].user)}),'
}
},
{
find: ".USER_INFO_CONNECTIONS:case",
replacement: {
match: /(?<={user:(\i),onClose:(\i)}\);)(?=case \i\.\i\.MUTUAL_FRIENDS)/,
replace: "case \"MUTUAL_GDMS\":return $self.renderMutualGDMs({user: $1, onClose: $2});"
}
},
{ {
find: ".MUTUAL_FRIENDS?(", find: ".MUTUAL_FRIENDS?(",
replacement: [ replacement: [
@ -73,6 +87,9 @@ export default definePlugin({
} }
], ],
isBotOrSelf,
getMutualGDMCountText,
pushSection(sections: any[], user: User) { pushSection(sections: any[], user: User) {
if (isBotOrSelf(user) || sections[IS_PATCHED]) return; if (isBotOrSelf(user) || sections[IS_PATCHED]) return;

View file

@ -1,3 +0,0 @@
# NoMaskedUrlPaste
Pasting a link while you have text selected will NOT paste your link as a masked link.

View file

@ -1,23 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Devs } from "@utils/constants.js";
import definePlugin from "@utils/types";
export default definePlugin({
name: "NoMaskedUrlPaste",
authors: [Devs.CatNoir],
description: "Pasting a link while having text selected will not paste as masked URL",
patches: [
{
find: ".selection,preventEmojiSurrogates:",
replacement: {
match: /if\(null!=\i.selection&&\i.\i.isExpanded\(\i.selection\)\)/,
replace: "if(false)"
}
}
],
});

View file

@ -18,21 +18,36 @@
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { UserStore } from "@webpack/common";
export default definePlugin({ export default definePlugin({
name: "NoProfileThemes", name: "NoProfileThemes",
description: "Completely removes Nitro profile themes from everyone but yourself", description: "Completely removes Nitro profile themes",
authors: [Devs.TheKodeToad], authors: [Devs.TheKodeToad],
patches: [ patches: [
{
find: ".NITRO_BANNER,",
replacement: {
// = isPremiumAtLeast(user.premiumType, TIER_2)
match: /=(?=\i\.\i\.isPremiumAtLeast\(null==(\i))/,
// = user.banner && isPremiumAtLeast(user.premiumType, TIER_2)
replace: "=(arguments[0]?.bannerSrc||$1?.banner)&&"
}
},
{
find: ".avatarPositionPremiumNoBanner,default:",
replacement: {
// premiumUserWithoutBanner: foo().avatarPositionPremiumNoBanner, default: foo().avatarPositionNormal
match: /\.avatarPositionPremiumNoBanner(?=,default:\i\.(\i))/,
// premiumUserWithoutBanner: foo().avatarPositionNormal...
replace: ".$1"
}
},
{ {
find: "hasThemeColors(){", find: "hasThemeColors(){",
replacement: { replacement: {
match: /get canUsePremiumProfileCustomization\(\){return /, match: /get canUsePremiumProfileCustomization\(\){return /,
replace: "$&$self.isCurrentUser(this.userId)&&" replace: "$&false &&"
} }
}, }
], ]
isCurrentUser: (userId: string) => userId === UserStore.getCurrentUser()?.id,
}); });

View file

@ -35,17 +35,15 @@ interface UserPermission {
type UserPermissions = Array<UserPermission>; type UserPermissions = Array<UserPermission>;
const { RoleRootClasses, RoleClasses, RoleBorderClasses } = proxyLazyWebpack(() => { const Classes = proxyLazyWebpack(() =>
const [RoleRootClasses, RoleClasses, RoleBorderClasses] = findBulk( Object.assign({}, ...findBulk(
filters.byProps("root", "showMoreButton", "collapseButton"), filters.byProps("roles", "rolePill", "rolePillBorder"),
filters.byProps("role", "roleCircle", "roleName"), filters.byProps("roleCircle", "dotBorderBase", "dotBorderColor"),
filters.byProps("roleCircle", "dot", "dotBorderColor") filters.byProps("roleNameOverflow", "root", "roleName", "roleRemoveButton")
) as Record<string, string>[]; ))
) as Record<"roles" | "rolePill" | "rolePillBorder" | "desaturateUserColors" | "flex" | "alignCenter" | "justifyCenter" | "svg" | "background" | "dot" | "dotBorderColor" | "roleCircle" | "dotBorderBase" | "flex" | "alignCenter" | "justifyCenter" | "wrap" | "root" | "role" | "roleRemoveButton" | "roleDot" | "roleFlowerStar" | "roleRemoveIcon" | "roleRemoveIconFocused" | "roleVerifiedIcon" | "roleName" | "roleNameOverflow" | "actionButton" | "overflowButton" | "addButton" | "addButtonIcon" | "overflowRolesPopout" | "overflowRolesPopoutArrowWrapper" | "overflowRolesPopoutArrow" | "popoutBottom" | "popoutTop" | "overflowRolesPopoutHeader" | "overflowRolesPopoutHeaderIcon" | "overflowRolesPopoutHeaderText" | "roleIcon", string>;
return { RoleRootClasses, RoleClasses, RoleBorderClasses }; function UserPermissionsComponent({ guild, guildMember, showBorder, forceOpen = false }: { guild: Guild; guildMember: GuildMember; showBorder: boolean; forceOpen?: boolean; }) {
});
function UserPermissionsComponent({ guild, guildMember, forceOpen = false }: { guild: Guild; guildMember: GuildMember; forceOpen?: boolean; }) {
const stns = settings.use(["permissionsSortOrder"]); const stns = settings.use(["permissionsSortOrder"]);
const [rolePermissions, userPermissions] = useMemo(() => { const [rolePermissions, userPermissions] = useMemo(() => {
@ -93,6 +91,8 @@ function UserPermissionsComponent({ guild, guildMember, forceOpen = false }: { g
return [rolePermissions, userPermissions]; return [rolePermissions, userPermissions];
}, [stns.permissionsSortOrder]); }, [stns.permissionsSortOrder]);
const { root, role, roleRemoveButton, roleNameOverflow, roles, rolePill, rolePillBorder, roleCircle, roleName } = Classes;
return ( return (
<ExpandableHeader <ExpandableHeader
forceOpen={forceOpen} forceOpen={forceOpen}
@ -130,18 +130,18 @@ function UserPermissionsComponent({ guild, guildMember, forceOpen = false }: { g
</Tooltip>) </Tooltip>)
]}> ]}>
{userPermissions.length > 0 && ( {userPermissions.length > 0 && (
<div className={classes(RoleRootClasses.root)}> <div className={classes(root, roles)}>
{userPermissions.map(({ permission, roleColor }) => ( {userPermissions.map(({ permission, roleColor }) => (
<div className={classes(RoleClasses.role)}> <div className={classes(role, rolePill, showBorder ? rolePillBorder : null)}>
<div className={RoleClasses.roleRemoveButton}> <div className={roleRemoveButton}>
<span <span
className={classes(RoleBorderClasses.roleCircle, RoleClasses.roleCircle)} className={roleCircle}
style={{ backgroundColor: roleColor }} style={{ backgroundColor: roleColor }}
/> />
</div> </div>
<div className={RoleClasses.roleName}> <div className={roleName}>
<Text <Text
className={RoleClasses.roleNameOverflow} className={roleNameOverflow}
variant="text-xs/medium" variant="text-xs/medium"
> >
{permission} {permission}

View file

@ -169,6 +169,13 @@ export default definePlugin({
settings, settings,
patches: [ patches: [
{
find: ".popularApplicationCommandIds,",
replacement: {
match: /showBorder:(.{0,60})}\),(?<=guild:(\i),guildMember:(\i),.+?)/,
replace: (m, showBoder, guild, guildMember) => `${m}$self.UserPermissions(${guild},${guildMember},${showBoder}),`
}
},
{ {
find: ".VIEW_ALL_ROLES,", find: ".VIEW_ALL_ROLES,",
replacement: { replacement: {
@ -178,13 +185,16 @@ export default definePlugin({
} }
], ],
UserPermissions: (guild: Guild, guildMember: GuildMember | undefined, showBorder: boolean) =>
!!guildMember && <UserPermissions guild={guild} guildMember={guildMember} showBorder={showBorder} />,
ViewPermissionsButton: ErrorBoundary.wrap(({ guild, guildMember }: { guild: Guild; guildMember: GuildMember; }) => ( ViewPermissionsButton: ErrorBoundary.wrap(({ guild, guildMember }: { guild: Guild; guildMember: GuildMember; }) => (
<Popout <Popout
position="bottom" position="bottom"
align="center" align="center"
renderPopout={() => ( renderPopout={() => (
<Dialog className={PopoutClasses.container} style={{ width: "500px" }}> <Dialog className={PopoutClasses.container} style={{ width: "500px" }}>
<UserPermissions guild={guild} guildMember={guildMember} forceOpen /> <UserPermissions guild={guild} guildMember={guildMember} showBorder forceOpen />
</Dialog> </Dialog>
)} )}
> >

View file

@ -26,6 +26,11 @@ import { CompactPronounsChatComponentWrapper, PronounsChatComponentWrapper } fro
import { useProfilePronouns } from "./pronoundbUtils"; import { useProfilePronouns } from "./pronoundbUtils";
import { settings } from "./settings"; import { settings } from "./settings";
const PRONOUN_TOOLTIP_PATCH = {
match: /text:(.{0,10}.Messages\.USER_PROFILE_PRONOUNS)(?=,)/,
replace: '$& + (typeof vcPronounSource !== "undefined" ? ` (${vcPronounSource})` : "")'
};
export default definePlugin({ export default definePlugin({
name: "PronounDB", name: "PronounDB",
authors: [Devs.Tyman, Devs.TheKodeToad, Devs.Ven, Devs.Elvyra], authors: [Devs.Tyman, Devs.TheKodeToad, Devs.Ven, Devs.Elvyra],
@ -46,23 +51,26 @@ export default definePlugin({
} }
] ]
}, },
// Patch the profile popout username header to use our pronoun hook instead of Discord's pronouns
{ {
find: ".Messages.USER_PROFILE_PRONOUNS", find: ".pronouns,children",
group: true,
replacement: [ replacement: [
{ {
match: /\.PANEL},/, match: /{user:(\i),[^}]*,pronouns:(\i),[^}]*}=\i.*?;(?=return)/,
replace: "$&[vcPronoun,vcPronounSource,vcHasPendingPronouns]=$self.useProfilePronouns(arguments[0].user?.id)," replace: "$&let vcPronounSource;[$2,vcPronounSource]=$self.useProfilePronouns($1.id);"
}, },
PRONOUN_TOOLTIP_PATCH
]
},
// Patch the profile modal username header to use our pronoun hook instead of Discord's pronouns
{
find: ".nameTagSmall)",
replacement: [
{ {
match: /text:\i\.\i.Messages.USER_PROFILE_PRONOUNS/, match: /\.getName\(\i\);(?<=displayProfile.{0,200})/,
replace: '$&+vcHasPendingPronouns?"":` (${vcPronounSource})`' replace: "$&const [vcPronounce,vcPronounSource]=$self.useProfilePronouns(arguments[0].user.id,true);if(arguments[0].displayProfile&&vcPronounce)arguments[0].displayProfile.pronouns=vcPronounce;"
}, },
{ PRONOUN_TOOLTIP_PATCH
match: /(\.pronounsText.+?children:)(\i)/,
replace: "$1vcHasPendingPronouns?$2:vcPronoun"
}
] ]
} }
], ],

View file

@ -21,16 +21,13 @@ import { debounce } from "@shared/debounce";
import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent"; import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent";
import { getCurrentChannel } from "@utils/discord"; import { getCurrentChannel } from "@utils/discord";
import { useAwaiter } from "@utils/react"; import { useAwaiter } from "@utils/react";
import { findStoreLazy } from "@webpack";
import { UserProfileStore, UserStore } from "@webpack/common"; import { UserProfileStore, UserStore } from "@webpack/common";
import { settings } from "./settings"; import { settings } from "./settings";
import { CachePronouns, PronounCode, PronounMapping, PronounsResponse } from "./types"; import { CachePronouns, PronounCode, PronounMapping, PronounsResponse } from "./types";
const UserSettingsAccountStore = findStoreLazy("UserSettingsAccountStore"); type PronounsWithSource = [string | null, string];
const EmptyPronouns: PronounsWithSource = [null, ""];
type PronounsWithSource = [pronouns: string | null, source: string, hasPendingPronouns: boolean];
const EmptyPronouns: PronounsWithSource = [null, "", false];
export const enum PronounsFormat { export const enum PronounsFormat {
Lowercase = "LOWERCASE", Lowercase = "LOWERCASE",
@ -78,15 +75,13 @@ export function useFormattedPronouns(id: string, useGlobalProfile: boolean = fal
onError: e => console.error("Fetching pronouns failed: ", e) onError: e => console.error("Fetching pronouns failed: ", e)
}); });
const hasPendingPronouns = UserSettingsAccountStore.getPendingPronouns() != null;
if (settings.store.pronounSource === PronounSource.PreferDiscord && discordPronouns) if (settings.store.pronounSource === PronounSource.PreferDiscord && discordPronouns)
return [discordPronouns, "Discord", hasPendingPronouns]; return [discordPronouns, "Discord"];
if (result && result !== PronounMapping.unspecified) if (result && result !== PronounMapping.unspecified)
return [result, "PronounDB", hasPendingPronouns]; return [result, "PronounDB"];
return [discordPronouns, "Discord", hasPendingPronouns]; return [discordPronouns, "Discord"];
} }
export function useProfilePronouns(id: string, useGlobalProfile: boolean = false): PronounsWithSource { export function useProfilePronouns(id: string, useGlobalProfile: boolean = false): PronounsWithSource {
@ -152,7 +147,7 @@ async function bulkFetchPronouns(ids: string[]): Promise<PronounsResponse> {
} }
} }
export function extractPronouns(pronounSet?: { [locale: string]: PronounCode[]; }): string { export function extractPronouns(pronounSet?: { [locale: string]: PronounCode[] }): string {
if (!pronounSet || !pronounSet.en) return PronounMapping.unspecified; if (!pronounSet || !pronounSet.en) return PronounMapping.unspecified;
// PronounDB returns an empty set instead of {sets: {en: ["unspecified"]}}. // PronounDB returns an empty set instead of {sets: {en: ["unspecified"]}}.
const pronouns = pronounSet.en; const pronouns = pronounSet.en;

View file

@ -20,16 +20,18 @@ import "./style.css";
import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { NavContextMenuPatchCallback } from "@api/ContextMenu";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { ExpandableHeader } from "@components/ExpandableHeader";
import { NotesIcon, OpenExternalIcon } from "@components/Icons"; import { NotesIcon, OpenExternalIcon } from "@components/Icons";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { Alerts, Button, Menu, Parser, TooltipContainer } from "@webpack/common"; import { Alerts, Button, Menu, Parser, TooltipContainer, useState } from "@webpack/common";
import { Guild, User } from "discord-types/general"; import { Guild, User } from "discord-types/general";
import { Auth, initAuth, updateAuth } from "./auth"; import { Auth, initAuth, updateAuth } from "./auth";
import { openReviewsModal } from "./components/ReviewModal"; import { openReviewsModal } from "./components/ReviewModal";
import ReviewsView from "./components/ReviewsView";
import { NotificationType } from "./entities"; import { NotificationType } from "./entities";
import { getCurrentUserInfo, readNotification } from "./reviewDbApi"; import { getCurrentUserInfo, readNotification } from "./reviewDbApi";
import { settings } from "./settings"; import { settings } from "./settings";
@ -76,25 +78,18 @@ export default definePlugin({
}, },
patches: [ patches: [
{
find: "showBorder:null",
replacement: {
match: /user:(\i),setNote:\i,canDM.+?\}\)/,
replace: "$&,$self.getReviewsComponent($1)"
}
},
{ {
find: ".BITE_SIZE,user:", find: ".BITE_SIZE,user:",
replacement: { replacement: {
match: /{profileType:\i\.\i\.BITE_SIZE,children:\[/, match: /(?<=\.BITE_SIZE,children:\[)\(0,\i\.jsx\)\(\i\.\i,\{user:(\i),/,
replace: "$&$self.BiteSizeReviewsButton({user:arguments[0].user})," replace: "$self.BiteSizeReviewsButton({user:$1}),$&"
}
},
{
find: ".FULL_SIZE,user:",
replacement: {
match: /{profileType:\i\.\i\.FULL_SIZE,children:\[/,
replace: "$&$self.BiteSizeReviewsButton({user:arguments[0].user}),"
}
},
{
find: ".PANEL,isInteractionSource:",
replacement: {
match: /{profileType:\i\.\i\.PANEL,children:\[/,
replace: "$&$self.BiteSizeReviewsButton({user:arguments[0].user}),"
} }
} }
], ],
@ -153,6 +148,31 @@ export default definePlugin({
}, 4000); }, 4000);
}, },
getReviewsComponent: ErrorBoundary.wrap((user: User) => {
const [reviewCount, setReviewCount] = useState<number>();
return (
<ExpandableHeader
headerText="User Reviews"
onMoreClick={() => openReviewsModal(user.id, user.username)}
moreTooltipText={
reviewCount && reviewCount > 50
? `View all ${reviewCount} reviews`
: "Open Review Modal"
}
onDropDownClick={state => settings.store.reviewsDropdownState = !state}
defaultState={settings.store.reviewsDropdownState}
>
<ReviewsView
discordId={user.id}
name={user.username}
onFetchReviews={r => setReviewCount(r.reviewCount)}
showInput
/>
</ExpandableHeader>
);
}, { message: "Failed to render Reviews" }),
BiteSizeReviewsButton: ErrorBoundary.wrap(({ user }: { user: User; }) => { BiteSizeReviewsButton: ErrorBoundary.wrap(({ user }: { user: User; }) => {
return ( return (
<TooltipContainer text="View Reviews"> <TooltipContainer text="View Reviews">

View file

@ -0,0 +1,6 @@
# ShowAllRoles
Display all roles on the new profiles instead of limiting them to the default two rows.
![image](https://github.com/Vendicated/Vencord/assets/71079641/3f021f03-c6f9-4fe5-83ac-a1891b5e4b37)

View file

@ -0,0 +1,23 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
export default definePlugin({
name: "ShowAllRoles",
description: "Show all roles in new profiles.",
authors: [Devs.Luna],
patches: [
{
find: ".Messages.VIEW_ALL_ROLES",
replacement: {
match: /(\i)\.slice\(0,\i\)/,
replace: "$1"
}
}
]
});

View file

@ -25,12 +25,15 @@ import { CopyIcon, LinkIcon } from "@components/Icons";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { copyWithToast } from "@utils/misc"; import { copyWithToast } from "@utils/misc";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findByPropsLazy } from "@webpack"; import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
import { Tooltip, UserProfileStore } from "@webpack/common"; import { Text, Tooltip, UserProfileStore } from "@webpack/common";
import { User } from "discord-types/general"; import { User } from "discord-types/general";
import { VerifiedIcon } from "./VerifiedIcon"; import { VerifiedIcon } from "./VerifiedIcon";
const Section = findComponentByCodeLazy(".lastSection", "children:");
const ThemeStore = findStoreLazy("ThemeStore");
const useLegacyPlatformType: (platform: string) => string = findByCodeLazy(".TWITTER_LEGACY:"); const useLegacyPlatformType: (platform: string) => string = findByCodeLazy(".TWITTER_LEGACY:");
const platforms: { get(type: string): ConnectionPlatform; } = findByPropsLazy("isSupported", "getByUrl"); const platforms: { get(type: string): ConnectionPlatform; } = findByPropsLazy("isSupported", "getByUrl");
const getProfileThemeProps = findByCodeLazy(".getPreviewThemeColors", "primaryColor:"); const getProfileThemeProps = findByCodeLazy(".getPreviewThemeColors", "primaryColor:");
@ -73,7 +76,7 @@ interface ConnectionPlatform {
} }
const profilePopoutComponent = ErrorBoundary.wrap( const profilePopoutComponent = ErrorBoundary.wrap(
(props: { user: User; displayProfile?: any; }) => ( (props: { user: User; displayProfile?: any; simplified?: boolean; }) => (
<ConnectionsComponent <ConnectionsComponent
{...props} {...props}
id={props.user.id} id={props.user.id}
@ -83,7 +86,17 @@ const profilePopoutComponent = ErrorBoundary.wrap(
{ noop: true } { noop: true }
); );
function ConnectionsComponent({ id, theme }: { id: string, theme: string; }) { const profilePanelComponent = ErrorBoundary.wrap(
(props: { id: string; simplified?: boolean; }) => (
<ConnectionsComponent
{...props}
theme={ThemeStore.theme}
/>
),
{ noop: true }
);
function ConnectionsComponent({ id, theme, simplified }: { id: string, theme: string, simplified?: boolean; }) {
const profile = UserProfileStore.getUserProfile(id); const profile = UserProfileStore.getUserProfile(id);
if (!profile) if (!profile)
return null; return null;
@ -92,14 +105,31 @@ function ConnectionsComponent({ id, theme }: { id: string, theme: string; }) {
if (!connections?.length) if (!connections?.length)
return null; return null;
return ( const connectionsContainer = (
<Flex style={{ <Flex style={{
marginTop: !simplified ? "8px" : undefined,
gap: getSpacingPx(settings.store.iconSpacing), gap: getSpacingPx(settings.store.iconSpacing),
flexWrap: "wrap" flexWrap: "wrap"
}}> }}>
{connections.map(connection => <CompactConnectionComponent connection={connection} theme={theme} />)} {connections.map(connection => <CompactConnectionComponent connection={connection} theme={theme} />)}
</Flex> </Flex>
); );
if (simplified)
return connectionsContainer;
return (
<Section>
<Text
tag="h2"
variant="eyebrow"
style={{ color: "var(--header-primary)" }}
>
Connections
</Text>
{connectionsContainer}
</Section>
);
} }
function CompactConnectionComponent({ connection, theme }: { connection: Connection, theme: string; }) { function CompactConnectionComponent({ connection, theme }: { connection: Connection, theme: string; }) {
@ -164,17 +194,31 @@ export default definePlugin({
name: "ShowConnections", name: "ShowConnections",
description: "Show connected accounts in user popouts", description: "Show connected accounts in user popouts",
authors: [Devs.TheKodeToad], authors: [Devs.TheKodeToad],
settings,
patches: [ patches: [
{ {
find: ".hasAvatarForGuild(null==", find: "{isUsingGuildBio:null!==(",
replacement: {
match: /,theme:\i\}\)(?=,.{0,150}setNote:)/,
replace: "$&,$self.profilePopoutComponent({ user: arguments[0].user, displayProfile: arguments[0].displayProfile })"
}
},
{
find: ".PROFILE_PANEL,",
replacement: {
// createElement(Divider, {}), createElement(NoteComponent)
match: /\(0,\i\.jsx\)\(\i\.\i,\{\}\).{0,100}setNote:(?=.+?channelId:(\i).id)/,
replace: "$self.profilePanelComponent({ id: $1.recipients[0] }),$&"
}
},
{
find: '"BiteSizeProfileBody"',
replacement: { replacement: {
match: /currentUser:\i,guild:\i}\)(?<=user:(\i),bio:null==(\i)\?.+?)/, match: /currentUser:\i,guild:\i}\)(?<=user:(\i),bio:null==(\i)\?.+?)/,
replace: "$&,$self.profilePopoutComponent({ user: $1, displayProfile: $2 })" replace: "$&,$self.profilePopoutComponent({ user: $1, displayProfile: $2, simplified: true })"
} }
} }
], ],
settings,
profilePopoutComponent, profilePopoutComponent,
profilePanelComponent
}); });

View file

@ -18,11 +18,12 @@
import "./VoiceChannelSection.css"; import "./VoiceChannelSection.css";
import { findByPropsLazy } from "@webpack"; import { findByCodeLazy, findByPropsLazy } from "@webpack";
import { Button, Forms, PermissionStore, Toasts } from "@webpack/common"; import { Button, Forms, PermissionStore, Toasts } from "@webpack/common";
import { Channel } from "discord-types/general"; import { Channel } from "discord-types/general";
const ChannelActions = findByPropsLazy("selectChannel", "selectVoiceChannel"); const ChannelActions = findByPropsLazy("selectChannel", "selectVoiceChannel");
const UserPopoutSection = findByCodeLazy(".lastSection", "children:");
const CONNECT = 1n << 20n; const CONNECT = 1n << 20n;
@ -33,8 +34,7 @@ interface VoiceChannelFieldProps {
} }
export const VoiceChannelSection = ({ channel, label, showHeader }: VoiceChannelFieldProps) => ( export const VoiceChannelSection = ({ channel, label, showHeader }: VoiceChannelFieldProps) => (
// @TODO The div is supposed to be a UserPopoutSection <UserPopoutSection>
<div>
{showHeader && <Forms.FormTitle className="vc-uvs-header">In a voice channel</Forms.FormTitle>} {showHeader && <Forms.FormTitle className="vc-uvs-header">In a voice channel</Forms.FormTitle>}
<Button <Button
className="vc-uvs-button" className="vc-uvs-button"
@ -57,5 +57,5 @@ export const VoiceChannelSection = ({ channel, label, showHeader }: VoiceChannel
> >
{label} {label}
</Button> </Button>
</div> </UserPopoutSection>
); );

View file

@ -84,7 +84,7 @@ export default definePlugin({
); );
}, },
patchProfilePopout: ({ user }: UserProps) => { patchPopout: ({ user }: UserProps) => {
const isSelfUser = user.id === UserStore.getCurrentUser().id; const isSelfUser = user.id === UserStore.getCurrentUser().id;
return ( return (
<div className={isSelfUser ? "vc-uvs-popout-margin-self" : ""}> <div className={isSelfUser ? "vc-uvs-popout-margin-self" : ""}>
@ -94,7 +94,21 @@ export default definePlugin({
}, },
patches: [ patches: [
// @TODO Maybe patch UserVoiceShow in simplified profile popout // above message box
// @TODO Patch new profile modal {
find: ".popularApplicationCommandIds,",
replacement: {
match: /(?<=,)(?=!\i&&!\i&&.{0,50}setNote:)/,
replace: "$self.patchPopout(arguments[0]),",
}
},
// below username
{
find: ".Messages.MUTUAL_GUILDS_WITH_END_COUNT", // Lazy-loaded
replacement: {
match: /\.body.+?displayProfile:\i}\),/,
replace: "$&$self.patchModal(arguments[0]),",
}
}
], ],
}); });

View file

@ -57,7 +57,14 @@ export default definePlugin({
settings, settings,
patches: [ patches: [
{ {
find: '.banner)==null?"COMPLETE"', find: ".NITRO_BANNER,",
replacement: {
match: /\?\(0,\i\.jsx\)\(\i,{type:\i,shown/,
replace: "&&$self.shouldShowBadge(arguments[0])$&"
}
},
{
find: ".banner)==null",
replacement: { replacement: {
match: /(?<=void 0:)\i.getPreviewBanner\(\i,\i,\i\)/, match: /(?<=void 0:)\i.getPreviewBanner\(\i,\i,\i\)/,
replace: "$self.patchBannerUrl(arguments[0])||$&" replace: "$self.patchBannerUrl(arguments[0])||$&"
@ -102,6 +109,10 @@ export default definePlugin({
if (this.userHasBackground(displayProfile?.userId)) return this.getImageUrl(displayProfile?.userId); if (this.userHasBackground(displayProfile?.userId)) return this.getImageUrl(displayProfile?.userId);
}, },
shouldShowBadge({ displayProfile, user }: any) {
return displayProfile?.banner && (!this.userHasBackground(user.id) || settings.store.nitroFirst);
},
userHasBackground(userId: string) { userHasBackground(userId: string) {
return !!this.data?.users[userId]; return !!this.data?.users[userId];
}, },

View file

@ -192,12 +192,31 @@ export default definePlugin({
}, },
all: true all: true
}, },
// Banners // Old Profiles Modal pfp
{ {
find: 'backgroundColor:"COMPLETE"', find: ".MODAL,hasProfileEffect",
replacement: { replacement: {
match: /(\.banner,.+?),style:{(?=.+?backgroundImage:null!=(\i)\?"url\("\.concat\(\2,)/, match: /\{src:(\i)(?=,avatarDecoration)/,
replace: (_, rest, bannerSrc) => `${rest},onClick:()=>${bannerSrc}!=null&&$self.openImage(${bannerSrc}),style:{cursor:${bannerSrc}!=null?"pointer":void 0,` replace: "{src:$1,onClick:()=>$self.openImage($1)"
}
},
// Banners
...[".NITRO_BANNER,", "=!1,canUsePremiumCustomization:"].map(find => ({
find,
replacement: {
// style: { backgroundImage: shouldShowBanner ? "url(".concat(bannerUrl,
match: /style:\{(?=backgroundImage:(null!=\i)\?"url\("\.concat\((\i),)/,
replace:
// onClick: () => shouldShowBanner && ev.target.style.backgroundImage && openImage(bannerUrl), style: { cursor: shouldShowBanner ? "pointer" : void 0,
'onClick:ev=>$1&&ev.target.style.backgroundImage&&$self.openImage($2),style:{cursor:$1?"pointer":void 0,'
}
})),
// Old User DMs "User Profile" popup in the right
{
find: ".avatarPositionPanel",
replacement: {
match: /(avatarWrapperNonUserBot.{0,50})onClick:(\i\|\|\i)\?void 0(?<=,avatarSrc:(\i).+?)/,
replace: "$1style:($2)?{cursor:\"pointer\"}:{},onClick:$2?()=>{$self.openImage($3)}"
} }
}, },
// Group DMs top small & large icon // Group DMs top small & large icon

View file

@ -35,7 +35,6 @@ export default definePlugin({
if (hasCtrl) switch (e.key) { if (hasCtrl) switch (e.key) {
case "t": case "t":
case "T": case "T":
if (!IS_VESKTOP) return;
e.preventDefault(); e.preventDefault();
if (e.shiftKey) { if (e.shiftKey) {
if (SelectedGuildStore.getGuildId()) NavigationRouter.transitionToGuild("@me"); if (SelectedGuildStore.getGuildId()) NavigationRouter.transitionToGuild("@me");
@ -48,15 +47,14 @@ export default definePlugin({
}); });
} }
break; break;
case "Tab":
if (!IS_VESKTOP) return;
const handler = e.shiftKey ? KeyBinds.SERVER_PREV : KeyBinds.SERVER_NEXT;
handler.action(e);
break;
case ",": case ",":
e.preventDefault(); e.preventDefault();
SettingsRouter.open("My Account"); SettingsRouter.open("My Account");
break; break;
case "Tab":
const handler = e.shiftKey ? KeyBinds.SERVER_PREV : KeyBinds.SERVER_NEXT;
handler.action(e);
break;
default: default:
if (e.key >= "1" && e.key <= "9") { if (e.key >= "1" && e.key <= "9") {
e.preventDefault(); e.preventDefault();

View file

@ -223,26 +223,9 @@ export interface Constants {
FriendsSections: Record<string, string>; FriendsSections: Record<string, string>;
} }
export type ActiveView = LiteralUnion<"emoji" | "gif" | "sticker" | "soundboard", string>;
export interface ExpressionPickerStoreState extends Record<PropertyKey, any> {
activeView: ActiveView | null;
lastActiveView: ActiveView | null;
activeViewType: any | null;
searchQuery: string;
isSearchSuggestion: boolean,
pickerId: string;
}
export interface ExpressionPickerStore { export interface ExpressionPickerStore {
openExpressionPicker(activeView: ActiveView, activeViewType?: any): void;
closeExpressionPicker(activeViewType?: any): void; closeExpressionPicker(activeViewType?: any): void;
toggleMultiExpressionPicker(activeViewType?: any): void; openExpressionPicker(activeView: LiteralUnion<"emoji" | "gif" | "sticker", string>, activeViewType?: any): void;
toggleExpressionPicker(activeView: ActiveView, activeViewType?: any): void;
setExpressionPickerView(activeView: ActiveView): void;
setSearchQuery(searchQuery: string, isSearchSuggestion?: boolean): void;
useExpressionPickerStore(): ExpressionPickerStoreState;
useExpressionPickerStore<T>(selector: (state: ExpressionPickerStoreState) => T): T;
} }
export interface BrowserWindowFeatures { export interface BrowserWindowFeatures {

View file

@ -16,6 +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 { canonicalizeMatch } from "@utils/patches";
import type { Channel } from "discord-types/general"; import type { Channel } from "discord-types/general";
// eslint-disable-next-line path-alias/no-relative // eslint-disable-next-line path-alias/no-relative
@ -161,14 +162,11 @@ export const InviteActions = findByPropsLazy("resolveInvite");
export const IconUtils: t.IconUtils = findByPropsLazy("getGuildBannerURL", "getUserAvatarURL"); export const IconUtils: t.IconUtils = findByPropsLazy("getGuildBannerURL", "getUserAvatarURL");
const openExpressionPickerMatcher = canonicalizeMatch(/setState\({activeView:\i,activeViewType:/);
// TODO: type
export const ExpressionPickerStore: t.ExpressionPickerStore = mapMangledModuleLazy("expression-picker-last-active-view", { export const ExpressionPickerStore: t.ExpressionPickerStore = mapMangledModuleLazy("expression-picker-last-active-view", {
openExpressionPicker: filters.byCode(/setState\({activeView:(?:(?!null)\i),activeViewType:/),
closeExpressionPicker: filters.byCode("setState({activeView:null"), closeExpressionPicker: filters.byCode("setState({activeView:null"),
toggleMultiExpressionPicker: filters.byCode(".EMOJI,"), openExpressionPicker: m => typeof m === "function" && openExpressionPickerMatcher.test(m.toString()),
toggleExpressionPicker: filters.byCode(/getState\(\)\.activeView===\i\?\i\(\):\i\(/),
setExpressionPickerView: filters.byCode(/setState\({activeView:\i,lastActiveView:/),
setSearchQuery: filters.byCode("searchQuery:"),
useExpressionPickerStore: filters.byCode("Object.is")
}); });
export const PopoutActions: t.PopoutActions = mapMangledModuleLazy('type:"POPOUT_WINDOW_OPEN"', { export const PopoutActions: t.PopoutActions = mapMangledModuleLazy('type:"POPOUT_WINDOW_OPEN"', {

View file

@ -233,7 +233,7 @@ function patchFactories(factories: Record<string, (module: any, exports: any, re
logger.error("Error while firing callback for Webpack subscription:\n", err, filter, callback); logger.error("Error while firing callback for Webpack subscription:\n", err, filter, callback);
} }
} }
} as any as { toString: () => string, original: any, (...args: any[]): void; $$vencordPatchedSource?: string; }; } as any as { toString: () => string, original: any, (...args: any[]): void; };
factory.toString = originalMod.toString.bind(originalMod); factory.toString = originalMod.toString.bind(originalMod);
factory.original = originalMod; factory.original = originalMod;
@ -354,17 +354,5 @@ function patchFactories(factories: Record<string, (module: any, exports: any, re
if (!patch.all) patches.splice(i--, 1); if (!patch.all) patches.splice(i--, 1);
} }
if (IS_DEV) {
if (mod !== originalMod) {
factory.$$vencordPatchedSource = String(mod);
} else if (wreq != null) {
const existingFactory = wreq.m[id];
if (existingFactory != null) {
factory.$$vencordPatchedSource = existingFactory.$$vencordPatchedSource;
}
}
}
} }
} }

View file

@ -38,15 +38,15 @@ export let cache: WebpackInstance["c"];
export type FilterFn = (mod: any) => boolean; export type FilterFn = (mod: any) => boolean;
export type PropsFilter = Array<string>; type PropsFilter = Array<string>;
export type CodeFilter = Array<string | RegExp>; type CodeFilter = Array<string | RegExp>;
export type StoreNameFilter = string; type StoreNameFilter = string;
export const stringMatches = (s: string, filter: CodeFilter) => const stringMatches = (s: string, filter: CodeFilter) =>
filter.every(f => filter.every(f =>
typeof f === "string" typeof f === "string"
? s.includes(f) ? s.includes(f)
: (f.global && (f.lastIndex = 0), f.test(s)) : f.test(s)
); );
export const filters = { export const filters = {
@ -258,8 +258,6 @@ export const findBulk = traceFunction("findBulk", function findBulk(...filterFns
* @returns string or null * @returns string or null
*/ */
export const findModuleId = traceFunction("findModuleId", function findModuleId(...code: CodeFilter) { export const findModuleId = traceFunction("findModuleId", function findModuleId(...code: CodeFilter) {
code = code.map(canonicalizeMatch);
for (const id in wreq.m) { for (const id in wreq.m) {
if (stringMatches(wreq.m[id].toString(), code)) return id; if (stringMatches(wreq.m[id].toString(), code)) return id;
} }
@ -454,9 +452,12 @@ export function findExportedComponentLazy<T extends object = any>(...props: Prop
* }) * })
*/ */
export const mapMangledModule = traceFunction("mapMangledModule", function mapMangledModule<S extends string>(code: string | RegExp | CodeFilter, mappers: Record<S, FilterFn>): Record<S, any> { export const mapMangledModule = traceFunction("mapMangledModule", function mapMangledModule<S extends string>(code: string | RegExp | CodeFilter, mappers: Record<S, FilterFn>): Record<S, any> {
if (!Array.isArray(code)) code = [code];
code = code.map(canonicalizeMatch);
const exports = {} as Record<S, any>; const exports = {} as Record<S, any>;
const id = findModuleId(...Array.isArray(code) ? code : [code]); const id = findModuleId(...code);
if (id === null) if (id === null)
return exports; return exports;
@ -605,8 +606,6 @@ export function waitFor(filter: string | PropsFilter | FilterFn, callback: Callb
* @returns Mapping of found modules * @returns Mapping of found modules
*/ */
export function search(...code: CodeFilter) { export function search(...code: CodeFilter) {
code = code.map(canonicalizeMatch);
const results = {} as Record<number, Function>; const results = {} as Record<number, Function>;
const factories = wreq.m; const factories = wreq.m;