GhostOfBitcoin (OP)
Jr. Member

Activity: 42
Merit: 21
|
 |
May 31, 2026, 10:56:20 AM Last edit: June 02, 2026, 06:09:16 PM by GhostOfBitcoin Merited by hugeblack (2), osasshem (1) |
|
Today I'm going to share with you another useful userscript I created that will add a live crypto price ticker panel to the side of your screen while browsing the forum. This is very useful as it saves you the hassle of having to go to different tabs to check market prices over and over again. It's perfect for those who want to keep an eye on the market while being active on the forum. Bitcointalk Price Ticker Sidebar v1.0.0This is a lightweight and compact sidebar that will be fixed to the right side of the forum. You can also drag it to move it anywhere. UI Preview Screenshot:   Key Features: Fast Mini Ticker: The top of the sidebar shows live BTC and ETH prices updated every 5 seconds (via Binance API). It shows a nice green or red flash animation when the price increases or decreases.
- Multi-API Support: You can fetch main price data from CoinGecko, CoinMarketCap (CMC), or Binance, whichever you prefer.
- Custom Token List: You can track your portfolio of choice by separating any token symbol or ID with a comma (,) (e.g. BTC, ETH, SOL, BNB).
- Multi-Currency Support: In addition to USD, it supports USDT, USDC, EUR, GBP, and our local currency BDT and INR.
- Smart Caching: The coin list is cached in the backend for 24 hours, so there is no extra load on your browser or API blocking.
- Collapse Feature: If you feel that the sidebar is taking up too much space on the screen, you can collapse it with just one click by pressing the (-) button.
How to Install:Step 1: First, install a userscript manager extension in your browser (Tampermonkey or Violentmonkey). Step 2: Go to the extension dashboard and click on Create a new script or the plus (+) icon. Step 3: Copy and paste the entire code below and save . Step 4: Now refresh the BitcoinTalk forum. You will see a beautiful market ticker on the right side! Or GreasyFork DownloadUserscript code:[/list] // ==UserScript== // @name Bitcointalk Price Ticker Sidebar // @namespace https://bitcointalk.org/ // @version 1.0.0 // @description Compact crypto ticker sidebar with fast live BTC/ETH mini prices and dynamic market discovery. // @author GhostOfBitcoin // @match http://bitcointalk.org/index.php* // @match https://bitcointalk.org/index.php* // @connect api.coingecko.com // @connect pro-api.coinmarketcap.com // @connect api.binance.com // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @run-at document-idle // ==/UserScript==
(function () { 'use strict';
const PANEL_ID = 'btct-price-ticker'; const GECKO_PRICE_URL = 'https://api.coingecko.com/api/v3/simple/price'; const GECKO_LIST_URL = 'https://api.coingecko.com/api/v3/coins/list?include_platform=false'; const CMC_URL = 'https://pro-api.coinmarketcap.com/v2/cryptocurrency/quotes/latest'; const BINANCE_TICKER_URL = 'https://api.binance.com/api/v3/ticker/24hr'; const BINANCE_EXCHANGE_URL = 'https://api.binance.com/api/v3/exchangeInfo'; const CACHE_TTL = 24 * 60 * 60 * 1000; const MINI_REFRESH_SECONDS = 5;
const DEFAULTS = { provider: 'coingecko', tokens: 'BTC,ETH,SOL,BNB', currency: 'USD', refreshSeconds: 60, collapsed: false, cmcApiKey: '', };
const priorityGeckoIdBySymbol = { btc: 'bitcoin', eth: 'ethereum', bnb: 'binancecoin', sol: 'solana', xrp: 'ripple', ada: 'cardano', doge: 'dogecoin', trx: 'tron', ltc: 'litecoin', xmr: 'monero', usdt: 'tether', usdc: 'usd-coin', ton: 'the-open-network', avax: 'avalanche-2', link: 'chainlink', dot: 'polkadot', matic: 'matic-network', pol: 'polygon-ecosystem-token', shib: 'shiba-inu', pepe: 'pepe', uni: 'uniswap', apt: 'aptos', near: 'near', atom: 'cosmos', arb: 'arbitrum', op: 'optimism', fil: 'filecoin', inj: 'injective-protocol', sui: 'sui', };
const state = loadState(); let refreshTimer = null; let miniRefreshTimer = null; let geckoIndexPromise = null; let binanceInfoPromise = null;
function getValue(key) { if (typeof GM_getValue === 'function') return GM_getValue(key, DEFAULTS[key]); const raw = localStorage.getItem(`btctTicker.${key}`); return raw === null ? DEFAULTS[key] : JSON.parse(raw); }
function setValue(key, value) { if (typeof GM_setValue === 'function') { GM_setValue(key, value); return; } localStorage.setItem(`btctTicker.${key}`, JSON.stringify(value)); }
function loadState() { return { provider: String(getValue('provider') || DEFAULTS.provider), tokens: String(getValue('tokens') || getValue('coinIds') || DEFAULTS.tokens), currency: String(getValue('currency') || DEFAULTS.currency).toUpperCase(), refreshSeconds: Number(getValue('refreshSeconds') || DEFAULTS.refreshSeconds), collapsed: Boolean(getValue('collapsed')), cmcApiKey: String(getValue('cmcApiKey') || ''), }; }
function saveState() { setValue('provider', state.provider); setValue('tokens', state.tokens); setValue('currency', state.currency); setValue('refreshSeconds', state.refreshSeconds); setValue('collapsed', state.collapsed); setValue('cmcApiKey', state.cmcApiKey); }
function injectStyles() { if (document.getElementById(`${PANEL_ID}-style`)) return; const style = document.createElement('style'); style.id = `${PANEL_ID}-style`; style.textContent = ` #${PANEL_ID}{ --text:#101828;--muted:#667085;--good:#067647;--bad:#b42318;--accent:#2563eb;--line:#d0d5dd; position:fixed;top:104px;right:12px;z-index:9999;width:248px;border:1px solid rgba(16,24,40,.18);border-radius:8px;background:#fff;color:var(--text); box-shadow:0 14px 34px rgba(16,24,40,.18);font:11px/1.35 Verdana,Arial,sans-serif;overflow:hidden } #${PANEL_ID}.btct-collapsed{width:218px} #${PANEL_ID} .btct-header{display:grid;gap:5px;padding:7px 8px;background:#111827;color:#fff;cursor:move;user-select:none} #${PANEL_ID} .btct-headline{display:flex;align-items:center;gap:7px;min-height:24px} #${PANEL_ID} .btct-title{flex:1;font-weight:bold;font-size:12px;letter-spacing:0} #${PANEL_ID} .btct-source{border:1px solid rgba(255,255,255,.26);border-radius:999px;padding:1px 6px;background:rgba(255,255,255,.1);font-size:10px;color:#e5edf8} #${PANEL_ID} .btct-mini{display:grid;grid-template-columns:1fr 1fr;gap:5px} #${PANEL_ID} .btct-mini-item{display:grid;gap:1px;padding:5px 6px;border:1px solid rgba(255,255,255,.16);border-radius:6px;background:rgba(255,255,255,.08)} #${PANEL_ID} .btct-mini-symbol{font-size:10px;color:#cbd5e1} #${PANEL_ID} .btct-mini-price{font-weight:bold;font-size:11px;color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:color .18s ease,transform .18s ease} #${PANEL_ID} .btct-mini-price.btct-flash-up{color:#7dd3a7;transform:translateY(-1px)} #${PANEL_ID} .btct-mini-price.btct-flash-down{color:#fda29b;transform:translateY(1px)} #${PANEL_ID} button{border:1px solid #a8b6c7;border-radius:5px;background:#fff;color:#111827;cursor:pointer;font:11px Verdana,Arial,sans-serif} #${PANEL_ID} button:hover{border-color:#64748b;background:#f1f5f9} #${PANEL_ID} .btct-header button{min-width:24px;min-height:22px;padding:1px 5px;border-color:rgba(255,255,255,.28);background:rgba(255,255,255,.1);color:#fff} #${PANEL_ID} .btct-body{padding:8px;background:#f6f8fb} #${PANEL_ID}.btct-collapsed .btct-body{display:none} #${PANEL_ID} .btct-list{display:grid;gap:5px} #${PANEL_ID} .btct-row{display:grid;grid-template-columns:58px 1fr 52px;gap:5px;align-items:center;padding:6px;border:1px solid #dbe3ee;border-radius:6px;background:#fff} #${PANEL_ID} .btct-symbol{font-weight:bold;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} #${PANEL_ID} .btct-price,#${PANEL_ID} .btct-change{text-align:right;font-variant-numeric:tabular-nums} #${PANEL_ID} .btct-price{font-weight:bold} #${PANEL_ID} .btct-positive{color:var(--good)} #${PANEL_ID} .btct-negative{color:var(--bad)} #${PANEL_ID} .btct-muted{color:var(--muted)} #${PANEL_ID} .btct-settings{display:grid;gap:6px;margin-top:7px;padding:7px;border:1px solid #dbe3ee;border-radius:6px;background:#fff} #${PANEL_ID} label{display:grid;gap:3px;color:#344054;font-size:10px} #${PANEL_ID} input,#${PANEL_ID} select{box-sizing:border-box;width:100%;min-height:26px;border:1px solid #a8b6c7;border-radius:5px;background:#fff;padding:3px 6px;color:#111827;font:11px Verdana,Arial,sans-serif} #${PANEL_ID} input:focus,#${PANEL_ID} select:focus{outline:2px solid rgba(37,99,235,.18);border-color:var(--accent)} #${PANEL_ID} .btct-actions{display:flex;gap:6px} #${PANEL_ID} .btct-actions button{flex:1;min-height:26px} #${PANEL_ID} .btct-actions button:first-child{border-color:#1d4ed8;background:#2563eb;color:#fff} #${PANEL_ID} .btct-actions button:first-child:hover{background:#1d4ed8} #${PANEL_ID} .btct-status{margin-top:6px;min-height:15px;color:#667085} #${PANEL_ID} .btct-hint{margin-top:1px;color:#667085;font-size:10px} `; document.head.appendChild(style); }
function parseTokens(value) { return value.split(',').map((item) => item.trim()).filter(Boolean).slice(0, 30); }
function tokenKey(token) { return String(token).trim().toLowerCase(); }
function displaySymbol(token) { const key = tokenKey(token); if (priorityGeckoIdBySymbol[key]) return key.toUpperCase(); return key.includes('-') ? key.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()) : key.toUpperCase(); }
function formatPrice(value, currency) { const number = Number(value); if (!Number.isFinite(number)) return '-'; const digits = number >= 100 ? 2 : number >= 1 ? 4 : 8; try { return new Intl.NumberFormat(undefined, { style: 'currency', currency, maximumFractionDigits: digits, }).format(number); } catch (error) { return `${number.toLocaleString(undefined, { maximumFractionDigits: digits })} ${currency}`; } }
function formatChange(value) { const number = Number(value); if (!Number.isFinite(number)) return '-'; return `${number > 0 ? '+' : ''}${number.toFixed(2)}%`; }
function requestJson(url, headers) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url, headers: headers || {}, timeout: 20000, onload: (response) => { try { const data = JSON.parse(response.responseText); if (response.status >= 400) { reject(new Error(data.status?.error_message || data.msg || `HTTP ${response.status}`)); return; } resolve(data); } catch (error) { reject(error); } }, onerror: () => reject(new Error('network error')), ontimeout: () => reject(new Error('request timed out')), }); }); }
async function getCachedJson(cacheKey, url, transform) { const cached = getValue(cacheKey); if (cached && cached.time && Date.now() - cached.time < CACHE_TTL && cached.data) return cached.data; const raw = await requestJson(url); const data = transform ? transform(raw) : raw; setValue(cacheKey, { time: Date.now(), data }); return data; }
function getCoinGeckoIndex() { if (!geckoIndexPromise) { geckoIndexPromise = getCachedJson('geckoCoinIndex', GECKO_LIST_URL, (coins) => { const byId = {}; const bySymbol = {};
for (const coin of coins) { byId[coin.id] = coin.id; const symbol = String(coin.symbol || '').toLowerCase(); if (!symbol) continue; if (!bySymbol[symbol]) bySymbol[symbol] = []; bySymbol[symbol].push({ id: coin.id, name: coin.name || coin.id }); }
for (const [symbol, id] of Object.entries(priorityGeckoIdBySymbol)) { bySymbol[symbol] = [{ id, name: id }, ...(bySymbol[symbol] || []).filter((coin) => coin.id !== id)]; }
return { byId, bySymbol, count: coins.length }; }); } return geckoIndexPromise; }
function getBinanceInfo() { if (!binanceInfoPromise) { binanceInfoPromise = getCachedJson('binanceExchangeInfo', BINANCE_EXCHANGE_URL, (info) => { const symbols = {}; const byBase = {};
for (const item of info.symbols || []) { if (item.status !== 'TRADING') continue; symbols[item.symbol] = { base: item.baseAsset, quote: item.quoteAsset, }; if (!byBase[item.baseAsset]) byBase[item.baseAsset] = []; byBase[item.baseAsset].push(item.quoteAsset); }
return { symbols, byBase, count: Object.keys(symbols).length }; }); } return binanceInfoPromise; }
async function resolveCoinGeckoIds(tokens) { const index = await getCoinGeckoIndex(); const resolved = {};
for (const token of tokens) { const key = tokenKey(token); if (priorityGeckoIdBySymbol[key]) { resolved[key] = priorityGeckoIdBySymbol[key]; } else if (index.byId[key]) { resolved[key] = key; } else if (index.bySymbol[key]?.length) { resolved[key] = index.bySymbol[key][0].id; } }
return resolved; }
async function fetchCoinGecko(tokens) { const currency = state.currency.toLowerCase(); const resolved = await resolveCoinGeckoIds(tokens); const ids = [...new Set(Object.values(resolved))]; const results = {};
if (!ids.length) return results;
const params = new URLSearchParams({ ids: ids.join(','), vs_currencies: currency, include_24hr_change: 'true', }); const data = await requestJson(`${GECKO_PRICE_URL}?${params}`);
for (const token of tokens) { const key = tokenKey(token); const id = resolved[key]; const coin = id ? data[id] : null; if (!coin) continue; results[key] = { price: coin[currency], change: coin[`${currency}_24h_change`], }; }
return results; }
async function fetchCoinMarketCap(tokens) { if (!state.cmcApiKey.trim()) throw new Error('CoinMarketCap API key missing');
const symbols = tokens.map((token) => token.trim().toUpperCase()).join(','); const params = new URLSearchParams({ symbol: symbols, convert: state.currency }); const data = await requestJson(`${CMC_URL}?${params}`, { Accept: 'application/json', 'X-CMC_PRO_API_KEY': state.cmcApiKey.trim(), });
const results = {}; for (const [symbol, value] of Object.entries(data.data || {})) { const item = Array.isArray(value) ? value[0] : value; const quote = item?.quote?.[state.currency]; if (!quote) continue; results[symbol.toLowerCase()] = { price: quote.price, change: quote.percent_change_24h, }; } return results; }
async function resolveBinanceSymbols(tokens) { const info = await getBinanceInfo(); const wantedQuote = state.currency === 'USD' ? 'USDT' : state.currency; const quotePriority = [wantedQuote, 'USDT', 'FDUSD', 'USDC', 'BTC', 'ETH', 'BNB']; const resolved = {};
for (const token of tokens) { const key = tokenKey(token); const clean = String(token).trim().toUpperCase().replace(/[^A-Z0-9]/g, '');
if (info.symbols[clean]) { resolved[key] = clean; continue; }
const quotes = info.byBase[clean] || []; const quote = quotePriority.find((candidate) => quotes.includes(candidate)); if (quote) resolved[key] = `${clean}${quote}`; }
return resolved; }
async function fetchBinance(tokens) { const resolved = await resolveBinanceSymbols(tokens); const results = {};
await Promise.all(tokens.map(async (token) => { const key = tokenKey(token); const symbol = resolved[key]; if (!symbol) return;
try { const data = await requestJson(`${BINANCE_TICKER_URL}?${new URLSearchParams({ symbol })}`); results[key] = { price: data.lastPrice, change: data.priceChangePercent, }; } catch (error) { results[key] = null; } }));
return results; }
function renderRows(panel, prices) { const list = panel.querySelector('.btct-list'); list.textContent = '';
const tokens = parseTokens(state.tokens); if (!tokens.length) { list.textContent = 'Add symbols, pairs, or CoinGecko IDs.'; return; }
for (const token of tokens) { const row = document.createElement('div'); row.className = 'btct-row';
const symbol = document.createElement('div'); symbol.className = 'btct-symbol'; symbol.textContent = displaySymbol(token); symbol.title = token;
const price = document.createElement('div'); price.className = 'btct-price';
const change = document.createElement('div'); change.className = 'btct-change';
const item = prices[tokenKey(token)] || prices[displaySymbol(token).toLowerCase()]; if (item && item.price !== undefined) { price.textContent = formatPrice(item.price, state.provider === 'binance' && state.currency === 'USD' ? 'USD' : state.currency); change.textContent = formatChange(item.change); change.classList.toggle('btct-positive', Number(item.change) > 0); change.classList.toggle('btct-negative', Number(item.change) < 0); } else { price.textContent = 'No data'; price.classList.add('btct-muted'); change.textContent = '-'; }
row.append(symbol, price, change); list.appendChild(row); } }
function renderMiniPrices(panel, prices) { const mini = panel.querySelector('.btct-mini'); if (!mini) return;
for (const symbol of ['BTC', 'ETH']) { const item = prices[symbol.toLowerCase()]; const priceEl = mini.querySelector(`[data-mini-price="${symbol}"]`); if (!priceEl) continue; if (item?.price !== undefined) { priceEl.textContent = formatPrice(item.price, 'USD'); priceEl.dataset.lastPrice = String(item.price); priceEl.classList.toggle('btct-flash-up', Number(item.price) > Number(priceEl.dataset.prevPrice || item.price)); priceEl.classList.toggle('btct-flash-down', Number(item.price) < Number(priceEl.dataset.prevPrice || item.price)); priceEl.dataset.prevPrice = String(item.price); window.setTimeout(() => { priceEl.classList.remove('btct-flash-up', 'btct-flash-down'); }, 650); } else if (!priceEl.dataset.lastPrice) { priceEl.textContent = '-'; } } }
async function refreshMiniPrices(panel) { try { const [btc, eth] = await Promise.all([ requestJson(`${BINANCE_TICKER_URL}?${new URLSearchParams({ symbol: 'BTCUSDT' })}`), requestJson(`${BINANCE_TICKER_URL}?${new URLSearchParams({ symbol: 'ETHUSDT' })}`), ]); renderMiniPrices(panel, { btc: { price: btc.lastPrice }, eth: { price: eth.lastPrice }, }); } catch (binanceError) { try { const params = new URLSearchParams({ ids: 'bitcoin,ethereum', vs_currencies: 'usd', }); const data = await requestJson(`${GECKO_PRICE_URL}?${params}`); const prices = { btc: { price: data.bitcoin?.usd }, eth: { price: data.ethereum?.usd }, }; renderMiniPrices(panel, prices); } catch (fallbackError) { renderMiniPrices(panel, {}); } } }
function providerLabel() { if (state.provider === 'coinmarketcap') return 'CMC'; if (state.provider === 'binance') return 'Binance'; return 'CoinGecko'; }
function setStatus(panel, text) { panel.querySelector('.btct-status').textContent = text; panel.querySelector('.btct-source').textContent = providerLabel(); }
async function refresh(panel) { const tokens = parseTokens(state.tokens); if (!tokens.length) { renderRows(panel, {}); setStatus(panel, 'No coins selected.'); return; }
setStatus(panel, 'Refreshing market data...'); try { const prices = state.provider === 'coinmarketcap' ? await fetchCoinMarketCap(tokens) : state.provider === 'binance' ? await fetchBinance(tokens) : await fetchCoinGecko(tokens); renderRows(panel, prices); refreshMiniPrices(panel); setStatus(panel, `${providerLabel()} updated ${new Date().toLocaleTimeString()}`); } catch (error) { renderRows(panel, {}); refreshMiniPrices(panel); setStatus(panel, `Update failed: ${error.message || 'unknown error'}`); } }
function scheduleRefresh(panel) { window.clearInterval(refreshTimer); window.clearInterval(miniRefreshTimer); refreshMiniPrices(panel); refresh(panel); refreshTimer = window.setInterval(() => refresh(panel), Math.max(state.refreshSeconds, 30) * 1000); miniRefreshTimer = window.setInterval(() => refreshMiniPrices(panel), MINI_REFRESH_SECONDS * 1000); }
function createSettings(panel) { const settings = document.createElement('div'); settings.className = 'btct-settings';
const providerField = document.createElement('label'); providerField.textContent = 'Price source'; const providerSelect = document.createElement('select'); for (const [value, label] of [['coingecko', 'CoinGecko'], ['coinmarketcap', 'CoinMarketCap'], ['binance', 'Binance']]) { const option = document.createElement('option'); option.value = value; option.textContent = label; option.selected = value === state.provider; providerSelect.appendChild(option); } providerField.appendChild(providerSelect);
const tokenField = document.createElement('label'); tokenField.textContent = 'Symbols, pairs, or CoinGecko IDs'; const tokenInput = document.createElement('input'); tokenInput.value = state.tokens; tokenInput.placeholder = 'BTC,ETH,SOL,PEPE or BTCUSDT'; const hint = document.createElement('div'); hint.className = 'btct-hint'; hint.textContent = 'CoinGecko and Binance market lists auto-cache for 24h.'; tokenField.append(tokenInput, hint);
const currencyField = document.createElement('label'); currencyField.textContent = 'Currency / quote'; const currencySelect = document.createElement('select'); for (const currency of ['USD', 'USDT', 'USDC', 'EUR', 'GBP', 'BTC', 'ETH', 'BNB', 'BDT', 'INR']) { const option = document.createElement('option'); option.value = currency; option.textContent = currency; option.selected = currency === state.currency; currencySelect.appendChild(option); } currencyField.appendChild(currencySelect);
const keyField = document.createElement('label'); keyField.textContent = 'CMC API key'; const keyInput = document.createElement('input'); keyInput.type = 'password'; keyInput.value = state.cmcApiKey; keyInput.placeholder = 'Required only for CoinMarketCap'; keyField.appendChild(keyInput);
const intervalField = document.createElement('label'); intervalField.textContent = 'Refresh seconds'; const intervalInput = document.createElement('input'); intervalInput.type = 'number'; intervalInput.min = '30'; intervalInput.step = '15'; intervalInput.value = String(Math.max(state.refreshSeconds, 30)); intervalField.appendChild(intervalInput);
const actions = document.createElement('div'); actions.className = 'btct-actions';
const saveButton = document.createElement('button'); saveButton.type = 'button'; saveButton.textContent = 'Save'; saveButton.addEventListener('click', () => { state.provider = providerSelect.value; state.tokens = parseTokens(tokenInput.value).join(','); state.currency = currencySelect.value.toUpperCase(); state.refreshSeconds = Math.max(Number(intervalInput.value) || DEFAULTS.refreshSeconds, 30); state.cmcApiKey = keyInput.value.trim(); saveState(); scheduleRefresh(panel); });
const refreshButton = document.createElement('button'); refreshButton.type = 'button'; refreshButton.textContent = 'Refresh'; refreshButton.addEventListener('click', () => refresh(panel));
actions.append(saveButton, refreshButton); settings.append(providerField, tokenField, currencyField, keyField, intervalField, actions); return settings; }
function addDragBehavior(panel, header) { let dragging = false; let offsetX = 0; let offsetY = 0;
header.addEventListener('mousedown', (event) => { if (event.target.closest('button')) return; const rect = panel.getBoundingClientRect(); dragging = true; offsetX = event.clientX - rect.left; offsetY = event.clientY - rect.top; event.preventDefault(); });
document.addEventListener('mousemove', (event) => { if (!dragging) return; const left = Math.max(6, Math.min(window.innerWidth - panel.offsetWidth - 6, event.clientX - offsetX)); const top = Math.max(6, Math.min(window.innerHeight - panel.offsetHeight - 6, event.clientY - offsetY)); panel.style.left = `${left}px`; panel.style.top = `${top}px`; panel.style.right = 'auto'; });
document.addEventListener('mouseup', () => { dragging = false; }); }
function createPanel() { const panel = document.createElement('aside'); panel.id = PANEL_ID; panel.classList.toggle('btct-collapsed', state.collapsed);
const header = document.createElement('div'); header.className = 'btct-header';
const headline = document.createElement('div'); headline.className = 'btct-headline';
const title = document.createElement('div'); title.className = 'btct-title'; title.textContent = 'Market Ticker';
const source = document.createElement('div'); source.className = 'btct-source'; source.textContent = providerLabel();
const collapseButton = document.createElement('button'); collapseButton.type = 'button'; collapseButton.title = 'Collapse or expand'; collapseButton.textContent = state.collapsed ? '+' : '-'; collapseButton.addEventListener('click', () => { state.collapsed = !state.collapsed; panel.classList.toggle('btct-collapsed', state.collapsed); collapseButton.textContent = state.collapsed ? '+' : '-'; saveState(); });
headline.append(title, source, collapseButton);
const mini = document.createElement('div'); mini.className = 'btct-mini'; for (const symbol of ['BTC', 'ETH']) { const item = document.createElement('div'); item.className = 'btct-mini-item';
const label = document.createElement('div'); label.className = 'btct-mini-symbol'; label.textContent = symbol;
const price = document.createElement('div'); price.className = 'btct-mini-price'; price.dataset.miniPrice = symbol; price.textContent = 'Loading';
item.append(label, price); mini.appendChild(item); }
header.append(headline, mini);
const body = document.createElement('div'); body.className = 'btct-body';
const list = document.createElement('div'); list.className = 'btct-list'; list.textContent = 'Loading prices...';
const status = document.createElement('div'); status.className = 'btct-status';
body.append(list, createSettings(panel), status); panel.append(header, body); addDragBehavior(panel, header);
return panel; }
function init() { if (document.getElementById(PANEL_ID)) return; injectStyles(); const panel = createPanel(); document.body.appendChild(panel); scheduleRefresh(panel); }
init(); })();
Some important tips: 1. If you want to use CoinMarketCap, you need to go to the settings option and install your own free CMC API Key. No key is required for CoinGecko or Binance. 2. It is better to set the refresh time to a minimum of 30 seconds, so that the API limit does not exit. However, the top mini ticker will automatically update after 5 seconds.
|