Compare commits

...

2 Commits

Author SHA1 Message Date
bvn13
76d8272aac update-7 2026-03-23 12:45:37 +03:00
bvn13
324cd8a959 update-5, update-6 2026-03-23 12:12:55 +03:00
5 changed files with 90 additions and 10 deletions

26
docs/update-5.md Normal file
View File

@ -0,0 +1,26 @@
# Обновление 5 — Нормализация URL картинок
## Проблема
Клиент Conversations (и его форки) не отображает OOB-картинки, если URL содержит символ `:`
в path-части (например, `…/1579780545_0:182:3048:1897_…jpg`).
При парсинге OOB `<url>` клиент интерпретирует `:` как разделитель схемы или порта
и обрезает URL — ссылка становится невалидной, картинка не загружается.
URL без двоеточий в пути отображаются корректно.
## Решение
URL-энкодировать path-часть URL картинки перед сохранением в `NewsItem`.
Символ `:` кодируется как `%3A`. Согласно RFC 3986, percent-encoded символы
эквивалентны оригинальным — CDN-серверы обязаны принимать такие URL.
## Техническая реализация
1. В `adapters/sources/rss/fetcher.py` добавить функцию `_normalize_image_url(url: str) -> str`
2. Функция использует `urllib.parse` (стандартная библиотека, без новых зависимостей):
- разобрать URL через `urlparse`
- перекодировать path через `quote(path, safe='/')` — слэши остаются, `:` кодируется
- собрать обратно через `urlunparse`
3. Вызывать `_normalize_image_url` в `_extract_image_url` перед возвратом URL

21
docs/update-6.md Normal file
View File

@ -0,0 +1,21 @@
# Обновление 6 — Убрать картинку из тела сообщения
## Контекст
В update-2 картинка добавлялась двумя способами:
- в XHTML-теле через `<p><img src="..." /></p>`
- в plain-text fallback через URL картинки первым блоком
В update-4 добавлен OOB (XEP-0066), который обеспечивает отображение картинки
в клиентах семейства Conversations. Таким образом, `<img>` и URL в fallback
стали избыточными и создают визуальный мусор в клиентах, не рендерящих XHTML.
## Задача
Убрать блок картинки из тела сообщения:
1. В `_build_xhtml` — убрать `<p><img src="..." /></p><br/><br/>`
2. В `_build_plain` — убрать добавление `image_url` в список блоков
Поле `image_url` в `NewsItem` и логика его извлечения в fetcher'е остаются —
оно по-прежнему используется для OOB.

31
docs/update-7.md Normal file
View File

@ -0,0 +1,31 @@
# Обновление 7 — Пропускать OOB если URL картинки содержит недопустимые символы
## Контекст
В update-5 была добавлена нормализация URL картинок: символ `:` в path кодировался как `%3A`.
Выяснилось, что CDN принимает закодированный URL (200 OK), но клиент Conversations
всё равно не отображает картинку — причина в hotlink protection на стороне CDN.
Таким образом, нормализация URL не решает проблему и бесполезна.
Практическое наблюдение: URL с `:` в path — признак CDN с hotlink protection.
Такие картинки не отобразятся в клиентах в любом случае.
Решение: не добавлять OOB для URL, содержащих `:` в path-части.
## Задача
1. В `adapters/sources/rss/fetcher.py`:
- Удалить функцию `_normalize_image_url` и импорты `urllib.parse`
- В `_extract_image_url` проверять raw URL: если path-часть содержит `:` — возвращать `None`
## Техническая реализация
Проверка выполняется через `urlparse`:
```python
from urllib.parse import urlparse
p = urlparse(url)
if ':' in p.path:
return None
return url
```

View File

@ -22,10 +22,7 @@ class JabberNewsPublisher(NewsPublisher):
return await self._connection.send_to_room_xhtml(room_jid, plain, xhtml, item.image_url)
def _build_plain(self, item: NewsItem) -> str:
blocks = []
if item.image_url:
blocks.append(item.image_url)
blocks.append(item.title)
blocks = [item.title]
if item.summary:
blocks.append(item.summary)
blocks.append(item.link)
@ -33,10 +30,7 @@ class JabberNewsPublisher(NewsPublisher):
def _build_xhtml(self, item: NewsItem) -> str:
SEP = "<br/><br/>"
parts = []
if item.image_url:
parts.append(f'<p><img src="{item.image_url}" alt="" /></p>{SEP}')
parts.append(f'<p>{item.title}</p>{SEP}')
parts = [f'<p>{item.title}</p>{SEP}']
if item.summary:
parts.append(f'<p>{item.summary}</p>{SEP}')
parts.append(f'<p><a href="{item.link}">{item.link}</a></p>')

View File

@ -3,6 +3,7 @@ import logging
import re
from html import unescape
from typing import List, Optional
from urllib.parse import urlparse
import feedparser
@ -20,10 +21,17 @@ def _strip_html(text: str) -> str:
def _extract_image_url(entry) -> Optional[str]:
"""Возвращает URL первого enclosure с type image/*."""
"""Возвращает URL первого enclosure с type image/*.
Возвращает None если URL содержит ':' в path признак hotlink-защищённого CDN.
"""
for enc in entry.get("enclosures", []):
if enc.get("type", "").startswith("image/"):
return enc.get("href") or enc.get("url")
raw = enc.get("href") or enc.get("url")
if not raw:
return None
if ":" in urlparse(raw).path:
return None
return raw
return None