Info.js
Go to the documentation of this file.
1 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
2 Components.utils.import("resource://app/jsmodules/sbLibraryUtils.jsm");
3 Components.utils.import("resource://app/jsmodules/sbProperties.jsm");
4 
5 const Cc = Components.classes;
6 const Ci = Components.interfaces;
7 const Cr = Components.results;
8 var JSON = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
9 
10 const DESCRIPTION = "mashTape Provider: UberMash Artist Info Provider";
11 const CID = "{7792e470-75ec-11dd-ad8b-0800200c9a66}";
12 const CONTRACTID = "@songbirdnest.com/mashTape/provider/info/UberMash;1";
13 
14 // XPCOM constructor for our Artist Info mashTape provider
15 function ArtistInfo() {
16  this.wrappedJSObject = this;
17  Components.utils.import("resource://mashtape/mtUtils.jsm");
18 
19  if (typeof(language) == "undefined") {
20  // Get the user's locale
21  var prefBranch = Cc["@mozilla.org/preferences-service;1"]
22  .getService(Ci.nsIPrefService).getBranch("general.");
23  var locale = prefBranch.getCharPref("useragent.locale");
24  language = locale.split(/-/)[0];
25  // last.fm screws up ja as jp
26  if (language == "ja")
27  language = "jp";
28  mtUtils.log("Info", "language set to " + language);
29 
30  }
31 }
32 
33 var linkMap = {
34  "Musicmoz":"MusicMoz",
35  "Discogs":"Discogs",
36  "Wikipedia":"Wikipedia",
37  "OfficialHomepage":"Homepage",
38  "Fanpage":"Fan site",
39  "IMDb":"IMDB",
40  "Myspace":"MySpace",
41  "Purevolume":"PureVolume"
42 };
43 
45 
46 var strings = Components.classes["@mozilla.org/intl/stringbundle;1"]
47  .getService(Components.interfaces.nsIStringBundleService)
48  .createBundle("chrome://mashtape/locale/mashtape.properties");
49 
50 var Application = Cc["@mozilla.org/fuel/application;1"]
51  .getService(Ci.fuelIApplication);
52 
53 ArtistInfo.prototype.constructor = ArtistInfo;
54 ArtistInfo.prototype = {
56  classID: Components.ID(CID),
58  QueryInterface: XPCOMUtils.generateQI([Ci.sbIMashTapeInfoProvider,
59  Ci.sbIMashTapeProvider]),
60 
61  providerName: "Last.fm + MusicBrainz + Freebase",
62  providerType: "info",
63  numSections: 6,
64  providerIconBio : "chrome://mashtape/content/tabs/lastfm.png",
65  providerIconDiscography: "chrome://mashtape/content/tabs/musicbrainz.png",
66  providerIconMembers: "chrome://mashtape/content/tabs/freebase.png",
67  providerIconTags: "chrome://mashtape/content/tabs/lastfm.png",
68  providerIconLinks: "chrome://mashtape/content/tabs/musicbrainz.png",
69 
70  triggerLastFM: function(artist, callback) {
71  /* Go retrieve the artist bio */
72  var bioReq = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
73  .createInstance(Ci.nsIXMLHttpRequest);
74  var url = "http://ws.audioscrobbler.com/2.0/?method=artist.getinfo" +
75  "&api_key=ad68d3b69dee88a912b193a35d235a5b" +
76  "&artist=" + encodeURIComponent(artist);
77  var prefBranch = Cc["@mozilla.org/preferences-service;1"]
78  .getService(Ci.nsIPrefService).getBranch("extensions.mashTape.");
79  var autolocalise = prefBranch.getBoolPref("info.autolocalise");
80  if (autolocalise && language)
81  url += "&lang=" + language;
82  mtUtils.log("Info", "Last.FM Bio URL: " + url);
83  bioReq.open("GET", url, true);
84  bioReq.onreadystatechange = function(ev) {
85  return function(artistName, updateFn) {
86  if (bioReq.readyState != 4)
87  return;
88  if (bioReq.status == 200) {
89  var xmlText = bioReq.responseText.replace(
90  /<\?xml version="1.0" encoding="[uU][tT][fF]-8"\?>/,
91  "");
92  var x = new XML(xmlText);
93 
94  var imgUrl;
95  var bio = new Object;
96  for each (var img in x.artist.image) {
97  if (img.@size.toString() === "small") {
98  imgUrl = img;
99  } else if (img.@size.toString() === "large") {
100  imgUrl = img;
101  } else if (img.@size.toString() === "medium") {
102  imgUrl = img;
103  }
104  }
105 
106  bio.provider = "Last.fm";
107  bio.bioUrl = "http://www.last.fm/music/" + artistName;
108  bio.bioEditUrl = "http://www.last.fm/music/" +
109  artistName + "/+wiki/edit";
110  var bioContent =x.artist.bio.content.toString().split("\n");
111  if (bioContent.length == 0 ||
112  (x.artist.bio.content.toString() == ""))
113  {
114  bio.bioText = null;
115  updateFn.wrappedJSObject.update(CONTRACTID, bio, "bio");
116  updateFn.wrappedJSObject.update(CONTRACTID,
117  null, "photo");
118  return;
119  }
120 
121  while (bioContent[bioContent.length-1].match(/^\s*$/))
122  bioContent.pop();
123  bio.bioText = "<p>" + bioContent.join("</p><p>") + "</p>";
124  bio.bioText = bio.bioText.replace(/http:\/\/ws.audioscrobbler.com/g, 'http://last.fm');
125 
126  updateFn.wrappedJSObject.update(CONTRACTID, bio, "bio");
127  updateFn.wrappedJSObject.update(CONTRACTID, imgUrl,"photo");
128  }
129  }(artist, callback);
130  }
131  bioReq.send(null);
132 
133  /* Now retrieve the artist tags */
134  var tagsReq = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
135  .createInstance(Ci.nsIXMLHttpRequest);
136  var url = "http://ws.audioscrobbler.com/1.0/artist/" +
137  escape(artist) + "/toptags.xml";
138  mtUtils.log("Info", "Last.FM Tags URL: " + url);
139  tagsReq.open("GET", url, true);
140  tagsReq.updateFn = callback;
141  tagsReq.artist = artist;
142  tagsReq.onreadystatechange = function() {
143  if (this.readyState != 4)
144  return;
145  if (this.status == 200) {
146  var xmlText = this.responseText.replace(
147  /<\?xml version="1.0" encoding="[uU][tT][fF]-8"\?>/,
148  "");
149  var x = new XML(xmlText);
150 
151  var data = new Object;
152  data.provider = "Last.fm";
153  data.url = "http://www.last.fm/music/" +
154  encodeURIComponent(this.artist) + "/+tags";
155  data.tags = new Array();
156  for each (var tag in x..tag) {
157  data.tags.push({
158  name: tag.name,
159  count: tag.count,
160  url: tag.url
161  });
162  }
163 
164  if (data.tags.length == 0) {
165  this.updateFn.wrappedJSObject.update(CONTRACTID,
166  null, "tags");
167  return;
168  }
169  data.wrappedJSObject = data;
170  this.updateFn.wrappedJSObject.update(CONTRACTID, data, "tags");
171  } else {
172  this.updateFn.wrappedJSObject.update(CONTRACTID, null, "tags");
173  }
174  }
175  tagsReq.send(null);
176  },
177 
178  triggerFreebase: function(artist, updateFn) {
179  var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
180  .createInstance(Ci.nsIXMLHttpRequest);
181  artist = artist.replace(/&/g, "%26");
182  var url = 'http://www.freebase.com/api/service/mqlread?queries={' +
183  '"qDiscography":{"query":[{"album":[{"id":null,"name":null,"release_date":null}],"id":null,"name":"' + (artist) + '","type":"/music/artist"}]},' +
184  '"qMembers":{"query":[{"member":[{"*":null}],"name":"' + (artist) + '","type":"/music/musical_group"}]},' +
185  '"qArticle":{"query":[{"/common/topic/article":[{"*":null}],"name":"' + (artist) + '","type":"/music/artist"}]},' +
186  '"qImages":{"query":[{"/common/topic/image":[{"*":null}],"name":"' + (artist) + '","type":"/music/artist"}]},' +
187  '"qLinks":{"query":[{"/common/topic/webpage":[{"*":null}],"name":"' + (artist) + '","type":"/music/artist"}]}' +
188  '}';
189  mtUtils.log("Info", "Freebase URL: " + url);
190  req.open("GET", url, true);
191  req.updateFn = updateFn;
192  req.onreadystatechange = function() {
193  if (this.readyState != 4)
194  return;
195  if (this.status == 200) {
196  var results = JSON.decode(this.responseText);
197 
198  if (typeof(results.qMembers.result) == 'undefined' ||
199  results.qMembers.result.length == 0)
200  {
201  // No member relationships found
202  this.updateFn.wrappedJSObject.update(CONTRACTID,
203  null, "members");
204  } else {
205  var data = new Object;
206  data.provider = "Freebase";
207  data.url = "http://www.freebase.com";
208  data.members = new Array();
209  if (typeof(results.qMembers.result[0].member) !=
210  'undefined')
211  {
212  var members = results.qMembers.result[0].member;
213  for (var i in members)
214  data.members.push(members[i].member);
215  }
216  this.updateFn.wrappedJSObject.update(CONTRACTID,
217  data, "members");
218  }
219  }
220  }
221  req.send(null);
222  },
223 
224  triggerMusicBrainz: function(artist, updateFn) {
225  // First lookup the artist to get the MBID
226  var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
227  .createInstance(Ci.nsIXMLHttpRequest);
228  var url =
229  "http://musicbrainz.org/ws/1/artist/?type=xml&inc=sa-Album&query=";
230  mtUtils.log("Info", "MusicBrainz URL: " + url +
232  req.open("GET", url + artist, true);
233  req.updateFn = updateFn;
234  req.onreadystatechange = function() {
235  if (this.readyState != 4)
236  return;
237  if (this.status == 200) {
238  var xmlText = this.responseText.replace(
239  /<\?xml version="1.0" encoding="[uU][tT][fF]-8"\?>/,
240  "");
241  var x = new XML(xmlText);
242 
243  var mbns = new Namespace('http://musicbrainz.org/ns/mmd-1.0#');
244  var extns = new Namespace('http://musicbrainz.org/ns/ext-1.0#');
245 
246  if (x..mbns::artist.length() <= 0) {
247  this.updateFn.wrappedJSObject.update(CONTRACTID,
248  null, "links");
249  return;
250  }
251 
252  var mbId = x..mbns::artist[0].@id;
253  mtUtils.log("Info", "MBID: " + mbId);
254 
255  var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
256  .createInstance(Ci.nsIXMLHttpRequest);
257  var url = "http://musicbrainz.org/ws/1/artist/" + mbId +
258  "?type=xml&inc=url-rels";
259  req.open("GET", url, true);
260  req.updateFn = updateFn;
261  req.mbId = mbId;
262  req.onreadystatechange = function() {
263  if (this.readyState != 4)
264  return;
265  if (this.status != 200)
266  return;
267  var xmlText = this.responseText.replace(
268  /<\?xml version="1.0" encoding="[uU][tT][fF]-8"\?>/,
269  "");
270  var x = new XML(xmlText);
271  var mbns =
272  new Namespace('http://musicbrainz.org/ns/mmd-1.0#');
273 
274  var data = new Object;
275  data.provider = "MusicBrainz";
276  data.url = "http://www.musicbrainz.org/artist/" + this.mbId;
277  data.links = new Array();
278  if (x..mbns::relation.length() == 0) {
279  this.updateFn.wrappedJSObject.update(CONTRACTID,
280  null, "links");
281  return;
282  }
283  for each (var link in x..mbns::relation) {
284  var type = link.@type.toString();
285  var url = link.@target.toString();
286 
287  var name;
288  if (typeof(linkMap[type]) != 'undefined')
289  name = linkMap[type];
290  else
291  name = type;
292  if (type == "Wikipedia") {
293  var country = url.substr(7,2);
294  name += " (" + country + ")";
295  }
296  data.links.push({name:name, url:url});
297  }
298 
299  // Add provider (MusicBrainz) to the list of links
300  data.links.push({name:data.provider, url:data.url});
301 
302  this.updateFn.wrappedJSObject.update(CONTRACTID,
303  data, "links");
304  };
305  req.send(null);
306  }
307  }
308  req.send(null);
309 
310  // Look up discography information
311  var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
312  .createInstance(Ci.nsIXMLHttpRequest);
313  var url = "http://musicbrainz.org/ws/1/release/?type=xml" +
314  "&artist=" + artist + "&releasetypes=Album+Official&limit=100";
315  mtUtils.log("Info", "MusicBrainz Discography URL: " + url);
316  req.open("GET", url, true);
317  req.updateFn = updateFn;
318  req.albumMetadata = this.getAlbumMetadata;
319  req.artist = artist;
320  req.onreadystatechange = function() {
321  if (this.readyState != 4)
322  return;
323  if (this.status == 200) {
324  var xmlText = this.responseText.replace(
325  /<\?xml version="1.0" encoding="[uU][tT][fF]-8"\?>/,
326  "");
327  var x = new XML(xmlText);
328  var mbns = new Namespace('http://musicbrainz.org/ns/mmd-1.0#');
329  var data = new Object;
330  if (x..mbns::release.length() == 0) {
331  this.updateFn.wrappedJSObject.update(CONTRACTID,
332  null, "discography");
333  return;
334  }
335  for each (var release in x..mbns::release) {
336  // Use the oldest release date to avoid re-releases, and
337  // track whether this is a US release (for foreign/import
338  // de-duping - see below)
339  var releaseDate = "9999";
340  var usRelease = false;
341  for each (var event in release..mbns::event) {
342  if (event.@country.toUpperCase() == "US")
343  usRelease = true;
344  if (event.@date.toString() < releaseDate) {
345  releaseDate = event.@date.toString();
346  }
347  }
348  if (releaseDate == "" || releaseDate == "9999")
349  releaseDate = null;
350 
351  // Craft an URL to an Amazon link from the ASIN
352  var disableAmazon = Application.prefs.getValue(
353  "extensions.mashTape.info.amazonstore.disabled",
354  false);
355  var asin = release.mbns::asin.toString();
356  var link = null;
357  var tooltip = null;
358  if (!disableAmazon && asin != "") {
359  link = "http://www.amazon.com/gp/product/" + asin;
360  tooltip = strings.GetStringFromName(
361  "extensions.mashTape.info.amazon");
362  }
363 
364  var albumArt = null;
365 
366  var title = release.mbns::title.toString();
367  var item = {
368  mbid: release.@id,
369  title: title,
370  asin: release.mbns::asin.toString(),
371  artwork: albumArt,
372  release_date: releaseDate,
373  artistMbid: release.mbns::artist.@id.toString(),
374  artistName: release.mbns::artist.mbns::name.toString(),
375  link: link,
376  tooltip: tooltip,
377  };
378 
379  // Only send one (let's prefer the US) of each title
380  // You can thank "Weezer" for giving me headaches on this
381  if (typeof(data[title]) != "undefined" && !usRelease)
382  continue;
383 
384  // Otherwise let's track this
385  data[title] = item;
386  }
387 
388  data.pending = 0;
389  data.artist = this.artist;
390  for (var i in data) {
391  if (typeof(data[i]) == "object") {
392  data.pending++;
393  this.albumMetadata(i, data, this.updateFn);
394  }
395  }
396  }
397  }
398  req.send(null);
399  },
400 
401  getAlbumMetadata : function(i, data, updateFn) {
402  var url = "http://ws.audioscrobbler.com/2.0/?method=album.getinfo" +
403  "&api_key=ad68d3b69dee88a912b193a35d235a5b" +
404  "&mbid=" + data[i].mbid;
405  var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
406  .createInstance(Ci.nsIXMLHttpRequest);
407  mtUtils.log("Info", "Last.fm Album URL: " + url);
408  req.open("GET", url, true);
409  req.updateFn = updateFn;
410  req.data = data;
411  req.i = i;
412  req.onreadystatechange = function() {
413  if (this.readyState != 4)
414  return;
415  if (this.status == 200) {
416  var xmlText = req.responseText.replace(
417  /<\?xml version="1.0" encoding="[uU][tT][fF]-8"\?>/, "");
418  mtUtils.log("Info", "albumMetadata: " +
419  this.data[this.i].mbid + " complete! " +
420  this.data.pending + " left.");
421  var x = new XML(xmlText);
422  var url = x.album.url.toString();
423  if (url != "") {
424  this.data[this.i].link = url;
425  this.data[this.i].tooltip = strings.GetStringFromName(
426  "extensions.mashTape.info.lastfm");
427  }
428  var artwork = null;
429  for each (var image in x..image) {
430  if (image.@size.toString() == "medium")
431  artwork = image.toString();
432  }
433  this.data[this.i].artwork = artwork;
434  }
435  this.data.pending--;
436  if (this.data.pending == 0) {
437  var results = new Object;
438  results.discography = new Array();
439  var mbId;
440  for each (var item in this.data) {
441  if (typeof(item) == "object") {
442  if (item.artistName == this.data.artist)
443  mbId = item.artistMbid;
444  results.discography.push(item);
445  }
446  }
447 
448  if (results.length == 0)
449  this.updateFn.wrappedJSObject.update(CONTRACTID,
450  null, "discography");
451  else {
452  results.provider = "MusicBrainz";
453  results.url = "http://www.musicbrainz.org/artist/" + mbId;
454  this.updateFn.wrappedJSObject.update(CONTRACTID,
455  results, "discography");
456  }
457  }
458  }
459  req.send(null);
460  },
461 
462  query: function(artist, updateFn) {
463  this.triggerLastFM(artist, updateFn);
464  this.triggerFreebase(artist, updateFn);
465  this.triggerMusicBrainz(artist, updateFn);
466  },
467 }
468 
469 var components = [ArtistInfo];
470 function NSGetModule(compMgr, fileSpec) {
471  return XPCOMUtils.generateModule([ArtistInfo]);
472 }
473 
const DESCRIPTION
Definition: Info.js:10
nsString encodeURIComponent(const nsString &c)
_setDateDatepicker date
const Cr
Definition: Info.js:7
menuItem id
Definition: FeedWriter.js:971
sbDeviceFirmwareAutoCheckForUpdate prototype contractID
var event
sbOSDControlService prototype QueryInterface
sbDeviceFirmwareAutoCheckForUpdate prototype classDescription
version(170)
var language
Definition: Info.js:44
var linkMap
Definition: Info.js:33
function ArtistInfo()
Definition: Info.js:15
var strings
Definition: Info.js:46
var getService(Components.interfaces.nsIWindowMediator).getMostRecentWindow('Songbird SBProperties artist
Definition: tuner2.js:40
var count
Definition: test_bug7406.js:32
grep callback
var JSON
Definition: Info.js:8
return null
Definition: FeedWriter.js:1143
const Cc
Definition: Info.js:5
var Application
Definition: Info.js:50
function url(spec)
const CONTRACTID
Definition: Info.js:12
sbDeviceFirmwareAutoCheckForUpdate prototype classID
dataSBGenres SBProperties tag
Definition: tuner2.js:871
observe data
Definition: FeedWriter.js:1329
const Ci
Definition: Info.js:6
_getSelectedPageStyle s i
function NSGetModule(compMgr, fileSpec)
Definition: Info.js:470
const CID
Definition: Info.js:11