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
+43
View File
@@ -17,6 +17,11 @@ from fastapi.staticfiles import StaticFiles
from jinja2 import Environment, FileSystemLoader
from markupsafe import Markup, escape
try: # bleach — санитайзер HTML из RSS (allowlist); см. requirements.txt
import bleach
except ImportError:
bleach = None
# CRUD источников: работа с sources.yaml (источник истины) + БД синк
from sources.sources import (
db_source_to_yaml,
@@ -51,6 +56,43 @@ def md_filter(text: str) -> Markup:
return Markup(html)
# Теги/атрибуты, разрешённые в текстах из RSS (безопасное подмножество HTML).
_SAFE_TAGS = [
"p", "br", "b", "strong", "i", "em", "u", "s", "strike", "del",
"a", "ul", "ol", "li", "blockquote", "pre", "code", "h1", "h2", "h3",
"h4", "h5", "h6", "table", "thead", "tbody", "tr", "th", "td",
]
_SAFE_ATTRS = {"a": ["href", "title", "rel"]}
_SAFE_PROTOCOLS = ["http", "https", "mailto"]
def safe_html_filter(text: str) -> Markup:
"""Исходный текст из RSS → безопасный HTML (allowlist-санитайзер).
Сохраняет «заложенное форматирование» (жирный, ссылки, списки, абзацы),
вырезает всё опасное (script/style/on*/iframe/svg/...) и делает ссылки
кликабельными (linkify). Для markdown-пересказов/комментариев НЕ используется —
они идут через md_filter.
"""
if not text:
return Markup("")
if bleach is None:
# запасной вариант без bleach — экранируем (безопасно, но без разметки)
return Markup(md_lib.markdown(escape(text), extensions=["nl2br", "sane_lists"]))
try:
cleaned = bleach.clean(
text,
tags=_SAFE_TAGS,
attributes=_SAFE_ATTRS,
protocols=_SAFE_PROTOCOLS,
strip=True,
)
cleaned = bleach.linkify(cleaned, parse_email=True)
return Markup(cleaned)
except Exception:
return Markup(escape(text))
def dt_filter(value) -> str:
"""2026-08-15T15:53 → 15:53 15.08.2026; None/мусор → ''."""
if not value:
@@ -64,6 +106,7 @@ def dt_filter(value) -> str:
tpl.filters["markdown"] = md_filter
tpl.filters["safe_html"] = safe_html_filter
tpl.filters["dt"] = dt_filter