EH – Page Scrobbler

Visualize GID and add the ability to easily jump or scrobble (since the old page navigation is going to be no more)

As of 03/11/2022. See the latest version.

// ==UserScript==
// @name         EH – Page Scrobbler
// @namespace    fabulous.cupcake.jp.net
// @version      2022.11.03.3
// @description  Visualize GID and add the ability to easily jump or scrobble (since the old page navigation is going to be no more)
// @author       FabulousCupcake, OsenTen
// @license      MIT
// @run-at       document-end
// @match        http*://e-hentai.org/*
// @match        http*://exhentai.org/*
// @grant        GM_addStyle
// ==/UserScript==

const stylesheet = `
.search-scrobbler {
  width: 800px;
  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: 800px;
  height: 25px;
  border: 1px solid red;
  box-sizing: border-box;
  position: relative;
}
.search-scrobbler .bar .bar-cursor {
  height: 100%;
  background: #0f0;
}
.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-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;
}
`;

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 tryUpdateKnownMaxGID = GID => {
    const url = new URL(location.href);
    if (url.pathname !== "/") return;
    if (url.search !== "") return;

    let maxGID = 0;
    if (!!document.querySelector(".itg tr .glname a")) { // Minimal and Compact
        maxGID = document.querySelector(".itg tr:nth-child(2) .glname a").href.match(/\/(\d+)\//)?.[1];
    } else if (!!document.querySelector(".itg tr a")) { // Extended
        maxGID = document.querySelector(".itg tr:first-child a").href.match(/\/(\d+)\//)?.[1];
    } else { // Thumbnail
        maxGID = document.querySelector(".itg .gl1t:first-child a").href.match(/\/(\d+)\//)?.[1];
    }
    localStorage.setItem("EHPS-maxGID", maxGID);
}

const addPageScrobbler = () => {
    const insertInitialElement = () => {
        const hook = document.querySelector(".searchnav");

        const maxGID = localStorage.getItem("EHPS-maxGID");
        let firstGID = maxGID, lastGID = 1;
        if (!!document.querySelector(".itg tr .glname a")) { // Minimal and Compact
            firstGID = document.querySelector(".itg tr:nth-child(2) .glname a").href.match(/\/(\d+)\//)?.[1];
            lastGID = document.querySelector(".itg tr:last-child .glname a").href.match(/\/(\d+)\//)?.[1];
        } else if (!!document.querySelector(".itg tr a")) { // Extended
            firstGID = document.querySelector(".itg tr:first-child a").href.match(/\/(\d+)\//)?.[1];
            lastGID = document.querySelector(".itg tr:last-child a").href.match(/\/(\d+)\//)?.[1];
        } else { // Thumbnail
            firstGID = document.querySelector(".itg .gl1t:first-child a").href.match(/\/(\d+)\//)?.[1];
            lastGID = document.querySelector(".itg .gl1t:last-child a").href.match(/\/(\d+)\//)?.[1];
        }
        const cursorLeftMargin = (1.0 - firstGID / maxGID) * 100;
        let cursorWidth = ((firstGID - lastGID) / maxGID) * 100;
        if (cursorWidth < 0.2) cursorWidth = 0.2;

        const el = `
<div class="search-scrobbler">
  <div class="bar-wrapper bar-full">
    <div class="bar">
      <div class="bar-cursor" style="width: ${cursorWidth}%; margin-left: ${cursorLeftMargin}% ">
        <div class="bar-hovertext">${firstGID}</div>
      </div>
    </div>
    <div class="bar-labels">
      <div class="bar-max">${maxGID}</div>
      <div class="bar-min">1</div>
    </div>
  </div>
</div>`;
        hook.insertAdjacentHTML("beforebegin", el);
    }

    const addEventListeners = () => {
        const addHoverElement = offset => {
            if (offset < 2) return;
            document.querySelector(".bar-hover")?.remove();

            const maxGID = localStorage.getItem("EHPS-maxGID");
            const width = 800;
            const hoverGID = ((1.0 - offset / 800) * maxGID).toFixed(0);

            const url = new URL(location.href);
            url.searchParams.set("next", hoverGID);

            const hook = document.querySelector(".bar-full .bar");
            const el = `
<a class="bar-hover" href="${url}" style="left: ${offset-2}px; width: 2px">
  <div class="bar-hovertext">${hoverGID}</div>
</a>`;

          hook.insertAdjacentHTML("afterbegin", el);
      }

      const handler = e => {
          addHoverElement(e.layerX);
      }

      const el = document.querySelector(".bar-full .bar");
      el.addEventListener("mousemove", handler);
  }

  insertInitialElement();
    addEventListeners();
}

const main = () => {
    if (!hasGalleryListTable()) return;
    tryUpdateKnownMaxGID();
    injectStylesheet();
    addPageScrobbler();
}

main();
//@uploaded by JoGaTo