唔,增加了几个节点和夜间模式的支持..
```
// ==UserScript==
// @
name V2EX Solana Balance Checker 0.7
// @
namespace http://tampermonkey.net/// @
version 0.7
// @
description Uses JavaScript to read and apply V2EX's native theme colors for perfect integration. Includes auto RPC-node failover.
// @
author Gemini
// @
match https://ex.noerr.eu.org/member/*// @
match https://ex.noerr.eu.org/member/*// @
match https://*.ex.noerr.eu.org/member/*
// @
grant GM_xmlhttpRequest
// @
grant GM_addStyle
// @
connect api.mainnet-beta.solana.com// @
connect rpc.ankr.com// @
connect solana-mainnet.rpc.extrnode.com// ==/UserScript==
(function() {
'use strict';
// 1. Configuration
const v2exTokenMintAddress = '9raUVuzeWUk53co63M4WXLWPWE4Xc6Lpn7RS9dnkpump';
const RPC_ENDPOINTS = [ '
https://rpc.ankr.com/solana', '
https://api.mainnet-beta.solana.com', '
https://solana-mainnet.rpc.extrnode.com' ];
function findAddressOnPage() {
const scripts = document.querySelectorAll('script');
for (const script of scripts) {
if (script.textContent.includes('const address =')) {
const match = script.textContent.match(/const address = "([1-9A-HJ-NP-Za-km-z]{32,44})";/);
if (match && match[1]) { return match[1]; }
}
}
return null;
}
const userSolanaAddress = findAddressOnPage();
if (!userSolanaAddress) { return; }
// 2. Add base styles for LAYOUT ONLY.
GM_addStyle(`
.solana-balance-box { background-color: var(--box-background-color); border-bottom: 1px solid var(--box-border-color); margin-bottom: 20px; }
.solana-balance-table { width: 100%; border-collapse: collapse; table-layout: fixed; margin-bottom: -1px; }
.solana-balance-table th, .solana-balance-table td { padding: 12px; text-align: left; border-top: 1px solid var(--box-border-color); font-size: 14px; line-height: 1.6; }
.solana-balance-table th { background-color: var(--box-header-background-color); font-weight: bold; }
.solana-balance-table td { font-family: var(--mono-font); word-wrap: break-word; }
.solana-balance-table th:nth-child(1) { width: 60%; } .solana-balance-table th:nth-child(2) { width: 20%; } .solana-balance-table th:nth-child(3) { width: 20%; }
`);
// 3. Create and insert DOM elements
const container = document.createElement('div');
container.className = 'solana-balance-box';
const table = document.createElement('table');
table.className = 'solana-balance-table';
const headerRow = table.insertRow();
headerRow.innerHTML = '<th>Address</th><th>SOL</th><th>$V2EX</th>';
const dataRow = table.insertRow();
const addressCell = dataRow.insertCell();
const solBalanceCell = dataRow.insertCell();
const tokenBalanceCell = dataRow.insertCell();
addressCell.textContent = userSolanaAddress;
solBalanceCell.textContent = 'Loading...';
tokenBalanceCell.textContent = 'Loading...';
container.appendChild(table);
const mainInfoBox = document.querySelector('#Main .box');
if (mainInfoBox) {
mainInfoBox.parentNode.insertBefore(container, mainInfoBox.nextSibling);
}
// 4. JavaScript function to READ and APPLY native text colors for BOTH headers and data
function updateTextColorsForTheme() {
// Read the actual color values V2EX is currently using
const nativeHeaderTextColor = getComputedStyle(document.body).getPropertyValue('--box-header-text-color').trim();
const nativeTextColor = getComputedStyle(document.body).getPropertyValue('--box-foreground-color').trim();
const nativeFadeColor = getComputedStyle(document.body).getPropertyValue('--color-fade').trim();
// --- FIX: Apply color to table headers (th) ---
const headers = table.querySelectorAll('th');
for (const header of headers) {
// By changing this to nativeTextColor, the header color will match the data cell color.
header.style.setProperty('color', nativeTextColor, 'important');
}
// --- Apply color to table data (td) ---
const cells = [addressCell, solBalanceCell, tokenBalanceCell];
for (const cell of cells) {
if (cell.textContent === 'Loading...' || cell.textContent === 'Error') {
cell.style.setProperty('color', nativeFadeColor, 'important');
} else {
cell.style.setProperty('color', nativeTextColor, 'important');
}
}
}
// 5. Observer to detect theme changes in real-time
const themeObserver = new MutationObserver(() => updateTextColorsForTheme());
themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
updateTextColorsForTheme();
// 6. Fetch data
function makeRpcRequest(requestPayload, onSuccess, onFailure) {
let endpointIndex = 0;
function tryNextEndpoint() {
if (endpointIndex >= RPC_ENDPOINTS.length) { if (onFailure) onFailure(); return; }
const currentEndpoint = RPC_ENDPOINTS[endpointIndex++];
GM_xmlhttpRequest({
method: 'POST', url: currentEndpoint, headers: { 'Content-Type': 'application/json' },
data: JSON.stringify(requestPayload), timeout: 8000,
onload: function(response) {
try { const data = JSON.parse(response.responseText); if (data.error) { tryNextEndpoint(); } else { onSuccess(data); } }
catch (e) { tryNextEndpoint(); }
},
onerror: tryNextEndpoint, ontimeout: tryNextEndpoint
});
}
tryNextEndpoint();
}
function getSolBalance() {
makeRpcRequest({ jsonrpc: '2.0', id: 1, method: 'getBalance', params: [userSolanaAddress] },
(data) => { solBalanceCell.textContent = `${(data.result.value / 1e9).toFixed(6)}`; updateTextColorsForTheme(); },
() => { solBalanceCell.textContent = 'Error'; updateTextColorsForTheme(); }
);
}
function getTokenBalance() {
makeRpcRequest({ jsonrpc: '2.0', id: 1, method: 'getTokenAccountsByOwner', params: [userSolanaAddress, { mint: v2exTokenMintAddress }, { encoding: 'jsonParsed' }] },
(data) => { tokenBalanceCell.textContent = data.result.value.length > 0 ? data.result.value[0].account.data.parsed.info.tokenAmount.uiAmountString : '0'; updateTextColorsForTheme(); },
() => { tokenBalanceCell.textContent = 'Error'; updateTextColorsForTheme(); }
);
}
getSolBalance();
getTokenBalance();
})();
```