FakeProfileThemes: fix error when own profile is not loaded (#3514)

Co-authored-by: V <vendicated@riseup.net>
This commit is contained in:
sadan4 2025-07-10 16:38:27 -04:00 committed by dorkbutt
parent 8ec5b0a8d8
commit 8411026c51
No known key found for this signature in database
11 changed files with 297 additions and 126 deletions

View file

@ -22,13 +22,14 @@ import "./index.css";
import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { fetchUserProfile } from "@utils/discord";
import { Margins } from "@utils/margins";
import { classes, copyWithToast } from "@utils/misc";
import { useAwaiter } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types";
import { User } from "@vencord/discord-types";
import { User, UserProfile } from "@vencord/discord-types";
import { findComponentByCodeLazy } from "@webpack";
import { Button, ColorPicker, Flex, Forms, React, Text, UserProfileStore, UserStore, useState } from "@webpack/common";
import { ReactElement } from "react";
import virtualMerge from "virtual-merge";
interface Colors {
@ -81,14 +82,6 @@ const settings = definePluginSettings({
}
});
interface ColorPickerProps {
color: number | null;
label: ReactElement<any>;
showEyeDropper?: boolean;
suggestedColors?: string[];
onChange(value: number | null): void;
}
// I can't be bothered to figure out the semantics of this component. The
// functions surely get some event argument sent to them and they likely aren't
// all required. If anyone who wants to use this component stumbles across this
@ -106,6 +99,100 @@ interface ProfileModalProps {
const ProfileModal = findComponentByCodeLazy<ProfileModalProps>("isTryItOutFlow:", "pendingThemeColors:", "pendingAvatarDecoration:", "EDIT_PROFILE_BANNER");
function SettingsAboutComponentWrapper() {
const [, , userProfileLoading] = useAwaiter(() => fetchUserProfile(UserStore.getCurrentUser().id));
return !userProfileLoading && <SettingsAboutComponent />;
}
function SettingsAboutComponent() {
const existingColors = decode(
UserProfileStore.getUserProfile(UserStore.getCurrentUser().id)?.bio ?? ""
) ?? [0, 0];
const [color1, setColor1] = useState(existingColors[0]);
const [color2, setColor2] = useState(existingColors[1]);
return (
<Forms.FormSection>
<Forms.FormTitle tag="h3">Usage</Forms.FormTitle>
<Forms.FormText>
After enabling this plugin, you will see custom colors in
the profiles of other people using compatible plugins.{" "}
<br />
To set your own colors:
<ul>
<li>
use the color pickers below to choose your colors
</li>
<li> click the "Copy 3y3" button</li>
<li> paste the invisible text anywhere in your bio</li>
</ul><br />
<Forms.FormDivider
className={classes(Margins.top8, Margins.bottom8)}
/>
<Forms.FormTitle tag="h3">Color pickers</Forms.FormTitle>
<Flex
direction={Flex.Direction.HORIZONTAL}
style={{ gap: "1rem" }}
>
<ColorPicker
color={color1}
label={
<Text
variant={"text-xs/normal"}
style={{ marginTop: "4px" }}
>
Primary
</Text>
}
onChange={(color: number) => {
setColor1(color);
}}
/>
<ColorPicker
color={color2}
label={
<Text
variant={"text-xs/normal"}
style={{ marginTop: "4px" }}
>
Accent
</Text>
}
onChange={(color: number) => {
setColor2(color);
}}
/>
<Button
onClick={() => {
const colorString = encode(color1, color2);
copyWithToast(colorString);
}}
color={Button.Colors.PRIMARY}
size={Button.Sizes.XLARGE}
>
Copy 3y3
</Button>
</Flex>
<Forms.FormDivider
className={classes(Margins.top8, Margins.bottom8)}
/>
<Forms.FormTitle tag="h3">Preview</Forms.FormTitle>
<div className="vc-fpt-preview">
<ProfileModal
user={UserStore.getCurrentUser()}
pendingThemeColors={[color1, color2]}
onAvatarChange={() => { }}
onBannerChange={() => { }}
canUsePremiumCustomization={true}
hideExampleButton={true}
hideFakeActivity={true}
isTryItOutFlow={true}
/>
</div>
</Forms.FormText>
</Forms.FormSection>);
}
export default definePlugin({
name: "FakeProfileThemes",
@ -117,7 +204,7 @@ export default definePlugin({
replacement: {
match: /(?<=getUserProfile\(\i\){return )(.+?)(?=})/,
replace: "$self.colorDecodeHook($1)"
}
},
},
{
find: "#{intl::USER_SETTINGS_RESET_PROFILE_THEME}",
@ -127,97 +214,12 @@ export default definePlugin({
}
}
],
settingsAboutComponent: () => {
const existingColors = decode(
UserProfileStore.getUserProfile(UserStore.getCurrentUser().id).bio
) ?? [0, 0];
const [color1, setColor1] = useState(existingColors[0]);
const [color2, setColor2] = useState(existingColors[1]);
return (
<Forms.FormSection>
<Forms.FormTitle tag="h3">Usage</Forms.FormTitle>
<Forms.FormText>
After enabling this plugin, you will see custom colors in
the profiles of other people using compatible plugins.{" "}
<br />
To set your own colors:
<ul>
<li>
use the color pickers below to choose your colors
</li>
<li> click the "Copy 3y3" button</li>
<li> paste the invisible text anywhere in your bio</li>
</ul><br />
<Forms.FormDivider
className={classes(Margins.top8, Margins.bottom8)}
/>
<Forms.FormTitle tag="h3">Color pickers</Forms.FormTitle>
<Flex
direction={Flex.Direction.HORIZONTAL}
style={{ gap: "1rem" }}
>
<ColorPicker
color={color1}
label={
<Text
variant={"text-xs/normal"}
style={{ marginTop: "4px" }}
>
Primary
</Text>
}
onChange={(color: number) => {
setColor1(color);
}}
/>
<ColorPicker
color={color2}
label={
<Text
variant={"text-xs/normal"}
style={{ marginTop: "4px" }}
>
Accent
</Text>
}
onChange={(color: number) => {
setColor2(color);
}}
/>
<Button
onClick={() => {
const colorString = encode(color1, color2);
copyWithToast(colorString);
}}
color={Button.Colors.PRIMARY}
size={Button.Sizes.XLARGE}
>
Copy 3y3
</Button>
</Flex>
<Forms.FormDivider
className={classes(Margins.top8, Margins.bottom8)}
/>
<Forms.FormTitle tag="h3">Preview</Forms.FormTitle>
<div className="vc-fpt-preview">
<ProfileModal
user={UserStore.getCurrentUser()}
pendingThemeColors={[color1, color2]}
onAvatarChange={() => { }}
onBannerChange={() => { }}
canUsePremiumCustomization={true}
hideExampleButton={true}
hideFakeActivity={true}
isTryItOutFlow={true}
/>
</div>
</Forms.FormText>
</Forms.FormSection>);
},
settingsAboutComponent: SettingsAboutComponentWrapper,
settings,
colorDecodeHook(user: User) {
if (user) {
colorDecodeHook(user: UserProfile) {
if (user?.bio) {
// don't replace colors if already set with nitro
if (settings.store.nitroFirst && user.themeColors) return user;
const colors = decode(user.bio);

View file

@ -25,7 +25,7 @@ import { CopyIcon, LinkIcon } from "@components/Icons";
import { Devs } from "@utils/constants";
import { copyWithToast } from "@utils/misc";
import definePlugin, { OptionType } from "@utils/types";
import { User } from "@vencord/discord-types";
import { ConnectedAccount, User } from "@vencord/discord-types";
import { findByCodeLazy, findByPropsLazy } from "@webpack";
import { Tooltip, UserProfileStore } from "@webpack/common";
@ -60,15 +60,8 @@ const settings = definePluginSettings({
}
});
interface Connection {
type: string;
id: string;
name: string;
verified: boolean;
}
interface ConnectionPlatform {
getPlatformUserUrl(connection: Connection): string;
getPlatformUserUrl(connection: ConnectedAccount): string;
icon: { lightSVG: string, darkSVG: string; };
}
@ -88,7 +81,7 @@ function ConnectionsComponent({ id, theme }: { id: string, theme: string; }) {
if (!profile)
return null;
const connections: Connection[] = profile.connectedAccounts;
const connections = profile.connectedAccounts;
if (!connections?.length)
return null;
@ -102,7 +95,7 @@ function ConnectionsComponent({ id, theme }: { id: string, theme: string; }) {
);
}
function CompactConnectionComponent({ connection, theme }: { connection: Connection, theme: string; }) {
function CompactConnectionComponent({ connection, theme }: { connection: ConnectedAccount, theme: string; }) {
const platform = platforms.get(useLegacyPlatformType(connection.type));
const url = platform.getPlatformUserUrl?.(connection);

View file

@ -22,6 +22,7 @@ import { isNonNullish } from "@utils/guards";
import { sleep } from "@utils/misc";
import { Queue } from "@utils/Queue";
import definePlugin from "@utils/types";
import { ProfileBadge } from "@vencord/discord-types";
import { Constants, FluxDispatcher, RestAPI, UserProfileStore, UserStore, useState } from "@webpack/common";
import { type ComponentType, type ReactNode } from "react";
@ -47,13 +48,6 @@ const badges: Record<string, ProfileBadge> = {
const fetching = new Set<string>();
const queue = new Queue(5);
interface ProfileBadge {
id: string;
description: string;
icon: string;
link?: string;
}
interface MentionProps {
data: {
userId?: string;
@ -102,10 +96,12 @@ async function getUser(id: string) {
// Fill in what we can deduce
const profile = UserProfileStore.getUserProfile(id);
profile.accentColor = user.accent_color;
profile.badges = fakeBadges;
profile.banner = user.banner;
profile.premiumType = user.premium_type;
if (profile) {
profile.accentColor = user.accent_color;
profile.badges = fakeBadges;
profile.banner = user.banner;
profile.premiumType = user.premium_type;
}
return userObj;
}

View file

@ -194,7 +194,7 @@ export async function fetchUserProfile(id: string, options?: FetchUserProfileOpt
});
FluxDispatcher.dispatch({ type: "USER_UPDATE", user: body.user });
await FluxDispatcher.dispatch({ type: "USER_PROFILE_FETCH_SUCCESS", ...body });
await FluxDispatcher.dispatch({ type: "USER_PROFILE_FETCH_SUCCESS", userProfile: body });
if (options?.guild_id && body.guild_member)
FluxDispatcher.dispatch({ type: "GUILD_MEMBER_PROFILE_UPDATE", guildId: options.guild_id, guildMember: body.guild_member });

View file

@ -42,7 +42,7 @@ export let GuildStore: t.GuildStore;
export let GuildRoleStore: t.GuildRoleStore;
export let GuildMemberStore: t.GuildMemberStore;
export let UserStore: t.UserStore;
export let UserProfileStore: GenericStore;
export let UserProfileStore: t.UserProfileStore;
export let SelectedChannelStore: t.SelectedChannelStore;
export let SelectedGuildStore: t.SelectedGuildStore;
export let ChannelStore: t.ChannelStore;