-
-
Save FoxxMD/f3045b4e2c0afe39631c9550bd131133 to your computer and use it in GitHub Desktop.
// Tested on qBittorrent 4.6.1 | |
// | |
// 1. open qbittorent web interface | |
// 1a. (optionally) filter to just the torrents that should be totalled | |
// 1b. MAY need to ensure any/all columns to be totalled are enabled -> Size, Downloaded, Uploaded | |
// 2. open developer tools | |
// 3. copy and paste the *entirety* of the below code into the developer tools console and hit enter | |
// | |
// Will print totals in MiB/GiB/TiB for: | |
// * Size + Min/Max/Avg | |
// * Downloaded + % of Size + Min/Max/Avg | |
// * Uploaded + % of Size + Min/Max/Avg | |
// and Ratio + Min/Max/Avg | |
(function() { | |
const sizeIndex = Array.from(document.querySelector('tr.dynamicTableHeader').childNodes).findIndex(x => x.title === 'Size'); | |
const downloadedIndex = Array.from(document.querySelector('tr.dynamicTableHeader').childNodes).findIndex(x => x.title === 'Downloaded'); | |
const uploadedIndex = Array.from(document.querySelector('tr.dynamicTableHeader').childNodes).findIndex(x => x.title === 'Uploaded'); | |
const ratioIndex = Array.from(document.querySelector('tr.dynamicTableHeader').childNodes).findIndex(x => x.title === 'Ratio'); | |
const tibThreshold = 1048576; | |
if(sizeIndex === -1 && downloadedIndex === -1 && uploadedIndex === -1) { | |
console.warn('Must have at least one of the following columns enabled to compute some statistics: Size, Downloaded, Uploaded'); | |
return; | |
} | |
const getMibVal = (valueStr, unit) => { | |
const val = parseFloat(valueStr); | |
if(val === 0) { | |
return 0; | |
} | |
switch (unit.toLocaleLowerCase()) { | |
case 'tib': | |
return val * 1024 * 1024; | |
case 'gib': | |
return val * 1024; | |
case 'mib': | |
return val; | |
case 'kib': | |
return val / 1024; | |
case 'B': | |
return val / 1024 / 1024; | |
} | |
} | |
const getTotalBreakdown = (val) => { | |
return [ | |
`${((val / 1024) / 1024).toLocaleString('en', {maximumFractionDigits: 2})} TiB`, | |
`${(val / 1024).toLocaleString('en', {maximumFractionDigits: 2})} GiB`, | |
`${val.toLocaleString('en', {maximumFractionDigits: 2})} MiB`, | |
].join(' == '); | |
} | |
const getHumanSize = (valMib) => { | |
if(valMib === 0) { | |
return '0 B'; | |
} | |
if(valMib < 1) { | |
return `${(valMib / 1024).toLocaleString('en', {maximumFractionDigits: 2})} KiB` | |
} | |
if(valMib >= tibThreshold) { | |
return `${(valMib / 1024 / 1024).toLocaleString('en', {maximumFractionDigits: 2})} TiB` | |
} | |
if(valMib >= 1024) { | |
return `${(valMib / 1024).toLocaleString('en', {maximumFractionDigits: 2})} GiB` | |
} | |
return `${(valMib).toLocaleString('en', {maximumFractionDigits: 2})} MiB` | |
} | |
let mibSize = sizeIndex !== -1 ? 0 : null; | |
let sizeMinMax = [Infinity,0]; | |
let ratio = ratioIndex !== -1 ? 0 : null; | |
let ratioMinMax = [Infinity,0]; | |
let mibDl = downloadedIndex !== -1 ? 0 : null; | |
let dlMinMax = [Infinity,0]; | |
let mibUl = uploadedIndex !== -1 ? 0 : null; | |
let ulMinMax = [Infinity,0]; | |
const elms = document.querySelectorAll('div#torrentsTableDiv tr.torrentsTableContextMenuTarget'); | |
const torNum = elms.length; | |
elms.forEach((row) => { | |
if(mibSize !== null) { | |
const [str, unit] = row.childNodes[sizeIndex].title.split(' '); | |
const val = getMibVal(str, unit); | |
mibSize += val; | |
sizeMinMax = [Math.min(val, sizeMinMax[0]), Math.max(val, sizeMinMax[1])]; | |
} | |
if(mibDl !== null) { | |
const [str, unit] = row.childNodes[downloadedIndex].title.split(' '); | |
const val = getMibVal(str, unit); | |
mibDl += val; | |
dlMinMax = [Math.min(val, dlMinMax[0]), Math.max(val, dlMinMax[1])]; | |
} | |
if(mibDl !== null) { | |
const [str, unit] = row.childNodes[uploadedIndex].title.split(' '); | |
const val = getMibVal(str, unit); | |
mibUl += val; | |
ulMinMax = [Math.min(val, ulMinMax[0]), Math.max(val, ulMinMax[1])]; | |
} | |
if(ratio !== null) { | |
const val = parseFloat(row.childNodes[ratioIndex].title); | |
ratio += val | |
ratioMinMax = [Math.min(val, ratioMinMax[0]), Math.max(val, ratioMinMax[1])]; | |
} | |
}); | |
if(mibSize !== null) { | |
console.log(`${'Size'.padEnd(11,' ')}: ${getTotalBreakdown(mibSize)} | Min ${getHumanSize(sizeMinMax[0])} | Max ${getHumanSize(sizeMinMax[1])} | Avg ${getHumanSize(mibSize / torNum)}`); | |
} | |
if(mibDl !== null) { | |
const parts = [`${'Downloaded'.padEnd(11,' ')}: ${getTotalBreakdown(mibDl)}`]; | |
if(mibSize !== null) { | |
parts.push(`${((mibDl / mibSize) * 100).toLocaleString('en', {maximumFractionDigits: 2})}% of Size`); | |
} | |
parts.push(`Min ${getHumanSize(dlMinMax[0])} | Max ${getHumanSize(dlMinMax[1])} | Avg ${getHumanSize(mibDl / torNum)}`); | |
console.log(parts.join(' | ')); | |
} | |
if(mibUl !== null) { | |
const parts = [`${'Uploaded'.padEnd(11,' ')}: ${getTotalBreakdown(mibUl)}`]; | |
if(mibSize !== null) { | |
parts.push(`${((mibUl / mibSize) * 100).toLocaleString('en', {maximumFractionDigits: 2})}% of Size`); | |
} | |
parts.push(`Min ${getHumanSize(ulMinMax[0])} | Max ${getHumanSize(ulMinMax[1])} | Avg ${getHumanSize(mibUl / torNum)}`); | |
console.log(parts.join(' | ')); | |
} | |
if(mibDl !== null && mibUl !== null) { | |
const parts = [ | |
`${'Ratio'.padEnd(11,' ')}: ${((mibUl / mibDl)).toLocaleString('en', {maximumFractionDigits: 2})}`, | |
`Min ${ratioMinMax[0]} | Max ${ratioMinMax[1]} | Avg ${(ratio / torNum).toLocaleString('en', {maximumFractionDigits: 2})}` | |
]; | |
console.log(parts.join(' | ')); | |
} | |
})(); | |
Hello! I just revised the script from https://greasyfork.org/en/scripts/483775-calculate-qbittorrent-selected-torrents-size to work with Qbitorrent latest version (5.x.x). If you access the webui remotely, make sure to adjust the @match to your address. Hope this helps someone.
// ==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')?10242:(u==='GIB')?10243:1024**4;
return v * mult;
}
function fmt(bytes) {
if (bytes < 1024) return ${bytes.toFixed(2)} B
;
if (bytes < 10242) return ${(bytes/1024).toFixed(2)} KiB
;
if (bytes < 10243) 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(); });
})();
An alternative that displays on the interface (not written by me) here: https://greasyfork.org/en/scripts/483775-calculate-qbittorrent-selected-torrents-size