diff --git a/src/equicordplugins/changeFriendAvatar/index.tsx b/src/equicordplugins/changeFriendAvatar/index.tsx deleted file mode 100644 index bd2eabdb0..000000000 --- a/src/equicordplugins/changeFriendAvatar/index.tsx +++ /dev/null @@ -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 = {}; - -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( - , - { - await requireSettingsMenu(); - openModal(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>(KEY_DATASTORE) || {}; - } -}); diff --git a/src/equicordplugins/changeFriendAvatar/AvatarModal.tsx b/src/equicordplugins/userpfp/AvatarModal.tsx similarity index 97% rename from src/equicordplugins/changeFriendAvatar/AvatarModal.tsx rename to src/equicordplugins/userpfp/AvatarModal.tsx index d77a24036..35708dc80 100644 --- a/src/equicordplugins/changeFriendAvatar/AvatarModal.tsx +++ b/src/equicordplugins/userpfp/AvatarModal.tsx @@ -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); diff --git a/src/equicordplugins/userpfp/index.tsx b/src/equicordplugins/userpfp/index.tsx index 0442c24a9..9faa173c4 100644 --- a/src/equicordplugins/userpfp/index.tsx +++ b/src/equicordplugins/userpfp/index.tsx @@ -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 }; +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 }; + 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: () => ( - - - - + <> + + Using the set avatar feature is local only meaning only you see it change. + + + + + + ), 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( + , + { + await requireSettingsMenu(); + openModal(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>(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); } });