增强MAA作业站的筛选功能
当前为
// ==UserScript==
// @name MaaCopilotPlus
// @namespace https://github.com/HauKuen
// @license MIT
// @version 1.4
// @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,
// 根据精英化等级计算已解锁的最大技能
maxSkill: op.elite === 0 ? 1 : op.elite === 1 ? 2 : 3
}));
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"
);
// 重置高亮样式
operatorTags.forEach(tag => {
const tagElement = tag.parentElement;
tagElement.style.color = "";
tagElement.style.backgroundColor = "";
tagElement.style.border = "";
tagElement.style.transition = "";
tagElement.onmouseover = null;
tagElement.onmouseout = null;
tagElement.title = "";
});
let missingOperators = 0;
let lastMissingTag = null;
operatorTags.forEach((tag) => {
const operatorText = tag.textContent.trim();
if (operatorText.match(/^\[.*\]$/)) return;
const [operatorName, skillText] = operatorText.split(" ");
const skillNumber = skillText ? parseInt(skillText.replace(/[^0-9]/g, "")) : 1;
const operator = myOperators.find(op => op.name === operatorName);
if (!operator || skillNumber > operator.maxSkill) {
missingOperators++;
if (missingOperators === 1) {
lastMissingTag = tag;
}
}
});
// 高亮缺失的一个干员
if (allowOneMissing && missingOperators === 1 && lastMissingTag) {
const tagElement = lastMissingTag.parentElement;
tagElement.style.backgroundColor = "rgba(59, 130, 246, 0.1)";
tagElement.style.color = "#2563eb";
tagElement.style.border = "1px solid rgba(59, 130, 246, 0.5)";
tagElement.style.transition = "all 0.2s ease";
tagElement.title = "缺少此干员/精英化等级不足";
}
// 判断是否隐藏卡片
const shouldHide = (!allowOneMissing && missingOperators > 0) ||
(allowOneMissing && missingOperators > 1);
if (shouldHide) {
card.style.display = "none";
filteredCount++;
} else {
card.style.display = "";
}
});
// 更新状态
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;
}
}
}
// 我嘞个广告啊
const removeAds = () => {
const sideAd = document.querySelector(
"body > main > div > div:nth-child(2) > div > div:nth-child(2) > div > a"
);
if (sideAd) {
sideAd.style.display = "none";
}
// 移除其他可能的 mumu 广告容器
document.querySelectorAll('a[href*="gad.netease.com"]').forEach(ad => {
ad.style.display = "none";
});
};
// 立即执行一次广告移除
removeAds();
});
// 等待页面加载完成
window.addEventListener("load", () => {
createUI();
const removeAds = () => {
const sideAd = document.querySelector(
"body > main > div > div:nth-child(2) > div > div:nth-child(2) > div > a"
);
if (sideAd) {
sideAd.style.display = "none";
}
document.querySelectorAll('a[href*="gad.netease.com"]').forEach(ad => {
ad.style.display = "none";
});
};
removeAds();
// 观察DOM变化
observer.observe(document.body, { childList: true, subtree: true });
// 如果已有角色列表且筛选功能已启用,自动筛选
if (filterEnabled && myOperators.length > 0) {
setTimeout(filterGuides, 1000);
}
});
})();