├── TODO.md ├── test ├── instagram.users.coffee ├── helpers.coffee ├── instagram.coffee ├── helpers.js ├── instagram.js ├── instagram.geographies.coffee ├── instagram.geographies.js ├── instagram.oauth.coffee ├── instagram.subscriptions.coffee ├── instagram.oauth.js ├── initialize.coffee ├── instagram.tags.coffee ├── instagram.subscriptions.js ├── instagram.locations.coffee ├── initialize.js ├── instagram.tags.js ├── instagram.locations.js ├── instagram.media.coffee ├── instagram.media.js └── instagram.users.js ├── .npmignore ├── .gitignore ├── lib ├── class.instagram.geographies.coffee ├── class.instagram.tags.coffee ├── class.instagram.locations.coffee ├── class.instagram.geographies.js ├── class.instagram.oauth.coffee ├── class.instagram.tags.js ├── class.instagram.locations.js ├── class.instagram.oauth.js ├── class.instagram.media.coffee ├── class.instagram.subscriptions.coffee ├── class.instagram.users.coffee ├── class.instagram.media.js ├── class.instagram.subscriptions.js ├── class.instagram.users.js ├── class.instagram.coffee └── class.instagram.js ├── HISTORY.md ├── package.json ├── LICENSE.md └── README.md /TODO.md: -------------------------------------------------------------------------------- 1 | 2 | ## Todo 3 | 4 | Visit [PivotalTracker](https://www.pivotaltracker.com/projects/273809) for detail. 5 | -------------------------------------------------------------------------------- /test/instagram.users.coffee: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bcherry/instagram-node-lib/master/test/instagram.users.coffee -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | 2 | # Remove Coffeescript # 3 | ####################### 4 | *.coffee 5 | 6 | # Remove Testing Environment # 7 | ############################## 8 | *.sh 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Edits/Editors # 2 | ################# 3 | ._* 4 | *~ 5 | *BAK 6 | .project 7 | 8 | # OS generated files # 9 | ###################### 10 | .DS_Store? 11 | ehthumbs.db 12 | Icon? 13 | Thumbs.db 14 | .AppleDouble 15 | 16 | # Testing Environment # 17 | ####################### 18 | *.sh 19 | -------------------------------------------------------------------------------- /lib/class.instagram.geographies.coffee: -------------------------------------------------------------------------------- 1 | 2 | ### 3 | Geographies 4 | ### 5 | 6 | class InstagramGeographies 7 | constructor: (parent) -> 8 | @parent = parent 9 | 10 | recent: (params) -> 11 | params['client_id'] = @parent._config.client_id 12 | params['path'] = "/#{@parent._api_version}/geographies/#{params['geography_id']}/media/recent?#{@parent._to_querystring(params)}" 13 | @parent._request params 14 | 15 | ### 16 | Subscriptions 17 | ### 18 | 19 | subscribe: (params) -> 20 | params['object'] = 'geography' 21 | @parent.subscriptions._subscribe params 22 | 23 | unsubscribe: (params) -> 24 | @parent.subscriptions._unsubscribe params 25 | 26 | unsubscribe_all: (params) -> 27 | params['object'] = 'geography' 28 | @parent.subscriptions._unsubscribe params 29 | 30 | module.exports = InstagramGeographies 31 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | 2 | 0.0.7 / 2011-06-23 3 | ================== 4 | 5 | * Added more control for server responses in oauth.ask_for_access_token 6 | * Added Instagram.oauth tests 7 | * Added Instagram.set tests 8 | * Added test server option (mostly for oauth testing) 9 | 10 | 0.0.6 / 2011-06-09 11 | ================== 12 | 13 | * Removed auto-response from ask_for_access_token 14 | 15 | 0.0.5 / 2011-05-21 16 | ================== 17 | 18 | * Added self/liked 19 | 20 | 0.0.4 / 2011-05-03 21 | ================== 22 | 23 | * Added additional tests 24 | * Handled ENOTCONN errors (IG closes the connection fast) 25 | 26 | 0.0.3 / 2011-04-15 27 | ================== 28 | 29 | * Added OAuth methods 30 | 31 | 0.0.2 / 2011-04-10 32 | ================== 33 | 34 | * Updated name and published through npm 35 | 36 | 0.0.1 / 2011-04-07 37 | ================== 38 | 39 | * Initial release 40 | -------------------------------------------------------------------------------- /test/helpers.coffee: -------------------------------------------------------------------------------- 1 | 2 | indent = " " 3 | 4 | module.exports = 5 | indent: indent 6 | helper: (title = '', Instagram, type, method, params = {}, assertions) -> 7 | params['complete'] = (data, pagination) -> 8 | console.log "\n#{title}\n#{indent}connection/parsing succeeded" 9 | try 10 | assertions data, pagination 11 | console.log "#{indent}data met assertions" 12 | catch e 13 | console.log "#{indent}data failed to meet the assertion(s): #{e}" 14 | throw e 15 | params['error'] = (e, data, caller) -> 16 | console.log "#{indent}error: #{e}\n#{indent}data: #{data}\n#{indent}caller: #{caller}" 17 | throw e 18 | Instagram[type][method] params 19 | output: (message, value = null) -> 20 | console.log "#{indent}#{message}" 21 | if value? 22 | if typeof value is 'object' 23 | console.log "#{indent}#{indent}it was: " + JSON.stringify(value) 24 | else 25 | console.log "#{indent}#{indent}it was: #{value}" 26 | -------------------------------------------------------------------------------- /lib/class.instagram.tags.coffee: -------------------------------------------------------------------------------- 1 | 2 | class InstagramTags 3 | constructor: (parent) -> 4 | @parent = parent 5 | 6 | info: (params) -> 7 | credentials = @parent._credentials {} 8 | params['path'] = "/#{@parent._api_version}/tags/#{params['name']}?#{@parent._to_querystring(credentials)}" 9 | @parent._request params 10 | 11 | recent: (params) -> 12 | params = @parent._credentials params 13 | params['path'] = "/#{@parent._api_version}/tags/#{params['name']}/media/recent?#{@parent._to_querystring(params)}" 14 | @parent._request params 15 | 16 | search: (params) -> 17 | params = @parent._credentials params 18 | params['path'] = "/#{@parent._api_version}/tags/search?#{@parent._to_querystring(params)}" 19 | @parent._request params 20 | 21 | subscribe: (params) -> 22 | params['object'] = 'tag' 23 | @parent.subscriptions._subscribe params 24 | 25 | unsubscribe: (params) -> 26 | @parent.subscriptions._unsubscribe params 27 | 28 | unsubscribe_all: (params) -> 29 | params['object'] = 'tag' 30 | @parent.subscriptions._unsubscribe params 31 | 32 | module.exports = InstagramTags 33 | -------------------------------------------------------------------------------- /lib/class.instagram.locations.coffee: -------------------------------------------------------------------------------- 1 | 2 | class InstagramLocations 3 | constructor: (parent) -> 4 | @parent = parent 5 | 6 | info: (params) -> 7 | credentials = @parent._credentials {} 8 | params['path'] = "/#{@parent._api_version}/locations/#{params['location_id']}?#{@parent._to_querystring(credentials)}" 9 | @parent._request params 10 | 11 | recent: (params) -> 12 | params = @parent._credentials params 13 | params['path'] = "/#{@parent._api_version}/locations/#{params['location_id']}/media/recent?#{@parent._to_querystring(params)}" 14 | @parent._request params 15 | 16 | search: (params) -> 17 | params = @parent._credentials params 18 | params['path'] = "/#{@parent._api_version}/locations/search?#{@parent._to_querystring(params)}" 19 | @parent._request params 20 | 21 | subscribe: (params) -> 22 | params['object'] = 'location' 23 | @parent.subscriptions._subscribe params 24 | 25 | unsubscribe: (params) -> 26 | @parent.subscriptions._unsubscribe params 27 | 28 | unsubscribe_all: (params) -> 29 | params['object'] = 'location' 30 | @parent.subscriptions._unsubscribe params 31 | 32 | module.exports = InstagramLocations 33 | -------------------------------------------------------------------------------- /lib/class.instagram.geographies.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | /* 3 | Geographies 4 | */ var InstagramGeographies; 5 | InstagramGeographies = (function() { 6 | function InstagramGeographies(parent) { 7 | this.parent = parent; 8 | } 9 | InstagramGeographies.prototype.recent = function(params) { 10 | params['client_id'] = this.parent._config.client_id; 11 | params['path'] = "/" + this.parent._api_version + "/geographies/" + params['geography_id'] + "/media/recent?" + (this.parent._to_querystring(params)); 12 | return this.parent._request(params); 13 | }; 14 | /* 15 | Subscriptions 16 | */ 17 | InstagramGeographies.prototype.subscribe = function(params) { 18 | params['object'] = 'geography'; 19 | return this.parent.subscriptions._subscribe(params); 20 | }; 21 | InstagramGeographies.prototype.unsubscribe = function(params) { 22 | return this.parent.subscriptions._unsubscribe(params); 23 | }; 24 | InstagramGeographies.prototype.unsubscribe_all = function(params) { 25 | params['object'] = 'geography'; 26 | return this.parent.subscriptions._unsubscribe(params); 27 | }; 28 | return InstagramGeographies; 29 | })(); 30 | module.exports = InstagramGeographies; 31 | }).call(this); 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "instagram-node-lib", 3 | "description": "This package is a wrapper for the Instagram API.", 4 | "version": "0.0.7", 5 | "author": "David W. McKelvey (http://david.mckelveycreative.com/)", 6 | "contributors": [], 7 | "keywords": [ 8 | "instagram", 9 | "api", 10 | "lib" 11 | ], 12 | "main": "./lib/class.instagram.js", 13 | "engines": { 14 | "node": ">= 0.4.5" 15 | }, 16 | "directories": { 17 | "lib": "./lib" 18 | }, 19 | "files": [ 20 | "" 21 | ], 22 | "homepage": "https://github.com/mckelvey/instagram-node-lib", 23 | "repository": { 24 | "type": "git", 25 | "url": "git://github.com/mckelvey/instagram-node-lib.git" 26 | }, 27 | "scripts": { 28 | "test set": "expresso ./test/instagram.js", 29 | "test geographies": "expresso ./test/instagram.geographies.js", 30 | "test locations": "expresso ./test/instagram.locations.js", 31 | "test media": "expresso ./test/instagram.media.js", 32 | "test tags": "expresso ./test/instagram.tags.js", 33 | "test users": "expresso ./test/instagram.users.js", 34 | "test subscriptions": "expresso ./test/instagram.subscriptions.js", 35 | "test oauth": "expresso ./test/instagram.oauth.js" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | ## License 3 | 4 | (The MIT X License) 5 | 6 | Copyright (c) 2011 David W. McKelvey <david@mckelveycreative.com> 7 | 8 | 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: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | Except as contained in this notice, the name(s) of the above copyright holders shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization. 13 | 14 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 15 | 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. 16 | -------------------------------------------------------------------------------- /test/instagram.coffee: -------------------------------------------------------------------------------- 1 | 2 | ### 3 | Test Setup 4 | ### 5 | 6 | console.log "\nInstagram API Node.js Lib Tests :: Basic" 7 | 8 | Init = require './initialize' 9 | Instagram = Init.Instagram 10 | app = Init.app 11 | 12 | assert = require 'assert' 13 | should = require 'should' 14 | test = require './helpers' 15 | 16 | completed = 0 17 | to_do = 0 18 | 19 | ### 20 | Tests 21 | ### 22 | 23 | module.exports = 24 | 'instagram#set': -> 25 | console.log "\ninstagram#set" 26 | Instagram._config.should.have.property 'client_id', process.env['CLIENT_ID'] 27 | Instagram.set 'client_id', 'YOUR-CLIENT-ID' 28 | Instagram._config.should.have.property 'client_id', 'YOUR-CLIENT-ID' 29 | test.output "Instagram._config has 'client_id' and it is now reset", Instagram._config.client_id 30 | Instagram._config.should.have.property 'client_secret', process.env['CLIENT_SECRET'] 31 | Instagram.set 'client_secret', 'YOUR-CLIENT-SECRET' 32 | Instagram._config.should.have.property 'client_secret', 'YOUR-CLIENT-SECRET' 33 | test.output "Instagram._config has 'client_secret' and it is now reset", Instagram._config.client_secret 34 | Instagram.set 'maxSockets', 10 35 | Instagram._http_client.Agent.defaultMaxSockets.should.equal 10 36 | Instagram._https_client.Agent.defaultMaxSockets.should.equal 10 37 | test.output "Instagram._http(s)_client(s) have increased maxSockets to 10" 38 | app.finish_test() 39 | 40 | app.start_tests module.exports 41 | -------------------------------------------------------------------------------- /test/helpers.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var indent; 3 | indent = " "; 4 | module.exports = { 5 | indent: indent, 6 | helper: function(title, Instagram, type, method, params, assertions) { 7 | if (title == null) { 8 | title = ''; 9 | } 10 | if (params == null) { 11 | params = {}; 12 | } 13 | params['complete'] = function(data, pagination) { 14 | console.log("\n" + title + "\n" + indent + "connection/parsing succeeded"); 15 | try { 16 | assertions(data, pagination); 17 | return console.log("" + indent + "data met assertions"); 18 | } catch (e) { 19 | console.log("" + indent + "data failed to meet the assertion(s): " + e); 20 | throw e; 21 | } 22 | }; 23 | params['error'] = function(e, data, caller) { 24 | console.log("" + indent + "error: " + e + "\n" + indent + "data: " + data + "\n" + indent + "caller: " + caller); 25 | throw e; 26 | }; 27 | return Instagram[type][method](params); 28 | }, 29 | output: function(message, value) { 30 | if (value == null) { 31 | value = null; 32 | } 33 | console.log("" + indent + message); 34 | if (value != null) { 35 | if (typeof value === 'object') { 36 | return console.log(("" + indent + indent + "it was: ") + JSON.stringify(value)); 37 | } else { 38 | return console.log("" + indent + indent + "it was: " + value); 39 | } 40 | } 41 | } 42 | }; 43 | }).call(this); 44 | -------------------------------------------------------------------------------- /lib/class.instagram.oauth.coffee: -------------------------------------------------------------------------------- 1 | 2 | class InstagramOAuth 3 | constructor: (parent) -> 4 | @parent = parent 5 | 6 | authorization_url: (params) -> 7 | params['client_id'] = @parent._config.client_id 8 | params['redirect_uri'] = if params['redirect_uri'] is undefined or params['redirect_uri'] is null then @parent._config.redirect_uri else params['redirect_uri'] 9 | params['response_type'] = 'code' 10 | return "https://#{@parent._options['host']}/oauth/authorize/?#{@parent._to_querystring(params)}" 11 | 12 | ask_for_access_token: (params) -> 13 | url = require 'url' 14 | parsed_query = url.parse(params['request'].url, true).query 15 | if parsed_query.error? 16 | @parent._error "#{parsed_query.error}: #{parsed_query.error_reason}: #{parsed_query.error_description}", parsed_query, 'handshake' 17 | else if parsed_query.code? 18 | token_params = 19 | complete: params['complete'] 20 | response: params['response'] 21 | method: "POST" 22 | path: "/oauth/access_token" 23 | post_data: 24 | client_id: @parent._config.client_id 25 | client_secret: @parent._config.client_secret 26 | grant_type: 'authorization_code' 27 | redirect_uri: if params['redirect_uri'] is undefined or params['redirect_uri'] is null then @parent._config.redirect_uri else params['redirect_uri'] 28 | code: parsed_query.code 29 | @parent._request token_params 30 | if params['redirect']? 31 | params['response'].redirect(params['redirect']); 32 | params['response'].end() 33 | 34 | module.exports = InstagramOAuth 35 | -------------------------------------------------------------------------------- /test/instagram.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | /* 3 | Test Setup 4 | */ var Init, Instagram, app, assert, completed, should, test, to_do; 5 | console.log("\nInstagram API Node.js Lib Tests :: Basic"); 6 | Init = require('./initialize'); 7 | Instagram = Init.Instagram; 8 | app = Init.app; 9 | assert = require('assert'); 10 | should = require('should'); 11 | test = require('./helpers'); 12 | completed = 0; 13 | to_do = 0; 14 | /* 15 | Tests 16 | */ 17 | module.exports = { 18 | 'instagram#set': function() { 19 | console.log("\ninstagram#set"); 20 | Instagram._config.should.have.property('client_id', process.env['CLIENT_ID']); 21 | Instagram.set('client_id', 'YOUR-CLIENT-ID'); 22 | Instagram._config.should.have.property('client_id', 'YOUR-CLIENT-ID'); 23 | test.output("Instagram._config has 'client_id' and it is now reset", Instagram._config.client_id); 24 | Instagram._config.should.have.property('client_secret', process.env['CLIENT_SECRET']); 25 | Instagram.set('client_secret', 'YOUR-CLIENT-SECRET'); 26 | Instagram._config.should.have.property('client_secret', 'YOUR-CLIENT-SECRET'); 27 | test.output("Instagram._config has 'client_secret' and it is now reset", Instagram._config.client_secret); 28 | Instagram.set('maxSockets', 10); 29 | Instagram._http_client.Agent.defaultMaxSockets.should.equal(10); 30 | Instagram._https_client.Agent.defaultMaxSockets.should.equal(10); 31 | test.output("Instagram._http(s)_client(s) have increased maxSockets to 10"); 32 | return app.finish_test(); 33 | } 34 | }; 35 | app.start_tests(module.exports); 36 | }).call(this); 37 | -------------------------------------------------------------------------------- /lib/class.instagram.tags.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var InstagramTags; 3 | InstagramTags = (function() { 4 | function InstagramTags(parent) { 5 | this.parent = parent; 6 | } 7 | InstagramTags.prototype.info = function(params) { 8 | var credentials; 9 | credentials = this.parent._credentials({}); 10 | params['path'] = "/" + this.parent._api_version + "/tags/" + params['name'] + "?" + (this.parent._to_querystring(credentials)); 11 | return this.parent._request(params); 12 | }; 13 | InstagramTags.prototype.recent = function(params) { 14 | params = this.parent._credentials(params); 15 | params['path'] = "/" + this.parent._api_version + "/tags/" + params['name'] + "/media/recent?" + (this.parent._to_querystring(params)); 16 | return this.parent._request(params); 17 | }; 18 | InstagramTags.prototype.search = function(params) { 19 | params = this.parent._credentials(params); 20 | params['path'] = "/" + this.parent._api_version + "/tags/search?" + (this.parent._to_querystring(params)); 21 | return this.parent._request(params); 22 | }; 23 | InstagramTags.prototype.subscribe = function(params) { 24 | params['object'] = 'tag'; 25 | return this.parent.subscriptions._subscribe(params); 26 | }; 27 | InstagramTags.prototype.unsubscribe = function(params) { 28 | return this.parent.subscriptions._unsubscribe(params); 29 | }; 30 | InstagramTags.prototype.unsubscribe_all = function(params) { 31 | params['object'] = 'tag'; 32 | return this.parent.subscriptions._unsubscribe(params); 33 | }; 34 | return InstagramTags; 35 | })(); 36 | module.exports = InstagramTags; 37 | }).call(this); 38 | -------------------------------------------------------------------------------- /lib/class.instagram.locations.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var InstagramLocations; 3 | InstagramLocations = (function() { 4 | function InstagramLocations(parent) { 5 | this.parent = parent; 6 | } 7 | InstagramLocations.prototype.info = function(params) { 8 | var credentials; 9 | credentials = this.parent._credentials({}); 10 | params['path'] = "/" + this.parent._api_version + "/locations/" + params['location_id'] + "?" + (this.parent._to_querystring(credentials)); 11 | return this.parent._request(params); 12 | }; 13 | InstagramLocations.prototype.recent = function(params) { 14 | params = this.parent._credentials(params); 15 | params['path'] = "/" + this.parent._api_version + "/locations/" + params['location_id'] + "/media/recent?" + (this.parent._to_querystring(params)); 16 | return this.parent._request(params); 17 | }; 18 | InstagramLocations.prototype.search = function(params) { 19 | params = this.parent._credentials(params); 20 | params['path'] = "/" + this.parent._api_version + "/locations/search?" + (this.parent._to_querystring(params)); 21 | return this.parent._request(params); 22 | }; 23 | InstagramLocations.prototype.subscribe = function(params) { 24 | params['object'] = 'location'; 25 | return this.parent.subscriptions._subscribe(params); 26 | }; 27 | InstagramLocations.prototype.unsubscribe = function(params) { 28 | return this.parent.subscriptions._unsubscribe(params); 29 | }; 30 | InstagramLocations.prototype.unsubscribe_all = function(params) { 31 | params['object'] = 'location'; 32 | return this.parent.subscriptions._unsubscribe(params); 33 | }; 34 | return InstagramLocations; 35 | })(); 36 | module.exports = InstagramLocations; 37 | }).call(this); 38 | -------------------------------------------------------------------------------- /test/instagram.geographies.coffee: -------------------------------------------------------------------------------- 1 | 2 | ### 3 | Test Setup 4 | ### 5 | 6 | console.log "\nInstagram API Node.js Lib Tests :: Geographies" 7 | 8 | Init = require './initialize' 9 | Instagram = Init.Instagram 10 | app = Init.app 11 | 12 | assert = require 'assert' 13 | should = require 'should' 14 | test = require './helpers' 15 | 16 | completed = 0 17 | to_do = 0 18 | 19 | ### 20 | Tests 21 | ### 22 | 23 | module.exports = 24 | 'geographies#subscriptions': -> 25 | test.helper "geographies#subscriptions subscribe to geography near Eiffel Tower", Instagram, 'geographies', 'subscribe', { lat: 48.858844300000001, lng: 2.2943506, radius: 1000 }, (data) -> 26 | data.should.have.property 'id' 27 | test.output "data had the property 'id'" 28 | data.id.should.be.above 0 29 | test.output "data.id was greater than 0", data.id 30 | data.should.have.property 'type', 'subscription' 31 | test.output "data had the property 'type' equal to 'subscription'", data 32 | subscription_id = data.id 33 | test.helper 'geographies#subscriptions list', Instagram, 'subscriptions', 'list', {}, (data) -> 34 | data.length.should.be.above 0 35 | test.output "data had length greater than 0", data.length 36 | found = false 37 | for i of data 38 | found = true if data[i].id is subscription_id 39 | throw "subscription not found" if !found 40 | test.output "data had the subscription #{subscription_id}" 41 | test.helper "geographies#subscriptions unsubscribe from media near Eiffel Tower", Instagram, 'geographies', 'unsubscribe', { id: subscription_id }, (data) -> 42 | throw "geography near Eiffel Tower unsubscribe failed" if data isnt null 43 | test.output "data was null; we unsubscribed from the subscription #{subscription_id}" 44 | app.finish_test() 45 | 46 | app.start_tests module.exports 47 | -------------------------------------------------------------------------------- /lib/class.instagram.oauth.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var InstagramOAuth; 3 | InstagramOAuth = (function() { 4 | function InstagramOAuth(parent) { 5 | this.parent = parent; 6 | } 7 | InstagramOAuth.prototype.authorization_url = function(params) { 8 | params['client_id'] = this.parent._config.client_id; 9 | params['redirect_uri'] = params['redirect_uri'] === void 0 || params['redirect_uri'] === null ? this.parent._config.redirect_uri : params['redirect_uri']; 10 | params['response_type'] = 'code'; 11 | return "https://" + this.parent._options['host'] + "/oauth/authorize/?" + (this.parent._to_querystring(params)); 12 | }; 13 | InstagramOAuth.prototype.ask_for_access_token = function(params) { 14 | var parsed_query, token_params, url; 15 | url = require('url'); 16 | parsed_query = url.parse(params['request'].url, true).query; 17 | if (parsed_query.error != null) { 18 | return this.parent._error("" + parsed_query.error + ": " + parsed_query.error_reason + ": " + parsed_query.error_description, parsed_query, 'handshake'); 19 | } else if (parsed_query.code != null) { 20 | token_params = { 21 | complete: params['complete'], 22 | response: params['response'], 23 | method: "POST", 24 | path: "/oauth/access_token", 25 | post_data: { 26 | client_id: this.parent._config.client_id, 27 | client_secret: this.parent._config.client_secret, 28 | grant_type: 'authorization_code', 29 | redirect_uri: params['redirect_uri'] === void 0 || params['redirect_uri'] === null ? this.parent._config.redirect_uri : params['redirect_uri'], 30 | code: parsed_query.code 31 | } 32 | }; 33 | this.parent._request(token_params); 34 | if (params['redirect'] != null) { 35 | params['response'].redirect(params['redirect']); 36 | return params['response'].end(); 37 | } 38 | } 39 | }; 40 | return InstagramOAuth; 41 | })(); 42 | module.exports = InstagramOAuth; 43 | }).call(this); 44 | -------------------------------------------------------------------------------- /test/instagram.geographies.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | /* 3 | Test Setup 4 | */ var Init, Instagram, app, assert, completed, should, test, to_do; 5 | console.log("\nInstagram API Node.js Lib Tests :: Geographies"); 6 | Init = require('./initialize'); 7 | Instagram = Init.Instagram; 8 | app = Init.app; 9 | assert = require('assert'); 10 | should = require('should'); 11 | test = require('./helpers'); 12 | completed = 0; 13 | to_do = 0; 14 | /* 15 | Tests 16 | */ 17 | module.exports = { 18 | 'geographies#subscriptions': function() { 19 | return test.helper("geographies#subscriptions subscribe to geography near Eiffel Tower", Instagram, 'geographies', 'subscribe', { 20 | lat: 48.858844300000001, 21 | lng: 2.2943506, 22 | radius: 1000 23 | }, function(data) { 24 | var subscription_id; 25 | data.should.have.property('id'); 26 | test.output("data had the property 'id'"); 27 | data.id.should.be.above(0); 28 | test.output("data.id was greater than 0", data.id); 29 | data.should.have.property('type', 'subscription'); 30 | test.output("data had the property 'type' equal to 'subscription'", data); 31 | subscription_id = data.id; 32 | return test.helper('geographies#subscriptions list', Instagram, 'subscriptions', 'list', {}, function(data) { 33 | var found, i; 34 | data.length.should.be.above(0); 35 | test.output("data had length greater than 0", data.length); 36 | found = false; 37 | for (i in data) { 38 | if (data[i].id === subscription_id) { 39 | found = true; 40 | } 41 | } 42 | if (!found) { 43 | throw "subscription not found"; 44 | } 45 | test.output("data had the subscription " + subscription_id); 46 | return test.helper("geographies#subscriptions unsubscribe from media near Eiffel Tower", Instagram, 'geographies', 'unsubscribe', { 47 | id: subscription_id 48 | }, function(data) { 49 | if (data !== null) { 50 | throw "geography near Eiffel Tower unsubscribe failed"; 51 | } 52 | test.output("data was null; we unsubscribed from the subscription " + subscription_id); 53 | return app.finish_test(); 54 | }); 55 | }); 56 | }); 57 | } 58 | }; 59 | app.start_tests(module.exports); 60 | }).call(this); 61 | -------------------------------------------------------------------------------- /test/instagram.oauth.coffee: -------------------------------------------------------------------------------- 1 | 2 | ### 3 | Test Setup 4 | ### 5 | 6 | console.log "\nInstagram API Node.js Lib Tests :: OAuth" 7 | 8 | Init = require './initialize' 9 | Instagram = Init.Instagram 10 | app = Init.app 11 | 12 | assert = require 'assert' 13 | should = require 'should' 14 | test = require './helpers' 15 | http_client = require 'http' 16 | url = require 'url' 17 | 18 | completed = 0 19 | to_do = 0 20 | 21 | ### 22 | Tests 23 | ### 24 | 25 | module.exports = 26 | 'oauth#roundtrip': -> 27 | console.log "\noauth#roundtrip" 28 | result = Instagram.oauth.authorization_url { 29 | scope: 'comments likes' 30 | display: 'touch' 31 | } 32 | result.should.match(/\/oauth\/authorize\//) 33 | result.should.match(/client_id\=/) 34 | result.should.match(/redirect_uri\=/) 35 | result.should.match(/response_type\=code/) 36 | result.should.not.match(/client_id\=CLIENT\-ID/) 37 | result.should.not.match(/redirect_uri\=REDIRECT_URI/) 38 | test.output "result matched authorization url", result 39 | result = url.parse result 40 | options = 41 | host: if process.env['TEST_HOST']? then process.env['TEST_HOST'] else result.hostname 42 | port: if process.env['TEST_PORT']? then process.env['TEST_PORT'] else result.port 43 | method: "GET" 44 | path: "/fake#{result['pathname']}#{result['search']}" 45 | request = http_client.request options, (response) -> 46 | response.should.have.property 'statusCode', 302 47 | response.should.have.property 'headers' 48 | response.headers.should.have.property 'location' 49 | test.output "response met redirect criteria", response.headers.location 50 | token_uri = url.parse response.headers.location 51 | token_options = 52 | host: token_uri['hostname'] 53 | port: if typeof token_uri['port'] isnt 'undefined' then token_uri['port'] else null 54 | method: "GET" 55 | path: "#{token_uri['pathname']}#{token_uri['search']}" 56 | token_request = http_client.request token_options, (token_response) -> 57 | data = '' 58 | token_response.on 'data', (chunk) -> 59 | data += chunk 60 | token_response.on 'end', -> 61 | test.output "final receipt", data 62 | app.finish_test() 63 | token_request.addListener 'error', (connectionException) -> 64 | if connectionException.code isnt 'ENOTCONN' 65 | console.log "\n" + connectionException 66 | throw connectionException 67 | app.finish_test() 68 | token_request.end() 69 | request.addListener 'error', (connectionException) -> 70 | if connectionException.code isnt 'ENOTCONN' 71 | console.log "\n" + connectionException 72 | throw connectionException 73 | app.finish_test() 74 | request.end() 75 | 76 | app.start_tests module.exports 77 | -------------------------------------------------------------------------------- /lib/class.instagram.media.coffee: -------------------------------------------------------------------------------- 1 | 2 | class InstagramMedia 3 | constructor: (parent) -> 4 | @parent = parent 5 | 6 | ### 7 | Basic Media 8 | ### 9 | 10 | popular: (params) -> 11 | credentials = @parent._credentials {} 12 | params['path'] = "/#{@parent._api_version}/media/popular?#{@parent._to_querystring(credentials)}" 13 | @parent._request params 14 | 15 | info: (params) -> 16 | credentials = @parent._credentials {} 17 | params['path'] = "/#{@parent._api_version}/media/#{params['media_id']}?#{@parent._to_querystring(credentials)}" 18 | @parent._request params 19 | 20 | search: (params) -> 21 | params = @parent._credentials params 22 | params['path'] = "/#{@parent._api_version}/media/search?#{@parent._to_querystring(params)}" 23 | @parent._request params 24 | 25 | ### 26 | Likes 27 | ### 28 | 29 | likes: (params) -> 30 | credentials = @parent._credentials {} 31 | params['path'] = "/#{@parent._api_version}/media/#{params['media_id']}/likes?#{@parent._to_querystring(credentials)}" 32 | @parent._request params 33 | 34 | like: (params) -> 35 | params['post_data'] = @parent._credentials {}, 'access_token' 36 | params['method'] = 'POST' 37 | params['path'] = "/#{@parent._api_version}/media/#{params['media_id']}/likes" 38 | @parent._request params 39 | 40 | unlike: (params) -> 41 | params = @parent._credentials params, 'access_token' 42 | params['method'] = 'DELETE' 43 | params['path'] = "/#{@parent._api_version}/media/#{params['media_id']}/likes?#{@parent._to_querystring(params)}" 44 | @parent._request params 45 | 46 | ### 47 | Comments 48 | ### 49 | 50 | comments: (params) -> 51 | credentials = @parent._credentials {} 52 | params['path'] = "/#{@parent._api_version}/media/#{params['media_id']}/comments?#{@parent._to_querystring(credentials)}" 53 | @parent._request params 54 | 55 | comment: (params) -> 56 | params['post_data'] = @parent._credentials { text: params['text'] }, 'access_token' 57 | params['method'] = 'POST' 58 | params['path'] = "/#{@parent._api_version}/media/#{params['media_id']}/comments" 59 | @parent._request params 60 | 61 | uncomment: (params) -> 62 | credentials = @parent._credentials {}, 'access_token' 63 | params['method'] = 'DELETE' 64 | params['path'] = "/#{@parent._api_version}/media/#{params['media_id']}/comments/#{params['comment_id']}?#{@parent._to_querystring(credentials)}" 65 | @parent._request params 66 | 67 | ### 68 | Subscriptions 69 | ### 70 | 71 | subscribe: (params) -> 72 | params['object'] = 'geography' 73 | @parent.subscriptions._subscribe params 74 | 75 | unsubscribe: (params) -> 76 | @parent.subscriptions._unsubscribe params 77 | 78 | unsubscribe_all: (params) -> 79 | params['object'] = 'geography' 80 | @parent.subscriptions._unsubscribe params 81 | 82 | module.exports = InstagramMedia 83 | -------------------------------------------------------------------------------- /test/instagram.subscriptions.coffee: -------------------------------------------------------------------------------- 1 | 2 | ### 3 | Test Setup 4 | ### 5 | 6 | console.log "\nInstagram API Node.js Lib Tests :: Subscriptions" 7 | 8 | Init = require './initialize' 9 | Instagram = Init.Instagram 10 | app = Init.app 11 | 12 | assert = require 'assert' 13 | should = require 'should' 14 | test = require './helpers' 15 | 16 | completed = 0 17 | to_do = 0 18 | 19 | ### 20 | Tests 21 | ### 22 | 23 | module.exports = 24 | 'multi#subscriptions': -> 25 | subscriptions = [] 26 | test.helper "subscriptions subscribe to tag 'red'", Instagram, 'subscriptions', 'subscribe', { object: 'tag', object_id: 'red' }, (data) -> 27 | data.should.have.property 'id' 28 | test.output "data had the property 'id'" 29 | data.id.should.be.above 0 30 | test.output "data.id was greater than 0", data.id 31 | data.should.have.property 'type', 'subscription' 32 | test.output "data had the property 'type' equal to 'subscription'", data 33 | subscriptions[subscriptions.length] = data.id 34 | test.helper "subscriptions subscribe to location '1257285'", Instagram, 'subscriptions', 'subscribe', { object: 'location', object_id: '1257285' }, (data) -> 35 | data.should.have.property 'id' 36 | test.output "data had the property 'id'" 37 | data.id.should.be.above 0 38 | test.output "data.id was greater than 0", data.id 39 | data.should.have.property 'type', 'subscription' 40 | test.output "data had the property 'type' equal to 'subscription'", data 41 | subscriptions[subscriptions.length] = data.id 42 | test.helper "subscriptions subscribe to media near Eiffel Tower", Instagram, 'subscriptions', 'subscribe', { object: 'geography', lat: 48.858844300000001, lng: 2.2943506, radius: 1000 }, (data) -> 43 | data.should.have.property 'id' 44 | test.output "data had the property 'id'" 45 | data.id.should.be.above 0 46 | test.output "data.id was greater than 0", data.id 47 | data.should.have.property 'type', 'subscription' 48 | test.output "data had the property 'type' equal to 'subscription'", data 49 | subscriptions[subscriptions.length] = data.id 50 | test.helper 'subscriptions list', Instagram, 'subscriptions', 'list', {}, (data) -> 51 | data.length.should.be.above 0 52 | test.output "data had length greater than 0", data.length 53 | subscriptions_list = [] 54 | for i of data 55 | subscriptions_list[subscriptions_list.length] = data[i].id 56 | found = true 57 | for i of subscriptions 58 | found = false if subscriptions[i] not in subscriptions_list 59 | throw "subscription not found" if !found 60 | test.output "data had the subscriptions #{subscriptions.join(', ')}" 61 | test.helper "subscriptions unsubscribe_all", Instagram, 'subscriptions', 'unsubscribe_all', {}, (data) -> 62 | throw "unsubscribe_all failed" if data isnt null 63 | test.output "data was null; we unsubscribed from the subscriptions #{subscriptions_list.join(', ')}" 64 | app.finish_test() 65 | 66 | app.start_tests module.exports 67 | -------------------------------------------------------------------------------- /lib/class.instagram.subscriptions.coffee: -------------------------------------------------------------------------------- 1 | 2 | url = require 'url' 3 | crypto = require 'crypto' 4 | 5 | class InstagramSubscriptions 6 | constructor: (parent) -> 7 | @parent = parent 8 | 9 | ### 10 | Verification Methods 11 | ### 12 | 13 | handshake: (request, response, complete) -> 14 | parsedRequest = url.parse(request.url, true) 15 | if parsedRequest['query']['hub.mode'] is 'subscribe' and parsedRequest['query']['hub.challenge']? and parsedRequest['query']['hub.challenge'].length > 0 16 | body = parsedRequest['query']['hub.challenge'] 17 | headers = 18 | 'Content-Length': body.length 19 | 'Content-Type': 'text/plain' 20 | response.writeHead 200, headers 21 | response.write body 22 | complete(parsedRequest['query']['hub.verify_token']) if parsedRequest['query']['hub.verify_token']? and complete? 23 | else 24 | response.writeHead 400 25 | response.end() 26 | 27 | verified: (request) -> 28 | return false if request.rawBody is null or request.headers['x-hub-signature'] is undefined or request.headers['x-hub-signature'] is null 29 | hmac = crypto.createHmac 'sha1', @parent._config.client_secret 30 | hmac.update request.rawBody 31 | calculated_signature = hmac.digest(encoding='hex') 32 | return false if calculated_signature isnt request.headers['x-hub-signature'] 33 | true 34 | 35 | ### 36 | Shared Subscription Methods 37 | ### 38 | 39 | _subscribe: (params) -> 40 | params['method'] = "POST" 41 | params['path'] = "/#{@parent._api_version}/subscriptions/" 42 | if (typeof params['callback_url'] is 'undefined' or params['callback_url'] is null) and @parent._config.callback_url isnt null 43 | params['callback_url'] = @parent._config.callback_url 44 | params['post_data'] = 45 | object: params['object'] 46 | aspect: 'media' 47 | client_id: @parent._config.client_id 48 | client_secret: @parent._config.client_secret 49 | callback_url: params['callback_url'] 50 | for i in ['object_id', 'verify_token', 'lat', 'lng', 'radius'] 51 | params['post_data'][i] = params[i] if params[i]? 52 | @parent._request params 53 | 54 | _unsubscribe: (params) -> 55 | params['method'] = "DELETE" 56 | params['path'] = "/#{@parent._api_version}/subscriptions/" 57 | if params['id']? 58 | params['path'] += "?id=#{params['id']}" 59 | else 60 | params['path'] += "?object=#{params['object']}" 61 | params['path'] += "&client_secret=#{@parent._config.client_secret}&client_id=#{@parent._config.client_id}" 62 | @parent._request params 63 | 64 | ### 65 | Shared Public Methods 66 | ### 67 | 68 | subscribe: (params) -> 69 | @_subscribe params 70 | 71 | list: (params) -> 72 | params = @parent._clone(params) 73 | params['path'] = "/#{@parent._api_version}/subscriptions?client_secret=#{@parent._config.client_secret}&client_id=#{@parent._config.client_id}" 74 | @parent._request params 75 | 76 | unsubscribe: (params) -> 77 | @_unsubscribe params 78 | 79 | unsubscribe_all: (params) -> 80 | params['object'] = 'all' if params['object'] is undefined or not params['object']? 81 | @_unsubscribe params 82 | 83 | module.exports = InstagramSubscriptions 84 | -------------------------------------------------------------------------------- /test/instagram.oauth.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | /* 3 | Test Setup 4 | */ var Init, Instagram, app, assert, completed, http_client, should, test, to_do, url; 5 | console.log("\nInstagram API Node.js Lib Tests :: OAuth"); 6 | Init = require('./initialize'); 7 | Instagram = Init.Instagram; 8 | app = Init.app; 9 | assert = require('assert'); 10 | should = require('should'); 11 | test = require('./helpers'); 12 | http_client = require('http'); 13 | url = require('url'); 14 | completed = 0; 15 | to_do = 0; 16 | /* 17 | Tests 18 | */ 19 | module.exports = { 20 | 'oauth#roundtrip': function() { 21 | var options, request, result; 22 | console.log("\noauth#roundtrip"); 23 | result = Instagram.oauth.authorization_url({ 24 | scope: 'comments likes', 25 | display: 'touch' 26 | }); 27 | result.should.match(/\/oauth\/authorize\//); 28 | result.should.match(/client_id\=/); 29 | result.should.match(/redirect_uri\=/); 30 | result.should.match(/response_type\=code/); 31 | result.should.not.match(/client_id\=CLIENT\-ID/); 32 | result.should.not.match(/redirect_uri\=REDIRECT_URI/); 33 | test.output("result matched authorization url", result); 34 | result = url.parse(result); 35 | options = { 36 | host: process.env['TEST_HOST'] != null ? process.env['TEST_HOST'] : result.hostname, 37 | port: process.env['TEST_PORT'] != null ? process.env['TEST_PORT'] : result.port, 38 | method: "GET", 39 | path: "/fake" + result['pathname'] + result['search'] 40 | }; 41 | request = http_client.request(options, function(response) { 42 | var token_options, token_request, token_uri; 43 | response.should.have.property('statusCode', 302); 44 | response.should.have.property('headers'); 45 | response.headers.should.have.property('location'); 46 | test.output("response met redirect criteria", response.headers.location); 47 | token_uri = url.parse(response.headers.location); 48 | token_options = { 49 | host: token_uri['hostname'], 50 | port: typeof token_uri['port'] !== 'undefined' ? token_uri['port'] : null, 51 | method: "GET", 52 | path: "" + token_uri['pathname'] + token_uri['search'] 53 | }; 54 | token_request = http_client.request(token_options, function(token_response) { 55 | var data; 56 | data = ''; 57 | token_response.on('data', function(chunk) { 58 | return data += chunk; 59 | }); 60 | return token_response.on('end', function() { 61 | test.output("final receipt", data); 62 | return app.finish_test(); 63 | }); 64 | }); 65 | token_request.addListener('error', function(connectionException) { 66 | if (connectionException.code !== 'ENOTCONN') { 67 | console.log("\n" + connectionException); 68 | throw connectionException; 69 | return app.finish_test(); 70 | } 71 | }); 72 | return token_request.end(); 73 | }); 74 | request.addListener('error', function(connectionException) { 75 | if (connectionException.code !== 'ENOTCONN') { 76 | console.log("\n" + connectionException); 77 | throw connectionException; 78 | return app.finish_test(); 79 | } 80 | }); 81 | return request.end(); 82 | } 83 | }; 84 | app.start_tests(module.exports); 85 | }).call(this); 86 | -------------------------------------------------------------------------------- /lib/class.instagram.users.coffee: -------------------------------------------------------------------------------- 1 | 2 | class InstagramUsers 3 | constructor: (parent) -> 4 | @parent = parent 5 | 6 | ### 7 | User Basics 8 | ### 9 | 10 | info: (params) -> 11 | credentials = @parent._credentials {} 12 | params['path'] = "/#{@parent._api_version}/users/#{params['user_id']}?#{@parent._to_querystring(credentials)}" 13 | @parent._request params 14 | 15 | search: (params) -> 16 | params = @parent._credentials params 17 | params['path'] = "/#{@parent._api_version}/users/search?#{@parent._to_querystring(params)}" 18 | @parent._request params 19 | 20 | ### 21 | Media 22 | ### 23 | 24 | self: (params) -> 25 | params = @parent._credentials params, 'access_token' 26 | params['path'] = "/#{@parent._api_version}/users/self/feed?#{@parent._to_querystring(params)}" 27 | @parent._request params 28 | 29 | liked_by_self: (params) -> 30 | params = @parent._credentials params, 'access_token' 31 | params['path'] = "/#{@parent._api_version}/users/self/media/liked?#{@parent._to_querystring(params)}" 32 | @parent._request params 33 | 34 | recent: (params) -> 35 | params = @parent._credentials params, 'access_token' 36 | params['path'] = "/#{@parent._api_version}/users/#{params['user_id']}/media/recent?#{@parent._to_querystring(params)}" 37 | @parent._request params 38 | 39 | ### 40 | Relationships 41 | ### 42 | 43 | follows: (params) -> 44 | credentials = @parent._credentials {}, 'access_token' 45 | params['path'] = "/#{@parent._api_version}/users/#{params['user_id']}/follows?#{@parent._to_querystring(credentials)}" 46 | @parent._request params 47 | 48 | followed_by: (params) -> 49 | credentials = @parent._credentials {}, 'access_token' 50 | params['path'] = "/#{@parent._api_version}/users/#{params['user_id']}/followed-by?#{@parent._to_querystring(credentials)}" 51 | @parent._request params 52 | 53 | requested_by: (params) -> 54 | credentials = @parent._credentials {}, 'access_token' 55 | params['path'] = "/#{@parent._api_version}/users/self/requested-by?#{@parent._to_querystring(credentials)}" 56 | @parent._request params 57 | 58 | relationship: (params) -> 59 | credentials = @parent._credentials {}, 'access_token' 60 | if params['action']? 61 | params['method'] = 'POST' 62 | params['post_data'] = 63 | access_token: credentials['access_token'] 64 | action: params['action'] 65 | params['path'] = "/#{@parent._api_version}/users/#{params['user_id']}/relationship" 66 | else 67 | params['path'] = "/#{@parent._api_version}/users/#{params['user_id']}/relationship?#{@parent._to_querystring(credentials)}" 68 | @parent._request params 69 | 70 | follow: (params) -> 71 | params['action'] = 'follow' 72 | @relationship params 73 | 74 | unfollow: (params) -> 75 | params['action'] = 'unfollow' 76 | @relationship params 77 | 78 | block: (params) -> 79 | params['action'] = 'block' 80 | @relationship params 81 | 82 | unblock: (params) -> 83 | params['action'] = 'unblock' 84 | @relationship params 85 | 86 | approve: (params) -> 87 | params['action'] = 'approve' 88 | @relationship params 89 | 90 | ignore: (params) -> 91 | params['action'] = 'ignore' 92 | @relationship params 93 | 94 | ### 95 | Subscriptions 96 | ### 97 | 98 | subscribe: (params) -> 99 | params['object'] = 'user' 100 | @parent.subscriptions._subscribe params 101 | 102 | unsubscribe_all: (params) -> 103 | params['object'] = 'user' 104 | @parent.subscriptions._unsubscribe params 105 | 106 | module.exports = InstagramUsers 107 | -------------------------------------------------------------------------------- /test/initialize.coffee: -------------------------------------------------------------------------------- 1 | 2 | ### 3 | Initialize Instagram 4 | ### 5 | 6 | Instagram = require '../lib/class.instagram' 7 | 8 | ### 9 | Setup App for Subscription Callbacks 10 | ### 11 | 12 | url = require 'url' 13 | CALLBACK_URL = if process.env['CALLBACK_URL']? then process.env['CALLBACK_URL'] else "http://your.callback/url" 14 | callback = url.parse CALLBACK_URL 15 | 16 | if callback? 17 | HOST = callback['hostname'] 18 | PORT = if typeof callback['port'] isnt 'undefined' then callback['port'] else null 19 | PATH = callback['pathname'] 20 | 21 | express = require 'express' 22 | app = express.createServer() 23 | 24 | app.configure -> 25 | app.set 'host', HOST 26 | app.use app.router 27 | 28 | app.configure 'development', -> 29 | app.set 'port', PORT 30 | app.use express.logger() 31 | app.use express.errorHandler { dumpExceptions: true, showStack: true } 32 | 33 | app.get PATH, 34 | (request, response) -> 35 | Instagram.subscriptions.handshake request, response 36 | 37 | app.get '/fake/oauth/authorize', 38 | (request, response) -> 39 | params = url.parse(request.url, true).query 40 | response.writeHead 302, {'Location': "#{params.redirect_uri}?code=some-test-code"} 41 | response.end() 42 | 43 | app.post '/fake/oauth/access_token', 44 | (request, response) -> 45 | querystring = require 'querystring' 46 | util = require 'util' 47 | should = require 'should' 48 | fake_response = 49 | access_token: 'fb2e77d.47a0479900504cb3ab4a1f626d174d2d' 50 | user: 51 | id: 1574083 52 | username: 'snoopdogg' 53 | full_name: 'Snoop Dogg' 54 | profile_picture: 'http://distillery.s3.amazonaws.com/profiles/profile_1574083_75sq_1295469061.jpg' 55 | data = '' 56 | request.on 'data', (chunk) -> 57 | data += chunk 58 | request.on 'end', -> 59 | parsed = querystring.parse data 60 | parsed.should.have.property 'client_id' 61 | parsed.client_id.should.have.property 'length', 32 62 | parsed.should.have.property 'client_secret' 63 | parsed.client_secret.should.have.property 'length', 32 64 | parsed.should.have.property 'grant_type', 'authorization_code' 65 | parsed.should.have.property 'redirect_uri', Instagram._config.redirect_uri 66 | parsed.should.have.property 'code', 'some-test-code' 67 | console.log " access_token request data met assertions at /fake/oauth/access_token" 68 | response.writeHead 200, {'Content-Type': 'application/json'} 69 | response.end(JSON.stringify(fake_response)) 70 | 71 | app.get '/oauth', 72 | (request, response) -> 73 | Instagram.oauth.ask_for_access_token { 74 | request: request, 75 | response: response, 76 | complete: (access, response) -> 77 | access.should.have.property 'access_token', 'fb2e77d.47a0479900504cb3ab4a1f626d174d2d' 78 | console.log " the fake access token was received", JSON.stringify(access) 79 | response.writeHead 200, {'Content-Type': 'text/plain'} 80 | response.end('Successful End-Of-Chain\n') 81 | error: (e, data, caller, response) -> 82 | Instagram._error e, data, caller 83 | response.writeHead 406, {'Content-Type': 'text/plain'} 84 | response.end('Failure End-Of-Chain\n') 85 | } 86 | return null 87 | 88 | app.listen PORT 89 | 90 | ### 91 | Add-on App Test Monitoring 92 | ### 93 | 94 | app._tests_to_do = 0 95 | app._tests_completed = 0 96 | app._max_execution_time = 10 97 | 98 | app.start_tests = (tests) -> 99 | for i of tests 100 | app._tests_to_do += 1 101 | iterations = 0 102 | monitor = setInterval `function(){if(app.fd==null){clearInterval(monitor);}else if((app._tests_completed==app._tests_to_do&&app._tests_completed!=0)||iterations>app._max_execution_time){clearInterval(monitor);app.close();}else{iterations+=1;}}`, 1000 103 | 104 | app.finish_test = -> 105 | app._tests_completed += 1 106 | 107 | ### 108 | Exports 109 | ### 110 | 111 | module.exports = 112 | host: HOST 113 | port: PORT 114 | app: app 115 | Instagram: Instagram 116 | -------------------------------------------------------------------------------- /lib/class.instagram.media.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var InstagramMedia; 3 | InstagramMedia = (function() { 4 | function InstagramMedia(parent) { 5 | this.parent = parent; 6 | } 7 | /* 8 | Basic Media 9 | */ 10 | InstagramMedia.prototype.popular = function(params) { 11 | var credentials; 12 | credentials = this.parent._credentials({}); 13 | params['path'] = "/" + this.parent._api_version + "/media/popular?" + (this.parent._to_querystring(credentials)); 14 | return this.parent._request(params); 15 | }; 16 | InstagramMedia.prototype.info = function(params) { 17 | var credentials; 18 | credentials = this.parent._credentials({}); 19 | params['path'] = "/" + this.parent._api_version + "/media/" + params['media_id'] + "?" + (this.parent._to_querystring(credentials)); 20 | return this.parent._request(params); 21 | }; 22 | InstagramMedia.prototype.search = function(params) { 23 | params = this.parent._credentials(params); 24 | params['path'] = "/" + this.parent._api_version + "/media/search?" + (this.parent._to_querystring(params)); 25 | return this.parent._request(params); 26 | }; 27 | /* 28 | Likes 29 | */ 30 | InstagramMedia.prototype.likes = function(params) { 31 | var credentials; 32 | credentials = this.parent._credentials({}); 33 | params['path'] = "/" + this.parent._api_version + "/media/" + params['media_id'] + "/likes?" + (this.parent._to_querystring(credentials)); 34 | return this.parent._request(params); 35 | }; 36 | InstagramMedia.prototype.like = function(params) { 37 | params['post_data'] = this.parent._credentials({}, 'access_token'); 38 | params['method'] = 'POST'; 39 | params['path'] = "/" + this.parent._api_version + "/media/" + params['media_id'] + "/likes"; 40 | return this.parent._request(params); 41 | }; 42 | InstagramMedia.prototype.unlike = function(params) { 43 | params = this.parent._credentials(params, 'access_token'); 44 | params['method'] = 'DELETE'; 45 | params['path'] = "/" + this.parent._api_version + "/media/" + params['media_id'] + "/likes?" + (this.parent._to_querystring(params)); 46 | return this.parent._request(params); 47 | }; 48 | /* 49 | Comments 50 | */ 51 | InstagramMedia.prototype.comments = function(params) { 52 | var credentials; 53 | credentials = this.parent._credentials({}); 54 | params['path'] = "/" + this.parent._api_version + "/media/" + params['media_id'] + "/comments?" + (this.parent._to_querystring(credentials)); 55 | return this.parent._request(params); 56 | }; 57 | InstagramMedia.prototype.comment = function(params) { 58 | params['post_data'] = this.parent._credentials({ 59 | text: params['text'] 60 | }, 'access_token'); 61 | params['method'] = 'POST'; 62 | params['path'] = "/" + this.parent._api_version + "/media/" + params['media_id'] + "/comments"; 63 | return this.parent._request(params); 64 | }; 65 | InstagramMedia.prototype.uncomment = function(params) { 66 | var credentials; 67 | credentials = this.parent._credentials({}, 'access_token'); 68 | params['method'] = 'DELETE'; 69 | params['path'] = "/" + this.parent._api_version + "/media/" + params['media_id'] + "/comments/" + params['comment_id'] + "?" + (this.parent._to_querystring(credentials)); 70 | return this.parent._request(params); 71 | }; 72 | /* 73 | Subscriptions 74 | */ 75 | InstagramMedia.prototype.subscribe = function(params) { 76 | params['object'] = 'geography'; 77 | return this.parent.subscriptions._subscribe(params); 78 | }; 79 | InstagramMedia.prototype.unsubscribe = function(params) { 80 | return this.parent.subscriptions._unsubscribe(params); 81 | }; 82 | InstagramMedia.prototype.unsubscribe_all = function(params) { 83 | params['object'] = 'geography'; 84 | return this.parent.subscriptions._unsubscribe(params); 85 | }; 86 | return InstagramMedia; 87 | })(); 88 | module.exports = InstagramMedia; 89 | }).call(this); 90 | -------------------------------------------------------------------------------- /test/instagram.tags.coffee: -------------------------------------------------------------------------------- 1 | 2 | ### 3 | Test Setup 4 | ### 5 | 6 | console.log "\nInstagram API Node.js Lib Tests :: Tags" 7 | 8 | Init = require './initialize' 9 | Instagram = Init.Instagram 10 | app = Init.app 11 | 12 | assert = require 'assert' 13 | should = require 'should' 14 | test = require './helpers' 15 | 16 | completed = 0 17 | to_do = 0 18 | 19 | ### 20 | Tests 21 | ### 22 | 23 | module.exports = 24 | 'tags#info for blue': -> 25 | test.helper 'tags#info for blue', Instagram, 'tags', 'info', { name: 'blue' }, (data) -> 26 | data.should.have.property 'name', 'blue' 27 | test.output "data had the property 'name' equal to 'blue'" 28 | data.media_count.should.be.above 0 29 | test.output "data had the property 'media_count' greater than zero", data.media_count 30 | app.finish_test() 31 | 'tags#recent for blue': -> 32 | test.helper 'tags#recent for blue', Instagram, 'tags', 'recent', { name: 'blue' }, (data, pagination) -> 33 | data.length.should.equal 20 34 | test.output "data had length equal to 20" 35 | data[0].should.have.property 'id' 36 | test.output "data[0] had the property 'id'", data[0] 37 | pagination.should.have.property 'next_url' 38 | test.output "pagination had the property 'next_url'", pagination.next_url 39 | pagination.should.have.property 'next_max_id' 40 | test.output "pagination had the property 'next_max_id'", pagination.next_max_id 41 | pagination.should.have.property 'next_min_id' 42 | test.output "pagination had the property 'next_min_id'", pagination.next_min_id 43 | app.finish_test() 44 | 'tags#recent for blue with count of 50': -> 45 | test.helper 'tags#recent for blue with count of 50', Instagram, 'tags', 'recent', { name: 'blue', count: 50 }, (data, pagination) -> 46 | data.length.should.equal 50 47 | test.output "data had length equal to 50" 48 | app.finish_test() 49 | 'tags#search for blue': -> 50 | test.helper 'tags#search for blue', Instagram, 'tags', 'search', { q: 'blue' }, (data) -> 51 | data.length.should.be.above 0 52 | test.output "data had length greater than 0", data.length 53 | data[0].should.have.property 'name', 'blue' 54 | test.output "data[0] had the property 'name' equal to 'blue'" 55 | data[0].media_count.should.be.above 0 56 | test.output "data[0] had the property 'media_count' greater than zero", data[0].media_count 57 | app.finish_test() 58 | 'tags#subscription for blue': -> 59 | test.helper "tags#subscriptions subscribe to 'blue'", Instagram, 'tags', 'subscribe', { object_id: 'blue' }, (data) -> 60 | data.should.have.property 'id' 61 | test.output "data had the property 'id'" 62 | data.id.should.be.above 0 63 | test.output "data.id was greater than 0", data.id 64 | data.should.have.property 'type', 'subscription' 65 | test.output "data had the property 'type' equal to 'subscription'", data 66 | subscription_id = data.id 67 | test.helper 'tags#subscriptions list', Instagram, 'subscriptions', 'list', {}, (data) -> 68 | data.length.should.be.above 0 69 | test.output "data had length greater than 0", data.length 70 | found = false 71 | for i of data 72 | found = true if data[i].id is subscription_id 73 | throw "subscription not found" if !found 74 | test.output "data had the subscription #{subscription_id}" 75 | test.helper "tags#subscriptions unsubscribe from 'blue'", Instagram, 'tags', 'unsubscribe', { id: subscription_id }, (data) -> 76 | throw "tag 'blue' unsubscribe failed" if data isnt null 77 | test.output "data was null; we unsubscribed from the subscription #{subscription_id}" 78 | app.finish_test() 79 | 80 | ### 81 | seems to max out at 49 rather than 100 (ruby api docs) otherwise works perfectly 82 | 83 | 'tags#search for sex with count 200': -> 84 | test.helper 'tags#search for sex with count 200', Instagram, 'tags', 'search', { q: 'sex', count: 200 }, (data) -> 85 | data.length.should.equal 200 86 | test.output "data had length equal to 200", data.length 87 | app.finish_test() 88 | ### 89 | 90 | app.start_tests module.exports 91 | -------------------------------------------------------------------------------- /test/instagram.subscriptions.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | /* 3 | Test Setup 4 | */ var Init, Instagram, app, assert, completed, should, test, to_do; 5 | var __indexOf = Array.prototype.indexOf || function(item) { 6 | for (var i = 0, l = this.length; i < l; i++) { 7 | if (this[i] === item) return i; 8 | } 9 | return -1; 10 | }; 11 | console.log("\nInstagram API Node.js Lib Tests :: Subscriptions"); 12 | Init = require('./initialize'); 13 | Instagram = Init.Instagram; 14 | app = Init.app; 15 | assert = require('assert'); 16 | should = require('should'); 17 | test = require('./helpers'); 18 | completed = 0; 19 | to_do = 0; 20 | /* 21 | Tests 22 | */ 23 | module.exports = { 24 | 'multi#subscriptions': function() { 25 | var subscriptions; 26 | subscriptions = []; 27 | return test.helper("subscriptions subscribe to tag 'red'", Instagram, 'subscriptions', 'subscribe', { 28 | object: 'tag', 29 | object_id: 'red' 30 | }, function(data) { 31 | data.should.have.property('id'); 32 | test.output("data had the property 'id'"); 33 | data.id.should.be.above(0); 34 | test.output("data.id was greater than 0", data.id); 35 | data.should.have.property('type', 'subscription'); 36 | test.output("data had the property 'type' equal to 'subscription'", data); 37 | subscriptions[subscriptions.length] = data.id; 38 | return test.helper("subscriptions subscribe to location '1257285'", Instagram, 'subscriptions', 'subscribe', { 39 | object: 'location', 40 | object_id: '1257285' 41 | }, function(data) { 42 | data.should.have.property('id'); 43 | test.output("data had the property 'id'"); 44 | data.id.should.be.above(0); 45 | test.output("data.id was greater than 0", data.id); 46 | data.should.have.property('type', 'subscription'); 47 | test.output("data had the property 'type' equal to 'subscription'", data); 48 | subscriptions[subscriptions.length] = data.id; 49 | return test.helper("subscriptions subscribe to media near Eiffel Tower", Instagram, 'subscriptions', 'subscribe', { 50 | object: 'geography', 51 | lat: 48.858844300000001, 52 | lng: 2.2943506, 53 | radius: 1000 54 | }, function(data) { 55 | data.should.have.property('id'); 56 | test.output("data had the property 'id'"); 57 | data.id.should.be.above(0); 58 | test.output("data.id was greater than 0", data.id); 59 | data.should.have.property('type', 'subscription'); 60 | test.output("data had the property 'type' equal to 'subscription'", data); 61 | subscriptions[subscriptions.length] = data.id; 62 | return test.helper('subscriptions list', Instagram, 'subscriptions', 'list', {}, function(data) { 63 | var found, i, subscriptions_list, _ref; 64 | data.length.should.be.above(0); 65 | test.output("data had length greater than 0", data.length); 66 | subscriptions_list = []; 67 | for (i in data) { 68 | subscriptions_list[subscriptions_list.length] = data[i].id; 69 | } 70 | found = true; 71 | for (i in subscriptions) { 72 | if (_ref = subscriptions[i], __indexOf.call(subscriptions_list, _ref) < 0) { 73 | found = false; 74 | } 75 | } 76 | if (!found) { 77 | throw "subscription not found"; 78 | } 79 | test.output("data had the subscriptions " + (subscriptions.join(', '))); 80 | return test.helper("subscriptions unsubscribe_all", Instagram, 'subscriptions', 'unsubscribe_all', {}, function(data) { 81 | if (data !== null) { 82 | throw "unsubscribe_all failed"; 83 | } 84 | test.output("data was null; we unsubscribed from the subscriptions " + (subscriptions_list.join(', '))); 85 | return app.finish_test(); 86 | }); 87 | }); 88 | }); 89 | }); 90 | }); 91 | } 92 | }; 93 | app.start_tests(module.exports); 94 | }).call(this); 95 | -------------------------------------------------------------------------------- /test/instagram.locations.coffee: -------------------------------------------------------------------------------- 1 | 2 | ### 3 | Test Setup 4 | ### 5 | 6 | console.log "\nInstagram API Node.js Lib Tests :: Locations" 7 | 8 | Init = require './initialize' 9 | Instagram = Init.Instagram 10 | app = Init.app 11 | 12 | assert = require 'assert' 13 | should = require 'should' 14 | test = require './helpers' 15 | 16 | completed = 0 17 | to_do = 0 18 | 19 | ### 20 | Tests 21 | ### 22 | 23 | module.exports = 24 | 'locations#info for id#1': -> 25 | test.helper 'locations#info for id#1', Instagram, 'locations', 'info', { location_id: 1 }, (data) -> 26 | data.should.have.property 'name', 'Dogpatch Labs' 27 | test.output "data had the property 'name' equal to 'Dogpatch Labs'" 28 | data.latitude.should.be.above 0 29 | test.output "data had the property 'latitude' greater than zero", data.latitude 30 | data.longitude.should.be.below 0 31 | test.output "data had the property 'longitude' less than zero", data.longitude 32 | app.finish_test() 33 | 'locations#recent for id#1': -> 34 | test.helper 'locations#recent for id#1', Instagram, 'locations', 'recent', { location_id: 1 }, (data, pagination) -> 35 | data.length.should.be.above 0 36 | test.output "data had length greater than 0", data.length 37 | data[0].should.have.property 'id' 38 | test.output "data[0] had the property 'id'", data[0].id 39 | pagination.should.have.property 'next_url' 40 | test.output "pagination had the property 'next_url'", pagination.next_url 41 | pagination.should.have.property 'next_max_id' or pagination.should.have.property 'next_min_id' 42 | test.output "pagination had the property 'next_max_id' or 'next_min_id'", pagination 43 | app.finish_test() 44 | 'locations#search for 48.858844300000001/2.2943506': -> 45 | test.helper 'locations#search for 48.858844300000001/2.2943506', Instagram, 'locations', 'search', { lat: 48.858844300000001, lng: 2.2943506 }, (data) -> 46 | data.length.should.be.above 0 47 | test.output "data had length greater than 0", data.length 48 | data[0].should.have.property 'id' 49 | test.output "data[0] had the property 'id'", data[0].id 50 | data[0].should.have.property 'name' 51 | test.output "data[0] had the property 'name'", data[0].name 52 | app.finish_test() 53 | 'locations#search for 40.77/-73.98 with count 60': -> 54 | test.helper 'locations#search for 40.77/-73.98 with count 60', Instagram, 'locations', 'search', { lat: 40.77, lng: -73.98, distance: 5000, count: 60 }, (data) -> 55 | data.length.should.equal 60 56 | test.output "data had length of 60", data.length 57 | app.finish_test() 58 | 'locations#subscriptions': -> 59 | test.helper "locations#subscriptions subscribe to location '1257285'", Instagram, 'locations', 'subscribe', { object_id: '1257285' }, (data) -> 60 | data.should.have.property 'id' 61 | test.output "data had the property 'id'" 62 | data.id.should.be.above 0 63 | test.output "data.id was greater than 0", data.id 64 | data.should.have.property 'type', 'subscription' 65 | test.output "data had the property 'type' equal to 'subscription'", data 66 | subscription_id = data.id 67 | test.helper 'locations#subscriptions list', Instagram, 'subscriptions', 'list', {}, (data) -> 68 | data.length.should.be.above 0 69 | test.output "data had length greater than 0", data.length 70 | found = false 71 | for i of data 72 | found = true if data[i].id is subscription_id 73 | throw "subscription not found" if !found 74 | test.output "data had the subscription #{subscription_id}" 75 | test.helper "locations#subscriptions unsubscribe from location '1257285'", Instagram, 'locations', 'unsubscribe', { id: subscription_id }, (data) -> 76 | throw "location '1257285' unsubscribe failed" if data isnt null 77 | test.output "data was null; we unsubscribed from the subscription #{subscription_id}" 78 | app.finish_test() 79 | 80 | ### 81 | count seems to return n-1 results; e.g. request 50 returns 49, request 30 returns 29,... 82 | 83 | 'locations#recent for id#1 with count of 50': -> 84 | test.helper 'locations#recent for id#1 with count of 50', Instagram, 'locations', 'recent', { location_id: 1, count: 50 }, (data, pagination) -> 85 | data.length.should.equal 49 86 | test.output "data had length of 49" 87 | app.finish_test() 88 | ### 89 | 90 | app.start_tests module.exports 91 | -------------------------------------------------------------------------------- /lib/class.instagram.subscriptions.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var InstagramSubscriptions, crypto, url; 3 | url = require('url'); 4 | crypto = require('crypto'); 5 | InstagramSubscriptions = (function() { 6 | function InstagramSubscriptions(parent) { 7 | this.parent = parent; 8 | } 9 | /* 10 | Verification Methods 11 | */ 12 | InstagramSubscriptions.prototype.handshake = function(request, response, complete) { 13 | var body, headers, parsedRequest; 14 | parsedRequest = url.parse(request.url, true); 15 | if (parsedRequest['query']['hub.mode'] === 'subscribe' && (parsedRequest['query']['hub.challenge'] != null) && parsedRequest['query']['hub.challenge'].length > 0) { 16 | body = parsedRequest['query']['hub.challenge']; 17 | headers = { 18 | 'Content-Length': body.length, 19 | 'Content-Type': 'text/plain' 20 | }; 21 | response.writeHead(200, headers); 22 | response.write(body); 23 | if ((parsedRequest['query']['hub.verify_token'] != null) && (complete != null)) { 24 | complete(parsedRequest['query']['hub.verify_token']); 25 | } 26 | } else { 27 | response.writeHead(400); 28 | } 29 | return response.end(); 30 | }; 31 | InstagramSubscriptions.prototype.verified = function(request) { 32 | var calculated_signature, encoding, hmac; 33 | if (request.rawBody === null || request.headers['x-hub-signature'] === void 0 || request.headers['x-hub-signature'] === null) { 34 | return false; 35 | } 36 | hmac = crypto.createHmac('sha1', this.parent._config.client_secret); 37 | hmac.update(request.rawBody); 38 | calculated_signature = hmac.digest(encoding = 'hex'); 39 | if (calculated_signature !== request.headers['x-hub-signature']) { 40 | return false; 41 | } 42 | return true; 43 | }; 44 | /* 45 | Shared Subscription Methods 46 | */ 47 | InstagramSubscriptions.prototype._subscribe = function(params) { 48 | var i, _i, _len, _ref; 49 | params['method'] = "POST"; 50 | params['path'] = "/" + this.parent._api_version + "/subscriptions/"; 51 | if ((typeof params['callback_url'] === 'undefined' || params['callback_url'] === null) && this.parent._config.callback_url !== null) { 52 | params['callback_url'] = this.parent._config.callback_url; 53 | } 54 | params['post_data'] = { 55 | object: params['object'], 56 | aspect: 'media', 57 | client_id: this.parent._config.client_id, 58 | client_secret: this.parent._config.client_secret, 59 | callback_url: params['callback_url'] 60 | }; 61 | _ref = ['object_id', 'verify_token', 'lat', 'lng', 'radius']; 62 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 63 | i = _ref[_i]; 64 | if (params[i] != null) { 65 | params['post_data'][i] = params[i]; 66 | } 67 | } 68 | return this.parent._request(params); 69 | }; 70 | InstagramSubscriptions.prototype._unsubscribe = function(params) { 71 | params['method'] = "DELETE"; 72 | params['path'] = "/" + this.parent._api_version + "/subscriptions/"; 73 | if (params['id'] != null) { 74 | params['path'] += "?id=" + params['id']; 75 | } else { 76 | params['path'] += "?object=" + params['object']; 77 | } 78 | params['path'] += "&client_secret=" + this.parent._config.client_secret + "&client_id=" + this.parent._config.client_id; 79 | return this.parent._request(params); 80 | }; 81 | /* 82 | Shared Public Methods 83 | */ 84 | InstagramSubscriptions.prototype.subscribe = function(params) { 85 | return this._subscribe(params); 86 | }; 87 | InstagramSubscriptions.prototype.list = function(params) { 88 | params = this.parent._clone(params); 89 | params['path'] = "/" + this.parent._api_version + "/subscriptions?client_secret=" + this.parent._config.client_secret + "&client_id=" + this.parent._config.client_id; 90 | return this.parent._request(params); 91 | }; 92 | InstagramSubscriptions.prototype.unsubscribe = function(params) { 93 | return this._unsubscribe(params); 94 | }; 95 | InstagramSubscriptions.prototype.unsubscribe_all = function(params) { 96 | if (params['object'] === void 0 || !(params['object'] != null)) { 97 | params['object'] = 'all'; 98 | } 99 | return this._unsubscribe(params); 100 | }; 101 | return InstagramSubscriptions; 102 | })(); 103 | module.exports = InstagramSubscriptions; 104 | }).call(this); 105 | -------------------------------------------------------------------------------- /test/initialize.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | /* 3 | Initialize Instagram 4 | */ var CALLBACK_URL, HOST, Instagram, PATH, PORT, app, callback, express, url; 5 | Instagram = require('../lib/class.instagram'); 6 | /* 7 | Setup App for Subscription Callbacks 8 | */ 9 | url = require('url'); 10 | CALLBACK_URL = process.env['CALLBACK_URL'] != null ? process.env['CALLBACK_URL'] : "http://your.callback/url"; 11 | callback = url.parse(CALLBACK_URL); 12 | if (callback != null) { 13 | HOST = callback['hostname']; 14 | PORT = typeof callback['port'] !== 'undefined' ? callback['port'] : null; 15 | PATH = callback['pathname']; 16 | } 17 | express = require('express'); 18 | app = express.createServer(); 19 | app.configure(function() { 20 | app.set('host', HOST); 21 | return app.use(app.router); 22 | }); 23 | app.configure('development', function() { 24 | app.set('port', PORT); 25 | app.use(express.logger()); 26 | return app.use(express.errorHandler({ 27 | dumpExceptions: true, 28 | showStack: true 29 | })); 30 | }); 31 | app.get(PATH, function(request, response) { 32 | return Instagram.subscriptions.handshake(request, response); 33 | }); 34 | app.get('/fake/oauth/authorize', function(request, response) { 35 | var params; 36 | params = url.parse(request.url, true).query; 37 | response.writeHead(302, { 38 | 'Location': "" + params.redirect_uri + "?code=some-test-code" 39 | }); 40 | return response.end(); 41 | }); 42 | app.post('/fake/oauth/access_token', function(request, response) { 43 | var data, fake_response, querystring, should, util; 44 | querystring = require('querystring'); 45 | util = require('util'); 46 | should = require('should'); 47 | fake_response = { 48 | access_token: 'fb2e77d.47a0479900504cb3ab4a1f626d174d2d', 49 | user: { 50 | id: 1574083, 51 | username: 'snoopdogg', 52 | full_name: 'Snoop Dogg', 53 | profile_picture: 'http://distillery.s3.amazonaws.com/profiles/profile_1574083_75sq_1295469061.jpg' 54 | } 55 | }; 56 | data = ''; 57 | request.on('data', function(chunk) { 58 | return data += chunk; 59 | }); 60 | return request.on('end', function() { 61 | var parsed; 62 | parsed = querystring.parse(data); 63 | parsed.should.have.property('client_id'); 64 | parsed.client_id.should.have.property('length', 32); 65 | parsed.should.have.property('client_secret'); 66 | parsed.client_secret.should.have.property('length', 32); 67 | parsed.should.have.property('grant_type', 'authorization_code'); 68 | parsed.should.have.property('redirect_uri', Instagram._config.redirect_uri); 69 | parsed.should.have.property('code', 'some-test-code'); 70 | console.log(" access_token request data met assertions at /fake/oauth/access_token"); 71 | response.writeHead(200, { 72 | 'Content-Type': 'application/json' 73 | }); 74 | return response.end(JSON.stringify(fake_response)); 75 | }); 76 | }); 77 | app.get('/oauth', function(request, response) { 78 | Instagram.oauth.ask_for_access_token({ 79 | request: request, 80 | response: response, 81 | complete: function(access, response) { 82 | access.should.have.property('access_token', 'fb2e77d.47a0479900504cb3ab4a1f626d174d2d'); 83 | console.log(" the fake access token was received", JSON.stringify(access)); 84 | response.writeHead(200, { 85 | 'Content-Type': 'text/plain' 86 | }); 87 | return response.end('Successful End-Of-Chain\n'); 88 | }, 89 | error: function(e, data, caller, response) { 90 | Instagram._error(e, data, caller); 91 | response.writeHead(406, { 92 | 'Content-Type': 'text/plain' 93 | }); 94 | return response.end('Failure End-Of-Chain\n'); 95 | } 96 | }); 97 | return null; 98 | }); 99 | app.listen(PORT); 100 | /* 101 | Add-on App Test Monitoring 102 | */ 103 | app._tests_to_do = 0; 104 | app._tests_completed = 0; 105 | app._max_execution_time = 10; 106 | app.start_tests = function(tests) { 107 | var i, iterations, monitor; 108 | for (i in tests) { 109 | app._tests_to_do += 1; 110 | } 111 | iterations = 0; 112 | return monitor = setInterval(function(){if(app.fd==null){clearInterval(monitor);}else if((app._tests_completed==app._tests_to_do&&app._tests_completed!=0)||iterations>app._max_execution_time){clearInterval(monitor);app.close();}else{iterations+=1;}}, 1000); 113 | }; 114 | app.finish_test = function() { 115 | return app._tests_completed += 1; 116 | }; 117 | /* 118 | Exports 119 | */ 120 | module.exports = { 121 | host: HOST, 122 | port: PORT, 123 | app: app, 124 | Instagram: Instagram 125 | }; 126 | }).call(this); 127 | -------------------------------------------------------------------------------- /lib/class.instagram.users.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var InstagramUsers; 3 | InstagramUsers = (function() { 4 | function InstagramUsers(parent) { 5 | this.parent = parent; 6 | } 7 | /* 8 | User Basics 9 | */ 10 | InstagramUsers.prototype.info = function(params) { 11 | var credentials; 12 | credentials = this.parent._credentials({}); 13 | params['path'] = "/" + this.parent._api_version + "/users/" + params['user_id'] + "?" + (this.parent._to_querystring(credentials)); 14 | return this.parent._request(params); 15 | }; 16 | InstagramUsers.prototype.search = function(params) { 17 | params = this.parent._credentials(params); 18 | params['path'] = "/" + this.parent._api_version + "/users/search?" + (this.parent._to_querystring(params)); 19 | return this.parent._request(params); 20 | }; 21 | /* 22 | Media 23 | */ 24 | InstagramUsers.prototype.self = function(params) { 25 | params = this.parent._credentials(params, 'access_token'); 26 | params['path'] = "/" + this.parent._api_version + "/users/self/feed?" + (this.parent._to_querystring(params)); 27 | return this.parent._request(params); 28 | }; 29 | InstagramUsers.prototype.liked_by_self = function(params) { 30 | params = this.parent._credentials(params, 'access_token'); 31 | params['path'] = "/" + this.parent._api_version + "/users/self/media/liked?" + (this.parent._to_querystring(params)); 32 | return this.parent._request(params); 33 | }; 34 | InstagramUsers.prototype.recent = function(params) { 35 | params = this.parent._credentials(params, 'access_token'); 36 | params['path'] = "/" + this.parent._api_version + "/users/" + params['user_id'] + "/media/recent?" + (this.parent._to_querystring(params)); 37 | return this.parent._request(params); 38 | }; 39 | /* 40 | Relationships 41 | */ 42 | InstagramUsers.prototype.follows = function(params) { 43 | var credentials; 44 | credentials = this.parent._credentials({}, 'access_token'); 45 | params['path'] = "/" + this.parent._api_version + "/users/" + params['user_id'] + "/follows?" + (this.parent._to_querystring(credentials)); 46 | return this.parent._request(params); 47 | }; 48 | InstagramUsers.prototype.followed_by = function(params) { 49 | var credentials; 50 | credentials = this.parent._credentials({}, 'access_token'); 51 | params['path'] = "/" + this.parent._api_version + "/users/" + params['user_id'] + "/followed-by?" + (this.parent._to_querystring(credentials)); 52 | return this.parent._request(params); 53 | }; 54 | InstagramUsers.prototype.requested_by = function(params) { 55 | var credentials; 56 | credentials = this.parent._credentials({}, 'access_token'); 57 | params['path'] = "/" + this.parent._api_version + "/users/self/requested-by?" + (this.parent._to_querystring(credentials)); 58 | return this.parent._request(params); 59 | }; 60 | InstagramUsers.prototype.relationship = function(params) { 61 | var credentials; 62 | credentials = this.parent._credentials({}, 'access_token'); 63 | if (params['action'] != null) { 64 | params['method'] = 'POST'; 65 | params['post_data'] = { 66 | access_token: credentials['access_token'], 67 | action: params['action'] 68 | }; 69 | params['path'] = "/" + this.parent._api_version + "/users/" + params['user_id'] + "/relationship"; 70 | } else { 71 | params['path'] = "/" + this.parent._api_version + "/users/" + params['user_id'] + "/relationship?" + (this.parent._to_querystring(credentials)); 72 | } 73 | return this.parent._request(params); 74 | }; 75 | InstagramUsers.prototype.follow = function(params) { 76 | params['action'] = 'follow'; 77 | return this.relationship(params); 78 | }; 79 | InstagramUsers.prototype.unfollow = function(params) { 80 | params['action'] = 'unfollow'; 81 | return this.relationship(params); 82 | }; 83 | InstagramUsers.prototype.block = function(params) { 84 | params['action'] = 'block'; 85 | return this.relationship(params); 86 | }; 87 | InstagramUsers.prototype.unblock = function(params) { 88 | params['action'] = 'unblock'; 89 | return this.relationship(params); 90 | }; 91 | InstagramUsers.prototype.approve = function(params) { 92 | params['action'] = 'approve'; 93 | return this.relationship(params); 94 | }; 95 | InstagramUsers.prototype.ignore = function(params) { 96 | params['action'] = 'ignore'; 97 | return this.relationship(params); 98 | }; 99 | /* 100 | Subscriptions 101 | */ 102 | InstagramUsers.prototype.subscribe = function(params) { 103 | params['object'] = 'user'; 104 | return this.parent.subscriptions._subscribe(params); 105 | }; 106 | InstagramUsers.prototype.unsubscribe_all = function(params) { 107 | params['object'] = 'user'; 108 | return this.parent.subscriptions._unsubscribe(params); 109 | }; 110 | return InstagramUsers; 111 | })(); 112 | module.exports = InstagramUsers; 113 | }).call(this); 114 | -------------------------------------------------------------------------------- /test/instagram.tags.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | /* 3 | Test Setup 4 | */ var Init, Instagram, app, assert, completed, should, test, to_do; 5 | console.log("\nInstagram API Node.js Lib Tests :: Tags"); 6 | Init = require('./initialize'); 7 | Instagram = Init.Instagram; 8 | app = Init.app; 9 | assert = require('assert'); 10 | should = require('should'); 11 | test = require('./helpers'); 12 | completed = 0; 13 | to_do = 0; 14 | /* 15 | Tests 16 | */ 17 | module.exports = { 18 | 'tags#info for blue': function() { 19 | return test.helper('tags#info for blue', Instagram, 'tags', 'info', { 20 | name: 'blue' 21 | }, function(data) { 22 | data.should.have.property('name', 'blue'); 23 | test.output("data had the property 'name' equal to 'blue'"); 24 | data.media_count.should.be.above(0); 25 | test.output("data had the property 'media_count' greater than zero", data.media_count); 26 | return app.finish_test(); 27 | }); 28 | }, 29 | 'tags#recent for blue': function() { 30 | return test.helper('tags#recent for blue', Instagram, 'tags', 'recent', { 31 | name: 'blue' 32 | }, function(data, pagination) { 33 | data.length.should.equal(20); 34 | test.output("data had length equal to 20"); 35 | data[0].should.have.property('id'); 36 | test.output("data[0] had the property 'id'", data[0]); 37 | pagination.should.have.property('next_url'); 38 | test.output("pagination had the property 'next_url'", pagination.next_url); 39 | pagination.should.have.property('next_max_id'); 40 | test.output("pagination had the property 'next_max_id'", pagination.next_max_id); 41 | pagination.should.have.property('next_min_id'); 42 | test.output("pagination had the property 'next_min_id'", pagination.next_min_id); 43 | return app.finish_test(); 44 | }); 45 | }, 46 | 'tags#recent for blue with count of 50': function() { 47 | return test.helper('tags#recent for blue with count of 50', Instagram, 'tags', 'recent', { 48 | name: 'blue', 49 | count: 50 50 | }, function(data, pagination) { 51 | data.length.should.equal(50); 52 | test.output("data had length equal to 50"); 53 | return app.finish_test(); 54 | }); 55 | }, 56 | 'tags#search for blue': function() { 57 | return test.helper('tags#search for blue', Instagram, 'tags', 'search', { 58 | q: 'blue' 59 | }, function(data) { 60 | data.length.should.be.above(0); 61 | test.output("data had length greater than 0", data.length); 62 | data[0].should.have.property('name', 'blue'); 63 | test.output("data[0] had the property 'name' equal to 'blue'"); 64 | data[0].media_count.should.be.above(0); 65 | test.output("data[0] had the property 'media_count' greater than zero", data[0].media_count); 66 | return app.finish_test(); 67 | }); 68 | }, 69 | 'tags#subscription for blue': function() { 70 | return test.helper("tags#subscriptions subscribe to 'blue'", Instagram, 'tags', 'subscribe', { 71 | object_id: 'blue' 72 | }, function(data) { 73 | var subscription_id; 74 | data.should.have.property('id'); 75 | test.output("data had the property 'id'"); 76 | data.id.should.be.above(0); 77 | test.output("data.id was greater than 0", data.id); 78 | data.should.have.property('type', 'subscription'); 79 | test.output("data had the property 'type' equal to 'subscription'", data); 80 | subscription_id = data.id; 81 | return test.helper('tags#subscriptions list', Instagram, 'subscriptions', 'list', {}, function(data) { 82 | var found, i; 83 | data.length.should.be.above(0); 84 | test.output("data had length greater than 0", data.length); 85 | found = false; 86 | for (i in data) { 87 | if (data[i].id === subscription_id) { 88 | found = true; 89 | } 90 | } 91 | if (!found) { 92 | throw "subscription not found"; 93 | } 94 | test.output("data had the subscription " + subscription_id); 95 | return test.helper("tags#subscriptions unsubscribe from 'blue'", Instagram, 'tags', 'unsubscribe', { 96 | id: subscription_id 97 | }, function(data) { 98 | if (data !== null) { 99 | throw "tag 'blue' unsubscribe failed"; 100 | } 101 | test.output("data was null; we unsubscribed from the subscription " + subscription_id); 102 | return app.finish_test(); 103 | }); 104 | }); 105 | }); 106 | } 107 | }; 108 | /* 109 | seems to max out at 49 rather than 100 (ruby api docs) otherwise works perfectly 110 | 111 | 'tags#search for sex with count 200': -> 112 | test.helper 'tags#search for sex with count 200', Instagram, 'tags', 'search', { q: 'sex', count: 200 }, (data) -> 113 | data.length.should.equal 200 114 | test.output "data had length equal to 200", data.length 115 | app.finish_test() 116 | */ 117 | app.start_tests(module.exports); 118 | }).call(this); 119 | -------------------------------------------------------------------------------- /test/instagram.locations.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | /* 3 | Test Setup 4 | */ var Init, Instagram, app, assert, completed, should, test, to_do; 5 | console.log("\nInstagram API Node.js Lib Tests :: Locations"); 6 | Init = require('./initialize'); 7 | Instagram = Init.Instagram; 8 | app = Init.app; 9 | assert = require('assert'); 10 | should = require('should'); 11 | test = require('./helpers'); 12 | completed = 0; 13 | to_do = 0; 14 | /* 15 | Tests 16 | */ 17 | module.exports = { 18 | 'locations#info for id#1': function() { 19 | return test.helper('locations#info for id#1', Instagram, 'locations', 'info', { 20 | location_id: 1 21 | }, function(data) { 22 | data.should.have.property('name', 'Dogpatch Labs'); 23 | test.output("data had the property 'name' equal to 'Dogpatch Labs'"); 24 | data.latitude.should.be.above(0); 25 | test.output("data had the property 'latitude' greater than zero", data.latitude); 26 | data.longitude.should.be.below(0); 27 | test.output("data had the property 'longitude' less than zero", data.longitude); 28 | return app.finish_test(); 29 | }); 30 | }, 31 | 'locations#recent for id#1': function() { 32 | return test.helper('locations#recent for id#1', Instagram, 'locations', 'recent', { 33 | location_id: 1 34 | }, function(data, pagination) { 35 | data.length.should.be.above(0); 36 | test.output("data had length greater than 0", data.length); 37 | data[0].should.have.property('id'); 38 | test.output("data[0] had the property 'id'", data[0].id); 39 | pagination.should.have.property('next_url'); 40 | test.output("pagination had the property 'next_url'", pagination.next_url); 41 | pagination.should.have.property('next_max_id' || pagination.should.have.property('next_min_id')); 42 | test.output("pagination had the property 'next_max_id' or 'next_min_id'", pagination); 43 | return app.finish_test(); 44 | }); 45 | }, 46 | 'locations#search for 48.858844300000001/2.2943506': function() { 47 | return test.helper('locations#search for 48.858844300000001/2.2943506', Instagram, 'locations', 'search', { 48 | lat: 48.858844300000001, 49 | lng: 2.2943506 50 | }, function(data) { 51 | data.length.should.be.above(0); 52 | test.output("data had length greater than 0", data.length); 53 | data[0].should.have.property('id'); 54 | test.output("data[0] had the property 'id'", data[0].id); 55 | data[0].should.have.property('name'); 56 | test.output("data[0] had the property 'name'", data[0].name); 57 | return app.finish_test(); 58 | }); 59 | }, 60 | 'locations#search for 40.77/-73.98 with count 60': function() { 61 | return test.helper('locations#search for 40.77/-73.98 with count 60', Instagram, 'locations', 'search', { 62 | lat: 40.77, 63 | lng: -73.98, 64 | distance: 5000, 65 | count: 60 66 | }, function(data) { 67 | data.length.should.equal(60); 68 | test.output("data had length of 60", data.length); 69 | return app.finish_test(); 70 | }); 71 | }, 72 | 'locations#subscriptions': function() { 73 | return test.helper("locations#subscriptions subscribe to location '1257285'", Instagram, 'locations', 'subscribe', { 74 | object_id: '1257285' 75 | }, function(data) { 76 | var subscription_id; 77 | data.should.have.property('id'); 78 | test.output("data had the property 'id'"); 79 | data.id.should.be.above(0); 80 | test.output("data.id was greater than 0", data.id); 81 | data.should.have.property('type', 'subscription'); 82 | test.output("data had the property 'type' equal to 'subscription'", data); 83 | subscription_id = data.id; 84 | return test.helper('locations#subscriptions list', Instagram, 'subscriptions', 'list', {}, function(data) { 85 | var found, i; 86 | data.length.should.be.above(0); 87 | test.output("data had length greater than 0", data.length); 88 | found = false; 89 | for (i in data) { 90 | if (data[i].id === subscription_id) { 91 | found = true; 92 | } 93 | } 94 | if (!found) { 95 | throw "subscription not found"; 96 | } 97 | test.output("data had the subscription " + subscription_id); 98 | return test.helper("locations#subscriptions unsubscribe from location '1257285'", Instagram, 'locations', 'unsubscribe', { 99 | id: subscription_id 100 | }, function(data) { 101 | if (data !== null) { 102 | throw "location '1257285' unsubscribe failed"; 103 | } 104 | test.output("data was null; we unsubscribed from the subscription " + subscription_id); 105 | return app.finish_test(); 106 | }); 107 | }); 108 | }); 109 | } 110 | }; 111 | /* 112 | count seems to return n-1 results; e.g. request 50 returns 49, request 30 returns 29,... 113 | 114 | 'locations#recent for id#1 with count of 50': -> 115 | test.helper 'locations#recent for id#1 with count of 50', Instagram, 'locations', 'recent', { location_id: 1, count: 50 }, (data, pagination) -> 116 | data.length.should.equal 49 117 | test.output "data had length of 49" 118 | app.finish_test() 119 | */ 120 | app.start_tests(module.exports); 121 | }).call(this); 122 | -------------------------------------------------------------------------------- /test/instagram.media.coffee: -------------------------------------------------------------------------------- 1 | 2 | ### 3 | Test Setup 4 | ### 5 | 6 | console.log "\nInstagram API Node.js Lib Tests :: Media" 7 | 8 | Init = require './initialize' 9 | Instagram = Init.Instagram 10 | app = Init.app 11 | 12 | assert = require 'assert' 13 | should = require 'should' 14 | test = require './helpers' 15 | 16 | completed = 0 17 | to_do = 0 18 | 19 | ### 20 | Tests 21 | ### 22 | 23 | module.exports = 24 | 'media#popular': -> 25 | test.helper 'media#popular', Instagram, 'media', 'popular', {}, (data) -> 26 | data.length.should.equal 32 27 | test.output "data had length equal to 32" 28 | data[0].should.have.property 'id' 29 | test.output "data[0] had the property 'id'", data[0].id 30 | app.finish_test() 31 | 'media#info for id#3': -> 32 | test.helper 'media#info for id#3', Instagram, 'media', 'info', { media_id: 3 }, (data) -> 33 | data.should.have.property 'id', '3' 34 | test.output "data had the property 'id' equal to 3" 35 | data.should.have.property 'created_time', '1279315783' 36 | test.output "data had the property 'created_time' equal to 1279315783" 37 | app.finish_test() 38 | 'media#search for 48.858844300000001/2.2943506': -> 39 | test.helper 'media#search for 48.858844300000001/2.2943506', Instagram, 'media', 'search', { lat: 48.858844300000001, lng: 2.2943506 }, (data) -> 40 | data.length.should.be.above 0 41 | test.output "data had length greater than 0", data.length 42 | data[0].should.have.property 'id' 43 | test.output "data[0] had the property 'id'", data[0].id 44 | app.finish_test() 45 | 'media#like id#3': -> 46 | test.helper 'media#like id#3', Instagram, 'media', 'like', { media_id: 3 }, (data) -> 47 | throw "like failed" if data isnt null 48 | test.output "data was null; we liked media #3" 49 | test.helper 'media#likes for id#3', Instagram, 'media', 'likes', { media_id: 3 }, (data) -> 50 | data.length.should.be.above 0 51 | test.output "data had length greater than 0", data.length 52 | test.helper 'media#unlike id#3', Instagram, 'media', 'unlike', { media_id: 3 }, (data) -> 53 | throw "unlike failed" if data isnt null 54 | test.output "data was null; we unliked media #3" 55 | app.finish_test() 56 | 'media#comment id#53355234': -> 57 | test.helper 'media#comment id#53355234', Instagram, 'media', 'comment', { media_id: 53355234, text: 'Instagame was here.' }, (data) -> 58 | data.should.have.property 'id' 59 | test.output "data had the property 'id'", data.id 60 | data.should.have.property 'from' 61 | test.output "data had the property 'from'", data.from 62 | data.should.have.property 'created_time' 63 | test.output "data had the property 'created_time'", data.created_time 64 | data.should.have.property 'text' 65 | test.output "data had the property 'text'", data.text 66 | comment_id = data['id'] 67 | test.output "created comment #{comment_id}" 68 | test.helper 'media#comments for id#53355234', Instagram, 'media', 'comments', { media_id: 53355234 }, (data) -> 69 | data.length.should.be.above 0 70 | test.output "data had length greater than 0", data.length 71 | test.helper 'media#uncomment id#53355234', Instagram, 'media', 'uncomment', { media_id: 53355234, comment_id: comment_id }, (data) -> 72 | throw "uncomment failed" if data isnt null 73 | test.output "data was null; we deleted comment #{comment_id}" 74 | app.finish_test() 75 | 'media#subscriptions': -> 76 | test.helper "media#subscriptions subscribe to geography near Eiffel Tower", Instagram, 'media', 'subscribe', { lat: 48.858844300000001, lng: 2.2943506, radius: 1000 }, (data) -> 77 | data.should.have.property 'id' 78 | test.output "data had the property 'id'" 79 | data.id.should.be.above 0 80 | test.output "data.id was greater than 0", data.id 81 | data.should.have.property 'type', 'subscription' 82 | test.output "data had the property 'type' equal to 'subscription'", data 83 | subscription_id = data.id 84 | test.helper 'media#subscriptions list', Instagram, 'subscriptions', 'list', {}, (data) -> 85 | data.length.should.be.above 0 86 | test.output "data had length greater than 0", data.length 87 | found = false 88 | for i of data 89 | found = true if data[i].id is subscription_id 90 | throw "subscription not found" if !found 91 | test.output "data had the subscription #{subscription_id}" 92 | test.helper "media#subscriptions unsubscribe from media near Eiffel Tower", Instagram, 'media', 'unsubscribe', { id: subscription_id }, (data) -> 93 | throw "geography near Eiffel Tower unsubscribe failed" if data isnt null 94 | test.output "data was null; we unsubscribed from the subscription #{subscription_id}" 95 | app.finish_test() 96 | 97 | ### 98 | tested on Austin, Tx. { lat: 30.30, lng: -97.70, distance: 5000 }; weird, request with count 200 produces 54, request with count 50 produces 46, request with count 46 produces 42, request with count 42 produces 38... I think you see the pattern. :) 99 | 100 | 'media#search for 30.30/-97.70 with count of 42': -> 101 | test.helper 'media#search for 30.30/-97.70 with count of 42', Instagram, 'media', 'search', { lat: 30.30, lng: -97.70, distance: 5000, count: 42 }, (data) -> 102 | data.length.should.equal 42 103 | test.output "data had length equal to 42", data.length 104 | app.finish_test() 105 | ### 106 | 107 | app.start_tests module.exports 108 | -------------------------------------------------------------------------------- /lib/class.instagram.coffee: -------------------------------------------------------------------------------- 1 | 2 | querystring = require 'querystring' 3 | 4 | class InstagramAPI 5 | constructor: -> 6 | @_api_version = 'v1' 7 | @_http_client = require 'http' 8 | @_https_client = require 'https' 9 | @_config = 10 | client_id: if process.env['CLIENT_ID']? then process.env['CLIENT_ID'] else 'CLIENT-ID' 11 | client_secret: if process.env['CLIENT_SECRET']? then process.env['CLIENT_SECRET'] else 'CLIENT-SECRET' 12 | callback_url: if process.env['CALLBACK_URL']? then process.env['CALLBACK_URL'] else 'CALLBACK-URL' 13 | redirect_uri: if process.env['REDIRECT_URI']? then process.env['REDIRECT_URI'] else 'REDIRECT_URI' 14 | access_token: if process.env['ACCESS_TOKEN']? then process.env['ACCESS_TOKEN'] else null 15 | maxSockets: 5 16 | @_options = 17 | host: if process.env['TEST_HOST']? then process.env['TEST_HOST'] else 'api.instagram.com' 18 | port: if process.env['TEST_PORT']? then process.env['TEST_PORT'] else null 19 | method: "GET" 20 | path: '' 21 | headers: { 22 | 'User-Agent': 'Instagram Node Lib 0.0.7' 23 | 'Accept': 'application/json' 24 | 'Content-Length': 0 25 | } 26 | for module in ['media', 'tags', 'users', 'locations', 'geographies', 'subscriptions', 'oauth'] 27 | moduleClass = require "./class.instagram.#{module}" 28 | @[module] = new moduleClass @ 29 | 30 | ### 31 | Generic Response Methods 32 | ### 33 | 34 | _error: (e, value, caller) -> 35 | value = '[undefined]' if value is undefined 36 | value = '[null]' if value is null 37 | message = "#{e} occurred: #{value} in #{caller}" 38 | console.log(message) 39 | message 40 | 41 | _complete: (data, pagination) -> 42 | for i of data 43 | console.log data[i] 44 | console.log pagination if pagination? 45 | 46 | ### 47 | Shared Data Manipulation Methods 48 | ### 49 | 50 | _to_querystring: (params) -> 51 | obj = {} 52 | for i of params 53 | obj[i] = params[i] if i isnt 'complete' and i isnt 'error' 54 | querystring.stringify obj 55 | 56 | _merge: (obj1, obj2) -> 57 | for i of obj2 58 | obj1[i] = obj2[i] 59 | obj1 60 | 61 | _to_value: (key, value) -> 62 | return value if typeof value isnt 'object' 63 | return value[key] if value[key]? 64 | null 65 | 66 | _to_object: (key, value) -> 67 | return value if typeof value is 'object' and value[key]? 68 | obj = {} 69 | obj[key] = value 70 | 71 | _clone: (from_object) -> 72 | to_object = {} 73 | for property of from_object 74 | to_object[property] = from_object[property] 75 | to_object 76 | 77 | ### 78 | Set Methods 79 | ### 80 | 81 | _set_maxSockets: (value) -> 82 | if parseInt(value) is value and value > 0 83 | @_http_client.Agent.defaultMaxSockets = value 84 | @_https_client.Agent.defaultMaxSockets = value 85 | 86 | set: (property, value) -> 87 | @_config[property] = value if @_config[property] isnt undefined 88 | @_options[property] = value if @_options[property] isnt undefined 89 | @["_set_#{property}"](value) if typeof @["_set_#{property}"] is 'function' 90 | 91 | ### 92 | Shared Request Methods 93 | ### 94 | 95 | _credentials: (params, require = null) -> 96 | if require? and params[require]? or params['access_token']? or params['client_id']? 97 | return params 98 | if require isnt null and @_config[require]? 99 | params[require] = @_config[require] 100 | else if @_config['access_token']? 101 | params['access_token'] = @_config['access_token'] 102 | else if @_config['client_id']? 103 | params['client_id'] = @_config['client_id'] 104 | return params 105 | 106 | _request: (params) -> 107 | options = @_clone(@_options) 108 | options['method'] = params['method'] if params['method']? 109 | options['path'] = params['path'] if params['path']? 110 | options['path'] = if process.env['TEST_PATH_PREFIX']? then "#{process.env['TEST_PATH_PREFIX']}#{options['path']}" else options['path'] 111 | complete = params['complete'] ?= @_complete 112 | appResponse = params['response'] 113 | error = params['error'] ?= @_error 114 | if options['method'] isnt "GET" and params['post_data']? 115 | post_data = @_to_querystring params['post_data'] 116 | else 117 | post_data = '' 118 | options['headers']['Content-Length'] = post_data.length 119 | if process.env['TEST_PROTOCOL'] is 'http' 120 | http_client = @_http_client 121 | else 122 | http_client = @_https_client 123 | request = http_client.request options, (response) -> 124 | data = '' 125 | response.setEncoding 'utf8' 126 | response.on 'data', (chunk) -> 127 | data += chunk 128 | response.on 'end', -> 129 | try 130 | parsedResponse = JSON.parse data 131 | if parsedResponse? and parsedResponse['meta']? and parsedResponse['meta']['code'] isnt 200 132 | error parsedResponse['meta']['error_type'], parsedResponse['meta']['error_message'], "_request" 133 | else if parsedResponse['access_token']? 134 | complete parsedResponse, appResponse 135 | else 136 | pagination = if typeof parsedResponse['pagination'] is 'undefined' then {} else parsedResponse['pagination'] 137 | complete parsedResponse['data'], pagination 138 | catch e 139 | if appResponse? 140 | error e, data, '_request', appResponse 141 | else 142 | error e, data, '_request' 143 | if post_data? 144 | request.write post_data 145 | request.addListener 'error', (connectionException) -> 146 | if connectionException.code isnt 'ENOTCONN' 147 | console.log "\n" + connectionException 148 | throw connectionException 149 | request.end() 150 | 151 | APIClient = new InstagramAPI 152 | module.exports = APIClient 153 | -------------------------------------------------------------------------------- /test/instagram.media.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | /* 3 | Test Setup 4 | */ var Init, Instagram, app, assert, completed, should, test, to_do; 5 | console.log("\nInstagram API Node.js Lib Tests :: Media"); 6 | Init = require('./initialize'); 7 | Instagram = Init.Instagram; 8 | app = Init.app; 9 | assert = require('assert'); 10 | should = require('should'); 11 | test = require('./helpers'); 12 | completed = 0; 13 | to_do = 0; 14 | /* 15 | Tests 16 | */ 17 | module.exports = { 18 | 'media#popular': function() { 19 | return test.helper('media#popular', Instagram, 'media', 'popular', {}, function(data) { 20 | data.length.should.equal(32); 21 | test.output("data had length equal to 32"); 22 | data[0].should.have.property('id'); 23 | test.output("data[0] had the property 'id'", data[0].id); 24 | return app.finish_test(); 25 | }); 26 | }, 27 | 'media#info for id#3': function() { 28 | return test.helper('media#info for id#3', Instagram, 'media', 'info', { 29 | media_id: 3 30 | }, function(data) { 31 | data.should.have.property('id', '3'); 32 | test.output("data had the property 'id' equal to 3"); 33 | data.should.have.property('created_time', '1279315783'); 34 | test.output("data had the property 'created_time' equal to 1279315783"); 35 | return app.finish_test(); 36 | }); 37 | }, 38 | 'media#search for 48.858844300000001/2.2943506': function() { 39 | return test.helper('media#search for 48.858844300000001/2.2943506', Instagram, 'media', 'search', { 40 | lat: 48.858844300000001, 41 | lng: 2.2943506 42 | }, function(data) { 43 | data.length.should.be.above(0); 44 | test.output("data had length greater than 0", data.length); 45 | data[0].should.have.property('id'); 46 | test.output("data[0] had the property 'id'", data[0].id); 47 | return app.finish_test(); 48 | }); 49 | }, 50 | 'media#like id#3': function() { 51 | return test.helper('media#like id#3', Instagram, 'media', 'like', { 52 | media_id: 3 53 | }, function(data) { 54 | if (data !== null) { 55 | throw "like failed"; 56 | } 57 | test.output("data was null; we liked media #3"); 58 | return test.helper('media#likes for id#3', Instagram, 'media', 'likes', { 59 | media_id: 3 60 | }, function(data) { 61 | data.length.should.be.above(0); 62 | test.output("data had length greater than 0", data.length); 63 | return test.helper('media#unlike id#3', Instagram, 'media', 'unlike', { 64 | media_id: 3 65 | }, function(data) { 66 | if (data !== null) { 67 | throw "unlike failed"; 68 | } 69 | test.output("data was null; we unliked media #3"); 70 | return app.finish_test(); 71 | }); 72 | }); 73 | }); 74 | }, 75 | 'media#comment id#53355234': function() { 76 | return test.helper('media#comment id#53355234', Instagram, 'media', 'comment', { 77 | media_id: 53355234, 78 | text: 'Instagame was here.' 79 | }, function(data) { 80 | var comment_id; 81 | data.should.have.property('id'); 82 | test.output("data had the property 'id'", data.id); 83 | data.should.have.property('from'); 84 | test.output("data had the property 'from'", data.from); 85 | data.should.have.property('created_time'); 86 | test.output("data had the property 'created_time'", data.created_time); 87 | data.should.have.property('text'); 88 | test.output("data had the property 'text'", data.text); 89 | comment_id = data['id']; 90 | test.output("created comment " + comment_id); 91 | return test.helper('media#comments for id#53355234', Instagram, 'media', 'comments', { 92 | media_id: 53355234 93 | }, function(data) { 94 | data.length.should.be.above(0); 95 | test.output("data had length greater than 0", data.length); 96 | return test.helper('media#uncomment id#53355234', Instagram, 'media', 'uncomment', { 97 | media_id: 53355234, 98 | comment_id: comment_id 99 | }, function(data) { 100 | if (data !== null) { 101 | throw "uncomment failed"; 102 | } 103 | test.output("data was null; we deleted comment " + comment_id); 104 | return app.finish_test(); 105 | }); 106 | }); 107 | }); 108 | }, 109 | 'media#subscriptions': function() { 110 | return test.helper("media#subscriptions subscribe to geography near Eiffel Tower", Instagram, 'media', 'subscribe', { 111 | lat: 48.858844300000001, 112 | lng: 2.2943506, 113 | radius: 1000 114 | }, function(data) { 115 | var subscription_id; 116 | data.should.have.property('id'); 117 | test.output("data had the property 'id'"); 118 | data.id.should.be.above(0); 119 | test.output("data.id was greater than 0", data.id); 120 | data.should.have.property('type', 'subscription'); 121 | test.output("data had the property 'type' equal to 'subscription'", data); 122 | subscription_id = data.id; 123 | return test.helper('media#subscriptions list', Instagram, 'subscriptions', 'list', {}, function(data) { 124 | var found, i; 125 | data.length.should.be.above(0); 126 | test.output("data had length greater than 0", data.length); 127 | found = false; 128 | for (i in data) { 129 | if (data[i].id === subscription_id) { 130 | found = true; 131 | } 132 | } 133 | if (!found) { 134 | throw "subscription not found"; 135 | } 136 | test.output("data had the subscription " + subscription_id); 137 | return test.helper("media#subscriptions unsubscribe from media near Eiffel Tower", Instagram, 'media', 'unsubscribe', { 138 | id: subscription_id 139 | }, function(data) { 140 | if (data !== null) { 141 | throw "geography near Eiffel Tower unsubscribe failed"; 142 | } 143 | test.output("data was null; we unsubscribed from the subscription " + subscription_id); 144 | return app.finish_test(); 145 | }); 146 | }); 147 | }); 148 | } 149 | }; 150 | /* 151 | tested on Austin, Tx. { lat: 30.30, lng: -97.70, distance: 5000 }; weird, request with count 200 produces 54, request with count 50 produces 46, request with count 46 produces 42, request with count 42 produces 38... I think you see the pattern. :) 152 | 153 | 'media#search for 30.30/-97.70 with count of 42': -> 154 | test.helper 'media#search for 30.30/-97.70 with count of 42', Instagram, 'media', 'search', { lat: 30.30, lng: -97.70, distance: 5000, count: 42 }, (data) -> 155 | data.length.should.equal 42 156 | test.output "data had length equal to 42", data.length 157 | app.finish_test() 158 | */ 159 | app.start_tests(module.exports); 160 | }).call(this); 161 | -------------------------------------------------------------------------------- /lib/class.instagram.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var APIClient, InstagramAPI, querystring; 3 | querystring = require('querystring'); 4 | InstagramAPI = (function() { 5 | function InstagramAPI() { 6 | var module, moduleClass, _i, _len, _ref; 7 | this._api_version = 'v1'; 8 | this._http_client = require('http'); 9 | this._https_client = require('https'); 10 | this._config = { 11 | client_id: process.env['CLIENT_ID'] != null ? process.env['CLIENT_ID'] : 'CLIENT-ID', 12 | client_secret: process.env['CLIENT_SECRET'] != null ? process.env['CLIENT_SECRET'] : 'CLIENT-SECRET', 13 | callback_url: process.env['CALLBACK_URL'] != null ? process.env['CALLBACK_URL'] : 'CALLBACK-URL', 14 | redirect_uri: process.env['REDIRECT_URI'] != null ? process.env['REDIRECT_URI'] : 'REDIRECT_URI', 15 | access_token: process.env['ACCESS_TOKEN'] != null ? process.env['ACCESS_TOKEN'] : null, 16 | maxSockets: 5 17 | }; 18 | this._options = { 19 | host: process.env['TEST_HOST'] != null ? process.env['TEST_HOST'] : 'api.instagram.com', 20 | port: process.env['TEST_PORT'] != null ? process.env['TEST_PORT'] : null, 21 | method: "GET", 22 | path: '', 23 | headers: { 24 | 'User-Agent': 'Instagram Node Lib 0.0.7', 25 | 'Accept': 'application/json', 26 | 'Content-Length': 0 27 | } 28 | }; 29 | _ref = ['media', 'tags', 'users', 'locations', 'geographies', 'subscriptions', 'oauth']; 30 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 31 | module = _ref[_i]; 32 | moduleClass = require("./class.instagram." + module); 33 | this[module] = new moduleClass(this); 34 | } 35 | } 36 | /* 37 | Generic Response Methods 38 | */ 39 | InstagramAPI.prototype._error = function(e, value, caller) { 40 | var message; 41 | if (value === void 0) { 42 | value = '[undefined]'; 43 | } 44 | if (value === null) { 45 | value = '[null]'; 46 | } 47 | message = "" + e + " occurred: " + value + " in " + caller; 48 | console.log(message); 49 | return message; 50 | }; 51 | InstagramAPI.prototype._complete = function(data, pagination) { 52 | var i; 53 | for (i in data) { 54 | console.log(data[i]); 55 | } 56 | if (pagination != null) { 57 | return console.log(pagination); 58 | } 59 | }; 60 | /* 61 | Shared Data Manipulation Methods 62 | */ 63 | InstagramAPI.prototype._to_querystring = function(params) { 64 | var i, obj; 65 | obj = {}; 66 | for (i in params) { 67 | if (i !== 'complete' && i !== 'error') { 68 | obj[i] = params[i]; 69 | } 70 | } 71 | return querystring.stringify(obj); 72 | }; 73 | InstagramAPI.prototype._merge = function(obj1, obj2) { 74 | var i; 75 | for (i in obj2) { 76 | obj1[i] = obj2[i]; 77 | } 78 | return obj1; 79 | }; 80 | InstagramAPI.prototype._to_value = function(key, value) { 81 | if (typeof value !== 'object') { 82 | return value; 83 | } 84 | if (value[key] != null) { 85 | return value[key]; 86 | } 87 | return null; 88 | }; 89 | InstagramAPI.prototype._to_object = function(key, value) { 90 | var obj; 91 | if (typeof value === 'object' && (value[key] != null)) { 92 | return value; 93 | } 94 | obj = {}; 95 | return obj[key] = value; 96 | }; 97 | InstagramAPI.prototype._clone = function(from_object) { 98 | var property, to_object; 99 | to_object = {}; 100 | for (property in from_object) { 101 | to_object[property] = from_object[property]; 102 | } 103 | return to_object; 104 | }; 105 | /* 106 | Set Methods 107 | */ 108 | InstagramAPI.prototype._set_maxSockets = function(value) { 109 | if (parseInt(value) === value && value > 0) { 110 | this._http_client.Agent.defaultMaxSockets = value; 111 | return this._https_client.Agent.defaultMaxSockets = value; 112 | } 113 | }; 114 | InstagramAPI.prototype.set = function(property, value) { 115 | if (this._config[property] !== void 0) { 116 | this._config[property] = value; 117 | } 118 | if (this._options[property] !== void 0) { 119 | this._options[property] = value; 120 | } 121 | if (typeof this["_set_" + property] === 'function') { 122 | return this["_set_" + property](value); 123 | } 124 | }; 125 | /* 126 | Shared Request Methods 127 | */ 128 | InstagramAPI.prototype._credentials = function(params, require) { 129 | if (require == null) { 130 | require = null; 131 | } 132 | if ((require != null) && (params[require] != null) || (params['access_token'] != null) || (params['client_id'] != null)) { 133 | return params; 134 | } 135 | if (require !== null && (this._config[require] != null)) { 136 | params[require] = this._config[require]; 137 | } else if (this._config['access_token'] != null) { 138 | params['access_token'] = this._config['access_token']; 139 | } else if (this._config['client_id'] != null) { 140 | params['client_id'] = this._config['client_id']; 141 | } 142 | return params; 143 | }; 144 | InstagramAPI.prototype._request = function(params) { 145 | var appResponse, complete, error, http_client, options, post_data, request, _ref, _ref2; 146 | options = this._clone(this._options); 147 | if (params['method'] != null) { 148 | options['method'] = params['method']; 149 | } 150 | if (params['path'] != null) { 151 | options['path'] = params['path']; 152 | } 153 | options['path'] = process.env['TEST_PATH_PREFIX'] != null ? "" + process.env['TEST_PATH_PREFIX'] + options['path'] : options['path']; 154 | complete = (_ref = params['complete']) != null ? _ref : params['complete'] = this._complete; 155 | appResponse = params['response']; 156 | error = (_ref2 = params['error']) != null ? _ref2 : params['error'] = this._error; 157 | if (options['method'] !== "GET" && (params['post_data'] != null)) { 158 | post_data = this._to_querystring(params['post_data']); 159 | } else { 160 | post_data = ''; 161 | } 162 | options['headers']['Content-Length'] = post_data.length; 163 | if (process.env['TEST_PROTOCOL'] === 'http') { 164 | http_client = this._http_client; 165 | } else { 166 | http_client = this._https_client; 167 | } 168 | request = http_client.request(options, function(response) { 169 | var data; 170 | data = ''; 171 | response.setEncoding('utf8'); 172 | response.on('data', function(chunk) { 173 | return data += chunk; 174 | }); 175 | return response.on('end', function() { 176 | var pagination, parsedResponse; 177 | try { 178 | parsedResponse = JSON.parse(data); 179 | if ((parsedResponse != null) && (parsedResponse['meta'] != null) && parsedResponse['meta']['code'] !== 200) { 180 | return error(parsedResponse['meta']['error_type'], parsedResponse['meta']['error_message'], "_request"); 181 | } else if (parsedResponse['access_token'] != null) { 182 | return complete(parsedResponse, appResponse); 183 | } else { 184 | pagination = typeof parsedResponse['pagination'] === 'undefined' ? {} : parsedResponse['pagination']; 185 | return complete(parsedResponse['data'], pagination); 186 | } 187 | } catch (e) { 188 | if (appResponse != null) { 189 | return error(e, data, '_request', appResponse); 190 | } else { 191 | return error(e, data, '_request'); 192 | } 193 | } 194 | }); 195 | }); 196 | if (post_data != null) { 197 | request.write(post_data); 198 | } 199 | request.addListener('error', function(connectionException) { 200 | if (connectionException.code !== 'ENOTCONN') { 201 | console.log("\n" + connectionException); 202 | throw connectionException; 203 | } 204 | }); 205 | return request.end(); 206 | }; 207 | return InstagramAPI; 208 | })(); 209 | APIClient = new InstagramAPI; 210 | module.exports = APIClient; 211 | }).call(this); 212 | -------------------------------------------------------------------------------- /test/instagram.users.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | /* 3 | Test Setup 4 | */ var Init, Instagram, app, assert, completed, should, test, to_do; 5 | console.log("\nInstagram API Node.js Lib Tests :: Users"); 6 | Init = require('./initialize'); 7 | Instagram = Init.Instagram; 8 | app = Init.app; 9 | assert = require('assert'); 10 | should = require('should'); 11 | test = require('./helpers'); 12 | completed = 0; 13 | to_do = 0; 14 | /* 15 | Tests 16 | */ 17 | module.exports = { 18 | 'users#info for id#291024': function() { 19 | return test.helper('users#info for id#291024', Instagram, 'users', 'info', { 20 | user_id: 291024 21 | }, function(data) { 22 | data.should.have.property('id', '291024'); 23 | test.output("data had the property 'id' equal to '291024'"); 24 | data.should.have.property('profile_picture'); 25 | test.output("data had the property 'profile_picture'", data.profile_picture); 26 | return app.finish_test(); 27 | }); 28 | }, 29 | 'users#self for mckelvey': function() { 30 | return test.helper('users#self for mckelvey', Instagram, 'users', 'self', {}, function(data) { 31 | data.length.should.be.above(0); 32 | test.output("data had length greater than 0", data.length); 33 | data[0].should.have.property('id'); 34 | test.output("data[0] had the property 'id'", data[0].id); 35 | data[0].should.have.property('user'); 36 | test.output("data[0] had the property 'user'", data[0].user); 37 | return app.finish_test(); 38 | }); 39 | }, 40 | 'users#liked_by_self for mckelvey': function() { 41 | return test.helper('users#liked_by_self for mckelvey', Instagram, 'users', 'liked_by_self', {}, function(data) { 42 | data.length.should.be.above(0); 43 | test.output("data had length greater than 0", data.length); 44 | data[0].should.have.property('id'); 45 | test.output("data[0] had the property 'id'", data[0].id); 46 | data[0].should.have.property('user'); 47 | test.output("data[0] had the property 'user'", data[0].user); 48 | return app.finish_test(); 49 | }); 50 | }, 51 | 'users#liked_by_self for mckelvey with count of 3 and max_like_id': function() { 52 | return test.helper('users#liked_by_self for mckelvey with count of 3', Instagram, 'users', 'liked_by_self', { 53 | count: 3 54 | }, function(data, pagination) { 55 | data.length.should.equal(3); 56 | test.output("data had length equal to 3"); 57 | pagination.next_url.should.include.string('count=3'); 58 | test.output("pagination next_url included count of 3", pagination.next_url); 59 | pagination.should.have.property('next_max_like_id'); 60 | test.output("pagination had the property 'next_max_like_id'", pagination.next_max_like_id); 61 | return test.helper("users#liked_by_self for mckelvey with max_like_id of " + pagination.next_max_like_id, Instagram, 'users', 'liked_by_self', { 62 | max_like_id: pagination.next_max_like_id 63 | }, function(data, pagination) { 64 | data.length.should.be.above(0); 65 | test.output("data had length greater than 0", data.length); 66 | return app.finish_test(); 67 | }); 68 | }); 69 | }, 70 | 'users#recent for mckelvey': function() { 71 | return test.helper('users#recent for mckelvey', Instagram, 'users', 'recent', { 72 | user_id: 291024 73 | }, function(data) { 74 | data.length.should.be.above(0); 75 | data[0].should.have.property('id'); 76 | data[0]['user'].should.have.property('username', 'mckelvey'); 77 | return app.finish_test(); 78 | }); 79 | }, 80 | 'users#recent for mckelvey with count 60': function() { 81 | return test.helper('users#recent for mckelvey with count 60', Instagram, 'users', 'recent', { 82 | user_id: 291024, 83 | count: 60 84 | }, function(data) { 85 | data.length.should.equal(60); 86 | test.output("data had length equal to 60", data.length); 87 | return app.finish_test(); 88 | }); 89 | }, 90 | 'users#search for mckelvey': function() { 91 | return test.helper('users#search for mckelvey', Instagram, 'users', 'search', { 92 | q: 'mckelvey' 93 | }, function(data) { 94 | data.length.should.be.above(0); 95 | test.output("data had length greater than 0", data.length); 96 | data[0].should.have.property('username', 'mckelvey'); 97 | test.output("data had the property 'username' equal to 'mckelvey'"); 98 | data[0].should.have.property('id', '291024'); 99 | test.output("data had the property 'id' equal to '291024'"); 100 | return app.finish_test(); 101 | }); 102 | }, 103 | 'users#search for i with count': function() { 104 | return test.helper('users#search for i with count', Instagram, 'users', 'search', { 105 | q: 'i', 106 | count: 50 107 | }, function(data) { 108 | data.length.should.equal(50); 109 | test.output("data had length equal to 50", data.length); 110 | return app.finish_test(); 111 | }); 112 | }, 113 | 'users#follows id#291024': function() { 114 | return test.helper('users#follows id#291024', Instagram, 'users', 'follows', { 115 | user_id: 291024 116 | }, function(data, pagination) { 117 | data.length.should.be.above(0); 118 | data.length.should.be.below(51); 119 | return app.finish_test(); 120 | }); 121 | }, 122 | 'users#follows id#291024 with count 50': function() { 123 | return test.helper('users#follows id#291024 with count 50', Instagram, 'users', 'follows', { 124 | user_id: 291024, 125 | count: 50 126 | }, function(data, pagination) { 127 | data.length.should.equal(50); 128 | test.output("data had length equal to 50", data.length); 129 | return app.finish_test(); 130 | }); 131 | }, 132 | 'users#followed_by id#291024': function() { 133 | return test.helper('users#followed_by id#291024', Instagram, 'users', 'followed_by', { 134 | user_id: 291024 135 | }, function(data, pagination) { 136 | data.length.should.be.above(0); 137 | test.output("data had length greater than 0"); 138 | data.length.should.be.below(51); 139 | test.output("data had length less than 51", data.length); 140 | return app.finish_test(); 141 | }); 142 | }, 143 | 'users#followed_by id#291024 with count 50': function() { 144 | return test.helper('users#followed_by id#291024 with count 50', Instagram, 'users', 'followed_by', { 145 | user_id: 291024, 146 | count: 50 147 | }, function(data, pagination) { 148 | data.length.should.equal(50); 149 | test.output("data had length equal to 50", data.length); 150 | return app.finish_test(); 151 | }); 152 | }, 153 | 'users#requested_by id#291024': function() { 154 | return test.helper('users#requested_by id#291024', Instagram, 'users', 'requested_by', { 155 | user_id: 291024 156 | }, function(data, pagination) { 157 | data.should.have.property('length'); 158 | test.output("data had the property 'length'", data.length); 159 | return app.finish_test(); 160 | }); 161 | }, 162 | 'users#relationship with id#291024': function() { 163 | return test.helper('users#relationship with id#291024', Instagram, 'users', 'relationship', { 164 | user_id: 291024 165 | }, function(data, pagination) { 166 | data.should.have.property('outgoing_status'); 167 | test.output("data had the property 'outgoing_status'", data.outgoing_status); 168 | data.should.have.property('incoming_status'); 169 | test.output("data had the property 'incoming_status'", data.incoming_status); 170 | return app.finish_test(); 171 | }); 172 | }, 173 | 'users#unfollow id#291024': function() { 174 | return test.helper('users#unfollow id#291024', Instagram, 'users', 'unfollow', { 175 | user_id: 291024 176 | }, function(data, pagination) { 177 | data.should.have.property('outgoing_status', 'none'); 178 | test.output("data had the property 'outgoing_status' equal to 'none'", data); 179 | return test.helper('users#block id#291024', Instagram, 'users', 'block', { 180 | user_id: 291024 181 | }, function(data, pagination) { 182 | data.should.have.property('incoming_status', 'blocked_by_you'); 183 | test.output("data had the property 'incoming_status' equal to 'blocked_by_you'", data); 184 | return test.helper('users#unblock id#291024', Instagram, 'users', 'unblock', { 185 | user_id: 291024 186 | }, function(data, pagination) { 187 | data.should.have.property('incoming_status', 'none'); 188 | test.output("data had the property 'incoming_status' equal to 'none'", data); 189 | return test.helper('users#ignore id#291024', Instagram, 'users', 'ignore', { 190 | user_id: 291024 191 | }, function(data, pagination) { 192 | data.should.have.property('incoming_status', 'none'); 193 | test.output("data had the property 'incoming_status' equal to 'none'", data); 194 | return test.helper('users#follow id#291024', Instagram, 'users', 'follow', { 195 | user_id: 291024 196 | }, function(data, pagination) { 197 | data.should.have.property('outgoing_status', 'follows'); 198 | test.output("data had the property 'outgoing_status' equal to 'follows'", data); 199 | return app.finish_test(); 200 | }); 201 | }); 202 | }); 203 | }); 204 | }); 205 | } 206 | }; 207 | /* 208 | for me, returns 9 regardless, requesting with count seems to have no impact � time based only now? 209 | 210 | 'users#self for mckelvey with count 200': -> 211 | test.helper 'users#self for mckelvey with count 200', Instagram, 'users', 'self', { count: 200 }, (data) -> 212 | data.length.should.equal 200 213 | test.output "data had length equal to 200", data.length 214 | app.finish_test() 215 | */ 216 | app.start_tests(module.exports); 217 | }).call(this); 218 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## What It Is 2 | 3 | The Instagram Node Lib is a helper library for [node](http://nodejs.org) that makes communicating with the [Instagram API](http://instagram.com/developer/) easy. 4 | 5 | ## Simple Example 6 | 7 | Following is an example that loads the library, sets my CLIENT_ID and CLIENT_SECRET for accessing the API and makes a simple call to get information on the tag `#blue`. 8 | 9 | Instagram = require('instagram'); 10 | 11 | Instagram.set('client_id', 'YOUR-CLIENT-ID'); 12 | Instagram.set('client_secret', 'YOUR-CLIENT-SECRET'); 13 | 14 | Instagram.tags.info({ 15 | name: 'blue', 16 | complete: function(data){ 17 | console.log(data); 18 | } 19 | }); 20 | 21 | When successful, the data logged in the console would be a javascript object like `{ media_count: 10863, name: 'blue' }`. 22 | 23 | ## Installation 24 | 25 | $ npm install instagram-node-lib 26 | 27 | ## Setup 28 | 29 | To use the library, you'll need to require it and at minimum, set your CLIENT_ID and CLIENT_SECRET given to you by Instagram. 30 | 31 | Instagram = require('instagram-node-lib'); 32 | 33 | Instagram.set('client_id', 'YOUR-CLIENT-ID'); 34 | Instagram.set('client_secret', 'YOUR-CLIENT-SECRET'); 35 | 36 | Optionally, if you intend to use the real-time API to manage subscriptions, then you can also set a global callback url. (You may also provide/override the callback url when subscribing.) 37 | 38 | Instagram.set('callback_url', 'CALLBACK-URL'); 39 | 40 | If you intend to use user-specific methods (e.g. relationships), then you must also set a global redirect_uri (matching that in your [app settings in the API](http://instagram.com/developer/manage/)). 41 | 42 | Instagram.set('redirect_uri', 'YOUR-REDIRECT-URI'); 43 | 44 | Lastly, if you find that the default max sockets of 5 is too few for the http(s) client, you can increase it as needed with the set method. The new max sockets value must be a positive integer greater than zero. 45 | 46 | Instagram.set('maxSockets', 10); 47 | 48 | ## Available Methods 49 | 50 | All of the methods below follow a similar pattern. Each accepts a single javascript object with the needed parameters to complete the API call. Required parameters are shown below; refer to [the API docs](http://instagram.com/developer/endpoints/) for the optional parameters. All parameters are passed through to the request, so use the exact terms that the API docs provide. 51 | 52 | In addition, the parameters object may include two functions, one of which will be executed at the conclusion of the request (i.e. `complete` and `error`). 53 | 54 | { 55 | name: 'blue', 56 | complete: function(data, pagination){ 57 | // data is a javascript object/array/null matching that shipped Instagram 58 | // when available (mostly /recent), pagination is a javascript object with the pagination information 59 | }, 60 | error: function(errorMessage, errorObject, caller){ 61 | // errorMessage is the raised error message 62 | // errorObject is either the object that caused the issue, or the nearest neighbor 63 | // caller is the method in which the error occurred 64 | } 65 | } 66 | 67 | In the event you do not provide a `complete` or `error` function, the library has fallback functions which post results to the console log. 68 | 69 | ### Media 70 | 71 | The following media methods are available. Required parameters are shown below, see the [Instagram API docs](http://instagram.com/developer/endpoints/media/) for the optional parameters. 72 | 73 | #### Popular 74 | 75 | Get the a set of 32 current popular media, each with all it's associated likes and comments. 76 | 77 | Instagram.media.popular(); 78 | -> [ { media object }, 79 | { media object }, 80 | { media object }, ... ] 81 | 82 | #### Info 83 | 84 | Get the metadata for a single media item by media id. 85 | 86 | Instagram.media.info({ media_id: 3 }); 87 | -> { media object } 88 | 89 | #### Search 90 | 91 | With a latitude and longitude (and an optional distance), find nearby media by geography. 92 | 93 | Instagram.media.search({ lat: 48.858844300000001, lng: 2.2943506 }); 94 | -> [ { media object }, 95 | { media object }, 96 | { media object }, ... ] 97 | 98 | #### Likes 99 | 100 | Akin to an info request, this method only returns an array of likers for the media. 101 | 102 | Instagram.media.likes({ media_id: 3 }); 103 | -> [ { username: 'krisrak', 104 | profile_picture: 'http://profile/path.jpg', 105 | id: '#', 106 | full_name: 'Rak Kris' }, 107 | { username: 'mikeyk', 108 | profile_picture: 'http://profile/path.jpg', 109 | id: '#', 110 | full_name: 'Mike Krieger' } ] 111 | 112 | Using an `access_token`, you can have the token user like or unlike a media item. 113 | 114 | Instagram.media.like({ media_id: 3 }); 115 | -> null // null is success, an error is failure 116 | 117 | Instagram.media.unlike({ media_id: 3 }); 118 | -> null // null is success, an error is failure 119 | 120 | #### Comments 121 | 122 | Akin to an info request, this method only returns an array of comments on the media. 123 | 124 | Instagram.media.comments({ media_id: 3 }); 125 | -> [ { created_time: '1279306830', 126 | text: 'Love the sign here', 127 | from: 128 | { username: 'mikeyk', 129 | profile_picture: 'http://profile/path.jpg', 130 | id: '#', 131 | full_name: 'Mike Krieger' }, 132 | id: '8' }, 133 | { created_time: '1279315804', 134 | text: 'Chilako taco', 135 | from: 136 | { username: 'kevin', 137 | profile_picture: 'http://profile/path.jpg', 138 | id: '#', 139 | full_name: 'Kevin Systrom' }, 140 | id: '3' } ] 141 | 142 | Using an `access_token`, you can have the token user comment upon or delete their comment from a media item. 143 | 144 | Instagram.media.comment({ media_id: 3, text: 'Instagame was here.' }); 145 | -> { created_time: '1302926497', 146 | text: 'Instagame was here.', 147 | from: 148 | { username: 'instagame', 149 | profile_picture: 'http://profile/path.jpg', 150 | id: '#', 151 | full_name: '' }, 152 | id: '67236858' } 153 | 154 | Instagram.media.uncomment({ media_id: 3, comment_id: 67236858 }); 155 | -> null // null is success, an error is failure 156 | 157 | #### Subscriptions 158 | 159 | Geography subscriptions for media are also available with the following methods. A `callback_url` is required if not specified globally, and you may also provide a `verify_token` if you want to keep track of which subscription is coming back. Note that while `unsubscribe` is identical to the generic subscriptions method below, here, `unsubscribe_all` only removes geography subscriptions. 160 | 161 | Instagram.media.subscribe({ lat: 48.858844300000001, lng: 2.2943506, radius: 1000 }); 162 | -> { object: 'geography', 163 | object_id: '#', 164 | aspect: 'media', 165 | callback_url: 'http://your.callback/path', 166 | type: 'subscription', 167 | id: '#' } 168 | 169 | Instagram.media.unsubscribe({ id: # }); 170 | -> null // null is success, an error is failure 171 | 172 | Instagram.media.unsubscribe_all(); 173 | -> null // null is success, an error is failure 174 | 175 | ### Tags 176 | 177 | The following tag methods are available. Required parameters are shown below, see the [Instagram API docs](http://instagram.com/developer/endpoints/tags/) for the optional parameters. 178 | 179 | #### Info 180 | 181 | Get the metadata for a single tag by name. 182 | 183 | Instagram.tags.info({ name: 'blue' }); 184 | -> { media_count: 10863, name: 'blue' } 185 | 186 | #### Recent 187 | 188 | Get an array of media that have been tagged with the tag recently. 189 | 190 | Instagram.tags.recent({ name: 'blue' }); 191 | -> [ { media object }, 192 | { media object }, 193 | { media object }, ... ] 194 | 195 | #### Search 196 | 197 | Search for matching tags by name (q). 198 | 199 | Instagram.tags.search({ q: 'blue' }); 200 | -> [ { media_count: 10872, name: 'blue' }, 201 | { media_count: 931, name: 'bluesky' }, 202 | { media_count: 178, name: 'blueeyes' }, ... ] 203 | 204 | #### Subscriptions 205 | 206 | Tag subscriptions are also available with the following methods. A `callback_url` is required if not specified globally, and you may also provide a `verify_token` if you want to keep track of which subscription is coming back. Note that while `unsubscribe` is identical to the generic subscriptions method below, here, `unsubscribe_all` only removes tag subscriptions. 207 | 208 | Instagram.tags.subscribe({ object_id: 'blue' }); 209 | -> { object: 'tag', 210 | object_id: 'blue', 211 | aspect: 'media', 212 | callback_url: 'http://your.callback/path', 213 | type: 'subscription', 214 | id: '#' } 215 | 216 | Instagram.tags.unsubscribe({ id: # }); 217 | -> null // null is success, an error is failure 218 | 219 | Instagram.tags.unsubscribe_all(); 220 | -> null // null is success, an error is failure 221 | 222 | ### Locations 223 | 224 | The following location methods are available. Required parameters are shown below, see the [Instagram API docs](http://instagram.com/developer/endpoints/locations/) for the optional parameters. 225 | 226 | #### Info 227 | 228 | Get the metadata for a single location by location id. 229 | 230 | Instagram.locations.info({ location_id: 1 }); 231 | -> { latitude: 37.78265474565738, 232 | id: '1', 233 | longitude: -122.387866973877, 234 | name: 'Dogpatch Labs' } 235 | 236 | #### Recent 237 | 238 | Get an array of media that have been located with the matching location (by id) recently. 239 | 240 | Instagram.locations.recent({ location_id: 1 }); 241 | -> [ { media object }, 242 | { media object }, 243 | { media object }, ... ] 244 | 245 | #### Search 246 | 247 | With a latitude and longitude (and an optional distance), find nearby locations by geography. 248 | 249 | Instagram.locations.search({ lat: 48.858844300000001, lng: 2.2943506 }); 250 | -> [ { latitude: 48.8588443, 251 | id: '723695', 252 | longitude: 2.2943506, 253 | name: 'Restaurant Jules Verne' }, 254 | { latitude: 48.8588443, 255 | id: '788029', 256 | longitude: 2.2943506, 257 | name: 'Eiffel Tower, Paris' }, 258 | { latitude: 48.858543, 259 | id: '1894075', 260 | longitude: 2.2938285, 261 | name: 'Caf� de l\'homme' }, ... ] 262 | 263 | #### Subscriptions 264 | 265 | Location subscriptions are also available with the following methods. A `callback_url` is required when subscribing if not specified globally, and you may also provide a `verify_token` if you want to keep track of which subscription is coming back. Note that while `unsubscribe` is identical to the generic subscriptions method below, here, `unsubscribe_all` only removes location subscriptions. 266 | 267 | Instagram.locations.subscribe({ object_id: '1257285' }); 268 | -> { object: 'location', 269 | object_id: '1257285', 270 | aspect: 'media', 271 | callback_url: 'http://your.callback/path', 272 | type: 'subscription', 273 | id: '#' } 274 | 275 | Instagram.locations.unsubscribe({ id: # }); 276 | -> null // null is success, an error is failure 277 | 278 | Instagram.locations.unsubscribe_all(); 279 | -> null // null is success, an error is failure 280 | 281 | ### Users 282 | 283 | The following user methods are available. Required parameters are shown below, see the [Instagram API docs](http://instagram.com/developer/endpoints/users/) for the optional parameters. 284 | 285 | #### Info 286 | 287 | Get the metadata for a single user by user id. 288 | 289 | Instagram.users.info({ user_id: 291024 }); 290 | -> { username: 'mckelvey', 291 | counts: { media: 526, followed_by: 293, follows: 265 }, 292 | profile_picture: 'http://profile/path.jpg', 293 | id: '291024', 294 | full_name: 'David McKelvey' } 295 | 296 | #### Search 297 | 298 | Search for matching users by name (q). 299 | 300 | Instagram.users.search({ q: 'mckelvey' }); 301 | -> [ { username: 'mckelvey', 302 | profile_picture: 'http://profile/path.jpg', 303 | id: '291024', 304 | full_name: 'David McKelvey' }, ... ] 305 | 306 | #### Self 307 | 308 | Get the user media feed for the `access_token` supplied. This method obviously then requires `access_token` rather than simply `client_id`; see the OAuth section on obtaining an `access_token`. You can either supply it here or set it within the library. 309 | 310 | Instagram.users.self(); 311 | -> [ { media object }, 312 | { media object }, 313 | { media object }, ... ] 314 | 315 | #### Liked by Self 316 | 317 | Get the media that has been liked by the user for the `access_token` supplied. This method obviously then requires `access_token` rather than simply `client_id`; see the OAuth section on obtaining an `access_token`. You can either supply it here or set it within the library. 318 | 319 | Instagram.users.liked_by_self(); 320 | -> [ { media object }, 321 | { media object }, 322 | { media object }, ... ] 323 | 324 | #### Recent 325 | 326 | Get the user media feed for a user by user_id. This method requires `access_token` rather than simply `client_id` in case the requested user media is protected and the requesting user is not authorized to view the media; see the OAuth section on obtaining an `access_token`. You can either supply it here or set it within the library. 327 | 328 | Instagram.users.recent({ user_id: 291024 }); 329 | -> [ { media object }, 330 | { media object }, 331 | { media object }, ... ] 332 | 333 | #### Relationships 334 | 335 | The following methods allow you to view and alter user-to-user relationships via an `access_token` (assuming the scope `relationships` has been authorized for the token). Do review the outgoing and incoming references in the [Instagram API Relationship Docs](http://instagram.com/developer/endpoints/relationships/) as they can be confusing since they act in relation to the `access_token` used. _I didn't have any users to fully test the request/approve/ignore against; let me know if you encounter difficulties._ 336 | 337 | Instagram.users.follows({ user_id: 291024 }); 338 | -> [ { username: 'mckelvey', 339 | profile_picture: 'http://profile/path.jpg', 340 | id: '291024', 341 | full_name: 'David McKelvey' }, ... ] 342 | 343 | Instagram.users.followed_by({ user_id: 291024 }); 344 | -> [ { username: 'instagame', 345 | profile_picture: 'http://profile/path.jpg', 346 | id: '1340677', 347 | full_name: '' }, ... ] 348 | 349 | Instagram.users.requested_by({ user_id: 291024 }); 350 | -> [ { username: 'instagame', 351 | profile_picture: 'http://profile/path.jpg', 352 | id: '1340677', 353 | full_name: '' }, ... ] 354 | 355 | Instagram.users.relationship({ user_id: 291024 }); 356 | -> { outgoing_status: 'follows', // access_token user follows user 291024 357 | incoming_status: 'none' } // user 291024 has no relationship with the access_token user 358 | 359 | Instagram.users.follow({ user_id: 291024 }); 360 | -> { outgoing_status: 'follows' } // success: access_token user follows user 291024 361 | 362 | Instagram.users.unfollow({ user_id: 291024 }); 363 | -> { outgoing_status: 'none' } // success: access_token user no longer follows user 291024 364 | 365 | Instagram.users.block({ user_id: 291024 }); 366 | -> { incoming_status: 'blocked_by_you' } // success: access_token user has blocked user 291024 367 | 368 | Instagram.users.unblock({ user_id: 291024 }); 369 | -> { incoming_status: 'none' } // success: access_token user no longer blocks user 291024 370 | 371 | Instagram.users.approve({ user_id: 291024 }); 372 | -> { incoming_status: 'followed_by' } // success: access_token user has allowed user 291024 to follow 373 | 374 | Instagram.users.ignore({ user_id: 291024 }); 375 | -> { incoming_status: 'requested_by' } // success: access_token user has ignored user 291024's follow request 376 | 377 | #### Subscriptions 378 | 379 | User subscriptions are also available with the following methods. A `callback_url` is required when subscribing if not specified globally, and you may also provide a `verify_token` if you want to keep track of which subscription is coming back. Note that because Instagram user subscriptions are based on your API client's authenticated users, `unsubscribe` is equivalent to `unsubscribe_all`, so only `unsubscribe_all` is provided. 380 | 381 | Instagram.users.subscribe(); 382 | -> { object: 'user', 383 | aspect: 'media', 384 | callback_url: 'http://your.callback/path', 385 | type: 'subscription', 386 | id: '#' } 387 | 388 | Instagram.users.unsubscribe_all(); 389 | -> null // null is success, an error is failure 390 | 391 | ### Real-time Subscriptions 392 | 393 | In addition to the above subscription methods within tags, locations and media, you can also interact with any subscription directly with the methods below. As with the others, it will be helpful to review the [Instagram API docs](http://instagram.com/developer/realtime/) for additional information. 394 | 395 | Be sure to include a GET route/method for the callback handshake at the `callback_url` that can handle the setup. This library includes a handshake method (example below based on Express), to which you can provide the request, the response and a complete method that will act upon the `verify_token` should you have provided it in the initial request. 396 | 397 | app.get('/subscribe', function(request, response){ 398 | Instagram.subscriptions.handshake(request, response); 399 | }); 400 | 401 | #### Subscribe 402 | 403 | The subscription request differs here in that it will not know what kind of object (tag, location, geography) to which you want to subscribe, so be sure to specify it. A `callback_url` is required when subscribing if not specified globally, and you may also provide a `verify_token` if you want to keep track of which subscription is coming back. 404 | 405 | Instagram.subscriptions.subscribe({ object: 'tag', object_id: 'blue' }); 406 | -> { object: 'tag', 407 | object_id: 'blue', 408 | aspect: 'media', 409 | callback_url: 'http://your.callback/path', 410 | type: 'subscription', 411 | id: '#' } 412 | 413 | #### Subscriptions 414 | 415 | Retrieve a list of all your subscriptions. 416 | 417 | Instagram.subscriptions.list(); 418 | -> [ { object: 'tag', 419 | object_id: 'blue', 420 | aspect: 'media', 421 | callback_url: 'http://your.callback/path', 422 | type: 'subscription', 423 | id: '#' }, ... ] 424 | 425 | #### Unsubscribe 426 | 427 | To unsubscribe from a single subscription, you must provide the subscription id. 428 | 429 | Instagram.subscriptions.unsubscribe({ id: # }); 430 | -> null // null is success, an error is failure 431 | 432 | #### Unsubscribe All 433 | 434 | Unsubscribe from all subscriptions of all kinds. 435 | 436 | Instagram.subscriptions.unsubscribe_all(); 437 | -> null // null is success, an error is failure 438 | 439 | ## OAuth 440 | 441 | In order to perform specific methods upon user data, you will need to have authorization from them through [Instagram OAuth](http://instagram.com/developer/auth/). Several methods are provided so that you can request authorization from users. You will need to specify your redirect_uri from your [application setup at Instagram](http://instagram.com/developer/manage/). 442 | 443 | Instagram.set('redirect_uri', 'YOUR-REDIRECT-URI'); 444 | 445 | #### Authorization Url 446 | 447 | To obtain a user url for the link to Instagram, use the authorization_url method. You can include the optional parameters as needed, but be sure to use spaces instead of pluses (as they will be encoded to pluses). 448 | 449 | url = Instagram.oauth.authorization_url({ 450 | scope: 'comments likes' // use a space when specifying a scope; it will be encoded into a plus 451 | display: 'touch' 452 | }); 453 | 454 | #### Ask for an Access Token 455 | 456 | The example below uses Express to specify a route to respond to the user's return from Instagram. It will pass the access_token and user object returned to a provided complete function. Your complete and error functions should handle your app server response (passed as a parameter for oauth only) *or* include a redirect parameter for simple redirects. 457 | 458 | If you choose to use the simple redirect, be advised that due to the event model of node.js, your users may reach the redirect address before the complete method is executed. 459 | 460 | app.get('/oauth', function(request, response){ 461 | Instagram.oauth.ask_for_access_token({ 462 | request: request, 463 | response: response, 464 | redirect: 'http://your.redirect/url', // optional 465 | complete: function(params, response){ 466 | // params['access_token'] 467 | // params['user'] 468 | response.writeHead(200, {'Content-Type': 'text/plain'}); 469 | // or some other response ended with 470 | response.end(); 471 | }, 472 | error: function(errorMessage, errorObject, caller, response){ 473 | // errorMessage is the raised error message 474 | // errorObject is either the object that caused the issue, or the nearest neighbor 475 | // caller is the method in which the error occurred 476 | response.writeHead(406, {'Content-Type': 'text/plain'}); 477 | // or some other response ended with 478 | response.end(); 479 | } 480 | }); 481 | return null; 482 | }); 483 | 484 | ## Developers 485 | 486 | Hey, this is my first Node.js project, my first NPM package, and my first public repo (and happy to finally be giving back for all the code I've enjoyed over the years). If you have suggestions please email me, register an issue, fork (dev please) and branch, etc. (You know the routine probably better than I.) 487 | 488 | If you add additional functionality, your pull request must have corresponding additional tests and supporting documentation. 489 | 490 | I've used [CoffeeScript](http://jashkenas.github.com/coffee-script) to write this library. If you haven't tried it, I highly recommend it. CoffeeScript takes some of the work out of javascript structures. Refer to the CoffeeScript docs for installation and usage. 491 | 492 | ### Contributors 493 | 494 | * [Andrew Senter](https://github.com/andrewsenter) 495 | * [Olivier Balais](https://github.com/bobey) 496 | 497 | Both Andrew and Olivier suggested better ways of handling the server response when requesting a token during OAuth. 498 | 499 | ### Tests 500 | 501 | There is a test suite in the /tests folder with the tests I used to ensure the library functions as intended. If you're adding or changing functionality, please add to or update the corresponding tests before issuing a pull request. The tests require [Express](https://github.com/visionmedia/express), [Expresso](https://github.com/visionmedia/expresso) and [Should](https://github.com/visionmedia/should.js): 502 | 503 | npm install express 504 | npm install expresso 505 | npm install should 506 | 507 | In addition, either export or add to your shell profile your CLIENT_ID, CLIENT_SECRET, ACCESS_TOKEN (if applicable) and CALLBACK_URL so that they are available during testing. 508 | --------------------------------------------------------------------------------