您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
增强MAA作业站的筛选功能
// ==UserScript== // @name MaaCopilotPlus // @namespace https://github.com/HauKuen // @license MIT // @version 1.6 // @description 增强MAA作业站的筛选功能 // @author haukuen // @match https://prts.plus/* // @match https://zoot.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); // 添加一个安全的查询函数 function safeQuerySelector(selector, parent = document) { try { return parent.querySelector(selector); } catch (e) { console.warn(`查询选择器失败: ${selector}`, e); return null; } } function safeQuerySelectorAll(selector, parent = document) { try { return parent.querySelectorAll(selector); } catch (e) { console.warn(`查询选择器失败: ${selector}`, e); return []; } } // 创建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(filteredCount) { try { const status = document.getElementById("maa-status"); if (status) { let statusText = `已导入 ${myOperators.length} 个角色`; if (filterEnabled) { statusText += filteredCount !== undefined ? `, 筛选掉 ${filteredCount} 个不符合条件的攻略` : " (筛选已启用)"; } else { statusText += " (筛选已禁用)"; } status.textContent = statusText; status.style.color = filterEnabled ? "green" : "gray"; } } catch (e) { console.warn("更新状态显示失败:", e); } } // 导入角色对话框 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() { try { if (myOperators.length === 0) { console.warn("未导入角色列表"); return; } if (!filterEnabled) { return; } // 更新选择器以匹配新的卡片结构 const guideCards = safeQuerySelectorAll( 'div[class*="bp4-card"]' ); if (!guideCards.length) { console.warn("未找到攻略卡片"); return; } let filteredCount = 0; guideCards.forEach((card) => { try { // 更新干员标签选择器 const operatorTags = safeQuerySelectorAll( 'div:has(> div.text-sm.text-zinc-600) .bp4-tag span.bp4-fill', card ); if (!operatorTags.length) return; // 重置高亮样式 operatorTags.forEach(tag => { const tagElement = tag.closest('.bp4-tag'); if (!tagElement) return; 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.closest('.bp4-tag'); if (tagElement) { 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 = ""; } } catch (e) { console.warn("处理卡片时出错:", e); } }); updateStatus(filteredCount); } catch (e) { console.error("筛选功能出错:", e); } } // 重置筛选 function resetFilter() { try { // 获取所有攻略卡片 const guideCards = safeQuerySelectorAll( 'div[class*="bp4-card"]' ); guideCards.forEach((card) => { // 恢复显示 card.style.display = ""; // 重置所有干员标签的样式 const operatorTags = safeQuerySelectorAll( 'div:has(> div.text-sm.text-zinc-600) .bp4-tag', card ); operatorTags.forEach(tag => { tag.style.color = ""; tag.style.backgroundColor = ""; tag.style.border = ""; tag.style.transition = ""; tag.style.boxShadow = ""; tag.onmouseover = null; tag.onmouseout = null; tag.title = ""; }); }); // 更新状态 updateStatus(); } catch (e) { console.warn("重置筛选时出错:", e); } } let lastUrl = location.href; // MutationObserver 监视DOM变化 const observer = new MutationObserver((mutations) => { try { // URL 变化检查 if (lastUrl !== location.href) { lastUrl = location.href; setTimeout(() => { try { if (filterEnabled && myOperators.length > 0) { filterGuides(); } } catch (e) { console.warn("URL变化后筛选失败:", e); } }, 1000); return; // URL 变化时直接返回,避免重复处理 } // 检查攻略列表变化 for (const mutation of mutations) { // 只处理节点添加的情况 if (mutation.type === "childList" && mutation.addedNodes.length > 0) { // 检查是否有新的攻略卡片添加 const hasNewCards = Array.from(mutation.addedNodes).some(node => { return node.nodeType === 1 && // 元素节点 (node.classList.contains('bp4-card') || // 直接是卡片 node.querySelector('div[class*="bp4-card"]')); // 包含卡片 }); if (hasNewCards) { // 给一点延时确保 DOM 完全更新 setTimeout(() => { try { if (filterEnabled && myOperators.length > 0) { console.log("检测到新的攻略卡片,重新应用筛选"); filterGuides(); } } catch (e) { console.warn("处理新卡片时出错:", e); } }, 200); break; // 找到新卡片后退出循环 } } } // 移除广告 try { removeAds(); } catch (e) { console.warn("移除广告失败:", e); } } catch (e) { console.error("Observer错误:", e); } }); // 修改观察配置,增加更多细节监听 observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'style'] // 只监听类名和样式变化 }); const removeAds = () => { // 移除侧边广告 const sideAd = safeQuerySelector( "body > main > div > div:nth-child(2) > div > div:nth-child(2) > div > a" ); if (sideAd) { sideAd.style.display = "none"; } // 移除所有广告链接 const adSelectors = [ 'a[href*="gad.netease.com"]', 'a[href*="ldmnq.com"]', 'a[href*="ldy/ldymuban"]', 'a[class*="block relative"]' ]; adSelectors.forEach(selector => { safeQuerySelectorAll(selector).forEach(ad => { ad.style.display = "none"; }); }); }; // 等待页面加载完成 window.addEventListener("load", () => { createUI(); removeAds(); // 观察DOM变化 observer.observe(document.body, { childList: true, subtree: true }); // 如果已有角色列表且筛选功能已启用,自动筛选 if (filterEnabled && myOperators.length > 0) { setTimeout(filterGuides, 1000); } }); })();