// ==UserScript== // @name EH – Page Scrobbler // @namespace https://github.com/Meldo-Megimi/EH-Page-Scrobbler/raw/main/PageScrobbler.user.js // @version 2024.01.01.01 // @description Visualize GID and add the ability to easily jump or scrobble // @author FabulousCupcake, OsenTen, Qserty, Meldo-Megimi // @license MIT // @run-at document-end // @match http://e-hentai.org/* // @match https://e-hentai.org/* // @match http://exhentai.org/* // @match https://exhentai.org/* // @match http://exhentai55ld2wyap5juskbm67czulomrouspdacjamjeloj7ugjbsad.onion/* // @grant GM_addStyle // ==/UserScript== const defaultBarWidth = 730; const stylesheet = ` .search-scrobbler { width: ${defaultBarWidth}px; outline: 1px cyan dashed; margin: 0 auto; padding: 20px 0 0 0; display: flex; flex-direction: column; gap: 0.5em; } .search-scrobbler .bar { display: block; width: ${defaultBarWidth}px; height: 25px; border: 2px solid #3c3c3c; box-sizing: border-box; position: relative; } .search-scrobbler .bar .bar-cursor { height: 100%; background: #5FA9CF; } .search-scrobbler .bar-wrapper { display: flex; flex-direction: column; } .search-scrobbler .bar-labels { width: 100%; display: flex; flex-direction: row; justify-content: space-between; } .search-scrobbler .bar-config { color: red; cursor: pointer; } .search-scrobbler .bar-hover { display: block; width: 1px; height: 100%; background: #f0f; position: absolute; } .search-scrobbler .bar-hovertext { position: absolute; outline: 1px solid #f0f; top: -1.5em; } .search-scrobbler, .search-scrobbler * { outline: 0px none !important; } .bar-year-labels { position:absolute; width:inherit; opacity:50%; pointer-events: none; } .bar-year-label { position:absolute; } .search-scrobbler-altcfg { width: 100%; display: flex; flex-direction: row; justify-content: space-between; } .search-scrobbler-altcfg .bar-config { color: red; cursor: pointer; } .saved-search { width: ${defaultBarWidth}px; margin: 0 auto; font-size: 10pt; } .search-relpager-top { width: ${defaultBarWidth}px; margin: 0px auto 0px auto; text-align: center; } .pg-jump { width: fit-content !important; padding: 0px 5px; } .search-scrobbler-config-bg { position: fixed; z-index: 100; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; display:none; backdrop-filter: blur(2px); } .search-scrobbler-config-window { margin: 15% auto !important; padding: 0px 5px 10px 10px !important; min-width: 300px !important; width: min-content; box-shadow:2px 2px 3px 2px gray; border-radius:7px; } .search-scrobbler-config-close { text-align: right; font-size:25px; cursor: pointer; white-space: nowrap; display: block ruby; } .search-scrobbler-config-title { font-size:16px; font-weight: bold; white-space: nowrap; margin: 4px 10px 0px 0px; } `; const gidYear = { 74629: 2008, 190496: 2009, 321076: 2010, 449183: 2011, 553117: 2012, 660230: 2013, 771830: 2014, 888870: 2015, 1012224: 2016, 1162942: 2017, 1338484: 2018, 1543397: 2019, 1813647: 2020, 2100270: 2021, 2419586: 2022, 2783947: 2023 }; const defaultMaxPrefetch = 5; const injectStylesheet = () => { if (typeof GM_addStyle != "undefined") { GM_addStyle(stylesheet); } else if (typeof addStyle != "undefined") { addStyle(stylesheet); } else { const stylesheetEl = document.createElement("style"); stylesheetEl.innerHTML = stylesheet; document.body.appendChild(stylesheetEl); } } const hasGalleryListTable = () => { return !!document.querySelector(".itg.gltm, .itg.gltc, .itg.glte, .itg.gld"); } const getMaxGID = doc => { let maxGID = 0; if (!!document.querySelector(".itg tr .glname a")) { // Minimal and Compact maxGID = doc.querySelector(".itg tr:nth-child(2) .glname a").href.match(/\/(\d+)\//)?.[1]; } else if (!!doc.querySelector(".itg tr a")) { // Extended maxGID = doc.querySelector(".itg tr:first-child a").href.match(/\/(\d+)\//)?.[1]; } else { // Thumbnail maxGID = doc.querySelector(".itg .gl1t:first-child a").href.match(/\/(\d+)\//)?.[1]; } return maxGID; } const tryUpdateKnownMaxGID = GID => { if (localStorage.getItem("EHPS-maxGID") === null) localStorage.setItem("EHPS-maxGID", -1); if (document.querySelector(".searchnav") == null) return; const url = new URL(location.href); if ((url.pathname !== "/") || (url.search !== "")) { if (url.pathname == "/popular") return; // not on frontpage or searching fetch(location.origin, { method: 'get' }).then((response) => { return response.text() }).then((res) => { let doc = document.implementation.createHTMLDocument("New Document"); doc.write(res); doc.close(); let maxGID = getMaxGID(doc); let currentMaxGID = localStorage.getItem("EHPS-maxGID"); if ((currentMaxGID === null) || (currentMaxGID < maxGID)) { localStorage.setItem("EHPS-maxGID", maxGID); } updatePageScrobbler(); }).catch((error) => { console.log(error) }); } else { // we are on the frontpage let maxGID = getMaxGID(document); let currentMaxGID = localStorage.getItem("EHPS-maxGID"); if ((currentMaxGID === null) || (currentMaxGID < maxGID)) { localStorage.setItem("EHPS-maxGID", maxGID); } } } const resetPageCounter = (pageInfo) => { if (pageInfo == null) pageInfo = {}; pageInfo.path = null; pageInfo.last = null; pageInfo.knownPages = { "min": 0, "max": 0 }; pageInfo.endLow = null; pageInfo.endHigh = null; return pageInfo; } const updatePageInfo = async () => { let pageInfo = JSON.parse(sessionStorage.getItem("EHPS-Paginator")); if (pageInfo == null) { pageInfo = resetPageCounter(pageInfo); } else { pageInfo.knownPages.min = parseInt(pageInfo.knownPages.min); pageInfo.knownPages.max = parseInt(pageInfo.knownPages.max); } // get current page nr. const parser = new URL(window.location); for (let i = pageInfo.knownPages.min; i <= pageInfo.knownPages.max; i++) { if (decodeURIComponent(pageInfo.knownPages[`P${i}`]) == decodeURIComponent(`${parser.search}`)) { window.currentPage = i; break; } } // current page is unknown, reset paginator if (isNaN(window.currentPage)) { pageInfo = resetPageCounter(pageInfo); window.currentPage = 0; } // check path if (pageInfo.path == null) pageInfo.path = location.pathname; else if (pageInfo.path != location.pathname) { pageInfo = resetPageCounter(pageInfo); pageInfo.path = location.pathname; } if (document.querySelector("#uprev").localName === "span") pageInfo.endLow = window.currentPage; if (document.querySelector("#unext").localName === "span") pageInfo.endHigh = window.currentPage; // do we know the current page? if (pageInfo.knownPages[`P${window.currentPage}`] == null) { if (window.location.search != "") { pageInfo.knownPages[`P${window.currentPage}`] = `${parser.search}`; } else { let maxGID = parseInt(getMaxGID(document), 10) + 1; pageInfo.knownPages[`P${window.currentPage}`] = `?next=${maxGID}`; // fix reload page window.history.pushState('forward', null, pageInfo.knownPages[`P${window.currentPage}`]); } if (pageInfo.knownPages.min > window.currentPage) pageInfo.knownPages.min = window.currentPage; if (pageInfo.knownPages.max < window.currentPage) pageInfo.knownPages.max = window.currentPage; } pageInfo.last = location.href; // look if next page announced is known if ((pageInfo.knownPages[`P${window.currentPage + 1}`] == null) && (document.querySelector("#unext").localName === "a")) { pageInfo.knownPages[`P${window.currentPage + 1}`] = `${(new URL(document.querySelector("#unext").href)).search}`; if (pageInfo.knownPages.min > window.currentPage + 1) pageInfo.knownPages.min = window.currentPage + 1; if (pageInfo.knownPages.max < window.currentPage + 1) pageInfo.knownPages.max = window.currentPage + 1; } else { // look if new or deleted entries have shifted the pages if ((pageInfo.knownPages[`P${window.currentPage + 1}`] != null) && (document.querySelector("#unext").href != null)) if (pageInfo.knownPages[`P${window.currentPage + 1}`] != `${(new URL(document.querySelector("#unext").href)).search}`) { pageInfo.knownPages[`P${window.currentPage + 1}`] = `${(new URL(document.querySelector("#unext").href)).search}`; } } // look if previous page announced is known if ((pageInfo.knownPages[`P${window.currentPage - 1}`] == null) && (document.querySelector("#uprev").localName === "a")) { pageInfo.knownPages[`P${window.currentPage - 1}`] = `${(new URL(document.querySelector("#uprev").href)).search}`; if (pageInfo.knownPages.min > window.currentPage - 1) pageInfo.knownPages.min = window.currentPage - 1; if (pageInfo.knownPages.max < window.currentPage - 1) pageInfo.knownPages.max = window.currentPage - 1; } else { // look if new or deleted entries have shifted the pages if ((pageInfo.knownPages[`P${window.currentPage - 1}`] != null) && (document.querySelector("#uprev").href != null)) if (pageInfo.knownPages[`P${window.currentPage - 1}`] != `${(new URL(document.querySelector("#uprev").href)).search}`) { pageInfo.knownPages[`P${window.currentPage - 1}`] = `${(new URL(document.querySelector("#uprev").href)).search}`; } } if (localStorage.getItem("EHPS-EnablePageinatorPrefetch") == "true") pageInfo = await prefetchPageInfo(pageInfo); sessionStorage.setItem("EHPS-Paginator", JSON.stringify(pageInfo)); return pageInfo; } const fetchDocument = async (url) => { try { let res = await fetch(url); let doc = document.implementation.createHTMLDocument("New Document"); doc.write(await res.text()); doc.close(); return doc; } catch (error) { console.log(error); return document.implementation.createHTMLDocument("New Document"); } } const prefetchPrev = async (pageInfo, count) => { for (let i = window.currentPage; i > window.currentPage - count; i--) { if ((pageInfo.knownPages[`P${i}`] == null) && (i > (pageInfo.endLow ?? Number.MIN_SAFE_INTEGER))) { if (pageInfo.knownPages[`P${i + 1}`] != null) { let doc = await fetchDocument(`${location.origin}${location.pathname}${pageInfo.knownPages[`P${i + 1}`]}`); let jumpElement = doc.querySelector("#uprev"); if (jumpElement != null) { if (jumpElement.localName === "span") pageInfo.endLow = i; if (jumpElement.localName === "a") { pageInfo.knownPages[`P${i}`] = `${(new URL(jumpElement.href)).search}`; if (pageInfo.knownPages.min > i) pageInfo.knownPages.min = i; if (pageInfo.knownPages.max < i) pageInfo.knownPages.max = i; } } } } } return pageInfo; } const prefetchNext = async (pageInfo, count) => { for (let i = window.currentPage; i < window.currentPage + count; i++) { if ((pageInfo.knownPages[`P${i}`] == null) && (i < (pageInfo.endHigh ?? Number.MAX_SAFE_INTEGER))) { if (pageInfo.knownPages[`P${i - 1}`] != null) { let doc = await fetchDocument(`${location.origin}${location.pathname}${pageInfo.knownPages[`P${i - 1}`]}`); let jumpElement = doc.querySelector("#unext"); if (jumpElement != null) { if (jumpElement.localName === "span") pageInfo.endHigh = i; if (jumpElement.localName === "a") { pageInfo.knownPages[`P${i}`] = `${(new URL(jumpElement.href)).search}`; if (pageInfo.knownPages.min > i) pageInfo.knownPages.min = i; if (pageInfo.knownPages.max < i) pageInfo.knownPages.max = i; } } } } } return pageInfo; } const prefetchPageInfo = async (pageInfo) => { // prefetch prev pagees let maxPrefetch = parseInt(localStorage.getItem("EHPS-EnablePageinatorPrefetchSize") ?? defaultMaxPrefetch); if (isNaN(maxPrefetch)) maxPrefetch = defaultMaxPrefetch; pageInfo = await prefetchPrev(pageInfo, maxPrefetch + 1); let nextCount = maxPrefetch + 1; let knownCount = pageInfo.knownPages.max - pageInfo.knownPages.min + 1; if (knownCount <= maxPrefetch + 1) nextCount += maxPrefetch + pageInfo.knownPages.min; // prefetch next pages to fill pages pageInfo = await prefetchNext(pageInfo, nextCount); // we do not have enough next pages, try with more prev again knownCount = pageInfo.knownPages.max - pageInfo.knownPages.min + 1; if (knownCount < (maxPrefetch * 2) + 1) { pageInfo = await prefetchPrev(pageInfo, maxPrefetch + (((maxPrefetch * 2) + 1) - knownCount) + 1); } return pageInfo; } const addBaseUIElements = () => { if ((new URL(location.href)).pathname == "/popular") return false; const addInitialElement = () => { const nav = document.querySelectorAll('.searchnav'); if (nav.length < 2) return false; if (!document.querySelector(".search-scrobbler")) { nav[0].insertAdjacentHTML("beforebegin", `
`); nav[1].insertAdjacentHTML("afterend", ``); } if (!document.querySelector(".saved-search")) { nav[0].insertAdjacentHTML("beforebegin", `