- // ==UserScript==
- // @name xeHentai Helper
- // @version 0.41
- // @description Become a hentai
- // @namespace https://yooooo.us
- // @include http*://*e-hentai.org/*
- // @include http*://exhentai.org/*
- // @license GNU General Public License (GPL)
- // @run-at document-end
- // @grant GM_getValue
- // @grant GM_setValue
- // @grant GM_deleteValue
- // @grant GM_xmlHttpRequest
- // @grant GM.getValue
- // @grant GM.setValue
- // @grant GM.deleteValue
- // @grant GM.xmlHttpRequest
- // @homepageURL https://github.com/fffonion/xeHentaiHelper.user.js
- // @supportURL https://github.com/fffonion/xeHentaiHelper.user.js/issues
- // ==/UserScript==
-
- // ==== ARIA2 class taken from Binux's ThunderLixianExported === //
- (function () {
- var JSONRPC = (function () {
- var jsonrpc_version = '2.0';
-
- function get_auth(url) {
- return url.match(/^(?:(?![^:@]+:[^:@\/]*@)[^:\/?#.]+:)?(?:\/\/)?(?:([^:@]*(?::[^:@]*)?)?@)?/)[1];
- };
-
- function request(jsonrpc_path, method, params) {
- var auth = get_auth(jsonrpc_path);
- jsonrpc_path = jsonrpc_path.replace(/^((?![^:@]+:[^:@\/]*@)[^:\/?#.]+:)?(\/\/)?(?:(?:[^:@]*(?::[^:@]*)?)?@)?(.*)/, '$1$2$3'); // auth string not allowed in url for firefox
-
- var request_obj = {
- jsonrpc: jsonrpc_version,
- method: method,
- id: (new Date()).getTime().toString(),
- };
- if (params) request_obj.params = params;
- if (auth && auth.indexOf('token:') == 0) params.unshift(auth);
-
- var headers = {
- "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
- }
- if (auth && auth.indexOf('token:') != 0) {
- headers["Authorization"] = "Basic " + btoa(auth);
- }
- var err = function () {
- console.error(method, params[params.length-2], params[params.length-1], "=>")
- };
- var r;
- if (jsonrpc_path.match(/\/\/localhost:/)) {
- // there's not CORS on both sites, we only need GM_xmlHttpRequest
- // to bypass insecure http downgrade error
- // but chrome is happy to send to http://localhost
- // use original XHR gives us better error message
- r = GM_xmlHttpRequest_fallback;
- } else {
- r = GM_xmlHttpRequest;
- }
- new Promise((_, reject) => {
- try {
- r({
- method: "POST",
- url: jsonrpc_path + "?tm=" + (new Date()).getTime().toString(),
- headers: headers,
- data: JSON.stringify(request_obj),
- onerror: err,
- onabort: err,
- ontimeout: err,
- onload: function (r) {
- console.info(method, params[params.length-2], params[params.length-1]
- , "=>", JSON.parse(r.responseText))
- },
- })
- } catch (error) {
- reject(error);
- }
- });
- };
-
- return function (jsonrpc_path) {
- this.jsonrpc_path = jsonrpc_path;
- this.addTask = function (uri, options) {
- request(this.jsonrpc_path, 'xeH.addTask', [
- [uri, ], options
- ]);
- };
- this.setCookie = function (cookie) {
- request(this.jsonrpc_path, 'xeH.setCookie', [
- [cookie, ], {}
- ]);
- };
- return this;
- };
- })();
-
- // dom helper functions
- function newWrapperDiv(label, dom) {
- var grp = document.createElement("div");
- grp.innerHTML = label + ":";
- grp.appendChild(dom);
- return grp;
- }
-
- function newInput() {
- var input = document.createElement("input");
- input.type = "text";
- input.size = 50;
- input.style = "margin-bottom: 5px;"
- return input;
- }
-
- function newButton(label, style, f) {
- var btn = document.createElement("div");
- btn.innerHTML = "<a href='javascript:void(0)' style='text-decoration: none'>" + label + "</a>";
- btn.style = "position: absolute; " + (style || "");
- btn.onclick = f;
- btn.className = "gt";
- return btn;
- }
-
- if (GM !== undefined) {
- this.GM_getValue = GM.getValue;
- this.GM_setValue = GM.setValue;
- this.GM_deleteValue = GM.deleteValue;
- this.GM_xmlHttpRequest = GM.xmlHttpRequest;
- }
- if (!this.GM_getValue || (this.GM_getValue.toString && this.GM_getValue.toString().indexOf("not supported") > -1)) {
- console.info("[XEH] using fallback set/getValue")
- this.GM_getValue = function (key, def) {
- return localStorage[key] || def;
- };
- this.GM_setValue = function (key, value) {
- return localStorage[key] = value;
- };
- this.GM_deleteValue = function (key) {
- return delete localStorage[key];
- };
- }
- var GM_xmlHttpRequest_fallback = function (opts) {
- var xhr = new XMLHttpRequest();
- xhr.open(opts.method, opts.url, !opts.synchronous);
- for (let key in opts.headers || {}) {
- xhr.setRequestHeader(key, opts.headers[key]);
- }
- xhr.onerror = opts.onerror;
- xhr.onabort = opts.onabort;
- xhr.onload = function () {
- if (xhr.readyState === xhr.DONE) {
- opts.onload(xhr);
- }
- }
- xhr.send(opts.data);
- }
- if (!this.GM_xmlHttpRequest) {
- console.info("[XEH] using fallback XHR")
- this.GM_xmlHttpRequest = GM_xmlHttpRequest_fallback;
- }
-
- XEH = {
- config_keys: ["host", "port", "token", "name"],
- };
- (function (XEH) {
- // JSONRPC client, will be initlized in the config area
- var jr;
- var gd5 = document.getElementById("gd5");
- if (gd5) { // gallery page
- var p = document.createElement("p");
- p.innerHTML = '<img src="https://ehgt.org/g/mr.gif"> <a id="xeh_addtask" href="#">添加到xeHentai</a>';
- p.className = "g2";
- p.onclick = function () {
- if (document.cookie.indexOf("ipb_pass_hash") != -1 && document.cookie.indexOf("ipb_member_id") != -1) {
- jr.setCookie(document.cookie);
- }
- jr.addTask(location.protocol + '//' + location.host + location.pathname, {});
- };
- gd5.childNodes[gd5.childNodes.length - 1].className = "g2";
- gd5.appendChild(p);
- }
- var glnames = document.getElementsByClassName("glname");
- if (glnames && glnames.length) { // index page
- function saveInputState() {
- var checked = {};
- for (var i = 0; i < allinps.length; ++i) {
- if (allinps[i].checked) {
- checked[allinps[i].value] = 1;
- }
- }
- checked.ts = new Date().getTime();
- GM_setValue("xeh_checked", JSON.stringify(checked));
- }
-
- function loadInputState() {
- var checked = JSON.parse(GM_getValue("xeh_checked", "0"));
- if (!checked) return;
- // ignore saved states longer than 30 minutes
- if (checked.ts && new Date().getTime() - checked.ts > 10 * 60 * 1000) {
- return;
- }
- for (var i = 0; i < allinps.length; ++i) {
- if (checked[allinps[i].value] === 1) {
- allinps[i].checked = true;
- }
- }
- }
- var inp_onclick = function (e) {
- e.stopPropagation();
- saveInputState();
- };
-
- var inputSize = "0.7em";
- if (glnames[0].className.search(/gl\dm/) != -1) { // minimal, minimal+
- } else if (glnames[0].className.search(/gl\dc/) != -1) { // compact
- inputSize = "0.8em";
- } else if (glnames[0].className.search(/gl\d[te]/) != 1) { // extended or thumbail mode
- inputSize = "0.9em";
- }
-
- var allinps = [];
- for (var i = 0; i < glnames.length; ++i) {
- var glname = glnames[i];
- var ip = document.createElement("input");
- ip.type = "checkbox";
- ip.style = "float:left;font-size:20px;width:" + inputSize + ";height:" + inputSize + ";top:0;";
- var href;
- var href_dom = glname;
- for (var j = 0; j < 3; j++) {
- href = href_dom.innerHTML.match(/\/g\/\d+\/[a-f0-9]+\//);
- if (href) break;
- href_dom = href_dom.parentNode;
- }
- ip.value = decodeURIComponent(location.protocol + "//" + location.hostname + href[0]);
- ip.onclick = inp_onclick;
- var doms = href_dom.childNodes;
- for (var k = 0; k < doms.length; k++) {
- if (doms[k].tagName === "A") {
- doms[k].insertBefore(ip, doms[k].childNodes[0]);
- }
- }
- allinps.push(ip);
- }
- var titlebar = document.getElementsByClassName("itg")[0].childNodes[0].childNodes[0];
- var titleInnerHTML = '<input type="checkbox" id="xeh_toogle" style="margin-left:5px;top:0;font-size: 20px;width:##SIZE##;height:##SIZE##;">反选</input>' +
- ' <a id="xeh_clear" href="javascript:void(0)">清空</a>' +
- ' <a id="xeh_export" href="javascript:void(0)">导出选中项到xeHentai</a>';
-
- titleInnerHTML = titleInnerHTML.replaceAll("##SIZE##", inputSize);
-
- if (glnames[0].className.search(/gl\d[te]/) != -1) { // extended or thumbail mode
- var el = document.createElement("tr");
- el.style = "padding:0;font-size:14px;";
- var tbl = document.getElementsByClassName("itg")[0];
- if (glnames[0].className.search(/gl\dt/) != -1) { // thumbnail is not a table
- var tt = document.createElement("table");
- tt.className = "itg";
- tbl.parentNode.insertBefore(tt, tbl);
- tbl = tt;
- titleInnerHTML += "<tbody></tbody>";
- } else { // extended, shift right by one cell
- titleInnerHTML = "<td></td><td><div style='padding-left: 124px;'><a>" + titleInnerHTML + "</a></div></td>"
- }
- el.innerHTML = titleInnerHTML;
- tbl.insertBefore(el, tbl.childNodes[0]);
- } else {
- for (var n = 0; n < titlebar.childNodes.length; n++) {
- if (titlebar.childNodes[n].innerHTML.search(/title/i) !== -1) {
- titlebar.childNodes[n].innerHTML = titleInnerHTML;
- }
- }
- }
-
- loadInputState();
-
- document.getElementById("xeh_toogle").onclick = function (s) {
- for (var i = 0; i < allinps.length; ++i) {
- allinps[i].checked = !allinps[i].checked;
- }
- saveInputState();
- };
- document.getElementById("xeh_clear").onclick = function (s) {
- for (var i = 0; i < allinps.length; ++i) {
- allinps[i].checked = false;
- }
- saveInputState();
- };
- document.getElementById("xeh_export").onclick = function (s) {
- if (document.cookie.indexOf("ipb_pass_hash") != -1 && document.cookie.indexOf("ipb_member_id") != -1) {
- jr.setCookie(document.cookie);
- }
- for (var i = 0; i < allinps.length; ++i) {
- if (allinps[i].checked) {
- jr.addTask(allinps[i].value, {});
- }
- }
- };
- }
-
- // config
- var titleBar = document.getElementById("nb");
- if (titleBar) {
- // increase width for exhentai
- if (titleBar.childElementCount < 10) {
- titleBar.style.maxWidth = "750px";
- }
- var div = document.createElement("div");
- div.innerHTML = '<div><a>xeHentai</a></div>';
- div.onclick = function () {
- configBox.style.display = "block";
- };
- titleBar.appendChild(div);
-
- // the config box
- var configBox = document.createElement("div");
- configBox.className = "gm"
- configBox.innerHTML = '<h1 id="gn" style="text-align: center;">xeHentai 配置</h1>';
- configBox.style = "height: 300px; position: absolute; top: 200px; z-index: 999; left: 50%; margin-left: -250px; min-width: 320px;";
- configBox.style.display = "none";
- document.body.appendChild(configBox)
-
- /****************** starts top buttons ************************/
- var closeBtn = newButton("关闭", "right: 5px; top: 5px;", function () {
- configBox.style.display = "none";
- });
- configBox.appendChild(closeBtn);
- /****************** ends top buttons ************************/
-
- /****************** starts input areas ************************/
- var controlsGrp = document.createElement("div");
- controlsGrp.style = "padding: 10px;"
-
- XEH.configs = undefined;
- var inputs = {};
-
- function saveConfigSet(who) {
- GM_setValue("xeh_configs", JSON.stringify(XEH.configs));
- GM_setValue("xeh_config_idx", who.selectedIndex);
- }
-
- function loadConfigSet(i) {
- if (XEH.configs === undefined) {
- XEH.configs = JSON.parse(GM_getValue("xeh_configs", "[]"));
- }
- if (XEH.configs.length === 0) {
- XEH.configs = [{
- "host": "localhost",
- "port": 8010,
- "name": "<默认(点右上角配置)>"
- }];
- }
- if (i === undefined) {
- i = parseInt(GM_getValue("xeh_config_idx", "0"));
- }
- if (i >= XEH.configs.length) {
- i = 0
- }
-
- var cfg = XEH.configs[i];
- for (var j = 0; j < XEH.config_keys.length; j++) {
- var k = XEH.config_keys[j];
- inputs[k].value = cfg[k] || "";
- }
-
- return i;
- }
-
- function initJSONRPC(i) {
- var cfg = XEH.configs[i]
- jr = JSONRPC("http://" + (cfg.token ? ("token:" + cfg.token + "@") : "") +
- cfg.host + ":" + cfg.port + "/jsonrpc");
- }
-
- var hasNewConfigUnsaved = false;
-
- // add <select> first, its options will be filled later
- var configSet = document.createElement("select");
- var configSetOnChangeHandler = function () {
- if (hasNewConfigUnsaved) {
- this.remove(this.length - 1)
- hasNewConfigUnsaved = false;
- }
- var i = this.selectedIndex
- loadConfigSet(i)
- initJSONRPC(i)
- }
- configSet.onchange = configSetOnChangeHandler;
- controlsGrp.appendChild(newWrapperDiv("当前配置", configSet));
-
- configBox.appendChild(controlsGrp);
-
- var __input_labels = ["地址", "端口", "密钥", "名称"];
- for (var i = 0; i < XEH.config_keys.length; i++) {
- var k = XEH.config_keys[i];
- inputs[k] = newInput();
- controlsGrp.appendChild(newWrapperDiv(__input_labels[i], inputs[k]));
- }
- inputs.port.size = 6;
-
- // load input boxes etc.
- var shouldSelectedIdx = loadConfigSet();
-
- for (var i = 0; i < XEH.configs.length; i++) {
- var c = document.createElement("option");
- c.text = XEH.configs[i].name;
- configSet.options.add(c)
- }
- configSet.selectedIndex = shouldSelectedIdx;
- initJSONRPC(shouldSelectedIdx)
- /****************** ends input areas ************************/
-
- var webuiBtn = newButton("打开WebUI", "left: 20px; bottom: 60px; font-size: 12px;", function () {
- window.open("https://xehentai.yooooo.us/#host=" + inputs.host.value +
- ",port=" + inputs.port.value + ",token=" + inputs.token.value +
- ",https=no",
- '_blank').focus();
- win;
- });
- configBox.appendChild(webuiBtn);
-
- /****************** starts bottom buttons ************************/
- var addBtn = newButton("新建", "left: 20px; bottom: 5px;", function () {
- for (var i = 0; i < XEH.config_keys.length; i++) {
- var k = XEH.config_keys[i];
- inputs[k].value = "";
- }
- var c = document.createElement("option");
- c.text = "<新配置>";
- configSet.options.add(c);
- configSet.selectedIndex = configSet.options.length - 1;
- hasNewConfigUnsaved = true;
- });
- configBox.appendChild(addBtn);
-
- var delBtn = newButton("删除", "left: 70px; bottom: 5px;", function () {
- var i = configSet.selectedIndex;
- configSet.remove(i);
- configSet.selectedIndex = Math.max(i - 1, 0);
- hasNewConfigUnsaved = false;
- XEH.configs.splice(i);
- saveConfigSet(configSet);
- });
- configBox.appendChild(delBtn);
-
- var ojbkBtn = newButton("保存", "left: 150px; bottom: 5px;", function (e) {
- var idx = configSet.selectedIndex;
- var cfg;
- if (inputs.host.value == "" || parseInt(inputs.port.value) === undefined) {
- alert("地址不能为空,端口必须为数字");
- e.stopImmediatePropagation();
- return;
- }
- if (hasNewConfigUnsaved) {
- hasNewConfigUnsaved = false
- cfg = {}
- XEH.configs.push(cfg)
- } else {
- cfg = XEH.configs[idx]
- }
- inputs.name.value = inputs.name.value || (inputs.host.value + ":" + inputs.port.value);
- for (var i = 0; i < XEH.config_keys.length; i++) {
- var k = XEH.config_keys[i];
- cfg[k] = inputs[k].value;
- }
- configSet.options.item(idx).text = cfg.name;
- saveConfigSet(configSet);
- initJSONRPC(idx);
- });
- configBox.appendChild(ojbkBtn);
-
- var resetBtn = newButton("重置", "right: 5px; bottom: 5px;", function () {
- loadConfigSet(configSet.selectedIndex)
- });
- configBox.appendChild(resetBtn);
- /****************** ends bottom buttons ************************/
-
- /****************** starts duplicate ui ************************/
- var closeBtn2 = closeBtn.cloneNode(true);
- closeBtn2.style = "position: absolute; left: 200px; bottom: 5px;";
- closeBtn2.onclick = closeBtn.onclick;
- configBox.appendChild(closeBtn2);
-
- var xehExportAnchor = document.getElementById("xeh_export");
- if (xehExportAnchor) {
- var configSet2 = configSet.cloneNode(true);
- configSet2.style = configSet2.style + "; margin-left: 10px;width:auto;"
- configSet2.addEventListener("change", function () {
- configSet.selectedIndex = this.selectedIndex;
- saveConfigSet(this);
- });
- configSet2.addEventListener("change", configSetOnChangeHandler);
-
- configSet.addEventListener("change", function () {
- configSet2.selectedIndex = this.selectedIndex;
- })
-
- delBtn.addEventListener("click", function () {
- var i = configSet2.selectedIndex;
- configSet2.remove(i);
- configSet2.selectedIndex = Math.max(i - 1, 0);
- });
- ojbkBtn.addEventListener("click", function () {
- var i = configSet.selectedIndex;
- if (i >= configSet2.options.length) {
- var c = document.createElement("option");
- c.text = XEH.configs[i].name;
- configSet2.options.add(c);
- } else {
- configSet2.options.item(i).text = XEH.configs[i].name;
- }
- configSet2.selectedIndex = i;
- });
- xehExportAnchor.parentNode.appendChild(configSet2);
- configSet2.selectedIndex = shouldSelectedIdx;
- }
- /****************** ends duplicate ui ************************/
- }
- })(XEH);
- })();
-