NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Top Tokens with Markers – Extended Alerts // @namespace http://tampermonkey.net/ // @version 1.21-mod // @description Расширенные уведомления с двумя порогами объёма и тремя индикаторами (или прочерками) в таблице. Также сохраняет самый волатильный токен (топ-1 по объёму) в localStorage. // @author Mars // @match https://photon-sol.tinyastro.io/en/trending* // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; const NOTIFIED_TOKENS_KEY = 'notifiedTokenAddresses'; const TELEGRAM_BOT_TOKEN = '7871085451:AAGMRpygqWTSmYNsopvarYfspbiyxJ1S6pQ'; const TELEGRAM_CHAT_ID = '-4792720092'; const VOLUME_THRESHOLD_500K = 500000; const VOLUME_THRESHOLD_2M = 1200000; // Минимальный объём для уведомления по росту const VOLUME_MIN_GROWTH = 200000; // Новый диапазон ликвидности для уведомлений const LIQUIDITY_THRESHOLD_MIN = 100000; const LIQUIDITY_THRESHOLD_MAX = 700000; const PRICE_JUMP_MIN = 150; const PRICE_JUMP_MAX = 400; const PRICE_DROP_THRESHOLD = -50; const PRICE_DROP_30M_THRESHOLD = -50; const TRIGGER_SYMBOLS = { vol2M: '2', vol500: '5', growth: '↑' }; // Здесь будем хранить массив триггеров для каждого токена const notificationTriggers = {}; const newTokensWithHighLiquidity = new Set(); // Для контроля интервала между уведомлениями о росте (10 минут) const GROWTH_COOLDOWN = 10 * 60 * 1000; const lastGrowthAlertTimes = {}; function getNotifiedTokens() { const notified = JSON.parse(localStorage.getItem(NOTIFIED_TOKENS_KEY)) || []; const oneDayInMilliseconds = 24 * 60 * 60 * 1000; const currentTime = Date.now(); return notified.filter(token => (currentTime - token.timestamp) <= oneDayInMilliseconds); } function addNotifiedToken(address) { let notified = getNotifiedTokens(); const currentTime = Date.now(); notified = notified.filter(token => currentTime - token.timestamp <= 24 * 60 * 60 * 1000); notified.push({ address, timestamp: currentTime }); localStorage.setItem(NOTIFIED_TOKENS_KEY, JSON.stringify(notified)); } // Для объёмных уведомлений – ростовые будут отправляться без этой проверки const notifiedTokens = new Set(getNotifiedTokens().map(token => token.address)); function sendTelegramMessage(message) { const url = `https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage`; const params = new URLSearchParams(); params.append('chat_id', TELEGRAM_CHAT_ID); params.append('text', message); fetch(url, { method: 'POST', body: params }) .then(response => response.json()) .then(data => { if (!data.ok) { console.error('Ошибка отправки в Telegram:', data); } else { console.log('Отправлено в Telegram:', message); } }) .catch(error => console.error('Ошибка при запросе в Telegram:', error)); } function formatTokenMessage(token, reason) { const { tokenAddress, symbol, volume, cur_liq, gains } = token; const liquidity = cur_liq?.usd ? formatNumber(cur_liq.usd) : '–'; const volumeFormatted = formatNumber(volume); const growth5m = gains?.["5m"] ? gains["5m"].toFixed(2) + "%" : "–"; const lpLink = `https://photon-sol.tinyastro.io/en/lp/${tokenAddress}?handle=727809998130fdf6f5aa6`; if (reason.includes("Рост")) { return `🚀 ${symbol} вырос на ${growth5m} за 5 минут!\n\n` + `💰 Volume: ${volumeFormatted} USD\n` + `📊 Liquidity: ${liquidity} USD\n\n` + `${lpLink}`; } else { const emoji = volume > VOLUME_THRESHOLD_2M ? "🔥🔥" : "👁️"; return `${emoji} Volume ${volumeFormatted} USD on ${symbol}!\n\n` + `💰 Volume: ${volumeFormatted} USD\n` + `📊 Liquidity: ${liquidity} USD\n` + `📈 Pumps for last 5 mins: ${growth5m}\n\n` + `${lpLink}`; } } function formatNumber(number) { const value = Number(number); if (value >= 1000000) { return (value / 1000000).toFixed(1) + 'M'; } else if (value >= 1000) { return Math.floor(value / 1000) + 'K'; } else { return value.toFixed(0); } } function fetchTokens() { const url = 'https://photon-sol.tinyastro.io/api/trending?dexes=raydium&period=1m&usd_liq_from=10000&usd_liq_to=2000000'; fetch(url) .then(response => response.json()) .then(data => { if (!data || !data.data) return; let tokens = data.data.filter(token => { const hasMarker = token.fromPump || token.fromMoonshot || (token.fromMemeDex && token.fromMemeDex !== false); const hasLiquidity = token.cur_liq?.usd && token.cur_liq.usd >= 10000 && token.cur_liq.usd <= 2000000; return hasMarker && hasLiquidity; }); // Сортировка токенов по объёму (от большего к меньшему) tokens.sort((a, b) => b.volume - a.volume); // Сохраняем топ-1 токен (самый волатильный по объёму) в localStorage, // используя поле "address" из API if (tokens.length > 0) { const topTokenAddress = tokens[0].address; localStorage.setItem('topToken', JSON.stringify({ address: topTokenAddress })); } const now = Date.now(); tokens.forEach(token => { const tokenAddress = token.tokenAddress; const volume = Number(token.volume); const liquidity = token.cur_liq?.usd ? Number(token.cur_liq.usd) : 0; const growth1m = token.gains?.["1m"] || 0; const growth5m = token.gains?.["5m"] || 0; const growth30m = token.gains?.["30m"] || 0; const growth1h = token.gains?.["1h"] || 0; const timestamp = token.timestamp ? new Date(token.timestamp * 1000) : null; // Пропуск токенов с сильным падением цены if (growth1m <= PRICE_DROP_THRESHOLD || growth5m <= PRICE_DROP_THRESHOLD || growth30m <= PRICE_DROP_30M_THRESHOLD || growth1h <= PRICE_DROP_THRESHOLD) { return; } // Функция для добавления триггера в массив уведомлений для токена function addTrigger(trigger) { if (!notificationTriggers[tokenAddress]) { notificationTriggers[tokenAddress] = []; } if (!notificationTriggers[tokenAddress].includes(trigger)) { notificationTriggers[tokenAddress].push(trigger); } } // Уведомления по росту цены (price jump) if (liquidity >= LIQUIDITY_THRESHOLD_MIN && liquidity <= LIQUIDITY_THRESHOLD_MAX) { if (growth5m >= PRICE_JUMP_MIN && growth5m <= PRICE_JUMP_MAX && volume >= VOLUME_MIN_GROWTH) { if (!lastGrowthAlertTimes[tokenAddress] || (now - lastGrowthAlertTimes[tokenAddress] >= GROWTH_COOLDOWN)) { sendTelegramMessage(formatTokenMessage(token, `Рост ${growth5m.toFixed(2)}% за 5 минут`)); lastGrowthAlertTimes[tokenAddress] = now; addTrigger('growth'); } } } // Уведомления по объёму – отдельно для каждого порога if (liquidity >= LIQUIDITY_THRESHOLD_MIN && liquidity <= LIQUIDITY_THRESHOLD_MAX) { const tokenKey500 = tokenAddress + '_500K'; const tokenKey2M = tokenAddress + '_2M'; if (volume > VOLUME_THRESHOLD_500K && !notifiedTokens.has(tokenKey500)) { sendTelegramMessage(formatTokenMessage(token, `Объём ${volume.toFixed(2)} USD`)); notifiedTokens.add(tokenKey500); addNotifiedToken(tokenKey500); addTrigger('vol500'); } if (volume > VOLUME_THRESHOLD_2M && !notifiedTokens.has(tokenKey2M)) { sendTelegramMessage(formatTokenMessage(token, `Объём ${volume.toFixed(2)} USD`)); notifiedTokens.add(tokenKey2M); addNotifiedToken(tokenKey2M); addTrigger('vol2M'); } } // Трекинг новых токенов с высокой ликвидностью if (liquidity >= 700000 && timestamp && (new Date() - timestamp <= 10 * 60 * 1000)) { newTokensWithHighLiquidity.add(tokenAddress); } }); updateTable(tokens); }) .catch(error => console.error('Ошибка при получении токенов:', error)); } // Формируем ячейку таблицы с 3 столбцами-индикаторами: для vol2M, vol500 и growth. // Если уведомления не было, выводится прочерк "–". function formatNotificationCell(tokenAddress) { const triggers = ['vol2M', 'vol500', 'growth']; return triggers.map(trigger => { return (notificationTriggers[tokenAddress] && notificationTriggers[tokenAddress].includes(trigger)) ? TRIGGER_SYMBOLS[trigger] : '–'; }).join(' '); } function updateTable(tokens) { const tbody = document.getElementById('token-tbody'); tbody.innerHTML = ''; tokens.forEach(token => { const tr = document.createElement('tr'); const change5m = token.gains?.["5m"] ? token.gains["5m"].toFixed(2) + "%" : "–"; const notificationStatus = formatNotificationCell(token.tokenAddress); const liquidity = token.cur_liq?.usd ? Number(token.cur_liq.usd) : 0; const volume = Number(token.volume); let rowStyle = ''; const drop5m = token.gains?.["5m"] || 0; const drop30m = token.gains?.["30m"] || 0; const isDrop = (drop5m <= -50 || drop30m <= -50); if (!isDrop) { if (volume > VOLUME_THRESHOLD_2M) { rowStyle = 'background-color: #ff6347;'; // Красный для объёма > 1.2M } else if (volume > VOLUME_THRESHOLD_500K) { rowStyle = 'background-color: #6495ed;'; // Синий для объёма > 500K } } if (!rowStyle && liquidity > LIQUIDITY_THRESHOLD_MIN) { rowStyle = 'background-color: #dbdbdb;'; } tr.innerHTML = ` <td style="color: black;">${token.symbol}</td> <td style="color: black;">${formatNumber(token.volume)}</td> <td style="color: black;">${formatNumber(token.cur_liq?.usd)}</td> <td style="color: black;">${change5m}</td> <td style="color: black; text-align: center;">${notificationStatus}</td> `; tr.style = rowStyle; tbody.appendChild(tr); }); } const container = document.createElement('div'); container.style.position = 'fixed'; container.style.bottom = '10px'; container.style.left = '10px'; container.style.backgroundColor = 'white'; container.style.borderRadius = '8px'; container.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)'; container.style.zIndex = '9999'; container.style.padding = '10px'; container.innerHTML = ` <table border="1" style="width:100%; margin-top:10px;"> <thead> <tr> <th style="color: black;">Symbol</th> <th style="color: black;">Volume</th> <th style="color: black;">Liquidity</th> <th style="color: black;">5 min</th> <th style="color: black;">TG</th> </tr> </thead> <tbody id="token-tbody"></tbody> </table> `; document.body.appendChild(container); fetchTokens(); setInterval(fetchTokens, 5000); // Глобальная функция для очистки внутреннего хранилища уведомлений и localStorage. window.clearNotifiedTokens = function() { notifiedTokens.clear(); localStorage.removeItem(NOTIFIED_TOKENS_KEY); // Сбрасываем триггеры уведомлений для таблицы for (const key in notificationTriggers) { if (notificationTriggers.hasOwnProperty(key)) { notificationTriggers[key] = []; } } console.log('notifiedTokens, notificationTriggers и localStorage очищены'); } })();