mirror of
https://github.com/Equicord/Equicord.git
synced 2025-02-25 01:28:52 -05:00
Remove FakeProfileThemesAndEffects
This commit is contained in:
parent
6a9a0b07c7
commit
7206502f4d
20 changed files with 94 additions and 1005 deletions
|
@ -10,7 +10,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
||||||
|
|
||||||
### Extra included plugins
|
### Extra included plugins
|
||||||
<details>
|
<details>
|
||||||
<summary>148 additional plugins</summary>
|
<summary>147 additional plugins</summary>
|
||||||
|
|
||||||
### All Platforms
|
### All Platforms
|
||||||
- AllCallTimers by MaxHerbold & D3SOX
|
- AllCallTimers by MaxHerbold & D3SOX
|
||||||
|
@ -51,7 +51,6 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
||||||
- Encryptcord by Inbestigator
|
- Encryptcord by Inbestigator
|
||||||
- EquicordCSS by thororen, Panniku, Dablulite, Coolesding, MiniDiscordThemes, LuckFire, gold_me
|
- EquicordCSS by thororen, Panniku, Dablulite, Coolesding, MiniDiscordThemes, LuckFire, gold_me
|
||||||
- ExportContacts by dat_insanity
|
- ExportContacts by dat_insanity
|
||||||
- FakeProfileThemesAndEffects by ryan
|
|
||||||
- FindReply by newwares
|
- FindReply by newwares
|
||||||
- FixFileExtensions by thororen
|
- FixFileExtensions by thororen
|
||||||
- FrequentQuickSwitcher by Samwich
|
- FrequentQuickSwitcher by Samwich
|
||||||
|
|
|
@ -8,7 +8,6 @@ import { classNameFactory } from "@api/Styles";
|
||||||
import { getIntlMessage } from "@utils/discord";
|
import { getIntlMessage } from "@utils/discord";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import { closeModal, openModal } from "@utils/modal";
|
import { closeModal, openModal } from "@utils/modal";
|
||||||
import { findByPropsLazy } from "@webpack";
|
|
||||||
import { Avatar, ChannelStore, ContextMenuApi, FluxDispatcher, GuildStore, Menu, ReadStateStore, ReadStateUtils, Text, Tooltip, useDrag, useDrop, useEffect, useRef, UserStore } from "@webpack/common";
|
import { Avatar, ChannelStore, ContextMenuApi, FluxDispatcher, GuildStore, Menu, ReadStateStore, ReadStateUtils, Text, Tooltip, useDrag, useDrop, useEffect, useRef, UserStore } from "@webpack/common";
|
||||||
|
|
||||||
import { BasicChannelTabsProps, Bookmark, BookmarkFolder, BookmarkProps, CircleQuestionIcon, isBookmarkFolder, settings, switchChannel, useBookmarks } from "../util";
|
import { BasicChannelTabsProps, Bookmark, BookmarkFolder, BookmarkProps, CircleQuestionIcon, isBookmarkFolder, settings, switchChannel, useBookmarks } from "../util";
|
||||||
|
@ -17,7 +16,23 @@ import { BookmarkContextMenu, EditModal } from "./ContextMenus";
|
||||||
|
|
||||||
const cl = classNameFactory("vc-channeltabs-");
|
const cl = classNameFactory("vc-channeltabs-");
|
||||||
|
|
||||||
const { StarIcon } = findByPropsLazy("StarIcon");
|
function StarIcon({ className = "", ...props }) {
|
||||||
|
return <svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
className={className}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />
|
||||||
|
</svg >;
|
||||||
|
}
|
||||||
|
|
||||||
function FolderIcon({ fill }: { fill: string; }) {
|
function FolderIcon({ fill }: { fill: string; }) {
|
||||||
return (
|
return (
|
||||||
|
@ -289,9 +304,7 @@ export default function BookmarkContainer(props: BasicChannelTabsProps & { userI
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<StarIcon
|
<StarIcon
|
||||||
height={20}
|
className={isCurrentChannelBookmarked ? cl("bookmark-star-checked") : cl("bookmark-star")}
|
||||||
width={20}
|
|
||||||
colorClass={isCurrentChannelBookmarked ? cl("bookmark-star-checked") : cl("bookmark-star")}
|
|
||||||
/>
|
/>
|
||||||
</button>}
|
</button>}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
|
@ -15,10 +15,11 @@ import { ChannelTabsProps, CircleQuestionIcon, closeTab, isTabSelected, moveDrag
|
||||||
import { TabContextMenu } from "./ContextMenus";
|
import { TabContextMenu } from "./ContextMenus";
|
||||||
|
|
||||||
const ThreeDots = findComponentByCodeLazy(".dots,", "dotRadius:");
|
const ThreeDots = findComponentByCodeLazy(".dots,", "dotRadius:");
|
||||||
const { getBadgeWidthForValue } = findByPropsLazy("getBadgeWidthForValue");
|
|
||||||
const dotStyles = findByPropsLazy("numberBadge", "textBadge");
|
const dotStyles = findByPropsLazy("numberBadge", "textBadge");
|
||||||
|
|
||||||
const { FriendsIcon } = findByPropsLazy("FriendsIcon");
|
function FriendsIcon() {
|
||||||
|
return <svg className="linkButtonIcon_c91bad" aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path fill="currentColor" d="M13 10a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z"></path><path fill="currentColor" d="M3 5v-.75C3 3.56 3.56 3 4.25 3s1.24.56 1.33 1.25C6.12 8.65 9.46 12 13 12h1a8 8 0 0 1 8 8 2 2 0 0 1-2 2 .21.21 0 0 1-.2-.15 7.65 7.65 0 0 0-1.32-2.3c-.15-.2-.42-.06-.39.17l.25 2c.02.15-.1.28-.25.28H9a2 2 0 0 1-2-2v-2.22c0-1.57-.67-3.05-1.53-4.37A15.85 15.85 0 0 1 3 5Z"></path></svg>;
|
||||||
|
}
|
||||||
const ChannelTypeIcon = findComponentByCodeLazy(".iconContainerWithGuildIcon,");
|
const ChannelTypeIcon = findComponentByCodeLazy(".iconContainerWithGuildIcon,");
|
||||||
|
|
||||||
const cl = classNameFactory("vc-channeltabs-");
|
const cl = classNameFactory("vc-channeltabs-");
|
||||||
|
@ -71,7 +72,7 @@ export const NotificationDot = ({ channelIds }: { channelIds: string[]; }) => {
|
||||||
data-has-mention={!!mentionCount}
|
data-has-mention={!!mentionCount}
|
||||||
className={classes(dotStyles.numberBadge, dotStyles.baseShapeRound)}
|
className={classes(dotStyles.numberBadge, dotStyles.baseShapeRound)}
|
||||||
style={{
|
style={{
|
||||||
width: getBadgeWidthForValue(mentionCount || unreadCount)
|
width: 16
|
||||||
}}
|
}}
|
||||||
ref={node => node?.style.setProperty("background-color",
|
ref={node => node?.style.setProperty("background-color",
|
||||||
mentionCount ? "var(--red-400)" : "var(--brand-500)", "important"
|
mentionCount ? "var(--red-400)" : "var(--brand-500)", "important"
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { useForceUpdater } from "@utils/react";
|
import { useForceUpdater } from "@utils/react";
|
||||||
import { findByPropsLazy } from "@webpack";
|
|
||||||
import { Button, ContextMenuApi, Flex, FluxDispatcher, Forms, useCallback, useEffect, useRef, UserStore, useState } from "@webpack/common";
|
import { Button, ContextMenuApi, Flex, FluxDispatcher, Forms, useCallback, useEffect, useRef, UserStore, useState } from "@webpack/common";
|
||||||
|
|
||||||
import { BasicChannelTabsProps, ChannelTabsProps, createTab, handleChannelSwitch, openedTabs, openStartupTabs, saveTabs, settings, setUpdaterFunction, useGhostTabs } from "../util";
|
import { BasicChannelTabsProps, ChannelTabsProps, createTab, handleChannelSwitch, openedTabs, openStartupTabs, saveTabs, settings, setUpdaterFunction, useGhostTabs } from "../util";
|
||||||
|
@ -16,7 +15,23 @@ import { BasicContextMenu } from "./ContextMenus";
|
||||||
|
|
||||||
type TabSet = Record<string, ChannelTabsProps[]>;
|
type TabSet = Record<string, ChannelTabsProps[]>;
|
||||||
|
|
||||||
const { PlusSmallIcon } = findByPropsLazy("PlusSmallIcon");
|
function PlusSmallIcon() {
|
||||||
|
return <svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
>
|
||||||
|
<line x1="12" y1="5" x2="12" y2="19" />
|
||||||
|
<line x1="5" y1="12" x2="19" y2="12" />
|
||||||
|
</svg>;
|
||||||
|
}
|
||||||
|
|
||||||
const cl = classNameFactory("vc-channeltabs-");
|
const cl = classNameFactory("vc-channeltabs-");
|
||||||
|
|
||||||
export default function ChannelsTabsContainer(props: BasicChannelTabsProps) {
|
export default function ChannelsTabsContainer(props: BasicChannelTabsProps) {
|
||||||
|
@ -75,7 +90,7 @@ export default function ChannelsTabsContainer(props: BasicChannelTabsProps) {
|
||||||
onClick={() => createTab(props, true)}
|
onClick={() => createTab(props, true)}
|
||||||
className={cl("button", "new-button", "hoverable")}
|
className={cl("button", "new-button", "hoverable")}
|
||||||
>
|
>
|
||||||
<PlusSmallIcon height={20} width={20} />
|
<PlusSmallIcon />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{GhostTabs}
|
{GhostTabs}
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { OptionType } from "@utils/types";
|
import { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
|
||||||
|
|
||||||
import { ChannelTabsPreview } from "../components/ChannelTabsContainer";
|
import { ChannelTabsPreview } from "../components/ChannelTabsContainer";
|
||||||
|
|
||||||
|
@ -74,4 +73,21 @@ export const settings = definePluginSettings({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { CircleQuestionIcon } = findByPropsLazy("CircleQuestionIcon");
|
export function CircleQuestionIcon({ height = 24, width = 24 }) {
|
||||||
|
return <svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<path d="M12 17h.01" />
|
||||||
|
<path d="M12 13c1.38 0 2.5-1.12 2.5-2.5S13.38 8 12 8s-2.5 1.12-2.5 2.5" />
|
||||||
|
</svg>;
|
||||||
|
|
||||||
|
}
|
|
@ -1,109 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2024 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { copyWithToast } from "@utils/misc";
|
|
||||||
import { Button, showToast, Switch, UserStore, useState, useToken } from "@webpack/common";
|
|
||||||
import type { Guild } from "discord-types/general";
|
|
||||||
|
|
||||||
import { buildFPTE, useAccentColor, usePrimaryColor, useProfileEffect, useShowPreview } from "../lib";
|
|
||||||
import { BuilderButton, BuilderColorButton, CustomizationSection, openProfileEffectModal, tokens, useAvatarColors } from ".";
|
|
||||||
|
|
||||||
export interface BuilderProps {
|
|
||||||
guild?: Guild | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
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(guild?.id, 80),
|
|
||||||
useToken(tokens.unsafe_rawColors.PRIMARY_530).hex(),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<CustomizationSection title="FPTE Builder">
|
|
||||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
|
||||||
<BuilderColorButton
|
|
||||||
label="Primary"
|
|
||||||
color={primaryColor}
|
|
||||||
setColor={setPrimaryColor}
|
|
||||||
suggestedColors={avatarColors}
|
|
||||||
/>
|
|
||||||
<BuilderColorButton
|
|
||||||
label="Accent"
|
|
||||||
color={accentColor}
|
|
||||||
setColor={setAccentColor}
|
|
||||||
suggestedColors={avatarColors}
|
|
||||||
/>
|
|
||||||
<BuilderButton
|
|
||||||
label="Effect"
|
|
||||||
tooltip={effect?.title}
|
|
||||||
selectedStyle={effect ? {
|
|
||||||
background: `top / cover url(${effect.thumbnailPreviewSrc}), top / cover url(/assets/f328a6f8209d4f1f5022.png)`
|
|
||||||
} : undefined}
|
|
||||||
buttonProps={{
|
|
||||||
onClick() {
|
|
||||||
openProfileEffectModal(effect?.id, setEffect, guild);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
flexDirection: "column"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
size={Button.Sizes.SMALL}
|
|
||||||
onClick={() => {
|
|
||||||
const strToCopy = buildFPTE(primaryColor ?? -1, accentColor ?? -1, effect?.id ?? "", buildLegacy);
|
|
||||||
if (strToCopy)
|
|
||||||
copyWithToast(strToCopy, "FPTE copied to clipboard!");
|
|
||||||
else
|
|
||||||
showToast("FPTE Builder is empty; nothing to copy!");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Copy FPTE
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
look={Button.Looks.LINK}
|
|
||||||
color={Button.Colors.PRIMARY}
|
|
||||||
size={Button.Sizes.SMALL}
|
|
||||||
style={primaryColor === null && accentColor === null && !effect ? { visibility: "hidden" } : undefined}
|
|
||||||
onClick={() => {
|
|
||||||
setPrimaryColor(null);
|
|
||||||
setAccentColor(null);
|
|
||||||
setEffect(null);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Reset
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CustomizationSection>
|
|
||||||
<Switch
|
|
||||||
value={preview}
|
|
||||||
onChange={setPreview}
|
|
||||||
>
|
|
||||||
FPTE Builder Preview
|
|
||||||
</Switch>
|
|
||||||
<Switch
|
|
||||||
value={buildLegacy}
|
|
||||||
note="Will use more characters"
|
|
||||||
onChange={setBuildLegacy}
|
|
||||||
>
|
|
||||||
Build backwards compatible FPTE
|
|
||||||
</Switch>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2024 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Text, Tooltip } from "@webpack/common";
|
|
||||||
import type { ComponentProps } from "react";
|
|
||||||
|
|
||||||
export interface BuilderButtonProps {
|
|
||||||
label?: string | undefined;
|
|
||||||
tooltip?: string | undefined;
|
|
||||||
selectedStyle?: ComponentProps<"div">["style"];
|
|
||||||
buttonProps?: ComponentProps<"div"> | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const BuilderButton = ({ label, tooltip, selectedStyle, buttonProps }: BuilderButtonProps) => (
|
|
||||||
<Tooltip text={tooltip} shouldShow={!!tooltip}>
|
|
||||||
{tooltipProps => (
|
|
||||||
<div style={{ width: "60px" }}>
|
|
||||||
<div
|
|
||||||
{...tooltipProps}
|
|
||||||
{...buttonProps}
|
|
||||||
aria-label={label}
|
|
||||||
role="button"
|
|
||||||
tabIndex={0}
|
|
||||||
style={{
|
|
||||||
...selectedStyle ?? { border: "2px dashed var(--header-secondary)" },
|
|
||||||
display: "grid",
|
|
||||||
placeItems: "center",
|
|
||||||
height: "60px",
|
|
||||||
borderRadius: "4px",
|
|
||||||
cursor: "pointer"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{!selectedStyle && (
|
|
||||||
<svg
|
|
||||||
fill="var(--header-secondary)"
|
|
||||||
width="40%"
|
|
||||||
height="40%"
|
|
||||||
viewBox="0 0 144 144"
|
|
||||||
>
|
|
||||||
<path d="M144 64H80V0H64v64H0v16h64v64h16V80h64Z" />
|
|
||||||
</svg>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{!!label && (
|
|
||||||
<Text
|
|
||||||
color="header-secondary"
|
|
||||||
variant="text-xs/normal"
|
|
||||||
tag="div"
|
|
||||||
style={{
|
|
||||||
marginTop: "4px",
|
|
||||||
textAlign: "center"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
|
@ -1,41 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2024 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Popout } from "@webpack/common";
|
|
||||||
|
|
||||||
import { BuilderButton, type BuilderButtonProps, CustomColorPicker, type CustomColorPickerProps } from ".";
|
|
||||||
|
|
||||||
export interface BuilderColorButtonProps extends Pick<BuilderButtonProps, "label">, Pick<CustomColorPickerProps, "suggestedColors"> {
|
|
||||||
color: number | null;
|
|
||||||
setColor: (color: number | null) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const BuilderColorButton = ({ label, color, setColor, suggestedColors }: BuilderColorButtonProps) => (
|
|
||||||
<Popout
|
|
||||||
position="bottom"
|
|
||||||
renderPopout={() => (
|
|
||||||
<CustomColorPicker
|
|
||||||
value={color}
|
|
||||||
onChange={setColor}
|
|
||||||
showEyeDropper={true}
|
|
||||||
suggestedColors={suggestedColors}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{popoutProps => {
|
|
||||||
const hexColor = color ? "#" + color.toString(16).padStart(6, "0") : undefined;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BuilderButton
|
|
||||||
label={label}
|
|
||||||
tooltip={hexColor}
|
|
||||||
selectedStyle={hexColor ? { background: hexColor } : undefined}
|
|
||||||
buttonProps={popoutProps}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Popout>
|
|
||||||
);
|
|
|
@ -1,99 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2024 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { type ModalProps, openModal } from "@utils/modal";
|
|
||||||
import { extractAndLoadChunksLazy, findByCodeLazy, findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
|
||||||
import type { useToken } from "@webpack/types";
|
|
||||||
import type { Guild } from "discord-types/general";
|
|
||||||
import type { ComponentType, FunctionComponent, PropsWithChildren, ReactNode } from "react";
|
|
||||||
|
|
||||||
import type { ProfileEffectConfig } from "../lib";
|
|
||||||
|
|
||||||
export * from "./Builder";
|
|
||||||
export * from "./BuilderButton";
|
|
||||||
export * from "./BuilderColorButton";
|
|
||||||
export * from "./settingsAboutComponent";
|
|
||||||
|
|
||||||
export interface CustomizationSectionProps extends PropsWithChildren {
|
|
||||||
borderType?: FeatureBorderType | 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Original name: FeatureBorderTypes
|
|
||||||
export const enum FeatureBorderType {
|
|
||||||
LIMITED = "limited",
|
|
||||||
PREMIUM = "premium",
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CustomizationSection: ComponentType<CustomizationSectionProps>
|
|
||||||
= findByCodeLazy(".customizationSectionBackground");
|
|
||||||
|
|
||||||
export const tokens: {
|
|
||||||
unsafe_rawColors: Record<string, Parameters<useToken>[0]>;
|
|
||||||
} = findByPropsLazy("unsafe_rawColors", "modules");
|
|
||||||
|
|
||||||
export const useAvatarColors: (
|
|
||||||
avatarURL: string | null | undefined,
|
|
||||||
fallbackColor: string,
|
|
||||||
desaturateColors?: boolean | undefined /* = true */
|
|
||||||
) => [string, string, ...string[]] = findByCodeLazy(".palette[", ".desaturateUserColors");
|
|
||||||
|
|
||||||
export interface CustomColorPickerProps {
|
|
||||||
className?: string | undefined;
|
|
||||||
eagerUpdate?: boolean | undefined /* = false */;
|
|
||||||
footer?: ReactNode;
|
|
||||||
middle?: ReactNode;
|
|
||||||
onChange: (color: number) => void;
|
|
||||||
onClose?: (() => void) | undefined;
|
|
||||||
showEyeDropper?: boolean | undefined /* = false */;
|
|
||||||
suggestedColors?: string[] | null | undefined;
|
|
||||||
wrapperComponentType?: ComponentType | null | undefined;
|
|
||||||
value?: string | number | null | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CustomColorPicker = findComponentByCodeLazy<CustomColorPickerProps>(".customColorPicker");
|
|
||||||
|
|
||||||
interface ProfileEffectModalProps extends ModalProps {
|
|
||||||
analyticsLocations?: string[] | undefined;
|
|
||||||
guild?: Guild | null | undefined;
|
|
||||||
initialSelectedEffectId?: string | undefined;
|
|
||||||
onApply: (effect: ProfileEffectConfig | null) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
let ProfileEffectModal: FunctionComponent<ProfileEffectModalProps> = () => null;
|
|
||||||
|
|
||||||
export function setProfileEffectModal(comp: typeof ProfileEffectModal) {
|
|
||||||
ProfileEffectModal = comp;
|
|
||||||
}
|
|
||||||
|
|
||||||
const requireProfileEffectModal = extractAndLoadChunksLazy(["initialSelectedEffectId:", ".openModalLazy"]);
|
|
||||||
|
|
||||||
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}
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2024 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Margins } from "@utils/margins";
|
|
||||||
import { Forms } from "@webpack/common";
|
|
||||||
|
|
||||||
export const settingsAboutComponent = () => (
|
|
||||||
<Forms.FormSection>
|
|
||||||
<Forms.FormTitle tag="h3">Usage</Forms.FormTitle>
|
|
||||||
<Forms.FormText>
|
|
||||||
After enabling this plugin, you will see custom theme colors and effects in the profiles of others using this plugin.
|
|
||||||
<div className={Margins.top8}>
|
|
||||||
<b>To set your own profile theme colors and effect:</b>
|
|
||||||
</div>
|
|
||||||
<ol
|
|
||||||
className={Margins.bottom8}
|
|
||||||
style={{ listStyle: "decimal", paddingLeft: "40px" }}
|
|
||||||
>
|
|
||||||
<li>Go to your profile settings</li>
|
|
||||||
<li>Use the FPTE Builder to choose your profile theme colors and effect</li>
|
|
||||||
<li>Click the "Copy FPTE" button</li>
|
|
||||||
<li>Paste the invisible text anywhere in your About Me</li>
|
|
||||||
</ol>
|
|
||||||
</Forms.FormText>
|
|
||||||
</Forms.FormSection>
|
|
||||||
);
|
|
|
@ -1,205 +0,0 @@
|
||||||
/*
|
|
||||||
* 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
|
|
||||||
});
|
|
|
@ -1,205 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2024 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
/** The FPTE delimiter codepoint (codepoint of zero-width space). */
|
|
||||||
const DELIMITER_CODEPOINT = 0x200B;
|
|
||||||
/** The FPTE delimiter (zero-width space). */
|
|
||||||
const DELIMITER = String.fromCodePoint(DELIMITER_CODEPOINT);
|
|
||||||
/** The FPTE radix (number of default-ignorable codepoints in the SSP plane). */
|
|
||||||
const RADIX = 0x1000;
|
|
||||||
/** The FPTE starting codepoint (first codepoint in the SSP plane). */
|
|
||||||
const STARTING_CODEPOINT = 0xE0000;
|
|
||||||
/** The FPTE starting character (first character in the SSP plane). */
|
|
||||||
const STARTING = String.fromCodePoint(STARTING_CODEPOINT);
|
|
||||||
/** The FPTE ending codepoint (last default-ignorable codepoint in the SSP plane). */
|
|
||||||
const ENDING_CODEPOINT = STARTING_CODEPOINT + RADIX - 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds a theme color string in the legacy format: `[#primary,#accent]`, where primary and accent are
|
|
||||||
* 24-bit colors as base-16 strings, with each codepoint of the string offset by +{@link STARTING_CODEPOINT}.
|
|
||||||
* @param primary The 24-bit primary color.
|
|
||||||
* @param accent The 24-bit accent color.
|
|
||||||
* @returns The built legacy-format theme color string.
|
|
||||||
*/
|
|
||||||
export function encodeColorsLegacy(primary: number, accent: number) {
|
|
||||||
let str = "";
|
|
||||||
for (const char of `[#${primary.toString(16)},#${accent.toString(16)}]`)
|
|
||||||
str += String.fromCodePoint(char.codePointAt(0)! + STARTING_CODEPOINT);
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the theme colors from a legacy-format string.
|
|
||||||
* @param str The legacy-format string to extract the theme colors from.
|
|
||||||
* @returns The profile theme colors. Colors will be -1 if not found.
|
|
||||||
* @see {@link encodeColorsLegacy}
|
|
||||||
*/
|
|
||||||
export function decodeColorsLegacy(str: string): [primaryColor: number, accentColor: number] {
|
|
||||||
const [primary, accent] = str.matchAll(/(?<=#)[\dA-Fa-f]{1,6}/g);
|
|
||||||
return [primary ? parseInt(primary[0], 16) : -1, accent ? parseInt(accent[0], 16) : -1];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a 24-bit color to a base-{@link RADIX} string with each codepoint offset by +{@link STARTING_CODEPOINT}.
|
|
||||||
* @param color The 24-bit color to be converted.
|
|
||||||
* @returns The converted base-{@link RADIX} string with +{@link STARTING_CODEPOINT} offset.
|
|
||||||
*/
|
|
||||||
export function encodeColor(color: number) {
|
|
||||||
if (color === 0) return STARTING;
|
|
||||||
let str = "";
|
|
||||||
for (; color > 0; color = Math.trunc(color / RADIX))
|
|
||||||
str = String.fromCodePoint(color % RADIX + STARTING_CODEPOINT) + str;
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a no-offset base-{@link RADIX} string to a 24-bit color.
|
|
||||||
* @param str The no-offset base-{@link RADIX} string to be converted.
|
|
||||||
* @returns The converted 24-bit color.
|
|
||||||
* Will be -1 if `str` is empty and -2 if the color is greater than the maximum 24-bit color, 0xFFFFFF.
|
|
||||||
*/
|
|
||||||
export function decodeColor(str: string) {
|
|
||||||
if (!str) return -1;
|
|
||||||
let color = 0;
|
|
||||||
for (let i = 0; i < str.length; i++) {
|
|
||||||
if (color > 0xFFF_FFF) return -2;
|
|
||||||
color += str.codePointAt(i)! * RADIX ** (str.length - 1 - i);
|
|
||||||
}
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts an effect ID to a base-{@link RADIX} string with each code point offset by +{@link STARTING_CODEPOINT}.
|
|
||||||
* @param id The effect ID to be converted.
|
|
||||||
* @returns The converted base-{@link RADIX} string with +{@link STARTING_CODEPOINT} offset.
|
|
||||||
*/
|
|
||||||
export function encodeEffect(id: bigint) {
|
|
||||||
if (id === 0n) return STARTING;
|
|
||||||
let str = "";
|
|
||||||
for (; id > 0n; id /= BigInt(RADIX))
|
|
||||||
str = String.fromCodePoint(Number(id % BigInt(RADIX)) + STARTING_CODEPOINT) + str;
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a no-offset base-{@link RADIX} string to an effect ID.
|
|
||||||
* @param str The no-offset base-{@link RADIX} string to be converted.
|
|
||||||
* @returns The converted effect ID.
|
|
||||||
* Will be -1n if `str` is empty and -2n if the color is greater than the maximum effect ID.
|
|
||||||
*/
|
|
||||||
export function decodeEffect(str: string) {
|
|
||||||
if (!str) return -1n;
|
|
||||||
let id = 0n;
|
|
||||||
for (let i = 0; i < str.length; i++) {
|
|
||||||
if (id >= 10_000_000_000_000_000_000n) return -2n;
|
|
||||||
id += BigInt(str.codePointAt(i)!) * BigInt(RADIX) ** BigInt(str.length - 1 - i);
|
|
||||||
}
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds a FPTE string containing the given primary/accent colors and effect ID. If the FPTE Builder is NOT set to backwards
|
|
||||||
* compatibility mode, the primary and accent colors will be converted to base-{@link RADIX} before they are encoded.
|
|
||||||
* @param primary The primary profile theme color. Must be negative if unset.
|
|
||||||
* @param accent The accent profile theme color. Must be negative if unset.
|
|
||||||
* @param effect The profile effect ID. Must be empty if unset.
|
|
||||||
* @param legacy Whether the primary and accent colors should be legacy encoded.
|
|
||||||
* @returns The built FPTE string. Will be empty if the given colors and effect are all unset.
|
|
||||||
*/
|
|
||||||
export function buildFPTE(primary: number, accent: number, effect: string, legacy: boolean) {
|
|
||||||
/** The FPTE string to be returned. */
|
|
||||||
let fpte = "";
|
|
||||||
|
|
||||||
// If the FPTE Builder is set to backwards compatibility mode,
|
|
||||||
// the primary and accent colors, if set, will be legacy encoded.
|
|
||||||
if (legacy) {
|
|
||||||
// Legacy FPTE strings must include both the primary and accent colors, even if they are the same.
|
|
||||||
|
|
||||||
if (primary >= 0) {
|
|
||||||
// If both the primary and accent colors are set, they will be legacy encoded and added to the
|
|
||||||
// string; otherwise, if the accent color is unset, the primary color will be used in its place.
|
|
||||||
if (accent >= 0)
|
|
||||||
fpte = encodeColorsLegacy(primary, accent);
|
|
||||||
else
|
|
||||||
fpte = encodeColorsLegacy(primary, primary);
|
|
||||||
|
|
||||||
// If the effect ID is set, it will be encoded and added to the string prefixed by one delimiter.
|
|
||||||
if (effect)
|
|
||||||
fpte += DELIMITER + encodeEffect(BigInt(effect));
|
|
||||||
|
|
||||||
return fpte;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since the primary color is unset, the accent color, if set, will be used in its place.
|
|
||||||
if (accent >= 0) {
|
|
||||||
fpte = encodeColorsLegacy(accent, accent);
|
|
||||||
|
|
||||||
// If the effect ID is set, it will be encoded and added to the string prefixed by one delimiter.
|
|
||||||
if (effect)
|
|
||||||
fpte += DELIMITER + encodeEffect(BigInt(effect));
|
|
||||||
|
|
||||||
return fpte;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If the primary color is set, it will be encoded and added to the string.
|
|
||||||
else if (primary >= 0) {
|
|
||||||
fpte = encodeColor(primary);
|
|
||||||
|
|
||||||
// If the accent color is set and different from the primary color, it
|
|
||||||
// will be encoded and added to the string prefixed by one delimiter.
|
|
||||||
if (accent >= 0 && primary !== accent) {
|
|
||||||
fpte += DELIMITER + encodeColor(accent);
|
|
||||||
|
|
||||||
// If the effect ID is set, it will be encoded and added to the string prefixed by one delimiter.
|
|
||||||
if (effect)
|
|
||||||
fpte += DELIMITER + encodeEffect(BigInt(effect));
|
|
||||||
|
|
||||||
return fpte;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If only the accent color is set, it will be encoded and added to the string.
|
|
||||||
else if (accent >= 0)
|
|
||||||
fpte = encodeColor(accent);
|
|
||||||
|
|
||||||
// Since either the primary/accent colors are the same, both are unset, or just one is set, only one color will be added
|
|
||||||
// to the string; therefore, the effect ID, if set, will be encoded and added to the string prefixed by two delimiters.
|
|
||||||
if (effect)
|
|
||||||
fpte += DELIMITER + DELIMITER + encodeEffect(BigInt(effect));
|
|
||||||
|
|
||||||
return fpte;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the delimiter-separated values of the first FPTE substring in a string.
|
|
||||||
* @param str The string to be searched for a FPTE substring.
|
|
||||||
* @returns An array of the found FPTE substring's extracted values. Values will be empty if not found.
|
|
||||||
*/
|
|
||||||
export function extractFPTE(str: string) {
|
|
||||||
/** The array of extracted FPTE values to be returned. */
|
|
||||||
const fpte: [maybePrimaryOrLegacy: string, maybeAccentOrEffect: string, maybeEffect: string] = ["", "", ""];
|
|
||||||
/** The current index of {@link fpte} getting extracted. */
|
|
||||||
let i = 0;
|
|
||||||
|
|
||||||
for (const char of str) {
|
|
||||||
/** The current character's codepoint. */
|
|
||||||
const cp = char.codePointAt(0)!;
|
|
||||||
|
|
||||||
// If the current character is a delimiter, then the current index of fpte has been completed.
|
|
||||||
if (cp === DELIMITER_CODEPOINT) {
|
|
||||||
// If the current index of fpte is the last, then the extraction is done.
|
|
||||||
if (i >= 2) break;
|
|
||||||
i++; // Start extracting the next index of fpte.
|
|
||||||
}
|
|
||||||
// If the current character is not a delimiter but a valid FPTE
|
|
||||||
// character, it will be added to the current index of fpte.
|
|
||||||
else if (cp >= STARTING_CODEPOINT && cp <= ENDING_CODEPOINT)
|
|
||||||
fpte[i] += String.fromCodePoint(cp - STARTING_CODEPOINT);
|
|
||||||
// If an FPTE string has been found and its end has been reached, then the extraction is done.
|
|
||||||
else if (i > 0 || fpte[0]) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return fpte;
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2024 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
export * from "./ftpe";
|
|
||||||
export * from "./profileEffects";
|
|
||||||
export * from "./profilePreview";
|
|
||||||
export * from "./userProfile";
|
|
|
@ -1,72 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2024 Vendicated and contributors
|
|
||||||
* 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");
|
|
||||||
|
|
||||||
export type ProfileEffectProperties = Omit<ProfileEffectRecordInstance, "type">;
|
|
||||||
|
|
||||||
export interface ProfileEffectRecordInstance {
|
|
||||||
id: string;
|
|
||||||
skuId: string;
|
|
||||||
type: CollectiblesItemType.PROFILE_EFFECT;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ProfileEffect {
|
|
||||||
config: ProfileEffectConfig;
|
|
||||||
id: string;
|
|
||||||
skuId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ProfileEffectConfig {
|
|
||||||
accessibilityLabel: string;
|
|
||||||
animationType: number;
|
|
||||||
description: string;
|
|
||||||
effects: {
|
|
||||||
duartion: number;
|
|
||||||
height: number;
|
|
||||||
loop: boolean;
|
|
||||||
loopDelay: number;
|
|
||||||
position: {
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
};
|
|
||||||
src: string;
|
|
||||||
start: number;
|
|
||||||
width: number;
|
|
||||||
zIndex: number;
|
|
||||||
}[];
|
|
||||||
id: string;
|
|
||||||
reducedMotionSrc: string;
|
|
||||||
sku_id: string;
|
|
||||||
staticFrameSrc?: string;
|
|
||||||
thumbnailPreviewSrc: string;
|
|
||||||
title: string;
|
|
||||||
type: CollectiblesItemType.PROFILE_EFFECT;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const enum CollectiblesItemType {
|
|
||||||
AVATAR_DECORATION = 0,
|
|
||||||
PROFILE_EFFECT = 1,
|
|
||||||
NONE = 100,
|
|
||||||
BUNDLE = 1_000,
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2024 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { FluxDispatcher, useState } from "@webpack/common";
|
|
||||||
|
|
||||||
import type { ProfileEffectConfig } from "./profileEffects";
|
|
||||||
|
|
||||||
function updatePreview() {
|
|
||||||
FluxDispatcher.dispatch({ type: "USER_SETTINGS_ACCOUNT_SUBMIT_SUCCESS" });
|
|
||||||
}
|
|
||||||
|
|
||||||
let primaryColor: number | null = null;
|
|
||||||
export function usePrimaryColor(initialState: typeof primaryColor) {
|
|
||||||
const [state, setState] = useState(() => primaryColor = initialState);
|
|
||||||
return [
|
|
||||||
state,
|
|
||||||
(color: typeof primaryColor) => {
|
|
||||||
setState(primaryColor = color);
|
|
||||||
if (showPreview) updatePreview();
|
|
||||||
}
|
|
||||||
] as const;
|
|
||||||
}
|
|
||||||
|
|
||||||
let accentColor: number | null = null;
|
|
||||||
export function useAccentColor(initialState: typeof accentColor) {
|
|
||||||
const [state, setState] = useState(() => accentColor = initialState);
|
|
||||||
return [
|
|
||||||
state,
|
|
||||||
(color: typeof accentColor) => {
|
|
||||||
setState(accentColor = color);
|
|
||||||
if (showPreview) updatePreview();
|
|
||||||
}
|
|
||||||
] as const;
|
|
||||||
}
|
|
||||||
|
|
||||||
let profileEffect: ProfileEffectConfig | null = null;
|
|
||||||
export function useProfileEffect(initialState: typeof profileEffect) {
|
|
||||||
const [state, setState] = useState(() => profileEffect = initialState);
|
|
||||||
return [
|
|
||||||
state,
|
|
||||||
(effect: typeof profileEffect) => {
|
|
||||||
setState(profileEffect = effect);
|
|
||||||
if (showPreview) updatePreview();
|
|
||||||
}
|
|
||||||
] as const;
|
|
||||||
}
|
|
||||||
|
|
||||||
let showPreview = true;
|
|
||||||
export function useShowPreview(initialState: typeof showPreview) {
|
|
||||||
const [state, setState] = useState(() => showPreview = initialState);
|
|
||||||
return [
|
|
||||||
state,
|
|
||||||
(preview: typeof showPreview) => {
|
|
||||||
setState(showPreview = preview);
|
|
||||||
updatePreview();
|
|
||||||
}
|
|
||||||
] as const;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function profilePreviewHook(props: Record<string, any>) {
|
|
||||||
if (showPreview) {
|
|
||||||
if (primaryColor !== null) {
|
|
||||||
props.pendingThemeColors = [primaryColor, accentColor ?? primaryColor];
|
|
||||||
props.canUsePremiumCustomization = true;
|
|
||||||
} else if (accentColor !== null) {
|
|
||||||
props.pendingThemeColors = [accentColor, accentColor];
|
|
||||||
props.canUsePremiumCustomization = true;
|
|
||||||
}
|
|
||||||
if (!props.forProfileEffectModal && profileEffect) {
|
|
||||||
props.pendingProfileEffectId = profileEffect.id;
|
|
||||||
props.canUsePremiumCustomization = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2024 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { settings } from "..";
|
|
||||||
import { decodeColor, decodeColorsLegacy, decodeEffect, extractFPTE } from ".";
|
|
||||||
|
|
||||||
export interface UserProfile {
|
|
||||||
bio: string;
|
|
||||||
premiumType: number | null | undefined;
|
|
||||||
profileEffectId: string | undefined;
|
|
||||||
themeColors: [primaryColor: number, accentColor: number] | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateProfileThemeColors(profile: UserProfile, primary: number, accent: number) {
|
|
||||||
if (primary > -1) {
|
|
||||||
profile.themeColors = [primary, accent > -1 ? accent : primary];
|
|
||||||
profile.premiumType = 2;
|
|
||||||
} else if (accent > -1) {
|
|
||||||
profile.themeColors = [accent, accent];
|
|
||||||
profile.premiumType = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateProfileEffectId(profile: UserProfile, id: bigint) {
|
|
||||||
if (id > -1n) {
|
|
||||||
profile.profileEffectId = id.toString();
|
|
||||||
profile.premiumType = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function decodeAboutMeFPTEHook(profile?: UserProfile) {
|
|
||||||
if (!profile) return profile;
|
|
||||||
|
|
||||||
if (settings.store.prioritizeNitro) {
|
|
||||||
if (profile.themeColors) {
|
|
||||||
if (!profile.profileEffectId) {
|
|
||||||
const fpte = extractFPTE(profile.bio);
|
|
||||||
if (decodeColor(fpte[0]) === -2)
|
|
||||||
updateProfileEffectId(profile, decodeEffect(fpte[1]));
|
|
||||||
else
|
|
||||||
updateProfileEffectId(profile, decodeEffect(fpte[2]));
|
|
||||||
}
|
|
||||||
return profile;
|
|
||||||
} else if (profile.profileEffectId) {
|
|
||||||
const fpte = extractFPTE(profile.bio);
|
|
||||||
const primaryColor = decodeColor(fpte[0]);
|
|
||||||
if (primaryColor === -2)
|
|
||||||
updateProfileThemeColors(profile, ...decodeColorsLegacy(fpte[0]));
|
|
||||||
else
|
|
||||||
updateProfileThemeColors(profile, primaryColor, decodeColor(fpte[1]));
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fpte = extractFPTE(profile.bio);
|
|
||||||
const primaryColor = decodeColor(fpte[0]);
|
|
||||||
if (primaryColor === -2) {
|
|
||||||
updateProfileThemeColors(profile, ...decodeColorsLegacy(fpte[0]));
|
|
||||||
updateProfileEffectId(profile, decodeEffect(fpte[1]));
|
|
||||||
} else {
|
|
||||||
updateProfileThemeColors(profile, primaryColor, decodeColor(fpte[1]));
|
|
||||||
updateProfileEffectId(profile, decodeEffect(fpte[2]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return profile;
|
|
||||||
}
|
|
|
@ -6,9 +6,10 @@
|
||||||
|
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findExportedComponentLazy, findStoreLazy } from "@webpack";
|
import { findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
||||||
import { useStateFromStores } from "@webpack/common";
|
import { useStateFromStores } from "@webpack/common";
|
||||||
const ThreeDots = findExportedComponentLazy("Dots", "AnimatedDots");
|
|
||||||
|
const ThreeDots = findComponentByCodeLazy(".dots,", "dotRadius:");
|
||||||
|
|
||||||
const TypingStore = findStoreLazy("TypingStore");
|
const TypingStore = findStoreLazy("TypingStore");
|
||||||
|
|
||||||
|
|
|
@ -59,20 +59,20 @@ export default definePlugin({
|
||||||
// Discord adds it's paste listeners to #app-mount. We can intercept them
|
// Discord adds it's paste listeners to #app-mount. We can intercept them
|
||||||
// by attaching listeners a child element.
|
// by attaching listeners a child element.
|
||||||
containerEl = document.querySelector("[class^=appAsidePanelWrapper]")!;
|
containerEl = document.querySelector("[class^=appAsidePanelWrapper]")!;
|
||||||
containerEl.addEventListener("paste", blockPastePropogation);
|
containerEl?.addEventListener("paste", blockPastePropogation);
|
||||||
|
|
||||||
// Also add them to body to intercept the event listeners on document
|
// Also add them to body to intercept the event listeners on document
|
||||||
document.body.addEventListener("paste", blockPastePropogation);
|
document?.body?.addEventListener("paste", blockPastePropogation);
|
||||||
|
|
||||||
document.body.addEventListener("mousedown", disablePasteOnMousedown);
|
document?.body?.addEventListener("mousedown", disablePasteOnMousedown);
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
containerEl.removeEventListener("paste", blockPastePropogation);
|
containerEl?.removeEventListener("paste", blockPastePropogation);
|
||||||
|
|
||||||
document.body.removeEventListener("paste", blockPastePropogation);
|
document?.body?.removeEventListener("paste", blockPastePropogation);
|
||||||
|
|
||||||
document.body.removeEventListener("mousedown", disablePasteOnMousedown);
|
document?.body?.removeEventListener("mousedown", disablePasteOnMousedown);
|
||||||
pasteDisabled = false;
|
pasteDisabled = false;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,7 +21,7 @@ import definePlugin from "@utils/types";
|
||||||
import { Message } from "discord-types/general";
|
import { Message } from "discord-types/general";
|
||||||
|
|
||||||
function PinIcon({ style = {} }) {
|
function PinIcon({ style = {} }) {
|
||||||
return <svg className="icon__9293f" aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 24 24"><path fill="currentColor" d="M19.38 11.38a3 3 0 0 0 4.24 0l.03-.03a.5.5 0 0 0 0-.7L13.35.35a.5.5 0 0 0-.7 0l-.03.03a3 3 0 0 0 0 4.24L13 5l-2.92 2.92-3.65-.34a2 2 0 0 0-1.6.58l-.62.63a1 1 0 0 0 0 1.42l9.58 9.58a1 1 0 0 0 1.42 0l.63-.63a2 2 0 0 0 .58-1.6l-.34-3.64L19 11l.38.38ZM9.07 17.07a.5.5 0 0 1-.08.77l-5.15 3.43a.5.5 0 0 1-.63-.06l-.42-.42a.5.5 0 0 1-.06-.63L6.16 15a.5.5 0 0 1 .77-.08l2.14 2.14Z" className="" style={style}></path></svg>;
|
return <svg className="icon__9293f" aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 24 24"><path fill="currentColor" d="M19.38 11.38a3 3 0 0 0 4.24 0l.03-.03a.5.5 0 0 0 0-.7L13.35.35a.5.5 0 0 0-.7 0l-.03.03a3 3 0 0 0 0 4.24L13 5l-2.92 2.92-3.65-.34a2 2 0 0 0-1.6.58l-.62.63a1 1 0 0 0 0 1.42l9.58 9.58a1 1 0 0 0 1.42 0l.63-.63a2 2 0 0 0 .58-1.6l-.34-3.64L19 11l.38.38ZM9.07 17.07a.5.5 0 0 1-.08.77l-5.15 3.43a.5.5 0 0 1-.63-.06l-.42-.42a.5.5 0 0 1-.06-.63L6.16 15a.5.5 0 0 1 .77-.08l2.14 2.14Z" style={style}></path></svg>;
|
||||||
}
|
}
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "PinIcon",
|
name: "PinIcon",
|
||||||
|
|
|
@ -10,14 +10,37 @@ import { definePluginSettings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
import { findStoreLazy } from "@webpack";
|
||||||
import { ReadStateStore, useStateFromStores } from "@webpack/common";
|
import { ReadStateStore, useStateFromStores } from "@webpack/common";
|
||||||
import { Channel } from "discord-types/general";
|
import { Channel } from "discord-types/general";
|
||||||
import { JSX } from "react";
|
import { JSX } from "react";
|
||||||
|
|
||||||
const UserGuildSettingsStore = findStoreLazy("UserGuildSettingsStore");
|
const UserGuildSettingsStore = findStoreLazy("UserGuildSettingsStore");
|
||||||
const JoinedThreadsStore = findStoreLazy("JoinedThreadsStore");
|
const JoinedThreadsStore = findStoreLazy("JoinedThreadsStore");
|
||||||
const { NumberBadge } = findByPropsLazy("NumberBadge");
|
|
||||||
|
function NumberBadge({ color, className, count }) {
|
||||||
|
return <svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="10" fill={color} />
|
||||||
|
<text
|
||||||
|
x="50%"
|
||||||
|
y="50%"
|
||||||
|
textAnchor="middle"
|
||||||
|
fontSize="10"
|
||||||
|
fill="white"
|
||||||
|
fontWeight="bold"
|
||||||
|
dy=".3em"
|
||||||
|
>
|
||||||
|
{count}
|
||||||
|
</text>
|
||||||
|
</svg>;
|
||||||
|
}
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
showOnMutedChannels: {
|
showOnMutedChannels: {
|
||||||
|
|
Loading…
Add table
Reference in a new issue