From ef08db8160d4dfabd6777b0af4edb4415a08e970 Mon Sep 17 00:00:00 2001 From: Leafus Date: Sat, 11 Jul 2026 22:49:41 +0200 Subject: [PATCH 1/2] feat: add presences on ready_supplemental --- src/gateway/opcodes/Identify.ts | 78 ++++++++++++++++++++++++++------- 1 file changed, 63 insertions(+), 15 deletions(-) diff --git a/src/gateway/opcodes/Identify.ts b/src/gateway/opcodes/Identify.ts index 08b77b6583..873ff93c5c 100644 --- a/src/gateway/opcodes/Identify.ts +++ b/src/gateway/opcodes/Identify.ts @@ -50,6 +50,8 @@ import { Intents, OPCodes, OrmUtils, + getMostRelevantSession, + Presence, PresenceUpdateEvent, ReadyEventData, ReadyGuildDTO, @@ -58,7 +60,7 @@ import { TraceNode, TraceRoot, } from "@spacebar/util"; -import { ChannelType, DefaultUserGuildSettings, DMChannel, IdentifySchema, PrivateUserProjection, PublicUser, PublicUserProjection } from "@spacebar/schemas"; +import { ChannelType, DefaultUserGuildSettings, DMChannel, IdentifySchema, PrivateUserProjection, PublicUser, PublicUserProjection, RelationshipType } from "@spacebar/schemas"; import { check } from "./instanceOf"; // TODO: user sharding @@ -323,6 +325,46 @@ export async function onIdentify(this: WebSocket, data: Payload) { const userMetaQueryTime = taskSw.getElapsedAndReset(); + const friendPresenceUserIds = [...new Set(relationships.filter((relationship) => relationship.type === RelationshipType.friends).map((relationship) => relationship.to_id))]; + const { result: friendPresenceSessions, elapsed: friendPresenceSessionsQueryTime } = await timePromise(() => + friendPresenceUserIds.length === 0 + ? Promise.resolve([] as Session[]) + : getDatabase()!.getRepository(Session).find({ + where: { + user_id: In(friendPresenceUserIds), + is_admin_session: false, + status: Not(In(["offline", "invisible"])), + }, + relations: { user: true }, + select: { + user_id: true, + status: true, + activities: true, + client_status: true, + user: Object.fromEntries(PublicUserProjection.map((x) => [x, true])), + }, + }), + ); + + const { result: friendPresences, elapsed: generateFriendPresencesTime } = timeFunction(() => { + const sessionsByUserId = arrayGroupBy(friendPresenceSessions, (session) => session.user_id); + + return friendPresenceUserIds.flatMap((userId) => { + const sessions = sessionsByUserId.get(userId); + if (!sessions?.length) return []; + + const session = getMostRelevantSession(sessions); + return [ + { + user: session.user.toPublicUser(), + status: session.getPublicStatus(), + activities: session.activities, + client_status: session.client_status, + }, + ]; + }); + }); + const memberGuildIds = members.map((m) => m.guild_id); // select relations @@ -688,7 +730,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { version: 0, // TODO }, private_channels: channels, - presences: [], // TODO: Send actual data + presences: [], session_id: this.session_id, country_code: this.session?.last_seen_location_info?.country_code ?? user.settings!.locale, users: Array.from(users), @@ -746,10 +788,12 @@ export async function onIdentify(this: WebSocket, data: Payload) { queryGuildsTime, guildRelationQueryTime, createUserSettingsTime, + friendPresenceSessionsQueryTime, mergedMembersTime, generateGuildsListTime, generateUserGuildSettingsTime, generateDmChannelsTime, + generateFriendPresencesTime, appendRelationshipsTime, findAndGenerateSessionReplaceTime, emitSessionsReplaceTime, @@ -768,6 +812,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { sessionSaveTime, sessionQueryTime, relationshipQueryTime, + friendPresenceSessionsQueryTime, settingsQueryTime, settingsProtosQueryTime, applicationQueryTime, @@ -845,28 +890,31 @@ export async function onIdentify(this: WebSocket, data: Payload) { }), ); - const readySupplementalGuilds = (guilds.filter((guild) => !guild.unavailable) as Guild[]).map((guild) => ({ - voice_states: guild.voice_states.map((state) => VoiceState.prototype.toPublicVoiceState.apply(state)), - id: guild.id, - embedded_activities: [], - })); + const readySupplementalGuilds = guilds.map((guild) => { + if (!("voice_states" in guild)) return { id: guild.id }; + + const availableGuild = guild as Guild; + return { + id: availableGuild.id, + voice_states: availableGuild.voice_states.map((state) => VoiceState.prototype.toPublicVoiceState.apply(state)), + activity_instances: [], + }; + }); - // TODO: ready supplemental await Send(this, { op: OPCodes.DISPATCH, t: EVENTEnum.ReadySupplemental, s: this.sequence++, d: { + guilds: readySupplementalGuilds, + merged_members: guilds.map(() => []), merged_presences: { - guilds: [], - friends: [], + friends: friendPresences, + guilds: guilds.map(() => []), }, - // these merged members seem to be all users currently in vc in your guilds - merged_members: [], lazy_private_channels: [], - guilds: readySupplementalGuilds, // { voice_states: [], id: string, embedded_activities: [] } - // embedded_activities are users currently in an activity? - disclose: [], // Config.get().general.uniqueUsernames ? ["pomelo"] : [] + disclose: [], + game_invites: [], }, }); From ac259b07e34755a5a0d39da6d9d49e02a0b63587 Mon Sep 17 00:00:00 2001 From: Leafus Date: Sun, 12 Jul 2026 15:36:17 +0200 Subject: [PATCH 2/2] feat: restore comments --- src/gateway/opcodes/Identify.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/gateway/opcodes/Identify.ts b/src/gateway/opcodes/Identify.ts index 873ff93c5c..5257c4d0df 100644 --- a/src/gateway/opcodes/Identify.ts +++ b/src/gateway/opcodes/Identify.ts @@ -906,14 +906,15 @@ export async function onIdentify(this: WebSocket, data: Payload) { t: EVENTEnum.ReadySupplemental, s: this.sequence++, d: { - guilds: readySupplementalGuilds, - merged_members: guilds.map(() => []), + guilds: readySupplementalGuilds, // { voice_states: [], id: string, embedded_activities: [] } + merged_members: guilds.map(() => []), // these merged members seem to be all users currently in vc in your guilds merged_presences: { friends: friendPresences, guilds: guilds.map(() => []), }, lazy_private_channels: [], - disclose: [], + // embedded_activities are users currently in an activity? + disclose: [], // Config.get().general.uniqueUsernames ? ["pomelo"] : [] game_invites: [], }, });