Skip to content

Commit 07ccea1

Browse files
committed
feat: remove unmonitored Shows & new setting (#695)
1 parent 7e1bb7c commit 07ccea1

File tree

12 files changed

+80
-2
lines changed

12 files changed

+80
-2
lines changed

cypress/config/settings.cypress.json

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"trustProxy": false,
2222
"mediaServerType": 1,
2323
"partialRequestsEnabled": true,
24+
"removeUnmonitoredFromRequestsEnabled": false,
2425
"locale": "en"
2526
},
2627
"plex": {

overseerr-api.yml

+3
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@ components:
174174
partialRequestsEnabled:
175175
type: boolean
176176
example: false
177+
removeUnmonitoredFromRequestsEnabled:
178+
type: boolean
179+
example: false
177180
localLogin:
178181
type: boolean
179182
example: true

server/interfaces/api/settingsInterfaces.ts

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export interface PublicSettingsResponse {
3636
originalLanguage: string;
3737
mediaServerType: number;
3838
partialRequestsEnabled: boolean;
39+
removeUnmonitoredFromRequestsEnabled: boolean;
3940
cacheImages: boolean;
4041
vapidPublic: string;
4142
enablePushRegistration: boolean;

server/lib/scanners/baseScanner.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export interface ProcessableSeason {
4848
episodes4k: number;
4949
is4kOverride?: boolean;
5050
processing?: boolean;
51+
monitored?: boolean;
5152
}
5253

5354
class BaseScanner<T> {
@@ -234,6 +235,7 @@ class BaseScanner<T> {
234235
}: ProcessOptions = {}
235236
): Promise<void> {
236237
const mediaRepository = getRepository(Media);
238+
const settings = getSettings();
237239

238240
await this.asyncLock.dispatch(tmdbId, async () => {
239241
const media = await this.getExisting(tmdbId, MediaType.TV);
@@ -283,6 +285,8 @@ class BaseScanner<T> {
283285
? MediaStatus.PARTIALLY_AVAILABLE
284286
: !season.is4kOverride && season.processing
285287
? MediaStatus.PROCESSING
288+
: settings.main.removeUnmonitoredFromRequestsEnabled && !season.monitored && season.episodes == 0
289+
? MediaStatus.UNKNOWN
286290
: existingSeason.status;
287291

288292
// Same thing here, except we only do updates if 4k is enabled
@@ -296,6 +300,8 @@ class BaseScanner<T> {
296300
? MediaStatus.PARTIALLY_AVAILABLE
297301
: season.is4kOverride && season.processing
298302
? MediaStatus.PROCESSING
303+
: settings.main.removeUnmonitoredFromRequestsEnabled && !season.monitored && season.episodes4k == 0
304+
? MediaStatus.UNKNOWN
299305
: existingSeason.status4k;
300306
} else {
301307
newSeasons.push(
@@ -623,7 +629,6 @@ class BaseScanner<T> {
623629
const mediaRepository = getRepository(Media);
624630
await this.asyncLock.dispatch(tmdbId, async () => {
625631
const existing = await this.getExisting(tmdbId, MediaType.MOVIE);
626-
// For some reason the status of missing movies isn't PENDING but PROCESSING
627632
if (existing && existing.status === MediaStatus.PROCESSING) {
628633
existing.status = MediaStatus.UNKNOWN;
629634
await mediaRepository.save(existing);

server/lib/scanners/radarr/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ class RadarrScanner
7979
}
8080

8181
private async processRadarrMovie(radarrMovie: RadarrMovie): Promise<void> {
82-
if (!radarrMovie.monitored && !radarrMovie.hasFile) {
82+
const settings = getSettings();
83+
if (settings.main.removeUnmonitoredFromRequestsEnabled && !radarrMovie.monitored && !radarrMovie.hasFile) {
8384
this.processUnmonitoredMovie(radarrMovie.tmdbId);
8485
return;
8586
}

server/lib/scanners/sonarr/index.ts

+33
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import type { SonarrSeries } from '@server/api/servarr/sonarr';
22
import SonarrAPI from '@server/api/servarr/sonarr';
33
import type { TmdbTvDetails } from '@server/api/themoviedb/interfaces';
4+
import { MediaType } from '@server/constants/media';
45
import { getRepository } from '@server/datasource';
56
import Media from '@server/entity/Media';
7+
import { MediaRequest } from '@server/entity/MediaRequest';
8+
import SeasonRequest from '@server/entity/SeasonRequest';
69
import type {
710
ProcessableSeason,
811
RunnableScanner,
@@ -85,6 +88,7 @@ class SonarrScanner
8588
private async processSonarrSeries(sonarrSeries: SonarrSeries) {
8689
try {
8790
const mediaRepository = getRepository(Media);
91+
const settings = getSettings();
8892
const server4k = this.enable4kShow && this.currentServer.is4k;
8993
const processableSeasons: ProcessableSeason[] = [];
9094
let tvShow: TmdbTvDetails;
@@ -112,13 +116,42 @@ class SonarrScanner
112116
for (const season of filteredSeasons) {
113117
const totalAvailableEpisodes = season.statistics?.episodeFileCount ?? 0;
114118

119+
if (settings.main.removeUnmonitoredFromRequestsEnabled && season.monitored === false && totalAvailableEpisodes === 0) {
120+
// Remove unmonitored seasons from Requests
121+
const requestRepository = getRepository(MediaRequest);
122+
const seasonRequestRepository = getRepository(SeasonRequest);
123+
124+
const existingRequests = await requestRepository
125+
.createQueryBuilder('request')
126+
.innerJoinAndSelect('request.media', 'media')
127+
.innerJoinAndSelect('request.seasons', 'seasons')
128+
.where('media.tmdbId = :tmdbId', { tmdbId: tmdbId })
129+
.andWhere('media.mediaType = :mediaType', {
130+
mediaType: MediaType.TV
131+
})
132+
.andWhere('seasons.seasonNumber = :seasonNumber', { seasonNumber: season.seasonNumber })
133+
.getMany();
134+
135+
if (existingRequests && existingRequests.length > 0) {
136+
existingRequests.forEach((existingRequest) => {
137+
existingRequest.seasons.forEach(async (requestedSeason) => {
138+
if (requestedSeason.seasonNumber === season.seasonNumber) {
139+
this.log(`Removing request for Season ${season.seasonNumber} of ${sonarrSeries.title} as it is unmonitored`);
140+
await seasonRequestRepository.remove(requestedSeason);
141+
}
142+
});
143+
});
144+
}
145+
}
146+
115147
processableSeasons.push({
116148
seasonNumber: season.seasonNumber,
117149
episodes: !server4k ? totalAvailableEpisodes : 0,
118150
episodes4k: server4k ? totalAvailableEpisodes : 0,
119151
totalEpisodes: season.statistics?.totalEpisodeCount ?? 0,
120152
processing: season.monitored && totalAvailableEpisodes === 0,
121153
is4kOverride: server4k,
154+
monitored: season.monitored,
122155
});
123156
}
124157

server/lib/settings/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ export interface MainSettings {
129129
trustProxy: boolean;
130130
mediaServerType: number;
131131
partialRequestsEnabled: boolean;
132+
removeUnmonitoredFromRequestsEnabled: boolean;
132133
locale: string;
133134
proxy: ProxySettings;
134135
}
@@ -151,6 +152,7 @@ interface FullPublicSettings extends PublicSettings {
151152
jellyfinForgotPasswordUrl?: string;
152153
jellyfinServerName?: string;
153154
partialRequestsEnabled: boolean;
155+
removeUnmonitoredFromRequestsEnabled: boolean;
154156
cacheImages: boolean;
155157
vapidPublic: string;
156158
enablePushRegistration: boolean;
@@ -336,6 +338,7 @@ class Settings {
336338
trustProxy: false,
337339
mediaServerType: MediaServerType.NOT_CONFIGURED,
338340
partialRequestsEnabled: true,
341+
removeUnmonitoredFromRequestsEnabled: false,
339342
locale: 'en',
340343
proxy: {
341344
enabled: false,
@@ -574,6 +577,7 @@ class Settings {
574577
originalLanguage: this.data.main.originalLanguage,
575578
mediaServerType: this.main.mediaServerType,
576579
partialRequestsEnabled: this.data.main.partialRequestsEnabled,
580+
removeUnmonitoredFromRequestsEnabled: this.data.main.removeUnmonitoredFromRequestsEnabled,
577581
cacheImages: this.data.main.cacheImages,
578582
vapidPublic: this.vapidPublic,
579583
enablePushRegistration: this.data.notifications.agents.webpush.enabled,

src/components/Settings/SettingsMain/index.tsx

+26
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ const messages = defineMessages('components.Settings.SettingsMain', {
5454
validationApplicationUrl: 'You must provide a valid URL',
5555
validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash',
5656
partialRequestsEnabled: 'Allow Partial Series Requests',
57+
removeUnmonitoredFromRequestsEnabled: 'Remove Request for Movies/Seasons that have been un-monitored since',
5758
locale: 'Display Language',
5859
proxyEnabled: 'HTTP(S) Proxy',
5960
proxyHostname: 'Proxy Hostname',
@@ -152,6 +153,7 @@ const SettingsMain = () => {
152153
region: data?.region,
153154
originalLanguage: data?.originalLanguage,
154155
partialRequestsEnabled: data?.partialRequestsEnabled,
156+
removeUnmonitoredFromRequestsEnabled: data?.removeUnmonitoredFromRequestsEnabled,
155157
trustProxy: data?.trustProxy,
156158
cacheImages: data?.cacheImages,
157159
proxyEnabled: data?.proxy?.enabled,
@@ -181,6 +183,7 @@ const SettingsMain = () => {
181183
region: values.region,
182184
originalLanguage: values.originalLanguage,
183185
partialRequestsEnabled: values.partialRequestsEnabled,
186+
removeUnmonitoredFromRequestsEnabled: values.removeUnmonitoredFromRequestsEnabled,
184187
trustProxy: values.trustProxy,
185188
cacheImages: values.cacheImages,
186189
proxy: {
@@ -472,6 +475,29 @@ const SettingsMain = () => {
472475
/>
473476
</div>
474477
</div>
478+
<div className="form-row">
479+
<label
480+
htmlFor="removeUnmonitoredFromRequestsEnabled"
481+
className="checkbox-label"
482+
>
483+
<span className="mr-2">
484+
{intl.formatMessage(messages.removeUnmonitoredFromRequestsEnabled)}
485+
</span>
486+
</label>
487+
<div className="form-input-area">
488+
<Field
489+
type="checkbox"
490+
id="removeUnmonitoredFromRequestsEnabled"
491+
name="removeUnmonitoredFromRequestsEnabled"
492+
onChange={() => {
493+
setFieldValue(
494+
'removeUnmonitoredFromRequestsEnabled',
495+
!values.removeUnmonitoredFromRequestsEnabled
496+
);
497+
}}
498+
/>
499+
</div>
500+
</div>
475501
<div className="form-row">
476502
<label htmlFor="proxyEnabled" className="checkbox-label">
477503
<span className="mr-2">

src/context/SettingsContext.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const defaultSettings = {
2020
originalLanguage: '',
2121
mediaServerType: MediaServerType.NOT_CONFIGURED,
2222
partialRequestsEnabled: true,
23+
removeUnmonitoredFromRequestsEnabled: false,
2324
cacheImages: false,
2425
vapidPublic: '',
2526
enablePushRegistration: false,

src/i18n/locale/en.json

+1
Original file line numberDiff line numberDiff line change
@@ -885,6 +885,7 @@
885885
"components.Settings.SettingsMain.originallanguage": "Discover Language",
886886
"components.Settings.SettingsMain.originallanguageTip": "Filter content by original language",
887887
"components.Settings.SettingsMain.partialRequestsEnabled": "Allow Partial Series Requests",
888+
"components.Settings.SettingsMain.removeUnmonitoredFromRequestsEnabled": "Remove Request for Movies/Seasons that have been un-monitored since",
888889
"components.Settings.SettingsMain.region": "Discover Region",
889890
"components.Settings.SettingsMain.regionTip": "Filter content by regional availability",
890891
"components.Settings.SettingsMain.toastApiKeyFailure": "Something went wrong while generating a new API key.",

src/i18n/locale/fr.json

+1
Original file line numberDiff line numberDiff line change
@@ -1240,6 +1240,7 @@
12401240
"components.Settings.SettingsMain.trustProxy": "Activer la prise en charge proxy",
12411241
"components.Settings.SettingsMain.trustProxyTip": "Permettre Jellyseerr à enregistrer correctement les adresses IP des clients derrière un proxy",
12421242
"components.Settings.SettingsMain.partialRequestsEnabled": "Permettre les demandes partielles des séries",
1243+
"components.Settings.SettingsMain.removeUnmonitoredFromRequestsEnabled": "Supprimer les requests de Films/Séries qui ne sont plus suivis",
12431244
"components.Selector.nooptions": "Aucun résultat.",
12441245
"components.Layout.Sidebar.browsetv": "Séries",
12451246
"components.Selector.showmore": "En voir plus",

src/pages/_app.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ CoreApp.getInitialProps = async (initialProps) => {
196196
originalLanguage: '',
197197
mediaServerType: MediaServerType.NOT_CONFIGURED,
198198
partialRequestsEnabled: true,
199+
removeUnmonitoredFromRequestsEnabled: false,
199200
cacheImages: false,
200201
vapidPublic: '',
201202
enablePushRegistration: false,

0 commit comments

Comments
 (0)