您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically refreshes peer ranks every 10 seconds. Highlights your username and row, and assigns Gold, Silver, and Bronze badges to the top three peers.
// ==UserScript== // @name TorrentBD Peer Rank (Improved) // @version 2.0 // @description Automatically refreshes peer ranks every 10 seconds. Highlights your username and row, and assigns Gold, Silver, and Bronze badges to the top three peers. // @author gaara (Improved by 5ifty6ix) // @namespace 5ifty6ix // @match https://*.torrentbd.com/* // @match https://*.torrentbd.net/* // @match https://*.torrentbd.org/* // @match https://*.torrentbd.me/* // @grant none // @license MIT // @run-at document-end // ==/UserScript== (function() { 'use strict'; const CONFIG = { selectors: { peerTable: 'table.peers-table', username: '.card-content .card-title span.tbdrank', tableHeaderRow: 'thead tr', tableBody: 'tbody', }, classNames: { selfRow: 'tbd-self-row', selfUser: 'tbd-self-user', rankHeader: 'peer-rank-header', rankCell: 'peer-rank-cell', }, uploadColumnIndex: 4, refreshIntervalMs: 10000, style: ` .rank-badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-weight: bold; color: #fff; text-shadow: 1px 1px 2px rgba(0,0,0,0.4); font-size: 0.9em; } .rank-1 { background-color: #d4af37; } .rank-2 { background-color: #c0c0c0; } .rank-3 { background-color: #cd7f32; } .tbd-self-row { background: linear-gradient(90deg, rgba(0, 255, 255, 0.15) 0%, rgba(0, 255, 255, 0) 20%); border-left: 3px solid #00ffff; } .tbd-self-user { color: #00ffff !important; font-weight: bold; } ` }; let refreshIntervalId = null; let cachedUsername = null; function injectStyles() { const style = document.createElement('style'); style.textContent = CONFIG.style; document.head.appendChild(style); } function parseToBytes(uploadStr) { if (!uploadStr) return 0; const parts = uploadStr.trim().split(' '); if (parts.length < 2) return 0; const value = parseFloat(parts[0].replace(/,/g, '')); const unit = parts[1].toUpperCase(); switch (unit) { case 'TIB': return value * Math.pow(1024, 4); case 'GIB': return value * Math.pow(1024, 3); case 'MIB': return value * Math.pow(1024, 2); case 'KIB': return value * 1024; default: return value; } } function getLoggedInUsername() { if (cachedUsername) return cachedUsername; try { const userElement = document.querySelector(CONFIG.selectors.username); const name = userElement?.childNodes[0]?.textContent.trim(); if (name) { cachedUsername = name; return cachedUsername; } } catch (e) { console.error("Peer Rank Script: Could not find username element.", e); } return null; } function escapeRegExp(str) { return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } function applyHighlightInPeersTable(peerTable) { const username = getLoggedInUsername(); if (!username) return; peerTable.querySelectorAll(`.${CONFIG.classNames.selfUser}`).forEach(span => { const parent = span.parentNode; if (parent) { parent.replaceChild(document.createTextNode(span.textContent), span); parent.normalize(); } }); const walker = document.createTreeWalker(peerTable, NodeFilter.SHOW_TEXT); const nodesToProcess = []; while (walker.nextNode()) nodesToProcess.push(walker.currentNode); const pattern = new RegExp(`\\b${escapeRegExp(username)}\\b`, 'g'); nodesToProcess.forEach(node => { if (!node.nodeValue || !pattern.test(node.nodeValue)) return; pattern.lastIndex = 0; const parent = node.parentNode; if (!parent || parent.nodeName === 'SCRIPT' || parent.nodeName === 'STYLE') return; const fragment = document.createDocumentFragment(); let lastIndex = 0; let match; while ((match = pattern.exec(node.nodeValue)) !== null) { fragment.appendChild(document.createTextNode(node.nodeValue.slice(lastIndex, match.index))); const span = document.createElement('span'); span.className = CONFIG.classNames.selfUser; span.textContent = match[0]; fragment.appendChild(span); lastIndex = pattern.lastIndex; } fragment.appendChild(document.createTextNode(node.nodeValue.slice(lastIndex))); parent.replaceChild(fragment, node); parent.normalize(); }); peerTable.querySelectorAll('tbody tr').forEach(tr => { tr.classList.remove(CONFIG.classNames.selfRow); if (tr.querySelector(`.${CONFIG.classNames.selfUser}`)) { tr.classList.add(CONFIG.classNames.selfRow); } }); } function processPeerTable() { const peerTable = document.querySelector(CONFIG.selectors.peerTable); if (!peerTable) return; const headerRow = peerTable.querySelector(CONFIG.selectors.tableHeaderRow); const tbody = peerTable.querySelector(CONFIG.selectors.tableBody); if (!headerRow || !tbody) return; headerRow.querySelector(`.${CONFIG.classNames.rankHeader}`)?.remove(); const rows = Array.from(tbody.querySelectorAll('tr')); rows.forEach(row => row.querySelector(`.${CONFIG.classNames.rankCell}`)?.remove()); const rankHeader = document.createElement('th'); rankHeader.className = CONFIG.classNames.rankHeader; rankHeader.textContent = 'Rank'; headerRow.insertBefore(rankHeader, headerRow.firstChild); const peers = rows.map(row => { const ulText = row.cells[CONFIG.uploadColumnIndex]?.textContent || '0 B'; return { row, ul: parseToBytes(ulText) }; }); peers.sort((a, b) => b.ul - a.ul); peers.forEach((peer, index) => { const rank = index + 1; const rankCell = document.createElement('td'); rankCell.className = CONFIG.classNames.rankCell; rankCell.style.textAlign = 'center'; if (rank <= 3) { rankCell.innerHTML = `<span class="rank-badge rank-${rank}">${rank}</span>`; } else { rankCell.textContent = rank.toString(); } peer.row.insertBefore(rankCell, peer.row.firstChild); tbody.appendChild(peer.row); }); applyHighlightInPeersTable(peerTable); } function init() { injectStyles(); const observer = new MutationObserver(() => { const peerTable = document.querySelector(CONFIG.selectors.peerTable); if (peerTable && !refreshIntervalId) { getLoggedInUsername(); processPeerTable(); refreshIntervalId = setInterval(processPeerTable, CONFIG.refreshIntervalMs); } else if (!peerTable && refreshIntervalId) { clearInterval(refreshIntervalId); refreshIntervalId = null; } }); observer.observe(document.body, { childList: true, subtree: true }); } init(); })();