feat: add a setting for special episodes (#1193)

* feat: add a setting for special episodes

This PR adds a separate setting for special episodes and disables them by default, to avoid unwanted
library status updates.

* refactor(settings): re-order setting for allow specials request

---------

Co-authored-by: fallenbagel <98979876+Fallenbagel@users.noreply.github.com>
This commit is contained in:
Gauthier
2024-12-29 22:03:49 +01:00
committed by GitHub
parent 66948b420f
commit b6e2e6ce61
15 changed files with 108 additions and 29 deletions

View File

@@ -22,6 +22,7 @@
"trustProxy": false,
"mediaServerType": 1,
"partialRequestsEnabled": true,
"enableSpecialEpisodes": false,
"locale": "en"
},
"plex": {

View File

@@ -188,6 +188,9 @@ components:
defaultPermissions:
type: number
example: 32
enableSpecialEpisodes:
type: boolean
example: false
PlexLibrary:
type: object
properties:

View File

@@ -58,6 +58,7 @@ export class MediaRequest {
const mediaRepository = getRepository(Media);
const requestRepository = getRepository(MediaRequest);
const userRepository = getRepository(User);
const settings = getSettings();
let requestUser = user;
@@ -258,7 +259,11 @@ export class MediaRequest {
>;
const requestedSeasons =
requestBody.seasons === 'all'
? tmdbMediaShow.seasons.map((season) => season.season_number)
? settings.main.enableSpecialEpisodes
? tmdbMediaShow.seasons.map((season) => season.season_number)
: tmdbMediaShow.seasons
.map((season) => season.season_number)
.filter((sn) => sn > 0)
: (requestBody.seasons as number[]);
let existingSeasons: number[] = [];

View File

@@ -37,6 +37,7 @@ export interface PublicSettingsResponse {
originalLanguage: string;
mediaServerType: number;
partialRequestsEnabled: boolean;
enableSpecialEpisodes: boolean;
cacheImages: boolean;
vapidPublic: string;
enablePushRegistration: boolean;

View File

@@ -277,8 +277,13 @@ class PlexScanner
const seasons = tvShow.seasons;
const processableSeasons: ProcessableSeason[] = [];
const settings = getSettings();
for (const season of seasons) {
const filteredSeasons = settings.main.enableSpecialEpisodes
? seasons
: seasons.filter((sn) => sn.season_number !== 0);
for (const season of filteredSeasons) {
const matchedPlexSeason = metadata.Children?.Metadata.find(
(md) => Number(md.index) === season.season_number
);

View File

@@ -102,9 +102,12 @@ class SonarrScanner
}
const tmdbId = tvShow.id;
const settings = getSettings();
const filteredSeasons = sonarrSeries.seasons.filter((sn) =>
tvShow.seasons.find((s) => s.season_number === sn.seasonNumber)
const filteredSeasons = sonarrSeries.seasons.filter(
(sn) =>
tvShow.seasons.find((s) => s.season_number === sn.seasonNumber) &&
(!settings.main.partialRequestsEnabled ? sn.seasonNumber !== 0 : true)
);
for (const season of filteredSeasons) {

View File

@@ -131,6 +131,7 @@ export interface MainSettings {
trustProxy: boolean;
mediaServerType: number;
partialRequestsEnabled: boolean;
enableSpecialEpisodes: boolean;
locale: string;
proxy: ProxySettings;
}
@@ -154,6 +155,7 @@ interface FullPublicSettings extends PublicSettings {
jellyfinForgotPasswordUrl?: string;
jellyfinServerName?: string;
partialRequestsEnabled: boolean;
enableSpecialEpisodes: boolean;
cacheImages: boolean;
vapidPublic: string;
enablePushRegistration: boolean;
@@ -343,6 +345,7 @@ class Settings {
trustProxy: false,
mediaServerType: MediaServerType.NOT_CONFIGURED,
partialRequestsEnabled: true,
enableSpecialEpisodes: false,
locale: 'en',
proxy: {
enabled: false,
@@ -587,6 +590,7 @@ class Settings {
originalLanguage: this.data.main.originalLanguage,
mediaServerType: this.main.mediaServerType,
partialRequestsEnabled: this.data.main.partialRequestsEnabled,
enableSpecialEpisodes: this.data.main.enableSpecialEpisodes,
cacheImages: this.data.main.cacheImages,
vapidPublic: this.vapidPublic,
enablePushRegistration: this.data.notifications.agents.webpush.enabled,

View File

@@ -5,6 +5,7 @@ import Tooltip from '@app/components/Common/Tooltip';
import RequestModal from '@app/components/RequestModal';
import StatusBadge from '@app/components/StatusBadge';
import useDeepLinks from '@app/hooks/useDeepLinks';
import useSettings from '@app/hooks/useSettings';
import { Permission, useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
@@ -219,6 +220,7 @@ interface RequestCardProps {
}
const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
const settings = useSettings();
const { ref, inView } = useInView({
triggerOnce: true,
});
@@ -411,7 +413,11 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
<span className="mr-2 font-bold ">
{intl.formatMessage(messages.seasons, {
seasonCount:
title.seasons.length === request.seasons.length
(settings.currentSettings.enableSpecialEpisodes
? title.seasons.length
: title.seasons.filter(
(season) => season.seasonNumber !== 0
).length) === request.seasons.length
? 0
: request.seasons.length,
})}

View File

@@ -5,6 +5,7 @@ import ConfirmButton from '@app/components/Common/ConfirmButton';
import RequestModal from '@app/components/RequestModal';
import StatusBadge from '@app/components/StatusBadge';
import useDeepLinks from '@app/hooks/useDeepLinks';
import useSettings from '@app/hooks/useSettings';
import { Permission, useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
@@ -294,6 +295,7 @@ interface RequestItemProps {
}
const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
const settings = useSettings();
const { ref, inView } = useInView({
triggerOnce: true,
});
@@ -481,7 +483,11 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
<span className="card-field-name">
{intl.formatMessage(messages.seasons, {
seasonCount:
title.seasons.length === request.seasons.length
(settings.currentSettings.enableSpecialEpisodes
? title.seasons.length
: title.seasons.filter(
(season) => season.seasonNumber !== 0
).length) === request.seasons.length
? 0
: request.seasons.length,
})}

View File

@@ -253,9 +253,13 @@ const TvRequestModal = ({
};
const getAllSeasons = (): number[] => {
return (data?.seasons ?? [])
.filter((season) => season.episodeCount !== 0)
.map((season) => season.seasonNumber);
let allSeasons = (data?.seasons ?? []).filter(
(season) => season.episodeCount !== 0
);
if (!settings.currentSettings.partialRequestsEnabled) {
allSeasons = allSeasons.filter((season) => season.seasonNumber !== 0);
}
return allSeasons.map((season) => season.seasonNumber);
};
const getAllRequestedSeasons = (): number[] => {
@@ -577,7 +581,12 @@ const TvRequestModal = ({
</thead>
<tbody className="divide-y divide-gray-700">
{data?.seasons
.filter((season) => season.episodeCount !== 0)
.filter(
(season) =>
(!settings.currentSettings.enableSpecialEpisodes
? season.seasonNumber !== 0
: true) && season.episodeCount !== 0
)
.map((season) => {
const seasonRequest = getSeasonRequest(
season.seasonNumber

View File

@@ -56,6 +56,7 @@ const messages = defineMessages('components.Settings.SettingsMain', {
validationApplicationUrl: 'You must provide a valid URL',
validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash',
partialRequestsEnabled: 'Allow Partial Series Requests',
enableSpecialEpisodes: 'Allow Special Episodes Requests',
locale: 'Display Language',
proxyEnabled: 'HTTP(S) Proxy',
proxyHostname: 'Proxy Hostname',
@@ -158,6 +159,7 @@ const SettingsMain = () => {
originalLanguage: data?.originalLanguage,
streamingRegion: data?.streamingRegion,
partialRequestsEnabled: data?.partialRequestsEnabled,
enableSpecialEpisodes: data?.enableSpecialEpisodes,
trustProxy: data?.trustProxy,
cacheImages: data?.cacheImages,
proxyEnabled: data?.proxy?.enabled,
@@ -188,6 +190,7 @@ const SettingsMain = () => {
streamingRegion: values.streamingRegion,
originalLanguage: values.originalLanguage,
partialRequestsEnabled: values.partialRequestsEnabled,
enableSpecialEpisodes: values.enableSpecialEpisodes,
trustProxy: values.trustProxy,
cacheImages: values.cacheImages,
proxy: {
@@ -498,6 +501,47 @@ const SettingsMain = () => {
/>
</div>
</div>
<div className="form-row">
<label
htmlFor="enableSpecialEpisodes"
className="checkbox-label"
>
<span className="mr-2">
{intl.formatMessage(messages.enableSpecialEpisodes)}
</span>
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="enableSpecialEpisodes"
name="enableSpecialEpisodes"
onChange={() => {
setFieldValue(
'enableSpecialEpisodes',
!values.enableSpecialEpisodes
);
}}
/>
</div>
</div>
<div className="actions">
<div className="flex justify-end">
<span className="ml-3 inline-flex rounded-md shadow-sm">
<Button
buttonType="primary"
type="submit"
disabled={isSubmitting || !isValid}
>
<ArrowDownOnSquareIcon />
<span>
{isSubmitting
? intl.formatMessage(globalMessages.saving)
: intl.formatMessage(globalMessages.save)}
</span>
</Button>
</span>
</div>
</div>
<div className="form-row">
<label htmlFor="proxyEnabled" className="checkbox-label">
<span className="mr-2">
@@ -668,24 +712,6 @@ const SettingsMain = () => {
</div>
</>
)}
<div className="actions">
<div className="flex justify-end">
<span className="ml-3 inline-flex rounded-md shadow-sm">
<Button
buttonType="primary"
type="submit"
disabled={isSubmitting || !isValid}
>
<ArrowDownOnSquareIcon />
<span>
{isSubmitting
? intl.formatMessage(globalMessages.saving)
: intl.formatMessage(globalMessages.save)}
</span>
</Button>
</span>
</div>
</div>
</Form>
);
}}

View File

@@ -301,7 +301,9 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
};
const showHasSpecials = data.seasons.some(
(season) => season.seasonNumber === 0
(season) =>
season.seasonNumber === 0 &&
settings.currentSettings.partialRequestsEnabled
);
const isComplete =
@@ -799,6 +801,11 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
{data.seasons
.slice()
.reverse()
.filter(
(season) =>
settings.currentSettings.enableSpecialEpisodes ||
season.seasonNumber !== 0
)
.map((season) => {
const show4k =
settings.currentSettings.series4kEnabled &&

View File

@@ -21,6 +21,7 @@ const defaultSettings = {
originalLanguage: '',
mediaServerType: MediaServerType.NOT_CONFIGURED,
partialRequestsEnabled: true,
enableSpecialEpisodes: false,
cacheImages: false,
vapidPublic: '',
enablePushRegistration: false,

View File

@@ -919,6 +919,7 @@
"components.Settings.SettingsMain.csrfProtectionTip": "Set external API access to read-only (requires HTTPS)",
"components.Settings.SettingsMain.discoverRegion": "Discover Region",
"components.Settings.SettingsMain.discoverRegionTip": "Filter content by regional availability",
"components.Settings.SettingsMain.enableSpecialEpisodes": "Allow Special Episodes Requests",
"components.Settings.SettingsMain.general": "General",
"components.Settings.SettingsMain.generalsettings": "General Settings",
"components.Settings.SettingsMain.generalsettingsDescription": "Configure global and default settings for Jellyseerr.",

View File

@@ -199,6 +199,7 @@ CoreApp.getInitialProps = async (initialProps) => {
originalLanguage: '',
mediaServerType: MediaServerType.NOT_CONFIGURED,
partialRequestsEnabled: true,
enableSpecialEpisodes: false,
cacheImages: false,
vapidPublic: '',
enablePushRegistration: false,