GMGN 前排数据统计

统计GMGN任意代币前排地址的数据,让数字来说话!

当前为 2025-07-19 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         GMGN 前排数据统计
// @namespace    http://tampermonkey.net/
// @version      1.7
// @description  统计GMGN任意代币前排地址的数据,让数字来说话!
// @match        https://gmgn.ai/*
// @match        https://www.gmgn.ai/*
// @grant        none
// @run-at       document-start
// @license MIT
// ==/UserScript==


/*
各位看官,如果脚本好用,可否点个关注?x:@tianxiamingxin
*/

(function() {
    'use strict';

    // 动态添加 CSS
    const style = document.createElement('style');
    style.textContent = `
        .gmgn-stats-container {
            background-color: #000;
            border-radius: 4px;
            font-family: Arial, sans-serif;
            margin-right: 10px;
        }
        .gmgn-stats-header, .gmgn-stats-data {
            display: grid;
            grid-template-columns: repeat(8, 1fr);
            text-align: center;
            gap: 8px;
            font-weight: normal;
        }
        .gmgn-stats-header span {
            color: #ccc;
            font-weight: normal;
        }
        .gmgn-stats-data span {
            color: #00ff00;
            font-weight: normal;
        }
    `;
    document.head.appendChild(style);

    // 存储拦截到的数据
    let interceptedData = null;

    // 1. 拦截 fetch 请求
    const originalFetch = window.fetch;
    window.fetch = function(url, options) {
        if (isTargetApi(url)) {
            console.log('[拦截] fetch 请求:', url);
            return originalFetch.apply(this, arguments)
                .then(response => {
                if (response.ok) {
                    processResponse(response.clone());
                }
                return response;
            });
        }
        return originalFetch.apply(this, arguments);
    };

    // 2. 拦截 XMLHttpRequest
    const originalXHR = window.XMLHttpRequest;
    window.XMLHttpRequest = function() {
        const xhr = new originalXHR();
        const originalOpen = xhr.open;
        xhr.open = function(method, url) {
            if (isTargetApi(url)) {
                console.log('[拦截] XHR 请求:', url);
                const originalOnload = xhr.onload;
                xhr.onload = function() {
                    if (xhr.readyState === 4 && xhr.status === 200) {
                        processResponse(xhr.responseText);
                    }
                    originalOnload?.apply(this, arguments);
                };
            }
            return originalOpen.apply(this, arguments);
        };
        return xhr;
    };

    // 检测目标 API
    function isTargetApi(url) {
        return typeof url === 'string' &&
            /vas\/api\/v1\/token_holders\/sol(\/|$|\?)/i.test(url);
    }

    // 处理响应数据
    function processResponse(response) {
        console.log('开始处理响应数据');

        try {
            const dataPromise = typeof response === 'string' ?
                  Promise.resolve(JSON.parse(response)) :
            response.json();

            dataPromise.then(data => {
                interceptedData = data;
                console.log('[成功] 拦截到数据量:', data.data?.list?.length);
                console.log('[成功] 拦截到数据:',data)
                updateStatsDisplay();
            }).catch(e => console.error('解析失败:', e));
        } catch (e) {
            console.error('处理响应错误:', e);
        }
    }

    // 3. 计算所有统计指标
    function calculateStats() {
        if (!interceptedData?.data?.list) return null;

        const currentTime = Math.floor(Date.now() / 1000); // 修复:移入函数内部
        const holders = interceptedData.data.list;
        const stats = {
            fullPosition: 0,    // 全仓
            profitable: 0,      // 盈利
            losing: 0,         // 亏损
            active24h: 0,      // 24h活跃
            diamondHands: 0,   // 钻石手
            newAddress: 0,     // 新地址
            highProfit: 0,     // 10x盈利
            suspicious: 0        // 新增:可疑地址
        };

        holders.forEach(holder => {
            if (holder.sell_amount_percentage === 0) stats.fullPosition++;
            if (holder.profit > 0) stats.profitable++;
            if (holder.profit < 0) stats.losing++;
            if (holder.last_active_timestamp > currentTime - 86400) stats.active24h++;
            if (holder.maker_token_tags?.includes('diamond_hands')) stats.diamondHands++;
            if (holder.is_new) stats.newAddress++;
            if (holder.profit_change > 10) stats.highProfit++;
            if (holder.is_suspicious) stats.suspicious++; // 新增检测
        });
        console.log('计算所有统计指标',stats)
        return stats;
    }


    // 1. 持久化容器监听
    const observer = new MutationObserver(() => {
        const targetContainer = document.querySelector('.flex.overflow-x-auto.overflow-y-hidden.scroll-smooth.w-full');
        if (targetContainer && !targetContainer.querySelector('#gmgn-stats-item')) {
            injectStatsItem(targetContainer);
        }
    });



    function injectStatsItem(container) {
        if (container.querySelector('#gmgn-stats-item')) return;

        const statsItem = document.createElement('div');
        statsItem.id = 'gmgn-stats-item';
        statsItem.className = 'gmgn-stats-container'; // 新增 CSS 类
        statsItem.innerHTML = `
        <div class="gmgn-stats-header">
            <span>满仓</span>
            <span>盈利</span>
            <span>亏损</span>
            <span>活跃</span>
            <span>钻石</span>
            <span>新址</span>
            <span>10x</span>
            <span>可疑</span>
        </div>
        <div class="gmgn-stats-data">
            <span id="fullPosition">-</span>
            <span id="profitable">-</span>
            <span id="losing">-</span>
            <span id="active24h">-</span>
            <span id="diamondHands">-</span>
            <span id="newAddress">-</span>
            <span id="highProfit">-</span>
            <span id="suspicious">-</span>
        </div>
    `;
        container.insertAdjacentElement('afterbegin', statsItem);
    }



    // 3. 启动监听
    function startObserving() {
        // 立即检查一次
        const initialContainer = document.querySelector('.flex.overflow-x-auto.overflow-y-hidden.scroll-smooth.w-full');
        if (initialContainer) injectStatsItem(initialContainer);

        // 持续监听DOM变化
        observer.observe(document.body, {
            childList: true,
            subtree: true,
            attributes: false
        });
    }


    // 5. 更新统计显示
    function updateStatsDisplay() {
        const stats = calculateStats();
        if (!stats) return;

        // 确保DOM已存在
        if (!document.getElementById('gmgn-stats-item')) {
            injectStatsItem();
        }

        // 更新所有指标(绿色加粗)
        document.getElementById('fullPosition').innerHTML = `<strong style="color:green">${stats.fullPosition}</strong>`;
        document.getElementById('profitable').innerHTML = `<strong style="color: green">${stats.profitable}</strong>`;
        document.getElementById('losing').innerHTML = `<strong style="color: #ff5252">${stats.losing}</strong>`;
        document.getElementById('active24h').innerHTML = `<strong style="color: green">${stats.active24h}</strong>`;
        document.getElementById('diamondHands').innerHTML = `<strong style="color: green">${stats.diamondHands}</strong>`;
        document.getElementById('newAddress').innerHTML = `<strong style="color: green">${stats.newAddress}</strong>`;
        document.getElementById('highProfit').innerHTML = `<strong style="color: green">${stats.highProfit}</strong>`;
        document.getElementById('suspicious').innerHTML = `<strong style="color: #ff5252">${stats.suspicious}</strong>`;
    }

    /*
// 白色系
'#ffffff'  // 纯白 (white)
'#f8f9fa'  // 浅白 (light white)
'#e9ecef'  // 灰白 (off-white)

// 绿色系(按亮度排序)
'#00ff00'  // 荧光绿 (图片同款)
'#00cc00'  // 亮绿
'#28a745'  // Bootstrap成功绿
'#228b22'  // 森林绿

// 红色系
'#ff0000'  // 纯红 (图片同款)
'#dc3545'  // Bootstrap危险红
'#c00000'  // 深红
'#ff4500'  // 橙红

// 其他常用数据颜色
'#ffa500'  // 橙色 (警告)
'#ffff00'  // 黄色 (注意)
'#007bff'  // 蓝色 (信息)
'#6f42c1'  // 紫色 (特殊标识)

*/

    // 4. 初始化
    if (document.readyState === 'complete') {
        startObserving();
    } else {
        window.addEventListener('DOMContentLoaded', startObserving);
    }



})();