Compare commits
4 Commits
preview-av
...
preview-av
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13c71b5ae3 | ||
|
|
65844a2f23 | ||
|
|
62755692e9 | ||
|
|
beba2ea099 |
@@ -41,6 +41,7 @@
|
|||||||
"@formatjs/swc-plugin-experimental": "^0.4.0",
|
"@formatjs/swc-plugin-experimental": "^0.4.0",
|
||||||
"@headlessui/react": "1.7.12",
|
"@headlessui/react": "1.7.12",
|
||||||
"@heroicons/react": "2.2.0",
|
"@heroicons/react": "2.2.0",
|
||||||
|
"@seerr-team/react-tailwindcss-datepicker": "^1.3.4",
|
||||||
"@supercharge/request-ip": "1.2.0",
|
"@supercharge/request-ip": "1.2.0",
|
||||||
"@svgr/webpack": "6.5.1",
|
"@svgr/webpack": "6.5.1",
|
||||||
"@tanem/react-nprogress": "5.0.56",
|
"@tanem/react-nprogress": "5.0.56",
|
||||||
@@ -90,7 +91,6 @@
|
|||||||
"react-popper-tooltip": "4.4.2",
|
"react-popper-tooltip": "4.4.2",
|
||||||
"react-select": "5.10.2",
|
"react-select": "5.10.2",
|
||||||
"react-spring": "9.7.1",
|
"react-spring": "9.7.1",
|
||||||
"react-tailwindcss-datepicker-sct": "1.3.4",
|
|
||||||
"react-toast-notifications": "2.5.1",
|
"react-toast-notifications": "2.5.1",
|
||||||
"react-transition-group": "^4.4.5",
|
"react-transition-group": "^4.4.5",
|
||||||
"react-truncate-markup": "5.1.2",
|
"react-truncate-markup": "5.1.2",
|
||||||
|
|||||||
28
pnpm-lock.yaml
generated
28
pnpm-lock.yaml
generated
@@ -32,6 +32,9 @@ importers:
|
|||||||
'@heroicons/react':
|
'@heroicons/react':
|
||||||
specifier: 2.2.0
|
specifier: 2.2.0
|
||||||
version: 2.2.0(react@18.3.1)
|
version: 2.2.0(react@18.3.1)
|
||||||
|
'@seerr-team/react-tailwindcss-datepicker':
|
||||||
|
specifier: ^1.3.4
|
||||||
|
version: 1.3.4(dayjs@1.11.19)(react@18.3.1)
|
||||||
'@supercharge/request-ip':
|
'@supercharge/request-ip':
|
||||||
specifier: 1.2.0
|
specifier: 1.2.0
|
||||||
version: 1.2.0
|
version: 1.2.0
|
||||||
@@ -179,9 +182,6 @@ importers:
|
|||||||
react-spring:
|
react-spring:
|
||||||
specifier: 9.7.1
|
specifier: 9.7.1
|
||||||
version: 9.7.1(@react-three/fiber@8.16.8(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.3.3)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)(three@0.165.0))(konva@9.3.12)(react-dom@18.3.1(react@18.3.1))(react-konva@18.2.10(@types/react@18.3.3)(konva@9.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.3.3)(encoding@0.1.13)(react@18.3.1))(react-zdog@1.2.2)(react@18.3.1)(three@0.165.0)(zdog@1.1.3)
|
version: 9.7.1(@react-three/fiber@8.16.8(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.3.3)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)(three@0.165.0))(konva@9.3.12)(react-dom@18.3.1(react@18.3.1))(react-konva@18.2.10(@types/react@18.3.3)(konva@9.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-native@0.74.2(@babel/core@7.24.7)(@babel/preset-env@7.24.7(@babel/core@7.24.7))(@types/react@18.3.3)(encoding@0.1.13)(react@18.3.1))(react-zdog@1.2.2)(react@18.3.1)(three@0.165.0)(zdog@1.1.3)
|
||||||
react-tailwindcss-datepicker-sct:
|
|
||||||
specifier: 1.3.4
|
|
||||||
version: 1.3.4(dayjs@1.11.19)(react@18.3.1)
|
|
||||||
react-toast-notifications:
|
react-toast-notifications:
|
||||||
specifier: 2.5.1
|
specifier: 2.5.1
|
||||||
version: 2.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 2.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
@@ -2992,6 +2992,12 @@ packages:
|
|||||||
'@rushstack/eslint-patch@1.10.3':
|
'@rushstack/eslint-patch@1.10.3':
|
||||||
resolution: {integrity: sha512-qC/xYId4NMebE6w/V33Fh9gWxLgURiNYgVNObbJl2LZv0GUUItCcCqC5axQSwRaAgaxl2mELq1rMzlswaQ0Zxg==}
|
resolution: {integrity: sha512-qC/xYId4NMebE6w/V33Fh9gWxLgURiNYgVNObbJl2LZv0GUUItCcCqC5axQSwRaAgaxl2mELq1rMzlswaQ0Zxg==}
|
||||||
|
|
||||||
|
'@seerr-team/react-tailwindcss-datepicker@1.3.4':
|
||||||
|
resolution: {integrity: sha512-KZrnl6WL1lvUnAG4RZIkReJ+E0vSpOtMEuatobMqiWAa5Y+Z3d0ZcOOJWHoeRNtF19sIzzBkuLyhFNFlNtXg3A==}
|
||||||
|
peerDependencies:
|
||||||
|
dayjs: ^1.11.6
|
||||||
|
react: ^17.0.2 || ^18.2.0
|
||||||
|
|
||||||
'@selderee/plugin-htmlparser2@0.11.0':
|
'@selderee/plugin-htmlparser2@0.11.0':
|
||||||
resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==}
|
resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==}
|
||||||
|
|
||||||
@@ -7949,12 +7955,6 @@ packages:
|
|||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
|
||||||
react-tailwindcss-datepicker-sct@1.3.4:
|
|
||||||
resolution: {integrity: sha512-QlLekGZDbmW2DPGS33c4gfIxkk4gcgu4sRzBIm4/mZxfHuo7J+GR6SBVNIb5Xh8aCLlGtgyLqD+o0UmOVFIc4w==}
|
|
||||||
peerDependencies:
|
|
||||||
dayjs: ^1.11.6
|
|
||||||
react: ^17.0.2 || ^18.2.0
|
|
||||||
|
|
||||||
react-toast-notifications@2.5.1:
|
react-toast-notifications@2.5.1:
|
||||||
resolution: {integrity: sha512-eYuuiSPGLyuMHojRH2U7CbENvFHsvNia39pLM/s10KipIoNs14T7RIJk4aU2N+l++OsSgtJqnFObx9bpwLMU5A==}
|
resolution: {integrity: sha512-eYuuiSPGLyuMHojRH2U7CbENvFHsvNia39pLM/s10KipIoNs14T7RIJk4aU2N+l++OsSgtJqnFObx9bpwLMU5A==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -13179,6 +13179,11 @@ snapshots:
|
|||||||
|
|
||||||
'@rushstack/eslint-patch@1.10.3': {}
|
'@rushstack/eslint-patch@1.10.3': {}
|
||||||
|
|
||||||
|
'@seerr-team/react-tailwindcss-datepicker@1.3.4(dayjs@1.11.19)(react@18.3.1)':
|
||||||
|
dependencies:
|
||||||
|
dayjs: 1.11.19
|
||||||
|
react: 18.3.1
|
||||||
|
|
||||||
'@selderee/plugin-htmlparser2@0.11.0':
|
'@selderee/plugin-htmlparser2@0.11.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
domhandler: 5.0.3
|
domhandler: 5.0.3
|
||||||
@@ -19185,11 +19190,6 @@ snapshots:
|
|||||||
- three
|
- three
|
||||||
- zdog
|
- zdog
|
||||||
|
|
||||||
react-tailwindcss-datepicker-sct@1.3.4(dayjs@1.11.19)(react@18.3.1):
|
|
||||||
dependencies:
|
|
||||||
dayjs: 1.11.19
|
|
||||||
react: 18.3.1
|
|
||||||
|
|
||||||
react-toast-notifications@2.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
react-toast-notifications@2.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emotion/core': 10.3.1(react@18.3.1)
|
'@emotion/core': 10.3.1(react@18.3.1)
|
||||||
|
|||||||
@@ -332,9 +332,16 @@ export class MediaRequest {
|
|||||||
if (requestBody.mediaType === MediaType.MOVIE) {
|
if (requestBody.mediaType === MediaType.MOVIE) {
|
||||||
await mediaRepository.save(media);
|
await mediaRepository.save(media);
|
||||||
|
|
||||||
|
if (!media.id) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to save media before creating request. Media type: ${requestBody.mediaType}, TMDB ID: ${requestBody.mediaId}, persisted media id: ${media.id}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const request = new MediaRequest({
|
const request = new MediaRequest({
|
||||||
type: MediaType.MOVIE,
|
type: MediaType.MOVIE,
|
||||||
media,
|
media,
|
||||||
|
mediaId: media.id,
|
||||||
requestedBy: requestUser,
|
requestedBy: requestUser,
|
||||||
// If the user is an admin or has the "auto approve" permission, automatically approve the request
|
// If the user is an admin or has the "auto approve" permission, automatically approve the request
|
||||||
status: user.hasPermission(
|
status: user.hasPermission(
|
||||||
@@ -442,9 +449,16 @@ export class MediaRequest {
|
|||||||
|
|
||||||
await mediaRepository.save(media);
|
await mediaRepository.save(media);
|
||||||
|
|
||||||
|
if (!media.id) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to save media before creating request. Media type: TV, TMDB ID: ${requestBody.mediaId}, is4k: ${requestBody.is4k}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const request = new MediaRequest({
|
const request = new MediaRequest({
|
||||||
type: MediaType.TV,
|
type: MediaType.TV,
|
||||||
media,
|
media,
|
||||||
|
mediaId: media.id,
|
||||||
requestedBy: requestUser,
|
requestedBy: requestUser,
|
||||||
// If the user is an admin or has the "auto approve" permission, automatically approve the request
|
// If the user is an admin or has the "auto approve" permission, automatically approve the request
|
||||||
status: user.hasPermission(
|
status: user.hasPermission(
|
||||||
@@ -521,6 +535,9 @@ export class MediaRequest {
|
|||||||
})
|
})
|
||||||
public media: Media;
|
public media: Media;
|
||||||
|
|
||||||
|
@Column({ name: 'mediaId', nullable: true })
|
||||||
|
public mediaId: number;
|
||||||
|
|
||||||
@ManyToOne(() => User, (user) => user.requests, {
|
@ManyToOne(() => User, (user) => user.requests, {
|
||||||
eager: true,
|
eager: true,
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
|
|||||||
@@ -612,6 +612,24 @@ class AvailabilitySync {
|
|||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
let existsInRadarr = false;
|
let existsInRadarr = false;
|
||||||
|
|
||||||
|
const has4kServer = this.radarrServers.some((s) => s.is4k);
|
||||||
|
const hasNon4kServer = this.radarrServers.some((s) => !s.is4k);
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
`Checking Radarr for ${is4k ? '4K' : 'non-4K'} movie [TMDB ID ${
|
||||||
|
media.tmdbId
|
||||||
|
}]`,
|
||||||
|
{
|
||||||
|
label: 'AvailabilitySync',
|
||||||
|
has4kServer,
|
||||||
|
hasNon4kServer,
|
||||||
|
externalServiceId: media.externalServiceId,
|
||||||
|
externalServiceId4k: media.externalServiceId4k,
|
||||||
|
serversToCheck: this.radarrServers.filter((s) => s.is4k === is4k)
|
||||||
|
.length,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// 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
|
||||||
for (const server of this.radarrServers.filter(
|
for (const server of this.radarrServers.filter(
|
||||||
@@ -642,7 +660,55 @@ class AvailabilitySync {
|
|||||||
radarr?.movieFile?.mediaInfo?.resolution?.split('x');
|
radarr?.movieFile?.mediaInfo?.resolution?.split('x');
|
||||||
const is4kMovie =
|
const is4kMovie =
|
||||||
resolution?.length === 2 && Number(resolution[0]) >= 2000;
|
resolution?.length === 2 && Number(resolution[0]) >= 2000;
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
`Radarr file found for movie [TMDB ID ${media.tmdbId}]`,
|
||||||
|
{
|
||||||
|
label: 'AvailabilitySync',
|
||||||
|
serverId: server.id,
|
||||||
|
serverIs4k: server.is4k,
|
||||||
|
hasFile: radarr.hasFile,
|
||||||
|
resolution: radarr?.movieFile?.mediaInfo?.resolution,
|
||||||
|
parsedWidth: resolution?.[0],
|
||||||
|
is4kMovie,
|
||||||
|
checkingFor: is4k ? '4K' : 'non-4K',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (has4kServer && hasNon4kServer) {
|
||||||
|
// User has both server types so use resolution to distinguish
|
||||||
|
// This handles the case where same content exists in both qualities
|
||||||
existsInRadarr = is4k ? is4kMovie : !is4kMovie;
|
existsInRadarr = is4k ? is4kMovie : !is4kMovie;
|
||||||
|
logger.debug(
|
||||||
|
`Dual-server setup: using resolution check for movie [TMDB ID ${media.tmdbId}]`,
|
||||||
|
{
|
||||||
|
label: 'AvailabilitySync',
|
||||||
|
is4kMovie,
|
||||||
|
is4kCheck: is4k,
|
||||||
|
existsInRadarr,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// User only has one server type so if file exists, count it
|
||||||
|
// Don't penalize users whose Radarr upgrades to 4K on a non-4K server
|
||||||
|
existsInRadarr = true;
|
||||||
|
logger.debug(
|
||||||
|
`Single-server setup: file exists, marking as available for movie [TMDB ID ${media.tmdbId}]`,
|
||||||
|
{
|
||||||
|
label: 'AvailabilitySync',
|
||||||
|
is4kMovie,
|
||||||
|
is4kCheck: is4k,
|
||||||
|
existsInRadarr,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.debug(`Radarr response for movie [TMDB ID ${media.tmdbId}]`, {
|
||||||
|
label: 'AvailabilitySync',
|
||||||
|
serverId: server.id,
|
||||||
|
found: !!radarr,
|
||||||
|
hasFile: radarr?.hasFile ?? false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
if (!ex.message.includes('404')) {
|
if (!ex.message.includes('404')) {
|
||||||
@@ -816,6 +882,50 @@ class AvailabilitySync {
|
|||||||
this.plexSeasonsCache[ratingKey4k] =
|
this.plexSeasonsCache[ratingKey4k] =
|
||||||
await this.plexClient?.getChildrenMetadata(ratingKey4k);
|
await this.plexClient?.getChildrenMetadata(ratingKey4k);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (plexMedia) {
|
||||||
|
if (ratingKey === ratingKey4k) {
|
||||||
|
plexMedia = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
plexMedia &&
|
||||||
|
media.mediaType === 'movie' &&
|
||||||
|
!plexMedia.Media?.some(
|
||||||
|
(mediaItem) => (mediaItem.width ?? 0) >= 2000
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
plexMedia = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plexMedia && media.mediaType === 'tv') {
|
||||||
|
const cachedSeasons = this.plexSeasonsCache[ratingKey4k];
|
||||||
|
if (cachedSeasons?.length) {
|
||||||
|
let has4kInAnySeason = false;
|
||||||
|
for (const season of cachedSeasons) {
|
||||||
|
try {
|
||||||
|
const episodes = await this.plexClient?.getChildrenMetadata(
|
||||||
|
season.ratingKey
|
||||||
|
);
|
||||||
|
const has4kEpisode = episodes?.some((episode) =>
|
||||||
|
episode.Media?.some(
|
||||||
|
(mediaItem) => (mediaItem.width ?? 0) >= 2000
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (has4kEpisode) {
|
||||||
|
has4kInAnySeason = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// If we can't fetch episodes for a season, continue checking other seasons
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!has4kInAnySeason) {
|
||||||
|
plexMedia = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plexMedia) {
|
if (plexMedia) {
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ import {
|
|||||||
} from '@app/hooks/useUpdateQueryParams';
|
} from '@app/hooks/useUpdateQueryParams';
|
||||||
import defineMessages from '@app/utils/defineMessages';
|
import defineMessages from '@app/utils/defineMessages';
|
||||||
import { XCircleIcon } from '@heroicons/react/24/outline';
|
import { XCircleIcon } from '@heroicons/react/24/outline';
|
||||||
|
import Datepicker from '@seerr-team/react-tailwindcss-datepicker';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import Datepicker from 'react-tailwindcss-datepicker-sct';
|
|
||||||
|
|
||||||
const messages = defineMessages('components.Discover.FilterSlideover', {
|
const messages = defineMessages('components.Discover.FilterSlideover', {
|
||||||
filters: 'Filters',
|
filters: 'Filters',
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ const defaultTheme = require('tailwindcss/defaultTheme');
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
mode: 'jit',
|
mode: 'jit',
|
||||||
content: [
|
content: [
|
||||||
'./node_modules/react-tailwindcss-datepicker-sct/dist/index.esm.js',
|
'./node_modules/@seerr-team/react-tailwindcss-datepicker/dist/index.esm.js',
|
||||||
'./src/pages/**/*.{ts,tsx}',
|
'./src/pages/**/*.{ts,tsx}',
|
||||||
'./src/components/**/*.{ts,tsx}',
|
'./src/components/**/*.{ts,tsx}',
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user