rotten-api
Version:
A simple NodeJS interface to the Rotten Tomatoes API
382 lines (326 loc) • 10.1 kB
JavaScript
var request = require('request'),
util = require('util'),
API = require('./API'),
/**
* Returns the specified data as a JSON object,
* or an object with a single 'error' property if
* the parsing fails.
*
* @param data the data to parse as JSON
*/
toJSON = function(data) {
try {
return JSON.parse(data);
} catch (e) {
return {
"error": "Error parsing the response !"
};
}
},
// true if the provided id is valid, false otherwise
isId = (function () {
var regex = /^[0-9]+$/;
return (function (id) { return regex.test(id); });
})(),
// true if the provided IMDB id is valid, false otherwise
isImdbId = (function () {
var regex = /^(tt)?[0-9]+$/;
return (function (id) { return regex.test(id); });
})(),
// Invoke the callback with given error or throw it
cbOrThrow = function (cb, err) {
if (typeof cb === 'function') {
cb(err, null);
} else {
throw err;
}
},
// returns the specified id if valid, undefined otherwise
checkId = function (id) {
return (typeof id === 'string' && isId(id)) ? id : undefined;
},
/**
* Handles the actual HTTP GET request at the specified url
* with the given parameters.
*
* Calls the optionnal callback upon completion.
*
* @param url {String} the url where to make the request to
* @param params {Object} the request parameters
* @param cb [optional] {Function} the callback
*/
_get = function(url, params, cb) {
request.get({
"uri": url,
"qs": params
}, function(url, res, data) {
var err = null;
data = toJSON(data);
if (res.statusCode !== 200) {
err = new Error("Status code : " + res.statusCode);
} else if (data && data.error) {
err = new Error(data.error);
}
if (typeof cb === 'function') {
cb(err, data);
}
});
},
/**
* Handles the client request for the named API.
*
* @param name {String} the API service
* @param opt {Object} the request options
* @param cb [optional] {Function} the callback
*/
_handle = function (name, opt, cb) {
var api = API[name],
url = api.url,
params = api.params ? api.params() : {};
// Require a callback to exists.
if (typeof cb !== 'function') {
throw new Error("Callback is required !");
}
// Format url if needed
if (api.format && opt[api.format]) {
url = util.format.call(null, url, opt[api.format]);
}
// Get client parameters
opt = opt || {};
for (var p in params) {
if (p in opt) {
params[p] = opt[p];
}
}
params.apikey = this.key;
_get(url, params, cb);
};
/**
* Constructs a Rotten object with the given options.
*
* If opt is a String it is considered as the API key.
* If it is an object, the following properties apply :
*
* - 'key' : the Rotten Tomatoes API key
* - 'limit' : the default result limit per page when applicable
*/
function Rotten(opt) {
if (typeof opt === 'string') {
opt = {"key": opt};
}
opt = opt || {};
this.key = opt.key;
this.limit = opt.limit || API.LIMIT;
if (typeof this.key !== 'string') {
throw new Error("Invalid API key");
}
}
/**
* Retrieves Top Box Office Earning Movies.
* Sorted by Most Recent Weekend Gross Ticket Sales.
*
* If a single parameter is given, then it should
* be the callback. The result limit and country will defaults
* to respectively 10 and 'us'.'
*
* @param opt {Object|String|int|function}
* - a string to specify the country as (ISO 3166-1 alpha-2)
* - a number to specify the limit
* - an object with both properties 'limit' and 'country'
*
* @param cb {function} the callback
*/
Rotten.prototype.boxOffice = function(opt, cb) {
if (arguments.length === 1) {
cb = opt;
opt = null;
} else {
if (typeof opt === 'string') {
opt = {"country": opt};
} else if (typeof opt === 'number') {
opt = {"limit": opt};
}
}
_handle.call(this, "boxOffice", opt, cb);
};
/**
* Retrieves movies currently in theaters. A 'page'
* property can be specified if the first parameter is an object.
*
* The same parameters as the Rotten#boxOffice method apply.
*/
Rotten.prototype.inTheaters = function(opt, cb) {
if (arguments.length === 1) {
cb = opt;
opt = null;
} else {
if (typeof opt === 'string') {
opt = {"country": opt};
} else if (typeof opt === 'number') {
opt = {"page_limit": opt};
} else {
opt.page_limit = opt ? (opt.limit || opt.page_limit || this.limit) : this.limit;
}
}
_handle.call(this, "inTheaters", null, cb);
};
/**
* Retrieves current opening movies
*
* The same parameters as the Rotten#boxOffice method apply.
*/
Rotten.prototype.opening = function(opt, cb) {
if (arguments.length === 1) {
cb = opt;
opt = null;
} else {
if (typeof opt === 'string') {
opt = {"country": opt};
} else if (typeof opt === 'number') {
opt = {"limit": opt};
}
}
_handle.call(this, "opening", null, cb);
};
/**
* Retrieves upcoming movies.
*
* The same parameters as the Rotten#boxOffice method apply. A 'page'
* property can be specified if the first parameter is an object.
*/
Rotten.prototype.upcoming = function(opt, cb) {
if (arguments.length === 1) {
cb = opt;
opt = null;
} else {
if (typeof opt === 'string') {
opt = {"country": opt};
} else if (typeof opt === 'number') {
opt = {"limit": opt};
}
}
_handle.call(this, "upcoming", null, cb);
};
/**
* Retrieves detailed information on a movie specified by its id.
*
* @param id {String} the movie's id
* @param cb {function) the callback
*/
Rotten.prototype.get =
Rotten.prototype.info = function(id, cb) {
id = checkId(id);
if (!id) {
return cbOrThrow(cb, new Error("Invalid id."));
}
_handle.call(this, "info", {"id": id}, cb);
};
/**
* Retrieves the full casting of the movie specified by its id.
*
* @param id {String} the movie's id
* @param cb {function) the callback
*/
Rotten.prototype.cast = function(id, cb) {
id = checkId(id);
if (!id) {
return cbOrThrow(cb, new Error("Invalid id."));
}
_handle.call(this, "cast", {"id": id}, cb);
};
/**
* Retrieves movie clips related to the movie specified by its id.
*
* @param id {String} the movie's id
* @param cb {function) the callback
*/
Rotten.prototype.clips = function(id, cb) {
id = checkId(id);
if (!id) {
return cbOrThrow(cb, new Error("Invalid id."));
}
_handle.call(this, "clips", {"id": id}, cb);
};
/**
* Retrieves the reviews for a movie.
*
* @param opt {Object}
* - 'review_type' : one of "all", "top_critic" and "dvd"
* - 'limit' : number of reviews per page
* - 'page' : the page number
* - 'country' : for localized data
* @parama cb {function}
*/
Rotten.prototype.reviews = function(opt, cb) {
var id = checkId(opt);
opt = opt || {};
opt.id = id || opt.id;
opt.page_limit = opt.limit || opt.page_limit || this.limit;
if (!opt.id) {
return cbOrThrow(cb, new Error("Invalid id."));
}
_handle.call(this, "reviews", opt, cb);
};
/**
* Shows similar movies for a movie specified by its id.
*
* @param opt {Object|String}
* - a string to specify the movie's id.
* - a object with both 'limit' and 'id' properties
* @parama cb {function}
*/
Rotten.prototype.similar = function(opt, cb) {
var id = checkId(opt);
if (typeof opt ==='string') {
opt = {"id": id}
} else {
opt = opt || {};
opt.id = id || opt.id;
opt.page_limit = opt.limit || opt.page_limit || this.limit;
}
if (!opt.id) {
return cbOrThrow(cb, new Error("Invalid id."));
}
_handle.call(this, "similar", opt, cb);
};
/**
* Retrieves detailed information on a movie specified by its IMDB id.
*
* @param id {String} the movie's IMDB id, optionaly starting with 'tt' as IMDB ids do.
*/
Rotten.prototype.imdb =
Rotten.prototype.alias = function(id, cb) {
if (typeof id === 'string' && isImdbId(id)) {
id = {"id": id.replace("tt", '')};
} else {
return cbOrThrow(cb, new Error("Invalid IMDB id."));
}
_handle.call(this, "alias", id, cb);
};
/**
* Search for a movie based on a query String.
*
* @param opt {String|Object} the search options
* - If opt is a String then it is treated as the query String
* - If opt is an object then the following properties apply :
* q | query : the query string, must be non-empty !
* page : the page number (not the number of page)
* limit : the limit of result per page
*/
Rotten.prototype.search = function(opt, cb) {
if (typeof opt === 'string' || typeof opt === 'number') {
opt = {"q": opt + ''};
} else {
opt = opt || {};
opt.q = opt.q || opt.query || null;
opt.page_limit = opt.limit || opt.page_limit || this.limit;
}
// Don't make the query on an empty string
if (typeof opt.q !== 'string' || opt.q.trim() === '') {
return cbOrThrow(cb, new Error("Query must be a non-empty string."));
}
_handle.call(this, "search", opt, cb);
};
module.exports = function(opt) {
return new Rotten(opt);
};