// ==UserScript== // @name 游戏链接文字控制器 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 精确控制游戏界面中链接的显示文字,修复误隐藏问题,提供可收起面板和一键复原功能 // @author YourName // @match https://dld.qzapp.z.qq.com/qpet/cgi-bin/phonepk?* // @include https://dld.qzapp.z.qq.com/qpet/cgi-bin/phonepk?zapp_uin=&B_UID=0&sid=&channel=0&g_ut=1&cmd=index // @include https://dld.qzapp.z.qq.com/qpet/cgi-bin/phonepk?cmd=index&channel=0 // @include https://dld.qzapp.z.qq.com/qpet/cgi-bin/phonepk?zapp_uin=&sid=&channel=0&g_ut=1&cmd=index // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @run-at document-end // ==/UserScript== (function() { 'use strict'; // 添加样式 GM_addStyle(` .link-control-panel { position: fixed; top: 0; right: 0; background: rgba(30, 30, 30, 0.95); border: 1px solid #555; border-radius: 0 0 0 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.5); z-index: 99999; color: #eee; font-family: 'Arial', sans-serif; width: 300px; display: flex; flex-direction: column; max-height: 100vh; } .link-control-panel.collapsed { width: 24px; height: 24px; overflow: hidden; padding: 0; border-radius: 0 0 0 24px; background: rgba(30, 30, 30, 0.7); border: 1px solid #555; } .panel-header { padding: 8px 12px; background: rgba(40, 40, 40, 0.9); display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; } .panel-title { font-weight: bold; font-size: 14px; color: #4CAF50; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .toggle-collapse { background: none; border: none; color: #eee; font-size: 16px; cursor: pointer; padding: 0 6px; min-width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; } .panel-content { display: flex; flex-direction: column; height: 100%; overflow: hidden; } .search-container { padding: 8px 12px; background: rgba(40, 40, 40, 0.9); flex-shrink: 0; } .search-box { width: 100%; padding: 8px 10px; background: rgba(0, 0, 0, 0.3); border: 1px solid #555; border-radius: 4px; color: #eee; font-size: 13px; } .links-container { flex-grow: 1; overflow-y: auto; padding: 0 12px; } .link-item { display: flex; align-items: center; margin: 6px 0; padding: 8px; background: rgba(255, 255, 255, 0.08); border-radius: 4px; font-size: 13px; } .link-checkbox { margin-right: 8px; cursor: pointer; width: 16px; height: 16px; } .link-label { flex-grow: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: pointer; } .action-buttons { padding: 8px 12px; background: rgba(40, 40, 40, 0.9); display: flex; flex-direction: column; gap: 6px; flex-shrink: 0; } .action-btn { background: #4CAF50; color: white; border: none; padding: 8px; border-radius: 4px; cursor: pointer; font-size: 13px; text-align: center; } .restore-btn { background: #f39c12; } .link-control-hidden { visibility: hidden !important; position: absolute !important; width: 1px !important; height: 1px !important; padding: 0 !important; margin: -1px !important; overflow: hidden !important; clip: rect(0, 0, 0, 0) !important; white-space: nowrap !important; border: 0 !important; } .collapsed .panel-content { display: none; } .collapsed .toggle-collapse { position: absolute; top: 0; right: 0; width: 24px; height: 24px; } `); class LinkTextController { constructor() { this.panel = null; this.isCollapsed = false; this.links = []; this.hiddenLinks = {}; this.controlledLinks = new WeakMap(); this.init(); } init() { this.createPanel(); this.loadSavedState(); setTimeout(() => this.scanLinks(), 1500); this.setupMutationObserver(); } createPanel() { this.panel = document.createElement('div'); this.panel.className = 'link-control-panel'; // Header const header = document.createElement('div'); header.className = 'panel-header'; this.panelTitle = document.createElement('div'); this.panelTitle.className = 'panel-title'; this.panelTitle.textContent = '链接控制'; this.collapseBtn = document.createElement('button'); this.collapseBtn.className = 'toggle-collapse'; this.collapseBtn.innerHTML = '×'; this.collapseBtn.title = '收起/展开'; this.collapseBtn.addEventListener('click', () => this.toggleCollapse()); header.appendChild(this.panelTitle); header.appendChild(this.collapseBtn); this.panel.appendChild(header); // Content const content = document.createElement('div'); content.className = 'panel-content'; // Search const searchContainer = document.createElement('div'); searchContainer.className = 'search-container'; this.searchBox = document.createElement('input'); this.searchBox.className = 'search-box'; this.searchBox.type = 'text'; this.searchBox.placeholder = '搜索链接...'; this.searchBox.addEventListener('input', () => this.filterLinks()); searchContainer.appendChild(this.searchBox); content.appendChild(searchContainer); // Links this.linksContainer = document.createElement('div'); this.linksContainer.className = 'links-container'; content.appendChild(this.linksContainer); // Buttons const buttonsContainer = document.createElement('div'); buttonsContainer.className = 'action-buttons'; this.rescanBtn = document.createElement('button'); this.rescanBtn.className = 'action-btn'; this.rescanBtn.textContent = '重新扫描'; this.rescanBtn.addEventListener('click', () => this.scanLinks()); this.restoreBtn = document.createElement('button'); this.restoreBtn.className = 'action-btn restore-btn'; this.restoreBtn.textContent = '恢复所有'; this.restoreBtn.addEventListener('click', () => this.restoreAllLinks()); buttonsContainer.appendChild(this.rescanBtn); buttonsContainer.appendChild(this.restoreBtn); content.appendChild(buttonsContainer); this.panel.appendChild(content); document.body.appendChild(this.panel); } toggleCollapse() { this.isCollapsed = !this.isCollapsed; this.panel.classList.toggle('collapsed', this.isCollapsed); this.collapseBtn.innerHTML = this.isCollapsed ? '☰' : '×'; GM_setValue('panelCollapsed', this.isCollapsed); } loadSavedState() { try { this.hiddenLinks = GM_getValue('hiddenLinks', {}); this.isCollapsed = GM_getValue('panelCollapsed', false); if (this.isCollapsed) { this.panel.classList.add('collapsed'); this.collapseBtn.innerHTML = '☰'; } } catch (e) { console.error('加载保存状态失败:', e); } } setupMutationObserver() { const observer = new MutationObserver((mutations) => { let needsRescan = false; mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.nodeType === 1 && (node.tagName === 'A' || node.querySelector('a'))) { needsRescan = true; } }); }); if (needsRescan) { this.safeRescanLinks(); } }); observer.observe(document.body, { childList: true, subtree: true, attributes: false, characterData: false }); } safeRescanLinks() { const hiddenStates = new Map(); this.links.forEach(link => { if (this.hiddenLinks[link.selector]) { hiddenStates.set(link.element, true); } }); this.scanLinks(); this.links.forEach(link => { if (hiddenStates.has(link.element)) { this.toggleLinkText(link.selector, false); } }); } scanLinks() { this.linksContainer.innerHTML = ''; this.links = []; const allLinks = document.querySelectorAll('a'); allLinks.forEach(link => { if (link.textContent.trim() && this.isVisible(link)) { const linkText = link.textContent.trim(); const selector = this.generateUniqueSelector(link); const isHidden = this.hiddenLinks[selector]; const item = document.createElement('div'); item.className = 'link-item'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.className = 'link-checkbox'; checkbox.checked = !isHidden; checkbox.dataset.selector = selector; const label = document.createElement('label'); label.className = 'link-label'; label.textContent = linkText; label.title = `${linkText} (${selector})`; item.appendChild(checkbox); item.appendChild(label); this.linksContainer.appendChild(item); this.links.push({ element: link, selector: selector, originalHTML: link.innerHTML, textContent: linkText, controlItem: item }); if (isHidden) { link.classList.add('link-control-hidden'); } checkbox.addEventListener('change', () => { this.toggleLinkText(selector, checkbox.checked); }); label.addEventListener('click', () => { checkbox.checked = !checkbox.checked; checkbox.dispatchEvent(new Event('change')); }); } }); } generateUniqueSelector(link) { if (link.id) return `#${link.id}`; const path = []; let current = link; let index = 0; while (current && current !== document.body && index < 10) { let selector = current.tagName.toLowerCase(); if (current.className && typeof current.className === 'string') { const classes = current.className.split(/\s+/) .filter(c => c && c.length < 20) .join('.'); if (classes) selector += `.${classes}`; } const siblings = Array.from(current.parentNode.children) .filter(child => child.tagName === current.tagName); if (siblings.length > 1) { const position = siblings.indexOf(current) + 1; selector += `:nth-child(${position})`; const textContent = current.textContent.trim(); if (textContent && textContent.length < 30) { selector += `[text="${textContent.replace(/"/g, '\\"')}"]`; } } path.unshift(selector); current = current.parentNode; index++; } return path.join(' > '); } toggleLinkText(selector, show) { this.links.forEach(link => { if (link.selector === selector && link.element.isConnected) { if (show) { link.element.classList.remove('link-control-hidden'); delete this.hiddenLinks[selector]; } else { link.element.classList.add('link-control-hidden'); this.hiddenLinks[selector] = true; } this.controlledLinks.set(link.element, true); GM_setValue('hiddenLinks', this.hiddenLinks); } }); } filterLinks() { const term = this.searchBox.value.toLowerCase(); this.links.forEach(link => { const text = link.textContent.toLowerCase(); link.controlItem.style.display = text.includes(term) ? 'flex' : 'none'; }); } restoreAllLinks() { this.links.forEach(link => { link.element.classList.remove('link-control-hidden'); }); this.hiddenLinks = {}; GM_setValue('hiddenLinks', {}); this.linksContainer.querySelectorAll('.link-checkbox').forEach(checkbox => { checkbox.checked = true; }); this.showTemporaryMessage('所有链接已恢复', 2000); } showTemporaryMessage(message, duration) { const msg = document.createElement('div'); msg.textContent = message; msg.style.position = 'fixed'; msg.style.top = '60px'; msg.style.right = '30px'; msg.style.background = '#4CAF50'; msg.style.color = 'white'; msg.style.padding = '8px 16px'; msg.style.borderRadius = '4px'; msg.style.zIndex = '99999'; msg.style.fontSize = '13px'; document.body.appendChild(msg); setTimeout(() => { msg.style.transition = 'opacity 0.3s'; msg.style.opacity = '0'; setTimeout(() => msg.remove(), 300); }, duration); } isVisible(el) { const style = window.getComputedStyle(el); return style.display !== 'none' && style.visibility !== 'hidden' && (el.offsetWidth > 0 || el.offsetHeight > 0); } } new LinkTextController(); })();