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>("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(<NodeJS.Timeout>this.__clearCacheInterval);
|
|
this.__clearCacheInterval = null;
|
|
}
|
|
}
|
|
|
|
// Interface -----------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Verify the IMDb ID exists
|
|
*/
|
|
public verifyImdbId(id: string) {
|
|
return new Promise<boolean>((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 => <any>{
|
|
// image : movie.image_url ? `/api/tvdb/artwork${new URL(movie.image_url).pathname}`: null,
|
|
// name : movie.name,
|
|
// year : movie.year
|
|
// });
|
|
}
|
|
}
|