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
pull/3724/head
Ahmed Siddiqui 5 months ago
parent d4ae554400
commit 198a85a4bc

@ -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

@ -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[] = [];

@ -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
);

@ -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) {

@ -35,6 +35,7 @@ const messages = defineMessages({
decline: 'Decline Request',
edit: 'Edit Request',
delete: 'Delete Request',
specials: 'Specials',
});
interface RequestBlockProps {
@ -243,7 +244,11 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => {
key={`season-${season.id}`}
className="mb-1 mr-2 inline-block"
>
<Badge>{season.seasonNumber}</Badge>
<Badge>
{season.seasonNumber == 0
? intl.formatMessage(messages.specials)
: season.seasonNumber}
</Badge>
</span>
))}
</div>

@ -40,6 +40,7 @@ const messages = defineMessages({
cancelrequest: 'Cancel Request',
deleterequest: 'Delete Request',
unknowntitle: 'Unknown Title',
specials: 'Specials',
});
const isMovie = (movie: MovieDetails | TvDetails): movie is MovieDetails => {
@ -381,8 +382,7 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
<span className="mr-2 font-bold ">
{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 +390,11 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
<div className="hide-scrollbar overflow-x-scroll">
{request.seasons.map((season) => (
<span key={`season-${season.id}`} className="mr-2">
<Badge>{season.seasonNumber}</Badge>
<Badge>
{season.seasonNumber == 0
? intl.formatMessage(messages.specials)
: season.seasonNumber}
</Badge>
</span>
))}
</div>

@ -41,6 +41,7 @@ const messages = defineMessages({
tmdbid: 'TMDB ID',
tvdbid: 'TheTVDB ID',
unknowntitle: 'Unknown Title',
specials: 'Specials',
});
const isMovie = (movie: MovieDetails | TvDetails): movie is MovieDetails => {
@ -440,9 +441,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
<span className="card-field-name">
{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 +449,11 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
<div className="hide-scrollbar flex flex-nowrap overflow-x-scroll">
{request.seasons.map((season) => (
<span key={`season-${season.id}`} className="mr-2">
<Badge>{season.seasonNumber}</Badge>
<Badge>
{season.seasonNumber == 0
? intl.formatMessage(messages.specials)
: season.seasonNumber}
</Badge>
</span>
))}
</div>

@ -41,7 +41,7 @@ const messages = defineMessages({
season: 'Season',
numberofepisodes: '# of Episodes',
seasonnumber: 'Season {number}',
extras: 'Extras',
specials: 'Specials',
errorediting: 'Something went wrong while editing the request.',
requestedited: 'Request for <strong>{title}</strong> edited successfully!',
requestApproved: 'Request for <strong>{title}</strong> approved!',
@ -232,9 +232,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 +555,7 @@ const TvRequestModal = ({
</thead>
<tbody className="divide-y divide-gray-700">
{data?.seasons
.filter(
(season) =>
season.seasonNumber !== 0 && season.episodeCount !== 0
)
.filter((season) => season.episodeCount !== 0)
.map((season) => {
const seasonRequest = getSeasonRequest(
season.seasonNumber
@ -637,7 +632,7 @@ const TvRequestModal = ({
</td>
<td className="whitespace-nowrap px-1 py-4 text-sm font-medium leading-5 text-gray-100 md:px-6">
{season.seasonNumber === 0
? intl.formatMessage(messages.extras)
? intl.formatMessage(messages.specials)
: intl.formatMessage(messages.seasonnumber, {
number: season.seasonNumber,
})}

@ -82,6 +82,7 @@ const messages = defineMessages({
seasonstitle: 'Seasons',
episodeCount: '{episodeCount, plural, one {# Episode} other {# Episodes}}',
seasonnumber: 'Season {seasonNumber}',
specials: 'Specials',
status4k: '4K {status}',
rtcriticsscore: 'Rotten Tomatoes Tomatometer',
rtaudiencescore: 'Rotten Tomatoes Audience Score',
@ -200,6 +201,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
);
}
// Does NOT include "Specials"
const seasonCount = data.seasons.filter(
(season) => season.seasonNumber !== 0 && season.episodeCount !== 0
).length;
@ -257,9 +259,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 +532,6 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
{data.seasons
.slice()
.reverse()
.filter((season) => season.seasonNumber !== 0)
.map((season) => {
const show4k =
settings.currentSettings.series4kEnabled &&
@ -576,9 +585,11 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
>
<div className="flex flex-1 items-center space-x-2 text-lg">
<span>
{intl.formatMessage(messages.seasonnumber, {
seasonNumber: season.seasonNumber,
})}
{season.seasonNumber == 0
? intl.formatMessage(messages.specials)
: intl.formatMessage(messages.seasonnumber, {
seasonNumber: season.seasonNumber,
})}
</span>
<Badge badgeType="dark">
{intl.formatMessage(messages.episodeCount, {

@ -399,6 +399,7 @@
"components.RequestBlock.rootfolder": "Root Folder",
"components.RequestBlock.seasons": "{seasonCount, plural, one {Season} other {Seasons}}",
"components.RequestBlock.server": "Destination Server",
"components.RequestBlock.specials": "Specials",
"components.RequestButton.approve4krequests": "Approve {requestCount, plural, one {4K Request} other {{requestCount} 4K Requests}}",
"components.RequestButton.approverequest": "Approve Request",
"components.RequestButton.approverequest4k": "Approve 4K Request",
@ -419,6 +420,7 @@
"components.RequestCard.failedretry": "Something went wrong while retrying the request.",
"components.RequestCard.mediaerror": "{mediaType} Not Found",
"components.RequestCard.seasons": "{seasonCount, plural, one {Season} other {Seasons}}",
"components.RequestCard.specials": "Specials",
"components.RequestCard.tmdbid": "TMDB ID",
"components.RequestCard.tvdbid": "TheTVDB ID",
"components.RequestCard.unknowntitle": "Unknown Title",
@ -432,6 +434,7 @@
"components.RequestList.RequestItem.requested": "Requested",
"components.RequestList.RequestItem.requesteddate": "Requested",
"components.RequestList.RequestItem.seasons": "{seasonCount, plural, one {Season} other {Seasons}}",
"components.RequestList.RequestItem.specials": "Specials",
"components.RequestList.RequestItem.tmdbid": "TMDB ID",
"components.RequestList.RequestItem.tvdbid": "TheTVDB ID",
"components.RequestList.RequestItem.unknowntitle": "Unknown Title",
@ -471,7 +474,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.",
@ -498,6 +500,7 @@
"components.RequestModal.seasonnumber": "Season {number}",
"components.RequestModal.selectmovies": "Select Movie(s)",
"components.RequestModal.selectseason": "Select Season(s)",
"components.RequestModal.specials": "Specials",
"components.ResetPassword.confirmpassword": "Confirm Password",
"components.ResetPassword.email": "Email Address",
"components.ResetPassword.emailresetlink": "Email Recovery Link",
@ -1027,6 +1030,7 @@
"components.TvDetails.seasonstitle": "Seasons",
"components.TvDetails.showtype": "Series Type",
"components.TvDetails.similar": "Similar Series",
"components.TvDetails.specials": "Specials",
"components.TvDetails.status4k": "4K {status}",
"components.TvDetails.streamingproviders": "Currently Streaming On",
"components.TvDetails.tmdbuserscore": "TMDB User Score",

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save