UNPKG

omdb

Version:

A module to access OMDb API.

320 lines (254 loc) 9.17 kB
var durableJsonLint = require('durable-json-lint'), needle = require('needle'), stream = require('stream'); var HOST = 'http://www.omdbapi.com/', TYPES = [ 'movie', 'series', 'episode' ]; // Series have a different format to describe years, so account for that when we /// format it. For example, // "1989" == 1998 // "1989-" == { from: 1989, to: undefined } // "1989-2014" == { from: 1989, to: 2014 } function formatYear(year) { var from, to; year = year.split('–'); if (year.length === 2) { from = +year[0]; if (year[1]) { to = +year[1]; } return { from: from, to: to }; } return +year; } // Format strings of hours & minutes into minutes. For example, // "1 h 30 min" == 90. function formatRuntime(raw) { var hours, minutes; if (!raw) { return null; } hours = raw.match(/(\d+) h/); minutes = raw.match(/(\d+) min/); hours = hours ? hours[1] : 0; minutes = minutes ? +minutes[1] : 0; return (hours * 60) + minutes; } // Convert votes from a US formatted string of a number to a Number. function formatVotes(raw) { return raw ? +raw.match(/\d/g).join('') : null; } // Remove all the strings found within brackets and split by comma. function formatList(raw) { var list; if (!raw) { return []; } list = raw.replace(/\(.+?\)/g, '').split(', '); list = list.map(function (item) { return item.trim(); }); return list; } // Try to find the win and nomination count, but also keep raw just in case. function formatAwards(raw) { var wins, nominations; if (!raw) { return { wins: 0, nominations: 0, text: '' }; } wins = raw.match(/(\d+) wins?/i); nominations = raw.match(/(\d+) nominations?/i); return { wins: wins ? +wins[1] : 0, nominations: nominations ? +nominations[1] : 0, text: raw }; } // Search for movies by titles. module.exports.search = function (terms, done) { var query = {}; if (typeof terms === 'string') { query.s = terms; } else { query.s = terms.terms || terms.s; query.y = terms.year || terms.y; query.type = terms.type; } if (!query.s) { return done(new Error('No search terms specified.')); } if (query.type) { if (TYPES.indexOf(query.type) < 0) { return done(new Error('Invalid type specified. Valid types are: ' + TYPES.join(', ') + '.')); } } if (query.y) { query.y = parseInt(query.y, 10); if (isNaN(query.y)) { return done(new Error('Year is not an integer.')); } } needle.request('get', HOST, query, function (err, res, movies) { if (err) { return done(err); } if (res.statusCode !== 200) { return done(new Error('status code: ' + res.statusCode)); } // If no movies are found, the API returns // "{"Response":"False","Error":"Movie not found!"}" instead of an // empty array. So in this case, return an empty array to be consistent. if (movies.Response === 'False') { return done(null, []); } // Fix the ugly capitalized naming and cast the year as a Number. done(null, movies.Search.map(function (movie) { return { title: movie.Title, year: formatYear(movie.Year), imdb: movie.imdbID, type: movie.Type, poster: movie.Poster }; })); }); }; // Find a movie by title, title & year or IMDB ID. The second argument is // optional and determines whether or not to return an extended plot synopsis. module.exports.get = function (show, options, done) { var query = {}; // If the third argument is omitted, treat the second argument as the // callback. if (!done) { done = options; options = {}; // If options is given, but is not an object, assume fullPlot: true // for backwards compatibility. } else if (options && typeof options !== 'object') { options = { fullPlot: true }; } query.plot = options.fullPlot ? 'full' : 'short'; // Include Rotten Tomatoes rating, if requested. if (options.tomatoes) { query.tomatoes = true; } // Select query based on explicit IMDB ID, explicit title, title & year, // IMDB ID and title, respectively. if (show.imdb) { query.i = show.imdb; } else if (show.title) { query.t = show.title; // In order to search with a year, a title must be present. if (show.year) { query.y = show.year; } if (show.type) { query.type = show.type; if (TYPES.indexOf(query.type) < 0) { return done(new Error('Invalid type specified. Valid types ' + 'are: ' + TYPES.join(', ') + '.')); } } // Assume anything beginning with "tt" and ending with digits is an // IMDB ID. } else if (/^tt\d+$/.test(show)) { query.i = show; // Finally, assume options is a string repesenting the title. } else { query.t = show; } needle.request('get', HOST, query, function (err, res, movie) { if (err) { return done(err); } if (res.statusCode !== 200) { return done(new Error('status code: ' + res.statusCode)); } // Needle was unable to parse the JSON. Try durable-json-lint. if (typeof movie === 'string') { try { movie = JSON.parse(durableJsonLint(movie).json); } catch (e) { return done(new Error('Malformed JSON.')); } } // The movie being searched for could not be found. if (!movie || movie.Response === 'False') { return done(); } // Replace 'N/A' strings with null for simple checks in the return // value. Object.keys(movie).forEach(function (key) { if (movie[key] === 'N/A') { movie[key] = null; } }); // Beautify and normalize the ugly results the API returns. done(null, { title: movie.Title, year: formatYear(movie.Year), rated: movie.Rated, season: movie.Season ? +movie.Season : null, episode: movie.Episode ? +movie.Episode : null, totalSeasons: movie.totalSeasons ? + movie.totalSeasons : null, // Cast the API's release date as a native JavaScript Date type. released: movie.Released ? new Date(movie.Released) : null, // Return runtime as minutes casted as a Number instead of an // arbitrary string. runtime: formatRuntime(movie.Runtime), countries: formatList(movie.Country), genres: formatList(movie.Genre), director: movie.Director, writers: formatList(movie.Writer), actors: formatList(movie.Actors), plot: movie.Plot, // A hotlink to a JPG of the movie poster on IMDB. poster: movie.Poster, imdb: { id: movie.imdbID, rating: movie.imdbRating ? +movie.imdbRating : null, votes: formatVotes(movie.imdbVotes) }, // Determine tomatoRatings existance by the presense of tomatoMeter. tomato: !movie.tomatoMeter ? undefined : { meter: +movie.tomatoMeter, image: movie.tomatoImage, rating: +movie.tomatoRating, reviews: +movie.tomatoReviews, fresh: +movie.tomatoFresh, rotten: +movie.tomatoRotten, consensus: movie.tomatoConsensus, userMeter: +movie.tomatoUserMeter, userRating: +movie.tomatoUserRating, userReviews: +movie.tomatoUserReviews, url: movie.tomatoURL, dvdReleased: movie.DVD ? new Date(movie.DVD) : null }, metacritic: movie.Metascore ? +movie.Metascore : null, awards: formatAwards(movie.Awards), type: movie.Type }); }); }; // Get a Readable Stream with the jpg image data of the poster to the movie, // identified by title, title & year or IMDB ID. module.exports.poster = function (show) { var out = new stream.PassThrough(), req; module.exports.get(show, false, function (err, res) { if (err) { out.emit('error', err); } else if (!res) { out.emit('error', new Error('Movie not found')); } else if (!res.poster) { out.emit('error', new Error('Poster not found')); } else { req = needle.get(res.poster); req.on('error', function (err) { out.emit('error', err); }); req.pipe(out); } }); return out; };