diff --git a/packages/themoviedb/package.json b/packages/themoviedb/package.json new file mode 100644 index 0000000..afc9c5e --- /dev/null +++ b/packages/themoviedb/package.json @@ -0,0 +1,16 @@ +{ + "name": "@autoplex/themoviedb", + "version": "0.0.0", + "main": "dist/lib/index.js", + "types": "dist/typings/index.d.ts", + "license": "MIT", + "scripts": { + "build": "yarn run clean && tsc", + "clean": "rimraf ./dist" + }, + "devDependencies": { + "@types/node": "^15.0.1", + "rimraf": "^3.0.2", + "typescript": "^4.2.4" + } +} diff --git a/packages/themoviedb/src/index.ts b/packages/themoviedb/src/index.ts new file mode 100644 index 0000000..8a2759f --- /dev/null +++ b/packages/themoviedb/src/index.ts @@ -0,0 +1,38 @@ +import ApiRequestManager from "./request" +import * as Schema from "./schema"; + +export enum ExternalSource { + Facebook = "facebook_id", + Freebase = "freebase_id", + FreebaseM = "freebase_mid", + Imdb = "imdb_id", + Instagram = "instagram_id", + Tvdb = "tvdb_id", + TvRage = "tvrage_id", + Twitter = "twitter_id" +} + +export default class TheMovieDb +{ + protected requestManager!: ApiRequestManager; + + public constructor(apiKey: string) { + this.requestManager = new ApiRequestManager(apiKey); + } + + public async configuration() { + return await this.requestManager.get("/configuration"); + } + + public async searchMovie(query: string, year?: number, page?: number) { + return await this.requestManager.get>("/search/movie", { query, year }); + } + + public async movie(id: number) { + return await this.requestManager.get(`/movie/${id}`); + } + + public async findMovie(id: string, externalSource: ExternalSource) { + return await this.requestManager.get(`/find/${id}`, { external_source: externalSource }); + } +} diff --git a/packages/themoviedb/src/request.ts b/packages/themoviedb/src/request.ts new file mode 100644 index 0000000..cbabd1a --- /dev/null +++ b/packages/themoviedb/src/request.ts @@ -0,0 +1,126 @@ +import https, { RequestOptions } from "https"; + +/** + * The API URL + */ +const API_URL = "https://api.themoviedb.org/3"; + +/** + * A status error is used to indicate responses with non-200 status codes + */ +export class StatusError extends Error +{ + /** + * The resulting body of a response + */ + public readonly response: T; + + /** + * The resulting status code of a response + */ + public readonly statusCode?: number; + + /** + * Create a new error indicating non-200 status + */ + public constructor(response: T, statusCode: number) { + super(); + Object.setPrototypeOf(this, StatusError.prototype); + this.response = response; + this.statusCode = statusCode; + } +} + +/** + * A request manager with atomic/persistent request options + */ +export default class ApiRequestManager +{ + private __api_key: string; + + /** + * Store additional request options + */ + protected options: RequestOptions; + + /** + * Create a new API request manager + * + * @param options Additional request options + */ + public constructor(apiKey: string, options: RequestOptions = {}) { + this.__api_key = apiKey; + this.options = options; + } + + /** + * Perform a generic HTTPS request + * + * @param method The HTTP method + * @param url The URL to request + * @param apiKey An optional bearer token + * @param params Optional parameters + * @param body Optional body + */ + protected request(method: string, url: string, params?: any, body?: string) + { + return new Promise((resolve, reject) => { + // Create request options + let options = Object.assign({ method, headers: {} }, this.options); + if (body) { + options.headers["Content-Type"] = "application/json"; + options.headers["Content-Length"] = body.length; + } + + // Add search parameters if necessary + let requestUrl = new URL(url); + requestUrl.searchParams.set("api_key", this.__api_key); + if (params) { + Object.keys(params).forEach((key) => { + if (params[key] !== undefined) { + requestUrl.searchParams.set(key, params[key]); + } + }); + } + + // Create the request + let request = https.request(requestUrl, options, (res) => { + let rawData: string = ""; + res.setEncoding("utf8"); + res.on("data", chunk => {rawData += chunk}); + res.on("error", reject); + res.on("end", () => { + let response: T = JSON.parse(rawData); + if (res.statusCode == 200) { + resolve(response) + } else { + reject(new StatusError(response, res.statusCode)); + } + }); + }) + .on("error", reject) + .on("timeout", () => reject("timeout")); + if (body) { + request.write(body); + } + request.end(); + }); + } + + /** + * Perform a generic GET request + */ + public async get(path: string, params?: any) { + return await this.request("GET", `${API_URL}${path}`, params); + } + + /** + * Perform a generic POST request + */ + public async post(path: string, params?: any, body?: any) { + if (body !== undefined) { + body = JSON.stringify(body); + } + return await this.request("POST", `${API_URL}${path}`, params, body); + } +} diff --git a/packages/themoviedb/src/schema.ts b/packages/themoviedb/src/schema.ts new file mode 100644 index 0000000..3e7ccd9 --- /dev/null +++ b/packages/themoviedb/src/schema.ts @@ -0,0 +1,89 @@ +export enum Status { + Rumored = "Rumored", + Planned = "Planned", + InProduction = "InProduction", + PostProduction = "PostProduction", + Released = "Released", + Canceled = "Canceled" +} + +export interface IGenre { + id : number, + name: string +} + +export interface ILanguage { + iso_639_1: string, + name : string +} + +export interface IFindResult { + movie_results : IMovieSearchResult[], + person_results : unknown, + tv_results : unknown, + tv_episode_results: unknown, + tv_season_results : unknown +} + +export interface IMovieSearchResult { + adult : boolean, + backdrop_path : string | null, + genre_ids : number[], + id : number, + original_string: string, + original_title : string, + overview : string, + popularity : number, + poster_path : string | null, + release_date : string, + title : string, + video : boolean, + vote_average : number + vote_count : number, +} + +export interface IMovieDetails { + adult : boolean, + backdrop_path : string | null, + belongs_to_collection: any, + budget : number, + genres : IGenre[] + imdb_id : string | null, + original_language : string, + original_title : string, + overview : string | null, + popularity : number, + poster_path : string | null, + production_companies : IProductionCompany[], + production_countries : IProductionCountry[], + release_date : string, + revenue : number, + runtime : number | null, + spoken_languages : ILanguage[], + status : Status, + tagline : string | null, + title : string, + video : boolean, + vote_average : number, + vote_count : number +} + +export interface IProductionCompany { + name : string, + id : number, + logo_path : string | null, + origin_country: string +} + +export interface IProductionCountry { + release_date: string, + revenue : number, + runtime : number | null +} + +export interface IPaginatedResponse { + page : number, + results : T[], + total_results: number, + total_pages : number +} diff --git a/packages/themoviedb/tsconfig.json b/packages/themoviedb/tsconfig.json new file mode 100644 index 0000000..595f8fd --- /dev/null +++ b/packages/themoviedb/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.package.json", + "compilerOptions": { + "outDir": "./dist/lib", + "declarationDir": "./dist/typings" + } +} diff --git a/packages/themoviedb/yarn.lock b/packages/themoviedb/yarn.lock new file mode 100644 index 0000000..f175550 --- /dev/null +++ b/packages/themoviedb/yarn.lock @@ -0,0 +1,92 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/node@^15.0.1": + version "15.0.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-15.0.1.tgz#ef34dea0881028d11398be5bf4e856743e3dc35a" + integrity sha512-TMkXt0Ck1y0KKsGr9gJtWGjttxlZnnvDtphxUOSd0bfaR6Q1jle+sPvrzNR1urqYTWMinoKvjKfXUGsumaO1PA== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +glob@^7.1.3: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +typescript@^4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961" + integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg== + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=