哔哩哔哩自动画质

自动解锁并更改哔哩哔哩视频的画质和音质及直播画质,实现自动选择最高画质、无损音频、杜比全景声。

目前为 2025-01-19 提交的版本。查看 最新版本

// ==UserScript==
// @name         哔哩哔哩自动画质
// @namespace    https://github.com/AHCorn/Bilibili-Auto-Quality/
// @version      3.5
// @license      GPL-3.0
// @description  自动解锁并更改哔哩哔哩视频的画质和音质及直播画质,实现自动选择最高画质、无损音频、杜比全景声。
// @author       安和(AHCorn)
// @icon         https://www.bilibili.com/favicon.ico
// @match        *://www.bilibili.com/video/*
// @match        *://www.bilibili.com/list/*
// @match        *://www.bilibili.com/blackboard/*
// @match        *://www.bilibili.com/watchlater/*
// @match        *://www.bilibili.com/bangumi/*
// @match        *://www.bilibili.com/watchroom/*
// @match        *://www.bilibili.com/medialist/*
// @match        *://bangumi.bilibili.com/*
// @match        *://live.bilibili.com/*
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// ==/UserScript==

(function () {
  "use strict";

  if (typeof unsafeWindow === "undefined") {
    unsafeWindow = window;
  }

  let hiResAudioEnabled = GM_getValue("hiResAudio", false);
  let dolbyAtmosEnabled = GM_getValue("dolbyAtmos", false);
  let userQualitySetting = GM_getValue("qualitySetting", "最高画质");
  let userHasChangedQuality = false;
  let takeOverQualityControl = GM_getValue("takeOverQualityControl", false);
  let isVipUser = false;
  let vipStatusChecked = false;
  let isLoading = true;
  let isLivePage = false;
  let userLiveQualitySetting = GM_getValue("liveQualitySetting", "原画");

  // 开发者模式用的变量,仅供测试
  let devModeEnabled = GM_getValue("devModeEnabled", false);
  let devModeVipStatus = GM_getValue("devModeVipStatus", false);
  let devModeDelay = GM_getValue("devModeDelay", 4000);
  let devModeDisableUA = GM_getValue("devModeDisableUA", false);

  try {
    if (!devModeDisableUA || !devModeEnabled) {
      Object.defineProperty(navigator, 'userAgent', {
        value: "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15",
        configurable: true
      });
      console.log('UA 修改成功');
    } else {
      console.log('开发者模式已禁用 UA 修改');
    }
  } catch (error) {
    console.error('修改 UserAgent 失败,解锁功能可能失效,若需要使用解锁功能,请先关闭与修改 UA 相关的插件及脚本:', error);
  }

  GM_addStyle(`
        #bilibili-quality-selector, #bilibili-live-quality-selector {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: linear-gradient(135deg, #f6f8fa, #e9ecef);
            border-radius: 24px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1), 0 1px 8px rgba(0, 0, 0, 0.06);
            padding: 30px;
            width: 90%;
            max-width: 400px;
            display: none;
            z-index: 10000;
            font-family: 'Segoe UI', 'Roboto', sans-serif;
            transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
        }

        #bilibili-quality-selector h2, #bilibili-live-quality-selector h2,
        #bilibili-live-quality-selector h3 {
            margin: 0 0 20px;
            color: #00a1d6;
            font-size: 28px;
            text-align: center;
            font-weight: 700;
        }

        #bilibili-live-quality-selector h3 {
            font-size: 24px;
            margin-top: 20px;
        }

        #bilibili-quality-selector p, #bilibili-live-quality-selector p {
            margin: 0 0 25px;
            color: #5f6368;
            font-size: 14px;
            text-align: center;
        }

        .quality-group {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
            gap: 12px;
            margin-bottom: 25px;
        }

        .line-group {
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            gap: 8px;
            margin-bottom: 25px;
        }

        .quality-button, .line-button {
            background-color: #ffffff;
            border: 2px solid #dadce0;
            border-radius: 12px;
            padding: 12px 8px;
            font-size: 14px;
            color: #3c4043;
            cursor: pointer;
            transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
            font-weight: 600;
        }

        .line-button {
            font-size: 12px;
            padding: 8px 4px;
        }

        .quality-button:hover, .line-button:hover {
            background-color: #f1f3f4;
            transform: translateY(-2px);
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
        }

        .quality-button.active, .line-button.active {
            background-color: #00a1d6;
            color: white;
            border-color: #00a1d6;
            box-shadow: 0 6px 12px rgba(0, 161, 214, 0.3);
        }

        .quality-button.active.vip-quality {
            background-color: #f25d8e;
            color: white;
            border-color: #f25d8e;
            box-shadow: 0 6px 12px rgba(242, 93, 142, 0.3);
        }

        .quality-button.unavailable {
            opacity: 0.5;
            cursor: not-allowed;
        }

        .toggle-switch {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 12px;
            padding: 10px 15px;
            background-color: #f1f3f4;
            border-radius: 12px;
            transition: all 0.3s ease;
        }

        .toggle-switch:hover {
            background-color: #e8eaed;
        }

        .toggle-switch label {
            font-size: 16px;
            color: #3c4043;
            font-weight: 600;
        }

        .switch {
            position: relative;
            display: inline-block;
            width: 52px;
            height: 28px;
        }

        .switch input {
            opacity: 0;
            width: 0;
            height: 0;
        }

        .slider {
            position: absolute;
            cursor: pointer;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-color: #ccc;
            transition: .4s;
            border-radius: 34px;
        }

        .slider:before {
            position: absolute;
            content: "";
            height: 20px;
            width: 20px;
            left: 4px;
            bottom: 4px;
            background-color: white;
            transition: .4s;
            border-radius: 50%;
        }

        input:checked + .slider {
            background-color: #00a1d6;
        }

        input:checked + .slider.vip-audio {
            background-color: #f25d8e;
        }

        input:checked + .slider:before {
            transform: translateX(24px);
        }

        @keyframes fadeIn {
            from { opacity: 0; }
            to { opacity: 1; }
        }

        @keyframes slideIn {
            from { transform: translate(-50%, -60%); }
            to { transform: translate(-50%, -50%); }
        }

        #bilibili-quality-selector.show, #bilibili-live-quality-selector.show {
            display: block;
            animation: fadeIn 0.3s ease-out, slideIn 0.3s ease-out;
        }

        @media (max-width: 480px) {
            #bilibili-quality-selector, #bilibili-live-quality-selector {
                width: 95%;
                padding: 25px;
            }

            .quality-group {
                grid-template-columns: repeat(2, 1fr);
            }
        }

        .status-bar {
            padding: 10px;
            border-radius: 8px;
            margin-bottom: 15px;
            text-align: center;
            font-weight: bold;
            transition: all 0.5s ease;
        }

        .status-bar.non-vip {
            background-color: #f0f0f0;
            color: #666666;
        }

        .status-bar.vip {
            background-color: #fff1f5;
            color: #f25d8e;
        }

        .warning {
            background-color: #fce8e6;
            color: #d93025;
            padding: 10px;
            border-radius: 8px;
            margin-top: 12px;
            margin-bottom: 12px;
            text-align: center;
            font-weight: bold;
            transition: all 0.3s ease;
        }

        .warning::before {
            content: "";
            margin-right: 10px;
        }

        /* 开发者模式面板样式 */
        #bilibili-dev-settings {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: linear-gradient(135deg, #ffffff, #f8f9fa);
            border-radius: 24px;
            box-shadow: 0 12px 36px rgba(0, 0, 0, 0.15), 0 4px 12px rgba(0, 0, 0, 0.08);
            padding: 32px;
            width: 90%;
            max-width: 420px;
            display: none;
            z-index: 10000;
            font-family: 'Segoe UI', 'Roboto', sans-serif;
        }

        #bilibili-dev-settings.show {
            display: block;
            animation: fadeIn 0.3s ease-out, slideIn 0.3s ease-out;
        }

        #bilibili-dev-settings h2 {
            margin: 0 0 24px;
            color: #f25d8e;
            font-size: 28px;
            text-align: center;
            font-weight: 700;
            letter-spacing: -0.5px;
            text-shadow: 0 2px 4px rgba(242, 93, 142, 0.1);
        }

        #bilibili-dev-settings .dev-warning {
            background: linear-gradient(135deg, #fff1f5, #fce8e6);
            color: #d93025;
            padding: 14px 18px;
            border-radius: 16px;
            margin-bottom: 24px;
            text-align: center;
            font-weight: 600;
            font-size: 14px;
            border: 2px solid rgba(217, 48, 37, 0.1);
            box-shadow: 0 4px 12px rgba(217, 48, 37, 0.05);
        }

        #bilibili-dev-settings .toggle-switch {
            background: #f8f9fa;
            border-radius: 16px;
            padding: 16px 20px;
            margin-bottom: 16px;
            display: flex;
            align-items: center;
            justify-content: space-between;
            transition: all 0.3s ease;
            border: 2px solid transparent;
        }

        #bilibili-dev-settings .toggle-switch:hover {
            background: #f1f3f4;
            transform: translateY(-1px);
            border-color: rgba(242, 93, 142, 0.1);
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
        }

        #bilibili-dev-settings .toggle-switch label {
            font-size: 15px;
            font-weight: 600;
            color: #3c4043;
        }

        #bilibili-dev-settings .input-group {
            background: #f8f9fa;
            border-radius: 16px;
            padding: 20px;
            margin-bottom: 16px;
            border: 2px solid transparent;
            transition: all 0.3s ease;
        }

        #bilibili-dev-settings .input-group:hover {
            background: #f1f3f4;
            border-color: rgba(242, 93, 142, 0.1);
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
        }

        #bilibili-dev-settings .input-group label {
            display: block;
            margin-bottom: 12px;
            color: #3c4043;
            font-weight: 600;
            font-size: 15px;
        }

        #bilibili-dev-settings .input-group input[type="number"] {
            width: 100%;
            padding: 14px;
            border: 2px solid #dadce0;
            border-radius: 12px;
            font-size: 15px;
            font-weight: 500;
            color: #3c4043;
            transition: all 0.3s ease;
            background: #ffffff;
            -moz-appearance: textfield;
        }

        #bilibili-dev-settings .input-group input[type="number"]::-webkit-outer-spin-button,
        #bilibili-dev-settings .input-group input[type="number"]::-webkit-inner-spin-button {
            -webkit-appearance: none;
            margin: 0;
        }

        #bilibili-dev-settings .input-group input[type="number"]:hover {
            border-color: #f25d8e;
            box-shadow: 0 2px 8px rgba(242, 93, 142, 0.1);
        }

        #bilibili-dev-settings .input-group input[type="number"]:focus {
            border-color: #f25d8e;
            outline: none;
            box-shadow: 0 0 0 3px rgba(242, 93, 142, 0.15);
            background: #ffffff;
        }

        #bilibili-dev-settings .input-group input[type="number"]:disabled {
            background: #f1f3f4;
            cursor: not-allowed;
            opacity: 0.7;
            border-color: #dadce0;
            box-shadow: none;
        }

        #bilibili-dev-settings .switch {
            position: relative;
            width: 56px;
            height: 30px;
            flex-shrink: 0;
        }

        #bilibili-dev-settings .slider {
            background-color: #dadce0;
            border-radius: 34px;
            transition: all 0.3s ease;
        }

        #bilibili-dev-settings .slider:before {
            height: 22px;
            width: 22px;
            left: 4px;
            bottom: 4px;
            background-color: #ffffff;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
        }

        #bilibili-dev-settings input:checked + .slider {
            background-color: #f25d8e;
        }

        #bilibili-dev-settings input:checked + .slider:before {
            transform: translateX(26px);
            box-shadow: 0 2px 4px rgba(242, 93, 142, 0.2);
        }

        #bilibili-dev-settings input:disabled + .slider {
            opacity: 0.5;
            cursor: not-allowed;
        }

        #bilibili-dev-settings input:disabled + .slider:before {
            box-shadow: none;
        }

        @media (max-width: 480px) {
            #bilibili-dev-settings {
                width: 95%;
                padding: 24px;
            }

            #bilibili-dev-settings .toggle-switch,
            #bilibili-dev-settings .input-group {
                padding: 14px 16px;
            }
        }
    `);

  function checkIfLivePage() {
    isLivePage = window.location.href.includes("live.bilibili.com");
  }

  function checkVipStatus() {
    if (devModeEnabled) {
      isVipUser = devModeVipStatus;
      vipStatusChecked = true;
      console.log(`开发者模式:用户是否为大会员: ${isVipUser ? "是" : "否"}`);
    } else {
      const vipElement = document.querySelector(
        ".bili-avatar-icon.bili-avatar-right-icon.bili-avatar-icon-big-vip"
      );
      const currentQuality = document.querySelector(
        ".bpx-player-ctrl-quality-menu-item.bpx-state-active .bpx-player-ctrl-quality-text"
      );
      isVipUser =
        vipElement !== null ||
        (currentQuality && currentQuality.textContent.includes("大会员"));
      vipStatusChecked = true;
      console.log(`用户是否为大会员: ${isVipUser ? "是" : "否"}`);
    }
    updateQualityButtons(document.getElementById("bilibili-quality-selector"));
  }

  function createSettingsPanel() {
    const panel = document.createElement("div");
    panel.id = "bilibili-quality-selector";

    const QUALITIES = [
      "最高画质",
      "8K",
      "杜比视界",
      "HDR",
      "4K",
      "1080P 高码率",
      "1080P 60帧",
      "1080P 高清",
      "720P",
      "480P",
      "360P",
      "默认",
    ];

    panel.innerHTML = `
            <h2>画质设置</h2>
            <div class="status-bar"></div>
            <div id="non-vip-warning" class="warning" style="display: none;"></div>
            <div class="quality-group">
                ${QUALITIES.map(
                  (quality) =>
                    `<button class="quality-button" data-quality="${quality}">${quality}</button>`
                ).join("")}
            </div>
            <div id="quality-warning" class="warning" style="display: none;"></div>
            <div class="toggle-switch">
                <label for="hi-res-audio">Hi-Res 音质</label>
                <label class="switch">
                    <input type="checkbox" id="hi-res-audio">
                    <span class="slider vip-audio"></span>
                </label>
            </div>
            <div class="toggle-switch">
                <label for="dolby-atmos">杜比全景声</label>
                <label class="switch">
                    <input type="checkbox" id="dolby-atmos">
                    <span class="slider vip-audio"></span>
                </label>
            </div>
            <div id="audio-warning" class="warning" style="display: none;"></div>
            <div class="toggle-switch">
                <label for="remove-quality-button">移除清晰度按钮(Beta)</label>
                <label class="switch">
                    <input type="checkbox" id="remove-quality-button">
                    <span class="slider"></span>
                </label>
            </div>
        `;

    panel.querySelectorAll(".quality-button").forEach((button) => {
      button.addEventListener("click", () => {
        if (!isLoading) {
          userQualitySetting = button.dataset.quality;
          GM_setValue("qualitySetting", userQualitySetting);
          userHasChangedQuality = true;
          updateQualityButtons(panel);
          selectQualityBasedOnSetting();
        }
      });
    });

    panel.querySelector("#hi-res-audio").addEventListener("change", (e) => {
      if (!isLoading) {
        hiResAudioEnabled = e.target.checked;
        GM_setValue("hiResAudio", hiResAudioEnabled);
        updateQualityButtons(panel);
        selectQualityBasedOnSetting();
      }
    });

    panel.querySelector("#dolby-atmos").addEventListener("change", (e) => {
      if (!isLoading) {
        dolbyAtmosEnabled = e.target.checked;
        GM_setValue("dolbyAtmos", dolbyAtmosEnabled);
        updateQualityButtons(panel);
        selectQualityBasedOnSetting();
      }
    });

    panel
      .querySelector("#remove-quality-button")
      .addEventListener("change", (e) => {
        if (!isLoading) {
          takeOverQualityControl = e.target.checked;
          GM_setValue("takeOverQualityControl", takeOverQualityControl);
          selectQualityBasedOnSetting();

          const warningElement = panel.querySelector("#quality-warning");
          if (takeOverQualityControl) {
            warningElement.textContent =
              "若启用该选项,画质设置将由本脚本接管。";
            warningElement.style.display = "block";
          } else {
            warningElement.style.display = "none";
          }
        }
      });

    document.body.appendChild(panel);
    updateQualityButtons(panel);
  }

  function updateQualityButtons(panel) {
    if (!panel) return;

    const statusBar = panel.querySelector(".status-bar");

    if (isLoading) {
      statusBar.textContent = "加载中,请稍候...";
      statusBar.className = "status-bar";
      panel
        .querySelectorAll(".quality-button, .toggle-switch")
        .forEach((el) => (el.style.opacity = "0.5"));
    } else {
      panel
        .querySelectorAll(".quality-button, .toggle-switch")
        .forEach((el) => (el.style.opacity = "1"));

      if (vipStatusChecked) {
        statusBar.textContent = isVipUser
          ? "您是大会员用户,可正常使用所有选项。"
          : "您不是大会员用户,部分会员选项不可用。";
        statusBar.className = `status-bar ${isVipUser ? "vip" : "non-vip"}`;
      }
    }

    panel.querySelectorAll(".quality-button").forEach((button) => {
      button.classList.remove("active", "vip-quality");
      if (button.dataset.quality === userQualitySetting) {
        button.classList.add("active");
        if (
          [
            "8K",
            "杜比视界",
            "HDR",
            "4K",
            "1080P 高码率",
            "1080P 60帧",
          ].includes(userQualitySetting)
        ) {
          button.classList.add("vip-quality");
        }
      }
    });

    const hiResAudioSwitch = panel.querySelector("#hi-res-audio");
    hiResAudioSwitch.checked = hiResAudioEnabled;

    const dolbyAtmosSwitch = panel.querySelector("#dolby-atmos");
    dolbyAtmosSwitch.checked = dolbyAtmosEnabled;

    panel.querySelector("#remove-quality-button").checked =
      takeOverQualityControl;

    updateWarnings(panel);
  }

  function updateWarnings(panel) {
    if (!panel || isLoading || !vipStatusChecked) return;

    const nonVipWarning = panel.querySelector("#non-vip-warning");
    const qualityWarning = panel.querySelector("#quality-warning");
    const audioWarning = panel.querySelector("#audio-warning");

    if (
      !isVipUser &&
      ["8K", "杜比视界", "HDR", "4K", "1080P 高码率", "1080P 60帧"].includes(
        userQualitySetting
      )
    ) {
      nonVipWarning.textContent =
        "无法使用此会员画质。已自动选择最高可用画质。";
      nonVipWarning.style.display = "block";
    } else {
      nonVipWarning.style.display = "none";
    }

    if (takeOverQualityControl) {
      qualityWarning.textContent = "若启用该选项,画质设置将由本脚本接管。";
      qualityWarning.style.display = "block";
    } else {
      qualityWarning.style.display = "none";
    }

    if (!isVipUser && (hiResAudioEnabled || dolbyAtmosEnabled)) {
      audioWarning.textContent = "非大会员用户不能使用高级音频选项。";
      audioWarning.style.display = "block";
    } else {
      audioWarning.style.display = "none";
    }
  }

  function selectQualityBasedOnSetting() {
    if (isLivePage) {
      selectLiveQuality();
    } else {
      selectVideoQuality();
    }
  }

  function selectVideoQuality() {
    if (!vipStatusChecked) {
      checkVipStatus();
    }

    let currentQuality = document.querySelector(
      ".bpx-player-ctrl-quality-menu-item.bpx-state-active .bpx-player-ctrl-quality-text"
    ).textContent;
    console.log(`当前画质: ${currentQuality}`);
    console.log(`目标画质: ${userQualitySetting}`);

    const qualityItems = document.querySelectorAll(
      ".bpx-player-ctrl-quality-menu .bpx-player-ctrl-quality-menu-item"
    );
    const availableQualities = Array.from(qualityItems).map((item) => ({
      name: item.textContent.trim(),
      element: item,
      isVipOnly: !!item.querySelector(".bpx-player-ctrl-quality-badge-bigvip"),
      isFreeNow: !!item.querySelector(".bpx-player-ctrl-quality-badge-bigvip")?.textContent.includes("限免中")
    }));

    console.log(
      `当前视频可用画质:`,
      availableQualities.map((q) => q.name)
    );

    const qualityPreferences = [
      "8K",
      "杜比视界",
      "HDR",
      "4K",
      "1080P 高码率",
      "1080P 60帧",
      "1080P 高清",
      "720P 60帧",
      "720P",
      "480P",
      "360P",
      "默认",
    ];

    availableQualities.sort((a, b) => {
      const getQualityIndex = (name) => {
        for (let i = 0; i < qualityPreferences.length; i++) {
          if (name.includes(qualityPreferences[i])) {
            return i;
          }
        }
        return qualityPreferences.length;
      };
      return getQualityIndex(a.name) - getQualityIndex(b.name);
    });

    let targetQuality;
    if (userQualitySetting === "最高画质") {
      const hasFreeVipQualities = availableQualities.some(quality => quality.isFreeNow);
      if (isVipUser || hasFreeVipQualities) {
        targetQuality = availableQualities[0];
      } else {
        targetQuality = availableQualities.find(quality => !quality.isVipOnly);
      }
    } else if (userQualitySetting === "默认") {
      console.log("使用默认画质");
      return;
    } else {
      targetQuality = availableQualities.find((quality) =>
        quality.name.includes(userQualitySetting)
      );
      if (!targetQuality) {
        console.log(`未找到目标画质 ${userQualitySetting}, 将选择最高可用画质`);
        targetQuality = isVipUser
          ? availableQualities.find((quality) => quality.isVipOnly) ||
            availableQualities[0]
          : availableQualities.find((quality) => !quality.isVipOnly);
      }
    }

    console.log(`实际目标画质: ${targetQuality.name}`);
    targetQuality.element.click();

    const hiResButton = document.querySelector(".bpx-player-ctrl-flac");
    if (hiResButton) {
      if (isVipUser) {
        if (
          hiResAudioEnabled &&
          !hiResButton.classList.contains("bpx-state-active")
        ) {
          hiResButton.click();
        } else if (
          !hiResAudioEnabled &&
          hiResButton.classList.contains("bpx-state-active")
        ) {
          hiResButton.click();
        }
      } else {
        if (hiResButton.classList.contains("bpx-state-active")) {
          hiResButton.click();
        }
      }
    }

    const dolbyButton = document.querySelector(".bpx-player-ctrl-dolby");
    if (dolbyButton) {
      if (isVipUser) {
        if (
          dolbyAtmosEnabled &&
          !dolbyButton.classList.contains("bpx-state-active")
        ) {
          dolbyButton.click();
        } else if (
          !dolbyAtmosEnabled &&
          dolbyButton.classList.contains("bpx-state-active")
        ) {
          dolbyButton.click();
        }
      } else {
        if (dolbyButton.classList.contains("bpx-state-active")) {
          dolbyButton.click();
        }
      }
    }

    if (takeOverQualityControl) {
      const qualityControlElement = document.querySelector(
        ".bpx-player-ctrl-btn.bpx-player-ctrl-quality"
      );
      if (qualityControlElement) {
        qualityControlElement.style.display = "none";
      }
    } else {
      const qualityControlElement = document.querySelector(
        ".bpx-player-ctrl-btn.bpx-player-ctrl-quality"
      );
      if (qualityControlElement) {
        qualityControlElement.style.display = "";
      }
    }

    updateWarnings(document.getElementById("bilibili-quality-selector"));
  }

  function createLiveSettingsPanel() {
    const panel = document.createElement("div");
    panel.id = "bilibili-live-quality-selector";

    const updatePanel = () => {
      const qualityCandidates =
        unsafeWindow.livePlayer.getPlayerInfo().qualityCandidates;
      const LIVE_QUALITIES = ["原画", "蓝光","超清", "高清"];

      const lineSelector = document.querySelector(".YccudlUCmLKcUTg_yzKN");
      const lines = lineSelector
        ? Array.from(lineSelector.children).map((li) => li.textContent)
        : ["加载中..."];
      const currentLineIndex = lineSelector
        ? Array.from(lineSelector.children).findIndex((li) =>
            li.classList.contains("fG2r2piYghHTQKQZF8bl")
          )
        : 0;

      panel.innerHTML = `
            <h2>直播设置</h2>
            <div class="line-group">
                ${lines
                  .map(
                    (line, index) =>
                      `<button class="line-button ${
                        index === currentLineIndex ? "active" : ""
                      }" data-line="${index}">${line}</button>`
                  )
                  .join("")}
            </div>
            <div class="quality-group">
                ${LIVE_QUALITIES.map(
                  (quality) =>
                    `<button class="quality-button ${
                      quality === userLiveQualitySetting ? "active" : ""
                    }" data-quality="${quality}">${quality}</button>`
                ).join("")}
            </div>
        `;

      panel.querySelectorAll(".line-button").forEach((button) => {
        button.addEventListener("click", () => {
          const lineIndex = parseInt(button.dataset.line);
          changeLine(lineIndex);
        });
      });

      panel.querySelectorAll(".quality-button").forEach((button) => {
        button.addEventListener("click", () => {
          userLiveQualitySetting = button.dataset.quality;
          GM_setValue("liveQualitySetting", userLiveQualitySetting);
          updatePanel();
          selectLiveQuality();
        });
      });
    };

    document.body.appendChild(panel);
    panel.updatePanel = updatePanel;
    updatePanel();
  }

  function selectLiveQuality() {
    return new Promise((resolve) => {
      const timer = setInterval(() => {
        if (
          unsafeWindow.livePlayer &&
          unsafeWindow.livePlayer.getPlayerInfo &&
          unsafeWindow.livePlayer.getPlayerInfo().playurl &&
          unsafeWindow.livePlayer.switchQuality
        ) {
          clearInterval(timer);
          resolve();
        }
      }, 1000);
    }).then(() => {
      const qualityCandidates =
        unsafeWindow.livePlayer.getPlayerInfo().qualityCandidates;

      console.log("可用画质选项:");
      qualityCandidates.forEach((quality, index) => {
        console.log(`${index + 1}. ${quality.desc} (qn: ${quality.qn})`);
      });

      console.log(`选择的画质: ${userLiveQualitySetting}`);

      let targetQuality;

      targetQuality = qualityCandidates.find(
        (q) => q.desc === userLiveQualitySetting
      );

      if (!targetQuality) {
        const qualityPriority = ["原画", "蓝光", "超清", "高清"];
        for (let quality of qualityPriority) {
          targetQuality = qualityCandidates.find((q) => q.desc === quality);
          if (targetQuality) break;
        }
      }

      if (!targetQuality) {
        targetQuality = qualityCandidates[0];
      }

      const targetQualityNumber = targetQuality.qn;
      const targetQualityName = targetQuality.desc;

      console.log(
        `目标画质:${targetQualityName} (qn: ${targetQualityNumber})`
      );

      const switchQuality = () => {
        const currentQualityNumber =
          unsafeWindow.livePlayer.getPlayerInfo().quality;
        if (currentQualityNumber !== targetQualityNumber) {
          unsafeWindow.livePlayer.switchQuality(targetQualityNumber);
          console.log(`已切换到目标画质:${targetQualityName}`);
          userLiveQualitySetting = targetQualityName;
          GM_setValue("liveQualitySetting", userLiveQualitySetting);
          updateLiveSettingsPanel();
        } else {
          console.log(`已经是目标画质:${targetQualityName}`);
        }
      };

      switchQuality();
    });
  }

  function changeLine(lineIndex) {
    const lineSelector = document.querySelector(".YccudlUCmLKcUTg_yzKN");
    if (lineSelector && lineSelector.children[lineIndex]) {
      lineSelector.children[lineIndex].click();
      console.log(
        `已切换到线路:${lineSelector.children[lineIndex].textContent}`
      );
      const panel = document.getElementById("bilibili-live-quality-selector");
      if (panel) {
        panel.querySelectorAll(".line-button").forEach((button, index) => {
          if (index === lineIndex) {
            button.classList.add("active");
          } else {
            button.classList.remove("active");
          }
        });
      }
    } else {
      console.log("无法切换线路");
    }
  }

  function observeLineChanges() {
    const lineSelector = document.querySelector(".YccudlUCmLKcUTg_yzKN");
    if (lineSelector) {
      const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
          if (
            mutation.type === "attributes" &&
            mutation.attributeName === "class"
          ) {
            const currentLineIndex = Array.from(
              lineSelector.children
            ).findIndex((li) => li.classList.contains("fG2r2piYghHTQKQZF8bl"));
            updateLiveSettingsPanel();
          }
        });
      });

      observer.observe(lineSelector, {
        attributes: true,
        subtree: true,
        attributeFilter: ["class"],
      });
    }
  }

  function updateLiveSettingsPanel() {
    const panel = document.getElementById("bilibili-live-quality-selector");
    if (panel && typeof panel.updatePanel === "function") {
      panel.updatePanel();
    }
  }

  function toggleSettingsPanel() {
    let panel = document.getElementById("bilibili-quality-selector");
    if (!panel) {
      createSettingsPanel();
      panel = document.getElementById("bilibili-quality-selector");
    }
    panel.classList.toggle("show");
    updateQualityButtons(panel);
  }

  function toggleLiveSettingsPanel() {
    let panel = document.getElementById("bilibili-live-quality-selector");
    if (!panel) {
      createLiveSettingsPanel();
      panel = document.getElementById("bilibili-live-quality-selector");
    }
    panel.classList.toggle("show");
    updateLiveSettingsPanel();
  }

  document.addEventListener("mousedown", function (event) {
    const panel = document.getElementById("bilibili-quality-selector");
    const livePanel = document.getElementById("bilibili-live-quality-selector");
    const devPanel = document.getElementById("bilibili-dev-settings");

    if (panel && !panel.contains(event.target) && panel.classList.contains("show")) {
        panel.classList.remove("show");
    }
    if (livePanel && !livePanel.contains(event.target) && livePanel.classList.contains("show")) {
        livePanel.classList.remove("show");
    }
    if (devPanel && !devPanel.contains(event.target) && devPanel.classList.contains("show")) {
        devPanel.classList.remove("show");
    }
  });

  GM_registerMenuCommand("设置画质和音质", () => {
    checkIfLivePage();
    if (isLivePage) {
      toggleLiveSettingsPanel();
    } else {
      toggleSettingsPanel();
    }
  });

  GM_registerMenuCommand("开发者选项", toggleDevSettingsPanel);

  window.addEventListener("load", () => {
    if (isLivePage) {
      observeLineChanges();
    }
  });

  window.onload = function () {
    checkIfLivePage();
    if (isLivePage) {
      selectLiveQuality().then(() => {
        createLiveSettingsPanel();
      });
    } else {
      let hasElementAppeared = false;
      isLoading = true;
      const observer = new MutationObserver(function (mutations, me) {
        const element = document.querySelector(
          ".v-popover-wrap.header-avatar-wrap"
        );
        if (element) {
          hasElementAppeared = true;
          console.log("正在判断用户是否为会员...");
          setTimeout(() => {
            isLoading = false;
            checkVipStatus();
            selectVideoQuality();
            updateQualityButtons(
              document.getElementById("bilibili-quality-selector")
            );
          }, devModeEnabled ? devModeDelay : 4000);
          console.log(`脚本开始运行,${devModeEnabled ? devModeDelay/1000 : 4}秒后切换画质`);
          me.disconnect();
        }
      });

      observer.observe(document.body, {
        childList: true,
        subtree: true,
      });

      setTimeout(function () {
        observer.disconnect();
        if (!hasElementAppeared) {
          console.error("等待超时,尝试执行中...");
          isLoading = false;
          checkVipStatus();
          selectVideoQuality();
          updateQualityButtons(
            document.getElementById("bilibili-quality-selector")
          );
        }
      }, 12000);
    }
  };

  const parentElement = document.body;

  parentElement.addEventListener("click", function (event) {
    const targetElement = event.target;

    if (!isLivePage) {
      if (targetElement.tagName === "DIV" || targetElement.tagName === "P") {
        if (
          targetElement.hasAttribute("title") ||
          targetElement.classList.contains("title")
        ) {
          isLoading = true;
          updateQualityButtons(
            document.getElementById("bilibili-quality-selector")
          );
          setTimeout(() => {
            isLoading = false;
            checkVipStatus();
            selectQualityBasedOnSetting();
            updateQualityButtons(
              document.getElementById("bilibili-quality-selector")
            );
          }, devModeEnabled ? devModeDelay : 5000);
          console.log(
            "视频标题点击事件,页面发生切换:",
            targetElement.textContent.trim()
          );
        }
      }

      if (targetElement.classList.contains("b-img")) {
        isLoading = true;
        updateQualityButtons(
          document.getElementById("bilibili-quality-selector")
        );
        setTimeout(() => {
          isLoading = false;
          checkVipStatus();
          selectQualityBasedOnSetting();
          updateQualityButtons(
            document.getElementById("bilibili-quality-selector")
          );
        }, devModeEnabled ? devModeDelay : 5000);
        console.log("封面点击事件,页面发生切换");
      }
    }
  });

  function createDevSettingsPanel() {
    const panel = document.createElement("div");
    panel.id = "bilibili-dev-settings";

    panel.innerHTML = `
        <h2>开发者设置</h2>
        <div class="dev-warning">以下选项的错误配置可能会影响脚本正常工作</div>
        <div class="toggle-switch">
            <label for="dev-mode">开发者模式</label>
            <label class="switch">
                <input type="checkbox" id="dev-mode" ${devModeEnabled ? 'checked' : ''}>
                <span class="slider"></span>
            </label>
        </div>
        <div class="toggle-switch">
            <label for="dev-vip">模拟大会员状态</label>
            <label class="switch">
                <input type="checkbox" id="dev-vip" ${devModeVipStatus ? 'checked' : ''} ${!devModeEnabled ? 'disabled' : ''}>
                <span class="slider"></span>
            </label>
        </div>
        <div class="toggle-switch">
            <label for="dev-ua">禁用 UA 修改</label>
            <label class="switch">
                <input type="checkbox" id="dev-ua" ${devModeDisableUA ? 'checked' : ''} ${!devModeEnabled ? 'disabled' : ''}>
                <span class="slider"></span>
            </label>
        </div>
        <div class="input-group">
            <label for="dev-delay">执行延迟 (毫秒)</label>
            <input type="number" id="dev-delay" value="${devModeDelay}" min="0" max="10000" step="100" ${!devModeEnabled ? 'disabled' : ''}>
        </div>
        <div id="dev-warning" class="warning" style="display: none;"></div>
    `;

    document.body.appendChild(panel);

    panel.querySelector('#dev-mode').addEventListener('change', (e) => {
        devModeEnabled = e.target.checked;
        GM_setValue("devModeEnabled", devModeEnabled);

        const vipSwitch = panel.querySelector('#dev-vip');
        const uaSwitch = panel.querySelector('#dev-ua');
        const delayInput = panel.querySelector('#dev-delay');
        const warning = panel.querySelector('#dev-warning');

        vipSwitch.disabled = !devModeEnabled;
        uaSwitch.disabled = !devModeEnabled;
        delayInput.disabled = !devModeEnabled;

        if (!devModeEnabled) {
            vipStatusChecked = false;
            checkVipStatus();
            if (devModeDisableUA) {
                warning.textContent = "开发者模式已关闭,UA 修改将在刷新页面后恢复";
                warning.style.display = "block";
            }
        } else {
            warning.style.display = "none";
        }
    });

    panel.querySelector('#dev-vip').addEventListener('change', (e) => {
        devModeVipStatus = e.target.checked;
        GM_setValue("devModeVipStatus", devModeVipStatus);
        if (devModeEnabled) {
            isVipUser = devModeVipStatus;
            vipStatusChecked = true;
            updateQualityButtons(document.getElementById("bilibili-quality-selector"));
        }
    });

    panel.querySelector('#dev-ua').addEventListener('change', (e) => {
        devModeDisableUA = e.target.checked;
        GM_setValue("devModeDisableUA", devModeDisableUA);
        const warning = panel.querySelector('#dev-warning');
        warning.textContent = devModeDisableUA ?
            "UA 修改已禁用,请刷新页面生效" :
            "UA 修改已启用,请刷新页面生效";
        warning.style.display = "block";
    });

    panel.querySelector('#dev-delay').addEventListener('change', (e) => {
        devModeDelay = parseInt(e.target.value);
        GM_setValue("devModeDelay", devModeDelay);
    });

    return panel;
  }

  function toggleDevSettingsPanel() {
    let panel = document.getElementById("bilibili-dev-settings");
    if (!panel) {
        panel = createDevSettingsPanel();
    }
    panel.classList.toggle("show");
  }
})();