Compare commits
4 Commits
preview-re
...
preview-av
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed3a9b1c8d | ||
|
|
8431ef3f3b | ||
|
|
88b2e7843f | ||
|
|
dbd5935ade |
@@ -209,6 +209,34 @@ class SonarrAPI extends ServarrBase<{
|
|||||||
series: newSeriesResponse.data,
|
series: newSeriesResponse.data,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const episodes = await this.getEpisodes(newSeriesResponse.data.id);
|
||||||
|
const episodeIdsToMonitor = episodes
|
||||||
|
.filter(
|
||||||
|
(ep) =>
|
||||||
|
options.seasons.includes(ep.seasonNumber) && !ep.monitored
|
||||||
|
)
|
||||||
|
.map((ep) => ep.id);
|
||||||
|
|
||||||
|
if (episodeIdsToMonitor.length > 0) {
|
||||||
|
logger.debug(
|
||||||
|
'Re-monitoring unmonitored episodes for requested seasons.',
|
||||||
|
{
|
||||||
|
label: 'Sonarr',
|
||||||
|
seriesId: newSeriesResponse.data.id,
|
||||||
|
episodeCount: episodeIdsToMonitor.length,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await this.monitorEpisodes(episodeIdsToMonitor);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn('Failed to re-monitor episodes', {
|
||||||
|
label: 'Sonarr',
|
||||||
|
errorMessage: e.message,
|
||||||
|
seriesId: newSeriesResponse.data.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (options.searchNow) {
|
if (options.searchNow) {
|
||||||
this.searchSeries(newSeriesResponse.data.id);
|
this.searchSeries(newSeriesResponse.data.id);
|
||||||
}
|
}
|
||||||
@@ -318,6 +346,38 @@ class SonarrAPI extends ServarrBase<{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getEpisodes(seriesId: number): Promise<EpisodeResult[]> {
|
||||||
|
try {
|
||||||
|
const response = await this.axios.get<EpisodeResult[]>('/episode', {
|
||||||
|
params: { seriesId },
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('Failed to retrieve episodes', {
|
||||||
|
label: 'Sonarr API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
seriesId,
|
||||||
|
});
|
||||||
|
throw new Error('Failed to get episodes');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async monitorEpisodes(episodeIds: number[]): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this.axios.put('/episode/monitor', {
|
||||||
|
episodeIds,
|
||||||
|
monitored: true,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('Failed to monitor episodes', {
|
||||||
|
label: 'Sonarr API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
episodeIds,
|
||||||
|
});
|
||||||
|
throw new Error('Failed to monitor episodes');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private buildSeasonList(
|
private buildSeasonList(
|
||||||
seasons: number[],
|
seasons: number[],
|
||||||
existingSeasons?: SonarrSeason[]
|
existingSeasons?: SonarrSeason[]
|
||||||
|
|||||||
@@ -97,7 +97,10 @@ app
|
|||||||
|
|
||||||
// Register HTTP proxy
|
// Register HTTP proxy
|
||||||
if (settings.network.proxy.enabled) {
|
if (settings.network.proxy.enabled) {
|
||||||
await createCustomProxyAgent(settings.network.proxy);
|
await createCustomProxyAgent(
|
||||||
|
settings.network.proxy,
|
||||||
|
settings.network.forceIpv4First
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate library types
|
// Migrate library types
|
||||||
|
|||||||
@@ -143,7 +143,9 @@ class AvailabilitySync {
|
|||||||
const { existsInPlex: existsInPlex4k } =
|
const { existsInPlex: existsInPlex4k } =
|
||||||
await this.mediaExistsInPlex(media, true);
|
await this.mediaExistsInPlex(media, true);
|
||||||
|
|
||||||
if (existsInPlex || existsInRadarr) {
|
// Media must exist in Plex to be considered available
|
||||||
|
// If it exists in Radarr but not in Plex, it should be marked as deleted
|
||||||
|
if (existsInPlex) {
|
||||||
movieExists = true;
|
movieExists = true;
|
||||||
logger.info(
|
logger.info(
|
||||||
`The non-4K movie [TMDB ID ${media.tmdbId}] still exists. Preventing removal.`,
|
`The non-4K movie [TMDB ID ${media.tmdbId}] still exists. Preventing removal.`,
|
||||||
@@ -153,7 +155,7 @@ class AvailabilitySync {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existsInPlex4k || existsInRadarr4k) {
|
if (existsInPlex4k) {
|
||||||
movieExists4k = true;
|
movieExists4k = true;
|
||||||
logger.info(
|
logger.info(
|
||||||
`The 4K movie [TMDB ID ${media.tmdbId}] still exists. Preventing removal.`,
|
`The 4K movie [TMDB ID ${media.tmdbId}] still exists. Preventing removal.`,
|
||||||
@@ -240,7 +242,9 @@ class AvailabilitySync {
|
|||||||
|
|
||||||
//plex
|
//plex
|
||||||
if (mediaServerType === MediaServerType.PLEX) {
|
if (mediaServerType === MediaServerType.PLEX) {
|
||||||
if (existsInPlex || existsInSonarr) {
|
// Media must exist in Plex to be considered available
|
||||||
|
// If it exists in Sonarr but not in Plex, it should be marked as deleted
|
||||||
|
if (existsInPlex) {
|
||||||
showExists = true;
|
showExists = true;
|
||||||
logger.info(
|
logger.info(
|
||||||
`The non-4K show [TMDB ID ${media.tmdbId}] still exists. Preventing removal.`,
|
`The non-4K show [TMDB ID ${media.tmdbId}] still exists. Preventing removal.`,
|
||||||
@@ -252,7 +256,7 @@ class AvailabilitySync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (mediaServerType === MediaServerType.PLEX) {
|
if (mediaServerType === MediaServerType.PLEX) {
|
||||||
if (existsInPlex4k || existsInSonarr4k) {
|
if (existsInPlex4k) {
|
||||||
showExists4k = true;
|
showExists4k = true;
|
||||||
logger.info(
|
logger.info(
|
||||||
`The 4K show [TMDB ID ${media.tmdbId}] still exists. Preventing removal.`,
|
`The 4K show [TMDB ID ${media.tmdbId}] still exists. Preventing removal.`,
|
||||||
@@ -611,6 +615,21 @@ class AvailabilitySync {
|
|||||||
is4k: boolean
|
is4k: boolean
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
let existsInRadarr = false;
|
let existsInRadarr = false;
|
||||||
|
const externalServiceId = is4k
|
||||||
|
? media.externalServiceId4k
|
||||||
|
: media.externalServiceId;
|
||||||
|
|
||||||
|
if (!externalServiceId) {
|
||||||
|
logger.debug(
|
||||||
|
`Skipping Radarr check for ${is4k ? '4K' : 'non-4K'} movie [TMDB ID ${
|
||||||
|
media.tmdbId
|
||||||
|
}] - no externalServiceId available`,
|
||||||
|
{
|
||||||
|
label: 'Availability Sync',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Check for availability in all of the available radarr servers
|
// Check for availability in all of the available radarr servers
|
||||||
// If any find the media, we will assume the media exists
|
// If any find the media, we will assume the media exists
|
||||||
@@ -637,22 +656,63 @@ class AvailabilitySync {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (radarr && radarr.hasFile) {
|
if (radarr) {
|
||||||
const resolution =
|
if (radarr.hasFile) {
|
||||||
radarr?.movieFile?.mediaInfo?.resolution?.split('x');
|
const resolution =
|
||||||
const is4kMovie =
|
radarr?.movieFile?.mediaInfo?.resolution?.split('x');
|
||||||
resolution?.length === 2 && Number(resolution[0]) >= 2000;
|
const is4kMovie =
|
||||||
existsInRadarr = is4k ? is4kMovie : !is4kMovie;
|
resolution?.length === 2 && Number(resolution[0]) >= 2000;
|
||||||
|
const matches4k = is4k ? is4kMovie : !is4kMovie;
|
||||||
|
if (matches4k) {
|
||||||
|
existsInRadarr = true;
|
||||||
|
logger.debug(
|
||||||
|
`Found ${is4k ? '4K' : 'non-4K'} movie [TMDB ID ${
|
||||||
|
media.tmdbId
|
||||||
|
}] in Radarr`,
|
||||||
|
{
|
||||||
|
radarrId: radarr.id,
|
||||||
|
radarrTitle: radarr.title,
|
||||||
|
hasFile: radarr.hasFile,
|
||||||
|
externalServiceId: externalServiceId,
|
||||||
|
label: 'Availability Sync',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
logger.debug(
|
||||||
|
`Movie [TMDB ID ${media.tmdbId}] found in Radarr but resolution doesn't match (is4k: ${is4k}, movie resolution: ${radarr?.movieFile?.mediaInfo?.resolution})`,
|
||||||
|
{
|
||||||
|
label: 'Availability Sync',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.debug(
|
||||||
|
`Movie [TMDB ID ${media.tmdbId}] found in Radarr but has no file`,
|
||||||
|
{
|
||||||
|
radarrId: radarr.id,
|
||||||
|
radarrTitle: radarr.title,
|
||||||
|
externalServiceId: externalServiceId,
|
||||||
|
label: 'Availability Sync',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
if (!ex.message.includes('404')) {
|
if (ex.message.includes('404')) {
|
||||||
existsInRadarr = true;
|
logger.debug(
|
||||||
|
`Movie [TMDB ID ${media.tmdbId}] not found in Radarr (404) - externalServiceId may be stale: ${externalServiceId}`,
|
||||||
|
{
|
||||||
|
label: 'Availability Sync',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`Failure retrieving the ${is4k ? '4K' : 'non-4K'} movie [TMDB ID ${
|
`Failure retrieving the ${is4k ? '4K' : 'non-4K'} movie [TMDB ID ${
|
||||||
media.tmdbId
|
media.tmdbId
|
||||||
}] from Radarr.`,
|
}] from Radarr.`,
|
||||||
{
|
{
|
||||||
errorMessage: ex.message,
|
errorMessage: ex.message,
|
||||||
|
externalServiceId: externalServiceId,
|
||||||
label: 'Availability Sync',
|
label: 'Availability Sync',
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -700,7 +760,6 @@ class AvailabilitySync {
|
|||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
if (!ex.message.includes('404')) {
|
if (!ex.message.includes('404')) {
|
||||||
existsInSonarr = true;
|
|
||||||
preventSeasonSearch = true;
|
preventSeasonSearch = true;
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`Failure retrieving the ${is4k ? '4K' : 'non-4K'} show [TMDB ID ${
|
`Failure retrieving the ${is4k ? '4K' : 'non-4K'} show [TMDB ID ${
|
||||||
@@ -799,31 +858,66 @@ class AvailabilitySync {
|
|||||||
// We can use the cache we built when we fetched the series with mediaExistsInPlex
|
// We can use the cache we built when we fetched the series with mediaExistsInPlex
|
||||||
try {
|
try {
|
||||||
let plexMedia: PlexMetadata | undefined;
|
let plexMedia: PlexMetadata | undefined;
|
||||||
|
const currentRatingKey = is4k ? ratingKey4k : ratingKey;
|
||||||
|
|
||||||
if (ratingKey && !is4k) {
|
if (!currentRatingKey) {
|
||||||
plexMedia = await this.plexClient?.getMetadata(ratingKey);
|
logger.debug(
|
||||||
|
`Skipping Plex check for ${is4k ? '4K' : 'non-4K'} ${
|
||||||
|
media.mediaType === 'tv' ? 'show' : 'movie'
|
||||||
|
} [TMDB ID ${media.tmdbId}] - no ratingKey available`,
|
||||||
|
{
|
||||||
|
label: 'Availability Sync',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (ratingKey && !is4k) {
|
||||||
|
plexMedia = await this.plexClient?.getMetadata(ratingKey);
|
||||||
|
|
||||||
if (media.mediaType === 'tv') {
|
if (media.mediaType === 'tv') {
|
||||||
this.plexSeasonsCache[ratingKey] =
|
this.plexSeasonsCache[ratingKey] =
|
||||||
await this.plexClient?.getChildrenMetadata(ratingKey);
|
await this.plexClient?.getChildrenMetadata(ratingKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (ratingKey4k && is4k) {
|
if (ratingKey4k && is4k) {
|
||||||
plexMedia = await this.plexClient?.getMetadata(ratingKey4k);
|
plexMedia = await this.plexClient?.getMetadata(ratingKey4k);
|
||||||
|
|
||||||
if (media.mediaType === 'tv') {
|
if (media.mediaType === 'tv') {
|
||||||
this.plexSeasonsCache[ratingKey4k] =
|
this.plexSeasonsCache[ratingKey4k] =
|
||||||
await this.plexClient?.getChildrenMetadata(ratingKey4k);
|
await this.plexClient?.getChildrenMetadata(ratingKey4k);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (plexMedia) {
|
if (plexMedia) {
|
||||||
existsInPlex = true;
|
existsInPlex = true;
|
||||||
|
logger.debug(
|
||||||
|
`Found ${is4k ? '4K' : 'non-4K'} ${
|
||||||
|
media.mediaType === 'tv' ? 'show' : 'movie'
|
||||||
|
} [TMDB ID ${media.tmdbId}] in Plex`,
|
||||||
|
{
|
||||||
|
ratingKey: is4k ? ratingKey4k : ratingKey,
|
||||||
|
plexTitle: plexMedia.title,
|
||||||
|
plexRatingKey: plexMedia.ratingKey,
|
||||||
|
plexGuid: plexMedia.guid,
|
||||||
|
label: 'Availability Sync',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
if (!ex.message.includes('404')) {
|
if (ex.message.includes('404')) {
|
||||||
existsInPlex = true;
|
logger.debug(
|
||||||
|
`Media ${is4k ? '4K' : 'non-4K'} ${
|
||||||
|
media.mediaType === 'tv' ? 'show' : 'movie'
|
||||||
|
} [TMDB ID ${
|
||||||
|
media.tmdbId
|
||||||
|
}] not found in Plex (404) - ratingKey may be stale`,
|
||||||
|
{
|
||||||
|
ratingKey: is4k ? ratingKey4k : ratingKey,
|
||||||
|
label: 'Availability Sync',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
preventSeasonSearch = true;
|
preventSeasonSearch = true;
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`Failure retrieving the ${is4k ? '4K' : 'non-4K'} ${
|
`Failure retrieving the ${is4k ? '4K' : 'non-4K'} ${
|
||||||
|
|||||||
@@ -11,9 +11,14 @@ export let requestInterceptorFunction: (
|
|||||||
) => InternalAxiosRequestConfig;
|
) => InternalAxiosRequestConfig;
|
||||||
|
|
||||||
export default async function createCustomProxyAgent(
|
export default async function createCustomProxyAgent(
|
||||||
proxySettings: ProxySettings
|
proxySettings: ProxySettings,
|
||||||
|
forceIpv4First?: boolean
|
||||||
) {
|
) {
|
||||||
const defaultAgent = new Agent({ keepAliveTimeout: 5000 });
|
const defaultAgent = new Agent({
|
||||||
|
keepAliveTimeout: 5000,
|
||||||
|
connections: 50,
|
||||||
|
connect: forceIpv4First ? { family: 4 } : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
const skipUrl = (url: string | URL) => {
|
const skipUrl = (url: string | URL) => {
|
||||||
const hostname =
|
const hostname =
|
||||||
@@ -67,16 +72,23 @@ export default async function createCustomProxyAgent(
|
|||||||
uri: proxyUrl,
|
uri: proxyUrl,
|
||||||
token,
|
token,
|
||||||
keepAliveTimeout: 5000,
|
keepAliveTimeout: 5000,
|
||||||
|
connections: 50,
|
||||||
|
connect: forceIpv4First ? { family: 4 } : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
setGlobalDispatcher(proxyAgent.compose(noProxyInterceptor));
|
setGlobalDispatcher(proxyAgent.compose(noProxyInterceptor));
|
||||||
|
|
||||||
axios.defaults.httpAgent = new HttpProxyAgent(proxyUrl, {
|
const agentOptions = {
|
||||||
headers: token ? { 'proxy-authorization': token } : undefined,
|
headers: token ? { 'proxy-authorization': token } : undefined,
|
||||||
});
|
keepAlive: true,
|
||||||
axios.defaults.httpsAgent = new HttpsProxyAgent(proxyUrl, {
|
maxSockets: 50,
|
||||||
headers: token ? { 'proxy-authorization': token } : undefined,
|
maxFreeSockets: 10,
|
||||||
});
|
timeout: 5000,
|
||||||
|
scheduling: 'lifo' as const,
|
||||||
|
family: forceIpv4First ? 4 : undefined,
|
||||||
|
};
|
||||||
|
axios.defaults.httpAgent = new HttpProxyAgent(proxyUrl, agentOptions);
|
||||||
|
axios.defaults.httpsAgent = new HttpsProxyAgent(proxyUrl, agentOptions);
|
||||||
|
|
||||||
requestInterceptorFunction = (config) => {
|
requestInterceptorFunction = (config) => {
|
||||||
const url = config.baseURL
|
const url = config.baseURL
|
||||||
|
|||||||
Reference in New Issue
Block a user