diff --git a/.github/workflows/docker_dev.yml b/.github/workflows/docker_dev.yml index 176ac912b..db5961ed4 100644 --- a/.github/workflows/docker_dev.yml +++ b/.github/workflows/docker_dev.yml @@ -15,9 +15,9 @@ on: - '**.md' workflow_dispatch: inputs: - tags: + tag: required: true - description: 'Tags to deploy to' + description: 'Tag to deploy to' env: # Use docker.io for Docker Hub if empty @@ -79,7 +79,8 @@ jobs: # generate Docker tags based on the following events/attributes tags: | type=ref,event=pr - tpye=raw,value=dev,priority=1 + type=raw,value=${{ github.event.inputs.tag }}, prefix=test-,enable=${{ github.event.inputs.tag != '' }} + tpye=raw,value=dev,priority=1,enable=${{ github.event.inputs.tag == '' }} - name: Set up QEMU uses: docker/setup-qemu-action@v2 diff --git a/Dockerfile b/Dockerfile index af28768ba..4480896d7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ghcr.io/linuxserver/baseimage-alpine:3.16 +FROM node:16-alpine WORKDIR /app ENV NEXT_TELEMETRY_DISABLED 1 diff --git a/package.json b/package.json index f855bdcf8..27b16f743 100644 --- a/package.json +++ b/package.json @@ -32,30 +32,33 @@ "@dnd-kit/utilities": "^3.2.0", "@emotion/react": "^11.10.0", "@emotion/server": "^11.10.0", - "@mantine/carousel": "^5.0.0", - "@mantine/core": "^5.0.2", - "@mantine/dates": "^5.0.2", - "@mantine/dropzone": "^5.0.2", - "@mantine/form": "^5.0.2", - "@mantine/hooks": "^5.0.2", - "@mantine/next": "^5.0.2", - "@mantine/notifications": "^5.0.2", + "@mantine/carousel": "^5.1.0", + "@mantine/core": "^5.1.0", + "@mantine/dates": "^5.1.0", + "@mantine/dropzone": "^5.1.0", + "@mantine/form": "^5.1.0", + "@mantine/hooks": "^5.1.0", + "@mantine/modals": "^5.1.0", + "@mantine/next": "^5.1.0", + "@mantine/notifications": "^5.1.0", "@mantine/prism": "^5.0.0", "@nivo/core": "^0.79.0", "@nivo/line": "^0.79.1", "@tabler/icons": "^1.78.0", "add": "^2.0.6", "axios": "^0.27.2", + "consola": "^2.15.3", "cookies-next": "^2.1.1", "dayjs": "^1.11.4", "dockerode": "^3.3.2", - "embla-carousel-react": "^7.0.0-rc05", + "embla-carousel-react": "^7.0.0", "framer-motion": "^6.5.1", "js-file-download": "^0.4.12", "next": "12.1.6", "prism-react-renderer": "^1.3.5", "react": "^18.2.0", "react-dom": "^18.2.0", + "sharp": "^0.30.7", "systeminformation": "^5.12.1", "uuid": "^8.3.2", "yarn": "^1.22.19" diff --git a/src/components/AppShelf/AddAppShelfItem.tsx b/src/components/AppShelf/AddAppShelfItem.tsx index e603e1ad1..699110782 100644 --- a/src/components/AppShelf/AddAppShelfItem.tsx +++ b/src/components/AppShelf/AddAppShelfItem.tsx @@ -18,10 +18,10 @@ import { Tooltip, } from '@mantine/core'; import { useForm } from '@mantine/form'; -import { useDebouncedValue } from '@mantine/hooks'; import { IconApps } from '@tabler/icons'; import { useEffect, useState } from 'react'; import { v4 as uuidv4 } from 'uuid'; +import { useDebouncedValue } from '@mantine/hooks'; import { useConfig } from '../../tools/state'; import { tryMatchPort, ServiceTypeList, StatusCodes } from '../../tools/types'; import Tip from '../layout/Tip'; @@ -152,7 +152,7 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & return ( <> -
+
void } & {(form.values.type === 'Sonarr' || form.values.type === 'Radarr' || form.values.type === 'Lidarr' || + form.values.type === 'Overseerr' || form.values.type === 'Readarr') && ( <> ( { diff --git a/src/modules/calendar/CalendarModule.tsx b/src/modules/calendar/CalendarModule.tsx index cc9a03cc1..896faa940 100644 --- a/src/modules/calendar/CalendarModule.tsx +++ b/src/modules/calendar/CalendarModule.tsx @@ -12,6 +12,7 @@ import React, { useEffect, useState } from 'react'; import { Calendar } from '@mantine/dates'; import { IconCalendar as CalendarIcon } from '@tabler/icons'; import axios from 'axios'; +import { useDisclosure } from '@mantine/hooks'; import { useConfig } from '../../tools/state'; import { IModule } from '../ModuleTypes'; import { @@ -170,7 +171,7 @@ function DayComponent(props: any) { readarrmedias, }: { renderdate: Date; sonarrmedias: []; radarrmedias: []; lidarrmedias: []; readarrmedias: [] } = props; - const [opened, setOpened] = useState(false); + const [opened, { close, open }] = useDisclosure(false); const day = renderdate.getDate(); @@ -191,129 +192,129 @@ function DayComponent(props: any) { const date = new Date(media.inCinemas); return date.toDateString() === renderdate.toDateString(); }); - if ( - sonarrFiltered.length === 0 && - radarrFiltered.length === 0 && - lidarrFiltered.length === 0 && - readarrFiltered.length === 0 - ) { + const totalFiltered = [ + ...readarrFiltered, + ...lidarrFiltered, + ...sonarrFiltered, + ...radarrFiltered, + ]; + if (totalFiltered.length === 0) { return
{day}
; } return ( - { - setOpened(true); - }} + - {readarrFiltered.length > 0 && ( - - )} - {radarrFiltered.length > 0 && ( - - )} - {sonarrFiltered.length > 0 && ( - - )} - {lidarrFiltered.length > 0 && ( - - )} - setOpened(false)} - opened={opened} - > - + + + {readarrFiltered.length > 0 && ( + + )} + {radarrFiltered.length > 0 && ( + + )} + {sonarrFiltered.length > 0 && ( + + )} + {lidarrFiltered.length > 0 && ( + + )}
{day}
-
- - - {sonarrFiltered.map((media: any, index: number) => ( - - - {index < sonarrFiltered.length - 1 && } - - ))} - {radarrFiltered.length > 0 && sonarrFiltered.length > 0 && ( - - )} - {radarrFiltered.map((media: any, index: number) => ( - - - {index < radarrFiltered.length - 1 && } - - ))} - {sonarrFiltered.length > 0 && lidarrFiltered.length > 0 && ( - - )} - {lidarrFiltered.map((media: any, index: number) => ( - - - {index < lidarrFiltered.length - 1 && } - - ))} - {lidarrFiltered.length > 0 && readarrFiltered.length > 0 && ( - - )} - {readarrFiltered.map((media: any, index: number) => ( - - - {index < readarrFiltered.length - 1 && } - - ))} - - -
-
+ + + + 1 ? totalFiltered.slice(0, 2).length * 150 : 200, + width: 400, + }} + > + {sonarrFiltered.map((media: any, index: number) => ( + + + {index < sonarrFiltered.length - 1 && } + + ))} + {radarrFiltered.length > 0 && sonarrFiltered.length > 0 && ( + + )} + {radarrFiltered.map((media: any, index: number) => ( + + + {index < radarrFiltered.length - 1 && } + + ))} + {sonarrFiltered.length > 0 && lidarrFiltered.length > 0 && ( + + )} + {lidarrFiltered.map((media: any, index: number) => ( + + + {index < lidarrFiltered.length - 1 && } + + ))} + {lidarrFiltered.length > 0 && readarrFiltered.length > 0 && ( + + )} + {readarrFiltered.map((media: any, index: number) => ( + + + {index < readarrFiltered.length - 1 && } + + ))} + + + ); } diff --git a/src/modules/common/MediaDisplay.tsx b/src/modules/common/MediaDisplay.tsx index 02764c5e1..2de1707b0 100644 --- a/src/modules/common/MediaDisplay.tsx +++ b/src/modules/common/MediaDisplay.tsx @@ -1,103 +1,45 @@ -import { - Image, - Group, - Title, - Badge, - Text, - ActionIcon, - Anchor, - ScrollArea, - createStyles, - Stack, -} from '@mantine/core'; -import { useMediaQuery } from '@mantine/hooks'; -import { IconLink as Link } from '@tabler/icons'; +import { Badge, Button, Group, Image, Stack, Text, Title } from '@mantine/core'; +import { IconDownload, IconExternalLink, IconPlayerPlay } from '@tabler/icons'; +import { useState } from 'react'; import { useConfig } from '../../tools/state'; import { serviceItem } from '../../tools/types'; +import { RequestModal } from '../overseerr/RequestModal'; +import { Result } from '../overseerr/SearchResult'; export interface IMedia { overview: string; imdbId?: any; artist?: string; - title: string; + title?: string; + type: 'movie' | 'tvshow' | 'book' | 'music' | 'overseer'; + episodetitle?: string; + voteAverage?: string; poster?: string; genres: string[]; seasonNumber?: number; + plexUrl?: string; episodeNumber?: number; + [key: string]: any; } -const useStyles = createStyles((theme) => ({ - overview: { - [theme.fn.largerThan('sm')]: { - width: 400, - }, - }, -})); - -export function MediaDisplay(props: { media: IMedia }) { - const { media }: { media: IMedia } = props; - const { classes, cx } = useStyles(); - const phone = useMediaQuery('(min-width: 800px)'); +export function OverseerrMediaDisplay(props: any) { + const { media }: { media: Result } = props; return ( - - - {media.poster && ( - {media.title} - )} - - - {media.title} - {media.imdbId && ( - - - - - - )} - - {media.artist && ( - - New release from {media.artist} - - )} - {media.episodeNumber && media.seasonNumber && ( - - Season {media.seasonNumber} episode {media.episodeNumber} - - )} - - - {media.overview} - - {media.genres.slice(-5).map((genre: string, i: number) => ( - - {genre} - - ))} - - - - + ); } @@ -118,11 +60,14 @@ export function ReadarrMediaDisplay(props: any) { return ( ); @@ -145,6 +90,7 @@ export function LidarrMediaDisplay(props: any) { return ( image.coverType === 'poster'); - // Return a movie poster containting the title and the description return ( image.coverType === 'poster')?.url ?? undefined, + voteAverage: media.ratings.tmdb.value.toString() ?? undefined, + imdbId: media.imdbId ?? undefined, + type: 'movie', }} /> ); @@ -181,14 +128,106 @@ export function SonarrMediaDisplay(props: any) { return ( ); } + +export function MediaDisplay({ media }: { media: IMedia }) { + const [opened, setOpened] = useState(false); + return ( + + + + + + {media.title} + + + {media.type === 'tvshow' && ( + + s{media.seasonNumber}e{media.episodeNumber} - {media.episodetitle} + + )} + {media.type === 'music' && ( + + {media.artist} + + )} + {media.type === 'movie' && ( + + Radarr + + )} + {media.type === 'book' && ( + + Readarr + + )} + {media.genres.slice(0, 2).map((genre) => ( + + {genre} + + ))} + + + {media.overview} + + + + {media.plexUrl && ( + + )} + {media.imdbId && ( + + )} + {media.type === 'overseer' && ( + <> + + + + )} + + + + ); +} diff --git a/src/modules/common/examples/book.json b/src/modules/common/examples/book.json new file mode 100644 index 000000000..9548309a4 --- /dev/null +++ b/src/modules/common/examples/book.json @@ -0,0 +1,57 @@ +{ + "title": "Mika in Real Life", + "authorTitle": "jean, emiko Mika in Real Life", + "seriesTitle": "", + "disambiguation": "", + "authorId": 1, + "foreignBookId": "93584169", + "titleSlug": "93584169", + "monitored": true, + "anyEditionOk": false, + "ratings": { + "votes": 149, + "value": 4.15, + "popularity": 618.35 + }, + "releaseDate": "2022-08-09T00:00:00Z", + "pageCount": 384, + "genres": [ + "fiction", + "romance", + "contemporary", + "adult", + "adult-fiction", + "chick-lit", + "womens-fiction", + "asian-literature", + "family", + "lgbt" + ], + "images": [ + { + "url": "/MediaCover/Books/1/cover.jpg?lastWrite=637899714580000000", + "coverType": "cover", + "extension": ".jpg" + } + ], + "links": [ + { + "url": "https://www.goodreads.com/work/editions/93584169", + "name": "Goodreads Editions" + }, + { + "url": "https://www.goodreads.com/book/show/59430548-mika-in-real-life", + "name": "Goodreads Book" + } + ], + "statistics": { + "bookFileCount": 0, + "bookCount": 0, + "totalBookCount": 1, + "sizeOnDisk": 0, + "percentOfBooks": 0 + }, + "added": "2022-08-07T20:48:09Z", + "grabbed": false, + "id": 1 +} \ No newline at end of file diff --git a/src/modules/common/examples/movie.json b/src/modules/common/examples/movie.json new file mode 100644 index 000000000..594b14c73 --- /dev/null +++ b/src/modules/common/examples/movie.json @@ -0,0 +1,70 @@ +{ + "title": "The Tunnel to Summer, the Exit of Goodbyes", + "originalTitle": "夏へのトンネル、さよならの出口", + "originalLanguage": { + "id": 8, + "name": "Japanese" + }, + "alternateTitles": [ + { + "sourceType": "tmdb", + "movieId": 1, + "title": "Natsu e no Tunnel, Sayonara no Deguchi", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1 + } + ], + "secondaryYearSourceId": 0, + "sortTitle": "tunnel to summer exit goodbyes", + "sizeOnDisk": 0, + "status": "announced", + "overview": "Tono Kaoru heard a rumor: The laws of space and time mean nothing to the Urashima Tunnel. If you find it, walk through and you'll find your heart's desire on the other side...in exchange for years of your own life. On the night Kaoru just so happens to find himself standing in front of a tunnel that looks suspiciously like the one the rumor describes, he finds himself thinking of Karen, the sister he lost in an accident five years ago. To Kaoru's surprise, he's been followed by the new transfer student Anzu Hanaki, who promises to help him experiment with the mysterious tunnel--but what does she want from Kaoru in exchange? And what will he have left to give, after the tunnel's done with him?", + "inCinemas": "2022-09-09T00:00:00Z", + "images": [ + { + "coverType": "poster", + "url": "https://image.tmdb.org/t/p/original/3x5gc6dHsfNqZryipu159IALEPH.jpg" + }, + { + "coverType": "fanart", + "url": "https://image.tmdb.org/t/p/original/zO3QSYs858SqiapafD7iJp17KVD.jpg" + } + ], + "website": "https://natsuton.com/", + "year": 2022, + "hasFile": false, + "youTubeTrailerId": "", + "studio": "Pony Canyon", + "path": "/data/Library/Movies/The Tunnel to Summer, the Exit of Goodbyes (2022)", + "qualityProfileId": 4, + "monitored": true, + "minimumAvailability": "announced", + "isAvailable": true, + "folderName": "/data/Library/Movies/The Tunnel to Summer, the Exit of Goodbyes (2022)", + "runtime": 0, + "cleanTitle": "thetunneltosummerexitgoodbyes", + "imdbId": "tt17382524", + "tmdbId": 916192, + "titleSlug": "916192", + "genres": [ + "Animation", + "Drama", + "Mystery" + ], + "tags": [], + "added": "2022-07-05T07:50:42Z", + "ratings": { + "tmdb": { + "votes": 0, + "value": 0, + "type": "user" + } + }, + "id": 1 +} \ No newline at end of file diff --git a/src/modules/common/examples/multiplemovies.json b/src/modules/common/examples/multiplemovies.json new file mode 100644 index 000000000..d1f8dd71d --- /dev/null +++ b/src/modules/common/examples/multiplemovies.json @@ -0,0 +1,3324 @@ +[ + { + "title": "Sword Art Online the Movie -Progressive- Aria of a Starless Night", + "originalTitle": "劇場版 ソードアート・オンライン-プログレッシブ- 星なき夜のアリア", + "originalLanguage": { + "id": 8, + "name": "Japanese" + }, + "alternateTitles": [ + { + "sourceType": "tmdb", + "movieId": 170, + "title": "Gekijō-ban Sword Art Online: Progressive Hoshi Naki Yoru no Aria", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1749 + }, + { + "sourceType": "tmdb", + "movieId": 170, + "title": "SAO Progressive", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1751 + }, + { + "sourceType": "tmdb", + "movieId": 170, + "title": "Gekijouban Sword Art Online: Progressive - Hoshinaki Yoru no Ari", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1752 + }, + { + "sourceType": "tmdb", + "movieId": 170, + "title": "Sōdo Āto Onrain", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1753 + }, + { + "sourceType": "tmdb", + "movieId": 170, + "title": "Sword Art Online the Movie: The Aria of the Night Without Stars", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1754 + }, + { + "sourceType": "tmdb", + "movieId": 170, + "title": "극장판 소드 아트 온라인 -프로그레시브-: 별 없는 밤의 아리아", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1755 + }, + { + "sourceType": "tmdb", + "movieId": 170, + "title": "소드 아트 온라인 프로그레시브", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1756 + }, + { + "sourceType": "tmdb", + "movieId": 170, + "title": "Sword Art Online: Progressive - Aria of a Starless Night", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1757 + }, + { + "sourceType": "tmdb", + "movieId": 170, + "title": "刀劍神域劇場版 - PROGRESSIVE - 無星夜的詠嘆調", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1758 + }, + { + "sourceType": "tmdb", + "movieId": 170, + "title": "ซอร์ดอาร์ตออนไลน์ โปรเกรสซีฟ เดอะมูฟวี่", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 28, + "name": "Thai" + }, + "id": 1759 + }, + { + "sourceType": "tmdb", + "movieId": 170, + "title": "剧场版 刀剑神域 进击篇 无星之夜的咏叹调", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1760 + }, + { + "sourceType": "tmdb", + "movieId": 170, + "title": "Sword Art Online Progressive: Ária de Uma Noite Sem Estrelas", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1761 + }, + { + "sourceType": "tmdb", + "movieId": 170, + "title": "刀剑神域 进击篇 无星夜的咏叹调", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1776 + }, + { + "sourceType": "tmdb", + "movieId": 170, + "title": "Sword Art Online Progressive Movie: Hoshi Naki Yoru no Aria", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1788 + }, + { + "sourceType": "tmdb", + "movieId": 170, + "title": "刀剑神域:无星之夜的咏叹调", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1826 + } + ], + "secondaryYearSourceId": 0, + "sortTitle": "sword art online movie progressive aria starless night", + "sizeOnDisk": 0, + "status": "released", + "overview": "One month after Kayaba Akihiko's game of death began, the death toll continues to rise, two thousand players having already lost their lives to the ultra-difficult VRMMO world of Sword Art Online. On the day of the strategy meeting to plan out the first-floor boss battle, Kirito, a solo player who vows to fight alone to get stronger, runs into a rare, high-level female player. She gracefully dispatches powerful monsters with a single rapier that flashes like a shooting star in the night...", + "inCinemas": "2021-10-30T00:00:00Z", + "physicalRelease": "2022-07-08T00:00:00Z", + "images": [ + { + "coverType": "poster", + "url": "https://image.tmdb.org/t/p/original/yD9RhgIVydQNBK7OLEbCWYcWMUd.jpg" + }, + { + "coverType": "fanart", + "url": "https://image.tmdb.org/t/p/original/2kvl6lcgoyAaf8cSRkzxE611u6T.jpg" + } + ], + "website": "https://sao-p.net", + "year": 2021, + "hasFile": false, + "youTubeTrailerId": "Jf6RyZkuVco", + "studio": "Aniplex", + "path": "/movies/Sword Art Online the Movie -Progressive- Aria of a Starless Night (2021)", + "qualityProfileId": 1, + "monitored": true, + "minimumAvailability": "released", + "isAvailable": true, + "folderName": "/movies/Sword Art Online the Movie -Progressive- Aria of a Starless Night (2021)", + "runtime": 97, + "cleanTitle": "swordartonlinemovieprogressiveariastarlessnight", + "imdbId": "tt13424422", + "tmdbId": 761898, + "titleSlug": "761898", + "certification": "PG-13", + "genres": [ + "Animation", + "Action", + "Fantasy" + ], + "tags": [], + "added": "2022-07-04T17:05:05Z", + "ratings": { + "imdb": { + "votes": 1048, + "value": 7.1, + "type": "user" + }, + "tmdb": { + "votes": 82, + "value": 8.341, + "type": "user" + } + }, + "collection": { + "name": "Sword Art Online the Movie -Progressive-", + "tmdbId": 893713, + "images": [] + }, + "id": 170 + }, + { + "title": "Jujutsu Kaisen 0", + "originalTitle": "劇場版 呪術廻戦 0", + "originalLanguage": { + "id": 8, + "name": "Japanese" + }, + "alternateTitles": [ + { + "sourceType": "tmdb", + "movieId": 164, + "title": "Gekijō-ban Jujutsu Kaisen 0", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1672 + }, + { + "sourceType": "tmdb", + "movieId": 164, + "title": "Gekijou-ban Jujutsu Kaisen 0", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1673 + }, + { + "sourceType": "tmdb", + "movieId": 164, + "title": "극장판 주술회전 0", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1674 + }, + { + "sourceType": "tmdb", + "movieId": 164, + "title": "Jujutsu Kaisen 0: The Movie", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1675 + }, + { + "sourceType": "tmdb", + "movieId": 164, + "title": "呪術廻戦 0", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1676 + }, + { + "sourceType": "tmdb", + "movieId": 164, + "title": "JUJUTSU KAISEN 0 the Movie", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1677 + }, + { + "sourceType": "tmdb", + "movieId": 164, + "title": "劇場版 咒術迴戰 0", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1678 + }, + { + "sourceType": "tmdb", + "movieId": 164, + "title": "Jujutsu Kaisen 0: Film", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 12, + "name": "Polish" + }, + "id": 1845 + } + ], + "secondaryYearSourceId": 0, + "sortTitle": "jujutsu kaisen 0", + "sizeOnDisk": 0, + "status": "inCinemas", + "overview": "Yuta Okkotsu is a nervous high school student who is suffering from a serious problem—his childhood friend Rika has turned into a curse and won't leave him alone. Since Rika is no ordinary curse, his plight is noticed by Satoru Gojo, a teacher at Jujutsu High, a school where fledgling exorcists learn how to combat curses. Gojo convinces Yuta to enroll, but can he learn enough in time to confront the curse that haunts him?", + "inCinemas": "2021-12-24T00:00:00Z", + "physicalRelease": "2022-09-21T00:00:00Z", + "digitalRelease": "2022-09-21T00:00:00Z", + "images": [ + { + "coverType": "poster", + "url": "https://image.tmdb.org/t/p/original/3pTwMUEavTzVOh6yLN0aEwR7uSy.jpg" + }, + { + "coverType": "fanart", + "url": "https://image.tmdb.org/t/p/original/t9K8ycUBCplWiICDOKRNRYcEH9e.jpg" + } + ], + "website": "", + "year": 2021, + "hasFile": false, + "youTubeTrailerId": "2docezZl574", + "studio": "Toho", + "path": "/movies/Jujutsu Kaisen 0 (2021)", + "qualityProfileId": 1, + "monitored": true, + "minimumAvailability": "released", + "isAvailable": false, + "folderName": "/movies/Jujutsu Kaisen 0 (2021)", + "runtime": 105, + "cleanTitle": "jujutsukaisen0", + "imdbId": "tt14331144", + "tmdbId": 810693, + "titleSlug": "810693", + "certification": "PG-13", + "genres": [ + "Animation", + "Action", + "Fantasy" + ], + "tags": [], + "added": "2022-06-27T13:00:43Z", + "ratings": { + "imdb": { + "votes": 11063, + "value": 7.9, + "type": "user" + }, + "tmdb": { + "votes": 241, + "value": 7.8, + "type": "user" + }, + "metacritic": { + "votes": 0, + "value": 72, + "type": "user" + }, + "rottenTomatoes": { + "votes": 0, + "value": 98, + "type": "user" + } + }, + "id": 164 + }, + { + "title": "Morbius", + "originalTitle": "Morbius", + "originalLanguage": { + "id": 1, + "name": "English" + }, + "alternateTitles": [ + { + "sourceType": "tmdb", + "movieId": 113, + "title": "Morbiuss", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1484 + }, + { + "sourceType": "tmdb", + "movieId": 113, + "title": "暗夜博士:莫比亚斯", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1600 + } + ], + "secondaryYearSourceId": 0, + "sortTitle": "morbius", + "sizeOnDisk": 2130913775, + "status": "released", + "overview": "Dangerously ill with a rare blood disorder, and determined to save others suffering his same fate, Dr. Michael Morbius attempts a desperate gamble. What at first appears to be a radical success soon reveals itself to be a remedy potentially worse than the disease.", + "inCinemas": "2022-03-30T00:00:00Z", + "physicalRelease": "2022-06-14T00:00:00Z", + "digitalRelease": "2022-05-14T00:00:00Z", + "images": [ + { + "coverType": "poster", + "url": "https://image.tmdb.org/t/p/original/6JjfSchsU6daXk2AKX8EEBjO3Fm.jpg" + }, + { + "coverType": "fanart", + "url": "https://image.tmdb.org/t/p/original/gG9fTyDL03fiKnOpf2tr01sncnt.jpg" + } + ], + "website": "https://www.morbius.movie/", + "year": 2022, + "hasFile": true, + "youTubeTrailerId": "wG2TjtueeSU", + "studio": "Columbia Pictures", + "path": "/movies/Morbius (2022)", + "qualityProfileId": 1, + "monitored": true, + "minimumAvailability": "released", + "isAvailable": true, + "folderName": "/movies/Morbius (2022)", + "runtime": 105, + "cleanTitle": "morbius", + "imdbId": "tt5108870", + "tmdbId": 526896, + "titleSlug": "526896", + "certification": "PG-13", + "genres": [ + "Action", + "Science Fiction", + "Fantasy" + ], + "tags": [], + "added": "2022-02-11T19:39:55Z", + "ratings": { + "imdb": { + "votes": 92720, + "value": 5.2, + "type": "user" + }, + "tmdb": { + "votes": 2174, + "value": 6.4, + "type": "user" + }, + "metacritic": { + "votes": 0, + "value": 35, + "type": "user" + }, + "rottenTomatoes": { + "votes": 0, + "value": 16, + "type": "user" + } + }, + "movieFile": { + "movieId": 113, + "relativePath": "Morbius (2022) WEBRip-1080p.mp4", + "path": "/movies/Morbius (2022)/Morbius (2022) WEBRip-1080p.mp4", + "size": 2130913775, + "dateAdded": "2022-05-17T03:04:01Z", + "sceneName": "Morbius.2022.1080p.WEBRip.x264-RARBG", + "indexerFlags": 1, + "quality": { + "quality": { + "id": 15, + "name": "WEBRip-1080p", + "source": "webrip", + "resolution": 1080, + "modifier": "none" + }, + "revision": { + "version": 1, + "real": 0, + "isRepack": false + } + }, + "mediaInfo": { + "audioBitrate": 224000, + "audioChannels": 5.1, + "audioCodec": "AAC", + "audioLanguages": "eng", + "audioStreamCount": 1, + "videoBitDepth": 8, + "videoBitrate": 2498238, + "videoCodec": "x264", + "videoDynamicRangeType": "", + "videoFps": 23.976, + "resolution": "1920x800", + "runTime": "1:44:09", + "scanType": "Progressive", + "subtitles": "" + }, + "originalFilePath": "Morbius.2022.1080p.WEBRip.x264-RARBG/Morbius.2022.1080p.WEBRip.x264-RARBG.mp4", + "qualityCutoffNotMet": false, + "languages": [ + { + "id": 1, + "name": "English" + } + ], + "releaseGroup": "RARBG", + "edition": "", + "id": 219 + }, + "id": 113 + }, + { + "title": "Rise", + "originalTitle": "En corps", + "originalLanguage": { + "id": 2, + "name": "French" + }, + "alternateTitles": [], + "secondaryYearSourceId": 0, + "sortTitle": "rise", + "sizeOnDisk": 0, + "status": "released", + "overview": "Elise thought she had the perfect life: an ideal boyfriend and a promising career as a ballet dancer. It all falls apart the day she catches him cheating on her with her stage backup; and after she suffers an injury on stage, it seems like she might not be able to dance ever again. A heartwarming and inspiring story that tells us how sometimes, the worst thing that could happen may turn out to be the best.", + "inCinemas": "2022-03-30T00:00:00Z", + "physicalRelease": "2022-08-03T00:00:00Z", + "digitalRelease": "2022-07-30T00:00:00Z", + "images": [ + { + "coverType": "poster", + "url": "https://image.tmdb.org/t/p/original/9lbVcQfSfcMewo03UM7knSvN31T.jpg" + }, + { + "coverType": "fanart", + "url": "https://image.tmdb.org/t/p/original/duCaYLwr7LOhGTcyhcZP94rd2BS.jpg" + } + ], + "website": "", + "year": 2022, + "hasFile": false, + "youTubeTrailerId": "", + "studio": "France 2 Cinéma", + "path": "/movies/Rise (2022)", + "qualityProfileId": 1, + "monitored": true, + "minimumAvailability": "released", + "isAvailable": true, + "folderName": "/movies/Rise (2022)", + "runtime": 117, + "cleanTitle": "rise", + "imdbId": "tt13531468", + "tmdbId": 771077, + "titleSlug": "771077", + "genres": [ + "Comedy", + "Drama" + ], + "tags": [], + "added": "2022-03-29T14:52:43Z", + "ratings": { + "imdb": { + "votes": 818, + "value": 7.5, + "type": "user" + }, + "tmdb": { + "votes": 167, + "value": 7.913, + "type": "user" + } + }, + "id": 147 + }, + { + "title": "Fantastic Beasts: The Secrets of Dumbledore", + "originalTitle": "Fantastic Beasts: The Secrets of Dumbledore", + "originalLanguage": { + "id": 1, + "name": "English" + }, + "alternateTitles": [ + { + "sourceType": "tmdb", + "movieId": 121, + "title": "Фантастические твари и где они обитают 3", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 11, + "name": "Russian" + }, + "id": 1251 + }, + { + "sourceType": "tmdb", + "movieId": 121, + "title": "Φανταστικά Ζώα και που Βρίσκονται 3", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1252 + }, + { + "sourceType": "tmdb", + "movieId": 121, + "title": "Les Animaux fantastiques - 3e partie", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 2, + "name": "French" + }, + "id": 1253 + }, + { + "sourceType": "tmdb", + "movieId": 121, + "title": "Fantastic Beasts 3", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1255 + }, + { + "sourceType": "tmdb", + "movieId": 121, + "title": "Fantastická zvířata 3", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1256 + }, + { + "sourceType": "tmdb", + "movieId": 121, + "title": "Фантастичні звірі 3", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1257 + }, + { + "sourceType": "tmdb", + "movieId": 121, + "title": "Fantastiske Skabninger: Dumbledores Hemmeligheder", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1258 + }, + { + "sourceType": "tmdb", + "movieId": 121, + "title": "怪獸與鄧不利多的秘密", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1453 + }, + { + "sourceType": "tmdb", + "movieId": 121, + "title": "Fantastyczne zwierzęta: Tajemnice Dumbledore'a", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 12, + "name": "Polish" + }, + "id": 1482 + }, + { + "sourceType": "tmdb", + "movieId": 121, + "title": "Fantastiskās būtnes: Dumidora noslēpumi", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1483 + }, + { + "sourceType": "tmdb", + "movieId": 121, + "title": "Animales Fantásticos: Los Secretos de Dumbledore", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1540 + }, + { + "sourceType": "tmdb", + "movieId": 121, + "title": "Fantastic Beasts 3 - The Secrets of Dumbledore", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1595 + }, + { + "sourceType": "tmdb", + "movieId": 121, + "title": "Animali Fantastici - I Segreti Di Silente", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 5, + "name": "Italian" + }, + "id": 1638 + }, + { + "sourceType": "tmdb", + "movieId": 121, + "title": "Animali Fantastici 3 I Segreti Di Silente", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 5, + "name": "Italian" + }, + "id": 1639 + }, + { + "sourceType": "tmdb", + "movieId": 121, + "title": "Les Animaux fantastiques 3 - Les Secrets de Dumbledore", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 2, + "name": "French" + }, + "id": 1774 + }, + { + "sourceType": "tmdb", + "movieId": 121, + "title": "神奇动物3:邓布利多之谜", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1823 + } + ], + "secondaryYearSourceId": 0, + "sortTitle": "fantastic beasts secrets dumbledore", + "sizeOnDisk": 9631363612, + "status": "released", + "overview": "Professor Albus Dumbledore knows the powerful, dark wizard Gellert Grindelwald is moving to seize control of the wizarding world. Unable to stop him alone, he entrusts magizoologist Newt Scamander to lead an intrepid team of wizards and witches. They soon encounter an array of old and new beasts as they clash with Grindelwald's growing legion of followers.", + "inCinemas": "2022-04-06T00:00:00Z", + "physicalRelease": "2022-06-28T00:00:00Z", + "digitalRelease": "2022-05-30T00:00:00Z", + "images": [ + { + "coverType": "poster", + "url": "https://image.tmdb.org/t/p/original/3c5GNLB4yRSLBby0trHoA1DSQxQ.jpg" + }, + { + "coverType": "fanart", + "url": "https://image.tmdb.org/t/p/original/zGLHX92Gk96O1DJvLil7ObJTbaL.jpg" + } + ], + "website": "https://www.fantasticbeasts.com", + "year": 2022, + "hasFile": true, + "youTubeTrailerId": "Fo6TfHkLW6Y", + "studio": "Warner Bros. Pictures", + "path": "/movies/Fantastic Beasts The Secrets of Dumbledore (2022)", + "qualityProfileId": 1, + "monitored": true, + "minimumAvailability": "released", + "isAvailable": true, + "folderName": "/movies/Fantastic Beasts The Secrets of Dumbledore (2022)", + "runtime": 142, + "cleanTitle": "fantasticbeastssecretsdumbledore", + "imdbId": "tt4123432", + "tmdbId": 338953, + "titleSlug": "338953", + "certification": "PG-13", + "genres": [ + "Fantasy", + "Adventure", + "Action" + ], + "tags": [], + "added": "2022-03-04T21:08:32Z", + "ratings": { + "imdb": { + "votes": 118753, + "value": 6.2, + "type": "user" + }, + "tmdb": { + "votes": 2455, + "value": 6.864, + "type": "user" + }, + "metacritic": { + "votes": 0, + "value": 47, + "type": "user" + }, + "rottenTomatoes": { + "votes": 0, + "value": 46, + "type": "user" + } + }, + "movieFile": { + "movieId": 121, + "relativePath": "Fantastic Beasts The Secrets of Dumbledore (2022) WEBDL-1080p.mkv", + "path": "/movies/Fantastic Beasts The Secrets of Dumbledore (2022)/Fantastic Beasts The Secrets of Dumbledore (2022) WEBDL-1080p.mkv", + "size": 9631363612, + "dateAdded": "2022-05-30T09:12:04Z", + "sceneName": "Fantastic.Beasts.The.Secrets.of.Dumbledore.2022.1080p.HMAX.WEBRip.DDP5.1.Atmos.x264-SMURF", + "indexerFlags": 1, + "quality": { + "quality": { + "id": 3, + "name": "WEBDL-1080p", + "source": "webdl", + "resolution": 1080, + "modifier": "none" + }, + "revision": { + "version": 1, + "real": 0, + "isRepack": false + } + }, + "mediaInfo": { + "audioBitrate": 768000, + "audioChannels": 5.1, + "audioCodec": "EAC3 Atmos", + "audioLanguages": "eng", + "audioStreamCount": 1, + "videoBitDepth": 8, + "videoBitrate": 0, + "videoCodec": "x264", + "videoDynamicRangeType": "", + "videoFps": 23.976, + "resolution": "1920x816", + "runTime": "2:22:39", + "scanType": "Progressive", + "subtitles": "eng/eng/bul/cze/dan/spa/spa/fin/hrv/hun/mac/dut/nor/pol/por/por/rum/slo/slv/srp/swe/spa" + }, + "originalFilePath": "Fantastic.Beasts.The.Secrets.of.Dumbledore.2022.1080p.HMAX.WEBRip.DDP5.1.Atmos.x264-SMURF/Fantastic.Beasts.The.Secrets.of.Dumbledore.2022.1080p.HMAX.WEB-DL.DDP5.1.Atmos.H.264-SMURF.mkv", + "qualityCutoffNotMet": false, + "languages": [ + { + "id": 1, + "name": "English" + } + ], + "releaseGroup": "SMURF", + "edition": "", + "id": 223 + }, + "collection": { + "name": "Fantastic Beasts Collection", + "tmdbId": 435259, + "images": [] + }, + "id": 121 + }, + { + "title": "Doctor Strange in the Multiverse of Madness", + "originalTitle": "Doctor Strange in the Multiverse of Madness", + "originalLanguage": { + "id": 1, + "name": "English" + }, + "alternateTitles": [ + { + "sourceType": "tmdb", + "movieId": 153, + "title": "Doctor Strange 2", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1504 + }, + { + "sourceType": "tmdb", + "movieId": 153, + "title": "Доктор Стрэндж 2", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 11, + "name": "Russian" + }, + "id": 1505 + }, + { + "sourceType": "tmdb", + "movieId": 153, + "title": "Doutor Estranho 2", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1506 + }, + { + "sourceType": "tmdb", + "movieId": 153, + "title": "Doctor Strange v multivesmíre šialenstva", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1507 + }, + { + "sourceType": "tmdb", + "movieId": 153, + "title": "Doctor Strange 2: El multiverso de la locura", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 3, + "name": "Spanish" + }, + "id": 1508 + }, + { + "sourceType": "tmdb", + "movieId": 153, + "title": "মহাবিশ্বের পাগলামিতে অদ্ভুত চিকিৎসক", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1510 + }, + { + "sourceType": "tmdb", + "movieId": 153, + "title": "จอมเวทย์มหากาฬ ในมัลติเวิร์สมหาภัย", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 28, + "name": "Thai" + }, + "id": 1511 + }, + { + "sourceType": "tmdb", + "movieId": 153, + "title": "Marvel Studios' Doctor Strange in the Multiverse of Madness", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1512 + }, + { + "sourceType": "tmdb", + "movieId": 153, + "title": "Doctor Strange en el Multiverso de la Locura de Marvel Studios", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 3, + "name": "Spanish" + }, + "id": 1513 + }, + { + "sourceType": "tmdb", + "movieId": 153, + "title": "Doktors Streindžs neprāta multivisumā", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1514 + }, + { + "sourceType": "tmdb", + "movieId": 153, + "title": "Doctor Strange in the Multitude of Madness 3D", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1515 + }, + { + "sourceType": "tmdb", + "movieId": 153, + "title": "닥터 스트레인지 2", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1531 + }, + { + "sourceType": "tmdb", + "movieId": 153, + "title": "奇異博士2: 失控多元宇宙", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1532 + }, + { + "sourceType": "tmdb", + "movieId": 153, + "title": "Ο Δόκτωρ Στρέιντζ στο πολυσύμπαν της τρέλας", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1544 + }, + { + "sourceType": "tmdb", + "movieId": 153, + "title": "Doktor Streync 2: Multikainatın Dəliliklərində", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1566 + }, + { + "sourceType": "tmdb", + "movieId": 153, + "title": "Doctor Strange 2 - The Multiverse of Madness", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1664 + }, + { + "sourceType": "tmdb", + "movieId": 153, + "title": "奇异博士2:疯狂多元宇宙", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1665 + }, + { + "sourceType": "tmdb", + "movieId": 153, + "title": "奇異博士2:失控多重宇宙", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1667 + }, + { + "sourceType": "tmdb", + "movieId": 153, + "title": "Doutor Estranho no Multiverso da Loucura (2022)", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1668 + }, + { + "sourceType": "tmdb", + "movieId": 153, + "title": "Doctor Strange: Đa Vũ Trụ Hỗn Loạn", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1766 + }, + { + "sourceType": "tmdb", + "movieId": 153, + "title": "Բժիշկ Սթրենջը Խելագարության Բազմաշխարհում", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1772 + }, + { + "sourceType": "tmdb", + "movieId": 153, + "title": "Stellar Vortex", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1773 + }, + { + "sourceType": "tmdb", + "movieId": 153, + "title": "Doktor Strange 2", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 17, + "name": "Turkish" + }, + "id": 1777 + }, + { + "sourceType": "tmdb", + "movieId": 153, + "title": "დოქტორი სტრეინჯი სიგიჟის მრავალსამყაროში", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1834 + }, + { + "sourceType": "tmdb", + "movieId": 153, + "title": "დოქტორი სტრეინჯი სიგიჟის მულტისამყაროში", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1835 + }, + { + "sourceType": "tmdb", + "movieId": 153, + "title": "마블 닥터 스트레인지: 대혼돈의 멀티버스", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1836 + }, + { + "sourceType": "tmdb", + "movieId": 153, + "title": "닥터 스트레인지 대혼돈의 멀티버스", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1837 + } + ], + "secondaryYearSourceId": 0, + "sortTitle": "doctor strange in multiverse madness", + "sizeOnDisk": 9961899948, + "status": "released", + "overview": "Doctor Strange, with the help of mystical allies both old and new, traverses the mind-bending and dangerous alternate realities of the Multiverse to confront a mysterious new adversary.", + "inCinemas": "2022-05-04T00:00:00Z", + "physicalRelease": "2022-07-26T00:00:00Z", + "digitalRelease": "2022-06-22T00:00:00Z", + "images": [ + { + "coverType": "poster", + "url": "https://image.tmdb.org/t/p/original/9Gtg2DzBhmYamXBS1hKAhiwbBKS.jpg" + }, + { + "coverType": "fanart", + "url": "https://image.tmdb.org/t/p/original/wcKFYIiVDvRURrzglV9kGu7fpfY.jpg" + } + ], + "website": "https://www.marvel.com/movies/doctor-strange-in-the-multiverse-of-madness", + "year": 2022, + "hasFile": true, + "youTubeTrailerId": "Rf8LAYJSOL8", + "studio": "Marvel Studios", + "path": "/movies/Doctor Strange in the Multiverse of Madness (2022)", + "qualityProfileId": 1, + "monitored": true, + "minimumAvailability": "released", + "isAvailable": true, + "folderName": "/movies/Doctor Strange in the Multiverse of Madness (2022)", + "runtime": 126, + "cleanTitle": "doctorstrangeinmultiversemadness", + "imdbId": "tt9419884", + "tmdbId": 453395, + "titleSlug": "453395", + "certification": "PG-13", + "genres": [ + "Fantasy", + "Action", + "Adventure" + ], + "tags": [], + "added": "2022-05-03T20:20:14Z", + "ratings": { + "imdb": { + "votes": 336918, + "value": 7, + "type": "user" + }, + "tmdb": { + "votes": 5076, + "value": 7.5, + "type": "user" + }, + "metacritic": { + "votes": 0, + "value": 60, + "type": "user" + }, + "rottenTomatoes": { + "votes": 0, + "value": 74, + "type": "user" + } + }, + "movieFile": { + "movieId": 153, + "relativePath": "Doctor Strange in the Multiverse of Madness (2022) WEBRip-1080p.mkv", + "path": "/movies/Doctor Strange in the Multiverse of Madness (2022)/Doctor Strange in the Multiverse of Madness (2022) WEBRip-1080p.mkv", + "size": 9961899948, + "dateAdded": "2022-06-22T00:45:05Z", + "sceneName": "Doctor.Strange.in.the.Multiverse.of.Madness.2022.1080p.WEBRip.DDP5.1.Atmos.x264-NOGRP", + "indexerFlags": 1, + "quality": { + "quality": { + "id": 15, + "name": "WEBRip-1080p", + "source": "webrip", + "resolution": 1080, + "modifier": "none" + }, + "revision": { + "version": 1, + "real": 0, + "isRepack": false + } + }, + "mediaInfo": { + "audioBitrate": 768000, + "audioChannels": 5.1, + "audioCodec": "EAC3 Atmos", + "audioLanguages": "eng", + "audioStreamCount": 1, + "videoBitDepth": 8, + "videoBitrate": 0, + "videoCodec": "x264", + "videoDynamicRangeType": "", + "videoFps": 23.976, + "resolution": "1920x804", + "runTime": "2:06:31", + "scanType": "Progressive", + "subtitles": "eng/eng" + }, + "originalFilePath": "Doctor.Strange.in.the.Multiverse.of.Madness.2022.1080p.WEBRip.DDP5.1.Atmos.x264-NOGRP/Doctor.Strange.in.the.Multiverse.of.Madness.2022.1080p.WEBRip.DDP5.1.Atmos.x264-NOGRP.mkv", + "qualityCutoffNotMet": false, + "languages": [ + { + "id": 1, + "name": "English" + } + ], + "releaseGroup": "NOGRP", + "edition": "", + "id": 224 + }, + "collection": { + "name": "Doctor Strange Collection", + "tmdbId": 618529, + "images": [] + }, + "id": 153 + }, + { + "title": "Top Gun: Maverick", + "originalTitle": "Top Gun: Maverick", + "originalLanguage": { + "id": 1, + "name": "English" + }, + "alternateTitles": [ + { + "sourceType": "tmdb", + "movieId": 160, + "title": "Лучший стрелок 2", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 11, + "name": "Russian" + }, + "id": 1611 + }, + { + "sourceType": "tmdb", + "movieId": 160, + "title": "Top Gun 2", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1612 + }, + { + "sourceType": "tmdb", + "movieId": 160, + "title": "Кращий стрілець 2", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1614 + }, + { + "sourceType": "tmdb", + "movieId": 160, + "title": "Top Gun 2 - Maverick", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 4, + "name": "German" + }, + "id": 1615 + }, + { + "sourceType": "tmdb", + "movieId": 160, + "title": "Phi Công Siêu Đẳng Maverick", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1621 + }, + { + "sourceType": "tmdb", + "movieId": 160, + "title": "Кращий стрілець: Меверік", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1623 + }, + { + "sourceType": "tmdb", + "movieId": 160, + "title": "壮志凌云2:独行侠", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1671 + }, + { + "sourceType": "tmdb", + "movieId": 160, + "title": "Топ Ган: Мэверик", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 11, + "name": "Russian" + }, + "id": 1729 + }, + { + "sourceType": "tmdb", + "movieId": 160, + "title": "توب قن: مافريك", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1763 + }, + { + "sourceType": "tmdb", + "movieId": 160, + "title": "Top Gun: Sayohatchi", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1764 + }, + { + "sourceType": "tmdb", + "movieId": 160, + "title": "Топ ган: Маверик", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1765 + } + ], + "secondaryYearSourceId": 0, + "sortTitle": "top gun maverick", + "sizeOnDisk": 0, + "status": "released", + "overview": "After more than thirty years of service as one of the Navy’s top aviators, and dodging the advancement in rank that would ground him, Pete “Maverick” Mitchell finds himself training a detachment of TOP GUN graduates for a specialized mission the likes of which no living pilot has ever seen.", + "inCinemas": "2022-05-24T00:00:00Z", + "physicalRelease": "2022-10-05T00:00:00Z", + "digitalRelease": "2022-08-08T00:00:00Z", + "images": [ + { + "coverType": "poster", + "url": "https://image.tmdb.org/t/p/original/62HCnUTziyWcpDaBO2i1DX17ljH.jpg" + }, + { + "coverType": "fanart", + "url": "https://image.tmdb.org/t/p/original/odJ4hx6g6vBt4lBWKFD1tI8WS4x.jpg" + } + ], + "website": "https://www.topgunmovie.com", + "year": 2022, + "hasFile": false, + "youTubeTrailerId": "giXco2jaZ_4", + "studio": "Paramount", + "path": "/movies/Top Gun Maverick (2022)", + "qualityProfileId": 1, + "monitored": true, + "minimumAvailability": "released", + "isAvailable": true, + "folderName": "/movies/Top Gun Maverick (2022)", + "runtime": 131, + "cleanTitle": "topgunmaverick", + "imdbId": "tt1745960", + "tmdbId": 361743, + "titleSlug": "361743", + "certification": "PG-13", + "genres": [ + "Action", + "Drama" + ], + "tags": [], + "added": "2022-06-07T19:53:34Z", + "ratings": { + "imdb": { + "votes": 255677, + "value": 8.6, + "type": "user" + }, + "tmdb": { + "votes": 1811, + "value": 8.3, + "type": "user" + }, + "metacritic": { + "votes": 0, + "value": 78, + "type": "user" + }, + "rottenTomatoes": { + "votes": 0, + "value": 97, + "type": "user" + } + }, + "collection": { + "name": "Top Gun Collection", + "tmdbId": 531330, + "images": [] + }, + "id": 160 + }, + { + "title": "Jurassic World Dominion", + "originalTitle": "Jurassic World Dominion", + "originalLanguage": { + "id": 1, + "name": "English" + }, + "alternateTitles": [ + { + "sourceType": "tmdb", + "movieId": 176, + "title": "Jurassic Park 6", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1872 + }, + { + "sourceType": "tmdb", + "movieId": 176, + "title": "Jurassic World 3", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1873 + }, + { + "sourceType": "tmdb", + "movieId": 176, + "title": "쥬라기 공원 3", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1874 + }, + { + "sourceType": "tmdb", + "movieId": 176, + "title": "Мир Юрского периода 3", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 11, + "name": "Russian" + }, + "id": 1875 + }, + { + "sourceType": "tmdb", + "movieId": 176, + "title": "Jurassic World 3: Dominion", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1876 + }, + { + "sourceType": "tmdb", + "movieId": 176, + "title": "Jurassic World 3: Dominio", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1877 + }, + { + "sourceType": "tmdb", + "movieId": 176, + "title": "Світ Юрського періоду 3", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1878 + }, + { + "sourceType": "tmdb", + "movieId": 176, + "title": "Світ Юрського періоду: Домініон", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1879 + }, + { + "sourceType": "tmdb", + "movieId": 176, + "title": "Мир Юрского периода: Власть", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 11, + "name": "Russian" + }, + "id": 1880 + }, + { + "sourceType": "tmdb", + "movieId": 176, + "title": "จูราสสิค เวิลด์ ทวงคืนอาณาจักร", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 28, + "name": "Thai" + }, + "id": 1881 + }, + { + "sourceType": "tmdb", + "movieId": 176, + "title": "La monde jurassique : Domination", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1882 + }, + { + "sourceType": "tmdb", + "movieId": 176, + "title": "Monde jurassique : la domination", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1883 + }, + { + "sourceType": "tmdb", + "movieId": 176, + "title": "ジュラシックワールドドミニオン", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1884 + }, + { + "sourceType": "tmdb", + "movieId": 176, + "title": "Thế Giới Khủng Long: Lãnh Địa", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1885 + }, + { + "sourceType": "tmdb", + "movieId": 176, + "title": "Jurassic World 3 - Dominion (2022)", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1887 + }, + { + "sourceType": "tmdb", + "movieId": 176, + "title": "Jurassic World", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 2, + "name": "French" + }, + "id": 1888 + }, + { + "sourceType": "tmdb", + "movieId": 176, + "title": "侏儸紀世界:統霸天下", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1891 + } + ], + "secondaryYearSourceId": 0, + "sortTitle": "jurassic world dominion", + "sizeOnDisk": 3288297053, + "status": "released", + "overview": "Four years after Isla Nublar was destroyed, dinosaurs now live—and hunt—alongside humans all over the world. This fragile balance will reshape the future and determine, once and for all, whether human beings are to remain the apex predators on a planet they now share with history’s most fearsome creatures.", + "inCinemas": "2022-06-01T00:00:00Z", + "physicalRelease": "2022-08-25T00:00:00Z", + "digitalRelease": "2022-07-14T00:00:00Z", + "images": [ + { + "coverType": "poster", + "url": "https://image.tmdb.org/t/p/original/kAVRgw7GgK1CfYEJq8ME6EvRIgU.jpg" + }, + { + "coverType": "fanart", + "url": "https://image.tmdb.org/t/p/original/9eAn20y26wtB3aet7w9lHjuSgZ3.jpg" + } + ], + "website": "https://www.jurassicworld.com", + "year": 2022, + "hasFile": true, + "youTubeTrailerId": "DtQycgMD4HQ", + "studio": "Universal Pictures", + "path": "/movies/Jurassic World Dominion (2022)", + "qualityProfileId": 4, + "monitored": true, + "minimumAvailability": "released", + "isAvailable": true, + "folderName": "/movies/Jurassic World Dominion (2022)", + "runtime": 147, + "cleanTitle": "jurassicworlddominion", + "imdbId": "tt8041270", + "tmdbId": 507086, + "titleSlug": "507086", + "certification": "PG-13", + "genres": [ + "Adventure", + "Action", + "Science Fiction" + ], + "tags": [], + "added": "2022-08-03T22:37:52Z", + "ratings": { + "imdb": { + "votes": 105728, + "value": 5.7, + "type": "user" + }, + "tmdb": { + "votes": 2532, + "value": 7.1, + "type": "user" + }, + "metacritic": { + "votes": 0, + "value": 38, + "type": "user" + }, + "rottenTomatoes": { + "votes": 0, + "value": 30, + "type": "user" + } + }, + "movieFile": { + "movieId": 176, + "relativePath": "Jurassic World Dominion (2022) Bluray-1080p.mp4", + "path": "/movies/Jurassic World Dominion (2022)/Jurassic World Dominion (2022) Bluray-1080p.mp4", + "size": 3288297053, + "dateAdded": "2022-08-03T22:40:58Z", + "sceneName": "Jurassic.World.3.Dominion.2022.EXTENDED.1080p.BluRay.H264.AAC-RARBG", + "indexerFlags": 1, + "quality": { + "quality": { + "id": 7, + "name": "Bluray-1080p", + "source": "bluray", + "resolution": 1080, + "modifier": "none" + }, + "revision": { + "version": 1, + "real": 0, + "isRepack": false + } + }, + "mediaInfo": { + "audioBitrate": 224000, + "audioChannels": 5.1, + "audioCodec": "AAC", + "audioLanguages": "eng", + "audioStreamCount": 1, + "videoBitDepth": 8, + "videoBitrate": 2497768, + "videoCodec": "x264", + "videoDynamicRangeType": "", + "videoFps": 23.976, + "resolution": "1920x960", + "runTime": "2:40:40", + "scanType": "Progressive", + "subtitles": "" + }, + "originalFilePath": "Jurassic.World.3.Dominion.2022.EXTENDED.1080p.BluRay.H264.AAC-RARBG/Jurassic.World.3.Dominion.2022.EXTENDED.1080p.BluRay.H264.AAC-RARBG.mp4", + "qualityCutoffNotMet": false, + "languages": [ + { + "id": 1, + "name": "English" + } + ], + "releaseGroup": "RARBG", + "edition": "EXTENDED", + "id": 232 + }, + "collection": { + "name": "Jurassic Park Collection", + "tmdbId": 328, + "images": [] + }, + "id": 176 + }, + { + "title": "Lightyear", + "originalTitle": "Lightyear", + "originalLanguage": { + "id": 1, + "name": "English" + }, + "alternateTitles": [ + { + "sourceType": "tmdb", + "movieId": 157, + "title": "Баз Светлинна година", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 29, + "name": "Bulgarian" + }, + "id": 1580 + }, + { + "sourceType": "tmdb", + "movieId": 157, + "title": "Buzz Astral", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 12, + "name": "Polish" + }, + "id": 1581 + }, + { + "sourceType": "tmdb", + "movieId": 157, + "title": "Buzz Lightyear", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 18, + "name": "Portuguese" + }, + "id": 1582 + }, + { + "sourceType": "tmdb", + "movieId": 157, + "title": "Lightyear - Cảnh Sát Vũ Trụ", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1583 + }, + { + "sourceType": "tmdb", + "movieId": 157, + "title": "Bazs Gaismasgads", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1584 + }, + { + "sourceType": "tmdb", + "movieId": 157, + "title": "光年正傳", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1598 + }, + { + "sourceType": "tmdb", + "movieId": 157, + "title": "บัซ ไลท์เยียร์", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 28, + "name": "Thai" + }, + "id": 1633 + }, + { + "sourceType": "tmdb", + "movieId": 157, + "title": "光年正传", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1635 + }, + { + "sourceType": "tmdb", + "movieId": 157, + "title": "Buzz l'Éclair", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 2, + "name": "French" + }, + "id": 1648 + }, + { + "sourceType": "tmdb", + "movieId": 157, + "title": "Rakeťák", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1649 + }, + { + "sourceType": "tmdb", + "movieId": 157, + "title": "Svjetlosni", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1650 + }, + { + "sourceType": "tmdb", + "movieId": 157, + "title": "लाइटईयर", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1651 + }, + { + "sourceType": "tmdb", + "movieId": 157, + "title": "שנות-אור", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1652 + }, + { + "sourceType": "tmdb", + "movieId": 157, + "title": "バズ・ライトイヤー", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1653 + }, + { + "sourceType": "tmdb", + "movieId": 157, + "title": "Lightyear - La vera storia di Buzz", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 5, + "name": "Italian" + }, + "id": 1654 + }, + { + "sourceType": "tmdb", + "movieId": 157, + "title": "Базз Лайтер", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 11, + "name": "Russian" + }, + "id": 1655 + }, + { + "sourceType": "tmdb", + "movieId": 157, + "title": "버즈 라이트이어", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1656 + }, + { + "sourceType": "tmdb", + "movieId": 157, + "title": "Баз Светлосни", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1657 + }, + { + "sourceType": "tmdb", + "movieId": 157, + "title": "巴斯光年", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1658 + }, + { + "sourceType": "tmdb", + "movieId": 157, + "title": "Işıkyılı", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 17, + "name": "Turkish" + }, + "id": 1659 + }, + { + "sourceType": "tmdb", + "movieId": 157, + "title": "Bazz Layter", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1660 + }, + { + "sourceType": "tmdb", + "movieId": 157, + "title": "ბაზ ლაითერი", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1829 + }, + { + "sourceType": "tmdb", + "movieId": 157, + "title": "სათამაშოების ისტორია: ბაზ ლაითერი", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1830 + } + ], + "secondaryYearSourceId": 0, + "sortTitle": "lightyear", + "sizeOnDisk": 4162236849, + "status": "released", + "overview": "Legendary Space Ranger Buzz Lightyear embarks on an intergalactic adventure alongside a group of ambitious recruits and his robot companion Sox.", + "inCinemas": "2022-06-15T00:00:00Z", + "physicalRelease": "2022-09-29T00:00:00Z", + "digitalRelease": "2022-08-03T00:00:00Z", + "images": [ + { + "coverType": "poster", + "url": "https://image.tmdb.org/t/p/original/ox4goZd956BxqJH6iLwhWPL9ct4.jpg" + }, + { + "coverType": "fanart", + "url": "https://image.tmdb.org/t/p/original/nW5fUbldp1DYf2uQ3zJTUdachOu.jpg" + } + ], + "website": "https://movies.disney.com/lightyear", + "year": 2022, + "hasFile": true, + "youTubeTrailerId": "0ir3Z8hLeHc", + "studio": "Walt Disney Pictures", + "path": "/movies/Lightyear (2022)", + "qualityProfileId": 1, + "monitored": true, + "minimumAvailability": "announced", + "isAvailable": true, + "folderName": "/movies/Lightyear (2022)", + "runtime": 105, + "cleanTitle": "lightyear", + "imdbId": "tt10298810", + "tmdbId": 718789, + "titleSlug": "718789", + "certification": "PG", + "genres": [ + "Animation", + "Science Fiction", + "Adventure" + ], + "tags": [], + "added": "2022-05-25T11:01:52Z", + "ratings": { + "imdb": { + "votes": 57059, + "value": 5.5, + "type": "user" + }, + "tmdb": { + "votes": 1405, + "value": 7.362, + "type": "user" + }, + "metacritic": { + "votes": 0, + "value": 60, + "type": "user" + }, + "rottenTomatoes": { + "votes": 0, + "value": 75, + "type": "user" + } + }, + "movieFile": { + "movieId": 157, + "relativePath": "Lightyear (2022) WEBRip-1080p.mkv", + "path": "/movies/Lightyear (2022)/Lightyear (2022) WEBRip-1080p.mkv", + "size": 4162236849, + "dateAdded": "2022-07-14T12:39:18Z", + "sceneName": "Lightyear.2022.1080p.WEBRip.DD5.1.x264-EVO[TGx]", + "indexerFlags": 1, + "quality": { + "quality": { + "id": 15, + "name": "WEBRip-1080p", + "source": "webrip", + "resolution": 1080, + "modifier": "none" + }, + "revision": { + "version": 1, + "real": 0, + "isRepack": false + } + }, + "mediaInfo": { + "audioBitrate": 384000, + "audioChannels": 5.1, + "audioCodec": "AC3", + "audioLanguages": "eng", + "audioStreamCount": 1, + "videoBitDepth": 8, + "videoBitrate": 0, + "videoCodec": "x264", + "videoDynamicRangeType": "", + "videoFps": 23.976, + "resolution": "1920x804", + "runTime": "1:45:04", + "scanType": "Progressive", + "subtitles": "" + }, + "originalFilePath": "Lightyear.2022.1080p.WEBRip.DD5.1.x264-EVO[TGx]/Lightyear.2022.1080p.WEBRip.DD5.1.x264-EVO.mkv", + "qualityCutoffNotMet": false, + "languages": [ + { + "id": 1, + "name": "English" + } + ], + "releaseGroup": "EVO", + "edition": "", + "id": 227 + }, + "id": 157 + }, + { + "title": "Minions: The Rise of Gru", + "originalTitle": "Minions: The Rise of Gru", + "originalLanguage": { + "id": 1, + "name": "English" + }, + "alternateTitles": [ + { + "sourceType": "tmdb", + "movieId": 165, + "title": "미니언즈: 라이즈 오브 그루", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1679 + }, + { + "sourceType": "tmdb", + "movieId": 165, + "title": "Miljenici: Uspon Grua", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1680 + }, + { + "sourceType": "tmdb", + "movieId": 165, + "title": "Käsilased 2", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1681 + }, + { + "sourceType": "tmdb", + "movieId": 165, + "title": "Minions 2: Η Άνοδος του Γκρου", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1682 + }, + { + "sourceType": "tmdb", + "movieId": 165, + "title": "Minioni 2", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1683 + }, + { + "sourceType": "tmdb", + "movieId": 165, + "title": "Minions: Historien om Gru", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 15, + "name": "Norwegian" + }, + "id": 1684 + }, + { + "sourceType": "tmdb", + "movieId": 165, + "title": "Mínimos 2: A Ascensão de Gru", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 18, + "name": "Portuguese" + }, + "id": 1685 + }, + { + "sourceType": "tmdb", + "movieId": 165, + "title": "Миньоны: Грювитация", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 11, + "name": "Russian" + }, + "id": 1686 + }, + { + "sourceType": "tmdb", + "movieId": 165, + "title": "Малци: Успон Груа", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1687 + }, + { + "sourceType": "tmdb", + "movieId": 165, + "title": "Mimoni: Zloduch prichádza", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1688 + }, + { + "sourceType": "tmdb", + "movieId": 165, + "title": "Minions: El origen de Gru", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 3, + "name": "Spanish" + }, + "id": 1689 + }, + { + "sourceType": "tmdb", + "movieId": 165, + "title": "Minioner: Berättelsen om Gru", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1690 + }, + { + "sourceType": "tmdb", + "movieId": 165, + "title": "Minions 2: Come Gru diventa cattivissimo", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 5, + "name": "Italian" + }, + "id": 1691 + }, + { + "sourceType": "tmdb", + "movieId": 165, + "title": "Minions 2: Historien om Gru", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1692 + }, + { + "sourceType": "tmdb", + "movieId": 165, + "title": "Minions 2", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1693 + }, + { + "sourceType": "tmdb", + "movieId": 165, + "title": "Minions 2: Nace un villano", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1694 + }, + { + "sourceType": "tmdb", + "movieId": 165, + "title": "Minions - Auf der Suche nach dem Mini-Boss", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 4, + "name": "German" + }, + "id": 1695 + }, + { + "sourceType": "tmdb", + "movieId": 165, + "title": "Minions 2: The Rise of Gru", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 7, + "name": "Dutch" + }, + "id": 1696 + }, + { + "sourceType": "tmdb", + "movieId": 165, + "title": "迷你兵團 2", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1697 + }, + { + "sourceType": "tmdb", + "movieId": 165, + "title": "Minions: Hoe Gru superschurk were", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 7, + "name": "Dutch" + }, + "id": 1698 + }, + { + "sourceType": "tmdb", + "movieId": 165, + "title": "มินเนี่ยน 2", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 28, + "name": "Thai" + }, + "id": 1699 + }, + { + "sourceType": "tmdb", + "movieId": 165, + "title": "Minyonlar 2: Gru'nun Yükselişi", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 17, + "name": "Turkish" + }, + "id": 1726 + }, + { + "sourceType": "tmdb", + "movieId": 165, + "title": "Minions: Nace un villano", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1734 + }, + { + "sourceType": "tmdb", + "movieId": 165, + "title": "Մինիոններ. Գրուի վերելքը", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1787 + }, + { + "sourceType": "tmdb", + "movieId": 165, + "title": "Minions: KEBANGKITAN GRU", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1838 + }, + { + "sourceType": "tmdb", + "movieId": 165, + "title": "미니언즈 2", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1846 + }, + { + "sourceType": "tmdb", + "movieId": 165, + "title": "小黄人大眼萌:神偷奶爸前传", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1860 + } + ], + "secondaryYearSourceId": 0, + "sortTitle": "minions rise gru", + "sizeOnDisk": 6621540111, + "status": "released", + "overview": "A fanboy of a supervillain supergroup known as the Vicious 6, Gru hatches a plan to become evil enough to join them, with the backup of his followers, the Minions.", + "inCinemas": "2022-06-29T00:00:00Z", + "physicalRelease": "2022-09-15T00:00:00Z", + "digitalRelease": "2022-08-02T00:00:00Z", + "images": [ + { + "coverType": "poster", + "url": "https://image.tmdb.org/t/p/original/wKiOkZTN9lUUUNZLmtnwubZYONg.jpg" + }, + { + "coverType": "fanart", + "url": "https://image.tmdb.org/t/p/original/nmGWzTLMXy9x7mKd8NKPLmHtWGa.jpg" + } + ], + "website": "https://www.minionsmovie.com/", + "year": 2022, + "hasFile": true, + "youTubeTrailerId": "HhIl_XJ-OGA", + "studio": "Universal Pictures", + "path": "/movies/Minions The Rise of Gru (2022)", + "qualityProfileId": 1, + "monitored": true, + "minimumAvailability": "released", + "isAvailable": true, + "folderName": "/movies/Minions The Rise of Gru (2022)", + "runtime": 87, + "cleanTitle": "minionsrisegru", + "imdbId": "tt5113044", + "tmdbId": 438148, + "titleSlug": "438148", + "certification": "PG", + "genres": [ + "Family", + "Animation", + "Adventure" + ], + "tags": [], + "added": "2022-06-27T13:00:47Z", + "ratings": { + "imdb": { + "votes": 29174, + "value": 6.8, + "type": "user" + }, + "tmdb": { + "votes": 1038, + "value": 7.8, + "type": "user" + }, + "metacritic": { + "votes": 0, + "value": 56, + "type": "user" + }, + "rottenTomatoes": { + "votes": 0, + "value": 70, + "type": "user" + } + }, + "movieFile": { + "movieId": 165, + "relativePath": "Minions The Rise of Gru (2022) WEBRip-1080p.mkv", + "path": "/movies/Minions The Rise of Gru (2022)/Minions The Rise of Gru (2022) WEBRip-1080p.mkv", + "size": 6621540111, + "dateAdded": "2022-08-02T07:18:57Z", + "sceneName": "Minions.The.Rise.of.Gru.2022.1080p.AMZN.WEBRip.DDP5.1.Atmos.x264-NOGRP", + "indexerFlags": 1, + "quality": { + "quality": { + "id": 15, + "name": "WEBRip-1080p", + "source": "webrip", + "resolution": 1080, + "modifier": "none" + }, + "revision": { + "version": 1, + "real": 0, + "isRepack": false + } + }, + "mediaInfo": { + "audioBitrate": 768000, + "audioChannels": 5.1, + "audioCodec": "EAC3 Atmos", + "audioLanguages": "eng", + "audioStreamCount": 1, + "videoBitDepth": 8, + "videoBitrate": 0, + "videoCodec": "x264", + "videoDynamicRangeType": "", + "videoFps": 23.976, + "resolution": "1920x800", + "runTime": "1:27:35", + "scanType": "Progressive", + "subtitles": "eng/eng/fre" + }, + "originalFilePath": "Minions.The.Rise.of.Gru.2022.1080p.AMZN.WEBRip.DDP5.1.Atmos.x264-NOGRP/Minions.The.Rise.of.Gru.2022.1080p.AMZN.WEBRip.DDP5.1.Atmos.x264-NOGRP.mkv", + "qualityCutoffNotMet": false, + "languages": [ + { + "id": 1, + "name": "English" + } + ], + "releaseGroup": "NOGRP", + "edition": "", + "id": 230 + }, + "collection": { + "name": "Minions Collection", + "tmdbId": 544669, + "images": [] + }, + "id": 165 + }, + { + "title": "Thor: Love and Thunder", + "originalTitle": "Thor: Love and Thunder", + "originalLanguage": { + "id": 1, + "name": "English" + }, + "alternateTitles": [ + { + "sourceType": "tmdb", + "movieId": 168, + "title": "Thor 4", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1735 + }, + { + "sourceType": "tmdb", + "movieId": 168, + "title": "雷神4:爱与雷霆", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1736 + }, + { + "sourceType": "tmdb", + "movieId": 168, + "title": "Тор 4", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 11, + "name": "Russian" + }, + "id": 1737 + }, + { + "sourceType": "tmdb", + "movieId": 168, + "title": "থর: প্রেম এবং বজ্রপাত", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1738 + }, + { + "sourceType": "tmdb", + "movieId": 168, + "title": "Marvel Studios' Thor: Love and Thunder", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1739 + }, + { + "sourceType": "tmdb", + "movieId": 168, + "title": "Thor: Love and Thunder de Marvel Studios", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 3, + "name": "Spanish" + }, + "id": 1740 + }, + { + "sourceType": "tmdb", + "movieId": 168, + "title": "Tors: Mīla un Pērkons", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1741 + }, + { + "sourceType": "tmdb", + "movieId": 168, + "title": "Thor : Amour et tonnerre de Marvel Studios", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1742 + }, + { + "sourceType": "tmdb", + "movieId": 168, + "title": "Marvel Studios' Thor: Tình Yêu và Sấm Sét", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1743 + }, + { + "sourceType": "tmdb", + "movieId": 168, + "title": "Thor: amor y trueno", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 3, + "name": "Spanish" + }, + "id": 1745 + }, + { + "sourceType": "tmdb", + "movieId": 168, + "title": "თორი: სიყვარული და ჭექა-ქუხილი", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1746 + }, + { + "sourceType": "tmdb", + "movieId": 168, + "title": "토르 4", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1770 + }, + { + "sourceType": "tmdb", + "movieId": 168, + "title": "Թոր: Սեր և ամպրոպ", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1775 + }, + { + "sourceType": "tmdb", + "movieId": 168, + "title": "תור: אהבה ורעם", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1781 + }, + { + "sourceType": "tmdb", + "movieId": 168, + "title": "Thor: Cinta dan Guntur", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1790 + }, + { + "sourceType": "tmdb", + "movieId": 168, + "title": "마블 토르: 러브 앤 썬더", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1839 + }, + { + "sourceType": "tmdb", + "movieId": 168, + "title": "토르 러브 앤 썬더", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1840 + } + ], + "secondaryYearSourceId": 0, + "sortTitle": "thor love thunder", + "sizeOnDisk": 0, + "status": "inCinemas", + "overview": "After his retirement is interrupted by Gorr the God Butcher, a galactic killer who seeks the extinction of the gods, Thor enlists the help of King Valkyrie, Korg, and ex-girlfriend Jane Foster, who now inexplicably wields Mjolnir as the Mighty Thor. Together they embark upon a harrowing cosmic adventure to uncover the mystery of the God Butcher’s vengeance and stop him before it’s too late.", + "inCinemas": "2022-07-06T00:00:00Z", + "images": [ + { + "coverType": "poster", + "url": "https://image.tmdb.org/t/p/original/pIkRyD18kl4FhoCNQuWxWu5cBLM.jpg" + }, + { + "coverType": "fanart", + "url": "https://image.tmdb.org/t/p/original/p1F51Lvj3sMopG948F5HsBbl43C.jpg" + } + ], + "website": "https://www.marvel.com/movies/thor-love-and-thunder", + "year": 2022, + "hasFile": false, + "youTubeTrailerId": "Go8nTmfrQd8", + "studio": "Marvel Studios", + "path": "/movies/Thor Love and Thunder (2022)", + "qualityProfileId": 1, + "monitored": true, + "minimumAvailability": "released", + "isAvailable": false, + "folderName": "/movies/Thor Love and Thunder (2022)", + "runtime": 119, + "cleanTitle": "thorlovethunder", + "imdbId": "tt10648342", + "tmdbId": 616037, + "titleSlug": "616037", + "certification": "PG-13", + "genres": [ + "Action", + "Adventure", + "Fantasy" + ], + "tags": [], + "added": "2022-07-04T17:04:57Z", + "ratings": { + "imdb": { + "votes": 146274, + "value": 6.8, + "type": "user" + }, + "tmdb": { + "votes": 1695, + "value": 6.8, + "type": "user" + }, + "metacritic": { + "votes": 0, + "value": 57, + "type": "user" + }, + "rottenTomatoes": { + "votes": 0, + "value": 67, + "type": "user" + } + }, + "collection": { + "name": "Thor Collection", + "tmdbId": 131296, + "images": [] + }, + "id": 168 + }, + { + "title": "Sword Art Online the Movie -Progressive- Scherzo of a Deep Night", + "originalTitle": "劇場版 ソードアート・オンライン プログレッシブ 冥き夕闇のスケルツォ", + "originalLanguage": { + "id": 8, + "name": "Japanese" + }, + "alternateTitles": [ + { + "sourceType": "tmdb", + "movieId": 169, + "title": "Sword Art Online Progressive", + "sourceId": 0, + "votes": 0, + "voteCount": 0, + "language": { + "id": 1, + "name": "English" + }, + "id": 1748 + } + ], + "secondaryYearSourceId": 0, + "sortTitle": "sword art online movie progressive scherzo deep night", + "sizeOnDisk": 0, + "status": "announced", + "overview": "", + "inCinemas": "2022-09-10T00:00:00Z", + "images": [ + { + "coverType": "poster", + "url": "https://image.tmdb.org/t/p/original/1L904CSzPCEEpPcUoBWH4cjuGJW.jpg" + } + ], + "website": "", + "year": 2022, + "hasFile": false, + "youTubeTrailerId": "", + "studio": "Aniplex", + "path": "/movies/Sword Art Online the Movie -Progressive- Scherzo of a Deep Night (2022)", + "qualityProfileId": 1, + "monitored": true, + "minimumAvailability": "released", + "isAvailable": false, + "folderName": "/movies/Sword Art Online the Movie -Progressive- Scherzo of a Deep Night (2022)", + "runtime": 0, + "cleanTitle": "swordartonlinemovieprogressivescherzodeepnight", + "tmdbId": 893712, + "titleSlug": "893712", + "genres": [ + "Animation", + "Action", + "Fantasy" + ], + "tags": [], + "added": "2022-07-04T17:05:01Z", + "ratings": { + "tmdb": { + "votes": 0, + "value": 0, + "type": "user" + } + }, + "collection": { + "name": "Sword Art Online the Movie -Progressive-", + "tmdbId": 893713, + "images": [] + }, + "id": 169 + } +] \ No newline at end of file diff --git a/src/modules/common/examples/multipletvshows.json b/src/modules/common/examples/multipletvshows.json new file mode 100644 index 000000000..7857b70ce --- /dev/null +++ b/src/modules/common/examples/multipletvshows.json @@ -0,0 +1,409 @@ +{ + "page": 1, + "totalPages": 2, + "totalResults": 21, + "results": [ + { + "id": 66025, + "firstAirDate": "2016-06-14", + "genreIds": [ + 80, + 18 + ], + "mediaType": "tv", + "name": "Animal Kingdom", + "originCountry": [ + "US" + ], + "originalLanguage": "en", + "originalName": "Animal Kingdom", + "overview": "Un jeune homme de dix-sept ans emménage avec la famille Cody après le décès de sa mère, une fratrie baignant dans la criminalité gouvernée d'une main de maître par la matriarche, Smurf.", + "popularity": 75.653, + "voteAverage": 7.7, + "voteCount": 318, + "backdropPath": "/eQJwfyMqSra10ck8HOoiCrbQR32.jpg", + "posterPath": "/rzvdKrnSRKPFI0pgqMQknDPpRC9.jpg", + "mediaInfo": { + "downloadStatus": [], + "downloadStatus4k": [], + "id": 217, + "mediaType": "tv", + "tmdbId": 66025, + "tvdbId": 304262, + "imdbId": null, + "status": 3, + "status4k": 1, + "createdAt": "2022-08-08T11:06:20.000Z", + "updatedAt": "2022-08-08T11:06:23.000Z", + "lastSeasonChange": "2022-08-08T11:06:20.000Z", + "mediaAddedAt": null, + "serviceId": 0, + "serviceId4k": null, + "externalServiceId": 56, + "externalServiceId4k": null, + "externalServiceSlug": "animal-kingdom-2016", + "externalServiceSlug4k": null, + "ratingKey": null, + "ratingKey4k": null, + "seasons": [], + "serviceUrl": "http://sonarr:8989/series/animal-kingdom-2016" + } + }, + { + "id": 44629, + "mediaType": "movie", + "adult": false, + "genreIds": [ + 18, + 53, + 80, + 9648 + ], + "originalLanguage": "en", + "originalTitle": "Animal Kingdom", + "overview": "Une rue anonyme dans la banlieue de Melbourne. C’est là que vit la famille Cody. Profession: criminels. L’irruption parmi eux de Joshua, un neveu éloigné, offre à la police le moyen de les infiltrer. Il ne reste plus à Joshua qu’à choisir son camp...", + "popularity": 11.839, + "releaseDate": "2010-06-03", + "title": "Animal Kingdom", + "video": false, + "voteAverage": 6.8, + "voteCount": 643, + "backdropPath": "/dxOv6K3LNbZfQaGDyx7Tp94Koy.jpg", + "posterPath": "/qrVjc5JcaujL58SMMW9lqrp3bBX.jpg" + }, + { + "id": 95731, + "firstAirDate": "2020-09-25", + "genreIds": [ + 99 + ], + "mediaType": "tv", + "name": "Au cœur de Disney's Animal Kingdom", + "originCountry": [], + "originalLanguage": "en", + "originalName": "Magic of Disney's Animal Kingdom", + "overview": "Au cœur d’Animal Kingdom narrée par Josh Gad, une célébrité parmi les fans de Disney, nous emmène en coulisses découvrir la magie de deux des animations animalières les plus visitées au monde : le parc à thème de Disney, Animal Kingdom, et The Seas with Nemo & Friends à Epcot. Les spectateurs s’approchent au plus près de créatures parmi les plus rares et les plus belles de la planète et rencontrent les experts en soins animaliers qui ont tissé des liens stupéfiants avec les 5 000 et plus animaux du parc. Chacun des huit épisodes plonge au cœur de l’endroit le plus magique sur Terre, dévoilant les multiples facettes de sa conception et de sa gestion.", + "popularity": 3.367, + "voteAverage": 8, + "voteCount": 4, + "backdropPath": "/gMTMnd54VVAbGiodBqMTGCjM3b2.jpg", + "posterPath": "/gvNTeRAfu4KN3dD5HUO4Nbnri07.jpg" + }, + { + "id": 120862, + "mediaType": "movie", + "adult": false, + "genreIds": [ + 35, + 18, + 10749 + ], + "originalLanguage": "en", + "originalTitle": "The Animal Kingdom", + "overview": "Tom Collier, jeune éditeur, a entretenu une liaison passionnée et intellectuelle avec une dessinatrice, Daisy Sage. Celle-ci ayant mis un terme à leur relation, il a fait la connaissance de Cecilia, qu'il a rapidement décidé d'épouser. Alors que les fiançailles sont annoncées, Daisy, toujours amoureuse, fait son retour, mais trop tard. Le mariage a lieu. Sous l'influence de Cecilia, Tom Collier, qui était un éditeur intègre et exigeant, fait de plus en plus de concessions commerciales. Daisy, elle demeure fidèle à elle-même. Tom Collier, se retrouve a évoluer, par amour pour sa femme, dans un milieu de conventions bourgeoises qui ne l'intéressent pas.", + "popularity": 2.102, + "releaseDate": "1932-12-28", + "title": "The Animal Kingdom", + "video": false, + "voteAverage": 6.3, + "voteCount": 13, + "backdropPath": "/5P1Hx46wvCVx9D9yT8M5rdUIHZB.jpg", + "posterPath": "/3sLWwNvS77xynAGLkbiHVXlO3UH.jpg" + }, + { + "id": 311015, + "mediaType": "movie", + "adult": false, + "genreIds": [ + 99 + ], + "originalLanguage": "en", + "originalTitle": "Disney Parks: Disney's Animal Kingdom", + "overview": "", + "popularity": 1.208, + "releaseDate": "2010-01-01", + "title": "Disney Parks: Disney's Animal Kingdom", + "video": true, + "voteAverage": 9, + "voteCount": 2, + "backdropPath": null, + "posterPath": "/93OEKY5vnKqGFbOyHtUAdcEz8NV.jpg" + }, + { + "id": 291774, + "mediaType": "movie", + "adult": false, + "genreIds": [], + "originalLanguage": "en", + "originalTitle": "Kenya 3D: Animal Kingdom", + "overview": "", + "popularity": 0.6, + "releaseDate": "2013-03-08", + "title": "Kenya 3D: Animal Kingdom", + "video": false, + "voteAverage": 0, + "voteCount": 0, + "backdropPath": null, + "posterPath": null + }, + { + "id": 640253, + "mediaType": "movie", + "adult": false, + "genreIds": [], + "originalLanguage": "it", + "originalTitle": "Animal Kingdom", + "overview": "", + "popularity": 0.6, + "releaseDate": "2016-11-12", + "title": "Animal Kingdom", + "video": false, + "voteAverage": 0, + "voteCount": 0, + "backdropPath": null, + "posterPath": "/vJFK5cCcIh4X4op0oeK5iY2ibPv.jpg" + }, + { + "id": 507434, + "mediaType": "movie", + "adult": false, + "genreIds": [ + 27 + ], + "originalLanguage": "en", + "originalTitle": "Animal Kingdom", + "overview": "", + "popularity": 0.6, + "releaseDate": "2017-02-25", + "title": "Animal Kingdom", + "video": false, + "voteAverage": 0, + "voteCount": 0, + "backdropPath": "/8QxSJRLLw2m8ymrFsC2xJ26yd1n.jpg", + "posterPath": "/s77Q92boNGgkT2J5se3gwq5N8Xp.jpg" + }, + { + "id": 775877, + "mediaType": "movie", + "adult": false, + "genreIds": [], + "originalLanguage": "en", + "originalTitle": "Disney's Animal Kingdom", + "overview": "", + "popularity": 0.6, + "releaseDate": "2004-05-12", + "title": "Disney's Animal Kingdom", + "video": true, + "voteAverage": 0, + "voteCount": 0, + "backdropPath": null, + "posterPath": null + }, + { + "id": 318575, + "mediaType": "movie", + "adult": false, + "genreIds": [ + 99 + ], + "originalLanguage": "en", + "originalTitle": "Nature: Love in the Animal Kingdom", + "overview": "", + "popularity": 0.655, + "releaseDate": "2013-11-06", + "title": "Nature: Love in the Animal Kingdom", + "video": true, + "voteAverage": 9.5, + "voteCount": 2, + "backdropPath": "/vx2dfrXPTn0dKoyIqCEgrGvzwkd.jpg", + "posterPath": "/1fd53UCxtLAItNI5jMtVetFuw6v.jpg" + }, + { + "id": 743266, + "mediaType": "movie", + "adult": false, + "genreIds": [], + "originalLanguage": "en", + "originalTitle": "Animal Kingdom: Great Are Thy Works", + "overview": "", + "popularity": 0.6, + "releaseDate": "1993-01-01", + "title": "Animal Kingdom: Great Are Thy Works", + "video": false, + "voteAverage": 0, + "voteCount": 0, + "backdropPath": null, + "posterPath": "/vjnsGLvymjG7dAIbjwzgFCdbhl6.jpg" + }, + { + "id": 828152, + "mediaType": "movie", + "adult": false, + "genreIds": [ + 99 + ], + "originalLanguage": "en", + "originalTitle": "Disney's Animal Kingdom: Alive with Magic", + "overview": "", + "popularity": 0.6, + "releaseDate": "2017-06-27", + "title": "Disney's Animal Kingdom: Alive with Magic", + "video": false, + "voteAverage": 0, + "voteCount": 0, + "backdropPath": null, + "posterPath": "/amzVT8T9Ju3KLCDnBq4Rhf3LO8j.jpg" + }, + { + "id": 280391, + "mediaType": "movie", + "adult": false, + "genreIds": [ + 12, + 35, + 16 + ], + "originalLanguage": "fr", + "originalTitle": "Pourquoi j'ai pas mangé mon père", + "overview": "L’histoire trépidante d’Édouard, fils aîné du roi des simiens, qui, considéré à sa naissance comme trop malingre, est rejeté par sa tribu. Il grandit loin d’eux, auprès de son ami Ian, et, incroyablement ingénieux, il découvre le feu, la chasse, l’habitat moderne, l’amour et même… l’espoir. Généreux, il veut tout partager, révolutionne l’ordre établi, et mène son peuple avec éclat et humour vers la véritable humanité… celle où on ne mange pas son père.", + "popularity": 12.971, + "releaseDate": "2015-04-08", + "title": "Pourquoi j'ai pas mangé mon père", + "video": false, + "voteAverage": 5.3, + "voteCount": 303, + "backdropPath": "/msDLrSt7Ozpe6oOg4XJrsQJd2IE.jpg", + "posterPath": "/efpzs2g1uRNcP8wPbIKSRPPH0aC.jpg" + }, + { + "id": 775559, + "mediaType": "movie", + "adult": false, + "genreIds": [], + "originalLanguage": "en", + "originalTitle": "A New species of Theme Park: Disney’s Animal Kingdom", + "overview": "", + "popularity": 0.6, + "releaseDate": "1998-04-14", + "title": "A New species of Theme Park: Disney’s Animal Kingdom", + "video": true, + "voteAverage": 0, + "voteCount": 0, + "backdropPath": null, + "posterPath": null + }, + { + "id": 775831, + "mediaType": "movie", + "adult": false, + "genreIds": [], + "originalLanguage": "en", + "originalTitle": "Disney Animal Kingdom Villas: A Village Comes to Life", + "overview": "", + "popularity": 0.6, + "releaseDate": "2007-06-14", + "title": "Disney Animal Kingdom Villas: A Village Comes to Life", + "video": true, + "voteAverage": 0, + "voteCount": 0, + "backdropPath": null, + "posterPath": null + }, + { + "id": 432906, + "mediaType": "movie", + "adult": false, + "genreIds": [ + 99 + ], + "originalLanguage": "en", + "originalTitle": "Out in Nature: Homosexual Behaviour in the Animal Kingdom", + "overview": "", + "popularity": 0.6, + "releaseDate": "2001-09-07", + "title": "Out in Nature: Homosexual Behaviour in the Animal Kingdom", + "video": false, + "voteAverage": 6.8, + "voteCount": 4, + "backdropPath": null, + "posterPath": "/jjxhR9ZxZ3vhauK8IDR6wIBlCLI.jpg" + }, + { + "id": 128887, + "mediaType": "movie", + "adult": false, + "genreIds": [ + 16, + 35 + ], + "originalLanguage": "ja", + "originalTitle": "クレヨンしんちゃん オタケべ!カスカベ野生王国", + "overview": "", + "popularity": 5.365, + "releaseDate": "2009-04-18", + "title": "クレヨンしんちゃん オタケべ!カスカベ野生王国", + "video": false, + "voteAverage": 8.5, + "voteCount": 10, + "backdropPath": "/azvwXB25Wvbx2Cou3Th7lbnjrqP.jpg", + "posterPath": "/h7LipCtdCyBOKR1By5wSP2Ufy3c.jpg" + }, + { + "id": 579733, + "mediaType": "movie", + "adult": false, + "genreIds": [], + "originalLanguage": "no", + "originalTitle": "Dyreriket", + "overview": "", + "popularity": 0.6, + "releaseDate": "2018-05-01", + "title": "Dyreriket", + "video": false, + "voteAverage": 0, + "voteCount": 0, + "backdropPath": null, + "posterPath": null + }, + { + "id": 111612, + "firstAirDate": "2018-10-12", + "genreIds": [ + 10764 + ], + "mediaType": "tv", + "name": "坂上どうぶつ王国", + "originCountry": [ + "JP" + ], + "originalLanguage": "ja", + "originalName": "坂上どうぶつ王国", + "overview": "", + "popularity": 1.186, + "voteAverage": 0, + "voteCount": 0, + "backdropPath": "/op8bK5R76L9QpwcVTnYG7nKXKsU.jpg", + "posterPath": "/2VPq9RYaDohOT8YqTibKZMMT2Ue.jpg" + }, + { + "id": 156216, + "firstAirDate": "2022-01-17", + "genreIds": [ + 16 + ], + "mediaType": "tv", + "name": "动物王国的故事", + "originCountry": [ + "CN" + ], + "originalLanguage": "zh", + "originalName": "动物王国的故事", + "overview": "", + "popularity": 0.6, + "voteAverage": 0, + "voteCount": 0, + "backdropPath": "/uxIJQnjzIQn2MGHk17nNhoIEkxU.jpg", + "posterPath": "/v90bqYZRUT30n22DdwahmW18LFn.jpg" + } + ] +} \ No newline at end of file diff --git a/src/modules/common/examples/music.json b/src/modules/common/examples/music.json new file mode 100644 index 000000000..e9d7933e2 --- /dev/null +++ b/src/modules/common/examples/music.json @@ -0,0 +1,832 @@ +{ + "title": "Celebrate", + "disambiguation": "", + "overview": "", + "artistId": 9, + "foreignAlbumId": "bfedab35-92b7-449b-adf0-875439ec9a85", + "monitored": true, + "anyReleaseOk": true, + "profileId": 1, + "duration": 1818062, + "albumType": "Album", + "secondaryTypes": [], + "mediumCount": 1, + "ratings": { + "votes": 1, + "value": 10 + }, + "releaseDate": "2022-07-27T00:00:00Z", + "releases": [ + { + "id": 202, + "albumId": 32, + "foreignReleaseId": "22bd49a1-f858-427d-94ee-1788b54fb508", + "title": "Celebrate", + "status": "Official", + "duration": 0, + "trackCount": 9, + "media": [ + { + "mediumNumber": 1, + "mediumName": "", + "mediumFormat": "CD" + } + ], + "mediumCount": 1, + "disambiguation": "ONCE JAPAN限定盤", + "country": [ + "Japan" + ], + "label": [ + "Warner Music Japan" + ], + "format": "CD", + "monitored": false + }, + { + "id": 203, + "albumId": 32, + "foreignReleaseId": "52c73f5f-4f91-451b-96d1-3ac3ef9371ee", + "title": "Celebrate", + "status": "Official", + "duration": 0, + "trackCount": 9, + "media": [ + { + "mediumNumber": 1, + "mediumName": "", + "mediumFormat": "CD" + } + ], + "mediumCount": 1, + "disambiguation": "初回限定盤B", + "country": [ + "Japan" + ], + "label": [ + "Warner Music Japan" + ], + "format": "CD", + "monitored": false + }, + { + "id": 204, + "albumId": 32, + "foreignReleaseId": "5745040b-a5fa-4dae-ad31-0bce9d501e23", + "title": "Celebrate", + "status": "Official", + "duration": 0, + "trackCount": 9, + "media": [ + { + "mediumNumber": 1, + "mediumName": "", + "mediumFormat": "CD" + } + ], + "mediumCount": 1, + "disambiguation": "JEONGYEON盤", + "country": [ + "Japan" + ], + "label": [ + "Warner Music Japan" + ], + "format": "CD", + "monitored": false + }, + { + "id": 205, + "albumId": 32, + "foreignReleaseId": "006f9135-454b-4182-a057-47d1b002a282", + "title": "Celebrate", + "status": "Official", + "duration": 0, + "trackCount": 9, + "media": [ + { + "mediumNumber": 1, + "mediumName": "", + "mediumFormat": "CD" + } + ], + "mediumCount": 1, + "disambiguation": "NAYEON盤", + "country": [ + "Japan" + ], + "label": [ + "Warner Music Japan" + ], + "format": "CD", + "monitored": false + }, + { + "id": 206, + "albumId": 32, + "foreignReleaseId": "eeacd54b-a2bd-48f8-8d7c-3ab55b68f17c", + "title": "Celebrate", + "status": "Official", + "duration": 0, + "trackCount": 81, + "media": [ + { + "mediumNumber": 1, + "mediumName": "NAYEON盤", + "mediumFormat": "CD" + }, + { + "mediumNumber": 2, + "mediumName": "JEONGYEON盤", + "mediumFormat": "CD" + }, + { + "mediumNumber": 3, + "mediumName": "MOMO盤", + "mediumFormat": "CD" + }, + { + "mediumNumber": 4, + "mediumName": "SANA盤", + "mediumFormat": "CD" + }, + { + "mediumNumber": 5, + "mediumName": "JIHYO盤", + "mediumFormat": "CD" + }, + { + "mediumNumber": 6, + "mediumName": "MINA盤", + "mediumFormat": "CD" + }, + { + "mediumNumber": 7, + "mediumName": "DAHYUN盤", + "mediumFormat": "CD" + }, + { + "mediumNumber": 8, + "mediumName": "CHAEYOUNG盤", + "mediumFormat": "CD" + }, + { + "mediumNumber": 9, + "mediumName": "TZUYU盤", + "mediumFormat": "CD" + } + ], + "mediumCount": 9, + "disambiguation": "5th Anniversary Collection BOX", + "country": [ + "Japan" + ], + "label": [ + "Warner Music Japan", + "Warner Music Japan", + "Warner Music Japan", + "Warner Music Japan", + "Warner Music Japan", + "Warner Music Japan", + "Warner Music Japan", + "Warner Music Japan", + "Warner Music Japan" + ], + "format": "9xCD", + "monitored": false + }, + { + "id": 207, + "albumId": 32, + "foreignReleaseId": "8ddd43f0-859e-4cff-be7c-daf6806cc035", + "title": "Celebrate", + "status": "Official", + "duration": 0, + "trackCount": 9, + "media": [ + { + "mediumNumber": 1, + "mediumName": "", + "mediumFormat": "CD" + } + ], + "mediumCount": 1, + "disambiguation": "JIHYO盤", + "country": [ + "Japan" + ], + "label": [ + "Warner Music Japan" + ], + "format": "CD", + "monitored": false + }, + { + "id": 208, + "albumId": 32, + "foreignReleaseId": "ad8e0553-97de-499b-8010-85bd02c62859", + "title": "Celebrate", + "status": "Official", + "duration": 0, + "trackCount": 9, + "media": [ + { + "mediumNumber": 1, + "mediumName": "", + "mediumFormat": "CD" + } + ], + "mediumCount": 1, + "disambiguation": "TZUYU盤", + "country": [ + "Japan" + ], + "label": [ + "Warner Music Japan" + ], + "format": "CD", + "monitored": false + }, + { + "id": 209, + "albumId": 32, + "foreignReleaseId": "276bf831-8cae-49a0-bc50-479869d401ac", + "title": "Celebrate", + "status": "Official", + "duration": 0, + "trackCount": 9, + "media": [ + { + "mediumNumber": 1, + "mediumName": "", + "mediumFormat": "CD" + } + ], + "mediumCount": 1, + "disambiguation": "MOMO盤", + "country": [ + "Japan" + ], + "label": [ + "Warner Music Japan" + ], + "format": "CD", + "monitored": false + }, + { + "id": 210, + "albumId": 32, + "foreignReleaseId": "3d201058-deb0-4159-a82f-d9076a608036", + "title": "Celebrate", + "status": "Official", + "duration": 0, + "trackCount": 9, + "media": [ + { + "mediumNumber": 1, + "mediumName": "", + "mediumFormat": "CD" + } + ], + "mediumCount": 1, + "disambiguation": "MINA盤", + "country": [ + "Japan" + ], + "label": [ + "Warner Music Japan" + ], + "format": "CD", + "monitored": false + }, + { + "id": 211, + "albumId": 32, + "foreignReleaseId": "e1fbf96d-f83e-478c-be7d-f0f6dd5305d1", + "title": "Celebrate", + "status": "Official", + "duration": 0, + "trackCount": 9, + "media": [ + { + "mediumNumber": 1, + "mediumName": "", + "mediumFormat": "CD" + } + ], + "mediumCount": 1, + "disambiguation": "DAHYUN盤", + "country": [ + "Japan" + ], + "label": [ + "Warner Music Japan" + ], + "format": "CD", + "monitored": false + }, + { + "id": 212, + "albumId": 32, + "foreignReleaseId": "769a7006-763b-4cd8-8d1f-d389d52ec002", + "title": "Celebrate", + "status": "Official", + "duration": 0, + "trackCount": 9, + "media": [ + { + "mediumNumber": 1, + "mediumName": "", + "mediumFormat": "CD" + } + ], + "mediumCount": 1, + "disambiguation": "CHAEYOUNG盤", + "country": [ + "Japan" + ], + "label": [ + "Warner Music Japan" + ], + "format": "CD", + "monitored": false + }, + { + "id": 213, + "albumId": 32, + "foreignReleaseId": "42e74581-0ef3-4db9-8a20-ba8a3daa1cf0", + "title": "Celebrate", + "status": "Official", + "duration": 0, + "trackCount": 9, + "media": [ + { + "mediumNumber": 1, + "mediumName": "", + "mediumFormat": "CD" + } + ], + "mediumCount": 1, + "disambiguation": "初回限定盤A", + "country": [ + "Japan" + ], + "label": [ + "Warner Music Japan", + "Warner Music Japan" + ], + "format": "CD", + "monitored": false + }, + { + "id": 214, + "albumId": 32, + "foreignReleaseId": "81bdf07f-61ad-4436-bfae-63cd1d9e700c", + "title": "Celebrate", + "status": "Official", + "duration": 0, + "trackCount": 9, + "media": [ + { + "mediumNumber": 1, + "mediumName": "", + "mediumFormat": "CD" + } + ], + "mediumCount": 1, + "disambiguation": "通常盤", + "country": [ + "Japan" + ], + "label": [ + "Warner Music Japan" + ], + "format": "CD", + "monitored": false + }, + { + "id": 215, + "albumId": 32, + "foreignReleaseId": "273b3ba1-88e8-4653-a542-c8b0489c1772", + "title": "Celebrate", + "status": "Official", + "duration": 0, + "trackCount": 9, + "media": [ + { + "mediumNumber": 1, + "mediumName": "", + "mediumFormat": "CD" + } + ], + "mediumCount": 1, + "disambiguation": "SANA盤", + "country": [ + "Japan" + ], + "label": [ + "Warner Music Japan" + ], + "format": "CD", + "monitored": false + }, + { + "id": 216, + "albumId": 32, + "foreignReleaseId": "2442df5f-4090-452c-be7f-5885dffee8e2", + "title": "Celebrate", + "status": "Official", + "duration": 1818062, + "trackCount": 9, + "media": [ + { + "mediumNumber": 1, + "mediumName": "", + "mediumFormat": "Digital Media" + } + ], + "mediumCount": 1, + "disambiguation": "", + "country": [ + "Algeria", + "Angola", + "Anguilla", + "Antigua and Barbuda", + "Argentina", + "Armenia", + "Australia", + "Austria", + "Azerbaijan", + "Bahamas", + "Bahrain", + "Barbados", + "Belgium", + "Belize", + "Benin", + "Bermuda", + "Bhutan", + "Bolivia", + "Bosnia and Herzegovina", + "Botswana", + "Brazil", + "Brunei", + "Bulgaria", + "Burkina Faso", + "Cambodia", + "Cameroon", + "Canada", + "Cape Verde", + "Cayman Islands", + "Chad", + "Chile", + "China", + "Colombia", + "Congo", + "Costa Rica", + "Côte d'Ivoire", + "Croatia", + "Cyprus", + "Czech Republic", + "Denmark", + "Dominica", + "Dominican Republic", + "Ecuador", + "Egypt", + "El Salvador", + "Estonia", + "Fiji", + "Finland", + "France", + "Gabon", + "Gambia", + "Georgia", + "Germany", + "Ghana", + "Greece", + "Grenada", + "Guatemala", + "Guinea-Bissau", + "Guyana", + "Honduras", + "Hong Kong", + "Hungary", + "Iceland", + "India", + "Indonesia", + "Iraq", + "Ireland", + "Israel", + "Italy", + "Jamaica", + "Japan", + "Jordan", + "Kazakhstan", + "Kenya", + "Kuwait", + "Kyrgyzstan", + "Laos", + "Latvia", + "Lebanon", + "Liberia", + "Libya", + "Lithuania", + "Luxembourg", + "Macao", + "North Macedonia", + "Madagascar", + "Malawi", + "Malaysia", + "Maldives", + "Mali", + "Malta", + "Mauritania", + "Mauritius", + "Mexico", + "Federated States of Micronesia", + "Moldova", + "Mongolia", + "Montserrat", + "Morocco", + "Mozambique", + "Myanmar", + "Namibia", + "Nepal", + "Netherlands", + "New Zealand", + "Nicaragua", + "Niger", + "Nigeria", + "Norway", + "Oman", + "Panama", + "Papua New Guinea", + "Paraguay", + "Peru", + "Philippines", + "Poland", + "Portugal", + "Qatar", + "Romania", + "Rwanda", + "Saint Kitts and Nevis", + "Saint Lucia", + "Saint Vincent and The Grenadines", + "Saudi Arabia", + "Senegal", + "Seychelles", + "Sierra Leone", + "Singapore", + "Slovakia", + "Slovenia", + "Solomon Islands", + "South Africa", + "Spain", + "Sri Lanka", + "Suriname", + "Eswatini", + "Sweden", + "Switzerland", + "Taiwan", + "Tajikistan", + "Tanzania", + "Thailand", + "Tonga", + "Trinidad and Tobago", + "Tunisia", + "Turkey", + "Turkmenistan", + "Turks and Caicos Islands", + "Uganda", + "Ukraine", + "United Arab Emirates", + "United Kingdom", + "United States", + "Uruguay", + "Uzbekistan", + "Vanuatu", + "Venezuela", + "Vietnam", + "British Virgin Islands", + "Yemen", + "Democratic Republic of the Congo", + "Zambia", + "Zimbabwe", + "Montenegro", + "Serbia", + "Kosovo" + ], + "label": [ + "Warner Music Japan" + ], + "format": "Digital Media", + "monitored": true + } + ], + "genres": [], + "media": [ + { + "mediumNumber": 1, + "mediumName": "", + "mediumFormat": "Digital Media" + } + ], + "artist": { + "artistMetadataId": 14, + "status": "continuing", + "ended": false, + "artistName": "TWICE", + "foreignArtistId": "8da127cc-c432-418f-b356-ef36210d82ac", + "tadbId": 0, + "discogsId": 0, + "overview": "Twice (Korean: 트와이스; RR: Teuwaiseu; Japanese: トゥワイス, Hepburn: To~uwaisu; commonly stylized in all caps as TWICE) is a South Korean girl group formed by JYP Entertainment. The group is composed of nine members: Nayeon, Jeongyeon, Momo, Sana, Jihyo, Mina, Dahyun, Chaeyoung, and Tzuyu. Twice was formed under the television program Sixteen (2015) and debuted on October 20, 2015, with the extended play (EP) The Story Begins.\nTwice rose to domestic fame in 2016 with their single \"Cheer Up\", which charted at number one on the Gaon Digital Chart, became the best-performing single of the year, and won \"Song of the Year\" at the Melon Music Awards and Mnet Asian Music Awards. Their next single, \"TT\", from their third EP Twicecoaster: Lane 1, topped the Gaon charts for four consecutive weeks. The EP was the highest selling Korean girl group album of 2016. Within 19 months after debut, Twice had already sold over 1.2 million units of their four EPs and special album. As of December 2020, the group has sold over 10 million albums cumulatively in South Korea and Japan, becoming the highest-selling K-Pop girl group of all time.The group debuted in Japan on June 28, 2017, under Warner Music Japan, with the release of a compilation album titled #Twice. The album charted at number 2 on the Oricon Albums Chart with the highest first-week album sales by a K-pop artist in Japan in two years. It was followed by the release of Twice's first original Japanese maxi single titled \"One More Time\" in October. Twice became the first Korean girl group to earn a platinum certification from the Recording Industry Association of Japan (RIAJ) for both an album and CD single in the same year. Twice ranked third in the Top Artist category of Billboard Japan's 2017 Year-end Rankings, and in 2019, they became the first Korean girl group to embark on a Japanese dome tour.\nTwice is the first female Korean act to simultaneously top both Billboard's World Albums and World Digital Song Sales charts with the release of their first studio album Twicetagram and its lead single \"Likey\" in 2017. With the release of their single \"Feel Special\" in 2019, Twice became the third female Korean act to chart into the Canadian Hot 100. After signing with Republic Records for American promotions as part of a partnership with JYP Entertainment, the group has charted into the US Billboard 200 with More & More and Eyes Wide Open in 2020 and Taste of Love and Formula of Love: O+T=<3 in 2021. Their first official English-language single, \"The Feels\", became their first song to enter the US Billboard Hot 100 and the UK Singles Chart, peaking at the 83rd and 80th positions of the charts, respectively. They have been dubbed the next \"Nation's Girl Group\", and their point choreography—including for \"Cheer Up\" (2016), \"TT\" (2016), \"Signal\" (2017), and \"What Is Love?\" (2018)—became dance crazes and viral memes imitated by many celebrities.", + "artistType": "Group", + "disambiguation": "South Korean girl group", + "links": [ + { + "url": "https://www.generasia.com/wiki/Twice", + "name": "generasia" + }, + { + "url": "http://twice.jype.com/", + "name": "jype" + }, + { + "url": "https://twitter.com/JYPETWICE", + "name": "twitter" + }, + { + "url": "https://www.facebook.com/JYPETWICE", + "name": "facebook" + }, + { + "url": "https://www.instagram.com/twicetagram/", + "name": "instagram" + }, + { + "url": "https://www.wikidata.org/wiki/Q20645861", + "name": "wikidata" + }, + { + "url": "http://fans.jype.com/twice", + "name": "jype" + }, + { + "url": "https://commons.wikimedia.org/wiki/File:Twice_performing_at_SAC_2016_02_(cropped).jpg", + "name": "wikimedia" + }, + { + "url": "https://www.discogs.com/artist/4786543", + "name": "discogs" + }, + { + "url": "https://www.last.fm/music/%ED%8A%B8%EC%99%80%EC%9D%B4%EC%8A%A4", + "name": "last" + }, + { + "url": "https://www.last.fm/music/TWICE", + "name": "last" + }, + { + "url": "https://commons.wikimedia.org/wiki/File:160507_Twice_guerrilla_concert.jpg", + "name": "wikimedia" + }, + { + "url": "https://open.spotify.com/artist/7n2Ycct7Beij7Dj7meI4X0", + "name": "spotify" + }, + { + "url": "http://www.twicejapan.com/", + "name": "twicejapan" + }, + { + "url": "https://www.instagram.com/jypetwice_japan/", + "name": "instagram" + }, + { + "url": "https://twitter.com/JYPETWICE_JAPAN", + "name": "twitter" + }, + { + "url": "https://itunes.apple.com/jp/artist/id1203816887", + "name": "apple" + }, + { + "url": "https://commons.wikimedia.org/wiki/File:(TV10)_%EC%97%AC%EC%9E%90%EC%B9%9C%EA%B5%AC%C2%B7%ED%8A%B8%EC%99%80%EC%9D%B4%EC%8A%A4%C2%B7%EB%B8%94%EB%9E%99%ED%95%91%ED%81%AC,_%EB%A0%88%EB%93%9C%EC%B9%B4%ED%8E%AB_%EA%B0%81%EC%96%91%EA%B0%81%EC%83%89_%ED%8C%A8%EC%85%98_%EC%97%B4%EC%A0%84_(2017_%EA%B3%A8%EB%93%A0%EB%94%94%EC%8A%A4%ED%81%AC_%EB%A0%88%EB%93%9C%EC%B9%B4%ED%8E%AB)_2m19s.jpg", + "name": "wikimedia" + }, + { + "url": "https://itunes.apple.com/us/artist/id1203816887", + "name": "apple" + }, + { + "url": "http://viaf.org/viaf/178150468353504172529", + "name": "viaf" + }, + { + "url": "https://www.deezer.com/artist/161553", + "name": "deezer" + }, + { + "url": "https://imvdb.com/n/twice", + "name": "imvdb" + }, + { + "url": "https://listen.tidal.com/artist/3577941", + "name": "tidal" + }, + { + "url": "https://www.youtube.com/TWICE", + "name": "youtube" + }, + { + "url": "https://www.youtube.com/twicejapan_official", + "name": "youtube" + }, + { + "url": "https://music.apple.com/mx/artist/1203816887", + "name": "apple" + }, + { + "url": "https://www.imdb.com/name/nm9652049/", + "name": "imdb" + }, + { + "url": "https://www.tiktok.com/@twice_tiktok_officialjp", + "name": "tiktok" + }, + { + "url": "https://music.youtube.com/channel/UCAq0pFGa2w9SjxOq0ZxKVIw", + "name": "youtube" + } + ], + "images": [ + { + "url": "http://assets.fanart.tv/fanart/music/8da127cc-c432-418f-b356-ef36210d82ac/musicbanner/twice-58fb678fb1219.jpg", + "coverType": "banner", + "extension": ".jpg" + }, + { + "url": "http://assets.fanart.tv/fanart/music/8da127cc-c432-418f-b356-ef36210d82ac/artistbackground/twice-619421e3c57cc.jpg", + "coverType": "fanart", + "extension": ".jpg" + }, + { + "url": "http://assets.fanart.tv/fanart/music/8da127cc-c432-418f-b356-ef36210d82ac/hdmusiclogo/twice-58d833d0a608a.png", + "coverType": "logo", + "extension": ".png" + }, + { + "url": "http://assets.fanart.tv/fanart/music/8da127cc-c432-418f-b356-ef36210d82ac/artistthumb/twice-58fb69c0c2b00.jpg", + "coverType": "poster", + "extension": ".jpg" + } + ], + "path": "/data/Library/Music/TWICE", + "qualityProfileId": 1, + "metadataProfileId": 1, + "monitored": true, + "monitorNewItems": "all", + "genres": [ + "Dance", + "Electronica", + "K-Pop", + "Pop", + "R&B" + ], + "cleanName": "twice", + "sortName": "twice", + "tags": [], + "added": "2022-07-30T19:32:06Z", + "ratings": { + "votes": 4, + "value": 9.5 + }, + "statistics": { + "albumCount": 0, + "trackFileCount": 0, + "trackCount": 0, + "totalTrackCount": 0, + "sizeOnDisk": 0, + "percentOfTracks": 0 + }, + "id": 9 + }, + "images": [ + { + "url": "/MediaCover/Albums/32/cover.jpg?lastWrite=637927379160000000", + "coverType": "cover", + "extension": ".jpg", + "remoteUrl": "https://imagecache.lidarr.audio/v1/caa/22bd49a1-f858-427d-94ee-1788b54fb508/32961181216-1200.jpg" + } + ], + "links": [], + "statistics": { + "trackFileCount": 9, + "trackCount": 9, + "totalTrackCount": 9, + "sizeOnDisk": 74968875, + "percentOfTracks": 100 + }, + "grabbed": false, + "id": 32 +} \ No newline at end of file diff --git a/src/modules/common/examples/request.json b/src/modules/common/examples/request.json new file mode 100644 index 000000000..486932600 --- /dev/null +++ b/src/modules/common/examples/request.json @@ -0,0 +1,47 @@ +{ + "id": 634649, + "mediaType": "movie", + "adult": false, + "genreIds": [ + 28, + 12, + 878 + ], + "originalLanguage": "en", + "originalTitle": "Spider-Man: No Way Home", + "overview": "Après les événements liés à l'affrontement avec Mysterio, l'identité secrète de Spider-Man a été révélée. Il est poursuivi par le gouvernement américain, qui l'accuse du meurtre de Mysterio, et traqué par les médias. Cet événement a également des conséquences terribles sur la vie de sa petite-amie M.J. et de son meilleur ami Ned. Désemparé, Peter Parker demande alors de l'aide au docteur Strange. Ce dernier lance un sort pour que tout le monde oublie que Peter est Spider-Man. Mais les choses ne se passent pas comme prévu, et cette action altère la stabilité de l'espace-temps. Cela ouvre le « multivers », un concept terrifiant dont ils ne savent quasiment rien...", + "popularity": 1643.549, + "releaseDate": "2021-12-15", + "title": "Spider-Man: No Way Home", + "video": false, + "voteAverage": 8, + "voteCount": 14510, + "backdropPath": "/ocUp7DJBIc8VJgLEw1prcyK1dYv.jpg", + "posterPath": "/3SyG7dq2q0ollxJ4pSsrqcfRmVj.jpg", + "mediaInfo": { + "downloadStatus": [], + "downloadStatus4k": [], + "id": 91, + "mediaType": "movie", + "tmdbId": 634649, + "tvdbId": null, + "imdbId": null, + "status": 5, + "status4k": 1, + "createdAt": "2021-11-15T15:15:57.000Z", + "updatedAt": "2022-08-01T08:40:19.000Z", + "lastSeasonChange": "2021-11-15T15:15:57.000Z", + "mediaAddedAt": "2021-12-23T12:04:39.000Z", + "serviceId": 0, + "serviceId4k": null, + "externalServiceId": 89, + "externalServiceId4k": null, + "externalServiceSlug": "634649", + "externalServiceSlug4k": null, + "ratingKey": "823", + "ratingKey4k": null, + "seasons": [], + "plexUrl": "https://app.plex.tv/desktop#!/server/719240db84d0795f30baa1c7283588fea536bb21/details?key=%2Flibrary%2Fmetadata%2F823", + "serviceUrl": "http://radarr:7878/movie/634649" + } +} \ No newline at end of file diff --git a/src/modules/common/examples/tvshow.json b/src/modules/common/examples/tvshow.json new file mode 100644 index 000000000..306b08e33 --- /dev/null +++ b/src/modules/common/examples/tvshow.json @@ -0,0 +1,110 @@ +{ + "seriesId": 37, + "episodeFileId": 7387, + "seasonNumber": 1, + "episodeNumber": 4, + "title": "Part IV", + "airDate": "2022-06-08", + "airDateUtc": "2022-06-08T07:00:00Z", + "overview": "Obi-Wan Kenobi plots a daring mission into enemy territory.", + "episodeFile": { + "seriesId": 37, + "seasonNumber": 1, + "relativePath": "Season 1/Obi-Wan.Kenobi.S01E04.1080p.WEB.h264-KOGi[rartv].mkv", + "path": "/tv/Obi-Wan Kenobi/Season 1/Obi-Wan.Kenobi.S01E04.1080p.WEB.h264-KOGi[rartv].mkv", + "size": 1893191174, + "dateAdded": "2022-06-08T07:32:27.158296Z", + "sceneName": "Obi-Wan.Kenobi.S01E04.1080p.WEB.h264-KOGi[rartv]", + "quality": { + "quality": { + "id": 3, + "name": "WEBDL-1080p", + "source": "web", + "resolution": 1080 + }, + "revision": { + "version": 1, + "real": 0, + "isRepack": false + } + }, + "language": { + "id": 1, + "name": "English" + }, + "mediaInfo": { + "audioChannels": 5.1, + "audioCodec": "EAC3 Atmos", + "videoCodec": "h264" + }, + "originalFilePath": "Obi-Wan.Kenobi.S01E04.1080p.WEB.h264-KOGi[rarbg]/Obi-Wan.Kenobi.S01E04.1080p.WEB.h264-KOGi.mkv", + "qualityCutoffNotMet": false, + "id": 7387 + }, + "hasFile": true, + "monitored": true, + "unverifiedSceneNumbering": false, + "series": { + "title": "Obi-Wan Kenobi", + "sortTitle": "obiwan kenobi", + "seasonCount": 1, + "status": "ended", + "overview": "During the reign of the Empire, Obi-Wan Kenobi embarks on a crucial mission.", + "network": "Disney+", + "airTime": "03:00", + "images": [ + { + "coverType": "banner", + "url": "https://artworks.thetvdb.com/banners/v4/series/393199/banners/6290d38b8c283.jpg" + }, + { + "coverType": "poster", + "url": "https://artworks.thetvdb.com/banners/v4/series/393199/posters/629668351aca3.jpg" + }, + { + "coverType": "fanart", + "url": "https://artworks.thetvdb.com/banners/v4/series/393199/backgrounds/62912a0fe623d.jpg" + } + ], + "seasons": [ + { + "seasonNumber": 1, + "monitored": true + } + ], + "year": 2022, + "path": "/tv/Obi-Wan Kenobi", + "profileId": 1, + "languageProfileId": 1, + "seasonFolder": true, + "monitored": true, + "useSceneNumbering": false, + "runtime": 39, + "tvdbId": 393199, + "tvRageId": 0, + "tvMazeId": 52260, + "firstAired": "2022-05-27T00:00:00Z", + "lastInfoSync": "2022-07-22T03:36:34.392414Z", + "seriesType": "standard", + "cleanTitle": "obiwankenobi", + "imdbId": "tt8466564", + "titleSlug": "obi-wan-kenobi", + "certification": "TV-14", + "genres": [ + "Action", + "Adventure", + "Fantasy", + "Mini-Series", + "Science Fiction" + ], + "tags": [], + "added": "2022-05-03T20:22:10.47688Z", + "ratings": { + "votes": 0, + "value": 0 + }, + "qualityProfileId": 1, + "id": 37 + }, + "id": 1407 +} \ No newline at end of file diff --git a/src/modules/dashdot/DashdotModule.tsx b/src/modules/dashdot/DashdotModule.tsx index 1cb9bc716..254ddadcc 100644 --- a/src/modules/dashdot/DashdotModule.tsx +++ b/src/modules/dashdot/DashdotModule.tsx @@ -237,7 +237,6 @@ export function DashdotComponent() { : '' }`} frameBorder="0" - allowTransparency /> ))} diff --git a/src/modules/downloads/DownloadsModule.tsx b/src/modules/downloads/DownloadsModule.tsx index e27f28195..d81de9123 100644 --- a/src/modules/downloads/DownloadsModule.tsx +++ b/src/modules/downloads/DownloadsModule.tsx @@ -8,8 +8,6 @@ import { Skeleton, ScrollArea, Center, - Image, - Stack, } from '@mantine/core'; import { IconDownload as Download } from '@tabler/icons'; import { useEffect, useState } from 'react'; @@ -189,23 +187,17 @@ export default function DownloadComponent() { }); return ( - - - {rows.length > 0 ? ( - - {ths} - {rows} -
- ) : ( -
- -
- )} -
-
+ + {rows.length > 0 ? ( + + {ths} + {rows} +
+ ) : ( +
+ No torrents found +
+ )} +
); } diff --git a/src/modules/index.ts b/src/modules/index.ts index 941ea3c99..88cb1ad02 100644 --- a/src/modules/index.ts +++ b/src/modules/index.ts @@ -6,3 +6,4 @@ export * from './ping'; export * from './search'; export * from './weather'; export * from './docker'; +export * from './overseerr'; diff --git a/src/modules/moduleWrapper.tsx b/src/modules/moduleWrapper.tsx index 3ae65a0b2..fc23877dd 100644 --- a/src/modules/moduleWrapper.tsx +++ b/src/modules/moduleWrapper.tsx @@ -1,6 +1,5 @@ import { ActionIcon, - Box, Button, Card, Group, @@ -10,9 +9,9 @@ import { TextInput, useMantineColorScheme, } from '@mantine/core'; -import { useHover } from '@mantine/hooks'; import { IconAdjustments } from '@tabler/icons'; import { motion } from 'framer-motion'; +import { useState } from 'react'; import { useConfig } from '../tools/state'; import { IModule } from './ModuleTypes'; @@ -148,7 +147,7 @@ export function ModuleWrapper(props: any) { // Remove 'Module' from enabled modules titles const isShown = enabledModules[module.title]?.enabled ?? false; //TODO: fix the hover problem - const { hovered, ref } = useHover(); + const [hovering, setHovering] = useState(false); if (!isShown) { return null; @@ -158,7 +157,6 @@ export function ModuleWrapper(props: any) { ); } @@ -185,6 +190,7 @@ export function ModuleMenu(props: any) { <> {module.options && ( - - - - - - - + + + + Settings diff --git a/src/modules/overseerr/Movie.d.ts b/src/modules/overseerr/Movie.d.ts new file mode 100644 index 000000000..558bccbcc --- /dev/null +++ b/src/modules/overseerr/Movie.d.ts @@ -0,0 +1,248 @@ +export interface MovieResult { + id: number; + adult: boolean; + budget: number; + genres: Genre[]; + relatedVideos: RelatedVideo[]; + originalLanguage: string; + originalTitle: string; + popularity: number; + productionCompanies: ProductionCompany[]; + productionCountries: ProductionCountry[]; + releaseDate: Date; + releases: Releases; + revenue: number; + spokenLanguages: SpokenLanguage[]; + status: string; + title: string; + video: boolean; + voteAverage: number; + voteCount: number; + backdropPath: string; + homepage: string; + imdbId: string; + overview: string; + posterPath: string; + runtime: number; + tagline: string; + credits: Credits; + collection: Collection; + externalIds: ExternalIDS; + mediaInfo: Media; + watchProviders: WatchProvider[]; +} + +export interface Collection { + id: number; + name: string; + posterPath: string; + backdropPath: string; +} + +export interface Credits { + cast: Cast[]; + crew: Crew[]; +} + +export interface Cast { + castId: number; + character: string; + creditId: string; + id: number; + name: string; + order: number; + gender: number; + profilePath: null | string; +} + +export interface Crew { + creditId: string; + department: Department; + id: number; + job: string; + name: string; + gender: number; + profilePath: null | string; +} + +export enum Department { + Art = 'Art', + Camera = 'Camera', + CostumeMakeUp = 'Costume & Make-Up', + Crew = 'Crew', + Directing = 'Directing', + Editing = 'Editing', + Production = 'Production', + Sound = 'Sound', + VisualEffects = 'Visual Effects', + Writing = 'Writing', +} + +export interface ExternalIDS { + facebookId: string; + imdbId: string; + instagramId: string; + twitterId: string; +} + +export interface Genre { + id: number; + name: string; +} + +export interface Request { + id: number; + status: number; + createdAt: Date; + updatedAt: Date; + type: string; + is4k: boolean; + serverId: number; + profileId: number; + rootFolder: string; + languageProfileId: null; + tags: any[]; + media: Media; + requestedBy: EdBy; + modifiedBy: EdBy; + seasons: any[]; + seasonCount: number; +} + +export interface Media { + downloadStatus: any[]; + downloadStatus4k: any[]; + id: number; + mediaType: string; + tmdbId: number; + tvdbId: null; + imdbId: null; + status: number; + status4k: number; + createdAt: Date; + updatedAt: Date; + lastSeasonChange: Date; + mediaAddedAt: Date; + serviceId: number; + serviceId4k: null; + externalServiceId: number; + externalServiceId4k: null; + externalServiceSlug: string; + externalServiceSlug4k: null; + ratingKey: string; + ratingKey4k: null; + requests?: Request[]; + issues?: any[]; + seasons: any[]; + plexUrl: string; + serviceUrl: string; +} + +export interface EdBy { + permissions: number; + id: number; + email: string; + plexUsername: string; + username: string; + recoveryLinkExpirationDate: null; + userType: number; + avatar: string; + movieQuotaLimit: null; + movieQuotaDays: null; + tvQuotaLimit: null; + tvQuotaDays: null; + createdAt: Date; + updatedAt: Date; + settings: Settings; + requestCount: number; + displayName: string; +} + +export interface Settings { + id: number; + locale: string; + region: string; + originalLanguage: null; + pgpKey: null; + discordId: string; + pushbulletAccessToken: null; + pushoverApplicationToken: null; + pushoverUserKey: null; + telegramChatId: null; + telegramSendSilently: null; + notificationTypes: NotificationTypes; +} + +export interface NotificationTypes { + discord: number; + email: number; + webpush: number; +} + +export interface ProductionCompany { + id: number; + name: string; + originCountry?: string; + logoPath: string; + displayPriority?: number; +} + +export interface ProductionCountry { + iso_3166_1: string; + name: string; +} + +export interface RelatedVideo { + site: string; + key: string; + name: string; + size: number; + type: string; + url: string; +} + +export interface Releases { + results: Result[]; +} + +export interface Result { + iso_3166_1: string; + release_dates: ReleaseDate[]; +} + +export interface ReleaseDate { + certification: string; + iso_639_1: ISO639_1 | null; + note: Note; + release_date: Date; + type: number; +} + +export enum ISO639_1 { + CS = 'cs', + Empty = '', +} + +export enum Note { + Empty = '', + HBOMax = 'HBO Max', + LosAngelesCalifornia = 'Los Angeles, California', + Starz = 'STARZ', + The4KUHDBluRayDVD = '4K UHD, Blu-ray & DVD', + TheMoreFunStuffVersion = 'The More Fun Stuff Version', + Tvod = 'TVOD', + VOD = 'VOD', +} + +export interface SpokenLanguage { + english_name: string; + iso_639_1: string; + name: string; +} + +export interface WatchProvider { + iso_3166_1: string; + link: string; + buy: ProductionCompany[]; + flatrate: ProductionCompany[]; +} diff --git a/src/modules/overseerr/OverseerrModule.tsx b/src/modules/overseerr/OverseerrModule.tsx new file mode 100644 index 000000000..4f8e44802 --- /dev/null +++ b/src/modules/overseerr/OverseerrModule.tsx @@ -0,0 +1,14 @@ +import { IconEyeglass } from '@tabler/icons'; +import { OverseerrMediaDisplay } from '../common'; +import { IModule } from '../ModuleTypes'; + +export const OverseerrModule: IModule = { + title: 'Overseerr', + description: 'Allows you to search and add media from Overseerr', + icon: IconEyeglass, + component: OverseerrMediaDisplay, +}; + +export interface OverseerSearchProps { + query: string; +} diff --git a/src/modules/overseerr/RequestModal.tsx b/src/modules/overseerr/RequestModal.tsx new file mode 100644 index 000000000..ee23e2678 --- /dev/null +++ b/src/modules/overseerr/RequestModal.tsx @@ -0,0 +1,240 @@ +import { Alert, Button, Checkbox, createStyles, Group, Modal, Stack, Table } from '@mantine/core'; +import { showNotification, updateNotification } from '@mantine/notifications'; +import { IconAlertCircle, IconCheck, IconDownload } from '@tabler/icons'; +import axios from 'axios'; +import Consola from 'consola'; +import { useState } from 'react'; +import { useColorTheme } from '../../tools/color'; +import { MovieResult } from './Movie.d'; +import { MediaType, Result } from './SearchResult.d'; +import { TvShowResult, TvShowResultSeason } from './TvShow.d'; + +interface RequestModalProps { + base: Result; + opened: boolean; + setOpened: (opened: boolean) => void; +} + +const useStyles = createStyles((theme) => ({ + rowSelected: { + backgroundColor: + theme.colorScheme === 'dark' + ? theme.fn.rgba(theme.colors[theme.primaryColor][7], 0.2) + : theme.colors[theme.primaryColor][0], + }, +})); + +export function RequestModal({ base, opened, setOpened }: RequestModalProps) { + const [result, setResult] = useState(); + const { secondaryColor } = useColorTheme(); + function getResults(base: Result) { + axios.get(`/api/modules/overseerr/${base.id}?type=${base.mediaType}`).then((res) => { + setResult(res.data); + }); + } + if (opened && !result) { + getResults(base); + } + if (!result || !opened) { + return null; + } + return base.mediaType === 'movie' ? ( + + ) : ( + + ); +} + +export function MovieRequestModal({ + result, + opened, + setOpened, +}: { + result: MovieResult; + opened: boolean; + setOpened: (opened: boolean) => void; +}) { + const { secondaryColor } = useColorTheme(); + return ( + setOpened(false)} + radius="lg" + size="lg" + trapFocus + zIndex={150} + withinPortal + opened={opened} + title={ + + + Ask for {result.title} + + } + > + + } + title="Using API key" + color={secondaryColor} + radius="md" + variant="filled" + > + This request will be automatically approved + + + + + + + + ); +} + +export function TvRequestModal({ + result, + opened, + setOpened, +}: { + result: TvShowResult; + opened: boolean; + setOpened: (opened: boolean) => void; +}) { + const [selection, setSelection] = useState(result.seasons); + const { classes, cx } = useStyles(); + + const toggleRow = (container: TvShowResultSeason) => + setSelection((current: TvShowResultSeason[]) => + current.includes(container) ? current.filter((c) => c !== container) : [...current, container] + ); + const toggleAll = () => + setSelection((current: any) => + current.length === result.seasons.length ? [] : result.seasons.map((c) => c) + ); + + const rows = result.seasons.map((element) => { + const selected = selection.includes(element); + return ( + + + toggleRow(element)} + transitionDuration={0} + /> + + {element.name} + {element.episodeCount} + + ); + }); + const { secondaryColor } = useColorTheme(); + + return ( + setOpened(false)} + radius="lg" + size="lg" + opened={opened} + title={ + + + Ask for {result.name ?? result.originalName ?? 'a TV show'} + + } + > + + } + title="Using API key" + color={secondaryColor} + radius="md" + variant="filled" + > + This request will be automatically approved + + + + + + + + + + + {rows} +
Tick the seasons that you want to be downloaded
+ 0 && selection.length !== result.seasons.length} + transitionDuration={0} + /> + SeasonNumber of episodes
+ + + + +
+
+ ); +} + +function askForMedia(type: MediaType, id: number, name: string, seasons?: number[]) { + Consola.info(`Requesting ${type} ${id} ${name}`); + showNotification({ + title: 'Request', + id: id.toString(), + message: `Requesting media ${name}`, + color: 'orange', + loading: true, + autoClose: false, + disallowClose: true, + icon: , + }); + axios + .post(`/api/modules/overseerr/${id}`, { type, seasons }) + .then(() => { + updateNotification({ + id: id.toString(), + title: '', + color: 'green', + message: ` ${name} requested`, + icon: , + autoClose: 2000, + }); + }) + .catch((err) => { + updateNotification({ + id: id.toString(), + color: 'red', + title: 'There was an error', + message: err.message, + autoClose: 2000, + }); + }); +} diff --git a/src/modules/overseerr/SearchResult.d.ts b/src/modules/overseerr/SearchResult.d.ts new file mode 100644 index 000000000..89b3d3ade --- /dev/null +++ b/src/modules/overseerr/SearchResult.d.ts @@ -0,0 +1,65 @@ +export interface SearchResult { + page: number; + totalPages: number; + totalResults: number; + results: Result[]; +} + +export interface Result { + id: number; + mediaType: MediaType; + adult?: boolean; + genreIds: number[]; + originalLanguage: OriginalLanguage; + originalTitle?: string; + overview: string; + popularity: number; + releaseDate?: Date; + title?: string; + video?: boolean; + voteAverage: number; + voteCount: number; + backdropPath: null | string; + posterPath: string; + mediaInfo?: MediaInfo; + firstAirDate?: Date; + name?: string; + originCountry?: string[]; + originalName?: string; +} + +export interface MediaInfo { + downloadStatus: any[]; + downloadStatus4k: any[]; + id: number; + mediaType: MediaType; + tmdbId: number; + tvdbId: null; + imdbId: null; + status: number; + status4k: number; + createdAt: Date; + updatedAt: Date; + lastSeasonChange: Date; + mediaAddedAt: Date; + serviceId: number; + serviceId4k: null; + externalServiceId: number; + externalServiceId4k: null; + externalServiceSlug: string; + externalServiceSlug4k: null; + ratingKey: string; + ratingKey4k: null; + seasons: any[]; + plexUrl: string; + serviceUrl: string; +} + +export enum MediaType { + Movie = 'movie', + Tv = 'tv', +} + +export enum OriginalLanguage { + En = 'en', +} diff --git a/src/modules/overseerr/TvShow.d.ts b/src/modules/overseerr/TvShow.d.ts new file mode 100644 index 000000000..f9c3bd64a --- /dev/null +++ b/src/modules/overseerr/TvShow.d.ts @@ -0,0 +1,295 @@ +export interface TvShowResult { + createdBy: CreatedBy[]; + episodeRunTime: number[]; + firstAirDate: Date; + genres: Genre[]; + relatedVideos: RelatedVideo[]; + homepage: string; + id: number; + inProduction: boolean; + languages: string[]; + lastAirDate: Date; + name: string; + networks: Network[]; + numberOfEpisodes: number; + numberOfSeasons: number; + originCountry: string[]; + originalLanguage: string; + originalName: string; + tagline: string; + overview: string; + popularity: number; + productionCompanies: Network[]; + productionCountries: ProductionCountry[]; + contentRatings: ContentRatings; + spokenLanguages: SpokenLanguage[]; + seasons: TvShowResultSeason[]; + status: string; + type: string; + voteAverage: number; + voteCount: number; + backdropPath: string; + lastEpisodeToAir: LastEpisodeToAir; + posterPath: string; + credits: Credits; + externalIds: ExternalIDS; + keywords: Genre[]; + mediaInfo: Media; + watchProviders: WatchProvider[]; +} + +export interface ContentRatings { + results: Result[]; +} + +export interface Result { + iso_3166_1: string; + rating: string; +} + +export interface CreatedBy { + id: number; + credit_id: string; + name: string; + gender: number; + profile_path: string; +} + +export interface Credits { + cast: Cast[]; + crew: Crew[]; +} + +export interface Cast { + character: string; + creditId: string; + id: number; + name: string; + order: number; + gender: number; + profilePath: null | string; +} + +export interface Crew { + creditId: string; + department: string; + id: number; + job: string; + name: string; + gender: number; + profilePath: string; +} + +export interface ExternalIDS { + facebookId: string; + freebaseId: null; + freebaseMid: string; + imdbId: string; + instagramId: string; + tvdbId: number; + tvrageId: number; + twitterId: string; +} + +export interface Genre { + id: number; + name: string; +} + +export interface LastEpisodeToAir { + id: number; + airDate: Date; + episodeNumber: number; + name: string; + overview: string; + productionCode: string; + seasonNumber: number; + showId: number; + voteAverage: number; + stillPath: string; +} + +export interface Request { + id: number; + status: number; + createdAt: Date; + updatedAt: Date; + type: Type; + is4k: boolean; + serverId: null; + profileId: null; + rootFolder: null; + languageProfileId: null; + tags: null; + media: Media; + requestedBy: EdBy; + modifiedBy: EdBy; + seasons: MediaInfoSeason[]; + seasonCount: number; +} + +export interface Media { + downloadStatus: DownloadStatus[]; + downloadStatus4k: any[]; + id: number; + mediaType: Type; + tmdbId: number; + tvdbId: number; + imdbId: null; + status: number; + status4k: number; + createdAt: Date; + updatedAt: Date; + lastSeasonChange: Date; + mediaAddedAt: Date; + serviceId: number; + serviceId4k: null; + externalServiceId: number; + externalServiceId4k: null; + externalServiceSlug: string; + externalServiceSlug4k: null; + ratingKey: string; + ratingKey4k: null; + requests?: Request[]; + issues?: any[]; + seasons: MediaInfoSeason[]; + plexUrl: string; + serviceUrl: string; +} + +export interface EdBy { + permissions: number; + id: number; + email: string; + plexUsername: string; + username: string; + recoveryLinkExpirationDate: null; + userType: number; + avatar: string; + movieQuotaLimit: null; + movieQuotaDays: null; + tvQuotaLimit: null; + tvQuotaDays: null; + createdAt: Date; + updatedAt: Date; + settings: Settings; + requestCount: number; + displayName: string; +} + +export interface Settings { + id: number; + locale: string; + region: string; + originalLanguage: null; + pgpKey: null; + discordId: string; + pushbulletAccessToken: null; + pushoverApplicationToken: null; + pushoverUserKey: null; + telegramChatId: null; + telegramSendSilently: null; + notificationTypes: NotificationTypes; +} + +export interface NotificationTypes { + discord: number; + email: number; + webpush: number; +} + +export interface MediaInfoSeason { + id: number; + seasonNumber: number; + status: number; + status4k?: number; + createdAt: Date; + updatedAt: Date; +} + +export enum Type { + Tv = 'tv', +} + +export interface DownloadStatus { + externalId: number; + estimatedCompletionTime: Date; + mediaType: Type; + size: number; + sizeLeft: number; + status: Status; + timeLeft: string; + title: string; +} + +export enum Status { + Completed = 'completed', + Downloading = 'downloading', +} + +export interface Network { + id: number; + name: Name; + originCountry?: string; + logoPath: LogoPath | null; + displayPriority?: number; +} + +export enum LogoPath { + HbifXPpM55B1FL5WPo7T72VzN78PNG = '/hbifXPpM55B1fL5wPo7t72vzN78.png', + KhiCshsZBdtUUYOr4VLoCtuqCEqPNG = '/khiCshsZBdtUUYOr4VLoCtuqCEq.png', + O9ExgOSLF3OTwR6T3DJOuwOKJgqJpg = '/o9ExgOSLF3OTwR6T3DJOuwOKJgq.jpg', + PEURlLlr8JggOwK53FJ5WdQl05YJpg = '/peURlLlr8jggOwK53fJ5wdQl05y.jpg', + T2YyOv40HZeVlLjYsCSPHnWLk4WJpg = '/t2yyOv40HZeVlLjYsCsPHnWLk4W.jpg', + TBEdFQDwx5LEVr8WpSEXQSIirVqJpg = '/tbEdFQDwx5LEVr8WpSeXQSIirVq.jpg', + The5NyLm42TmCqCMOZFvH4FcoSNKEWJpg = '/5NyLm42TmCqCMOZFvH4fcoSNKEW.jpg', + WwemzKWzjKYJFfCeiB57Q3R4BcmPNG = '/wwemzKWzjKYJFfCeiB57q3r4Bcm.png', +} + +export enum Name { + AmazonVideo = 'Amazon Video', + AppleITunes = 'Apple iTunes', + Channel4 = 'Channel 4', + GooglePlayMovies = 'Google Play Movies', + HouseOfTomorrow = 'House of Tomorrow', + Ivi = 'Ivi', + Netflix = 'Netflix', + Zeppotron = 'Zeppotron', +} + +export interface ProductionCountry { + iso_3166_1: string; + name: string; +} + +export interface RelatedVideo { + site: string; + key: string; + name: string; + size: number; + type: string; + url: string; +} + +export interface TvShowResultSeason { + airDate: Date; + episodeCount: number; + id: number; + name: string; + overview: string; + seasonNumber: number; + posterPath: string; +} + +export interface SpokenLanguage { + englishName: string; + iso_639_1: string; + name: string; +} + +export interface WatchProvider { + iso_3166_1: string; + link: string; + buy: Network[]; + flatrate: Network[]; +} diff --git a/src/modules/overseerr/example.json b/src/modules/overseerr/example.json new file mode 100644 index 000000000..d9f35cad6 --- /dev/null +++ b/src/modules/overseerr/example.json @@ -0,0 +1,72 @@ +{ + "id": 86831, + "firstAirDate": "2019-03-15", + "genreIds": [ + 16, + 10765 + ], + "mediaType": "tv", + "name": "Love, Death & Robots", + "originCountry": [ + "US" + ], + "originalLanguage": "en", + "originalName": "Love, Death & Robots", + "overview": "Terrifying creatures, wicked surprises and dark comedy converge in this NSFW anthology of animated stories presented by Tim Miller and David Fincher.", + "popularity": 623.833, + "voteAverage": 8.2, + "voteCount": 1720, + "backdropPath": "/78NtUwwo3lhH7QGh4vG3U1qK1mc.jpg", + "posterPath": "/cRiDlzzZC5lL7fvImuSjs04SUIJ.jpg", + "mediaInfo": { + "downloadStatus": [], + "downloadStatus4k": [], + "id": 79, + "mediaType": "tv", + "tmdbId": 86831, + "tvdbId": 357888, + "imdbId": null, + "status": 4, + "status4k": 1, + "createdAt": "2022-02-05T04:30:01.000Z", + "updatedAt": "2022-02-05T09:25:22.000Z", + "lastSeasonChange": "2022-02-05T04:30:01.000Z", + "mediaAddedAt": "2022-02-04T01:16:35.000Z", + "serviceId": 0, + "serviceId4k": null, + "externalServiceId": 7, + "externalServiceId4k": null, + "externalServiceSlug": "love-death-and-robots", + "externalServiceSlug4k": null, + "ratingKey": "182", + "ratingKey4k": null, + "seasons": [ + { + "id": 11, + "seasonNumber": 1, + "status": 1, + "status4k": 1, + "createdAt": "2022-02-05T04:30:01.000Z", + "updatedAt": "2022-02-05T04:30:01.000Z" + }, + { + "id": 24, + "seasonNumber": 2, + "status": 5, + "status4k": 1, + "createdAt": "2022-02-05T04:30:01.000Z", + "updatedAt": "2022-02-05T04:30:01.000Z" + }, + { + "id": 85, + "seasonNumber": 3, + "status": 3, + "status4k": 1, + "createdAt": "2022-04-26T04:30:02.000Z", + "updatedAt": "2022-04-26T04:30:02.000Z" + } + ], + "plexUrl": "https://app.plex.tv/desktop#!/server/5b88b3c20d2d092c0ee848f9044f3f3bee033d91/details?key=%2Flibrary%2Fmetadata%2F182", + "serviceUrl": "http://server:8989/series/love-death-and-robots" + } +} \ No newline at end of file diff --git a/src/modules/overseerr/index.ts b/src/modules/overseerr/index.ts new file mode 100644 index 000000000..bc20880a5 --- /dev/null +++ b/src/modules/overseerr/index.ts @@ -0,0 +1 @@ +export { OverseerrModule } from './OverseerrModule'; diff --git a/src/modules/search/SearchModule.tsx b/src/modules/search/SearchModule.tsx index 3e16c0c07..02a39bfba 100644 --- a/src/modules/search/SearchModule.tsx +++ b/src/modules/search/SearchModule.tsx @@ -1,15 +1,19 @@ -import { Kbd, createStyles, Autocomplete } from '@mantine/core'; +import { Kbd, createStyles, Autocomplete, Popover, ScrollArea, Divider } from '@mantine/core'; import { useDebouncedValue, useHotkeys } from '@mantine/hooks'; import { useForm } from '@mantine/form'; -import { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { IconSearch as Search, IconBrandYoutube as BrandYoutube, IconDownload as Download, + IconMovie, } from '@tabler/icons'; import axios from 'axios'; +import { showNotification } from '@mantine/notifications'; import { useConfig } from '../../tools/state'; import { IModule } from '../ModuleTypes'; +import { OverseerrModule } from '../overseerr'; +import { OverseerrMediaDisplay } from '../common'; const useStyles = createStyles((theme) => ({ hide: { @@ -23,47 +27,71 @@ const useStyles = createStyles((theme) => ({ export const SearchModule: IModule = { title: 'Search Bar', - description: 'Show the current time and date in a card', + description: 'Search bar to search the web, youtube, torrents or overseerr', icon: Search, component: SearchBar, }; export default function SearchBar(props: any) { - const { config, setConfig } = useConfig(); - const [opened, setOpened] = useState(false); - const [icon, setIcon] = useState(); + const { classes, cx } = useStyles(); + // Config + const { config } = useConfig(); + const isModuleEnabled = config.modules?.[SearchModule.title]?.enabled ?? false; + const isOverseerrEnabled = config.modules?.[OverseerrModule.title]?.enabled ?? false; + const OverseerrService = config.services.find((service) => service.type === 'Overseerr'); const queryUrl = config.settings.searchUrl ?? 'https://www.google.com/search?q='; + + const [OverseerrResults, setOverseerrResults] = useState([]); + const [loading, setLoading] = useState(false); + const [icon, setIcon] = useState(); + const [results, setResults] = useState([]); + const [opened, setOpened] = useState(false); + const textInput = useRef(); - // Find a service with the type of 'Overseerr' + useHotkeys([['ctrl+K', () => textInput.current && textInput.current.focus()]]); + const form = useForm({ initialValues: { query: '', }, }); - const [debounced, cancel] = useDebouncedValue(form.values.query, 250); - const [results, setResults] = useState([]); + useEffect(() => { - if (form.values.query !== debounced || form.values.query === '') return; - axios - .get(`/api/modules/search?q=${form.values.query}`) - .then((res) => setResults(res.data ?? [])); + if (OverseerrService === undefined && isOverseerrEnabled) { + showNotification({ + title: 'Overseerr integration', + message: 'Module enabled but no service is configured with the type "Overseerr"', + color: 'red', + }); + } + }, [OverseerrService, isOverseerrEnabled]); + + useEffect(() => { + if ( + form.values.query !== debounced || + form.values.query === '' || + (form.values.query.startsWith('!') && !form.values.query.startsWith('!os')) + ) { + return; + } + if (form.values.query.startsWith('!os')) { + axios + .get(`/api/modules/overseerr?query=${form.values.query.replace('!os ', '')}`) + .then((res) => { + setOverseerrResults(res.data.results ?? []); + setLoading(false); + }); + setLoading(true); + } else { + setOverseerrResults([]); + axios + .get(`/api/modules/search?q=${form.values.query}`) + .then((res) => setResults(res.data ?? [])); + } }, [debounced]); - useHotkeys([['ctrl+K', () => textInput.current && textInput.current.focus()]]); - const { classes, cx } = useStyles(); - const rightSection = ( -
- Ctrl - + - K -
- ); - // If enabled modules doesn't contain the module, return null - // If module in enabled - - const exists = config.modules?.[SearchModule.title]?.enabled ?? false; - if (!exists) { + if (!isModuleEnabled) { return null; } @@ -76,53 +104,90 @@ export default function SearchBar(props: any) { onChange={() => { // If query contains !yt or !t add "Searching on YouTube" or "Searching torrent" const query = form.values.query.trim(); - const isYoutube = query.startsWith('!yt'); - const isTorrent = query.startsWith('!t'); - if (isYoutube) { - setIcon(); - } else if (isTorrent) { - setIcon(); - } else { - setIcon(); + switch (query.substring(0, 3)) { + case '!yt': + setIcon(); + break; + case '!t ': + setIcon(); + break; + case '!os': + setIcon(); + break; + default: + setIcon(); + break; } }} onSubmit={form.onSubmit((values) => { const query = values.query.trim(); - const isYoutube = query.startsWith('!yt'); - const isTorrent = query.startsWith('!t'); - form.setValues({ query: '' }); setTimeout(() => { - if (isYoutube) { - window.open(`https://www.youtube.com/results?search_query=${query.substring(3)}`); - } else if (isTorrent) { - window.open(`https://bitsearch.to/search?q=${query.substring(3)}`); - } else { - window.open( - `${ - queryUrl.includes('%s') - ? queryUrl.replace('%s', values.query) - : queryUrl + values.query - }` - ); + form.setValues({ query: '' }); + switch (query.substring(0, 3)) { + case '!yt': + window.open(`https://www.youtube.com/results?search_query=${query.substring(3)}`); + break; + case '!t ': + window.open(`https://www.torrentdownloads.me/search/?search=${query.substring(3)}`); + break; + case '!os': + break; + default: + window.open( + `${queryUrl.includes('%s') ? queryUrl.replace('%s', query) : `${queryUrl}${query}`}` + ); + break; } - }, 20); + }, 500); })} > - 0 && opened} + position="bottom" + withArrow + withinPortal + shadow="md" radius="md" - size="md" - styles={{ rightSection: { pointerEvents: 'none' } }} - placeholder="Search the web..." - {...props} - {...form.getInputProps('query')} - /> + zIndex={100} + trapFocus + transition="pop-top-right" + > + + setOpened(true)} + autoFocus + variant="filled" + data={autocompleteData} + icon={icon} + ref={textInput} + rightSectionWidth={90} + rightSection={ +
+ Ctrl + + + K +
+ } + radius="md" + size="md" + styles={{ rightSection: { pointerEvents: 'none' } }} + placeholder="Search the web..." + {...props} + {...form.getInputProps('query')} + /> +
+ + setOpened(false)}> + + {OverseerrResults.slice(0, 5).map((result, index) => ( + + + {index < OverseerrResults.length - 1 && } + + ))} + + + ); } diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 5fab2f2bc..776347495 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -6,6 +6,7 @@ import Head from 'next/head'; import { MantineProvider, ColorScheme, ColorSchemeProvider, MantineTheme } from '@mantine/core'; import { NotificationsProvider } from '@mantine/notifications'; import { useHotkeys } from '@mantine/hooks'; +import { ModalsProvider } from '@mantine/modals'; import { ConfigProvider } from '../tools/state'; import { theme } from '../tools/theme'; import { ColorTheme } from '../tools/color'; @@ -44,6 +45,20 @@ export default function App(this: any, props: AppProps & { colorScheme: ColorSch - - - + + + + + diff --git a/src/pages/api/modules/overseerr/[id].tsx b/src/pages/api/modules/overseerr/[id].tsx new file mode 100644 index 000000000..3368ea6a7 --- /dev/null +++ b/src/pages/api/modules/overseerr/[id].tsx @@ -0,0 +1,126 @@ +import { NextApiRequest, NextApiResponse } from 'next'; +import { getCookie } from 'cookies-next'; +import axios from 'axios'; +import Consola from 'consola'; +import { getConfig } from '../../../../tools/getConfig'; +import { Config } from '../../../../tools/types'; +import { MediaType } from '../../../../modules/overseerr/SearchResult'; + +async function Get(req: NextApiRequest, res: NextApiResponse) { + // Get the slug of the request + const { id, type } = req.query as { id: string; type: string }; + const configName = getCookie('config-name', { req }); + const { config }: { config: Config } = getConfig(configName?.toString() ?? 'default').props; + const service = config.services.find((service) => service.type === 'Overseerr'); + if (!id) { + return res.status(400).json({ error: 'No id provided' }); + } + if (!type) { + return res.status(400).json({ error: 'No type provided' }); + } + if (!service?.apiKey) { + return res.status(400).json({ error: 'No service found' }); + } + + const serviceUrl = new URL(service.url); + switch (type) { + case 'movie': + return axios + .get(`${serviceUrl.origin}/api/v1/movie/${id}`, { + headers: { + // Set X-Api-Key to the value of the API key + 'X-Api-Key': service.apiKey, + }, + }) + .then((axiosres) => res.status(200).json(axiosres.data)) + + .catch((err) => { + Consola.error(err); + return res.status(500).json({ + message: 'Something went wrong', + }); + }); + case 'tv': + // Make request to the tv api + return axios + .get(`${serviceUrl.origin}/api/v1/tv/${id}`, { + headers: { + // Set X-Api-Key to the value of the API key + 'X-Api-Key': service.apiKey, + }, + }) + .then((axiosres) => res.status(200).json(axiosres.data)) + .catch((err) => { + Consola.error(err); + return res.status(500).json({ + message: 'Something went wrong', + }); + }); + + default: + return res.status(400).json({ + message: 'Wrong request, type should be movie or tv', + }); + } +} + +async function Post(req: NextApiRequest, res: NextApiResponse) { + // Get the slug of the request + const { id } = req.query as { id: string }; + const { seasons, type } = req.body as { seasons?: number[]; type: MediaType }; + const configName = getCookie('config-name', { req }); + const { config }: { config: Config } = getConfig(configName?.toString() ?? 'default').props; + const service = config.services.find((service) => service.type === 'Overseerr'); + if (!id) { + return res.status(400).json({ error: 'No id provided' }); + } + if (!type) { + return res.status(400).json({ error: 'No type provided' }); + } + if (!service?.apiKey) { + return res.status(400).json({ error: 'No service found' }); + } + if (type === 'movie' && !seasons) { + return res.status(400).json({ error: 'No seasons provided' }); + } + const serviceUrl = new URL(service.url); + Consola.info('Got an Overseerr request with these arguments', { + mediaType: type, + mediaId: id, + seasons, + }); + return axios + .post( + `${serviceUrl.origin}/api/v1/request`, + { + mediaType: type, + mediaId: Number(id), + seasons, + }, + { + headers: { + // Set X-Api-Key to the value of the API key + 'X-Api-Key': service.apiKey, + }, + } + ) + .then((axiosres) => res.status(200).json(axiosres.data)) + .catch((err) => + res.status(500).json({ + message: err.message, + }) + ); +} + +export default async (req: NextApiRequest, res: NextApiResponse) => { + if (req.method === 'POST') { + return Post(req, res); + } + if (req.method === 'GET') { + return Get(req, res); + } + return res.status(405).json({ + statusCode: 405, + message: 'Method not allowed', + }); +}; diff --git a/src/pages/api/modules/overseerr/index.ts b/src/pages/api/modules/overseerr/index.ts new file mode 100644 index 000000000..92b86a5ac --- /dev/null +++ b/src/pages/api/modules/overseerr/index.ts @@ -0,0 +1,43 @@ +import axios from 'axios'; +import { getCookie } from 'cookies-next'; +import { NextApiRequest, NextApiResponse } from 'next'; +import { getConfig } from '../../../../tools/getConfig'; +import { Config } from '../../../../tools/types'; + +async function Get(req: NextApiRequest, res: NextApiResponse) { + const configName = getCookie('config-name', { req }); + const { config }: { config: Config } = getConfig(configName?.toString() ?? 'default').props; + const { query } = req.query; + const service = config.services.find((service) => service.type === 'Overseerr'); + // If query is an empty string, return an empty array + if (query === '' || query === undefined) { + return res.status(200).json([]); + } + if (!service || !query || service === undefined || !service.apiKey) { + return res.status(400).json({ + error: 'Wrong request', + }); + } + const serviceUrl = new URL(service.url); + const data = await axios + .get(`${serviceUrl.origin}/api/v1/search?query=${query}`, { + headers: { + // Set X-Api-Key to the value of the API key + 'X-Api-Key': service.apiKey, + }, + }) + .then((res) => res.data); + // Get login, password and url from the body + res.status(200).json(data); +} + +export default async (req: NextApiRequest, res: NextApiResponse) => { + // Filter out if the reuqest is a POST or a GET + if (req.method === 'GET') { + return Get(req, res); + } + return res.status(405).json({ + statusCode: 405, + message: 'Method not allowed', + }); +}; diff --git a/src/tools/styles.ts b/src/tools/styles.ts deleted file mode 100644 index 089a92727..000000000 --- a/src/tools/styles.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { MantineProviderProps } from '@mantine/core'; - -//TODO: Migarate this to v5.0 -// export const styles: MantineProviderProps['styles'] = { -// Checkbox: { -// input: { cursor: 'pointer' }, -// label: { cursor: 'pointer' }, -// }, -// Switch: { -// input: { cursor: 'pointer' }, -// label: { cursor: 'pointer' }, -// }, -// }; diff --git a/src/tools/types.ts b/src/tools/types.ts index f745c3524..00ef088c1 100644 --- a/src/tools/types.ts +++ b/src/tools/types.ts @@ -70,6 +70,7 @@ export const ServiceTypeList = [ 'Readarr', 'Sonarr', 'Transmission', + 'Overseerr', ]; export type ServiceType = | 'Other' @@ -82,6 +83,7 @@ export type ServiceType = | 'Radarr' | 'Readarr' | 'Sonarr' + | 'Overseerr' | 'Transmission'; export function tryMatchPort(name: string | undefined, form?: any) { @@ -104,6 +106,9 @@ export const portmap = [ { name: 'readarr', value: '8787' }, { name: 'deluge', value: '8112' }, { name: 'transmission', value: '9091' }, + { name: 'plex', value: '32400' }, + { name: 'emby', value: '8096' }, + { name: 'overseerr', value: '5055' }, { name: 'dash.', value: '3001' }, ]; @@ -167,7 +172,7 @@ export const MatchingImages: { export interface serviceItem { id: string; name: string; - type: string; + type: ServiceType; url: string; icon: string; category?: string; diff --git a/yarn.lock b/yarn.lock index dbb8259a7..5527bb6e1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1084,112 +1084,127 @@ __metadata: languageName: node linkType: hard -"@mantine/carousel@npm:^5.0.0": - version: 5.0.0 - resolution: "@mantine/carousel@npm:5.0.0" +"@mantine/carousel@npm:^5.1.0": + version: 5.1.0 + resolution: "@mantine/carousel@npm:5.1.0" dependencies: - "@mantine/utils": 5.0.0 + "@mantine/utils": 5.1.0 peerDependencies: - "@mantine/core": 5.0.0 - "@mantine/hooks": 5.0.0 - embla-carousel-react: 6.2.0 + "@mantine/core": 5.1.0 + "@mantine/hooks": 5.1.0 + embla-carousel-react: 7.0.0 react: ">=16.8.0" - checksum: 67930f5c4db077c250d40d1ecc641a0cd92e8223db3cb41a061fa3505d67fc8fdc7040bb74b4093512e053f4807d314e301d7d4c7039bad8b71a0dea771866d6 + checksum: 0aa6beda2c1c406ea1cf07ece919999a7591b838579c1a0b90f88d1491d75f0871418deb700976791e14d870543f7594ea28569af13b330444ed8c810ab47ac3 languageName: node linkType: hard -"@mantine/core@npm:^5.0.2": - version: 5.0.2 - resolution: "@mantine/core@npm:5.0.2" +"@mantine/core@npm:^5.1.0": + version: 5.1.0 + resolution: "@mantine/core@npm:5.1.0" dependencies: "@floating-ui/react-dom-interactions": 0.6.6 - "@mantine/styles": 5.0.2 - "@mantine/utils": 5.0.2 + "@mantine/styles": 5.1.0 + "@mantine/utils": 5.1.0 "@radix-ui/react-scroll-area": 1.0.0 react-textarea-autosize: 8.3.4 peerDependencies: - "@mantine/hooks": 5.0.2 + "@mantine/hooks": 5.1.0 react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: 3677995cb6b31481b29ee56096ba9366d68062fb9e636e271685a2e0ec7df852231edbe111aded6b02e7ddf2c6abc20f6c6aead42a30d130d8687540406dcedd + checksum: a170d3a97c66fc78ade98a4ec01ff854e998d5af3acca46275c241bb74ed8e3980cdb71e40fed6b9ce1d210d3ec6dfc8bb71cd591b359d10f194dac3faa6f4b9 languageName: node linkType: hard -"@mantine/dates@npm:^5.0.2": - version: 5.0.2 - resolution: "@mantine/dates@npm:5.0.2" +"@mantine/dates@npm:^5.1.0": + version: 5.1.0 + resolution: "@mantine/dates@npm:5.1.0" dependencies: - "@mantine/utils": 5.0.2 + "@mantine/utils": 5.1.0 peerDependencies: - "@mantine/core": 5.0.2 - "@mantine/hooks": 5.0.2 + "@mantine/core": 5.1.0 + "@mantine/hooks": 5.1.0 dayjs: ">=1.0.0" react: ">=16.8.0" - checksum: 818fce70324347c870dd04354c9e4b29f7d4241f56b3d7fbac668de3b25efa29a265e2ba817a42bf7151548e89f17825b63047fb2694215ccad1c993a3b2a772 + checksum: 0352073ed3f553853f5024f319f170ff22f34317ad6fe4500f847828667763f9a1108b701c60754f159ddab35a91e68a29f60ee4f00f9b43fce6128f8d1d0d10 languageName: node linkType: hard -"@mantine/dropzone@npm:^5.0.2": - version: 5.0.2 - resolution: "@mantine/dropzone@npm:5.0.2" +"@mantine/dropzone@npm:^5.1.0": + version: 5.1.0 + resolution: "@mantine/dropzone@npm:5.1.0" dependencies: - "@mantine/utils": 5.0.2 + "@mantine/utils": 5.1.0 react-dropzone: 14.2.1 peerDependencies: - "@mantine/core": 5.0.2 - "@mantine/hooks": 5.0.2 + "@mantine/core": 5.1.0 + "@mantine/hooks": 5.1.0 react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: 3837d3f4763a33407c197cf7ca3510b2d0051be5419bd48e421a0fc748545c409f49200ee18942932403bd770d76c6ac78770d1067b67dfcbe7837d6606b100a + checksum: 5a9c7fe0db1bf6af845a161c1620fb544fcebd376785abfb77d0e9cf2babef00068a5e8f32c7707f100ccf2f7caf6e75c817ec6d6fa8b5d470ebf749dcbd5624 languageName: node linkType: hard -"@mantine/form@npm:^5.0.2": - version: 5.0.2 - resolution: "@mantine/form@npm:5.0.2" +"@mantine/form@npm:^5.1.0": + version: 5.1.0 + resolution: "@mantine/form@npm:5.1.0" dependencies: + fast-deep-equal: ^3.1.3 klona: ^2.0.5 peerDependencies: react: ">=16.8.0" - checksum: db08b33a0e95a20fb4dffc22d58be7f5ece6bbd824b40fa1bccee310587b932f73492bdef1de8d8023777a8bf6d1c7fe76594dba319055b8865543b53f3c60e0 + checksum: 4727e7f8842918aa3adf58030d4f27083bb245661b540f0ad3d14d37fc14d1851e454f5462a78b773e3839aaf535d93ed4d4628782d04d0cb899a900d8e6b70b languageName: node linkType: hard -"@mantine/hooks@npm:^5.0.2": - version: 5.0.2 - resolution: "@mantine/hooks@npm:5.0.2" +"@mantine/hooks@npm:^5.1.0": + version: 5.1.0 + resolution: "@mantine/hooks@npm:5.1.0" peerDependencies: react: ">=16.8.0" - checksum: 792b2ce59bc5a3bfb14e3f12762eaeb1e9b7edf080d03e2515c68ddd6b54fb6923e9c6461e5bd1030c043801f241f09be15bae5d6236b58a4206203a4d0166c6 + checksum: de4c2c1fe408efddeda88c331242c14e6aa44f65a0d78fc9ca2812e07f9593f27dbc593afd4f320bc36e618bbb7d3cbe5869aebe6d7f2a8b9af8f342f1913a5a languageName: node linkType: hard -"@mantine/next@npm:^5.0.2": - version: 5.0.2 - resolution: "@mantine/next@npm:5.0.2" +"@mantine/modals@npm:^5.1.0": + version: 5.1.0 + resolution: "@mantine/modals@npm:5.1.0" dependencies: - "@mantine/ssr": 5.0.2 - "@mantine/styles": 5.0.2 + "@mantine/utils": 5.1.0 + peerDependencies: + "@mantine/core": 5.1.0 + "@mantine/hooks": 5.1.0 + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: ab3d9d78f70b631bec7f0a89d0f4c5275b043fb147ac5db83c7be8ef5d80fa75e9a4560c470f8c888e13c00b1d533c5d8c89eb6c9ce19297c32178044946cb93 + languageName: node + linkType: hard + +"@mantine/next@npm:^5.1.0": + version: 5.1.0 + resolution: "@mantine/next@npm:5.1.0" + dependencies: + "@mantine/ssr": 5.1.0 + "@mantine/styles": 5.1.0 peerDependencies: next: "*" react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: f03257fe69a36fc54e6d8eda2d4a791277eecc1642fe6ffccae6d252e728288ee30cd89acb16a35c58ac1b064f2e8ba7950b4f98a0f2b003d34ae6024c107159 + checksum: 422f53fa3b18525b2baf95988260782c0b4321fdb6d004f611830b456d492feaa53c40fb5f2ba8f546701ed8b608c8a01ff129d7f76c5dabacb164bf3af20529 languageName: node linkType: hard -"@mantine/notifications@npm:^5.0.2": - version: 5.0.2 - resolution: "@mantine/notifications@npm:5.0.2" +"@mantine/notifications@npm:^5.1.0": + version: 5.1.0 + resolution: "@mantine/notifications@npm:5.1.0" dependencies: - "@mantine/utils": 5.0.2 + "@mantine/utils": 5.1.0 react-transition-group: 4.4.2 peerDependencies: - "@mantine/core": 5.0.2 - "@mantine/hooks": 5.0.2 + "@mantine/core": 5.1.0 + "@mantine/hooks": 5.1.0 react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: 6284e9e26fa64a20417eaf9593a935083bc95095a84d8ae98c32c22becec202419523d7bb71ecfb08e307d05d11565a8403da68da42252c867a351fa08435a63 + checksum: 0e86dd8f114b6b971f2ab72ac76e0faf9c592a59893e6ffbd0f22fef14abfe58610fc6bf3526b1258e95591d5b5472b0d7fef455a3caab5bca7b795d0fc4c545 languageName: node linkType: hard @@ -1208,24 +1223,24 @@ __metadata: languageName: node linkType: hard -"@mantine/ssr@npm:5.0.2": - version: 5.0.2 - resolution: "@mantine/ssr@npm:5.0.2" +"@mantine/ssr@npm:5.1.0": + version: 5.1.0 + resolution: "@mantine/ssr@npm:5.1.0" dependencies: - "@mantine/styles": 5.0.2 + "@mantine/styles": 5.1.0 html-react-parser: 1.4.12 peerDependencies: "@emotion/react": ">=11.9.0" "@emotion/server": ">=11.4.0" react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: 396f3da4cdd15dde1a6707350b428862127a66d204c5b625cab70b92729d709d42a4ffeeb8dfa267035648373f3f3e016d78e15ed9758b00266013475008e663 + checksum: 1b3f9d81eaccd7a43db21b496b2cb6e5ba019e96e05faf8109577330a54b6eb28b393204c1eb01639bde3c04ae503491b7913e4573806803388ed91947a0bfb0 languageName: node linkType: hard -"@mantine/styles@npm:5.0.2": - version: 5.0.2 - resolution: "@mantine/styles@npm:5.0.2" +"@mantine/styles@npm:5.1.0": + version: 5.1.0 + resolution: "@mantine/styles@npm:5.1.0" dependencies: clsx: 1.1.1 csstype: 3.0.9 @@ -1233,7 +1248,7 @@ __metadata: "@emotion/react": ">=11.9.0" react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: a602c1453007d52c2d779e3ff107b26f6072158c3d9ffda4be42276bdccfff4b7695cb48f41ead0aacd42af97d40d61b61b022c530200c60f8cf3db711721bee + checksum: 9d7dc4d55eeea09f86e205a33051d397da45c06ce213860d4f013b9770dde34256bd8056d76ceceb6993828f864df103b1967b2a9d9b0cbe29d7334ec2c30318 languageName: node linkType: hard @@ -1246,12 +1261,12 @@ __metadata: languageName: node linkType: hard -"@mantine/utils@npm:5.0.2": - version: 5.0.2 - resolution: "@mantine/utils@npm:5.0.2" +"@mantine/utils@npm:5.1.0": + version: 5.1.0 + resolution: "@mantine/utils@npm:5.1.0" peerDependencies: react: ">=16.8.0" - checksum: fe618eb37c8f900ea2ed7ba25a95c01d55a9cc6d3e82ee46d36446258e52a8828098a5d8bd0c53ca269bb876cffd4c8162110715da7e912f552a99b1f2f5ba22 + checksum: f6d2dd28f97d9e2d09eea3db7d1f2e0a82c451bd7a0c80f9a38c421c7003052b822917d4128a055e39c5822c6de61ecb53728aa86ab726bf4dcda911ab47f650 languageName: node linkType: hard @@ -2960,13 +2975,23 @@ __metadata: languageName: node linkType: hard -"color-name@npm:~1.1.4": +"color-name@npm:^1.0.0, color-name@npm:~1.1.4": version: 1.1.4 resolution: "color-name@npm:1.1.4" checksum: b0445859521eb4021cd0fb0cc1a75cecf67fceecae89b63f62b201cca8d345baf8b952c966862a9d9a2632987d4f6581f0ec8d957dfacece86f0a7919316f610 languageName: node linkType: hard +"color-string@npm:^1.9.0": + version: 1.9.1 + resolution: "color-string@npm:1.9.1" + dependencies: + color-name: ^1.0.0 + simple-swizzle: ^0.2.2 + checksum: c13fe7cff7885f603f49105827d621ce87f4571d78ba28ef4a3f1a104304748f620615e6bf065ecd2145d0d9dad83a3553f52bb25ede7239d18e9f81622f1cc5 + languageName: node + linkType: hard + "color-support@npm:^1.1.3": version: 1.1.3 resolution: "color-support@npm:1.1.3" @@ -2976,6 +3001,16 @@ __metadata: languageName: node linkType: hard +"color@npm:^4.2.3": + version: 4.2.3 + resolution: "color@npm:4.2.3" + dependencies: + color-convert: ^2.0.1 + color-string: ^1.9.0 + checksum: 0579629c02c631b426780038da929cca8e8d80a40158b09811a0112a107c62e10e4aad719843b791b1e658ab4e800558f2e87ca4522c8b32349d497ecb6adeb4 + languageName: node + linkType: hard + "combined-stream@npm:^1.0.8": version: 1.0.8 resolution: "combined-stream@npm:1.0.8" @@ -3016,6 +3051,13 @@ __metadata: languageName: node linkType: hard +"consola@npm:^2.15.3": + version: 2.15.3 + resolution: "consola@npm:2.15.3" + checksum: 8ef7a09b703ec67ac5c389a372a33b6dc97eda6c9876443a60d76a3076eea0259e7f67a4e54fd5a52f97df73690822d090cf8b7e102b5761348afef7c6d03e28 + languageName: node + linkType: hard + "console-control-strings@npm:^1.1.0": version: 1.1.0 resolution: "console-control-strings@npm:1.1.0" @@ -3292,6 +3334,13 @@ __metadata: languageName: node linkType: hard +"deep-extend@npm:^0.6.0": + version: 0.6.0 + resolution: "deep-extend@npm:0.6.0" + checksum: 7be7e5a8d468d6b10e6a67c3de828f55001b6eb515d014f7aeb9066ce36bd5717161eb47d6a0f7bed8a9083935b465bc163ee2581c8b128d29bf61092fdf57a7 + languageName: node + linkType: hard + "deep-is@npm:^0.1.3": version: 0.1.4 resolution: "deep-is@npm:0.1.4" @@ -3351,6 +3400,13 @@ __metadata: languageName: node linkType: hard +"detect-libc@npm:^2.0.0, detect-libc@npm:^2.0.1": + version: 2.0.1 + resolution: "detect-libc@npm:2.0.1" + checksum: ccb05fcabbb555beb544d48080179c18523a343face9ee4e1a86605a8715b4169f94d663c21a03c310ac824592f2ba9a5270218819bb411ad7be578a527593d7 + languageName: node + linkType: hard + "detect-newline@npm:^3.0.0": version: 3.1.0 resolution: "detect-newline@npm:3.1.0" @@ -3485,21 +3541,21 @@ __metadata: languageName: node linkType: hard -"embla-carousel-react@npm:^7.0.0-rc05": - version: 7.0.0-rc05 - resolution: "embla-carousel-react@npm:7.0.0-rc05" +"embla-carousel-react@npm:^7.0.0": + version: 7.0.0 + resolution: "embla-carousel-react@npm:7.0.0" dependencies: - embla-carousel: 7.0.0-rc05 + embla-carousel: 7.0.0 peerDependencies: react: ^18.1.0 - checksum: d6d579b047e7ba106653c052e30b198f74288e7cfb501d3212e6516afa5417b9539415a546e38e21ba1fe97069db4c809be3317eaee2bd963bf530a6b73eef5c + checksum: d44b93901fb6a5be2236ce86115d7132a91f3e1943dc4d2cb0bccf045173008e947a35ebf0e345b90dc33ba06d8a0011913d45e2dbfd6cc47d58953bab96486e languageName: node linkType: hard -"embla-carousel@npm:7.0.0-rc05": - version: 7.0.0-rc05 - resolution: "embla-carousel@npm:7.0.0-rc05" - checksum: 7cfe080ab3bdfc013a7d4304a3deb6f2aeef34f1c8f613f5d5760995dcb91512787edac534830d5c22aaa803e6377b3964cf4d2a41eb519f1d6cd297d9a2cbee +"embla-carousel@npm:7.0.0": + version: 7.0.0 + resolution: "embla-carousel@npm:7.0.0" + checksum: e662d18caf4371c04673372bf0e9144aec4b97629bbf48eb623e938ef27bd9f8de0d0f5b344d67bfdc777cce011518b7e833ec74773338292701c7d7efacb779 languageName: node linkType: hard @@ -4044,6 +4100,13 @@ __metadata: languageName: node linkType: hard +"expand-template@npm:^2.0.3": + version: 2.0.3 + resolution: "expand-template@npm:2.0.3" + checksum: 588c19847216421ed92befb521767b7018dc88f88b0576df98cb242f20961425e96a92cbece525ef28cc5becceae5d544ae0f5b9b5e2aa05acb13716ca5b3099 + languageName: node + linkType: hard + "expect@npm:^28.1.3": version: 28.1.3 resolution: "expect@npm:28.1.3" @@ -4396,6 +4459,13 @@ __metadata: languageName: node linkType: hard +"github-from-package@npm:0.0.0": + version: 0.0.0 + resolution: "github-from-package@npm:0.0.0" + checksum: 14e448192a35c1e42efee94c9d01a10f42fe790375891a24b25261246ce9336ab9df5d274585aedd4568f7922246c2a78b8a8cd2571bfe99c693a9718e7dd0e3 + languageName: node + linkType: hard + "glob-parent@npm:^5.1.2": version: 5.1.2 resolution: "glob-parent@npm:5.1.2" @@ -4613,14 +4683,15 @@ __metadata: "@dnd-kit/utilities": ^3.2.0 "@emotion/react": ^11.10.0 "@emotion/server": ^11.10.0 - "@mantine/carousel": ^5.0.0 - "@mantine/core": ^5.0.2 - "@mantine/dates": ^5.0.2 - "@mantine/dropzone": ^5.0.2 - "@mantine/form": ^5.0.2 - "@mantine/hooks": ^5.0.2 - "@mantine/next": ^5.0.2 - "@mantine/notifications": ^5.0.2 + "@mantine/carousel": ^5.1.0 + "@mantine/core": ^5.1.0 + "@mantine/dates": ^5.1.0 + "@mantine/dropzone": ^5.1.0 + "@mantine/form": ^5.1.0 + "@mantine/hooks": ^5.1.0 + "@mantine/modals": ^5.1.0 + "@mantine/next": ^5.1.0 + "@mantine/notifications": ^5.1.0 "@mantine/prism": ^5.0.0 "@next/bundle-analyzer": ^12.1.4 "@next/eslint-plugin-next": ^12.1.4 @@ -4635,10 +4706,11 @@ __metadata: "@typescript-eslint/parser": ^5.30.7 add: ^2.0.6 axios: ^0.27.2 + consola: ^2.15.3 cookies-next: ^2.1.1 dayjs: ^1.11.4 dockerode: ^3.3.2 - embla-carousel-react: ^7.0.0-rc05 + embla-carousel-react: ^7.0.0 eslint: ^8.20.0 eslint-config-airbnb: ^19.0.4 eslint-config-airbnb-typescript: ^17.0.0 @@ -4658,6 +4730,7 @@ __metadata: prism-react-renderer: ^1.3.5 react: ^18.2.0 react-dom: ^18.2.0 + sharp: ^0.30.7 systeminformation: ^5.12.1 typescript: ^4.7.4 uuid: ^8.3.2 @@ -4860,6 +4933,13 @@ __metadata: languageName: node linkType: hard +"ini@npm:~1.3.0": + version: 1.3.8 + resolution: "ini@npm:1.3.8" + checksum: dfd98b0ca3a4fc1e323e38a6c8eb8936e31a97a918d3b377649ea15bdb15d481207a0dda1021efbd86b464cae29a0d33c1d7dcaf6c5672bee17fa849bc50a1b3 + languageName: node + linkType: hard + "inline-style-parser@npm:0.1.1": version: 0.1.1 resolution: "inline-style-parser@npm:0.1.1" @@ -4899,6 +4979,13 @@ __metadata: languageName: node linkType: hard +"is-arrayish@npm:^0.3.1": + version: 0.3.2 + resolution: "is-arrayish@npm:0.3.2" + checksum: 977e64f54d91c8f169b59afcd80ff19227e9f5c791fa28fa2e5bce355cbaf6c2c356711b734656e80c9dd4a854dd7efcf7894402f1031dfc5de5d620775b4d5f + languageName: node + linkType: hard + "is-bigint@npm:^1.0.1": version: 1.0.4 resolution: "is-bigint@npm:1.0.4" @@ -5931,7 +6018,7 @@ __metadata: languageName: node linkType: hard -"minimist@npm:^1.2.0, minimist@npm:^1.2.6, minimist@npm:~1.2.5": +"minimist@npm:^1.2.0, minimist@npm:^1.2.3, minimist@npm:^1.2.6, minimist@npm:~1.2.5": version: 1.2.6 resolution: "minimist@npm:1.2.6" checksum: d15428cd1e11eb14e1233bcfb88ae07ed7a147de251441d61158619dfb32c4d7e9061d09cab4825fdee18ecd6fce323228c8c47b5ba7cd20af378ca4048fb3fb @@ -6008,7 +6095,7 @@ __metadata: languageName: node linkType: hard -"mkdirp-classic@npm:^0.5.2": +"mkdirp-classic@npm:^0.5.2, mkdirp-classic@npm:^0.5.3": version: 0.5.3 resolution: "mkdirp-classic@npm:0.5.3" checksum: 3f4e088208270bbcc148d53b73e9a5bd9eef05ad2cbf3b3d0ff8795278d50dd1d11a8ef1875ff5aea3fa888931f95bfcb2ad5b7c1061cfefd6284d199e6776ac @@ -6080,6 +6167,13 @@ __metadata: languageName: node linkType: hard +"napi-build-utils@npm:^1.0.1": + version: 1.0.2 + resolution: "napi-build-utils@npm:1.0.2" + checksum: 06c14271ee966e108d55ae109f340976a9556c8603e888037145d6522726aebe89dd0c861b4b83947feaf6d39e79e08817559e8693deedc2c94e82c5cbd090c7 + languageName: node + linkType: hard + "natural-compare@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare@npm:1.4.0" @@ -6158,6 +6252,24 @@ __metadata: languageName: node linkType: hard +"node-abi@npm:^3.3.0": + version: 3.24.0 + resolution: "node-abi@npm:3.24.0" + dependencies: + semver: ^7.3.5 + checksum: d90ab48802497b2203800cac71018668e99c246435395ca4f67afcabf689e7e81568ed36e8036bae79a052b63ea5707375bece6ca0a1d2e2b99bfafde7a5c9b2 + languageName: node + linkType: hard + +"node-addon-api@npm:^5.0.0": + version: 5.0.0 + resolution: "node-addon-api@npm:5.0.0" + dependencies: + node-gyp: latest + checksum: 7c5e2043ac37f6108784d94ed73a44ae6d3e68eb968de60680922fc6bc3d17fa69448c0feb4e0c9d3f4c74a0324822e566a8340a56916d9d6f23cb3e85620334 + languageName: node + linkType: hard + "node-domexception@npm:1.0.0": version: 1.0.0 resolution: "node-domexception@npm:1.0.0" @@ -6560,6 +6672,28 @@ __metadata: languageName: node linkType: hard +"prebuild-install@npm:^7.1.1": + version: 7.1.1 + resolution: "prebuild-install@npm:7.1.1" + dependencies: + detect-libc: ^2.0.0 + expand-template: ^2.0.3 + github-from-package: 0.0.0 + minimist: ^1.2.3 + mkdirp-classic: ^0.5.3 + napi-build-utils: ^1.0.1 + node-abi: ^3.3.0 + pump: ^3.0.0 + rc: ^1.2.7 + simple-get: ^4.0.0 + tar-fs: ^2.0.0 + tunnel-agent: ^0.6.0 + bin: + prebuild-install: bin.js + checksum: dbf96d0146b6b5827fc8f67f72074d2e19c69628b9a7a0a17d0fad1bf37e9f06922896972e074197fc00a52eae912993e6ef5a0d471652f561df5cb516f3f467 + languageName: node + linkType: hard + "prelude-ls@npm:^1.2.1": version: 1.2.1 resolution: "prelude-ls@npm:1.2.1" @@ -6680,6 +6814,20 @@ __metadata: languageName: node linkType: hard +"rc@npm:^1.2.7": + version: 1.2.8 + resolution: "rc@npm:1.2.8" + dependencies: + deep-extend: ^0.6.0 + ini: ~1.3.0 + minimist: ^1.2.0 + strip-json-comments: ~2.0.1 + bin: + rc: ./cli.js + checksum: 2e26e052f8be2abd64e6d1dabfbd7be03f80ec18ccbc49562d31f617d0015fbdbcf0f9eed30346ea6ab789e0fdfe4337f033f8016efdbee0df5354751842080e + languageName: node + linkType: hard + "react-dom@npm:^18.2.0": version: 18.2.0 resolution: "react-dom@npm:18.2.0" @@ -6972,6 +7120,13 @@ __metadata: languageName: node linkType: hard +"safe-buffer@npm:^5.0.1, safe-buffer@npm:~5.2.0": + version: 5.2.1 + resolution: "safe-buffer@npm:5.2.1" + checksum: b99c4b41fdd67a6aaf280fcd05e9ffb0813654894223afb78a31f14a19ad220bba8aba1cb14eddce1fcfb037155fe6de4e861784eb434f7d11ed58d1e70dd491 + languageName: node + linkType: hard + "safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1": version: 5.1.2 resolution: "safe-buffer@npm:5.1.2" @@ -6979,13 +7134,6 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:~5.2.0": - version: 5.2.1 - resolution: "safe-buffer@npm:5.2.1" - checksum: b99c4b41fdd67a6aaf280fcd05e9ffb0813654894223afb78a31f14a19ad220bba8aba1cb14eddce1fcfb037155fe6de4e861784eb434f7d11ed58d1e70dd491 - languageName: node - linkType: hard - "safer-buffer@npm:>= 2.1.2 < 3.0.0, safer-buffer@npm:~2.1.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" @@ -7029,6 +7177,23 @@ __metadata: languageName: node linkType: hard +"sharp@npm:^0.30.7": + version: 0.30.7 + resolution: "sharp@npm:0.30.7" + dependencies: + color: ^4.2.3 + detect-libc: ^2.0.1 + node-addon-api: ^5.0.0 + node-gyp: latest + prebuild-install: ^7.1.1 + semver: ^7.3.7 + simple-get: ^4.0.1 + tar-fs: ^2.1.1 + tunnel-agent: ^0.6.0 + checksum: bbc63ca3c7ea8a5bff32cd77022cfea30e25a03f5bd031e935924bf6cf0e11e3388e8b0e22b3137bf8816aa73407f1e4fbeb190f3a35605c27ffca9f32b91601 + languageName: node + linkType: hard + "shebang-command@npm:^2.0.0": version: 2.0.0 resolution: "shebang-command@npm:2.0.0" @@ -7063,6 +7228,33 @@ __metadata: languageName: node linkType: hard +"simple-concat@npm:^1.0.0": + version: 1.0.1 + resolution: "simple-concat@npm:1.0.1" + checksum: 4d211042cc3d73a718c21ac6c4e7d7a0363e184be6a5ad25c8a1502e49df6d0a0253979e3d50dbdd3f60ef6c6c58d756b5d66ac1e05cda9cacd2e9fc59e3876a + languageName: node + linkType: hard + +"simple-get@npm:^4.0.0, simple-get@npm:^4.0.1": + version: 4.0.1 + resolution: "simple-get@npm:4.0.1" + dependencies: + decompress-response: ^6.0.0 + once: ^1.3.1 + simple-concat: ^1.0.0 + checksum: e4132fd27cf7af230d853fa45c1b8ce900cb430dd0a3c6d3829649fe4f2b26574c803698076c4006450efb0fad2ba8c5455fbb5755d4b0a5ec42d4f12b31d27e + languageName: node + linkType: hard + +"simple-swizzle@npm:^0.2.2": + version: 0.2.2 + resolution: "simple-swizzle@npm:0.2.2" + dependencies: + is-arrayish: ^0.3.1 + checksum: a7f3f2ab5c76c4472d5c578df892e857323e452d9f392e1b5cf74b74db66e6294a1e1b8b390b519fa1b96b5b613f2a37db6cffef52c3f1f8f3c5ea64eb2d54c0 + languageName: node + linkType: hard + "sirv@npm:^1.0.7": version: 1.0.19 resolution: "sirv@npm:1.0.19" @@ -7317,6 +7509,13 @@ __metadata: languageName: node linkType: hard +"strip-json-comments@npm:~2.0.1": + version: 2.0.1 + resolution: "strip-json-comments@npm:2.0.1" + checksum: 1074ccb63270d32ca28edfb0a281c96b94dc679077828135141f27d52a5a398ef5e78bcf22809d23cadc2b81dfbe345eb5fd8699b385c8b1128907dec4a7d1e1 + languageName: node + linkType: hard + "style-to-js@npm:1.1.0": version: 1.1.0 resolution: "style-to-js@npm:1.1.0" @@ -7420,6 +7619,18 @@ __metadata: languageName: node linkType: hard +"tar-fs@npm:^2.0.0, tar-fs@npm:^2.1.1": + version: 2.1.1 + resolution: "tar-fs@npm:2.1.1" + dependencies: + chownr: ^1.1.1 + mkdirp-classic: ^0.5.2 + pump: ^3.0.0 + tar-stream: ^2.1.4 + checksum: f5b9a70059f5b2969e65f037b4e4da2daf0fa762d3d232ffd96e819e3f94665dbbbe62f76f084f1acb4dbdcce16c6e4dac08d12ffc6d24b8d76720f4d9cf032d + languageName: node + linkType: hard + "tar-fs@npm:~2.0.1": version: 2.0.1 resolution: "tar-fs@npm:2.0.1" @@ -7432,7 +7643,7 @@ __metadata: languageName: node linkType: hard -"tar-stream@npm:^2.0.0": +"tar-stream@npm:^2.0.0, tar-stream@npm:^2.1.4": version: 2.2.0 resolution: "tar-stream@npm:2.2.0" dependencies: @@ -7582,6 +7793,15 @@ __metadata: languageName: node linkType: hard +"tunnel-agent@npm:^0.6.0": + version: 0.6.0 + resolution: "tunnel-agent@npm:0.6.0" + dependencies: + safe-buffer: ^5.0.1 + checksum: 05f6510358f8afc62a057b8b692f05d70c1782b70db86d6a1e0d5e28a32389e52fa6e7707b6c5ecccacc031462e4bc35af85ecfe4bbc341767917b7cf6965711 + languageName: node + linkType: hard + "tweetnacl@npm:^0.14.3": version: 0.14.5 resolution: "tweetnacl@npm:0.14.5"