diff --git a/CHANGELOG.md b/CHANGELOG.md index dffeeda..2951d45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.52.5 - 2026-08-09 + +- Changed Watchlist title links to open the anime on the provider it was added from instead of the retired metadata source. +- Added provider-route coverage for Anikoto, AniNeko, and AnimePahe Watchlist links. + ## 0.52.4 - 2026-08-09 - Added provider metadata to Watchlist entries and display a `Source: ...` tag on Watchlist cards. diff --git a/README.md b/README.md index 134786f..426fa48 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Kaizoku is a local web app for searching, tracking, and downloading anime from A - Fall back across the other configured providers when an episode or stream cannot be resolved on the primary provider. - Save files with Jellyfin-friendly layout: `TV/Series Name/Season 01/Series Name - S01E01.mp4`. - Save English external subtitles when the provider exposes usable subtitle tracks. -- Manage focused watchlists for `Watching`, `Planned`, `Finished`, and `Dropped`, with provider source tags on each entry. +- Manage focused watchlists for `Watching`, `Planned`, `Finished`, and `Dropped`, with provider source tags and title links back to the original provider page. - Periodically refresh selected watchlists and auto-download newly available episodes. - Sync watchlist download flags from the filesystem. - Hand completed libraries to Jellyfin TV or movie folders. diff --git a/VERSION b/VERSION index 5e3df49..e56c472 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.52.4 +0.52.5 diff --git a/app.py b/app.py index dfe2632..443baf5 100644 --- a/app.py +++ b/app.py @@ -37,7 +37,6 @@ from app_support import ( MAX_JSON_BODY_BYTES, METADATA_API, METADATA_REFERER, - METADATA_SITE_BASE, MODE_CHOICES, QUALITY_CHOICES, send_discord_webhook_event, @@ -161,11 +160,28 @@ def search_thumbnail_cache_key(title): return f"search-{digest}" -def anime_source_url(show_id, title): - normalized_show_id = str(show_id or "").strip() - if not normalized_show_id: - return f"{METADATA_SITE_BASE}/search-anime?tr=sub&cty=ALL&query={quote(str(title or '').strip(), safe='')}" - return f"{METADATA_SITE_BASE}/bangumi/{quote(normalized_show_id, safe='')}" +PROVIDER_SITE_BASES = { + "anikoto": "https://anikototv.to", + "anineko": "https://anineko.to", + "pahe": "https://animepahe.pw", +} + + +def anime_source_url(show_id, title, provider=None): + parsed_provider, provider_id = provider_bridge.parse_provider_id(show_id, provider or "anikoto") + normalized_provider = normalize_provider(provider, parsed_provider) + base_url = PROVIDER_SITE_BASES[normalized_provider] + normalized_provider_id = str(provider_id or "").strip() + if normalized_provider_id: + if normalized_provider == "pahe": + return f"{base_url}/anime/{quote(normalized_provider_id, safe='')}" + return f"{base_url}/watch/{quote(normalized_provider_id, safe='')}" + query = quote(str(title or "").strip(), safe="") + if normalized_provider == "anineko": + return f"{base_url}/browser?keyword={query}" + if normalized_provider == "pahe": + return f"{base_url}/api?m=search&q={query}" + return f"{base_url}/search?keyword={query}" def infer_image_extension(url, content_type): @@ -1705,7 +1721,7 @@ class WatchlistStore: item["finished_badge"] = ( "downloaded" if item["category"] == "finished" and item["downloaded"] else "manual" if item["category"] == "finished" else "" ) - item["source_url"] = anime_source_url(item.get("show_id"), item.get("title")) + item["source_url"] = anime_source_url(item.get("show_id"), item.get("title"), item.get("provider")) thumb_path = thumbnail_file_path(item.get("thumbnail_path")) item["thumbnail_ready"] = bool(thumb_path and thumb_path.exists()) item["thumbnail_url"] = cover_route(item.get("show_id")) diff --git a/test_app.py b/test_app.py index d06a8d4..1e579a8 100644 --- a/test_app.py +++ b/test_app.py @@ -1642,6 +1642,27 @@ class WatchlistCompletionTests(unittest.TestCase): self.assertEqual(item["provider"], "pahe") self.assertEqual(item["provider_label"], "AnimePahe") + def test_watchlist_source_url_uses_anikoto_show_page(self): + self.seed_watchlist_item(show_id="anikoto:provider-show", title="Provider Show", provider="anikoto") + + item = APP.WATCHLIST.get("anikoto:provider-show") + + self.assertEqual(item["source_url"], "https://anikototv.to/watch/provider-show") + + def test_watchlist_source_url_uses_anineko_show_page(self): + self.seed_watchlist_item(show_id="anineko:provider-show", title="Provider Show", provider="anineko") + + item = APP.WATCHLIST.get("anineko:provider-show") + + self.assertEqual(item["source_url"], "https://anineko.to/watch/provider-show") + + def test_watchlist_source_url_uses_animepahe_show_page(self): + self.seed_watchlist_item(show_id="pahe:provider-show", title="Provider Show", provider="pahe") + + item = APP.WATCHLIST.get("pahe:provider-show") + + self.assertEqual(item["source_url"], "https://animepahe.pw/anime/provider-show") + def test_refresh_persists_expected_episode_total_and_ready_flags(self): self.seed_watchlist_item()