From 1f010328df0edfffb61b204c86608764bb762ad8 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Sat, 10 Oct 2026 00:03:08 +0200 Subject: [PATCH] fix(plex): plain text survives a decimal reference too long to parse int() refuses a decimal run past 4300 digits, so one such reference in a summary raised out of the sync and the text migration. Any number past seven significant digits is beyond U+10FFFF, so it becomes U+FFFD like any out-of-range reference; leading zeros are dropped first. The rails test's comment now says what it guards: both rails agree with the rollup. --- backend/app/plex/plaintext.py | 9 ++++++++- backend/tests/test_plex_discover.py | 4 ++-- backend/tests/test_plex_plaintext.py | 3 +++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/backend/app/plex/plaintext.py b/backend/app/plex/plaintext.py index ef29afb3..45ded13e 100644 --- a/backend/app/plex/plaintext.py +++ b/backend/app/plex/plaintext.py @@ -27,7 +27,14 @@ _REF = re.compile(r"&(?:#[0-9]+|#[xX][0-9a-fA-F]+|[A-Za-z][A-Za-z0-9]*);") def _decode(m: re.Match[str]) -> str: ref = m.group() - return html.unescape(ref) if ref[1] == "#" or ref[1:] in html5 else ref + if ref[1] != "#": + return html.unescape(ref) if ref[1:] in html5 else ref + if ref[2] in "xX": + return html.unescape(ref) + # `int()` refuses a decimal run past 4300 digits, and any number past seven significant digits is + # beyond U+10FFFF, which the parser turns into U+FFFD anyway. + digits = ref[2:-1].lstrip("0") or "0" + return "�" if len(digits) > 7 else html.unescape(f"&#{digits};") def plain_text(value: str | None) -> str | None: diff --git a/backend/tests/test_plex_discover.py b/backend/tests/test_plex_discover.py index 3cfc406b..7f1d6789 100644 --- a/backend/tests/test_plex_discover.py +++ b/backend/tests/test_plex_discover.py @@ -168,8 +168,8 @@ def test_continue_watching_movies_and_shows(db, monkeypatch, override_dep): def test_a_continue_watching_show_says_whether_it_has_an_episode_to_resume( db, monkeypatch, override_dep ): - # Both in-progress rails (Discover's and Home's) take a show's card state from the one rollup: - # a show part watched with nothing to resume offers Play alone, not Continue + Restart. + # Both in-progress rails (Discover's and Home's) agree with the one rollup: a show part watched + # with nothing to resume offers Play alone, not Continue + Restart. from app.discover.queries import plex_in_progress user = _seed(db) diff --git a/backend/tests/test_plex_plaintext.py b/backend/tests/test_plex_plaintext.py index 49c09b09..ffd86706 100644 --- a/backend/tests/test_plex_plaintext.py +++ b/backend/tests/test_plex_plaintext.py @@ -36,6 +36,9 @@ HU_SUMMARY = "

A műsorban minden párosnak stoppal kell eljutnia.

" # ...and only a whole entity name: a legacy one at its start ("¬", "&") is not. ("AT¬ice; and &x;", "AT¬ice; and &x;"), ("été ©", "été ©"), + # A decimal reference too long for int() to parse is out of range, like �. + ("a &#" + "9" * 5000 + "; b", "a � b"), + ("&#" + "0" * 5000 + "65;", "A"), ("Plain summary.", "Plain summary."), (" padded ", "padded"), ("

", None),