diff --git a/src/equicordplugins/customUserColors/SetColorModal.tsx b/src/equicordplugins/customUserColors/SetColorModal.tsx new file mode 100644 index 00000000..ba8919f9 --- /dev/null +++ b/src/equicordplugins/customUserColors/SetColorModal.tsx @@ -0,0 +1,95 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { set } from "@api/DataStore"; +import { classNameFactory } from "@api/Styles"; +import { Margins } from "@utils/margins"; +import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot } from "@utils/modal"; +import { findComponentByCodeLazy } from "@webpack"; +import { Button, Forms, useState } from "@webpack/common"; + +import { colors, DATASTORE_KEY } from "./index"; + +interface ColorPickerProps { + color: number; + showEyeDropper?: boolean; + suggestedColors?: string[]; + onChange(value: number | null): void; +} +const ColorPicker = findComponentByCodeLazy("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)"); + +const cl = classNameFactory("vc-customColors-"); + +export function SetColorModal({ userId, modalProps }: { userId: string, modalProps: ModalProps; }) { + + const userColor = colors[userId]; + + const initialColor = parseInt(colors[userId], 16) || 372735; + // color picker default to current color set for user (if null it's 0x05afff :3 ) + + const [colorPickerColor, setColorPickerColor] = useState(initialColor); + // hex color code as an int (NOT rgb 0-255) + + + function setUserColor(color: number) { + setColorPickerColor(color); + } + + function handleKey(e: KeyboardEvent) { + if (e.key === "Enter") + saveUserColor(); + } + + async function saveUserColor() { + colors[userId] = colorPickerColor.toString(16).padStart(6, "0"); + await set(DATASTORE_KEY, colors); + modalProps.onClose(); + } + + async function deleteUserColor() { + delete colors[userId]; + await set(DATASTORE_KEY, colors); + modalProps.onClose(); + } + + return ( + + + + Custom Color + + + + +
+ + Pick a color + + +
+
+ + + + + +
+ ); +} diff --git a/src/equicordplugins/customUserColors/index.tsx b/src/equicordplugins/customUserColors/index.tsx new file mode 100644 index 00000000..b204350d --- /dev/null +++ b/src/equicordplugins/customUserColors/index.tsx @@ -0,0 +1,119 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import "./styles.css"; + +import { NavContextMenuPatchCallback } from "@api/ContextMenu"; +import { get } from "@api/DataStore"; +import { definePluginSettings, Settings } from "@api/Settings"; +import { EquicordDevs } from "@utils/constants"; +import { openModal } from "@utils/modal"; +import definePlugin, { OptionType } from "@utils/types"; +import { extractAndLoadChunksLazy } from "@webpack"; +import { Menu, UserStore } from "@webpack/common"; +import { User } from "discord-types/general"; + +import { SetColorModal } from "./SetColorModal"; + +export const DATASTORE_KEY = "equicord-customcolors"; +export let colors: Record = {}; +(async () => { + colors = await get>(DATASTORE_KEY) || {}; +})(); + +const requireSettingsMenu = extractAndLoadChunksLazy(['name:"UserSettings"'], /createPromise:.{0,20}(\i\.\i\("?.+?"?\).*?).then\(\i\.bind\(\i,"?(.+?)"?\)\).{0,50}"UserSettings"/); +// needed for color picker to be available without opening settings (ty pindms!!) +const userContextMenuPatch: NavContextMenuPatchCallback = (children, { user }: { user: User; }) => { + if (user?.id == null) return; + + const setCustomColorItem = ( + { + await requireSettingsMenu(); + openModal(modalProps => ); + }} + /> + ); + + children.push(, setCustomColorItem); + +}; + +export function getCustomColorString(userId: string, withHash?: boolean): string | undefined { + if (!colors[userId] || !Settings.plugins.customUserColors.enabled) + return; + + if (withHash) + return `#${colors[userId]}`; + + return colors[userId]; +} + +const settings = definePluginSettings({ + DmList: { + type: OptionType.BOOLEAN, + description: "Users with custom colors defined will have their name in the dm list colored", + default: true, + }, + colorInServers: { + type: OptionType.BOOLEAN, + description: "If name colors should be changed within servers", + default: true, + } +}); + +export default definePlugin({ + name: "customUserColors", + description: "Lets you add a custom color to any user, anywhere! Highly recommend to use with typingTweaks and roleColorEverywhere", + authors: [EquicordDevs.mochienya], + contextMenus: { "user-context": userContextMenuPatch }, + settings, + requireSettingsMenu, + getCustomColorString, + + patches: [ + { + // this also affects name headers in chats outside of servers + find: /type:\i\.\i\.Types\.REMIX/, + replacement: { + match: /style:"username".*?void 0/, + replace: "style:{color:$self.colorIfServer(arguments[0])}" + } + }, + { + predicate: () => settings.store.DmList, + find: /muted:\i=!1,highlighted:\i=!1/, + replacement: { + match: /(nameAndDecorators,)/, + replace: "$1style:{color:$self.colorDMList(arguments[0])}," + }, + }, + ], + + colorDMList(a: any): string | undefined { + try { + // @ts-ignore + const { id } = UserStore.findByTag(a.avatar.props["aria-label"]); + // get user id by props on avatars having username as aria label + const colorString = getCustomColorString(id, true); + if (colorString) + return colorString; + return "inherit"; + } catch { return; } // if you have a group in your dms then discord will crash on load without this + }, + + colorIfServer(a: any): string | undefined { + const roleColor = a.author.colorString; + + if (a.channel.guild_id && !settings.store.colorInServers) + return roleColor; + + const color = getCustomColorString(a.message.author.id, true); + return color ?? roleColor; + } +}); diff --git a/src/equicordplugins/customUserColors/styles.css b/src/equicordplugins/customUserColors/styles.css new file mode 100644 index 00000000..d48407d3 --- /dev/null +++ b/src/equicordplugins/customUserColors/styles.css @@ -0,0 +1,16 @@ +.vc-customColors-modal-header { + place-content: center; + justify-content: space-between; +} + +.vc-customColors-modal-header h1 { + margin: 0; +} + +.vc-customColors-modal-content { + padding: 1em; +} + +.vc-customColors-modal-footer { + gap: 16px; +} diff --git a/src/plugins/roleColorEverywhere/index.tsx b/src/plugins/roleColorEverywhere/index.tsx index 7b7bcc44..0330193c 100644 --- a/src/plugins/roleColorEverywhere/index.tsx +++ b/src/plugins/roleColorEverywhere/index.tsx @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { definePluginSettings } from "@api/Settings"; +import { definePluginSettings, Settings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { makeRange } from "@components/PluginSettings/components"; import { Devs } from "@utils/constants"; @@ -24,6 +24,7 @@ import { Logger } from "@utils/Logger"; import definePlugin, { OptionType } from "@utils/types"; import { findByCodeLazy } from "@webpack"; import { ChannelStore, GuildMemberStore, GuildStore } from "@webpack/common"; +import { getCustomColorString } from "@equicordplugins/customUserColors"; const useMessageAuthor = findByCodeLazy('"Result cannot be null because the message is not null"'); @@ -164,6 +165,9 @@ export default definePlugin({ getColorString(userId: string, channelOrGuildId: string) { try { + if (Settings.plugins.customUserColors.enabled) + return getCustomColorString(userId, true); + const guildId = ChannelStore.getChannel(channelOrGuildId)?.guild_id ?? GuildStore.getGuild(channelOrGuildId)?.id; if (guildId == null) return null; diff --git a/src/plugins/typingTweaks/index.tsx b/src/plugins/typingTweaks/index.tsx index ff68a486..ec86f0aa 100644 --- a/src/plugins/typingTweaks/index.tsx +++ b/src/plugins/typingTweaks/index.tsx @@ -16,13 +16,14 @@ * along with this program. If not, see . */ -import { definePluginSettings } from "@api/Settings"; +import { definePluginSettings, Settings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { openUserProfile } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; import { Avatar, GuildMemberStore, React, RelationshipStore } from "@webpack/common"; import { User } from "discord-types/general"; +import { getCustomColorString } from "@equicordplugins/customUserColors"; import { PropsWithChildren } from "react"; const settings = definePluginSettings({ @@ -57,6 +58,12 @@ interface Props { guildId: string; } +function TypingUserColor(guildId: string, userId: string) { + if (!settings.store.showRoleColors) return; + if (Settings.plugins.customUserColors.enabled) return getCustomColorString(userId, true); + return GuildMemberStore.getMember(guildId, userId)?.colorString; +} + const TypingUser = ErrorBoundary.wrap(function ({ user, guildId }: Props) { return (