From 7da4d3b465faf514dbbc452224f52abecfa3fa19 Mon Sep 17 00:00:00 2001 From: Marius V Miliunas Date: Thu, 5 Dec 2013 19:02:46 -0500 Subject: [PATCH] Marius Miliunas - initial commit --- .gitignore | 3 + .travis.yml | 6 ++ README.md | 61 ++++++++++++ lib/soundclouder.coffee | 174 ++++++++++++++++++++++++++++++++ lib/soundclouder.js | 215 ++++++++++++++++++++++++++++++++++++++++ package.json | 38 +++++++ test/config.js | 8 ++ test/test.js | 75 ++++++++++++++ 8 files changed, 580 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 README.md create mode 100644 lib/soundclouder.coffee create mode 100644 lib/soundclouder.js create mode 100644 package.json create mode 100644 test/config.js create mode 100644 test/test.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d93cc57 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +config.dysf.js +.DS_Store +node_modules diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..a62d0db --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ + language: node_js + node_js: + - "0.8" + branches: + only: + - master diff --git a/README.md b/README.md new file mode 100644 index 0000000..6cf11aa --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +Forked off of soundclouder.js [![Build Status](https://api.travis-ci.org/khilnani/soundclouder.js.png?branch=master)](https://travis-ci.org/khilnani/soundclouder.js) +=============== + +Provides seamless modular support for working with SoundCloud and Nodejs + + +SoundCloud APIs Implemented +=============== +- Connection/Authorization Url +- OAuth Authorization (/oauth2/token) +- General GET, PUT, POST and DELETE. + +Usage +============== + +
+var SoundCloudAPI = require("soundcloud-node");
+
+// instantiate the client
+var client = SoundCloudAPI(client_id, client_secret, redirect_uri);
+
+// redirect to the url
+var oauthInit = function(req, res) {
+	var url = client.getConnectUrl();
+
+    res.writeHead(301, Location: url);
+    res.end();
+}
+
+var oauthHandleToken = function(req, res) {
+	var query = req.query;
+
+	client.getToken(query.code, function(err, tokens) {
+        if (err)
+            callback(err);
+        else {
+            callback(null, res);
+        }
+    });
+}
+
+
+client.get('/tracks/' + track_id, function (data) {
+	console.log( data.title );
+});
+
+ + +Links +============ +- Application Setup - http://developers.soundcloud.com/docs/api/guide#authentication +- Error Codes - http://developers.soundcloud.com/docs/api/guide#errors + + +Installation +============ + +Global +--------- +- Run: sudo npm install soundcloud-node -g +- Usually installed at - /usr/local/lib/node_modules/soundcloud-node diff --git a/lib/soundclouder.coffee b/lib/soundclouder.coffee new file mode 100644 index 0000000..c062885 --- /dev/null +++ b/lib/soundclouder.coffee @@ -0,0 +1,174 @@ +https = require("https") +qs = require("querystring") +log = require("dysf.utils").logger +host_api = "api.soundcloud.com" +host_connect = "https://soundcloud.com/connect" + +_getConfig = -> + client_id: @client_id + client_secret: @client_secret + redirect_uri: @redirect_uri + response_type: 'code' + scope: 'non-expiring' + +_makeCall = (method, path, access_token, params, callback) -> + + if path and path.indexOf("/") is 0 + + if typeof (params) is "function" + callback = params + params = {} + + callback = callback or -> + + params = params or + oauth_token: access_token + format: "json" + + _request.call @, + method: method + uri: host_api + path: path + qs: params + , callback + + else + callback(message: "Invalid path: " + path) + false + +_request = (data, callback) -> + qsdata = (if (data.qs) then qs.stringify(data.qs) else "") + options = + hostname: data.uri + path: "#{data.path}?#{qsdata}" + method: data.method + + if data.method is "POST" + options.path = data.path + options.headers = + "Content-Type": "application/x-www-form-urlencoded" + "Content-Length": qsdata.length + + log.debug "Attempting Request: " + options.method + "; " + options.hostname + options.path + + req = https.request options, (response) -> + log.debug "Request executed: " + options.method + "; " + options.hostname + options.path + log.trace "Response http code: " + response.statusCode + log.trace "Response headers: " + JSON.stringify(response.headers) + body = "" + + response.on "data", (chunk) -> + body += chunk + + #log.trace("chunk: " + chunk); + response.on "end", -> + log.trace "Response body: " + body + try + d = JSON.parse(body) + + # See http://developers.soundcloud.com/docs/api/guide#errors for full list of error codes + unless response.statusCode is 200 + log.error "SoundCloud API ERROR: " + response.statusCode + callback d.errors, d + else + log.trace "SoundCloud API OK: " + response.statusCode + callback null, d + catch e + callback e + + req.on "error", (e) -> + log.error "For Request: " + options.method + "; " + options.hostname + options.path + log.error "Request error: " + e.message + callback e + + if data.method is "POST" + log.debug "POST Body: " + qsdata + req.write qsdata + req.end() + +###* + * @class + * @namespace SoundCloud +### +module.exports = class SoundCloud + + ### + * Initialize with client id, client secret and redirect url. + * + * @constructor + * @param {String} client_id + * @param {String} client_secret + * @param {String} redirect_uri + ### + constructor: (client_id, client_secret, redirect_uri) -> + + if not (@ instanceof SoundCloud) then return new SoundCloud(client_id, client_secret, redirect_uri) + + required = [] + + [].slice.call(arguments).forEach (arg) -> + required.push(arg) if not arg? + + if required.length + console.log 'The following arguments are required: ', required + return false + + @client_id = client_id + @client_secret = client_secret + @redirect_uri = redirect_uri + + ### + * Get the url to SoundCloud's authorization/connection page. + * + * @param {Object} options + * @return {String} + ### + getConnectUrl: (options) -> + options = _getConfig.call(@) if not options + + host_connect + "?" + ((if options then qs.stringify(options) else "")) + + setToken: (@access_token) -> + + ### + * Perform authorization with SoundCLoud and obtain OAuth token needed + * + * for subsequent requests. See http://developers.soundcloud.com/docs/api/guide#authentication + * + * @param {String} code sent by the browser based SoundCloud Login that redirects to the redirect_uri + * @param {Function} callback(error, access_token) No token returned if error != null + ### + getToken: (code, callback) -> + options = + uri: host_api + path: "/oauth2/token" + method: "POST" + qs: + client_id: @client_id + client_secret: @client_secret + grant_type: "authorization_code" + redirect_uri: @redirect_uri + code: code + + _request.apply(@, [options, callback]) + + ### + * Make an API call + * + * @param {String} path + * @param {String} access_token + * @param {Object} params + * @param {Function} callback(error, data) + * @return {Request} + ### + get: (path, params, callback) -> + _makeCall.apply(@, ["GET", path, @access_token, params, callback]) + + post: (path, params, callback) -> + _makeCall.apply(@, ["POST", path, @access_token, params, callback]) + + put: (path, params, callback) -> + _makeCall.apply(@, ["PUT", path, @access_token, params, callback]) + + delete: (path, params, callback) -> + _makeCall.apply(@, ["DELETE", path, @access_token, params, callback]) \ No newline at end of file diff --git a/lib/soundclouder.js b/lib/soundclouder.js new file mode 100644 index 0000000..cbadb95 --- /dev/null +++ b/lib/soundclouder.js @@ -0,0 +1,215 @@ +// Generated by CoffeeScript 1.6.3 +var SoundCloud, host_api, host_connect, https, log, qs, _getConfig, _makeCall, _request; + +https = require("https"); + +qs = require("querystring"); + +log = require("dysf.utils").logger; + +host_api = "api.soundcloud.com"; + +host_connect = "https://soundcloud.com/connect"; + +_getConfig = function() { + return { + client_id: this.client_id, + client_secret: this.client_secret, + redirect_uri: this.redirect_uri, + response_type: 'code', + scope: 'non-expiring' + }; +}; + +_makeCall = function(method, path, access_token, params, callback) { + if (path && path.indexOf("/") === 0) { + if (typeof params === "function") { + callback = params; + params = {}; + } + callback = callback || function() {}; + params = params || { + oauth_token: access_token, + format: "json" + }; + return _request.call(this, { + method: method, + uri: host_api, + path: path, + qs: params + }, callback); + } else { + callback({ + message: "Invalid path: " + path + }); + return false; + } +}; + +_request = function(data, callback) { + var options, qsdata, req; + qsdata = (data.qs ? qs.stringify(data.qs) : ""); + options = { + hostname: data.uri, + path: "" + data.path + "?" + qsdata, + method: data.method + }; + if (data.method === "POST") { + options.path = data.path; + options.headers = { + "Content-Type": "application/x-www-form-urlencoded", + "Content-Length": qsdata.length + }; + } + log.debug("Attempting Request: " + options.method + "; " + options.hostname + options.path); + req = https.request(options, function(response) { + var body; + log.debug("Request executed: " + options.method + "; " + options.hostname + options.path); + log.trace("Response http code: " + response.statusCode); + log.trace("Response headers: " + JSON.stringify(response.headers)); + body = ""; + response.on("data", function(chunk) { + return body += chunk; + }); + return response.on("end", function() { + var d, e; + log.trace("Response body: " + body); + try { + d = JSON.parse(body); + if (response.statusCode !== 200) { + log.error("SoundCloud API ERROR: " + response.statusCode); + return callback(d.errors, d); + } else { + log.trace("SoundCloud API OK: " + response.statusCode); + return callback(null, d); + } + } catch (_error) { + e = _error; + return callback(e); + } + }); + }); + req.on("error", function(e) { + log.error("For Request: " + options.method + "; " + options.hostname + options.path); + log.error("Request error: " + e.message); + return callback(e); + }); + if (data.method === "POST") { + log.debug("POST Body: " + qsdata); + req.write(qsdata); + } + return req.end(); +}; + +/** + * @class + * @namespace SoundCloud +*/ + + +module.exports = SoundCloud = (function() { + /* + * Initialize with client id, client secret and redirect url. + * + * @constructor + * @param {String} client_id + * @param {String} client_secret + * @param {String} redirect_uri + */ + + function SoundCloud(client_id, client_secret, redirect_uri) { + var required; + if (!(this instanceof SoundCloud)) { + return new SoundCloud(client_id, client_secret, redirect_uri); + } + required = []; + [].slice.call(arguments).forEach(function(arg) { + if (arg == null) { + return required.push(arg); + } + }); + if (required.length) { + console.log('The following arguments are required: ', required); + return false; + } + this.client_id = client_id; + this.client_secret = client_secret; + this.redirect_uri = redirect_uri; + } + + /* + * Get the url to SoundCloud's authorization/connection page. + * + * @param {Object} options + * @return {String} + */ + + + SoundCloud.prototype.getConnectUrl = function(options) { + if (!options) { + options = _getConfig.call(this); + } + return host_connect + "?" + (options ? qs.stringify(options) : ""); + }; + + SoundCloud.prototype.setToken = function(access_token) { + this.access_token = access_token; + }; + + /* + * Perform authorization with SoundCLoud and obtain OAuth token needed + * + * for subsequent requests. See http://developers.soundcloud.com/docs/api/guide#authentication + * + * @param {String} code sent by the browser based SoundCloud Login that redirects to the redirect_uri + * @param {Function} callback(error, access_token) No token returned if error != null + */ + + + SoundCloud.prototype.getToken = function(code, callback) { + var options; + options = { + uri: host_api, + path: "/oauth2/token", + method: "POST", + qs: { + client_id: this.client_id, + client_secret: this.client_secret, + grant_type: "authorization_code", + redirect_uri: this.redirect_uri, + code: code + } + }; + return _request.apply(this, [options, callback]); + }; + + /* + * Make an API call + * + * @param {String} path + * @param {String} access_token + * @param {Object} params + * @param {Function} callback(error, data) + * @return {Request} + */ + + + SoundCloud.prototype.get = function(path, params, callback) { + return _makeCall.apply(this, ["GET", path, this.access_token, params, callback]); + }; + + SoundCloud.prototype.post = function(path, params, callback) { + return _makeCall.apply(this, ["POST", path, this.access_token, params, callback]); + }; + + SoundCloud.prototype.put = function(path, params, callback) { + return _makeCall.apply(this, ["PUT", path, this.access_token, params, callback]); + }; + + SoundCloud.prototype["delete"] = function(path, params, callback) { + return _makeCall.apply(this, ["DELETE", path, this.access_token, params, callback]); + }; + + return SoundCloud; + +})(); diff --git a/package.json b/package.json new file mode 100644 index 0000000..62826a7 --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ +{ + "name": "soundcloud-node", + "description": "SoundCloud API + Node.js", + "version": "0.0.1", + "author": "Marius Miliunas ", + "maintainers": [ + "Marius Miliunas " + ], + "repository": { + "type": "git", + "url": "https://github.com/maruf89/soundcloud-node" + }, + "keywords": [ + "SoundCloud", + "API", + "node.js" + ], + "dependencies": { + "vows": "0.7.0", + "request": "2.x.x", + "dysf.utils": "0.0.9" + }, + "main": "./lib/soundclouder", + "engines": { + "node": ">= 0.8.0" + }, + "scripts": { + "test": "vows --spec" + }, + "bugs": { + "url": "https://github.com/maruf89/soundcloud-node/issues" + }, + "directories": { + "test": "test" + }, + "devDependencies": {}, + "license": "BSD" +} diff --git a/test/config.js b/test/config.js new file mode 100644 index 0000000..132e37c --- /dev/null +++ b/test/config.js @@ -0,0 +1,8 @@ +var config = exports; + +config.sc_client_id = ""; +config.sc_client_secret = ""; +config.sc_redirect_uri = ""; +config.sc_code = ""; +config.oauth_token = ""; + diff --git a/test/test.js b/test/test.js new file mode 100644 index 0000000..7f12026 --- /dev/null +++ b/test/test.js @@ -0,0 +1,75 @@ +var sc = require("../lib/soundclouder"), + request = require("request"), + log = require("dysf.utils").logger + config = require("./config") + vows = require("vows"); + +log.setLogLevel(5); + +var sccode = config.sc_code; +var oauth_token = config.oauth_token; + + +vows.describe('soundclouder.js').addBatch({ + "When using soundclouder ": { + "initialization": { + "should not error": function () { + sc.init(config.sc_client_id, config.sc_client_secret, config.sc_redirect_uri); + var c = sc.getConfig(); + log.info(c); + } + } + } +}).addBatch({ + "When using soundclouder ": { + "authorization": { + "should not error": { + topic: function () { + sc.auth(sccode, this.callback); + }, + 'returns': function (e, token) { + log.event("sc.auth()"); + if(e != null) + { + log.error('sc.auth(): ERROR'); + log.error(e); + } + else + { + log.event('sc.auth(): access_token=' + token ); + } + } + } + } + } +}).addBatch({ + "When using soundclouder ": { + "a get /me API call": { + "should not error": { + topic: function () { + sc.get('/me', oauth_token, this.callback); + }, + 'returns': function(error, data) { + log.event("sc.get() /me"); + if(error) log.error(error.message); + else log.info(data); + } + } + } + } +}).addBatch({ + "When using soundclouder ": { + "a get /track API call": { + "should not error": { + topic: function () { + sc.get('/tracks/103094732', oauth_token, this.callback); + }, + 'returns': function(error, data) { + log.event("sc.get() /tracks/103094732"); + if(error) log.error(error.message); + else log.info(data); + } + } + } + } +}).export(module);