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