Your ROOT_URL in app.ini is https://git.cloudchain.link/ but you are visiting https://dash.bss.nz/open-source-mirrors/overseerr/commit/e88dc83aeba0475e3ad421d5ab130cea4fc9a806
You should set ROOT_URL correctly, otherwise the web may not work correctly.
9 changed files with
143 additions and
6 deletions
@ -383,6 +383,36 @@ components:
type : string
name:
type : string
RelatedVideo:
type : object
properties:
url:
type : string
example : https://www.youtube.com/watch?v=9qhL2_UxXM0/
key:
type : string
example : 9qhL2_UxXM0
name:
type : string
example : Trailer for some movie (1978)
size:
type : number
example : 1080
type:
type : string
example : Trailer
enum:
- Clip
- Teaser
- Trailer
- Featurette
- Opening Credits
- Behind the Scenes
- Bloopers
site:
type : string
enum:
- 'YouTube'
MovieDetails:
type : object
properties:
@ -408,6 +438,10 @@ components:
$ref : '#/components/schemas/Genre'
homepage:
type : string
relatedVideos:
type : array
items:
$ref : '#/components/schemas/RelatedVideo'
originalLanguage:
type : string
originalTitle:
@ -1724,7 +1758,8 @@ paths:
application/json:
schema:
type : array
$ref : '#/components/schemas/User'
items:
$ref : '#/components/schemas/User'
/user/{userId}:
get:
@ -197,6 +197,23 @@ export interface TmdbMovieDetails {
backdrop_path? : string ;
} ;
external_ids : TmdbExternalIds ;
videos : TmdbVideoResult ;
}
export interface TmdbVideo {
id : string ;
key : string ;
name : string ;
site : 'YouTube' ;
size : number ;
type :
| 'Clip'
| 'Teaser'
| 'Trailer'
| 'Featurette'
| 'Opening Credits'
| 'Behind the Scenes'
| 'Bloopers' ;
}
export interface TmdbTvEpisodeResult {
@ -284,6 +301,11 @@ export interface TmdbTvDetails {
keywords : {
results : TmdbKeyword [ ] ;
} ;
videos : TmdbVideoResult ;
}
export interface TmdbVideoResult {
results : TmdbVideo [ ] ;
}
export interface TmdbKeyword {
@ -453,7 +475,10 @@ class TheMovieDb {
const response = await this . axios . get < TmdbMovieDetails > (
` /movie/ ${ movieId } ` ,
{
params : { language , append_to_response : 'credits,external_ids' } ,
params : {
language ,
append_to_response : 'credits,external_ids,videos' ,
} ,
}
) ;
@ -474,7 +499,7 @@ class TheMovieDb {
const response = await this . axios . get < TmdbTvDetails > ( ` /tv/ ${ tvId } ` , {
params : {
language ,
append_to_response : 'credits,external_ids,keywords ',
append_to_response : 'credits,external_ids,keywords ,videos ',
} ,
} ) ;
@ -8,9 +8,26 @@ import {
mapCrew ,
ExternalIds ,
mapExternalIds ,
mapVideos ,
} from './common' ;
import Media from '../entity/Media' ;
export interface Video {
url? : string ;
site : 'YouTube' ;
key : string ;
name : string ;
size : number ;
type :
| 'Clip'
| 'Teaser'
| 'Trailer'
| 'Featurette'
| 'Opening Credits'
| 'Behind the Scenes'
| 'Bloopers' ;
}
export interface MovieDetails {
id : number ;
imdbId? : string ;
@ -23,6 +40,7 @@ export interface MovieDetails {
originalTitle : string ;
overview? : string ;
popularity : number ;
relatedVideos? : Video [ ] ;
posterPath? : string ;
productionCompanies : ProductionCompany [ ] ;
productionCountries : {
@ -64,6 +82,7 @@ export const mapMovieDetails = (
adult : movie.adult ,
budget : movie.budget ,
genres : movie.genres ,
relatedVideos : mapVideos ( movie . videos ) ,
originalLanguage : movie.original_language ,
originalTitle : movie.original_title ,
popularity : movie.popularity ,
@ -8,6 +8,7 @@ import {
ExternalIds ,
mapExternalIds ,
Keyword ,
mapVideos ,
} from './common' ;
import {
TmdbTvEpisodeResult ,
@ -16,6 +17,7 @@ import {
TmdbSeasonWithEpisodes ,
} from '../api/themoviedb' ;
import type Media from '../entity/Media' ;
import { Video } from './Movie' ;
interface Episode {
id : number ;
@ -67,6 +69,7 @@ export interface TvDetails {
genres : Genre [ ] ;
homepage : string ;
inProduction : boolean ;
relatedVideos? : Video [ ] ;
languages : string [ ] ;
lastAirDate : string ;
lastEpisodeToAir? : Episode ;
@ -145,6 +148,7 @@ export const mapTvDetails = (
id : genre.id ,
name : genre.name ,
} ) ) ,
relatedVideos : mapVideos ( show . videos ) ,
homepage : show.homepage ,
id : show.id ,
inProduction : show.in_production ,
@ -2,8 +2,12 @@ import {
TmdbCreditCast ,
TmdbCreditCrew ,
TmdbExternalIds ,
TmdbVideo ,
TmdbVideoResult ,
} from '../api/themoviedb' ;
import { Video } from '../models/Movie' ;
export interface ProductionCompany {
id : number ;
logoPath? : string ;
@ -84,3 +88,18 @@ export const mapExternalIds = (eids: TmdbExternalIds): ExternalIds => ({
tvrageId : eids.tvrage_id ,
twitterId : eids.twitter_id ,
} ) ;
export const mapVideos = ( videoResult : TmdbVideoResult ) : Video [ ] = >
videoResult ? . results . map ( ( { key , name , size , type , site } : TmdbVideo ) = > ( {
site ,
key ,
name ,
size ,
type ,
url : siteUrlCreator ( site , key ) ,
} ) ) ;
const siteUrlCreator = ( site : Video [ 'site' ] , key : string ) : string = >
( {
YouTube : ` https://www.youtube.com/watch?v= ${ key } / ` ,
} [ site ] ) ;
@ -4,6 +4,7 @@ import { mapMovieDetails } from '../models/Movie';
import { mapMovieResult } from '../models/Search' ;
import Media from '../entity/Media' ;
import RottenTomatoes from '../api/rottentomatoes' ;
import logger from '../logger' ;
const movieRoutes = Router ( ) ;
@ -11,15 +12,19 @@ movieRoutes.get('/:id', async (req, res, next) => {
const tmdb = new TheMovieDb ( ) ;
try {
const movie = await tmdb . getMovie ( {
const t mdbM ovie = await tmdb . getMovie ( {
movieId : Number ( req . params . id ) ,
language : req.query.language as string ,
} ) ;
const media = await Media . getMedia ( movie. id ) ;
const media = await Media . getMedia ( t mdbM ovie. id ) ;
return res . status ( 200 ) . json ( mapMovieDetails ( movie, media ) ) ;
return res . status ( 200 ) . json ( mapMovieDetails ( t mdbM ovie, media ) ) ;
} catch ( e ) {
logger . error ( 'Something went wrong getting movie' , {
label : 'Movie' ,
message : e.message ,
} ) ;
return next ( { status : 404 , message : 'Movie does not exist' } ) ;
}
} ) ;
@ -46,6 +46,7 @@ const messages = defineMessages({
status : 'Status' ,
revenue : 'Revenue' ,
budget : 'Budget' ,
watchtrailer : 'Watch Trailer' ,
originallanguage : 'Original Language' ,
overview : 'Overview' ,
runtime : '{minutes} minutes' ,
@ -121,6 +122,11 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
( request ) = > request . status === MediaRequestStatus . PENDING
) ;
const trailerUrl = data . relatedVideos
? . filter ( ( r ) = > r . type === 'Trailer' )
. sort ( ( a , b ) = > a . size - b . size )
. pop ( ) ? . url ;
const modifyRequest = async ( type : 'approve' | 'decline' ) = > {
const response = await axios . get (
` /api/v1/request/ ${ activeRequest ? . id } / ${ type } `
@ -244,10 +250,18 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
< / span >
< / div >
< div className = "flex justify-end flex-1 mt-4 md:mt-0" >
{ trailerUrl && (
< a href = { trailerUrl } target = { '_blank' } rel = "noreferrer" >
< Button buttonType = "ghost" >
< FormattedMessage { ...messages.watchtrailer } / >
< / Button >
< / a >
) }
{ ( ! data . mediaInfo ||
data . mediaInfo ? . status === MediaStatus . UNKNOWN ) && (
< Button
buttonType = "primary"
className = "ml-2"
onClick = { ( ) = > setShowRequestModal ( true ) }
>
{ activeRequest ? (
@ -48,6 +48,7 @@ const messages = defineMessages({
recommendations : 'Recommendations' ,
similar : 'Similar Series' ,
cancelrequest : 'Cancel Request' ,
watchtrailer : 'Watch Trailer' ,
available : 'Available' ,
unavailable : 'Unavailable' ,
request : 'Request' ,
@ -130,6 +131,11 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
( request ) = > request . status === MediaRequestStatus . PENDING
) ;
const trailerUrl = data . relatedVideos
? . filter ( ( r ) = > r . type === 'Trailer' )
. sort ( ( a , b ) = > a . size - b . size )
. pop ( ) ? . url ;
const modifyRequests = async ( type : 'approve' | 'decline' ) : Promise < void > = > {
if ( ! activeRequests ) {
return ;
@ -265,9 +271,17 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
< / span >
< / div >
< div className = "flex justify-end flex-1 mt-4 md:mt-0" >
{ trailerUrl && (
< a href = { trailerUrl } target = "_blank" rel = "noreferrer" >
< Button buttonType = "ghost" >
< FormattedMessage { ...messages.watchtrailer } / >
< / Button >
< / a >
) }
{ ( ! data . mediaInfo ||
data . mediaInfo . status === MediaStatus . UNKNOWN ) && (
< Button
className = "ml-2"
buttonType = "primary"
onClick = { ( ) = > setShowRequestModal ( true ) }
>
@ -58,6 +58,7 @@
"components.MovieDetails.userrating" : "User Rating" ,
"components.MovieDetails.viewfullcrew" : "View Full Crew" ,
"components.MovieDetails.viewrequest" : "View Request" ,
"components.MovieDetails.watchtrailer" : "Watch Trailer" ,
"components.PersonDetails.appearsin" : "Appears in" ,
"components.PersonDetails.ascharacter" : "as {character}" ,
"components.PersonDetails.crewmember" : "Crew Member" ,
@ -322,6 +323,7 @@
"components.TvDetails.unavailable" : "Unavailable" ,
"components.TvDetails.userrating" : "User Rating" ,
"components.TvDetails.viewfullcrew" : "View Full Crew" ,
"components.TvDetails.watchtrailer" : "Watch Trailer" ,
"components.UserEdit.admin" : "Admin" ,
"components.UserEdit.adminDescription" : "Full administrator access. Bypasses all permission checks." ,
"components.UserEdit.autoapprove" : "Auto Approve" ,