fix: make compatible with post-doomsday Discord

This commit is contained in:
ryan-0324 2024-07-11 14:23:28 -04:00
parent ffebe32af5
commit 780b865e79
6 changed files with 128 additions and 154 deletions

View file

@ -6,23 +6,24 @@
import { copyWithToast } from "@utils/misc";
import { Button, showToast, Switch, UserStore, useState } from "@webpack/common";
import type { Guild } from "discord-types/general";
import { buildFPTE } from "../lib/fpte";
import { useAccentColor, usePrimaryColor, useProfileEffect, useShowPreview } from "../lib/profilePreview";
import { BuilderButton, BuilderColorButton, CustomizationSection, openProfileEffectModal, useAvatarColors } from ".";
export interface BuilderProps {
guildId?: string | undefined;
guild?: Guild | undefined;
}
export function Builder({ guildId }: BuilderProps) {
export function Builder({ guild }: BuilderProps) {
const [primaryColor, setPrimaryColor] = usePrimaryColor(null);
const [accentColor, setAccentColor] = useAccentColor(null);
const [effect, setEffect] = useProfileEffect(null);
const [preview, setPreview] = useShowPreview(true);
const [buildLegacy, setBuildLegacy] = useState(false);
const avatarColors = useAvatarColors(UserStore.getCurrentUser().getAvatarURL(guildId, 80));
const avatarColors = useAvatarColors(UserStore.getCurrentUser().getAvatarURL(guild?.id, 80));
return (
<>
@ -48,7 +49,7 @@ export function Builder({ guildId }: BuilderProps) {
} : undefined}
buttonProps={{
onClick() {
openProfileEffectModal(effect?.id, setEffect);
openProfileEffectModal(effect?.id, setEffect, guild);
}
}}
/>

View file

@ -26,7 +26,7 @@ export const BuilderColorButton = ({ label, color, setColor, suggestedColors }:
)}
>
{popoutProps => {
const hexColor = color?.toString(16).padStart(6, "0").padStart(7, "#");
const hexColor = color ? "#" + color.toString(16).padStart(6, "0") : undefined;
return (
<BuilderButton

View file

@ -5,7 +5,8 @@
*/
import { type ModalProps, openModal } from "@utils/modal";
import { extractAndLoadChunksLazy } from "@webpack";
import { extractAndLoadChunksLazy, findByCodeLazy, findComponentByCodeLazy } from "@webpack";
import type { Guild } from "discord-types/general";
import type { ComponentType, FunctionComponent, PropsWithChildren, ReactNode } from "react";
import type { ProfileEffectConfig } from "../lib/profileEffects";
@ -15,51 +16,49 @@ export * from "./BuilderButton";
export * from "./BuilderColorButton";
export * from "./settingsAboutComponent";
export interface CustomColorPickerProps {
value?: number | null | undefined;
onChange: (color: number) => void;
onClose?: (() => void) | undefined;
suggestedColors?: string[] | undefined;
middle?: ReactNode;
footer?: ReactNode;
showEyeDropper?: boolean | undefined;
}
export let CustomColorPicker: ComponentType<CustomColorPickerProps> = () => null;
export function setCustomColorPicker(comp: typeof CustomColorPicker) {
CustomColorPicker = comp;
}
export let useAvatarColors: (avatarURL: string, fillerColor?: string | undefined, desaturateColors?: boolean | undefined) => string[] = () => [];
export function setUseAvatarColors(hook: typeof useAvatarColors) {
useAvatarColors = hook;
}
export interface CustomizationSectionProps {
export interface CustomizationSectionProps extends PropsWithChildren {
borderType?: "limited" | "premium" | undefined;
className?: string | undefined;
description?: ReactNode;
disabled?: boolean | undefined /* = false */;
errors?: string[] | undefined;
forcedDivider?: boolean | undefined /* = false */;
hasBackground?: boolean | undefined /* = false */;
hideDivider?: boolean | undefined /* = false */;
showBorder?: boolean | undefined /* = false */;
showPremiumIcon?: boolean | undefined /* = false */;
title?: ReactNode;
titleIcon?: ReactNode;
titleId?: string | undefined;
description?: ReactNode;
}
export const CustomizationSection: ComponentType<CustomizationSectionProps>
= findByCodeLazy(".customizationSectionBackground");
export const useAvatarColors: (
avatarURL: string,
fillerColor?: string | undefined,
desaturateColors?: boolean | undefined /* = true */
) => string[] = findByCodeLazy(".palette[", ".desaturateUserColors");
export interface CustomColorPickerProps {
className?: string | undefined;
errors?: string[] | undefined;
disabled?: boolean | undefined;
hideDivider?: boolean | undefined;
showBorder?: boolean | undefined;
borderType?: "limited" | "premium" | undefined;
hasBackground?: boolean | undefined;
forcedDivider?: boolean | undefined;
showPremiumIcon?: boolean | undefined;
eagerUpdate?: boolean | undefined /* = false */;
footer?: ReactNode;
middle?: ReactNode;
onChange: (color: number) => void;
onClose?: (() => void) | undefined;
showEyeDropper?: boolean | null | undefined /* = false */;
suggestedColors?: string[] | undefined;
wrapperComponentType?: ComponentType | null | undefined;
value?: string | number | null | undefined;
}
export let CustomizationSection: ComponentType<PropsWithChildren<CustomizationSectionProps>> = () => null;
export function setCustomizationSection(comp: typeof CustomizationSection) {
CustomizationSection = comp;
}
export const CustomColorPicker = findComponentByCodeLazy<CustomColorPickerProps>(".customColorPicker");
export interface ProfileEffectModalProps extends ModalProps {
analyticsLocations?: string[] | undefined;
guild?: Guild | null | undefined;
initialSelectedEffectId?: string | undefined;
onApply: (effect: ProfileEffectConfig | null) => void;
}
@ -70,16 +69,20 @@ export function setProfileEffectModal(comp: typeof ProfileEffectModal) {
ProfileEffectModal = comp;
}
const requireProfileEffectModal = extractAndLoadChunksLazy(["openProfileEffectModal:function(){"]);
const requireProfileEffectModal = extractAndLoadChunksLazy([".openModalLazy", "initialSelectedEffectId:"]);
export function openProfileEffectModal(initialEffectId: ProfileEffectModalProps["initialSelectedEffectId"], onApply: ProfileEffectModalProps["onApply"]) {
requireProfileEffectModal().then(() => {
openModal(modalProps => (
<ProfileEffectModal
{...modalProps}
initialSelectedEffectId={initialEffectId}
onApply={onApply}
/>
));
});
export async function openProfileEffectModal(
initialEffectId: ProfileEffectModalProps["initialSelectedEffectId"],
onApply: ProfileEffectModalProps["onApply"],
guild?: ProfileEffectModalProps["guild"]
) {
await requireProfileEffectModal();
openModal(modalProps => (
<ProfileEffectModal
{...modalProps}
initialSelectedEffectId={initialEffectId}
guild={guild}
onApply={onApply}
/>
));
}

View file

@ -10,8 +10,8 @@ import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches";
import definePlugin, { OptionType } from "@utils/types";
import { useMemo } from "@webpack/common";
import { Builder, type BuilderProps, setCustomColorPicker, setCustomizationSection, setProfileEffectModal, settingsAboutComponent, setUseAvatarColors } from "./components";
import { ProfileEffectRecord, ProfileEffectStore, setProfileEffectRecord, setProfileEffectStore } from "./lib/profileEffects";
import { Builder, type BuilderProps, setProfileEffectModal, settingsAboutComponent } from "./components";
import { ProfileEffectRecord, ProfileEffectStore } from "./lib/profileEffects";
import { profilePreviewHook } from "./lib/profilePreview";
import { decodeAboutMeFPTEHook } from "./lib/userProfile";
@ -21,7 +21,7 @@ function replaceHelper(string: string, replaceArgs: [searchRegExp: RegExp, repla
const beforeReplace = result;
result = result.replace(
canonicalizeMatch(searchRegExp),
canonicalizeReplace(replaceString, "FakeProfileThemesAndEffects") as string
canonicalizeReplace(replaceString, "FakeProfileThemesAndEffects")
);
if (beforeReplace === result)
throw new Error("Replace had no effect: " + searchRegExp);
@ -76,67 +76,33 @@ export default definePlugin({
},
// Adds the FPTE Builder to the Server Profiles settings page
{
find: ".setNewPendingGuildIdentity",
find: '"guild should not be null"',
replacement: {
match: /\.sectionsContainer,.*?children:\[(?=.+?[{,]guild:(\i))/,
replace: "$&$self.addFPTEBuilder($1),"
}
},
// CustomizationSection
{
find: ".customizationSectionBackground",
replacement: {
match: /default:function\(\){return (\i)}.+?;/,
replace: "$&$self.CustomizationSection=$1;"
}
},
// CustomColorPicker
{
find: ".colorPickerSwatch",
replacement: {
match: /CustomColorPicker:function\(\){return (\i)}.+?[ ,;}]\1=(?!=)/,
replace: "$&$self.CustomColorPicker="
}
},
// useAvatarColors
{
find: "useAvatarColors:function(){",
replacement: {
match: /useAvatarColors:function\(\){return (\i)}.+?;/,
replace: "$&$self.useAvatarColors=$1;"
}
},
// ProfileEffectRecord
{
find: "isProfileEffectRecord:function(){",
replacement: {
match: /default:function\(\){return (\i)}.+(?=}$)/,
replace: "$&;$self.ProfileEffectRecord=$1"
}
},
// ProfileEffectStore
{
find: '"ProfileEffectStore"',
replacement: {
match: /function\(\i,(\i),.+[,;}]\1\.default=(?!=)/,
replace: "$&$self.ProfileEffectStore="
}
},
// ProfileEffectModal
{
find: "initialSelectedProfileEffectId",
replacement: {
match: /default:function\(\){return (\i)}(?=.+?(function \1\((?:.(?!function |}$))+\.jsxs?\)\((\i),.+?})(?:function |}$)).+?(function \3\(.+?})(?=function |}$).*(?=}$)/,
replace: (wpModule, modalRootName, ModalRoot, _modalInnerName, ModalInner) => (
`${wpModule}{$self.ProfileEffectModal=${modalRootName};`
+ replaceHelper(ModalRoot, [
// Required for the profile preview to show profile effects
[
/(?<=[{,]purchases:.+?}=).+?(?=,\i=|,{\i:|;)/,
"{isFetching:!1,categories:new Map,purchases:$self.getPurchases()}"
]
])
+ replaceHelper(ModalInner, [
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.getPurchases()}"
]
])
+ "}"
},
// Modal content
{
match: /(function \i\([^)]*\){(?:.(?!function ))*\.ModalContent,(?:.(?!function ))*}).*(?=}}$)/,
replace: (match, func) => match + replaceHelper(func, [
// Required to show the apply button
[
/(?<=[{,]purchase:.+?}=).+?(?=,\i=|,{\i:|;)/,
@ -149,7 +115,7 @@ export default definePlugin({
],
// Replaces the apply profile effect function with the modified version
[
/(?<=[{,]onApply:).+?\.setNewPendingProfileEffectId\)\((\i).+?(?=,\i:|}\))/,
/(?<=[{,]onApply:).*?\)\((\i).*?(?=,\i:|}\))/,
"()=>$self.onApply($1)"
],
// Required to show the apply button
@ -163,18 +129,17 @@ export default definePlugin({
"!1"
]
])
+ "}"
)
}
}
],
group: true
},
// ProfileEffectModalList
{
find: "selectedProfileEffectRef",
replacement: {
match: /function\(\i,(\i),.+[,;}]\1\.default=([^=].+?})(?=;|}$).*(?=}$)/,
replace: (wpModule, _wpModuleVar, List) => (
`${wpModule};$self.ProfileEffectModalList=`
+ replaceHelper(List, [
match: /function\(\i,(\i),.+[,;}]\1\.\i=([^=].+?})(?=;|}$).*(?=}$)/,
replace: (match, _, func) => `${match};$self.ProfileEffectModalList=`
+ 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
[
@ -182,14 +147,13 @@ export default definePlugin({
"$self.getListSections($&)"
]
])
)
}
}
],
addFPTEBuilder: (guildId?: BuilderProps["guildId"]) => settings.store.hideBuilder ? null : <Builder guildId={guildId} />,
addFPTEBuilder: (guild?: BuilderProps["guild"]) => settings.store.hideBuilder ? null : <Builder guild={guild} />,
onApply(_effectId: string | undefined) { },
onApply(_effectId: string | undefined) {},
set ProfileEffectModal(comp: Parameters<typeof setProfileEffectModal>[0]) {
setProfileEffectModal(props => {
this.onApply = effectId => {
@ -210,7 +174,7 @@ export default definePlugin({
[ProfileEffectStore.profileEffects]
),
getListSections: (origSections: any[]) => useMemo(
getListSections: (origSections: Record<string, any>[]) => useMemo(
() => {
origSections.splice(1);
origSections[0].items.splice(1);
@ -222,12 +186,6 @@ export default definePlugin({
[ProfileEffectStore.profileEffects]
),
set CustomizationSection(comp: Parameters<typeof setCustomizationSection>[0]) { setCustomizationSection(comp); },
set CustomColorPicker(comp: Parameters<typeof setCustomColorPicker>[0]) { setCustomColorPicker(comp); },
set useAvatarColors(hook: Parameters<typeof setUseAvatarColors>[0]) { setUseAvatarColors(hook); },
set ProfileEffectRecord(obj: Parameters<typeof setProfileEffectRecord>[0]) { setProfileEffectRecord(obj); },
set ProfileEffectStore(store: Parameters<typeof setProfileEffectStore>[0]) { setProfileEffectStore(store); },
settingsAboutComponent,
settings,
decodeAboutMeFPTEHook,

View file

@ -4,7 +4,38 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { findByCodeLazy, findStoreLazy } from "@webpack";
import type { FluxStore } from "@webpack/types";
import type { SnakeCasedProperties } from "type-fest";
export const ProfileEffectStore: FluxStore & {
canFetch: () => boolean;
getProfileEffectById: (effectId: string) => ProfileEffect | undefined;
hasFetched: () => boolean;
readonly fetchError: Error | undefined;
readonly isFetching: boolean;
readonly profileEffects: ProfileEffect[];
readonly tryItOutId: string | null;
} = findStoreLazy("ProfileEffectStore");
export const ProfileEffectRecord: {
new (profileEffectProperties: ProfileEffectProperties): ProfileEffectRecordInstance;
fromServer: (profileEffectFromServer: SnakeCasedProperties<ProfileEffectProperties>) => ProfileEffectRecordInstance;
} = findByCodeLazy(",this.type=", ".PROFILE_EFFECT");
type ProfileEffectProperties = Omit<ProfileEffectRecordInstance, "type">;
interface ProfileEffectRecordInstance {
id: string;
skuId: string;
type: CollectiblesItemType.PROFILE_EFFECT;
}
export interface ProfileEffect {
config: ProfileEffectConfig;
id: string;
skuId: string;
}
export interface ProfileEffectConfig {
accessibilityLabel: string;
@ -30,31 +61,12 @@ export interface ProfileEffectConfig {
staticFrameSrc?: string;
thumbnailPreviewSrc: string;
title: string;
type: 1;
type: CollectiblesItemType.PROFILE_EFFECT;
}
export interface ProfileEffect extends Pick<ProfileEffectConfig, "id"> {
config: ProfileEffectConfig;
skuId: ProfileEffectConfig["sku_id"];
}
export let ProfileEffectRecord: {
new (effect: Omit<ProfileEffect, "config">): typeof effect & Pick<ProfileEffectConfig, "type">;
fromServer: (effect: Pick<ProfileEffectConfig, "id" | "sku_id">) => Omit<ProfileEffect, "config"> & Pick<ProfileEffectConfig, "type">;
};
export function setProfileEffectRecord(obj: typeof ProfileEffectRecord) {
ProfileEffectRecord = obj;
}
export let ProfileEffectStore: FluxStore & {
readonly isFetching: boolean;
readonly fetchError: Error | undefined;
readonly profileEffects: ProfileEffect[];
readonly tryItOutId: string | null;
getProfileEffectById: (effectId: string) => ProfileEffect | undefined;
};
export function setProfileEffectStore(store: typeof ProfileEffectStore) {
ProfileEffectStore = store;
export const enum CollectiblesItemType {
AVATAR_DECORATION = 0,
PROFILE_EFFECT = 1,
NONE = 100,
BUNDLE = 1_000,
}

View file

@ -60,7 +60,7 @@ export function useShowPreview(initialState: typeof showPreview) {
] as const;
}
export function profilePreviewHook(props: any) {
export function profilePreviewHook(props: Record<string, any>) {
if (showPreview) {
if (primaryColor !== null) {
props.pendingThemeColors = [primaryColor, accentColor ?? primaryColor];