instagram-node
Version:
Simple Instagram driver for Node.js
1,546 lines (1,358 loc) • 58.4 kB
JavaScript
// Copyright Teleportd Ltd. and other Contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var fwk = require('fwk');
var http = require('http');
var https = require('https');
var query = require('querystring');
var url = require('url');
var crypto = require('crypto');
/**
* Instagram API driver for NodeJS
* Proceeds the call to the API and give
* back the response
*
* @param spec { agent, host, port }
*/
var instagram = function(spec, my) {
var _super = {};
my = my || {};
spec = spec || {};
my.limit = null;
my.remaining = null;
my.agent = spec.agent;
my.host = spec.host || 'https://api.instagram.com';
my.port = spec.port || 443;
my.enforce_signed_requests = spec.enforce_signed_requests || false;
// public
var use; /* use(spec); */
var user; /* user(user_id, cb); */
var user_self_feed; /* user_self_feed(options, cb); */
var user_media_recent; /* user_media_recent(user_id, options, cb); */
var user_self_media_recent; /* user_self_media_recent(options, cb); */
var user_self_liked; /* user_self_liked(options, cb); */
var user_search; /* user_search(query, options, cb); */
var user_follows; /* user_follows(user_id, cb); */
var user_followers; /* user_followers(user_id, cb); */
var user_self_requested_by; /* user_self_requested_by(cb); */
var user_relationship; /* user_relationship(user_id, cb); */
var set_user_relationship; /* set_user_relationship(user_id, action, cb); */
var media; /* media(media_id, cb); */
var media_shortcode; /* media_shortcode(media_shortcode, cb); */
var media_search; /* media_search(lat, lng, options, cb); */
var media_popular; /* media_popular(cb); */
var comments; /* comments(media_id, cb); */
var add_comment; /* add_comment(media_id, text, cb); */
var del_comment; /* del_comment(media_id, comment_id, cb); */
var likes; /* likes(media_id, cb); */
var add_like; /* add_like(media_id, cb); */
var del_like; /* del_like(media_id, cb); */
var tag; /* tag(tag, cb); */
var tag_media_recent; /* tag_media_recent(tag, options, cb); */
var tag_search; /* tag_search(query, cb); */
var location; /* location(location_id, cb); */
var location_media_recent; /* location_media_recent(location_id, options, cb); */
var location_search; /* location_search(spec, options, cb); */
var geography_media_recent; /* geography_media_recent(id, options, cb); */
var subscriptions; /* subscriptions(cb); */
var del_subscription; /* del_subscription(options, cb); */
var add_tag_subscription; /* add_tag_subscription(tag, cb_url, cb); */
var add_geography_subscription; /* add_geography_subscription(lat, lng, radius, cb_url, cb); */
var add_user_subscription; /* add_user_subscription(cb_url, cb); */
var add_location_subscription; /* add_location_subscription(id, cb_url, cb); */
var get_authorization_url; /* get_authorization_url(redirect_uri, permissions);*/
var authorize_user; /* authorize_user(code, redirect_uri, cb); */
var oembed; /* oembed(url, cb); */
// private
var call; /* call(method, path, params, cb, retry); */
var handle_error; /* handle_error(body, cb, retry); */
var sign_request; /* sign_request(endpoint, params, client_secret); */
var sort_object; /* sort_object(params); */
var that = {};
/*******************************/
/* Private helpers */
/*******************************/
/**
* Make a call on instagram API with the given params, path & method
* @param method string the request method
* @param path string the path
* @param params object the params
* @param cb function (err, result, remaining, limit);
* @param retry function a retry function
*/
call = function(method, path, params, cb, retry) {
if(my.auth) {
// we don't need auth parameters if we're hitting the oembed endpoint
if (path.search('oembed') < 0) {
for(var opt in my.auth) {
if(my.auth.hasOwnProperty(opt)) {
params[opt] = my.auth[opt];
}
}
}
// Signature parameter
if(params.sign_request || my.enforce_signed_requests) {
try {
var client_secret;
if (params.sign_request) {
client_secret = params.sign_request.client_secret;
delete params.sign_request;
} else {
client_secret = my.auth.client_secret;
}
params['sig'] = sign_request(
path,
params,
client_secret
);
}
catch(err) {
return handle_error(err, cb, retry);
}
}
var options = {
host: url.parse(my.host).hostname,
port: my.port,
method: method,
path: '/v1' + path + (method === 'GET' || method === 'DELETE' ? '?' + query.stringify(params) : ''),
agent: my.agent,
headers: {}
};
// oauth and oembed calls don't use /v1
if (path.search('oauth') >= 0 || path.search('oembed') >= 0) {
options.path = options.path.substring(3); // chop off '/v1'
}
var data = null;
if (method !== 'GET' && method !== 'DELETE') {
data = query.stringify(params);
options.headers['Content-Type'] = 'application/x-www-form-urlencoded';
options.headers['Content-Length'] = data.length;
}
var req = https.request(options, function(res) {
var body = '';
res.setEncoding('utf8');
res.on('data', function(chunk) {
body += chunk;
});
res.on('end', function() {
var result;
var limit = parseInt(res.headers['x-ratelimit-limit'], 10) || 0;
var remaining = parseInt(res.headers['x-ratelimit-remaining'], 10) || 0;
my.limit = limit;
my.remaining = remaining;
try {
result = JSON.parse(body);
} catch(err) {
return handle_error(err, cb, retry, res.statusCode, body);
}
return cb(null, result, remaining, limit);
});
});
req.on('error', function(err) {
return handle_error(err, cb, retry);
});
if (data !== null) {
req.write(data);
}
req.end();
} else {
return handle_error(new Error('Must be authentified'), cb, retry);
}
};
/**
* Handle API errors
* @param body object the response from instagram API
* @param cb function (err);
* @param retry function can be called to retry
* @param status number the status code [opt]
* @param bdy string the body received from instagram [opt]
* Error objects can have
* - status_code status code [opt]
* - body body received [opt]
* - error_type error type from instagram
* - error_message error message from instagram
* - code if error comes from instagram
* - retry a function that can be called to retry
* with same params
*/
handle_error = function(body, cb, retry, status, bdy) {
if(body && ((body.meta && body.meta.error_type) || body.error_type)) {
// if body is an instagram error
if(!body.meta) {
body.meta = {
code: body.code,
error_type: body.error_type,
error_message: body.error_message
};
}
var error = new Error(body.meta.error_type + ': ' + body.meta.error_message);
error.code = body.meta.code;
error.error_type = body.meta.error_type;
error.error_message = body.meta.error_message;
error.retry = retry;
return cb(error);
} else if(body && body.message && body.stack) {
// if body is an error
body.retry = retry;
if(status)
body.status_code = status;
if(bdy)
body.body = bdy;
return cb(body);
} else {
var error = new Error('Unknown error');
error.retry = retry;
return cb(error);
}
};
/**
* Sign the request using new instagram sign rules.
* We are merging endpoint, params and client_secret and hashing all together
* @param endpoint string api endpoint to call
* @param params object api call params to be hashed
* @param client_secret string the client secret to sign the request
* @throws Error if arguments are not correct
*/
sign_request = function(endpoint, params, client_secret) {
if(typeof client_secret !== 'string') {
throw new Error('Wrong param "client_secret"');
}
var sig = endpoint;
params = sort_object(params);
for (var key in params){
if (params.hasOwnProperty(key)) {
sig += "|"+key+"="+params[key];
}
}
var hmac = crypto.createHmac('sha256', client_secret);
hmac.update(sig);
return hmac.digest('hex');
};
/**
* Sort onject function.
* We need to sort params that being added to api call.
* @param object
* @returns {Object}
*/
sort_object = function(object) {
var keys = Object.keys(object),
i, len = keys.length;
keys.sort();
var newobj = new Object;
for (i = 0; i < len; i++)
{
k = keys[i];
newobj[k] = object[keys[i]];
}
return newobj;
};
/*****************************/
/* Public functions */
/*****************************/
/**
* Use the specified options to sign requests: can be an access_key
* or a client_id/client_secret keys pair
* @param options object { access_key } ||
* { client_id, client_secret }
* @throws Error if options is wrong
*/
use = function(options) {
if(typeof options === 'object') {
if (typeof options.enforce_signed_requests != 'undefined') {
my.enforce_signed_requests = options.enforce_signed_requests;
}
if(options.access_token) {
my.limit = null;
my.remaining = null;
my.auth = {
access_token: options.access_token
};
if (options.client_secret) {
my.auth.client_secret = options.client_secret;
}
} else if(options.client_id && options.client_secret) {
my.limit = null;
my.remaining = null;
my.auth = {
client_id: options.client_id,
client_secret: options.client_secret
};
} else {
throw new Error('Wrong param "options"');
}
} else {
throw new Error('Wrong param "options"');
}
};
/**
* Retrieves information about the given user
* @param id string the user id
* @param cb function (err, user, remaining, limit);
*/
user = function(id, cb) {
var retry = function() {
user(id, cb);
};
if(typeof id !== 'string' || id === '') {
return handle_error(new Error('Wrong param "id"'), cb, retry);
}
call('GET', '/users/' + id, {}, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.meta.code === 200) {
return cb(null, result.data, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Retrieves the current user feed
* @param options object { count, [opt]
* min_id, [opt]
* max_id [opt] }
* @param cb function (err, feed, pagination, remaining, limit);
*/
user_self_feed = function(options, cb) {
var retry = function() {
user_self_feed(options, cb);
};
if(!cb && typeof options === 'function') {
cb = options;
options = {};
}
var params = {};
if(options.count) {
params.count = options.count;
}
if(options.min_id) {
params.min_id = options.min_id;
}
if(options.max_id) {
params.max_id = options.max_id;
}
call('GET', '/users/self/feed', params, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.meta.code === 200) {
if(result.pagination && result.pagination.next_max_id) {
options = fwk.shallow(options);
options.max_id = result.pagination.next_max_id;
var next = function(cb) {
user_self_feed(options, cb);
};
result.pagination.next = next;
}
return cb(null, result.data, result.pagination || {}, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Retrieves the current user likes
* @param options object { count, [opt]
* max_like_id [opt] }
* @param cb function (err, likes, pagination, remaining, limit);
*/
user_self_liked = function(options, cb) {
var retry = function() {
user_self_liked(options, cb);
};
if(!cb && typeof options === 'function') {
cb = options;
options = {};
}
var params = {};
if(options.count) {
params.count = options.count;
}
if(options.max_like_id) {
params.max_like_id = options.max_like_id;
}
call('GET', '/users/self/media/liked', params, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.meta.code === 200) {
if(result.pagination && result.pagination.next_max_like_id) {
options = fwk.shallow(options);
options.max_like_id = result.pagination.next_max_like_id;
var next = function(cb) {
user_self_liked(options, cb);
};
result.pagination.next = next;
delete result.pagination.next_url;
}
return cb(null, result.data, result.pagination || {}, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Get the most recent media published by a user
* @param user_id string the user id
* @param options object { count, [opt]
* max_timestamp, [opt]
* min_timestamp, [opt]
* max_id, [opt]
* min_id [opt] }
* @param cb(err, results, pagination, remaining, limit);
*/
user_media_recent = function(user_id, options, cb) {
var retry = function() {
user_media_recent(user_id, options, cb);
};
if(!cb && typeof options === 'function') {
cb = options;
options = {};
}
var params = {};
if(typeof user_id !== 'string' || user_id === '') {
return handle_error(new Error('Wrong param "user_id"'), cb, retry);
}
if(options.count) {
params.count = options.count;
}
if(options.max_timestamp) {
params.max_timestamp = options.max_timestamp;
}
if(options.min_timestamp) {
params.min_timestamp = options.min_timestamp;
}
if(options.max_id) {
params.max_id = options.max_id;
}
if(options.min_id) {
params.min_id = options.min_id;
}
call('GET', '/users/' + user_id + '/media/recent', params, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.meta.code === 200) {
if(result.pagination && result.pagination.next_max_id) {
options = fwk.shallow(options);
options.max_id = result.pagination.next_max_id;
var next = function(cb) {
user_media_recent(user_id, options, cb);
};
result.pagination.next = next;
delete result.pagination.next_url;
}
return cb(null, result.data, result.pagination || {}, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Get the most recent media published by the user being authenticated
* @param options object { count, [opt]
* max_timestamp, [opt]
* min_timestamp, [opt]
* max_id, [opt]
* min_id [opt] }
* @param cb(err, results, pagination, remaining, limit);
*/
user_self_media_recent = function(options, cb) {
var retry = function() {
user_self_media_recent(options, cb);
};
if(!cb && typeof options === 'function') {
cb = options;
options = {};
}
if(typeof my.auth.access_token !== 'string') {
return handle_error(new Error('You must first set an `access_token` with `use()`'), cb, retry);
}
var user_id = my.auth.access_token.split('.')[0];
return user_media_recent(user_id, options, cb);
};
/**
* Search for a user according to the given query
* @param query string the name to search for
* @param options object { count [opt] }
* @param cb function (err, users, limit);
*/
user_search = function(query, options, cb) {
var retry = function() {
user_search(query, options, cb);
};
if(!cb && typeof options === 'function') {
cb = options;
options = {};
}
var params = {};
if(typeof query !== 'string' || query === '') {
return handle_error(new Error('Wrong param "query": ' + query), cb, retry);
}
params.q = query;
if(options.count) {
params.count = options.count;
}
call('GET', '/users/search', params, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.meta.code === 200) {
return cb(null, result.data, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Retrieves the list of users the given user follows
* @param user_id string the user to check
* @param options object { count, [opt]
* cursor [opt] }
* @param cb function (err, users, pagination, remaining, limit);
*/
user_follows = function(user_id, options, cb) {
var retry = function() {
user_follows(user_id, options, cb);
};
if(!cb && typeof options === 'function') {
cb = options;
options = {};
}
if(typeof user_id !== 'string' || user_id === '') {
return handle_error(new Error('Wrong param "user_id"'), cb, retry);
}
var params = {};
if(options.count) {
params.count = options.count;
}
if(options.cursor) {
params.cursor = options.cursor;
}
call('GET', '/users/' + user_id + '/follows', params, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.meta.code === 200) {
if(result.pagination && result.pagination.next_cursor) {
options = fwk.shallow(options);
options.cursor = result.pagination.next_cursor;
var next = function(cb) {
user_follows(user_id, options, cb);
};
result.pagination.next = next;
delete result.pagination.next_url;
}
return cb(null, result.data, result.pagination || {}, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Retrieves the list of users the given user is followed by
* @param user_id string the user to check
* @param options object { count, [opt]
* cursor [opt] }
* @param cb function (err, users, pagination, remaining, limit);
*/
user_followers = function(user_id, options, cb) {
var retry = function() {
user_followers(user_id, options, cb);
};
if(!cb && typeof options === 'function') {
cb = options;
options = {};
}
if(typeof user_id !== 'string' || user_id === '') {
return handle_error(new Error('Wrong param "user_id"'), cb, retry);
}
var params = {};
if(options.count) {
params.count = options.count;
}
if(options.cursor) {
params.cursor = options.cursor;
}
call('GET', '/users/' + user_id + '/followed-by', params, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.meta.code === 200) {
if(result.pagination && result.pagination.next_cursor) {
options = fwk.shallow(options);
options.cursor = result.pagination.next_cursor;
var next = function(cb) {
user_followers(user_id, options, cb);
};
result.pagination.next = next;
delete result.pagination.next_url;
}
return cb(null, result.data, result.pagination || {}, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Retrieves the list of users who have requested the current users's
* permission to follow
* @param cb function (err, users, limit);
*/
user_self_requested_by = function(cb) {
var retry = function() {
user_self_requested_by(cb);
};
call('GET', '/users/self/requested-by', {}, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.meta.code === 200) {
return cb(null, result.data, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Get information about a relationship to another user
* @param user_id string the user to check
* @param cb function (err, result, limit);
*/
user_relationship = function(user_id, cb) {
var retry = function() {
user_relationship(user_id, cb);
};
if(typeof user_id !== 'string' || user_id === '') {
return handle_error(new Error('Wrong param "user_id"'), cb, retry);
}
call('GET', '/users/' + user_id + '/relationship', {}, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.meta.code === 200) {
return cb(null, result.data, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Modify the relationship between current user and the target user
* @param user_id string the target user
* @param action string 'follow' || 'unfollow' || 'block' || 'unblock' || 'approve' || 'ignore'
* @param options object { sign_request: {
* client_secret: 'xxx'
* }}
* @param cb function (err, result, limit);
*/
set_user_relationship = function(user_id, action, options, cb) {
var retry = function() {
set_user_relationship(user_id, action, options, cb);
};
if(!cb && typeof options === 'function') {
cb = options;
options = {};
}
var params = {};
if(typeof user_id !== 'string' || user_id === '') {
return handle_error(new Error('Wrong param "user_id"'), cb, retry);
}
if(['follow', 'unfollow', 'block', 'unblock', 'approve', 'ignore'].indexOf(action.toLowerCase()) === -1) {
return handle_error(new Error('Wrong param "action"'), cb);
}
params.action = action.toLowerCase();
if(options.sign_request) {
params.sign_request = options.sign_request;
}
call('POST', '/users/' + user_id + '/relationship', params, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.meta.code === 200) {
return cb(null, result.data, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Retrieves information about a given media
* @param media_id string the id of the media
* @param cb function (err, result, limit);
*/
media = function(media_id, cb) {
var retry = function() {
media(media_id, cb);
};
if(typeof media_id !== 'string' || media_id === '') {
return handle_error(new Error('Wrong param "media_id"'), cb, retry);
}
call('GET', '/media/' + media_id, {}, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.meta.code === 200) {
return cb(null, result.data, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Retrieves information about a media with given shortcode
* @param media_shortcode string the shortcode of the media
* For media http://instagram.com/p/ABC/ shortcode is ABC
* @param cb function (err, result, limit);
*/
media_shortcode = function(media_shortcode, cb) {
var retry = function() {
media_shortcode(media_shortcode, cb);
};
if(typeof media_shortcode !== 'string' || media_shortcode === '') {
return handle_error(new Error('Wrong param "media_shortcode"'), cb, retry);
}
call('GET', '/media/shortcode/' + media_shortcode, {}, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.meta.code === 200) {
return cb(null, result.data, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Search for media in a given area
* @param lat number the latitude
* @param lng number the longitude
* @param options object { min_timestamp, [opt]
* max_timestamp, [opt]
* distance [opt] }
* @param cb function (err, result, limit);
*/
media_search = function(lat, lng, options, cb) {
var retry = function() {
media_search(lat, lng, options, cb);
};
if(!cb && typeof options === 'function') {
cb = options;
options = {};
}
var params = {};
if(typeof lat !== 'number' || typeof lng !== 'number') {
return handle_error(new Error('Wrong params "lat" & "lng"'), cb, retry);
}
params.lat = lat;
params.lng = lng;
if(options.max_timestamp) {
if(options.max_timestamp > parseInt(Date.now() / 1000, 10)) {
options.max_timestamp = parseInt(Date.now() / 1000, 10);
}
params.max_timestamp = options.max_timestamp;
}
if(options.min_timestamp) {
if(options.min_timestamp > parseInt(Date.now() / 1000, 10)) {
options.min_timestamp = parseInt(Date.now() / 1000, 10);
}
params.min_timestamp = options.min_timestamp;
}
if(options.distance) {
params.distance = options.distance;
}
if(options.count) {
params.count = options.count;
}
call('GET', '/media/search', params, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.meta.code === 200) {
return cb(null, result.data, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Retrieves a list of most popular media at the moment
* @param cb function (err, result, limit);
*/
media_popular = function(cb) {
var retry = function() {
media_popular(cb);
};
call('GET', '/media/popular', {}, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.meta.code === 200) {
return cb(null, result.data, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Retrieves all the comments for the given media
* @param media_id string the media id
* @param cb function (err, result, limit);
*/
comments = function(media_id, cb) {
var retry = function() {
comments(media_id, cb);
};
if(typeof media_id !== 'string' || media_id === '') {
return handle_error(new Error('Wrong param "media_id"'), cb, retry);
}
call('GET', '/media/' + media_id + '/comments', {}, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.meta.code === 200) {
return cb(null, result.data, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Add a comment on the given media
* @param media_id string the media id
* @param text string the text to post
* @param options object { sign_request: {
* client_secret: 'xxx'
* }}
* @param cb function (err, limit);
*/
add_comment = function(media_id, text, options, cb) {
var retry = function() {
add_comment(media_id, text, options, cb);
};
if(!cb && typeof options === 'function') {
cb = options;
options = {};
}
var params = {};
if(typeof media_id !== 'string' || media_id === '' ||
typeof text !== 'string' || text === '') {
return handle_error(new Error('Wrong param "media_id" or "text"'), cb, retry);
}
params.text = text;
if(options.sign_request) {
params.sign_request = options.sign_request;
}
call('POST', '/media/' + media_id + '/comments', params, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.meta.code === 200) {
return cb(null, result.data, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Delete the given comment from the given media
* @param media_id string the media id
* @param comment_id string the comment id
* @param options object { sign_request: {
* client_secret: 'xxx'
* }}
* @param cb function (err, limit);
*/
del_comment = function(media_id, comment_id, options, cb) {
var retry = function() {
del_comment(media_id, comment_id, options, cb);
};
if(!cb && typeof options === 'function') {
cb = options;
options = {};
}
var params = {};
if(typeof media_id !== 'string' || media_id === '' ||
typeof comment_id !== 'string' || comment_id === '') {
return handle_error(new Error('Wrong param "media_id" or "comment_id"'), cb, retry);
}
if(options.sign_request) {
params.sign_request = options.sign_request;
}
call('DELETE', '/media/' + media_id + '/comments/' + comment_id, params, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.meta.code === 200) {
return cb(null, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Retrieves all the likes for the given media
* @param media_id string the media id
* @param cb function (err, result, limit);
*/
likes = function(media_id, cb) {
var retry = function() {
likes(media_id, cb);
};
if(typeof media_id !== 'string' || media_id === '') {
return handle_error(new Error('Wrong param "media_id"'), cb, retry);
}
call('GET', '/media/' + media_id + '/likes', {}, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.meta.code === 200) {
return cb(null, result.data, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Add a like on the given media
* @param media_id string the media id
* @param options object { sign_request: {
* client_secret: 'xxx'
* }}
* @param cb function (err, limit);
*/
add_like = function(media_id, options, cb) {
var retry = function() {
add_like(media_id, options, cb);
};
if(!cb && typeof options === 'function') {
cb = options;
options = {};
}
var params = {};
if(typeof media_id !== 'string' || media_id === '') {
return handle_error(new Error('Wrong param "media_id"'), cb, retry);
}
if(options.sign_request) {
params.sign_request = options.sign_request;
}
call('POST', '/media/' + media_id + '/likes', params, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.meta.code === 200) {
return cb(null, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Delete the like from the given media
* @param media_id string the media id
* @param options object { sign_request: {
* client_secret: 'xxx'
* }}
* @param cb function (err, limit);
*/
del_like = function(media_id, options, cb) {
var retry = function() {
del_like(media_id, options, cb);
};
if(!cb && typeof options === 'function') {
cb = options;
options = {};
}
var params = {};
if(typeof media_id !== 'string' || media_id === '') {
return handle_error(new Error('Wrong param "media_id"'), cb, retry);
}
if(options.sign_request) {
params.sign_request = options.sign_request
}
call('DELETE', '/media/' + media_id + '/likes', params, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.meta.code === 200) {
return cb(null, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Retrieves information about a tag
* @param _tag string the tag
* @param cb function (err, result, limit);
*/
tag = function(_tag, cb) {
var retry = function() {
tag(_tag, cb);
};
if(typeof _tag !== 'string' || _tag === '') {
return handle_error(new Error('Wrong param "tag"'), cb, retry);
}
call('GET', '/tags/' + encodeURIComponent(_tag), {}, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.meta.code === 200) {
return cb(null, result.data, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Get recent medias for the given tag
* @param tag string the tag
* @param options object { min_id, [opt]
* max_id [opt] }
* @param cb function (err, result, pagination, remaining, limit);
*/
tag_media_recent = function(tag, options, cb) {
var retry = function() {
tag_media_recent(tag, options, cb);
};
if(!cb && typeof options === 'function') {
cb = options;
options = {};
}
var params = {};
if(typeof tag !== 'string' || tag === '') {
return handle_error(new Error('Wrong param "tag"'), cb, retry);
}
if(options.min_id || options.min_tag_id) {
params.min_tag_id = options.min_id || options.min_tag_id;
}
if(options.max_id || options.max_tag_id) {
params.max_tag_id = options.max_id || options.max_tag_id;
}
if(options.count) {
params.count = options.count;
}
call('GET', '/tags/' + encodeURIComponent(tag) + '/media/recent', params, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.meta.code === 200) {
if(result.pagination && result.pagination.next_max_tag_id) {
options = fwk.shallow(options);
// Syntax weirdness coming from IG API (max_tag_id instead of max_id)
// [see http://instagram.com/developer/endpoints/tags/]
options.max_tag_id = result.pagination.next_max_tag_id;
var next = function(cb) {
tag_media_recent(tag, options, cb);
};
result.pagination.next = next;
delete result.pagination.next_url;
}
return cb(null, result.data, result.pagination || {}, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Search for tags by name
* @param query string the search to perform
* @param cb function (err, result, limit);
*/
tag_search = function(query, cb) {
var retry = function() {
tag_search(query, cb);
};
if(typeof query !== 'string' || query === '') {
return handle_error(new Error('Wrong param "query"'), cb, retry);
}
call('GET', '/tags/search', { q: query }, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.meta.code === 200) {
return cb(null, result.data, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Retrieves information about a location
* @param location_id string the location id
* @param cb function (err, result, limit);
*/
location = function(location_id, cb) {
var retry = function() {
location(location_id, cb);
};
if(typeof location_id !== 'string' || location_id === '') {
return handle_error(new Error('Wrong param "location_id"'), cb, retry);
}
call('GET', '/locations/' + location_id, {}, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.meta.code === 200) {
return cb(null, result.data, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Get recent medias for the given location
* @param location_id string the location id
* @param options object { min_id, [opt]
* max_id, [opt]
* min_timestamp, [opt]
* max_timestamp [opt] }
* @param cb function (err, result, pagination, remaining, limit);
*/
location_media_recent = function(location_id, options, cb) {
var retry = function() {
location_media_recent(location_id, options, cb);
};
if(!cb && typeof options === 'function') {
cb = options;
options = {};
}
if(typeof location_id !== 'string' || location_id === '') {
return handle_error(new Error('Wrong param "location_id"'), cb, retry);
}
var params = {};
if(options.min_id) {
params.min_id = options.min_id;
}
if(options.max_id) {
params.max_id = options.max_id;
}
if(options.min_timestamp) {
params.min_timestamp = options.min_timestamp;
}
if(options.max_timestamp) {
params.max_timestamp = options.max_timestamp;
}
call('GET', '/locations/' + location_id + '/media/recent', params, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.meta.code === 200) {
if(result.pagination && result.pagination.next_max_id) {
options = fwk.shallow(options);
options.max_id = result.pagination.next_max_id;
var next = function(cb) {
location_media_recent(location_id, options, cb);
};
result.pagination.next = next;
delete result.pagination.next_url;
}
return cb(null, result.data, result.pagination || {}, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Search for locations by lat/lng
* @param spec object { lat, lng } ||
* { foursquare_v2_id } ||
* { foursquare_id } ||
* { facebook_places_id }
* @param options object { distance [opt] }
* @param cb function (err, result, limit);
*/
location_search = function(spec, options, cb) {
var retry = function() {
location_search(spec, options, cb);
};
if(!cb && typeof options === 'function') {
cb = options;
options = {};
}
var params = {};
if(options.distance) {
params.distance = options.distance;
}
if(typeof spec.lat === 'number' && typeof spec.lng === 'number') {
params.lat = spec.lat;
params.lng = spec.lng;
} else if(spec.foursquare_v2_id) {
params.foursquare_v2_id = spec.foursquare_v2_id;
} else if(spec.foursquare_id) {
params.foursquare_id = spec.foursquare_id;
} else if(spec.facebook_places_id) {
params.facebook_places_id = spec.facebook_places_id;
} else {
return handle_error(new Error('Wrong param "lat/lng" or "foursquare(_v2)_id" or "facebook_places_id"'), cb, retry);
}
call('GET', '/locations/search', params, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.meta.code === 200) {
return cb(null, result.data, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Get recent medias for the given geography
* @param geo_id string the instagram geography id
* @param options object { min_id, [opt]
* count [opt] }
* @param cb function (err, result, pagination, remaining, limit);
*/
geography_media_recent = function(geography_id, options, cb) {
var retry = function() {
geography_media_recent(geography_id, options, cb);
};
if(!cb && typeof options === 'function') {
cb = options;
options = {};
}
if(typeof geography_id !== 'string' || geography_id === '') {
return handle_error(new Error('Wrong param "geography_id"'), cb, retry);
}
var params = {};
if(options.min_id) {
params.min_id = options.min_id;
}
if(options.count) {
params.count = options.count;
}
call('GET', '/geographies/' + geography_id + '/media/recent', params, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.meta.code === 200) {
if(result.pagination && result.pagination.next_min_id) {
options = fwk.shallow(options);
options.min_id = result.pagination.next_min_id;
var next = function(cb) {
geography_media_recent(geography_id, options, cb);
};
result.pagination.next = next;
delete result.pagination.next_url;
}
return cb(null, result.data, result.pagination || {}, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Retrieve authentication URL for user.
* @param redirect_uri string the url to redirect to
* @param options object { scope, [opt] array ['likes', 'comments', 'relationships']
* state [opt] string}
* @return url string the formated url
* @throw err if client_id/client_secret are not set.
*/
get_authorization_url = function(redirect_uri, options) {
var options = options || {};
var url_obj = url.parse(my.host);
url_obj.pathname = '/oauth/authorize';
if(!my.auth.client_id || !my.auth.client_secret) {
throw new Error('Please supply client_id and client_secret via use()');
}
var params = {
client_id: my.auth.client_id,
redirect_uri: redirect_uri,
response_type: 'code'
};
if(options.state) {
params.state = options.state;
}
url_obj.query = params;
var auth_url = url.format(url_obj);
if(Array.isArray(options.scope)) {
auth_url += '&scope=' + options.scope.join('+');
}
return auth_url;
};
/**
* Authorizes a user and returns the response from the server.
* This is the final leg in instagram's authentication process.
* Note this function also sets and uses the access token that
* was retrieved via authentication.
* @param code string the code received from instagram
* Passed as a get parameter to a redirect uri once a user has
* authenticated via Instagram. See Instagram's Authorization
* API documentation for more information
* @param redirect_uri string the url to redirect to
* @param cb function (err, result);
*/
authorize_user = function(code, redirect_uri, cb) {
var retry = function() {
authorize_user(code, redirect_uri, cb);
};
var params = {
client_id: my.auth.client_id,
client_secret: my.auth.client_secret,
grant_type: 'authorization_code',
redirect_uri: redirect_uri,
code: code
};
call('POST', '/oauth/access_token', params, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
}
else if(result && result.access_token) {
return cb(null, result);
}
else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Retrieves information about a given media based on its url (see http://instagram.com/developer/embedding/)
* @param url string the url of the instagram media item to get the oEmbed data for
* @param options object { callback, [opt]
* omitscript, [opt]
* hidecaption, [opt]
* maxwidth [opt] }
* @param cb function (err, result, limit);
*/
oembed = function(url, options, cb) {
var retry = function() {
oembed(url, options, cb);
}
if(!cb && typeof options === 'function') {
cb = options;
options = {};
}
var params = {};
if(typeof url !== 'string' || url === '') {
return handle_error(new Error('Wrong param "url"'), cb, retry);
}
else {
params.url = url;
}
if(options.callback) {
params.callback = options.callback;
}
if(options.omitscript) {
params.omitscript = options.omitscript;
}
if(options.hidecaption) {
params.hidecaption = options.hidecaption;
}
if(options.maxwidth) {
params.maxwidth = options.maxwidth;
}
call('GET', '/oembed/', params, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result) {
return cb(null, result, remaining, limit);
} else {
return handle_error(result, cb, retry);
}
}, retry);
};
/**
* Get a list of realtime subscriptions (see http://instagram.com/developer/realtime/#list-your-subscriptions)
* @param function cb function (err, result, limit);
*/
subscriptions = function(cb){
var retry = function(){
subscriptions(cb);
};
call('GET', '/subscriptions/', {}, function(err, result, remaining, limit) {
if(err) {
return handle_error(err, cb, retry);
} else if(result && result.meta && result.met