From 3feb3d91d96510e45895b6cbdb2d775f87919d97 Mon Sep 17 00:00:00 2001 From: sirpiglr <49359077-sirpiglr@users.noreply.replit.com> Date: Sat, 13 Dec 2025 04:51:25 +0000 Subject: [PATCH] Add a way to display participants in an activity Define interfaces for participant and voice state, fetch and subscribe to participant updates, and display participants in a new component within the Activity page. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 9203795e-937a-4306-b81d-b4d5c78c240e Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: 4414273a-a40f-4598-8758-a875f7eccc1c Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7c94b7a0-29c7-4f2e-94ef-44b2153872b7/9203795e-937a-4306-b81d-b4d5c78c240e/139vJay Replit-Helium-Checkpoint-Created: true --- client/contexts/DiscordActivityContext.tsx | 111 +++++++++++++++++++++ client/pages/Activity.tsx | 56 ++++++++++- 2 files changed, 166 insertions(+), 1 deletion(-) diff --git a/client/contexts/DiscordActivityContext.tsx b/client/contexts/DiscordActivityContext.tsx index b5805111..dea0f31a 100644 --- a/client/contexts/DiscordActivityContext.tsx +++ b/client/contexts/DiscordActivityContext.tsx @@ -13,12 +13,38 @@ interface DiscordUser { primary_arm: string | null; } +interface Participant { + id: string; + username: string; + discriminator: string; + avatar: string | null; + bot: boolean; + flags: number; + global_name: string | null; +} + +interface VoiceState { + mute: boolean; + deaf: boolean; + self_mute: boolean; + self_deaf: boolean; + suppress: boolean; +} + +interface ParticipantWithVoice extends Participant { + voice_state?: VoiceState; + speaking?: boolean; +} + interface DiscordActivityContextType { isActivity: boolean; isLoading: boolean; user: DiscordUser | null; error: string | null; discordSdk: any | null; + participants: ParticipantWithVoice[]; + channelId: string | null; + guildId: string | null; openExternalLink: (url: string) => Promise; } @@ -28,6 +54,9 @@ const DiscordActivityContext = createContext({ user: null, error: null, discordSdk: null, + participants: [], + channelId: null, + guildId: null, openExternalLink: async () => {}, }); @@ -54,6 +83,10 @@ export const DiscordActivityProvider: React.FC< const [error, setError] = useState(null); const [discordSdk, setDiscordSdk] = useState(null); const [auth, setAuth] = useState(null); + const [participants, setParticipants] = useState([]); + const [channelId, setChannelId] = useState(null); + const [guildId, setGuildId] = useState(null); + const [speakingUsers, setSpeakingUsers] = useState>(new Set()); useEffect(() => { const initializeActivity = async () => { @@ -208,6 +241,81 @@ export const DiscordActivityProvider: React.FC< setUser(userData); setError(null); console.log("[Discord Activity] User authenticated successfully"); + + // Store channel and guild info + setChannelId(sdk.channelId); + setGuildId(sdk.guildId); + + // Fetch initial participants + try { + console.log("[Discord Activity] Fetching participants..."); + const participantsResult = await sdk.commands.getInstanceConnectedParticipants(); + if (participantsResult?.participants) { + const participantList = participantsResult.participants.map((p: any) => ({ + id: p.id, + username: p.username, + discriminator: p.discriminator || "0", + avatar: p.avatar, + bot: p.bot || false, + flags: p.flags || 0, + global_name: p.global_name, + })); + setParticipants(participantList); + console.log("[Discord Activity] Initial participants:", participantList.length); + } + + // Subscribe to participant updates + sdk.subscribe("ACTIVITY_INSTANCE_PARTICIPANTS_UPDATE", (data: any) => { + console.log("[Discord Activity] Participants updated:", data); + if (data?.participants) { + const updatedList = data.participants.map((p: any) => ({ + id: p.id, + username: p.username, + discriminator: p.discriminator || "0", + avatar: p.avatar, + bot: p.bot || false, + flags: p.flags || 0, + global_name: p.global_name, + })); + setParticipants(updatedList); + } + }); + + // Subscribe to speaking updates if in voice channel + if (sdk.channelId) { + try { + sdk.subscribe("SPEAKING_START", (data: any) => { + console.log("[Discord Activity] Speaking start:", data); + if (data?.user_id) { + setSpeakingUsers(prev => new Set(prev).add(data.user_id)); + setParticipants(prev => prev.map(p => + p.id === data.user_id ? { ...p, speaking: true } : p + )); + } + }, { channel_id: sdk.channelId }); + + sdk.subscribe("SPEAKING_STOP", (data: any) => { + console.log("[Discord Activity] Speaking stop:", data); + if (data?.user_id) { + setSpeakingUsers(prev => { + const next = new Set(prev); + next.delete(data.user_id); + return next; + }); + setParticipants(prev => prev.map(p => + p.id === data.user_id ? { ...p, speaking: false } : p + )); + } + }, { channel_id: sdk.channelId }); + + console.log("[Discord Activity] Voice subscriptions active"); + } catch (voiceErr) { + console.log("[Discord Activity] Voice subscription not available:", voiceErr); + } + } + } catch (participantErr) { + console.log("[Discord Activity] Could not fetch participants:", participantErr); + } } catch (err: any) { console.error("Discord Activity initialization error:", err); console.error("Error details:", { @@ -255,6 +363,9 @@ export const DiscordActivityProvider: React.FC< user, error, discordSdk, + participants, + channelId, + guildId, openExternalLink, }} > diff --git a/client/pages/Activity.tsx b/client/pages/Activity.tsx index b715fc9e..684bfc12 100644 --- a/client/pages/Activity.tsx +++ b/client/pages/Activity.tsx @@ -679,8 +679,59 @@ function BadgesTab({ userId, openExternalLink }: { userId?: string; openExternal ); } +function ParticipantsBar({ participants, currentUserId }: { participants: any[]; currentUserId?: string }) { + const otherParticipants = participants.filter(p => p.id !== currentUserId); + + if (otherParticipants.length === 0) return null; + + return ( +
+ + {otherParticipants.length} here +
+ {otherParticipants.slice(0, 8).map((p) => ( + + {p.avatar ? ( + {p.global_name + ) : ( +
+ {(p.global_name || p.username)?.[0]?.toUpperCase() || "?"} +
+ )} + {p.speaking && ( + + )} + + ))} + {otherParticipants.length > 8 && ( +
+ +{otherParticipants.length - 8} +
+ )} +
+
+ ); +} + export default function Activity() { - const { isActivity, isLoading, user, error, openExternalLink } = useDiscordActivity(); + const { isActivity, isLoading, user, error, openExternalLink, participants } = useDiscordActivity(); const [activeTab, setActiveTab] = useState("feed"); const [xpGain, setXpGain] = useState(null); const [showConfetti, setShowConfetti] = useState(false); @@ -818,6 +869,9 @@ export default function Activity() { + {/* Participants Bar */} + + {/* Tab Navigation */}
{tabs.map((tab) => {