From 843be2307564a8a594b83ef835a0b368d3ca1246 Mon Sep 17 00:00:00 2001 From: Davide <137603413+LW-Davide@users.noreply.github.com> Date: Wed, 18 Feb 2026 18:16:45 +0100 Subject: [PATCH 1/8] Visual BUG fix after adding new friend+DM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Visual BUG, ​​after adding a new friend and (without refreshing the page) immediately creating a DM, the displayed name is your own name instead of the other person's name --- src/util/entities/Channel.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/util/entities/Channel.ts b/src/util/entities/Channel.ts index e47d078a21..d8462c8f63 100644 --- a/src/util/entities/Channel.ts +++ b/src/util/entities/Channel.ts @@ -494,13 +494,12 @@ export class Channel extends BaseClass { } else { await emitEvent({ event: "CHANNEL_CREATE", - data: channel_dto, + data: channel_dto.excludedRecipients([creator_user_id]), user_id: creator_user_id, }); } - if (recipients.length === 1) return channel_dto; - else return channel_dto.excludedRecipients([creator_user_id]); + if (recipients.length === 1) return channel_dto.excludedRecipients([creator_user_id]); } static async removeRecipientFromChannel(channel: Channel, user_id: string) { From d1e8c55f53c6628a48ebb0867d3df989517166fe Mon Sep 17 00:00:00 2001 From: Davide <137603413+LW-Davide@users.noreply.github.com> Date: Thu, 19 Feb 2026 03:11:40 +0100 Subject: [PATCH 2/8] Deleting settings along with the account This aims to fix the account deletion error. --- src/api/routes/users/@me/delete.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/api/routes/users/@me/delete.ts b/src/api/routes/users/@me/delete.ts index 9f4fbf927e..754b77d1f5 100644 --- a/src/api/routes/users/@me/delete.ts +++ b/src/api/routes/users/@me/delete.ts @@ -17,7 +17,7 @@ */ import { route } from "@spacebar/api"; -import { DiscordApiErrors, Guild, Member, User, UserSettingsProtos } from "@spacebar/util"; +import { Member, User, UserSettingsProtos } from "@spacebar/util"; import bcrypt from "bcrypt"; import { Request, Response, Router } from "express"; import { HTTPError } from "lambert-server"; @@ -55,14 +55,8 @@ router.post( // TODO: decrement guild member count if (correctpass) { - // Check if the user owns any guilds. - const ownedGuilds = await Guild.findOne({ where: { owner_id: req.user_id } }); - if (ownedGuilds) { - throw new HTTPError("User owns guilds and cannot be deleted", 403); - } - const members = await Member.find({ where: { id: req.user_id } }); - await UserSettingsProtos.delete({ user_id: req.user_id }); + await UserSettingsProtos.delete({ user_id: req.user_id }); await Promise.all([User.delete({ id: req.user_id }), ...members.map((member) => Member.removeFromGuild(member.id, member.guild_id))]); res.sendStatus(204); From 6aebd7e4e73fa1a6e7dd2124a37d1e9c856b2d9f Mon Sep 17 00:00:00 2001 From: Davide <137603413+LW-Davide@users.noreply.github.com> Date: Thu, 19 Feb 2026 06:06:27 +0100 Subject: [PATCH 3/8] Handle deleted users This needs to avoid empty or self recipients in case of deleted users --- src/util/dtos/DmChannelDTO.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/util/dtos/DmChannelDTO.ts b/src/util/dtos/DmChannelDTO.ts index 956fe48a4b..f703630a73 100644 --- a/src/util/dtos/DmChannelDTO.ts +++ b/src/util/dtos/DmChannelDTO.ts @@ -51,6 +51,20 @@ export class DmChannelDTO { }) || [], ) ).map((u) => new MinimalPublicUserDTO(u)); + + if (obj.type === 1 && obj.recipients.length === 0) { + obj.recipients = [ + new MinimalPublicUserDTO({ + avatar: null, + discriminator: "0000", + id: "0", + public_flags: 0, + username: "Deleted User", + badge_ids: null, + } as any), + ]; + } + return obj; } From 7970d3560b9376b8710e9ef369f0609621e4ff37 Mon Sep 17 00:00:00 2001 From: Davide <137603413+LW-Davide@users.noreply.github.com> Date: Thu, 19 Feb 2026 06:08:47 +0100 Subject: [PATCH 4/8] Handle deleted users (gateway) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This needs to avoid empty or self recipients in case of deleted users, same logic as for src/util/dtos/DmChannelDTO.ts‎ --- src/gateway/opcodes/Identify.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/gateway/opcodes/Identify.ts b/src/gateway/opcodes/Identify.ts index 9b9e1d14ca..4d4a8f758c 100644 --- a/src/gateway/opcodes/Identify.ts +++ b/src/gateway/opcodes/Identify.ts @@ -518,12 +518,20 @@ export async function onIdentify(this: WebSocket, data: Payload) { let channelUsers = channel.recipients?.map((recipient) => recipient.user.toPublicUser()); if (channelUsers && channelUsers.length > 0) channelUsers.forEach((user) => users.add(user)); - // HACK: insert self into recipients for DMs with users that no longer exist + // HACK: insert "Deleted User" into recipients for DMs with users that no longer exist else if (channel.type === ChannelType.DM) { - const selfUser = user.toPublicUser(); - users.add(selfUser); - channelUsers ??= []; - channelUsers.push(selfUser); + const deletedUser = { + avatar: null, + discriminator: "0000", + id: "0", + public_flags: 0, + username: "Deleted User", + badge_ids: null, + }; + + users.add(deletedUser as any); + channelUsers ??= []; + channelUsers.push(deletedUser as any); } return { From 6c67a074529f219627f60b5af918f981e689f4d4 Mon Sep 17 00:00:00 2001 From: Davide <137603413+LW-Davide@users.noreply.github.com> Date: Thu, 19 Feb 2026 19:03:06 +0100 Subject: [PATCH 5/8] Integrating original commit c671db2 Integrating commit c671db2 from the original project, which fixes the issue: "Fix user self delete not removing settings protos" --- src/api/routes/users/@me/delete.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/api/routes/users/@me/delete.ts b/src/api/routes/users/@me/delete.ts index 754b77d1f5..9f4fbf927e 100644 --- a/src/api/routes/users/@me/delete.ts +++ b/src/api/routes/users/@me/delete.ts @@ -17,7 +17,7 @@ */ import { route } from "@spacebar/api"; -import { Member, User, UserSettingsProtos } from "@spacebar/util"; +import { DiscordApiErrors, Guild, Member, User, UserSettingsProtos } from "@spacebar/util"; import bcrypt from "bcrypt"; import { Request, Response, Router } from "express"; import { HTTPError } from "lambert-server"; @@ -55,8 +55,14 @@ router.post( // TODO: decrement guild member count if (correctpass) { + // Check if the user owns any guilds. + const ownedGuilds = await Guild.findOne({ where: { owner_id: req.user_id } }); + if (ownedGuilds) { + throw new HTTPError("User owns guilds and cannot be deleted", 403); + } + const members = await Member.find({ where: { id: req.user_id } }); - await UserSettingsProtos.delete({ user_id: req.user_id }); + await UserSettingsProtos.delete({ user_id: req.user_id }); await Promise.all([User.delete({ id: req.user_id }), ...members.map((member) => Member.removeFromGuild(member.id, member.guild_id))]); res.sendStatus(204); From 9ccad753746f471e7d9e8015993cfc93de3342c7 Mon Sep 17 00:00:00 2001 From: Davide <137603413+LW-Davide@users.noreply.github.com> Date: Fri, 20 Feb 2026 02:37:23 +0100 Subject: [PATCH 6/8] Fix for the BUG "All users show online status 24/7" plus 5s to allow clients to reconnect It tries to use Session.delete on this.session_id, but it's just a TEMP session name, so it doesn't match the one in the DB, which is this.session.session_id. Sometimes, when your internet is unstable, it may trigger, so we give a 5-second delay to avoid false positives. --- src/gateway/events/Close.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/gateway/events/Close.ts b/src/gateway/events/Close.ts index 988f17fa4f..7612f4183d 100644 --- a/src/gateway/events/Close.ts +++ b/src/gateway/events/Close.ts @@ -28,7 +28,26 @@ export async function Close(this: WebSocket, code: number, reason: Buffer) { this.removeAllListeners(); if (this.session_id) { - await Session.delete({ session_id: this.session_id }); + const authSessionId = this.session?.session_id; + const closedAt = Date.now(); + + setTimeout(async () => { + try { + if (authSessionId && this.user_id) { + const s = await Session.findOne({ + where: { user_id: this.user_id, session_id: authSessionId }, + }); + if (s && (s.last_seen?.getTime() ?? 0) <= closedAt) { + await Session.update( + { user_id: this.user_id, session_id: authSessionId }, + { status: "offline", activities: [], client_status: {} } + ); + } + } + } catch(e) { + console.error("[WebSocket] Close session cleanup failed", code, e); + } + }, 5_000); const voiceState = await VoiceState.findOne({ where: { user_id: this.user_id }, From c5f678f2528548db3b9f072f23464cd46344d58a Mon Sep 17 00:00:00 2001 From: Davide <137603413+LW-Davide@users.noreply.github.com> Date: Mon, 23 Feb 2026 01:42:15 +0100 Subject: [PATCH 7/8] setTimeout from 5s to 10s More time in case of page reload before setting the user to offline --- src/gateway/events/Close.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gateway/events/Close.ts b/src/gateway/events/Close.ts index 7612f4183d..17781edffe 100644 --- a/src/gateway/events/Close.ts +++ b/src/gateway/events/Close.ts @@ -47,7 +47,7 @@ export async function Close(this: WebSocket, code: number, reason: Buffer) { } catch(e) { console.error("[WebSocket] Close session cleanup failed", code, e); } - }, 5_000); + }, 10_000); const voiceState = await VoiceState.findOne({ where: { user_id: this.user_id }, From d05baa64c2a6c280400521fe5d3fadc02a72e6e2 Mon Sep 17 00:00:00 2001 From: Davide <137603413+LW-Davide@users.noreply.github.com> Date: Mon, 23 Feb 2026 01:45:44 +0100 Subject: [PATCH 8/8] Auto creating DM channel after adding a friend Replicating Discord's actions when adding a friend --- src/api/routes/users/@me/relationships.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/api/routes/users/@me/relationships.ts b/src/api/routes/users/@me/relationships.ts index 88f95a3d38..5cd8a89ba1 100644 --- a/src/api/routes/users/@me/relationships.ts +++ b/src/api/routes/users/@me/relationships.ts @@ -17,7 +17,7 @@ */ import { route } from "@spacebar/api"; -import { Config, DiscordApiErrors, Relationship, RelationshipAddEvent, RelationshipRemoveEvent, RelationshipUpdateEvent, User, emitEvent } from "@spacebar/util"; +import { Channel, Config, DiscordApiErrors, Relationship, RelationshipAddEvent, RelationshipRemoveEvent, RelationshipUpdateEvent, User, emitEvent } from "@spacebar/util"; import { Request, Response, Router } from "express"; import { HTTPError } from "lambert-server"; import { PublicUserProjection, RelationshipType, RelationshipPatchSchema } from "@spacebar/schemas"; @@ -315,5 +315,14 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ } as RelationshipAddEvent), ]); + + if ( + incoming_relationship.type === RelationshipType.friends && + outgoing_relationship.type === RelationshipType.friends + ) { + await Channel.createDMChannel([id], req.user_id); + await Channel.createDMChannel([req.user_id], id); + } + return res.sendStatus(204); }