A Plex agent can hand a summary over as HTML (<p> paragraphs, á entities); the info page and the hover card showed the tags. One cleaner, app/plex/plaintext.py, now runs on every summary and tagline the sync, the Hungarian overlay and the rich-detail fetch store, and migration 0101 cleans the rows written before through the same function. Paragraph breaks survive as newlines, which the info page and the hover card now keep.
33 lines
1.4 KiB
Python
33 lines
1.4 KiB
Python
"""Plex text fields as plain text.
|
|
|
|
Plex hands a summary over as the agent wrote it, and some agents (a Hungarian TV listing, for one)
|
|
write HTML: `<p>…</p>` paragraphs and `á`-style entities. The app renders summaries as text,
|
|
so the tags showed literally on the info page and the hover card, and a card's 300-character cut
|
|
could land inside one. Every summary and tagline is stored through `plain_text`, and the migration
|
|
that cleaned the existing rows calls it too, so the two cannot disagree.
|
|
"""
|
|
|
|
import html
|
|
import re
|
|
|
|
_BREAK = re.compile(r"<br\s*/?>", re.IGNORECASE)
|
|
# A paragraph or block boundary: a blank line between the two. Only a tag name ends the match, so a
|
|
# bare "<" in prose ("a < b", "<3") is left alone.
|
|
_BLOCK = re.compile(r"</?(?:p|div|li|ul|ol|h[1-6]|blockquote)\b[^>]*>", re.IGNORECASE)
|
|
_TAG = re.compile(r"</?[a-zA-Z][^>]*>")
|
|
_BLANKS = re.compile(r"[ \t]*\n[ \t]*\n\s*")
|
|
|
|
|
|
def plain_text(value: str | None) -> str | None:
|
|
"""`value` with its markup gone: line breaks kept as newlines, paragraphs as a blank line,
|
|
every other tag dropped and entities decoded. None or an all-blank result stays None."""
|
|
if value is None:
|
|
return None
|
|
if "<" in value or "&" in value:
|
|
value = _BREAK.sub("\n", value)
|
|
value = _BLOCK.sub("\n\n", value)
|
|
value = _TAG.sub("", value)
|
|
value = html.unescape(value)
|
|
value = _BLANKS.sub("\n\n", value)
|
|
return value.strip() or None
|