Files
vesti/openspec/changes/rss-formatting-preserve/design.md
T

58 lines
4.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# rss-formatting-preserve: сохранять форматирование RSS-новостей (абзацы, разметка)
## Why
Пользователь: в RSS omarchy внутри текста новости есть HTML-символы, а мы их «выкидываем»
из текста, из-за чего вся новость в предпросмотре отображается сплошным текстом, ничего
не понять. Нужно при скачивании из источников RSS сохранять заложенное форматирование.
Разбор:
- `html_to_text()` (crawler/rss_crawler.py) режет HTML корректно, но без разделителей
между блочными элементами (p/li/h1-6/blockquote) — абзацы/списки сливаются в один кусок.
- После этого `re.sub(r"\s+", " ", text)` (стр. 143) схлопывает и переносы строк —
даже если бы были `\n\n`, они превращаются в один пробел. Итог: «сплошной текст».
- В вебе `md_filter` (web/app.py) вызывается на `text` из RSS — синтаксис HTML (b/i/a)
экранируется и виден как текст-каша, а markdown-разметки в RSS-тексте нет.
## Design
### 1. Краулер: сохранять структуру абзацев
`html_to_text()`:
- после удаления script/style/noscript — вставлять переносы-разделители вокруг блочных
тегов (p, div, li, h1–h6, blockquote, pre, tr), чтобы они давали `\n\n` между абзацами
и `\n` внутри list-item.
- текст по-прежнему plain (в БД хранится текст, а не HTML) — просто читаемая структура.
`parse_entries()` (стр. 143): заменить `re.sub(r"\s+", " ", text)` на нормализацию:
- схлопывать 3+ переноса до 2 (максимум пустая строка между абзацами);
- убирать горизонтальные пробелы в начале/конце строк и перед переносами;
- пробелы внутри строки НЕ трогать (сохраняются слова/URL).
### 2. Веб: безопасный рендер HTML-разметки из RSS
Исходный текст поста (RSS) в предпросмотре (кандидаты, отобранные) — рендерить как
безопасный HTML подмножеством тегов, а не экранировать в тёмную кашу:
- новый фильтр `safe_html` в web/app.py на основе `bleach`:
`bleach.clean(text, tags=ALLOWED, attributes={'a': ['href', 'title']},
protocols=['http','https'])` + `bleach.linkify`;
- применяется ТОЛЬКО к `text` (оригинал из RSS) в candidates.html и selected.html;
- markdown-фильтр (`md_filter`) остаётся для пересказов/комментариев (markdown от модели).
Публикация (publisher/card.py) не меняется: в канал уходит plain text, абзацы `\n\n`
будут сохранены — Telegram корректно отобразит переносы.
### Безопасность
- `safe_html` — allowlist-санитайзер (bleach): никаких script/style/iframe/on*, href
только http(s), linkify распознаёт ссылки. XSS-поверхности нет.
- `md_filter` не меняется (остаётся escape→markdown).
## Files
- crawler/rss_crawler.py — html_to_text(), parse_entries() (абзацы, нормализация).
- web/app.py — фильтр safe_html (bleach).
- web/templates/candidates.html — `{{ selected.text | safe_html }}`.
- web/templates/selected.html — `{{ selected.text | safe_html }}`.
- requirements.txt — +bleach.