Add watchlist source name override for ani-cli queries

This commit is contained in:
Dymas
2026-06-13 16:52:11 +02:00
parent ef00ff20a9
commit 6403c98b7d
7 changed files with 70 additions and 21 deletions
+6
View File
@@ -1,5 +1,11 @@
# Changelog # Changelog
## 0.45.18 - 2026-06-13
- Added a `Source name` field to the watchlist auto-download editor so per-show jobs can send a more specific anime title to `ani-cli` when provider searches return multiple similarly named results.
- Migrated existing watchlist databases by adding an `auto_download_source_name` column and defaulting stored entries to the tracked title, preserving current behavior until you override it.
- Changed both watchlist `Download all` and automatic watchlist episode queueing to use the saved `Source name` for the `ani-cli` query while keeping the separate library `Name` field for folder and file naming.
## 0.45.17 - 2026-06-07 ## 0.45.17 - 2026-06-07
- Added a lightweight `/api/watchlist/homepage` endpoint for Homepage's Custom API widget, returning simple watchlist category counts for `watchlist`, `planned`, `finished`, `dropped`, and `total`. - Added a lightweight `/api/watchlist/homepage` endpoint for Homepage's Custom API widget, returning simple watchlist category counts for `watchlist`, `planned`, `finished`, `dropped`, and `total`.
+4 -4
View File
@@ -2,7 +2,7 @@
Local web UI for a system-wide `ani-cli` install. Local web UI for a system-wide `ani-cli` install.
Current version: `0.45.17` Current version: `0.45.18`
## What it does ## What it does
@@ -11,7 +11,7 @@ Current version: `0.45.17`
- Track shows in a watchlist with `Watching`, `Planned`, `Finished`, and `Dropped` categories - Track shows in a watchlist with `Watching`, `Planned`, `Finished`, and `Dropped` categories
- Expose a small JSON summary endpoint for Homepage dashboard widgets - Expose a small JSON summary endpoint for Homepage dashboard widgets
- Refresh watchlist episode counts manually or on a schedule - Refresh watchlist episode counts manually or on a schedule
- Auto-download missing episodes for `Watching` entries after later refreshes - Auto-download missing episodes for `Watching` entries after later refreshes, with a per-show `Source name` override for `ani-cli` search
- Move fully completed libraries into Jellyfin TV or movie folders - Move fully completed libraries into Jellyfin TV or movie folders
- Send optional Discord webhook notifications - Send optional Discord webhook notifications
@@ -19,7 +19,7 @@ Current version: `0.45.17`
- `/`: Search and queue downloads, with poster-card search results below the selection box and alternate English titles under the main name when available - `/`: Search and queue downloads, with poster-card search results below the selection box and alternate English titles under the main name when available
- `/queue`: Monitor jobs, inspect logs, retry failed jobs, remove finished jobs - `/queue`: Monitor jobs, inspect logs, retry failed jobs, remove finished jobs
- `/watchlist`: Track shows, refresh them, download all available episodes, manage auto-download settings - `/watchlist`: Track shows, refresh them, download all available episodes, and manage auto-download settings such as `Source name`, library `Name`, season, and episode offset
- `/config`: Save defaults, schedule refreshes, configure Jellyfin and Discord, view runtime info and the changelog - `/config`: Save defaults, schedule refreshes, configure Jellyfin and Discord, view runtime info and the changelog
## Quick start ## Quick start
@@ -96,7 +96,7 @@ Docker notes:
### Auto-download ### Auto-download
- Runs only for entries in `Watching` - Runs only for entries in `Watching`
- Uses the per-show language, quality, season, library name, and optional episode offset settings - Uses the per-show language, quality, `Source name` search override, season, library name, and optional episode offset settings
- Can queue backlog episodes that were already available before the show was first tracked - Can queue backlog episodes that were already available before the show was first tracked
### Jellyfin handoff ### Jellyfin handoff
+1 -1
View File
@@ -1 +1 @@
0.45.17 0.45.18
+35 -9
View File
@@ -1338,10 +1338,12 @@ class WatchlistStore:
def _auto_download_defaults(self, title): def _auto_download_defaults(self, title):
config = self.config_getter() or {} config = self.config_getter() or {}
normalized_title = str(title or "").strip() or "Anime"
return { return {
"auto_download_mode": str(config.get("auto_download_mode") or "dub").strip().lower() or "dub", "auto_download_mode": str(config.get("auto_download_mode") or "dub").strip().lower() or "dub",
"auto_download_quality": str(config.get("auto_download_quality") or "best").strip().lower() or "best", "auto_download_quality": str(config.get("auto_download_quality") or "best").strip().lower() or "best",
"auto_download_name": sanitize_path_component(title, "Anime"), "auto_download_name": sanitize_path_component(normalized_title, "Anime"),
"auto_download_source_name": normalized_title,
"auto_download_series": "1", "auto_download_series": "1",
"auto_download_offset": None, "auto_download_offset": None,
"media_type": "tv", "media_type": "tv",
@@ -1422,6 +1424,7 @@ class WatchlistStore:
auto_download_mode TEXT, auto_download_mode TEXT,
auto_download_quality TEXT, auto_download_quality TEXT,
auto_download_name TEXT, auto_download_name TEXT,
auto_download_source_name TEXT,
auto_download_series TEXT, auto_download_series TEXT,
auto_download_offset TEXT, auto_download_offset TEXT,
downloaded_sub_episodes_json TEXT, downloaded_sub_episodes_json TEXT,
@@ -1468,6 +1471,8 @@ class WatchlistStore:
conn.execute("ALTER TABLE watchlist ADD COLUMN auto_download_quality TEXT") conn.execute("ALTER TABLE watchlist ADD COLUMN auto_download_quality TEXT")
if "auto_download_name" not in columns: if "auto_download_name" not in columns:
conn.execute("ALTER TABLE watchlist ADD COLUMN auto_download_name TEXT") conn.execute("ALTER TABLE watchlist ADD COLUMN auto_download_name TEXT")
if "auto_download_source_name" not in columns:
conn.execute("ALTER TABLE watchlist ADD COLUMN auto_download_source_name TEXT")
if "auto_download_series" not in columns: if "auto_download_series" not in columns:
conn.execute("ALTER TABLE watchlist ADD COLUMN auto_download_series TEXT") conn.execute("ALTER TABLE watchlist ADD COLUMN auto_download_series TEXT")
if "auto_download_offset" not in columns: if "auto_download_offset" not in columns:
@@ -1548,6 +1553,7 @@ class WatchlistStore:
item["auto_download_mode"] = mode if mode in MODE_CHOICES else defaults["auto_download_mode"] item["auto_download_mode"] = mode if mode in MODE_CHOICES else defaults["auto_download_mode"]
item["auto_download_quality"] = quality if quality in QUALITY_CHOICES else defaults["auto_download_quality"] item["auto_download_quality"] = quality if quality in QUALITY_CHOICES else defaults["auto_download_quality"]
item["auto_download_name"] = sanitize_path_component(item.get("auto_download_name") or defaults["auto_download_name"], defaults["auto_download_name"]) item["auto_download_name"] = sanitize_path_component(item.get("auto_download_name") or defaults["auto_download_name"], defaults["auto_download_name"])
item["auto_download_source_name"] = str(item.get("auto_download_source_name") or defaults["auto_download_source_name"]).strip() or defaults["auto_download_source_name"]
item["auto_download_series"] = normalize_season(item.get("auto_download_series") or defaults["auto_download_series"]) item["auto_download_series"] = normalize_season(item.get("auto_download_series") or defaults["auto_download_series"])
item["auto_download_offset"] = normalize_episode_offset(item.get("auto_download_offset")) item["auto_download_offset"] = normalize_episode_offset(item.get("auto_download_offset"))
item["downloaded_sub_episodes"] = decode_episode_values(item.get("downloaded_sub_episodes_json")) item["downloaded_sub_episodes"] = decode_episode_values(item.get("downloaded_sub_episodes_json"))
@@ -1574,10 +1580,10 @@ class WatchlistStore:
animeschedule_route, animeschedule_title, animeschedule_route, animeschedule_title,
anilist_id, anilist_title, anilist_site_url, alternative_titles_json, anilist_id, anilist_title, anilist_site_url, alternative_titles_json,
anidb_aid, anidb_title, media_type, anidb_aid, anidb_title, media_type,
auto_download_mode, auto_download_quality, auto_download_name, auto_download_series, auto_download_offset, auto_download_mode, auto_download_quality, auto_download_name, auto_download_source_name, auto_download_series, auto_download_offset,
downloaded_sub_episodes_json, downloaded_dub_episodes_json, downloaded_sub_episodes_json, downloaded_dub_episodes_json,
thumbnail_path, thumbnail_checked_at thumbnail_path, thumbnail_checked_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(show_id) DO UPDATE SET ON CONFLICT(show_id) DO UPDATE SET
title = excluded.title, title = excluded.title,
category = excluded.category, category = excluded.category,
@@ -1604,6 +1610,7 @@ class WatchlistStore:
auto_download_mode = excluded.auto_download_mode, auto_download_mode = excluded.auto_download_mode,
auto_download_quality = excluded.auto_download_quality, auto_download_quality = excluded.auto_download_quality,
auto_download_name = excluded.auto_download_name, auto_download_name = excluded.auto_download_name,
auto_download_source_name = excluded.auto_download_source_name,
auto_download_series = excluded.auto_download_series, auto_download_series = excluded.auto_download_series,
auto_download_offset = excluded.auto_download_offset, auto_download_offset = excluded.auto_download_offset,
downloaded_sub_episodes_json = excluded.downloaded_sub_episodes_json, downloaded_sub_episodes_json = excluded.downloaded_sub_episodes_json,
@@ -1639,6 +1646,7 @@ class WatchlistStore:
item.get("auto_download_mode"), item.get("auto_download_mode"),
item.get("auto_download_quality"), item.get("auto_download_quality"),
item.get("auto_download_name"), item.get("auto_download_name"),
str(item.get("auto_download_source_name") or "").strip() or str(item["title"]).strip(),
item.get("auto_download_series"), item.get("auto_download_series"),
normalize_episode_offset(item.get("auto_download_offset")), normalize_episode_offset(item.get("auto_download_offset")),
item.get("downloaded_sub_episodes_json"), item.get("downloaded_sub_episodes_json"),
@@ -1696,6 +1704,7 @@ class WatchlistStore:
"auto_download_mode": None, "auto_download_mode": None,
"auto_download_quality": None, "auto_download_quality": None,
"auto_download_name": None, "auto_download_name": None,
"auto_download_source_name": None,
"auto_download_series": None, "auto_download_series": None,
"auto_download_offset": None, "auto_download_offset": None,
"downloaded_sub_episodes_json": None, "downloaded_sub_episodes_json": None,
@@ -1867,6 +1876,9 @@ class WatchlistStore:
} }
item["auto_download_series"] = normalize_season(payload.get("auto_download_series") or item.get("auto_download_series") or "1") item["auto_download_series"] = normalize_season(payload.get("auto_download_series") or item.get("auto_download_series") or "1")
item["auto_download_offset"] = normalize_episode_offset(payload.get("auto_download_offset")) item["auto_download_offset"] = normalize_episode_offset(payload.get("auto_download_offset"))
item["auto_download_source_name"] = (
str(payload.get("auto_download_source_name") or item.get("auto_download_source_name") or title).strip() or title
)
self._upsert_conn(conn, item) self._upsert_conn(conn, item)
self._clear_removal_conn(conn, show_id) self._clear_removal_conn(conn, show_id)
@@ -2196,6 +2208,7 @@ class WatchlistStore:
"anidb_aid": None, "anidb_aid": None,
"anidb_title": None, "anidb_title": None,
"media_type": normalized_media_type, "media_type": normalized_media_type,
"auto_download_source_name": None,
"auto_download_series": None, "auto_download_series": None,
"downloaded_sub_episodes_json": encode_episode_values(downloaded_episode_values) if normalized_mode == "sub" else None, "downloaded_sub_episodes_json": encode_episode_values(downloaded_episode_values) if normalized_mode == "sub" else None,
"downloaded_dub_episodes_json": encode_episode_values(downloaded_episode_values) if normalized_mode == "dub" else None, "downloaded_dub_episodes_json": encode_episode_values(downloaded_episode_values) if normalized_mode == "dub" else None,
@@ -2206,7 +2219,7 @@ class WatchlistStore:
self.schedule_refresh(normalized_show_id, source="sync") self.schedule_refresh(normalized_show_id, source="sync")
return self.get(normalized_show_id) return self.get(normalized_show_id)
def update_auto_download_settings(self, show_id, mode=None, quality=None, name=None, series=None, episode_offset=None): def update_auto_download_settings(self, show_id, mode=None, quality=None, name=None, source_name=None, series=None, episode_offset=None):
existing = self.get(show_id) existing = self.get(show_id)
defaults = self._auto_download_defaults(existing["title"]) defaults = self._auto_download_defaults(existing["title"])
normalized_mode = str(mode or defaults["auto_download_mode"]).strip().lower() normalized_mode = str(mode or defaults["auto_download_mode"]).strip().lower()
@@ -2216,6 +2229,9 @@ class WatchlistStore:
if normalized_quality not in QUALITY_CHOICES: if normalized_quality not in QUALITY_CHOICES:
raise ValueError("Unknown auto-download quality.") raise ValueError("Unknown auto-download quality.")
normalized_name = sanitize_path_component(name or defaults["auto_download_name"], defaults["auto_download_name"]) normalized_name = sanitize_path_component(name or defaults["auto_download_name"], defaults["auto_download_name"])
normalized_source_name = str(source_name or existing.get("auto_download_source_name") or defaults["auto_download_source_name"]).strip()
if not normalized_source_name:
normalized_source_name = defaults["auto_download_source_name"]
normalized_series = normalize_season(series or existing.get("auto_download_series") or defaults["auto_download_series"]) normalized_series = normalize_season(series or existing.get("auto_download_series") or defaults["auto_download_series"])
normalized_offset = ( normalized_offset = (
normalize_episode_offset(existing.get("auto_download_offset")) normalize_episode_offset(existing.get("auto_download_offset"))
@@ -2226,10 +2242,19 @@ class WatchlistStore:
conn.execute( conn.execute(
""" """
UPDATE watchlist UPDATE watchlist
SET auto_download_mode = ?, auto_download_quality = ?, auto_download_name = ?, auto_download_series = ?, auto_download_offset = ?, updated_at = ? SET auto_download_mode = ?, auto_download_quality = ?, auto_download_name = ?, auto_download_source_name = ?, auto_download_series = ?, auto_download_offset = ?, updated_at = ?
WHERE show_id = ? WHERE show_id = ?
""", """,
(normalized_mode, normalized_quality, normalized_name, normalized_series, normalized_offset, now_iso(), existing["show_id"]), (
normalized_mode,
normalized_quality,
normalized_name,
normalized_source_name,
normalized_series,
normalized_offset,
now_iso(),
existing["show_id"],
),
) )
return self.get(show_id) return self.get(show_id)
@@ -2442,7 +2467,7 @@ def download_watchlist_item(show_id, mode):
if normalized_mode not in MODE_CHOICES: if normalized_mode not in MODE_CHOICES:
raise ValueError("Mode must be sub or dub") raise ValueError("Mode must be sub or dub")
query = str(item.get("title") or item.get("show_id") or "").strip() query = str(item.get("auto_download_source_name") or item.get("title") or item.get("show_id") or "").strip()
if not query: if not query:
raise ValueError("Watchlist item is missing a searchable title.") raise ValueError("Watchlist item is missing a searchable title.")
@@ -2502,7 +2527,7 @@ def queue_watchlist_auto_download(item, refresh_source="manual"):
if not specs: if not specs:
return {"queued": False, "reason": "nothing_missing"} return {"queued": False, "reason": "nothing_missing"}
query = str(item.get("title") or item.get("show_id") or "").strip() query = str(item.get("auto_download_source_name") or item.get("title") or item.get("show_id") or "").strip()
if not query: if not query:
return {"queued": False, "reason": "missing_title"} return {"queued": False, "reason": "missing_title"}
@@ -2619,13 +2644,14 @@ def update_watchlist_media_type(show_id, media_type):
return {"message": f"Saved {item['title']} as {item['media_type_label']}.", "item": item} return {"message": f"Saved {item['title']} as {item['media_type_label']}.", "item": item}
def update_watchlist_auto_download(show_id, mode=None, quality=None, name=None, series=None, episode_offset=None): def update_watchlist_auto_download(show_id, mode=None, quality=None, name=None, source_name=None, series=None, episode_offset=None):
ensure_runtime() ensure_runtime()
item = WATCHLIST.update_auto_download_settings( item = WATCHLIST.update_auto_download_settings(
show_id, show_id,
mode=mode, mode=mode,
quality=quality, quality=quality,
name=name, name=name,
source_name=source_name,
series=series, series=series,
episode_offset=episode_offset, episode_offset=episode_offset,
) )
+1
View File
@@ -729,6 +729,7 @@ class Handler(BaseHTTPRequestHandler):
payload.get("mode"), payload.get("mode"),
payload.get("quality"), payload.get("quality"),
payload.get("name"), payload.get("name"),
payload.get("source_name"),
payload.get("series"), payload.get("series"),
payload.get("episode_offset"), payload.get("episode_offset"),
) )
+15 -7
View File
@@ -1598,6 +1598,7 @@ class WatchlistCompletionTests(unittest.TestCase):
"show_id": "show-42", "show_id": "show-42",
"title": "Queue Show", "title": "Queue Show",
"auto_download_name": "Queue Show Library", "auto_download_name": "Queue Show Library",
"auto_download_source_name": "Queue Show Source",
"auto_download_series": "4", "auto_download_series": "4",
"auto_download_quality": "720", "auto_download_quality": "720",
"media_type": "movie", "media_type": "movie",
@@ -1613,7 +1614,7 @@ class WatchlistCompletionTests(unittest.TestCase):
runtime["download_queue"].add.assert_called_once_with( runtime["download_queue"].add.assert_called_once_with(
{ {
"show_id": "show-42", "show_id": "show-42",
"query": "Queue Show", "query": "Queue Show Source",
"title": "Queue Show", "title": "Queue Show",
"anime_name": "Queue Show Library", "anime_name": "Queue Show Library",
"media_type": "movie", "media_type": "movie",
@@ -1656,6 +1657,7 @@ class WatchlistCompletionTests(unittest.TestCase):
"show_id": "show-42", "show_id": "show-42",
"title": "Queue Show", "title": "Queue Show",
"auto_download_name": "Queue Show", "auto_download_name": "Queue Show",
"auto_download_source_name": "Queue Show Alt Search",
"auto_download_series": "1", "auto_download_series": "1",
"media_type": "tv", "media_type": "tv",
} }
@@ -2339,6 +2341,7 @@ class AutoDownloadQueueTests(unittest.TestCase):
"auto_download_mode": "dub", "auto_download_mode": "dub",
"auto_download_quality": "best", "auto_download_quality": "best",
"auto_download_name": 'Queue Show: Alt/Name', "auto_download_name": 'Queue Show: Alt/Name',
"auto_download_source_name": "Queue Show Source",
"auto_download_series": "3", "auto_download_series": "3",
"auto_download_offset": "13", "auto_download_offset": "13",
"downloaded_dub_episodes": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"], "downloaded_dub_episodes": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"],
@@ -2370,7 +2373,7 @@ class AutoDownloadQueueTests(unittest.TestCase):
download_queue.add.assert_called_once_with( download_queue.add.assert_called_once_with(
{ {
"show_id": "show-42", "show_id": "show-42",
"query": "Queue Show", "query": "Queue Show Source",
"title": "Queue Show", "title": "Queue Show",
"anime_name": "Queue Show- Alt-Name", "anime_name": "Queue Show- Alt-Name",
"media_type": "tv", "media_type": "tv",
@@ -2395,6 +2398,7 @@ class AutoDownloadQueueTests(unittest.TestCase):
"auto_download_mode": "dub", "auto_download_mode": "dub",
"auto_download_quality": "best", "auto_download_quality": "best",
"auto_download_name": "Backlog Show", "auto_download_name": "Backlog Show",
"auto_download_source_name": "Backlog Show Search",
"auto_download_series": "1", "auto_download_series": "1",
"auto_download_offset": None, "auto_download_offset": None,
"downloaded_dub_episodes": [], "downloaded_dub_episodes": [],
@@ -2424,7 +2428,7 @@ class AutoDownloadQueueTests(unittest.TestCase):
download_queue.add.assert_called_once_with( download_queue.add.assert_called_once_with(
{ {
"show_id": "show-99", "show_id": "show-99",
"query": "Backlog Show", "query": "Backlog Show Search",
"title": "Backlog Show", "title": "Backlog Show",
"anime_name": "Backlog Show", "anime_name": "Backlog Show",
"media_type": "tv", "media_type": "tv",
@@ -2449,6 +2453,7 @@ class AutoDownloadQueueTests(unittest.TestCase):
"auto_download_mode": "dub", "auto_download_mode": "dub",
"auto_download_quality": "best", "auto_download_quality": "best",
"auto_download_name": "First Refresh Show", "auto_download_name": "First Refresh Show",
"auto_download_source_name": "First Refresh Source",
"auto_download_series": "1", "auto_download_series": "1",
"auto_download_offset": None, "auto_download_offset": None,
"downloaded_dub_episodes": [], "downloaded_dub_episodes": [],
@@ -2478,7 +2483,7 @@ class AutoDownloadQueueTests(unittest.TestCase):
download_queue.add.assert_called_once_with( download_queue.add.assert_called_once_with(
{ {
"show_id": "show-first-refresh", "show_id": "show-first-refresh",
"query": "First Refresh Show", "query": "First Refresh Source",
"title": "First Refresh Show", "title": "First Refresh Show",
"anime_name": "First Refresh Show", "anime_name": "First Refresh Show",
"media_type": "tv", "media_type": "tv",
@@ -2503,6 +2508,7 @@ class AutoDownloadQueueTests(unittest.TestCase):
"auto_download_mode": "dub", "auto_download_mode": "dub",
"auto_download_quality": "best", "auto_download_quality": "best",
"auto_download_name": "Split Season Show", "auto_download_name": "Split Season Show",
"auto_download_source_name": "Split Season Search",
"auto_download_series": "2", "auto_download_series": "2",
"auto_download_offset": "13", "auto_download_offset": "13",
"downloaded_dub_episodes": [], "downloaded_dub_episodes": [],
@@ -2531,7 +2537,7 @@ class AutoDownloadQueueTests(unittest.TestCase):
download_queue.add.assert_called_once_with( download_queue.add.assert_called_once_with(
{ {
"show_id": "show-77", "show_id": "show-77",
"query": "Split Season Show", "query": "Split Season Search",
"title": "Split Season Show", "title": "Split Season Show",
"anime_name": "Split Season Show", "anime_name": "Split Season Show",
"media_type": "tv", "media_type": "tv",
@@ -3089,7 +3095,7 @@ class HandlerRouteTests(unittest.TestCase):
self.assertEqual(handler.json_status, HTTPStatus.CREATED) self.assertEqual(handler.json_status, HTTPStatus.CREATED)
def test_watchlist_auto_download_settings_post_returns_updated_item(self): def test_watchlist_auto_download_settings_post_returns_updated_item(self):
body = b'{"show_id":"show-1","mode":"dub","quality":"best","name":"Example Name","series":"2","episode_offset":"13"}' body = b'{"show_id":"show-1","mode":"dub","quality":"best","name":"Example Name","source_name":"Example Source","series":"2","episode_offset":"13"}'
handler = DummyHandler("/api/watchlist/update-auto-download", body=body, content_length=len(body)) handler = DummyHandler("/api/watchlist/update-auto-download", body=body, content_length=len(body))
payload = {"message": "Saved auto-download settings for Show 1.", "item": {"show_id": "show-1"}} payload = {"message": "Saved auto-download settings for Show 1.", "item": {"show_id": "show-1"}}
handler.handler_context = mock.Mock(update_watchlist_auto_download=mock.Mock(return_value=payload)) handler.handler_context = mock.Mock(update_watchlist_auto_download=mock.Mock(return_value=payload))
@@ -3097,7 +3103,7 @@ class HandlerRouteTests(unittest.TestCase):
self.assertEqual(handler.json_payload, payload) self.assertEqual(handler.json_payload, payload)
self.assertEqual(handler.json_status, HTTPStatus.OK) self.assertEqual(handler.json_status, HTTPStatus.OK)
handler.handler_context.update_watchlist_auto_download.assert_called_once_with( handler.handler_context.update_watchlist_auto_download.assert_called_once_with(
"show-1", "dub", "best", "Example Name", "2", "13" "show-1", "dub", "best", "Example Name", "Example Source", "2", "13"
) )
def test_watchlist_media_type_post_returns_updated_item(self): def test_watchlist_media_type_post_returns_updated_item(self):
@@ -3316,6 +3322,8 @@ class TemplateHelperTests(unittest.TestCase):
self.assertIn("closeAutoDownloadOverlay()", APP.WATCHLIST_HTML) self.assertIn("closeAutoDownloadOverlay()", APP.WATCHLIST_HTML)
self.assertIn("Save auto-download", APP.WATCHLIST_HTML) self.assertIn("Save auto-download", APP.WATCHLIST_HTML)
self.assertIn("Manual add does not trigger it", APP.WATCHLIST_HTML) self.assertIn("Manual add does not trigger it", APP.WATCHLIST_HTML)
self.assertIn("Source name", APP.WATCHLIST_HTML)
self.assertIn('id="autoDownloadSourceName"', APP.WATCHLIST_HTML)
self.assertIn("Name source", APP.WATCHLIST_HTML) self.assertIn("Name source", APP.WATCHLIST_HTML)
self.assertIn('class="wide-half">Name source', APP.WATCHLIST_HTML) self.assertIn('class="wide-half">Name source', APP.WATCHLIST_HTML)
self.assertIn('class="wide-half">Name', APP.WATCHLIST_HTML) self.assertIn('class="wide-half">Name', APP.WATCHLIST_HTML)
+8
View File
@@ -833,6 +833,9 @@ WATCHLIST_HTML = r"""<!doctype html>
<label>Episode offset <label>Episode offset
<input class="auto-download-offset" id="autoDownloadOffset" type="number" min="1" step="1" placeholder="Optional"> <input class="auto-download-offset" id="autoDownloadOffset" type="number" min="1" step="1" placeholder="Optional">
</label> </label>
<label class="wide-half">Source name
<input class="auto-download-source-name" id="autoDownloadSourceName" type="text">
</label>
<label class="wide-half">Name source <label class="wide-half">Name source
<select class="auto-download-name-select" id="autoDownloadNameSelect"></select> <select class="auto-download-name-select" id="autoDownloadNameSelect"></select>
</label> </label>
@@ -887,6 +890,7 @@ WATCHLIST_HTML = r"""<!doctype html>
const autoDownloadQuality = document.getElementById("autoDownloadQuality"); const autoDownloadQuality = document.getElementById("autoDownloadQuality");
const autoDownloadSeries = document.getElementById("autoDownloadSeries"); const autoDownloadSeries = document.getElementById("autoDownloadSeries");
const autoDownloadOffset = document.getElementById("autoDownloadOffset"); const autoDownloadOffset = document.getElementById("autoDownloadOffset");
const autoDownloadSourceName = document.getElementById("autoDownloadSourceName");
const autoDownloadNameSelect = document.getElementById("autoDownloadNameSelect"); const autoDownloadNameSelect = document.getElementById("autoDownloadNameSelect");
const autoDownloadName = document.getElementById("autoDownloadName"); const autoDownloadName = document.getElementById("autoDownloadName");
const autoDownloadSaveBtn = document.getElementById("autoDownloadSaveBtn"); const autoDownloadSaveBtn = document.getElementById("autoDownloadSaveBtn");
@@ -1088,6 +1092,7 @@ WATCHLIST_HTML = r"""<!doctype html>
autoDownloadQuality.value = item.auto_download_quality || "best"; autoDownloadQuality.value = item.auto_download_quality || "best";
autoDownloadSeries.value = item.auto_download_series || "1"; autoDownloadSeries.value = item.auto_download_series || "1";
autoDownloadOffset.value = item.auto_download_offset || ""; autoDownloadOffset.value = item.auto_download_offset || "";
autoDownloadSourceName.value = item.auto_download_source_name || item.title || "";
populateAutoDownloadNameChoices(item); populateAutoDownloadNameChoices(item);
autoDownloadOverlay.hidden = false; autoDownloadOverlay.hidden = false;
} }
@@ -1123,6 +1128,7 @@ WATCHLIST_HTML = r"""<!doctype html>
mode: autoDownloadMode.value, mode: autoDownloadMode.value,
quality: autoDownloadQuality.value, quality: autoDownloadQuality.value,
name: autoDownloadName.value, name: autoDownloadName.value,
source_name: autoDownloadSourceName.value,
series: autoDownloadSeries.value, series: autoDownloadSeries.value,
episode_offset: autoDownloadOffset.value episode_offset: autoDownloadOffset.value
}) })
@@ -1133,12 +1139,14 @@ WATCHLIST_HTML = r"""<!doctype html>
auto_download_mode: savedItem.auto_download_mode || autoDownloadMode.value, auto_download_mode: savedItem.auto_download_mode || autoDownloadMode.value,
auto_download_quality: savedItem.auto_download_quality || autoDownloadQuality.value, auto_download_quality: savedItem.auto_download_quality || autoDownloadQuality.value,
auto_download_name: savedItem.auto_download_name || autoDownloadName.value, auto_download_name: savedItem.auto_download_name || autoDownloadName.value,
auto_download_source_name: savedItem.auto_download_source_name || autoDownloadSourceName.value,
auto_download_series: savedItem.auto_download_series || autoDownloadSeries.value, auto_download_series: savedItem.auto_download_series || autoDownloadSeries.value,
auto_download_offset: savedItem.auto_download_offset || "" auto_download_offset: savedItem.auto_download_offset || ""
}; };
populateAutoDownloadNameChoices(state.autoDownloadDraft); populateAutoDownloadNameChoices(state.autoDownloadDraft);
autoDownloadSeries.value = state.autoDownloadDraft.auto_download_series || autoDownloadSeries.value; autoDownloadSeries.value = state.autoDownloadDraft.auto_download_series || autoDownloadSeries.value;
autoDownloadOffset.value = state.autoDownloadDraft.auto_download_offset || ""; autoDownloadOffset.value = state.autoDownloadDraft.auto_download_offset || "";
autoDownloadSourceName.value = state.autoDownloadDraft.auto_download_source_name || autoDownloadSourceName.value;
autoDownloadStatus.textContent = data.message || "Auto-download settings saved."; autoDownloadStatus.textContent = data.message || "Auto-download settings saved.";
setNotice(data.message || "Auto-download settings saved."); setNotice(data.message || "Auto-download settings saved.");
closeAutoDownloadOverlay(); closeAutoDownloadOverlay();