Plugin

RoleColorEverywhere

Adds the top role color anywhere possible

Roles Appearance
index.tsx
Download

Source

src/plugins/roleColorEverywhere/index.tsx
1import { definePluginSettings } from "@api/Settings";
2import ErrorBoundary from "@components/ErrorBoundary";
3import { Devs } from "@utils/constants";
4import { Logger } from "@utils/Logger";
5import definePlugin, { makeRange, OptionType } from "@utils/types";
6import { findByCodeLazy } from "@webpack";
7import { ChannelStore, GuildMemberStore, GuildRoleStore, GuildStore } from "@webpack/common";
8
9const useMessageAuthor = findByCodeLazy('"Result cannot be null because the message is not null"');
10
11const settings = definePluginSettings({
12 chatMentions: {
13 type: OptionType.BOOLEAN,
14 default: true,
15 description: "Show role colors in chat mentions (including in the message box)",
16 restartNeeded: true
17 },
18 memberList: {
19 type: OptionType.BOOLEAN,
20 default: true,
21 description: "Show role colors in member list role headers",
22 restartNeeded: true
23 },
24 voiceUsers: {
25 type: OptionType.BOOLEAN,
26 default: true,
27 description: "Show role colors in the voice chat user list",
28 restartNeeded: true
29 },
30 reactorsList: {
31 type: OptionType.BOOLEAN,
32 default: true,
33 description: "Show role colors in the reactors list",
34 restartNeeded: true
35 },
36 pollResults: {
37 type: OptionType.BOOLEAN,
38 default: true,
39 description: "Show role colors in the poll results",
40 restartNeeded: true
41 },
42 colorChatMessages: {
43 type: OptionType.BOOLEAN,
44 default: false,
45 description: "Color chat messages based on the author's role color",
46 restartNeeded: true,
47 },
48 messageSaturation: {
49 type: OptionType.SLIDER,
50 description: "Intensity of message coloring.",
51 markers: makeRange(0, 100, 10),
52 default: 30
53 }
54});
55
56export default definePlugin({
57 name: "RoleColorEverywhere",
58 authors: [Devs.KingFish, Devs.lewisakura, Devs.AutumnVN, Devs.Kyuuhachi, Devs.jamesbt365],
59 description: "Adds the top role color anywhere possible",
60 tags: ["Roles", "Appearance"],
61 settings,
62
63 patches: [
64 // Chat Mentions
65 {
66 find: ".USER_MENTION)",
67 replacement: [
68 {
69 match: /(?<=user:(\i),guildId:([^,]+?),.{0,100}?children:\i=>\i)\((\i)\)/,
70 replace: "({...$3,color:$self.getColorInt($1?.id,$2)})",
71 }
72 ],
73 predicate: () => settings.store.chatMentions
74 },
75 // Slate
76 {
77 // Same find as FullUserInChatbox
78 find: &#039;"text":"locked"&#039;,
79 replacement: [
80 {
81 match: /let\{id:(\i),guildId:\i,channelId:(\i)[^}]*\}.*?\.\i,{(?=children)/,
82 replace: "$&color:$self.getColorInt($1,$2),"
83 }
84 ],
85 predicate: () => settings.store.chatMentions
86 },
87 // Member List Role Headers
88 {
89 find: &#039;tutorialId:"whos-online&#039;,
90 replacement: [
91 {
92 match: /(#{intl::CHANNEL_MEMBERS_A11Y_LABEL}.+}\):null,).{0,100}?(?:—|\\u2014) ",\i\]\}\)\]/,
93 replace: "$1$self.RoleGroupColor(arguments[0])]"
94 },
95 ],
96 predicate: () => settings.store.memberList
97 },
98 {
99 find: "#{intl::THREAD_BROWSER_PRIVATE}",
100 replacement: [
101 {
102 match: /children:\[\i," (?:—|\\u2014) ",\i\]/,
103 replace: "children:[$self.RoleGroupColor(arguments[0])]"
104 },
105 ],
106 predicate: () => settings.store.memberList
107 },
108 // Voice Users
109 {
110 find: "#{intl::GUEST_NAME_SUFFIX})]",
111 replacement: [
112 {
113 match: /#{intl::GUEST_NAME_SUFFIX}.{0,50}?""\](?<=guildId:(\i),.+?user:(\i).+?)/,
114 replace: "$&,style:$self.getColorStyle($2.id,$1),"
115 }
116 ],
117 predicate: () => settings.store.voiceUsers
118 },
119 // Reaction List
120 {
121 find: "MessageReactions.render:",
122 replacement: {
123 match: /tag:"strong",variant:"text-md\/medium"(?<=onContextMenu:.{0,15}\((\i),(\i),\i\).+?)/,
124 replace: "$&,style:$self.getColorStyle($2?.id,$1?.channel?.id)"
125 },
126 predicate: () => settings.store.reactorsList,
127 },
128 // Poll Results
129 {
130 find: ",reactionVoteCounts",
131 replacement: {
132 match: /\.SIZE_32.+?variant:"text-md\/normal",className:\i\.\i,(?="aria-label":)/,
133 replace: "$&style:$self.getColorStyle(arguments[0]?.user?.id,arguments[0]?.channel?.id),"
134 },
135 predicate: () => settings.store.pollResults
136 },
137 // Messages
138 {
139 find: ".SEND_FAILED,",
140 replacement: {
141 match: /(?<=\]:(\i)\.isUnsupported.{0,50}?,)(?=children:\[)/,
142 replace: "style:$self.useMessageColorsStyle($1),"
143 },
144 predicate: () => settings.store.colorChatMessages
145 }
146 ],
147
148 getColorString(userId: string, channelOrGuildId: string) {
149 try {
150 const guildId = ChannelStore.getChannel(channelOrGuildId)?.guild_id ?? GuildStore.getGuild(channelOrGuildId)?.id;
151 if (guildId == null) return null;
152
153 return GuildMemberStore.getMember(guildId, userId)?.colorString ?? null;
154 } catch (e) {
155 new Logger("RoleColorEverywhere").error("Failed to get color string", e);
156 }
157
158 return null;
159 },
160
161 getColorInt(userId: string, channelOrGuildId: string) {
162 const colorString = this.getColorString(userId, channelOrGuildId);
163 return colorString && parseInt(colorString.slice(1), 16);
164 },
165
166 getColorStyle(userId: string, channelOrGuildId: string) {
167 const colorString = this.getColorString(userId, channelOrGuildId);
168
169 return colorString && {
170 color: colorString
171 };
172 },
173
174 useMessageColorsStyle(message: any) {
175 try {
176 const { messageSaturation } = settings.use(["messageSaturation"]);
177 const author = useMessageAuthor(message);
178
179 // Do not apply role color if the send fails, otherwise it becomes indistinguishable
180 if (message.state === "SEND_FAILED") return;
181
182 if (author.colorString != null && messageSaturation !== 0) {
183 const value = `color-mix(in oklab, ${author.colorString} ${messageSaturation}%, var({DEFAULT}))`;
184
185 return {
186 color: value.replace("{DEFAULT}", "--text-default"),
187 "--text-strong": value.replace("{DEFAULT}", "--text-strong"),
188 "--text-muted": value.replace("{DEFAULT}", "--text-muted")
189 };
190 }
191 } catch (e) {
192 new Logger("RoleColorEverywhere").error("Failed to get message color", e);
193 }
194
195 return null;
196 },
197
198 RoleGroupColor: ErrorBoundary.wrap(({ id, count, title, guildId, label }: { id: string; count: number; title: string; guildId: string; label: string; }) => {
199 const role = GuildRoleStore.getRole(guildId, id);
200
201 return (
202 <span style={{
203 color: role?.colorString,
204 fontWeight: "unset",
205 letterSpacing: ".05em"
206 }}>
207 {title ?? label} &mdash; {count}
208 </span>
209 );
210 }, { noop: true })
211});
212