├── README.md
├── lib
├── GraceError.js
└── Gracenote.js
└── package.json
/README.md:
--------------------------------------------------------------------------------
1 | node-gracenote
2 | ==============
3 |
4 | A node.js wrapper for the Gracenote API - https://developer.gracenote.com/web-api
5 |
6 | ## Installation
7 |
8 | ```
9 | npm install node-gracenote
10 | ```
11 |
12 |
13 | ## Gracenote Options
14 |
15 | Request defaults can be sent as an extra parameter to the constructor.
16 | More information can be found at [the Request Github page](https://github.com/mikeal/request#requestdefaultsoptions).
17 |
18 | ```
19 | var api = new Gracenote(clientId,clientTag,userId,requestDefaults);
20 | ```
21 |
22 | ## Register
23 |
24 | Function - `api.register(req callback)`
25 |
26 | ```
27 | var Gracenote = require("node-gracenote");
28 | var clientId = "XXX";
29 | var clientTag = "YYY";
30 | var userId = null;
31 | var api = new Gracenote(clientId,clientTag,userId);
32 | api.register(function(err, uid) {
33 | // store this somewhere for the next session
34 | })`;
35 | ```
36 |
37 | ## Search For Track
38 |
39 | Function - `api.searchTrack(req artistName, req albumTitle, req trackTitle, req callback, opt matchMode)`
40 |
41 | ```
42 | var Gracenote = require("node-gracenote");
43 | var clientId = "XXX";
44 | var clientTag = "YYY";
45 | var userId = "ZZZ";
46 | var api = new Gracenote(clientId,clientTag,userId);
47 | api.searchTrack("Kings of Leon", "Only by the Night", "Sex on fire", function(err, result) {
48 | // Search Result as array
49 | });
50 | ```
51 |
52 | ## Search for Artist
53 |
54 | Function - `api.searchArtist(req artistName, req callback, opt matchMode)`
55 |
56 | ```
57 | var Gracenote = require("node-gracenote");
58 | var clientId = "XXX";
59 | var clientTag = "YYY";
60 | var userId = "ZZZ";
61 | var api = new Gracenote(clientId,clientTag,userId);
62 | api.searchArtist("Kings of Leon", function(err, result) {
63 | // Search Result as array
64 | });
65 | ```
66 |
67 | ## Search for Album
68 |
69 | Function - `api.searchAlbum(req artistName, req albumTitle, req callback, opt matchMode)`
70 |
71 | ```
72 | var Gracenote = require("node-gracenote");
73 | var clientId = "XXX";
74 | var clientTag = "YYY";
75 | var userId = "ZZZ";
76 | var api = new Gracenote(clientId,clientTag,userId);
77 | api.searchAlbum("Kings of Leon", "Only by the Night", function(err, result) {
78 | // Search Result as array
79 | });
80 | ```
81 |
82 | ## Config options
83 |
84 | `matchMode`- can be either `Gracenote.BEST_MATCH_ONLY`or `Gracenote.ALL_RESULTS`(default)
85 |
86 | `setExtended(str)` - let you change the extended data from gracenote - default : COVER,REVIEW,ARTIST_BIOGRAPHY,ARTIST_IMAGE,ARTIST_OET,MOOD,TEMPO
87 | `setCoverSize(str)` - let you change the cover size - default: MEDIUM
88 | `setLanguage(iso)` - pass a language parameter - default: null
89 |
--------------------------------------------------------------------------------
/lib/GraceError.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | //var util = require('util');
4 |
5 | module.exports = GraceError;
6 |
7 | function GraceError(code, msg) {
8 | if (typeof msg !== 'undefined' && msg != null && msg !== '')
9 | msg = ' | ' + msg;
10 | else
11 | msg = '';
12 | var err = new Error(GraceError.MESSAGES[code]+msg);
13 | //GraceError.super_.apply(this, arguments);
14 | err.statusCode = code;
15 |
16 | return err;
17 | };
18 |
19 | GraceError.UNABLE_TO_PARSE_RESPONSE = 1; // The response couldn't be parsed. Maybe an error, or maybe the API changed.
20 | GraceError.API_RESPONSE_ERROR = 1000; // There was a GN error code returned in the response.
21 | GraceError.API_NO_MATCH = 1001; // The API returned a NO_MATCH (i.e. there were no results).
22 | GraceError.API_NON_OK_RESPONSE = 1002; // There was some unanticipated non-"OK" response from the API.
23 | GraceError.HTTP_REQUEST_ERROR = 2000; // An uncaught exception was raised while doing a cURL request.
24 | GraceError.HTTP_REQUEST_TIMEOUT = 2001; // The external request timed out.
25 | GraceError.HTTP_RESPONSE_ERROR_CODE = 2002; // There was a HTTP400 error code returned.
26 | GraceError.HTTP_RESPONSE_ERROR = 2003; // A cURL error that wasn't a timeout or HTTP400 response.
27 | GraceError.INVALID_INPUT_SPECIFIED = 3000; // Some input the user gave wasn't valid.
28 | GraceError.RANGE_NOT_VALID = 4000;
29 | GraceError.MESSAGES = {
30 | // Generic Errors
31 | 1 : "Unable to parse response from Gracenote WebAPI.",
32 | // Specific API Errors
33 | 1000 : "The API returned an error code.",
34 | 1001 : "The API returned no results.",
35 | 1002 : "The API returned an unacceptable response.",
36 | // HTTP Errors
37 | 2000 : "There was an error while performing an external request.",
38 | 2001 : "Request to a Gracenote WebAPI timed out.",
39 | 2002 : "WebAPI response had a HTTP error code.",
40 | 2003 : "cURL returned an error when trying to make the request.",
41 | // Input Errors
42 | 3000 : "Invalid input.",
43 | 4000 : "Range was not valid (needs start & end)"
44 | };
45 |
46 | //util.inherits(GraceError, Error);
--------------------------------------------------------------------------------
/lib/Gracenote.js:
--------------------------------------------------------------------------------
1 | var GraceError = require('./GraceError'),
2 | request = require('request'),
3 | qs = require('querystring'),
4 | parseString = require('xml2js').parseString,
5 | cache = require('memory-cache'),
6 | extend = require('util')._extend,
7 | API_URL = 'https://c[[CLID]].web.cddbp.net/webapi/xml/1.0/';
8 |
9 | module.exports = Gracenote;
10 |
11 | function Gracenote(clientId, clientTag, userId, requestDefaults) {
12 | if (requestDefaults)
13 | request = request.defaults(requestDefaults);
14 | var me = this;
15 | if (clientId === null || typeof clientId === 'undefined' || clientId === '')
16 | throw new GraceError(GraceError.INVALID_INPUT_SPECIFIED, 'clientId');
17 | if (clientTag === null || typeof clientTag === 'undefined' || clientTag === '')
18 | throw new Error(GraceError.INVALID_INPUT_SPECIFIED, 'clientTag');
19 | if (typeof userId === 'undefined' || userId === '') {
20 | if (cache.get('Gracenote.userId')) {
21 | userId = cache.get('Gracenote.userId');
22 | }
23 | else
24 | userId = null;
25 | }
26 |
27 | this.clientId = clientId;
28 | this.clientTag = clientTag;
29 | this.userId = userId;
30 | this.apiURL = API_URL.replace('[[CLID]]', this.clientId);
31 |
32 | this._coverSize = 'MEDIUM';
33 | this._extendedData = 'COVER,REVIEW,ARTIST_BIOGRAPHY,ARTIST_IMAGE,ARTIST_OET,MOOD,TEMPO'; //COVER,REVIEW,ARTIST_BIOGRAPHY,ARTIST_IMAGE,ARTIST_OET,MOOD,TEMPO
34 | this._lang = null;
35 |
36 | this._execute = function(data, cb) {
37 | this._performRequest(data, function(error, response, body) {
38 | if (error) return cb(error);
39 | var xml = me._parseResponse(body, function(err, xml) {
40 | if (err) return cb(err);
41 | cb(null, xml);
42 | });
43 | });
44 | };
45 |
46 | this._constructQueryRequest = function(body, command) {
47 | if (command === null || typeof command === 'undefined' || command === '')
48 | command = 'ALBUM_SEARCH';
49 |
50 | return ''+
51 | ''+
52 | '' + this.clientId + '-' + this.clientTag + ''+
53 | '' + this.userId + ''+
54 | ''+
55 | (this._lang ? ''+this._lang+'' : '') +
56 | ''+
57 | body +
58 | ''+
59 | '';
60 | };
61 |
62 | this._constructQueryBody = function(artist, album, track, gn_id, command, options) {
63 | if (command === null || typeof command === 'undefined' || command === '')
64 | command = 'ALBUM_SEARCH';
65 |
66 | if (typeof options === 'undefined' || options === null)
67 | options = {matchMode: Gracenote.ALL_RESULTS};
68 | else if (!options.hasOwnProperty('matchMode'))
69 | options = extend(options, {matchMode: Gracenote.ALL_RESULTS});
70 |
71 | var body = "";
72 |
73 | if (command === 'ALBUM_FETCH') {
74 | body += '' + gn_id + '';
75 | }
76 | else {
77 | if (options.matchMode === Gracenote.BEST_MATCH_ONLY) { body += 'SINGLE_BEST_COVER'; }
78 | if (options.hasOwnProperty('range')) {
79 | var range = options.range;
80 | if (!range.hasOwnProperty('start') || !range.hasOwnProperty('end'))
81 | throw new Error(GraceError.RANGE_NOT_VALID, 'clientTag');
82 | body += ''+options.range.start+''+options.range.end+'';
83 | }
84 |
85 | if (artist != "") { body += '' + artist + ''; }
86 | if (track != "") { body += '' + track + ''; }
87 | if (album != "") { body += '' + album + ''; }
88 | }
89 |
90 | body += '';
94 |
95 | body += '';
99 |
100 | // (LARGE,XLARGE,SMALL,MEDIUM,THUMBNAIL)
101 | body += '';
105 |
106 | return body;
107 | }
108 |
109 | this._checkResponse = function(response, cb) {
110 | response = this._formatXML(response);
111 | parseString(response, function (err, xml) {
112 |
113 | if (err !== null)
114 | throw new GraceError(GraceError.UNABLE_TO_PARSE_RESPONSE);
115 |
116 | var status = xml.RESPONSES.RESPONSE[0].$.STATUS;
117 | switch (status) {
118 | case "ERROR": throw new GraceError(GraceError.API_RESPONSE_ERROR, xml.RESPONSES.MESSAGE[0]); break;
119 | case "NO_MATCH": throw new GraceError(GraceError.API_NO_MATCH); break;
120 | default:
121 | if (status !== "OK") { throw new GraceError(GraceError.API_NON_OK_RESPONSE, status); }
122 | }
123 |
124 | cb(xml.RESPONSES);
125 | });
126 | }
127 |
128 | this._parseResponse = function(response, cb) {
129 | response = this._formatXML(response);
130 | parseString(response, function (err, xml) {
131 | if (err !== null)
132 | throw new GraceError(GraceError.UNABLE_TO_PARSE_RESPONSE);
133 |
134 | try {
135 | me._checkResponse(response, function() {
136 | var output = [],
137 | entries = xml.RESPONSES.RESPONSE[0].ALBUM;
138 | for (var i = 0; i < entries.length; i++) {
139 | var entry = entries[i];
140 |
141 | var obj = {
142 | "album_gnid": entry.GN_ID[0],
143 | 'album_artist_name': entry.ARTIST[0],
144 | 'album_title': entry.TITLE[0],
145 | 'album_year': '',
146 | 'genre': me._getOETElem(entry.GENRE),
147 | 'album_art_url': '',
148 | 'artist_image_url': '',
149 | 'artist_bio_url': '',
150 | 'review_url': ''
151 | };
152 |
153 | if (entry.DATE) {
154 | obj.album_year = entry.DATE[0];
155 | }
156 |
157 | if (entry.URL) {
158 | obj.album_art_url = me._getAttribElem(entry.URL, "TYPE", "COVERART"),
159 | obj.artist_image_url = me._getAttribElem(entry.URL, "TYPE", "ARTIST_IMAGE"),
160 | obj.artist_bio_url = me._getAttribElem(entry.URL, "TYPE", "ARTIST_BIOGRAPHY"),
161 | obj.review_url = me._getAttribElem(entry.URL, "TYPE", "REVIEW")
162 | }
163 |
164 | if (entry.ARTIST_ORIGIN) {
165 | obj.artist_era = me._getOETElem(entry.ARTIST_ERA);
166 | obj.artist_type = me._getOETElem(entry.ARTIST_TYPE);
167 | obj.artist_origin = me._getOETElem(entry.ARTIST_ORIGIN);
168 | }
169 | else {
170 | // NOT A GOOD APROACH TO ASK AN ASYNC CALL
171 | //me.fetchOETData(entry.GN_ID[0], function(data) {
172 | // console.log("CALLBACK",data);
173 | //})
174 | }
175 |
176 | var tracks = [];
177 | if (entry.TRACK) {
178 | for (var x = 0; x < entry.TRACK.length; x++) {
179 | var t = entry.TRACK[x];
180 | var track = {
181 | 'track_number': t.TRACK_NUM[0],
182 | 'track_gnid': t.GN_ID[0],
183 | 'track_title': t.TITLE[0],
184 | 'mood': '',
185 | 'tempo': ''
186 | };
187 |
188 | if (t.MOOD)
189 | track.mood = me._getOETElem(t.MOOD);
190 | if (t.TEMPO)
191 | track.tempo = me._getOETElem(t.TEMPO)
192 |
193 |
194 | if (!t.ARTIST) {
195 | track.track_artist_name = obj.album_artist_name;
196 | }
197 | else {
198 | track.track_artist_name = t.ARTIST[0];
199 | }
200 |
201 | if (t.GENRE)
202 | obj.genre = me._getOETElem(t.GENRE);
203 | if (t.ARTIST_ERA)
204 | obj.artist_era = me._getOETElem(t.ARTIST_ERA);
205 | if (t.ARTIST_TYPE)
206 | obj.artist_type = me._getOETElem(t.ARTIST_TYPE);
207 | if (t.ARTIST_ORIGIN)
208 | obj.artist_origin = me._getOETElem(t.ARTIST_ORIGIN);
209 |
210 | tracks.push(track);
211 | }
212 | }
213 | obj.tracks = tracks;
214 | output.push(obj);
215 | }
216 |
217 | cb(null, output);
218 | });
219 | }
220 | catch (err) {
221 | if (err.statusCode == GraceError.API_NO_MATCH)
222 | return cb(null, []);
223 | cb(err.message);
224 | }
225 | });
226 | }
227 |
228 | this._getAttribElem = function(root, attribute, value) {
229 | for (var i = 0; i < root.length; i++) {
230 | var r = root[i];
231 |
232 | if (r.$[attribute] == value) { return r._; }
233 | }
234 | return "";
235 | }
236 |
237 | this._getOETElem = function(root) {
238 | if (root) {
239 | var out = [];
240 | for (var i = 0; i < root.length; i++) {
241 | var r = root[i];
242 | out.push({
243 | 'id': r.$.ID,
244 | 'text': r._,
245 | });
246 | }
247 | return out;
248 | }
249 | return '';
250 | }
251 |
252 | this._performRequest = function(body, cb) {
253 | request({
254 | url: this.apiURL,
255 | body: body,
256 | method: 'POST',
257 | headers: {
258 | 'User-Agent': 'nodejs-gracenote'
259 | }
260 | }, cb)
261 | },
262 |
263 | this._formatXML = function(response) {
264 | response = response.replace(/\r\n+|\r\n|\n+|\n|\s+|\s$/, '');
265 | return response;
266 | }
267 | };
268 |
269 |
270 | Gracenote.prototype.register = function(cb) {
271 | var me = this;
272 |
273 | if (this.userId !== null) {
274 | console.warn('Warning: You already have a userId, no need to register another. Using current ID.');
275 | return this.userId;
276 | }
277 |
278 | var data = ''+
279 | ''+
280 | '' + this.clientId + '-' + this.clientTag + ''+
281 | ''+
282 | '';
283 |
284 | this._performRequest(data, function(error, response, body) {
285 | if (error) return cb(error);
286 | var xml = me._checkResponse(body, function(xml) {
287 | me.userId = xml.RESPONSE[0].USER[0];
288 | cache.put('Gracenote.userId',me.userId);
289 | cb(null, me.userId);
290 | });
291 | });
292 | };
293 |
294 | Gracenote.prototype.searchTrack = function(artistName, albumTitle, trackTitle, cb, options) {
295 | var body = this._constructQueryBody(artistName, albumTitle, trackTitle, "", "ALBUM_SEARCH", options),
296 | data = this._constructQueryRequest(body);
297 | this._execute(data, cb);
298 | };
299 |
300 | Gracenote.prototype.searchArtist = function(artistName, cb, options) {
301 | this.searchTrack(artistName, "", "", cb, options);
302 | };
303 |
304 | Gracenote.prototype.searchAlbum = function(artistName, albumTitle, cb, options) {
305 | this.searchTrack(artistName, albumTitle, "", cb, options);
306 | };
307 |
308 | Gracenote.prototype.fetchAlbum = function(gn_id, cb) {
309 | var body = this._constructQueryBody("", "", "", gn_id, "ALBUM_FETCH");
310 | var data = this._constructQueryRequest(body, "ALBUM_FETCH");
311 | this._execute(data, cb);
312 | };
313 |
314 | Gracenote.prototype.setExtended = function(str) {
315 | this._extendedData = str;
316 | };
317 |
318 | Gracenote.prototype.setLanguage = function(iso) {
319 | this._lang = iso;
320 | };
321 |
322 | Gracenote.prototype.setCoverSize = function(size) {
323 | this._coverSize = size;
324 | };
325 |
326 | Gracenote.prototype.fetchOETData = function(gn_id, cb) {
327 | var me = this;
328 | var body = '' + gn_id +''+
329 | ''+
333 | '';
337 |
338 | var data = this._constructQueryRequest(body, 'ALBUM_FETCH');
339 | this._performRequest(data, function(error, response, body) {
340 | if (error) return cb(error);
341 | me._checkResponse(body, function(xml) {
342 | var output = {
343 | 'artist_origin': (xml.RESPONSE[0].ALBUM[0].ARTIST_ORIGIN[0]) ? me._getOETElem(xml.RESPONSE[0].ALBUM[0].ARTIST_ORIGIN[0]) : "",
344 | 'artist_era': (xml.RESPONSE[0].ALBUM[0].ARTIST_ERA[0]) ? me._getOETElem(xml.RESPONSE[0].ALBUM[0].ARTIST_ERA[0]) : "",
345 | 'artist_type': (xml.RESPONSE[0].ALBUM[0].ARTIST_TYPE[0]) ? me._getOETElem(xml.RESPONSE[0].ALBUM[0].ARTIST_TYPE[0]) : ""
346 | }
347 | cb(null, output);
348 | });
349 | });
350 | };
351 |
352 | Gracenote.prototype.albumToc = function(toc, cb) {
353 | var body = '' + toc + '';
354 | var data = this._constructQueryRequest(body, "ALBUM_TOC");
355 | this._execute(data, cb);
356 | };
357 |
358 |
359 | Gracenote.BEST_MATCH_ONLY = 0;
360 | Gracenote.ALL_RESULTS = 1;
361 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-gracenote",
3 | "version": "0.0.3",
4 | "description": "A node.js wrapper for the Gracenote API - https://developer.gracenote.com",
5 | "keywords": [
6 | "gracenote"
7 | ],
8 | "author": "Dominik Danninger ",
9 | "main": "./lib/Gracenote.js",
10 | "dependencies": {
11 | "memory-cache": "0.0.5",
12 | "request": "^2.36.0",
13 | "xml2js": "^0.4.4"
14 | },
15 | "engines": {
16 | "node": "*"
17 | },
18 | "directories": {
19 | "lib": "lib"
20 | },
21 | "devDependencies": {},
22 | "repository": {
23 | "type": "git",
24 | "url": "git://github.com/ddanninger/node-gracenote.git"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------