feature: adjust season detection

This commit is contained in:
WonderAliceUwU committed 2026-07-10 11:23:26 +02:00
1 parent b2a6a80ec4
commit f40e1248b5
3 files changed
+75 -57

No files matched your search

+63 -53
View File
@@ -2195,63 +2195,73 @@ export async function requestMovie(config: AppConfig, tmdbId: number): Promise<v
if (!tmdbId) {
throw new Error('Movie TMDb id is required.');
}
const radarrRequestConfig = await resolveRadarrRequestConfig(config);
const details = await tmdbFetch(config, `/movie/${tmdbId}?language=${encodeURIComponent(config.preferences.language)}`);
if (!details) {
throw new Error('Failed to load the movie from TMDb.');
// First, check if the movie already exists in Radarr
let movie = await getRadarrMovieRecord(config, tmdbId);
// If the movie does not exist, add it to Radarr
if (!movie) {
const radarrRequestConfig = await resolveRadarrRequestConfig(config);
const details = await tmdbFetch(config, `/movie/${tmdbId}?language=${encodeURIComponent(config.preferences.language)}`);
if (!details) {
throw new Error('Failed to load the movie from TMDb.');
}
try {
movie = await fetchJson<RadarrMovieRecord>(`${config.radarr.baseUrl}/api/v3/movie`, {
method: 'POST',
headers: {
'X-Api-Key': config.radarr.apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({
title: details.title || details.original_title || 'Movie',
qualityProfileId: radarrRequestConfig.qualityProfileId,
rootFolderPath: radarrRequestConfig.rootFolderPath,
tmdbId: details.id,
year: Number(String(details.release_date || '').slice(0, 4)) || 0,
minimumAvailability: 'released',
addOptions: {
monitored: config.radarr.monitor !== 'none',
searchForMovie: false
}
})
});
} catch (error) {
const message = error instanceof Error ? error.message.toLowerCase() : '';
const looksLikeAlreadyExists = message.includes('already') || message.includes('exists') || message.includes('duplicate');
if (looksLikeAlreadyExists) {
// Race condition: movie was added between our check and our add call. Get it again.
movie = await getRadarrMovieRecord(config, tmdbId);
} else {
throw error; // Rethrow other errors
}
}
}
try {
await fetchJson(`${config.radarr.baseUrl}/api/v3/movie`, {
method: 'POST',
headers: {
'X-Api-Key': config.radarr.apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({
title: details.title || details.original_title || 'Movie',
qualityProfileId: radarrRequestConfig.qualityProfileId,
rootFolderPath: radarrRequestConfig.rootFolderPath,
tmdbId: details.id,
year: Number(String(details.release_date || '').slice(0, 4)) || 0,
minimumAvailability: 'released',
addOptions: {
monitored: config.radarr.monitor !== 'none',
searchForMovie: true
}
})
});
} catch (error) {
const message = error instanceof Error ? error.message.toLowerCase() : '';
const looksLikeAlreadyExists = message.includes('already') || message.includes('exists') || message.includes('duplicate');
if (!looksLikeAlreadyExists) {
throw error;
}
const existingMovie = await getRadarrMovieRecord(config, tmdbId);
if (!existingMovie?.id) {
throw error;
}
if (existingMovie.hasFile) {
return;
}
await fetchJson(`${config.radarr.baseUrl}/api/v3/command`, {
method: 'POST',
headers: {
'X-Api-Key': config.radarr.apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'MoviesSearch',
movieIds: [existingMovie.id],
qualityProfileId: radarrRequestConfig.qualityProfileId
})
});
// If we still don't have a movie record, something is wrong.
if (!movie?.id) {
throw new Error('Failed to add or find movie in Radarr.');
}
// If movie is already downloaded, we're done.
if (movie.hasFile) {
return;
}
// If movie is not downloaded, trigger a search.
await fetchJson(`${config.radarr.baseUrl}/api/v3/command`, {
method: 'POST',
headers: {
'X-Api-Key': config.radarr.apiKey,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'MoviesSearch',
movieIds: [movie.id]
})
});
}
export async function requestSeries(config: AppConfig, tmdbId: number): Promise<void> {
+11 -3
View File
@@ -1586,8 +1586,12 @@ function TitleDetailsModal(props: {
|| effectiveActiveSeasonMeta?.canDownload
|| (canRequest && !details?.sonarrSeriesId && typeof activeSeasonNumber === 'number')
)
const canDeleteSeasonAction = Boolean(effectiveActiveSeasonMeta?.canDelete)
const shouldShowDownloadSeasonAction = canDownloadSeasonAction && !canDeleteSeasonAction
const episodesForSeason = visibleEpisodes.filter(episode => episode.seasonId === seasonValue);
const isSeasonFullyDownloadedExceptUnaired = episodesForSeason.length > 0 && episodesForSeason.every(
(episode) => episode.availability === 'available' || episode.availability === 'unaired'
);
const canDeleteSeasonAction = (Boolean(effectiveActiveSeasonMeta?.canDelete) || isSeasonFullyDownloadedExceptUnaired);
const shouldShowDownloadSeasonAction = canDownloadSeasonAction && !canDeleteSeasonAction;
useEffect(() => {
if (!details || details.mediaType !== 'series') {
@@ -2296,7 +2300,11 @@ function TitleDetailsModal(props: {
) : null}
</div>
</div>
<span>{formatEpisodeMeta(episode.subtitle)}</span>
<span>
{episode.availability === 'unaired' && episode.airDate
? `${formatEpisodeMeta(episode.subtitle)} - Airs ${new Date(episode.airDate).toLocaleDateString('en-US', { day: 'numeric', month: 'short' })}`
: formatEpisodeMeta(episode.subtitle)}
</span>
</div>
</div>
))}
+1 -1
View File
@@ -723,7 +723,7 @@ img {
gap: 1rem;
overflow-x: auto;
overflow-y: visible;
padding: 0 1rem;
padding: 0.5rem 1.5rem;
scrollbar-width: none;
}