您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
统计GMGN任意代币前排地址的数据,让数字来说话!新增首次记录和涨跌提醒功能,所有数字可点击查看详情,弹框显示净流入数据,负数红色显示,点击外部关闭
// ==UserScript== // @name GMGN 前排统计 // @namespace http://tampermonkey.net/ // @version 4.6 // @description 统计GMGN任意代币前排地址的数据,让数字来说话!新增首次记录和涨跌提醒功能,所有数字可点击查看详情,弹框显示净流入数据,负数红色显示,点击外部关闭 // @match https://gmgn.ai/* // @match https://www.gmgn.ai/* // @require https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js // @require https://code.jquery.com/jquery-3.6.0.min.js // @grant none // @run-at document-start // @license MIT // ==/UserScript== (function() { 'use strict'; // 动态添加 CSS const style = document.createElement('style'); style.textContent = ` .statistic-gmgn-stats-container { background-color: transparent; border-radius: 4px; font-family: Arial, sans-serif; margin-right: 8px; margin-bottom:8px; border: 1px solid #333; /* 精细的右侧和下侧发光效果 */ box-shadow: 2px 2px 4px rgba(0, 119, 255, 0.6), /* 右下外发光(更小的偏移和模糊) */ 1px 1px 2px rgba(0, 119, 255, 0.4), /* 精细的次级发光 */ inset 0 0 3px rgba(0, 119, 255, 0.2); /* 更细腻的内发光 */ padding: 4px 6px; max-width: fit-content; } .statistic-gmgn-stats-header, .statistic-gmgn-stats-data { display: grid; grid-template-columns: repeat(9, 1fr); text-align: center; gap: 6px; font-weight: normal; font-size: 13px; } .statistic-gmgn-stats-header.sol-network, .statistic-gmgn-stats-data.sol-network { grid-template-columns: repeat(10, minmax(auto, 1fr)); gap: 4px; font-size: 12px; } .statistic-gmgn-stats-header span { color: #ccc; font-weight: normal; padding: 1px 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .statistic-gmgn-stats-header.sol-network span { font-size: 11px; padding: 1px; } .statistic-gmgn-stats-data span { color: #00ff00; font-weight: normal; cursor: default; transition: all 0.2s ease; padding: 1px 3px; border-radius: 2px; min-width: 0; white-space: nowrap; } .statistic-gmgn-stats-data span.clickable { cursor: pointer; } .statistic-gmgn-stats-data span.clickable:hover { background-color: rgba(0, 255, 0, 0.1); border-radius: 3px; transform: scale(1.03); } .statistic-gmgn-stats-data.sol-network span { padding: 1px 2px; font-size: 12px; } .statistic-gmgn-stats-data span .statistic-up-arrow, .statistic-up-arrow { color: green !important; margin-left: 2px; font-weight: bold; } .statistic-gmgn-stats-data span .statistic-down-arrow, .statistic-down-arrow { color: red !important; margin-left: 2px; font-weight: bold; } /* 完整弹框CSS样式 - 从token_holding_temp.js复制并添加statistic前缀 */ .statistic-gmgn-modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; z-index: 1000; } .statistic-gmgn-modal-content { background-color: #1e293b !important; border-radius: 8px !important; width: 80% !important; max-width: 800px !important; max-height: 80vh !important; overflow-y: auto !important; padding: 20px !important; color: white !important; position: fixed !important; top: 50% !important; left: 50% !important; transform: translate(-50%, -50%) !important; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5) !important; margin: 0 !important; z-index: 100000 !important; box-sizing: border-box !important; min-height: auto !important; min-width: 300px !important; pointer-events: auto !important; } .statistic-gmgn-modal-header { display: flex !important; justify-content: space-between !important; align-items: center !important; margin-bottom: 16px !important; padding: 0 !important; } .statistic-gmgn-modal-title { font-size: 18px !important; font-weight: 600 !important; color: white !important; margin: 0 !important; } .statistic-gmgn-modal-close { background: none !important; border: none !important; color: #94a3b8 !important; font-size: 20px !important; cursor: pointer !important; padding: 5px !important; line-height: 1 !important; width: auto !important; height: auto !important; min-width: 30px !important; min-height: 30px !important; } .statistic-gmgn-modal-close:hover { color: #ff4444 !important; background-color: rgba(255, 255, 255, 0.1) !important; border-radius: 4px !important; } .statistic-gmgn-result-item { background-color: #334155; border-radius: 6px; padding: 12px; margin-bottom: 12px; } .statistic-gmgn-analysis-summary { margin-bottom: 16px; padding: 12px; background-color: #263238; border-radius: 6px; display: flex; justify-content: space-between; align-items: center; } .statistic-gmgn-summary-stats { display: flex; gap: 20px; } .statistic-gmgn-stat-item { display: flex; align-items: baseline; } .statistic-gmgn-stat-label { color: #94a3b8; margin-right: 5px; } .statistic-gmgn-stat-value { font-weight: 600; color: #3b82f6; } .statistic-gmgn-result-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; flex-wrap: wrap; gap: 8px; } .statistic-gmgn-result-rank { font-size: 14px; color: #94a3b8; font-weight: 600; min-width: 30px; } .statistic-gmgn-result-address { font-weight: 600; word-break: break-all; cursor: pointer; padding: 4px 8px; border-radius: 4px; transition: all 0.2s ease; background-color: #475569; flex: 1; min-width: 200px; color: #00ff88; font-family: monospace; } .statistic-gmgn-result-address:hover { background-color: #64748b; transform: translateY(-1px); } .statistic-gmgn-detail-section { margin-bottom: 12px; } .statistic-gmgn-section-title { font-size: 13px; font-weight: 600; color: #94a3b8; margin-bottom: 8px; } .statistic-gmgn-detail-grid { display: grid; grid-template-columns: 80px 1fr 80px 1fr; gap: 4px 8px; align-items: start; font-size: 12px; } .statistic-gmgn-detail-label { color: #94a3b8; font-size: 12px; padding: 2px 0; align-self: start; } .statistic-gmgn-detail-value { font-size: 12px; color: #e2e8f0; padding: 2px 0; word-break: break-word; line-height: 1.4; } .statistic-gmgn-value-highlight { color: #3b82f6; font-weight: 600; } .statistic-gmgn-compact-details .statistic-gmgn-detail-section { margin-bottom: 8px; } .statistic-gmgn-compact-details .statistic-gmgn-detail-section { margin-left: 10px; } .statistic-gmgn-address-jump-btn { background-color: #10b981; color: white; padding: 4px 8px; border-radius: 6px; font-size: 11px; font-weight: 500; margin-left: 8px; cursor: pointer; transition: all 0.2s ease; text-decoration: none; display: inline-block; border: none; } .statistic-gmgn-address-jump-btn:hover { background-color: #059669; transform: translateY(-1px); box-shadow: 0 2px 4px rgba(16, 185, 129, 0.3); } .statistic-gmgn-profit-positive { color: #00ff88 !important; } .statistic-gmgn-profit-negative { color: #ff4444 !important; } .statistic-gmgn-empty-message { text-align: center; color: #ccc; padding: 20px; margin: 0; } .statistic-gmgn-stats-info { text-align: center !important; margin-bottom: 15px !important; padding: 10px !important; background: rgba(0, 119, 255, 0.1) !important; border-radius: 8px !important; border: 1px solid rgba(0, 119, 255, 0.3) !important; color: #fff !important; font-size: 14px !important; } .statistic-gmgn-export-btn { background-color: #10b981 !important; color: white !important; border: none !important; padding: 8px 16px !important; border-radius: 6px !important; font-size: 12px !important; font-weight: 500 !important; cursor: pointer !important; transition: all 0.2s ease !important; display: flex !important; align-items: center !important; gap: 4px !important; } .statistic-gmgn-export-btn:hover { background-color: #059669 !important; transform: translateY(-1px) !important; box-shadow: 0 2px 4px rgba(16, 185, 129, 0.3) !important; } `; document.head.appendChild(style); // 存储拦截到的数据 let interceptedData = null; // 存储首次加载的数据 let initialStats = null; // 标记是否是首次加载 let isFirstLoad = true; // 新增存储当前CA地址 let currentCaAddress = null; // 存储首次加载的CA地址 let initialCaAddress = null; // 检查当前网络是否为SOL function isSolNetwork() { const url = window.location.href; return url.includes('/sol/') || url.includes('gmgn.ai/sol'); } // 优化后的弹框管理函数 function createModal(title, data, caAddress, showSolBalance = false) { // 移除已存在的弹框 const existingModal = document.querySelector('.statistic-gmgn-modal'); if (existingModal) { existingModal.remove(); } // 1. 数据预处理和排序 - 按收益率排序 const processedData = data .sort((a, b) => (b.profit_change || 0) - (a.profit_change || 0)) // 按收益率排序 .map((holder, index) => { const baseData = { rank: index + 1, address: holder.address, balance: formatNumber(holder.balance), usdValue: formatNumber(holder.usd_value), netflowUsd: formatNumber(holder.netflow_usd), netflowClass: (holder.netflow_usd || 0) >= 0 ? 'statistic-gmgn-profit-positive' : 'statistic-gmgn-profit-negative', profit: formatNumber(holder.profit), profitSign: holder.profit >= 0 ? '+' : '', profitClass: holder.profit >= 0 ? 'statistic-gmgn-profit-positive' : 'statistic-gmgn-profit-negative', profitChange: holder.profit_change ? (holder.profit_change * 100).toFixed(1) + '%' : 'N/A', profitChangeClass: (holder.profit_change || 0) >= 0 ? 'statistic-gmgn-profit-positive' : 'statistic-gmgn-profit-negative' }; // 只有在需要显示SOL余额时才添加 if (showSolBalance) { baseData.solBalance = holder.native_balance ? ((holder.native_balance / 1000000000).toFixed(2) + ' SOL') : 'N/A'; } return baseData; }); // 2. 创建弹框基础结构 - 使用token_holding_temp.js的DOM结构 const modal = document.createElement('div'); modal.className = 'statistic-gmgn-modal'; modal.innerHTML = ` <div class="statistic-gmgn-modal-content"> <div class="statistic-gmgn-modal-header"> <div class="statistic-gmgn-modal-title">📊 ${title} (${processedData.length}个地址)</div> <button class="statistic-gmgn-modal-close">×</button> </div> <div class="statistic-gmgn-analysis-summary"> <div class="statistic-gmgn-summary-stats"> <div class="statistic-gmgn-stat-item"> <span class="statistic-gmgn-stat-label">CA地址:</span> <span class="statistic-gmgn-stat-value">${caAddress || 'N/A'}</span> </div> <div class="statistic-gmgn-stat-item"> <span class="statistic-gmgn-stat-label">总数量:</span> <span class="statistic-gmgn-stat-value">${processedData.length}</span> </div> </div> <button id="statistic-export-excel-btn" class="statistic-gmgn-export-btn" title="导出Excel">📊 导出Excel</button> </div> <div id="statistic-gmgn-results-list"></div> </div> `; // 3. 插入DOM document.body.appendChild(modal); // 4. 填充结果列表 - 参考token_holding_temp.js的方式 const resultsList = document.getElementById('statistic-gmgn-results-list'); processedData.forEach((holder, index) => { const item = document.createElement('div'); item.className = 'statistic-gmgn-result-item'; item.innerHTML = ` <div class="statistic-gmgn-result-header"> <div class="statistic-gmgn-result-rank">#${holder.rank}</div> <div class="statistic-gmgn-result-address" title="点击复制地址">${holder.address}</div> <a href="https://gmgn.ai/sol/address/${holder.address}" target="_blank" class="statistic-gmgn-address-jump-btn" title="查看钱包详情">详情</a> </div> <div class="statistic-gmgn-compact-details"> <div class="statistic-gmgn-detail-section"> <div class="statistic-gmgn-section-title">基本信息</div> <div class="statistic-gmgn-detail-grid"> <span class="statistic-gmgn-detail-label">持仓:</span> <span class="statistic-gmgn-detail-value">${holder.balance}</span> <span class="statistic-gmgn-detail-label">净流入:</span> <span class="statistic-gmgn-detail-value ${holder.netflowClass}">$${holder.netflowUsd}</span> <span class="statistic-gmgn-detail-label">盈亏:</span> <span class="statistic-gmgn-detail-value ${holder.profitClass}">${holder.profitSign}$${holder.profit}</span> <span class="statistic-gmgn-detail-label">倍数:</span> <span class="statistic-gmgn-detail-value ${holder.profitChangeClass}">${holder.profitChange}</span> ${holder.solBalance ? ` <span class="statistic-gmgn-detail-label">SOL餘額:</span> <span class="statistic-gmgn-detail-value statistic-gmgn-value-highlight">${holder.solBalance}</span> ` : ''} </div> </div> </div> `; // 添加地址复制功能 const addressElement = item.querySelector('.statistic-gmgn-result-address'); addressElement.addEventListener('click', () => { navigator.clipboard.writeText(holder.address).then(() => { addressElement.style.backgroundColor = '#16a34a'; addressElement.style.color = 'white'; setTimeout(() => { addressElement.style.backgroundColor = ''; addressElement.style.color = ''; }, 1000); }); }); resultsList.appendChild(item); }); // ESC键关闭处理函数 const escKeyHandler = (e) => { if (e.key === 'Escape') { document.body.removeChild(modal); document.removeEventListener('keydown', escKeyHandler); } }; document.addEventListener('keydown', escKeyHandler); // 5. 绑定导出Excel按钮事件 const exportBtn = modal.querySelector('#statistic-export-excel-btn'); if (exportBtn) { exportBtn.addEventListener('click', () => { exportToExcel(processedData, title, caAddress, showSolBalance); }); } // 6. 绑定关闭按钮事件 modal.querySelector('.statistic-gmgn-modal-close').addEventListener('click', () => { document.body.removeChild(modal); document.removeEventListener('keydown', escKeyHandler); }); // 点击模态框外部关闭 modal.addEventListener('click', (e) => { if (e.target === modal) { document.body.removeChild(modal); document.removeEventListener('keydown', escKeyHandler); } }); } // 数字格式化函数 function formatNumber(num) { if (num === null || num === undefined) return 'N/A'; // 處理負數:保留負號,對絕對值進行格式化 const isNegative = num < 0; const absNum = Math.abs(num); let formatted; if (absNum >= 1000000000) { formatted = (absNum / 1000000000).toFixed(2) + 'B'; } else if (absNum >= 1000000) { formatted = (absNum / 1000000).toFixed(2) + 'M'; } else if (absNum >= 1000) { formatted = (absNum / 1000).toFixed(2) + 'K'; } else { formatted = absNum.toFixed(2); } return isNegative ? '-' + formatted : formatted; } // Excel导出功能 function exportToExcel(data, title, caAddress, showSolBalance) { try { // 创建工作表数据 const worksheetData = []; // 添加标题行 const headers = ['排名', '地址', '持仓数量', 'USD价值', '净流入USD', '盈亏USD', '盈亏倍数']; if (showSolBalance) { headers.push('SOL餘額'); } worksheetData.push(headers); // 添加数据行 data.forEach((holder, index) => { const row = [ holder.rank, holder.address, holder.balance, holder.usdValue, holder.netflowUsd, (holder.profitSign || '') + holder.profit, holder.profitChange ]; if (showSolBalance) { row.push(holder.solBalance || 'N/A'); } worksheetData.push(row); }); // 创建工作簿 const wb = XLSX.utils.book_new(); const ws = XLSX.utils.aoa_to_sheet(worksheetData); // 设置列宽 const colWidths = [ {wch: 6}, // 排名 {wch: 45}, // 地址 {wch: 15}, // 持仓数量 {wch: 15}, // USD价值 {wch: 15}, // 净流入 {wch: 15}, // 盈亏 {wch: 12} // 倍数 ]; if (showSolBalance) { colWidths.push({wch: 12}); // SOL餘額 } ws['!cols'] = colWidths; // 添加工作表到工作簿 XLSX.utils.book_append_sheet(wb, ws, title); // 生成文件名 const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-'); const fileName = `${title}_${caAddress ? caAddress.slice(0, 8) : 'data'}_${timestamp}.xlsx`; // 下载文件 XLSX.writeFile(wb, fileName); // 显示成功提示 const exportBtn = document.querySelector('#statistic-export-excel-btn'); if (exportBtn) { const originalText = exportBtn.textContent; exportBtn.textContent = '✅ 导出成功'; exportBtn.style.backgroundColor = '#059669'; setTimeout(() => { exportBtn.textContent = originalText; exportBtn.style.backgroundColor = ''; }, 2000); } } catch (error) { console.error('Excel导出失败:', error); alert('导出失败,请检查浏览器控制台了解详情'); } } // 根据类型获取对应的地址数据 function getAddressByType(type) { if (!interceptedData?.data?.list) return []; const currentTime = Math.floor(Date.now() / 1000); const sevenDaysInSeconds = 7 * 24 * 60 * 60; const holders = interceptedData.data.list; switch(type) { case 'fullPosition': return holders.filter(h => h.sell_amount_percentage === 0 && (!h.token_transfer_out || !h.token_transfer_out.address) ); case 'profitable': return holders.filter(h => h.profit > 0); case 'losing': return holders.filter(h => h.profit < 0); case 'active24h': return holders.filter(h => h.last_active_timestamp > currentTime - 86400); case 'diamondHands': return holders.filter(h => h.maker_token_tags?.includes('diamond_hands')); case 'newAddress': return holders.filter(h => h.tags?.includes('fresh_wallet')); case 'holdingLessThan7Days': return holders.filter(h => h.start_holding_at && (currentTime - h.start_holding_at) < sevenDaysInSeconds ); case 'highProfit': return holders.filter(h => h.profit_change > 5); case 'suspicious': return holders.filter(h => h.is_suspicious || (h.maker_token_tags && ( h.maker_token_tags.includes('rat_trader') || h.maker_token_tags.includes('transfer_in') )) ); case 'lowSolBalance': return holders.filter(h => h.native_balance && (h.native_balance / 1000000000) < 1 ); default: return []; } } // 获取类型对应的中文标题 function getTypeTitle(type) { const titles = { 'fullPosition': '满仓地址', 'profitable': '盈利地址', 'losing': '亏损地址', 'active24h': '24小时活跃地址', 'diamondHands': '钻石手地址', 'newAddress': '新地址', 'holdingLessThan7Days': '持仓小于7天的地址', 'highProfit': '5倍以上盈利地址', 'suspicious': '可疑地址', 'lowSolBalance': 'SOL餘額不足1的地址' }; return titles[type] || '未知类型'; } // 1. 拦截 fetch 请求 const originalFetch = window.fetch; window.fetch = function(url, options) { if (isTargetApi(url)) { console.log('[拦截] fetch 请求:', url); return originalFetch.apply(this, arguments) .then(response => { if (response.ok) { processResponse(response.clone()); } return response; }); } return originalFetch.apply(this, arguments); }; // 2. 拦截 XMLHttpRequest const originalXHR = window.XMLHttpRequest; window.XMLHttpRequest = function() { const xhr = new originalXHR(); const originalOpen = xhr.open; xhr.open = function(method, url) { if (isTargetApi(url)) { console.log('[拦截] XHR 请求:', url); const originalOnload = xhr.onload; xhr.onload = function() { if (xhr.readyState === 4 && xhr.status === 200) { processResponse(xhr.responseText); } originalOnload?.apply(this, arguments); }; } return originalOpen.apply(this, arguments); }; return xhr; }; function isTargetApi(url) { if (typeof url !== 'string') return false; const isTarget = /vas\/api\/v1\/token_holders\/(sol|eth|base|bsc|tron)(\/|$|\?)/i.test(url); if (isTarget) { // 从URL中提取CA地址 const match = url.match(/vas\/api\/v1\/token_holders\/sol\/([^/?]+)/i); console.log('匹配的ca:',match) if (match && match[1]) { currentCaAddress = match[1]; } } return isTarget; } function processResponse(response) { console.log('开始处理响应数据'); try { const dataPromise = typeof response === 'string' ? Promise.resolve(JSON.parse(response)) : response.json(); dataPromise.then(data => { interceptedData = data; console.log('[成功] 拦截到数据量:', data.data?.list?.length); console.log('[成功] 拦截到数据:',data); const currentStats = calculateStats(); if (isFirstLoad) { // 首次加载,记录初始数据和CA地址 initialStats = currentStats; initialCaAddress = currentCaAddress; isFirstLoad = false; updateStatsDisplay(currentStats, true); } else { // 非首次加载,比较CA地址 const isSameCa = currentCaAddress === initialCaAddress; updateStatsDisplay(currentStats, !isSameCa); // 如果CA地址不同,更新初始数据为当前数据 if (!isSameCa) { initialStats = currentStats; initialCaAddress = currentCaAddress; } } }).catch(e => console.error('解析失败:', e)); } catch (e) { console.error('处理响应错误:', e); } } // 3. 计算所有统计指标 function calculateStats() { if (!interceptedData?.data?.list) return null; const currentTime = Math.floor(Date.now() / 1000); const sevenDaysInSeconds = 7 * 24 * 60 * 60; // 7天的秒数 const holders = interceptedData.data.list; const stats = { fullPosition: 0, // 全仓 profitable: 0, // 盈利 losing: 0, // 亏损 active24h: 0, // 24h活跃 diamondHands: 0, // 钻石手 newAddress: 0, // 新地址 highProfit: 0, // 10x盈利 suspicious: 0, // 新增:可疑地址 holdingLessThan7Days: 0, // 新增:持仓小于7天 lowSolBalance: 0 // 新增:SOL餘額小於1的地址 }; holders.forEach(holder => { // 满判断条件:1.没有卖出;2.没有出货地址 if (holder.sell_amount_percentage === 0 && (!holder.token_transfer_out || !holder.token_transfer_out.address)) { stats.fullPosition++; } if (holder.profit > 0) stats.profitable++; if (holder.profit < 0) stats.losing++; if (holder.last_active_timestamp > currentTime - 86400) stats.active24h++; if (holder.maker_token_tags?.includes('diamond_hands')) stats.diamondHands++; if (holder.tags?.includes('fresh_wallet')) stats.newAddress++; if (holder.profit_change > 5) stats.highProfit++; // 增强版可疑地址检测 if ( holder.is_suspicious || (holder.maker_token_tags && ( holder.maker_token_tags.includes('rat_trader') || holder.maker_token_tags.includes('transfer_in') )) ) { stats.suspicious++; } // 新增7天持仓统计 if (holder.start_holding_at && (currentTime - holder.start_holding_at) < sevenDaysInSeconds) { stats.holdingLessThan7Days++; } // 新增低SOL餘額統計(小於1 SOL) if (holder.native_balance && (holder.native_balance / 1000000000) < 1) { stats.lowSolBalance++; } }); return stats; } // 1. 持久化容器监听 const observer = new MutationObserver(() => { const targetContainer = document.querySelector('.flex.overflow-x-auto.overflow-y-hidden.scroll-smooth.w-full'); if (targetContainer && !targetContainer.querySelector('#statistic-gmgn-stats-item')) { injectStatsItem(targetContainer); } }); function injectStatsItem(container) { if (container.querySelector('#statistic-gmgn-stats-item')) return; const isSol = isSolNetwork(); const statsItem = document.createElement('div'); statsItem.id = 'statistic-gmgn-stats-item'; statsItem.className = 'statistic-gmgn-stats-container'; const headerClass = isSol ? 'statistic-gmgn-stats-header sol-network' : 'statistic-gmgn-stats-header'; const dataClass = isSol ? 'statistic-gmgn-stats-data sol-network' : 'statistic-gmgn-stats-data'; statsItem.innerHTML = ` <div class="${headerClass}"> <span title="持有代币且未卖出任何数量的地址(排除转移代币卖出的地址)">满仓</span> <span title="当前持仓价值高于买入成本的地址">盈利</span> <span title="当前持仓价值低于买入成本的地址">亏损</span> <span title="过去24小时内有交易活动的地址">活跃</span> <span title="长期持有且很少卖出的地址">钻石</span> <span title="新钱包">新址</span> <span title="持仓时间小于7天的地址">7天</span> <span title="盈利超过5倍的地址">5X</span> <span title="标记为可疑或异常行为的地址">可疑</span> ${isSol ? '<span title="SOL餘額小於1的地址">低SOL</span>' : ''} </div> <div class="${dataClass}"> <span id="fullPosition">-</span> <span id="profitable">-</span> <span id="losing">-</span> <span id="active24h">-</span> <span id="diamondHands">-</span> <span id="newAddress">-</span> <span id="holdingLessThan7Days">-</span> <span id="highProfit">-</span> <span id="suspicious">-</span> ${isSol ? '<span id="lowSolBalance">-</span>' : ''} </div> `; container.insertAdjacentElement('afterbegin', statsItem); } function updateStatsDisplay(currentStats, forceNoArrows) { if (!currentStats) return; // 确保DOM已存在 if (!document.getElementById('statistic-gmgn-stats-item')) { injectStatsItem(); } const updateStatElement = (id, value, hasChanged, isIncrease) => { const element = document.getElementById(id); if (!element) return; element.innerHTML = `<strong style="color: ${id === 'profitable' ? '#2E8B57' : (id === 'losing' || id === 'suspicious' ? '#FF1493' : id === 'holdingLessThan7Days' ? '#00E5EE' : id === 'lowSolBalance' ? '#FFA500' : '#e9ecef')}">${value}</strong>`; // 只有当不是强制不显示箭头且确实有变化时才显示箭头 if (!forceNoArrows && hasChanged) { const arrow = document.createElement('span'); arrow.className = isIncrease ? 'statistic-up-arrow' : 'statistic-down-arrow'; arrow.textContent = isIncrease ? '▲' : '▼'; // 移除旧的箭头(如果有) const oldArrow = element.querySelector('.statistic-up-arrow, .statistic-down-arrow'); if (oldArrow) oldArrow.remove(); element.appendChild(arrow); } else { // 没有变化或强制不显示箭头,移除箭头(如果有) const oldArrow = element.querySelector('.statistic-up-arrow, .statistic-down-arrow'); if (oldArrow) oldArrow.remove(); } // 为所有统计类型添加点击事件监听器 const baseClickableTypes = ['fullPosition', 'profitable', 'losing', 'active24h', 'diamondHands', 'newAddress', 'holdingLessThan7Days', 'highProfit', 'suspicious']; const clickableTypes = isSolNetwork() ? [...baseClickableTypes, 'lowSolBalance'] : baseClickableTypes; if (clickableTypes.includes(id)) { element.classList.add('clickable'); element.onclick = (e) => { e.preventDefault(); e.stopPropagation(); const addresses = getAddressByType(id); const title = getTypeTitle(id); const showSolBalance = id === 'lowSolBalance'; createModal(title, addresses, currentCaAddress, showSolBalance); }; } else { // 其他类型移除点击样式和事件 element.classList.remove('clickable'); element.onclick = null; } }; // 更新各个统计指标 // 新增7天持仓统计更新 updateStatElement('holdingLessThan7Days', currentStats.holdingLessThan7Days, initialStats && currentStats.holdingLessThan7Days !== initialStats.holdingLessThan7Days, initialStats && currentStats.holdingLessThan7Days > initialStats.holdingLessThan7Days); updateStatElement('fullPosition', currentStats.fullPosition, initialStats && currentStats.fullPosition !== initialStats.fullPosition, initialStats && currentStats.fullPosition > initialStats.fullPosition); updateStatElement('profitable', currentStats.profitable, initialStats && currentStats.profitable !== initialStats.profitable, initialStats && currentStats.profitable > initialStats.profitable); updateStatElement('losing', currentStats.losing, currentStats.losing !== initialStats.losing, currentStats.losing > initialStats.losing); updateStatElement('active24h', currentStats.active24h, currentStats.active24h !== initialStats.active24h, currentStats.active24h > initialStats.active24h); updateStatElement('diamondHands', currentStats.diamondHands, currentStats.diamondHands !== initialStats.diamondHands, currentStats.diamondHands > initialStats.diamondHands); updateStatElement('newAddress', currentStats.newAddress, currentStats.newAddress !== initialStats.newAddress, currentStats.newAddress > initialStats.newAddress); updateStatElement('highProfit', currentStats.highProfit, currentStats.highProfit !== initialStats.highProfit, currentStats.highProfit > initialStats.highProfit); updateStatElement('suspicious', currentStats.suspicious, currentStats.suspicious !== initialStats.suspicious, currentStats.suspicious > initialStats.suspicious); // 只在SOL网络时更新低SOL余额统计 if (isSolNetwork()) { updateStatElement('lowSolBalance', currentStats.lowSolBalance, initialStats && currentStats.lowSolBalance !== initialStats.lowSolBalance, initialStats && currentStats.lowSolBalance > initialStats.lowSolBalance); } } // 4. 初始化 if (document.readyState === 'complete') { startObserving(); } else { window.addEventListener('DOMContentLoaded', startObserving); } function startObserving() { // 立即检查一次 const initialContainer = document.querySelector('.flex.overflow-x-auto.overflow-y-hidden.scroll-smooth.w-full'); if (initialContainer) injectStatsItem(initialContainer); // 持续监听DOM变化 observer.observe(document.body, { childList: true, subtree: true, attributes: false }); } })();