├── .gitignore ├── .npmignore ├── .travis.yml ├── README.md ├── index.js ├── package.json └── test ├── run.js └── unit ├── test-authRequest.js ├── test-getAccessToken.js ├── test-getPhotoInfo.js ├── test-getPhotos.js ├── test-getUser.js ├── test-init.js ├── test-tools.js └── test-uploadPhoto.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.sublime-* 2 | /node_modules/ 3 | /test/ 4 | .gitignore 5 | .npmignore 6 | .travis.yml 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.6" 4 | - "0.8" 5 | - "0.10" 6 | - "0.11" 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # api_500px [![Build Status](https://secure.travis-ci.org/alexindigo/node-api_500px.png)](http://travis-ci.org/alexindigo/node-api_500px) [![Dependency Status](https://gemnasium.com/alexindigo/node-api_500px.png)](https://gemnasium.com/alexindigo/node-api_500px) 2 | 3 | Helper (simple wrapper) for 500px API. 4 | 5 | *Focused on authenticated user photo manipulations, including upload* 6 | 7 | ## Installation 8 | 9 | ``` 10 | npm install api_500px 11 | ``` 12 | 13 | 14 | ## Examples 15 | 16 | 17 | * Include module 18 | 19 | ``` 20 | var Api500px = require('api_500px'); 21 | ``` 22 | 23 | * Create instance 24 | 25 | ``` 26 | var api = new Api500px( 27 | { 28 | key: 'your_500px_key_token', 29 | secret: 'your_500px_secret_token', 30 | callback: 'http://example.com/oauth_callback' 31 | }); 32 | ``` 33 | 34 | * Request auth token 35 | 36 | ``` 37 | api.authRequest(function(err, authToken, authSecret, results) 38 | { 39 | if (err) return callback(err); 40 | 41 | // redirect client to OAuth page 42 | callback(null, 'https://api.500px.com/v1/oauth/authorize?oauth_token='+authToken); 43 | }); 44 | ``` 45 | 46 | * Upon client's return with verification token, request access token 47 | 48 | ``` 49 | api.getAccessToken('api_500px_auth_verifier_token', function(err, accessToken, accessSecret, results) 50 | { 51 | if (err) return callback(err); 52 | 53 | // access token's been stored within the api instance as well 54 | callback(null, {status: 'ready'}); 55 | }); 56 | ``` 57 | 58 | * Get authenticated user's info 59 | 60 | ``` 61 | api.getUser(function(err, userData) 62 | { 63 | if (err) return callback(err); 64 | 65 | // user's info has been stored within the api instance 66 | // - normalized: api.data.user.id and api.data.user.username 67 | // - original: api.data.user._details 68 | // "returned" userData has same format 69 | 70 | callback(null, userData); 71 | }); 72 | ``` 73 | 74 | * Get user's photos (first page up to 100 photos) 75 | 76 | ``` 77 | api.getPhotos(1, function(err, photos) 78 | { 79 | if (err) return callback(err); 80 | 81 | // "returned" photos is unparsed JSON string 82 | callback(null, photos); 83 | }); 84 | ``` 85 | 86 | * Custom photo search 87 | 88 | ``` 89 | var params = 90 | { 91 | feature: 'popular', 92 | user_id: 26, 93 | rpp: 50, 94 | page: 4, 95 | image_size: 2 96 | }; 97 | api.getPhotos(params, function(err, photos) 98 | { 99 | if (err) return callback(err); 100 | 101 | // "returned" photos is unparsed JSON string 102 | callback(null, photos); 103 | }); 104 | ``` 105 | 106 | * Get simple photo info 107 | 108 | ``` 109 | var photoId = 12539831; 110 | api.getPhotoInfo(photoId, function(err, info) 111 | { 112 | if (err) return callback(err); 113 | 114 | // "returned" info is normalized photo object 115 | callback(null, info); 116 | }); 117 | ``` 118 | 119 | * Get customized photo info 120 | 121 | ``` 122 | var photoId = 12539831 123 | , params = 124 | { 125 | image_size: 2, 126 | tags: 0, 127 | comments: 1, 128 | comments_page: 4 129 | }; 130 | api.getPhotoInfo(photoId, params, function(err, info) 131 | { 132 | if (err) return callback(err); 133 | 134 | // "returned" info is normalized photo object 135 | callback(null, info); 136 | }); 137 | ``` 138 | 139 | *Normalized photo object:* 140 | 141 | ``` 142 | { 143 | id : 12539831, 144 | title : 'Toronto panorama', 145 | description: 'Taken in Toronto, Ontario @ March 16, 2008', 146 | private : false, 147 | url : 'http://pcdn.500px.net/12539831/805beb838eafaf9253ff176b366af6f9fb31d593/4.jpg', 148 | tags : [ 'toronto', 'night', 'skyline' ], 149 | safety : true, 150 | license : null, 151 | dates : '2008-03-15T00:00:00-04:00', 152 | latitude : 43.685650966634206, 153 | longitude : -79.41691160202026 154 | } 155 | ``` 156 | 157 | * Upload photo from remote photo hosting to 500px 158 | 159 | *TODO: Refactor it.* 160 | 161 | ``` 162 | var photoUrl = 'http://farm9.staticflickr.com/8438/7789275832_3ab0e23be3_o.png'; 163 | 164 | api.uploadPhoto(photoUrl, flickrNormalizedPhotoInfoData, function(err, result) 165 | { 166 | if (err) return callback(err); 167 | 168 | // result consists of two properties 169 | // - result.message: upload status message 170 | // - result.photo: parsed, non-normalized created photo object 171 | callback(null, result.photo); 172 | }); 173 | 174 | ``` 175 | 176 | ## Everything else 177 | 178 | TBD 179 | 180 | ## License 181 | 182 | (The MIT License) 183 | 184 | Copyright (c) 2012 Alex Indigo 185 | 186 | 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: 187 | 188 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 189 | 190 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 191 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var util = require('util') 2 | , EventEmitter = require('events').EventEmitter 3 | , queryString = require('querystring') 4 | 5 | // third-party modules 6 | , OAuth = require('oauth').OAuth 7 | , FormData = require('form-data') 8 | // , request = require('request') 9 | 10 | // globals 11 | , basePoint = 'https://api.500px.com/v1/' 12 | , endPoints = 13 | { 14 | user : basePoint + 'users', 15 | photos : basePoint + 'photos', 16 | upload : basePoint + 'upload' 17 | } 18 | , config = 19 | { 20 | request : basePoint + 'oauth/request_token', 21 | access : basePoint + 'oauth/access_token', 22 | version : '1.0A', 23 | signature : 'HMAC-SHA1', 24 | separator : ', ', 25 | 26 | key : '', 27 | secret : '', 28 | callback : '' 29 | } 30 | ; 31 | 32 | module.exports = api_500px; 33 | util.inherits(api_500px, EventEmitter); 34 | 35 | function api_500px(options) 36 | { 37 | // house keeping 38 | if (!(this instanceof api_500px)) throw new Error('usage: var apiInstance = new api_500px(config);'); 39 | EventEmitter.call(this); 40 | 41 | options = options || {}; 42 | 43 | this.OA = new OAuth 44 | ( 45 | options.request || config.request, 46 | options.access || config.access, 47 | options.key || config.key, 48 | options.secret || config.secret, 49 | options.version || config.version, 50 | options.callback || config.callback, 51 | options.signature || config.signature 52 | ); 53 | 54 | // prepare place for user's info 55 | this.data = {}; 56 | 57 | // change header separator to make it work with 500px 58 | this.OA._oauthParameterSeperator = options.separator || config.separator; 59 | 60 | return this; 61 | }; 62 | 63 | // Sends auth request 64 | api_500px.prototype.authRequest = function api500pxAuthRequest(callback) 65 | { 66 | // request token 67 | this.OA.getOAuthRequestToken(function handler_api500pxAuthRequest(err, auth_token, auth_secret, results) 68 | { 69 | if (err) return callback(err); 70 | 71 | // TODO: Check if there is any overlapping 72 | this.data.auth_token = auth_token; 73 | this.data.auth_secret = auth_secret; 74 | 75 | callback(null, auth_token, auth_secret, results); 76 | }.bind(this)); 77 | } 78 | 79 | // Sends access token request 80 | api_500px.prototype.getAccessToken = function api500pxGetAccessToken(verifier, callback) 81 | { 82 | // get access token 83 | this.OA.getOAuthAccessToken(this.data.auth_token, this.data.auth_secret, verifier, function handler_api500pxGetAccessToken(err, access_token, access_secret, results) 84 | { 85 | if (err) return callback(err); 86 | 87 | // cleanup auth token 88 | delete this.data.auth_token; 89 | delete this.data.auth_secret; 90 | 91 | // store access token and secret 92 | this.data.access_token = access_token; 93 | this.data.access_secret = access_secret; 94 | // it's not normalized data, therefore underscore 95 | this.data._details = results; 96 | 97 | callback(null, access_token, access_secret, results); 98 | }.bind(this)); 99 | } 100 | 101 | api_500px.prototype.getUser = function api500pxGetUser(callback) 102 | { 103 | // TODO: make callbacks optional 104 | // JSONStream + .emit('error') 105 | 106 | // TODO: Make it real fallback, just fail at this point 107 | if (!this.data.access_token) return callback('Missing access token'); 108 | 109 | this._get(endPoints.user, function handler_api500pxGetUser(err, data) 110 | { 111 | if (err) return callback(err); 112 | 113 | try 114 | { 115 | data = JSON.parse(data); 116 | } 117 | catch (e) 118 | { 119 | console.error(['Parse error:', e, 'in handler_api500pxGetUser']) 120 | return callback('Cannot parse 500px user data'); 121 | } 122 | 123 | // update user info 124 | this.data.user = 125 | { 126 | id : data.user.id, 127 | username: data.user.username, 128 | // not normalized 129 | _details: data.user 130 | }; 131 | 132 | callback(null, this.data.user); 133 | 134 | }.bind(this)); 135 | } 136 | 137 | // TODO: Should it parsed? (not at the moment) 138 | api_500px.prototype.getPhotos = function api500pxGetPhotos(options_or_page, callback) 139 | { 140 | var params; 141 | 142 | // check for user data 143 | if (!this.data.user) 144 | { 145 | return callback('Missing user data'); 146 | } 147 | 148 | params = 149 | { 150 | feature : 'user', 151 | user_id : this.data.user.id, 152 | rpp : 100, 153 | page : 1, 154 | image_size: 4 155 | }; 156 | 157 | if (typeof options_or_page == 'object') 158 | { 159 | for (var key in options_or_page) 160 | { 161 | params[key] = options_or_page[key]; 162 | } 163 | } 164 | else 165 | { 166 | params.page = parseInt(options_or_page, 10) || params.page; 167 | } 168 | 169 | this._get(endPoints.photos, params, callback); 170 | } 171 | 172 | api_500px.prototype.getPhotoInfo = function api500pxGetPhotoInfo(id, options, callback) 173 | { 174 | var params= 175 | { 176 | image_size: 4, 177 | tags : 1 178 | }; 179 | 180 | // again go prototypejs style, do magic 181 | if (typeof options == 'function') 182 | { 183 | callback = options; 184 | } 185 | else 186 | { 187 | for (var key in options) 188 | { 189 | params[key] = options[key]; 190 | } 191 | } 192 | 193 | this._get(endPoints.photos+'/'+id, params, function handler_api500pxGetPhotoInfo(err, data) 194 | { 195 | var info; 196 | 197 | if (err) return callback(err); 198 | 199 | try 200 | { 201 | data = JSON.parse(data); 202 | } 203 | catch (e) 204 | { 205 | console.error(['Parse error:', e, 'in handler_api500pxGetPhotoInfo']) 206 | return callback('Cannot parse 500px photo info data'); 207 | } 208 | 209 | // normalize data 210 | info = 211 | { 212 | id : data.photo.id, 213 | title : data.photo.name, 214 | description: data.photo.description, 215 | private : data.photo.privacy, 216 | url : data.photo.image_url, 217 | tags : data.photo.tags, 218 | safety : !data.photo.nsfw, 219 | license : null, 220 | dates : data.photo.taken_at, 221 | // exif : data.photo.shutter_speed, 222 | latitude : data.photo.latitude, 223 | longitude : data.photo.longitude 224 | }; 225 | 226 | callback(null, info, data); 227 | }); 228 | } 229 | 230 | 231 | // TODO: This one goes to refactoring for sure 232 | api_500px.prototype.uploadPhoto = function api500pxUploadPhoto(photo, options, callback) 233 | { 234 | var params; 235 | 236 | if (typeof options == 'function') 237 | { 238 | callback = options; 239 | options = null; 240 | } 241 | 242 | // Justin Case 243 | options = options || {}; 244 | 245 | // get essentials 246 | params = 247 | { 248 | name: options.title || '', 249 | description: options.description || '', 250 | category: 0, 251 | privacy: options.private || 0 252 | } 253 | 254 | // TODO: add extra 255 | // for (var key in options) 256 | // { 257 | // params[key] = options[key]; 258 | // } 259 | 260 | // send post to get upload key 261 | this._post(endPoints.photos, params, function handler_api500pxUploadPhoto(err, data) 262 | { 263 | 264 | if (err) return callback(err); 265 | 266 | try 267 | { 268 | data = JSON.parse(data); 269 | } 270 | catch (e) 271 | { 272 | console.error(['Parse error:', e, 'in handler_api500pxGetPhotoInfo']) 273 | return callback('Cannot parse 500px upload photo data'); 274 | } 275 | 276 | // notify stalkers 277 | this.emit('uploadkey', data); 278 | 279 | // start uploading 280 | this.upload(photo, data, callback) 281 | 282 | }.bind(this)); 283 | } 284 | 285 | // TODO: This one goes to refactoring for sure 286 | api_500px.prototype.upload = function api500pxUpload(photo, info, callback) 287 | { 288 | var form = new FormData(); 289 | 290 | form.append('photo_id', ''+info.photo.id); 291 | form.append('consumer_key', this.OA._consumerKey); 292 | form.append('access_key', this.data.access_token); 293 | form.append('upload_key', info.upload_key); 294 | form.append('file', request(photo)); 295 | 296 | form.submit(endPoints.upload, function handler_api500pxUpload(err, res) 297 | { 298 | var body = ''; 299 | 300 | if (err) return callback(err); 301 | 302 | res.setEncoding('utf8'); 303 | 304 | res.on('data', function(data) 305 | { 306 | body += data; 307 | }); 308 | 309 | res.on('end', function() 310 | { 311 | try 312 | { 313 | body = JSON.parse(body); 314 | } 315 | catch (e) 316 | { 317 | console.error(['Parse error:', e, 'in handler_api500pxUpload']); 318 | return callback('Cannot parse 500px upload photo response'); 319 | } 320 | 321 | // serious check 322 | if (body.error != 'None.') return callback(body.status); 323 | 324 | // finally everything is good 325 | callback(null, {message: body.status, photo: info.photo}); 326 | }); 327 | 328 | }); 329 | } 330 | 331 | /* 332 | * Tool functions 333 | */ 334 | 335 | // TODO: Streaming JSON parser? 336 | api_500px.prototype._get = function api500pxGet(url, options, callback) 337 | { 338 | // again go prototypejs style, do magic 339 | if (typeof options == 'function') 340 | { 341 | callback = options; 342 | } 343 | else 344 | { 345 | url += '?' + queryString.stringify(options); 346 | } 347 | 348 | return this.OA.get(url, this.data.access_token, this.data.access_secret, callback); 349 | } 350 | 351 | api_500px.prototype._post = function api500pxPost(url, body, contentType, callback) 352 | { 353 | // check for callback 354 | if (typeof body == 'function') 355 | { 356 | callback = body; 357 | body = null; 358 | } 359 | else if (typeof contentType == 'function') 360 | { 361 | callback = contentType; 362 | contentType = null; 363 | } 364 | 365 | // content type 366 | contentType = contentType || 'application/json'; 367 | 368 | // {{{ Guys, you just killing me 369 | if (body) url += '?' + ((typeof body == 'object') ? queryString.stringify(body) : body); 370 | 371 | // Seriously? POST method? 372 | return this.OA.post(url, this.data.access_token, this.data.access_secret, null, contentType, callback); 373 | // }}} 374 | 375 | // TODO: Don't use until 500px fix their API 376 | // return this.OA.post(url, this.data.access_token, this.data.access_secret, body, contentType, callback); 377 | } 378 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api_500px", 3 | "version": "0.0.3", 4 | "description": "500px API helper, including OAuth and upload", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "node test/run.js" 8 | }, 9 | "engines": { 10 | "node": ">= 0.6" 11 | }, 12 | "repository": "git@github.com:alexindigo/node-api_500px.git", 13 | "keywords": [ 14 | "500px", 15 | "api", 16 | "oauth", 17 | "upload" 18 | ], 19 | "author": "Alex Indigo ", 20 | "license": "MIT", 21 | "devDependencies": { 22 | "utest": "~0.0.8", 23 | "urun": "~0.0.6" 24 | }, 25 | "dependencies": { 26 | "oauth": "~0.9.10", 27 | "form-data": "~0.1.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/run.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('urun')(__dirname); 3 | -------------------------------------------------------------------------------- /test/unit/test-authRequest.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | , test = require('utest') 3 | 4 | // package thingy 5 | , Api500px = require('../../index') 6 | , api // instance 7 | , bareFunction = function(){} 8 | ; 9 | 10 | test('api_500px-authRequest', 11 | { 12 | before: function() 13 | { 14 | api = new Api500px( 15 | { 16 | key: 'api_test_key_token', 17 | secret: 'api_test_secret_token', 18 | callback: 'http://api_test_callback_url' 19 | }); 20 | }, 21 | 22 | 'request auth token': function() 23 | { 24 | // override OAuth 25 | api.OA.getOAuthRequestToken = function(callback) 26 | { 27 | assert.ok(typeof callback == 'function'); 28 | 29 | // emulate return data 30 | callback(null, 'api_test_auth_key_token', 'api_test_auth_secret_token', {status: 'ok'}); 31 | }; 32 | 33 | api.authRequest(function(err, auth_token, auth_secret, results) 34 | { 35 | // check return values 36 | assert.ifError(err); 37 | assert.equal(auth_token, 'api_test_auth_key_token'); 38 | assert.equal(auth_secret, 'api_test_auth_secret_token'); 39 | assert.equal(results.status, 'ok'); 40 | 41 | // check stored values 42 | assert.equal(api.data.auth_token, 'api_test_auth_key_token'); 43 | assert.equal(api.data.auth_secret, 'api_test_auth_secret_token'); 44 | }); 45 | } 46 | 47 | }); 48 | -------------------------------------------------------------------------------- /test/unit/test-getAccessToken.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | , test = require('utest') 3 | 4 | // package thingy 5 | , Api500px = require('../../index') 6 | , api // instance 7 | , bareFunction = function(){} 8 | ; 9 | 10 | test('api_500px-getAccessToken', 11 | { 12 | before: function() 13 | { 14 | api = new Api500px( 15 | { 16 | key: 'api_test_key_token', 17 | secret: 'api_test_secret_token', 18 | callback: 'http://api_test_callback_url' 19 | }); 20 | }, 21 | 22 | 'get access token': function() 23 | { 24 | // set auth_token 25 | api.data.auth_token = 'api_test_auth_key_token'; 26 | api.data.auth_secret = 'api_test_auth_secret_token'; 27 | 28 | // override OAuth 29 | api.OA.getOAuthAccessToken = function(token, secret, verifier, callback) 30 | { 31 | assert.equal(token, 'api_test_auth_key_token'); 32 | assert.equal(secret, 'api_test_auth_secret_token'); 33 | assert.equal(verifier, 'api_test_auth_verifier_token'); 34 | assert.ok(typeof callback == 'function'); 35 | 36 | // emulate return data 37 | callback(null, 'api_test_access_key_token', 'api_test_access_secret_token', {user: 'user'}); 38 | }; 39 | 40 | api.getAccessToken('api_test_auth_verifier_token', function(err, access_token, access_secret, results) 41 | { 42 | // check return values 43 | assert.ifError(err); 44 | assert.equal(access_token, 'api_test_access_key_token'); 45 | assert.equal(access_secret, 'api_test_access_secret_token'); 46 | assert.equal(results.user, 'user'); 47 | 48 | // check stored values 49 | assert.equal(api.data.access_token, 'api_test_access_key_token'); 50 | assert.equal(api.data.access_secret, 'api_test_access_secret_token'); 51 | assert.equal(api.data._details.user, 'user'); 52 | }); 53 | } 54 | 55 | }); 56 | -------------------------------------------------------------------------------- /test/unit/test-getPhotoInfo.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | , test = require('utest') 3 | 4 | // package thingy 5 | , Api500px = require('../../index') 6 | , api // instance 7 | , bareFunction = function(){} 8 | 9 | , photoInfoJSON = '{"photo":{"id":12539831,"user_id":13,"name":"Toronto panorama","description":"Taken in Toronto, Ontario @ March 16, 2008","camera":"Canon EOS 20D","lens":"","focal_length":"100","iso":"100","shutter_speed":"5","aperture":"8","times_viewed":5,"rating":0.0,"status":1,"created_at":"2012-08-26T14:29:43-04:00","category":9,"location":null,"privacy":false,"latitude":43.685650966634206,"longitude":-79.41691160202026,"taken_at":"2008-03-15T00:00:00-04:00","hi_res_uploaded":2,"for_sale":true,"width":9723,"height":2617,"votes_count":0,"favorites_count":0,"comments_count":0,"nsfw":false,"sales_count":0,"for_sale_date":null,"highest_rating":0.0,"highest_rating_date":null,"tags":["toronto","night","skyline"],"image_url":"http://pcdn.500px.net/12539831/805beb838eafaf9253ff176b366af6f9fb31d593/4.jpg","images":[{"size":"4","url":"http://pcdn.500px.net/12539831/805beb838eafaf9253ff176b366af6f9fb31d593/4.jpg"}],"store_download":true,"store_print":true,"voted":false,"favorited":false,"purchased":false,"user":{"id":13,"username":"alexindigo","firstname":"Alex","lastname":"Indigo","city":"Palo Alto","country":"United States","fullname":"Alex Indigo","userpic_url":"http://500px.com/graphics/userpic.png","upgrade_status":2}},"comments":[]}' 10 | , photoInfoData = 11 | { 12 | id : 12539831, 13 | title : 'Toronto panorama', 14 | description: 'Taken in Toronto, Ontario @ March 16, 2008', 15 | private : false, 16 | url : 'http://pcdn.500px.net/12539831/805beb838eafaf9253ff176b366af6f9fb31d593/4.jpg', 17 | tags : [ 'toronto', 'night', 'skyline' ], 18 | safety : true, 19 | license : null, 20 | dates : '2008-03-15T00:00:00-04:00', 21 | latitude : 43.685650966634206, 22 | longitude : -79.41691160202026 23 | } 24 | , photoInfoRaw = 25 | { 26 | photo: 27 | { 28 | id: 12539831, 29 | user_id: 13, 30 | name: 'Toronto panorama', 31 | description: 'Taken in Toronto, Ontario @ March 16, 2008', 32 | camera: 'Canon EOS 20D', 33 | lens: '', 34 | focal_length: '100', 35 | iso: '100', 36 | shutter_speed: '5', 37 | aperture: '8', 38 | times_viewed: 5, 39 | rating: 0, 40 | status: 1, 41 | created_at: '2012-08-26T14:29:43-04:00', 42 | category: 9, 43 | location: null, 44 | privacy: false, 45 | latitude: 43.685650966634206, 46 | longitude: -79.41691160202026, 47 | taken_at: '2008-03-15T00:00:00-04:00', 48 | hi_res_uploaded: 2, 49 | for_sale: true, 50 | width: 9723, 51 | height: 2617, 52 | votes_count: 0, 53 | favorites_count: 0, 54 | comments_count: 0, 55 | nsfw: false, 56 | sales_count: 0, 57 | for_sale_date: null, 58 | highest_rating: 0, 59 | highest_rating_date: null, 60 | tags: 61 | [ 62 | 'toronto', 63 | 'night', 64 | 'skyline' 65 | ], 66 | image_url: 'http://pcdn.500px.net/12539831/805beb838eafaf9253ff176b366af6f9fb31d593/4.jpg', 67 | images: 68 | [ 69 | { 70 | size: '4', 71 | url: 'http://pcdn.500px.net/12539831/805beb838eafaf9253ff176b366af6f9fb31d593/4.jpg' 72 | } 73 | ], 74 | store_download: true, 75 | store_print: true, 76 | voted: false, 77 | favorited: false, 78 | purchased: false, 79 | user: 80 | { 81 | id: 13, 82 | username: 'alexindigo', 83 | firstname: 'Alex', 84 | lastname: 'Indigo', 85 | city: 'Palo Alto', 86 | country: 'United States', 87 | fullname: 'Alex Indigo', 88 | userpic_url: 'http://500px.com/graphics/userpic.png', 89 | upgrade_status: 2 90 | } 91 | }, 92 | comments: [] 93 | } 94 | ; 95 | 96 | test('api_500px-getPhotoInfo', 97 | { 98 | before: function() 99 | { 100 | api = new Api500px( 101 | { 102 | key: 'api_test_key_token', 103 | secret: 'api_test_secret_token', 104 | callback: 'http://api_test_callback_url' 105 | }); 106 | }, 107 | 108 | 'get photo info, simple': function() 109 | { 110 | // set access_token 111 | api.data.access_token = 'api_test_access_key_token'; 112 | api.data.access_secret = 'api_test_access_key_token'; 113 | 114 | // override OAuth 115 | api.OA.get = function(url, token, secret, callback) 116 | { 117 | assert.equal(url, 'https://api.500px.com/v1/photos/12539831?image_size=4&tags=1'); 118 | assert.equal(token, 'api_test_access_key_token'); 119 | assert.equal(secret, 'api_test_access_key_token'); 120 | assert.ok(typeof callback == 'function'); 121 | 122 | // emulate return data 123 | callback(null, photoInfoJSON); 124 | }; 125 | 126 | api.getPhotoInfo(12539831, function(err, info) 127 | { 128 | // check return values 129 | assert.ifError(err); 130 | assert.deepEqual(info, photoInfoData); 131 | }); 132 | 133 | }, 134 | 135 | 'get photo info, advanced': function() 136 | { 137 | var params = 138 | { 139 | image_size: 2, 140 | tags: 0, 141 | comments: 1, 142 | comments_page: 4, 143 | extra: 'params' 144 | }; 145 | 146 | // set access_token 147 | api.data.access_token = 'api_test_access_key_token'; 148 | api.data.access_secret = 'api_test_access_key_token'; 149 | 150 | // override OAuth 151 | api.OA.get = function(url, token, secret, callback) 152 | { 153 | assert.equal(url, 'https://api.500px.com/v1/photos/12539831?image_size=2&tags=0&comments=1&comments_page=4&extra=params'); 154 | assert.equal(token, 'api_test_access_key_token'); 155 | assert.equal(secret, 'api_test_access_key_token'); 156 | assert.ok(typeof callback == 'function'); 157 | 158 | // emulate return data 159 | callback(null, photoInfoJSON); 160 | }; 161 | 162 | api.getPhotoInfo(12539831, params, function(err, info, raw) 163 | { 164 | // check return values 165 | assert.ifError(err); 166 | assert.deepEqual(info, photoInfoData); 167 | 168 | // check raw data 169 | assert.deepEqual(raw, photoInfoRaw); 170 | }); 171 | 172 | } 173 | }); 174 | -------------------------------------------------------------------------------- /test/unit/test-getPhotos.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | , test = require('utest') 3 | 4 | // package thingy 5 | , Api500px = require('../../index') 6 | , api // instance 7 | , bareFunction = function(){} 8 | 9 | , photosJSON = '{"feature":"user","filters":{"category":false,"exclude":false,"user_id":"13"},"current_page":1,"total_pages":20,"total_items":159,"photos":[{"id":12539831,"name":"Toronto panorama","description":"Taken in Toronto, Ontario @ March 16, 2008","times_viewed":5,"rating":0.0,"created_at":"2012-08-26T14:29:43-04:00","category":9,"privacy":false,"width":9723,"height":2617,"votes_count":0,"favorites_count":0,"comments_count":0,"nsfw":false,"image_url":"http://pcdn.500px.net/12539831/805beb838eafaf9253ff176b366af6f9fb31d593/4.jpg","images":[{"size":"4","url":"http://pcdn.500px.net/12539831/805beb838eafaf9253ff176b366af6f9fb31d593/4.jpg"}]},{"id":11728095,"name":"Party","description":"","times_viewed":2,"rating":59.7,"created_at":"2012-08-15T01:41:32-04:00","category":6,"privacy":false,"width":2000,"height":1125,"votes_count":1,"favorites_count":1,"comments_count":1,"nsfw":false,"image_url":"http://pcdn.500px.net/11728095/31b8ff6b706b4146180213640fca8d039b184ebd/4.jpg","images":[{"size":"4","url":"http://pcdn.500px.net/11728095/31b8ff6b706b4146180213640fca8d039b184ebd/4.jpg"}]},{"id":11728081,"name":"Party","description":"","times_viewed":1,"rating":0,"created_at":"2012-08-15T01:40:51-04:00","category":7,"privacy":false,"width":2000,"height":1125,"votes_count":0,"favorites_count":0,"comments_count":0,"nsfw":false,"image_url":"http://pcdn.500px.net/11728081/4eb6c6c3b98ec6681441b5571e6f51969a186986/4.jpg","images":[{"size":"4","url":"http://pcdn.500px.net/11728081/4eb6c6c3b98ec6681441b5571e6f51969a186986/4.jpg"}]}]}' 10 | ; 11 | 12 | test('api_500px-getPhotos', 13 | { 14 | before: function() 15 | { 16 | api = new Api500px( 17 | { 18 | key: 'api_test_key_token', 19 | secret: 'api_test_secret_token', 20 | callback: 'http://api_test_callback_url' 21 | }); 22 | }, 23 | 24 | 'get photos, simple': function() 25 | { 26 | // set access_token 27 | api.data.access_token = 'api_test_access_key_token'; 28 | api.data.access_secret = 'api_test_access_key_token'; 29 | 30 | // set user data 31 | api.data.user = {id: 13}; 32 | 33 | // override OAuth 34 | api.OA.get = function(url, token, secret, callback) 35 | { 36 | assert.equal(url, 'https://api.500px.com/v1/photos?feature=user&user_id=13&rpp=100&page=1&image_size=4'); 37 | assert.equal(token, 'api_test_access_key_token'); 38 | assert.equal(secret, 'api_test_access_key_token'); 39 | assert.ok(typeof callback == 'function'); 40 | 41 | // emulate return data 42 | callback(null, photosJSON); 43 | }; 44 | 45 | api.getPhotos(1, function(err, photos) 46 | { 47 | // check return values 48 | assert.ifError(err); 49 | assert.equal(photos, photosJSON); 50 | }); 51 | 52 | }, 53 | 54 | 'get photos, advanced': function() 55 | { 56 | var params = 57 | { 58 | feature: 'popular', 59 | user_id: 26, 60 | rpp: 50, 61 | page: 4, 62 | image_size: 2, 63 | extra: 'params' 64 | }; 65 | 66 | // set access_token 67 | api.data.access_token = 'api_test_access_key_token'; 68 | api.data.access_secret = 'api_test_access_key_token'; 69 | 70 | // set user data 71 | api.data.user = {id: 13}; 72 | 73 | // override OAuth 74 | api.OA.get = function(url, token, secret, callback) 75 | { 76 | assert.equal(url, 'https://api.500px.com/v1/photos?feature=popular&user_id=26&rpp=50&page=4&image_size=2&extra=params'); 77 | assert.equal(token, 'api_test_access_key_token'); 78 | assert.equal(secret, 'api_test_access_key_token'); 79 | assert.ok(typeof callback == 'function'); 80 | 81 | // emulate return data 82 | callback(null, 'advanced'+photosJSON); 83 | }; 84 | 85 | api.getPhotos(params, function(err, photos) 86 | { 87 | // check return values 88 | assert.ifError(err); 89 | assert.equal(photos, 'advanced'+photosJSON); 90 | }); 91 | 92 | } 93 | }); 94 | -------------------------------------------------------------------------------- /test/unit/test-getUser.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | , test = require('utest') 3 | 4 | // package thingy 5 | , Api500px = require('../../index') 6 | , api // instance 7 | , bareFunction = function(){} 8 | 9 | , userJSON = '{"user":{"id":13,"username":"alexindigo","firstname":"Alex","lastname":"Indigo","birthday":null,"sex":1,"city":"Palo Alto","state":"CA","country":"United States","registration_date":"1070-01-01T01:02:03-05:00","about":"Hallo, Earthlings!","domain":"alexindigo.500px.com","upgrade_status":2,"fotomoto_on":true,"locale":"en","show_nude":true,"store_on":true,"contacts":{"website":"www.alexindigo.com","flickr":"alexindigo","twitter":"alexindigo"},"equipment":{"camera":["iPhone 4","Canon 350D","Canon 20D"],"lens":["Hipstamatic","Canon 100-300mm f/5.6 L","Canon 50mm f/1.8","Canon 17-85mm f/4-5.6","Sigma 18-125mm f/3.5-5.6","Sigma 24-70mm f/2.8"]},"fullname":"Alex Indigo","userpic_url":"http://500px.com/graphics/userpic.png","email":"user@example.com","photos_count":159,"affection":992,"in_favorites_count":221,"friends_count":857,"followers_count":336,"upload_limit":null,"upload_limit_expiry":"2032-04-17T11:27:36-04:00","upgrade_status_expiry":"2033-12-25","auth":{"facebook":0,"twitter":0}}}' 10 | ; 11 | 12 | test('api_500px-getUser', 13 | { 14 | before: function() 15 | { 16 | api = new Api500px( 17 | { 18 | key: 'api_test_key_token', 19 | secret: 'api_test_secret_token', 20 | callback: 'http://api_test_callback_url' 21 | }); 22 | }, 23 | 24 | 'get user object': function() 25 | { 26 | // set access_token 27 | api.data.access_token = 'api_test_access_key_token'; 28 | api.data.access_secret = 'api_test_access_key_token'; 29 | 30 | // override OAuth 31 | api.OA.get = function(url, token, secret, callback) 32 | { 33 | assert.equal(url, 'https://api.500px.com/v1/users'); 34 | assert.equal(token, 'api_test_access_key_token'); 35 | assert.equal(secret, 'api_test_access_key_token'); 36 | assert.ok(typeof callback == 'function'); 37 | 38 | // emulate return data 39 | callback(null, userJSON); 40 | }; 41 | 42 | api.getUser(function(err, userData) 43 | { 44 | // check return values 45 | assert.ifError(err); 46 | assert.equal(userData.id, 13); 47 | assert.equal(userData.username, 'alexindigo'); 48 | assert.equal(userData._details.city, 'Palo Alto'); 49 | 50 | // check stored values 51 | assert.equal(api.data.user.id, 13); 52 | assert.equal(api.data.user.username, 'alexindigo'); 53 | assert.equal(api.data.user._details.about, 'Hallo, Earthlings!'); 54 | }); 55 | 56 | } 57 | }); 58 | -------------------------------------------------------------------------------- /test/unit/test-init.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | , test = require('utest') 3 | 4 | // package thingy 5 | , Api500px = require('../../index') 6 | , api // instance 7 | , bareFunction = function(){} 8 | ; 9 | 10 | test('api_500px-init', 11 | { 12 | before: function() 13 | { 14 | api = new Api500px( 15 | { 16 | key: 'api_test_key_token', 17 | secret: 'api_test_secret_token', 18 | callback: 'http://api_test_callback_url' 19 | }); 20 | }, 21 | 22 | 'check api instance': function() 23 | { 24 | assert.equal(api.OA._requestUrl, 'https://api.500px.com/v1/oauth/request_token'); 25 | assert.equal(api.OA._accessUrl, 'https://api.500px.com/v1/oauth/access_token'); 26 | assert.equal(api.OA._consumerKey, 'api_test_key_token'); 27 | assert.equal(api.OA._consumerSecret, 'api_test_secret_token'); 28 | assert.equal(api.OA._version, '1.0A'); 29 | assert.equal(api.OA._authorize_callback, 'http://api_test_callback_url'); 30 | assert.equal(api.OA._signatureMethod, 'HMAC-SHA1'); 31 | assert.equal(api.OA._oauthParameterSeperator, ', '); 32 | } 33 | 34 | }); 35 | -------------------------------------------------------------------------------- /test/unit/test-tools.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | , test = require('utest') 3 | 4 | // package thingy 5 | , Api500px = require('../../index') 6 | , api // instance 7 | , bareFunction = function(){} 8 | ; 9 | 10 | test('api_500px-tools', 11 | { 12 | before: function() 13 | { 14 | api = new Api500px( 15 | { 16 | key: 'api_test_key_token', 17 | secret: 'api_test_secret_token', 18 | callback: 'http://api_test_callback_url' 19 | }); 20 | }, 21 | 22 | 'check _get, short version': function() 23 | { 24 | // set access_token 25 | api.data.access_token = 'api_test_access_key_token'; 26 | api.data.access_secret = 'api_test_access_key_token'; 27 | 28 | // override OAuth 29 | api.OA.get = function(url, token, secret, callback) 30 | { 31 | assert.equal(url, 'api_test_get_url'); 32 | assert.equal(token, 'api_test_access_key_token'); 33 | assert.equal(secret, 'api_test_access_key_token'); 34 | assert.ok(typeof callback == 'function'); 35 | 36 | return 'api_test_get_return'; 37 | }; 38 | 39 | var r = api._get('api_test_get_url', bareFunction); 40 | 41 | // check return values 42 | assert.equal(r, 'api_test_get_return'); 43 | }, 44 | 45 | 'check _get, long version': function() 46 | { 47 | // set access_token 48 | api.data.access_token = 'api_test_access_key_token'; 49 | api.data.access_secret = 'api_test_access_key_token'; 50 | 51 | // override OAuth 52 | api.OA.get = function(url, token, secret, callback) 53 | { 54 | assert.equal(url, 'api_test_get_url?param1=value1¶m2=value2'); 55 | assert.equal(token, 'api_test_access_key_token'); 56 | assert.equal(secret, 'api_test_access_key_token'); 57 | assert.ok(typeof callback == 'function'); 58 | 59 | return 'api_test_get_return'; 60 | }; 61 | 62 | var r = api._get('api_test_get_url', {param1: 'value1', param2: 'value2'}, bareFunction); 63 | 64 | // check return values 65 | assert.equal(r, 'api_test_get_return'); 66 | }, 67 | 68 | 'check _post, short version': function() 69 | { 70 | // set access_token 71 | api.data.access_token = 'api_test_access_key_token'; 72 | api.data.access_secret = 'api_test_access_key_token'; 73 | 74 | // override OAuth 75 | api.OA.post = function(url, token, secret, body, contentType, callback) 76 | { 77 | assert.equal(url, 'api_test_post_url'); 78 | assert.equal(token, 'api_test_access_key_token'); 79 | assert.equal(secret, 'api_test_access_key_token'); 80 | assert.equal(body, null); 81 | assert.equal(contentType, 'application/json'); 82 | assert.ok(typeof callback == 'function'); 83 | 84 | return 'api_test_post_return'; 85 | }; 86 | 87 | var r = api._post('api_test_post_url', bareFunction); 88 | 89 | // check return values 90 | assert.equal(r, 'api_test_post_return'); 91 | }, 92 | 93 | 'check _post, short +body version': function() 94 | { 95 | // set access_token 96 | api.data.access_token = 'api_test_access_key_token'; 97 | api.data.access_secret = 'api_test_access_key_token'; 98 | 99 | // override OAuth 100 | api.OA.post = function(url, token, secret, body, contentType, callback) 101 | { 102 | // Commented out until 500px fix their API 103 | //assert.equal(url, 'api_test_post_url'); 104 | assert.equal(url, 'api_test_post_url?param=value'); 105 | assert.equal(token, 'api_test_access_key_token'); 106 | assert.equal(secret, 'api_test_access_key_token'); 107 | //assert.equal(body.param, 'value'); 108 | assert.equal(contentType, 'application/json'); 109 | assert.ok(typeof callback == 'function'); 110 | 111 | return 'api_test_post_return'; 112 | }; 113 | 114 | var r = api._post('api_test_post_url', {param: 'value'}, bareFunction); 115 | 116 | // check return values 117 | assert.equal(r, 'api_test_post_return'); 118 | }, 119 | 120 | 'check _post, full version': function() 121 | { 122 | // set access_token 123 | api.data.access_token = 'api_test_access_key_token'; 124 | api.data.access_secret = 'api_test_access_key_token'; 125 | 126 | // override OAuth 127 | api.OA.post = function(url, token, secret, body, contentType, callback) 128 | { 129 | // Commented out until 500px fix their API 130 | //assert.equal(url, 'api_test_post_url'); 131 | assert.equal(url, 'api_test_post_url?api_test_post_body'); 132 | assert.equal(token, 'api_test_access_key_token'); 133 | assert.equal(secret, 'api_test_access_key_token'); 134 | //assert.equal(body, 'api_test_post_body'); 135 | assert.equal(contentType, 'text/xml'); 136 | assert.ok(typeof callback == 'function'); 137 | 138 | return 'api_test_post_return'; 139 | }; 140 | 141 | var r = api._post('api_test_post_url', 'api_test_post_body', 'text/xml', bareFunction); 142 | 143 | // check return values 144 | assert.equal(r, 'api_test_post_return'); 145 | } 146 | }); 147 | -------------------------------------------------------------------------------- /test/unit/test-uploadPhoto.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | , test = require('utest') 3 | 4 | // package thingy 5 | , Api500px = require('../../index') 6 | , api // instance 7 | , bareFunction = function(){} 8 | 9 | , photoUploadKey = '{"upload_key":"a8728e70694a2f846b63ef1b202d717d","photo":{"id":12572743,"user_id":13,"name":"alexindigo-356","description":"","camera":null,"lens":null,"focal_length":null,"iso":null,"shutter_speed":null,"aperture":null,"times_viewed":0,"rating":null,"status":0,"created_at":"2012-08-27T01:47:15-04:00","category":0,"location":null,"privacy":true,"latitude":null,"longitude":null,"taken_at":null,"hi_res_uploaded":0,"for_sale":false,"width":null,"height":null,"votes_count":0,"favorites_count":0,"comments_count":0,"positive_votes_count":0,"nsfw":false,"sales_count":0,"for_sale_date":null,"highest_rating":0.0,"highest_rating_date":null}}' 10 | , photoInfoData = 11 | { 12 | id : 12539831, 13 | title : 'Toronto panorama', 14 | description : 'Taken in Toronto, Ontario @ March 16, 2008', 15 | private : 0, 16 | url : 'http://pcdn.500px.net/12539831/805beb838eafaf9253ff176b366af6f9fb31d593/4.jpg', 17 | tags : [ 'toronto', 'night', 'skyline' ], 18 | safety : 1, 19 | license : null, 20 | dates : '2008-03-15T00:00:00-04:00', 21 | latitude : 43.685650966634206, 22 | longitude : -79.41691160202026 23 | } 24 | , photoUploadData = 25 | { 26 | id: 12572743, 27 | user_id: 13, 28 | name: 'alexindigo-356', 29 | description: '', 30 | camera: null, 31 | lens: null, 32 | focal_length: null, 33 | iso: null, 34 | shutter_speed: null, 35 | aperture: null, 36 | times_viewed: 0, 37 | rating: null, 38 | status: 0, 39 | created_at: '2012-08-27T01:47:15-04:00', 40 | category: 0, 41 | location: null, 42 | privacy: true, 43 | latitude: null, 44 | longitude: null, 45 | taken_at: null, 46 | hi_res_uploaded: 0, 47 | for_sale: 0, 48 | width: null, 49 | height: null, 50 | votes_count: 0, 51 | favorites_count: 0, 52 | comments_count: 0, 53 | positive_votes_count: 0, 54 | nsfw: 0, 55 | sales_count: 0, 56 | for_sale_date: null, 57 | highest_rating: 0, 58 | highest_rating_date: null 59 | } 60 | ; 61 | 62 | test('api_500px-uploadPhoto', 63 | { 64 | before: function() 65 | { 66 | api = new Api500px( 67 | { 68 | key: 'api_test_key_token', 69 | secret: 'api_test_secret_token', 70 | callback: 'http://api_test_callback_url' 71 | }); 72 | }, 73 | 74 | 'upload photo, get upload key': function() 75 | { 76 | // set access_token 77 | api.data.access_token = 'api_test_access_key_token'; 78 | api.data.access_secret = 'api_test_access_key_token'; 79 | 80 | // override OAuth 81 | api.OA.post = function(url, token, secret, body, contentType, callback) 82 | { 83 | // TODO: Update it from 500px fix their API 84 | assert.equal(url, 'https://api.500px.com/v1/photos?name=Toronto%20panorama&description=Taken%20in%20Toronto%2C%20Ontario%20%40%20March%2016%2C%202008&category=0&privacy=0'); 85 | assert.equal(token, 'api_test_access_key_token'); 86 | assert.equal(secret, 'api_test_access_key_token'); 87 | assert.equal(body, null); 88 | assert.equal(contentType, 'application/json'); 89 | assert.ok(typeof callback == 'function'); 90 | 91 | callback(null, photoUploadKey); 92 | }; 93 | 94 | // override upload 95 | api.upload = function(photo, info, callback) 96 | { 97 | assert.equal(photo, photoInfoData.url); 98 | 99 | // check options 100 | assert.equal(info.upload_key, 'a8728e70694a2f846b63ef1b202d717d'); 101 | assert.deepEqual(info.photo, photoUploadData); 102 | } 103 | 104 | api.uploadPhoto(photoInfoData.url, photoInfoData, bareFunction); 105 | } 106 | 107 | 108 | }); 109 | --------------------------------------------------------------------------------