feat(rating): added IMDB Radarr proxy (#3496)
* feat(rating): added imdb radarr proxy Signed-off-by: marcofaggian <m@marcofaggian.com> * refactor(rating/imdb): rm export unused interfaces Signed-off-by: marcofaggian <m@marcofaggian.com> * docs(rating/imdb): rt to imdb Signed-off-by: marcofaggian <m@marcofaggian.com> * refactor(rating/imdb): specified error message Signed-off-by: marcofaggian <m@marcofaggian.com> * refactor(rating/imdb): rm line break Signed-off-by: marcofaggian <m@marcofaggian.com> * refactor(rating): conform to types patter Signed-off-by: marcofaggian <m@marcofaggian.com> * chore(rating/imdb): added line to translation file Signed-off-by: marcofaggian <m@marcofaggian.com> * feat(rating/imdb): ratings to ratingscombined Signed-off-by: marcofaggian <m@marcofaggian.com> * fix(rating/imdb): reinstating ratings route Signed-off-by: marcofaggian <m@marcofaggian.com> * docs(ratings): openapi ratings Signed-off-by: marcofaggian <m@marcofaggian.com> * chore(ratings): undo openapi ratings apex Signed-off-by: marcofaggian <m@marcofaggian.com> --------- Signed-off-by: marcofaggian <m@marcofaggian.com>
This commit is contained in:
@@ -5338,6 +5338,63 @@ paths:
|
|||||||
audienceRating:
|
audienceRating:
|
||||||
type: string
|
type: string
|
||||||
enum: ['Spilled', 'Upright']
|
enum: ['Spilled', 'Upright']
|
||||||
|
/movie/{movieId}/ratingscombined:
|
||||||
|
get:
|
||||||
|
summary: Get RT and IMDB movie ratings combined
|
||||||
|
description: Returns ratings from RottenTomatoes and IMDB based on the provided movieId in a JSON object.
|
||||||
|
tags:
|
||||||
|
- movies
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: movieId
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
example: 337401
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Ratings returned
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
rt:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
example: Mulan
|
||||||
|
year:
|
||||||
|
type: number
|
||||||
|
example: 2020
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
example: 'http://www.rottentomatoes.com/m/mulan_2020/'
|
||||||
|
criticsScore:
|
||||||
|
type: number
|
||||||
|
example: 85
|
||||||
|
criticsRating:
|
||||||
|
type: string
|
||||||
|
enum: ['Rotten', 'Fresh', 'Certified Fresh']
|
||||||
|
audienceScore:
|
||||||
|
type: number
|
||||||
|
example: 65
|
||||||
|
audienceRating:
|
||||||
|
type: string
|
||||||
|
enum: ['Spilled', 'Upright']
|
||||||
|
imdb:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
example: I am Legend
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
example: 'https://www.imdb.com/title/tt0480249'
|
||||||
|
criticsScore:
|
||||||
|
type: number
|
||||||
|
example: 6.5
|
||||||
/tv/{tvId}:
|
/tv/{tvId}:
|
||||||
get:
|
get:
|
||||||
summary: Get TV details
|
summary: Get TV details
|
||||||
|
|||||||
195
server/api/rating/imdbRadarrProxy.ts
Normal file
195
server/api/rating/imdbRadarrProxy.ts
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
import ExternalAPI from '@server/api/externalapi';
|
||||||
|
import cacheManager from '@server/lib/cache';
|
||||||
|
|
||||||
|
type IMDBRadarrProxyResponse = IMDBMovie[];
|
||||||
|
|
||||||
|
interface IMDBMovie {
|
||||||
|
ImdbId: string;
|
||||||
|
Overview: string;
|
||||||
|
Title: string;
|
||||||
|
OriginalTitle: string;
|
||||||
|
TitleSlug: string;
|
||||||
|
Ratings: Rating[];
|
||||||
|
MovieRatings: MovieRatings;
|
||||||
|
Runtime: number;
|
||||||
|
Images: Image[];
|
||||||
|
Genres: string[];
|
||||||
|
Popularity: number;
|
||||||
|
Premier: string;
|
||||||
|
InCinema: string;
|
||||||
|
PhysicalRelease: any;
|
||||||
|
DigitalRelease: string;
|
||||||
|
Year: number;
|
||||||
|
AlternativeTitles: AlternativeTitle[];
|
||||||
|
Translations: Translation[];
|
||||||
|
Recommendations: Recommendation[];
|
||||||
|
Credits: Credits;
|
||||||
|
Studio: string;
|
||||||
|
YoutubeTrailerId: string;
|
||||||
|
Certifications: Certification[];
|
||||||
|
Status: any;
|
||||||
|
Collection: Collection;
|
||||||
|
OriginalLanguage: string;
|
||||||
|
Homepage: string;
|
||||||
|
TmdbId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Rating {
|
||||||
|
Count: number;
|
||||||
|
Value: number;
|
||||||
|
Origin: string;
|
||||||
|
Type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MovieRatings {
|
||||||
|
Tmdb: Tmdb;
|
||||||
|
Imdb: Imdb;
|
||||||
|
Metacritic: Metacritic;
|
||||||
|
RottenTomatoes: RottenTomatoes;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Tmdb {
|
||||||
|
Count: number;
|
||||||
|
Value: number;
|
||||||
|
Type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Imdb {
|
||||||
|
Count: number;
|
||||||
|
Value: number;
|
||||||
|
Type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Metacritic {
|
||||||
|
Count: number;
|
||||||
|
Value: number;
|
||||||
|
Type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RottenTomatoes {
|
||||||
|
Count: number;
|
||||||
|
Value: number;
|
||||||
|
Type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Image {
|
||||||
|
CoverType: string;
|
||||||
|
Url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AlternativeTitle {
|
||||||
|
Title: string;
|
||||||
|
Type: string;
|
||||||
|
Language: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Translation {
|
||||||
|
Title: string;
|
||||||
|
Overview: string;
|
||||||
|
Language: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Recommendation {
|
||||||
|
TmdbId: number;
|
||||||
|
Title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Credits {
|
||||||
|
Cast: Cast[];
|
||||||
|
Crew: Crew[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Cast {
|
||||||
|
Name: string;
|
||||||
|
Order: number;
|
||||||
|
Character: string;
|
||||||
|
TmdbId: number;
|
||||||
|
CreditId: string;
|
||||||
|
Images: Image2[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Image2 {
|
||||||
|
CoverType: string;
|
||||||
|
Url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Crew {
|
||||||
|
Name: string;
|
||||||
|
Job: string;
|
||||||
|
Department: string;
|
||||||
|
TmdbId: number;
|
||||||
|
CreditId: string;
|
||||||
|
Images: Image3[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Image3 {
|
||||||
|
CoverType: string;
|
||||||
|
Url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Certification {
|
||||||
|
Country: string;
|
||||||
|
Certification: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Collection {
|
||||||
|
Name: string;
|
||||||
|
Images: any;
|
||||||
|
Overview: any;
|
||||||
|
Translations: any;
|
||||||
|
Parts: any;
|
||||||
|
TmdbId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMDBRating {
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
criticsScore: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a best-effort API. The IMDB API is technically
|
||||||
|
* private and getting access costs money/requires approval.
|
||||||
|
*
|
||||||
|
* Radarr hosts a public proxy that's in use by all Radarr instances.
|
||||||
|
*/
|
||||||
|
class IMDBRadarrProxy extends ExternalAPI {
|
||||||
|
constructor() {
|
||||||
|
super('https://api.radarr.video/v1', {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Accept: 'application/json',
|
||||||
|
},
|
||||||
|
nodeCache: cacheManager.getCache('imdb').data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ask the Radarr IMDB Proxy for the movie
|
||||||
|
*
|
||||||
|
* @param IMDBid Id of IMDB movie
|
||||||
|
*/
|
||||||
|
public async getMovieRatings(IMDBid: string): Promise<IMDBRating | null> {
|
||||||
|
try {
|
||||||
|
const data = await this.get<IMDBRadarrProxyResponse>(
|
||||||
|
`/movie/imdb/${IMDBid}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!data?.length || data[0].ImdbId !== IMDBid) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: data[0].Title,
|
||||||
|
url: `https://www.imdb.com/title/${data[0].ImdbId}`,
|
||||||
|
criticsScore: data[0].MovieRatings.Imdb.Value,
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(
|
||||||
|
`[IMDB RADARR PROXY API] Failed to retrieve movie ratings: ${e.message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IMDBRadarrProxy;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import ExternalAPI from '@server/api/externalapi';
|
||||||
import cacheManager from '@server/lib/cache';
|
import cacheManager from '@server/lib/cache';
|
||||||
import { getSettings } from '@server/lib/settings';
|
import { getSettings } from '@server/lib/settings';
|
||||||
import ExternalAPI from './externalapi';
|
|
||||||
|
|
||||||
interface RTAlgoliaSearchResponse {
|
interface RTAlgoliaSearchResponse {
|
||||||
results: {
|
results: {
|
||||||
@@ -144,6 +144,9 @@ class RottenTomatoes extends ExternalAPI {
|
|||||||
? 'Fresh'
|
? 'Fresh'
|
||||||
: 'Rotten',
|
: 'Rotten',
|
||||||
criticsScore: movie.rottenTomatoes.criticsScore,
|
criticsScore: movie.rottenTomatoes.criticsScore,
|
||||||
|
audienceRating:
|
||||||
|
movie.rottenTomatoes.audienceScore >= 60 ? 'Upright' : 'Spilled',
|
||||||
|
audienceScore: movie.rottenTomatoes.audienceScore,
|
||||||
year: Number(movie.releaseYear),
|
year: Number(movie.releaseYear),
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -192,6 +195,9 @@ class RottenTomatoes extends ExternalAPI {
|
|||||||
criticsRating:
|
criticsRating:
|
||||||
tvshow.rottenTomatoes.criticsScore >= 60 ? 'Fresh' : 'Rotten',
|
tvshow.rottenTomatoes.criticsScore >= 60 ? 'Fresh' : 'Rotten',
|
||||||
criticsScore: tvshow.rottenTomatoes.criticsScore,
|
criticsScore: tvshow.rottenTomatoes.criticsScore,
|
||||||
|
audienceRating:
|
||||||
|
tvshow.rottenTomatoes.audienceScore >= 60 ? 'Upright' : 'Spilled',
|
||||||
|
audienceScore: tvshow.rottenTomatoes.audienceScore,
|
||||||
year: Number(tvshow.releaseYear),
|
year: Number(tvshow.releaseYear),
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
7
server/api/ratings.ts
Normal file
7
server/api/ratings.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { type IMDBRating } from '@server/api/rating/imdbRadarrProxy';
|
||||||
|
import { type RTRating } from '@server/api/rating/rottentomatoes';
|
||||||
|
|
||||||
|
export interface RatingResponse {
|
||||||
|
rt?: RTRating;
|
||||||
|
imdb?: IMDBRating;
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ export type AvailableCacheIds =
|
|||||||
| 'radarr'
|
| 'radarr'
|
||||||
| 'sonarr'
|
| 'sonarr'
|
||||||
| 'rt'
|
| 'rt'
|
||||||
|
| 'imdb'
|
||||||
| 'github'
|
| 'github'
|
||||||
| 'plexguid'
|
| 'plexguid'
|
||||||
| 'plextv';
|
| 'plextv';
|
||||||
@@ -51,6 +52,10 @@ class CacheManager {
|
|||||||
stdTtl: 43200,
|
stdTtl: 43200,
|
||||||
checkPeriod: 60 * 30,
|
checkPeriod: 60 * 30,
|
||||||
}),
|
}),
|
||||||
|
imdb: new Cache('imdb', 'IMDB Radarr Proxy', {
|
||||||
|
stdTtl: 43200,
|
||||||
|
checkPeriod: 60 * 30,
|
||||||
|
}),
|
||||||
github: new Cache('github', 'GitHub API', {
|
github: new Cache('github', 'GitHub API', {
|
||||||
stdTtl: 21600,
|
stdTtl: 21600,
|
||||||
checkPeriod: 60 * 30,
|
checkPeriod: 60 * 30,
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import RottenTomatoes from '@server/api/rottentomatoes';
|
import IMDBRadarrProxy from '@server/api/rating/imdbRadarrProxy';
|
||||||
|
import RottenTomatoes from '@server/api/rating/rottentomatoes';
|
||||||
|
import { type RatingResponse } from '@server/api/ratings';
|
||||||
import TheMovieDb from '@server/api/themoviedb';
|
import TheMovieDb from '@server/api/themoviedb';
|
||||||
import { MediaType } from '@server/constants/media';
|
import { MediaType } from '@server/constants/media';
|
||||||
import Media from '@server/entity/Media';
|
import Media from '@server/entity/Media';
|
||||||
@@ -116,6 +118,9 @@ movieRoutes.get('/:id/similar', async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint backed by RottenTomatoes
|
||||||
|
*/
|
||||||
movieRoutes.get('/:id/ratings', async (req, res, next) => {
|
movieRoutes.get('/:id/ratings', async (req, res, next) => {
|
||||||
const tmdb = new TheMovieDb();
|
const tmdb = new TheMovieDb();
|
||||||
const rtapi = new RottenTomatoes();
|
const rtapi = new RottenTomatoes();
|
||||||
@@ -151,4 +156,53 @@ movieRoutes.get('/:id/ratings', async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint combining RottenTomatoes and IMDB
|
||||||
|
*/
|
||||||
|
movieRoutes.get('/:id/ratingscombined', async (req, res, next) => {
|
||||||
|
const tmdb = new TheMovieDb();
|
||||||
|
const rtapi = new RottenTomatoes();
|
||||||
|
const imdbApi = new IMDBRadarrProxy();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const movie = await tmdb.getMovie({
|
||||||
|
movieId: Number(req.params.id),
|
||||||
|
});
|
||||||
|
|
||||||
|
const rtratings = await rtapi.getMovieRatings(
|
||||||
|
movie.title,
|
||||||
|
Number(movie.release_date.slice(0, 4))
|
||||||
|
);
|
||||||
|
|
||||||
|
let imdbRatings;
|
||||||
|
if (movie.imdb_id) {
|
||||||
|
imdbRatings = await imdbApi.getMovieRatings(movie.imdb_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rtratings && !imdbRatings) {
|
||||||
|
return next({
|
||||||
|
status: 404,
|
||||||
|
message: 'No ratings found.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const ratings: RatingResponse = {
|
||||||
|
...(rtratings ? { rt: rtratings } : {}),
|
||||||
|
...(imdbRatings ? { imdb: imdbRatings } : {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
return res.status(200).json(ratings);
|
||||||
|
} catch (e) {
|
||||||
|
logger.debug('Something went wrong retrieving movie ratings', {
|
||||||
|
label: 'API',
|
||||||
|
errorMessage: e.message,
|
||||||
|
movieId: req.params.id,
|
||||||
|
});
|
||||||
|
return next({
|
||||||
|
status: 500,
|
||||||
|
message: 'Unable to retrieve movie ratings.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default movieRoutes;
|
export default movieRoutes;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import RottenTomatoes from '@server/api/rottentomatoes';
|
import RottenTomatoes from '@server/api/rating/rottentomatoes';
|
||||||
import TheMovieDb from '@server/api/themoviedb';
|
import TheMovieDb from '@server/api/themoviedb';
|
||||||
import { MediaType } from '@server/constants/media';
|
import { MediaType } from '@server/constants/media';
|
||||||
import Media from '@server/entity/Media';
|
import Media from '@server/entity/Media';
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import RTAudFresh from '@app/assets/rt_aud_fresh.svg';
|
|||||||
import RTAudRotten from '@app/assets/rt_aud_rotten.svg';
|
import RTAudRotten from '@app/assets/rt_aud_rotten.svg';
|
||||||
import RTFresh from '@app/assets/rt_fresh.svg';
|
import RTFresh from '@app/assets/rt_fresh.svg';
|
||||||
import RTRotten from '@app/assets/rt_rotten.svg';
|
import RTRotten from '@app/assets/rt_rotten.svg';
|
||||||
|
import ImdbLogo from '@app/assets/services/imdb.svg';
|
||||||
import TmdbLogo from '@app/assets/tmdb_logo.svg';
|
import TmdbLogo from '@app/assets/tmdb_logo.svg';
|
||||||
import Button from '@app/components/Common/Button';
|
import Button from '@app/components/Common/Button';
|
||||||
import CachedImage from '@app/components/Common/CachedImage';
|
import CachedImage from '@app/components/Common/CachedImage';
|
||||||
@@ -40,7 +41,7 @@ import {
|
|||||||
ChevronDoubleDownIcon,
|
ChevronDoubleDownIcon,
|
||||||
ChevronDoubleUpIcon,
|
ChevronDoubleUpIcon,
|
||||||
} from '@heroicons/react/24/solid';
|
} from '@heroicons/react/24/solid';
|
||||||
import type { RTRating } from '@server/api/rottentomatoes';
|
import { type RatingResponse } from '@server/api/ratings';
|
||||||
import { IssueStatus } from '@server/constants/issue';
|
import { IssueStatus } from '@server/constants/issue';
|
||||||
import { MediaStatus } from '@server/constants/media';
|
import { MediaStatus } from '@server/constants/media';
|
||||||
import type { MovieDetails as MovieDetailsType } from '@server/models/Movie';
|
import type { MovieDetails as MovieDetailsType } from '@server/models/Movie';
|
||||||
@@ -86,6 +87,7 @@ const messages = defineMessages({
|
|||||||
rtcriticsscore: 'Rotten Tomatoes Tomatometer',
|
rtcriticsscore: 'Rotten Tomatoes Tomatometer',
|
||||||
rtaudiencescore: 'Rotten Tomatoes Audience Score',
|
rtaudiencescore: 'Rotten Tomatoes Audience Score',
|
||||||
tmdbuserscore: 'TMDB User Score',
|
tmdbuserscore: 'TMDB User Score',
|
||||||
|
imdbuserscore: 'IMDB User Score',
|
||||||
});
|
});
|
||||||
|
|
||||||
interface MovieDetailsProps {
|
interface MovieDetailsProps {
|
||||||
@@ -120,8 +122,8 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: ratingData } = useSWR<RTRating>(
|
const { data: ratingData } = useSWR<RatingResponse>(
|
||||||
`/api/v1/movie/${router.query.movieId}/ratings`
|
`/api/v1/movie/${router.query.movieId}/ratingscombined`
|
||||||
);
|
);
|
||||||
|
|
||||||
const sortedCrew = useMemo(
|
const sortedCrew = useMemo(
|
||||||
@@ -511,44 +513,62 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
|||||||
)}
|
)}
|
||||||
<div className="media-facts">
|
<div className="media-facts">
|
||||||
{(!!data.voteCount ||
|
{(!!data.voteCount ||
|
||||||
(ratingData?.criticsRating && !!ratingData?.criticsScore) ||
|
(ratingData?.rt?.criticsRating &&
|
||||||
(ratingData?.audienceRating && !!ratingData?.audienceScore)) && (
|
!!ratingData?.rt?.criticsScore) ||
|
||||||
|
(ratingData?.rt?.audienceRating &&
|
||||||
|
!!ratingData?.rt?.audienceScore) ||
|
||||||
|
ratingData?.imdb?.criticsScore) && (
|
||||||
<div className="media-ratings">
|
<div className="media-ratings">
|
||||||
{ratingData?.criticsRating && !!ratingData?.criticsScore && (
|
{ratingData?.rt?.criticsRating &&
|
||||||
<Tooltip
|
!!ratingData?.rt?.criticsScore && (
|
||||||
content={intl.formatMessage(messages.rtcriticsscore)}
|
<Tooltip
|
||||||
>
|
content={intl.formatMessage(messages.rtcriticsscore)}
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href={ratingData.rt.url}
|
||||||
|
className="media-rating"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
{ratingData.rt.criticsRating === 'Rotten' ? (
|
||||||
|
<RTRotten className="w-6" />
|
||||||
|
) : (
|
||||||
|
<RTFresh className="w-6" />
|
||||||
|
)}
|
||||||
|
<span>{ratingData.rt.criticsScore}%</span>
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{ratingData?.rt?.audienceRating &&
|
||||||
|
!!ratingData?.rt?.audienceScore && (
|
||||||
|
<Tooltip
|
||||||
|
content={intl.formatMessage(messages.rtaudiencescore)}
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href={ratingData.rt.url}
|
||||||
|
className="media-rating"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
{ratingData.rt.audienceRating === 'Spilled' ? (
|
||||||
|
<RTAudRotten className="w-6" />
|
||||||
|
) : (
|
||||||
|
<RTAudFresh className="w-6" />
|
||||||
|
)}
|
||||||
|
<span>{ratingData.rt.audienceScore}%</span>
|
||||||
|
</a>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{ratingData?.imdb?.criticsScore && (
|
||||||
|
<Tooltip content={intl.formatMessage(messages.imdbuserscore)}>
|
||||||
<a
|
<a
|
||||||
href={ratingData.url}
|
href={ratingData.imdb.url}
|
||||||
className="media-rating"
|
className="media-rating"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
{ratingData.criticsRating === 'Rotten' ? (
|
<ImdbLogo className="mr-1 w-6" />
|
||||||
<RTRotten className="w-6" />
|
<span>{ratingData.imdb.criticsScore}</span>
|
||||||
) : (
|
|
||||||
<RTFresh className="w-6" />
|
|
||||||
)}
|
|
||||||
<span>{ratingData.criticsScore}%</span>
|
|
||||||
</a>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
{ratingData?.audienceRating && !!ratingData?.audienceScore && (
|
|
||||||
<Tooltip
|
|
||||||
content={intl.formatMessage(messages.rtaudiencescore)}
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href={ratingData.url}
|
|
||||||
className="media-rating"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
{ratingData.audienceRating === 'Spilled' ? (
|
|
||||||
<RTAudRotten className="w-6" />
|
|
||||||
) : (
|
|
||||||
<RTAudFresh className="w-6" />
|
|
||||||
)}
|
|
||||||
<span>{ratingData.audienceScore}%</span>
|
|
||||||
</a>
|
</a>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
@@ -797,7 +817,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
|||||||
tmdbId={data.id}
|
tmdbId={data.id}
|
||||||
tvdbId={data.externalIds.tvdbId}
|
tvdbId={data.externalIds.tvdbId}
|
||||||
imdbId={data.externalIds.imdbId}
|
imdbId={data.externalIds.imdbId}
|
||||||
rtUrl={ratingData?.url}
|
rtUrl={ratingData?.rt?.url}
|
||||||
plexUrl={data.mediaInfo?.plexUrl ?? data.mediaInfo?.plexUrl4k}
|
plexUrl={data.mediaInfo?.plexUrl ?? data.mediaInfo?.plexUrl4k}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import {
|
|||||||
PlayIcon,
|
PlayIcon,
|
||||||
} from '@heroicons/react/24/outline';
|
} from '@heroicons/react/24/outline';
|
||||||
import { ChevronDownIcon } from '@heroicons/react/24/solid';
|
import { ChevronDownIcon } from '@heroicons/react/24/solid';
|
||||||
import type { RTRating } from '@server/api/rottentomatoes';
|
import type { RTRating } from '@server/api/rating/rottentomatoes';
|
||||||
import { ANIME_KEYWORD_ID } from '@server/api/themoviedb/constants';
|
import { ANIME_KEYWORD_ID } from '@server/api/themoviedb/constants';
|
||||||
import { IssueStatus } from '@server/constants/issue';
|
import { IssueStatus } from '@server/constants/issue';
|
||||||
import { MediaRequestStatus, MediaStatus } from '@server/constants/media';
|
import { MediaRequestStatus, MediaStatus } from '@server/constants/media';
|
||||||
|
|||||||
@@ -256,6 +256,7 @@
|
|||||||
"components.MovieDetails.budget": "Budget",
|
"components.MovieDetails.budget": "Budget",
|
||||||
"components.MovieDetails.cast": "Cast",
|
"components.MovieDetails.cast": "Cast",
|
||||||
"components.MovieDetails.digitalrelease": "Digital Release",
|
"components.MovieDetails.digitalrelease": "Digital Release",
|
||||||
|
"components.MovieDetails.imdbuserscore": "IMDB User Score",
|
||||||
"components.MovieDetails.managemovie": "Manage Movie",
|
"components.MovieDetails.managemovie": "Manage Movie",
|
||||||
"components.MovieDetails.mark4kavailable": "Mark as Available in 4K",
|
"components.MovieDetails.mark4kavailable": "Mark as Available in 4K",
|
||||||
"components.MovieDetails.markavailable": "Mark as Available",
|
"components.MovieDetails.markavailable": "Mark as Available",
|
||||||
|
|||||||
Reference in New Issue
Block a user