Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
// ==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
You can’t perform that action at this time.