/**
* share-on-delicious - an Ubiquity command for sharing bookmarks on
* delicious.com
*
* l.m.orchard@pobox.com
* http://decafbad.com/
* Share and Enjoy!
*
* This version is entirely ripped up: status bar delete, non-cookie authorization included. Bon appetit!
*/
var share_on_delicious_cmd = (function() {
return {
name:
'delicious',
icon:
'http://delicious.com/favicon.ico',
description:
'Share the current page as a bookmark on delicious.com',
help:
Select text on the page to use as notes, or enter your own
text after the command word. You can also assign tags to the
bookmark with the tagged modifier, and alter the bookmark
default page title with the entitled modifier. Note that
you must also already be logged in at delicious.com to use
this command.
.toXMLString(),
homepage:
'http://decafbad.com',
author: {
name: 'Leslie Michael Orchard',
email: 'l.m.orchard@pobox.com'
},
license:
'MPL/GPL/LGPL',
/**
* Initialize the command package. Creates the command after doing
* a last few bits of wiring. Called at the very end of the file.
*/
init: function() {
this.takes = {
quote: noun_arb_text
};
this.modifiers = {
quoted: noun_arb_text,
tagged: noun_arb_text,
// tagged: this.noun_type_tags,
entitled: noun_arb_text
};
CmdUtils.CreateCommand(this);
return this;
},
/**
* Command configuration settings.
*/
config: {
// Base URL for the delicious v1 API
api_base: 'https://api.del.icio.us',
// Domain and name of the delicious login session cookie.
cookie_domain: '.delicious.com',
cookie_name: '_user',
// ID for the XUL element displaying bookmark info
status_bar_id: 'ubiq-delicious-panel'
},
// XML NS for XUL elements, used for the status bar panel.
XUL_NS: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
// Cache of URL info loaded up from Delicious
urlinfo: {},
/**
* Present a preview of the bookmark under construction during the course
* of composing the command.
*/
preview: function(pblock, input_obj, mods) {
var bm = this.extractBookmarkData(input_obj, mods);
var user_cookie = this.getUserCookie();
var user_name = (user_cookie) ? user_cookie.split(' ')[0] : '';
var chars_left = (bm.extended) ? 1000 - bm.extended.length : 1000;
var ns = {
user_name: user_name,
bm: bm,
chars_left: chars_left,
chars_over: (chars_left < 0)
};
var tmpl;
if (!bm.description) {
tmpl = this.templates.preview_title_error;
} else {
tmpl = this.templates.preview_full;
}
pblock.innerHTML = this.renderE4XTemplate(tmpl, ns);
},
/**
* Attempt to use the delicious v1 API to post a bookmark using the
* command input
*/
execute: function(input_obj, mods) {
var bm = this.extractBookmarkData(input_obj, mods);
var user_cookie = this.getUserCookie();
var user_name = (user_cookie) ? user_cookie.split(' ')[0] : '';
if (!bm.description) {
displayMessage(
"A title is required for bookmarks at delicious.com"
);
return false;
}
if (bm.extended && bm.extended.length > 1000) {
displayMessage(
"The bookmark notes are " +
Math.abs(1000-bm.extended.length) + " characters " +
"too long."
);
return false;
}
this.v1api(
'/v1/posts/add', bm,
function() {
displayMessage('Bookmark "' + bm.description + '" ' +
'shared at delicious.com/' + user_name);
},
function() {
// TODO: more informative reporting on errors
displayMessage('ERROR: Bookmark "' + bm.description + '" ' +
' NOT shared on delicious.com/' + user_name);
}
);
},
/**
* A noun type for suggesting Delicious tags
*
* TODO: fetch and cache tags from user's account
* TODO: use the tag suggestion call on the API
*/
noun_type_tags: {
_name: 'tags',
getTags: function() {
return [ 'osx', 'apple', 'software', 'testing' ];
},
suggest: function(text, html) {
var sugg_tags = this.getTags();
var curr_tags = (''+text).split(' ');
var suggestions = [];
if (curr_tags.length) {
var last_tag = curr_tags.pop();
for (var i=0,tag; tag=sugg_tags[i]; i++) {
if ( tag.indexOf( last_tag ) > -1 ) {
var sugg_tags = curr_tags.join(' ') + ' ' + tag;
suggestions.push( CmdUtils.makeSugg( sugg_tags, sugg_tags, sugg_tags ) );
}
}
}
return suggestions;
}
},
/**
* Fire off a Delicious V1 API request.
*/
v1api: function(path, data, success, error) {
// Build a User-Agent string derived from the browser's, if none
// previously defined.
if (!this.user_agent) {
var mediator = Components.classes["@mozilla.org/appshell/window-mediator;1"].
getService(Components.interfaces.nsIWindowMediator);
var win = mediator.getMostRecentWindow(null);
this.user_agent = win.navigator.userAgent + ";Ubiquity-share-on-delicious";
}
// Inject the user auth cookie into the API parameters.
data['_user'] = this.getUserCookie();
jQuery.ajax({
type: 'POST',
url: this.config.api_base + path,
data: this.buildQueryString(data),
beforeSend: function(req) {
req.setRequestHeader("User-Agent", this.user_agent);
},
success: success || function() {
displayMessage('Delicious API call ' + path + ' succeeded.');
},
error: error || function() {
displayMessage('ERROR: Delicious API call ' + path + ' failed!');
}
});
},
/**
* Given input data and modifiers, attempt to assemble data necessary to
* post a bookmark.
*/
extractBookmarkData: function(input_obj, mods) {
return {
url:
Application.activeWindow.activeTab.uri.spec,
description:
mods.entitled.text || context.focusedWindow.document.title,
extended:
input_obj.text + ( mods.quoted.text ? ' "' + mods.quoted.text + '"' : '' ),
tags:
mods.tagged.text
};
},
/**
* Dig up the Delicious login session cookie.
*/
getUserCookie: function() {
var cookie_mgr = Components.classes["@mozilla.org/cookiemanager;1"]
.getService(Components.interfaces.nsICookieManager);
var iter = cookie_mgr.enumerator;
while (iter.hasMoreElements()) {
var cookie = iter.getNext();
if( cookie instanceof Components.interfaces.nsICookie &&
cookie.host.indexOf(this.config.cookie_domain) != -1 &&
cookie.name == this.config.cookie_name) {
return decodeURIComponent(cookie.value);
}
}
},
/**
* Given an object, build a URL query string
*/
buildQueryString: function(data) {
var qs = [];
for (k in data) if (data[k])
qs.push( encodeURIComponent(k) + '=' +
encodeURIComponent(data[k]) );
return qs.join('&');
},
/**
* Calculate an MD5 hash for a given string
*/
md5: function(str) {
var Ci = Components.interfaces;
var Cc = Components.classes;
var converter = Cc['@mozilla.org/intl/scriptableunicodeconverter']
.createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = 'UTF-8';
var result = {};
var data = converter.convertToByteArray(str, result);
var ch = Cc["@mozilla.org/security/hash;1"]
.createInstance(Ci.nsICryptoHash);
ch.initWithString('md5');
ch.update(data, data.length);
var hash = ch.finish(false);
return [
('0' + (hash.charCodeAt(i).toString(16)) ).slice(-2)
for (i in hash)
].join("");
},
/**
* Given an XML doc, render as a template.
*
* Since both JST and E4X use { and } functionally, [[ and ]] are
* used as a convention for JST's syntax.
*/
renderE4XTemplate: function(xml, ns) {
var tmpl = xml.toXMLString().replace(/\[\[/g,'{').replace(/\]\]/g, '}');
return CmdUtils.renderTemplate(tmpl, ns);
},
/**
* Templates for producing previews and other content.
*/
templates: {
// Preview displayed when no user login found.
preview_user_error:
No active user found - log in at
delicious.com
to use this command.
A title is required for bookmarks on
delicious.com
Share a bookmark at
delicious.com/$[[user_name]]