Plugin

ValidUser

Fix mentions for unknown users showing up as '@unknown-user' (hover over a mention to fix it)

Chat Utility
index.tsx
Download

Source

src/plugins/validUser/index.tsx
1/*
2 * FiveCord — a Discord client mod
3 * Copyright (c) 2025 FiveCord
4 * SPDX-License-Identifier: GPL-3.0-or-later
5 */
6
7import ErrorBoundary from "@components/ErrorBoundary";
8import { Devs } from "@utils/constants";
9import { isNonNullish } from "@utils/guards";
10import { sleep } from "@utils/misc";
11import { Queue } from "@utils/Queue";
12import definePlugin from "@utils/types";
13import { ProfileBadge } from "@vencord/discord-types";
14import { Constants, FluxDispatcher, RestAPI, UserProfileStore, UserStore, useState } from "@webpack/common";
15import { type ComponentType, type ReactNode } from "react";
16
17// LYING to the type checker here
18const UserFlags = Constants.UserFlags as Record<string, number>;
19const badges: Record<string, ProfileBadge> = {
20 active_developer: { id: "active_developer", description: "Active Developer", icon: "6bdc42827a38498929a4920da12695d9", link: "https:class="ts-cmt">//support-dev.discord.com/hc/en-us/articles/10113997751447" },
21 bug_hunter_level_1: { id: "bug_hunter_level_1", description: "Discord Bug Hunter", icon: "2717692c7dca7289b35297368a940dd0", link: "https:class="ts-cmt">//support.discord.com/hc/en-us/articles/360046057772-Discord-Bugs" },
22 bug_hunter_level_2: { id: "bug_hunter_level_2", description: "Discord Bug Hunter", icon: "848f79194d4be5ff5f81505cbd0ce1e6", link: "https:class="ts-cmt">//support.discord.com/hc/en-us/articles/360046057772-Discord-Bugs" },
23 certified_moderator: { id: "certified_moderator", description: "Moderator Programs Alumni", icon: "fee1624003e2fee35cb398e125dc479b", link: "https:class="ts-cmt">//discord.com/safety" },
24 discord_employee: { id: "staff", description: "Discord Staff", icon: "5e74e9b61934fc1f67c65515d1f7e60d", link: "https:class="ts-cmt">//discord.com/company" },
25 get staff() { return this.discord_employee; },
26 hypesquad: { id: "hypesquad", description: "HypeSquad Events", icon: "bf01d1073931f921909045f3a39fd264", link: "https:class="ts-cmt">//discord.com/hypesquad" },
27 hypesquad_online_house_1: { id: "hypesquad_house_1", description: "HypeSquad Bravery", icon: "8a88d63823d8a71cd5e390baa45efa02", link: "https:class="ts-cmt">//discord.com/settings/hypesquad-online" },
28 hypesquad_online_house_2: { id: "hypesquad_house_2", description: "HypeSquad Brilliance", icon: "011940fd013da3f7fb926e4a1cd2e618", link: "https:class="ts-cmt">//discord.com/settings/hypesquad-online" },
29 hypesquad_online_house_3: { id: "hypesquad_house_3", description: "HypeSquad Balance", icon: "3aa41de486fa12454c3761e8e223442e", link: "https:class="ts-cmt">//discord.com/settings/hypesquad-online" },
30 partner: { id: "partner", description: "Partnered Server Owner", icon: "3f9748e53446a137a052f3454e2de41e", link: "https:class="ts-cmt">//discord.com/partners" },
31 premium: { id: "premium", description: "Subscriber", icon: "2ba85e8026a8614b640c2837bcdfe21b", link: "https:class="ts-cmt">//discord.com/settings/premium" },
32 premium_early_supporter: { id: "early_supporter", description: "Early Supporter", icon: "7060786766c9c840eb3019e725d2b358", link: "https:class="ts-cmt">//discord.com/settings/premium" },
33 verified_developer: { id: "verified_developer", description: "Early Verified Bot Developer", icon: "6df5892e0f35b051f8b61eace34f4967" },
34};
35
36const fetching = new Set<string>();
37const queue = new Queue(5);
38
39interface MentionProps {
40 data: {
41 userId?: string;
42 channelId?: string;
43 content: any;
44 };
45 parse: (content: any, props: MentionProps["props"]) => ReactNode;
46 props: {
47 key: string;
48 formatInline: boolean;
49 noStyleAndInteraction: boolean;
50 };
51 RoleMention: ComponentType<any>;
52 UserMention: ComponentType<any>;
53}
54
55async function getUser(id: string) {
56 let userObj = UserStore.getUser(id);
57 if (userObj)
58 return userObj;
59
60 const user: any = await RestAPI.get({ url: Constants.Endpoints.USER(id) }).then(response => {
61 FluxDispatcher.dispatch({
62 type: "USER_UPDATE",
63 user: response.body,
64 });
65
66 return response.body;
67 });
68
69 // Populate the profile
70 await FluxDispatcher.dispatch(
71 {
72 type: "USER_PROFILE_FETCH_FAILURE",
73 userId: id,
74 }
75 );
76
77 userObj = UserStore.getUser(id);
78 const fakeBadges: ProfileBadge[] = Object.entries(UserFlags)
79 .filter(([_, flag]) => !isNaN(flag) && userObj.hasFlag(flag))
80 .map(([key]) => badges[key.toLowerCase()])
81 .filter(isNonNullish);
82 if (user.premium_type || !user.bot && (user.banner || user.avatar?.startsWith?.("a_")))
83 fakeBadges.push(badges.premium);
84
85 // Fill in what we can deduce
86 const profile = UserProfileStore.getUserProfile(id);
87 if (profile) {
88 profile.accentColor = user.accent_color;
89 profile.badges = fakeBadges;
90 profile.banner = user.banner;
91 profile.premiumType = user.premium_type;
92 }
93
94 return userObj;
95}
96
97function MentionWrapper({ data, UserMention, RoleMention, parse, props }: MentionProps) {
98 const [userId, setUserId] = useState(data.userId);
99
100 // if userId is set it means the user is cached. Uncached users have userId set to undefined
101 if (userId)
102 return (
103 <UserMention
104 className="mention"
105 userId={userId}
106 channelId={data.channelId}
107 inlinePreview={props.noStyleAndInteraction}
108 key={props.key}
109 />
110 );
111
112 // Parses the raw text node array data.content into a ReactNode[]: ["<@userid>"]
113 const children = parse(data.content, props);
114
115 return (
116 // Discord is deranged and renders unknown user mentions as role mentions
117 <RoleMention
118 {...data}
119 inlinePreview={props.formatInline}
120 >
121 <span
122 onMouseEnter={() => {
123 const mention = children?.[0]?.props?.children;
124 if (typeof mention !== "string") return;
125
126 const id = mention.match(/<@!?(\d+)>/)?.[1];
127 if (!id) return;
128
129 if (fetching.has(id))
130 return;
131
132 if (UserStore.getUser(id))
133 return setUserId(id);
134
135 const fetch = () => {
136 fetching.add(id);
137
138 queue.unshift(() =>
139 getUser(id)
140 .then(() => {
141 setUserId(id);
142 fetching.delete(id);
143 })
144 .catch(e => {
145 if (e?.status === 429) {
146 queue.unshift(() => sleep(e?.body?.retry_after ?? 1000).then(fetch));
147 fetching.delete(id);
148 }
149 })
150 .finally(() => sleep(300))
151 );
152 };
153
154 fetch();
155 }}
156 >
157 {children}
158 </span>
159 </RoleMention>
160 );
161}
162
163export default definePlugin({
164 name: "ValidUser",
165 description: "Fix mentions for unknown users showing up as &#039;@unknown-user&#039; (hover over a mention to fix it)",
166 tags: ["Chat", "Utility"],
167 authors: [Devs.Ven, Devs.Dolfies],
168 searchTerms: ["MentionCacheFix"],
169
170 patches: [
171 {
172 find: &#039;className:"mention"&#039;,
173 replacement: {
174 // mention = { react: function (data, parse, props) { if (data.userId == null) return RoleMention() else return UserMention()
175 match: /react(?=\(\i,\i,\i\).{0,100}return null==.{0,70}\?\(0,\i\.jsx\)\((\i\.\i),.+?jsx\)\((\i\.\i),\{className:"mention")/,
176 // react: (...args) => OurWrapper(RoleMention, UserMention, ...args), originalReact: theirFunc
177 replace: "react:(...args)=>$self.renderMention($1,$2,...args),originalReact"
178 }
179 },
180 {
181 find: "unknownUserMentionPlaceholder:",
182 replacement: {
183 match: /unknownUserMentionPlaceholder:/,
184 replace: "$&false&&"
185 }
186 }
187 ],
188
189 renderMention(RoleMention, UserMention, data, parse, props) {
190 return (
191 <ErrorBoundary noop>
192 <MentionWrapper
193 key={"mention" + data.userId}
194 RoleMention={RoleMention}
195 UserMention={UserMention}
196 data={data}
197 parse={parse}
198 props={props}
199 />
200 </ErrorBoundary>
201 );
202 },
203});
204