diff --git a/README.md b/README.md index db22ede..117a48f 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ A simple Node.JS module to access and normalize data from the ```javascript var omdb = require('omdb'); +omdb.key("your-key-here"); omdb.search('saw', function(err, movies) { if(err) { diff --git a/index.js b/index.js index 46c7126..6ba91c2 100644 --- a/index.js +++ b/index.js @@ -1,319 +1,347 @@ +// modules var durableJsonLint = require('durable-json-lint'), needle = require('needle'), - stream = require('stream'); +// variables var HOST = 'http://www.omdbapi.com/', TYPES = [ 'movie', 'series', 'episode' ]; +// apiKey support +var apiKey = undefined; + +// apiKey export requirement +module.exports.key = function(key) +{ + apiKey = key; +} + // 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; + var from, to; - year = year.split('–'); + year = year.split('–'); - if (year.length === 2) { - from = +year[0]; + if (year.length === 2) { + from = +year[0]; - if (year[1]) { - to = +year[1]; - } - - return { from: from, to: to }; + if (year[1]) { + to = +year[1]; } - return +year; + 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; + var hours, minutes; - if (!raw) { - return null; - } + if (!raw) { + return null; + } - hours = raw.match(/(\d+) h/); - minutes = raw.match(/(\d+) min/); + hours = raw.match(/(\d+) h/); + minutes = raw.match(/(\d+) min/); - hours = hours ? hours[1] : 0; - minutes = minutes ? +minutes[1] : 0; + hours = hours ? hours[1] : 0; + minutes = minutes ? +minutes[1] : 0; - return (hours * 60) + minutes; + 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; + return raw ? +raw.match(/\d/g).join('') : null; } // Remove all the strings found within brackets and split by comma. function formatList(raw) { - var list; + var list; - if (!raw) { - return []; - } + if (!raw) { + return []; + } - list = raw.replace(/\(.+?\)/g, '').split(', '); - list = list.map(function (item) { - return item.trim(); - }); + list = raw.replace(/\(.+?\)/g, '').split(', '); + list = list.map(function (item) { + return item.trim(); + }); - return list; + return list; } // Try to find the win and nomination count, but also keep raw just in case. function formatAwards(raw) { - var wins, nominations; + var wins, nominations; - if (!raw) { - return { wins: 0, nominations: 0, text: '' }; - } + if (!raw) { + return { wins: 0, nominations: 0, text: '' }; + } - wins = raw.match(/(\d+) wins?/i); - nominations = raw.match(/(\d+) nominations?/i); + 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 - }; + 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; + var query = {}; + + // if apiKey isn't defined + if(apiKey === undefined) + return done(new Error('API Key was not specified.')); + + // sending apikey + query.apikey = apiKey; + + 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.s) { - return done(new Error('No search terms specified.')); + if (query.y) { + query.y = parseInt(query.y, 10); + + if (isNaN(query.y)) { + return done(new Error('Year is not an integer.')); } + } - if (query.type) { - if (TYPES.indexOf(query.type) < 0) { - return done(new Error('Invalid type specified. Valid types are: ' + - TYPES.join(', ') + '.')); - } + needle.request('get', HOST, query, function (err, res, movies) { + if (err) { + return done(err); } - if (query.y) { - query.y = parseInt(query.y, 10); + if (res.statusCode !== 200) { + return done(new Error('status code: ' + res.statusCode)); + } - if (isNaN(query.y)) { - return done(new Error('Year is not an integer.')); - } + // 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, []); } - 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 - }; - })); - }); + // 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 = {}; + var query = {}; - // If the third argument is omitted, treat the second argument as the - // callback. - if (!done) { - done = options; - options = {}; + // 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 }; + } else if (options && typeof options !== 'object') { + options = { fullPlot: true }; + } + + // if apiKey isn't defined + if(apiKey === undefined) + return done(new Error('API Key was not specified.')); + + // sending apikey + query.apikey = apiKey; + + 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; } - query.plot = options.fullPlot ? 'full' : 'short'; + if (show.type) { + query.type = show.type; - // Include Rotten Tomatoes rating, if requested. - if (options.tomatoes) { - query.tomatoes = true; + if (TYPES.indexOf(query.type) < 0) { + return done(new Error('Invalid type specified. Valid types ' + + 'are: ' + TYPES.join(', ') + '.')); + } } - // 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; + // Assume anything beginning with "tt" and ending with digits is an + // IMDB ID. + } else if (/^tt\d+$/.test(show)) { + query.i = show; - // In order to search with a year, a title must be present. - if (show.year) { - query.y = show.year; - } + // Finally, assume options is a string repesenting the title. + } else { + query.t = show; + } - if (show.type) { - query.type = show.type; + needle.request('get', HOST, query, function (err, res, movie) { + if (err) { + return done(err); + } - if (TYPES.indexOf(query.type) < 0) { - return done(new Error('Invalid type specified. Valid types ' + - 'are: ' + TYPES.join(', ') + '.')); - } - } + if (res.statusCode !== 200) { + return done(new Error('status code: ' + res.statusCode)); + } - // Assume anything beginning with "tt" and ending with digits is an - // IMDB ID. - } else if (/^tt\d+$/.test(show)) { - query.i = show; + // 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.')); + } + } - // Finally, assume options is a string repesenting the title. - } else { - query.t = show; + // The movie being searched for could not be found. + if (!movie || movie.Response === 'False') { + return done(); } - 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 - }); + // 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, + seriesId: movie.seriesID ? movie.seriesID : null, + 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) + }, + + // This is basically useless since it only returns score at most + // added just incase this changes in the future + // unless tomatoes parameter passed, this will not show. + tomato: !query.tomatoes ? undefined : { + score: movie.Ratings[1] ? movie.Ratings[1].Value : null, + meter: movie.tomatoMeter ? +movie.tomatoMeter : null, + image: movie.tomatoImage ? +movie.tomatoImage : null, + rating: movie.tomatoRating ? +movie.tomatoRating : null, + reviews: movie.tomatoReviews ? +movie.tomatoReviews : null, + fresh: movie.tomatoFresh ? +movie.tomatoFresh : null, + rotten: movie.tomatoRotten ? +movie.tomatoRotten : null, + consensus: movie.tomatoConsensus ? movie.tomatoConsensus : null, + userMeter: movie.tomatoUserMeter ? movie.tomatoUserMeter : null, + userRating: movie.tomatoUserRating ? movie.tomatoUserRating : null, + userReviews: movie.tomatoUserReviews ? movie.tomatoUserReviews : null, + url: movie.tomatoURL ? movie.tomatoURL : null + }, + + metacritic: movie.Metascore ? +movie.Metascore : null, + awards: formatAwards(movie.Awards), + boxOffice: movie.BoxOffice ? movie.BoxOffice : null, + productionCompany: movie.Production, + website: movie.Website, + dvdReleased: movie.DVD ? new Date(movie.DVD) : null, + 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); - } - }); + 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; + return out; }; diff --git a/package.json b/package.json index a72b5b8..78d47d7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "omdb", - "version": "0.8.0", + "version": "0.9.0", "author": "Mister Hat ", "description": "A module to access OMDb API.", "keywords": [ diff --git a/test.js b/test.js index ff27d07..f256593 100644 --- a/test.js +++ b/test.js @@ -2,6 +2,9 @@ var tap = require('tap').test, omdb = require('./'); +//api key +omdb.key("your-key-here"); + var SEARCH_PROPERTIES = [ 'title', 'year', 'imdb', 'type' ]; tap('correct searching', function (test) {