diff --git a/src/plugins/editUsers/data.ts b/src/plugins/editUsers/data.ts new file mode 100644 index 000000000..7957d2ce8 --- /dev/null +++ b/src/plugins/editUsers/data.ts @@ -0,0 +1,42 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { definePluginSettings } from "@api/Settings"; + +export const enum OverrideFlags { + None = 0, + PreferServerNicks = 1 << 0, + DisableNicks = 1 << 1, + KeepServerAvatar = 1 << 2, + DisableServerAvatars = 1 << 3, + KeepServerBanner = 1 << 4, + DisableServerBanners = 1 << 5, +} + +export interface UserOverride { + username: string; + avatarUrl: string; + bannerUrl: string; + pronouns: string; + flags: OverrideFlags; +} + +export const emptyOverride: UserOverride = Object.freeze({ + username: "", + avatarUrl: "", + bannerUrl: "", + pronouns: "", + flags: OverrideFlags.None, +}); + +export const settings = definePluginSettings({}) + .withPrivateSettings<{ + users?: Record; + }>(); + +export const getUserOverride = (userId: string) => settings.store.users?.[userId] ?? emptyOverride; + +export const hasFlag = (field: OverrideFlags, flag: OverrideFlags) => (field & flag) === flag; diff --git a/src/plugins/editUsers/index.tsx b/src/plugins/editUsers/index.tsx new file mode 100644 index 000000000..f96563f2b --- /dev/null +++ b/src/plugins/editUsers/index.tsx @@ -0,0 +1,125 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import "./styles.css"; + +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; +import { Menu } from "@webpack/common"; +import { User } from "discord-types/general"; + +import { getUserOverride, hasFlag, OverrideFlags, settings } from "./data"; +import { openUserEditModal } from "./modal"; + +export default definePlugin({ + name: "EditUsers", + description: "Edit users", + authors: [Devs.Ven], + + settings, + + contextMenus: { + "user-context"(children, { user }: { user?: User; }) { + if (!user) return; + + children.push( + openUserEditModal(user)} + /> + ); + } + }, + + patches: [ + { + find: ",getUserTag:", + replacement: { + match: /if\(\i\((\i)\.global_name\)\)return(?=.{0,100}return"\?\?\?")/, + replace: "const vcEuName=$self.getUsername($1);if(vcEuName)return vcEuName;$&" + } + }, + { + find: "=this.guildMemberAvatars[", + replacement: [ + { + match: /&&null!=this\.guildMemberAvatars\[\i\]/, + replace: "$& && !$self.shouldIgnoreGuildAvatar(this)" + }, + { + match: /(?<=null!=(\i))\?(\i\.\i\.getGuildMemberAvatarURLSimple.+?):(?=\i\.\i\.getUserAvatarURL\(this)/, + replace: "&& this.hasAvatarForGuild?.($1) ? $2 : $self.getAvatarUrl(this)||" + } + ] + }, + { + find: "this.isUsingGuildMemberBanner()", + replacement: [ + { + match: /:\i\.banner\)!=null/, + replace: "$& && !$self.shouldIgnoreGuildBanner(this.userId)" + }, + { + match: /(?<=:).{0,10}\(\{id:this\.userId,banner/, + replace: "$self.getBannerUrl(this.userId)||$&" + }, + { + match: /isUsingGuildMemberPronouns\(\)\{/, + replace: + "set pronouns(v){this._vcPronouns=v}" + + "get pronouns(){return $self.getPronouns(this.userId)||this._vcPronouns}" + + "isUsingGuildMemberPronouns(){" + }, + { + match: /\i\(this,"pronouns",void 0\),/, + replace: "" + } + ] + }, + { + find: '"GuildMemberStore"', + replacement: { + match: /getNick\(\i,(\i)\)\{/, + replace: "$& if ($self.shouldIgnoreNick($1)) return null;" + } + } + ], + + getUsername: (user: User) => getUserOverride(user.id).username, + getAvatarUrl: (user: User) => getUserOverride(user.id).avatarUrl, + getBannerUrl: (userId: string) => getUserOverride(userId).bannerUrl, + getPronouns: (userId: string) => getUserOverride(userId).pronouns, + + shouldIgnoreGuildAvatar(user: User) { + const { avatarUrl, flags } = getUserOverride(user.id); + + if (avatarUrl && !hasFlag(flags, OverrideFlags.KeepServerAvatar)) + return true; + + return hasFlag(flags, OverrideFlags.DisableServerAvatars); + }, + + shouldIgnoreGuildBanner(userId: string) { + const { bannerUrl, flags } = getUserOverride(userId); + + if (bannerUrl && !hasFlag(flags, OverrideFlags.KeepServerBanner)) + return true; + + return hasFlag(flags, OverrideFlags.DisableServerBanners); + }, + + shouldIgnoreNick(userId?: string) { + if (!userId) return false; + + const { username, flags } = getUserOverride(userId); + + if (username && !hasFlag(flags, OverrideFlags.PreferServerNicks)) + return true; + + return hasFlag(flags, OverrideFlags.DisableNicks); + } +}); diff --git a/src/plugins/editUsers/modal.tsx b/src/plugins/editUsers/modal.tsx new file mode 100644 index 000000000..ba0c127d0 --- /dev/null +++ b/src/plugins/editUsers/modal.tsx @@ -0,0 +1,170 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { classNameFactory } from "@api/Styles"; +import { Flex } from "@components/Flex"; +import { Margins } from "@utils/margins"; +import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; +import { Button, DisplayProfileUtils, showToast, Switch, TabBar, Text, TextInput, Toasts, UsernameUtils, useState } from "@webpack/common"; +import { User } from "discord-types/general"; +import type { Dispatch, SetStateAction } from "react"; + +import { emptyOverride, hasFlag, OverrideFlags, settings, UserOverride } from "./data"; + +const cl = classNameFactory("vc-editUsers-"); + +interface OverrideProps { + override: UserOverride; + setOverride: Dispatch>; +} + +interface SettingsRowProps extends OverrideProps { + overrideKey: keyof Omit; + name: string; + flagDisable: OverrideFlags; + flagPrefer: OverrideFlags; + placeholder: string; +} + +function SettingsRow(props: SettingsRowProps) { + const { name, override, setOverride, overrideKey, placeholder, flagDisable, flagPrefer } = props; + const namePlural = name + "s"; + const { flags } = override; + + const toggleFlag = (on: boolean, flag: OverrideFlags) => + on + ? flags | flag + : flags & ~flag; + + return ( + <> + setOverride(o => ({ ...o, [overrideKey]: v }))} + placeholder={placeholder} + autoFocus + /> + setOverride(o => ({ ...o, flags: toggleFlag(v, flagDisable) }))} + note={`Will use the user's global ${name} (or your EditUser configured ${name}) over server specific ${namePlural}`} + > + Disable server specific {namePlural} + + setOverride(o => ({ ...o, flags: toggleFlag(v, flagPrefer) }))} + note={`Will use server specific ${namePlural} over the EditUser configured ${name}`} + hideBorder + > + Prefer server specific {namePlural} + + + ); +} + +const Tabs = { + username: { + name: "Username", + flagDisable: OverrideFlags.DisableNicks, + flagPrefer: OverrideFlags.PreferServerNicks, + placeholder: (user: User) => UsernameUtils.getName(user), + }, + avatarUrl: { + name: "Avatar", + flagDisable: OverrideFlags.DisableServerAvatars, + flagPrefer: OverrideFlags.KeepServerAvatar, + placeholder: (user: User) => user.getAvatarURL(), + }, + bannerUrl: { + name: "Banner", + flagDisable: OverrideFlags.DisableServerBanners, + flagPrefer: OverrideFlags.KeepServerBanner, + placeholder: (user: User) => DisplayProfileUtils.getDisplayProfile(user.id)?.getBannerURL({ canAnimate: true, size: 64 }) ?? "", + }, +} as const; +const TabKeys = Object.keys(Tabs) as (keyof typeof Tabs)[]; + +function EditTabs({ user, override, setOverride }: { user: User; } & OverrideProps) { + const [currentTabName, setCurrentTabName] = useState(TabKeys[0]); + + const currentTab = Tabs[currentTabName]; + + return ( + <> + + {TabKeys.map(key => ( + + {Tabs[key].name} + + ))} + + + + + ); +} + +function EditModal({ user, modalProps }: { user: User; modalProps: ModalProps; }) { + const [override, setOverride] = useState(() => ({ ...settings.store.users?.[user.id] ?? emptyOverride })); + + return ( + + + Edit User {user.username} + + + + +
+ +
+
+ + + + + + + +
+ ); +} + +export function openUserEditModal(user: User) { + openModal(props => ); +} diff --git a/src/plugins/editUsers/styles.css b/src/plugins/editUsers/styles.css new file mode 100644 index 000000000..61746abbf --- /dev/null +++ b/src/plugins/editUsers/styles.css @@ -0,0 +1,14 @@ +.vc-editUsers-modal { + padding: 1em 0; +} + +.vc-editUsers-tabBar { + gap: 1em; + margin-bottom: 16px; + border-bottom: 2px solid var(--background-modifier-accent); +} + +.vc-editUsers-tab { + padding-bottom: 8px; +} +