Add media library reset action

This commit is contained in:
MiTHRAL 2026-05-15 15:53:36 -04:00
parent 224257e211
commit 81756addf8
3 changed files with 53 additions and 1 deletions

View file

@ -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` tabs `Catalog URL` field, or with `PUBLIC_CATALOG_URL`, and Discord posts will include an `Open Catalog` button. The dashboard also serves a read-only catalog page at `/catalog`. Set the public reverse-proxy URL for that page in the `Media` tabs `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: Channel selections are stored in:

View file

@ -577,6 +577,7 @@
</div> </div>
<div class="media-toolbar"> <div class="media-toolbar">
<button id="saveJellyfin" type="button">Save Jellyfin</button> <button id="saveJellyfin" type="button">Save Jellyfin</button>
<button id="resetMediaLibrary" class="danger" type="button">Reset library</button>
<button id="syncJellyfin" type="button">Sync now</button> <button id="syncJellyfin" type="button">Sync now</button>
<button id="forceJellyfinPublish" type="button">Force publish</button> <button id="forceJellyfinPublish" type="button">Force publish</button>
</div> </div>
@ -924,6 +925,18 @@
return payload; 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) { async function syncJellyfin(forcePublish = false) {
setJellyfinMessage(forcePublish ? "Syncing and publishing..." : "Syncing Jellyfin..."); setJellyfinMessage(forcePublish ? "Syncing and publishing..." : "Syncing Jellyfin...");
await saveChannelSettings(); await saveChannelSettings();
@ -1199,6 +1212,10 @@
saveJellyfinSettings().catch((error) => setJellyfinMessage(error.message, true)); saveJellyfinSettings().catch((error) => setJellyfinMessage(error.message, true));
}); });
document.querySelector("#resetMediaLibrary").addEventListener("click", () => {
resetMediaLibrary().catch((error) => setJellyfinMessage(error.message, true));
});
document.querySelector("#syncJellyfin").addEventListener("click", () => { document.querySelector("#syncJellyfin").addEventListener("click", () => {
syncJellyfin(false).catch((error) => setJellyfinMessage(error.message, true)); syncJellyfin(false).catch((error) => setJellyfinMessage(error.message, true));
}); });

View file

@ -807,6 +807,29 @@ def save_media_library(runtime: BotRuntime, movies: list[MediaItem], shows: list
return payload 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: def jellyfin_runtime(runtime_ticks: Any) -> str | None:
try: try:
ticks = int(runtime_ticks or 0) 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": if path == "/api/media/library":
self.handle_media_library() self.handle_media_library()
return return
if path == "/api/media/reset":
self.handle_media_reset()
return
if path == "/api/settings": if path == "/api/settings":
self.handle_settings() self.handle_settings()
return 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: def handle_settings(self) -> None:
try: try:
data = self.read_json() data = self.read_json()