mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2025-12-24 02:39:18 -05:00
Compare commits
6 Commits
renovate/n
...
fallenbage
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d02906de8a | ||
|
|
537ef81eb6 | ||
|
|
62b43b83f9 | ||
|
|
c6e4e0446c | ||
|
|
6fac2964c3 | ||
|
|
f3786ce0bb |
@@ -34,6 +34,8 @@ interface ProcessOptions {
|
||||
is4k?: boolean;
|
||||
mediaAddedAt?: Date;
|
||||
ratingKey?: string;
|
||||
jellyfinMediaId?: string;
|
||||
imdbId?: string;
|
||||
serviceId?: number;
|
||||
externalServiceId?: number;
|
||||
externalServiceSlug?: string;
|
||||
@@ -95,6 +97,8 @@ class BaseScanner<T> {
|
||||
is4k = false,
|
||||
mediaAddedAt,
|
||||
ratingKey,
|
||||
jellyfinMediaId,
|
||||
imdbId,
|
||||
serviceId,
|
||||
externalServiceId,
|
||||
externalServiceSlug,
|
||||
@@ -133,6 +137,21 @@ class BaseScanner<T> {
|
||||
changedExisting = true;
|
||||
}
|
||||
|
||||
if (
|
||||
jellyfinMediaId &&
|
||||
existing[is4k ? 'jellyfinMediaId4k' : 'jellyfinMediaId'] !==
|
||||
jellyfinMediaId
|
||||
) {
|
||||
existing[is4k ? 'jellyfinMediaId4k' : 'jellyfinMediaId'] =
|
||||
jellyfinMediaId;
|
||||
changedExisting = true;
|
||||
}
|
||||
|
||||
if (imdbId && !existing.imdbId) {
|
||||
existing.imdbId = imdbId;
|
||||
changedExisting = true;
|
||||
}
|
||||
|
||||
if (
|
||||
serviceId !== undefined &&
|
||||
existing[is4k ? 'serviceId4k' : 'serviceId'] !== serviceId
|
||||
@@ -173,6 +192,7 @@ class BaseScanner<T> {
|
||||
} else {
|
||||
const newMedia = new Media();
|
||||
newMedia.tmdbId = tmdbId;
|
||||
newMedia.imdbId = imdbId;
|
||||
|
||||
newMedia.status =
|
||||
!is4k && !processing
|
||||
@@ -203,6 +223,13 @@ class BaseScanner<T> {
|
||||
newMedia.ratingKey4k =
|
||||
is4k && this.enable4kMovie ? ratingKey : undefined;
|
||||
}
|
||||
|
||||
if (jellyfinMediaId) {
|
||||
newMedia.jellyfinMediaId = !is4k ? jellyfinMediaId : undefined;
|
||||
newMedia.jellyfinMediaId4k =
|
||||
is4k && this.enable4kMovie ? jellyfinMediaId : undefined;
|
||||
}
|
||||
|
||||
await mediaRepository.save(newMedia);
|
||||
this.log(`Saved new media: ${title}`);
|
||||
}
|
||||
@@ -221,11 +248,12 @@ class BaseScanner<T> {
|
||||
*/
|
||||
protected async processShow(
|
||||
tmdbId: number,
|
||||
tvdbId: number,
|
||||
tvdbId: number | undefined,
|
||||
seasons: ProcessableSeason[],
|
||||
{
|
||||
mediaAddedAt,
|
||||
ratingKey,
|
||||
jellyfinMediaId,
|
||||
serviceId,
|
||||
externalServiceId,
|
||||
externalServiceSlug,
|
||||
@@ -257,7 +285,7 @@ class BaseScanner<T> {
|
||||
(es) => es.seasonNumber === season.seasonNumber
|
||||
);
|
||||
|
||||
// We update the rating keys in the seasons loop because we need episode counts
|
||||
// We update the rating keys and jellyfinMediaId in the seasons loop because we need episode counts
|
||||
if (media && season.episodes > 0 && media.ratingKey !== ratingKey) {
|
||||
media.ratingKey = ratingKey;
|
||||
}
|
||||
@@ -271,6 +299,23 @@ class BaseScanner<T> {
|
||||
media.ratingKey4k = ratingKey;
|
||||
}
|
||||
|
||||
if (
|
||||
media &&
|
||||
season.episodes > 0 &&
|
||||
media.jellyfinMediaId !== jellyfinMediaId
|
||||
) {
|
||||
media.jellyfinMediaId = jellyfinMediaId;
|
||||
}
|
||||
|
||||
if (
|
||||
media &&
|
||||
season.episodes4k > 0 &&
|
||||
this.enable4kShow &&
|
||||
media.jellyfinMediaId4k !== jellyfinMediaId
|
||||
) {
|
||||
media.jellyfinMediaId4k = jellyfinMediaId;
|
||||
}
|
||||
|
||||
if (existingSeason) {
|
||||
// Here we update seasons if they already exist.
|
||||
// If the season is already marked as available, we
|
||||
@@ -491,6 +536,22 @@ class BaseScanner<T> {
|
||||
)
|
||||
? ratingKey
|
||||
: undefined,
|
||||
jellyfinMediaId: newSeasons.some(
|
||||
(sn) =>
|
||||
sn.status === MediaStatus.PARTIALLY_AVAILABLE ||
|
||||
sn.status === MediaStatus.AVAILABLE
|
||||
)
|
||||
? jellyfinMediaId
|
||||
: undefined,
|
||||
jellyfinMediaId4k:
|
||||
this.enable4kShow &&
|
||||
newSeasons.some(
|
||||
(sn) =>
|
||||
sn.status4k === MediaStatus.PARTIALLY_AVAILABLE ||
|
||||
sn.status4k === MediaStatus.AVAILABLE
|
||||
)
|
||||
? jellyfinMediaId
|
||||
: undefined,
|
||||
status: isAllStandardSeasons
|
||||
? MediaStatus.AVAILABLE
|
||||
: newSeasons.some(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
193
server/lib/scanners/serviceAvailabilityChecker.ts
Normal file
193
server/lib/scanners/serviceAvailabilityChecker.ts
Normal file
@@ -0,0 +1,193 @@
|
||||
import RadarrAPI from '@server/api/servarr/radarr';
|
||||
import SonarrAPI from '@server/api/servarr/sonarr';
|
||||
import type { RadarrSettings, SonarrSettings } from '@server/lib/settings';
|
||||
import { getSettings } from '@server/lib/settings';
|
||||
import logger from '@server/logger';
|
||||
|
||||
interface InstanceAvailability {
|
||||
hasStandard: boolean;
|
||||
has4k: boolean;
|
||||
serviceStandardId?: number;
|
||||
service4kId?: number;
|
||||
externalStandardId?: number;
|
||||
external4kId?: number;
|
||||
}
|
||||
|
||||
interface SeasonInstanceAvailability {
|
||||
seasonNumber: number;
|
||||
episodesStandard: number;
|
||||
episodes4k: number;
|
||||
}
|
||||
|
||||
interface ShowInstanceAvailability extends InstanceAvailability {
|
||||
seasons: SeasonInstanceAvailability[];
|
||||
}
|
||||
|
||||
class ServiceAvailabilityChecker {
|
||||
private movieCache: Map<number, InstanceAvailability>;
|
||||
private showCache: Map<number, ShowInstanceAvailability>;
|
||||
|
||||
constructor() {
|
||||
this.movieCache = new Map();
|
||||
this.showCache = new Map();
|
||||
}
|
||||
|
||||
public clearCache(): void {
|
||||
this.movieCache.clear();
|
||||
this.showCache.clear();
|
||||
}
|
||||
|
||||
public async checkMovieAvailability(
|
||||
tmdbid: number
|
||||
): Promise<InstanceAvailability> {
|
||||
const cached = this.movieCache.get(tmdbid);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const settings = getSettings();
|
||||
const result: InstanceAvailability = {
|
||||
hasStandard: false,
|
||||
has4k: false,
|
||||
};
|
||||
|
||||
if (!settings.radarr || settings.radarr.length === 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (const radarrSettings of settings.radarr) {
|
||||
try {
|
||||
const radarr = this.createRadarrClient(radarrSettings);
|
||||
const movie = await radarr.getMovieByTmdbId(tmdbid);
|
||||
|
||||
if (movie?.hasFile) {
|
||||
if (radarrSettings.is4k) {
|
||||
result.has4k = true;
|
||||
result.service4kId = radarrSettings.id;
|
||||
result.external4kId = movie.id;
|
||||
} else {
|
||||
result.hasStandard = true;
|
||||
result.serviceStandardId = radarrSettings.id;
|
||||
result.externalStandardId = movie.id;
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`Found movie (TMDB: ${tmdbid}) in ${
|
||||
radarrSettings.is4k ? '4K' : 'Standard'
|
||||
} Radarr instance (name: ${radarrSettings.name})`,
|
||||
{
|
||||
label: 'Service Availability',
|
||||
radarrId: radarrSettings.id,
|
||||
movieId: movie?.id,
|
||||
}
|
||||
);
|
||||
} catch {
|
||||
// movie not found in this instance, continue
|
||||
}
|
||||
}
|
||||
|
||||
this.movieCache.set(tmdbid, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public async checkShowAvailability(
|
||||
tvdbid: number
|
||||
): Promise<ShowInstanceAvailability> {
|
||||
const cached = this.showCache.get(tvdbid);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const settings = getSettings();
|
||||
const result: ShowInstanceAvailability = {
|
||||
hasStandard: false,
|
||||
has4k: false,
|
||||
seasons: [],
|
||||
};
|
||||
|
||||
if (!settings.sonarr || settings.sonarr.length === 0) {
|
||||
return result;
|
||||
}
|
||||
const standardSeasons = new Map<number, number>();
|
||||
const seasons4k = new Map<number, number>();
|
||||
|
||||
for (const sonarrSettings of settings.sonarr) {
|
||||
try {
|
||||
const sonarr = this.createSonarrClient(sonarrSettings);
|
||||
const series = await sonarr.getSeriesByTvdbId(tvdbid);
|
||||
|
||||
if (series?.id && series.statistics?.episodeFileCount > 0) {
|
||||
if (sonarrSettings.is4k) {
|
||||
result.has4k = true;
|
||||
result.service4kId = sonarrSettings.id;
|
||||
result.external4kId = series.id;
|
||||
} else {
|
||||
result.hasStandard = true;
|
||||
result.serviceStandardId = sonarrSettings.id;
|
||||
result.externalStandardId = series.id;
|
||||
}
|
||||
|
||||
for (const season of series.seasons) {
|
||||
const episodeCount = season.statistics?.episodeFileCount ?? 0;
|
||||
if (episodeCount > 0) {
|
||||
const targetMap = sonarrSettings.is4k
|
||||
? seasons4k
|
||||
: standardSeasons;
|
||||
const current = targetMap.get(season.seasonNumber) ?? 0;
|
||||
targetMap.set(
|
||||
season.seasonNumber,
|
||||
Math.max(current, episodeCount)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`Found series (TVDB: ${tvdbid}) in ${
|
||||
sonarrSettings.is4k ? '4K' : 'Standard'
|
||||
} Sonarr instance (name: ${sonarrSettings.name}`,
|
||||
{
|
||||
label: 'Service Availability',
|
||||
sonarrId: sonarrSettings.id,
|
||||
seriesId: series.id,
|
||||
}
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
// series not found in this instance, continue
|
||||
}
|
||||
}
|
||||
|
||||
const allSeasonNumbers = new Set({
|
||||
...standardSeasons.keys(),
|
||||
...seasons4k.keys(),
|
||||
});
|
||||
|
||||
result.seasons = Array.from(allSeasonNumbers).map((seasonNumber) => ({
|
||||
seasonNumber,
|
||||
episodesStandard: standardSeasons.get(seasonNumber) ?? 0,
|
||||
episodes4k: seasons4k.get(seasonNumber) ?? 0,
|
||||
}));
|
||||
|
||||
this.showCache.set(tvdbid, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private createRadarrClient(settings: RadarrSettings): RadarrAPI {
|
||||
return new RadarrAPI({
|
||||
url: RadarrAPI.buildUrl(settings, '/api/v3'),
|
||||
apiKey: settings.apiKey,
|
||||
});
|
||||
}
|
||||
|
||||
private createSonarrClient(settings: SonarrSettings): SonarrAPI {
|
||||
return new SonarrAPI({
|
||||
url: SonarrAPI.buildUrl(settings, '/api/v3'),
|
||||
apiKey: settings.apiKey,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const serviceAvailabilityChecker = new ServiceAvailabilityChecker();
|
||||
|
||||
export default serviceAvailabilityChecker;
|
||||
Reference in New Issue
Block a user