working on images

This commit is contained in:
koplenov 2026-05-09 19:33:33 +03:00
parent 1b3fd93150
commit be4a3e928c

View file

@ -14,6 +14,13 @@
body: string
time: number
nick?: string // sender nick for MUC groupchat messages
media_uri?: string
media_mime?: string
media_name?: string
media_kind?: Media_type
media_size?: number
media_hash?: string
media_hash_algo?: string
}
type Xmpp_room = {
@ -22,6 +29,8 @@
nick: string // my nickname in this room
}
type Media_type = 'image' | 'audio' | 'link' | null
// $mol_db schema for persisted messages.
type Xmpp_db_schema = {
Messages: {
@ -35,13 +44,18 @@
body: string
time: number
nick?: string
media_uri?: string
media_mime?: string
media_name?: string
media_kind?: Media_type
media_size?: number
media_hash?: string
media_hash_algo?: string
}
Indexes: {}
}
}
type Media_type = 'image' | 'audio' | 'link' | null
function media_type(url: string): Media_type {
const s = url.trim()
if (!/^https?:\/\/\S+$/.test(s)) return null
@ -51,6 +65,13 @@
return 'link'
}
function media_type_from_mime(mime: string): Media_type {
const s = mime.trim().toLowerCase()
if (s.startsWith('image/')) return 'image'
if (s.startsWith('audio/')) return 'audio'
return null
}
// ─── XMPP over WebSocket (RFC 7590) ──────────────────────────────────────
class Xmpp_conn {
@ -342,6 +363,27 @@ private _getBody(el: Element): string | null {
return new DOMParser().parseFromString(data, 'text/xml')
}
private _parse_media_sharing(el: Element) {
const reference = el.getElementsByTagNameNS('urn:xmpp:reference:0', 'reference')[0]
if (!reference || reference.getAttribute('type') !== 'data') return null
const media = reference.getElementsByTagNameNS('urn:xmpp:sims:1', 'media-sharing')[0]
if (!media) return null
const file = media.getElementsByTagNameNS('urn:xmpp:jingle:apps:file-transfer:5', 'file')[0]
if (!file) return null
const name = file.getElementsByTagNameNS('urn:xmpp:jingle:apps:file-transfer:5', 'name')[0]?.textContent || undefined
const mime = file.getElementsByTagNameNS('urn:xmpp:jingle:apps:file-transfer:5', 'media-type')[0]?.textContent || undefined
const sizeText = file.getElementsByTagNameNS('urn:xmpp:jingle:apps:file-transfer:5', 'size')[0]?.textContent
const hashElem = file.getElementsByTagNameNS('urn:xmpp:hashes:2', 'hash')[0]
const sourceRef = Array.from(media.getElementsByTagNameNS('urn:xmpp:reference:0', 'reference'))
.find(r => r.getAttribute('type') === 'data')
const uri = sourceRef?.getAttribute('uri') || undefined
const size = sizeText ? Number(sizeText) : undefined
const hash = hashElem?.textContent || undefined
const hash_algo = hashElem?.getAttribute('algo') || undefined
const kind = mime ? media_type_from_mime(mime) : uri ? media_type(uri) : null
return { media_uri: uri, media_mime: mime, media_name: name, media_kind: kind, media_size: size, media_hash: hash, media_hash_algo: hash_algo }
}
private _handle(data: string) {
const doc = this._parse(data)
const root = doc.documentElement
@ -552,16 +594,10 @@ private _handle_message(el: Element) {
const type = el.getAttribute('type') || 'chat';
let body = this._getBody(el);
const media = this._parse_media_sharing(el)
if (!body) {
const reference = this._find(el, 'urn:xmpp:reference:0', 'reference')
if (reference?.getAttribute('type') === 'data') {
const media = reference.querySelector('media-sharing')
const file = media?.querySelector('file')
const name = file?.querySelector('name')?.textContent
const source = media?.querySelector('sources reference')?.getAttribute('uri')
if (name) body = `Shared file: ${ name }`
else if (source) body = source
}
if (media?.media_name) body = `Shared file: ${ media.media_name }`
else if (media?.media_uri) body = media.media_uri
}
if (!body) return;
@ -589,6 +625,7 @@ private _handle_message(el: Element) {
from: room_jid,
to: (el.getAttribute('to') || '').split('/')[0],
body, time, nick,
...(media || {}),
});
return;
}
@ -599,6 +636,7 @@ private _handle_message(el: Element) {
from: (el.getAttribute('from') || '').split('/')[0],
to: (el.getAttribute('to') || '').split('/')[0],
body, time,
...(media || {}),
});
}
@ -607,7 +645,12 @@ private _handle_message(el: Element) {
if (!fwd) return
const msg = fwd.querySelector('message')
if (!msg) return
const body = msg.querySelector('body')?.textContent
let body = msg.querySelector('body')?.textContent
const media = this._parse_media_sharing(msg)
if (!body) {
if (media?.media_name) body = `Shared file: ${ media.media_name }`
else if (media?.media_uri) body = media.media_uri
}
if (!body) return
const type = msg.getAttribute('type') || 'chat'
if (type !== 'chat' && type !== 'normal' && type !== 'groupchat') return
@ -625,6 +668,7 @@ private _handle_message(el: Element) {
to: (msg.getAttribute('to') || '').split('/')[0],
body, time,
...(nick !== undefined ? { nick } : {}),
...(media || {}),
})
}
@ -1473,14 +1517,19 @@ private _handle_message(el: Element) {
@ $mol_mem_key // ← FIX: make reactive
msg_body(id: string) {
const body = this._msg_by_id.get(id)?.body ?? ''
const msg = this._msg_by_id.get(id)
if (!msg) return ''
if (msg.media_uri || msg.media_kind) return ''
const body = msg.body ?? ''
return media_type(body) ? '' : body
}
@ $mol_mem_key // ← FIX: make reactive
msg_media(id: string): $mol_view[] {
const body = this._msg_by_id.get(id)?.body.trim() ?? ''
const type = media_type(body)
const msg = this._msg_by_id.get(id)
if (!msg) return []
const uri = msg.media_uri || msg.body.trim()
const type = msg.media_kind ?? (msg.media_uri ? media_type(uri) : media_type(msg.body.trim()))
if (type === 'image') return [this.Msg_image(id)]
if (type === 'audio') return [this.Msg_audio(id)]
if (type === 'link') return [this.Msg_link(id)]
@ -1488,13 +1537,13 @@ private _handle_message(el: Element) {
}
@ $mol_mem_key // ← FIX: make reactive
msg_image_uri(id: string) { return this._msg_by_id.get(id)?.body.trim() ?? '' }
msg_image_uri(id: string) { const msg = this._msg_by_id.get(id); return msg?.media_uri || (msg?.body.trim() ?? '') }
@ $mol_mem_key // ← FIX: make reactive
msg_audio_src(id: string) { return this._msg_by_id.get(id)?.body.trim() ?? '' }
msg_audio_src(id: string) { const msg = this._msg_by_id.get(id); return msg?.media_uri || (msg?.body.trim() ?? '') }
@ $mol_mem_key // ← FIX: make reactive
msg_link_uri(id: string) { return this._msg_by_id.get(id)?.body.trim() ?? '' }
msg_link_uri(id: string) { const msg = this._msg_by_id.get(id); return msg?.media_uri || (msg?.body.trim() ?? '') }
@ $mol_mem_key
msg_time(id: string) {
@ -1881,6 +1930,13 @@ private _handle_message(el: Element) {
account, peer,
id: msg.id, from: msg.from, to: msg.to, body: msg.body, time: msg.time,
...(msg.nick !== undefined ? { nick: msg.nick } : {}),
...(msg.media_uri !== undefined ? { media_uri: msg.media_uri } : {}),
...(msg.media_mime !== undefined ? { media_mime: msg.media_mime } : {}),
...(msg.media_name !== undefined ? { media_name: msg.media_name } : {}),
...(msg.media_kind !== undefined ? { media_kind: msg.media_kind } : {}),
...(msg.media_size !== undefined ? { media_size: msg.media_size } : {}),
...(msg.media_hash !== undefined ? { media_hash: msg.media_hash } : {}),
...(msg.media_hash_algo !== undefined ? { media_hash_algo: msg.media_hash_algo } : {}),
}
await Messages.put(doc, [account, msg.id])
} catch (e) { console.warn('[xmpp] persist failed', e) }
@ -1901,6 +1957,13 @@ private _handle_message(el: Element) {
id: doc.id, from: doc.from, to: doc.to,
body: doc.body, time: doc.time,
...(doc.nick !== undefined ? { nick: doc.nick } : {}),
...(doc.media_uri !== undefined ? { media_uri: doc.media_uri } : {}),
...(doc.media_mime !== undefined ? { media_mime: doc.media_mime } : {}),
...(doc.media_name !== undefined ? { media_name: doc.media_name } : {}),
...(doc.media_kind !== undefined ? { media_kind: doc.media_kind } : {}),
...(doc.media_size !== undefined ? { media_size: doc.media_size } : {}),
...(doc.media_hash !== undefined ? { media_hash: doc.media_hash } : {}),
...(doc.media_hash_algo !== undefined ? { media_hash_algo: doc.media_hash_algo } : {}),
})
}
} finally {
@ -2000,7 +2063,16 @@ private _handle_message(el: Element) {
const id = `l${ Date.now() }_${ Math.random().toString(36).slice(2) }`
const type = this._rooms.has(jid) ? 'groupchat' : 'chat'
this._conn.send_media_sharing(jid, body, file.name, file.size, file.type || 'application/octet-stream', 'sha-256', hash, get, type, id)
this._add_message({ id, from: this.my_jid().split('/')[0], to: jid, body, time: Date.now() })
this._add_message({
id, from: this.my_jid().split('/')[0], to: jid, body, time: Date.now(),
media_uri: get,
media_mime: file.type || 'application/octet-stream',
media_name: file.name,
media_kind: media_type_from_mime(file.type) ?? media_type(get),
media_size: file.size,
media_hash: hash,
media_hash_algo: 'sha-256',
})
this._scroll_to_bottom(jid)
}
}