Soul Plus增强套件

Soul-Plus Kit

// ==UserScript==
// @name         Soul Plus增强套件
// @license      MIT
// @namespace    http://otakustay.me
// @version      1.0.2
// @description  Soul-Plus Kit
// @author       otakustay
// @match        https://www.south-plus.net/read.php?*
// @match        https://gofile.io/d/*
// @match        https://workupload.com/archive/*
// @match        https://workupload.com/file/*
// @grant        GM.setClipboard
// @grant        GM.openInTab
// ==/UserScript==

(function() {
  "use strict";
  function waitForElementVisible(selector, options) {
    const detect = (resolve) => {
      const result = document.querySelector(selector);
      if (result) {
        resolve(result);
      } else {
        setTimeout(detect, options.interval, resolve);
      }
    };
    const abort = (resolve, reject) => {
      setTimeout(() => reject(new Error(`Query ${selector} timed out`)), options.timeout);
    };
    return Promise.race([new Promise(detect), new Promise(abort)]);
  }
  function dispatchClick(element) {
    if ("click" in element) {
      element.click();
    } else {
      const event = new MouseEvent("click");
      element.dispatchEvent(event);
    }
  }
  function selectElementContext(selectors2, args) {
    const context = {};
    for (const [key, selector] of Object.entries(selectors2)) {
      if (typeof selector === "string") {
        const element = document.querySelector(selector.replace(/!$/, ""));
        if (selector.endsWith("!") && !element) {
          throw new Error(`Unable to find required element "${selector}"`);
        }
        context[key] = element;
      } else {
        const element = selector(args);
        context[key] = element;
      }
    }
    return context;
  }
  function injectStyle(strings, ...values) {
    const parts = [];
    for (let i = 0; i < strings.length; i++) {
      parts.push(strings[i]);
      if (i < values.length) {
        parts.push(values[i]);
      }
    }
    const element = document.createElement("style");
    element.textContent = parts.join("").trim();
    element.setAttribute("data-source", "south-plus-kit");
    document.head.appendChild(element);
  }
  function registerGlobalShortcut(key, listener) {
    document.addEventListener(
      "keyup",
      (e) => {
        if (e.key === key) {
          listener();
        }
      }
    );
  }
  class Banner {
    dom;
    constructor() {
      this.dom = document.createElement("div");
      this.dom.className = "spk-banner";
    }
    add(text) {
      const label = document.createElement("span");
      label.className = "spk-banner-text";
      label.innerText = text;
      this.dom.appendChild(label);
    }
    render() {
      document.body.appendChild(this.dom);
    }
  }
  injectStyle`
            .spk-banner {
                position: fixed;
                z-index: 1;
                bottom: 0;
                left: 0;
                right: 0;
                display: flex;
                align-items: center;
                justify-content: center;
                gap: 1em;
                height: 2em;
                padding: 0 4em;
                background-color: hsl(358, 96%, 60%);
                color: #fff;
            }
        `;
  const selectors$1 = {
    titleLabel: "#subject_tpc!",
    purchaseButton: 'input[value="愿意购买,我买,我付钱"]',
    purchasedContent: "blockquote.jumbotron",
    goLink: 'a[href^="https://gofile.io/"]',
    workUploadLink: 'a[href^="https://workupload.com/"]',
    megaLink: 'a[href^="https://mega.nz/"]'
  };
  class SouthPlusLogic {
    constructor(context) {
      this.context = context;
    }
    static initialize() {
      const context = selectElementContext(selectors$1, {});
      return new SouthPlusLogic(context);
    }
    canDownload() {
      return !!(this.context.goLink || this.context.workUploadLink || this.context.megaLink);
    }
    canPurchase() {
      return !this.context.purchasedContent && !!this.context.purchaseButton;
    }
    canCopy() {
      return !!this.context.titleLabel;
    }
    isSatisfied() {
      return this.canDownload() || this.canPurchase();
    }
    purchase() {
      if (this.context.purchaseButton) {
        dispatchClick(this.context.purchaseButton);
      }
    }
    openAvailableLink() {
      if (this.context.goLink) {
        this.openDownloadLink(this.context.goLink);
      } else if (this.context.workUploadLink) {
        this.openDownloadLink(this.context.workUploadLink);
      } else if (this.context.megaLink) {
        const url = new URL(this.context.megaLink.getAttribute("href"));
        url.searchParams.set("__spk_download__", "immediate");
        void GM.openInTab(url.toString(), { active: true });
      }
    }
    copyTitle() {
      if (this.context.titleLabel) {
        void GM.setClipboard(this.context.titleLabel.innerText.replaceAll(":", " "));
      }
    }
    openDownloadLink(element) {
      const url = new URL(element.getAttribute("href"));
      if (this.context.titleLabel) {
        url.searchParams.set("__spk_download__", this.context.titleLabel.innerText);
      }
      void GM.openInTab(url.toString(), { active: true });
    }
  }
  function setupSouthPlus() {
    const logic = SouthPlusLogic.initialize();
    if (!logic.isSatisfied()) {
      return;
    }
    const banner = new Banner();
    if (logic.canDownload()) {
      banner.add("下载资源 [D]");
      registerGlobalShortcut("d", () => logic.openAvailableLink());
    }
    if (logic.canPurchase()) {
      banner.add("购买资源 [B]");
      registerGlobalShortcut("b", () => logic.purchase());
    }
    if (logic.canCopy()) {
      banner.add("复制标题 [C]");
      registerGlobalShortcut("c", () => logic.copyTitle());
    }
    banner.render();
  }
  const SPLIT_REGEX = /[[\]()【】+_\-.・!\s]/;
  function mostSimilar(from, to) {
    const toTokens = to.split(SPLIT_REGEX).filter((v) => v.length > 1);
    const state = {
      max: 0,
      value: ""
    };
    for (const value of from) {
      if (value === to) {
        return value;
      }
      const fromTokens = value.split(SPLIT_REGEX).filter((v) => v.length > 1);
      const intersected = toTokens.filter((v) => fromTokens.includes(v));
      const similarity = intersected.length / toTokens.length;
      if (similarity > state.max) {
        state.max = similarity;
        state.value = value;
      }
    }
    return state.value;
  }
  const selectors = {
    row: ({ name }) => {
      const elements = [...document.querySelectorAll(".contentName")];
      const targetText = mostSimilar(elements.map((v) => v.innerText), name);
      const element = elements.find((v) => v.innerText === targetText);
      return element ? element.closest(".row") : null;
    }
  };
  class GoFileLogic {
    constructor(context) {
      this.context = context;
    }
    static initialize(name) {
      const context = selectElementContext(selectors, { name });
      return new GoFileLogic(context);
    }
    hasContent() {
      return !!this.context.row;
    }
    highlightRow() {
      var _a;
      (_a = this.context.row) == null ? void 0 : _a.classList.add("spk-row-active");
    }
    download() {
      var _a;
      const button = (_a = this.context.row) == null ? void 0 : _a.querySelector("button");
      if (button) {
        dispatchClick(button);
      }
    }
  }
  async function setupGoFile() {
    const searchParams = new URLSearchParams(location.search);
    const name = searchParams.get("__spk_download__");
    injectStyle`
        .spk-row-active {
            background-color: hsl(358, 96%, 25%);
        }
    `;
    if (!name) {
      return;
    }
    await waitForElementVisible(".contentName", { timeout: 10 * 1e3, interval: 100 });
    const logic = GoFileLogic.initialize(name);
    if (!logic.hasContent()) {
      return;
    }
    logic.highlightRow();
    const banner = new Banner();
    banner.add("下载 [D]");
    registerGlobalShortcut("d", () => logic.download());
    banner.render();
  }
  const archiveSelectors = {
    container: ({ name }) => {
      const elements = document.querySelectorAll(".filename");
      const element = [...elements].find((v) => v.innerText.includes(name));
      return element ? element.closest(".frame") : null;
    }
  };
  class WorkUploadArchiveLogic {
    constructor(context) {
      this.context = context;
    }
    static initialize(name) {
      const context = selectElementContext(archiveSelectors, { name });
      return new WorkUploadArchiveLogic(context);
    }
    hasContent() {
      return !!this.context.container;
    }
    highlightRow() {
      var _a;
      (_a = this.context.container) == null ? void 0 : _a.classList.add("spk-row-active");
    }
    download() {
      var _a;
      const link = (_a = this.context.container) == null ? void 0 : _a.querySelector(".filedownload > a");
      if (link) {
        dispatchClick(link);
      }
    }
  }
  const fileSelectors = {
    downloadButton: "a.btn.btn-prio"
  };
  class WorkUploadFileLogic {
    constructor(context) {
      this.context = context;
    }
    static initialize() {
      const context = selectElementContext(fileSelectors, {});
      return new WorkUploadFileLogic(context);
    }
    hasContent() {
      return !!this.context.downloadButton;
    }
    download() {
      if (this.context.downloadButton) {
        dispatchClick(this.context.downloadButton);
      }
    }
  }
  async function setupArchivePage() {
    const searchParams = new URLSearchParams(location.search);
    const name = searchParams.get("__spk_download__");
    injectStyle`
        .spk-row-active {
            background-color: #f6c050;
        }
    `;
    if (!name) {
      return;
    }
    await waitForElementVisible(".filename", { timeout: 10 * 1e3, interval: 100 });
    const logic = WorkUploadArchiveLogic.initialize(name);
    if (!logic.hasContent()) {
      return;
    }
    logic.highlightRow();
    const banner = new Banner();
    banner.add("下载 [D]");
    registerGlobalShortcut("d", () => logic.download());
    banner.render();
  }
  async function setupFilePage() {
    await waitForElementVisible("a.btn.btn-prio", { timeout: 10 * 1e3, interval: 100 });
    const logic = WorkUploadFileLogic.initialize();
    if (!logic.hasContent()) {
      return;
    }
    const banner = new Banner();
    banner.add("下载 [D]");
    registerGlobalShortcut("d", () => logic.download());
    banner.render();
  }
  async function setupWorkUpload() {
    const path = location.pathname;
    if (path.startsWith("/archive/")) {
      await setupArchivePage();
    } else if (path.startsWith("/file/")) {
      await setupFilePage();
    }
  }
  switch (location.hostname) {
    case "www.south-plus.net":
      setupSouthPlus();
      break;
    case "gofile.io":
      void setupGoFile();
      break;
    case "workupload.com":
      void setupWorkUpload();
      break;
  }
})();