印象中我获知userscripts.org大约是在09年前后。Firefox五花八门的插件阵营里的明星GreaseMonkey让Firefox倡导的自定义互联网的理念不止步于浏览器,进入原本是网站开发者决定的网页领域。按照自己的想法修正、增强和美化一直以来只能被动接受的网页,这个新奇的主意令很多网民中的开发者跃跃欲试,也让更多普通的Firefox用户踊跃安装使用数量快速增长、针对大量网站、功能繁多的脚本。个性化和自主性需求的满足似乎一时让上网变得更加有趣。用户脚本的集散地userscripts.org网站也应需而生。当年这个专门用途的网站在我眼里是个有趣又精致的地方,在这能发现他人的巧思,仅凭一个脚本就使熟悉的网站更加友好方便。而当我也开始在上面发布脚本后,经常去看看每日脚本下载次数就变得我想有点像股民关注股市,毕竟这是第一次自己写的程序被互联网那一端成千上万不认识的人下载使用。
Chrome、Opera随后也有了各自的XXXMonkey插件,内容脚本不再是Firefox的专利。渐渐地随着对脚本改动的减少,我对它的兴趣也淡漠了。网站的改版、浏览器和插件的升级都可能使脚本失效或不再有用,另一方面设计和开发精良的网站,用户操作友好,内容脚本的用武之地也减小(除了那些突破网站开发者本意的限制的功能)。不知不觉我几个月也未必会上一次userscripts.org。
这时userscripts.org离去了,我惊愕之馀若有所失。有人评论,userscripts.org关闭之前因为未对广告和恶意脚本加以限制,已显颓像,后继者greasyfork.org维护管理更好。我上去看后却觉得它的页面美观和操作性不如前者。当互联网日益成为普通人生活中密切的一部分,抽象的数字空间也会被附以感情。万物有生有灭。在新网站不断涌现被人接受和熟悉之际,一些曾经的老伙伴也已消失,有人将这种现象比作数字黑洞,一旦网站关闭,上面的所有数据,包括成千上万使用者的活动记录,就被永远吞噬。所以已经有人开始“备份”整个互联网,web.archive.org现在已记录了4350亿个历史页次,userscripts.org的静态页面也可以通过它访问。
相较于针对特定网站的脚本,一些通用功能的脚本是我觉得最有用的。我的最爱就包括在userscripts.org下载的用左右键翻页的脚本。将它附在末尾以志念。
// ==UserScript== // @name HotKey Next Page // @namespace [email protected] // @author scottxp // @version 1.0 // @description 按左右键翻页,可以自己针对网站定制xpath规则 // @include http://* // @include https://* // Modified by Starrow. // 1. Add the detection of contenteditable div used as textarea in HTML 5. // 2. Remove the reference to unsafeWindow, which is unnecessary and doesn't work since Firefox 30. // ==/UserScript== const nextStrs = [ '下一页', '下页', '下一节', '下一章', '下一篇', '后一章', '后一篇', '»', 'next', 'next page', 'old', 'older', 'earlier', '下頁', '下一頁', '后一页', '后一頁', '下一則', '翻下页', '翻下頁', '后页', '后頁', '下翻', '下一个', '下一张', '下一幅', 'Next >' ]; const lastStrs = [ '上一页', '上页', '上一节', '上一章', '上一篇', '前一章', '前一篇', '«', 'previous', 'prev', 'previous page', 'new', 'newer', 'later', '上頁', '上一頁', '上一則', '前一页', '前一頁', '翻上页', '翻上頁', '前页', '前頁', '上翻', '上一个', '上一张', '上一幅', '< Prev' ]; const GeneralXpaths = [ ["//a[(text()='","')]"], ["//input[@type='button' and @value='","']"] ]; //编辑下面的数组来自定义规则 const SpecialXpaths = [ { //匹配的url urls : [ "http://www.google.com" ], //上一页节点的xpath last : "//a[@id='pnprev']", //下一页节点的xpath next : "//a[@id='pnnext']" } ]; const LEFT = 37; const RIGHT = 39; function checkKey(e){ if (e.ctrlKey || e.shiftKey || e.altKey || e.metaKey ) return ; if(checkTextArea(e.target)) return ; var node; if(e.keyCode == LEFT && (node = getNode(LEFT))){ click(node); } if(e.keyCode == RIGHT && (node = getNode(RIGHT))){ click(node); } } function checkTextArea(node){ var name = node.localName.toLowerCase(); if (name == 'textarea' || name == 'input' || name == 'select') return true; if(name == 'div' && node.id.toLowerCase().indexOf('textarea')!=-1) return true; //Add new textarea which are div with contenteditable being true. //contenteditable is new in HTML 5 and supported in all major browsers. //In case contenteditable is supported but cannot be read via DOM, use attributes["contenteditable"].value. if (node.contenteditable===true || (node.attributes["contenteditable"] && node.attributes["contenteditable"].value==="true")) { return true; } return false; } function click(node){ if(node.onclick) node.onclick(); if(node.click) node.click(); if(node.href) location.href = node.href; } function xpath(query) { return document.evaluate(query, document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null); } function getNode(keyCode){ var node = getNodeByGeneralXpath(keyCode) if (!node) node = getNodeBySpecialXpath(keyCode); return node; } function getNodeByGeneralXpath(keyCode){ var strs; if(keyCode == LEFT) strs = lastStrs; else if(keyCode == RIGHT) strs = nextStrs; else return null; var x = GeneralXpaths; for (var i in x){ for (var j in strs){ var query = x[i][0]+strs[j]+x[i][1]; var nodes = xpath(query); if(nodes.snapshotLength > 0) return nodes.snapshotItem(0); } } return null; } function getNodeBySpecialXpath(keyCode){ var s = SpecialXpaths; for (var i in s){ if(checkXpathUrl(s[i].urls)){ if (keyCode == LEFT){ return xpath(s[i].last).snapshotItem(0); } else if(keyCode == RIGHT){ return xpath(s[i].next).snapshotItem(0); } } } return null; } function checkXpathUrl(urls){ for(var i in urls) if(location.href.indexOf(urls[i]) == 0) return true; return false; } if (top.location != self.location) return; document.addEventListener('keydown', checkKey, false);