您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
解锁 Hath Perks 及增加一些小工具
// ==UserScript== // @name Unlock Hath Perks // @name:zh-TW 解鎖 Hath Perks // @name:zh-CN 解锁 Hath Perks // @description Unlock Hath Perks and add other helpers // @description:zh-TW 解鎖 Hath Perks 及增加一些小工具 // @description:zh-CN 解锁 Hath Perks 及增加一些小工具 // @namespace https://flandre.in/github // @version 3.0.2 // @match https://e-hentai.org/* // @match https://exhentai.org/* // @icon https://i.imgur.com/JsU0vTd.png // @grant GM_getValue // @grant GM_setValue // @noframes // @author FlandreDaisuki // @supportURL https://github.com/FlandreDaisuki/My-Browser-Extensions/issues // @homepageURL https://github.com/FlandreDaisuki/My-Browser-Extensions/blob/master/userscripts/UnlockHathPerks/README.md // @license MPLv2 // ==/UserScript== (function () { 'use strict'; const noop = () => {}; const $find = (el, selectors) => el.querySelector(selectors); const $ = (selectors) => document.querySelector(selectors); const $$ = (selectors) => Array.from(document.querySelectorAll(selectors)); const $el = (tag, attr = {}, cb = noop) => { const el = document.createElement(tag); if (typeof(attr) === 'string') { el.textContent = attr; } else { Object.assign(el, attr); } cb(el); return el; }; const $html = (htmlText) => { const tmpEl = $el('div'); tmpEl.innerHTML = htmlText; return tmpEl.firstElementChild; }; const $style = (stylesheet) => $el('style', stylesheet, (el) => document.head.appendChild(el)); const sleep = (ms) => new Promise((resolve) => { setTimeout(resolve, ms); }); /* cSpell:ignore exhentai juicyads favcat searchnav favform */ /* eslint-disable no-console */ /** @type {{abg: boolean, mt: boolean, pe: boolean, fw: boolean}} */ const uhpConfig = (() => { const _conf = Object.assign({ abg: true, mt: true, pe: true, fw: false }, GM_getValue('uhp') ); GM_setValue('uhp', _conf); return new Proxy(_conf, { set(target, propertyKey, value){ const r = Reflect.set(target, propertyKey, value); GM_setValue('uhp', _conf); return r; }, }); })(); // #region Ads-Be-Gone if (uhpConfig.abg) { $style('iframe[src*="juicyads"] { display:none !important; }'); } // #endregion Ads-Be-Gone // #region More Thumbs if (uhpConfig.mt) { (async() => { if (!location.pathname.startsWith('/g/')){ return; } const NEXT_PAGE_SELECTOR = '.ptt td:last-child > a'; const IMAGE_PARENT_SELECTOR = '#gdt'; const imgParentEl = $(IMAGE_PARENT_SELECTOR); if (!imgParentEl){ return console.error('No imgParentEl'); } imgParentEl.innerHTML = ''; /** @param {string} initUrl */ async function *newPagedImgElsGen(initUrl) { let url = initUrl; /** @type {HTMLElement[]} */ let imgEls = []; while (url) { const resp = await fetch(url, { credentials: 'same-origin' }); url = ''; imgEls = []; if (resp.ok) { const html = await resp.text(); const docEl = (new DOMParser()) .parseFromString(html, 'text/html') .documentElement; imgEls = Array.from($find(docEl, IMAGE_PARENT_SELECTOR)?.children ?? []); const nextEl = $find(docEl, NEXT_PAGE_SELECTOR); url = nextEl?.href ?? ''; } yield imgEls; } return []; } const pagedImgEls = newPagedImgElsGen(location.href); const replaceResult = async(ob) => { const pagedImgElsResult = await pagedImgEls.next(); if (pagedImgElsResult.done) { return ob.disconnect(); } for (const imgEl of pagedImgElsResult.value) { if (!imgEl.classList.contains('c')) { imgParentEl.appendChild(imgEl); } } }; let isIntersecting = false; const ob = new IntersectionObserver(async(entries) => { isIntersecting = entries[0].isIntersecting; if (isIntersecting) { do { await replaceResult(ob); await sleep(300); } while (isIntersecting); } }); ob.observe($('table.ptb')); })(); } // #endregion More Thumbs // #region Page Enlargement if (uhpConfig.pe) { (async() => { if (! $('input[name="f_search"]')) { return; } if (! $('.itg')) { return; } const isTableLayout = Boolean($('table.itg')); const NEXT_PAGE_SELECTOR = '.ptt td:last-child > a, .searchnav a[href*="next="]'; const IMAGE_PARENT_SELECTOR = isTableLayout ? 'table.itg > tbody' : 'div.itg'; const imgParentEl = $(IMAGE_PARENT_SELECTOR); if (!imgParentEl){ return console.error('No imgParentEl'); } imgParentEl.innerHTML = ''; const statusEl = $el('h1', { textContent: 'Loading...', id: '🔓-status' }); $('table.ptb, .itg + .searchnav, #favform + .searchnav').replaceWith(statusEl); /** @param {string} initUrl */ async function *newPagedImgElsGen(initUrl) { let url = initUrl; /** @type {HTMLElement[]} */ let imgEls = []; while (url) { const resp = await fetch(url, { credentials: 'same-origin' }); url = ''; imgEls = []; if (resp.ok) { const html = await resp.text(); const docEl = (new DOMParser()) .parseFromString(html, 'text/html') .documentElement; imgEls = Array.from($find(docEl, IMAGE_PARENT_SELECTOR)?.children ?? []); const nextEl = $find(docEl, NEXT_PAGE_SELECTOR); url = nextEl?.href ?? ''; } yield imgEls; } return []; } const pagedImgEls = newPagedImgElsGen(location.href); const replaceResult = async(ob) => { const pagedImgElsResult = await pagedImgEls.next(); if (pagedImgElsResult.done) { statusEl.textContent = 'End'; return ob.disconnect(); } for (const imgEl of pagedImgElsResult.value) { imgParentEl.appendChild(imgEl); } }; let isIntersecting = false; const ob = new IntersectionObserver(async(entries) => { isIntersecting = entries[0].isIntersecting; if (isIntersecting) { do { await replaceResult(ob); await sleep(300); } while (isIntersecting); } }); ob.observe(statusEl); })(); } // #endregion Page Enlargement // #region Full Width if (uhpConfig.fw) { document.body.classList.add('🔓-full-width'); } // #endregion Full Width // #region ubp dialog setup const uhpDialogEl = $el('dialog', { id: '🔓-dialog' }); uhpDialogEl.className = (location.host === 'exhentai.org') ? 'dark' : ''; uhpDialogEl.innerHTML = ` <fieldset> <legend>Unlock Hath Perks</legend> <div role="group"> <div class="option-grid"> <label class="material-switch"> <input type="checkbox" id="🔓-conf-abg" value="abg" /> </label> <span class="🔓-conf-title">Ads-Be-Gone</span> <span class="🔓-conf-desc">Remove ads. You can use it with adblock webextensions.</span> </div> <div class="option-grid"> <label class="material-switch"> <input type="checkbox" id="🔓-conf-mt" value="mt" /> </label> <span class="🔓-conf-title">More Thumbs</span> <span class="🔓-conf-desc">Scroll infinitely in gallery pages.</span> </div> <div class="option-grid"> <label class="material-switch"> <input type="checkbox" id="🔓-conf-pe" value="pe" /> </label> <span class="🔓-conf-title">Page Enlargement</span> <span class="🔓-conf-desc">Scroll infinitely in search results pages.</span> </div> <div class="option-grid"> <label class="material-switch"> <input type="checkbox" id="🔓-conf-fw" value="fw" /> </label> <span class="🔓-conf-title">Full Width</span> <span class="🔓-conf-desc">Utilize your monitor.</span> </div> </div> </fieldset> `; uhpDialogEl.onclick = (evt) => { if (evt.target === uhpDialogEl) { uhpDialogEl.close(); if (uhpDialogEl.dataset.hasChanged) { location.reload(); } } }; document.body.appendChild(uhpDialogEl); /** @type {HTMLInputElement[]} */ const checkboxEls = $$('dialog#🔓-dialog input[type="checkbox"]'); for (const checkboxEl of checkboxEls) { checkboxEl.checked = uhpConfig[checkboxEl.value]; checkboxEl.onchange = () => { uhpConfig[checkboxEl.value] = checkboxEl.checked; uhpDialogEl.dataset.hasChanged = true; }; } const nb = $('#nb'); nb.appendChild( $html(` <div> <a id="🔓-entry" href="javascript:;">Unlock Hath Perks</a> </div> `), ); $('a#🔓-entry').onclick = () => uhpDialogEl.showModal(); // #endregion ubp dialog setup // #region override e-h style $style(` /* nav bar */ #nb { width: initial; max-width: initial; max-height: initial; justify-content: center; } /* search input */ table.itc + p.nopm { display: flex; flex-flow: row wrap; justify-content: center; } input[name="f_search"] { width: 100%; } /* /favorites.php */ input[name="favcat"] + div { display: flex; flex-flow: row wrap; justify-content: center; gap: 8px; } /* gallery grid */ .gl1t { display: flex; flex-flow: column; } .gl1t > .gl3t { flex: 1; } .gl1t > .gl3t > a { display: flex; align-items: center; justify-content: center; height: 100%; }`); // #endregion override e-h style // #region uhp style $style(` #🔓-status { text-align: center; font-size: 3rem; clear: both; padding: 2rem 0; } #🔓-dialog { padding: 1.2rem; background-color: floralwhite; border-radius: 1rem; font-size: 1.4rem; color: darkred; max-width: 950px; &.dark { background-color: dimgray; color: ghostwhite; } fieldset > legend { font-size: 2rem; } .option-grid { display: grid; grid-template-columns: max-content 14rem 1fr; column-gap: 1rem; padding: 0.5rem 1rem; align-items: center; } } .🔓-full-width :where(#gdt, div.ido) { max-width: initial !important; margin: 1rem !important; } @supports (display:grid) { .🔓-full-width .gld { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 0.5rem; } } /* Modified https://bootsnipp.com/snippets/featured/material-design-switch */ label.material-switch > input[type="checkbox"] { display: none; } label.material-switch { display: inline-block; position: relative; margin: 6px; border-radius: 8px; width: 40px; height: 16px; opacity: 0.3; background-color: rgb(0, 0, 0); box-shadow: inset 0px 0px 10px rgba(0, 0, 0, 0.5); transition: all 0.4s ease-in-out; cursor: pointer; } label.material-switch::after { position: absolute; top: -4px; left: -4px; border-radius: 16px; width: 24px; height: 24px; content: ""; background-color: rgb(255, 255, 255); box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.3); transition: all 0.3s ease-in-out; } label.material-switch:has(> input[type="checkbox"]:checked) { background-color: #0e0; opacity: 0.7; } label.material-switch:has(> input[type="checkbox"]:checked)::after { background-color: inherit; left: 20px; }`); // #endregion uhp style })();