Sleazy Fork is available in English.

JavDB 添加跳转在线观看

在影片详情页添加跳转到在线观看网站的按钮,并检查对应是否存在资源,如果对应网站上存在该资源则为绿色,否则显示红色,顺便检测有无中文字幕。

اعتبارا من 18-09-2022. شاهد أحدث إصدار.

// ==UserScript==
// @name         JavDB 添加跳转在线观看
// @namespace    https://greasyfork.org/users/58790
// @version      0.30.3
// @author       mission522
// @description  在影片详情页添加跳转到在线观看网站的按钮,并检查对应是否存在资源,如果对应网站上存在该资源则为绿色,否则显示红色,顺便检测有无中文字幕。
// @license      MIT
// @icon         https://javdb.com/favicon-32x32.png
// @include      /^https:\/\/(\w*\.)?javdb(\d)*\.com.*$/
// @match        *://*.javdb.com/*
// @connect      jable.tv
// @connect      missav.com
// @connect      javhhh.com
// @connect      netflav.com
// @connect      avgle.com
// @connect      bestjavporn.com
// @connect      jav.guru
// @connect      javmost.cx
// @connect      hpjav.tv
// @connect      av01.tv
// @connect      javbus.com
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// ==/UserScript==

"use strict";
const jdbStyle = `
  .button-g {
    font-size: .75rem;
    margin:0.35rem 0.75rem 0.35rem 0;
  }
  .has-subtitle::before {
    position: absolute;
    content: "\u5B57\u5E55";
    padding: 1px;
    top: -8px;
    left: -3px;
    line-height: 1;
    color: white;
    background: green;
  }
  .has-leakage::after {
    position: absolute;
    content: "\u65E0\u7801";
    padding: 1px;
    top: -8px;
    left: 30px;
    line-height: 1;
    color: white;
    background: green;
  }
`;
const temp = () => {};
const siteList = [
  {
    id: 0,
    name: "Jable",
    hostname: "jable.tv",
    url: "https://jable.tv/videos/{{code}}/",
    fetcher: "get",
    domQuery: { subQuery: ".header-right>h6" },
    method: temp,
  },
  {
    id: 1,
    name: "MISSAV",
    hostname: "missav.com",
    url: "https://missav.com/{{code}}/",
    fetcher: "get",
    domQuery: {
      subQuery: '.space-y-2 a.text-nord13[href="https://missav.com/chinese-subtitle"]',
      leakQuery: ".order-first div.rounded-md a[href]:last-child",
    },
    method: temp,
  },
  {
    id: 2,
    name: "NETFLAV",
    hostname: "netflav.com",
    url: "https://netflav.com/search?type=title&keyword={{code}}",
    fetcher: "parser",
    domQuery: { linkQuery: ".grid_cell>a", titleQuery: ".grid_cell>a>.grid_title" },
    method: temp,
  },
  {
    id: 3,
    name: "Avgle",
    hostname: "avgle.com",
    url: "https://avgle.com/search/videos?search_query={{code}}&search_type=videos",
    fetcher: "parser",
    domQuery: {
      linkQuery: ".container>.row .row .well>a[href]",
      titleQuery: ".container>.row .row .well .video-title",
    },
    method: temp,
  },
  {
    id: 4,
    name: "JAVHHH",
    hostname: "javhhh.com",
    url: "https://javhhh.com/v/?wd={{code}}",
    fetcher: "parser",
    domQuery: {
      linkQuery: ".typelist>.i-container>a[href]",
      titleQuery: ".typelist>.i-container>a[href]",
    },
    method: temp,
  },
  {
    id: 6,
    name: "Jav.Guru",
    hostname: "jav.guru",
    url: "https://jav.guru/?s={{code}}",
    fetcher: "parser",
    domQuery: { linkQuery: ".imgg>a[href]", titleQuery: ".inside-article>.grid1 a[title]" },
    method: temp,
  },
  {
    id: 7,
    name: "JAVMOST",
    hostname: "javmost.cx",
    url: "https://javmost.cx/search/{{code}}/",
    fetcher: "parser",
    domQuery: {
      linkQuery: "#content .card a#MyImage",
      titleQuery: "#content .card-block .card-title",
    },
    method: temp,
  },
  {
    id: 8,
    name: "AV01",
    hostname: "av01.tv",
    url: "https://www.av01.tv/search/videos?search_query={{code}}",
    fetcher: "parser",
    domQuery: { linkQuery: "div[id].well-sm>a", titleQuery: ".video-views>.pull-left" },
    method: temp,
  },
];

const envCode = document.querySelector(`[data-clipboard-text]`)?.dataset.clipboardText;

function videoPageParser(responseText, { subQuery, leakQuery }) {
  const doc = new DOMParser().parseFromString(responseText, "text/html");
  const subNode = subQuery ? doc.querySelector(subQuery) : "";
  const subNodeText = subNode ? subNode.innerHTML : "";
  const leakNode = leakQuery ? doc.querySelector(leakQuery) : null;
  return {
    hasSubtitle: subNodeText.includes("\u5B57\u5E55") || subNodeText.includes("subtitle"),
    hasLeakage: !!leakNode,
  };
}
function serachPageParser(responseText, { linkQuery, titleQuery }, siteHostName) {
  const doc = new DOMParser().parseFromString(responseText, "text/html");
  const linkNode = linkQuery ? doc.querySelectorAll(linkQuery)[0] : null;
  const titleNode = titleQuery ? doc.querySelectorAll(titleQuery)[0] : null;
  const titleNodeText = titleNode ? (titleNode == null ? void 0 : titleNode.innerHTML) : "";

  function query() {
    if (linkNode && titleNode && titleNode.outerHTML.includes(envCode)) {
      return {
        isSuccess: true,
        targetLink: linkNode.href.replace(linkNode.hostname, siteHostName),
        hasLeakage: titleNodeText.includes("\u65E0\u7801") || titleNodeText.includes("Uncensored"),
        hasSubtitle: titleNodeText.includes("\u5B57\u5E55") || titleNodeText.includes("subtitle"),
      };
    } else {
      return { targetLink: "", isSuccess: false };
    }
  }
  return query();
}
async function xhr(siteItem, siteUrl) {
  const xhrPromise = new Promise((resolve) => {
    GM_xmlhttpRequest({
      method: "GET",
      url: siteUrl,
      onload: function (response) {
        if (siteItem.fetcher === "get") {
          if (response.status === 404) {
            resolve({
              isSuccess: false,
              targetLink: siteUrl,
              name: siteItem.name,
              msg: "\u5E94\u8BE5\u662F\u6CA1\u6709\u8D44\u6E90",
            });
          } else {
            const successResult = videoPageParser(response.responseText, siteItem.domQuery);
            resolve({
              isSuccess: true,
              targetLink: siteUrl,
              name: siteItem.name,
              ...successResult,
              msg: "[get]\uFF0C\u5B58\u5728\u8D44\u6E90",
            });
          }
        } else if (siteItem.fetcher === "parser") {
          const { targetLink, isSuccess, hasLeakage, hasSubtitle } = serachPageParser(
            response.responseText,
            siteItem.domQuery,
            siteItem.hostname,
          );
          resolve({
            name: siteItem.name,
            isSuccess,
            targetLink: isSuccess ? targetLink : siteUrl,
            hasSubtitle,
            hasLeakage,
            msg: "[parser]\u5B58\u5728\u8D44\u6E90",
          });
        }
      },
      onerror: function (error) {
        resolve({
          isSuccess: false,
          targetLink: siteUrl,
          name: siteItem.name,
          msg: error.error,
        });
      },
    });
  });
  return xhrPromise;
}

function addStyle() {
  const style = jdbStyle;
  GM_addStyle(style);
}
function createPanelNode() {
  const parentNodeQueryString = `.panel.movie-panel-info`;
  const parentNode = document.querySelector(parentNodeQueryString);
  const panelNode = document.createElement("div");
  parentNode && parentNode.appendChild(panelNode);
  panelNode.classList.add("panel-block", "column");
  return panelNode;
}
function createButtonNode(panelNode, siteItemName, siteUrl) {
  const buttonNode = document.createElement("a");
  buttonNode.setAttribute("target", "_blank");
  panelNode.appendChild(buttonNode);
  const buttonClassList = ["button", "is-info", "is-outlined"];
  buttonClassList.forEach((item) => {
    buttonNode.classList.add(item, "button-g");
  });
  buttonNode.innerHTML = siteItemName;
  buttonNode.href = siteUrl;
  return {
    buttonNode,
    setButtonStatus: (targetLink, color, hasLeakage, hasSubtitle) => {
      buttonNode.href = targetLink;
      buttonNode.style.color = color;
      buttonNode.style.borderColor = color;
      buttonNode.style.backgroundColor = buttonNode.style.backgroundColor;
      hasLeakage && buttonNode.classList.add("has-leakage");
      hasSubtitle && buttonNode.classList.add("has-subtitle");
    },
  };
}
(function main() {
  addStyle();
  const panelNode = createPanelNode();
  siteList.forEach(async (item) => {
    const siteUrl = item.url.replace("{{code}}", envCode);
    const { setButtonStatus } = createButtonNode(panelNode, item.name, siteUrl);
    const { isSuccess, hasLeakage, hasSubtitle, targetLink } = await xhr(item, siteUrl);
    setButtonStatus(targetLink, isSuccess ? "green" : "red", hasLeakage, hasSubtitle);
  });
})();