GMGN追踪者统计分析

监听gmgn.ai的持仓者API,提供追踪者数据分析和可视化

// ==UserScript==
// @name         GMGN追踪者统计分析
// @namespace    http://tampermonkey.net/
// @version      1.1.0
// @description  监听gmgn.ai的持仓者API,提供追踪者数据分析和可视化
// @author       Assistant
// @match        https://gmgn.ai/*
// @grant        none
// @run-at       document-start
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // 全局数据存储
    let followerData = null;
    let currentToken = null;
    let currentUrl = window.location.href;

    // 工具函数:格式化数字为K/M/B单位
    function formatNumber(num) {
        if (num === 0) return '$0';
        const absNum = Math.abs(num);
        const sign = num < 0 ? '-' : '';

        if (absNum >= 1e9) {
            return sign + '$' + (absNum / 1e9).toFixed(1) + 'B';
        } else if (absNum >= 1e6) {
            return sign + '$' + (absNum / 1e6).toFixed(1) + 'M';
        } else if (absNum >= 1e3) {
            return sign + '$' + (absNum / 1e3).toFixed(1) + 'K';
        }
        return sign + '$' + absNum.toFixed(2);
    }

    // 工具函数:格式化百分比
    function formatPercentage(num) {
        return (num * 100).toFixed(3) + '%';
    }

    // 数据管理器
    class FollowerDataManager {
        constructor() {
            this.data = null;
            this.stats = null;
        }

        setData(data) {
            this.data = data;
            this.calculateStats();
            this.updateButtonState();
        }

        calculateStats() {
            if (!this.data || !this.data.list) {
                this.stats = null;
                return;
            }

            const holders = this.data.list;
            this.stats = {
                totalAddresses: holders.length, // 所有持有过的地址数
                totalHolders: holders.filter(h => h.amount_percentage >= 0.00001).length, // 0.001%以上认为未清仓
                profitableHolders: holders.filter(h => h.profit > 0).length,
                lossHolders: holders.filter(h => h.profit < 0).length,
                totalHoldingPercentage: holders.reduce((sum, h) => sum + h.amount_percentage, 0),
                // 所有追踪者数据(包括已清仓的)
                chartData: holders
                    .sort((a, b) => b.amount_percentage - a.amount_percentage)
                    .map(h => ({
                        address: h.address,
                        percentage: h.amount_percentage,
                        profit: h.profit,
                        profitRate: (h.profit_change || 0) * 100,
                        netflow: h.netflow_usd,
                        sellAmount: h.sell_amount_cur || 0,
                        balance: h.balance,
                        walletTag: h.wallet_tag_v2 || '',
                        hasHolding: h.amount_percentage >= 0.00001,
                        avgCost: h.avg_cost || 0,
                        avgSold: h.avg_sold || 0
                    })),
                // 只有持仓的地址用于饼图
                pieChartData: holders
                    .filter(h => h.amount_percentage >= 0.00001)
                    .sort((a, b) => b.amount_percentage - a.amount_percentage)
                    .map(h => ({
                        address: h.address,
                        percentage: h.amount_percentage,
                        profit: h.profit,
                        profitRate: (h.profit_change || 0) * 100,
                        netflow: h.netflow_usd,
                        sellAmount: h.sell_amount_cur || 0,
                        balance: h.balance,
                        walletTag: h.wallet_tag_v2 || '',
                        avgCost: h.avg_cost || 0,
                        avgSold: h.avg_sold || 0
                    }))
            };
        }

        clearData() {
            this.data = null;
            this.stats = null;
            this.updateButtonState();
        }

        updateButtonState() {
            const button = document.querySelector('#follower-stats-button');
            if (button) {
                if (this.stats && this.stats.totalHolders > 0) {
                    button.style.opacity = '1';
                    button.style.pointerEvents = 'auto';
                    button.title = `点击查看 ${this.stats.totalHolders} 个追踪者数据分析`;
                } else {
                    button.style.opacity = '0.5';
                    button.style.pointerEvents = 'none';
                    button.title = '暂无追踪者数据';
                }
            }
        }

        getStats() {
            return this.stats;
        }
    }

    const dataManager = new FollowerDataManager();

    // API拦截器
    function setupAPIInterception() {
        // 拦截fetch请求
        const originalFetch = window.fetch;
        window.fetch = async function(...args) {
            const response = await originalFetch.apply(this, args);
            const url = args[0];

            if (typeof url === 'string' && url.includes('/vas/api/v1/token_holders/') && url.includes('following=true')) {
                try {
                    const clone = response.clone();
                    const data = await clone.json();
                    if (data.code === 0 && data.data && data.data.list) {
                        dataManager.setData(data.data);
                        // 提取代币地址
                        const tokenMatch = url.match(/token_holders\/sol\/([^?]+)/);
                        if (tokenMatch) {
                            currentToken = tokenMatch[1];
                        }
                    }
                } catch (e) {
                    console.log('解析追踪者数据失败:', e);
                }
            }

            return response;
        };

        // 拦截XMLHttpRequest
        const originalOpen = XMLHttpRequest.prototype.open;
        const originalSend = XMLHttpRequest.prototype.send;

        XMLHttpRequest.prototype.open = function(method, url, ...args) {
            this._url = url;
            return originalOpen.apply(this, [method, url, ...args]);
        };

        XMLHttpRequest.prototype.send = function(...args) {
            if (this._url && this._url.includes('/vas/api/v1/token_holders/') && this._url.includes('following=true')) {
                const originalOnReadyStateChange = this.onreadystatechange;
                this.onreadystatechange = function() {
                    if (this.readyState === 4 && this.status === 200) {
                        try {
                            const data = JSON.parse(this.responseText);
                            if (data.code === 0 && data.data && data.data.list) {
                                dataManager.setData(data.data);
                                const tokenMatch = this._url.match(/token_holders\/sol\/([^?]+)/);
                                if (tokenMatch) {
                                    currentToken = tokenMatch[1];
                                }
                            }
                        } catch (e) {
                            console.log('解析追踪者数据失败:', e);
                        }
                    }
                    if (originalOnReadyStateChange) {
                        return originalOnReadyStateChange.apply(this, arguments);
                    }
                };
            }
            return originalSend.apply(this, args);
        };
    }

    // 创建按钮
    function createFollowerStatsButton() {
        const button = document.createElement('div');
        button.id = 'follower-stats-button';
        button.className = 'h-[28px] flex items-center text-[12px] font-medium cursor-pointer bg-btn-secondary p-6px rounded-6px gap-2px text-text-200 hover:text-text-100';
        button.style.opacity = '0.5';
        button.style.pointerEvents = 'none';
        button.title = '暂无追踪者数据';

        button.innerHTML = `
            <svg width="12px" height="12px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
                <path d="M8 2C4.691 2 2 4.691 2 8s2.691 6 6 6 6-2.691 6-6-2.691-6-6-6zm0 10.5c-2.481 0-4.5-2.019-4.5-4.5S5.519 3.5 8 3.5 12.5 5.519 12.5 8 10.481 12.5 8 12.5z"/>
                <path d="M8 5.5c-1.381 0-2.5 1.119-2.5 2.5S6.619 10.5 8 10.5 10.5 9.381 10.5 8 9.381 5.5 8 5.5zm0 3.5c-0.551 0-1-0.449-1-1s0.449-1 1-1 1 0.449 1 1-0.449 1-1 1z"/>
            </svg>
            追踪者分析
        `;

        button.addEventListener('click', () => {
            const stats = dataManager.getStats();
            if (stats && stats.totalHolders > 0) {
                showStatsModal(stats);
            }
        });

        return button;
    }

    // 甜甜圈图组件
    function createPieChart(data, containerId) {
        const container = document.getElementById(containerId);
        if (!container) return;

        const size = 280;
        const center = size / 2;
        const outerRadius = size * 0.4;
        const innerRadius = size * 0.25; // 内圆半径,创建空心效果

        // 计算总和用于百分比计算
        const total = data.reduce((sum, item) => sum + item.percentage, 0);

        let currentAngle = -90; // 从顶部开始
        const segments = [];

        // 只显示前10个最大的持仓者,其他合并为"其他"
        const displayData = data.slice(0, 10);
        const otherData = data.slice(10);

        if (otherData.length > 0) {
            const otherSum = otherData.reduce((sum, item) => sum + item.percentage, 0);
            displayData.push({
                address: 'Others',
                percentage: otherSum,
                profit: otherData.reduce((sum, item) => sum + item.profit, 0),
                profitRate: 0,
                netflow: otherData.reduce((sum, item) => sum + item.netflow, 0),
                sellAmount: otherData.reduce((sum, item) => sum + item.sellAmount, 0),
                isOther: true
            });
        }

        // 生成颜色
        const colors = [
            '#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#feca57',
            '#ff9ff3', '#54a0ff', '#5f27cd', '#00d2d3', '#ff9f43',
            '#c7ecee'
        ];

        const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        svg.setAttribute('width', size);
        svg.setAttribute('height', size);
        svg.style.cursor = 'pointer';

        displayData.forEach((item, index) => {
            const percentage = item.percentage / total;
            const angle = percentage * 360;

            if (angle < 0.1) return; // 跳过太小的片段

            const startAngle = currentAngle * Math.PI / 180;
            const endAngle = (currentAngle + angle) * Math.PI / 180;

            // 外圆弧点
            const outerX1 = center + outerRadius * Math.cos(startAngle);
            const outerY1 = center + outerRadius * Math.sin(startAngle);
            const outerX2 = center + outerRadius * Math.cos(endAngle);
            const outerY2 = center + outerRadius * Math.sin(endAngle);

            // 内圆弧点
            const innerX1 = center + innerRadius * Math.cos(startAngle);
            const innerY1 = center + innerRadius * Math.sin(startAngle);
            const innerX2 = center + innerRadius * Math.cos(endAngle);
            const innerY2 = center + innerRadius * Math.sin(endAngle);

            const largeArcFlag = angle > 180 ? 1 : 0;

            const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
            // 创建甜甜圈形状的路径
            const pathData = [
                `M ${outerX1} ${outerY1}`,
                `A ${outerRadius} ${outerRadius} 0 ${largeArcFlag} 1 ${outerX2} ${outerY2}`,
                `L ${innerX2} ${innerY2}`,
                `A ${innerRadius} ${innerRadius} 0 ${largeArcFlag} 0 ${innerX1} ${innerY1}`,
                'Z'
            ].join(' ');

            path.setAttribute('d', pathData);
            path.setAttribute('fill', colors[index % colors.length]);
            path.setAttribute('stroke', '#1a1a1a');
            path.setAttribute('stroke-width', '2');
            path.style.transition = 'all 0.3s ease';
            path.style.transformOrigin = `${center}px ${center}px`;

            // 添加悬停效果
            path.addEventListener('mouseenter', (e) => {
                path.style.transform = 'scale(1.05)';
                path.style.filter = 'brightness(1.1)';
                showTooltip(e, item);
            });

            path.addEventListener('mouseleave', () => {
                path.style.transform = 'scale(1)';
                path.style.filter = 'brightness(1)';
                hideTooltip();
            });

            path.addEventListener('mousemove', (e) => {
                updateTooltipPosition(e);
            });

            svg.appendChild(path);
            currentAngle += angle;
        });

        // 中心文字
        const centerText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
        centerText.setAttribute('x', center);
        centerText.setAttribute('y', center - 8);
        centerText.setAttribute('text-anchor', 'middle');
        centerText.setAttribute('dominant-baseline', 'middle');
        centerText.setAttribute('fill', '#fff');
        centerText.setAttribute('font-size', '14');
        centerText.setAttribute('font-weight', 'bold');
        centerText.textContent = '总持仓占比';

        const centerValue = document.createElementNS('http://www.w3.org/2000/svg', 'text');
        centerValue.setAttribute('x', center);
        centerValue.setAttribute('y', center + 12);
        centerValue.setAttribute('text-anchor', 'middle');
        centerValue.setAttribute('dominant-baseline', 'middle');
        centerValue.setAttribute('fill', '#4ecdc4');
        centerValue.setAttribute('font-size', '16');
        centerValue.setAttribute('font-weight', 'bold');
        centerValue.textContent = formatPercentage(total);

        svg.appendChild(centerText);
        svg.appendChild(centerValue);

        container.innerHTML = '';
        container.appendChild(svg);
    }

    // 工具提示
    let tooltip = null;

    function showTooltip(event, data) {
        hideTooltip();

        tooltip = document.createElement('div');
        tooltip.className = 'follower-tooltip';
        tooltip.innerHTML = `
            <div class="tooltip-header">
                ${data.isOther ? '其他持仓者' : `${data.address.substring(0, 8)}...`}
                ${data.walletTag ? `<span class="wallet-tag">${data.walletTag}</span>` : ''}
            </div>
            <div class="tooltip-row">
                <span>持仓占比:</span>
                <span class="tooltip-value">${formatPercentage(data.percentage)}</span>
            </div>
            <div class="tooltip-row">
                <span>总利润:</span>
                <span class="tooltip-value ${data.profit >= 0 ? 'profit-positive' : 'profit-negative'}">${formatNumber(data.profit)}</span>
            </div>
            <div class="tooltip-row">
                <span>利润率:</span>
                <span class="tooltip-value ${data.profitRate >= 0 ? 'profit-positive' : 'profit-negative'}">${data.profitRate.toFixed(2)}%</span>
            </div>
            <div class="tooltip-row">
                <span>净流入:</span>
                <span class="tooltip-value">${formatNumber(data.netflow)}</span>
            </div>
            <div class="tooltip-row">
                <span>总卖出:</span>
                <span class="tooltip-value">${formatNumber(data.sellAmount)}</span>
            </div>
        `;

        document.body.appendChild(tooltip);
        updateTooltipPosition(event);
    }

    function updateTooltipPosition(event) {
        if (!tooltip) return;

        const rect = tooltip.getBoundingClientRect();
        let x = event.clientX + 10;
        let y = event.clientY + 10;

        // 确保工具提示不会超出视口
        if (x + rect.width > window.innerWidth) {
            x = event.clientX - rect.width - 10;
        }
        if (y + rect.height > window.innerHeight) {
            y = event.clientY - rect.height - 10;
        }

        tooltip.style.left = x + 'px';
        tooltip.style.top = y + 'px';
    }

    function hideTooltip() {
        if (tooltip) {
            tooltip.remove();
            tooltip = null;
        }
    }

    // 复制地址功能
    function copyToClipboard(text) {
        if (navigator.clipboard) {
            navigator.clipboard.writeText(text).then(() => {
                showNotification('地址已复制到剪贴板');
            }).catch(() => {
                fallbackCopy(text);
            });
        } else {
            fallbackCopy(text);
        }
    }

    function fallbackCopy(text) {
        const textArea = document.createElement('textarea');
        textArea.value = text;
        textArea.style.position = 'fixed';
        textArea.style.opacity = '0';
        document.body.appendChild(textArea);
        textArea.focus();
        textArea.select();
        try {
            document.execCommand('copy');
            showNotification('地址已复制到剪贴板');
        } catch (err) {
            showNotification('复制失败,请手动复制');
        }
        document.body.removeChild(textArea);
    }

    // 显示通知
    function showNotification(message) {
        const notification = document.createElement('div');
        notification.className = 'follower-notification';
        notification.textContent = message;
        document.body.appendChild(notification);

        setTimeout(() => {
            notification.classList.add('show');
        }, 10);

        setTimeout(() => {
            notification.classList.remove('show');
            setTimeout(() => {
                if (notification.parentNode) {
                    notification.parentNode.removeChild(notification);
                }
            }, 300);
        }, 2000);
    }

    // 创建用户列表项
    function createUserListItem(user, index) {
        const profitClass = user.profit >= 0 ? 'profit-positive' : 'profit-negative';
        const itemClass = user.hasHolding ? '' : ' no-holding';
        return `
            <div class="user-list-item${itemClass}" data-index="${index}">
                <div class="address-column">
                    <div class="address-display" onclick="copyAddressWithAnimation(this, '${user.address}')" title="点击复制地址: ${user.address}">
                        ${user.address.substring(0, 6)}...${user.address.substring(user.address.length - 4)}
                    </div>
                </div>
                <div class="price-column">
                    <div class="price-buy" title="平均买价">${user.avgCost && user.avgCost > 0 ? '$' + user.avgCost.toFixed(6) : '-'}</div>
                    <div class="price-sell" title="平均卖价">${user.avgSold && user.avgSold > 0 ? '$' + user.avgSold.toFixed(6) : '-'}</div>
                </div>
                <div class="profit-column">
                    <div class="profit-amount ${profitClass}">${formatNumber(user.profit)}</div>
                    <div class="profit-rate ${profitClass}">${user.profitRate.toFixed(2)}%</div>
                </div>
                <div class="challenge-column">
                    <button class="challenge-btn" onclick="window.open('https://gmgn.ai/sol/address/${user.address}', '_blank')" title="跳转到该用户">
                        <svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor">
                            <path d="M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5z"/>
                            <path d="M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0v-5z"/>
                        </svg>
                    </button>
                </div>
            </div>
        `;
    }

    // 模态框
    function showStatsModal(stats) {
        // 创建用户列表HTML
        const userListHTML = stats.chartData.map((user, index) => createUserListItem(user, index)).join('');

        // 创建模态框
        const modal = document.createElement('div');
        modal.className = 'follower-modal-overlay';
        modal.innerHTML = `
            <div class="follower-modal">
                <div class="modal-header">
                    <h2>追踪者数据分析</h2>
                    <button class="modal-close">&times;</button>
                </div>
                <div class="modal-content">
                    <div class="stats-summary">
                        <div class="stat-item">
                            <div class="stat-value">${stats.totalAddresses}</div>
                            <div class="stat-label">总地址数</div>
                        </div>
                        <div class="stat-item">
                            <div class="stat-value">${stats.totalHolders}</div>
                            <div class="stat-label">未清仓地址</div>
                        </div>
                        <div class="stat-item profit">
                            <div class="stat-value">${stats.profitableHolders}</div>
                            <div class="stat-label">盈利地址</div>
                        </div>
                        <div class="stat-item loss">
                            <div class="stat-value">${stats.lossHolders}</div>
                            <div class="stat-label">亏损地址</div>
                        </div>
                    </div>
                    <div class="chart-container">
                        <div class="chart-title">持仓分布图</div>
                        <div class="chart-content">
                            <div class="user-list-container">
                                <div class="user-list-header">
                                    <div class="header-address">地址</div>
                                    <div class="header-price">平均买价/平均卖价</div>
                                    <div class="header-profit">收益</div>
                                    <div class="header-challenge">跳转</div>
                                </div>
                                <div class="user-list" id="user-list">
                                    ${userListHTML}
                                </div>
                            </div>
                            <div class="chart-right">
                                <div id="pie-chart-container"></div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        `;

        // 添加关闭事件
        const closeBtn = modal.querySelector('.modal-close');
        closeBtn.addEventListener('click', () => {
            modal.remove();
        });

        modal.addEventListener('click', (e) => {
            if (e.target === modal) {
                modal.remove();
            }
        });

        // 将复制函数挂载到window上,供内联事件使用
        window.copyToClipboard = copyToClipboard;

        document.body.appendChild(modal);

        // 添加地址点击复制功能
        window.copyAddressWithAnimation = function(element, address) {
            // 复制地址
            copyToClipboard(address);

            // 添加点击动画
            element.classList.add('address-clicked');
            setTimeout(() => {
                element.classList.remove('address-clicked');
            }, 300);
        };

        // 创建饼图(使用有持仓的数据)
        setTimeout(() => {
            if (stats.pieChartData && stats.pieChartData.length > 0) {
                createPieChart(stats.pieChartData, 'pie-chart-container');
            } else {
                console.log('没有有效的饼图数据:', stats);
            }
        }, 100);
    }

    // 添加样式
    function addStyles() {
        const style = document.createElement('style');
        style.textContent = `
            /* 模态框样式 */
            .follower-modal-overlay {
                position: fixed;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                background: rgba(0, 0, 0, 0.8);
                display: flex;
                align-items: center;
                justify-content: center;
                z-index: 10000;
                backdrop-filter: blur(5px);
                animation: fadeIn 0.3s ease;
            }

            .follower-modal {
                background: #1a1a1a;
                border-radius: 12px;
                width: 90%;
                max-width: 1000px;
                max-height: 90vh;
                overflow: hidden;
                box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
                animation: slideIn 0.3s ease;
                border: 1px solid #333;
            }

            .modal-header {
                display: flex;
                justify-content: space-between;
                align-items: center;
                padding: 20px 24px;
                border-bottom: 1px solid #333;
                background: #222;
            }

            .modal-header h2 {
                color: #fff;
                margin: 0;
                font-size: 18px;
                font-weight: 600;
            }

            .modal-close {
                background: none;
                border: none;
                color: #999;
                font-size: 24px;
                cursor: pointer;
                padding: 0;
                width: 32px;
                height: 32px;
                display: flex;
                align-items: center;
                justify-content: center;
                border-radius: 6px;
                transition: all 0.2s ease;
            }

            .modal-close:hover {
                background: #333;
                color: #fff;
            }

            .modal-content {
                padding: 24px;
                overflow-y: auto;
                max-height: calc(90vh - 80px);
            }

            .stats-summary {
                display: grid;
                grid-template-columns: repeat(4, 1fr);
                gap: 16px;
                margin-bottom: 32px;
            }

            .stat-item {
                text-align: center;
                padding: 16px;
                background: #2a2a2a;
                border-radius: 8px;
                border: 1px solid #333;
                min-width: 0;
            }

            .stat-item.profit {
                border-color: #4ecdc4;
                background: linear-gradient(135deg, #2a2a2a, #1f3a3a);
            }

            .stat-item.loss {
                border-color: #ff6b6b;
                background: linear-gradient(135deg, #2a2a2a, #3a1f1f);
            }

            .stat-value {
                font-size: 24px;
                font-weight: bold;
                color: #fff;
                margin-bottom: 4px;
            }

            .stat-item.profit .stat-value {
                color: #4ecdc4;
            }

            .stat-item.loss .stat-value {
                color: #ff6b6b;
            }

            .stat-label {
                font-size: 12px;
                color: #999;
                text-transform: uppercase;
                letter-spacing: 0.5px;
            }

            .chart-container {
                text-align: center;
            }

            .chart-title {
                font-size: 16px;
                font-weight: 600;
                color: #fff;
                margin-bottom: 20px;
            }

            .chart-content {
                display: flex;
                gap: 20px;
                align-items: flex-start;
                height: 400px;
            }

            .user-list-container {
                flex: 0 0 50%;
                min-width: 0;
                height: 100%;
                display: flex;
                flex-direction: column;
            }

            .user-list-header {
                display: grid;
                grid-template-columns: 1fr 100px 90px 50px;
                gap: 6px;
                padding: 8px 12px;
                background: #2a2a2a;
                border-radius: 8px 8px 0 0;
                border: 1px solid #333;
                font-size: 11px;
                font-weight: 600;
                color: #999;
                text-transform: uppercase;
                letter-spacing: 0.5px;
                flex-shrink: 0;
            }

            .header-address {
                text-align: left;
            }

            .header-price {
                text-align: center;
            }

            .header-profit {
                text-align: center;
            }

            .header-challenge {
                text-align: center;
            }

            .user-list {
                flex: 1;
                overflow-y: auto;
                border: 1px solid #333;
                border-top: none;
                border-radius: 0 0 8px 8px;
                background: #1e1e1e;
                min-height: 0;
            }

            .user-list::-webkit-scrollbar {
                width: 6px;
            }

            .user-list::-webkit-scrollbar-track {
                background: #2a2a2a;
            }

            .user-list::-webkit-scrollbar-thumb {
                background: #4ecdc4;
                border-radius: 3px;
            }

            .user-list::-webkit-scrollbar-thumb:hover {
                background: #45b7d1;
            }

            .user-list-item {
                display: grid;
                grid-template-columns: 1fr 100px 90px 50px;
                gap: 6px;
                padding: 8px 12px;
                border-bottom: 1px solid #333;
                align-items: center;
                transition: all 0.2s ease;
            }

            .user-list-item.no-holding {
                opacity: 0.4;
                background: #1a1a1a;
            }

            .user-list-item.no-holding:hover {
                opacity: 0.6;
                background: #202020;
            }

            .user-list-item:hover {
                background: #252525;
            }

            .user-list-item:last-child {
                border-bottom: none;
            }

            .challenge-column {
                display: flex;
                justify-content: center;
            }

            .challenge-btn {
                width: 28px;
                height: 28px;
                border: none;
                border-radius: 4px;
                background: linear-gradient(135deg, #4ecdc4, #45b7d1);
                color: white;
                cursor: pointer;
                display: flex;
                align-items: center;
                justify-content: center;
                transition: all 0.2s ease;
                box-shadow: 0 1px 4px rgba(76, 205, 196, 0.3);
            }

            .challenge-btn:hover {
                transform: translateY(-1px);
                box-shadow: 0 2px 8px rgba(76, 205, 196, 0.4);
            }

            .challenge-btn:active {
                transform: translateY(0);
            }

            .price-column {
                display: flex;
                flex-direction: column;
                align-items: center;
                gap: 2px;
            }

            .price-buy {
                font-size: 10px;
                color: #4ecdc4;
                font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
            }

            .price-sell {
                font-size: 10px;
                color: #ff9f43;
                font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
            }

            .user-list-item.no-holding .price-buy,
            .user-list-item.no-holding .price-sell {
                opacity: 0.6;
            }

            .profit-column {
                display: flex;
                flex-direction: column;
                align-items: center;
                gap: 4px;
            }

            .profit-amount {
                font-size: 14px;
                font-weight: 600;
            }

            .profit-rate {
                font-size: 12px;
                opacity: 0.8;
            }

            .address-column {
                display: flex;
                align-items: center;
                justify-content: flex-start;
            }

            .address-display {
                font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
                font-size: 11px;
                color: #fff;
                background: #333;
                padding: 6px 10px;
                border-radius: 4px;
                border: 1px solid #444;
                cursor: pointer;
                transition: all 0.2s ease;
                user-select: none;
            }

            .address-display:hover {
                background: #4ecdc4;
                color: #000;
                transform: translateY(-1px);
            }

            .address-display.address-clicked {
                animation: addressClickAnimation 0.3s ease;
            }

            @keyframes addressClickAnimation {
                0% {
                    transform: scale(1);
                    background: #4ecdc4;
                }
                50% {
                    transform: scale(1.1);
                    background: #45b7d1;
                }
                100% {
                    transform: scale(1);
                    background: #4ecdc4;
                }
            }

            .chart-right {
                flex: 0 0 50%;
                display: flex;
                justify-content: center;
                align-items: center;
                height: 100%;
            }

            #pie-chart-container {
                display: flex;
                justify-content: center;
                align-items: center;
                height: 100%;
                width: 100%;
            }

            /* 通知样式 */
            .follower-notification {
                position: fixed;
                top: 20px;
                right: 20px;
                background: #4ecdc4;
                color: #000;
                padding: 12px 20px;
                border-radius: 6px;
                font-size: 14px;
                font-weight: 500;
                box-shadow: 0 4px 12px rgba(76, 205, 196, 0.3);
                z-index: 10002;
                opacity: 0;
                transform: translateX(100%);
                transition: all 0.3s ease;
            }

            .follower-notification.show {
                opacity: 1;
                transform: translateX(0);
            }

            /* 工具提示样式 */
            .follower-tooltip {
                position: fixed;
                background: #1a1a1a;
                border: 1px solid #333;
                border-radius: 8px;
                padding: 12px;
                z-index: 10001;
                max-width: 280px;
                box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
                pointer-events: none;
                animation: tooltipIn 0.2s ease;
            }

            .tooltip-header {
                color: #fff;
                font-weight: 600;
                margin-bottom: 8px;
                font-size: 14px;
                display: flex;
                align-items: center;
                gap: 8px;
            }

            .wallet-tag {
                background: #4ecdc4;
                color: #000;
                padding: 2px 6px;
                border-radius: 4px;
                font-size: 10px;
                font-weight: bold;
                text-transform: uppercase;
            }

            .tooltip-row {
                display: flex;
                justify-content: space-between;
                align-items: center;
                margin-bottom: 4px;
                font-size: 12px;
            }

            .tooltip-row span:first-child {
                color: #999;
            }

            .tooltip-value {
                color: #fff;
                font-weight: 500;
            }

            .profit-positive {
                color: #4ecdc4 !important;
            }

            .profit-negative {
                color: #ff6b6b !important;
            }

            /* 动画 */
            @keyframes fadeIn {
                from { opacity: 0; }
                to { opacity: 1; }
            }

            @keyframes slideIn {
                from {
                    opacity: 0;
                    transform: translateY(-20px) scale(0.95);
                }
                to {
                    opacity: 1;
                    transform: translateY(0) scale(1);
                }
            }

            @keyframes tooltipIn {
                from {
                    opacity: 0;
                    transform: scale(0.9);
                }
                to {
                    opacity: 1;
                    transform: scale(1);
                }
            }

            /* 响应式设计 */
            @media (max-width: 768px) {
                .follower-modal {
                    width: 95%;
                    margin: 20px;
                    max-width: none;
                }

                .stats-summary {
                    display: grid;
                    grid-template-columns: 1fr 1fr;
                    gap: 12px;
                    margin-bottom: 24px;
                }

                .stat-item {
                    min-width: auto;
                    padding: 12px;
                }

                .modal-content {
                    padding: 16px;
                }

                .chart-content {
                    flex-direction: column;
                    gap: 16px;
                    height: auto;
                }

                .chart-right {
                    flex: none;
                }

                .user-list-container {
                    flex: none;
                    height: 250px;
                }

                .user-list-header {
                    grid-template-columns: 1fr 70px 60px 35px;
                    gap: 3px;
                    padding: 6px 8px;
                    font-size: 9px;
                }

                .user-list-item {
                    grid-template-columns: 1fr 70px 60px 35px;
                    gap: 3px;
                    padding: 6px 8px;
                }

                .challenge-btn {
                    width: 24px;
                    height: 24px;
                }

                .address-display {
                    font-size: 9px;
                    padding: 4px 6px;
                }

                .profit-amount {
                    font-size: 12px;
                }

                .profit-rate {
                    font-size: 10px;
                }

                #pie-chart-container svg {
                    width: 250px;
                    height: 250px;
                }

                .follower-notification {
                    top: 10px;
                    right: 10px;
                    left: 10px;
                    transform: translateY(-100%);
                }

                .follower-notification.show {
                    transform: translateY(0);
                }
            }

            /* 按钮悬停效果增强 */
            #follower-stats-button {
                transition: all 0.2s ease;
                position: relative;
                overflow: hidden;
            }

            #follower-stats-button:hover {
                transform: translateY(-1px);
                box-shadow: 0 4px 12px rgba(76, 205, 196, 0.2);
            }

            #follower-stats-button::before {
                content: '';
                position: absolute;
                top: 0;
                left: -100%;
                width: 100%;
                height: 100%;
                background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
                transition: left 0.5s ease;
            }

            #follower-stats-button:hover::before {
                left: 100%;
            }
        `;
        document.head.appendChild(style);
    }

    // 监控URL变化
    function monitorUrlChange() {
        const observer = new MutationObserver(() => {
            if (window.location.href !== currentUrl) {
                currentUrl = window.location.href;
                dataManager.clearData();
                currentToken = null;
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });

        // 监听popstate事件
        window.addEventListener('popstate', () => {
            if (window.location.href !== currentUrl) {
                currentUrl = window.location.href;
                dataManager.clearData();
                currentToken = null;
            }
        });
    }

    // 插入按钮到页面
    function insertButton() {
        const buttonContainer = document.querySelector('.flex.absolute.top-0.right-0.gap-8px.pl-4px');
        if (buttonContainer && !document.querySelector('#follower-stats-button')) {
            const button = createFollowerStatsButton();
            buttonContainer.insertBefore(button, buttonContainer.firstChild);
        }
    }

    // 初始化
    function init() {
        addStyles();
        setupAPIInterception();
        monitorUrlChange();

        // 等待页面加载后插入按钮
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => {
                setTimeout(insertButton, 1000);
            });
        } else {
            setTimeout(insertButton, 1000);
        }

        // 定期检查按钮是否存在,如果不存在则重新插入
        setInterval(() => {
            if (document.querySelector('.flex.absolute.top-0.right-0.gap-8px.pl-4px') &&
                !document.querySelector('#follower-stats-button')) {
                insertButton();
            }
        }, 2000);
    }

    // 启动脚本
    init();

    console.log('GMGN追踪者统计分析脚本已加载');
})();