diff --git a/scripts/adduser.sh b/scripts/bash/adduser.sh similarity index 100% rename from scripts/adduser.sh rename to scripts/bash/adduser.sh diff --git a/scripts/dock.sh b/scripts/bash/dock.sh similarity index 100% rename from scripts/dock.sh rename to scripts/bash/dock.sh diff --git a/scripts/movefiles.sh b/scripts/bash/movefiles.sh similarity index 100% rename from scripts/movefiles.sh rename to scripts/bash/movefiles.sh diff --git a/scripts/rclone-sync.sh b/scripts/bash/rclone-sync.sh similarity index 100% rename from scripts/rclone-sync.sh rename to scripts/bash/rclone-sync.sh diff --git a/scripts/toggle-metal-hud.sh b/scripts/bash/toggle-metal-hud.sh similarity index 100% rename from scripts/toggle-metal-hud.sh rename to scripts/bash/toggle-metal-hud.sh diff --git a/scripts/toggle-ota.sh b/scripts/bash/toggle-ota.sh similarity index 100% rename from scripts/toggle-ota.sh rename to scripts/bash/toggle-ota.sh diff --git a/scripts/update-forgejo.sh b/scripts/bash/update-forgejo.sh similarity index 100% rename from scripts/update-forgejo.sh rename to scripts/bash/update-forgejo.sh diff --git a/scripts/javascript/privatehd-seed-timer.user.js b/scripts/javascript/privatehd-seed-timer.user.js new file mode 100644 index 0000000..fda7820 --- /dev/null +++ b/scripts/javascript/privatehd-seed-timer.user.js @@ -0,0 +1,131 @@ +// ==UserScript== +// @name PrivateHD Seed Timer +// @namespace https://privatehd.to/ +// @version 1.0.0 +// @description Shows required seed time and whether you can stop seeding each torrent, based on file size. +// @author you +// @match https://privatehd.to/profile/*/active* +// @grant none +// ==/UserScript== + +(function () { + 'use strict'; + + // --------------------------------------------------------------------------- + // Formula: required seed time in hours, where x = size in GB + // x <= 1 → 72h + // 1 < x < 50 → 72 + 2x h + // x >= 50 → 100 * ln(x) - 219.2023 h + // --------------------------------------------------------------------------- + function requiredHours(sizeGB) { + if (sizeGB <= 1) return 72; + if (sizeGB < 50) return 72 + 2 * sizeGB; + return 100 * Math.log(sizeGB) - 219.2023; + } + + // --------------------------------------------------------------------------- + // Parse a size string like "32.62 GB", "512.00 MB", "1.20 TB" → GB (float) + // --------------------------------------------------------------------------- + function parseSizeToGB(str) { + const m = str.trim().match(/([\d.]+)\s*(B|KB|MB|GB|TB)/i); + if (!m) return null; + const val = parseFloat(m[1]); + const unit = m[2].toUpperCase(); + const factors = { B: 1e-9, KB: 1e-6, MB: 1e-3, GB: 1, TB: 1e3 }; + return val * (factors[unit] ?? 1); + } + + // --------------------------------------------------------------------------- + // Parse elapsed time from the "For" column (last in each row). + // Examples: "23h", "2d", "5d 3h", "1h 30m", "45m" + // Returns elapsed hours as a float. + // --------------------------------------------------------------------------- + function parseElapsedHours(str) { + let hours = 0; + const days = str.match(/(\d+)\s*d/); + const hrs = str.match(/(\d+)\s*h/); + const mins = str.match(/(\d+)\s*m/); + if (days) hours += parseInt(days[1], 10) * 24; + if (hrs) hours += parseInt(hrs[1], 10); + if (mins) hours += parseInt(mins[1], 10) / 60; + return hours; + } + + // --------------------------------------------------------------------------- + // Format hours nicely: "72h 00m" etc. + // --------------------------------------------------------------------------- + function fmtHours(h) { + const days = Math.floor(h / 24); + const hrs = Math.floor(h % 24); + const mins = Math.round((h % 1) * 60); + if (days > 0) return `${days}d ${hrs}h`; + return `${hrs}h ${String(mins).padStart(2, '0')}m`; + } + + // --------------------------------------------------------------------------- + // Add a "Seed Status" column header + // --------------------------------------------------------------------------- + const thead = document.querySelector('table thead tr'); + if (!thead) return; + + const th = document.createElement('th'); + th.textContent = 'Seed Status'; + th.style.whiteSpace = 'nowrap'; + thead.appendChild(th); + + // --------------------------------------------------------------------------- + // Process each data row + // --------------------------------------------------------------------------- + const rows = document.querySelectorAll('table tbody tr'); + + rows.forEach(row => { + const cells = row.querySelectorAll('td'); + // Column layout (0-indexed): + // 0: icon | 1: name+stats | 2: progress | 3: status | 4: client + // 5: upload | 6: download | 7: left | 8: ratio | 9: started | 10: updated | 11: For + + const nameCell = cells[1]; + const forCell = cells[11]; // "For" — how long you've been seeding + + if (!nameCell || !forCell) { + const td = document.createElement('td'); + td.textContent = '—'; + row.appendChild(td); + return; + } + + // File size lives inside the name cell: fa-database span + const sizeSpan = nameCell.querySelector('.fa-database'); + const sizeText = sizeSpan ? sizeSpan.textContent.trim() : null; + const sizeGB = sizeText ? parseSizeToGB(sizeText) : null; + + const elapsedText = forCell.textContent.trim(); + const elapsed = parseElapsedHours(elapsedText); + const required = sizeGB !== null ? requiredHours(sizeGB) : null; + + const td = document.createElement('td'); + td.style.whiteSpace = 'nowrap'; + td.style.verticalAlign = 'middle'; + + if (required === null) { + td.textContent = 'No size data'; + td.style.color = '#999'; + } else { + const remaining = required - elapsed; + const canStop = remaining <= 0; + + if (canStop) { + td.innerHTML = + `✔ Can stop
` + + `Req: ${fmtHours(required)} | Done: ${fmtHours(elapsed)}`; + } else { + td.innerHTML = + `✘ Keep seeding
` + + `${fmtHours(remaining)} left of ${fmtHours(required)}`; + } + } + + row.appendChild(td); + }); + +})(); diff --git a/scripts/yBzDuMp.pl b/scripts/perl/yBzDuMp.pl similarity index 100% rename from scripts/yBzDuMp.pl rename to scripts/perl/yBzDuMp.pl