Equicord/src/equicordplugins/fakeProfileThemesAndEffects/index.tsx

206 lines
8 KiB
TypeScript
Raw Normal View History

2024-10-19 15:49:41 -04:00
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { definePluginSettings } from "@api/Settings";
import { EquicordDevs } from "@utils/constants";
import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches";
import definePlugin, { OptionType } from "@utils/types";
import { useMemo } from "@webpack/common";
import { Builder, type BuilderProps, setProfileEffectModal, settingsAboutComponent } from "./components";
import { ProfileEffectRecord, ProfileEffectStore } from "./lib/profileEffects";
import { profilePreviewHook } from "./lib/profilePreview";
import { decodeAboutMeFPTEHook } from "./lib/userProfile";
function replaceHelper(
string: string,
replaceArgs: readonly (readonly [searchRegExp: RegExp, replaceString: string])[]
) {
let result = string;
for (const [searchRegExp, replaceString] of replaceArgs) {
const beforeReplace = result;
result = result.replace(
canonicalizeMatch(searchRegExp),
canonicalizeReplace(replaceString, "FakeProfileThemesAndEffects")
);
if (beforeReplace === result)
throw new Error("Replace had no effect: " + searchRegExp);
}
return result;
}
export const settings = definePluginSettings({
prioritizeNitro: {
description: "Source to prioritize",
type: OptionType.SELECT,
options: [
{ label: "Nitro", value: true },
{ label: "About Me", value: false, default: true }
]
},
hideBuilder: {
description: "Hide the FPTE Builder in the User Profile and Server Profiles settings pages",
type: OptionType.BOOLEAN,
default: false
}
});
export default definePlugin({
name: "FakeProfileThemesAndEffects",
description: "Allows profile theming and the usage of profile effects by hiding the colors and effect ID in your About Me using invisible, zero-width characters",
authors: [EquicordDevs.ryan],
patches: [
// Patches UserProfileStore.getUserProfile
{
find: '"UserProfileStore"',
replacement: {
match: /([{}]getUserProfile\([^)]*\){return) ?([^}]+)/,
replace: "$1 $self.decodeAboutMeFPTEHook($2)"
}
},
// Patches ProfileCustomizationPreview
{
find: ".EDIT_PROFILE_BANNER})",
replacement: {
match: /:function\(\){return (\i)}.+function \1\((\i)\){/,
replace: "$&$self.profilePreviewHook($2);"
}
},
// Adds the FPTE Builder to the User Profile settings page
{
find: '"DefaultCustomizationSections"',
replacement: {
match: /\.sectionsContainer,.*?children:\[/,
replace: "$&$self.addFPTEBuilder(),"
}
},
// Adds the FPTE Builder to the Server Profiles settings page
{
find: '"guild should not be null"',
replacement: {
match: /\.sectionsContainer,.*?children:\[(?=.+?[{,]guild:(\i))/,
replace: "$&$self.addFPTEBuilder($1),"
}
},
// ProfileEffectModal
{
find: "initialSelectedProfileEffectId:",
group: true,
replacement: [
// Modal root
{
match: /(function (\i)\([^)]*\){(?:.(?!function |}$))*\.ModalRoot,(?:.(?!function |}$))*}).*(?=})/,
replace: (match, func, funcName) => `${match}(()=>{$self.ProfileEffectModal=${funcName};`
+ replaceHelper(func, [
// Required for the profile preview to show profile effects
[
/(?<=[{,]purchases:.+?}=).+?(?=,\i=|,{\i:|;)/,
"{isFetching:!1,categories:new Map,purchases:$self.usePurchases()}"
]
])
+ "})()"
},
// Modal content
{
match: /(function \i\([^)]*\){(?:.(?!function ))*\.ModalContent,(?:.(?!function ))*}).*(?=}\))/,
replace: (match, func) => match + replaceHelper(func, [
// Required to show the apply button
[
/(?<=[{,]purchase:.+?}=).+?(?=,\i=|,{\i:|;)/,
"{purchase:{purchasedAt:new Date}}"
],
// Replaces the profile effect list with the modified version
[
/(?<=\.jsxs?\)\()[^,]+(?=,{(?:(?:.(?!\.jsxs?\)))+,)?onSelect:)/,
"$self.ProfileEffectSelection"
],
// Replaces the apply profile effect function with the modified version
[
/(?<=[{,]onApply:).*?\)\((\i).*?(?=,\i:|}\))/,
"()=>$self.onApply($1)"
],
// Required to show the apply button
[
/(?<=[{,]canUseCollectibles:).+?(?=,\i:|}\))/,
"!0"
],
// Required to enable the apply button
[
/(?<=[{,]disableApplyButton:).+?(?=,\i:|}\))/,
"!1"
]
])
}
]
},
// ProfileEffectSelection
{
find: ".presetEffectBackground",
replacement: {
match: /function\(\i,(\i),.+[,;}]\1\.\i=([^=].+?})(?=;|}$).*(?=}$)/,
replace: (match, _, func) => `${match};$self.ProfileEffectSelection=`
+ replaceHelper(func, [
// Removes the "Exclusive to Nitro" and "Preview The Shop" sections
// Adds every profile effect to the "Your Decorations" section and removes the "Shop" button
[
/(?<=[ ,](\i)=).+?(?=(?:,\i=|,{\i:|;).+?:\1\.map\()/,
"$self.useProfileEffectSections($&)"
]
])
}
},
// Patches ProfileEffectPreview
{
find: ".effectDescriptionContainer",
replacement: {
// Add back removed "forProfileEffectModal" property
match: /(?<=[{,])(?=pendingProfileEffectId:)/,
replace: "forProfileEffectModal:!0,"
}
}
],
addFPTEBuilder: (guild?: BuilderProps["guild"]) => settings.store.hideBuilder ? null : <Builder guild={guild} />,
onApply(_effectId?: string) { },
set ProfileEffectModal(comp: Parameters<typeof setProfileEffectModal>[0]) {
setProfileEffectModal(props => {
this.onApply = effectId => {
props.onApply(effectId ? ProfileEffectStore.getProfileEffectById(effectId)!.config : null);
props.onClose();
};
return comp(props);
});
},
ProfileEffectSelection: () => null,
usePurchases: () => useMemo(
() => new Map(ProfileEffectStore.profileEffects.map(effect => [
effect.id,
{ items: new ProfileEffectRecord(effect) }
])),
[ProfileEffectStore.profileEffects]
),
useProfileEffectSections: (origSections: Record<string, any>[]) => useMemo(
() => {
origSections.splice(1);
origSections[0].items.splice(1);
for (const effect of ProfileEffectStore.profileEffects)
origSections[0].items.push(new ProfileEffectRecord(effect));
return origSections;
},
[ProfileEffectStore.profileEffects]
),
settings,
replaceHelper,
settingsAboutComponent,
decodeAboutMeFPTEHook,
profilePreviewHook
});