您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
批量选中Magic Eden上的NFT项目,支持部分选中、取消选择和智能刷新
// ==UserScript== // @name Magic Eden批量选择器 // @namespace https://x.com/NotevenDe // @version 3.1 // @description 批量选中Magic Eden上的NFT项目,支持部分选中、取消选择和智能刷新 // @author Not // @match https://magiceden.io/* // @match https://*.magiceden.io/* // @grant none // ==/UserScript== (function() { 'use strict'; let currentCollectionName = ''; let autoRefreshEnabled = true; let isDragging = false; let dragOffset = { x: 0, y: 0 }; // 创建悬浮控制面板的样式 const panelStyle = ` position: fixed; top: 50%; right: 20px; transform: translateY(-50%); z-index: 10000; background: linear-gradient(145deg, #3b82f6 0%, #1d4ed8 100%); border: 2px solid rgba(255, 255, 255, 0.3); border-radius: 20px; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; box-shadow: 0 12px 48px rgba(59, 130, 246, 0.25), 0 4px 20px rgba(59, 130, 246, 0.15); min-width: 260px; max-width: 320px; transition: all 0.3s ease; cursor: move; backdrop-filter: blur(15px); overflow: hidden; `; const headerStyle = ` background: linear-gradient(135deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0.1) 100%); padding: 16px 20px 12px 20px; border-bottom: 1px solid rgba(255, 255, 255, 0.15); cursor: move; user-select: none; `; const contentStyle = ` background: linear-gradient(145deg, #ffffff 0%, #f8fafc 100%); padding: 20px; border-radius: 0 0 20px 20px; `; const buttonStyle = ` width: 100%; border: none; border-radius: 12px; padding: 12px 16px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.3s ease; margin-top: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); `; const primaryButtonStyle = ` ${buttonStyle} background: linear-gradient(135deg, #3b82f6 0%, #1e40af 100%); color: white; border: 1px solid rgba(59, 130, 246, 0.3); `; const secondaryButtonStyle = ` ${buttonStyle} background: linear-gradient(135deg, #0ea5e9 0%, #0369a1 100%); color: white; border: 1px solid rgba(14, 165, 233, 0.3); `; const dangerButtonStyle = ` ${buttonStyle} background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); color: white; border: 1px solid rgba(239, 68, 68, 0.3); `; const sliderStyle = ` width: 100%; height: 8px; border-radius: 4px; background: linear-gradient(to right, #e2e8f0 0%, #cbd5e0 100%); outline: none; opacity: 0.9; transition: opacity 0.2s; margin: 12px 0; cursor: pointer; border: 1px solid rgba(59, 130, 246, 0.2); `; const sliderThumbStyle = ` input[type="range"]::-webkit-slider-thumb { appearance: none; width: 24px; height: 24px; border-radius: 50%; background: linear-gradient(135deg, #3b82f6 0%, #1e40af 100%); cursor: pointer; box-shadow: 0 3px 12px rgba(59, 130, 246, 0.4), 0 1px 4px rgba(59, 130, 246, 0.2); transition: all 0.2s ease; border: 2px solid white; } input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.1); box-shadow: 0 4px 16px rgba(59, 130, 246, 0.6), 0 2px 8px rgba(59, 130, 246, 0.3); } input[type="range"]::-moz-range-thumb { width: 24px; height: 24px; border-radius: 50%; background: linear-gradient(135deg, #3b82f6 0%, #1e40af 100%); cursor: pointer; border: 2px solid white; box-shadow: 0 3px 12px rgba(59, 130, 246, 0.4); } .dragging { cursor: grabbing !important; transform: rotate(2deg); box-shadow: 0 15px 50px rgba(59, 130, 246, 0.3), 0 8px 25px rgba(59, 130, 246, 0.2); } `; // 添加CSS样式到页面 function addCustomStyles() { const style = document.createElement('style'); style.textContent = sliderThumbStyle; document.head.appendChild(style); } // 获取NFT集合名称 function getCollectionName() { const pathMatch = window.location.pathname.match(/\/ordinals\/marketplace\/([^\/]+)/); if (pathMatch) { return pathMatch[1]; } const linkElement = document.querySelector('a[href*="/ordinals/marketplace/"] span'); if (linkElement) { return linkElement.textContent.trim(); } return 'Unknown Collection'; } // 创建拖拽功能 function makeDraggable(panel) { const header = panel.querySelector('#panel-header'); header.addEventListener('mousedown', (e) => { isDragging = true; panel.classList.add('dragging'); const rect = panel.getBoundingClientRect(); dragOffset.x = e.clientX - rect.left; dragOffset.y = e.clientY - rect.top; document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); e.preventDefault(); }); function onMouseMove(e) { if (!isDragging) return; const x = e.clientX - dragOffset.x; const y = e.clientY - dragOffset.y; // 限制在窗口范围内 const maxX = window.innerWidth - panel.offsetWidth; const maxY = window.innerHeight - panel.offsetHeight; const constrainedX = Math.max(0, Math.min(x, maxX)); const constrainedY = Math.max(0, Math.min(y, maxY)); panel.style.left = constrainedX + 'px'; panel.style.top = constrainedY + 'px'; panel.style.right = 'auto'; panel.style.transform = 'none'; } function onMouseUp() { isDragging = false; panel.classList.remove('dragging'); document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); } } // 创建控制面板 function createControlPanel() { const panel = document.createElement('div'); panel.id = 'magic-eden-control-panel'; panel.style.cssText = panelStyle; panel.innerHTML = ` <div id="panel-header" style="${headerStyle}"> <div style="display: flex; align-items: center; justify-content: space-between;"> <div> <h3 style="margin: 0; color: white; font-size: 16px; font-weight: 700;">NFT批量选择器</h3> <div id="collection-name" style="font-size: 12px; color: rgba(255,255,255,0.8); margin-top: 4px; word-break: break-word;"> ${currentCollectionName} </div> </div> <a href="https://x.com/NotevenDe" target="_blank" rel="noopener" style="color: rgba(255,255,255,0.9); text-decoration: none; transition: all 0.2s ease; margin-left: 12px;" onmouseover="this.style.color='white'; this.style.transform='scale(1.1)'" onmouseout="this.style.color='rgba(255,255,255,0.9)'; this.style.transform='scale(1)'"> <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"> <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/> </svg> </a> </div> </div> <div style="${contentStyle}"> <div style="margin-bottom: 15px;"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;"> <span style="color: #1e40af; font-size: 14px; font-weight: 500;">总数量</span> <span id="total-count" style="color: #1e3a8a; font-weight: 700; font-size: 16px;">0</span> </div> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;"> <span style="color: #1e40af; font-size: 14px; font-weight: 500;">可添加</span> <span id="available-count" style="color: #0ea5e9; font-weight: 700; font-size: 16px;">0</span> </div> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;"> <span style="color: #1e40af; font-size: 14px; font-weight: 500;">已选择</span> <span id="selected-count" style="color: #ef4444; font-weight: 700; font-size: 16px;">0</span> </div> </div> <div style="margin-bottom: 15px;"> <label style="color: #1e40af; font-size: 13px; font-weight: 500; display: block; margin-bottom: 8px;"> 选择比例: <span id="percentage" style="color: #3b82f6; font-weight: 700;">0%</span> </label> <input type="range" id="selection-slider" min="0" max="100" value="0" style="${sliderStyle}"> <div style="display: flex; justify-content: space-between; font-size: 11px; color: #64748b; margin-top: 4px; font-weight: 500;"> <span>0%</span> <span>50%</span> <span>100%</span> </div> </div> <div style="display: flex; flex-direction: column; gap: 6px;"> <button id="refresh-count" style="${secondaryButtonStyle}"> 🔄 刷新统计 </button> <div style="display: flex; gap: 8px;"> <button id="bulk-select" style="${primaryButtonStyle} flex: 1;" disabled> ➕ 选择 </button> <button id="bulk-deselect" style="${dangerButtonStyle} flex: 1;" disabled> ➖ 取消 </button> </div> <div style="display: flex; gap: 8px; align-items: center; justify-content: center; margin-top: 8px;"> <label style="display: flex; align-items: center; font-size: 13px; color: #1e40af; cursor: pointer; font-weight: 500;"> <input type="checkbox" id="auto-refresh" checked style="margin-right: 8px; width: 16px; height: 16px; accent-color: #3b82f6;"> 自动刷新 </label> </div> </div> <div id="status-message" style="margin-top: 12px; font-size: 12px; text-align: center; color: #1e40af; min-height: 16px; font-weight: 500; padding: 8px; background: linear-gradient(135deg, rgba(59, 130, 246, 0.1) 0%, rgba(147, 197, 253, 0.15) 100%); border-radius: 8px; border: 1px solid rgba(59, 130, 246, 0.2);"> 点击"刷新统计"开始 </div> </div> `; document.body.appendChild(panel); makeDraggable(panel); return panel; } // 获取所有NFT项目的添加按钮(未选择的) function getAddButtons() { const buttons = document.querySelectorAll('button[data-test-id=""]'); return Array.from(buttons).filter(button => { const svg = button.querySelector('svg'); if (!svg) return false; const lines = svg.querySelectorAll('line'); if (lines.length !== 2) return false; const line1 = lines[0]; const line2 = lines[1]; return (line1.getAttribute('x1') === '12' && line1.getAttribute('x2') === '12' && line2.getAttribute('y1') === '12' && line2.getAttribute('y2') === '12'); }); } // 获取所有取消选择按钮(已选择的) function getRemoveButtons() { const selectedButtons = document.querySelectorAll('button[data-test-id="item-selected"]'); return Array.from(selectedButtons).filter(button => { const svg = button.querySelector('svg'); if (!svg) return false; const polyline = svg.querySelector('polyline'); if (polyline) { const points = polyline.getAttribute('points'); return points && points.includes('20 6 9 17 4 12'); } return false; }); } // 获取所有NFT项目 function getAllNFTItems() { return document.querySelectorAll('[data-index]'); } // 更新统计信息 function updateStats() { const addButtons = getAddButtons(); const removeButtons = getRemoveButtons(); const totalItems = getAllNFTItems().length; const availableCount = addButtons.length; const selectedCount = removeButtons.length; // 更新集合名称 currentCollectionName = getCollectionName(); const collectionElement = document.getElementById('collection-name'); if (collectionElement) { collectionElement.textContent = currentCollectionName; } // 更新计数 document.getElementById('total-count').textContent = totalItems; document.getElementById('available-count').textContent = availableCount; document.getElementById('selected-count').textContent = selectedCount; const slider = document.getElementById('selection-slider'); const selectCount = Math.floor(availableCount * (slider.value / 100)); document.getElementById('percentage').textContent = slider.value + '%'; // 更新按钮状态 const bulkSelectBtn = document.getElementById('bulk-select'); const bulkDeselectBtn = document.getElementById('bulk-deselect'); if (availableCount > 0) { bulkSelectBtn.disabled = false; bulkSelectBtn.textContent = selectCount > 0 ? `➕ 添加 ${selectCount}` : '➕ 添加 0'; } else { bulkSelectBtn.disabled = true; bulkSelectBtn.textContent = '➕ 无可添加'; } if (selectedCount > 0) { bulkDeselectBtn.disabled = false; bulkDeselectBtn.textContent = `➖ 取消 ${selectedCount}`; } else { bulkDeselectBtn.disabled = true; bulkDeselectBtn.textContent = '➖ 无已选择'; } // 更新状态消息 let statusMessage = ''; if (totalItems === 0) { statusMessage = '未找到NFT项目'; } else if (availableCount === 0 && selectedCount === 0) { statusMessage = '正在加载NFT数据...'; } else { statusMessage = `${currentCollectionName} - 可添加${availableCount}个,已选${selectedCount}个`; } document.getElementById('status-message').textContent = statusMessage; return { totalItems, availableCount, selectedCount, selectCount }; } // 刷新统计 function refreshCount() { const refreshBtn = document.getElementById('refresh-count'); const originalText = refreshBtn.textContent; refreshBtn.textContent = '🔄 统计中...'; refreshBtn.disabled = true; setTimeout(() => { const stats = updateStats(); refreshBtn.textContent = originalText; refreshBtn.disabled = false; console.log('统计更新:', stats); }, 500); } // 随机选择指定数量的按钮 function getRandomButtons(buttons, count) { if (count >= buttons.length) return buttons; const shuffled = [...buttons].sort(() => 0.5 - Math.random()); return shuffled.slice(0, count); } // 批量选择 function bulkSelect() { const addButtons = getAddButtons(); const slider = document.getElementById('selection-slider'); const selectCount = Math.floor(addButtons.length * (slider.value / 100)); if (selectCount === 0) { document.getElementById('status-message').textContent = '请选择要添加的数量'; return; } const confirmed = confirm(`确定要添加 ${selectCount}/${addButtons.length} 个NFT项目吗?`); if (!confirmed) return; executeOperation(getRandomButtons(addButtons, selectCount), '添加', '➕'); } // 批量取消选择 function bulkDeselect() { const removeButtons = getRemoveButtons(); if (removeButtons.length === 0) { document.getElementById('status-message').textContent = '没有已选择的NFT'; return; } const confirmed = confirm(`确定要取消选择所有 ${removeButtons.length} 个NFT项目吗?`); if (!confirmed) return; executeOperation(removeButtons, '取消', '➖'); } // 快速批量执行(优化版) function executeOperation(buttons, actionName, icon) { const bulkSelectBtn = document.getElementById('bulk-select'); const bulkDeselectBtn = document.getElementById('bulk-deselect'); const originalSelectText = bulkSelectBtn.textContent; const originalDeselectText = bulkDeselectBtn.textContent; bulkSelectBtn.disabled = true; bulkDeselectBtn.disabled = true; bulkSelectBtn.textContent = `${icon} ${actionName}中...`; let completedCount = 0; const totalCount = buttons.length; // 快速批量模式:分批并发执行 const batchSize = 8; // 每批8个,平衡速度和稳定性 let currentBatch = 0; function processBatch() { const startIndex = currentBatch * batchSize; const endIndex = Math.min(startIndex + batchSize, totalCount); const currentButtons = buttons.slice(startIndex, endIndex); // 并发点击当前批次的按钮 currentButtons.forEach((button, index) => { setTimeout(() => { try { // 模拟鼠标悬停 const container = button.closest('.group'); if (container) { container.classList.add('hover'); } button.click(); completedCount++; const progress = Math.round((completedCount / totalCount) * 100); bulkSelectBtn.textContent = `${icon} ${actionName}中 ${progress}%`; document.getElementById('status-message').textContent = `快速${actionName}中... ${completedCount}/${totalCount}`; // 如果所有操作完成 if (completedCount >= totalCount) { bulkSelectBtn.textContent = `${icon} ${actionName}完成`; document.getElementById('status-message').textContent = `完成!快速${actionName}了 ${completedCount} 个NFT`; setTimeout(() => { bulkSelectBtn.textContent = originalSelectText; bulkDeselectBtn.textContent = originalDeselectText; refreshCount(); // 自动刷新统计 }, 1500); } } catch (error) { console.error(`${actionName}失败:`, error); completedCount++; // 确保进度继续 } }, index * 15); // 每个按钮间隔15ms,快速执行 }); currentBatch++; // 如果还有剩余批次,继续处理 if (currentBatch * batchSize < totalCount) { setTimeout(processBatch, 80); // 批次间隔80ms } } processBatch(); } // 自动刷新功能 function setupAutoRefresh() { let lastStats = { total: 0, available: 0, selected: 0 }; setInterval(() => { if (!autoRefreshEnabled) return; const panel = document.getElementById('magic-eden-control-panel'); if (!panel) return; const addButtons = getAddButtons(); const removeButtons = getRemoveButtons(); const totalItems = getAllNFTItems().length; const currentStats = { total: totalItems, available: addButtons.length, selected: removeButtons.length }; if (currentStats.total !== lastStats.total || currentStats.available !== lastStats.available || currentStats.selected !== lastStats.selected) { lastStats = currentStats; updateStats(); console.log('自动刷新: NFT状态变化', currentStats); } }, 2000); } // 初始化事件监听器 function initEventListeners() { const slider = document.getElementById('selection-slider'); slider.addEventListener('input', updateStats); document.getElementById('refresh-count').addEventListener('click', refreshCount); document.getElementById('bulk-select').addEventListener('click', bulkSelect); document.getElementById('bulk-deselect').addEventListener('click', bulkDeselect); const autoRefreshCheckbox = document.getElementById('auto-refresh'); autoRefreshCheckbox.addEventListener('change', (e) => { autoRefreshEnabled = e.target.checked; console.log('自动刷新:', autoRefreshEnabled ? '开启' : '关闭'); }); } // 等待页面加载完成 function waitForPageLoad() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initPlugin); } else { initPlugin(); } } // 初始化插件 function initPlugin() { const isNFTListPage = document.querySelector('[data-test-id="grid-container"]') !== null; if (!isNFTListPage) { return; } setTimeout(() => { if (document.getElementById('magic-eden-control-panel')) { return; } currentCollectionName = getCollectionName(); addCustomStyles(); createControlPanel(); initEventListeners(); setupAutoRefresh(); setTimeout(refreshCount, 1000); console.log('Magic Eden批量选择器已加载 - 优化版'); }, 1000); } // 监听页面变化 function observePageChanges() { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'childList') { const hasGridContainer = document.querySelector('[data-test-id="grid-container"]'); const hasPanel = document.getElementById('magic-eden-control-panel'); if (hasGridContainer && !hasPanel) { setTimeout(initPlugin, 500); } else if (!hasGridContainer && hasPanel) { hasPanel.remove(); } } }); }); observer.observe(document.body, { childList: true, subtree: true }); } // 启动插件 waitForPageLoad(); observePageChanges(); })();