Skip to content

Instantly share code, notes, and snippets.

@retlet
Created March 21, 2010 09:54
Show Gist options
  • Save retlet/339192 to your computer and use it in GitHub Desktop.
Save retlet/339192 to your computer and use it in GitHub Desktop.
var PLUGIN_INFO =
<VimperatorPlugin>
<name>SBM Comments Viewer</name>
<description>List show Social Bookmark Comments</description>
<description lang="ja">ソーシャル・ブックマーク・コメントを表示します</description>
<version>0.1.2</version>
<minVersion>2.0pre</minVersion>
<maxVersion>2.3</maxVersion>
<updateURL>http://svn.coderepos.org/share/lang/javascript/vimperator-plugins/trunk/sbmcommentsviewer.js</updateURL>
<detail><![CDATA[
== 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 : Delicious
- l : livedoor clip
- z : Buzzurl
- XXX:今後増やしていきたい
>||
e.g.)
:viewSBMComments http://d.hatena.ne.jp/teramako/ -t hdl -f id,comment -c
||<
== 備考 ==
一度取得したものは(30分ほど)キャッシュに貯めてますので何度も見直すことが可能です。
粋なコマンド名募集中
]]></detail>
</VimperatorPlugin>;
liberator.plugins.sbmCommentsViewer = (function(){
var isFilterNoComments = liberator.globalVariables.sbm_comments_viewer_filter_nocomments || false;
/**
* 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
));
},
toHTML: 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="#" highlight="URL">{this.pageURL}</a> : <></>}
</>;
if (countOnly){
return label;
} else {
let xml = <dl class="liberator-sbmcommentsviewer" style="width: 99%; margin: 0; padding: .5em 0; line-height: 1.6;">
<dt highlight="CompTitle">{label}</dt>
</dl>;
let self = this;
xml.* += (function(){
var dd = <></>;
self.entries.forEach(function(e){
if (isFilterNoComments && !e.comment) return;
dd += e.toHTML(format);
});
return dd;
})();
return xml;
}
}
}; //}}}
// }}}
/**
* 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 = { //{{{
toHTML: function(format){
var xml = <dd highlight="Completions" style="margin: 0; padding: 3px 5px; border-bottom: 1px solid #333;"/>;
var self = this;
format.forEach(function(colum){
switch(colum){
case 'id':
xml.* += <span class="liberator-sbmcommentsviewer-id" style="margin-right: 10px;">{self.userIcon ? <><img src={self.userIcon} width="16" height="16" style="margin-right: 5px; vertical-align: middle;"/>{self.id}</> : <>{self.id}</>}</span>;
break;
case 'timestamp':
xml.* += <span class="liberator-sbmcommentsviewer-timestamp" style="margin-right: 10px;">{self.formatDate()}</span>; break;
case 'tags':
xml.* += <span class="liberator-sbmcommentsviewer-tags" highlight="Tag" style="margin-right: 10px;">{self.tags.join(',')}</span>; break;
case 'comment':
xml.* += <span class="liberator-sbmcommentsviewer-comment" style="margin-right: 10px; white-space: normal;">{self.comment}</span>; break;
default:
xml.* += <span>-</span>;
}
});
return xml;
},
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){
let sbmContainer = SBM[sbmLabel].parser.call(this, xhr);
if (!sbmContainer) return;
cacheManager.add(sbmContainer, url, type);
if (openToBrowser)
manager.open(sbmContainer.toHTML(format,false));
else
liberator.echo(sbmContainer.toHTML(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/jsonlite/?url=';
return urlPrefix + encodeURIComponent(url.replace(/%23/g,'#'));
},
parser: function(xhr){
//var json = window.eval(xhr.responseText);
var json = jsonDecode(xhr.responseText, false);
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('Delicious feed is none',true);
return;
}
var pageURL, items;
try {
pageURL = evaluateXPath(rss, '//rss:channel/rss:link')[0].textContent;
items = evaluateXPath(rss, '//rss:item');
} catch(e){
liberator.log(e);
}
var c = new SBMContainer('d', items.length, {
faviconURL: 'http://delicious.com/favicon.ico',
pageURL: pageURL
});
items.forEach(function(item){
var children = item.childNodes;
var [id,date,tags,comment,link] = ['','',[],'',''];
for (let i=0; i<children.length; i++){
let 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);
*/
var json = jsonDecode(xhr.responseText);
if (json && json.isSuccess){
let 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 : '',
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 {
liberator.log('Faild: LivedoorClip');
}
}
}, //}}}
buzzurl: { //{{{
getURL: function(url){
var urlPrefix = 'http://api.buzzurl.jp/api/posts/get/v1/json/?url=';
return urlPrefix + encodeURIComponent(url.replace(/%23/g,'#'));
},
parser: function(xhr){
var url = 'http://buzzurl.jp/user/';
var json = jsonDecode(xhr.responseText);
if (json && json[0] && json[0].user_num){
let c = new SBMContainer('buzzurl', json[0].user_num, {
faviconURL: 'http://buzzurl.jp/favicon.ico',
pageURL: 'http://buzzurl.jp/entry/' + json[0].url
});
json[0].posts.forEach(function(entry){
c.add( entry.user_name, window.eval('new Date(' + entry.date.split(/[-\s:]/,6).join(',') + ')'),
entry.comment ? entry.comment : '', entry.keywords.split(','),
{
userIcon: url + entry.user_name + '/photo',
link: url + '/' + entry.user_name
}
);
});
return c;
} else {
liberator.log('Faild: Buzzurl');
}
}
} //}}}
}; //}}}
/**
* jsonDecode {{{
* @param {String} str JSON String
* @param {Boolean} toRemove はてなブックマークのJSONの様に
* 前後に()が付いている場合に取り除くためのフラグ
*/
function jsonDecode(str, toRemove){
var json = Components.classes['@mozilla.org/dom/json;1'].getService(Components.interfaces.nsIJSON);
if (toRemove) str = str.substring(1, str.length -1);
return json.decode(str);
}
//}}}
/**
* 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 = [i < hash.length ? 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',
z: 'buzzurl'
},
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);
*/
}
}; //}}}
var options = [
[['-type','-t'], commands.OPTION_STRING, function(str) (new RegExp('^['+[t for(t in manager.type)].join('') + ']+$')).test(str)],
[['-format','-f'], commands.OPTION_LIST,null, [[f,manager.format[f]] for (f in manager.format)]],
[['-count','-c'], commands.OPTION_NOARG],
[['-browser','-b'],commands.OPTION_NORARG]
];
commands.addUserCommand(['viewSBMComments'], 'SBM Comments Viewer', //{{{
function(arg){ //{{{
var types = liberator.globalVariables.def_sbms || 'hdlz';
var format = (liberator.globalVariables.def_sbm_format || 'id,timestamp,tags,comment').split(',');
var countOnly = false, openToBrowser = false;
var url = buffer.URL;
[
let (v = arg['-' + name]) (v && f(v))
for ([name, f] in Iterator({
count: function () countOnly = true,
browser: function () openToBrowser = true,
type: function (v) (types = v),
format: function (v) (format = v),
arguments: function (v) (v.length > 0 && (url = v[0]))
}))
]
for (let i=0; i<types.length; i++){
let 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).toHTML(format,false), liberator.forceNewTab);
else
liberator.echo(cacheManager.get(url, type).toHTML(format,countOnly), true);
} else {
try {
openSBM(url, type, format, countOnly, openToBrowser);
} catch(e){
liberator.log(e);
}
}
}
}
}, //}}}
{
argCount:"*",
options: options,
completer: function(context) completion.url(context, 'l')
},
true
); //}}}
/**
* cacheManager {{{
*/
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 (let url in cache){
for (let 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:
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment