Greasy Fork

Greasy Fork is available in English.

GMGN.ai 前排标注查询工具

获取GMGN.ai前100持仓者的MemeRadar标注信息

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         GMGN.ai 前排标注查询工具
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  获取GMGN.ai前100持仓者的MemeRadar标注信息
// @author       专业油猴脚本开发者
// @match        https://gmgn.ai/*
// @run-at       document-start
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// @connect      plugin.chaininsight.vip
// @require      https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    console.log('[标注查询] MemeRadar标注查询工具已启动');

    // 全局变量
    let currentCA = ''; // 当前代币合约地址
    let currentChain = ''; // 当前链网络
    let topHolders = []; // 前排持仓者地址列表
    let tagData = []; // 标注数据
    let isDataReady = false; // 数据是否就绪
    let isFetchingTags = false; // 是否正在获取标注
    let hasInterceptedTags = false; // 是否已拦截到标注数据
    let hasInterceptedHolders = false; // 是否已拦截到持仓者数据
    let interceptedCA = ''; // 已拦截的CA地址

    // 链网络映射
    const chainMapping = {
        'sol': 'Solana',
        'eth': 'Ethereum',
        'base': 'Base',
        'bsc': 'bsc',
        // tron 不支持
    };

    // 立即设置XHR拦截
    setupXhrInterception();

    // DOM加载完成后初始化UI
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initializeUI);
    } else {
        setTimeout(initializeUI, 100);
    }

    /**
     * 设置XHR请求拦截
     */
    function setupXhrInterception() {
        console.log('[请求拦截] 开始设置token_holders请求拦截');

        // 避免重复设置
        if (window._memeradarInterceptionSetup) {
            console.log('[请求拦截] 检测到已存在拦截设置,跳过重复设置');
            return;
        }

        const originalOpen = XMLHttpRequest.prototype.open;
        const originalSend = XMLHttpRequest.prototype.send;

        XMLHttpRequest.prototype.open = function(method, url) {
            this._url = url;
            this._method = method;
            return originalOpen.apply(this, arguments);
        };

        XMLHttpRequest.prototype.send = function(body) {
            const url = this._url;

            // 监听token_holders请求
            if (url && url.includes('/vas/api/v1/token_holders/')) {
                // 解析链网络和CA地址
                const urlMatch = url.match(/\/token_holders\/([^\/]+)\/([^?]+)/);
                if (!urlMatch) {
                    console.warn('[请求拦截] ⚠️无法解析token_holders URL:', url);
                    return originalSend.apply(this, arguments);
                }

                const chain = urlMatch[1];
                const ca = urlMatch[2];

                // 检查是否已经拦截过这个CA
                if (hasInterceptedHolders && interceptedCA === ca) {
                    console.log(`[请求拦截] 📋已拦截过CA ${ca} 的持仓者数据,跳过重复拦截`);
                    return originalSend.apply(this, arguments);
                }

                console.log('[请求拦截] 🎯捕获到token_holders请求:', url);
                console.log(`[数据解析] 链网络: ${chain}, CA地址: ${ca}`);

                // 检查CA是否变化
                if (currentCA && currentCA !== ca) {
                    console.log('[数据重置] 检测到CA地址变化,清除所有数据');
                    resetAllData();
                }

                currentChain = chain;
                currentCA = ca;
                console.log('currentChain', currentChain);
                console.log('currentCA', currentCA);

                this.addEventListener('load', function() {
                    if (this.status === 200) {
                        console.log('[请求拦截] ✅token_holders请求成功');
                        try {
                            const response = JSON.parse(this.responseText);
                            if (response.code === 0 && response.data && response.data.list) {
                                processTokenHolders(response.data.list);
                                // 标记已拦截成功
                                hasInterceptedHolders = true;
                                interceptedCA = ca;
                                console.log(`[拦截完成] ✅已完成CA ${ca} 的持仓者数据拦截,后续请求将被跳过`);
                            } else {
                                console.warn('[数据处理] ⚠️token_holders返回数据格式异常:', response);
                            }
                        } catch (error) {
                            console.error('[数据处理] ❌解析token_holders响应失败:', error);
                        }
                    } else {
                        console.error('[请求拦截] ❌token_holders请求失败,状态码:', this.status);
                    }
                });
            }

            // 监听wallet_tags_v2请求
            if (url && url.includes('/api/v0/util/query/wallet_tags_v2')) {
                // 检查是否已经拦截过标注数据(针对当前CA)
                if (hasInterceptedTags && currentCA) {
                    console.log(`[请求拦截] 📋已拦截过CA ${currentCA} 的标注数据,跳过重复拦截`);
                    return originalSend.apply(this, arguments);
                }

                console.log('[请求拦截] 🎯捕获到wallet_tags_v2请求:', url);

                this.addEventListener('load', function() {
                    if (this.status === 200) {
                        console.log('[请求拦截] ✅wallet_tags_v2请求成功');
                        try {
                            const response = JSON.parse(this.responseText);
                            console.log('[标注拦截] wallet_tags_v2响应数据:', response);

                            if (response.code === 0 && response.data) {
                                console.log('[标注拦截] ✅成功拦截到标注数据,开始处理');

                                // 确保有持仓者数据才处理
                                if (topHolders && topHolders.length > 0) {
                                    processInterceptedTagData(response.data);
                                    hasInterceptedTags = true;
                                    updateButtonState();
                                    console.log('[标注拦截] ✅标注数据处理完成,已更新按钮状态');
                                } else {
                                    console.warn('[标注拦截] ⚠️持仓者数据尚未准备,延迟处理标注数据');
                                    // 保存标注数据,等待持仓者数据准备完成
                                    window._pendingTagData = response.data;
                                }
                            } else {
                                console.warn('[数据处理] ⚠️wallet_tags_v2返回数据格式异常:', response);
                                console.log('[数据处理] 响应码:', response.code, '消息:', response.msg);
                            }
                        } catch (error) {
                            console.error('[数据处理] ❌解析wallet_tags_v2响应失败:', error);
                        }
                    } else {
                        console.error('[请求拦截] ❌wallet_tags_v2请求失败,状态码:', this.status);
                    }
                });

                this.addEventListener('error', function(error) {
                    console.error('[请求拦截] ❌wallet_tags_v2网络请求错误:', error);
                });
            }

            return originalSend.apply(this, arguments);
        };

        window._memeradarInterceptionSetup = true;
        console.log('[请求拦截] ✅XHR拦截设置完成');
    }

    /**
     * 处理持仓者数据
     */
    function processTokenHolders(holdersList) {
        console.log(`[数据处理] 开始处理持仓者列表,总数量: ${holdersList.length}`);

        // 提取前100个地址
        topHolders = holdersList.slice(0, 100).map(holder => holder.address);
        isDataReady = true;

        console.log(`[数据处理] ✅已提取前${topHolders.length}个持仓者地址`);
        console.log('[数据处理] 前5个地址示例:', topHolders.slice(0, 5));

        // 检查是否有待处理的标注数据
        if (window._pendingTagData) {
            console.log('[数据处理] 🔄发现待处理的标注数据,开始处理');
            processInterceptedTagData(window._pendingTagData);
            hasInterceptedTags = true;
            window._pendingTagData = null; // 清除待处理数据
            console.log('[数据处理] ✅待处理标注数据处理完成');
        }

        // 更新按钮状态
        updateButtonState();
    }

    /**
     * 处理拦截到的标注数据
     */
    function processInterceptedTagData(responseData) {
        console.log('[拦截数据] 开始处理拦截到的标注数据');

        if (!responseData || !responseData.walletTags) {
            console.warn('[拦截数据] 响应数据格式异常');
            return;
        }

        // 创建地址到标注的映射
        const tagMap = {};
        responseData.walletTags.forEach(wallet => {
            tagMap[wallet.address] = {
                count: wallet.count || 0,
                tags: wallet.tags ? wallet.tags.map(tag => tag.tagName) : [],
                expertTags: wallet.expertTags ? wallet.expertTags.map(tag => tag.tagName) : []
            };
        });

        // 为所有地址创建完整的标注数据
        tagData = topHolders.map(address => {
            const walletTags = tagMap[address] || { count: 0, tags: [], expertTags: [] };
            return {
                address: address,
                tagCount: walletTags.count,
                tags: walletTags.tags,
                expertTags: walletTags.expertTags
            };
        });

        // 按标注数量降序排序
        tagData.sort((a, b) => b.tagCount - a.tagCount);

        console.log(`[拦截数据] ✅处理完成,共${tagData.length}个地址,有标注的地址:${tagData.filter(w => w.tagCount > 0).length}个`);
    }

    /**
     * 重置所有数据
     */
    function resetAllData() {
        console.log('[数据重置] 🔄开始重置所有数据和拦截状态');

        currentCA = '';
        currentChain = '';
        topHolders = [];
        tagData = [];
        isDataReady = false;
        isFetchingTags = false;
        hasInterceptedTags = false;
        hasInterceptedHolders = false;
        interceptedCA = '';

        // 清除待处理的标注数据
        if (window._pendingTagData) {
            window._pendingTagData = null;
        }

        console.log('[数据重置] ✅所有数据和拦截状态已重置,可开始新一轮拦截');
    }

    /**
     * 初始化UI界面
     */
    function initializeUI() {
        console.log('[UI初始化] 开始初始化用户界面');
        addStyles();
        setupUI();
        console.log('[UI初始化] ✅用户界面初始化完成');
    }

    /**
     * 添加CSS样式
     */
    function addStyles() {
        GM_addStyle(`
            .memeradar-btn {
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                color: white;
                border: none;
                border-radius: 6px;
                padding: 6px 12px;
                font-size: 12px;
                font-weight: 600;
                cursor: pointer;
                transition: all 0.3s ease;
                margin-right: 8px;
                min-width: 80px;
                height: 32px;
                display: flex;
                align-items: center;
                justify-content: center;
                box-shadow: 0 2px 4px rgba(102, 126, 234, 0.3);
            }

            .memeradar-btn:disabled {
                background: #94a3b8;
                cursor: not-allowed;
                transform: none;
                box-shadow: none;
            }
            .memeradar-btn.fetching {
                background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
                animation: pulse 2s infinite;
            }
            .memeradar-btn.ready {
                background: linear-gradient(135deg, #10b981 0%, #059669 100%);
            }
            @keyframes pulse {
                0%, 100% { opacity: 1; }
                50% { opacity: 0.7; }
            }

            .memeradar-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: 100000;
            }
            .memeradar-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;
            }
            .memeradar-modal-header {
                display: flex !important;
                justify-content: space-between !important;
                align-items: center !important;
                margin-bottom: 16px !important;
                padding: 0 !important;
            }
            .memeradar-modal-title {
                font-size: 18px !important;
                font-weight: 600 !important;
                color: white !important;
                margin: 0 !important;
            }
            .memeradar-modal-subtitle {
                color: #94a3b8 !important;
                font-size: 14px !important;
            }
            .memeradar-header-actions {
                display: flex !important;
                align-items: center !important;
                gap: 8px !important;
            }
            .memeradar-export-btn {
                background: #3b82f6 !important;
                color: white !important;
                border: none !important;
                padding: 6px 12px !important;
                border-radius: 4px !important;
                cursor: pointer !important;
                font-size: 14px !important;
            }
            .memeradar-export-btn:hover {
                background: #2563eb !important;
            }
            .memeradar-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: 30px !important;
                height: 30px !important;
                display: flex !important;
                align-items: center !important;
                justify-content: center !important;
            }
            .memeradar-modal-close:hover {
                color: #ff4444 !important;
                background-color: rgba(255, 255, 255, 0.1) !important;
                border-radius: 4px !important;
            }
            .memeradar-analysis-summary {
                margin-bottom: 16px;
                padding: 12px;
                background-color: #263238;
                border-radius: 6px;
                display: flex;
                justify-content: space-between;
                align-items: center;
            }
            .memeradar-summary-stats {
                display: flex;
                gap: 20px;
            }
            .memeradar-stat-item {
                display: flex;
                align-items: baseline;
            }
            .memeradar-stat-label {
                color: #94a3b8;
                margin-right: 5px;
            }
            .memeradar-stat-value {
                font-weight: 600;
                color: #3b82f6;
            }
            .memeradar-result-item {
                background-color: #334155;
                border-radius: 6px;
                padding: 12px;
                margin-bottom: 12px;
                display: flex;
                justify-content: space-between;
                align-items: center;
            }
            .memeradar-wallet-info {
                flex: 1;
            }
            .memeradar-wallet-address {
                color: #3b82f6;
                font-family: monospace;
                font-size: 14px;
                cursor: pointer;
                margin-bottom: 4px;
            }
            .memeradar-tags {
                display: flex;
                flex-wrap: wrap;
                gap: 4px;
            }
            .memeradar-tag {
                background: rgba(59, 130, 246, 0.2);
                color: #93c5fd;
                padding: 2px 6px;
                border-radius: 3px;
                font-size: 12px;
            }
            .memeradar-expert-tag {
                background: rgba(249, 115, 22, 0.2);
                color: #fb923c;
                padding: 2px 6px;
                border-radius: 3px;
                font-size: 12px;
            }
            .memeradar-tag-count {
                color: #10b981;
                font-weight: 600;
                margin-left: 12px;
                min-width: 40px;
                text-align: center;
            }
            .memeradar-modal-close {
                background: rgba(255, 255, 255, 0.2);
                border: none;
                color: white;
                font-size: 18px;
                width: 32px;
                height: 32px;
                border-radius: 50%;
                cursor: pointer;
            }

            .memeradar-modal-body {
                padding: 12px;
                /* 移除滾動條,自適應內容高度 */
                overflow: visible;
                /* 性能優化 */
                contain: layout style paint;
                transform: translateZ(0);
            }
            .memeradar-stats {
                background: rgba(59, 130, 246, 0.1);
                border: 1px solid rgba(59, 130, 246, 0.2);
                border-radius: 6px;
                padding: 10px;
                margin-bottom: 12px;
                display: grid;
                grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
                gap: 10px;
            }
            .memeradar-stat-item {
                text-align: center;
            }
            .memeradar-stat-label {
                color: #94a3b8;
                font-size: 10px;
                margin-bottom: 2px;
            }
            .memeradar-stat-value {
                color: #3b82f6;
                font-size: 16px;
                font-weight: 600;
            }
            .memeradar-export-btn {
                background: linear-gradient(135deg, #10b981 0%, #059669 100%);
                color: white;
                border: none;
                border-radius: 4px;
                padding: 4px 8px;
                font-size: 11px;
                font-weight: 500;
                cursor: pointer;
                display: flex;
                align-items: center;
                gap: 3px;
                height: 28px;
                box-shadow: 0 1px 3px rgba(16, 185, 129, 0.3);
            }

            .memeradar-wallets-table {
                width: 100%;
                border-collapse: collapse;
                font-size: 11px;
                /* 表格性能優化 */
                table-layout: fixed;
                contain: layout style paint;
                transform: translateZ(0);
                /* 進一步性能優化 */
                will-change: auto;
                backface-visibility: hidden;
            }
            .memeradar-wallets-table th {
                background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
                color: white;
                padding: 8px 6px;
                text-align: left;
                font-weight: 600;
                font-size: 10px;
                border-bottom: 1px solid rgba(59, 130, 246, 0.3);
                position: sticky;
                top: 0;
                z-index: 10;
            }
            .memeradar-wallets-table th:first-child {
                width: 45%;
            }
            .memeradar-wallets-table th:nth-child(2) {
                width: 10%;
                text-align: center;
            }
            .memeradar-wallets-table th:last-child {
                width: 45%;
            }
            .memeradar-wallets-table td {
                padding: 6px;
                border-bottom: 1px solid rgba(100, 116, 139, 0.2);
                vertical-align: top;
                background: linear-gradient(135deg, rgba(15, 23, 42, 0.9) 0%, rgba(30, 41, 59, 0.9) 100%);
            }
            .memeradar-wallets-table tr:nth-child(even) td {
                background: linear-gradient(135deg, rgba(30, 41, 59, 0.9) 0%, rgba(51, 65, 85, 0.9) 100%);
            }
            .memeradar-wallet-address {
                font-family: 'Courier New', monospace;
                color: #e2e8f0;
                font-size: 13px;
                cursor: pointer;
                padding: 2px 4px;
                background: rgba(15, 23, 42, 0.8);
                border-radius: 3px;
                word-break: break-all;
                display: inline-block;
                width: 100%;
            }
            .memeradar-tag-count {
                background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
                color: white;
                padding: 2px 6px;
                border-radius: 10px;
                font-size: 10px;
                font-weight: 600;
                box-shadow: 0 1px 2px rgba(245, 158, 11, 0.3);
                display: inline-block;
            }
            .memeradar-tags-container {
                display: flex;
                flex-wrap: wrap;
                gap: 2px;
                line-height: 1.3;
            }
            .memeradar-tag {
                background: linear-gradient(135deg, #c084fc 0%, #a855f7 100%);
                color: white;
                padding: 2px 6px;
                border-radius: 10px;
                font-size: 12px;
                font-weight: 500;
                white-space: nowrap;
                box-shadow: 0 1px 2px rgba(192, 132, 252, 0.3);
            }
            .memeradar-expert-tag {
                background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
                color: white;
                padding: 2px 6px;
                border-radius: 10px;
                font-size: 12px;
                font-weight: 500;
                white-space: nowrap;
                box-shadow: 0 1px 2px rgba(59, 130, 246, 0.3);
                position: relative;
            }
            .memeradar-expert-tag::before {
                content: "⭐";
                margin-right: 2px;
            }
            .memeradar-no-tags {
                color: #94a3b8;
                font-style: italic;
                font-size: 10px;
            }
            .memeradar-loading {
                text-align: center;
                padding: 40px;
                color: #94a3b8;
            }
            .memeradar-error {
                background: rgba(239, 68, 68, 0.1);
                border: 1px solid rgba(239, 68, 68, 0.2);
                color: #ef4444;
                padding: 12px;
                border-radius: 6px;
                margin-bottom: 16px;
                font-size: 14px;
            }
        `);
    }

    /**
     * 设置UI界面
     */
    function setupUI() {
        const observer = new MutationObserver(() => {
            const targetContainer = document.querySelector('.flex.overflow-x-auto.overflow-y-hidden.scroll-smooth.w-full');
            if (targetContainer && !targetContainer.querySelector('#memeradar-btn')) {
                injectButton(targetContainer);
            }
        });

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

    /**
     * 注入按钮到页面
     */
    function injectButton(container) {
        const button = document.createElement('button');
        button.id = 'memeradar-btn';
        button.className = 'memeradar-btn';
        button.textContent = '获取前排标注';

        container.insertAdjacentElement('afterbegin', button);

        button.addEventListener('click', handleButtonClick);
        console.log('[UI注入] ✅前排标注按钮已注入');
    }

    /**
     * 处理按钮点击事件
     */
    async function handleButtonClick() {
        const button = document.getElementById('memeradar-btn');

        if (isFetchingTags) {
            console.log('[按钮点击] 正在获取标注中,忽略点击');
            return;
        }

        // 检查数据是否就绪
        if (!isDataReady || !topHolders.length) {
            showErrorModal('数据尚未就绪', '请等待页面加载完成,或刷新页面重试。\n\n可能原因:\n1. 页面数据还在加载中\n2. 网络请求被拦截失败\n3. 当前页面不是代币详情页');
            return;
        }

        // 检查链网络是否支持
        if (!chainMapping[currentChain]) {
            showErrorModal('不支持的链网络', `当前链网络 "${currentChain}" 暂不支持标注查询。\n\n支持的链网络:\n• Solana (sol)\n• Ethereum (eth)\n• Base (base)\n• BSC (bsc)`);
            return;
        }

        // 如果已有标注数据(无论是拦截的还是API获取的),直接显示
        if (tagData.length > 0) {
            showTagsModal();
            return;
        }

        // 调试信息:显示当前状态
        console.log('[按钮点击] 当前数据状态检查:');
        console.log('  hasInterceptedTags:', hasInterceptedTags);
        console.log('  tagData.length:', tagData.length);
        console.log('  topHolders.length:', topHolders.length);
        console.log('  window._pendingTagData:', !!window._pendingTagData);

        // 如果已拦截到标注数据但还没处理完成,提示用户稍等
        if (hasInterceptedTags && tagData.length === 0) {
            showErrorModal('数据处理中', '已检测到标注数据,正在处理中,请稍候...');
            return;
        }

        // 开始通过API获取标注数据
        isFetchingTags = true;
        button.className = 'memeradar-btn fetching';
        button.textContent = '获取中...';

        try {
            console.log(`[API获取] 开始通过API获取${topHolders.length}个地址的标注信息`);
            await fetchWalletTags();

            button.className = 'memeradar-btn ready';
            button.textContent = '查看标注';

            console.log('[API获取] ✅标注数据获取完成');
            showTagsModal();

        } catch (error) {
            console.error('[API获取] ❌获取标注数据失败:', error);
            showErrorModal('获取失败', `标注数据获取失败:${error.message}\n\n请检查网络连接或稍后重试。`);

            button.className = 'memeradar-btn';
            button.textContent = '获取前排标注';
        } finally {
            isFetchingTags = false;
        }
    }

    /**
     * 获取钱包标注数据
     */
    async function fetchWalletTags() {
        const chainName = chainMapping[currentChain];
        const requestData = {
            walletAddresses: topHolders,
            chain: chainName
        };

        console.log(`[API请求] 发送标注查询请求,链网络: ${chainName}, 地址数量: ${topHolders.length}`);

        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'POST',
                url: 'https://plugin.chaininsight.vip/api/v0/util/query/wallet_tags_v2',
                headers: {
                    'Content-Type': 'application/json',
                    'Accept': 'application/json'
                },
                data: JSON.stringify(requestData),
                timeout: 30000,
                onload: function(response) {
                    console.log(`[API响应] 状态码: ${response.status}`);

                    if (response.status !== 200) {
                        reject(new Error(`HTTP ${response.status}: ${response.statusText}`));
                        return;
                    }

                    try {
                        const data = JSON.parse(response.responseText);
                        console.log('[API响应] 响应数据:', data);

                        if (data.code !== 0) {
                            reject(new Error(data.msg || `API错误码: ${data.code}`));
                            return;
                        }

                        processTagData(data.data);
                        resolve();

                    } catch (error) {
                        console.error('[API响应] JSON解析失败:', error);
                        reject(new Error('响应数据解析失败'));
                    }
                },
                onerror: function(error) {
                    console.error('[API请求] 网络请求失败:', error);
                    reject(new Error('网络请求失败'));
                },
                ontimeout: function() {
                    console.warn('[API请求] 请求超时');
                    reject(new Error('请求超时'));
                }
            });
        });
    }

    /**
     * 处理标注数据
     */
    function processTagData(responseData) {
        console.log('[数据处理] 开始处理标注数据');

        if (!responseData || !responseData.walletTags) {
            console.warn('[数据处理] 响应数据格式异常');
            tagData = [];
            return;
        }

        // 创建地址到标注的映射
        const tagMap = {};
        responseData.walletTags.forEach(wallet => {
            tagMap[wallet.address] = {
                count: wallet.count || 0,
                tags: wallet.tags ? wallet.tags.map(tag => tag.tagName) : [],
                expertTags: wallet.expertTags ? wallet.expertTags.map(tag => tag.tagName) : []
            };
        });

        // 为所有地址创建完整的标注数据
        tagData = topHolders.map(address => {
            const walletTags = tagMap[address] || { count: 0, tags: [], expertTags: [] };
            return {
                address: address,
                tagCount: walletTags.count,
                tags: walletTags.tags,
                expertTags: walletTags.expertTags
            };
        });

        // 按标注数量降序排序
        tagData.sort((a, b) => b.tagCount - a.tagCount);

        console.log(`[数据处理] ✅处理完成,共${tagData.length}个地址,有标注的地址:${tagData.filter(w => w.tagCount > 0).length}个`);
    }

    /**
     * 更新按钮状态
     */
    function updateButtonState() {
        const button = document.getElementById('memeradar-btn');
        if (!button) return;

        if (!isDataReady) {
            button.disabled = true;
            button.textContent = '等待数据...';
            button.className = 'memeradar-btn';
        } else if (hasInterceptedTags && tagData.length > 0) {
            // 已拦截到标注数据,可直接查看
            button.disabled = false;
            button.textContent = '查看标注';
            button.className = 'memeradar-btn ready';
        } else if (tagData.length > 0) {
            // 已获取标注数据,可查看
            button.disabled = false;
            button.textContent = '查看标注';
            button.className = 'memeradar-btn ready';
        } else {
            // 需要获取标注数据
            button.disabled = false;
            button.textContent = '获取前排标注';
            button.className = 'memeradar-btn';
        }
    }

    /**
     * 显示错误弹窗
     */
    function showErrorModal(title, message) {
        const modal = document.createElement('div');
        modal.className = 'memeradar-modal';
        modal.innerHTML = `
            <div class="memeradar-modal-content" style="max-width: 500px;">
                <div class="memeradar-modal-header" style="background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);">
                    <h3 class="memeradar-modal-title">${title}</h3>
                    <button class="memeradar-modal-close">&times;</button>
                </div>
                <div class="memeradar-modal-body">
                    <div style="color: #e2e8f0; line-height: 1.6; white-space: pre-line;">${message}</div>
                </div>
            </div>
        `;

        document.body.appendChild(modal);

        // 绑定关闭事件
        modal.querySelector('.memeradar-modal-close').addEventListener('click', () => {
            document.body.removeChild(modal);
        });

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

    /**
     * 分批渲染錢包列表(大數據量優化)
     */
    function renderWalletsBatched(walletsList, walletsWithTags) {
        const BATCH_SIZE = 50;
        let currentIndex = 0;
        
        // 創建錢包行的函數(分批渲染用)
        function createWalletRowBatch(wallet) {
            return createWalletRow(wallet);
        }
        
        // 分批渲染函數
        function renderBatch() {
            const fragment = document.createDocumentFragment();
            const endIndex = Math.min(currentIndex + BATCH_SIZE, walletsWithTags.length);
            
            for (let i = currentIndex; i < endIndex; i++) {
                const walletRow = createWalletRowBatch(walletsWithTags[i]);
                fragment.appendChild(walletRow);
            }
            
            walletsList.appendChild(fragment);
            currentIndex = endIndex;
            
            // 如果還有未渲染的項目,繼續下一批
            if (currentIndex < walletsWithTags.length) {
                // 使用 requestAnimationFrame 確保不阻塞主線程
                requestAnimationFrame(renderBatch);
            } else {
                console.log('[性能優化] 分批渲染完成');
                // 渲染完成後添加事件委託
                setupWalletListEvents(walletsList);
            }
        }
        
        // 開始渲染
        renderBatch();
    }
    
    /**
     * 創建錢包表格行
     */
    function createWalletRow(wallet) {
        const resultItem = document.createElement('div');
        resultItem.className = 'memeradar-result-item';
        
        // 錢包信息容器
        const walletInfo = document.createElement('div');
        walletInfo.className = 'memeradar-wallet-info';
        
        // 錢包地址
        const walletAddress = document.createElement('div');
        walletAddress.className = 'memeradar-wallet-address';
        walletAddress.title = '點擊複製地址';
        walletAddress.textContent = wallet.address;
        walletAddress.dataset.address = wallet.address;
        
        // 標籤容器
        const tagsContainer = document.createElement('div');
        tagsContainer.className = 'memeradar-tags';
        
        // 添加專業玩家標籤
        if (wallet.expertTags && wallet.expertTags.length > 0) {
            wallet.expertTags.forEach(tag => {
                const expertTag = document.createElement('span');
                expertTag.className = 'memeradar-expert-tag';
                expertTag.textContent = tag;
                tagsContainer.appendChild(expertTag);
            });
        }
        
        // 添加普通標籤
        wallet.tags.forEach(tag => {
            const regularTag = document.createElement('span');
            regularTag.className = 'memeradar-tag';
            regularTag.textContent = tag;
            tagsContainer.appendChild(regularTag);
        });
        
        // 标注數量
        const tagCount = document.createElement('div');
        tagCount.className = 'memeradar-tag-count';
        tagCount.textContent = wallet.tagCount;
        
        // 組裝
        walletInfo.appendChild(walletAddress);
        walletInfo.appendChild(tagsContainer);
        resultItem.appendChild(walletInfo);
        resultItem.appendChild(tagCount);
        
        return resultItem;
    }

    /**
     * 設置錢包列表事件委託
     */
    function setupWalletListEvents(walletsList) {
        walletsList.addEventListener('click', (e) => {
            const addressElement = e.target.closest('.memeradar-wallet-address');
            if (addressElement) {
                const address = addressElement.dataset.address;
                navigator.clipboard.writeText(address).then(() => {
                    const originalColor = addressElement.style.color;
                    const originalBg = addressElement.style.background;
                    
                    addressElement.style.color = '#10b981';
                    addressElement.style.background = 'rgba(16, 185, 129, 0.1)';
                    
                    setTimeout(() => {
                        addressElement.style.color = originalColor;
                        addressElement.style.background = originalBg;
                    }, 1000);
                }).catch(err => {
                    console.warn('[複製失敗]', err);
                });
            }
        });
    }

    /**
     * 显示标注数据弹窗
     */
    function showTagsModal() {
        console.log('[界面显示] 显示标注数据弹窗');

        // 只显示有标注的钱包(包括有专业玩家标注的)
        const walletsWithTags = tagData.filter(w => w.tagCount > 0 || (w.expertTags && w.expertTags.length > 0));
        const hasTagsCount = walletsWithTags.length;
        const totalTags = walletsWithTags.reduce((sum, w) => sum + w.tagCount, 0);
        const expertTaggedAddressCount = walletsWithTags.filter(w => w.expertTags && w.expertTags.length > 0).length;

        const modal = document.createElement('div');
        modal.className = 'memeradar-modal';
        modal.innerHTML = `
            <div class="memeradar-modal-content">
                <div class="memeradar-modal-header">
                    <h3 class="memeradar-modal-title">前排标注信息<span class="memeradar-modal-subtitle"> - ${currentCA.slice(0, 8)}...${currentCA.slice(-6)}</span></h3>
                    <div class="memeradar-header-actions">
                        <button class="memeradar-export-btn" id="export-excel-btn">📊 導出Excel</button>
                        <button class="memeradar-modal-close">×</button>
                    </div>
                </div>
                <div class="memeradar-analysis-summary">
                    <div class="memeradar-summary-stats">
                        <div class="memeradar-stat-item">
                            <div class="memeradar-stat-label">总地址數:</div>
                            <div class="memeradar-stat-value">${tagData.length}</div>
                        </div>
                        <div class="memeradar-stat-item">
                            <div class="memeradar-stat-label">有标注地址:</div>
                            <div class="memeradar-stat-value">${hasTagsCount}</div>
                        </div>
                        <div class="memeradar-stat-item">
                            <div class="memeradar-stat-label">总标注數:</div>
                            <div class="memeradar-stat-value">${totalTags}</div>
                        </div>
                        <div class="memeradar-stat-item">
                            <div class="memeradar-stat-label">专业玩家标注地址數:</div>
                            <div class="memeradar-stat-value">${expertTaggedAddressCount}</div>
                        </div>
                    </div>
                </div>
                ${walletsWithTags.length === 0 ?
                    '<div class="memeradar-loading">📝 暂无标注数据</div>' :
                    '<div id="wallets-list"></div>'
                }
            </div>
        `;

        document.body.appendChild(modal);

        // 填充钱包列表 - 只显示有标注的钱包(卡片版本)
        if (walletsWithTags.length === 0) {
            return; // 無數據時直接返回
        }
        
        const walletsList = modal.querySelector('#wallets-list');
        
        // 大數據量時使用分批渲染,提升性能
        const BATCH_SIZE = 100;
        const shouldUseBatchRendering = walletsWithTags.length > BATCH_SIZE;
        
        if (shouldUseBatchRendering) {
            console.log(`[性能優化] 檢測到${walletsWithTags.length}個錢包項目,啟用分批渲染`);
            renderWalletsBatched(walletsList, walletsWithTags);
            return;
        }
        
        // 使用DocumentFragment批量操作,避免多次DOM重排
        const fragment = document.createDocumentFragment();
        
        // 批量创建所有钱包行
        const walletRows = walletsWithTags.map(wallet => {
            return createWalletRow(wallet);
        });
        
        // 批量添加到fragment
        walletRows.forEach(row => fragment.appendChild(row));
        
        // 一次性添加到DOM
        walletsList.appendChild(fragment);
        
        // 設置事件委託
        setupWalletListEvents(walletsList);

        // 绑定导出Excel按钮事件
        const exportBtn = modal.querySelector('#export-excel-btn');
        if (exportBtn) {
            exportBtn.addEventListener('click', exportToExcel);
        }

        // 绑定关闭事件
        const closeModal = () => {
            if (modal && modal.parentNode) {
                document.body.removeChild(modal);
            }
        };

        // 關閉按鈕事件
        const closeBtn = modal.querySelector('.memeradar-modal-close');
        if (closeBtn) {
            closeBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                closeModal();
            });
        }

        // 背景點擊關閉
        modal.addEventListener('click', (e) => {
            if (e.target === modal) {
                closeModal();
            }
        });

        // ESC 鍵關閉
        const handleKeyDown = (e) => {
            if (e.key === 'Escape') {
                closeModal();
                document.removeEventListener('keydown', handleKeyDown);
            }
        };
        document.addEventListener('keydown', handleKeyDown);
    }

    /**
     * 导出数据到Excel
     */
    function exportToExcel() {
        try {
            console.log('[Excel导出] 开始导出标注数据');

            // 只导出有标注的地址(包括专业玩家标注)
            const walletsWithTags = tagData.filter(wallet => wallet.tagCount > 0 || (wallet.expertTags && wallet.expertTags.length > 0));
            console.log(`[Excel导出] 过滤后数据:总地址${tagData.length}个,有标注${walletsWithTags.length}个`);

            if (walletsWithTags.length === 0) {
                alert('没有找到有标注的地址,无法导出Excel文件');
                return;
            }

            // 准备Excel数据 - 只包含有标注的地址
            const excelData = walletsWithTags.map((wallet, index) => ({
                '排名': index + 1,
                '钱包地址': wallet.address,
                '标注数量': wallet.tagCount,
                '标签列表': wallet.tags.join(', '),
                '专业玩家打标': (wallet.expertTags && wallet.expertTags.length > 0) ? wallet.expertTags.join(',') : ''
            }));

            // 创建工作簿
            const wb = XLSX.utils.book_new();

            // 创建标注数据工作表
            const ws = XLSX.utils.json_to_sheet(excelData);
            XLSX.utils.book_append_sheet(wb, ws, "标注数据");

            // 生成文件名
            const now = new Date();
            const timeStr = now.toISOString().slice(0, 19).replace(/[:\-T]/g, '');
            const caShort = currentCA.slice(0, 8) + '...' + currentCA.slice(-6);
            const fileName = `${timeStr}-前排标注-${caShort}.xlsx`;

            // 下载文件
            XLSX.writeFile(wb, fileName);

            console.log(`[Excel导出] ✅Excel文件导出成功: ${fileName},包含${walletsWithTags.length}个有标注地址`);

            // 显示成功提示
            const exportBtn = document.getElementById('export-excel-btn');
            if (exportBtn) {
                const originalText = exportBtn.innerHTML;
                exportBtn.innerHTML = '✅ 导出成功';
                exportBtn.style.background = 'linear-gradient(135deg, #10b981 0%, #059669 100%)';
                setTimeout(() => {
                    exportBtn.innerHTML = originalText;
                    exportBtn.style.background = '';
                }, 2000);
            }

        } catch (error) {
            console.error('[Excel导出] ❌导出失败:', error);
            alert('Excel导出失败: ' + error.message);
        }
    }

    // 页面切换监听 - 精确检测CA地址变化
    let lastUrl = location.href;
    let lastCA = '';

    function checkCAChange() {
        const url = location.href;

        // 提取当前URL中的CA地址 - 支持多链
        let urlCA = '';
        const caMatch = url.match(/\/(sol|eth|bsc|base|tron)\/([A-Za-z0-9]{32,})/);
        if (caMatch) {
            urlCA = caMatch[2]; // CA地址
            // 也可以获取链网络: caMatch[1]
        }

        // 检查CA是否变化
        if (lastCA && lastCA !== urlCA && urlCA) {
            console.log(`[页面切换] 🔄检测到CA地址变化: ${lastCA} → ${urlCA}`);
            console.log(`[页面切换] 完整URL变化: ${lastUrl} → ${url}`);
            resetAllData();
            updateButtonState();
            lastCA = urlCA;
            lastUrl = url;
            return true;
        } else if (urlCA && !lastCA) {
            // 首次进入代币页面
            console.log(`[页面切换] 🎯首次进入代币页面: ${urlCA}`);
            lastCA = urlCA;
            lastUrl = url;
            return false;
        } else if (!urlCA && lastCA) {
            // 离开代币页面
            console.log(`[页面切换] 🚪离开代币页面: ${lastCA}`);
            resetAllData();
            updateButtonState();
            lastCA = '';
            lastUrl = url;
            return true;
        } else if (url !== lastUrl) {
            // URL变化但CA未变化(如参数变化)
            console.log(`[页面切换] 📝URL变化但CA未变(${urlCA || '无CA'}): ${url}`);
            lastUrl = url;
            return false;
        }

        return false;
    }

    // 监听页面变化
    new MutationObserver(() => {
        checkCAChange();
    }).observe(document, { subtree: true, childList: true });

    // 监听浏览器前进后退
    window.addEventListener('popstate', () => {
        setTimeout(checkCAChange, 100); // 延迟检查,确保URL已更新
    });

    // 初始化检查
    checkCAChange();

    console.log(`
🏷️ MemeRadar前排标注查询工具 v2.3 已启动
📋 v2.3优化更新:
   • 📈 Excel导出优化 - 只导出有标注的地址,精简数据
   • 🎯 智能数据过滤 - 避免导出无价值的空标注数据
   • ⚠️ 异常处理增强 - 无标注数据时给出友好提示

📋 v2.2重大优化:
   • 🎯 智能单次拦截 - token_holders成功后不再重复拦截
   • 🔄 精确CA切换检测 - 支持多链地址变化监听
   • 🚪 智能页面状态管理 - 进入/离开代币页面自动处理
   • 📊 减少不必要拦截 - 大幅提升性能和稳定性
   • 🛡️ 防重复请求机制 - 避免资源浪费

📋 核心功能:
   • 🎯 智能拦截token_holders和wallet_tags_v2 API
   • 🌐 支持多链网络 (SOL/ETH/BSC/BASE/TRON)
   • 🏷️ 获取前100持仓者标注信息
   • 📊 优雅的数据展示界面 (只显示有标注地址)
   • 📈 精简的Excel数据导出功能 (仅含有标注地址)
   • 🔄 精确的CA切换检测和状态重置

🔍 监听状态: 已启用
📍 当前页面: ${window.location.href}
    `);

})();