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 <noreply@anthropic.com>
This commit is contained in:
jjbruntonandClaude Opus 4.7 committed 2026-05-22 23:01:06 +01:00
1 parent 2edbc7dbf7
commit ad5c63fc50
1 file changed
+41 -3
+41 -3
View File
@@ -838,7 +838,12 @@ export class ChatService {
.filter((s): s is string => !!s); .filter((s): s is string => !!s);
let username = prev?.username ?? null; let username = prev?.username ?? null;
if (!username || username.startsWith('t2_') || username.startsWith('@')) { if (
!username ||
username === 'unknown' ||
username.startsWith('t2_') ||
username.startsWith('@')
) {
const resolved = otherT2 const resolved = otherT2
.map((id) => opts.knownUsernames.get(id.replace(/^t2_/, ''))) .map((id) => opts.knownUsernames.get(id.replace(/^t2_/, '')))
.find((x): x is string => !!x); .find((x): x is string => !!x);
@@ -994,12 +999,45 @@ export class ChatService {
*/ */
async enrichUsernames(): Promise<void> { async enrichUsernames(): Promise<void> {
if (!this.usernameResolver) return; 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<string, string> = 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[] = []; const needed: string[] = [];
for (const chat of this.cache.get()) { for (const chat of this.cache.get()) {
const t2 = this.extractT2(chat); const t2 = this.extractT2(chat);
if (t2 && !this.t2ToUsername.has(t2)) needed.push(`t2_${t2}`); 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 { try {
const got = await this.usernameResolver.resolveT2Ids(needed); const got = await this.usernameResolver.resolveT2Ids(needed);
let updated = 0; 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`); this.logger.info(`[chat] enriched ${updated} chats with usernames`);
await this.cache.save(); await this.cache.save();
} }