您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
A GUI for the Rugpull API.
// ==UserScript== // @name RugPlay Universal API GUI // @namespace http://tampermonkey.net/ // @version 1.01 // @description A GUI for the Rugpull API. // @author 4koy // @match https://rugplay.com/* // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @license MIT // ==/UserScript== (function() { 'use strict'; // TODO: maybe make this configurable later idk let apiKey = GM_getValue('rugplay_api_key', ''); // main gui stuff function createGUI() { const gui = document.createElement('div'); gui.id = 'rugplay-api-gui'; gui.innerHTML = ` <div class="api-header"> <h2>RugPlay *UNIVERSE Utility</h2> <button id="toggle-gui">−</button> </div> <div class="api-content"> <div class="api-key-section"> <label for="api-key-input">API Key:</label> <input type="password" id="api-key-input" value="${apiKey}" placeholder="Enter your API key"> <button id="save-key">Save</button> </div> <div class="endpoint-section"> <label for="endpoint-select">Endpoint:</label> <select id="endpoint-select"> <option value="stability">Coin Stability Analysis</option> <option value="top">Top Coins</option> <option value="market">Market Data</option> <option value="coin">Coin Details</option> <option value="holders">Coin Holders</option> <option value="hopium">Prediction Markets</option> <option value="hopium-detail">Prediction Market Detail</option> </select> </div> <div id="parameters-section"></div> <div class="action-section"> <button id="make-request">Make Request</button> <button id="clear-response">Clear Response</button> </div> <div class="response-section"> <div class="response-header"> <span>Response:</span> <div class="response-controls"> <span id="request-status"></span> <button id="copy-output-small" title="Copy to clipboard">📋</button> </div> </div> <pre id="response-output"></pre> </div> </div> `; document.body.appendChild(gui); return gui; } // styling - going for that sleek glassmorphism vibe GM_addStyle(` #rugplay-api-gui { position: fixed; top: 20px; right: 20px; width: 405px; max-height: 108vh; background: rgba(15, 15, 23, 0.85); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 14px; box-shadow: 0 7px 29px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.05), inset 0 1px 0 rgba(255, 255, 255, 0.1); z-index: 10000; font-family: 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif; color: rgba(255, 255, 255, 0.9); overflow: hidden; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); transform: scale(0.9); transform-origin: top right; } #rugplay-api-gui:hover { box-shadow: 0 11px 36px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.08), inset 0 1px 0 rgba(255, 255, 255, 0.15); } .api-header { display: flex; justify-content: space-between; align-items: center; padding: 16px 22px; background: rgba(255, 255, 255, 0.05); border-bottom: 1px solid rgba(255, 255, 255, 0.08); backdrop-filter: blur(10px); } .api-header h2 { margin: 0; font-size: 14px; font-weight: 600; background: linear-gradient(135deg, #ffffff 0%, #a0a0a0 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } #toggle-gui { background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.1); color: rgba(255, 255, 255, 0.8); font-size: 14px; cursor: pointer; padding: 7px 11px; border-radius: 7px; transition: all 0.2s ease; backdrop-filter: blur(10px); } #toggle-gui:hover { background: rgba(255, 255, 255, 0.12); border-color: rgba(255, 255, 255, 0.2); color: rgba(255, 255, 255, 1); transform: scale(1.05); } .api-content { padding: 22px; max-height: 99vh; overflow-y: auto; } .api-content::-webkit-scrollbar { width: 6px; } .api-content::-webkit-scrollbar-track { background: rgba(255, 255, 255, 0.05); border-radius: 3px; } .api-content::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.2); border-radius: 3px; } .api-content::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.3); } .api-content.collapsed { display: none; } .api-key-section, .endpoint-section, .action-section { margin-bottom: 22px; } .api-key-section { display: flex; gap: 11px; align-items: center; } label { font-weight: 500; margin-bottom: 7px; display: block; color: rgba(255, 255, 255, 0.8); font-size: 13px; } .api-key-section label { margin-bottom: 0; white-space: nowrap; } input, select, textarea { width: 100%; padding: 11px 14px; border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 9px; background: rgba(255, 255, 255, 0.08); backdrop-filter: blur(10px); color: rgba(255, 255, 255, 0.9); font-size: 13px; transition: all 0.2s ease; } input::placeholder { color: rgba(255, 255, 255, 0.4); } input:focus, select:focus, textarea:focus { outline: none; background: rgba(255, 255, 255, 0.12); border-color: rgba(99, 102, 241, 0.5); box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1), 0 4px 14px rgba(99, 102, 241, 0.1); } button { background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(10px); color: rgba(255, 255, 255, 0.9); border: 1px solid rgba(255, 255, 255, 0.15); padding: 11px 18px; border-radius: 9px; cursor: pointer; font-weight: 500; font-size: 13px; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); } button:hover { background: rgba(255, 255, 255, 0.15); border-color: rgba(255, 255, 255, 0.25); transform: translateY(-1px); box-shadow: 0 5px 18px rgba(0, 0, 0, 0.3); } button:active { transform: translateY(0); } #save-key { flex-shrink: 0; } .parameter-group { margin-bottom: 18px; } .parameter-row { display: flex; gap: 11px; margin-bottom: 11px; } .parameter-row input, .parameter-row select { flex: 1; } .action-section { display: flex; gap: 11px; } #make-request { background: linear-gradient(135deg, rgba(34, 197, 94, 0.9) 0%, rgba(22, 163, 74, 0.9) 100%); border: 1px solid rgba(34, 197, 94, 0.3); flex: 1; } #make-request:hover { background: linear-gradient(135deg, rgba(34, 197, 94, 1) 0%, rgba(22, 163, 74, 1) 100%); border-color: rgba(34, 197, 94, 0.5); box-shadow: 0 6px 25px rgba(34, 197, 94, 0.3); } #clear-response { background: linear-gradient(135deg, rgba(239, 68, 68, 0.9) 0%, rgba(220, 38, 38, 0.9) 100%); border: 1px solid rgba(239, 68, 68, 0.3); } #clear-response:hover { background: linear-gradient(135deg, rgba(239, 68, 68, 1) 0%, rgba(220, 38, 38, 1) 100%); border-color: rgba(239, 68, 68, 0.5); box-shadow: 0 6px 25px rgba(239, 68, 68, 0.3); } .response-section { margin-top: 22px; } .response-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 11px; } .response-controls { display: flex; gap: 11px; align-items: center; } #request-status { font-size: 11px; padding: 5px 11px; border-radius: 7px; background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.1); color: rgba(255, 255, 255, 0.8); } #response-output { background: rgba(0, 0, 0, 0.4); backdrop-filter: blur(15px); color: rgba(255, 255, 255, 0.9); padding: 18px; border-radius: 11px; border: 1px solid rgba(255, 255, 255, 0.08); white-space: pre-wrap; word-wrap: break-word; max-height: 450px; overflow-y: auto; font-size: 10px; line-height: 1.0; font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace; box-shadow: inset 0 2px 7px rgba(0, 0, 0, 0.3); } #response-output::-webkit-scrollbar { width: 6px; } #response-output::-webkit-scrollbar-track { background: rgba(255, 255, 255, 0.05); border-radius: 3px; } #response-output::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.2); border-radius: 3px; } #copy-output-small { background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.1); padding: 7px 11px; font-size: 13px; min-width: auto; border-radius: 7px; } #copy-output-small:hover { background: rgba(255, 255, 255, 0.15); border-color: rgba(255, 255, 255, 0.2); } .parameter-description { font-size: 10px; color: rgba(255, 255, 255, 0.5); margin-top: 4px; font-style: italic; } /* Subtle glow animations */ @keyframes subtle-glow { 0%, 100% { box-shadow: 0 0 20px rgba(99, 102, 241, 0.1); } 50% { box-shadow: 0 0 30px rgba(99, 102, 241, 0.2); } } #rugplay-api-gui:focus-within { animation: subtle-glow 3s ease-in-out infinite; } `); // endpoint configs - these match the API docs const endpointConfigs = { top: { url: 'https://rugplay.com/api/v1/top', method: 'GET', parameters: [] }, market: { url: 'https://rugplay.com/api/v1/market', method: 'GET', parameters: [ { name: 'search', type: 'text', description: 'Search by coin name or symbol' }, { name: 'sortBy', type: 'select', options: ['marketCap', 'currentPrice', 'change24h', 'volume24h', 'createdAt'], description: 'Sort field (default: marketCap)' }, { name: 'sortOrder', type: 'select', options: ['asc', 'desc'], description: 'Sort order (default: desc)' }, { name: 'priceFilter', type: 'select', options: ['all', 'under1', '1to10', '10to100', 'over100'], description: 'Price range filter' }, { name: 'changeFilter', type: 'select', options: ['all', 'gainers', 'losers', 'hot', 'wild'], description: 'Change filter' }, { name: 'page', type: 'number', description: 'Page number (default: 1)' }, { name: 'limit', type: 'number', description: 'Items per page, max 100 (default: 12)' } ] }, coin: { url: 'https://rugplay.com/api/v1/coin/{symbol}', method: 'GET', parameters: [ { name: 'symbol', type: 'text', required: true, description: 'Coin symbol (e.g., "TEST")' }, { name: 'timeframe', type: 'select', options: ['1m', '5m', '15m', '1h', '4h', '1d'], description: 'Chart timeframe (default: 1m)' } ] }, holders: { url: 'https://rugplay.com/api/v1/holders/{symbol}', method: 'GET', parameters: [ { name: 'symbol', type: 'text', required: true, description: 'Coin symbol (e.g., "TEST")' }, { name: 'limit', type: 'number', description: 'Number of holders to return, max 200 (default: 50)' } ] }, stability: { url: 'custom', method: 'GET', parameters: [ { name: 'symbol', type: 'text', required: true, description: 'Coin symbol (e.g., "UNIVERSE")' } ] }, hopium: { url: 'https://rugplay.com/api/v1/hopium', method: 'GET', parameters: [ { name: 'status', type: 'select', options: ['ACTIVE', 'RESOLVED', 'CANCELLED', 'ALL'], description: 'Filter by status (default: ACTIVE)' }, { name: 'page', type: 'number', description: 'Page number (default: 1)' }, { name: 'limit', type: 'number', description: 'Items per page, max 100 (default: 20)' } ] }, 'hopium-detail': { url: 'https://rugplay.com/api/v1/hopium/{question_id}', method: 'GET', parameters: [ { name: 'question_id', type: 'number', required: true, description: 'Question ID (e.g., 101)' } ] } }; // main initialization function function initializeGUI() { const gui = createGUI(); // grab all the elements we need const toggleBtn = document.getElementById('toggle-gui'); const apiContent = document.querySelector('.api-content'); const apiKeyInput = document.getElementById('api-key-input'); const saveKeyBtn = document.getElementById('save-key'); const endpointSelect = document.getElementById('endpoint-select'); const parametersSection = document.getElementById('parameters-section'); const makeRequestBtn = document.getElementById('make-request'); const clearResponseBtn = document.getElementById('clear-response'); const responseOutput = document.getElementById('response-output'); const requestStatus = document.getElementById('request-status'); // toggle gui collapse/expand toggleBtn.addEventListener('click', () => { if (apiContent.classList.contains('collapsed')) { apiContent.classList.remove('collapsed'); toggleBtn.textContent = '−'; } else { apiContent.classList.add('collapsed'); toggleBtn.textContent = '+'; } }); // save the api key saveKeyBtn.addEventListener('click', () => { apiKey = apiKeyInput.value; GM_setValue('rugplay_api_key', apiKey); requestStatus.textContent = 'API key saved'; requestStatus.style.background = '#4CAF50'; }); // when endpoint changes, update the parameter fields endpointSelect.addEventListener('change', updateParameters); // make the actual request makeRequestBtn.addEventListener('click', makeAPIRequest); // clear response output clearResponseBtn.addEventListener('click', () => { responseOutput.textContent = ''; requestStatus.textContent = ''; }); // copy functionality - this was a pain to get working properly const copyOutputBtn = document.getElementById('copy-output-small'); copyOutputBtn.addEventListener('click', () => { const outputText = responseOutput.textContent; if (!outputText) { requestStatus.textContent = 'No output to copy'; requestStatus.style.background = '#FF9800'; return; } // try modern clipboard API first if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(outputText).then(() => { requestStatus.textContent = 'Output copied to clipboard!'; requestStatus.style.background = '#4CAF50'; setTimeout(() => { requestStatus.textContent = ''; }, 2000); }).catch(() => { requestStatus.textContent = 'Failed to copy output'; requestStatus.style.background = '#f44336'; }); } else { // fallback for older browsers const textArea = document.createElement('textarea'); textArea.value = outputText; document.body.appendChild(textArea); textArea.select(); try { document.execCommand('copy'); requestStatus.textContent = 'Output copied to clipboard!'; requestStatus.style.background = '#4CAF50'; setTimeout(() => { requestStatus.textContent = ''; }, 2000); } catch (err) { requestStatus.textContent = 'Failed to copy output'; requestStatus.style.background = '#f44336'; } document.body.removeChild(textArea); } }); // set up initial parameters updateParameters(); function updateParameters() { const endpoint = endpointSelect.value; const config = endpointConfigs[endpoint]; parametersSection.innerHTML = ''; if (config.parameters.length > 0) { config.parameters.forEach(param => { const paramDiv = document.createElement('div'); paramDiv.className = 'parameter-group'; let inputHTML = ''; if (param.type === 'select') { inputHTML = `<select id="param-${param.name}"> <option value="">-- Select ${param.name} --</option> ${param.options.map(opt => `<option value="${opt}">${opt}</option>`).join('')} </select>`; } else { inputHTML = `<input type="${param.type}" id="param-${param.name}" placeholder="${param.description}">`; } paramDiv.innerHTML = ` <label for="param-${param.name}">${param.name}${param.required ? ' *' : ''}:</label> ${inputHTML} <div class="parameter-description">${param.description}</div> `; parametersSection.appendChild(paramDiv); }); } } function makeAPIRequest() { const endpoint = endpointSelect.value; // handle custom stability analysis if (endpoint === 'stability') { makeStabilityAnalysis(); return; } const config = endpointConfigs[endpoint]; if (!apiKey) { requestStatus.textContent = 'Please enter an API key'; requestStatus.style.background = '#f44336'; return; } // build the url with parameters let url = config.url; const queryParams = []; config.parameters.forEach(param => { const value = document.getElementById(`param-${param.name}`).value; if (param.required && !value) { requestStatus.textContent = `Required parameter missing: ${param.name}`; requestStatus.style.background = '#f44336'; return; } if (value) { if (url.includes(`{${param.name}}`)) { url = url.replace(`{${param.name}}`, value); } else { queryParams.push(`${param.name}=${encodeURIComponent(value)}`); } } }); if (queryParams.length > 0) { url += '?' + queryParams.join('&'); } requestStatus.textContent = 'Making request...'; requestStatus.style.background = '#2196F3'; GM_xmlhttpRequest({ method: config.method, url: url, headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, onload: function(response) { try { const jsonResponse = JSON.parse(response.responseText); responseOutput.textContent = JSON.stringify(jsonResponse, null, 2); requestStatus.textContent = `Success (${response.status})`; requestStatus.style.background = '#4CAF50'; } catch (e) { responseOutput.textContent = response.responseText; requestStatus.textContent = `Response (${response.status})`; requestStatus.style.background = '#FF9800'; } }, onerror: function(error) { responseOutput.textContent = JSON.stringify(error, null, 2); requestStatus.textContent = 'Error'; requestStatus.style.background = '#f44336'; } }); } function makeStabilityAnalysis() { const symbol = document.getElementById('param-symbol').value; if (!symbol) { requestStatus.textContent = 'Symbol is required for stability analysis'; requestStatus.style.background = '#f44336'; return; } requestStatus.textContent = 'Analyzing coin stability...'; requestStatus.style.background = '#2196F3'; // get both coin and holder data Promise.all([ fetchAPI(`https://rugplay.com/api/v1/coin/${symbol}`), fetchAPI(`https://rugplay.com/api/v1/holders/${symbol}`) ]).then(([coinData, holdersData]) => { const analysis = generateStabilityAnalysis(coinData, holdersData, symbol); displayStabilityAnalysis(analysis); requestStatus.textContent = 'Stability analysis complete'; requestStatus.style.background = '#4CAF50'; }).catch(error => { responseOutput.textContent = `Error fetching data: ${error.message}`; requestStatus.textContent = 'Analysis failed'; requestStatus.style.background = '#f44336'; }); } function fetchAPI(url) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: url, headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, onload: function(response) { try { const data = JSON.parse(response.responseText); resolve(data); } catch (e) { reject(new Error(`Failed to parse response: ${e.message}`)); } }, onerror: function(error) { reject(new Error('Network error')); } }); }); } function generateStabilityAnalysis(coinData, holdersData, symbol) { const coin = coinData.coin; const holders = holdersData.holders; // crunch the numbers const topHolderPercentage = holders[0]?.percentage || 0; const top5Percentage = holders.slice(0, 5).reduce((sum, holder) => sum + (holder.percentage || 0), 0); const poolPercentage = ((coin.poolCoinAmount / coin.circulatingSupply) * 100) || 0; // basic price trend stuff const priceChange = coin.change24h || 0; const priceChangePercent = ((priceChange / coin.currentPrice) * 100) || 0; // make a prediction let prediction = 'STABLE'; let predictionColor = '#4CAF50'; let predictionText = 'Stable - Normal trading patterns'; if (priceChangePercent > 100) { prediction = 'UP'; predictionColor = '#2196F3'; predictionText = `UP - Uptrend, last 10 candles up ${Math.abs(priceChangePercent).toFixed(2)}%`; } else if (priceChangePercent < -50) { prediction = 'DOWN'; predictionColor = '#f44336'; predictionText = `DOWN - Downtrend, last 10 candles down ${Math.abs(priceChangePercent).toFixed(2)}%`; } // calculate rug risk - this is where it gets spicy let riskLevel = 'LOW'; let riskColor = '#4CAF50'; let riskFactors = []; if (topHolderPercentage > 70) { riskLevel = 'HIGH'; riskColor = '#f44336'; riskFactors.push(`Top holder owns ${topHolderPercentage.toFixed(2)}% (+8%)`); } else if (topHolderPercentage > 50) { riskLevel = 'MEDIUM'; riskColor = '#FF9800'; riskFactors.push(`Top holder owns ${topHolderPercentage.toFixed(2)}% (+6%)`); } else { riskFactors.push(`Top holder owns ${topHolderPercentage.toFixed(2)}% (+0%)`); } if (top5Percentage > 80) { riskFactors.push(`Top 5 holders own ${top5Percentage.toFixed(2)}% (+8%)`); if (riskLevel === 'LOW') riskLevel = 'MEDIUM'; } else { riskFactors.push(`Top 5 holders own ${top5Percentage.toFixed(2)}% (+0%)`); } if (poolPercentage > 15) { riskFactors.push(`Pool is healthy (${poolPercentage.toFixed(3)}% of supply) (+0%)`); } else { riskFactors.push(`Pool is low (${poolPercentage.toFixed(3)}% of supply) (+4%)`); if (riskLevel === 'LOW') riskLevel = 'MEDIUM'; } if (Math.abs(priceChangePercent) < 20) { riskFactors.push('No major recent price drop (+0%)'); } else { riskFactors.push(`Major price movement detected (+2%)`); } return { symbol, coin, prediction, predictionColor, predictionText, riskLevel, riskColor, riskFactors, topHolderPercentage, top5Percentage, poolPercentage }; } function displayStabilityAnalysis(analysis) { // fancy ascii output because why not const output = ` ╔═══════════════════════════════════════════════╗ ${analysis.symbol.toUpperCase()} STABILITY ANALYSIS ╚═══════════════════════════════════════════════╝ 📊 BASIC INFO: Current Price: ${analysis.coin.currentPrice?.toFixed(6) || 'N/A'} 24h Change: ${analysis.coin.change24h?.toLocaleString() || 'N/A'} Market Cap: ${analysis.coin.marketCap?.toLocaleString() || 'N/A'} Volume 24h: ${analysis.coin.volume24h?.toLocaleString() || 'N/A'} 🔮 PREDICTION: ${analysis.predictionText} ⚠️ RUGPULL RISK: ${analysis.riskLevel} - ${analysis.riskFactors.join('; ')} 📋 RISK BREAKDOWN: ${analysis.riskFactors.join('\n ')} 🏆 HOLDER ANALYSIS: Top Holder: ${analysis.topHolderPercentage.toFixed(2)}% Top 5 Holders: ${analysis.top5Percentage.toFixed(2)}% Pool Health: ${analysis.poolPercentage.toFixed(3)}% of supply 💡 RECOMMENDATION: ${analysis.riskLevel === 'HIGH' ? '⛔ HIGH RISK - Exercise extreme caution' : analysis.riskLevel === 'MEDIUM' ? '⚠️ MEDIUM RISK - Monitor closely' : '✅ LOW RISK - Relatively stable'} `; responseOutput.textContent = output; } } // start everything up when ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initializeGUI); } else { initializeGUI(); } })();