Greasy Fork 支持简体中文。

七象影视解析

优酷、爱奇艺、腾讯、B站等视频网站视频解析,悬浮面板

// ==UserScript==
// @name         七象影视解析
// @namespace    qx-parse
// @version      0.0.1
// @description  优酷、爱奇艺、腾讯、B站等视频网站视频解析,悬浮面板
// @author       通天教主
// @icon         data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDMyIDMyIiB3aWR0aD0iMzIiIGhlaWdodD0iMzIiPgoJPHRpdGxlPmJyb3dzZXItY2hyb21lPC90aXRsZT4KCTxzdHlsZT4KCQkuczAgeyBmaWxsOiAjYWZjZGZmIH0gCgkJLnMxIHsgZmlsbDogIzM4ODNmZiB9IAoJPC9zdHlsZT4KCTxwYXRoIGlkPSLlm77lsYIgNCIgZmlsbC1ydWxlPSJldmVub2RkIiBjbGFzcz0iczAiIGQ9Im02IDBoMjBjMy4zIDAgNiAyLjcgNiA2djIwYzAgMy4zLTIuNyA2LTYgNmgtMjBjLTMuMyAwLTYtMi43LTYtNnYtMjBjMC0zLjMgMi43LTYgNi02eiIvPgoJPHBhdGggaWQ9IuW9oueKtiAxIiBjbGFzcz0iczEiIGQ9Im0yMiAxNy41YzAuNS0wLjcgMC41LTEuOSAwLTIuN2wtNy40LTYuMmMtMC42LTAuNS0xLjUtMC42LTIuMy0wLjMtMC44IDAuMi0xLjMgMC44LTEuMyAxLjR2MTIuOWMwIDAuNyAwLjUgMS4zIDEuMyAxLjUgMC44IDAuMyAxLjcgMC4xIDIuMy0wLjMgMCAwIDcuNC02LjMgNy40LTYuM3oiLz4KPC9zdmc+
// @match        *://*.youku.com/*
// @match        *://*.iqiyi.com/v_*
// @match        *://*.iqiyi.com/w_*
// @match        *://*.iqiyi.com/a_*
// @match        *://*.iqiyi.com/resource/pcw/play/*
// @match        *://*.iq.com/*
// @match        *://v.qq.com/x/cover/*
// @match        *://v.qq.com/x/page/*
// @match        *://v.qq.com/tv/*
// @match        *://m.v.qq.com/x/cover/*
// @match        *://m.v.qq.com/x/page/*
// @match        *://m.v.qq.com/*
// @match        *://*.bilibili.com/**
// @match        *://*.mgtv.com/b/*
// @match        *://*.le.com/ptv/vplay/*
// @match        *://*.tudou.com/listplay/*
// @match        *://*.tudou.com/albumplay/*
// @match        *://*.tudou.com/programs/view/*
// @match        *://*.pptv.com/show/*
// @match        *://*.1905.com/video/*
// @match        *://*.1905.com/play/*
// @match        *://*.1905.com/*/play/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @license      GPLv3
// ==/UserScript==

(function () {
  "use strict";

  const isMobile = /Android|webOS|iPhone|iPod|BlackBerry/i.test(
    navigator.userAgent
  );

  const parseApiListStr = GM_getValue("parseApiList", "[]");

  let parseApiList = JSON.parse(parseApiListStr);

  function parseApi2Text(parseApiList) {
    let texts = [];
    parseApiList.forEach((api) => {
      let line = api.name + "," + api.url;
      texts.push(line);
    });
    return texts.join("\n");
  }

  function text2ParseApi(text) {
    let lines = text.split("\n");
    let apis = [];
    lines.forEach((line) => {
      let res = line.split(",");
      if (res.length !== 2) {
        if (res.length === 1) {
          res.unshift(getSLDFromUrl(res[0]));
        } else return;
      }
      let [name, url] = res;
      apis.push({
        name: name.trim(),
        url: url.trim(),
      });
    });
    return apis;
  }

  let parseBtns = [];

  function setParseBtns() {
    const singleParsePanel = document.getElementById("qxjfal-singleParsePanel");
    parseBtns.forEach((btn) => singleParsePanel.removeChild(btn));
    parseBtns = [];
    parseApiList.forEach((api, index) => {
      const parseBtn = document.createElement("button");
      parseBtn.textContent = api.name;
      parseBtn.title = api.name + " " + api.url;
      if (index === fastUrlIndex) {
        parseBtn.style.backgroundColor = "#ff9999";
      }
      parseBtn.addEventListener("click", () => {
        fastUrlIndex = index;
        GM_setValue("fastUrlIndex", index);
        parseBtns.forEach((btn) => {
          btn.style.backgroundColor = "";
        });
        parseBtns[index].style.backgroundColor = "#ff9999";
        const fastbootBtn = document.getElementById("qxjfal-fastboot");
        fastbootBtn.title = `快速开始(当前所选接口:${api.name})`;
        parseVideo(api.url, showMode);
      });
      parseBtns.push(parseBtn);
      singleParsePanel.appendChild(parseBtn);
    });
  }

  function openSettingPanel() {
    const settingPanel = document.createElement("div");
    settingPanel.id = "qxjfal-setting-panel";
    const settingHtml = `
      <div class='qxjfal-setting-panel-header'>
        <div class='qxjfal-setting-panel-title'>设置自定义解析接口</div>
        <div class='qxjfal-setting-panel-closebtn'>X</div>
      </div>
      <div style='padding: 15px;'>
        <div>
          <p>自定义解析接口
          </p>
          <p>数据格式:[名字] + [,] + [接口地址]</p>
          <p>例如:名字,https://xxxxxx?url=</p>
          <p>一行一个自定义接口,如果不提供名字,则自动将二级域名作为名字
          </p>
        </div>
        <div>
          <textarea class="qxjfal-setting-panel-textarea" rows="10" cols="50"></textarea>
        </div>
        <div>
          <button class="qxjfal-setting-savebtn">保存</button>
        </div>
      </div>
    `;
    settingPanel.innerHTML = settingHtml;
    const settingPanelHeader = settingPanel.querySelector(
      ".qxjfal-setting-panel-header"
    );
    makeDraggable({
      element: settingPanel,
      handle: settingPanelHeader,
      enableX: true,
      enableY: true,
    });
    const settingPanelCloseBtn = settingPanel.querySelector(
      ".qxjfal-setting-panel-closebtn"
    );
    settingPanelCloseBtn.addEventListener("click", () => {
      document.body.removeChild(settingPanel);
    });
    const settingPanelTextarea = settingPanel.querySelector(
      ".qxjfal-setting-panel-textarea"
    );
    settingPanelTextarea.value = parseApi2Text(parseApiList);
    const settingSaveBtn = settingPanel.querySelector(
      ".qxjfal-setting-savebtn"
    );
    settingSaveBtn.addEventListener("click", () => {
      parseApiList = text2ParseApi(settingPanelTextarea.value);
      GM_setValue("parseApiList", JSON.stringify(parseApiList));
      setParseBtns();
      settingPanelCloseBtn.click();
    });
    document.body.appendChild(settingPanel);
  }

  const parseVideoAgainLater = () => {
    setTimeout(parseVideoAgain, 1000);
  };

  // 网站与解析规则的映射
  const siteRules = {
    "v.qq.com": {
      node: [".player__container", "#player-container"],
      area: "playlist-list",
    },
    "iqiyi.com": { node: ["#video"], area: "" },
    "iq.com": { node: [".intl-video-wrap"], area: "m-sliding-list" },
    "youku.com": { node: ["#ykPlayer"], area: "new-box-anthology-items" },
    "bilibili.com": {
      node: ["#bilibili-player", ".bpx-player-primary-area"],
      area: "video-episode-card",
    },
    "mgtv.com": { node: ["#mgtv-player-wrap"], area: "episode-items" },
    "le.com": { node: ["#le_playbox"], area: "juji_grid" },
    "tudou.com": { node: ["#player"], area: "" },
    "pptv.com": { node: ["#pptv_playpage_box"], area: "" },
    "1905.com": { node: ["#player", "#vodPlayer"], area: "" },
  };

  let floatVideoContainer = null;
  let originalVideoContainer = null;
  let originalVideoContainerSelector = null;
  let currentIframeContainer = null;
  let distanceTop = null;
  let distanceLeft = null;
  let videoContainerWidth = null;
  let videoContainerHeight = null;
  let hidePanelTimeout = null; // 隐藏面板的定时器
  let lastUrl = "";
  let parsed = false;
  let lastWindow = null;
  let parseAutoPause = GM_getValue("parseAutoPause", true);
  let parseAutoMute = GM_getValue("parseAutoMute", true);
  let showMode = GM_getValue("showMode", "emb"); // 1 为悬浮播放,2 为新窗口,3 为新标签页
  let fastUrlIndex = GM_getValue("fastUrlIndex", -1);

  function getSiteRule(host) {
    return (
      siteRules[Object.keys(siteRules).find((key) => host.includes(key))] ||
      null
    );
  }

  function getDomainFromUrl(url) {
    let domain;
    try {
      let parsedUrl = new URL(url);
      domain = parsedUrl.hostname;
    } catch (error) {
      console.error("Invalid URL", error);
    }
    return domain;
  }

  function getSLDFromUrl(url) {
    const domain = getDomainFromUrl(url);
    const domainLs = domain.split(".");
    if (domainLs.length >= 2) {
      return domainLs[domainLs.length - 2];
    } else {
      return "😊";
    }
  }

  function createParseElements() {
    const iconSize = isMobile ? 30 : GM_getValue("iconWidth", 24);
    const iconTop = isMobile ? 360 : GM_getValue("iconTop", 100);
    const iconPosition = isMobile
      ? "left"
      : GM_getValue("iconPosition", "left");

    const iconStyle = `
    #qxjfal-iconContainer {
      background-color: #fff;
      border: 1px solid #ccc;
      border-radius: 6px;
      padding: 0px;
      text-align: center;
      opacity: ${isMobile ? 1 : GM_getValue("iconOpacity", 100) / 100};
      width: ${iconSize}px;
      box-sizing: border-box;
      opacity: 0.5;
      /* transition: 0.1s; */
    }
    #qxjfal-iconContainer:hover {
      opacity: 1;
    }
    #qxjfal-optionIcons {
      cursor: pointer;
    }
    #qxjfal-optionIcons>div {
      padding: 6px 0px;
    }
    #qxjfal-container {
      position: fixed;
      top: ${iconTop}px;
      ${iconPosition}: 0px;
      z-index: 999999;
      display: flex;
      flex-direction: ${iconPosition === "left" ? "row" : "row-reverse"};
    }
    #qxjfal-dragIcon {
      cursor: move;
    }
    #qxjfal-dragIcon:hover {
      transform: scale(1.2);
    }
    #qxjfal-fastboot:hover {
      transform: scale(1.2);
    }
    #qxjfal-vidParseIcon:hover {
      transform: scale(1.2);
    }

    #qxjfal-parsePanel {
      position: fixed; /* 绝对定位 */
      top: 0px; /*  图标高度+5px的间距*/
      ${
        iconPosition === "left" ? "left" : "right"
      }: ${iconSize}px; /* 根据图标位置调整 */
      z-index: 999998;
      background-color: #fff;
      border: 1px solid #ccc;
      padding: 12px 15px;
      box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
      border-radius: 6px;
      width: 310px; /* 调整面板宽度 */
      height: 100vh;
      overflow: auto;
      display: none; /* 初始隐藏 */
      box-sizing: border-box;
    }

    #qxjfal-parsePanel button, #qxjfal-setting-panel button {
      margin: 3px 0;
      padding: 8px 18px;
      background-color: #285aa6;
      color: #fff;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      transition: background-color 0.3s;
      width: 100%;
      box-sizing: border-box;
    }
    #qxjfal-parsePanel button:hover {
      background-color: #1e4888;
    }
    #qxjfal-parsePanel * {
      color: #333333;
    }

    #qxjfal-configPanel {
      margin-top: 15px;
      padding-top: 10px;
      border-top: 1px solid #eee;
    }

    #qxjfal-configPanel label {
      display: block;
      margin-bottom: 8px;
      color: #333;
    }
    #qxjfal-configPanel input[type="radio"] {
      margin-right: 6px;
    }

    #qxjfal-saveConfigBtn {
      background-color: #4CAF50 !important;
    }
    #qxjfal-saveConfigBtn:hover {
      background-color: #45a049 !important;
    }

    #qxjfal-aboutPanel, #qxjfal-singleParsePanel {
      margin: 3px 0;
      padding: 15px;
      background-color: #f8f9fa;
      border-radius: 4px;
    }

    #qxjfal-aboutPanel h4 {
      margin-top: 0;
      color: #333333;
    }

    #qxjfal-aboutPanel p {
      color: #333333;
      line-height: 1.6;
    }

    #qxjfal-singleParsePanel {
      padding: 6px;
      max-height: 300px;
      overflow: auto;
    }

    #qxjfal-singleParsePanel button {
      padding: 6px 8px;
      width: 60px;
      overflow: hidden;
      text-overflow: ellipsis;
      border-radius: 6px;
      text-wrap: nowrap;
      background-color: #ffffff;
      color: #333333;
      margin: 3px;
    }

    #qxjfal-singleParsePanel button:hover {
      background-color: #dfeffd;
    }

    #qxjfal-telegramLink {
      color: #007bff;
      text-decoration: underline;
      cursor: pointer;
    }

    #qxjfal-showmode-select {
      border: 1px solid #999;
      padding: 4px 10px;
      border-radius: 4px;
      margin: 3px 0;
    }

    #qxjfal-parse-autopause {
      margin: 3px 0;
    }

    #qxjfal-parse-automute {
      margin: 3px 0;
    }

    /* ... 其他样式保持不变 ... */
    #qxjfal-float-video-container {
      position: absolute;
      top: 0;
      left: 0;
      width: 200px;
      height: 100px;
      z-index: 999997;
      display: flex;
      flex-direction: column;
    }

    .qxjfal-video-top-handle {
      background: #333333;
      width: 100%;
      box-sizing: border-box;
      padding: 4px;
    }

    .qxjfal-video-expand-handle {
      user-select: none;
      width: 50px;
      box-sizing: border-box;
      padding: 4px 6px;
      text-align: center;
      cursor: pointer;
    }

    .qxjfal-video-drag-handle {
      text-align: center;
      box-sizing: border-box;
      padding: 4px 6px;
      width: 50px;
      background: #333333;
      cursor: move;
    }
    .qxjfal-video-drag-title {
      color: #fff;
      display: none;
    }

    .qxjfal-iframe-container {
      flex: 1;
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      grid-template-rows: repeat(2, auto);
      grid-auto-rows: minmax(200px, auto);
      grid-gap: 1px;
      width: 100%;
      height: 100%;
    }
    .qxjfal-one-chunk {
      grid-template-columns: repeat(1, 1fr);
      grid-template-rows: repeat(1, auto);
    }
    .qxjfal-one-chunk .qxjfal-iframe-option {
      display: none;
    }
    .qxjfal-four-chunk {
      grid-template-columns: repeat(2, 1fr);
      grid-template-rows: repeat(2, auto);
    }
    .qxjfal-six-chunk {
      grid-template-columns: repeat(3, 1fr);
      grid-template-rows: repeat(2, auto);
    }
    .qxjfal-iframe-container iframe {
      flex: 1;
      border: 1px solid #ddd;
    }
      /* 可选:添加响应式设计 */
    @media (max-width: 768px) {
      .qxjfal-iframe-container {
          grid-template-columns: repeat(2, 1fr); /* 在小屏幕上显示两列 */
      }
    }

    @media (max-width: 480px) {
      .qxjfal-iframe-container {
          grid-template-columns: 1fr; /* 在非常小的屏幕上显示一列 */
      }
    }

    .qxjfal-iframe-wrapper {
      display: flex;
      flex-direction: column;
      items-align: stretch;
    }

    .qxjfal-iframe-wrapper button {
      margin: 0;
      padding: 2px 6px;
      background-color: #2871a6;
      color: #fff;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      transition: background-color 0.3s;
      box-sizing: border-box;
    }

    .qxjfal-iframe-option {
      display: flex;
      column-gap: 4px;
      background-color: #777777;
      color: white;
      text-align: center;
      padding: 4px;
      transition: 0.3s;
    }
    .qxjfal-expand-button {
      flex: 1;
    }
    .qxjfal-eliminate-button {
      background-color: #333333 !important;
      flex: 1;
    }

    #qxjfal-setting-panel {
      font-size: 14px;
      position: fixed;
      top: 0;
      ${iconPosition === "left" ? "left" : "right"}: 0px;
      margin: 0 auto;
      max-height: 100%;
      width: 100%;
      max-width: 500px;
      background-color: #ffffff;
      border-radius: 6px;
      overflow: auto;
      box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
      z-index: 999999;
      box-sizing: border-box;
    }

    .qxjfal-setting-panel-header {
      padding: 4px 10px;
      text-align: center;
      background-color: #efefef;
    }

    .qxjfal-setting-panel-title {
      display: inline-block;
      font-weight: bold;
      user-select: none;
    }
    .qxjfal-setting-panel-closebtn {
      font-size: 1.2em;
      line-height: 1.2em;
      vertical-align: middle;
      float: right;
      cursor: pointer;
    }
    .qxjfal-setting-panel-textarea {
      width: 99%;
      margin: 0 auto;
    }
    `;

    const styleEl = document.createElement("style");
    styleEl.textContent = iconStyle;
    document.head.appendChild(styleEl);

    const iconHtml = `
        <div id="qxjfal-iconContainer">
            <div id="qxjfal-optionIcons">
                <div id="qxjfal-dragIcon" title="拖拽调整位置">Ⓜ️</div>
                <div id="qxjfal-fastboot" title="快速开始(当前所选接口:${
                  fastUrlIndex === -1 ? "无" : parseApiList[fastUrlIndex].name
                })">🩷</div>
                <div id="qxjfal-vidParseIcon" title="解析">✨</div>
            </div>
        </div>
        <div id="qxjfal-parsePanel">
            <div>
                <button id="qxjfal-parseBtn">👉解析</button>
                <button id="qxjfal-restoreBtn" style="background:#5a6268;">还原</button>
            </div>
            <div id="qxjfal-singleParsePanel">
            </div>
            <div>
              <select id="qxjfal-showmode-select" name="showmode">
                <option value="emb">当前页面中</option>
                <option value="win">新窗口打开</option>
                <option value="tab">新标签打开</option>
              </select>
            </div>
            <div>
                <label><input type="checkbox" id="qxjfal-parse-autopause" ${
                  parseAutoPause ? "checked" : ""
                }> 解析时自动暂停原视频(部分网站可能无法成功暂停)</label>
            </div>
            <div>
                <label><input type="checkbox" id="qxjfal-parse-automute" ${
                  parseAutoMute ? "checked" : ""
                }> 解析时自动静音原视频</label>
            </div>
            <div id="qxjfal-configPanel">
                <label><input type="radio" name="qxjfal-iframeCount" value="6"> 6个格子解析</label>
                <label><input type="radio" name="qxjfal-iframeCount" value="4"> 4个格子解析</label>
                <label><input type="radio" name="qxjfal-iframeCount" value="1"> 1个格子解析</label>
                <button id="qxjfal-saveConfigBtn">保存配置</button>
                <div id="qxjfal-configTips" style="margin-top: 10px; padding: 5px 10px; color: red; display: none;font-size:12px;">配置已保存并生效!</div>
            </div>
            <div>
                <button id="qxjfal-setting-btn">更多设置</button>
            </div>
            <div id="qxjfal-aboutPanel">
                <h4>🎥 视频解析工具</h4>
                ${
                  GM_getValue("qxjfal-disclaimer", null) === "true"
                    ? ""
                    : `<p><b>免责声明:</b></p>
                <p>
                  1、<b style='color:red;'>需要使用视频解析的,请在更多设置自行添加接口</b>,版权问题请联系相关解析接口所有者,脚本不承担相关责任!"<br>
                  2、为创造良好的创作氛围,请大家支持正版!<br>
                  3、脚本仅限个人学习交流,使用即已代表您已经充分了解相关问题,否则后果自负,特此声明!<br>
                </p>
                <button id="qxjfal-disclaimer-btn" title="点击后表示确认,不再展示">确认</button>
                `
                }
            </div>
        </div>
    `;

    const container = document.createElement("div");
    container.id = "qxjfal-container";
    container.innerHTML = iconHtml;
    document.body.appendChild(container);

    const parsePanel = document.getElementById("qxjfal-parsePanel");
    const dragIcon = document.getElementById("qxjfal-dragIcon");
    const vidParseIcon = document.getElementById("qxjfal-vidParseIcon");
    const parseBtn = document.getElementById("qxjfal-parseBtn");
    const configPanel = document.getElementById("qxjfal-configPanel");
    const saveConfigBtn = document.getElementById("qxjfal-saveConfigBtn");
    const restoreBtn = document.getElementById("qxjfal-restoreBtn");
    const fastbootBtn = document.getElementById("qxjfal-fastboot");
    const settingBtn = document.getElementById("qxjfal-setting-btn");
    settingBtn.addEventListener("click", openSettingPanel);

    const showmodeSelector = document.getElementById("qxjfal-showmode-select");
    showmodeSelector.value = showMode;
    showmodeSelector.addEventListener("change", (e) => {
      showMode = e.target.value;
      GM_setValue("showMode", showMode);
    });

    const parseAutoPauseInput = document.getElementById(
      "qxjfal-parse-autopause"
    );
    parseAutoPauseInput.checked = parseAutoPause;
    parseAutoPauseInput.addEventListener("change", (e) => {
      parseAutoPause = e.target.checked;
      GM_setValue("parseAutoPause", parseAutoPause);
    });

    const parseAutoMuteInput = document.getElementById("qxjfal-parse-automute");
    parseAutoMuteInput.checked = parseAutoMute;
    parseAutoMuteInput.addEventListener("change", (e) => {
      parseAutoMute = e.target.checked;
      GM_setValue("parseAutoMute", parseAutoMute);
    });

    const icon = dragIcon;

    const disclaimerBtn = document.getElementById("qxjfal-disclaimer-btn");
    disclaimerBtn.addEventListener("click", () => {
      GM_setValue("qxjfal-disclaimer", "true");
    });

    setParseBtns();

    fastbootBtn.addEventListener("click", () => {
      if (fastUrlIndex === -1) {
        alert("请先使用一个解析接口!快速开始会自动使用最近一次使用的解析接口");
        return;
      } else {
        parseVideo(parseApiList[fastUrlIndex].url);
      }
    });

    // 初始化配置
    const iframeCount = GM_getValue("iframeCount", "6");
    configPanel.querySelector(`input[value="${iframeCount}"]`).checked = true;

    // 鼠标移入图标:显示面板,清除隐藏定时器
    icon.addEventListener("mouseover", () => {
      clearTimeout(hidePanelTimeout);
      parsePanel.style.display = "block";
    });

    // 鼠标移出图标:启动隐藏面板定时器
    icon.addEventListener("mouseleave", () => {
      hidePanelTimeout = setTimeout(() => {
        parsePanel.style.display = "none";
      }, 300);
    });

    // 鼠标移入面板:清除隐藏定时器
    parsePanel.addEventListener("mouseover", () => {
      clearTimeout(hidePanelTimeout);
    });

    // 鼠标移出面板:启动隐藏面板定时器
    parsePanel.addEventListener("mouseleave", () => {
      hidePanelTimeout = setTimeout(() => {
        parsePanel.style.display = "none";
      }, 300);
    });

    // 保存配置
    saveConfigBtn.addEventListener("click", () => {
      const newIframeCount = configPanel.querySelector(
        'input[name="qxjfal-iframeCount"]:checked'
      ).value;
      GM_setValue("iframeCount", newIframeCount);
      if (originalVideoContainer) {
        parseVideoAgain();
      }
      // 获取提示元素
      const tips = document.getElementById("qxjfal-configTips");
      tips.style.display = "block";
      // 3秒后隐藏
      setTimeout(() => {
        tips.style.display = "none";
      }, 3000);
    });

    parsePanel.addEventListener("click", (e) => {
      e.stopPropagation();
    });
    parseBtn.addEventListener("click", (e) => parseVideo());
    vidParseIcon.addEventListener("click", (e) => parseVideo());
    restoreBtn.addEventListener("click", restoreVideo);

    makeDraggable({
      element: container,
      handle: dragIcon,
      enableY: true,
      rememberY: "iconTop",
    });
  }

  function getVideoContainer() {
    const siteRule = getSiteRule(location.hostname);
    if (!siteRule) {
      console.log("未找到匹配的网站规则");
      return null;
    }
    let videoContainer = null;
    for (const node of siteRule.node) {
      videoContainer = document.querySelector(node);
      if (videoContainer) {
        originalVideoContainerSelector = node;
        distanceTop =
          videoContainer.getBoundingClientRect().top + window.scrollY;
        distanceLeft =
          videoContainer.getBoundingClientRect().left + window.scrollX;
        videoContainerWidth = videoContainer.offsetWidth;
        videoContainerHeight = videoContainer.offsetHeight;
        break;
      }
    }
    return videoContainer;
  }

  function expandIframe(iframeWrappers, index) {
    const videoContainer = getVideoContainer();
    if (!videoContainer) return;
    iframeWrappers.forEach((iframeWrapper, id) => {
      if (id !== index) currentIframeContainer.removeChild(iframeWrapper);
      // else {
      //   const iframeOption = iframeWrapper.querySelector(
      //     ".qxjfal-iframe-option"
      //   );
      //   iframeWrapper.removeChild(iframeOption);
      // }
    });
    currentIframeContainer.className =
      "qxjfal-iframe-container qxjfal-one-chunk";
  }

  function stopVideos() {
    let videos = document.querySelectorAll("video");
    for (let i = 0; i < videos.length; i++) {
      videos[i].pause();
    }
    videos = null;
  }
  function muteVideos() {
    let videos = document.querySelectorAll("video");
    for (let i = 0; i < videos.length; i++) {
      videos[i].muted = true;
    }
    videos = null;
  }

  function parseVideoAgain() {
    if (!parsed) return;
    if (lastUrl !== "") parseVideo(lastUrl, showMode);
    else parseVideo();
  }

  function parseVideo(url, showMode = "emb") {
    const videoContainer = getVideoContainer();
    if (parseAutoPause) stopVideos();
    if (parseAutoMute) muteVideos();
    parsed = true;
    if (url) lastUrl = url;
    else lastUrl = "";

    if (floatVideoContainer !== null) {
      document.body.removeChild(floatVideoContainer);
      floatVideoContainer = null;
    }

    if (showMode === "emb") {
      if (!videoContainer) return;
      //     if (!originalVideoContainer) {
      //       originalVideoContainer = videoContainer.innerHTML;
      //     }

      let iframeCount = 0;
      let urls = [];
      if (url) {
        iframeCount = 1;
        urls = [url];
      } else {
        iframeCount = parseInt(GM_getValue("iframeCount", "6"));
        urls = parseApiList.slice(0, iframeCount).map((api) => api.url);
      }

      let gridClass = "qxjfal-one-chunk";
      if (iframeCount === 6) {
        gridClass = "qxjfal-six-chunk";
      } else if (iframeCount === 4) {
        gridClass = "qxjfal-four-chunk";
      }

      let iframeHTML = `
      <div class="qxjfal-video-top-handle">
        <span class="qxjfal-video-drag-handle">Ⓜ️<span class="qxjfal-video-drag-title">拖拽窗口</span></span>
        <span class="qxjfal-video-expand-handle" title="收起/展开">🚥</span>
      </div>
      `;
      iframeHTML += `<div class="qxjfal-iframe-container ${gridClass}">`;
      urls.forEach((url) => {
        iframeHTML += `
                <div class="qxjfal-iframe-wrapper">
                    <iframe src="${url}${encodeURIComponent(
          location.href
        )}" allowfullscreen allowtransparency></iframe>
                    <div class="qxjfal-iframe-option">
                        <button class="qxjfal-expand-button">⬆️用这个视频继续播放</button>
                    </div>
                </div>
            `;
      });
      iframeHTML += "</div>";

      floatVideoContainer = document.createElement("div");
      floatVideoContainer.id = "qxjfal-float-video-container";
      floatVideoContainer.style.top = `${distanceTop}px`;
      floatVideoContainer.style.left = `${distanceLeft}px`;
      floatVideoContainer.style.width = `${videoContainerWidth}px`;
      floatVideoContainer.style.height = `${videoContainerHeight}px`;
      floatVideoContainer.innerHTML = iframeHTML;

      document.body.appendChild(floatVideoContainer);

      const videoDragHandle = floatVideoContainer.querySelector(
        ".qxjfal-video-drag-handle"
      );
      const videoDragTitle = floatVideoContainer.querySelector(
        ".qxjfal-video-drag-title"
      );
      const videoExpandHandle = floatVideoContainer.querySelector(
        ".qxjfal-video-expand-handle"
      );
      currentIframeContainer = floatVideoContainer.querySelector(
        ".qxjfal-iframe-container"
      );

      videoExpandHandle.addEventListener("click", function (e) {
        currentIframeContainer.style.display =
          currentIframeContainer.style.display === "" ? "none" : "";
        floatVideoContainer.style.height =
          floatVideoContainer.style.height === "28px"
            ? `${videoContainerHeight}px`
            : "28px";
      });

      videoDragHandle.addEventListener("mousedown", function (e) {
        videoDragHandle.style.position = "absolute";
        videoDragHandle.style.top = "0px";
        videoDragHandle.style.left = "0px";
        videoDragHandle.style.width = "100%";
        videoDragHandle.style.height = "100%";
        videoDragHandle.style.borderRadius = "0";
        videoDragHandle.style.textAlign = "center";
        videoDragTitle.style.display = "inline-block";
      });
      videoDragHandle.addEventListener("mouseup", function (e) {
        videoDragHandle.style.cssText = "";
        videoDragTitle.style.cssText = "";
      });

      makeDraggable({
        element: floatVideoContainer,
        handle: videoDragHandle,
        enableX: true,
        enableY: true,
      });

      //videoContainer.innerHTML = iframeHTML;

      const expandButtons = floatVideoContainer.querySelectorAll(
        ".qxjfal-expand-button"
      );
      expandButtons.forEach((button, index) => {
        button.addEventListener("click", () => {
          expandIframe(
            floatVideoContainer.querySelectorAll(".qxjfal-iframe-wrapper"),
            index
          );
        });
      });
    } else if (showMode === "win") {
      let windowOption = "width=600,height=400,top=100,left=100,resizable=yes";
      if (videoContainer)
        windowOption = `width=${videoContainerWidth - 10},height=${Math.max(
          400,
          videoContainerHeight - 150
        )},top=${distanceTop + 150},left=${distanceLeft},resizable=yes`;
      if (!url) url = parseApiList[0].url;
      if (lastWindow !== null) lastWindow.close();
      lastWindow = window.open(
        `${url}${encodeURIComponent(location.href)}`,
        "qx_parse_win",
        windowOption
      );
    } else if (showMode === "tab") {
      if (!url) url = parseApiList[0].url;
      if (lastWindow !== null) lastWindow.close();
      lastWindow = window.open(
        `${url}${encodeURIComponent(location.href)}`,
        "_blank"
      );
    }

    const siteRule = getSiteRule(location.hostname);
    if (siteRule && siteRule.area) {
      const areaSelector = `.${siteRule.area}`;
      if (!videoContainer.dataset.eventBound) {
        const bindAreaEvent = () => {
          const areaElement = document.querySelector(areaSelector);
          if (areaElement) {
            areaElement.addEventListener("click", parseVideoAgainLater);
            videoContainer.dataset.eventBound = "true";
          }
        };
        bindAreaEvent();
        const observer = new MutationObserver(bindAreaEvent);
        observer.observe(document.body, { childList: true, subtree: true });
      }
    }
  }

  function restoreVideo() {
    // 直接刷新页面
    location.reload();
  }

  function makeDraggable({
    element,
    handle,
    enableX,
    enableY,
    rememberX,
    rememberY,
  }) {
    let isDragging = false;
    let startX, startY, startTop, startLeft;

    handle.addEventListener("mousedown", (e) => {
      e.preventDefault();
      if (e.button !== 0) return;

      isDragging = true;
      if (enableX) {
        startX = e.clientX;
        startLeft = element.offsetLeft;
      }
      if (enableY) {
        startY = e.clientY;
        startTop = element.offsetTop;
      }

      document.addEventListener("mousemove", onMouseMoveWrapper);
      document.addEventListener("mouseup", onMouseUp);
    });

    function onMouseMoveX(e) {
      const deltaX = e.clientX - startX;
      let newLeft = startLeft + deltaX;
      const maxWidth = window.innerWidth - element.offsetWidth - 20;
      newLeft = Math.max(0, Math.min(newLeft, maxWidth));
      element.style.left = `${newLeft}px`;
    }

    function onMouseMoveY(e) {
      const deltaY = e.clientY - startY;
      let newTop = startTop + deltaY;
      const maxHeight = window.innerHeight - element.offsetHeight - 10;
      newTop = Math.max(0, Math.min(newTop, maxHeight));
      element.style.top = `${newTop}px`;
    }

    function getMouseMoveHandle() {
      if (enableX && enableY) {
        return function (e) {
          onMouseMoveX(e);
          onMouseMoveY(e);
        };
      } else if (enableX) {
        return onMouseMoveX;
      } else {
        return onMouseMoveY;
      }
    }

    function onMouseMoveWrapper(e) {
      if (!isDragging) return;
      const onMouseMove = getMouseMoveHandle();
      onMouseMove(e);
    }

    function onMouseUp() {
      isDragging = false;
      document.removeEventListener("mousemove", onMouseMoveWrapper);
      document.removeEventListener("mouseup", onMouseUp);
      if (rememberX) GM_setValue(rememberX, element.offsetLeft);
      if (rememberY) GM_setValue(rememberY, element.offsetTop);
    }
  }

  window.addEventListener("load", () => {
    if (getSiteRule(location.hostname)) {
      createParseElements();

      const siteRule = getSiteRule(location.hostname);
      if (siteRule && siteRule.area) {
        const areaSelector = `.${siteRule.area}`;
        const videoContainer = getVideoContainer();
        if (videoContainer && !videoContainer.dataset.eventBound) {
          const bindAreaEvent = () => {
            const areaElement = document.querySelector(areaSelector);
            if (areaElement) {
              areaElement.addEventListener("click", parseVideoAgainLater);
              videoContainer.dataset.eventBound = "true";
            }
          };

          bindAreaEvent();
          const observer = new MutationObserver(bindAreaEvent);
          observer.observe(document.body, { childList: true, subtree: true });
        }
      }
    }
  });

  GM_registerMenuCommand("设置解析线路", openSettingPanel);
})();