Add customUserColors (#150)

* add preferFriends to showMeYourName

* Add customUserColors

* pressing enter closes modal

* please update your discord types package

* sowwy..

* Do A Settings Check For Typing Tweaks

* Do a proper import on rolecoloreverywhere

---------

Co-authored-by: thororen <78185467+thororen1234@users.noreply.github.com>
This commit is contained in:
mochie 2025-02-14 08:27:38 +01:00 committed by GitHub
parent f385879b4b
commit 7ca3bbb8e6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 244 additions and 3 deletions

View file

@ -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<ColorPickerProps>("#{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 (
<ModalRoot {...modalProps}>
<ModalHeader className={cl("modal-header")}>
<Forms.FormTitle tag="h2">
Custom Color
</Forms.FormTitle>
<ModalCloseButton onClick={modalProps.onClose} />
</ModalHeader>
<ModalContent className={cl("modal-content")} onKeyDown={handleKey}>
<section className={Margins.bottom16}>
<Forms.FormTitle tag="h3">
Pick a color
</Forms.FormTitle>
<ColorPicker
color={colorPickerColor}
onChange={setUserColor}
showEyeDropper={false}
/>
</section>
</ModalContent>
<ModalFooter className={cl("modal-footer")}>
<Button
color={Button.Colors.RED}
onClick={deleteUserColor}
>
Delete Entry
</Button>
<Button
color={Button.Colors.BRAND}
onClick={saveUserColor}
>
Save
</Button>
</ModalFooter>
</ModalRoot>
);
}

View file

@ -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<string, string> = {};
(async () => {
colors = await get<Record<string, string>>(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 = (
<Menu.MenuItem
label="Set Color"
id="set-color"
action={async () => {
await requireSettingsMenu();
openModal(modalProps => <SetColorModal userId={user.id} modalProps={modalProps} />);
}}
/>
);
children.push(<Menu.MenuSeparator />, 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;
}
});

View file

@ -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;
}

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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;

View file

@ -16,13 +16,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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 (
<strong
@ -68,7 +75,7 @@ const TypingUser = ErrorBoundary.wrap(function ({ user, guildId }: Props) {
display: "grid",
gridAutoFlow: "column",
gap: "4px",
color: settings.store.showRoleColors ? GuildMemberStore.getMember(guildId, user.id)?.colorString : undefined,
color: typingUserColor(guildId, user.id),
cursor: "pointer"
}}
>