From ad5c63fc50f629d6b85cb82b000cde9b04128973 Mon Sep 17 00:00:00 2001 From: jjbrunton Date: Fri, 22 May 2026 23:01:06 +0100 Subject: [PATCH] Fall back to /joined_members when delta sync omits the other party Reddit's homeserver sometimes returns a room in /sync with no m.heroes and no joined m.room.member state events, leaving us with no t2 ID to resolve and the chat stuck on 'unknown'. enrichUsernames now hits /_matrix/client/v3/rooms/{roomId}/joined_members for any chat whose username is 'unknown' or a bare MXID, stamps the t2 ID, and lets the existing resolver fill in the real Reddit username. Also treats a cached 'unknown' username as stale in roomToActiveChat so subsequent deltas re-attempt the lookup instead of pinning it. Co-Authored-By: Claude Opus 4.7 --- src/services/chat/service.ts | 44 +++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/src/services/chat/service.ts b/src/services/chat/service.ts index adaf630..0cc92fc 100644 --- a/src/services/chat/service.ts +++ b/src/services/chat/service.ts @@ -838,7 +838,12 @@ export class ChatService { .filter((s): s is string => !!s); let username = prev?.username ?? null; - if (!username || username.startsWith('t2_') || username.startsWith('@')) { + if ( + !username || + username === 'unknown' || + username.startsWith('t2_') || + username.startsWith('@') + ) { const resolved = otherT2 .map((id) => opts.knownUsernames.get(id.replace(/^t2_/, ''))) .find((x): x is string => !!x); @@ -994,12 +999,45 @@ export class ChatService { */ async enrichUsernames(): Promise { if (!this.usernameResolver) return; + // First, for any chats whose username is still 'unknown' (heroes/state in + // the delta didn't surface the other member), fetch the room's joined + // members and stamp the t2 ID onto the chat so the resolver below can + // pick it up. + const roomT2: Map = new Map(); // roomId -> bare t2 id + for (const chat of this.cache.get()) { + if (!chat.roomId) continue; + const needsLookup = + !chat.username || + chat.username === 'unknown' || + chat.username.startsWith('@'); + if (!needsLookup) continue; + try { + const members = await this.withAuth(() => this.matrix.getJoinedMembers(chat.roomId!)); + const otherMxid = Object.keys(members).find((m) => m !== this.matrix.userId); + const t2 = otherMxid?.match(/^@(t2_[^:]+):/)?.[1]; + if (t2) { + const bare = t2.replace(/^t2_/, ''); + roomT2.set(chat.roomId, bare); + const known = this.t2ToUsername.get(bare); + chat.username = known ?? t2; + if (!chat.roomName || chat.roomName === 'unknown' || chat.roomName.startsWith('@')) { + chat.roomName = chat.username; + } + } + } catch (err) { + this.logger.debug(`[chat] joined_members lookup failed for ${chat.roomId}:`, err); + } + } + const needed: string[] = []; for (const chat of this.cache.get()) { const t2 = this.extractT2(chat); if (t2 && !this.t2ToUsername.has(t2)) needed.push(`t2_${t2}`); } - if (needed.length === 0) return; + if (needed.length === 0) { + if (roomT2.size > 0) await this.cache.save(); + return; + } try { const got = await this.usernameResolver.resolveT2Ids(needed); let updated = 0; @@ -1015,7 +1053,7 @@ export class ChatService { } } } - if (updated > 0) { + if (updated > 0 || roomT2.size > 0) { this.logger.info(`[chat] enriched ${updated} chats with usernames`); await this.cache.save(); }