EH Enhance(E绅士增强)

Scroll to top/comment/bottom, copy gallery title, search selected text, search multiple tags.(滚动到顶部/讨论区/底部,复制画廊标题,搜索选中文本,多标签搜索)

// ==UserScript==
// @name         EH Enhance(E绅士增强)
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Scroll to top/comment/bottom, copy gallery title, search selected text, search multiple tags.(滚动到顶部/讨论区/底部,复制画廊标题,搜索选中文本,多标签搜索)
// @author       ssnangua
// @match        https://e-hentai.org/*
// @match        https://exhentai.org/*
// @icon         https://e-hentai.org/favicon.ico
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function () {
  "use strict";

  const chinese = {
    search: "搜索",
    language: "中文",
    uncensored: "无修正",
    wnacg: "绅士漫画",
    hitomi: "hitomi",
    yhg: "移花宫",
    es: "ES",
    copy: "复制",
    searchSelectedTags: "搜索选中的标签",
    toTop: "滚动到顶部",
    toComment: "滚动到评论区",
    toBottom: "滚动到底部",
    toggleLanguage: "Change To English",
  };
  const english = {
    search: "Search",
    language: "English",
    uncensored: "Uncensored",
    wnacg: "wnacg",
    hitomi: "hitomi",
    yhg: "yhg",
    es: "ES",
    copy: "Copy",
    searchSelectedTags: "Search Selected Tags",
    toTop: "Scroll To Top",
    toComment: "Scroll To Comment",
    toBottom: "Scroll To Bottom",
    toggleLanguage: "切换为中文",
  };
  const lang = localStorage.language || navigator.language;
  const isChinese = lang === "zh-CN";
  const t = isChinese ? chinese : english;

  const EH = location.origin + "/?f_search=%s";
  const WNACG = "https://www.wnacg.com/search/?q=%s";
  const HITOMI = "https://hitomi.la/search.html?%s";
  const YHG = "https://yhg007.com/search-%s-0-0-1.html";
  const ES = "es:%s";

  const SEARCH_BAR = [
    { label: t.search, append: "", url: EH },
    { label: t.language, append: isChinese ? "language:chinese$" : "language:english$", url: EH },
    { label: t.uncensored, append: "other:uncensored$", url: EH },
    { label: t.wnacg, append: "", url: WNACG },
    { label: t.hitomi, append: "", url: HITOMI },
    { label: t.yhg, append: "", url: YHG },
    { label: t.es, append: "", url: ES },
  ];

  /**********************************************/
  /*                   滚动到                   */
  /**********************************************/

  const $scrollToBar = document.createElement("div");
  $scrollToBar.className = "eh-scroll-to-bar";
  document.body.appendChild($scrollToBar);

  const $comment = document.querySelector("#cdiv");
  $scrollToBar.appendChild(createGotoButton("🔝", t.toTop, 0));
  if ($comment) $scrollToBar.appendChild(createGotoButton("💬", t.toComment, $comment));
  $scrollToBar.appendChild(createGotoButton("⬇️", t.toBottom, 1000000));

  // const $langButton = createButton(isChinese ? "🇨🇳" : "🇬🇧", () => {
  //   localStorage.language = isChinese ? "en-US" : "zh-CN";
  //   location.reload();
  // });
  // $langButton.dataset.tip = t.toggleLanguage;
  // $langButton.style.marginTop = "20px";
  // $scrollToBar.appendChild($langButton);

  function createGotoButton(label, tip, scrollTopOrElement) {
    const $button = createButton(label, () => {
      if (typeof scrollTopOrElement === "number") {
        document.body.parentElement.scrollTo({
          top: scrollTopOrElement,
          behavior: "smooth",
        });
      } else {
        scrollTopOrElement.scrollIntoView({ behavior: "smooth" });
      }
    });
    $button.dataset.tip = tip;
    return $button;
  }

  /**********************************************/
  /*                    画廊                    */
  /**********************************************/

  // 复制标题
  document.querySelectorAll("h1#gn, h1#gj").forEach(($h1) => {
    const text = $h1.textContent.trim();
    if (text) {
      const $copyButton = createButton(t.copy, async (e) => {
        await navigator.clipboard.writeText(text);
        e.target.textContent = t.copy + "✔️";
        setTimeout(() => (e.target.textContent = t.copy), 1000);
      });
      $copyButton.className = "eh-copy";
      $h1.appendChild($copyButton);
    }
  });

  // 多标签搜索
  const $taglist = document.querySelector("#taglist");
  if ($taglist) {
    const $tags = [...$taglist.querySelectorAll("a")];
    $tags.forEach(($tag) => {
      $tag.addEventListener("click", () => {
        $tag.selected = !$tag.selected;
        $tag.parentNode.classList.toggle("selected", $tag.selected);
        $act2.style.display = $tags.some(($tag) => $tag.selected) ? "" : "none";
      });
    });

    const $act2 = document.createElement("div");
    $act2.id = "tagmenu_act2";
    $act2.style.display = "none";
    $act2.innerHTML = `
      <img src="https://ehgt.org/g/mr.gif" class="mr" alt="&gt;">
      <a id="search_tags" href="#">${t.searchSelectedTags}</a>
    `;
    $taglist.appendChild($act2);

    $act2.querySelector("#search_tags").addEventListener("click", () => {
      const tags = $tags
        .filter(($tag) => $tag.selected)
        .map(($tag) => {
          const [tc, td] = $tag.onclick
            .toString()
            .match(/'(.*?)'/)[1]
            .split(":");
          return `${tc}:"${td}$"`;
        });
      window.open(EH.replace("%s", tags.join("+")));
    });
  }

  // 划词搜索
  let selectedText;

  const $searchBar = document.createElement("div");
  $searchBar.className = `eh-search-bar`;
  $searchBar.addEventListener("mouseup", (e) => e.stopPropagation());
  document.body.appendChild($searchBar);

  SEARCH_BAR.forEach(({ label, append, url }) => {
    const $button = createButton(label, () => {
      const text = (selectedText + " " + append).trim();
      window.open(url.replace("%s", text));
    });
    $searchBar.appendChild($button);
  });

  window.addEventListener("mouseup", (e) => {
    selectedText = document.getSelection().toString().trim();
    if (selectedText) {
      $searchBar.style.display = "flex";
      const left = Math.min(e.x, window.innerWidth - $searchBar.offsetWidth - 20);
      const top = Math.min(e.y + 20, window.innerHeight - $searchBar.offsetHeight);
      $searchBar.style.left = left + "px";
      $searchBar.style.top = top + "px";
    } else {
      $searchBar.style.display = "none";
    }
  });

  function createButton(label, onClick) {
    const $button = document.createElement("button");
    $button.textContent = label;
    $button.addEventListener("click", onClick);
    return $button;
  }

  function prependChild(parent, child) {
    parent.insertBefore(child, parent.firstChild);
  }

  GM_addStyle(`
    .ehs-eh {
      --panel-bg: #edebdf;
      --panel-border: 1px solid #5c0d12;
      --tag-selected-bg: #d5c5c6;
      --tag-selected-color: royalblue;
    }
    .ehs-ex {
      --panel-bg: #4f535b;
      --panel-border: 1px solid #000000;
      --tag-selected-bg: #34353b;
      --tag-selected-color: skyblue;
    }

    .eh-scroll-to-bar {
      display: flex;
      flex-flow: column;
      gap: 10px;
      z-index: 10;
      position: fixed;
      right: 30px;
      top: 20px;
      & button {
        width: 40px;
        height: 40px;
        font-size: 18px;
        cursor: pointer;
        position: relative;

        /*&:hover::after {
          content: attr(data-tip);
          display: inline-block;
          white-space: nowrap;
          font-size: 14px;
          background: var(--panel-bg);
          border: var(--panel-border);
          padding: 5px 10px;
          position: absolute;
          right: 42px;
          top: 50%;
          transform: translateY(-50%);
        }*/
      }
    }

    .eh-copy {
      margin-left: 5px;
      cursor: pointer;
    }

    .eh-search-bar {
      display: none;
      position: fixed;
      z-index: 10000;
      padding: 5px;
      background: var(--panel-bg);
      border: var(--panel-border);

      & button {
        margin: 2px;
        white-space: nowrap;
        cursor: pointer;
      }
    }

    #taglist td>div a[style="color: blue;"] {
      color: var(--tag-selected-color) !important;
    }
    #taglist td>div.selected {
      background: var(--tag-selected-bg);
    }

    #tagmenu_act2 {
      margin: 0;
      float: left;
      width: 554px;
      height: 26px;
      font-size: 9pt;
    }
    #tagmenu_act2 img {
      padding-bottom: 1px;
    }
    #tagmenu_act2 a {
      text-decoration: none;
      font-weight: bold;
    }
  `);
})();