Last active
September 25, 2015 15:47
-
-
Save 958/945711 to your computer and use it in GitHub Desktop.
[keysnail]Multi Requester from KeySnail
This file contains 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
// original script license | |
/*** BEGIN LICENSE BLOCK {{{ | |
Copyright (c) 2008 suVene<suvene@zeromemory.info> | |
distributable under the terms of an MIT-style license. | |
http://www.opensource.jp/licenses/mit-license.html | |
}}} END LICENSE BLOCK ***/ | |
var PLUGIN_INFO = | |
<KeySnailPlugin> | |
<name>Multi Requester from KeySnail</name> | |
<description>request, and the result is displayed to the buffer.</description> | |
<description lang="ja">リクエストの結果をバッファに出力する。</description> | |
<updateURL>https://gist.github.com/958/945711/raw/multi_request.ks.js</updateURL> | |
<version>0.0.8</version> | |
<author>958</author> | |
<license>MIT</license> | |
<minVersion>1.8.0</minVersion> | |
<detail lang="ja"><![CDATA[ | |
=== 使い方 === | |
>|| | |
multi-request | |
subcommand [ANY_TEXT] | |
||< | |
- ANY_TEXT 任意の文字列 | |
>|| | |
alc[,goo,any1,any2…] ANY_TEXT -> 入力文字に対するリクエストを送信し、結果を表示します | |
goo[,any1,any2,…] {window.selection} -> 選択テキストに対するリクエストを送信し、結果を表示します | |
||< | |
=== ショートカットキー === | |
以下のような感じでショートカットキーを割り当てるとよいです | |
>|| | |
key.setGlobalKey(['C-t', 'j'], function (ev, arg) { | |
// Google 翻訳 と So-net 翻訳で日本語に翻訳 | |
ext.exec("multi-request", "googletrans-ja,so-net-e2j", ev); | |
}, 'Multi request', true); | |
key.setGlobalKey(['C-t', 'e'], function (ev, arg) { | |
// Google 翻訳 と So-net 翻訳で英語に翻訳 | |
ext.exec("multi-request", "googletrans-en,so-net-j2e", ev); | |
}); | |
key.setGlobalKey(['C-t', 'a'], function (ev, arg) { | |
// alc と goo で単語翻訳 | |
ext.exec("multi-request", "alc,goo", ev); | |
}); | |
||< | |
=== SITEINFO === | |
SITEINFO を任意に追加できます | |
>|| | |
plugins.options['multi_requester.siteinfo'] = [ | |
{ | |
name: "ex", // required: subcommand name | |
description: "example", // required: commandline short help | |
url: "http://example.com/?%s", // required: %s <-- replace string | |
xpath: "//*", // optional: default all | |
srcEncode: "SHIFT_JIS", // optional: default UTF-8 | |
urlEncode: "SHIFT_JIS", // optional: default srcEncode | |
ignoreTags: "img", // optional: default script, syntax "tag1,tag2,……" | |
extractLink: "//xpath" // optional: extract permalink | |
}, | |
]; | |
||< | |
デフォルトで、So-net 翻訳を追加しています | |
wedata の SITEINFO に追加しようと思ったのですが、本家 Vimplerator プラグインではうまく動作しなかったので、ローカルに追加しました | |
=== Wedata === | |
Wedata にある Vimperator Multi Requester Plugin の SITEINFO を使用します | |
http://wedata.net/databases/Multi%20Requester/items | |
=== 流用元 === | |
multi_requester.js at master from vimpr/vimperator-plugins - GitHub | |
https://github.com/vimpr/vimperator-plugins/blob/master/multi_requester.js | |
]]></detail> | |
</KeySnailPlugin> | |
const KEY = 'multi_requester' | |
let pOptions = plugins.setupOptions(KEY, { | |
'style': { | |
preset:'* { font-size: 10pt; } \ | |
.mr_site { display: block; margin: 1em 0; } \ | |
.mr_title { display: block; white-space: nowrap; overflow: hidden; margin-bottom: 0.5em; } \ | |
.mr_detail { margin: 0 1em; }', | |
description: M({ja: 'echo エリアのcss', en: 'css of echo area'}) | |
}, | |
'siteinfo': { | |
preset: [ | |
{ | |
name: 'so-net-e2j', | |
description: L('So-net 翻訳(英日)'), | |
url: 'http://www.so-net.ne.jp/translation/cgi-bin/text.cgi?text=%s&language=EJ&requestTranslate.x=60&requestTranslate.y=30', | |
xpath: 'id("textTextResult")/text()' | |
}, | |
{ | |
name: 'so-net-j2e', | |
description: L('So-net 翻訳(日英)'), | |
url: 'http://www.so-net.ne.jp/translation/cgi-bin/text.cgi?text=%s&language=JE&requestTranslate.x=60&requestTranslate.y=30', | |
xpath: 'id("textTextResult")/text()' | |
} | |
], | |
description: M({ja: '追加で使用する siteinfo', en: 'local siteinfo'}) | |
} | |
}, PLUGIN_INFO); | |
(function (callback){ | |
let store = persist.restore(KEY); | |
let isCache = false; | |
if (store) { | |
isCache = true; | |
callback(store.siteinfo); | |
} | |
if (!store || new Date(store.expire) > new Date()) { | |
util.requestGet('http://wedata.net/databases/Multi%20Requester/items.json', { | |
callback: function(xhr) { | |
if (xhr.status != 200) return; | |
if (xhr.responseText) { | |
store = {}; | |
store.expire = new Date(); | |
store.siteinfo = JSON.parse(xhr.responseText); | |
persist.preserve(store, KEY); | |
if (!isCache) | |
callback(store.siteinfo); | |
} | |
} | |
}); | |
} | |
})(function(siteinfo) { | |
let info = siteinfo.map((i) => i.data); | |
pOptions['siteinfo'].forEach((i) => info.push(i)); | |
info = info.sort((a, b) => a.name.toLowerCase() > b.name.toLowerCase()); | |
plugins.withProvides(function (provide) { | |
provide('multi-request', | |
function(ev, arg) { | |
MultiRequester.siteinfo = info; | |
let reader = function(str) { | |
prompt.reader({ | |
message : 'Select request site:', | |
group : "multi_requester", | |
initialInput : str, | |
cursorEnd : true, | |
completer : function (left, whole) { | |
var filters = left.split(","); | |
var prefilters = filters.slice(0, filters.length - 1); | |
var prefilter = !prefilters.length ? "" : prefilters.join(",") + ","; | |
var subfilters = info.filter((s) => prefilters.every((p) => s.name != p)); | |
var allSuggestions = subfilters.map((s) => [prefilter + s.name, s.description]); | |
var completions = left | |
? allSuggestions.filter((s) => s[0].indexOf(left) == 0) | |
: allSuggestions; | |
return { | |
collection : completions, | |
origin : 0, | |
query : left, | |
flags : [0, 0], | |
header : ['Name', 'Description'], | |
width : [30, 70], | |
}; | |
}, | |
callback : (str) => MultiRequester.cmdAction(str) | |
}); | |
}; | |
if (typeof arg === 'string') { | |
if (MultiRequester.cmdAction(arg.replace(/^\s+|\s+$/g, "")) == false) | |
reader(arg); | |
} else { | |
reader(''); | |
} | |
}, | |
'Multi request' | |
); | |
}, PLUGIN_INFO); | |
}); | |
// main controller {{{ | |
var MultiRequester = { | |
description: "request, and display to the buffer", | |
defaultSites: 'alc', | |
doProcess: false, | |
requestNames: "", | |
requestCount: 0, | |
echoHash: {}, | |
cmdAction: function(args) { //{{{ | |
var self = this; | |
args = args.split(' '); | |
if (MultiRequester.doProcess) return undefined; | |
var parsedArgs = this.parseArgs(args); | |
if (parsedArgs.count == 0) { return false; } // do nothing | |
if (parsedArgs.strs.length === 0 || | |
(parsedArgs.strs.length === 1 && parsedArgs.strs[0].length === 0)) | |
{ return false; } // do nothing | |
MultiRequester.doProcess = true; | |
MultiRequester.requestNames = parsedArgs.names; | |
MultiRequester.requestCount = 0; | |
MultiRequester.echoHash = {}; | |
var siteinfo = parsedArgs.siteinfo; | |
for (let i = 0, len = parsedArgs.count; i < len; i++) { | |
let info = siteinfo[i]; | |
let name = info.name; | |
let url = info.url; | |
// see: http://fifnel.com/2008/11/14/1980/ | |
let srcEncode = info.srcEncode || "UTF-8"; | |
let urlEncode = info.urlEncode || srcEncode; | |
let m = url.match(/%s/g); | |
let repStrCount = (m && m.length); | |
if (repStrCount && !parsedArgs.strs.length) continue; | |
// via. lookupDictionary.js | |
let ttbu = Components.classes["@mozilla.org/intl/texttosuburi;1"] | |
.getService(Components.interfaces.nsITextToSubURI); | |
let cnt = 0; | |
let strs = _.clone(parsedArgs.strs); | |
url = url.replace(/%s/g, (m, i) => ttbu.ConvertAndEscape(urlEncode, | |
(cnt >= strs.length ? strs[cnt - 1] : | |
cnt >= (repStrCount - 1) ? strs.splice(cnt).join(' ') : | |
strs[cnt++]))); | |
util.message(url + "[" + srcEncode + "][" + urlEncode + "]::" + info.xpath); | |
var opt = { | |
mimeType: 'text/html; charset=' + srcEncode, | |
callback: function(xhr){ | |
self.onSuccess(xhr, url, info); | |
}, | |
header: { | |
Referer: url.split('?')[0], | |
}, | |
}; | |
util.requestGet(url, opt); | |
MultiRequester.requestCount++; | |
} | |
if (MultiRequester.requestCount) { | |
display.echoStatusBar("Loading " + parsedArgs.names + " ..."); | |
} else { | |
MultiRequester.doProcess = false; | |
} | |
return true; | |
}, | |
// return {names: "", strs: [""], count: 0, siteinfo: [{}]} | |
parseArgs: function(args) { | |
var self = this; | |
var ret = {}; | |
ret.names = ""; | |
ret.strs = []; | |
ret.count = 0; | |
var sel = (document.commandDispatcher.focusedWindow || gBrowser.contentWindow).getSelection().toString(); | |
if (args.length < 1 && !sel.length) return ret; | |
function parse(args, names) { | |
args = Array.concat(args); | |
ret.siteinfo = []; | |
ret.names = names || args.shift() || ""; | |
ret.strs = (args.length < 1 ? [ sel.replace(/[\n\r]+/g, "") ] : args); | |
ret.names.split(",").forEach(function(name) { | |
var site = self.getSite(name); | |
if (site) { | |
ret.count++; | |
ret.siteinfo.push(site); | |
} | |
}); | |
} | |
parse(args); | |
if (!ret.siteinfo.length && this.defaultSites) | |
parse(args, this.defaultSites); | |
return ret; | |
}, | |
getSite: function(name) { | |
if (!name) this.siteinfo[0]; | |
var ret = null; | |
this.siteinfo.forEach(function(s) { | |
if (s.name == name) ret = s; | |
}); | |
return ret; | |
},//}}} | |
extractLink: function(res, baseUrl, info, extractLink) { //{{{ | |
var self = this; | |
var el = getHTMLDocument(res.responseText, extractLink); | |
if (!el) throw "extract link failed.: extractLink -> " + extractLink; | |
var url = pathToURL(el[0], baseUrl); | |
util.requestGet(url, { | |
callback: function(xhr){ | |
let temp = {}; | |
_.extend(temp, info); | |
temp.extractLink = false; | |
self.onSuccess(xhr, url, temp); | |
} | |
}); | |
MultiRequester.requestCount++; | |
MultiRequester.doProcess = true; | |
},//}}} | |
onSuccess: function(res, url, siteinfo) { //{{{ | |
if (!MultiRequester.doProcess) { | |
MultiRequester.requestCount = 0; | |
return; | |
} | |
display.echoStatusBar("success!!: " + url, 3000); | |
MultiRequester.requestCount--; | |
if (MultiRequester.requestCount == 0) { | |
MultiRequester.doProcess = false; | |
} | |
var url, escapedUrl, xpath, doc, source, extractLink, ignoreTags; | |
try { | |
if (res.responseText == "") throw "response is fail or null"; | |
escapedUrl = url.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); | |
xpath = siteinfo.xpath; | |
extractLink = siteinfo.extractLink; | |
if (extractLink) { | |
this.extractLink(res, url, siteinfo, extractLink); | |
return; | |
} | |
ignoreTags = [ "script" ].concat((siteinfo.ignoreTags) ? siteinfo.ignoreTags.split(',') : []); | |
doc = document.createElementNS(null, "div"); | |
getHTMLDocument(res.responseText, xpath, null, ignoreTags, function(node, i) { | |
if (!node.tagName || node.tagName.toLowerCase() != "html") { | |
doc.appendChild(node); | |
} | |
}).doc; | |
if (!doc || !doc.childNodes.length) throw "XPath result is undefined or null.: XPath -> " + xpath; | |
source = '<div class="mr_site" style="white-space:normal;"><base href="' + escapedUrl + '"/>' + | |
'<a href="' + escapedUrl + '" class="mr_title" target="_blank">' + siteinfo.description + '</a>' + | |
'<div class="mr_detail">' + | |
xmlSerialize(doc) + | |
'</div></div>'; | |
MultiRequester.echoHash[siteinfo.name] = source; | |
} catch (e) { | |
display.echoStatusBar(e, 3000); | |
MultiRequester.echoHash[siteinfo.name] = | |
'<div class="mr_site" style="white-space:normal;"><base href="' + escapedUrl + '"/>' + | |
'<a href="' + escapedUrl + '" class="mr_title" target="_blank">' + siteinfo.description + '</a>' + | |
'<div class="mr_detail">' + | |
'<span style="color: red;">error!!: ' + e + '</span>' + | |
'</div></div>'; | |
} | |
if (MultiRequester.requestCount == 0) { | |
let echoList = []; | |
MultiRequester.requestNames.split(",").forEach(function(name) { | |
echoList.push(MultiRequester.echoHash[name]); | |
}); | |
source = echoList.join(""); | |
try { | |
display.echo.html(source); | |
Array.slice(display.echo.document.querySelectorAll("a,img")).forEach(function(node){ | |
let tagName = node.nodeName.toLowerCase(); | |
if (tagName == "a") { | |
node.href = pathToURL(node, url, display.echo.document); | |
node.addEventListener('click', function(e){ openUILinkIn(e.target.href, "tab"); e.preventDefault(); }, false); | |
} else if (tagName == "img") { | |
node.src = pathToURL(node, url, display.echo.document); | |
} | |
}); | |
if (!display.echo.document.getElementById('ml_style')) { | |
let css = display.echo.document.createElement('style'); | |
css.type = "text/css"; | |
css.id = 'ml_style'; | |
css.innerHTML = pOptions.style; | |
display.echo.document.getElementsByTagName('head')[0].appendChild(css); | |
} | |
display.echo.open(); | |
display.echo.document.defaultView.scrollTo(0,0); | |
let onblur = function(e){ | |
display.echo.document.defaultView.removeEventListener('blur', onblur, false); | |
display.echo.close(); | |
}; | |
display.echo.document.defaultView.addEventListener('blur', onblur, false); | |
} catch (e) { | |
display.echoStatusBar(e, 3000); | |
util.message(source); | |
} | |
} | |
}, | |
onFailure: function(res) { | |
MultiRequester.doProcess = false; | |
util.message("request failure!!: " + res.statusText); | |
}, | |
onException: function(e) { | |
MultiRequester.doProcess = false; | |
util.message("exception!!: " + e); | |
} | |
//}}} | |
//}}} | |
}; | |
// via https://github.com/azu/KeySnail-Plugins/raw/master/JSReference/js-referrence.ks.js | |
function createHTMLDocument(source) { | |
var processor = new XSLTProcessor(); | |
var sheet = new DOMParser().parseFromString( | |
'<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">' + | |
'<xsl:output method="html"/>' + | |
'<xsl:template match="/">' + | |
'<html><head><title></title></head><body></body></html>' + | |
'</xsl:template>' + | |
'</xsl:stylesheet>', | |
'application/xml' | |
); | |
processor.importStylesheet(sheet); | |
var doc = processor.transformToDocument(sheet); | |
var range = doc.createRange(); | |
range.selectNodeContents(doc.documentElement); | |
range.deleteContents(); | |
doc.documentElement.appendChild(range.createContextualFragment(source)); | |
return doc; | |
} | |
function getHTMLDocument(text, xpath, xmlns, ignoreTags, callback, thisObj) { | |
let doc = createHTMLDocument(text, xmlns); | |
if (!xpath) xpath = '//*'; | |
return getNodesFromXPath(xpath, doc, callback, thisObj); | |
} | |
function getNodesFromXPath(xpath, context, callback, thisObj) { | |
var ret = []; | |
if (!xpath) return ret; | |
context = context || window.content.document; | |
var nodesSnapshot = (context.ownerDocument || context).evaluate(xpath, context, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); | |
for (let i = 0, l = nodesSnapshot.snapshotLength; i < l; i++) { | |
if (typeof callback == 'function') callback.call(thisObj, nodesSnapshot.snapshotItem(i), i); | |
ret.push(nodesSnapshot.snapshotItem(i)); | |
} | |
return ret; | |
} | |
function pathToURL(a, baseURL, doc) { | |
if (!a) return ''; | |
var XHTML_NS = "http://www.w3.org/1999/xhtml"; | |
var XML_NS = "http://www.w3.org/XML/1998/namespace"; | |
//var path = (a.href || a.getAttribute('src') || a.action || a.value || a); | |
var path = (a.getAttribute('href') || a.getAttribute('src') || a.action || a.value || a); | |
if (/^https?:\/\//.test(path)) return path; | |
var link = (doc || window.content.documtent).createElementNS(XHTML_NS, 'a'); | |
link.setAttributeNS(XML_NS, 'xml:base', baseURL); | |
link.href = path; | |
return link.href; | |
} | |
function xmlSerialize(xml) { | |
try { | |
return (new XMLSerializer()).serializeToString(xml) | |
.replace(/<!--(?:[^-]|-(?!->))*-->/g, '') | |
.replace(/<\s*\/?\s*\w+/g, (all) => all.toLowerCase()); | |
} catch (e) { return '' } | |
} | |
//}}} | |
// vim: fenc=utf-8 sw=2 sts=2: |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment