Created
December 23, 2012 09:10
-
-
Save Griever/4362682 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
// ==UserScript== | |
// @name fastforward.uc.js | |
// @namespace http://d.hatena.ne.jp/Griever/ | |
// @include main | |
// @compatibility Firefox 4.0 | |
// @charset UTF-8 | |
// @license MIT License | |
// @version 0.0.1 | |
// @note 昔作ったものが埋もれていたので公開 | |
// ==/UserScript== | |
/* | |
スペースキーでこれ以上スクロールできない場合、次のページを自動的に読み込む。 | |
uAutoPagerize があるときは SITEINFO を利用して次のページを探す。 | |
uAutoPagerize, OwataPagerize が次のページを読み込み中なら実行されないようにしてある。 | |
*/ | |
(function(){ | |
// 完全一致 | |
var full = [ | |
"→","次","つぎ","次→","次㌻→",">",">",">>","»", | |
"もっと読む","もっと","#次→","[次]","load more","さらに読み込む", | |
":: next ::" | |
]; | |
// 前方一致 | |
var prefix = [ | |
"next","NEXT","Next", | |
"次の","次を","次に","次へ", | |
"つぎの","つぎを","つぎに","つぎへ", | |
"次頁","次項","次ページ", | |
"進む","先へ","続く","続き","記事本文","過去の", | |
"次㌻" | |
]; | |
window.fastforward = { | |
full: full, | |
prefix: prefix, | |
microformat: '//link[contains(concat(" ", translate(normalize-space(@rel), "ENTX", "entx"), " "), " next ")] | //a[contains(concat(" ", translate(normalize-space(@rel), "ENTX", "entx"), " "), " next ")]', | |
_xpath: '', | |
get xpath() { | |
return this._xpath || this.makeXPath(); | |
}, | |
get focusedWindow() { | |
var win = document.commandDispatcher.focusedWindow; | |
return (!win || win === window) ? content : win; | |
}, | |
init: function() { | |
var key = document.createElement("key"); | |
key.setAttribute("id", "fastforward-key"); | |
key.setAttribute("key", " "); | |
key.setAttribute("oncommand", "fastforward.spaceKey(event);"); | |
document.getElementById("mainKeyset").appendChild(key); | |
}, | |
makeXPath: function() { | |
var fx = this.full.map(function(word) { | |
return 'normalize-space(.)=' + escapeXPathExpr(word) +''; | |
}).join(' or '); | |
var px = this.prefix.map(function(word) { | |
return 'starts-with(normalize-space(.), ' + escapeXPathExpr(word) +')'; | |
}).join(' or '); | |
return this._xpath = [ | |
'//text()[' + fx + ' or ' + px + ']/ancestor-or-self::*[@href or local-name()="button" or local-name()="BUTTON"]' | |
,'//*[local-name()="img" or local-name()="IMG" or local-name()="input" or local-name()="INPUT"]' | |
+ '[' + (fx + ' or ' + px).replace(/\(\.\)/g, '(@alt | @value | @title)') + ']' | |
+ '/ancestor-or-self::*[@href or @type="submit" or @type="SUBMIT" or @type="button" or @type="BUTTON"]' | |
// '//text()[' + fx + ' or ' + px + ']/ancestor-or-self::*[@href]' | |
// ,'//img[' + (fx + ' or ' + px).replace(/\(\.\)/g, '(@alt)') + ']/ancestor-or-self::*[@href]' | |
// ,'//input[' + (fx + ' or ' + px).replace(/\(\.\)/g, '(@value)') + ']' + '[@type="submit" or @type="SUBMIT" or @type="button" or @type="BUTTON"]' | |
].join('|'); | |
}, | |
next: function(win) { | |
win || (win = this.focusedWindow); | |
let [nextLink, nextURL] = this.getNext(win); | |
if (nextURL) { | |
win.location.href = nextURL; | |
} | |
else if (nextLink) { | |
dispatchMouseEvents({ target:nextLink }); | |
} | |
}, | |
getNext: function(win) { | |
win || (win = this.focusedWindow); | |
var doc = win.document; | |
var { URL, domain } = doc; | |
var [nextLink, nextURL] = [null, ""]; | |
// uAutoPagerize,OwataPagerizeが次のURLを見つけていればそれを使う | |
// loading 中なら実行しない | |
if (win.ap) { | |
if (win.ap.state == "loading") return [nextLink, nextURL]; | |
if (win.ap.requestURL) return [nextLink, win.ap.requestURL]; | |
} | |
else if (win.OwataPagerize) { | |
if (win.OwataPagerize.state == "loading") return [nextLink, nextURL]; | |
if (win.OwataPagerize.info.nextURL) return [nextLink, win.OwataPagerize.info.nextURL]; | |
} | |
// uAutoPagerizeがあればSITEINFOを一度だけチェックする | |
if (window.uAutoPagerize && !win.ffSiteinfoChecked) { | |
win.ffSiteinfoChecked = true; | |
let info = null; | |
if (!info) [, info] = uAutoPagerize.getInfo(uAutoPagerize.MY_SITEINFO, win); | |
if (!info) [, info] = uAutoPagerize.getInfo(uAutoPagerize.SITEINFO, win); | |
if (!info) [, info] = uAutoPagerize.getInfo(uAutoPagerize.MICROFORMAT, win); | |
if (info) { | |
nextLink = $X1(info.nextLink, doc); | |
if (nextLink) return [nextLink, nextLink.href]; | |
} | |
} | |
var x = $X(this.xpath, doc); | |
if (x.length > 0) { | |
x.some(function(elem) { | |
// hostを持ってない要素(button), 同一host, javascripスキームならOK | |
if (!"host" in elem || elem.host == domain || elem.protocol == "javascript:") { | |
nextLink = elem; | |
nextURL = elem.href || ""; | |
return true; | |
} | |
}) | |
return [nextLink || x.pop(), nextURL];// 条件を満たさなかったら最後の要素を"次"とする | |
} | |
if (/\d/.test(URL)) { | |
let newURL = this.incrementURL(URL, +1); | |
if (newURL !== domain) { | |
nextURL = newURL; | |
// 増加させた URL のリンクがあるかチェック | |
nextLink = doc.querySelector('[href$="' + nextURL.split("/").pop() + '"]'); | |
} | |
} | |
if (!nextLink) { | |
nextLink = $X1(this.microformat, doc); | |
} | |
return [nextLink, nextURL]; | |
}, | |
incrementURL: function(url, aIncrement) { | |
// page=n は優先的に増やす | |
return /\bpage=\d/.test(url) ? | |
url.replace(/(.*?(?:\bpage=))(\d+)(.*?)/g, re): | |
url.replace(/(.*)(\d+)(\D*?)$/, re); | |
function re(str, left, num, right) { | |
return left + format0((parseInt(num, 10) + (aIncrement || 1)), (num+'').length) + right; | |
} | |
}, | |
spaceKey: function(event) { | |
var win = this.focusedWindow; | |
if (win.scrollMaxY > win.scrollY) { | |
goDoCommand("cmd_scrollPageDown"); | |
return; | |
} | |
fastforward.next(win); | |
} | |
}; | |
fastforward.init(); | |
// http://os0x.g.hatena.ne.jp/os0x/20080515/1210821896 | |
function format0(str, len){ | |
return ('_' + Math.pow(10,len) + str).slice(-len); | |
} | |
// http://d.hatena.ne.jp/amachang/20090917/1253179486 | |
function escapeXPathExpr(text) { | |
var matches = text.match(/[^"]+|"/g); | |
function esc(t) { | |
return t == '"' ? ('\'' + t + '\'') : ('"' + t + '"'); | |
} | |
if (matches) { | |
if (matches.length == 1) { | |
return esc(matches[0]); | |
} else { | |
var results = []; | |
for (var i = 0, len = matches.length; i < len; i ++) { | |
results.push(esc(matches[i])); | |
} | |
return 'concat(' + results.join(', ') + ')'; | |
} | |
} else { | |
return '""'; | |
} | |
} | |
function $X(exp, context) { | |
var _document = context.ownerDocument || context; | |
var x = _document.evaluate(exp, context, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); | |
var h = []; | |
for (var i = 0, l = x.snapshotLength; i < l; i++) { | |
h[i] = x.snapshotItem(i); | |
} | |
return h; | |
} | |
function $X1(exp, context) { | |
var _document = context.ownerDocument || context; | |
var x = _document.evaluate(exp, context, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); | |
return x.singleNodeValue; | |
} | |
function $(id){ return document.getElementById(id); } | |
function $$(selector, e) { return $A((e || document).querySelectorAll(selector));} | |
function $A(likeArray) { return Array.prototype.slice.call(likeArray); } | |
function log() { Application.console.log(Array.prototype.slice.call(arguments)); } | |
function dispatchMouseEvents(opt) { | |
var evt = opt.target.ownerDocument.createEvent("MouseEvents"); | |
evt.initMouseEvent( | |
opt.type||"click", opt.canBubble||true, opt.cancelable||true, opt.view||opt.target.ownerDocument.defaultView, | |
opt.detail||0, opt.screenX||0, opt.screenY||0, opt.clientX||0, opt.clientY||0, | |
opt.ctrlKey||false, opt.altKey||false, opt.shiftKey||false, opt.metaKey||false, | |
opt.button||0, opt.relatedTarget||null); | |
opt.target.dispatchEvent(evt); | |
return evt; | |
} | |
})(); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment