Plugin
WhoReacted
Renders the avatars of users who reacted to a message
1
import ErrorBoundary from "@components/ErrorBoundary";2
import { Devs } from "@utils/constants";3
import { sleep } from "@utils/misc";4
import { Queue } from "@utils/Queue";5
import { useForceUpdater } from "@utils/react";6
import definePlugin from "@utils/types";7
import { CustomEmoji, Message, ReactionEmoji, User } from "@vencord/discord-types";8
import { ChannelStore, Constants, FluxDispatcher, React, RestAPI, useEffect, useLayoutEffect, UserStore, UserSummaryItem } from "@webpack/common";9
10
interface ReactionCacheEntry {11
fetched: boolean;12
users: Map<string, User>;13
}14
15
interface ReactionProps {16
message: Message;17
emoji: CustomEmoji;18
type: number;19
}20
21
let Scroll: any = null;22
const queue = new Queue();23
let reactions: Record<string, ReactionCacheEntry> = {};24
25
function fetchReactions(msg: Message, emoji: ReactionEmoji, type: number) {26
const key = emoji.name + (emoji.id ? `:${emoji.id}` : "");27
return RestAPI.get({28
url: Constants.Endpoints.REACTIONS(msg.channel_id, msg.id, key),29
query: {30
limit: 100,31
type32
},33
oldFormErrors: true34
})35
.then(res => {36
for (const user of res.body) {37
FluxDispatcher.dispatch({38
type: "USER_UPDATE",39
user40
});41
}42
43
FluxDispatcher.dispatch({44
type: "MESSAGE_REACTION_ADD_USERS",45
channelId: msg.channel_id,46
messageId: msg.id,47
users: res.body,48
emoji,49
reactionType: type50
});51
})52
.catch(console.error)53
.finally(() => sleep(250));54
}55
56
function getReactionsWithQueue(msg: Message, e: ReactionEmoji, type: number) {57
const key = `${msg.id}:${e.name}:${e.id ?? ""}:${type}`;58
const cache = reactions[key] ??= { fetched: false, users: new Map() };59
if (!cache.fetched) {60
queue.unshift(() => fetchReactions(msg, e, type));61
cache.fetched = true;62
}63
64
return cache.users;65
}66
67
function handleClickAvatar(event: React.UIEvent<HTMLElement, Event>) {68
event.stopPropagation();69
}70
71
function ReactionUsers({ message, emoji, type }: ReactionProps) {72
const forceUpdate = useForceUpdater();73
74
useLayoutEffect(() => { class="ts-cmt">// bc need to prevent autoscrolling75
if (Scroll?.scrollCounter > 0) {76
Scroll.setAutomaticAnchor(null);77
}78
});79
80
useEffect(() => {81
const cb = (e: any) => {82
if (e?.messageId === message.id)83
forceUpdate();84
};85
FluxDispatcher.subscribe("MESSAGE_REACTION_ADD_USERS", cb);86
87
return () => FluxDispatcher.unsubscribe("MESSAGE_REACTION_ADD_USERS", cb);88
}, [message.id, forceUpdate]);89
90
const reactions = getReactionsWithQueue(message, emoji, type);91
const users = Array.from(reactions, ([id]) => UserStore.getUser(id)).filter(Boolean);92
93
return (94
<div95
style={{ marginLeft: "0.5em", transform: "scale(0.9)" }}96
>97
<div onClick={handleClickAvatar} onKeyDown={handleClickAvatar}>98
<UserSummaryItem99
users={users}100
guildId={ChannelStore.getChannel(message.channel_id)?.guild_id}101
renderIcon={false}102
max={5}103
showDefaultAvatarsForNullUsers104
showUserPopout105
/>106
</div>107
</div>108
);109
}110
111
export default definePlugin({112
name: "WhoReacted",113
description: "Renders the avatars of users who reacted to a message",114
tags: ["Reactions", "Chat", "Appearance"],115
authors: [Devs.Ven, Devs.KannaDev, Devs.newwares],116
117
patches: [118
{119
find: ",reactionRef:",120
replacement: {121
match: /(\i)\?null:\(0,\i\.jsx\)\(\i\.\i,{className:\i\.reactionCount,.*?}\),(?<=(emoji:\i,message:\i,type:\i).+?)/,122
replace: "$&$1?null:$self.renderUsers({$2}),"123
}124
},125
{126
find: 039;"MessageReactionsStore"039;,127
replacement: {128
match: /CONNECTION_OPEN:function\(\){(\i)={}/,129
replace: "$&;$self.reactions=$1;"130
}131
},132
{133
134
find: "cleanAutomaticAnchor(){",135
replacement: {136
match: /constructor\(\i\)\{(?=.{0,100}(?:automaticAnchor|\.messages\.loadingMore))/,137
replace: "$&$self.setScrollObj(this);"138
}139
}140
],141
142
renderUsers: ErrorBoundary.wrap((props: ReactionProps) => {143
return props.message.reactions.length > 10144
? null145
: <ReactionUsers {...props} />;146
}, { noop: true }),147
148
setScrollObj(scroll: any) {149
Scroll = scroll;150
},151
152
set reactions(value: any) {153
reactions = value;154
}155
});156