Equicord/src/plugins/decor/index.tsx

164 lines
6.4 KiB
TypeScript
Raw Normal View History

2023-11-30 00:10:50 -05:00
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated, FieryFlames and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./ui/styles.css";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { UserStore } from "@webpack/common";
2023-11-30 00:10:50 -05:00
2024-04-17 14:29:47 -04:00
import { CDN_URL, RAW_SKU_ID, setBaseUrl, SKU_ID } from "./lib/constants";
2023-11-30 00:10:50 -05:00
import { useAuthorizationStore } from "./lib/stores/AuthorizationStore";
import { useCurrentUserDecorationsStore } from "./lib/stores/CurrentUserDecorationsStore";
import { useUserDecorAvatarDecoration, useUsersDecorationsStore } from "./lib/stores/UsersDecorationsStore";
import { settings } from "./settings";
2023-11-30 00:10:50 -05:00
import { setDecorationGridDecoration, setDecorationGridItem } from "./ui/components";
import DecorSection from "./ui/components/DecorSection";
export interface AvatarDecoration {
asset: string;
skuId: string;
}
export default definePlugin({
name: "Decor",
description: "Create and use your own custom avatar decorations, or pick your favorite from the presets.",
authors: [Devs.FieryFlames],
patches: [
// Patch MediaResolver to return correct URL for Decor avatar decorations
{
find: "getAvatarDecorationURL:",
replacement: {
2026-03-01 21:35:44 -05:00
match: /(?<=function \i\((\i)\){)(?=.{0,20}let{avatarDecoration)/,
2025-11-07 11:25:23 -05:00
replace: "const vcDecorDecoration=$self.getDecorAvatarDecorationURL($1);if(vcDecorDecoration)return vcDecorDecoration;"
2023-11-30 00:10:50 -05:00
}
},
// Patch profile customization settings to include Decor section
{
find: "DefaultCustomizationSections",
replacement: {
match: /(?<=#{intl::USER_SETTINGS_AVATAR_DECORATION}\)},"decoration"\),)/,
2023-11-30 00:10:50 -05:00
replace: "$self.DecorSection(),"
}
},
// Decoration modal module
{
2026-01-23 14:58:47 -05:00
find: "80,onlyAnimateOnHoverOrFocus:!",
2023-11-30 00:10:50 -05:00
replacement: [
{
2026-02-04 23:55:35 -05:00
match: /(?<==)\i=>{let{children.{20,200}isSelected:\i=!1.{0,5}\}=\i/,
replace: "$self.DecorationGridItem=$&",
},
{
2026-01-23 14:58:47 -05:00
match: /(?<==)\i=>{let{user:\i,avatarDecoration/,
replace: "$self.DecorationGridDecoration=$&",
2023-11-30 00:10:50 -05:00
},
// Remove NEW label from decor avatar decorations
{
match: /(?<=\.\i\.PURCHASE)(?=,)(?<=avatarDecoration:(\i).+?)/,
2024-03-28 10:30:29 -03:00
replace: "||$1.skuId===$self.SKU_ID"
2023-11-30 00:10:50 -05:00
}
]
},
{
find: "isAvatarDecorationAnimating:",
group: true,
replacement: [
// Add Decor avatar decoration hook to avatar decoration hook
{
2025-10-30 11:20:18 -04:00
match: /(?<=\.avatarDecoration,guildId:\i\}\)\),)(?<=user:(\i).+?)/,
2023-11-30 00:10:50 -05:00
replace: "vcDecorAvatarDecoration=$self.useUserDecorAvatarDecoration($1),"
},
// Use added hook
{
match: /(?<={avatarDecoration:).{1,20}?(?=,)(?<=avatarDecorationOverride:(\i).+?)/,
replace: "$1??vcDecorAvatarDecoration??($&)"
2023-11-30 00:10:50 -05:00
},
// Make memo depend on added hook
{
match: /(?<=size:\i}\),\[)/,
replace: "vcDecorAvatarDecoration,"
}
]
},
// Current user area, at bottom of channels/dm list
{
find: ".WIDGETS_RTC_UPSELL_COACHMARK),",
2023-11-30 00:10:50 -05:00
replacement: [
// Use Decor avatar decoration hook
{
2025-12-03 10:24:18 -05:00
match: /(?<=\i\)\({avatarDecoration:)\i(?=,)(?<=currentUser:(\i).+?)/,
2023-11-30 00:10:50 -05:00
replace: "$self.useUserDecorAvatarDecoration($1)??$&"
}
]
2025-11-11 09:26:35 -05:00
},
...[
'"Message Username"', // Messages
2026-01-24 00:53:46 +01:00
"#{intl::COLLECTIBLES_NAMEPLATE_PREVIEW_A11Y}", // Nameplate preview
"#{intl::COLLECTIBLES_PROFILE_PREVIEW_A11Y}", // Avatar preview
].map(find => ({
find,
2026-02-10 01:30:30 +01:00
replacement: {
match: /(?<=userValue:)((\i(?:\.author)?)\?\.avatarDecoration)/,
replace: "$self.useUserDecorAvatarDecoration($2)??$1"
}
})),
// Patch avatar decoration preview to display Decor avatar decorations as if they are purchased
{
find: "#{intl::PREMIUM_UPSELL_PROFILE_AVATAR_DECO_INLINE_UPSELL_DESCRIPTION}",
replacement: {
match: /(#{intl::PREMIUM_UPSELL_PROFILE_AVATAR_DECO_INLINE_UPSELL_DESCRIPTION}.+?return null!=(\i)&&\()(null==\i)/,
2026-02-05 11:38:17 -05:00
replace: (_, rest, avatarDecoration, hasPurchase) => `${rest}(${avatarDecoration}.skuId!==$self.SKU_ID&&${avatarDecoration}.skuId!==$self.RAW_SKU_ID&&${hasPurchase})`
}
}
2023-11-30 00:10:50 -05:00
],
settings,
flux: {
CONNECTION_OPEN: () => {
useAuthorizationStore.getState().init();
useCurrentUserDecorationsStore.getState().clear();
useUsersDecorationsStore.getState().fetch(UserStore.getCurrentUser().id, true);
},
USER_PROFILE_MODAL_OPEN: data => {
useUsersDecorationsStore.getState().fetch(data.userId, true);
},
},
set DecorationGridItem(e: any) {
setDecorationGridItem(e);
},
set DecorationGridDecoration(e: any) {
setDecorationGridDecoration(e);
},
SKU_ID,
2026-02-05 11:38:17 -05:00
RAW_SKU_ID,
2023-11-30 00:10:50 -05:00
useUserDecorAvatarDecoration,
async start() {
2024-04-17 14:29:47 -04:00
await setBaseUrl(settings.store.baseUrl);
2023-11-30 00:10:50 -05:00
useUsersDecorationsStore.getState().fetch(UserStore.getCurrentUser().id, true);
},
getDecorAvatarDecorationURL({ avatarDecoration, canAnimate }: { avatarDecoration: AvatarDecoration | null; canAnimate?: boolean; }) {
// Only Decor avatar decorations have this SKU ID
if (avatarDecoration?.skuId === SKU_ID) {
const parts = avatarDecoration.asset.split("_");
// Remove a_ prefix if it's animated and animation is disabled
if (avatarDecoration.asset.startsWith("a_") && !canAnimate) parts.shift();
return `${CDN_URL}/${parts.join("_")}.png`;
2023-11-30 00:10:50 -05:00
} else if (avatarDecoration?.skuId === RAW_SKU_ID) {
return avatarDecoration.asset;
}
},
DecorSection: ErrorBoundary.wrap(DecorSection, { noop: true })
2023-11-30 00:10:50 -05:00
});