diff --git a/data/icon.png b/data/icon.png new file mode 100644 index 0000000..f1eacc4 Binary files /dev/null and b/data/icon.png differ diff --git a/data/loading.gif b/data/loading.gif new file mode 100644 index 0000000..e5bfa1d Binary files /dev/null and b/data/loading.gif differ diff --git a/data/panel.css b/data/panel.css new file mode 100644 index 0000000..14af859 --- /dev/null +++ b/data/panel.css @@ -0,0 +1,43 @@ +body { + color: #93a1a1; + font-family: Arial, Helvetica, sans-serif; +} + +.centered { + text-align: center; +} + +#container { + padding: 1px; + height: 130px; + border-radius: 0.4em; + background-color: #002b36; +} + +#loading { + display: block; + margin-top: 25px; + margin-left: auto; + margin-right: auto; +} + +#connect { + display: block; + text-align: center; + margin-left: auto; + margin-right: auto; + margin-top: 25px; +} + +#song { + padding: 2px; + height: 100px; +} + +#connected { + height: 30px; + float: right; + font-size: 13px; + padding: 2px; +} + diff --git a/data/panel.html b/data/panel.html new file mode 100644 index 0000000..582a435 --- /dev/null +++ b/data/panel.html @@ -0,0 +1,11 @@ + + + + + +
+ +

Please wait...

+
+ + diff --git a/data/panel.js b/data/panel.js new file mode 100644 index 0000000..3181bcd --- /dev/null +++ b/data/panel.js @@ -0,0 +1,57 @@ + +var main = document.getElementById("container"); +var mesg = document.getElementById("message"); +var load = document.getElementById("loading"); + +self.port.on("connection", function(type) { + switch (type) { + case "none": + var div = document.createElement("div"); + div.id = "connect"; + + var wel = document.createElement("p"); + wel.class = "centered"; + wel.innerHTML = "Let's get you set up, shall we?"; + + var but = document.createElement("input"); + but.id = "connector"; + but.type = "button"; + but.value = "Connect to Last.FM"; + but.onclick = function() { + but.value = "Connecting..."; + but.disabled = true; + self.port.emit("connect"); + }; + + mesg.innerHTML = ""; + div.appendChild(wel); + div.appendChild(but); + load = main.replaceChild(div, load); + break; + + case "failure": + mesg.innerHTML = "Could not authenticate - Try again?"; + but.value = "Connect to Last.FM"; + but.disabled = false; + break; + + default: + main.innerHTML = ""; + + var song = document.createElement("div"); + song.id = "song"; + song.appendChild(document.createTextNode("You're not playing any music!")); + + var connected = document.createElement("div"); + connected.id = "connected"; + var asicon = document.createElement("img"); + asicon.src = "http://cdn.last.fm/flatness/favicon.2.ico"; + connected.appendChild(asicon); + connected.appendChild(document.createTextNode(" Connected as " + type)); + + main.appendChild(song); + main.appendChild(connected); + break; + } +}); + diff --git a/lib/lastfm.js b/lib/lastfm.js new file mode 100644 index 0000000..4580182 --- /dev/null +++ b/lib/lastfm.js @@ -0,0 +1,111 @@ +const tabs = require("tabs"); +const {Cc, Ci} = require("chrome"); // Needed for MD5 +const request = require("request"); + +var api_key = "6e91b4d40c233852d6174e300ece1930"; +var api_secret = ""; +var api_base = "http://ws.audioscrobbler.com/2.0/"; +var api_auth = "http://www.last.fm/api/auth/"; + +function LFMRequest() { + this._uri = api_base; +} +LFMRequest.prototype = { + _method: function(m) { + this._params["method"] = m; + this._params["api_key"] = api_key; + }, + + _makeSig: function() { + let sig = ""; + let keys = Object.keys(this._params); + keys.sort(); + + for (let i = 0; i < keys.length; i++) { + sig = sig + keys[i] + this._params[keys[i]]; + } + sig += api_secret; + + let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. + createInstance(Ci.nsIScriptableUnicodeConverter); + conv.charset = "UTF-8"; + + let data = conv.convertToByteArray(sig, {}); + let hasher = Cc["@mozilla.org/security/hash;1"]. + createInstance(Ci.nsICryptoHash); + hasher.init(hasher.MD5); + hasher.update(data, data.length); + + let hash = hasher.finish(false); + function toHexString(charCode) { + return ("0" + charCode.toString(16)).slice(-2); + } + + let hexed = [toHexString(hash.charCodeAt(i)) for (i in hash)].join(""); + return hexed; + }, + + _doCall: function(cb) { + this._uri += "?"; + for (let prop in this._params) { + this._uri = this._uri + prop + "=" + this._params[prop] + "&"; + } + this._uri = this._uri + "api_sig=" + this._makeSig(); + + /* We were including format=json before forming the signature, but + * last.fm doesn't like it :( */ + this._uri += "&format=json"; + + let req = request.Request({ + url: this._uri, + onComplete: function(response) { + cb(response.json); + } + }); + req.get(); + }, + + getToken: function(cb) { + this._params = {}; + this._method("auth.gettoken"); + this._doCall(function(result) { + cb(result.token); + }); + }, + + getSession: function(token, cb) { + this._params = {}; + this._method("auth.getsession"); + this._params["token"] = token; + this._doCall(function(result) { + if ("error" in result) { + cb(false); + } else { + cb(result.session); + } + }); + } +}; + +function authorize(cb) { + /* Get an auth token */ + let treq = new LFMRequest(); + treq.getToken(function(token) { + /* Request auth from user */ + let uri = api_auth + "?api_key=" + api_key + "&token=" + token; + tabs.open(uri); + tabs.on("ready", function(tab) { + if (tab.url != "http://www.last.fm/api/grantaccess") return; + + /* Verify the auth token by fetching a session token */ + tab.close(); + let sreq = new LFMRequest(); + sreq.getSession(token, function(key) { + if (!key) cb(false); + else cb(key); + }); + }); + }); +} + +exports.authorize = authorize; diff --git a/lib/main.js b/lib/main.js index d11fd66..31f56b8 100644 --- a/lib/main.js +++ b/lib/main.js @@ -1,13 +1,51 @@ +const {Cc, Ci} = require("chrome"); // Needed for anchoring panel correctly + +const self = require("self"); +const panel = require("panel"); +const lastfm = require("lastfm"); const widgets = require("widget"); -const tabs = require("tabs"); +const simple = require("simple-storage"); + + +var foxpan = panel.Panel({ + width: 300, + height: 150, + contentURL: self.data.url("panel.html"), + contentScriptFile: self.data.url("panel.js") +}); var widget = widgets.Widget({ - id: "mozilla-link", - label: "Mozilla website", - contentURL: "http://www.mozilla.org/favicon.ico", - onClick: function() { - tabs.open("http://www.mozilla.org/"); - } + id: "scrobfox", + label: "Scrobfox", + panel: foxpan, + contentURL: self.data.url("icon.png") }); -console.log("The add-on is running."); +/* Handlers for events from panel contentScript */ +foxpan.port.on("connect", function() { + /* Authorize */ + foxpan.hide(); + lastfm.authorize(function(keys) { + if (!keys) { + foxpan.port.emit("connection", "failed"); + } else { + simple.storage.lastfm = keys; + foxpan.port.emit("connection", simple.storage.lastfm.name); + + /* Calling show without an anchor shows the panel in the middle */ + let WM = Cc['@mozilla.org/appshell/window-mediator;1']. + getService(Ci.nsIWindowMediator); + let doc = WM.getMostRecentWindow("navigator:browser").document; + let bar = doc.getElementById("widget:" + self.id + "-scrobfox"); + foxpan.show(bar); + } + }); +}); + +/* Let's check if we have a last.fm username & auth on file */ +if (!simple.storage.lastfm || !simple.storage.lastfm.name) { + foxpan.port.emit("connection", "none"); +} else { + foxpan.port.emit("connection", simple.storage.lastfm.name); +} + diff --git a/package.json b/package.json index f7cd13c..af35848 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,9 @@ { - "name": "scrobfox", - "fullName": "scrobfox", - "description": "a basic add-on", - "author": "", - "license": "MPL 1.1/GPL 2.0/LGPL 2.1", - "version": "0.1" + "name": "scrobfox", + "license": "MPL 1.1/GPL 2.0/LGPL 2.1", + "author": "", + "version": "0.1", + "fullName": "scrobfox", + "id": "jid1-zcreHDyRZO9jfg", + "description": "a basic add-on" }