diff options
Diffstat (limited to 'src/plugins/whoReacted/index.tsx')
-rw-r--r-- | src/plugins/whoReacted/index.tsx | 262 |
1 files changed, 262 insertions, 0 deletions
diff --git a/src/plugins/whoReacted/index.tsx b/src/plugins/whoReacted/index.tsx new file mode 100644 index 0000000..0bdb5c2 --- /dev/null +++ b/src/plugins/whoReacted/index.tsx @@ -0,0 +1,262 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2022 Vendicated and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +import ErrorBoundary from "@components/ErrorBoundary"; +import { Devs } from "@utils/constants"; +import { sleep } from "@utils/misc"; +import { Queue } from "@utils/Queue"; +import { LazyComponent, useForceUpdater } from "@utils/react"; +import definePlugin from "@utils/types"; +import { findByCode, findByPropsLazy } from "@webpack"; +import { ChannelStore, FluxDispatcher, React, RestAPI, Tooltip } from "@webpack/common"; +import { ReactionEmoji, User } from "discord-types/general"; + +const UserSummaryItem = LazyComponent(() => findByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers")); +const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"); + +const ReactionStore = findByPropsLazy("getReactions"); + +const queue = new Queue(); + +function fetchReactions(msg: Message, emoji: ReactionEmoji, type: number) { + const key = emoji.name + (emoji.id ? `:${emoji.id}` : ""); + return RestAPI.get({ + url: `/channels/${msg.channel_id}/messages/${msg.id}/reactions/${key}`, + query: { + limit: 100, + type + }, + oldFormErrors: true + }) + .then(res => FluxDispatcher.dispatch({ + type: "MESSAGE_REACTION_ADD_USERS", + channelId: msg.channel_id, + messageId: msg.id, + users: res.body, + emoji, + reactionType: type + })) + .catch(console.error) + .finally(() => sleep(250)); +} + +function getReactionsWithQueue(msg: Message, e: ReactionEmoji, type: number) { + const key = `${msg.id}:${e.name}:${e.id ?? ""}:${type}`; + const cache = ReactionStore.__getLocalVars().reactions[key] ??= { fetched: false, users: {} }; + if (!cache.fetched) { + queue.unshift(() => + fetchReactions(msg, e, type) + ); + cache.fetched = true; + } + + return cache.users; +} + +function makeRenderMoreUsers(users: User[]) { + return function renderMoreUsers(_label: string, _count: number) { + return ( + <Tooltip text={users.slice(5).map(u => u.username).join(", ")} > + {({ onMouseEnter, onMouseLeave }) => ( + <div + className={AvatarStyles.moreUsers} + onMouseEnter={onMouseEnter} + onMouseLeave={onMouseLeave} + > + +{users.length - 5} + </div> + )} + </Tooltip > + ); + }; +} + +function handleClickAvatar(event: React.MouseEvent<HTMLElement, MouseEvent>) { + event.stopPropagation(); +} + +export default definePlugin({ + name: "WhoReacted", + description: "Renders the Avatars of reactors", + authors: [Devs.Ven, Devs.KannaDev], + + patches: [{ + find: ",reactionRef:", + replacement: { + match: /(?<=(\i)=(\i)\.hideCount,)(.+?reactionCount.+?\}\))/, + replace: (_, hideCount, props, rest) => `whoReactedProps=${props},${rest},${hideCount}?null:$self.renderUsers(whoReactedProps)` + } + }], + + renderUsers(props: RootObject) { + return props.message.reactions.length > 10 ? null : ( + <ErrorBoundary noop> + <this._renderUsers {...props} /> + </ErrorBoundary> + ); + }, + + _renderUsers({ message, emoji, type }: RootObject) { + const forceUpdate = useForceUpdater(); + React.useEffect(() => { + const cb = (e: any) => { + if (e.messageId === message.id) + forceUpdate(); + }; + FluxDispatcher.subscribe("MESSAGE_REACTION_ADD_USERS", cb); + + return () => FluxDispatcher.unsubscribe("MESSAGE_REACTION_ADD_USERS", cb); + }, [message.id]); + + const reactions = getReactionsWithQueue(message, emoji, type); + const users = Object.values(reactions).filter(Boolean) as User[]; + + for (const user of users) { + FluxDispatcher.dispatch({ + type: "USER_UPDATE", + user + }); + } + + return ( + <div + style={{ marginLeft: "0.5em", transform: "scale(0.9)" }} + > + <div onClick={handleClickAvatar}> + <UserSummaryItem + users={users} + guildId={ChannelStore.getChannel(message.channel_id)?.guild_id} + renderIcon={false} + max={5} + showDefaultAvatarsForNullUsers + showUserPopout + renderMoreUsers={makeRenderMoreUsers(users)} + /> + </div> + </div> + ); + } +}); + + +export interface GuildMemberAvatar { } + +export interface Author { + id: string; + username: string; + discriminator: string; + avatar: string; + avatarDecoration?: any; + email: string; + verified: boolean; + bot: boolean; + system: boolean; + mfaEnabled: boolean; + mobile: boolean; + desktop: boolean; + premiumType: number; + flags: number; + publicFlags: number; + purchasedFlags: number; + premiumUsageFlags: number; + phone: string; + nsfwAllowed: boolean; + guildMemberAvatars: GuildMemberAvatar; +} + +export interface Emoji { + id: string; + name: string; +} + +export interface Reaction { + emoji: Emoji; + count: number; + burst_user_ids: any[]; + burst_count: number; + burst_colors: any[]; + burst_me: boolean; + me: boolean; +} + +export interface Message { + id: string; + type: number; + channel_id: string; + author: Author; + content: string; + deleted: boolean; + editHistory: any[]; + attachments: any[]; + embeds: any[]; + mentions: any[]; + mentionRoles: any[]; + mentionChannels: any[]; + mentioned: boolean; + pinned: boolean; + mentionEveryone: boolean; + tts: boolean; + codedLinks: any[]; + giftCodes: any[]; + timestamp: string; + editedTimestamp?: any; + state: string; + nonce?: any; + blocked: boolean; + call?: any; + bot: boolean; + webhookId?: any; + reactions: Reaction[]; + applicationId?: any; + application?: any; + activity?: any; + messageReference?: any; + flags: number; + isSearchHit: boolean; + stickers: any[]; + stickerItems: any[]; + components: any[]; + loggingName?: any; + interaction?: any; + interactionData?: any; + interactionError?: any; +} + +export interface Emoji { + id: string; + name: string; + animated: boolean; +} + +export interface RootObject { + message: Message; + readOnly: boolean; + isLurking: boolean; + isPendingMember: boolean; + useChatFontScaling: boolean; + emoji: Emoji; + count: number; + burst_user_ids: any[]; + burst_count: number; + burst_colors: any[]; + burst_me: boolean; + me: boolean; + type: number; + hideEmoji: boolean; + remainingBurstCurrency: number; +} |