// BEGIN FLOCK GPL // // Copyright Flock Inc. 2005-2007 // http://flock.com // // This file may be used under the terms of of the // GNU General Public License Version 2 or later (the "GPL"), // http://www.gnu.org/licenses/gpl.html // // Software distributed under the License is distributed on an "AS IS" basis, // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License // for the specific language governing rights and limitations under the // License. // // END FLOCK GPL // This is a library to access any bookmark service through the Delicious API. // Many services are providing a compatibility layer with Delicious, // so this library is used also for Magnolia and Shadows. var EXPORTED_SYMBOLS = ["DeliciousAPI"]; const CC = Components.classes; const CI = Components.interfaces; const CR = Components.results; const CU = Components.utils; CU.import("resource:///modules/ISO8601DateUtils.jsm"); /************************************************************************** * Module: Delicious API **************************************************************************/ // Constructor. function DeliciousAPI(aUrl, aLogger) { this._url = aUrl; this._logger = aLogger; } // Private data. DeliciousAPI.prototype._url = ""; DeliciousAPI.prototype._logger = null; /************************************************************************** * Delicious API Public Interface **************************************************************************/ /** * Call the specified del.ico.us API method. * @param AString aAPIMethod (in) * One of the API methods; "posts/add", etc. * @param object aArgs (in) * As documented at http://del.icio.us/help/api/ * @param object aAPIListener (in) * An object (deliciousAPIListener) with the following two methods: * void function onSuccess(in nsIDOMDocument aXML) * void function onError(in flockIError aError) * @param object aAuth (in) * An object with the following two properties: * AString user * AString password * @return nothing */ DeliciousAPI.prototype.call = function deliciousAPI_call(aAPIMethod, aArgs, aAPIListener, aAuth) { this._logger.debug("deliciousAPI_call('" + aAPIMethod + "', aArgs," + " aAPIListener, aAuth)"); // Convert args from object notation to an array of "key=value" pairs, // performing any escaping necessary for use in URL. var argsArray = []; for (var key in aArgs) { argsArray.push(encodeURIComponent(key) + "=" + encodeURIComponent(aArgs[key])); } // Build the complete API command: host, API method, and args. var url = this._url + aAPIMethod; if (argsArray.length) { url = url + "?" +argsArray.join("&"); } // Build an XMLHTTPRequest for sending the API call. var request = CC["@mozilla.org/xmlextras/xmlhttprequest;1"] .createInstance(CI.nsIXMLHttpRequest); var onReadyStateFunc = function deliciousAPI_call_onReadyStateFunc(eEvt) { if (request.readyState == 4) { if (request.status >= 200 && request.status < 300) { // Scrub input and create an nsIDOMDocument. var text = request.responseText.replace(/[\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F]/g, ""); if (!text) { text = ""; } var xml = CC["@mozilla.org/xmlextras/domparser;1"] .createInstance(CI.nsIDOMParser) .parseFromString(text, "text/xml"); aAPIListener.onSuccess(xml); } else { var error = CC["@flock.com/error;1"].createInstance(CI.flockIError); error.serviceErrorCode = request.status; error.serviceErrorString = request.responseText; switch (request.status) { case 401: // Bad login/password error.errorCode = CI.flockIError.FAVS_INVALID_AUTH; break; case 503: // Service unavailable error.errorCode = CI.flockIError.FAVS_UNAVAILABLE; break; default: // Unknown error code error.errorCode = CI.flockIError.FAVS_UNKNOWN_ERROR; break; } aAPIListener.onError(error); } } }; request.onreadystatechange = onReadyStateFunc; request.backgroundRequest = true; request.overrideMimeType("text/txt"); if (aAuth) { request.open("GET", url, true, aAuth.user, aAuth.password); } else { request.open("GET", url, true); } this._logger.debug("deliciousAPI_call request URL: '" + url + "'"); request.send(null); }; /** * Call the del.ico.us "posts/all" API method. * @param flockIListener aFlockListener (in) * @param aAuth (in) * An object with the following two properties: * AString user * AString password * @param AString aSeparator (in) * The delimiter to expect in the returned list of tags. * @return nothing * * Except when calling for the first time, "posts/update" should be called * before calling this, so that only new posts are returned. * * @see flockIListener */ DeliciousAPI.prototype.postsAll = function deliciousAPI_postsAll(aFlockListener, aAuth, aSeparator, aUpdateTime) { var tagSep = " "; if (aSeparator) { tagSep = aSeparator; } var api = this; var deliciousAPIListener = { onError: function deliciousAPI_postsAll_onError(aError) { if (aFlockListener) { aFlockListener.onError(null, "error", aError); } }, onSuccess: function deliciousAPI_postsAll_onSuccess(aXML) { // Validate the nsIDOMDocument response. if (!api.isExpectedResponse(aXML, "posts")) { api._logger.error("posts/all succeeded, bad xml response"); var error = CC["@flock.com/error;1"].createInstance(CI.flockIError); // FIXME: FAVS_, not OPML_. Clean up flockIError.idl. error.errorCode = CI.flockIError.OPML_INVALID_XML; if (aXML && aXML.documentElement) { api._logger.debug("tagName is: " + aXML.documentElement.tagName); error.serviceErrorString = aXML.documentElement.tagName; } else { api._logger.debug("aXML.documentElement is not defined"); error.serviceErrorString = "aXML.documentElement is not defined"; } aFlockListener.onError(null, "error", error); } else { var result = []; var children = aXML.documentElement.childNodes; for (var i=0; i aLastUpdate.getTime()) { // There is new/updated stuff on the server if (api.timer) { api.timer.cancel(); } else { api.timer = CC["@mozilla.org/timer;1"] .createInstance(CI.nsITimer); } // Init a timer to start in 1.3 seconds. api.timer.initWithCallback({ notify: function deliciousAPI_postsUpdateTimer_notify(aTimer) { api.postsAll(aFlockListener, aAuth, tagSep, lastUpdate); } }, 1300, CI.nsITimer.TYPE_ONE_SHOT); } else { // No refresh needed if (aFlockListener) { aFlockListener.onSuccess(null, "nonew"); } } } } }; this.call("posts/update", {}, deliciousAPIListener, aAuth); }; /** * Call the del.ico.us "tags/get" API method. * @param flockIListener aFlockListener (in) * @param object aAuth (in) * An object with the following two properties: * AString user * AString password * @return nothing * * @see flockIListener */ DeliciousAPI.prototype.tagsGet = function deliciousAPI_tagsGet(aFlockListener, aAuth) { var api = this; var deliciousAPIListener = { onError: function deliciousAPI_tagsGet_onError(aError) { if (aFlockListener) { aFlockListener.onError(null, "error", aError); } }, onSuccess: function deliciousAPI_tagsGet_onSuccess(aXML) { // Validate the nsIDOMDocument response. if (!api.isExpectedResponse(aXML, "tags")) { api._logger.error('tags/get succeeded, bad xml response'); var error = CC["@flock.com/error;1"].createInstance(CI.flockIError); // FIXME: FAVS_, not OPML_. Clean up flockIError.idl. error.errorCode = CI.flockIError.OPML_INVALID_XML; if (aXML && aXML.documentElement) { api._logger.debug("tagName is: " + aXML.documentElement.tagName); error.serviceErrorString = aXML.documentElement.tagName; } else { api._logger.debug("aXML.documentElement is not defined"); error.serviceErrorString = "aXML.documentElement is not defined"; } aFlockListener.onError(null, "error", error); } else { var result = []; var children = aXML.documentElement.childNodes; for (var i=0; i