rss-formatting-preserve: сохранять абзацы и разметку из RSS (html_to_text/extract_full_text normalize; safe_html bleach для предпросмотра; пересборка старых постов)

This commit is contained in:
kpa39l
2026-09-15 18:53:57 +00:00
parent b25054e1df
commit 582dc3cf66
11 changed files with 521 additions and 4 deletions
@@ -0,0 +1,3 @@
schema: spec-driven
skip_specs: true
created: 2026-09-15
@@ -0,0 +1,58 @@
# 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.
@@ -0,0 +1,27 @@
# rss-formatting-preserve
Сохранять форматирование RSS-новостей (абзацы, разметка) при скачивании и в предпросмотре.
## Why
Пользователь: в RSS omarchy внутри текста новости есть HTML-символы, мы их выкидываем
из текста, поэтому вся новость в предпросмотре — сплошной текст, ничего не понять.
Нужно при скачивании из RSS сохранять заложенное форматирование.
## What Changes
- **crawler/rss_crawler.py**: html_to_text() вставляет переносы вокруг блочных тегов
(p/li/h1-6/blockquote/pre), чтобы абзацы и списки сохранялись; parse_entries() больше
не схлопывает все пробелы в один — нормализует: 3+ переноса → 2, пробелы по краям
строк убираются, внутренние сохраняются.
- **web/app.py**: новый фильтр `safe_html` (bleach.clean allowlist + linkify) для
безопасного рендера HTML-разметки исходных RSS-текстов в предпросмотре.
- **web/templates/candidates.html / selected.html**: `| safe_html` для `selected.text`.
- **requirements.txt**: +bleach.
## Impact
- Тексты в БД: появляются абзацы (\n\n), форматирование сохраняется читаемым.
- Предпросмотр кандидатов/отобранных: разметка (b/i/a/списки) рендерится безопасно.
- Markdown-пересказы (rewritten_text) и комментарии не задеваются — md_filter без изменений.
- Публикация (card.py) — plain text, абзацы \n\n дойдут в Telegram.
@@ -0,0 +1,18 @@
# rss-formatting-preserve
## Problem
Тексты из RSS (omarchy и др.) в предпросмотре — сплошной текст: абзацы/списки/разметка
теряются. При скачивании нужно сохранять заложенное форматирование.
## Tasks
- [x] rss_crawler.py: html_to_text() — разделители между блочными тегами (p/li/h1-6/blockquote/pre)
- [x] rss_crawler.py: parse_entries() — не схлопывать все пробелы `\s+`; normalize_text (3+ переноса → 2, пробелы по краям строк убрать, внутренние сохранить)
- [x] rss_crawler.py: extract_full_text() — include_formatting=True (trafilatura) для сохранения абзацев
- [x] web/app.py: фильтр `safe_html` (bleach.clean allowlist + linkify); md_filter не тронут
- [x] web/templates/candidates.html: `selected.text | safe_html` вместо `| markdown`
- [x] web/templates/selected.html: `selected.text | safe_html` вместо `| markdown`
- [x] requirements.txt: +bleach (установлен в venv)
- [x] Пересборка старых RSS-постов: 47 обновлено с абзацами (omarchy 25/25, bleepingcomputer 16/16; lwn 12 — 403/429, будут при следующем крауле)
- [x] STATUS.md актуализирован; validate; commit