From 2ff4e390c405c07e1730b06c46582c8666060dda Mon Sep 17 00:00:00 2001 From: Dymas Date: Sat, 23 May 2026 19:14:35 +0200 Subject: [PATCH] Revert "Show AllManga alternative watchlist titles" This reverts commit 84c2e533d0e4022025f49f8fdcaeb80834460f97. --- CHANGELOG.md | 4 -- README.md | 7 +-- VERSION | 2 +- app.py | 148 +--------------------------------------------- app_support.py | 2 +- test_app.py | 31 ---------- watchlist_page.py | 17 +----- 7 files changed, 7 insertions(+), 204 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcfafd3..965902b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,5 @@ # Changelog -## 0.37.0 - 2026-05-23 - -- Added watchlist support for alternative English and synonym titles pulled from the AllManga show page and shown directly on each watchlist card. - ## 0.36.1 - 2026-05-23 - Replaced the first-visit remote-access 403 dead end with a browser sign-in page and persistent auth cookie, so remote users no longer need to know the `?token=...` bootstrap URL format up front. diff --git a/README.md b/README.md index 13153de..e356ed8 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Small local web UI for a system-wide `ani-cli` install. -Current version: `0.37.0` +Current version: `0.36.1` ## Project Layout @@ -238,11 +238,6 @@ Thumbnail behavior: - Thumbnails are served from the local `/api/watchlist/thumb/...` route. - Cached thumbnails are reused. - -Title behavior: - -- Watchlist refreshes also try to pull alternative English and synonym titles from the AllManga show page. -- Up to three alternate names are shown as a muted secondary line under the main watchlist title. - Missing covers are warmed in small batches. - Failed lookups back off before retrying. - You can force `Reload` or use `Upload` when no thumbnail exists. diff --git a/VERSION b/VERSION index 0f1a7df..19199bc 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.37.0 +0.36.1 diff --git a/app.py b/app.py index 840cc0d..82a51e6 100644 --- a/app.py +++ b/app.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 import base64 import gzip -import html import json import mimetypes import os @@ -147,95 +146,6 @@ def anime_source_url(show_id, title): return f"{ALLMANGA_SITE_BASE}/bangumi/{quote(normalized_show_id, safe='')}" -def unique_title_values(values, *exclude_values): - seen = { - normalize_title_key(value) - for value in exclude_values - if normalize_title_key(value) - } - deduped = [] - for value in values: - text = str(value or "").strip() - normalized = normalize_title_key(text) - if not text or not normalized or normalized in seen: - continue - seen.add(normalized) - deduped.append(text) - return deduped - - -def parse_allmanga_alternative_titles(html_text, primary_title=""): - text = str(html_text or "") - primary = str(primary_title or "").strip() - candidates = [] - preferred_english = "" - - def add_candidate(value, english_preferred=False): - nonlocal preferred_english - cleaned = html.unescape(str(value or "")).replace('\\"', '"').strip() - if not cleaned: - return - candidates.append(cleaned) - if english_preferred and not preferred_english: - preferred_english = cleaned - - quoted_key_patterns = ( - (r'"english(?:Title|Name)?"\s*:\s*"([^"]+)"', True), - (r'"(?:altEnglish|alternativeEnglish(?:Title|Name)?)"\s*:\s*"([^"]+)"', True), - (r'"romaji(?:Title|Name)?"\s*:\s*"([^"]+)"', False), - (r'"native(?:Title|Name)?"\s*:\s*"([^"]+)"', False), - ) - for pattern, english_preferred in quoted_key_patterns: - for match in re.findall(pattern, text, flags=re.IGNORECASE): - add_candidate(match, english_preferred=english_preferred) - - array_key_patterns = ( - (r'"synonyms"\s*:\s*\[(.*?)\]', False), - (r'"alternative(?:Names|Titles)?"\s*:\s*\[(.*?)\]', False), - (r'"alt(?:Titles|Names)?"\s*:\s*\[(.*?)\]', False), - ) - for pattern, english_preferred in array_key_patterns: - for body in re.findall(pattern, text, flags=re.IGNORECASE | re.DOTALL): - for match in re.findall(r'"([^"]+)"', body): - add_candidate(match, english_preferred=english_preferred) - - deduped = unique_title_values(candidates, primary) - english_title = "" - for candidate in unique_title_values([preferred_english], primary): - english_title = candidate - break - if not english_title and deduped: - english_title = deduped[0] - return { - "english_title": english_title, - "alt_titles": deduped, - } - - -def fetch_allmanga_alternative_titles(show_id, title="", timeout=25): - url = anime_source_url(show_id, title) - request = urllib.request.Request( - url, - headers={ - "Accept": "text/html,application/xhtml+xml", - "Referer": ALLANIME_REFERER, - "User-Agent": AGENT, - }, - method="GET", - ) - try: - with urllib.request.urlopen(request, timeout=timeout) as response: - raw = response.read().decode("utf-8", errors="replace") - except urllib.error.URLError as exc: - raise RuntimeError(f"Could not reach AllManga page: {exc}") from exc - except TimeoutError as exc: - raise RuntimeError("AllManga page request timed out") from exc - parsed = parse_allmanga_alternative_titles(raw, primary_title=title) - if not parsed["alt_titles"] and not parsed["english_title"]: - raise RuntimeError("AllManga page did not expose any alternative titles") - return parsed - - def infer_image_extension(url, content_type): normalized_type = str(content_type or "").split(";", 1)[0].strip().lower() extension = mimetypes.guess_extension(normalized_type, strict=False) @@ -1118,8 +1028,6 @@ class WatchlistStore: dub_latest_episode TEXT, animeschedule_route TEXT, animeschedule_title TEXT, - english_title TEXT, - alt_titles_json TEXT NOT NULL DEFAULT '[]', anidb_aid TEXT, anidb_title TEXT, thumbnail_path TEXT, @@ -1148,10 +1056,6 @@ class WatchlistStore: conn.execute("ALTER TABLE watchlist ADD COLUMN animeschedule_route TEXT") if "animeschedule_title" not in columns: conn.execute("ALTER TABLE watchlist ADD COLUMN animeschedule_title TEXT") - if "english_title" not in columns: - conn.execute("ALTER TABLE watchlist ADD COLUMN english_title TEXT") - if "alt_titles_json" not in columns: - conn.execute("ALTER TABLE watchlist ADD COLUMN alt_titles_json TEXT NOT NULL DEFAULT '[]'") conn.execute( "UPDATE watchlist SET category = 'watching' WHERE category IS NULL OR TRIM(category) = ''" ) @@ -1173,22 +1077,6 @@ class WatchlistStore: item["dub_progress"] = format_episode_progress(item["dub_count"], item["expected_count"]) item["sub_complete"] = mode_is_complete(item["sub_count"], item["expected_count"], item["airing_status"]) item["dub_complete"] = mode_is_complete(item["dub_count"], item["expected_count"], item["airing_status"]) - try: - alt_titles = json.loads(item.get("alt_titles_json") or "[]") - except json.JSONDecodeError: - alt_titles = [] - if not isinstance(alt_titles, list): - alt_titles = [] - item["alt_titles"] = unique_title_values(alt_titles, item.get("title")) - item["english_title"] = next(iter(unique_title_values([item.get("english_title")], item.get("title"))), "") - display_alt_titles = [] - if item["english_title"]: - display_alt_titles.append(item["english_title"]) - for candidate in item["alt_titles"]: - if normalize_title_key(candidate) == normalize_title_key(item["english_title"]): - continue - display_alt_titles.append(candidate) - item["display_alt_titles"] = display_alt_titles[:3] item["finished_badge"] = ( "downloaded" if item["category"] == "finished" and item["downloaded"] else "manual" if item["category"] == "finished" else "" ) @@ -1209,19 +1097,15 @@ class WatchlistStore: return item def _upsert_conn(self, conn, item): - raw_alt_titles = item.get("alt_titles") if isinstance(item.get("alt_titles"), list) else [] - if not raw_alt_titles and isinstance(item.get("alt_titles_json"), list): - raw_alt_titles = item.get("alt_titles_json") - normalized_alt_titles = unique_title_values(raw_alt_titles, item.get("title")) conn.execute( """ INSERT INTO watchlist ( show_id, title, category, downloaded, status, status_message, created_at, updated_at, last_checked, sub_count, dub_count, expected_count, airing_status, sub_latest_episode, dub_latest_episode, - animeschedule_route, animeschedule_title, english_title, alt_titles_json, anidb_aid, anidb_title, + animeschedule_route, animeschedule_title, anidb_aid, anidb_title, thumbnail_path, thumbnail_checked_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(show_id) DO UPDATE SET title = excluded.title, category = excluded.category, @@ -1238,8 +1122,6 @@ class WatchlistStore: dub_latest_episode = excluded.dub_latest_episode, animeschedule_route = excluded.animeschedule_route, animeschedule_title = excluded.animeschedule_title, - english_title = excluded.english_title, - alt_titles_json = excluded.alt_titles_json, anidb_aid = excluded.anidb_aid, anidb_title = excluded.anidb_title, thumbnail_path = excluded.thumbnail_path, @@ -1263,8 +1145,6 @@ class WatchlistStore: item.get("dub_latest_episode"), item.get("animeschedule_route"), item.get("animeschedule_title"), - item.get("english_title"), - json.dumps(normalized_alt_titles), item.get("anidb_aid"), item.get("anidb_title"), item.get("thumbnail_path"), @@ -1310,8 +1190,6 @@ class WatchlistStore: "dub_latest_episode": None, "animeschedule_route": None, "animeschedule_title": None, - "english_title": None, - "alt_titles": [], "anidb_aid": None, "anidb_title": None, "thumbnail_path": None, @@ -1437,8 +1315,6 @@ class WatchlistStore: "dub_latest_episode": None, "animeschedule_route": None, "animeschedule_title": None, - "english_title": None, - "alt_titles": [], "anidb_aid": None, "anidb_title": None, "thumbnail_path": None, @@ -1465,10 +1341,6 @@ class WatchlistStore: sub_episodes = snapshot["episodes"]["sub"] dub_episodes = snapshot["episodes"]["dub"] schedule = None - allmanga_titles = { - "english_title": str(existing.get("english_title") or "").strip(), - "alt_titles": list(existing.get("alt_titles") or []), - } try: schedule = resolve_animeschedule_anime( snapshot["title"] or existing["title"], @@ -1477,14 +1349,6 @@ class WatchlistStore: ) except Exception: schedule = None - try: - allmanga_titles = fetch_allmanga_alternative_titles( - normalized_show_id, - snapshot["title"] or existing["title"], - timeout=request_timeout, - ) - except Exception as exc: - debug_log("watchlist.alt_titles.lookup_failed", show_id=normalized_show_id, error=exc) if self.refresh_stop_event.is_set(): return None if not self.has_show(normalized_show_id): @@ -1510,8 +1374,6 @@ class WatchlistStore: dub_episodes[-1] if dub_episodes else None, (schedule or {}).get("route") or existing.get("animeschedule_route"), (schedule or {}).get("title") or existing.get("animeschedule_title"), - allmanga_titles.get("english_title") or existing.get("english_title"), - json.dumps(unique_title_values(allmanga_titles.get("alt_titles") or [], snapshot["title"] or existing["title"])), normalized_show_id, ) except Exception as exc: @@ -1533,8 +1395,6 @@ class WatchlistStore: existing.get("dub_latest_episode"), existing.get("animeschedule_route"), existing.get("animeschedule_title"), - existing.get("english_title"), - existing.get("alt_titles_json") or "[]", normalized_show_id, ) @@ -1557,9 +1417,7 @@ class WatchlistStore: sub_latest_episode = ?, dub_latest_episode = ?, animeschedule_route = ?, - animeschedule_title = ?, - english_title = ?, - alt_titles_json = ? + animeschedule_title = ? WHERE show_id = ? """, payload, diff --git a/app_support.py b/app_support.py index 7af5d56..2b37da2 100644 --- a/app_support.py +++ b/app_support.py @@ -19,7 +19,7 @@ from uuid import uuid4 PROJECT_ROOT = Path(__file__).resolve().parent ANI_CLI = os.environ.get("ANI_CLI_BIN") or shutil.which("ani-cli") or "ani-cli" APP_NAME = "ani-cli-web" -VERSION = "0.37.0" +VERSION = "0.36.1" ALLANIME_BASE = "allanime.day" ALLANIME_API = f"https://api.{ALLANIME_BASE}" ALLANIME_REFERER = "https://allmanga.to" diff --git a/test_app.py b/test_app.py index 8a67001..e9baa23 100644 --- a/test_app.py +++ b/test_app.py @@ -849,23 +849,6 @@ class WatchlistCompletionTests(unittest.TestCase): self.assertIn("Sub ready.", item["status_message"]) self.assertIn("Dub ready.", item["status_message"]) - def test_refresh_persists_allmanga_alternative_titles(self): - self.seed_watchlist_item() - snapshot = { - "show_id": "show-1", - "title": "Example Show", - "episodes": {"sub": ["1"], "dub": []}, - } - with mock.patch.object(APP, "fetch_show_episode_snapshot", return_value=snapshot), mock.patch.object( - APP, "resolve_animeschedule_anime", return_value=None - ), mock.patch.object( - APP, "fetch_allmanga_alternative_titles", return_value={"english_title": "Example Show English", "alt_titles": ["Example Show English", "Sample Alias"]} - ), mock.patch.object(APP.WatchlistStore, "ensure_thumbnail", return_value=None): - item = APP.WATCHLIST.refresh("show-1") - - self.assertEqual(item["english_title"], "Example Show English") - self.assertEqual(item["display_alt_titles"], ["Example Show English", "Sample Alias"]) - def test_list_filters_by_category_and_reports_category_counts(self): self.seed_watchlist_item(show_id="show-1", title="Watching Show", category="watching", status="queued") self.seed_watchlist_item(show_id="show-2", title="Finished Show", category="finished", downloaded=True) @@ -1745,26 +1728,12 @@ class TemplateHelperTests(unittest.TestCase): self.assertIn('api("/api/watchlist/download-all"', APP.WATCHLIST_HTML) self.assertIn("Enter sub or dub", APP.WATCHLIST_HTML) - def test_watchlist_page_shows_alternative_titles(self): - self.assertIn('class="alt-title"', APP.WATCHLIST_HTML) - self.assertIn('Alt: ${item.display_alt_titles.join(" · ")}', APP.WATCHLIST_HTML) - def test_watchlist_thumbnail_warmup_skips_repeated_attempts(self): self.assertIn("thumbnailWarmupPending: new Set()", APP.WATCHLIST_HTML) self.assertIn("function thumbnailWarmupToken(item)", APP.WATCHLIST_HTML) self.assertIn("if (state.thumbnailWarmupTokens[item.show_id] === token) continue;", APP.WATCHLIST_HTML) self.assertIn("state.thumbnailWarmupPending.delete(showId);", APP.WATCHLIST_HTML) - def test_parse_allmanga_alternative_titles_extracts_english_and_synonyms(self): - html_text = """ - - """ - parsed = APP.parse_allmanga_alternative_titles(html_text, primary_title="Example Show") - self.assertEqual(parsed["english_title"], "Example Show English") - self.assertEqual(parsed["alt_titles"], ["Example Show English", "Sample Alias"]) - class AniDbCacheTests(unittest.TestCase): def test_get_cached_anidb_titles_reuses_cached_entries(self): diff --git a/watchlist_page.py b/watchlist_page.py index b19a23c..919552b 100644 --- a/watchlist_page.py +++ b/watchlist_page.py @@ -413,14 +413,6 @@ WATCHLIST_HTML = r""" flex: 1; min-width: 0; } - .alt-title { - color: var(--muted); - font-size: 12px; - line-height: 1.35; - margin-top: 4px; - overflow-wrap: anywhere; - min-height: 1.35em; - } .card-title a { color: inherit; text-decoration: none; @@ -999,10 +991,7 @@ WATCHLIST_HTML = r"""
-
-
-
-
+
${item.finished_badge ? "✓" : ""}
@@ -1030,10 +1019,6 @@ WATCHLIST_HTML = r""" titleLink.rel = "noopener noreferrer"; titleLink.textContent = item.title; titleNode.replaceChildren(titleLink); - const altTitleNode = card.querySelector(".alt-title"); - altTitleNode.textContent = (item.display_alt_titles || []).length - ? `Alt: ${item.display_alt_titles.join(" · ")}` - : ""; const finishCheck = card.querySelector(".finish-check"); if (finishCheck && item.finished_badge) { finishCheck.title = item.finished_badge === "downloaded"