/** * ==VimperatorPlugin== * @name SBM Comments Viewer * @description List show Social BookMark Comments * @description-ja ソーシャル・ブックマーク・コメントを表示します * @version 0.1a * ==/VimperatorPlugin== * * Usage: * * viewSBMComments [url] [options] * url : 省略時は現在のURL * options: * -f, -format : 出力時のフォーマット(`,'区切りのリスト) * (default: id,timestamp,tags,comment) * let g:def_sbm_format = ... で指定可能 * -t, -type : 出力するSBMタイプ * (default: hdl) * let g:def_sbms = ... で指定可能 * -c, -count : ブックマーク件数のみ出力 * -b, -browser: バッファ・ウィンドウではなくブラウザに開く * TODO:まだ出来てない * * 指定可能フォーマット: * id, timpstamp, tags, comment * * SBMタイプ: * h : hatena bookmark * d : del.icio.us bookmark * l : livedoor clip * XXX:今後増やしていきたい * * 例: * :viewSBMComments http://d.hatena.ne.jp/teramako/ -t hdl -f id,comment -c * * 備考: * * 一度取得したものは(30分ほど)キャッシュに貯めてますので何度も見直すことが可能です。 * * 粋なコマンド名募集中 */ liberator.plugins.sbmCommentsViewer = (function(){ /** * SBMEntry Container {{{ * @param {String} type * @param {Number} count * @param {Object} extra * extra = { * faviconURL, * pageURL * } */ function SBMContainer(type, count, extra){ //{{{ this.type = type; this.count = count || 0; this.entries = []; if (extra){ this.faviconURL = extra.faviconURL || ''; this.pageURL = extra.pageURL || ''; } } //}}} SBMContainer.prototype = { //{{{ add: function(id, timestamp, comment, tags, extra){ this.entries.push(new SBMEntry( id, timestamp, comment, tags, extra )); }, toHTMLString: function(format, countOnly){ var label = (this.faviconURL ? '' : '') + manager.type[this.type] + ' ' + this.count + '(' + this.entries.length + ')' + (this.pageURL ? ' ' + this.pageURL + '' : ''); if (countOnly){ return label; } else { var str = [ '' ]; format.forEach(function(colum){ var name = manager.format[colum] || '-'; str.push(''); }); str.push(''); this.entries.forEach(function(e){ str.push(e.toHTMLString(format)); }); str.push('
' + label + '
' + name + '
'); return str.join(''); } } }; //}}} // }}} /** * SBM Entry {{{ * @param {String} id UserName * @param {String|Date} timestamp * @param {String} comment * @param {String[]} tags * @param {Object} extra * extra = { * userIcon * link * } */ function SBMEntry(id, timestamp, comment, tags, extra){ //{{{ this.id = id || ''; this.timeStamp = timestamp instanceof Date ? timestamp : null; this.comment = comment || ''; this.tags = tags || []; if (extra){ this.userIcon = extra.userIcon || null; this.link = extra.link || null; } } //}}} SBMEntry.prototype = { //{{{ toHTMLString: function(format){ // E4X で書く手もあるけど、liberator.echoを使って出力すると // toString後に"\n"が
に変換されてしまうのでStringで var str = ['']; var self = this; format.forEach(function(colum){ switch(colum){ case 'id': str.push('' + (self.userIcon ? '' : '') + self.id + ''); break; case 'timestamp': str.push('' + self.formatDate() + ''); break; case 'tags': str.push('' + self.tags.join(',') + ''); break; case 'comment': str.push('' + self.comment + ''); break; default: str.push('-'); } }); str.push(''); return str.join(''); }, formatDate: function(){ if (!this.timeStamp) return ''; var [year,month,day,hour,min,sec] = [ this.timeStamp.getFullYear(), this.timeStamp.getMonth()+1, this.timeStamp.getDate(), this.timeStamp.getHours(), this.timeStamp.getMinutes(), this.timeStamp.getSeconds() ]; return [ year, '/', (month < 10 ? '0'+month : month), '/', (day < 10 ? '0'+day : day), ' ', (hour < 10 ? '0'+hour : hour), ':', (min < 10 ? '0'+min : min), ':', (sec < 10 ? '0'+sec : sec) ].join(''); } }; //}}} //}}} /** * openSBM {{{ * @param {String} url * @param {String} type * @param {String[]} format * @param {Boolean} countOnly * @param {Boolean} openToBrowser */ function openSBM(url, type, format, countOnly, openToBrowser){ var sbmLabel = manager.type[type]; var sbmURL = SBM[sbmLabel].getURL(url); var xhr = new XMLHttpRequest(); xhr.open('GET', sbmURL, true); xhr.onreadystatechange = function(){ if (xhr.readyState == 4){ if (xhr.status == 200){ var sbmContainer = SBM[sbmLabel].parser.call(this, xhr); if (!sbmContainer) return; cacheManager.add(sbmContainer, url, type); if (openToBrowser) manager.open(sbmContainer.toHTMLString(format,false)); else liberator.echo(sbmContainer.toHTMLString(format,countOnly), true); } else { liberator.echoerr(sbmURL + ' ' + xhr.status, true); } } } xhr.send(null); } //}}} /** * getURL と parser メソッドを供えること * getURL は 取得先のURLを返すこと * parser は SBMContainer オブジェクトを返すこと */ var SBM = { //{{{ hatena: { //{{{ getURL: function(url){ var urlPrefix = 'http://b.hatena.ne.jp/entry/json/?url='; return urlPrefix + encodeURIComponent(url.replace(/%23/g,'#')); }, parser: function(xhr){ var json = window.eval(xhr.responseText); var count = json.bookmarks.length; var c = new SBMContainer('h', json.count, { faviconURL:'http://b.hatena.ne.jp/favicon.ico', pageURL: 'http://b.hatena.ne.jp/entry/' + json.url }); json.bookmarks.forEach(function(bm){ c.add(bm.user, new Date(bm.timestamp), bm.comment, bm.tags, { userIcon: 'http://www.hatena.ne.jp/users/' + bm.user.substring(0,2) + '/' + bm.user +'/profile_s.gif' }); }); return c; } }, //}}} delicious: { //{{{ getURL: function(url){ //var urlPrefix = 'http://del.icio.us/rss/url/'; var urlPrefix = 'http://feeds.delicious.com/rss/url/'; return urlPrefix + getMD5Hash(url); }, parser: function(xhr){ var rss = xhr.responseXML; if (!rss){ liberator.echoerr('del.icio.us feed is none',true); return; } try { var pageURL = evaluateXPath(rss, '//rss:channel/rss:link')[0].textContent; var items = evaluateXPath(rss, '//rss:item'); } catch(e){ liberator.log(e); } var c = new SBMContainer('d', items.length, { faviconURL: 'http://del.icio.us/favicon.ico', pageURL: pageURL }); items.forEach(function(item){ var children = item.childNodes; var [id,date,tags,comment,link] = ['','',[],'','']; for (var i=0; i/g,'>') : '', clip.tags, { userIcon: 'http://image.clip.livedoor.com/profile/' + '?viewer_id=[%%20member.livedoor_id%20Z%]&target_id=' + clip.livedoor_id, link: 'http://clip.livedoor.com/clips/' + clip.livedoor_id } ); }); return c; } else { liverator.log('Faild: LivedoorClip'); } } }, //}}} }; //}}} /** * getMD5Hash {{{ * @param {String} str * @return {String} MD5HashString */ function getMD5Hash(str){ var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]. createInstance(Components.interfaces.nsIScriptableUnicodeConverter); converter.charset = 'UTF-8'; var result = {}; var data = converter.convertToByteArray(str, result); var ch = Components.classes['@mozilla.org/security/hash;1'].createInstance(Components.interfaces.nsICryptoHash); ch.init(ch.MD5); ch.update(data, data.length); var hash = ch.finish(false); function toHexString(charCode){ return ('0' + charCode.toString(16)).slice(-2); } var s = [toHexString(hash.charCodeAt(i)) for (i in hash)].join(''); return s; } //}}} /** * evaluateXPath {{{ * @param {Element} aNode * @param {String} aExpr XPath Expression * @return {Element[]} * @see http://developer.mozilla.org/ja/docs/Using_XPath */ function evaluateXPath(aNode, aExpr){ var xpe = new XPathEvaluator(); function nsResolver(prefix){ var ns = { xhtml: 'http://www.w3.org/1999/xhtml', rdf: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', dc: 'http://purl.org/dc/elements/1.1/', rss: 'http://purl.org/rss/1.0/', taxo: 'http://purl.org/rss/1.0/modules/taxonomy/', content: 'http://purl.org/rss/1.0/modules/content/', syn: 'http://purl.org/rss/1.0/modules/syndication/', admin: 'http://webns.net/mvcb/' }; return ns[prefix] || null; } var result = xpe.evaluate(aExpr, aNode, nsResolver, 0, null); var found = []; var res; while (res = result.iterateNext()) found.push(res); return found; } //}}} /** * sbmCommentsView manager {{{ * @alias liberator.plugins.sbmCommentsViewer */ var manager = { type: { h: 'hatena', d: 'delicious', l: 'livedoorclip' }, format: { id: 'ID', comment: 'Comment', timestamp: 'TimeStamp', tags: 'Tags' }, // for debug convertMD5: function(str){ return getMD5Hash(str); }, // for debug getXML: function(url){ var xhr = new XMLHttpRequest(); xhr.open('GET',url,false); xhr.send(null); return xhr; }, // for debug get cache(){ return cacheManager; }, /** * @param {String} str * @param {Number} where * TODO */ open: function(str, where){ /* getBrowser().addTab('data:text/html,'+str, null,null,null); */ } }; //}}} commands.addUserCommand(['viewSBMComments'], 'SBM Comments Viewer', //{{{ function(arg){ //{{{ var types = liberator.globalVariables.def_sbms || 'hdl'; var format = (liberator.globalVariables.def_sbm_format || 'id,timestamp,tags,comment').split(','); var countOnly = false, openToBrowser = false; var url = buffer.URL; var res = liberator.commands.parseArgs(arg, this.args); if (res){ if (res.args.length > 0){ res.args.forEach(function(arg){ switch(arg){ case '-c': case '-count': countOnly = true; break; case '-b': case '-browser': openToBrowser = true; break; default: url = arg; } }); } if (res.opts.length > 0){ res.opts.forEach(function(opt){ switch(opt[0]){ case '-t': if (opt[1]) types = opt[1]; break; case '-f': if (opt[1]) format = opt[1]; break; case '-c': countOnly = true; break; case '-b': openToBrowser = true; break; } }); } if (res.args[0]) url = res.args[0]; } for (var i=0; i threshold) delete cache[url][type]; } } }, isAvailable: function(url, type){ if (cache[url] && cache[url][type] && new Date() - cache[url][type][0] < threshold) return true; return false; } }; return c_manager; })(); return manager; })(); // vim: sw=4 ts=4 sts=0 et fdm=marker: