Unlock Hath Perks

Unlock Hath Perks and add other helpers

21.03.2019 itibariyledir. En son verisyonu görün.

  1. // ==UserScript==
  2. // @name Unlock Hath Perks
  3. // @name:zh-TW 解鎖 Hath Perks
  4. // @name:zh-CN 解锁 Hath Perks
  5. // @description Unlock Hath Perks and add other helpers
  6. // @description:zh-TW 解鎖 Hath Perks 及增加一些小工具
  7. // @description:zh-CN 解锁 Hath Perks 及增加一些小工具
  8. // @namespace https://github.com/FlandreDaisuki
  9. // @version 2.0.0
  10. // @match *://e-hentai.org/*
  11. // @match *://exhentai.org/*
  12. // @require https://unpkg.com/vue@2.6.9/dist/vue.min.js
  13. // @icon https://i.imgur.com/JsU0vTd.png
  14. // @grant GM_setValue
  15. // @grant GM_getValue
  16. // @noframes
  17. //
  18. // Addition metas
  19. //
  20. // @supportURL https://github.com/FlandreDaisuki/My-Browser-Extensions/issues
  21. // @homepageURL https://github.com/FlandreDaisuki/My-Browser-Extensions/blob/master/userscripts/UnlockHathPerks.md
  22. // @author FlandreDaisuki
  23. // @license MPLv2
  24. // @compatible firefox 52+
  25. // @compatible chrome 55+
  26. // @incompatible any not support async/await, CSS-grid browsers
  27. // ==/UserScript==
  28.  
  29. /* global Vue */
  30.  
  31. /** ***************** */
  32. /* helper functions */
  33.  
  34. const noop = () => { };
  35. const $ = (s, d = document) => d.querySelector(s);
  36. const $el = (tag, attr = {}, cb = noop) => {
  37. const el = document.createElement(tag);
  38. if (typeof (attr) === 'string') {
  39. el.textContent = attr;
  40. } else {
  41. Object.assign(el, attr);
  42. }
  43. cb(el);
  44. return el;
  45. };
  46. const $style = (text) => {
  47. $el('style', text, (el) => document.head.appendChild(el));
  48. };
  49.  
  50. /* helper functions end */
  51. /** ********************* */
  52.  
  53. /** ********* */
  54. /* easy DOM */
  55.  
  56. // nav
  57. const nb = $('#nb');
  58. const navdiv = $el('div');
  59. const navbtn = $el('a', 'Unlock Hath Perks');
  60. navbtn.id = 'uhp-btn';
  61. navbtn.addEventListener('click', () => {
  62. $('#uhp-panel-container').classList.remove('hidden');
  63. });
  64. nb.appendChild(navdiv);
  65. navdiv.appendChild(navbtn);
  66.  
  67. // panel container
  68. const uhpPanelContainer = $el('div', {
  69. className: 'hidden',
  70. id: 'uhp-panel-container',
  71. });
  72. uhpPanelContainer.addEventListener('click', () => {
  73. uhpPanelContainer.classList.add('hidden');
  74. });
  75. document.body.appendChild(uhpPanelContainer);
  76.  
  77. // panel
  78. const uhpPanel = $el('div', { id: 'uhp-panel' }, (el) => {
  79. if (location.host === 'exhentai.org') {
  80. el.classList.add('dark');
  81. }
  82. el.addEventListener('click', (ev) => ev.stopPropagation());
  83. });
  84. uhpPanelContainer.appendChild(uhpPanel);
  85.  
  86. /* easy DOM end */
  87. /** ************* */
  88.  
  89. /** ******************* */
  90. /* configs and events */
  91.  
  92. const uhpConfig = {
  93. abg: true,
  94. mt: true,
  95. pe: true,
  96. };
  97.  
  98. Object.assign(uhpConfig, GM_getValue('uhp', uhpConfig));
  99. GM_setValue('uhp', uhpConfig);
  100.  
  101. if (uhpConfig.abg) {
  102. Object.defineProperty(window, 'adsbyjuicy', {
  103. configurable: false,
  104. enumerable: false,
  105. writable: false,
  106. value: Object.create(null),
  107. });
  108. }
  109.  
  110.  
  111. // More Thumbs code block
  112. if (location.pathname.startsWith('/g/')) {
  113. (async () => {
  114. const getGalleryPageState = async (url, selectors) => {
  115. const result = {
  116. elements: [],
  117. nextURL: null,
  118. };
  119.  
  120. if (!url) { return result; }
  121.  
  122. const resp = await fetch(url, {
  123. credentials: 'same-origin',
  124. });
  125.  
  126. if (resp.ok) {
  127. const html = await resp.text();
  128. const doc = new DOMParser().parseFromString(html, 'text/html');
  129. const $find = (s) => ($(s, doc) || { children: [] });
  130. result.elements = [...$find(selectors.parent).children];
  131.  
  132. const nextEl = $find(selectors.np);
  133. result.nextURL = nextEl ? (nextEl.href || null) : null;
  134. }
  135.  
  136. console.log(result);
  137. return result;
  138. };
  139.  
  140. const selectors = {
  141. np: '.ptt td:last-child > a',
  142. parent: '#gdt',
  143. };
  144.  
  145. const pageState = {
  146. parent: $(selectors.parent),
  147. locked: false,
  148. nextURL: null,
  149. };
  150.  
  151. const thisPage = await getGalleryPageState(location.href, selectors);
  152.  
  153. while (pageState.parent.firstChild) {
  154. pageState.parent.firstChild.remove();
  155. }
  156.  
  157. thisPage.elements
  158. .filter((el) => !el.classList.contains('c'))
  159. .forEach((el) => pageState.parent.appendChild(el));
  160. pageState.nextURL = thisPage.nextURL;
  161. if (!pageState.nextURL) {
  162. return;
  163. }
  164.  
  165. if (uhpConfig.mt) {
  166. // search page found results
  167.  
  168. document.addEventListener('scroll', async () => {
  169. const anchorTop = $('table.ptb').getBoundingClientRect().top;
  170. const vh = window.innerHeight;
  171.  
  172. if (anchorTop < vh * 2 && !pageState.lock && pageState.nextURL) {
  173. pageState.lock = true;
  174.  
  175. const nextPage = await getGalleryPageState(pageState.nextURL, selectors);
  176. nextPage.elements
  177. .filter((el) => !el.classList.contains('c'))
  178. .forEach((el) => pageState.parent.appendChild(el));
  179. pageState.nextURL = nextPage.nextURL;
  180.  
  181. pageState.lock = false;
  182. }
  183. });
  184. }
  185. })();
  186. }
  187.  
  188. // Page Enlargement code block
  189. if ($('#searchbox') && $('.itg')) {
  190. (async () => {
  191. const getPageState = async (url, selectors) => {
  192. const result = {
  193. elements: [],
  194. nextURL: null,
  195. };
  196.  
  197. if (!url) { return result; }
  198.  
  199. const resp = await fetch(url, {
  200. credentials: 'same-origin',
  201. });
  202.  
  203. if (resp.ok) {
  204. const html = await resp.text();
  205. const doc = new DOMParser().parseFromString(html, 'text/html');
  206. const $find = (s) => ($(s, doc) || { children: [] });
  207. result.elements = [...$find(selectors.parent).children];
  208.  
  209. const nextEl = $find(selectors.np);
  210. result.nextURL = nextEl ? (nextEl.href || null) : null;
  211. }
  212.  
  213. console.log(result);
  214. return result;
  215. };
  216.  
  217. const modet = Boolean($('.itg.glt'));
  218. const status = $el('h1', { textContent: 'Loading...', id: 'uhp-status' });
  219. const selectors = {
  220. np: '.ptt td:last-child > a',
  221. parent: modet ? '.itg.glt > tbody' : '.itg.gld',
  222. };
  223.  
  224. const pageState = {
  225. parent: $(selectors.parent),
  226. locked: false,
  227. nextURL: null,
  228. };
  229.  
  230. const thisPage = await getPageState(location.href, selectors);
  231.  
  232. while (pageState.parent.firstChild) {
  233. pageState.parent.firstChild.remove();
  234. }
  235.  
  236. thisPage.elements.forEach((el) => pageState.parent.appendChild(el));
  237. pageState.nextURL = thisPage.nextURL;
  238. if (!pageState.nextURL) {
  239. status.textContent = 'End';
  240. }
  241.  
  242. if (uhpConfig.pe) {
  243. $('table.ptb').replaceWith(status);
  244.  
  245. // search page found results
  246.  
  247. document.addEventListener('scroll', async () => {
  248. const anchorTop = status.getBoundingClientRect().top;
  249. const vh = window.innerHeight;
  250.  
  251. if (anchorTop < vh * 2 && !pageState.lock && pageState.nextURL) {
  252. pageState.lock = true;
  253.  
  254. const nextPage = await getPageState(pageState.nextURL, selectors);
  255. nextPage.elements.forEach((el) => pageState.parent.appendChild(el));
  256. pageState.nextURL = nextPage.nextURL;
  257. if (!pageState.nextURL) {
  258. status.textContent = 'End';
  259. }
  260. pageState.lock = false;
  261. }
  262. });
  263. }
  264. })();
  265. }
  266.  
  267. /* configs and events end */
  268. /** *********************** */
  269.  
  270.  
  271. /** ************* */
  272. /* option panel */
  273.  
  274. const uhpPanelTmpl = `
  275. <div id="uhp-panel" :class="{ dark: isExH }" @click.stop>
  276. <h1>Hath Perks</h1>
  277. <div>
  278. <div v-for="d in HathPerks" class="option-grid">
  279. <div class="material-switch">
  280. <input :id="confid(d.abbr)" type="checkbox" v-model="conf[d.abbr]" @change="save" />
  281. <label :for="confid(d.abbr)"></label>
  282. </div>
  283. <span class="uhp-conf-title">{{d.title}}</span>
  284. <span class="uhp-conf-desc">{{d.desc}}</span>
  285. </div>
  286. </div>
  287. </div>
  288. `;
  289.  
  290. // eslint-disable-next-line no-new
  291. new Vue({
  292. el: '#uhp-panel',
  293. template: uhpPanelTmpl,
  294. data: {
  295. conf: uhpConfig,
  296. HathPerks: [{
  297. abbr: 'abg',
  298. title: 'Ads-Be-Gone',
  299. desc: 'Remove ads. You can use it with adblock webextensions.',
  300. }, {
  301. abbr: 'mt',
  302. title: 'More Thumbs',
  303. desc: 'Scroll infinitely in gallery pages.',
  304. }, {
  305. abbr: 'pe',
  306. title: 'Paging Enlargement',
  307. desc: 'Scroll infinitely in search results pages.',
  308. }],
  309. Others: [],
  310. },
  311. computed: {
  312. isExH() { return location.host === 'exhentai.org'; },
  313. },
  314. methods: {
  315. save() { GM_setValue('uhp', uhpConfig); },
  316. confid(id) { return `ubp-conf-${id}`; },
  317. },
  318. });
  319.  
  320. $style(`
  321. /* override */
  322. #nb {
  323. max-width: initial;
  324. justify-content: center;
  325. }
  326. .gl1t {
  327. display: flex;
  328. flex-flow: column;
  329. }
  330. .gl1t > .gl3t {
  331. flex: 1;
  332. }
  333. .gl1t > .gl3t > a {
  334. display: flex;
  335. align-items: center;
  336. justify-content: center;
  337. height: 100%;
  338. }
  339. div#gdt {
  340. clear: initial;
  341. display: flex;
  342. flex-flow: wrap;
  343. }
  344. /* uhp */
  345. #uhp-btn {
  346. cursor: pointer;
  347. }
  348. #uhp-panel-container {
  349. position: fixed;
  350. top: 0;
  351. height: 100vh;
  352. width: 100vw;
  353. background-color: rgba(200, 200, 200, 0.7);
  354. z-index: 2;
  355. display: flex;
  356. align-items: center;
  357. justify-content: center;
  358. }
  359. #uhp-panel-container.hidden {
  360. visibility: hidden;
  361. opacity: 0;
  362. }
  363. #uhp-panel {
  364. padding: 1.2rem;
  365. background-color: floralwhite;
  366. border-radius: 1rem;
  367. font-size: 1rem;
  368. color: darkred;
  369. max-width: 650px;
  370. }
  371. #uhp-panel.dark {
  372. background-color: dimgray;
  373. color: ghostwhite;
  374. }
  375. #uhp-panel .option-grid {
  376. display: grid;
  377. grid-template-columns: max-content 120px 1fr;
  378. grid-gap: 0.5rem 1rem;
  379. margin: 0.5rem 1rem;
  380. }
  381. #uhp-panel .option-grid > * {
  382. display: flex;
  383. justify-content: center;
  384. align-items: center;
  385. }
  386. #uhp-full-width-container.fullwidth,
  387. #uhp-full-width-container.fullwidth div.itg {
  388. max-width: none;
  389. }
  390. #uhp-full-width-container div.itg {
  391. display: grid;
  392. grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  393. grid-gap: 2px;
  394. }
  395. #uhp-full-width-container div.itg.uhp-tpf-dense {
  396. grid-auto-flow: dense;
  397. }
  398. #uhp-full-width-container div.id1 {
  399. height: 345px;
  400. float: none;
  401. display: flex;
  402. flex-direction: column;
  403. margin: 3px auto;
  404. padding: 4px 0;
  405. }
  406. #uhp-full-width-container div.id2 {
  407. overflow: visible;
  408. height: initial;
  409. margin: 4px auto;
  410. }
  411. #uhp-full-width-container div.id3 {
  412. flex: 1;
  413. display: flex;
  414. justify-content: center;
  415. align-items: center;
  416. }
  417. .uhp-list-parent-eh tr:nth-of-type(2n+1) {
  418. background-color: #EDEBDF;
  419. }
  420. .uhp-list-parent-eh tr:nth-of-type(2n+2) {
  421. background-color: #F2F0E4;
  422. }
  423. .uhp-list-parent-exh tr:nth-of-type(2n+1) {
  424. background-color: #363940;
  425. }
  426. .uhp-list-parent-exh tr:nth-of-type(2n+2) {
  427. background-color: #4F535B;
  428. }
  429. #uhp-status {
  430. text-align: center;
  431. font-size: 3rem;
  432. clear: both;
  433. padding: 2rem 0;
  434. }
  435.  
  436. /* https://bootsnipp.com/snippets/featured/material-design-switch */
  437. .material-switch {
  438. display: inline-block;
  439. }
  440.  
  441. .material-switch > input[type="checkbox"] {
  442. display: none;
  443. }
  444.  
  445. .material-switch > input[type="checkbox"] + label {
  446. display: inline-block;
  447. position: relative;
  448. margin: 6px;
  449. border-radius: 8px;
  450. width: 40px;
  451. height: 16px;
  452. opacity: 0.3;
  453. background-color: rgb(0, 0, 0);
  454. box-shadow: inset 0px 0px 10px rgba(0, 0, 0, 0.5);
  455. transition: all 0.4s ease-in-out;
  456. }
  457.  
  458. .material-switch > input[type="checkbox"] + label::after {
  459. position: absolute;
  460. top: -4px;
  461. left: -4px;
  462. border-radius: 16px;
  463. width: 24px;
  464. height: 24px;
  465. content: "";
  466. background-color: rgb(255, 255, 255);
  467. box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.3);
  468. transition: all 0.3s ease-in-out;
  469. }
  470.  
  471. .material-switch > input[type="checkbox"]:checked + label {
  472. background-color: #0e0;
  473. opacity: 0.7;
  474. }
  475.  
  476. .material-switch > input[type="checkbox"]:checked + label::after {
  477. background-color: inherit;
  478. left: 20px;
  479. }
  480. .material-switch > input[type="checkbox"]:disabled + label::after {
  481. content: "\\f023";
  482. line-height: 24px;
  483. font-size: 0.8em;
  484. font-family: FontAwesome;
  485. color: initial;
  486. }`);
  487.  
  488. $el('link', {
  489. href: 'https://use.fontawesome.com/releases/v5.8.0/css/all.css',
  490. rel: 'stylesheet',
  491. integrity: 'sha384-Mmxa0mLqhmOeaE8vgOSbKacftZcsNYDjQzuCOm6D02luYSzBG8vpaOykv9lFQ51Y',
  492. crossOrigin: 'anonymous',
  493. }, (el) => document.head.appendChild(el));