MaaCopilotPlus

增强MAA作业站的筛选功能

目前為 2025-02-28 提交的版本,檢視 最新版本

// ==UserScript==
// @name         MaaCopilotPlus
// @namespace    https://github.com/HauKuen
// @license MIT
// @version      1.1
// @description  增强MAA作业站的筛选功能
// @author       haukuen
// @match        https://prts.plus/*
// @icon         https://prts.plus/favicon-32x32.png?v=1
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function () {
  "use strict";

  // 初始化角色列表
  let myOperators = GM_getValue("myOperators", []);
  // 筛选开关状态
  let filterEnabled = GM_getValue("filterEnabled", true);
  // 允许缺少一个干员的设置
  let allowOneMissing = GM_getValue("allowOneMissing", false);

  // 创建UI
  function createUI() {
    // 创建插件控制面板
    const controlPanel = document.createElement("div");
    controlPanel.id = "maa-copilot-plus";
    controlPanel.style.position = "fixed";
    controlPanel.style.top = "10px";
    controlPanel.style.right = "10px";
    controlPanel.style.zIndex = "9999";
    controlPanel.style.backgroundColor = "#f0f0f0";
    controlPanel.style.padding = "10px";
    controlPanel.style.borderRadius = "5px";
    controlPanel.style.boxShadow = "0 0 10px rgba(0,0,0,0.2)";
    controlPanel.style.cursor = "move";

    // 添加拖拽功能
    let isDragging = false;
    let currentX;
    let currentY;
    let initialX;
    let initialY;

    controlPanel.addEventListener("mousedown", (e) => {
      isDragging = true;

      // 获取鼠标相对于面板的初始位置
      initialX = e.clientX - controlPanel.offsetLeft;
      initialY = e.clientY - controlPanel.offsetTop;

      controlPanel.style.opacity = "0.8";
      controlPanel.style.transition = "none";
    });

    document.addEventListener("mousemove", (e) => {
      if (isDragging) {
        e.preventDefault();

        // 计算新位置
        currentX = e.clientX - initialX;
        currentY = e.clientY - initialY;

        // 限制在窗口内
        const maxX = window.innerWidth - controlPanel.offsetWidth;
        const maxY = window.innerHeight - controlPanel.offsetHeight;

        currentX = Math.max(0, Math.min(currentX, maxX));
        currentY = Math.max(0, Math.min(currentY, maxY));

        // 更新位置
        controlPanel.style.left = currentX + "px";
        controlPanel.style.top = currentY + "px";
        controlPanel.style.right = "auto"; // 清除right属性以避免冲突
      }
    });

    document.addEventListener("mouseup", () => {
      if (isDragging) {
        isDragging = false;
        // 恢复正常样式
        controlPanel.style.opacity = "1";
        controlPanel.style.transition = "opacity 0.2s";
      }
    });

    // 保存面板位置到本地存储
    window.addEventListener("beforeunload", () => {
      if (controlPanel.style.left) {
        // 只有在面板被移动过时才保存
        GM_setValue("panelPosition", {
          left: controlPanel.style.left,
          top: controlPanel.style.top,
        });
      }
    });

    // 恢复上次保存的位置
    const savedPosition = GM_getValue("panelPosition", null);
    if (savedPosition) {
      controlPanel.style.left = savedPosition.left;
      controlPanel.style.top = savedPosition.top;
      controlPanel.style.right = "auto";
    }

    // 创建标题
    const title = document.createElement("h3");
    title.textContent = "MAA Copilot Plus";
    title.style.margin = "0 0 10px 0";
    title.style.cursor = "move"; // 标题也可以用来拖动

    const buttonContainer = document.createElement("div");
    buttonContainer.style.display = "flex";
    buttonContainer.style.marginBottom = "10px";

    const importButton = document.createElement("button");
    importButton.textContent = "导入角色列表";
    importButton.onclick = openImportDialog;

    buttonContainer.appendChild(importButton);

    // 创建开关容器
    const toggleContainer = document.createElement("div");
    toggleContainer.style.display = "flex";
    toggleContainer.style.alignItems = "center";
    toggleContainer.style.marginBottom = "10px";

    // 创建开关
    const toggleLabel = document.createElement("label");
    toggleLabel.style.display = "flex";
    toggleLabel.style.alignItems = "center";
    toggleLabel.style.cursor = "pointer";

    const toggleInput = document.createElement("input");
    toggleInput.type = "checkbox";
    toggleInput.checked = filterEnabled;
    toggleInput.style.margin = "0 5px 0 0";
    toggleInput.onchange = function () {
      filterEnabled = this.checked;
      GM_setValue("filterEnabled", filterEnabled);
      updateStatus();
      if (filterEnabled) {
        filterGuides();
      } else {
        resetFilter();
      }
    };

    const toggleText = document.createElement("span");
    toggleText.textContent = "启用筛选";

    toggleLabel.appendChild(toggleInput);
    toggleLabel.appendChild(toggleText);
    toggleContainer.appendChild(toggleLabel);

    // 创建允许缺少一个干员的设置
    const missingContainer = document.createElement("div");
    missingContainer.style.display = "flex";
    missingContainer.style.alignItems = "center";
    missingContainer.style.marginBottom = "10px";

    const missingLabel = document.createElement("label");
    missingLabel.style.display = "flex";
    missingLabel.style.alignItems = "center";
    missingLabel.style.cursor = "pointer";

    const missingInput = document.createElement("input");
    missingInput.type = "checkbox";
    missingInput.checked = allowOneMissing;
    missingInput.style.margin = "0 5px 0 0";
    missingInput.onchange = function () {
      allowOneMissing = this.checked;
      GM_setValue("allowOneMissing", allowOneMissing);
      if (filterEnabled) {
        filterGuides();
      }
    };

    const missingText = document.createElement("span");
    missingText.textContent = "允许缺少一个干员";

    missingLabel.appendChild(missingInput);
    missingLabel.appendChild(missingText);
    missingContainer.appendChild(missingLabel);



    // 创建状态显示
    const status = document.createElement("div");
    status.id = "maa-status";
    status.style.fontSize = "12px";

    // 组装控制面板
    controlPanel.appendChild(title);
    controlPanel.appendChild(buttonContainer);
    controlPanel.appendChild(toggleContainer);
    controlPanel.appendChild(missingContainer);
    controlPanel.appendChild(status);

    document.body.appendChild(controlPanel);

    // 初始化状态显示
    updateStatus();
  }

  // 更新状态显示
  function updateStatus() {
    const status = document.getElementById("maa-status");
    if (status) {
      let statusText = `已导入 ${myOperators.length} 个角色`;
      statusText += filterEnabled ? " (筛选已启用)" : " (筛选已禁用)";
      status.textContent = statusText;

      // 更新状态颜色
      status.style.color = filterEnabled ? "green" : "gray";
    }
  }

  // 导入角色对话框
  function openImportDialog() {
    // 创建模态对话框
    const modal = document.createElement("div");
    modal.style.position = "fixed";
    modal.style.top = "0";
    modal.style.left = "0";
    modal.style.width = "100%";
    modal.style.height = "100%";
    modal.style.backgroundColor = "rgba(0,0,0,0.5)";
    modal.style.display = "flex";
    modal.style.justifyContent = "center";
    modal.style.alignItems = "center";
    modal.style.zIndex = "10000";

    // 创建对话框内容
    const dialog = document.createElement("div");
    dialog.style.backgroundColor = "white";
    dialog.style.padding = "20px";
    dialog.style.borderRadius = "5px";
    dialog.style.width = "80%";
    dialog.style.maxWidth = "600px";
    dialog.style.maxHeight = "80%";
    dialog.style.overflow = "auto";

    // 创建标题
    const title = document.createElement("h3");
    title.textContent = "导入角色列表";
    title.style.marginTop = "0";

    // 创建文本区域
    const textarea = document.createElement("textarea");
    textarea.style.width = "100%";
    textarea.style.height = "200px";
    textarea.style.marginBottom = "10px";
    textarea.placeholder = "粘贴角色列表 JSON 数据...";

    // 创建按钮
    const buttonContainer = document.createElement("div");
    buttonContainer.style.display = "flex";
    buttonContainer.style.justifyContent = "flex-end";

    const cancelButton = document.createElement("button");
    cancelButton.textContent = "取消";
    cancelButton.style.marginRight = "10px";
    cancelButton.onclick = () => document.body.removeChild(modal);

    const importButton = document.createElement("button");
    importButton.textContent = "导入";
    importButton.onclick = () => {
      try {
        const data = JSON.parse(textarea.value);

        if (Array.isArray(data)) {
          myOperators = data
            .filter((op) => op.own)
            .map((op) => ({
              name: op.name,
              elite: op.elite,
              level: op.level,
              rarity: op.rarity,
            }));

          GM_setValue("myOperators", myOperators);
          updateStatus();

          document.body.removeChild(modal);

          // 导入成功后自动筛选(如果筛选功能已启用)
          if (filterEnabled) {
            filterGuides();
          }
        } else {
          alert("无效的数据格式,请确保是有效的 JSON 数组");
        }
      } catch (e) {
        alert("解析失败: " + e.message);
      }
    };

    buttonContainer.appendChild(cancelButton);
    buttonContainer.appendChild(importButton);

    // 组装对话框
    dialog.appendChild(title);
    dialog.appendChild(textarea);
    dialog.appendChild(buttonContainer);
    modal.appendChild(dialog);

    document.body.appendChild(modal);
  }

  // 筛选攻略
  function filterGuides() {
    if (myOperators.length === 0) {
      alert("请先导入角色列表");
      return;
    }

    if (!filterEnabled) {
      return;
    }

    // 获取所有攻略卡片
    const guideCards = document.querySelectorAll(
      "body > main > div > div:nth-child(2) > div > div:nth-child(1) > div:nth-child(2) > div > div"
    );
    let filteredCount = 0;

    guideCards.forEach((card) => {
      // 获取干员区域
      const operatorSection = card.querySelector(
        "div:has(> div.text-sm.text-zinc-600)"
      );
      if (!operatorSection) return;

      // 获取卡片中的干员标签,只获取干员区域内的标签
      const operatorTags = operatorSection.querySelectorAll(
        "span.bp4-tag > span.bp4-fill"
      );
      let missingOperators = 0;

      operatorTags.forEach((tag) => {
        const operatorText = tag.textContent.trim();

        // 检查是否为标签类型 (如 [奶盾])
        if (operatorText.match(/^\[.*\]$/)) {
          return; // 跳过标签类型
        }

        // 提取干员名和技能编号
        const [operatorName, skillNumber] = operatorText.split(" ");
        console.log(`提取干员: ${operatorName}, 技能: ${skillNumber}`);
        // 检查是否在我的干员列表中
        if (
          operatorName &&
          !myOperators.some((op) => op.name === operatorName)
        ) {
          missingOperators++;
          if (!allowOneMissing || missingOperators > 1) {
            // 如果不允许缺少干员,或缺少超过一个干员,则隐藏卡片
            card.style.display = "none";
            filteredCount++;
            return false;
          }
        }
      });

      // 显示卡片
      if (missingOperators === 0 || (allowOneMissing && missingOperators === 1)) {
        card.style.display = "";
      } else {
        card.style.display = "none";
        filteredCount++;
      }
    });

    // 更新状态
    const status = document.getElementById("maa-status");
    if (status) {
      status.textContent = `已导入 ${myOperators.length} 个干员,筛选掉 ${filteredCount} 个不符合条件的攻略 (筛选已启用)`;
      status.style.color = "green";
    }
  }

  // 重置筛选
  function resetFilter() {
    // 获取所有攻略卡片
    const guideCards = document.querySelectorAll(
      "body > main > div > div:nth-child(2) > div > div:nth-child(1) > div:nth-child(2) > div > div"
    );

    guideCards.forEach((card) => {
      // 恢复显示并移除高亮效果
      card.style.display = "";
      card.style.boxShadow = "";
    });

    // 更新状态
    updateStatus();
  }

  // 监听URL变化,用于在页面切换或搜索结果更新时重新筛选
  let lastUrl = location.href;

  // 创建一个MutationObserver来监视DOM变化
  const observer = new MutationObserver((mutations) => {
    // 检查URL是否变化
    if (lastUrl !== location.href) {
      lastUrl = location.href;

      // 给页面加载时间
      setTimeout(() => {
        if (filterEnabled && myOperators.length > 0) {
          filterGuides();
        }
      }, 1000);
    }

    // 检查是否有新的攻略卡片加载
    const guideContainer = document.querySelector(
      "body > main > div > div:nth-child(2) > div > div:nth-child(1) > div:nth-child(2) > div"
    );
    if (guideContainer) {
      for (const mutation of mutations) {
        if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
          // 如果有新节点添加,且筛选功能已启用,且有角色列表,重新筛选
          if (filterEnabled && myOperators.length > 0) {
            filterGuides();
          }
          break;
        }
      }
    }
  });

  // 等待页面加载完成
  window.addEventListener("load", () => {
    createUI();

    // 观察DOM变化
    observer.observe(document.body, { childList: true, subtree: true });

    // 如果已有角色列表且筛选功能已启用,自动筛选
    if (filterEnabled && myOperators.length > 0) {
      setTimeout(filterGuides, 1000);
    }
  });
})();