From c2d4c61fae2053157052f96e5de1cdb490ea704e Mon Sep 17 00:00:00 2001 From: Ahmed Siddiqui <36286128+AhmedNSidd@users.noreply.github.com> Date: Sun, 20 Oct 2024 22:02:10 -0700 Subject: [PATCH] feat: add support for requesting "Specials" for TV Shows (#3724) * feat: add support for requesting "Specials" for TV Shows This commit is responsible for adding support in Overseerr for requesting "Special" episodes for TV Shows. This request has become especially pertinent when you consider shows like "Doctor Who". These shows have Specials that are critical to understanding the plot of a TV show. fix #779 * chore(yarn.lock): undo inappropriate changes to yarn.lock I was informed by @sct in a comment on the #3724 PR that it was not appropriate to commit the changes that ended up being made to the yarn.lock file. This commit is responsible, then, for undoing the changes to the yarn.lock file that ended up being submitted. * refactor: change loose equality to strict equality I received a comment from OwsleyJr pointing out that we are using loose equality when we could alternatively just be using strict equality to increase the robustness of our code. This commit does exactly that by squashing out previous usages of loose equality in my commits and replacing them with strict equality * refactor: move 'Specials' string to a global message Owsley pointed out that we are redefining the 'Specials' string multiple times throughout this PR. Instead, we can just move it as a global message. This commit does exactly that. It squashes out and previous declarations of the 'Specials' string inside the src files, and moves it directly to the global messages file. --- overseerr-api.yml | 4 ++-- server/entity/MediaRequest.ts | 4 +--- server/lib/scanners/plex/index.ts | 4 +--- server/lib/scanners/sonarr/index.ts | 6 ++--- src/components/RequestBlock/index.tsx | 6 ++++- src/components/RequestCard/index.tsx | 9 +++++--- .../RequestList/RequestItem/index.tsx | 10 +++++---- .../RequestModal/TvRequestModal.tsx | 12 +++------- src/components/TvDetails/index.tsx | 22 ++++++++++++++----- src/i18n/globalMessages.ts | 1 + src/i18n/locale/en.json | 1 - 11 files changed, 43 insertions(+), 36 deletions(-) diff --git a/overseerr-api.yml b/overseerr-api.yml index c4c1e97b7..e23b97c36 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -5018,7 +5018,7 @@ paths: - type: array items: type: number - minimum: 1 + minimum: 0 - type: string enum: [all] is4k: @@ -5124,7 +5124,7 @@ paths: type: array items: type: number - minimum: 1 + minimum: 0 is4k: type: boolean example: false diff --git a/server/entity/MediaRequest.ts b/server/entity/MediaRequest.ts index ba67ab7be..bf4eb6df4 100644 --- a/server/entity/MediaRequest.ts +++ b/server/entity/MediaRequest.ts @@ -246,9 +246,7 @@ export class MediaRequest { >; const requestedSeasons = requestBody.seasons === 'all' - ? tmdbMediaShow.seasons - .map((season) => season.season_number) - .filter((sn) => sn > 0) + ? tmdbMediaShow.seasons.map((season) => season.season_number) : (requestBody.seasons as number[]); let existingSeasons: number[] = []; diff --git a/server/lib/scanners/plex/index.ts b/server/lib/scanners/plex/index.ts index f074872bb..58a948a86 100644 --- a/server/lib/scanners/plex/index.ts +++ b/server/lib/scanners/plex/index.ts @@ -278,9 +278,7 @@ class PlexScanner const seasons = tvShow.seasons; const processableSeasons: ProcessableSeason[] = []; - const filteredSeasons = seasons.filter((sn) => sn.season_number !== 0); - - for (const season of filteredSeasons) { + for (const season of seasons) { const matchedPlexSeason = metadata.Children?.Metadata.find( (md) => Number(md.index) === season.season_number ); diff --git a/server/lib/scanners/sonarr/index.ts b/server/lib/scanners/sonarr/index.ts index 3256c9482..5d28e0144 100644 --- a/server/lib/scanners/sonarr/index.ts +++ b/server/lib/scanners/sonarr/index.ts @@ -103,10 +103,8 @@ class SonarrScanner const tmdbId = tvShow.id; - const filteredSeasons = sonarrSeries.seasons.filter( - (sn) => - sn.seasonNumber !== 0 && - tvShow.seasons.find((s) => s.season_number === sn.seasonNumber) + const filteredSeasons = sonarrSeries.seasons.filter((sn) => + tvShow.seasons.find((s) => s.season_number === sn.seasonNumber) ); for (const season of filteredSeasons) { diff --git a/src/components/RequestBlock/index.tsx b/src/components/RequestBlock/index.tsx index ed4c3ec35..fda54499c 100644 --- a/src/components/RequestBlock/index.tsx +++ b/src/components/RequestBlock/index.tsx @@ -243,7 +243,11 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => { key={`season-${season.id}`} className="mb-1 mr-2 inline-block" > - {season.seasonNumber} + + {season.seasonNumber === 0 + ? intl.formatMessage(globalMessages.specials) + : season.seasonNumber} + ))} diff --git a/src/components/RequestCard/index.tsx b/src/components/RequestCard/index.tsx index 44abd555a..d971548f4 100644 --- a/src/components/RequestCard/index.tsx +++ b/src/components/RequestCard/index.tsx @@ -381,8 +381,7 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => { {intl.formatMessage(messages.seasons, { seasonCount: - title.seasons.filter((season) => season.seasonNumber !== 0) - .length === request.seasons.length + title.seasons.length === request.seasons.length ? 0 : request.seasons.length, })} @@ -390,7 +389,11 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
{request.seasons.map((season) => ( - {season.seasonNumber} + + {season.seasonNumber === 0 + ? intl.formatMessage(globalMessages.specials) + : season.seasonNumber} + ))}
diff --git a/src/components/RequestList/RequestItem/index.tsx b/src/components/RequestList/RequestItem/index.tsx index a42483abe..e48f75245 100644 --- a/src/components/RequestList/RequestItem/index.tsx +++ b/src/components/RequestList/RequestItem/index.tsx @@ -440,9 +440,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => { {intl.formatMessage(messages.seasons, { seasonCount: - title.seasons.filter( - (season) => season.seasonNumber !== 0 - ).length === request.seasons.length + title.seasons.length === request.seasons.length ? 0 : request.seasons.length, })} @@ -450,7 +448,11 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
{request.seasons.map((season) => ( - {season.seasonNumber} + + {season.seasonNumber === 0 + ? intl.formatMessage(globalMessages.specials) + : season.seasonNumber} + ))}
diff --git a/src/components/RequestModal/TvRequestModal.tsx b/src/components/RequestModal/TvRequestModal.tsx index 25c8fd3c2..723aa273c 100644 --- a/src/components/RequestModal/TvRequestModal.tsx +++ b/src/components/RequestModal/TvRequestModal.tsx @@ -41,7 +41,6 @@ const messages = defineMessages({ season: 'Season', numberofepisodes: '# of Episodes', seasonnumber: 'Season {number}', - extras: 'Extras', errorediting: 'Something went wrong while editing the request.', requestedited: 'Request for {title} edited successfully!', requestApproved: 'Request for {title} approved!', @@ -232,9 +231,7 @@ const TvRequestModal = ({ const getAllSeasons = (): number[] => { return (data?.seasons ?? []) - .filter( - (season) => season.seasonNumber !== 0 && season.episodeCount !== 0 - ) + .filter((season) => season.episodeCount !== 0) .map((season) => season.seasonNumber); }; @@ -557,10 +554,7 @@ const TvRequestModal = ({ {data?.seasons - .filter( - (season) => - season.seasonNumber !== 0 && season.episodeCount !== 0 - ) + .filter((season) => season.episodeCount !== 0) .map((season) => { const seasonRequest = getSeasonRequest( season.seasonNumber @@ -637,7 +631,7 @@ const TvRequestModal = ({ {season.seasonNumber === 0 - ? intl.formatMessage(messages.extras) + ? intl.formatMessage(globalMessages.specials) : intl.formatMessage(messages.seasonnumber, { number: season.seasonNumber, })} diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx index 2f44a7d8c..1b0b3c667 100644 --- a/src/components/TvDetails/index.tsx +++ b/src/components/TvDetails/index.tsx @@ -200,6 +200,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => { ); } + // Does NOT include "Specials" const seasonCount = data.seasons.filter( (season) => season.seasonNumber !== 0 && season.episodeCount !== 0 ).length; @@ -257,9 +258,17 @@ const TvDetails = ({ tv }: TvDetailsProps) => { return [...requestedSeasons, ...availableSeasons]; }; - const isComplete = seasonCount <= getAllRequestedSeasons(false).length; + const showHasSpecials = data.seasons.some( + (season) => season.seasonNumber === 0 + ); + + const isComplete = + (showHasSpecials ? seasonCount + 1 : seasonCount) <= + getAllRequestedSeasons(false).length; - const is4kComplete = seasonCount <= getAllRequestedSeasons(true).length; + const is4kComplete = + (showHasSpecials ? seasonCount + 1 : seasonCount) <= + getAllRequestedSeasons(true).length; const streamingProviders = data?.watchProviders?.find((provider) => provider.iso_3166_1 === region) @@ -522,7 +531,6 @@ const TvDetails = ({ tv }: TvDetailsProps) => { {data.seasons .slice() .reverse() - .filter((season) => season.seasonNumber !== 0) .map((season) => { const show4k = settings.currentSettings.series4kEnabled && @@ -576,9 +584,11 @@ const TvDetails = ({ tv }: TvDetailsProps) => { >
- {intl.formatMessage(messages.seasonnumber, { - seasonNumber: season.seasonNumber, - })} + {season.seasonNumber === 0 + ? intl.formatMessage(globalMessages.specials) + : intl.formatMessage(messages.seasonnumber, { + seasonNumber: season.seasonNumber, + })} {intl.formatMessage(messages.episodeCount, { diff --git a/src/i18n/globalMessages.ts b/src/i18n/globalMessages.ts index ca66a891f..64ae16c1c 100644 --- a/src/i18n/globalMessages.ts +++ b/src/i18n/globalMessages.ts @@ -55,6 +55,7 @@ const globalMessages = defineMessages({ noresults: 'No results.', open: 'Open', resolved: 'Resolved', + specials: 'Specials', }); export default globalMessages; diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 60a2d46bc..568895313 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -471,7 +471,6 @@ "components.RequestModal.cancel": "Cancel Request", "components.RequestModal.edit": "Edit Request", "components.RequestModal.errorediting": "Something went wrong while editing the request.", - "components.RequestModal.extras": "Extras", "components.RequestModal.numberofepisodes": "# of Episodes", "components.RequestModal.pending4krequest": "Pending 4K Request", "components.RequestModal.pendingapproval": "Your request is pending approval.",