diff --git a/xmpp.view.ts b/xmpp.view.ts index 440f080..53285bc 100644 --- a/xmpp.view.ts +++ b/xmpp.view.ts @@ -102,6 +102,7 @@ on_marker: ((kind: 'received' | 'displayed' | 'acknowledged', msg_id: string, from: string) => void) | null = null on_avatar_meta: ((from: string, info: { id: string; mime: string } | null) => void) | null = null on_presence: ((jid: string, show: string, status: string) => void) | null = null + on_muc_presence: ((room_jid: string, nick: string, real_bare?: string) => void) | null = null on_error: ((err: string) => void) | null = null on_room_error: ((err: string) => void) | null = null on_close: (() => void) | null = null @@ -693,7 +694,17 @@ private _handle_message(el: Element) { // MUC room presence — from contains resource (nick) const slash = from_full.indexOf('/') - if (slash >= 0) return // ignore occupant presence updates for now + if (slash >= 0) { + const room_jid = from_full.slice(0, slash) + const nick = from_full.slice(slash + 1) + // Extract real JID from MUC presence + const muc_x = el.querySelector('x[xmlns="http://jabber.org/protocol/muc#user"]') + const item = muc_x?.querySelector('item') + const real_jid_full = item?.getAttribute('jid') || '' + const real_bare = real_jid_full.split('/')[0] + this.on_muc_presence?.(room_jid, nick, real_bare || undefined) + return + } const from = from_full || '' if (!from) return @@ -717,6 +728,7 @@ private _handle_message(el: Element) { private _fin_count = new Map() private _scroll_setup = new Set() // scroll listener installed private _rooms = new Map() + private _room_occupants = new Map>() // room_jid → nick → real_bare_jid private _oldest_time = new Map() // room_jid → oldest msg timestamp private _poll_timer: number | null = null private _poll_count: number = 0 @@ -809,8 +821,10 @@ private _handle_message(el: Element) { contact_avatar_uri(jid: string) { const room = this._rooms.get(jid) + const avatar = this.avatar_uri(jid) + if (avatar) return avatar if (room) return this._default_avatar(jid, room.name || jid.split('@')[0]) - return this.avatar_uri(jid) || this._default_avatar(jid) + return this._default_avatar(jid) } chat_avatar_uri(jid: string) { return this.contact_avatar_uri(jid) } @@ -819,7 +833,18 @@ private _handle_message(el: Element) { msg_avatar_uri(id: string) { const msg = this._msg_by_id.get(id) if (!msg) return '' - if (msg.nick !== undefined) return this._default_avatar(`${ msg.from }/${ msg.nick }`, msg.nick) + if (msg.nick !== undefined) { + const room = this._rooms.get(msg.from) + if (room?.nick === msg.nick) return this.my_avatar_uri() + // Try to get real JID for the nick + const occupants = this._room_occupants.get(msg.from) + const real_bare = occupants?.get(msg.nick) + if (real_bare) { + const avatar = this.avatar_uri(real_bare) + if (avatar) return avatar + } + return this._default_avatar(`${ msg.from }/${ msg.nick }`, msg.nick) + } return this.avatar_uri(msg.from) || this._default_avatar(msg.from) } @@ -1242,6 +1267,7 @@ private _handle_message(el: Element) { const nick = b.nick || my_nick const room: Xmpp_room = { jid: b.jid, name: b.name || b.jid.split('@')[0], nick } this._rooms.set(b.jid, room) + void this._load_avatar(b.jid) if (b.autojoin) this._conn?.join_room(b.jid, nick) } this.rooms([ ...this._rooms.values() ]) @@ -1259,6 +1285,17 @@ private _handle_message(el: Element) { } void this._fetch_avatar_with(from, info.id, info.mime) } + conn.on_muc_presence = (room_jid, nick, real_bare) => { + if (real_bare) { + let occupants = this._room_occupants.get(room_jid) + if (!occupants) { + occupants = new Map() + this._room_occupants.set(room_jid, occupants) + } + occupants.set(nick, real_bare) + void this._load_avatar(real_bare) + } + } conn.on_message = msg => { if (this._msg_by_id.has(msg.id)) return // ← FIX: skip duplicates this._add_message(msg) @@ -1431,6 +1468,7 @@ private _handle_message(el: Element) { this._conn.join_room(jid, nick) const room: Xmpp_room = { jid, name: jid.split('@')[0], nick } this._rooms.set(jid, room) + void this._load_avatar(jid) this.rooms([ ...this._rooms.values() ]) this.search_query('') this.$.$mol_state_arg.value('chat', jid) @@ -1466,6 +1504,7 @@ private _handle_message(el: Element) { this._conn.join_room(jid, nick) const room: Xmpp_room = { jid, name: jid.split('@')[0], nick } this._rooms.set(jid, room) + void this._load_avatar(jid) this.rooms([ ...this._rooms.values() ]) this.room_jid('') this.$.$mol_state_arg.value('chat', jid) @@ -1476,6 +1515,7 @@ private _handle_message(el: Element) { if (!room) return this._conn?.leave_room(jid, room.nick) this._rooms.delete(jid) + this._room_occupants.delete(jid) this.rooms([ ...this._rooms.values() ]) this._msgs = this._msgs.filter(m => m.from !== jid && m.to !== jid) this._msg_by_id.forEach((m, k) => { if (m.from === jid || m.to === jid) this._msg_by_id.delete(k) }) @@ -1629,9 +1669,9 @@ private _handle_message(el: Element) { if (this._rooms.has(jid)) { this.compose(jid, '') - this._conn.send_groupchat(jid, text) - this._scroll_to_bottom(jid) - return + const id = `l${ Date.now() }_${ Math.random().toString(36).slice(2) }` + this._conn.send_groupchat(jid, text, id) + this._add_message({ id, from: this.my_jid().split('/')[0], to: jid, body: text, time: Date.now() }) } this.compose(jid, '')