From bc7c95af08aeeb0cda1b4b3aeacc248b78bc500f Mon Sep 17 00:00:00 2001 From: Thomas Gander Date: Sat, 14 Feb 2026 16:01:06 -0700 Subject: [PATCH 01/20] Added basic state with selections --- Jellyfin.Plugin.MediaCleaner/Pages/home.js | 30 +++++++++++++++++----- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/Jellyfin.Plugin.MediaCleaner/Pages/home.js b/Jellyfin.Plugin.MediaCleaner/Pages/home.js index d082522..8ab79f6 100644 --- a/Jellyfin.Plugin.MediaCleaner/Pages/home.js +++ b/Jellyfin.Plugin.MediaCleaner/Pages/home.js @@ -62,6 +62,28 @@ const getMediaCleanerMoviesTitle = async () => { return response.json(); }; +const selectedMovies = new Set(); +const selectedTvShows = new Set(); + +const createCheckbox = (mediaInfo = {}, state = []) => { + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.dataset.mediaInfo = JSON.stringify(mediaInfo) || ''; + + checkbox.addEventListener('change', (e) => { + const mediaInfo = checkbox.dataset.mediaInfo || '(no info)'; + if (checkbox.checked) { + state.add(mediaInfo); + } else { + state.delete(mediaInfo); + } + // Update UI or state — use console.log for debugging + console.log('selected:', Array.from(state)); + }); + + return checkbox; +}; + const populateTables = async () => { var moviesInfo = await getMediaCleanerMovieInfo(); var seriesInfo = await getMediaCleanerSeriesInfo(); @@ -78,9 +100,7 @@ const populateTables = async () => { var cell1 = row.insertCell(0); var cell2 = row.insertCell(1); cell1.innerHTML = moviesInfo[i].Name; - // Will need to be enabled once radarr and sonarr integration is enabled. - // Maybe change this to an element to remove hard coding. - cell2.innerHTML = ""; + cell2.appendChild(createCheckbox(moviesInfo[i], selectedMovies)); cell2.className = "actions-cell"; } } @@ -100,9 +120,7 @@ const populateTables = async () => { var cell3 = row.insertCell(2); cell1.innerHTML = seriesInfo[i].Name; cell2.innerHTML = seriesInfo[i].Seasons.map(season => season.replace("Season ", "")).join(", "); - // Will need to be enabled once radarr and sonarr integration is enabled. - // Maybe change this to an element to remove hard coding. - cell3.innerHTML = ""; + cell3.appendChild(createCheckbox(seriesInfo[i], selectedTvShows)); cell3.className = "actions-cell"; } } From 61e868bfa2292014b63f91788879c171fa9ae73c Mon Sep 17 00:00:00 2001 From: Thomas Gander Date: Sat, 14 Feb 2026 16:18:11 -0700 Subject: [PATCH 02/20] Added settings for radarr and sonarr addresses --- .../Configuration/PluginConfiguration.cs | 20 +++++++++++++ .../Configuration/settings.html | 28 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/Jellyfin.Plugin.MediaCleaner/Configuration/PluginConfiguration.cs b/Jellyfin.Plugin.MediaCleaner/Configuration/PluginConfiguration.cs index 572175f..2138eb1 100644 --- a/Jellyfin.Plugin.MediaCleaner/Configuration/PluginConfiguration.cs +++ b/Jellyfin.Plugin.MediaCleaner/Configuration/PluginConfiguration.cs @@ -17,11 +17,31 @@ public class PluginConfiguration : BasePluginConfiguration { } + /// + /// Gets or sets the http address for your Radarr instance. + /// + public string RadarrHTTPAddress { get; set; } = string.Empty; + + /// + /// Gets or sets the port for your Radarr instance. + /// + public string RadarrPort { get; set; } = string.Empty; + /// /// Gets or sets the api for your Radarr instance. /// public string RadarrAPIKey { get; set; } = string.Empty; + /// + /// Gets or sets the http address for your Sonarr instance. + /// + public string SonarrHTTPAddress { get; set; } = string.Empty; + + /// + /// Gets or sets the port for your Sonarr instance. + /// + public string SonarrPort { get; set; } = string.Empty; + /// /// Gets or sets the api for your Sonarr instance. /// diff --git a/Jellyfin.Plugin.MediaCleaner/Configuration/settings.html b/Jellyfin.Plugin.MediaCleaner/Configuration/settings.html index aa5229d..27aa260 100644 --- a/Jellyfin.Plugin.MediaCleaner/Configuration/settings.html +++ b/Jellyfin.Plugin.MediaCleaner/Configuration/settings.html @@ -9,11 +9,31 @@
+
+ + +
The http address of your radarr instance.
+
+
+ + +
The port of your radarr instance.
+
The api key used by your radarr instance
+
+ + +
The http address of your sonarr instance.
+
+
+ + +
The port of your sonarr instance.
+
@@ -74,7 +94,11 @@ // document.querySelector('#AnInteger').value = config.AnInteger; // document.querySelector('#TrueFalseSetting').checked = config.TrueFalseSetting; // document.querySelector('#AString').value = config.AString; + document.querySelector('#RadarrHTTPAddress').value = config.RadarrHTTPAddress; + document.querySelector('#RadarrPort').value = config.RadarrPort; document.querySelector('#RadarrAPIKey').value = config.RadarrAPIKey; + document.querySelector('#SonarrHTTPAddress').value = config.SonarrHTTPAddress; + document.querySelector('#SonarrPort').value = config.SonarrPort; document.querySelector('#SonarrAPIKey').value = config.SonarrAPIKey; document.querySelector('#StaleMediaCutoff').value = config.StaleMediaCutoff; document.querySelector('#DebugMode').checked = config.DebugMode; @@ -90,7 +114,11 @@ // config.AnInteger = document.querySelector('#AnInteger').value; // config.TrueFalseSetting = document.querySelector('#TrueFalseSetting').checked; // config.AString = document.querySelector('#AString').value; + config.RadarrHTTPAddress = document.querySelector('#RadarrHTTPAddress').value; + config.RadarrPort = document.querySelector('#RadarrPort').value; config.RadarrAPIKey = document.querySelector('#RadarrAPIKey').value; + config.SonarrHTTPAddress = document.querySelector('#SonarrHTTPAddress').value; + config.SonarrPort = document.querySelector('#SonarrPort').value; config.SonarrAPIKey = document.querySelector('#SonarrAPIKey').value; config.StaleMediaCutoff = document.querySelector('#StaleMediaCutoff').value; config.DebugMode = document.querySelector('#DebugMode').checked; From a720bba7a7733e248853e8fa191b323f329b8a63 Mon Sep 17 00:00:00 2001 From: Thomas Gander Date: Mon, 16 Feb 2026 14:08:15 -0700 Subject: [PATCH 03/20] Refactor of plugin structure to make more sense compared to the default template --- ...luginConfiguration.cs => Configuration.cs} | 11 +- .../Configuration/settings.html | 136 ------------------ .../Controllers/StateController.cs | 3 +- .../Helpers/LoggingHelper.cs | 9 +- .../Helpers/MovieHelper.cs | 4 +- .../Helpers/SeriesHelper.cs | 5 +- .../Jellyfin.Plugin.MediaCleaner.csproj | 2 - .../Pages/configuration.html | 55 +++++++ .../Pages/configuration.js | 40 ++++++ Jellyfin.Plugin.MediaCleaner/Pages/home.html | 2 +- Jellyfin.Plugin.MediaCleaner/Plugin.cs | 15 +- .../StaleMediaScanner.cs | 13 -- 12 files changed, 110 insertions(+), 185 deletions(-) rename Jellyfin.Plugin.MediaCleaner/{Configuration/PluginConfiguration.cs => Configuration.cs} (83%) delete mode 100644 Jellyfin.Plugin.MediaCleaner/Configuration/settings.html create mode 100644 Jellyfin.Plugin.MediaCleaner/Pages/configuration.html create mode 100644 Jellyfin.Plugin.MediaCleaner/Pages/configuration.js diff --git a/Jellyfin.Plugin.MediaCleaner/Configuration/PluginConfiguration.cs b/Jellyfin.Plugin.MediaCleaner/Configuration.cs similarity index 83% rename from Jellyfin.Plugin.MediaCleaner/Configuration/PluginConfiguration.cs rename to Jellyfin.Plugin.MediaCleaner/Configuration.cs index 2138eb1..dd7dadb 100644 --- a/Jellyfin.Plugin.MediaCleaner/Configuration/PluginConfiguration.cs +++ b/Jellyfin.Plugin.MediaCleaner/Configuration.cs @@ -3,20 +3,13 @@ using System.Collections.ObjectModel; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Plugins; -namespace Jellyfin.Plugin.MediaCleaner.Configuration; +namespace Jellyfin.Plugin.MediaCleaner; /// /// Plugin configuration. /// -public class PluginConfiguration : BasePluginConfiguration +public class Configuration : BasePluginConfiguration { - /// - /// Initializes a new instance of the class. - /// - public PluginConfiguration() - { - } - /// /// Gets or sets the http address for your Radarr instance. /// diff --git a/Jellyfin.Plugin.MediaCleaner/Configuration/settings.html b/Jellyfin.Plugin.MediaCleaner/Configuration/settings.html deleted file mode 100644 index 27aa260..0000000 --- a/Jellyfin.Plugin.MediaCleaner/Configuration/settings.html +++ /dev/null @@ -1,136 +0,0 @@ - - - - - Media Cleaner - - -
-
-
- -
- - -
The http address of your radarr instance.
-
-
- - -
The port of your radarr instance.
-
-
- - -
The api key used by your radarr instance
-
-
- - -
The http address of your sonarr instance.
-
-
- - -
The port of your sonarr instance.
-
-
- - -
The api key used by your sonarr instance
-
-
- - -
How many days to wait before marking files as stale
-
-
- -
- -
- -
- -
-
- -
- - diff --git a/Jellyfin.Plugin.MediaCleaner/Controllers/StateController.cs b/Jellyfin.Plugin.MediaCleaner/Controllers/StateController.cs index 5e3833b..589fbed 100644 --- a/Jellyfin.Plugin.MediaCleaner/Controllers/StateController.cs +++ b/Jellyfin.Plugin.MediaCleaner/Controllers/StateController.cs @@ -2,7 +2,6 @@ using Jellyfin.Plugin.MediaCleaner.Data; using Jellyfin.Plugin.MediaCleaner; using Jellyfin.Plugin.MediaCleaner.Models; using Microsoft.AspNetCore.Mvc; -using Jellyfin.Plugin.MediaCleaner.Configuration; namespace Jellyfin.Plugin.MediaCleaner.Controllers; @@ -10,7 +9,7 @@ namespace Jellyfin.Plugin.MediaCleaner.Controllers; public class StateController(MediaCleanerState state) : Controller { private readonly MediaCleanerState _state = state; - private static PluginConfiguration Configuration => + private static Configuration Configuration => Plugin.Instance!.Configuration; [HttpGet("getSeriesInfo")] diff --git a/Jellyfin.Plugin.MediaCleaner/Helpers/LoggingHelper.cs b/Jellyfin.Plugin.MediaCleaner/Helpers/LoggingHelper.cs index c78aadf..96787cc 100644 --- a/Jellyfin.Plugin.MediaCleaner/Helpers/LoggingHelper.cs +++ b/Jellyfin.Plugin.MediaCleaner/Helpers/LoggingHelper.cs @@ -1,12 +1,5 @@ using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using Jellyfin.Plugin.MediaCleaner.Configuration; -using Jellyfin.Plugin.MediaCleaner.Models; -using Jellyfin.Plugin.MediaCleaner.ScheduledTasks; -using MediaBrowser.Controller.Entities; using Microsoft.Extensions.Logging; namespace Jellyfin.Plugin.MediaCleaner.Helpers; @@ -30,6 +23,6 @@ public class LoggingHelper(ILogger logger) _logger.LogInformation(message, args); } - private static PluginConfiguration Configuration => + private static Configuration Configuration => Plugin.Instance!.Configuration; } diff --git a/Jellyfin.Plugin.MediaCleaner/Helpers/MovieHelper.cs b/Jellyfin.Plugin.MediaCleaner/Helpers/MovieHelper.cs index 86a9c06..9185ffb 100644 --- a/Jellyfin.Plugin.MediaCleaner/Helpers/MovieHelper.cs +++ b/Jellyfin.Plugin.MediaCleaner/Helpers/MovieHelper.cs @@ -1,8 +1,6 @@ using System; using System.Linq; -using System.Threading; using Jellyfin.Database.Implementations.Entities; -using Jellyfin.Plugin.MediaCleaner.Configuration; using MediaBrowser.Controller.Entities; using Microsoft.Extensions.Logging; @@ -12,7 +10,7 @@ public class MovieHelper(ILogger logger) { private readonly LoggingHelper _loggingHelper = new(logger); - private static PluginConfiguration Configuration => + private static Configuration Configuration => Plugin.Instance!.Configuration; public bool IsMovieStale(BaseItem movie) diff --git a/Jellyfin.Plugin.MediaCleaner/Helpers/SeriesHelper.cs b/Jellyfin.Plugin.MediaCleaner/Helpers/SeriesHelper.cs index cbd8c3d..07b7afa 100644 --- a/Jellyfin.Plugin.MediaCleaner/Helpers/SeriesHelper.cs +++ b/Jellyfin.Plugin.MediaCleaner/Helpers/SeriesHelper.cs @@ -1,10 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; using Jellyfin.Database.Implementations.Entities; -using Jellyfin.Database.Implementations.Entities.Libraries; -using Jellyfin.Plugin.MediaCleaner.Configuration; using MediaBrowser.Controller.Entities; using Microsoft.Extensions.Logging; @@ -15,7 +12,7 @@ public class SeriesHelper(ILogger logger) { private readonly LoggingHelper _loggingHelper = new(logger); - private static PluginConfiguration Configuration => + private static Configuration Configuration => Plugin.Instance!.Configuration; private List ProcessEpisodes(IReadOnlyCollection episodes) diff --git a/Jellyfin.Plugin.MediaCleaner/Jellyfin.Plugin.MediaCleaner.csproj b/Jellyfin.Plugin.MediaCleaner/Jellyfin.Plugin.MediaCleaner.csproj index 68b496e..81ec1ec 100644 --- a/Jellyfin.Plugin.MediaCleaner/Jellyfin.Plugin.MediaCleaner.csproj +++ b/Jellyfin.Plugin.MediaCleaner/Jellyfin.Plugin.MediaCleaner.csproj @@ -20,8 +20,6 @@ - - diff --git a/Jellyfin.Plugin.MediaCleaner/Pages/configuration.html b/Jellyfin.Plugin.MediaCleaner/Pages/configuration.html new file mode 100644 index 0000000..335b48e --- /dev/null +++ b/Jellyfin.Plugin.MediaCleaner/Pages/configuration.html @@ -0,0 +1,55 @@ +
+
+
+
+

Media Cleaner Configuration

+
+ + +
The http address of your radarr instance.
+
+
+ + +
The port of your radarr instance.
+
+
+ + +
The api key used by your radarr instance
+
+
+ + +
The http address of your sonarr instance.
+
+
+ + +
The port of your sonarr instance.
+
+
+ + +
The api key used by your sonarr instance
+
+
+ + +
How many days to wait before marking files as stale
+
+
+ +
+
+ +
+
+
+
+
diff --git a/Jellyfin.Plugin.MediaCleaner/Pages/configuration.js b/Jellyfin.Plugin.MediaCleaner/Pages/configuration.js new file mode 100644 index 0000000..7e70fcc --- /dev/null +++ b/Jellyfin.Plugin.MediaCleaner/Pages/configuration.js @@ -0,0 +1,40 @@ +var MediaCleanerConfig = { + pluginUniqueId: 'fef007a8-3e8f-4aa8-a22e-486a387f4192' +}; + +document.querySelector('#MediaCleanerConfigPage') + .addEventListener('pageshow', function() { + Dashboard.showLoadingMsg(); + ApiClient.getPluginConfiguration(MediaCleanerConfig.pluginUniqueId).then(function (config) { + document.querySelector('#RadarrHTTPAddress').value = config.RadarrHTTPAddress; + document.querySelector('#RadarrPort').value = config.RadarrPort; + document.querySelector('#RadarrAPIKey').value = config.RadarrAPIKey; + document.querySelector('#SonarrHTTPAddress').value = config.SonarrHTTPAddress; + document.querySelector('#SonarrPort').value = config.SonarrPort; + document.querySelector('#SonarrAPIKey').value = config.SonarrAPIKey; + document.querySelector('#StaleMediaCutoff').value = config.StaleMediaCutoff; + document.querySelector('#DebugMode').checked = config.DebugMode; + Dashboard.hideLoadingMsg(); + }); + }); + +document.querySelector('#MediaCleanerConfigForm') + .addEventListener('submit', function(e) { + Dashboard.showLoadingMsg(); + ApiClient.getPluginConfiguration(MediaCleanerConfig.pluginUniqueId).then(function (config) { + config.RadarrHTTPAddress = document.querySelector('#RadarrHTTPAddress').value; + config.RadarrPort = document.querySelector('#RadarrPort').value; + config.RadarrAPIKey = document.querySelector('#RadarrAPIKey').value; + config.SonarrHTTPAddress = document.querySelector('#SonarrHTTPAddress').value; + config.SonarrPort = document.querySelector('#SonarrPort').value; + config.SonarrAPIKey = document.querySelector('#SonarrAPIKey').value; + config.StaleMediaCutoff = document.querySelector('#StaleMediaCutoff').value; + config.DebugMode = document.querySelector('#DebugMode').checked; + ApiClient.updatePluginConfiguration(MediaCleanerConfig.pluginUniqueId, config).then(function (result) { + Dashboard.processPluginConfigurationUpdateResult(result); + }); + }); + + e.preventDefault(); + return false; +}); diff --git a/Jellyfin.Plugin.MediaCleaner/Pages/home.html b/Jellyfin.Plugin.MediaCleaner/Pages/home.html index e5a58a9..1a614a6 100644 --- a/Jellyfin.Plugin.MediaCleaner/Pages/home.html +++ b/Jellyfin.Plugin.MediaCleaner/Pages/home.html @@ -5,7 +5,7 @@
Loading...