├── .gitignore ├── AUTHORS ├── README.md ├── generate_docs.sh └── src └── nest.js /.gitignore: -------------------------------------------------------------------------------- 1 | docs 2 | *.html 3 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Reid Draper 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #nest.js 2 | nest.js is a thin javascript wrapper to the Echo Nest 3 | [developer API](http://developer.echonest.com). 4 | 5 | The currently supported methods are: 6 | 7 | ####Artist 8 | 9 | * audio 10 | * biographies 11 | * blogs 12 | * familiarity 13 | * hotttnesss 14 | * images 15 | * profile 16 | * news 17 | * reviews 18 | * songs 19 | * similar 20 | * terms 21 | * video 22 | 23 | ####Track 24 | 25 | * Profile 26 | 27 | ####Song 28 | 29 | * Profile 30 | 31 | To get started, [you'll need to get an API key](http://developer.echonest.com/account/register). 32 | 33 | ## Documentation 34 | In addition to this document, source documentation can be generated 35 | by running `generate_docs.sh`. This requires [docco](http://jashkenas.github.com/docco/). 36 | 37 | ## Usage 38 | `nest.js` provides everything in a global `nest` object. We can create 39 | a new `nest` object like this 40 | 41 | `var myNest = nest.nest("your API key here");` 42 | 43 | 44 | ### Artists 45 | Once you have your `nest` object, you can create a new `artist`. `artist` can 46 | be created with either a name, or an [Echo Nest ID](http://developer.echonest.com/docs/v4/index.html#identifiers). 47 | 48 | var a = myNest.artist({name: "The Sea and Cake"}); 49 | var b = myNest.artist({id: "AR94EZ61187B990729"}); 50 | 51 | Once we have our artist object, we can start calling the API. All methods take a 52 | callback function as their last argument. The callback will be called like `callback(err, results)`. `err` will be null if the request was successful. 53 | 54 | myArtist.biographies({results: 10, start: 5}, function(err, results) { 55 | if (err) { 56 | console.log("there was an error..."); 57 | return; 58 | } 59 | console.log(results); 60 | }); 61 | -------------------------------------------------------------------------------- /generate_docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | docco src/nest.js 4 | -------------------------------------------------------------------------------- /src/nest.js: -------------------------------------------------------------------------------- 1 | // nest.js is a javascript wrapper to the Echo Nest 2 | // [developer api](http://developer.echonest.com). 3 | 4 | // Wrap everything in the `nest` object, as to not clobber the 5 | // global namespace 6 | var nest = (function () { 7 | 8 | // Function to give javascript something closer 9 | // to pure prototypal inhereritance 10 | function clone(obj) { 11 | function F() {} 12 | F.prototype = obj; 13 | return new F; 14 | } 15 | 16 | // Helper function for iterating through 17 | // the keys in an object 18 | function each(obj, func) { 19 | var key; 20 | for (key in obj) { 21 | if (obj.hasOwnProperty(key)) { 22 | func.call(obj, key); 23 | } 24 | } 25 | } 26 | 27 | // Helper function to see if an object is 28 | // really an array. Taken from 29 | // `Javascript: The Good Parts` by 30 | // Douglas Crockford 31 | function isArray(obj) { 32 | return Object.prototype.toString.apply(obj) === '[object Array]'; 33 | } 34 | 35 | // Update `obj` with all of the 36 | // key/values of `source`, not 37 | // following the prototype 38 | // chain 39 | function update(obj, source) { 40 | each(source, function (key) { 41 | obj[key] = source[key]; 42 | }); 43 | return obj; 44 | } 45 | 46 | // return a query string from an object 47 | function queryString(params) { 48 | var query = '?', first = true; 49 | var value; 50 | each(params, function (key) { 51 | var i; 52 | // only prepend `&` when this 53 | // isn't the first k/v pair 54 | if (first) { 55 | first = false; 56 | } else { 57 | query += '&'; 58 | } 59 | value = params[key]; 60 | if (isArray(value)) { 61 | for (i = 0; i < value.length; i += 1) { 62 | query += (encodeURI(key) + '=' + encodeURI(value[i])); 63 | if (i < (value.length - 1)) { 64 | query += '&'; 65 | } 66 | } 67 | } else { 68 | query += (encodeURI(key) + '=' + encodeURI(value)); 69 | } 70 | }); 71 | return query; 72 | } 73 | 74 | 75 | // This is the main object that will be returned 76 | // as `nest` 77 | nest = {}; 78 | nest.nestproto = {}; 79 | nest.artistproto = {}; 80 | nest.trackproto = {}; 81 | nest.songproto = {}; 82 | 83 | nest.nest = function (api_key, host) { 84 | // this is the object that will 85 | // returned 86 | var container = clone(nest.nestproto); 87 | // optionaly take in another host, 88 | // for testing purposes 89 | host = host || "developer.echonest.com"; 90 | var api_path = "/api/v4/"; 91 | 92 | // make HTTP GET requests and call `callbacks.success` 93 | // or `callbacks.error` with the 94 | // response object, or the `status`, on error 95 | function nestGet(category, method, query, callback) { 96 | query.api_key = api_key; 97 | query.format = 'json'; 98 | var request = new XMLHttpRequest(); 99 | var url = 'http://'; 100 | url += host; 101 | url += api_path; 102 | url += category + '/'; 103 | url += method; 104 | url += queryString(query); 105 | 106 | request.open('GET', url, true); 107 | request.onreadystatechange = function () { 108 | var sc, json_response, response; 109 | // see if the response is ready 110 | if (request.readyState === 4) { 111 | // get the request status class, ie. 112 | // 200 == 2, 404 == 4, etc. 113 | sc = Math.floor(request.status / 100); 114 | if (sc === 2 || sc === 3) { 115 | json_response = JSON.parse(request.responseText); 116 | // unwrap the response from the outter 117 | // `response` wrapper 118 | response = json_response.response; 119 | callback(null, response); 120 | return; 121 | } else { 122 | // there was an error, 123 | // just return the `status` 124 | // as the first paramter 125 | callback(request.status); 126 | return; 127 | } 128 | } 129 | }; 130 | // do it 131 | request.send(); 132 | } 133 | 134 | // return the read-only `api_key` 135 | container.getAPIKey = function () { 136 | return api_key; 137 | }; 138 | 139 | // return the read-only `host` name 140 | container.getHost = function () { 141 | return host; 142 | }; 143 | 144 | function creator (type, methods) { 145 | // create a new artist, track, song, catalog, etc.. 146 | // 147 | // 148 | // params should have an 149 | // `id` or `name` property 150 | return function (params) { 151 | var i; 152 | // we'll be attaching functions to this 153 | // object 154 | var protoname = type + 'proto'; 155 | var container = clone(nest[protoname]); 156 | 157 | // add `id` or `name` 158 | // to the container object 159 | update(container, params); 160 | 161 | // Return the best way to identify the container, 162 | // first being an ID, second being a name 163 | container.identify = function () { 164 | return (this.id) ? {id: this.id} : {name: this.name}; 165 | }; 166 | 167 | // helper function for having a closure remember 168 | // a value in when it changes in a loop 169 | function methodCreator(method) { 170 | // this will be the function that 171 | // gets called for each of the methods 172 | // on `container`. It can be called 173 | // with a variable number of arguments, 174 | // the last one always being a callback function. 175 | // The callback function will be called like 176 | // `callback(err, result)` 177 | return function () { 178 | var args = Array.prototype.slice.call(arguments); 179 | var callback = args.pop(); 180 | var query = update({}, container.identify()); 181 | var options = args.pop(); 182 | if (options) { 183 | update(query, options); 184 | } 185 | // TODO: 186 | // maybe this should be called with an object, 187 | // it has a lot of parameters 188 | return nestGet(type, method, query, function (err, results) { 189 | if (err) { 190 | callback(err); 191 | return; 192 | } else { 193 | if (results[type]) { 194 | // If we get a result back that includes 195 | // information about the container, fill it 196 | // in the container object. This means if we 197 | // create an container object with a name, 198 | // but later get back a result that tells 199 | // us the container's ID, we can use it to 200 | // speed up further queries. 201 | container.name = results[type].name; 202 | container.id = results[type].id; 203 | if (method !== 'profile') { 204 | if (type !== 'song') { 205 | callback(err, results[type][method]); 206 | return; 207 | } else { 208 | callback(err, results[type]['songs'][0]); 209 | return; 210 | } 211 | } else { 212 | callback(err, results[type]); 213 | return; 214 | } 215 | } else { 216 | callback(err, results); 217 | return; 218 | } 219 | } 220 | }); 221 | }; 222 | } 223 | // go through and attach a function 224 | // to each of the `artist` methods 225 | for (i = 0; i < methods.length; i += 1) { 226 | var method = methods[i]; 227 | container[method] = methodCreator(method); 228 | } 229 | return container; 230 | }; 231 | } 232 | 233 | function searchHelper(type) { 234 | return function () { 235 | var args = Array.prototype.slice.call(arguments); 236 | var callback = args.pop(); 237 | var params = args.pop() || {}; 238 | nestGet(type, 'search', params, function (err, results) { 239 | var output = []; 240 | // turn `artist` into `artists` 241 | // and `song` into `songs` 242 | var resultKey = type + 's'; 243 | var objects = results[resultKey]; 244 | 245 | if (err) { 246 | callback(err); 247 | return; 248 | } else { 249 | // make the artist or song 250 | // objects out of the results 251 | for (i = 0; i < objects.length; i += 1) { 252 | output.push(container[type](objects[i])); 253 | } 254 | 255 | callback(null, output); 256 | return; 257 | } 258 | // this code path should 259 | // not be reachable but 260 | // it's good style to 261 | // keep it in 262 | return; 263 | }); 264 | }; 265 | } 266 | 267 | var artist_methods = [ 268 | 'audio', 269 | 'biographies', 270 | 'blogs', 271 | 'familiarity', 272 | 'hotttnesss', 273 | 'images', 274 | 'profile', 275 | 'news', 276 | 'reviews', 277 | 'songs', 278 | 'similar', 279 | 'terms', 280 | 'video']; 281 | container.artist = creator("artist", artist_methods); 282 | container.track = creator("track", ["profile"]); 283 | container.song = creator("song", ["profile"]); 284 | container.searchArtists = searchHelper('artist'); 285 | container.searchSongs = searchHelper('song'); 286 | return container; 287 | }; 288 | return nest; 289 | }()); 290 | --------------------------------------------------------------------------------