Greasy Fork

Greasy Fork is available in English.

GMGN交易者数据导出

监听GMGN.ai交易者数据并提供Excel导出功能

当前为 2025-08-10 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         GMGN交易者数据导出
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  监听GMGN.ai交易者数据并提供Excel导出功能
// @author       You
// @match        https://gmgn.ai/sol/token/*
// @match        https://gmgn.ai/eth/token/*
// @match        https://gmgn.ai/bsc/token/*
// @match        https://gmgn.ai/base/token/*
// @match        https://gmgn.ai/arb/token/*
// @match        https://gmgn.ai/op/token/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    let tradersData = [];
    let currentCA = '';
    let currentChain = '';
    let isListening = false;

    // 从URL中提取CA地址和链网络
    function extractCAFromURL() {
        const url = window.location.pathname;
        const match = url.match(/\/(\w+)\/token\/(?:\w+_)?([A-Za-z0-9]+)$/);
        if (match) {
            const chain = match[1];
            const ca = match[2];
            return { chain, ca };
        }
        return null;
    }

    // 清空数据并重新开始监听
    function resetData() {
        tradersData = [];
        isListening = false;
        const urlInfo = extractCAFromURL();
        if (urlInfo) {
            currentCA = urlInfo.ca;
            currentChain = urlInfo.chain;
            console.log(`开始监听新的CA: ${currentChain}/${currentCA}`);
            startXHRInterception();
        }
    }

    // 拦截XHR请求
    function startXHRInterception() {
        if (isListening) return;
        isListening = true;

        const originalXHROpen = XMLHttpRequest.prototype.open;
        const originalXHRSend = XMLHttpRequest.prototype.send;

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

        XMLHttpRequest.prototype.send = function(...args) {
            const xhr = this;

            // 检查是否为交易者数据请求
            if (xhr._url && xhr._url.includes(`/vas/api/v1/token_traders/${currentChain}/${currentCA}`)) {
                console.log('检测到交易者数据请求:', xhr._url);

                const originalOnReadyStateChange = xhr.onreadystatechange;
                xhr.onreadystatechange = function() {
                    if (xhr.readyState === 4 && xhr.status === 200) {
                        try {
                            const responseData = JSON.parse(xhr.responseText);
                            if (responseData.code === 0 && responseData.data && responseData.data.list) {
                                tradersData = responseData.data.list;
                                console.log(`获取到 ${tradersData.length} 条交易者数据`);
                                updateDownloadButton();
                            }
                        } catch (e) {
                            console.error('解析交易者数据失败:', e);
                        }
                    }
                    if (originalOnReadyStateChange) {
                        originalOnReadyStateChange.apply(xhr, arguments);
                    }
                };
            }

            return originalXHRSend.apply(this, args);
        };
    }

    // 格式化金额为$xxxK/M/B格式
    function formatCurrency(value) {
        if (!value || value === 0) return '$0';

        const absValue = Math.abs(value);
        let formattedValue;
        let suffix;

        if (absValue >= 1000000000) {
            formattedValue = (value / 1000000000).toFixed(1);
            suffix = 'B';
        } else if (absValue >= 1000000) {
            formattedValue = (value / 1000000).toFixed(1);
            suffix = 'M';
        } else if (absValue >= 1000) {
            formattedValue = (value / 1000).toFixed(1);
            suffix = 'K';
        } else {
            formattedValue = value.toFixed(2);
            suffix = '';
        }

        // 移除不必要的.0
        if (formattedValue.endsWith('.0')) {
            formattedValue = formattedValue.slice(0, -2);
        }

        return `$${formattedValue}${suffix}`;
    }

    // 格式化时间戳
    function formatTimestamp(timestamp) {
        if (!timestamp) return '-';
        const date = new Date(timestamp * 1000);
        return date.toLocaleDateString('zh-CN');
    }

    // 计算持仓时间(小时)
    function calculateHoldingTime(startTime, endTime) {
        if (!startTime || !endTime) return '-';
        const hours = Math.round((endTime - startTime) / 3600);
        return hours > 0 ? `${hours}小时` : '-';
    }

    // 导出Excel数据
    function exportToExcel() {
        if (tradersData.length === 0) {
            alert('没有获取到交易者数据,请切换到【交易者】tab页或重新刷新网页');
            return;
        }

        const headers = ['交易者地址', 'SOL余额', '总买入', '总卖出', '平均买价', '平均卖价', '总利润', '利润率', '持仓时间', '最后活跃'];

        let csvContent = "data:text/csv;charset=utf-8,\uFEFF" + headers.join(',') + '\n';

        tradersData.forEach(trader => {
            const row = [
                trader.address || '-',
                (parseFloat(trader.native_balance) / 1000000000).toFixed(2) || '0.00',
                formatCurrency(trader.buy_volume_cur || 0),
                formatCurrency(trader.sell_volume_cur || 0),
                trader.avg_cost?.toFixed(8) || '0',
                trader.avg_sold?.toFixed(8) || '0',
                formatCurrency(trader.profit || 0),
                ((trader.profit_change || 0) * 100).toFixed(2) + '%',
                calculateHoldingTime(trader.start_holding_at, trader.end_holding_at),
                formatTimestamp(trader.last_active_timestamp)
            ];
            csvContent += row.join(',') + '\n';
        });

        const encodedUri = encodeURI(csvContent);
        const link = document.createElement("a");
        link.setAttribute("href", encodedUri);
        link.setAttribute("download", `gmgn_traders_${currentChain}_${currentCA}_${new Date().getTime()}.csv`);
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);

        console.log(`成功导出 ${tradersData.length} 条交易者数据`);
    }

    // 创建下载按钮
    function createDownloadButton() {
        const button = document.createElement('div');
        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.id = 'gmgn-export-button';
        button.innerHTML = `
            <svg width="12px" height="12px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
                <path d="M8.35343 15.677L13.881 7.19356C14.098 6.86024 14.01 6.40976 13.684 6.1877C13.5677 6.10833 13.431 6.066 13.2912 6.06606H8.94507V0.725344C8.94507 0.324837 8.62782 0 8.23639 0C7.99931 0 7.77814 0.121166 7.64658 0.322951L2.11903 8.80644C1.902 9.13976 1.99001 9.59 2.31579 9.8123C2.43216 9.89175 2.56893 9.93416 2.70883 9.93417H7.05494V15.2747C7.05494 15.6752 7.37219 16 7.76362 16C8.0007 16 8.2221 15.8788 8.35343 15.677Z"></path>
            </svg>
            导出交易者数据
        `;

        button.addEventListener('click', exportToExcel);
        return button;
    }

    // 更新下载按钮状态
    function updateDownloadButton() {
        const button = document.getElementById('gmgn-export-button');
        if (button) {
            if (tradersData.length > 0) {
                button.style.opacity = '1';
                button.title = `点击导出 ${tradersData.length} 条交易者数据`;
            } else {
                button.style.opacity = '0.6';
                button.title = '没有数据,请切换到交易者tab页';
            }
        }
    }

    // 插入下载按钮到页面
    function insertDownloadButton() {
        const targetDiv = document.querySelector('.flex.absolute.top-0.right-0.gap-8px.pl-4px');
        const existingButton = document.getElementById('gmgn-export-button');

        if (targetDiv && !existingButton) {
            const downloadButton = createDownloadButton();
            targetDiv.insertBefore(downloadButton, targetDiv.firstChild);
            updateDownloadButton();
            console.log('下载按钮已插入');
        }
    }

    // 监听页面变化
    function observePageChanges() {
        let lastUrl = location.href;

        // 使用MutationObserver监听DOM变化
        const observer = new MutationObserver(function(mutations) {
            if (location.href !== lastUrl) {
                lastUrl = location.href;
                console.log('URL变化,重置数据:', lastUrl);
                resetData();

                // 延迟插入按钮,等待页面加载
                setTimeout(() => {
                    insertDownloadButton();
                }, 2000);
            }

            // 检查是否需要重新插入按钮
            if (!document.getElementById('gmgn-export-button')) {
                setTimeout(() => {
                    insertDownloadButton();
                }, 1000);
            }
        });

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

    // 初始化
    function init() {
        console.log('GMGN交易者数据导出插件已启动');
        resetData();

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

    init();

})();