From 81756addf8b8e1f9f9f442b14cfaea7427070128 Mon Sep 17 00:00:00 2001 From: MiTHRAL Date: Fri, 15 May 2026 15:53:36 -0400 Subject: [PATCH] Add media library reset action --- README.md | 2 +- dashboard.html | 17 +++++++++++++++++ status_bot.py | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6f118e5..d5c420f 100644 --- a/README.md +++ b/README.md @@ -204,7 +204,7 @@ Discord publishing uses one message with an attached `media-catalog.md` file so The dashboard also serves a read-only catalog page at `/catalog`. Set the public reverse-proxy URL for that page in the `Media` tab’s `Catalog URL` field, or with `PUBLIC_CATALOG_URL`, and Discord posts will include an `Open Catalog` button. -In Jellyfin, create an API key from the admin dashboard, then enter the Jellyfin URL and key in the `Media` tab. If you have duplicate free/premium libraries, enter only the premium Jellyfin library names in the `Libraries` field, separated by commas. `Sync now` replaces the editable library with the current Jellyfin movies and shows while comparing against the previously saved library. `Auto-sync changes` checks Jellyfin periodically and republishes only when the catalog fingerprint changes. Jellyfin results are deduplicated across included libraries using provider IDs first, then normalized title and year. +In Jellyfin, create an API key from the admin dashboard, then enter the Jellyfin URL and key in the `Media` tab. If you have duplicate free/premium libraries, enter only the premium Jellyfin library names in the `Libraries` field, separated by commas. `Reset library` clears the saved media list and sync fingerprints. `Sync now` replaces the editable library with the current Jellyfin movies and shows while comparing against the previously saved library. `Auto-sync changes` checks Jellyfin periodically and republishes only when the catalog fingerprint changes. Jellyfin results are deduplicated across included libraries using provider IDs first, then normalized title and year. Channel selections are stored in: diff --git a/dashboard.html b/dashboard.html index f33c014..d101e0f 100644 --- a/dashboard.html +++ b/dashboard.html @@ -577,6 +577,7 @@
+
@@ -924,6 +925,18 @@ return payload; } + async function resetMediaLibrary() { + setJellyfinMessage("Resetting saved media library..."); + const payload = await api("/api/media/reset", { method: "POST", body: "{}" }); + mediaLibrary = normalizeMediaLibrary(payload.library); + renderMediaLibrary(); + renderMediaChanges(payload.changes); + document.querySelector("#mediaMovieCount").textContent = payload.movieCount; + document.querySelector("#mediaShowCount").textContent = payload.showCount; + renderJellyfinStatus(payload); + setJellyfinMessage("Saved media library reset. Run Sync now to rebuild it from the selected Jellyfin libraries."); + } + async function syncJellyfin(forcePublish = false) { setJellyfinMessage(forcePublish ? "Syncing and publishing..." : "Syncing Jellyfin..."); await saveChannelSettings(); @@ -1199,6 +1212,10 @@ saveJellyfinSettings().catch((error) => setJellyfinMessage(error.message, true)); }); + document.querySelector("#resetMediaLibrary").addEventListener("click", () => { + resetMediaLibrary().catch((error) => setJellyfinMessage(error.message, true)); + }); + document.querySelector("#syncJellyfin").addEventListener("click", () => { syncJellyfin(false).catch((error) => setJellyfinMessage(error.message, true)); }); diff --git a/status_bot.py b/status_bot.py index 725a312..b0eb176 100644 --- a/status_bot.py +++ b/status_bot.py @@ -807,6 +807,29 @@ def save_media_library(runtime: BotRuntime, movies: list[MediaItem], shows: list return payload +def reset_media_library(runtime: BotRuntime) -> dict[str, Any]: + save_media_library(runtime, [], []) + state = load_state(runtime.settings_path) + for key in ( + "jellyfin_last_sync_at", + "jellyfin_last_sync_error", + "jellyfin_last_published_at", + "jellyfin_last_fingerprint", + "jellyfin_last_published_fingerprint", + "jellyfin_last_changes", + ): + state.pop(key, None) + state["updated_at"] = datetime.now(timezone.utc).isoformat() + save_state(runtime.settings_path, state) + return { + "library": media_library_to_jsonable([], []), + "movieCount": 0, + "showCount": 0, + "changes": {"added": [], "removed": [], "addedCount": 0, "removedCount": 0}, + "jellyfin": jellyfin_settings(runtime), + } + + def jellyfin_runtime(runtime_ticks: Any) -> str | None: try: ticks = int(runtime_ticks or 0) @@ -1824,6 +1847,9 @@ def make_dashboard_handler(runtime: BotRuntime, auth: DashboardAuth | None) -> t if path == "/api/media/library": self.handle_media_library() return + if path == "/api/media/reset": + self.handle_media_reset() + return if path == "/api/settings": self.handle_settings() return @@ -2061,6 +2087,15 @@ def make_dashboard_handler(runtime: BotRuntime, auth: DashboardAuth | None) -> t }, ) + def handle_media_reset(self) -> None: + try: + result = reset_media_library(runtime) + except Exception as exc: + self.send_json(HTTPStatus.BAD_REQUEST, {"error": str(exc)}) + return + + self.send_json(HTTPStatus.OK, result) + def handle_settings(self) -> None: try: data = self.read_json()