SDVX难度表功能增强

在SDVX难度表中查看歌曲详情时添加追加日、物量、雷达图,信息来自sdvxindex.com

// ==UserScript==
// @name         SDVX难度表功能增强
// @namespace    fun.sanhei
// @version      2024-11-08
// @description  在SDVX难度表中查看歌曲详情时添加追加日、物量、雷达图,信息来自sdvxindex.com
// @author       Sanhei
// @match        sdvx.maya2silence.com
// @include      *://sdvx.maya2silence.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=maya2silence.com
// @grant        GM_xmlhttpRequest
// @connect      sdvxindex.com
// @license MIT
// ==/UserScript==

(function () {
  "use strict";
  // 使用 GM_xmlhttpRequest 请求跨域数据
  GM_xmlhttpRequest({
    method: "GET",
    url: "https://sdvxindex.com/js/data.js",
    onload: function (response) {
      if (response.status === 200) {
        // 移除声明并解析 JSON 数据
        const text = response.responseText
          .replace("const songs = ", "")
          .replace(/;\s*$/, "");
        const jsonData = JSON.parse(text);

        // 查找 modal_body 元素并观察其变化
        const modalBody = document.querySelector("#modal_body");
        if (modalBody) {
          const observer = new MutationObserver((mutationsList) => {
            for (const mutation of mutationsList) {
              if (mutation.type === "childList") {
                // 检查是否有新的 .chartinfo_body 元素被添加
                mutation.addedNodes.forEach((node) => {
                  if (
                    node.nodeType === 1 &&
                    node.classList.contains("modal-content")
                  ) {
                    const chartinfoBody =
                      document.querySelector(".chartinfo_body");
                    const jacket = document.querySelector("#chartinfo_jacket");
                    // 修改展示的信息
                    proc(chartinfoBody, jacket, jsonData);
                  }
                });
              }
            }
          });

          // 监听 modal_body 子元素变化
          observer.observe(modalBody, { childList: true, subtree: true });
        }
      } else {
        console.error("Failed to fetch data.js:", response.statusText);
      }
    },
    onerror: function (error) {
      console.error("Error fetching data.js:", error);
    },
  });

  function proc(chartinfoBody, jacket, songsData) {
    // 查找包含 'タイトル' 文本的 dt 元素并找到后面的 dd 元素
    const dtElements = chartinfoBody.querySelectorAll("dt");
    let titleElement = null;

    // 获取难度类型
    const diffTypeMap = {
      EXH: "exhaust",
      MXM: "maximum",
      INF: "infinite",
      GRV: "gravity",
      HVN: "heavenly",
      VVD: "vivid",
      XCD: "exceed",
    };
    const diffType = jacket ? jacket.getAttribute("data-diff_type") : null;
    const difficulty = diffTypeMap[diffType];

    dtElements.forEach((dt) => {
      if (dt.textContent.trim() === "タイトル") {
        titleElement = dt.nextElementSibling;
      }
    });

    if (titleElement) {
      const titleText = titleElement.innerText.trim();
      const matchingSong = songsData.find((song) => song.title === titleText);
      if (matchingSong) {
        // 查找 "ノーツ数" 的 dt 元素
        let notesElement = null;
        dtElements.forEach((dt) => {
          if (dt.textContent.trim() === "ノーツ数") {
            notesElement = dt.nextElementSibling;
          }
        });

        if (notesElement && difficulty) {
          const difficultyData = matchingSong.difficulties.find(
            (d) => d.type === difficulty
          );
          if (difficultyData) {
            notesElement.textContent = difficultyData.max_chain;

            // 创建一个雷达图
            const radarCanvas = document.createElement("canvas");
            radarCanvas.width = 150;
            radarCanvas.height = 150;

            // 设置雷达图的样式,覆盖在 jacket 元素上
            radarCanvas.style.position = "absolute";
            radarCanvas.style.top = "50%";
            radarCanvas.style.left = "50%";
            radarCanvas.style.transform = "translate(-50%, -50%)";
            radarCanvas.style.pointerEvents = "none";

            jacket.appendChild(radarCanvas);

            const radarData = [
              difficultyData.radar.notes,
              difficultyData.radar.peak,
              difficultyData.radar.tsumami,
              difficultyData.radar.tricky,
              difficultyData.radar.handtrip,
              difficultyData.radar.onehand,
            ];

            drawRadarChart(radarCanvas, radarData);
          }
        }

        const dateAddedLabel = document.createElement("dt");
        dateAddedLabel.textContent = "追加日";
        const dateAddedValue = document.createElement("dd");
        dateAddedValue.textContent = matchingSong.date;

        chartinfoBody.appendChild(dateAddedLabel);
        chartinfoBody.appendChild(dateAddedValue);
      }
    }
  }

  function drawRadarChart(canvas, data) {
    const ctx = canvas.getContext("2d");
    const centerX = canvas.width / 2;
    const centerY = canvas.height / 2;
    const radius = 75;
    const halfRadius = radius / 2;
    const maxDataValue = 200;
    const angleOffset = Math.PI / 3;

    // Draw the outer hexagon (full radius)
    ctx.beginPath();
    for (let i = 0; i < 6; i++) {
      const angle = i * angleOffset - Math.PI / 2;
      const x = centerX + radius * Math.cos(angle);
      const y = centerY + radius * Math.sin(angle);
      if (i === 0) {
        ctx.moveTo(x, y);
      } else {
        ctx.lineTo(x, y);
      }
    }
    ctx.closePath();
    ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
    ctx.fill();
    ctx.strokeStyle = "#ddd";
    ctx.stroke();

    // Draw the inner hexagon (half radius)
    ctx.beginPath();
    for (let i = 0; i < 6; i++) {
      const angle = i * angleOffset - Math.PI / 2;
      const x = centerX + halfRadius * Math.cos(angle);
      const y = centerY + halfRadius * Math.sin(angle);
      if (i === 0) {
        ctx.moveTo(x, y);
      } else {
        ctx.lineTo(x, y);
      }
    }
    ctx.closePath();
    ctx.strokeStyle = "#bbb";
    ctx.stroke();

    // Draw the radar data (hexagon filled)
    ctx.beginPath();
    for (let i = 0; i < data.length; i++) {
      const angle = i * angleOffset - Math.PI / 2;
      const valueRadius = (data[i] / maxDataValue) * radius;
      const x = centerX + valueRadius * Math.cos(angle);
      const y = centerY + valueRadius * Math.sin(angle);
      if (i === 0) {
        ctx.moveTo(x, y);
      } else {
        ctx.lineTo(x, y);
      }
    }
    ctx.closePath();
    ctx.fillStyle = "rgba(0, 128, 255, 0.8)";
    ctx.fill();
    ctx.strokeStyle = "#44aaff";
    ctx.stroke();
  }
})();