Created
September 1, 2008 22:29
-
-
Save satyr/8351 to your computer and use it in GitHub Desktop.
はてブ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Utils.extend(feed, | |
{ title: 'Hatena::Bookmark Commands' | |
, author: {name: 'satyr', homepage: 'http://d.hatena.ne.jp/murky-satyr'} | |
, license: 'MIT' | |
}) | |
const | |
Hatebu = 'http://b.hatena.ne.jp/', | |
Hotent = Hatebu +'hotentry', | |
HLogin = 'https://www.hatena.ne.jp/login', | |
Icon = Hatebu +'favicon.ico', | |
Logo = <a class="logo" href={Hatebu} accesskey="/"/>.appendChild( | |
(<img width="64" height="11" src={Hatebu +'images/logo1.gif'}/>) + | |
(<img width="77" height="11" src={Hatebu +'images/logo2.gif'}/>)), | |
CSS = ''+<![CDATA[ | |
a img {border:none} | |
kbd, input[type=button] { | |
font-weight:bold; font-family:monospace; margin:0 0.2em} | |
input[type=button] {padding:0; border-width:1px} | |
kbd {text-decoration:underline; text-transform:uppercase} | |
.icon {vertical-align:middle} | |
.timestamp {font-size:smaller} | |
.logo { | |
background-color:#2c6ebd; display:inline-block; width:146px; | |
text-align:center; padding:2px 0 3px; line-height:1; | |
-moz-border-radius:6px} | |
.logo > img {vertical-align:bottom} | |
.error {font-style:oblique} | |
.loading {opacity:0.9} | |
.loading + .logo {opacity:0.4} | |
.error + .logo {opacity:0.7} | |
]]> | |
for each(let o in [{ | |
name: 'it', | |
description: 'Hatena::Bookmark'.link(Hatebu) +'s the current page.', | |
help: ''+ ( | |
<>Supported Services: | |
<ul style="list-style-image:none"> | |
<li><a href="http://reader.google.com">Google Reader</a></li> | |
<li><a href="http://reader.livedoor.com">livedoor Reader</a></li> | |
<li><a href="http://fastladder.com">Fastladder</a></li> | |
</ul></>), | |
contributor: {name: 'powchin', homepage: 'http://friendfeed.com/powchin'}, | |
arguments: { | |
'object [tag;[...;] ]comment': noun_arb_text, | |
alias: { | |
name: 'hatebu title', | |
label: 'title', | |
default: function nt_htb_ttl_default() [this._empty, this._paget], | |
suggest: function nt_htb_ttl_suggest(txt, htm, cb, sx) | |
[CmdUtils.makeSugg(txt, htm, null, .2, sx), this._paget], | |
_empty: {text: '', summary: ' ', score: .5}, | |
get _paget() | |
let(nf = info()) CmdUtils.makeSugg(nf.title || nf.url, null, null, .4), | |
}, | |
}, | |
execute: function htbi_execute({object: {text: cmn}, alias: {text: ttl}}){ | |
var bin = this._bin; | |
var {data, $f} = bin; | |
if(data && data.rks){ | |
if(ttl) data.title = ttl; | |
else ttl = $f.find('.title').text(); | |
cmn = data.comment = parse(cmn).comm.replace(/[\r\n]/g, ' '); | |
$.ajax({ | |
url: $f.attr('action'), data: data, type: 'POST', | |
error: howl, success: function() say(ttl, cmn), | |
}); | |
} else { | |
say('Not ready', 'Opening the bookmarklet page instead.'); | |
Utils.openUrlInBrowser(this._req()); | |
} | |
wipe(bin); | |
}, | |
preview: function htbi_preview(pb, {object, alias: {text: ttl}}){ | |
var {comm, size, tags} = parse(object.text), req = this._req(); | |
var lmn = this._lmn(pb), doc = pb.ownerDocument, bin = this._bin; | |
var [inp, cnt] = lmn.childNodes; | |
inp.innerHTML = ( | |
(<div class="comment">{comm}</div>).appendChild( | |
<span class="count"/>.appendChild( | |
<><sup>{size}</sup>/<sub>300</sub></>)) + | |
(ttl && <div class="mytitle">{ttl}</div>)); | |
Array.forEach(cnt.getElementsByClassName('tag'), check, tags); | |
if(cnt.childElementCount && req === bin.preq){ | |
let te = doc.getElementById('title-edit'); | |
if(te) te.style.textDecoration = ttl && 'line-through'; | |
return; | |
} | |
if(lmn.classList.contains('loading')) return; | |
wipe(bin); | |
var me = this; | |
ajax(lmn, {url: bin.preq = req}, function htbi_get(htm){ | |
if(!/<form [^]+?<\/form>/.exec(htm)){ | |
if(!~htm.indexOf(HLogin)) return ng(lmn, htm); | |
login( | |
function htbi_login_ok() ajax(lmn, {url: req}, htbi_get), | |
function htbi_login_ng(msg) | |
ng(lmn, 'Failed to auto-'+ 'login'.link(HLogin) +'. ('+ msg +')')); | |
return ok(lmn, '<em>Attempting auto-login ...</em>', 'loading'); | |
} | |
var div = doc.createElement('div'); | |
div.innerHTML = RegExp.lastMatch.replace(/<img src=..images[^>]+>/g, ''); | |
var val, data = bin.data = {}; | |
var [form] = bin.$f = ( | |
$(div.firstChild) | |
.find('input, textarea').each(function(){ | |
if(this.type == 'hidden') data[this.name] = this.value; | |
else if(this.id == 'comment' && this.value > '') | |
val = (this.value.replace(/\]\[/g, ';') | |
.replace(/^\[([^\[]+)\]/, '$1; ')); | |
}).end() | |
.find('#title-th-head, table:eq(1)').remove().end() | |
.find('a, img').each(abs).end() | |
.each(abs)) | |
ok(lmn, form.innerHTML) | |
Array.forEach(doc.querySelectorAll('#recommend-tags, #all-tags'), | |
function(n) n.addEventListener('click', tagplus, false)) | |
if(val && doc.getElementById(me._id)){ | |
with(box()) value = (/\S+/.exec(value) || me.name) +' '+ val; | |
htbi_preview.call(me, pb, {object: {text: val}, alias: {text: ''}}); | |
} | |
}); | |
}, | |
previewDelay: 99, | |
_req: function htb__req() Hatebu +'bookmarklet?url='+ enc4htb(info().url), | |
_css: <![CDATA[ | |
dl {margin:0.5em 0} | |
dt, .comment {font-weight:bold} | |
dd {margin-left:0.5em} | |
.comment {font-size:104%} | |
.mytitle {font-size:112%; text-align:center} | |
.count {font-size:80%; padding:0 0.5em} | |
.title {background:transparent none no-repeat left; padding-left:18px} | |
.tag { | |
display:inline-block; margin-right:0.5em; | |
-moz-outline-radius:4px; cursor:pointer} | |
.matched {outline:1px dotted} | |
.selected {outline:1px solid; font-weight:bolder} | |
.content {font-style:normal} | |
.error .content {padding:0 0.2em; font-style:oblique} | |
#comment, .bookmark-submit-container, .note {display:none} | |
]]>, | |
_lmn: <div><div class="input"> </div><div class="content"> </div></div>, | |
},{ | |
name: 'hotentry', | |
description: Hotent.link(Hotent), | |
execute: Hotent, | |
preview: function hoten_preview(pb){ | |
var me = this, lmn = me._lmn(pb); | |
ajax(lmn, {url: Hotent +'/diary.rss', dataType: 'xml'}, function(xml){ | |
CmdUtils.previewList( | |
lmn, | |
Array.map( | |
xml.getElementsByTagName('item'), | |
function(item)( | |
let([t, l] = | |
[item.querySelector(n).textContent for each(n in this)]) | |
<><img class="favicon" src={favicon(l)}/> | |
<a href={l}>{t}</a></> +' '+ entimg(l)), | |
['title', 'link']), | |
me.__go); | |
lmn.className = ''; | |
}); | |
}, | |
__go: function hoten__go(i, e){ | |
Utils.openUrlInBrowser(e.target.parentNode.querySelector('a').href); | |
}, | |
_css: <![CDATA[ img {vertical-align:middle} ]]>, | |
_lmn: <div> </div>, | |
},{ | |
name: 'comments', | |
description: 'Shows '+ 'hatebu'.link(Hatebu) +' comments for the page.', | |
help: 'Supports the same services as "hatebu-it".', | |
execute: function hbc_execute(){ Utils.openUrlInBrowser(entry(info().url)) }, | |
preview: function hbc_preview(pb){ | |
var lmn = this._lmn(pb), bin = this._bin, | |
req = Hatebu +'entry/json/?url='+ enc4htb(info().url); | |
if(lmn.children.length && req === bin.preq) return; | |
ajax(lmn, {url: bin.preq = req, dataType: 'json'}, function(r){ | |
if(r) ok( | |
lmn, | |
'<a class="entry_url" accesskey="e" href="'+ r.entry_url + | |
'"><span class="count">'+ r.count +'</span> us<u>e</u>rs</a><ol>'+ | |
r.bookmarks.reduce(function(s, {user, tags, comment, timestamp}){ | |
return !tags.length && !comment ? s : s.concat( | |
'<li><a class="user" href="', Hatebu, user, | |
'"><img src="http://www.hatena.ne.jp/users/', user.slice(0, 2), | |
'/', user, '/profile_s.gif"/><span class="name">', user, | |
'</span></a><span class="tags">', | |
[t.link(Hatebu + user +'/'+ t +'/') for each(t in tags)], | |
'</span><span class="comment">', comment, | |
'</span><span class="timestamp">', timestamp, '</span></li>'); | |
}, '') +'</ol>'); | |
else ng(lmn, 'No bookmarks.'); | |
}); | |
}, | |
_css: <![CDATA[ | |
ol {list-style:none; padding:0; margin:0.2em 0 0} | |
li img {vertical-align:middle} | |
span {margin-left:0.2em} | |
.entry_url {font-size:108%; float:right; margin:0 1em} | |
.count, .user {font-weight:bolder} | |
]]>, | |
},{ | |
name: 'search', | |
description: 'Searches '+ 'hatebu'.link(Hatebu) +' for the specified user.', | |
arguments: { | |
object: noun_arb_text, | |
source: { | |
name: 'hatena id', | |
label: 'id', | |
default: (function(ls){ | |
if(!ls.length) ls[0] = {username: ''}; | |
var ss = [{text: n, summary: n} for each({username: n} in ls)]; | |
return function nt_hatena_id_default() ss; | |
})(users()), | |
suggest: function nt_hatena_id_suggest(txt, htm, cb, sx) | |
/^[A-Za-z][\w-]{1,30}[A-Za-z\d]$/.test(txt = txt.trim()) | |
? [CmdUtils.makeSugg(txt, htm, .4, sx)] : [], | |
}, | |
}, | |
execute: function hbs_execute({object: {text}, source: {text: user}}){ | |
Utils.openUrlInBrowser( | |
Hatebu + user + (text && '/?q='+ encodeURIComponent(text))); | |
}, | |
preview: function hbs_preview(pb, args, offset){ | |
pb.innerHTML = | |
'Preview is disabled for now, '+ | |
'due to Firefox 4 having issue accessing <em>search.data</em>.' | |
return | |
var me = this, txt = args.object.text, usr = args.source.text; | |
var {dat} = this._bin, bms = dat[usr], lmn = this._lmn(pb); | |
if(usr) switch(bms){ | |
case 100: return; | |
case 403: let msg = 'Private mode: '; | |
case 404: return ng(lmn, (msg || 'No such user: ') + usr.bold()); | |
case void '': | |
dat[usr] = 100; | |
lmn.className = 'loading'; | |
return me._get(dat, usr); | |
} | |
lmn.className = ''; | |
if(!txt || !usr) return void this.previewDefault(pb); | |
var re = Utils.regexp(txt, 'i'); | |
var {txt, num} = bms, len = num.length, cnt = 0, ls = ['<ol>']; | |
for(var i = offset |= 0; i < len; ++i){ | |
let i3 = i * 3, t = txt[i3], c = txt[i3+1], u = txt[i3+2]; | |
if(!(re.test(t) || re.test(c) || re.test(u))) continue; | |
let k = (++cnt + 9).toString(36), [n, d] = num[i].split('\t'); | |
ls[cnt] = '<li>'+ ( | |
<><img class="icon" src={favicon(u)}/><a class="title" href={u} | |
accesskey={cnt}>{t}</a><nobr><a class="user" href={entry(u)} | |
accesskey={k}>{n}</a><kbd>{k}</kbd></nobr></> + | |
('<div class="comment">'+ tagsub(c, usr) + | |
'<span class="timestamp">'+ | |
d.slice(0, 4) +'-'+ d.slice(4, 6) +'-'+ d.slice(6, 8) + | |
'</span></div></li>')); | |
if(cnt >= 9) break; | |
} | |
lmn.innerHTML = ( | |
'<div id="navi">'+ | |
'<input type="button" id="prev" accesskey="," value="<,"/>'+ | |
'<span id="pos">'+ i +'/'+ len +'</span>'+ | |
'<input type="button" id="next" accesskey="." value=".>"/>'+ | |
'</div>'+ | |
(ls[1] ? ls.join('') +'</ol>' : 'No results.')); | |
var nxt = i + 1, doc = lmn.ownerDocument; | |
bms[nxt] = offset; | |
if(!offset) doc.getElementById('prev').disabled = true; | |
if(i >= len) doc.getElementById('next').disabled = true; | |
lmn.firstChild.addEventListener('click', function hbs_onfocus(e){ | |
var b = e.target; | |
if(b.type !== 'button') return; | |
e.preventDefault(); | |
b.disabled = true; | |
hbs_preview.call(me, pb, args, b.id === 'next' ? nxt : bms[offset]); | |
}, false); | |
}, | |
previewDelay: 222, | |
_get: function get(dat, usr, ofs){ | |
const Lim = 5e3; | |
return | |
var me = this, xhr = $.ajax({ | |
url: Hatebu + usr +'/search.data', | |
data: {limit: Lim, offset: ofs |= 0}, | |
error: function(x, s){ | |
Utils.log(x, s) | |
if(/^40[34]$/.test(x.status)) dat[usr] = x.status; | |
else howl(x, s); | |
}, | |
success: function(res){ | |
if(/^\s*</.test(res)){ | |
dat[usr] = 404; | |
return; | |
} | |
var bms = dat[usr], nfo = res.split('\n'), | |
len = nfo.length / 4, num = nfo.splice(len * 3); | |
if(bms === 100){ | |
dat[usr] = {txt: nfo, num: num}; | |
var U = context.chromeWindow.gUbiquity; | |
U.__lastValue = null; | |
U.__processInput(true); | |
} else { | |
var {push} = nfo; | |
push.apply(bms.txt, nfo); | |
push.apply(bms.num, num); | |
} | |
if(len === Lim) me._get(dat, usr, ofs + Lim); | |
}, | |
}); | |
}, | |
_bin: {dat: {}}, | |
_css: <![CDATA[ | |
ol {margin:0; padding-left:1.8em} | |
#navi {text-align:center; margin-bottom:0.1em} | |
#pos {margin:0 1ex} | |
.title {font-weight:bold} | |
.user {font:bold 1em/1 monospace; margin-left:0.2em; padding:0 0.1em; | |
border:1px solid; -moz-border-radius:3px} | |
]]>, | |
},{ | |
name: 'users', | |
description: ('Adds '+ entimg('http://gist.github.com/8351') + | |
' to each link in the page.'), | |
execute: function htbu_execute(){ | |
var doc = CmdUtils.getDocument(); | |
for each(let a in Array.slice(doc.links)){ | |
let span = doc.createElement('span'); | |
span.innerHTML = entimg(a.href); | |
a.parentNode.insertBefore(span, a.nextSibling); | |
} | |
}, | |
preview: function htbu_preview(pb){ | |
var lmn = this._lmn(pb); | |
if(lmn.ini) lmn.innerHTML = this.description; | |
}, | |
}]){ | |
o.name = 'hatebu '+ o.name; | |
o.icon = Icon; | |
let lmn = o._lmn || <div> </div>; | |
let id = lmn.@id = o._id = o.name.replace(/ /g, '-'); | |
XML.prettyPrinting = XML.ignoreWhitespace = false; | |
let htm = ( | |
'<style>'+ CSS + (o._css || '') +'</style>'+ | |
<div class={id}/>.appendChild(lmn).appendChild(Logo)); | |
delete o._css; | |
o._lmn = function htb_lmn(pb){ | |
var lmn, ini; | |
while(!(lmn = pb.ownerDocument.getElementById(id))) | |
pb.innerHTML = htm, ini = 1; | |
lmn.ini = !!ini; | |
return lmn; | |
}; | |
o._bin || (o._bin = {__proto__: null}); | |
CmdUtils.CreateCommand(o); | |
} | |
function favicon(u)( | |
'http://favicon.hatena.ne.jp/?url='+ encodeURIComponent(u)); | |
function tagsub(c, user){ | |
var ts = [], i = -1, m; | |
while((m = /\[.*?\]/gy(c))){ | |
var t = m[0].slice(1, -1); | |
ts[++i] = t.link(Hatebu + user +'/'+ t +'/'); | |
} | |
return i < 0 ? c : ts.join(',') +' '+ RegExp.rightContext; | |
} | |
function ng(c) ok(c, Array.slice(arguments, 1).join(' '), 'error'); | |
function ok(c, h, not){ | |
(c.querySelector('.content') || c).innerHTML = h; | |
c.className = not || ''; | |
return c; | |
} | |
function wipe(bin){ | |
for(var k in bin) delete bin[k]; | |
return bin; | |
} | |
function info(){ | |
var {defaultView: win, title, URL} = CmdUtils.getDocument(), t, u; | |
try { | |
(/^https?:\/\/www\.google\.[^/]+\/reader\/view\b/.test(URL) | |
? ({title: t, url : u}) = win.wrappedJSObject.getPermalink() : | |
/^https?:\/\/(?:reader\.livedoo|fastladde)r\.com\/reader\b/.test(URL) | |
? ({title: t, link: u}) = win.wrappedJSObject.get_active_item(1) | |
: 0); | |
} finally { return {title: t || title, url: u || URL} } | |
} | |
function entry(u, path)( | |
Hatebu +'entry/'+ (path ? path +'/' : '') + u.replace(/#/g, '%23')); | |
function entimg(u)( | |
<a href={entry(u)}><img src={entry(u, 'image')} border="0"/></a>); | |
function enc4htb(u) encodeURIComponent(u).replace(/#/g, '%23'); | |
function parse(txt){ | |
var [t, c] = /^\s*(?:[^;\s][^;]*;)*(?=\s*([^]*)\s*$)/.exec(txt) | |
, tags = t.split(';').slice(0, -1) | |
return { | |
comm: tags.length ? '['+ tags.join('][') +']'+ c : c, | |
size: encodeURI(c.replace(/%/g, 0)).replace(/%\w/g, '').length, | |
tags: tags} | |
} | |
function check(tag){ | |
var txt = tag.textContent.toLowerCase(), c = 'tag' | |
for each(var t in this){ | |
if(txt === (t = t.toLowerCase())){ c += ' selected'; break } | |
if(~txt.indexOf(t)) c += ' matched' | |
} | |
tag.removeAttribute('style') | |
tag.className = c | |
} | |
function say(title){ | |
var txt = Array.slice(arguments, 1).join(' '); | |
displayMessage({icon: Icon, title: title, text: txt}); | |
} | |
function howl(x, s) say(s, x.status, x.statusText); | |
function keygen(n) n < 36 ? n.toString(36) : '-^@;:[],./\\<>?_'[n - 36]; | |
function abs(){ | |
var attr, path = (this.getAttribute(attr = 'href') || | |
this.getAttribute(attr = 'src' ) || | |
this.getAttribute(attr = 'action')); | |
if(/^(?!\w+:\/\/)\/?/.test(path)) this[attr] = Hatebu + RegExp.rightContext; | |
} | |
function xerr(it)( | |
function err(x, s){ ng(it, x.status, x.statusText, '('+ s +')') }); | |
function ajax(it, op, succ){ | |
it.className = 'loading'; | |
op.error = xerr(it); | |
op.success = succ; | |
$.ajax(op); | |
} | |
function box()( | |
box.box || (box.box = context.chromeWindow.gUbiquity.textBox)); | |
function tagplus(e){ | |
var s = e.target; | |
if(s.nodeName.toLowerCase() != 'span') return; | |
with(box()){ | |
value = value.replace(/^\s*(\S*)\s*(\S+;)?/, | |
'$1 $2'+ s.textContent +';'); | |
focus(); | |
} | |
} | |
function users(url){ | |
url = url || 'https://www.hatena.ne.jp'; | |
var token = (Cc['@mozilla.org/security/pk11tokendb;1'] | |
.getService(Ci.nsIPK11TokenDB) | |
.getInternalKeyToken()); | |
return ( | |
!token.needsLogin() || token.isLoggedIn() | |
? (Cc['@mozilla.org/login-manager;1'].getService(Ci.nsILoginManager) | |
.findLogins({}, url, '', '')) | |
: []); | |
} | |
function login(ok, ng){ | |
const [L] = users(); | |
if(!L) return ng('No saved logins'); | |
$.ajax({ | |
url: HLogin, type: 'POST', data: {name: L.username, password: L.password}, | |
success: ok, error: function(x) ng(x.status +' '+ x.statusText), | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment