Calculate qBittorrent selected torrents size

Calculates setected torrents size and displays a total in the toolbar

< 脚本 Calculate qBittorrent selected torrents size 的反馈

评价:差评 - 脚本失效或无法使用

§
发布于:2025-08-06

I just revised the script so it works with the latest Qbitorrent version.

// ==UserScript==
// @name qBittorrent Selected Size (torrentsTableDiv fix)
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Show total size of selected torrents in the footer
// @match http://localhost:8080/*
// @run-at document-idle
// @grant none
// ==/UserScript==

(function () {
'use strict';

const FOOTER_ID = 'tmSelectedSizeTotal';
const LABEL = 'Selected Torrents Total Size:';
let sizeColIndex = null;
let lastSig = '';

function ensureFooter() {
const row = document.querySelector('#desktopFooter > table > tbody > tr');
if (!row) { setTimeout(ensureFooter, 500); return; }
if (!document.getElementById(FOOTER_ID)) {
const td = document.createElement('td');
td.id = FOOTER_ID;
td.textContent = `${LABEL} 0.00 MiB`;
const sep = document.createElement('td');
sep.className = 'statusBarSeparator';
row.insertBefore(td, row.firstElementChild);
row.insertBefore(sep, td.nextSibling);
}
}

// Find the "Size" column index using the header th.column_size
function findSizeColIndex() {
// Header and body are in separate tables; just grab any "th.column_size"
const th = document.querySelector('th.column_size');
if (!th) return null;
const tr = th.closest('tr');
if (!tr) return null;
const idx = Array.from(tr.children).indexOf(th);
return (idx >= 0) ? idx : null;
}

function toBytes(text) {
const t = text.replace(/\u00A0/g,' ').replace(/,/g,'').trim(); // NBSP/commas
const m = t.match(/^([\d.]+)\s*(B|KiB|MiB|GiB|TiB)$/i);
if (!m) return null;
const v = parseFloat(m[1]);
const u = m[2].toUpperCase();
const mult = (u==='B')?1:(u==='KIB')?1024:(u==='MIB')?1024**2:(u==='GIB')?1024**3:1024**4;
return v * mult;
}

function fmt(bytes) {
if (bytes < 1024) return `${bytes.toFixed(2)} B`;
if (bytes < 1024**2) return `${(bytes/1024).toFixed(2)} KiB`;
if (bytes < 1024**3) return `${(bytes/1024**2).toFixed(2)} MiB`;
if (bytes < 1024**4) return `${(bytes/1024**3).toFixed(2)} GiB`;
return `${(bytes/1024**4).toFixed(2)} TiB`;
}

function update() {
ensureFooter();

// Resolve Size column index once (or when header changes)
if (sizeColIndex == null) sizeColIndex = findSizeColIndex();

const rows = Array.from(document.querySelectorAll('#torrentsTableDiv tbody tr.selected'));
const sig = rows.map(r => r.getAttribute('data-row-id') || r.innerText.slice(0,50)).join('|') + `|idx:${sizeColIndex}`;
if (sig === lastSig) return;
lastSig = sig;

const el = document.getElementById(FOOTER_ID);
if (!el) return;

if (!rows.length || sizeColIndex == null) {
el.textContent = `${LABEL} 0.00 MiB`;
return;
}

let total = 0;
for (const r of rows) {
const cell = r.children[sizeColIndex] || r.querySelector('td:nth-child('+(sizeColIndex+1)+')');
if (!cell) continue;
const b = toBytes(cell.textContent);
if (b != null) total += b;
}
el.textContent = `${LABEL} ${fmt(total)}`;
}

function startObservers() {
const tableDiv = document.getElementById('torrentsTableDiv');
if (!tableDiv) { setTimeout(startObservers, 500); return; }

// Watch the body (selection/content changes)
const tb = tableDiv.querySelector('tbody') || tableDiv;
new MutationObserver(update).observe(tb, {subtree: true, attributes: true, childList: true});

// Watch headers — column moves/visibility can change index
const header = document.querySelector('th.column_size')?.closest('table') || document;
new MutationObserver(() => { sizeColIndex = null; update(); })
.observe(document.body, {subtree: true, attributes: true, childList: true});

document.addEventListener('click', update, true);
document.addEventListener('keyup', update, true);

update();
// Fallback timer in case nothing fires
setInterval(update, 1000);
}

window.addEventListener('load', () => { ensureFooter(); startObservers(); });
})();

发布留言

登录以发布留言。