diff --git a/charts/jellyseerr-chart/Chart.yaml b/charts/jellyseerr-chart/Chart.yaml index 15f18210..5408ac1a 100644 --- a/charts/jellyseerr-chart/Chart.yaml +++ b/charts/jellyseerr-chart/Chart.yaml @@ -3,8 +3,8 @@ kubeVersion: ">=1.23.0-0" name: jellyseerr-chart description: Jellyseerr helm chart for Kubernetes type: application -version: 2.2.0 -appVersion: "2.4.0" +version: 2.3.0 +appVersion: "2.5.0" maintainers: - name: Jellyseerr url: https://github.com/Fallenbagel/jellyseerr diff --git a/charts/jellyseerr-chart/README.md b/charts/jellyseerr-chart/README.md index 94850b96..eae3c6d6 100644 --- a/charts/jellyseerr-chart/README.md +++ b/charts/jellyseerr-chart/README.md @@ -1,6 +1,6 @@ # jellyseerr-chart -![Version: 2.2.0](https://img.shields.io/badge/Version-2.2.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 2.4.0](https://img.shields.io/badge/AppVersion-2.4.0-informational?style=flat-square) +![Version: 2.3.0](https://img.shields.io/badge/Version-2.3.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 2.5.0](https://img.shields.io/badge/AppVersion-2.5.0-informational?style=flat-square) Jellyseerr helm chart for Kubernetes diff --git a/docs/troubleshooting.mdx b/docs/troubleshooting.mdx index 2f3fed66..78b7e073 100644 --- a/docs/troubleshooting.mdx +++ b/docs/troubleshooting.mdx @@ -24,6 +24,12 @@ or for Cloudflare's DNS: ```bash --dns=1.1.1.1 ``` +or for Quad9 DNS: +```bash +--dns=9.9.9.9 +``` + +You can try them all and see which one works for your network. @@ -45,6 +51,16 @@ services: dns: - 1.1.1.1 ``` +or for Quad9's DNS: +```yaml +--- +services: + jellyseerr: + dns: + - 9.9.9.9 +``` + +You can try them all and see which one works for your network. @@ -56,7 +72,7 @@ services: 4. Click on Change adapter settings. 5. Right-click the network interface connected to the internet and select Properties. 6. Select Internet Protocol Version 4 (TCP/IPv4) and click Properties. -7. Select Use the following DNS server addresses and enter `8.8.8.8` for Google's DNS or `1.1.1.1` for Cloudflare's DNS. +7. Select Use the following DNS server addresses and enter `8.8.8.8` for Google's DNS or `1.1.1.1` for Cloudflare's DNS or `9.9.9.9` for Quad9's DNS. @@ -73,6 +89,10 @@ services: ```bash nameserver 1.1.1.1 ``` + or for Quad9's DNS: + ```bash + nameserver 9.9.9.9 + ``` @@ -81,7 +101,7 @@ services: Sometimes there are configuration issues with IPV6 that prevent the hostname resolution from working correctly. -You can try to force the resolution to use IPV4 first by setting the `FORCE_IPV4_FIRST` environment variable to `true`: +You can try to force the resolution to use IPV4 first by going to `Settings > Networking > Advanced Networking` and enabling `Force IPv4 Resolution First` setting and restarting. You can also add the environment variable, `FORCE_IPV4_FIRST=true`: diff --git a/server/entity/MediaRequest.ts b/server/entity/MediaRequest.ts index 8ef614b1..a0605374 100644 --- a/server/entity/MediaRequest.ts +++ b/server/entity/MediaRequest.ts @@ -999,7 +999,7 @@ export class MediaRequest { radarrMovie.id, [this.is4k ? 'externalServiceSlug4k' : 'externalServiceSlug']: radarrMovie.titleSlug, - [this.is4k ? 'serviceId4k' : 'serviceId']: radarrMovie?.id, + [this.is4k ? 'serviceId4k' : 'serviceId']: radarrSettings?.id, }; await mediaRepository.update({ id: this.media.id }, updateFields); diff --git a/server/lib/availabilitySync.ts b/server/lib/availabilitySync.ts index b85d29e4..0fdfd627 100644 --- a/server/lib/availabilitySync.ts +++ b/server/lib/availabilitySync.ts @@ -404,6 +404,34 @@ class AvailabilitySync { }); } + if ( + !showExists && + (media.status === MediaStatus.AVAILABLE || + media.status === MediaStatus.PARTIALLY_AVAILABLE || + media.seasons.some( + (season) => season.status === MediaStatus.AVAILABLE + ) || + media.seasons.some( + (season) => season.status === MediaStatus.PARTIALLY_AVAILABLE + )) + ) { + await this.mediaUpdater(media, false, mediaServerType); + } + + if ( + !showExists4k && + (media.status4k === MediaStatus.AVAILABLE || + media.status4k === MediaStatus.PARTIALLY_AVAILABLE || + media.seasons.some( + (season) => season.status4k === MediaStatus.AVAILABLE + ) || + media.seasons.some( + (season) => season.status4k === MediaStatus.PARTIALLY_AVAILABLE + )) + ) { + await this.mediaUpdater(media, true, mediaServerType); + } + // TODO: Figure out how to run seasonUpdater for each season if ([...finalSeasons.values()].includes(false)) { @@ -423,22 +451,6 @@ class AvailabilitySync { mediaServerType ); } - - if ( - !showExists && - (media.status === MediaStatus.AVAILABLE || - media.status === MediaStatus.PARTIALLY_AVAILABLE) - ) { - await this.mediaUpdater(media, false, mediaServerType); - } - - if ( - !showExists4k && - (media.status4k === MediaStatus.AVAILABLE || - media.status4k === MediaStatus.PARTIALLY_AVAILABLE) - ) { - await this.mediaUpdater(media, true, mediaServerType); - } } } } catch (ex) { @@ -466,6 +478,10 @@ class AvailabilitySync { { status: MediaStatus.PARTIALLY_AVAILABLE }, { status4k: MediaStatus.AVAILABLE }, { status4k: MediaStatus.PARTIALLY_AVAILABLE }, + { seasons: { status: MediaStatus.AVAILABLE } }, + { seasons: { status: MediaStatus.PARTIALLY_AVAILABLE } }, + { seasons: { status4k: MediaStatus.AVAILABLE } }, + { seasons: { status4k: MediaStatus.PARTIALLY_AVAILABLE } }, ]; let mediaPage: Media[]; diff --git a/server/routes/media.ts b/server/routes/media.ts index 60191e5d..3ad197c9 100644 --- a/server/routes/media.ts +++ b/server/routes/media.ts @@ -237,6 +237,19 @@ mediaRoutes.delete( } if (isMovie) { + // check if the movie exists + try { + await (service as RadarrAPI).getMovie({ + id: parseInt( + is4k + ? (media.externalServiceSlug4k as string) + : (media.externalServiceSlug as string) + ), + }); + } catch { + return res.status(204).send(); + } + // remove the movie await (service as RadarrAPI).removeMovie( parseInt( is4k @@ -251,6 +264,13 @@ mediaRoutes.delete( if (!tvdbId) { throw new Error('TVDB ID not found'); } + // check if the series exists + try { + await (service as SonarrAPI).getSeriesByTvdbId(tvdbId); + } catch { + return res.status(204).send(); + } + // remove the series await (service as SonarrAPI).removeSerie(tvdbId); } diff --git a/server/utils/customProxyAgent.ts b/server/utils/customProxyAgent.ts index 96ea7fed..5f163c3d 100644 --- a/server/utils/customProxyAgent.ts +++ b/server/utils/customProxyAgent.ts @@ -8,8 +8,9 @@ export default async function createCustomProxyAgent( ) { const defaultAgent = new Agent({ keepAliveTimeout: 5000 }); - const skipUrl = (url: string) => { - const hostname = new URL(url).hostname; + const skipUrl = (url: string | URL) => { + const hostname = + typeof url === 'string' ? new URL(url).hostname : url.hostname; if (proxySettings.bypassLocalAddresses && isLocalAddress(hostname)) { return true; @@ -38,8 +39,7 @@ export default async function createCustomProxyAgent( dispatch: Dispatcher['dispatch'] ): Dispatcher['dispatch'] => { return (opts, handler) => { - const url = opts.origin?.toString(); - return url && skipUrl(url) + return opts.origin && skipUrl(opts.origin) ? defaultAgent.dispatch(opts, handler) : dispatch(opts, handler); }; @@ -60,13 +60,10 @@ export default async function createCustomProxyAgent( ':' + proxySettings.port, token, - interceptors: { - Client: [noProxyInterceptor], - }, keepAliveTimeout: 5000, }); - setGlobalDispatcher(proxyAgent); + setGlobalDispatcher(proxyAgent.compose(noProxyInterceptor)); } catch (e) { logger.error('Failed to connect to the proxy: ' + e.message, { label: 'Proxy', @@ -95,7 +92,11 @@ export default async function createCustomProxyAgent( } function isLocalAddress(hostname: string) { - if (hostname === 'localhost' || hostname === '127.0.0.1') { + if ( + hostname === 'localhost' || + hostname === '127.0.0.1' || + hostname === '::1' + ) { return true; } diff --git a/src/components/AirDateBadge/index.tsx b/src/components/AirDateBadge/index.tsx index a51f39fc..1143c3e3 100644 --- a/src/components/AirDateBadge/index.tsx +++ b/src/components/AirDateBadge/index.tsx @@ -14,17 +14,13 @@ type AirDateBadgeProps = { const AirDateBadge = ({ airDate }: AirDateBadgeProps) => { const WEEK = 1000 * 60 * 60 * 24 * 8; const intl = useIntl(); - const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; const dAirDate = new Date(airDate); const nowDate = new Date(); const alreadyAired = dAirDate.getTime() < nowDate.getTime(); - const compareWeek = new Date( alreadyAired ? Date.now() - WEEK : Date.now() + WEEK ); - let showRelative = false; - if ( (alreadyAired && dAirDate.getTime() > compareWeek.getTime()) || (!alreadyAired && dAirDate.getTime() < compareWeek.getTime()) @@ -32,6 +28,10 @@ const AirDateBadge = ({ airDate }: AirDateBadgeProps) => { showRelative = true; } + const diffInDays = Math.round( + (dAirDate.getTime() - nowDate.getTime()) / (1000 * 60 * 60 * 24) + ); + return (
@@ -39,7 +39,7 @@ const AirDateBadge = ({ airDate }: AirDateBadgeProps) => { year: 'numeric', month: 'long', day: 'numeric', - timeZone, + timeZone: 'UTC', })} {showRelative && ( @@ -49,9 +49,9 @@ const AirDateBadge = ({ airDate }: AirDateBadgeProps) => { { relativeTime: ( ), } diff --git a/src/components/Login/JellyfinLogin.tsx b/src/components/Login/JellyfinLogin.tsx index 0b2776e3..239e4569 100644 --- a/src/components/Login/JellyfinLogin.tsx +++ b/src/components/Login/JellyfinLogin.tsx @@ -161,7 +161,6 @@ const JellyfinLogin: React.FC = ({ data-form-type="password" data-1pignore="false" data-lpignore="false" - data-bwignore="false" />
diff --git a/src/components/Login/LocalLogin.tsx b/src/components/Login/LocalLogin.tsx index 169a5a8f..1b9a5fe7 100644 --- a/src/components/Login/LocalLogin.tsx +++ b/src/components/Login/LocalLogin.tsx @@ -118,7 +118,6 @@ const LocalLogin = ({ revalidate }: LocalLoginProps) => { className="!bg-gray-700/80 placeholder:text-gray-400" data-1pignore="false" data-lpignore="false" - data-bwignore="false" />
diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx index 45cb0388..6d4a3be3 100644 --- a/src/components/MovieDetails/index.tsx +++ b/src/components/MovieDetails/index.tsx @@ -629,7 +629,9 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => { )} )} - +
+ +
& { profileName?: string }; revalidateList: () => void; + radarrData?: RadarrSettings[]; + sonarrData?: SonarrSettings[]; } -const RequestItem = ({ request, revalidateList }: RequestItemProps) => { +const RequestItem = ({ + request, + revalidateList, + radarrData, + sonarrData, +}: RequestItemProps) => { const settings = useSettings(); const { ref, inView } = useInView({ triggerOnce: true, @@ -390,6 +398,23 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => { iOSPlexUrl4k: requestData?.media?.iOSPlexUrl4k, }); + const serviceExists = () => { + if (title?.mediaInfo) { + if (title?.mediaInfo.mediaType === MediaType.MOVIE) { + return ( + radarrData?.find((radarr) => radarr.id === request.serverId) !== + undefined + ); + } else { + return ( + sonarrData?.find((sonarr) => sonarr.id === request.serverId) !== + undefined + ); + } + } + return false; + }; + if (!title && !error) { return (
{ )} {requestData.status !== MediaRequestStatus.PENDING && hasPermission(Permission.MANAGE_REQUESTS) && ( - <> - deleteRequest()} - confirmText={intl.formatMessage(globalMessages.areyousure)} - className="w-full" - > - - {intl.formatMessage(messages.deleterequest)} - - deleteMediaFile()} - confirmText={intl.formatMessage(globalMessages.areyousure)} - className="w-full" - > - - - {intl.formatMessage(messages.removearr, { - arr: request.type === 'movie' ? 'Radarr' : 'Sonarr', - })} - - - + deleteRequest()} + confirmText={intl.formatMessage(globalMessages.areyousure)} + className="w-full" + > + + {intl.formatMessage(messages.deleterequest)} + + )} + {hasPermission(Permission.MANAGE_REQUESTS) && + title?.mediaInfo?.serviceId && + serviceExists() && ( + deleteMediaFile()} + confirmText={intl.formatMessage(globalMessages.areyousure)} + className="w-full" + > + + + {intl.formatMessage(messages.removearr, { + arr: request.type === 'movie' ? 'Radarr' : 'Sonarr', + })} + + )} {requestData.status === MediaRequestStatus.PENDING && hasPermission(Permission.MANAGE_REQUESTS) && ( diff --git a/src/components/RequestList/index.tsx b/src/components/RequestList/index.tsx index 6cdf5b0b..468c0853 100644 --- a/src/components/RequestList/index.tsx +++ b/src/components/RequestList/index.tsx @@ -17,6 +17,8 @@ import { FunnelIcon, } from '@heroicons/react/24/solid'; import type { RequestResultsResponse } from '@server/interfaces/api/requestInterfaces'; +import { Permission } from '@server/lib/permissions'; +import type { RadarrSettings, SonarrSettings } from '@server/lib/settings'; import Link from 'next/link'; import { useRouter } from 'next/router'; import { useEffect, useState } from 'react'; @@ -51,7 +53,7 @@ const RequestList = () => { const { user } = useUser({ id: Number(router.query.userId), }); - const { user: currentUser } = useUser(); + const { user: currentUser, hasPermission } = useUser(); const [currentFilter, setCurrentFilter] = useState(Filter.PENDING); const [currentSort, setCurrentSort] = useState('added'); const [currentSortDirection, setCurrentSortDirection] = @@ -62,6 +64,13 @@ const RequestList = () => { const pageIndex = page - 1; const updateQueryParams = useUpdateQueryParams({ page: page.toString() }); + const { data: radarrData } = useSWR( + hasPermission(Permission.ADMIN) ? '/api/v1/settings/radarr' : null + ); + const { data: sonarrData } = useSWR( + hasPermission(Permission.ADMIN) ? '/api/v1/settings/sonarr' : null + ); + const { data, error, @@ -245,6 +254,8 @@ const RequestList = () => { revalidate()} + radarrData={radarrData} + sonarrData={sonarrData} />
); diff --git a/src/components/RequestModal/TvRequestModal.tsx b/src/components/RequestModal/TvRequestModal.tsx index 0ef1afd1..339b3768 100644 --- a/src/components/RequestModal/TvRequestModal.tsx +++ b/src/components/RequestModal/TvRequestModal.tsx @@ -217,7 +217,7 @@ const TvRequestModal = ({ mediaType: 'tv', is4k, seasons: settings.currentSettings.partialRequestsEnabled - ? selectedSeasons + ? selectedSeasons.sort((a, b) => a - b) : getAllSeasons().filter( (season) => !getAllRequestedSeasons().includes(season) ), diff --git a/src/components/Settings/Notifications/NotificationsEmail.tsx b/src/components/Settings/Notifications/NotificationsEmail.tsx index ac3853dd..a2431be0 100644 --- a/src/components/Settings/Notifications/NotificationsEmail.tsx +++ b/src/components/Settings/Notifications/NotificationsEmail.tsx @@ -221,6 +221,7 @@ const NotificationsEmail = () => { requireTls: values.encryption === 'opportunistic', authUser: values.authUser, authPass: values.authPass, + allowSelfSigned: values.allowSelfSigned, senderName: values.senderName, pgpPrivateKey: values.pgpPrivateKey, pgpPassword: values.pgpPassword, diff --git a/src/components/TitleCard/index.tsx b/src/components/TitleCard/index.tsx index 5bfb90e9..037afbc8 100644 --- a/src/components/TitleCard/index.tsx +++ b/src/components/TitleCard/index.tsx @@ -373,11 +373,10 @@ const TitleCard = ({ : intl.formatMessage(globalMessages.tvshow)}
- {showDetail && - currentStatus !== MediaStatus.BLACKLISTED && - user?.userType !== UserType.PLEX && ( -
- {toggleWatchlist ? ( + {showDetail && currentStatus !== MediaStatus.BLACKLISTED && ( +
+ {user?.userType !== UserType.PLEX && + (toggleWatchlist ? ( + ))} + {showHideButton && + currentStatus !== MediaStatus.PROCESSING && + currentStatus !== MediaStatus.AVAILABLE && + currentStatus !== MediaStatus.PARTIALLY_AVAILABLE && + currentStatus !== MediaStatus.PENDING && ( + )} - {showHideButton && - currentStatus !== MediaStatus.PROCESSING && - currentStatus !== MediaStatus.AVAILABLE && - currentStatus !== MediaStatus.PARTIALLY_AVAILABLE && - currentStatus !== MediaStatus.PENDING && ( - - )} -
- )} +
+ )} {showDetail && showHideButton && currentStatus == MediaStatus.BLACKLISTED && ( diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx index 66b4176d..e90fa9f5 100644 --- a/src/components/TvDetails/index.tsx +++ b/src/components/TvDetails/index.tsx @@ -671,7 +671,9 @@ const TvDetails = ({ tv }: TvDetailsProps) => { )} )} - +
+ +
revalidate()} diff --git a/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx b/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx index d8f0ded0..90fbb13c 100644 --- a/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx +++ b/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx @@ -415,7 +415,7 @@ const UserGeneralSettings = () => {
-
+
{
-
+