From 6a807df89e0e742995a567baa6a56fd1e486adab Mon Sep 17 00:00:00 2001 From: u61d Date: Sat, 13 Jun 2026 03:44:27 +0200 Subject: [PATCH] feat(sonarr): monitor new seasons when latest is requested --- server/lib/settings/index.ts | 2 +- server/subscriber/MediaRequestSubscriber.ts | 11 ++- server/utils/sonarr.test.ts | 71 +++++++++++++++++++ server/utils/sonarr.ts | 25 +++++++ src/components/Settings/SonarrModal/index.tsx | 15 +++- src/i18n/locale/en.json | 4 +- 6 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 server/utils/sonarr.test.ts create mode 100644 server/utils/sonarr.ts diff --git a/server/lib/settings/index.ts b/server/lib/settings/index.ts index 010c43d81d..a26d63c466 100644 --- a/server/lib/settings/index.ts +++ b/server/lib/settings/index.ts @@ -100,7 +100,7 @@ export interface SonarrSettings extends DVRSettings { activeLanguageProfileId?: number; animeTags?: number[]; enableSeasonFolders: boolean; - monitorNewItems: 'all' | 'none'; + monitorNewItems: 'all' | 'none' | 'latest'; } interface Quota { diff --git a/server/subscriber/MediaRequestSubscriber.ts b/server/subscriber/MediaRequestSubscriber.ts index a74c539b09..80133a4eac 100644 --- a/server/subscriber/MediaRequestSubscriber.ts +++ b/server/subscriber/MediaRequestSubscriber.ts @@ -20,6 +20,7 @@ import SeasonRequest from '@server/entity/SeasonRequest'; import notificationManager, { Notification } from '@server/lib/notifications'; import { getSettings } from '@server/lib/settings'; import logger from '@server/logger'; +import { resolveMonitorNewItems } from '@server/utils/sonarr'; import { isEqual, truncate } from 'lodash'; import type { EntityManager, @@ -711,7 +712,15 @@ export class MediaRequestSubscriber implements EntitySubscriberInterface season.seasonNumber + ), + availableSeasons: series.seasons.map( + (season) => season.season_number + ), + }), searchNow: !sonarrSettings.preventSearch, }; diff --git a/server/utils/sonarr.test.ts b/server/utils/sonarr.test.ts new file mode 100644 index 0000000000..644069ef63 --- /dev/null +++ b/server/utils/sonarr.test.ts @@ -0,0 +1,71 @@ +import { resolveMonitorNewItems } from '@server/utils/sonarr'; +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; + +describe('resolveMonitorNewItems', () => { + it('preserves the all setting', () => { + assert.equal( + resolveMonitorNewItems({ + setting: 'all', + requestedSeasons: [1], + availableSeasons: [1, 2, 3], + }), + 'all' + ); + }); + + it('preserves the none setting', () => { + assert.equal( + resolveMonitorNewItems({ + setting: 'none', + requestedSeasons: [3], + availableSeasons: [1, 2, 3], + }), + 'none' + ); + }); + + it('does not monitor new seasons when the latest season is not requested', () => { + assert.equal( + resolveMonitorNewItems({ + setting: 'latest', + requestedSeasons: [1, 2], + availableSeasons: [0, 1, 2, 3], + }), + 'none' + ); + }); + + it('monitors new seasons when the latest season is requested', () => { + assert.equal( + resolveMonitorNewItems({ + setting: 'latest', + requestedSeasons: [1, 3], + availableSeasons: [0, 1, 2, 3], + }), + 'all' + ); + }); + + it('does not treat specials as the latest season', () => { + assert.equal( + resolveMonitorNewItems({ + setting: 'latest', + requestedSeasons: [0], + availableSeasons: [0, 1], + }), + 'none' + ); + }); + + it('does not monitor new seasons when no regular seasons are available', () => { + assert.equal( + resolveMonitorNewItems({ + setting: 'latest', + requestedSeasons: [0], + availableSeasons: [0], + }), + 'none' + ); + }); +}); diff --git a/server/utils/sonarr.ts b/server/utils/sonarr.ts new file mode 100644 index 0000000000..baeef1dbf4 --- /dev/null +++ b/server/utils/sonarr.ts @@ -0,0 +1,25 @@ +import type { SonarrSeries } from '@server/api/servarr/sonarr'; +import type { SonarrSettings } from '@server/lib/settings'; + +export const resolveMonitorNewItems = ({ + setting, + requestedSeasons, + availableSeasons, +}: { + setting: SonarrSettings['monitorNewItems']; + requestedSeasons: number[]; + availableSeasons: number[]; +}): SonarrSeries['monitorNewItems'] => { + if (setting !== 'latest') { + return setting; + } + + const latestSeason = Math.max( + ...availableSeasons.filter((seasonNumber) => seasonNumber > 0) + ); + + return Number.isFinite(latestSeason) && + requestedSeasons.includes(latestSeason) + ? 'all' + : 'none'; +}; diff --git a/src/components/Settings/SonarrModal/index.tsx b/src/components/Settings/SonarrModal/index.tsx index fbc6652488..3519a209ca 100644 --- a/src/components/Settings/SonarrModal/index.tsx +++ b/src/components/Settings/SonarrModal/index.tsx @@ -82,7 +82,9 @@ const messages = defineMessages('components.Settings.SonarrModal', { selecttags: 'Select tags', monitorNewItems: 'Monitor New Seasons', monitorNewItemsHelp: - 'Whether Sonarr should monitor (All) or not (None) new seasons when a series is added.', + 'Choose when Sonarr should monitor new seasons after a series is added.', + monitorNewItemsNone: 'None', + monitorNewItemsLatest: 'Latest Season Requested', apiKeyHelp: 'Find it in Sonarr: Settings > General > Security > API Key', baseUrlHelp: 'If you set a URL Base in Sonarr (Settings > General > Host), enter it here (e.g. /sonarr). Leave blank otherwise.', @@ -1026,8 +1028,15 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => { name="monitorNewItems" disabled={!isValidated || isTesting} > - - + + + diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index ba57d83b99..a46ea716e9 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -1115,7 +1115,9 @@ "components.Settings.SonarrModal.loadingprofiles": "Loading quality profiles…", "components.Settings.SonarrModal.loadingrootfolders": "Loading root folders…", "components.Settings.SonarrModal.monitorNewItems": "Monitor New Seasons", - "components.Settings.SonarrModal.monitorNewItemsHelp": "Whether Sonarr should monitor (All) or not (None) new seasons when a series is added.", + "components.Settings.SonarrModal.monitorNewItemsHelp": "Choose when Sonarr should monitor new seasons after a series is added.", + "components.Settings.SonarrModal.monitorNewItemsLatest": "Latest Season Requested", + "components.Settings.SonarrModal.monitorNewItemsNone": "None", "components.Settings.SonarrModal.notagoptions": "No tags.", "components.Settings.SonarrModal.port": "Port", "components.Settings.SonarrModal.qualityprofile": "Quality Profile",