import Application from "@server/Application"; import TheMovieDb from "@lib/tmdb"; import { env, secret } from "@server/util"; import { request } from "https"; import Service from "./Service"; import TvDb from "./TvDb"; import { IApiMovieDetails } from "@common/api_schema"; import { MovieInfo } from "@server/database/entities"; const CACHE_CLEAR_INTERVAL = 1000*60; // 60 seconds export default class MovieSearch extends Service { protected tmdb!: TheMovieDb; /** * The instance of TVDB */ protected tvdb!: TvDb; /** * Hold a cache of recently fetched movies to speed up request times */ protected movieCache: { [tmdbId: number]: { timestamp: number, movie: IApiMovieDetails } }; /** * Hold the clear cache interval reference */ private __clearCacheInterval: NodeJS.Timeout | null; /** * Create a new Movie Search service instance */ public constructor(app: Application) { super("Movie Search", app); this.movieCache = {}; this.__clearCacheInterval = null; } /** * Start the service */ public start() { this.tvdb = this.app.service("TVDB"); } /** * Boot the service */ public async boot() { let apiKey = await secret(env("TMDB_KEY_FILE")); this.tmdb = new TheMovieDb(apiKey); } /** * Shutdown the service */ public async shutdown() { // no-op } /** * Store an IMDb ID in cache */ protected cacheMovie(tmdbId: number, movie: IApiMovieDetails) { this.movieCache[tmdbId] = { movie, timestamp: Date.now() + CACHE_CLEAR_INTERVAL }; if (this.__clearCacheInterval === null) { this.__clearCacheInterval = setInterval(() => this.cleanMovieCache(), CACHE_CLEAR_INTERVAL); } return this.movieCache[tmdbId]; } /** * Clean the IMDb cache */ protected cleanMovieCache() { let now = Date.now(); let remaining = 0; for (let key in this.movieCache) { if (now > this.movieCache[key].timestamp) { delete this.movieCache[key]; } else { remaining++; } } if (remaining == 0) { clearInterval(this.__clearCacheInterval); this.__clearCacheInterval = null; } } // Interface ----------------------------------------------------------------------------------- /** * Verify the IMDb ID exists */ public verifyImdbId(id: string) { return new Promise((resolve, reject) => { // If the ID is cached, no need to fetch it // if (id in this.imdbCache) { // resolve(true); // } // Verify the movie exists on IMDb by checking for a 404 let req = request({ method: "HEAD", host: "www.imdb.com", path: `/title/${id}/` }, (response) => { response.resume(); if (response.statusCode == undefined) { reject(); return; } resolve(response.statusCode === 200); response.destroy(); }); req.end(); }); } /** * Get the details of a movie */ public async details(id: number) { if (id in this.movieCache) { if (!this.movieCache[id].movie.is_requested) { let isRequested = await MovieInfo.count({tmdbId: id}) > 0; this.movieCache[id].movie.is_requested = isRequested; } return this.movieCache[id].movie; } let movie = await this.tmdb.movie(id); let result: IApiMovieDetails = { backdrop_path: movie.backdrop_path, imdb_id : movie.imdb_id, overview : movie.overview, poster_path : movie.poster_path, release_date : movie.release_date, runtime : movie.runtime, title : movie.title, is_requested : await MovieInfo.count({tmdbId: id}) > 0 }; return this.cacheMovie(id, result).movie; } /** * Search for a movie */ public async search(query: string, year?: number) { return await this.tmdb.searchMovie(query, year); // let results = await this.tvdb.searchMovie(query, year); // return results.map(movie => { // image : movie.image_url ? `/api/tvdb/artwork${new URL(movie.image_url).pathname}`: null, // name : movie.name, // year : movie.year // }); } }