Greasy Fork is available in English.
监听GMGN.ai交易者数据并提供Excel导出功能
当前为
// ==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();
})();