Sleazy Fork is available in English.

xeHentai Helper

Become a hentai

As of 11.11.2022. See ბოლო ვერსია.

  1. // ==UserScript==
  2. // @name xeHentai Helper
  3. // @version 0.41
  4. // @description Become a hentai
  5. // @namespace https://yooooo.us
  6. // @include http*://*e-hentai.org/*
  7. // @include http*://exhentai.org/*
  8. // @license GNU General Public License (GPL)
  9. // @run-at document-end
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // @grant GM_deleteValue
  13. // @grant GM_xmlHttpRequest
  14. // @grant GM.getValue
  15. // @grant GM.setValue
  16. // @grant GM.deleteValue
  17. // @grant GM.xmlHttpRequest
  18. // @homepageURL https://github.com/fffonion/xeHentaiHelper.user.js
  19. // @supportURL https://github.com/fffonion/xeHentaiHelper.user.js/issues
  20. // ==/UserScript==
  21.  
  22. // ==== ARIA2 class taken from Binux's ThunderLixianExported === //
  23. (function () {
  24. var JSONRPC = (function () {
  25. var jsonrpc_version = '2.0';
  26.  
  27. function get_auth(url) {
  28. return url.match(/^(?:(?![^:@]+:[^:@\/]*@)[^:\/?#.]+:)?(?:\/\/)?(?:([^:@]*(?::[^:@]*)?)?@)?/)[1];
  29. };
  30.  
  31. function request(jsonrpc_path, method, params) {
  32. var auth = get_auth(jsonrpc_path);
  33. jsonrpc_path = jsonrpc_path.replace(/^((?![^:@]+:[^:@\/]*@)[^:\/?#.]+:)?(\/\/)?(?:(?:[^:@]*(?::[^:@]*)?)?@)?(.*)/, '$1$2$3'); // auth string not allowed in url for firefox
  34.  
  35. var request_obj = {
  36. jsonrpc: jsonrpc_version,
  37. method: method,
  38. id: (new Date()).getTime().toString(),
  39. };
  40. if (params) request_obj.params = params;
  41. if (auth && auth.indexOf('token:') == 0) params.unshift(auth);
  42.  
  43. var headers = {
  44. "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
  45. }
  46. if (auth && auth.indexOf('token:') != 0) {
  47. headers["Authorization"] = "Basic " + btoa(auth);
  48. }
  49. var err = function () {
  50. console.error(method, params[params.length-2], params[params.length-1], "=>")
  51. };
  52. var r;
  53. if (jsonrpc_path.match(/\/\/localhost:/)) {
  54. // there's not CORS on both sites, we only need GM_xmlHttpRequest
  55. // to bypass insecure http downgrade error
  56. // but chrome is happy to send to http://localhost
  57. // use original XHR gives us better error message
  58. r = GM_xmlHttpRequest_fallback;
  59. } else {
  60. r = GM_xmlHttpRequest;
  61. }
  62. new Promise((_, reject) => {
  63. try {
  64. r({
  65. method: "POST",
  66. url: jsonrpc_path + "?tm=" + (new Date()).getTime().toString(),
  67. headers: headers,
  68. data: JSON.stringify(request_obj),
  69. onerror: err,
  70. onabort: err,
  71. ontimeout: err,
  72. onload: function (r) {
  73. console.info(method, params[params.length-2], params[params.length-1]
  74. , "=>", JSON.parse(r.responseText))
  75. },
  76. })
  77. } catch (error) {
  78. reject(error);
  79. }
  80. });
  81. };
  82.  
  83. return function (jsonrpc_path) {
  84. this.jsonrpc_path = jsonrpc_path;
  85. this.addTask = function (uri, options) {
  86. request(this.jsonrpc_path, 'xeH.addTask', [
  87. [uri, ], options
  88. ]);
  89. };
  90. this.setCookie = function (cookie) {
  91. request(this.jsonrpc_path, 'xeH.setCookie', [
  92. [cookie, ], {}
  93. ]);
  94. };
  95. return this;
  96. };
  97. })();
  98.  
  99. // dom helper functions
  100. function newWrapperDiv(label, dom) {
  101. var grp = document.createElement("div");
  102. grp.innerHTML = label + ":";
  103. grp.appendChild(dom);
  104. return grp;
  105. }
  106.  
  107. function newInput() {
  108. var input = document.createElement("input");
  109. input.type = "text";
  110. input.size = 50;
  111. input.style = "margin-bottom: 5px;"
  112. return input;
  113. }
  114.  
  115. function newButton(label, style, f) {
  116. var btn = document.createElement("div");
  117. btn.innerHTML = "<a href='javascript:void(0)' style='text-decoration: none'>" + label + "</a>";
  118. btn.style = "position: absolute; " + (style || "");
  119. btn.onclick = f;
  120. btn.className = "gt";
  121. return btn;
  122. }
  123.  
  124. if (GM !== undefined) {
  125. this.GM_getValue = GM.getValue;
  126. this.GM_setValue = GM.setValue;
  127. this.GM_deleteValue = GM.deleteValue;
  128. this.GM_xmlHttpRequest = GM.xmlHttpRequest;
  129. }
  130. if (!this.GM_getValue || (this.GM_getValue.toString && this.GM_getValue.toString().indexOf("not supported") > -1)) {
  131. console.info("[XEH] using fallback set/getValue")
  132. this.GM_getValue = function (key, def) {
  133. return localStorage[key] || def;
  134. };
  135. this.GM_setValue = function (key, value) {
  136. return localStorage[key] = value;
  137. };
  138. this.GM_deleteValue = function (key) {
  139. return delete localStorage[key];
  140. };
  141. }
  142. var GM_xmlHttpRequest_fallback = function (opts) {
  143. var xhr = new XMLHttpRequest();
  144. xhr.open(opts.method, opts.url, !opts.synchronous);
  145. for (let key in opts.headers || {}) {
  146. xhr.setRequestHeader(key, opts.headers[key]);
  147. }
  148. xhr.onerror = opts.onerror;
  149. xhr.onabort = opts.onabort;
  150. xhr.onload = function () {
  151. if (xhr.readyState === xhr.DONE) {
  152. opts.onload(xhr);
  153. }
  154. }
  155. xhr.send(opts.data);
  156. }
  157. if (!this.GM_xmlHttpRequest) {
  158. console.info("[XEH] using fallback XHR")
  159. this.GM_xmlHttpRequest = GM_xmlHttpRequest_fallback;
  160. }
  161.  
  162. XEH = {
  163. config_keys: ["host", "port", "token", "name"],
  164. };
  165. (function (XEH) {
  166. // JSONRPC client, will be initlized in the config area
  167. var jr;
  168. var gd5 = document.getElementById("gd5");
  169. if (gd5) { // gallery page
  170. var p = document.createElement("p");
  171. p.innerHTML = '<img src="https://ehgt.org/g/mr.gif"> <a id="xeh_addtask" href="#">添加到xeHentai</a>';
  172. p.className = "g2";
  173. p.onclick = function () {
  174. if (document.cookie.indexOf("ipb_pass_hash") != -1 && document.cookie.indexOf("ipb_member_id") != -1) {
  175. jr.setCookie(document.cookie);
  176. }
  177. jr.addTask(location.protocol + '//' + location.host + location.pathname, {});
  178. };
  179. gd5.childNodes[gd5.childNodes.length - 1].className = "g2";
  180. gd5.appendChild(p);
  181. }
  182. var glnames = document.getElementsByClassName("glname");
  183. if (glnames && glnames.length) { // index page
  184. function saveInputState() {
  185. var checked = {};
  186. for (var i = 0; i < allinps.length; ++i) {
  187. if (allinps[i].checked) {
  188. checked[allinps[i].value] = 1;
  189. }
  190. }
  191. checked.ts = new Date().getTime();
  192. GM_setValue("xeh_checked", JSON.stringify(checked));
  193. }
  194.  
  195. function loadInputState() {
  196. var checked = JSON.parse(GM_getValue("xeh_checked", "0"));
  197. if (!checked) return;
  198. // ignore saved states longer than 30 minutes
  199. if (checked.ts && new Date().getTime() - checked.ts > 10 * 60 * 1000) {
  200. return;
  201. }
  202. for (var i = 0; i < allinps.length; ++i) {
  203. if (checked[allinps[i].value] === 1) {
  204. allinps[i].checked = true;
  205. }
  206. }
  207. }
  208. var inp_onclick = function (e) {
  209. e.stopPropagation();
  210. saveInputState();
  211. };
  212.  
  213. var inputSize = "0.7em";
  214. if (glnames[0].className.search(/gl\dm/) != -1) { // minimal, minimal+
  215. } else if (glnames[0].className.search(/gl\dc/) != -1) { // compact
  216. inputSize = "0.8em";
  217. } else if (glnames[0].className.search(/gl\d[te]/) != 1) { // extended or thumbail mode
  218. inputSize = "0.9em";
  219. }
  220.  
  221. var allinps = [];
  222. for (var i = 0; i < glnames.length; ++i) {
  223. var glname = glnames[i];
  224. var ip = document.createElement("input");
  225. ip.type = "checkbox";
  226. ip.style = "float:left;font-size:20px;width:" + inputSize + ";height:" + inputSize + ";top:0;";
  227. var href;
  228. var href_dom = glname;
  229. for (var j = 0; j < 3; j++) {
  230. href = href_dom.innerHTML.match(/\/g\/\d+\/[a-f0-9]+\//);
  231. if (href) break;
  232. href_dom = href_dom.parentNode;
  233. }
  234. ip.value = decodeURIComponent(location.protocol + "//" + location.hostname + href[0]);
  235. ip.onclick = inp_onclick;
  236. var doms = href_dom.childNodes;
  237. for (var k = 0; k < doms.length; k++) {
  238. if (doms[k].tagName === "A") {
  239. doms[k].insertBefore(ip, doms[k].childNodes[0]);
  240. }
  241. }
  242. allinps.push(ip);
  243. }
  244. var titlebar = document.getElementsByClassName("itg")[0].childNodes[0].childNodes[0];
  245. var titleInnerHTML = '<input type="checkbox" id="xeh_toogle" style="margin-left:5px;top:0;font-size: 20px;width:##SIZE##;height:##SIZE##;">反选</input>' +
  246. '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a id="xeh_clear" href="javascript:void(0)">清空</a>' +
  247. '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a id="xeh_export" href="javascript:void(0)">导出选中项到xeHentai</a>';
  248.  
  249. titleInnerHTML = titleInnerHTML.replaceAll("##SIZE##", inputSize);
  250.  
  251. if (glnames[0].className.search(/gl\d[te]/) != -1) { // extended or thumbail mode
  252. var el = document.createElement("tr");
  253. el.style = "padding:0;font-size:14px;";
  254. var tbl = document.getElementsByClassName("itg")[0];
  255. if (glnames[0].className.search(/gl\dt/) != -1) { // thumbnail is not a table
  256. var tt = document.createElement("table");
  257. tt.className = "itg";
  258. tbl.parentNode.insertBefore(tt, tbl);
  259. tbl = tt;
  260. titleInnerHTML += "<tbody></tbody>";
  261. } else { // extended, shift right by one cell
  262. titleInnerHTML = "<td></td><td><div style='padding-left: 124px;'><a>" + titleInnerHTML + "</a></div></td>"
  263. }
  264. el.innerHTML = titleInnerHTML;
  265. tbl.insertBefore(el, tbl.childNodes[0]);
  266. } else {
  267. for (var n = 0; n < titlebar.childNodes.length; n++) {
  268. if (titlebar.childNodes[n].innerHTML.search(/title/i) !== -1) {
  269. titlebar.childNodes[n].innerHTML = titleInnerHTML;
  270. }
  271. }
  272. }
  273.  
  274. loadInputState();
  275.  
  276. document.getElementById("xeh_toogle").onclick = function (s) {
  277. for (var i = 0; i < allinps.length; ++i) {
  278. allinps[i].checked = !allinps[i].checked;
  279. }
  280. saveInputState();
  281. };
  282. document.getElementById("xeh_clear").onclick = function (s) {
  283. for (var i = 0; i < allinps.length; ++i) {
  284. allinps[i].checked = false;
  285. }
  286. saveInputState();
  287. };
  288. document.getElementById("xeh_export").onclick = function (s) {
  289. if (document.cookie.indexOf("ipb_pass_hash") != -1 && document.cookie.indexOf("ipb_member_id") != -1) {
  290. jr.setCookie(document.cookie);
  291. }
  292. for (var i = 0; i < allinps.length; ++i) {
  293. if (allinps[i].checked) {
  294. jr.addTask(allinps[i].value, {});
  295. }
  296. }
  297. };
  298. }
  299.  
  300. // config
  301. var titleBar = document.getElementById("nb");
  302. if (titleBar) {
  303. // increase width for exhentai
  304. if (titleBar.childElementCount < 10) {
  305. titleBar.style.maxWidth = "750px";
  306. }
  307. var div = document.createElement("div");
  308. div.innerHTML = '<div><a>xeHentai</a></div>';
  309. div.onclick = function () {
  310. configBox.style.display = "block";
  311. };
  312. titleBar.appendChild(div);
  313.  
  314. // the config box
  315. var configBox = document.createElement("div");
  316. configBox.className = "gm"
  317. configBox.innerHTML = '<h1 id="gn" style="text-align: center;">xeHentai 配置</h1>';
  318. configBox.style = "height: 300px; position: absolute; top: 200px; z-index: 999; left: 50%; margin-left: -250px; min-width: 320px;";
  319. configBox.style.display = "none";
  320. document.body.appendChild(configBox)
  321.  
  322. /****************** starts top buttons ************************/
  323. var closeBtn = newButton("关闭", "right: 5px; top: 5px;", function () {
  324. configBox.style.display = "none";
  325. });
  326. configBox.appendChild(closeBtn);
  327. /****************** ends top buttons ************************/
  328.  
  329. /****************** starts input areas ************************/
  330. var controlsGrp = document.createElement("div");
  331. controlsGrp.style = "padding: 10px;"
  332.  
  333. XEH.configs = undefined;
  334. var inputs = {};
  335.  
  336. function saveConfigSet(who) {
  337. GM_setValue("xeh_configs", JSON.stringify(XEH.configs));
  338. GM_setValue("xeh_config_idx", who.selectedIndex);
  339. }
  340.  
  341. function loadConfigSet(i) {
  342. if (XEH.configs === undefined) {
  343. XEH.configs = JSON.parse(GM_getValue("xeh_configs", "[]"));
  344. }
  345. if (XEH.configs.length === 0) {
  346. XEH.configs = [{
  347. "host": "localhost",
  348. "port": 8010,
  349. "name": "<默认(点右上角配置)>"
  350. }];
  351. }
  352. if (i === undefined) {
  353. i = parseInt(GM_getValue("xeh_config_idx", "0"));
  354. }
  355. if (i >= XEH.configs.length) {
  356. i = 0
  357. }
  358.  
  359. var cfg = XEH.configs[i];
  360. for (var j = 0; j < XEH.config_keys.length; j++) {
  361. var k = XEH.config_keys[j];
  362. inputs[k].value = cfg[k] || "";
  363. }
  364.  
  365. return i;
  366. }
  367.  
  368. function initJSONRPC(i) {
  369. var cfg = XEH.configs[i]
  370. jr = JSONRPC("http://" + (cfg.token ? ("token:" + cfg.token + "@") : "") +
  371. cfg.host + ":" + cfg.port + "/jsonrpc");
  372. }
  373.  
  374. var hasNewConfigUnsaved = false;
  375.  
  376. // add <select> first, its options will be filled later
  377. var configSet = document.createElement("select");
  378. var configSetOnChangeHandler = function () {
  379. if (hasNewConfigUnsaved) {
  380. this.remove(this.length - 1)
  381. hasNewConfigUnsaved = false;
  382. }
  383. var i = this.selectedIndex
  384. loadConfigSet(i)
  385. initJSONRPC(i)
  386. }
  387. configSet.onchange = configSetOnChangeHandler;
  388. controlsGrp.appendChild(newWrapperDiv("当前配置", configSet));
  389.  
  390. configBox.appendChild(controlsGrp);
  391.  
  392. var __input_labels = ["地址", "端口", "密钥", "名称"];
  393. for (var i = 0; i < XEH.config_keys.length; i++) {
  394. var k = XEH.config_keys[i];
  395. inputs[k] = newInput();
  396. controlsGrp.appendChild(newWrapperDiv(__input_labels[i], inputs[k]));
  397. }
  398. inputs.port.size = 6;
  399.  
  400. // load input boxes etc.
  401. var shouldSelectedIdx = loadConfigSet();
  402.  
  403. for (var i = 0; i < XEH.configs.length; i++) {
  404. var c = document.createElement("option");
  405. c.text = XEH.configs[i].name;
  406. configSet.options.add(c)
  407. }
  408. configSet.selectedIndex = shouldSelectedIdx;
  409. initJSONRPC(shouldSelectedIdx)
  410. /****************** ends input areas ************************/
  411.  
  412. var webuiBtn = newButton("打开WebUI", "left: 20px; bottom: 60px; font-size: 12px;", function () {
  413. window.open("https://xehentai.yooooo.us/#host=" + inputs.host.value +
  414. ",port=" + inputs.port.value + ",token=" + inputs.token.value +
  415. ",https=no",
  416. '_blank').focus();
  417. win;
  418. });
  419. configBox.appendChild(webuiBtn);
  420.  
  421. /****************** starts bottom buttons ************************/
  422. var addBtn = newButton("新建", "left: 20px; bottom: 5px;", function () {
  423. for (var i = 0; i < XEH.config_keys.length; i++) {
  424. var k = XEH.config_keys[i];
  425. inputs[k].value = "";
  426. }
  427. var c = document.createElement("option");
  428. c.text = "<新配置>";
  429. configSet.options.add(c);
  430. configSet.selectedIndex = configSet.options.length - 1;
  431. hasNewConfigUnsaved = true;
  432. });
  433. configBox.appendChild(addBtn);
  434.  
  435. var delBtn = newButton("删除", "left: 70px; bottom: 5px;", function () {
  436. var i = configSet.selectedIndex;
  437. configSet.remove(i);
  438. configSet.selectedIndex = Math.max(i - 1, 0);
  439. hasNewConfigUnsaved = false;
  440. XEH.configs.splice(i);
  441. saveConfigSet(configSet);
  442. });
  443. configBox.appendChild(delBtn);
  444.  
  445. var ojbkBtn = newButton("保存", "left: 150px; bottom: 5px;", function (e) {
  446. var idx = configSet.selectedIndex;
  447. var cfg;
  448. if (inputs.host.value == "" || parseInt(inputs.port.value) === undefined) {
  449. alert("地址不能为空,端口必须为数字");
  450. e.stopImmediatePropagation();
  451. return;
  452. }
  453. if (hasNewConfigUnsaved) {
  454. hasNewConfigUnsaved = false
  455. cfg = {}
  456. XEH.configs.push(cfg)
  457. } else {
  458. cfg = XEH.configs[idx]
  459. }
  460. inputs.name.value = inputs.name.value || (inputs.host.value + ":" + inputs.port.value);
  461. for (var i = 0; i < XEH.config_keys.length; i++) {
  462. var k = XEH.config_keys[i];
  463. cfg[k] = inputs[k].value;
  464. }
  465. configSet.options.item(idx).text = cfg.name;
  466. saveConfigSet(configSet);
  467. initJSONRPC(idx);
  468. });
  469. configBox.appendChild(ojbkBtn);
  470.  
  471. var resetBtn = newButton("重置", "right: 5px; bottom: 5px;", function () {
  472. loadConfigSet(configSet.selectedIndex)
  473. });
  474. configBox.appendChild(resetBtn);
  475. /****************** ends bottom buttons ************************/
  476.  
  477. /****************** starts duplicate ui ************************/
  478. var closeBtn2 = closeBtn.cloneNode(true);
  479. closeBtn2.style = "position: absolute; left: 200px; bottom: 5px;";
  480. closeBtn2.onclick = closeBtn.onclick;
  481. configBox.appendChild(closeBtn2);
  482.  
  483. var xehExportAnchor = document.getElementById("xeh_export");
  484. if (xehExportAnchor) {
  485. var configSet2 = configSet.cloneNode(true);
  486. configSet2.style = configSet2.style + "; margin-left: 10px;width:auto;"
  487. configSet2.addEventListener("change", function () {
  488. configSet.selectedIndex = this.selectedIndex;
  489. saveConfigSet(this);
  490. });
  491. configSet2.addEventListener("change", configSetOnChangeHandler);
  492.  
  493. configSet.addEventListener("change", function () {
  494. configSet2.selectedIndex = this.selectedIndex;
  495. })
  496.  
  497. delBtn.addEventListener("click", function () {
  498. var i = configSet2.selectedIndex;
  499. configSet2.remove(i);
  500. configSet2.selectedIndex = Math.max(i - 1, 0);
  501. });
  502. ojbkBtn.addEventListener("click", function () {
  503. var i = configSet.selectedIndex;
  504. if (i >= configSet2.options.length) {
  505. var c = document.createElement("option");
  506. c.text = XEH.configs[i].name;
  507. configSet2.options.add(c);
  508. } else {
  509. configSet2.options.item(i).text = XEH.configs[i].name;
  510. }
  511. configSet2.selectedIndex = i;
  512. });
  513. xehExportAnchor.parentNode.appendChild(configSet2);
  514. configSet2.selectedIndex = shouldSelectedIdx;
  515. }
  516. /****************** ends duplicate ui ************************/
  517. }
  518. })(XEH);
  519. })();
  520.