// ==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 2.2.2
// @match https://e-hentai.org/*
// @match https://exhentai.org/*
// @require https://unpkg.com/vue@2.6.9/dist/vue.min.js
// @icon https://i.imgur.com/JsU0vTd.png
// @grant GM_getValue
// @grant GM.getValue
// @grant GM_setValue
// @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 $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 $style = (stylesheet) => $el('style', stylesheet, (el) => document.head.appendChild(el));
const throttle = (fn, timeout = 1000) => {
let locked = false;
return (...args) => {
if (!locked){
locked = true;
fn(...args);
setTimeout(() => {
locked = false;
}, timeout);
}
};
};
/* cSpell:ignore navdiv navbtn exhentai adsbyjuicy searchbox favcat searchnav favform */
/* cSpell:ignoreRegExp \b\.\w+\b */
/* eslint-disable no-console */
/* global Vue */
// #region easy DOM
// nav
const nb = $('#nb');
const navdiv = $el('div');
const navbtn = $el('a', {
id: 'uhp-btn',
textContent: 'Unlock Hath Perks',
});
navbtn.addEventListener('click', () => {
$('#uhp-panel-container').classList.remove('hidden');
});
nb.appendChild(navdiv);
navdiv.appendChild(navbtn);
// panel container
const uhpPanelContainer = $el('div', {
className: 'hidden',
id: 'uhp-panel-container',
});
uhpPanelContainer.addEventListener('click', () => {
uhpPanelContainer.classList.add('hidden');
});
document.body.appendChild(uhpPanelContainer);
// panel
const uhpPanel = $el('div', { id: 'uhp-panel' }, (el) => {
if (location.host === 'exhentai.org') {
el.classList.add('dark');
}
el.addEventListener('click', (ev) => ev.stopPropagation());
});
uhpPanelContainer.appendChild(uhpPanel);
// #endregion easy DOM
// #region configs and events
const uhpConfig = {
abg: true,
mt: true,
pe: true,
};
Object.assign(uhpConfig, GM_getValue('uhp', uhpConfig));
GM_setValue('uhp', uhpConfig);
if (uhpConfig.abg) {
Object.defineProperty(window, 'adsbyjuicy', {
configurable: false,
enumerable: false,
writable: false,
value: Object.create(null),
});
}
// More Thumbs code block
if (location.pathname.startsWith('/g/')) {
(async() => {
const getGalleryPageState = async(url, selectors) => {
const result = {
elements: [],
nextURL: null,
};
if (!url) { return result; }
const resp = await fetch(url, {
credentials: 'same-origin',
});
if (resp.ok) {
const html = await resp.text();
const docEl = (new DOMParser())
.parseFromString(html, 'text/html')
.documentElement;
result.elements = [...$find(docEl, selectors.parent)?.children ?? []];
const nextEl = $find(docEl, selectors.np);
result.nextURL = nextEl ? (nextEl.href || null) : null;
}
console.log(result);
return result;
};
const selectors = {
np: '.ptt td:last-child > a',
parent: '#gdt',
};
const pageState = {
parent: $(selectors.parent),
locked: false,
nextURL: null,
};
const thisPage = await getGalleryPageState(location.href, selectors);
while (pageState.parent.firstChild) {
pageState.parent.firstChild.remove();
}
thisPage.elements
.filter((el) => !el.classList.contains('c'))
.forEach((el) => pageState.parent.appendChild(el));
pageState.nextURL = thisPage.nextURL;
if (!pageState.nextURL) {
return;
}
if (uhpConfig.mt) {
// search page found results
document.addEventListener('scroll', throttle(async() => {
const anchorTop = $('table.ptb').getBoundingClientRect().top;
const vh = window.innerHeight;
if (anchorTop < vh * 2 && !pageState.lock && pageState.nextURL) {
pageState.lock = true;
const nextPage = await getGalleryPageState(pageState.nextURL, selectors);
nextPage.elements
.filter((el) => !el.classList.contains('c'))
.forEach((el) => pageState.parent.appendChild(el));
pageState.nextURL = nextPage.nextURL;
pageState.lock = false;
}
}));
}
})();
}
// Page Enlargement code block
if ($('input[name="f_search"]') && $('.itg')) {
(async() => {
const getPageState = async(url, selectors) => {
const result = {
elements: [],
nextURL: null,
};
if (!url) { return result; }
const resp = await fetch(url, {
credentials: 'same-origin',
});
if (resp.ok) {
const html = await resp.text();
const docEl = (new DOMParser())
.parseFromString(html, 'text/html')
.documentElement;
result.elements = [...$find(docEl, selectors.parent)?.children ?? []];
const nextEl = $find(docEl, selectors.np);
result.nextURL = nextEl ? (nextEl.href || null) : null;
}
console.log(result);
return result;
};
const isTableLayout = Boolean($('table.itg'));
const status = $el('h1', { textContent: 'Loading...', id: 'uhp-status' });
const selectors = {
np: '.ptt td:last-child > a, .searchnav a[href*="next="]',
parent: isTableLayout ? 'table.itg > tbody' : 'div.itg',
};
const pageState = {
parent: $(selectors.parent),
locked: false,
nextURL: null,
};
const thisPage = await getPageState(location.href, selectors);
while (pageState.parent.firstChild) {
pageState.parent.firstChild.remove();
}
thisPage.elements.forEach((el) => pageState.parent.appendChild(el));
pageState.nextURL = thisPage.nextURL;
if (!pageState.nextURL) {
status.textContent = 'End';
}
if (uhpConfig.pe) {
$('table.ptb, .itg + .searchnav, #favform + .searchnav').replaceWith(status);
// search page found results
document.addEventListener('scroll', async() => {
const anchorTop = status.getBoundingClientRect().top;
const vh = window.innerHeight;
if (anchorTop < vh * 2 && !pageState.lock && pageState.nextURL) {
pageState.lock = true;
const nextPage = await getPageState(pageState.nextURL, selectors);
nextPage.elements.forEach((el) => pageState.parent.appendChild(el));
pageState.nextURL = nextPage.nextURL;
if (!pageState.nextURL) {
status.textContent = 'End';
}
pageState.lock = false;
}
});
}
})();
}
// #endregion configs and events
const uhpPanelTemplate = `
<div id="uhp-panel" :class="{ dark: isExH }" @click.stop>
<h1>Hath Perks</h1>
<div>
<div v-for="d in HathPerks" class="option-grid">
<div class="material-switch">
<input :id="getConfId(d.abbr)" type="checkbox" v-model="conf[d.abbr]" @change="save" />
<label :for="getConfId(d.abbr)"></label>
</div>
<span class="uhp-conf-title">{{d.title}}</span>
<span class="uhp-conf-desc">{{d.desc}}</span>
</div>
</div>
</div>
`;
// eslint-disable-next-line no-new
new Vue({
el: '#uhp-panel',
template: uhpPanelTemplate,
data: {
conf: uhpConfig,
HathPerks: [{
abbr: 'abg',
title: 'Ads-Be-Gone',
desc: 'Remove ads. You can use it with adblock webextensions.',
}, {
abbr: 'mt',
title: 'More Thumbs',
desc: 'Scroll infinitely in gallery pages.',
}, {
abbr: 'pe',
title: 'Paging Enlargement',
desc: 'Scroll infinitely in search results pages.',
}],
Others: [],
},
computed: {
isExH() { return location.host === 'exhentai.org'; },
},
methods: {
save() { GM_setValue('uhp', uhpConfig); },
getConfId(id) { return `ubp-conf-${ id }`; },
},
});
$style(`
/* nav bar */
#nb {
width: initial;
max-width: 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%;
}
/* /g/{gid}/{token} */
div#gdt {
clear: initial;
display: flex;
flex-flow: wrap;
}`);
$style(`
/* uhp */
#uhp-btn {
cursor: pointer;
}
#uhp-panel-container {
position: fixed;
top: 0;
height: 100vh;
width: 100vw;
background-color: rgba(200, 200, 200, 0.7);
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
}
#uhp-panel-container.hidden {
visibility: hidden;
opacity: 0;
}
#uhp-panel {
padding: 1.2rem;
background-color: floralwhite;
border-radius: 1rem;
font-size: 1rem;
color: darkred;
max-width: 650px;
}
#uhp-panel.dark {
background-color: dimgray;
color: ghostwhite;
}
#uhp-panel .option-grid {
display: grid;
grid-template-columns: max-content 120px 1fr;
grid-gap: 0.5rem 1rem;
margin: 0.5rem 1rem;
}
#uhp-panel .option-grid > * {
display: flex;
justify-content: center;
align-items: center;
}
#uhp-full-width-container.fullwidth,
#uhp-full-width-container.fullwidth div.itg {
max-width: none;
}
#uhp-full-width-container div.itg {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
grid-gap: 2px;
}
#uhp-full-width-container div.itg.uhp-tpf-dense {
grid-auto-flow: dense;
}
#uhp-full-width-container div.id1 {
height: 345px;
float: none;
display: flex;
flex-direction: column;
margin: 3px auto;
padding: 4px 0;
}
#uhp-full-width-container div.id2 {
overflow: visible;
height: initial;
margin: 4px auto;
}
#uhp-full-width-container div.id3 {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
}
.uhp-list-parent-eh tr:nth-of-type(2n+1) {
background-color: #EDEBDF;
}
.uhp-list-parent-eh tr:nth-of-type(2n+2) {
background-color: #F2F0E4;
}
.uhp-list-parent-exh tr:nth-of-type(2n+1) {
background-color: #363940;
}
.uhp-list-parent-exh tr:nth-of-type(2n+2) {
background-color: #4F535B;
}
#uhp-status {
text-align: center;
font-size: 3rem;
clear: both;
padding: 2rem 0;
}
/* https://bootsnipp.com/snippets/featured/material-design-switch */
.material-switch {
display: inline-block;
}
.material-switch > input[type="checkbox"] {
display: none;
}
.material-switch > input[type="checkbox"] + label {
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;
}
.material-switch > input[type="checkbox"] + label::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;
}
.material-switch > input[type="checkbox"]:checked + label {
background-color: #0e0;
opacity: 0.7;
}
.material-switch > input[type="checkbox"]:checked + label::after {
background-color: inherit;
left: 20px;
}
.material-switch > input[type="checkbox"]:disabled + label::after {
content: "\\f023";
line-height: 24px;
font-size: 0.8em;
font-family: FontAwesome;
color: initial;
}`);
$el('link', {
href: 'https://use.fontawesome.com/releases/v5.8.0/css/all.css',
rel: 'stylesheet',
integrity: 'sha384-Mmxa0mLqhmOeaE8vgOSbKacftZcsNYDjQzuCOm6D02luYSzBG8vpaOykv9lFQ51Y',
crossOrigin: 'anonymous',
}, (el) => document.head.appendChild(el));
})();