// Last Change: 27-May-2011. Jan 2008 var PLUGIN_INFO = {NAME} Direct Post to Social Bookmarks Trapezoid 0.15 GPL 2.0pre 2.2 https://github.com/vimpr/vimperator-plugins/raw/master/direct_bookmark.js || Use social bookmark services to extract tags 'h': Hatena Bookmark 'd': del.icio.us 'l': livedoor clip 'g': Google Bookmarks 'p': Places (Firefox bookmarks) Usage: let g:direct_sbm_use_services_by_tag = "hdl" ||< === g:direct_sbm_use_services_by_post === >|| Use social bookmark services to post 'h': Hatena Bookmark 'd': del.icio.us 'l': livedoor clip 'g': Google Bookmarks 'p': Places (Firefox bookmarks) Usage: let g:direct_sbm_use_services_by_post = "hdl" ||< === g:direct_sbm_echo_type === >|| Post message type 'simple' : single line, no posted services description 'multiline' : multi line, display services description 'none' : hide post message ||< === g:direct_sbm_is_normalize === >|| Use normalize permalink ||< === g:direct_sbm_is_use_migemo === >|| Use Migemo completion ||< == Commands == === :btags === >|| Extract tags from social bookmarks for completion ||< === :sbm === >|| Post a current page to social bookmarks Arguments -s,-service: default:"hdl" Specify target SBM services to post ||< === :bentry === >|| Goto Bookmark Entry Page ||< === :bicon === >|| Show Bookmark Count as Icon ||< ]]> ; (function(){ var evalFunc = window.eval; try { var sandbox = new Components.utils.Sandbox(window); if (Components.utils.evalInSandbox("true", sandbox) === true) { evalFunc = function(text) { return Components.utils.evalInSandbox(text, sandbox); } } } catch(e) { liberator.log('warning: direct_bookmark.js is working with unsafe sandbox.'); } var useServicesByPost = liberator.globalVariables.direct_sbm_use_services_by_post || 'hdl'; var useServicesByTag = liberator.globalVariables.direct_sbm_use_services_by_tag || 'hdl'; var echoType = liberator.globalVariables.direct_sbm_echo_type || 'multiline'; var isNormalize = typeof liberator.globalVariables.direct_sbm_is_normalize == 'undefined' ? true : evalFunc(liberator.globalVariables.direct_sbm_is_normalize); var isUseMigemo = typeof liberator.globalVariables.direct_sbm_is_use_migemo == 'undefined' ? true : evalFunc(liberator.globalVariables.direct_sbm_is_use_migemo); var XMigemoCore; try{ XMigemoCore = Components.classes['@piro.sakura.ne.jp/xmigemo/factory;1'] .getService(Components.interfaces.pIXMigemoFactory) .getService("ja"); } catch(ex if ex instanceof TypeError){} function Deferred () this instanceof Deferred ? this.init(this) : new Deferred(); Deferred.prototype = { init : function () { this._next = null; this.callback = { ok: function (x) x, ng: function (x) { throw x } }; return this; }, next : function (fun) this._post("ok", fun), error : function (fun) this._post("ng", fun), call : function (val) this._fire("ok", val), fail : function (err) this._fire("ng", err), cancel : function () { (this.canceller || function () {})(); return this.init(); }, _post : function (okng, fun) { this._next = new Deferred(); this._next.callback[okng] = fun; return this._next; }, _fire : function (okng, value) { var self = this, next = "ok"; try { value = self.callback[okng].call(self, value); } catch (e) { next = "ng"; value = e; } if (value instanceof Deferred) { value._next = self._next; } else if (self._next) { self._next._fire(next, value); } return this; } }; Deferred.next = function (fun) { var d = new Deferred(); var id = setTimeout(function () { clearTimeout(id); d.call() }, 0); if (fun) d.callback.ok = fun; d.canceller = function () { try { clearTimeout(id) } catch (e) {} }; return d; }; function http (opts) { var d = Deferred(); var req = new XMLHttpRequest(); req.open(opts.method, opts.url, true, opts.user || null, opts.password || null); if (opts.headers) { for (var k in opts.headers) if (opts.headers.hasOwnProperty(k)) { req.setRequestHeader(k, opts.headers[k]); } } req.onreadystatechange = function () { if (req.readyState == 4) d.call(req); }; req.send(opts.data || null); d.xhr = req; return d; } http.get = function (url) http({method:"get", url:url}); http.post = function (url, data) http({method:"post", url:url, data:data, headers:{"Content-Type":"application/x-www-form-urlencoded"}}); Deferred.Deferred = Deferred; Deferred.http = http; function WSSEUtils(aUserName, aPassword){ this._init(aUserName, aPassword); } WSSEUtils.prototype = { get userName() this._userName, get noce() this._nonce, get created() this._created, get passwordDigest() this._passwordDigest, getWSSEHeader: function(){ var result = [ 'UsernameToken Username="' + this._userName + '", ', 'PasswordDigest="' + this._passwordDigest + '=", ', 'Nonce="' + this._nonce + '", ', 'Created="' + this._created + '"' ].join(""); return result; }, _init: function(aUserName, aPassword){ var uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator); var seed = (new Date()).toUTCString() + uuidGenerator.generateUUID().toString(); this._userName = aUserName; this._nonce = this._getSha1Digest(seed, true); this._created = this._getISO8601String((new Date())); this._passwordDigest = this._getSha1Digest(this._getSha1Digest(seed, false) + this._created + aPassword, true); }, _getSha1Digest: function(aString, aBase64){ var cryptoHash = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash); cryptoHash.init(Ci.nsICryptoHash.SHA1); var inputStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream); inputStream.setData(aString, aString.length); cryptoHash.updateFromStream(inputStream, -1); return cryptoHash.finish(aBase64); }, _getISO8601String: function(aDate){ var result = [ zeropad(aDate.getUTCFullYear(), 4), "-", zeropad(aDate.getUTCMonth() + 1, 2), "-", zeropad(aDate.getUTCDate(), 2), "T", zeropad(aDate.getUTCHours(), 2), ":", zeropad(aDate.getUTCMinutes(), 2), ":", zeropad(aDate.getUTCSeconds(), 2), "Z" ].join(""); return result; function zeropad(s, l){ s = String(s); while(s.length < l){ s = "0" + s; } return s; } } }; // copied from AutoPagerize (c) id:swdyh function getElementsByXPath(xpath, node){ node = node || document; var nodesSnapshot = (node.ownerDocument || node).evaluate(xpath, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); var data = []; for(var i = 0, l = nodesSnapshot.snapshotLength; i < l; data.push(nodesSnapshot.snapshotItem(i++))); return (data.length > 0) ? data : null; } function getFirstElementByXPath(xpath, node){ node = node || document; var result = (node.ownerDocument || node).evaluate(xpath, node, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); return result.singleNodeValue ? result.singleNodeValue : null; } // copied from http://d.hatena.ne.jp/odz/20060901/1157165797 id:odz function parseHTML(text) { var createHTMLDocument = function() { var xsl = (new DOMParser()).parseFromString( ['', '', '', ''].join("\n"), "text/xml"); var xsltp = new XSLTProcessor(); xsltp.importStylesheet(xsl); var doc = xsltp.transformToDocument( document.implementation.createDocument("", "", null)); return doc; }; var doc = createHTMLDocument(); var range = doc.createRange(); doc.appendChild(doc.createElement("html")); range.selectNodeContents(doc.documentElement); doc.documentElement.appendChild( range.createContextualFragment(text)); return doc; } // // // // function getNormalizedPermalink(url){ var canonical = plugins.libly.$U.getFirstNodeFromXPath('//link[@rel="canonical"]'); return canonical ? canonical.href : url; } function getUserAccount(form,post,arg){ var user, password; try{ var passwordManager = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager); var logins = passwordManager.findLogins({}, form, post, arg); if(logins.length > 0){ [user, password] = [logins[0].username, logins[0].password]; } else { var promptUser = { value : this.loginPrompt.user }, promptPass = { value : this.loginPrompt.password }; var promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"] .getService(Ci.nsIPromptService); var nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo, "init"); var ret = promptSvc.promptUsernameAndPassword( window, form, this.loginPrompt.description, promptUser, promptPass, null, {} ); if(ret){ [user, password] = [promptUser.value, promptPass.value]; var formLoginInfo = new nsLoginInfo(form, post, null, user, password, '', ''); passwordManager.addLogin(formLoginInfo); } else { liberator.echoerr("Direct Social Bookmark: account not found - " + form); } } } catch(ex){ liberator.echoerr("Direct Social Bookmark: handled exception during tag extracting"); liberator.log(ex); } return [user, password]; } // // // // var services = { 'h': { description:'Hatena bookmark', account:['https://www.hatena.ne.jp', 'https://www.hatena.ne.jp', null], loginPrompt:{ user:'', password:'', description:'Enter username and password.' }, entryPage:'http://b.hatena.ne.jp/entry/%URL::HATENA%', poster:function(user,password,url,title,comment,tags){ var tagString = tags.length > 0 ? '[' + tags.join('][') + ']' : ""; var request = dummy {tagString + comment} ; var wsse = new WSSEUtils(user,password); return Deferred.http({ method: "post", url: "http://b.hatena.ne.jp/atom/post", data: request.toString(), headers: { "X-WSSE": wsse.getWSSEHeader(), "Content-Type": "application/atom+xml", }, }).next(function(xhr){ if(xhr.status != 201) throw "Hatena Bookmark: failed"; }); }, tags:function(user,password){ var xhr = new XMLHttpRequest(); var hatena_tags = []; // http://b.hatena.ne.jp/retlet/20110322#bookmark-34906937 xhr.open("GET","http://b.hatena.ne.jp/"+user+"/sidebar?with_tags=1",false); xhr.send(null); var mypage_html = parseHTML(xhr.responseText); var tags = getElementsByXPath('id("tags")/li/a', mypage_html); tags.forEach(function(tag){ hatena_tags.push(tag.innerHTML); }); return hatena_tags; }, icon:function(url){ return ''; }, }, 'd': { description:'del.icio.us', account:['https://secure.delicious.com', 'https://secure.delicious.com', null], loginPrompt:{ user:'', password:'', description:'Enter username and password.' }, entryPage:'http://del.icio.us/url/%URL::MD5%', poster:function(user,password,url,title,comment,tags){ var request_url = 'https://api.del.icio.us/v1/posts/add?' + [ ['url', url], ['description', title], ['extended', comment], ['tags', tags.join(' ')] ].map(function(p) p[0] + '=' + encodeURIComponent(p[1])).join('&'); return Deferred.http({ method: "get", url: request_url, user: user, password: password, }).next(function(xhr){ if(xhr.status != 200) throw "del.icio.us: failed"; }); }, tags:function(user,password){ const feed_url = 'http://feeds.delicious.com/feeds/json/tags/'; var returnValue = []; var xhr = new XMLHttpRequest(); xhr.open("GET", feed_url + user + "?raw", false, user, password); xhr.send(null); var tags = evalFunc("(" + xhr.responseText + ")"); for(var tag in tags) returnValue.push(tag); return returnValue; }, icon:function(url){ var url = liberator.modules.buffer.URL; var cryptoHash = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash); cryptoHash.init(Ci.nsICryptoHash.MD5); var inputStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream); inputStream.setData(url, url.length); cryptoHash.updateFromStream(inputStream, -1); var hash = cryptoHash.finish(false), ascii = []; const hexchars = '0123456789ABCDEF'; var hexrep = new Array(hash.length * 2); for(var i = 0; i < hash.length; i++) { ascii[i * 2] = hexchars.charAt((hash.charCodeAt(i) >> 4) & 0xF); ascii[i * 2 + 1] = hexchars.charAt(hash.charCodeAt(i) & 0xF); } return ''; }, }, 'l': { description:'livedoor clip', account:['http://api.clip.livedoor.com', 'http://api.clip.livedoor.com', null], loginPrompt:{ user:'', password:'apikey', description:'Enter username and apikey.\nyou can get "api-key" from\n\thttp://clip.livedoor.com/config/api' }, entryPage:'http://clip.livedoor.com/page/%URL%', poster:function(user,password,url,title,comment,tags){ var rate=0; var starFullRate=5; if(comment.match(/\*+$/)){ comment = RegExp.leftContext; rate = (RegExp.lastMatch.length > starFullRate)? starFullRate : RegExp.lastMatch.length; } var request_url = 'http://api.clip.livedoor.com/v1/posts/add?' + [ ['url', url], ['description', title], ['extended', comment], ['rate', rate], ['tags', tags.join(' ')], ['cache', (new Date()).getTime()] ].map(function(p) p[0] + '=' + encodeURIComponent(p[1])).join('&'); return Deferred.http({ method: "get", url: request_url, user: user, password: password, }).next(function(xhr){ if(xhr.status != 200) throw "livedoor clip: failed"; }); }, tags:function(user,password){ var xhr = new XMLHttpRequest(); var ldc_tags = []; xhr.open("GET","http://clip.livedoor.com/clip/add?link=http://example.example/",false); xhr.send(null); var mypage_html = parseHTML(xhr.responseText); var tags = getElementsByXPath("id(\"tag_list\")/div/span",mypage_html); tags.forEach(function(tag){ ldc_tags.push(tag.textContent); }); return ldc_tags; }, icon:function(url){ return ''; }, }, 'g': { description:'Google Bookmarks', account:null, loginPrompt:null, entryPage:'%URL%', poster:function(user,password,url,title,comment,tags){ var request_url = 'http://www.google.com/bookmarks/mark'; var params = [ ['bkmk', url], ['title', title], ['labels', tags.join(',')], ['annotation', comment] ].map(function(p) p[0] + '=' + encodeURIComponent(p[1])).join('&'); return Deferred.http({ method: "post", url: request_url, data: params, headers: { "User-Agent": navigator.userAgent + " GoogleToolbarFF 3.0.20070525", }, }).next(function(xhr){ if(xhr.status != 200) throw "Google Bookmarks: failed"; }); }, tags:function(user,password){ var returnValue = []; var xhr = new XMLHttpRequest(); xhr.open("GET", "https://www.google.com/bookmarks", false, user, password); xhr.send(null); var html = parseHTML(xhr.responseText); var tags = getElementsByXPath('id("sidenav")/div/ul/li/a[count(*)=1]/text()',html); tags.forEach(function(tag){ returnValue.push(tag.textContent.match(/\S+/)); }); return returnValue; }, }, 'f': { description:'foves', account:['https://secure.faves.com', 'https://secure.faves.com', null], loginPrompt:{ user:'', password:'', description:'Enter username and password.' }, entryPage:'%URL%', poster:function(user,password,url,title,comment,tags){ var request_url = 'https://secure.faves.com/v1/posts/add?' + [ ['url', url], ['description', title], ['extended', comment], ['tags', tags.join(' ')] ].map(function(p) p[0] + '=' + encodeURIComponent(p[1])).join('&'); return Deferred.http({ method: "get", url: request_url, user: user, password: password, }).next(function(xhr){ if(xhr.status != 200) throw "foves: failed"; }); }, tags:function(user,password){ const feed_url = 'https://secure.faves.com/v1/tags/get'; var returnValue = []; var xhr = new XMLHttpRequest(); xhr.open("GET", feed_url, false, user, password); xhr.send(null); var tags = xhr.responseXML.getElementsByTagName('tag'); for(var n = 0; n < tags.length; n++) returnValue.push(tags[n].getAttribute('tag')); return returnValue; }, }, 'p': { description:'Places', account:null, loginPrompt:null, entryPage:'%URL%', poster:function(user,password,url,title,comment,tags){ const taggingService = Cc["@mozilla.org/browser/tagging-service;1"].getService(Ci.nsITaggingService); var nsUrl = Cc["@mozilla.org/network/standard-url;1"].createInstance(Ci.nsIURL); nsUrl.spec = url; taggingService.tagURI(nsUrl,tags); try{ Application.bookmarks.tags.addBookmark(title, nsUrl); }catch(e){ throw "Places: failed"; } }, tags:function(user,password) Application.bookmarks.tags.children.map(function(x) x.title), }, }; liberator.plugins.direct_bookmark = { services: services, tags: [] }; function getTagsAsync(arg){ var d,first; d = first = Deferred(); useServicesByTag.split(/\s*/).forEach(function(service){ var user, password, currentService = services[service] || null; [user,password] = currentService.account ? getUserAccount.apply(currentService,currentService.account) : ["", ""]; d = d.next(function(t) { var tags = currentService.tags(user,password); liberator.echo(currentService.description + ": Tag parsing is finished. Taglist length: " + tags.length); return t.concat(tags); }); }); d.next(function(tags){liberator.plugins.direct_bookmark.tags = tags.filter(function(e,i,a) a.indexOf(e) == i).sort()}) .error(function(e){liberator.echoerr("direct_bookmark.js: Exception throwed! " + e)}); return first; } function getTags(arg){ var user,password; var tags = []; useServicesByTag.split(/\s*/).forEach(function(service){ var currentService = services[service] || null; [user,password] = currentService.account ? getUserAccount.apply(currentService,currentService.account) : [null, null]; tags = tags.concat(currentService.tags(user,password)); }); liberator.plugins.direct_bookmark.tags = tags.filter(function(e,i,a) a.indexOf(e) == i).sort(); } liberator.modules.commands.addUserCommand(['btags'],"Update Social Bookmark Tags", function(arg){setTimeout(function(){getTagsAsync().call([])},0)}, {}); liberator.modules.commands.addUserCommand(['bentry'],"Goto Bookmark Entry Page", function(args){ var service = args.string || useServicesByPost.split(/\s*/)[0]; var currentService = services[service] || null; if(!currentService || !currentService.entryPage) { return; } liberator.open(currentService.entryPage .replace(/%URL(?:::(HATENA|ESC|MD5))?%/g, function(x, t){ if(!t) return liberator.modules.buffer.URL.replace(/#/, '%23'); if(t == "HATENA") return liberator.modules.buffer.URL.replace(/^http:\/\//, '').replace(/^https:\/\//, 's/').replace(/#/, '%23'); if(t == "ESC") return encodeURIComponent(liberator.modules.buffer.URL); if(t == "MD5"){ var url = liberator.modules.buffer.URL; var cryptoHash = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash); cryptoHash.init(Ci.nsICryptoHash.MD5); var inputStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream); inputStream.setData(url, url.length); cryptoHash.updateFromStream(inputStream, -1); var hash = cryptoHash.finish(false), ascii = []; const hexchars = '0123456789ABCDEF'; var hexrep = new Array(hash.length * 2); for(var i = 0; i < hash.length; i++) { ascii[i * 2] = hexchars.charAt((hash.charCodeAt(i) >> 4) & 0xF); ascii[i * 2 + 1] = hexchars.charAt(hash.charCodeAt(i) & 0xF); } return ascii.join('').toLowerCase(); } }), args.bang ? liberator.NEW_TAB : liberator.CURRENT_TAB); },{ completer: function(filter) [0, useServicesByPost.split(/\s*/).map(function(p) [p, services[p].description])] } ); liberator.modules.commands.addUserCommand(['bicon'],"Show Bookmark Count as Icon", function(arg){ var url = getNormalizedPermalink(liberator.modules.buffer.URL); var html = useServicesByTag.split(/\s*/).map(function(service){ var currentService = services[service] || null; return (currentService && typeof currentService.icon === 'function') ? (currentService.description + ': ' + currentService.icon(url)) : null; }).join('
'); liberator.echo(html, true); }, {}); liberator.modules.commands.addUserCommand(['sbm'],"Post to Social Bookmark", function(arg){ var comment = ""; var targetServices = useServicesByPost; if (arg["-s"]) targetServices = arg["-s"]; if (arg.length > 0) comment = arg.join(" "); var tags = []; var re = /\[([^\]]+)\]([^\[].*)?/g; var d = new Deferred(); var first = d; if(/^\[[^\]]+\]/.test(comment)){ var tag, text; while((tag = re.exec(comment))){ [, tag, text] = tag; tags.push(tag); } comment = text || ''; } var url = liberator.modules.buffer.URL; var title = liberator.modules.buffer.title; targetServices.split(/\s*/).forEach(function(service){ var user, password, currentService = services[service] || null; [user,password] = currentService.account ? getUserAccount.apply(currentService,currentService.account) : ["", ""]; d = d.next(function() currentService.poster( user,password, isNormalize ? getNormalizedPermalink(url) : url,title, comment,tags //)); )); if(echoType == "multiline") { d = d.next(function(){ liberator.echo("[" + services[service].description + "] post completed."); }); } }); if(echoType == "simple") { d = d.next(function(){ liberator.echo("post completed."); }); } d.error(function(e){liberator.echoerr("direct_bookmark.js: Exception throwed! " + e);liberator.log(e);}); setTimeout(function(){first.call();},0); },{ completer: function(context, arg){ let filter = context.filter; var match_result = filter.match(/((?:\[[^\]]*\])*)\[?(.*)/); //[all, commited, now inputting] var m = new RegExp(XMigemoCore && isUseMigemo ? "^(" + XMigemoCore.getRegExp(match_result[2]) + ")" : "^" + match_result[2],'i'); var completionList = []; // XXX: completer works asynchronous. thus we shouldn't call getTagsAsync(). if(liberator.plugins.direct_bookmark.tags.length == 0) getTags().call([]); context.title = ['Tag','Description']; context.advance( match_result[1].length ); context.completions = [["[" + tag + "]","Tag"] for each (tag in liberator.plugins.direct_bookmark.tags) if (m.test(tag) && match_result[1].indexOf('[' + tag + ']') < 0)]; }, options: [ [['-s','-service'], liberator.modules.commands.OPTION_STRING], ] } ); })(); // vim:sw=4 ts=4 et: