diff options
author | teramako | 2008-05-04 21:41:22 +0000 |
---|---|---|
committer | teramako | 2008-05-04 21:41:22 +0000 |
commit | 827d36be905b07e85608d0d5547796e514466f6d (patch) | |
tree | 2c12c902dcbba9ef5b5dd3c045423c0e8383715e /sbmcommentsviewer.js | |
parent | e5fe7d3cd082eceef055aa0d4f442a8b6aadb4b5 (diff) | |
download | vimperator-plugins-827d36be905b07e85608d0d5547796e514466f6d.tar.bz2 |
new add
git-svn-id: http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk@11139 d0d07461-0603-4401-acd4-de1884942a52
Diffstat (limited to 'sbmcommentsviewer.js')
-rw-r--r-- | sbmcommentsviewer.js | 508 |
1 files changed, 508 insertions, 0 deletions
diff --git a/sbmcommentsviewer.js b/sbmcommentsviewer.js new file mode 100644 index 0000000..fe91579 --- /dev/null +++ b/sbmcommentsviewer.js @@ -0,0 +1,508 @@ +/** + * ==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 ? '<img src="' + this.faviconURL + '" width="16" height="16"/>' : '') + + manager.type[this.type] + ' ' + this.count + '(' + this.entries.length + ')' + + (this.pageURL ? ' <a href="' + this.pageURL + '">' + this.pageURL + '</a>' : ''); + if (countOnly){ + return label; + } else { + var str = [ + '<table><caption style="text-align:left;" class="hl-Title">' + label + '</caption><tr>' + ]; + format.forEach(function(colum){ + var name = manager.format[colum] || '-'; + str.push('<th>' + name + '</th>'); + }); + str.push('</tr>'); + this.entries.forEach(function(e){ + str.push(e.toHTMLString(format)); + }); + str.push('</table>'); + 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"が<br/>に変換されてしまうのでStringで + var str = ['<tr>']; + var self = this; + format.forEach(function(colum){ + switch(colum){ + case 'id': + str.push('<td>' + (self.userIcon ? '<img src="'+self.userIcon +'" width="16" height="16"/>' : '') + + self.id + '</td>'); + break; + case 'timestamp': + str.push('<td>' + self.formatDate() + '</td>'); break; + case 'tags': + str.push('<td>' + self.tags.join(',') + '</td>'); break; + case 'comment': + str.push('<td style="white-space:normal;">' + self.comment + '</td>'); break; + default: + str.push('<td>-</td>'); + } + }); + str.push('</tr>'); + 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<children.length; i++){ + var node = children[i]; + if (node.nodeType == 1){ + switch (node.localName){ + case 'creator': id = node.textContent; break; + case 'link': link = node.textContent; break; + case 'date': + date = window.eval('new Date(' + node.textContent.split(/[-T:Z]/,6).join(',') + ')'); + break; + case 'description': comment = node.textContent; break; + case 'subject': tags = node.textContent.split(/\s+/); break; + } + } + } + c.add(id, date, comment, tags, {link: link}); + }); + return c; + } + }, //}}} + livedoorclip: { //{{{ + getURL: function(url){ + var urlPrefix = 'http://api.clip.livedoor.com/json/comments?link='; + return urlPrefix + encodeURIComponent(url.replace(/%23/g,'#')) + '&all=0'; + }, + parser: function(xhr){ + var json = Components.classes['@mozilla.org/dom/json;1']. + getService(Components.interfaces.nsIJSON). + decode(xhr.responseText); + if (json && json.isSuccess){ + var c = new SBMContainer('l', json.total_clip_count, { + faviconURL: 'http://clip.livedoor.com/favicon.ico', + pageURL: 'http://clip.livedoor.com/page/' + json.link + }); + json.Comments.forEach(function(clip){ + c.add( clip.livedoor_id, new Date(clip.created_on * 1000), + clip.notes ? clip.notes.replace(/</g,'<').replace(/>/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<types.length; i++){ + var type = types.charAt(i); + if ( manager.type[type] ){ + if ( cacheManager.isAvailable(url, type) ){ + liberator.log('cache avairable'); + if (openToBrowser) + // TODO + manager.open(cacheManager.get(url,type).toHTMLString(format,false), liberator.forceNewTab); + else + liberator.echo(cacheManager.get(url, type).toHTMLString(format,countOnly), true); + } else { + try { + openSBM(url, type, format, countOnly, openToBrowser); + } catch(e){ + liberator.log(e); + } + } + } + } + }, //}}} + { + args: [ + [['-t','-type'], liberator.commands.OPTION_STRING], + [['-f','-format'], liberator.commands.OPTION_LIST], + [['-c','-count'], liberator.commands.OPTION_NOARG], + [['-b','-browser'],liberator.commands.OPTION_NORARG] + ] + } +); //}}} + +var cacheManager = (function(){ + var cache = {}; + // min sec millisec + var threshold = 30 * 60 * 1000; + var interval = 10 * 60 * 1000; + var c_manager = { + get raw(){ + return cache; + }, + has: function(url){ + if (cache[url]) + return true; + else + return false; + }, + add: function(sbmComments, url, type){ + if (!cache[url]) cache[url] = {}; + cache[url][type] = [new Date(), sbmComments]; + }, + get: function(url, type){ + return cache[url][type][1]; + }, + delete: function(url, type) { + if (!cache[url]) return true; + if (!type) return delete cache[url]; + return delete cache[url][type]; + }, + garbage: function(){ + var date = new Date(); + for (var url in cache){ + for (var type in cache[url]){ + if (date - cache[url][type][0] > 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: |