/* * Vencord, a Discord client mod * Copyright (c) 2024 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ import { definePluginSettings } from "@api/Settings"; import { classNameFactory } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { Flex } from "@components/Flex"; import { EquicordDevs } from "@utils/constants"; import { openUserProfile } from "@utils/discord"; import { Margins } from "@utils/margins"; import { classes } from "@utils/misc"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack"; import { Clickable, Forms, i18n, RelationshipStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common"; import { User } from "discord-types/general"; interface WatchingProps { userIds: string[]; guildId?: string; } const cl = classNameFactory("whosWatching-"); function getUsername(user: any): string { return RelationshipStore.getNickname(user.id) || user.globalName || user.username; } const settings = definePluginSettings({ showPanel: { description: "Show spectators under screenshare panel", type: OptionType.BOOLEAN, default: true, restartNeeded: true }, }); function encodeStreamKey(stream) { const { streamType, guildId, channelId, ownerId } = stream; switch (streamType) { case "guild": return [streamType, guildId, channelId, ownerId].join(":"); case "call": return [streamType, channelId, ownerId].join(":"); default: throw console.log("Unknown stream type ".concat(streamType)); } } function Watching({ userIds, guildId }: WatchingProps): JSX.Element { // Missing Users happen when UserStore.getUser(id) returns null -- The client should automatically cache spectators, so this might not be possible but it's better to be sure just in case let missingUsers = 0; const users = userIds.map(id => UserStore.getUser(id)).filter(user => Boolean(user) ? true : (missingUsers += 1, false)); return (
{userIds.length ? (<> {i18n.Messages.SPECTATORS.format({ numViewers: userIds.length })} {users.map(user => ( {getUsername(user)} ))} {missingUsers > 0 && {`+${i18n.Messages.NUM_USERS.format({ num: missingUsers })}`}} ) : (No spectators)}
); } const ApplicationStreamingStore = findStoreLazy("ApplicationStreamingStore"); const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers"); const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"); export default definePlugin({ name: "WhosWatching", description: "Hover over the screenshare icon to view what users are watching your stream", authors: [EquicordDevs.Fres], settings: settings, patches: [ { find: ".Masks.STATUS_SCREENSHARE,width:32", replacement: { match: /(\i):function\(\)\{return (\i)\}/, replace: "$1:function(){return $self.component({OriginalComponent:$2})}" } }, { predicate: () => settings.store.showPanel, find: "this.isJoinableActivity()||", replacement: { match: /(this\.isJoinableActivity\(\).{0,200}children:.{0,50})"div"/, replace: "$1$self.WrapperComponent" } } ], WrapperComponent: ErrorBoundary.wrap(props => { const stream = useStateFromStores([ApplicationStreamingStore], () => ApplicationStreamingStore.getCurrentUserActiveStream()); if (!stream) return
{props.children}
; const userIds = ApplicationStreamingStore.getViewerIds(encodeStreamKey(stream)); let missingUsers = 0; const users = userIds.map(id => UserStore.getUser(id)).filter(user => Boolean(user) ? true : (missingUsers += 1, false)); function renderMoreUsers(_label: string, count: number) { const sliced = users.slice(count - 1); return ( }> {({ onMouseEnter, onMouseLeave }) => (
+{sliced.length + missingUsers}
)}
); } return ( <>
{props.children}
{users.length ? <> {i18n.Messages.SPECTATORS.format({ numViewers: userIds.length })} ( openUserProfile(user.id)} > {user.username} )} /> : No spectators }
); }), component: function ({ OriginalComponent }) { return ErrorBoundary.wrap((props: any) => { const stream = useStateFromStores([ApplicationStreamingStore], () => ApplicationStreamingStore.getCurrentUserActiveStream()); const viewers = ApplicationStreamingStore.getViewerIds(encodeStreamKey(stream)); return }> {({ onMouseEnter, onMouseLeave }) => (
)}
; }); } });