mirror of
https://github.com/Equicord/Equicord.git
synced 2025-02-26 01:58:52 -05:00
205 lines
8 KiB
TypeScript
205 lines
8 KiB
TypeScript
/*
|
|
* 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
|
|
});
|