UserPFP: Combine ChangeFriendAvatar
This commit is contained in:
parent
d4dada31ce
commit
406a9a5e3b
3 changed files with 95 additions and 154 deletions
|
|
@ -1,127 +0,0 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { get } from "@api/DataStore";
|
||||
import { isPluginEnabled } from "@api/PluginManager";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { PencilIcon } from "@components/Icons";
|
||||
import userpfp from "@equicordplugins/userpfp";
|
||||
import { EquicordDevs } from "@utils/constants";
|
||||
import { openModal } from "@utils/modal";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { User } from "@vencord/discord-types";
|
||||
import { extractAndLoadChunksLazy } from "@webpack";
|
||||
import { IconUtils, Menu, UserStore } from "@webpack/common";
|
||||
|
||||
import { SetAvatarModal } from "./AvatarModal";
|
||||
|
||||
export const requireSettingsMenu = extractAndLoadChunksLazy(['name:"UserSettings"'], /createPromise:.{0,20}(\i\.\i\("?.+?"?\).*?).then\(\i\.bind\(\i,"?(.+?)"?\)\).{0,50}"UserSettings"/);
|
||||
export const KEY_DATASTORE = "vencord-custom-avatars";
|
||||
export let avatars: Record<string, string> = {};
|
||||
|
||||
const settings = definePluginSettings({
|
||||
overrideServerAvatars: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Override server avatars with custom avatars or the default user avatar if no custom avatar is set.",
|
||||
default: true
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "ChangeFriendAvatar",
|
||||
description: "Set custom avatar URLs for any user",
|
||||
authors: [EquicordDevs.soapphia],
|
||||
settings,
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: "getUserAvatarURL:",
|
||||
replacement: [
|
||||
{
|
||||
match: /(getUserAvatarURL:)(\i),/,
|
||||
replace: "$1$self.getAvatarHook($2),"
|
||||
},
|
||||
{
|
||||
match: /(getGuildMemberAvatarURLSimple:)(\i),/,
|
||||
replace: "$1$self.getAvatarServerHook($2),"
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
|
||||
contextMenus: {
|
||||
"user-context": (children, { user }) => {
|
||||
if (!user?.id) return;
|
||||
|
||||
children.push(
|
||||
<Menu.MenuSeparator />,
|
||||
<Menu.MenuItem
|
||||
label="Set Avatar"
|
||||
id="set-avatar"
|
||||
icon={PencilIcon}
|
||||
action={async () => {
|
||||
await requireSettingsMenu();
|
||||
openModal(modalProps => <SetAvatarModal userId={user.id} modalProps={modalProps} />);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
getAvatarHook: (original: any) => (user: User, animated: boolean, size: number) => {
|
||||
if (isPluginEnabled(userpfp.name) && userpfp.data?.avatars?.[user.id]) {
|
||||
if (userpfp.settings.store.preferNitro && user.avatar?.startsWith("a_")) return original(user, animated, size);
|
||||
|
||||
const res = new URL(userpfp.data.avatars[user.id]);
|
||||
res.searchParams.set("animated", animated ? "true" : "false");
|
||||
if (res && !animated) {
|
||||
res.pathname = res.pathname.replaceAll(/\.gifv?/g, ".png");
|
||||
}
|
||||
|
||||
return res.toString();
|
||||
}
|
||||
|
||||
if (!avatars[user.id]) return original(user, animated, size);
|
||||
|
||||
const customUrl = avatars[user.id];
|
||||
try {
|
||||
const res = new URL(customUrl);
|
||||
res.searchParams.set("size", size.toString());
|
||||
return res.toString();
|
||||
} catch {
|
||||
return customUrl;
|
||||
}
|
||||
},
|
||||
|
||||
getAvatarServerHook: (original: any) => (config: any) => {
|
||||
const { userId, avatar, size, canAnimate } = config;
|
||||
if (!settings.store.overrideServerAvatars) return original(config);
|
||||
|
||||
if (avatars[userId]) {
|
||||
const customUrl = avatars[userId];
|
||||
try {
|
||||
const res = new URL(customUrl);
|
||||
if (size) res.searchParams.set("size", size.toString());
|
||||
return res.toString();
|
||||
} catch {
|
||||
return customUrl;
|
||||
}
|
||||
}
|
||||
|
||||
if (avatar) {
|
||||
const user = UserStore.getUser(userId);
|
||||
if (user?.avatar) {
|
||||
return IconUtils.getUserAvatarURL(user, canAnimate, size);
|
||||
}
|
||||
}
|
||||
|
||||
return original(config);
|
||||
},
|
||||
|
||||
async start() {
|
||||
avatars = await get<Record<string, string>>(KEY_DATASTORE) || {};
|
||||
}
|
||||
});
|
||||
|
|
@ -7,16 +7,17 @@
|
|||
import { set } from "@api/DataStore";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { Button } from "@components/Button";
|
||||
import { Heading } from "@components/Heading";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { Heading } from "@components/Heading";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot } from "@utils/modal";
|
||||
import { TextInput, useState } from "@webpack/common";
|
||||
|
||||
import { avatars, KEY_DATASTORE } from ".";
|
||||
import { data, KEY_DATASTORE } from ".";
|
||||
const cl = classNameFactory("vc-custom-avatars-");
|
||||
|
||||
export function SetAvatarModal({ userId, modalProps }: { userId: string, modalProps: ModalProps; }) {
|
||||
const { avatars } = data;
|
||||
const initialAvatarUrl = avatars[userId] || "";
|
||||
const [url, setUrl] = useState(initialAvatarUrl);
|
||||
|
||||
|
|
@ -6,24 +6,39 @@
|
|||
|
||||
import "./styles.css";
|
||||
|
||||
import { isPluginEnabled } from "@api/PluginManager";
|
||||
import { get } from "@api/DataStore";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Button } from "@components/Button";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { Heart } from "@components/Heart";
|
||||
import changeFriendAvatar from "@equicordplugins/changeFriendAvatar";
|
||||
import { PencilIcon } from "@components/Icons";
|
||||
import { Margins } from "@components/margins";
|
||||
import { Notice } from "@components/Notice";
|
||||
import { Devs, EquicordDevs } from "@utils/constants";
|
||||
import { classNameFactory } from "@utils/css";
|
||||
import { openInviteModal } from "@utils/discord";
|
||||
import { openModal } from "@utils/modal";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { User } from "@vencord/discord-types";
|
||||
import { extractAndLoadChunksLazy } from "@webpack";
|
||||
import { IconUtils, Menu, UserStore } from "@webpack/common";
|
||||
|
||||
import { SetAvatarModal } from "./AvatarModal";
|
||||
|
||||
const cl = classNameFactory("vc-userpfp-");
|
||||
const DONO_URL = "https://ko-fi.com/coolesding";
|
||||
const INVITE_LINK = "userpfp-1129784704267210844";
|
||||
|
||||
let data = { avatars: {} as Record<string, string> };
|
||||
export const requireSettingsMenu = extractAndLoadChunksLazy(['name:"UserSettings"'], /createPromise:.{0,20}(\i\.\i\("?.+?"?\).*?).then\(\i\.bind\(\i,"?(.+?)"?\)\).{0,50}"UserSettings"/);
|
||||
export const KEY_DATASTORE = "vencord-custom-avatars";
|
||||
export const data = { avatars: {} as Record<string, string> };
|
||||
|
||||
const settings = definePluginSettings({
|
||||
overrideServerAvatars: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Override server avatars with custom avatars or the default user avatar if no custom avatar is set.",
|
||||
default: true
|
||||
},
|
||||
preferNitro: {
|
||||
description: "Which avatar to use if both default animated (Nitro) pfp and UserPFP avatars are present",
|
||||
type: OptionType.SELECT,
|
||||
|
|
@ -44,45 +59,71 @@ const settings = definePluginSettings({
|
|||
}
|
||||
return true;
|
||||
})
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "UserPFP",
|
||||
description: "Allows you to use an animated avatar without Nitro",
|
||||
authors: [EquicordDevs.nexpid, Devs.thororen],
|
||||
authors: [EquicordDevs.nexpid, Devs.thororen, EquicordDevs.soapphia],
|
||||
settings,
|
||||
data,
|
||||
settingsAboutComponent: () => (
|
||||
<Flex className={cl("settings")}>
|
||||
<Button
|
||||
variant="link"
|
||||
className={cl("settings-button")}
|
||||
onClick={() => openInviteModal(INVITE_LINK)}
|
||||
>
|
||||
Join UserPFP Server
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className={cl("settings-button")}
|
||||
onClick={() => VencordNative.native.openExternal(DONO_URL)}
|
||||
>
|
||||
Support UserPFP here <Heart className={cl("settings-heart")} />
|
||||
</Button>
|
||||
</Flex>
|
||||
<>
|
||||
<Notice.Info className={Margins.bottom8}>
|
||||
Using the set avatar feature is local only meaning only you see it change.
|
||||
</Notice.Info>
|
||||
<Flex className={cl("settings")}>
|
||||
<Button
|
||||
variant="link"
|
||||
className={cl("settings-button")}
|
||||
onClick={() => openInviteModal(INVITE_LINK)}
|
||||
>
|
||||
Join UserPFP Server
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className={cl("settings-button")}
|
||||
onClick={() => VencordNative.native.openExternal(DONO_URL)}
|
||||
>
|
||||
Support UserPFP here <Heart className={cl("settings-heart")} />
|
||||
</Button>
|
||||
</Flex>
|
||||
</>
|
||||
),
|
||||
patches: [
|
||||
{
|
||||
find: "getUserAvatarURL:",
|
||||
predicate: () => !isPluginEnabled(changeFriendAvatar.name),
|
||||
replacement: [
|
||||
{
|
||||
match: /(getUserAvatarURL:)(\i),/,
|
||||
replace: "$1$self.getAvatarHook($2),"
|
||||
},
|
||||
{
|
||||
match: /(getGuildMemberAvatarURLSimple:)(\i),/,
|
||||
replace: "$1$self.getAvatarServerHook($2),"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
contextMenus: {
|
||||
"user-context": (children, { user }) => {
|
||||
if (!user?.id) return;
|
||||
|
||||
children.push(
|
||||
<Menu.MenuSeparator />,
|
||||
<Menu.MenuItem
|
||||
label="Set Avatar"
|
||||
id="set-avatar"
|
||||
icon={PencilIcon}
|
||||
action={async () => {
|
||||
await requireSettingsMenu();
|
||||
openModal(modalProps => <SetAvatarModal userId={user.id} modalProps={modalProps} />);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
getAvatarHook: (original: any) => (user: User, animated: boolean, size: number) => {
|
||||
if (settings.store.preferNitro && user.avatar?.startsWith("a_")) return original(user, animated, size);
|
||||
if (!data.avatars[user.id]) return original(user, animated, size);
|
||||
|
|
@ -95,11 +136,37 @@ export default definePlugin({
|
|||
|
||||
return res.toString();
|
||||
},
|
||||
getAvatarServerHook: (original: any) => (config: any) => {
|
||||
const { userId, avatar, size, canAnimate } = config;
|
||||
const { avatars } = data;
|
||||
if (!settings.store.overrideServerAvatars) return original(config);
|
||||
|
||||
if (avatars[userId]) {
|
||||
const customUrl = avatars[userId];
|
||||
try {
|
||||
const res = new URL(customUrl);
|
||||
if (size) res.searchParams.set("size", size.toString());
|
||||
return res.toString();
|
||||
} catch {
|
||||
return customUrl;
|
||||
}
|
||||
}
|
||||
|
||||
if (avatar) {
|
||||
const user = UserStore.getUser(userId);
|
||||
if (user?.avatar) {
|
||||
return IconUtils.getUserAvatarURL(user, canAnimate, size);
|
||||
}
|
||||
}
|
||||
|
||||
return original(config);
|
||||
},
|
||||
async start() {
|
||||
data.avatars = await get<Record<string, string>>(KEY_DATASTORE) || {};
|
||||
|
||||
await fetch(settings.store.databaseSource)
|
||||
.then(async res => {
|
||||
if (res.ok) data = await res.json();
|
||||
})
|
||||
.then(res => res.ok && res.json())
|
||||
.then(remote => remote?.avatars && Object.assign(data.avatars, remote.avatars))
|
||||
.catch(() => null);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue