From 9bd0ab2e727c6840ea497e5a32b5bba302791408 Mon Sep 17 00:00:00 2001 From: bvn13 Date: Mon, 23 Mar 2026 11:45:43 +0300 Subject: [PATCH] update-4 --- docs/update-4.md | 68 +++++++++++++++++++++++++++ src/adapters/jabber/connection.py | 12 ++++- src/adapters/jabber/news_publisher.py | 5 +- 3 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 docs/update-4.md diff --git a/docs/update-4.md b/docs/update-4.md new file mode 100644 index 0000000..7c2ae05 --- /dev/null +++ b/docs/update-4.md @@ -0,0 +1,68 @@ +# Обновление 4 — Исправление отображения картинок + +## Контекст + +В update-2 и update-3 была реализована отправка новостей через XHTML-IM (XEP-0071) с картинкой через тег ``. +По результатам тестирования картинки не отображаются: +- **Gajim** — показывает URL картинки вместо изображения (использует plain-text fallback) +- **Monocle Chat (форк Conversations)** — показывает незагруженную заглушку + +Причины установлены в ходе анализа лога stanza. + +--- + +## Исправление 1 — двойной `` в XHTML-IM + +### Проблема + +slixmpp xep_0071 при присвоении `msg["html"]["body"] = xhtml_body` самостоятельно оборачивает +содержимое в ``. Текущая реализация `_build_xhtml` +также включает этот тег, в результате в stanza попадает вложенный ``: + +```xml + ← добавляет slixmpp + ← добавляет наш код +

...

+ + +``` + +Это невалидный XHTML-IM, что приводит к использованию plain-text fallback в строгих клиентах. + +### Решение + +В методе `_build_xhtml` убрать обёртку `...` — возвращать только +внутренние элементы (`

`, `
`). slixmpp добавит `` сам. + +--- + +## Исправление 2 — OOB для отображения картинок в Conversations-клиентах + +### Проблема + +Клиенты семейства Conversations (включая Monocle Chat) блокируют загрузку внешних изображений +из XHTML-IM `` по умолчанию — это намеренная защита от трекинг-пикселей. +Картинка парсится, но не загружается. + +### Решение + +Дополнительно к XHTML-IM прикреплять URL картинки как **Out-of-Band Data (XEP-0066)**. +Conversations и его форки умеют рендерить OOB-ссылки как inline-превью. + +Для этого к сообщению добавляется элемент: + +```xml + + https://example.com/image.jpg + +``` + +### Техническая реализация + +1. Зарегистрировать плагин `xep_0066` в `JabberConnection` +2. В методе `send_to_room_xhtml` добавить OOB-поле к сообщению, если передан `image_url`: + ```python + msg["oob"]["url"] = image_url + ``` +3. Сигнатуру `send_to_room_xhtml` расширить необязательным параметром `image_url: str | None = None` +4. В `JabberNewsPublisher.publish` передавать `item.image_url` в `send_to_room_xhtml` diff --git a/src/adapters/jabber/connection.py b/src/adapters/jabber/connection.py index 7063ae2..a66244f 100644 --- a/src/adapters/jabber/connection.py +++ b/src/adapters/jabber/connection.py @@ -25,6 +25,7 @@ class JabberConnection(ClientXMPP, JabberRoomJoiner, JabberRoomLeaver, MucJidRes self._rooms_on_start: List[str] = [] self.register_plugin("xep_0045") # MUC + self.register_plugin("xep_0066") # Out-of-Band Data self.register_plugin("xep_0071") # XHTML-IM self.register_plugin("xep_0199") # XMPP Ping @@ -106,16 +107,25 @@ class JabberConnection(ClientXMPP, JabberRoomJoiner, JabberRoomLeaver, MucJidRes logger.warning("Не удалось отправить сообщение в %s", room_jid) return False - async def send_to_room_xhtml(self, room_jid: str, plain_text: str, xhtml_body: str) -> bool: + async def send_to_room_xhtml( + self, + room_jid: str, + plain_text: str, + xhtml_body: str, + image_url: Optional[str] = None, + ) -> bool: """ Отправляет XHTML-IM сообщение в конференцию (XEP-0071). plain_text — fallback для клиентов без поддержки XHTML. + image_url — если указан, добавляется OOB (XEP-0066) для клиентов семейства Conversations. Возвращает False если возникла ошибка. """ try: msg = self.make_message(mto=room_jid, mtype="groupchat") msg["body"] = plain_text msg["html"]["body"] = xhtml_body + if image_url: + msg["oob"]["url"] = image_url msg.send() return True except Exception: diff --git a/src/adapters/jabber/news_publisher.py b/src/adapters/jabber/news_publisher.py index 4cbbd5d..4df9600 100644 --- a/src/adapters/jabber/news_publisher.py +++ b/src/adapters/jabber/news_publisher.py @@ -19,7 +19,7 @@ class JabberNewsPublisher(NewsPublisher): """ plain = self._build_plain(item) xhtml = self._build_xhtml(item) - return await self._connection.send_to_room_xhtml(room_jid, plain, xhtml) + return await self._connection.send_to_room_xhtml(room_jid, plain, xhtml, item.image_url) def _build_plain(self, item: NewsItem) -> str: blocks = [] @@ -33,12 +33,11 @@ class JabberNewsPublisher(NewsPublisher): def _build_xhtml(self, item: NewsItem) -> str: SEP = "

" - parts = [''] + parts = [] if item.image_url: parts.append(f'

{SEP}') parts.append(f'

{item.title}

{SEP}') if item.summary: parts.append(f'

{item.summary}

{SEP}') parts.append(f'

{item.link}

') - parts.append('') return ''.join(parts)