From fa0aa4736b31b474f9ff1067972b070057c3dc2c Mon Sep 17 00:00:00 2001 From: Administrator Date: Tue, 17 Mar 2026 06:03:30 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20Umlaute=20in=20README=20und=20Konsolenau?= =?UTF-8?q?sgaben=20(=C3=A4/=C3=B6/=C3=BC=20statt=20ae/oe/ue)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 44 ++++++++++++++++++++++---------------------- index.js | 44 ++++++++++++++++++++++---------------------- package-lock.json | 27 +++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 44 deletions(-) create mode 100644 package-lock.json diff --git a/README.md b/README.md index c60a53d..7811375 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ # Byse.sx Video Manager -CLI-Tool zum Aufraumen deines Byse.sx-Accounts. Scannt alle Videos, findet solche mit **0 Views** die aelter als **3 Monate** sind, und loescht sie auf Wunsch in Batches. +CLI-Tool zum Aufräumen deines Byse.sx-Accounts. Scannt alle Videos, findet solche mit **0 Views** die älter als **3 Monate** sind, und löscht sie auf Wunsch in Batches. ## Features - Scannt alle Videos deines Accounts (paginiert, ~70K+ Videos kein Problem) - Filtert nach Alter und Views -- Interaktives Menue: Liste durchblaettern, CSV exportieren, loeschen -- Batch-Loeschung mit Fortschrittsanzeige -- Sicherheitsabfrage vor dem Loeschen (Tippe `LOESCHEN`) -- Optionaler Alters-Filter (z.B. nur Videos aelter als 6 Monate) +- Interaktives Menü: Liste durchblättern, CSV exportieren, löschen +- Batch-Löschung mit Fortschrittsanzeige +- Sicherheitsabfrage vor dem Löschen (Tippe `LOESCHEN`) +- Optionaler Alters-Filter (z.B. nur Videos älter als 6 Monate) ## Voraussetzungen @@ -25,10 +25,10 @@ npm install ### Session-Cookie holen -Das Tool nutzt die interne Byse.sx-API, die einen Session-Cookie benoetigt: +Das Tool nutzt die interne Byse.sx-API, die einen Session-Cookie benötigt: -1. Oeffne [byse.sx](https://byse.sx) im Browser und logge dich ein -2. Oeffne die DevTools (`F12`) +1. Öffne [byse.sx](https://byse.sx) im Browser und logge dich ein +2. Öffne die DevTools (`F12`) 3. Gehe zu **Application** > **Cookies** > `https://byse.sx` 4. Kopiere den Wert des `sid`-Cookies 5. Erstelle eine `.env` Datei im Projektordner: @@ -43,7 +43,7 @@ cp .env.example .env BYSE_SID=dein_session_cookie_hier ``` -> **Hinweis:** Der Session-Cookie laeuft nach einiger Zeit ab. Wenn du eine Fehlermeldung bekommst, hole dir einfach einen neuen aus dem Browser. +> **Hinweis:** Der Session-Cookie läuft nach einiger Zeit ab. Wenn du eine Fehlermeldung bekommst, hole dir einfach einen neuen aus dem Browser. ## Benutzung @@ -51,13 +51,13 @@ BYSE_SID=dein_session_cookie_hier node index.js ``` -### Menue-Optionen +### Menü-Optionen ``` -[1] Liste anzeigen - Blaettert durch die Kandidaten (je 50) +[1] Liste anzeigen - Blättert durch die Kandidaten (je 50) [2] CSV exportieren - Speichert alle Kandidaten als byse-candidates.csv -[3] ALLE loeschen - Loescht alle Kandidaten (Sicherheitsabfrage) -[4] Nach Alter filtern - Nur Videos aelter als X Monate loeschen +[3] ALLE löschen - Löscht alle Kandidaten (Sicherheitsabfrage) +[4] Nach Alter filtern - Nur Videos älter als X Monate löschen [5] Beenden ``` @@ -69,27 +69,27 @@ node index.js ════════════════════════════════════════════════ Gesamt Videos: 69.595 Kandidaten (0 Views, - aelter als 3 Mon.): 17.525 + älter als 3 Mon.): 17.525 Speicher freigeben: 13.67 TB - Aeltestes Video: 2025-05-02 - Neuestes Kandidat: 2025-12-04 + Ältestes Video: 2025-05-02 + Neuester Kandidat: 2025-12-04 ════════════════════════════════════════════════ ``` ## API-Details -Das Tool nutzt die **interne** Byse.sx Dashboard-API (nicht die oeffentliche API): +Das Tool nutzt die **interne** Byse.sx Dashboard-API (nicht die öffentliche API): | Endpoint | Methode | Zweck | |----------|---------|-------| -| `/api/my_files_overview` | GET | Account-Uebersicht | +| `/api/my_files_overview` | GET | Account-Übersicht | | `/api/my_files_files` | GET | Video-Liste (Cursor-Pagination) | -| `/api/del_file` | POST | Videos loeschen | +| `/api/del_file` | POST | Videos löschen | -Authentifizierung erfolgt ueber den `sid` Session-Cookie. +Authentifizierung erfolgt über den `sid` Session-Cookie. ## Sicherheit - Die `.env` Datei mit deinem Session-Cookie wird **nicht** ins Git committed (`.gitignore`) -- Vor dem Loeschen muss `LOESCHEN` getippt werden -- Loeschung ist **unwiderruflich** +- Vor dem Löschen muss `LOESCHEN` getippt werden +- Löschung ist **unwiderruflich** diff --git a/index.js b/index.js index e47b314..146f489 100644 --- a/index.js +++ b/index.js @@ -3,7 +3,7 @@ import readline from 'readline'; import fs from 'fs'; const SID = process.env.BYSE_SID; -if (!SID) { console.error('BYSE_SID fehlt in .env'); process.exit(1); } +if (!SID) { console.error('BYSE_SID fehlt in .env!'); process.exit(1); } const COOKIE = `sid=${SID}`; const BASE = 'https://byse.sx/api'; @@ -37,7 +37,7 @@ async function apiFetch(path) { headers: { cookie: COOKIE, 'content-type': 'application/json' } }); if (res.status === 401 || res.status === 403) { - console.error('\nSession abgelaufen! Hol dir einen neuen SID-Cookie aus dem Browser.'); + console.error('\nSession abgelaufen! Hol dir einen neuen SID-Cookie aus dem Browser (F12 → Application → Cookies).'); process.exit(1); } return res.json(); @@ -62,10 +62,10 @@ function progressBar(current, total, width = 30) { // ── Scan ── async function scanAllVideos() { - console.log('\nVerbindung pruefen...'); + console.log('\nVerbindung prüfen...'); const overview = await apiFetch('/my_files_overview'); if (overview.status !== 'ok') { - console.error('API-Fehler:', overview); + console.error('API-Fehler (prüfe deinen SID-Cookie):', overview); process.exit(1); } const totalFiles = overview.totals.files; @@ -89,7 +89,7 @@ async function scanAllVideos() { cursor = data.file_pagination?.next_cursor; if (!cursor) break; } catch (err) { - console.warn(`\nFehler bei Batch ${batch}, ueberspringe... (${err.message})`); + console.warn(`\nFehler bei Batch ${batch}, überspringe... (${err.message})`); break; } // kleine Pause um Rate-Limits zu vermeiden @@ -105,7 +105,7 @@ function filterCandidates(files) { return files.filter(f => { const created = new Date(f.created); return f.views === 0 && created < THREE_MONTHS_AGO; - }).sort((a, b) => new Date(a.created) - new Date(b.created)); // aelteste zuerst + }).sort((a, b) => new Date(a.created) - new Date(b.created)); // älteste zuerst } // ── Display ── @@ -120,16 +120,16 @@ function showSummary(candidates, totalFiles) { console.log('════════════════════════════════════════════════'); console.log(` Gesamt Videos: ${totalFiles.toLocaleString('de-DE')}`); console.log(` Kandidaten (0 Views,`); - console.log(` aelter als 3 Mon.): ${candidates.length.toLocaleString('de-DE')}`); + console.log(` älter als 3 Mon.): ${candidates.length.toLocaleString('de-DE')}`); console.log(` Speicher freigeben: ${formatSize(totalSize)}`); - console.log(` Aeltestes Video: ${oldest}`); + console.log(` Ältestes Video: ${oldest}`); console.log(` Neuestes Kandidat: ${newest}`); console.log('════════════════════════════════════════════════\n'); } function showTable(candidates, offset = 0, limit = 50) { const slice = candidates.slice(offset, offset + limit); - const header = `${'#'.padStart(6)} ${'Titel'.padEnd(55)} ${'Alter'.padStart(6)} ${'Groesse'.padStart(10)} ${'Views'.padStart(5)}`; + const header = `${'#'.padStart(6)} ${'Titel'.padEnd(55)} ${'Alter'.padStart(6)} ${'Größe'.padStart(10)} ${'Views'.padStart(5)}`; console.log(header); console.log('─'.repeat(header.length)); for (let i = 0; i < slice.length; i++) { @@ -153,12 +153,12 @@ async function deleteVideos(candidates) { let deleted = 0; let errors = 0; - console.log(`\nLoesche ${total.toLocaleString('de-DE')} Videos in ${Math.ceil(total / DELETE_BATCH)} Batches...\n`); + console.log(`\nLösche${total.toLocaleString('de-DE')} Videos in ${Math.ceil(total / DELETE_BATCH)} Batches...\n`); for (let i = 0; i < total; i += DELETE_BATCH) { const batch = candidates.slice(i, i + DELETE_BATCH); const ids = batch.map(f => f.id); - process.stdout.write(`\rLoeschen... ${progressBar(Math.min(i + DELETE_BATCH, total), total)} `); + process.stdout.write(`\rLöschen... ${progressBar(Math.min(i + DELETE_BATCH, total), total)} `); try { const result = await apiDelete(ids); @@ -178,13 +178,13 @@ async function deleteVideos(candidates) { } process.stdout.write('\n'); - console.log(`\nFertig! Geloescht: ${deleted.toLocaleString('de-DE')} | Fehler: ${errors}`); + console.log(`\nFertig! Gelöscht: ${deleted.toLocaleString('de-DE')} | Fehler: ${errors}`); } // ── CSV Export ── function exportCSV(candidates, filename = 'byse-candidates.csv') { - const header = 'ID,Code,Titel,Views,Erstellt,Groesse_MB,Alter_Tage\n'; + const header = 'ID,Code,Titel,Views,Erstellt,Größe_MB,Alter_Tage\n'; const rows = candidates.map(f => { const days = Math.floor((Date.now() - new Date(f.created).getTime()) / 86400000); const sizeMB = ((f.size || 0) / 1048576).toFixed(1); @@ -192,7 +192,7 @@ function exportCSV(candidates, filename = 'byse-candidates.csv') { return `${f.id},"${f.code}","${title}",${f.views},${f.created},${sizeMB},${days}`; }).join('\n'); fs.writeFileSync(filename, header + rows, 'utf8'); - console.log(`CSV exportiert: ${filename} (${candidates.length} Eintraege)`); + console.log(`CSV exportiert: ${filename} (${candidates.length} Einträge)`); } // ── Main ── @@ -207,7 +207,7 @@ async function main() { const candidates = filterCandidates(allFiles); if (candidates.length === 0) { - console.log('\nKeine Videos gefunden die aelter als 3 Monate sind UND 0 Views haben.'); + console.log('\nKeine Videos gefunden die älter als 3 Monate sind UND 0 Views haben.'); process.exit(0); } @@ -216,10 +216,10 @@ async function main() { let offset = 0; while (true) { console.log('\nOptionen:'); - console.log(' [1] Liste anzeigen (naechste 50)'); + console.log(' [1] Liste anzeigen (nächste 50)'); console.log(' [2] CSV exportieren'); - console.log(' [3] ALLE Kandidaten loeschen'); - console.log(' [4] Nur Videos aelter als X Monate loeschen'); + console.log(' [3] ALLE Kandidaten löschen'); + console.log(' [4] Nur Videos älter als X Monate löschen'); console.log(' [5] Beenden'); const choice = await ask('\nAuswahl: '); @@ -230,7 +230,7 @@ async function main() { } else if (choice === '2') { exportCSV(candidates); } else if (choice === '3') { - const confirm = await ask(`\nSICHER? ${candidates.length.toLocaleString('de-DE')} Videos werden UNWIDERRUFLICH geloescht!\nTippe "LOESCHEN" zum Bestaetigen: `); + const confirm = await ask(`\nSICHER? ${candidates.length.toLocaleString('de-DE')} Videos werden UNWIDERRUFLICH gelöscht!\nTippe "LOESCHEN" zum Bestätigen: `); if (confirm === 'LOESCHEN') { await deleteVideos(candidates); break; @@ -240,15 +240,15 @@ async function main() { } else if (choice === '4') { const months = await ask('Mindest-Alter in Monaten (z.B. 6): '); const m = parseInt(months); - if (isNaN(m) || m < 1) { console.log('Ungueltige Eingabe.'); continue; } + if (isNaN(m) || m < 1) { console.log('Ungültige Eingabe.'); continue; } const cutoff = new Date(Date.now() - m * 30 * 24 * 60 * 60 * 1000); const filtered = candidates.filter(f => new Date(f.created) < cutoff); - console.log(`\n${filtered.length.toLocaleString('de-DE')} Videos aelter als ${m} Monate mit 0 Views.`); + console.log(`\n${filtered.length.toLocaleString('de-DE')} Videos älter als ${m} Monate mit 0 Views.`); const totalSize = filtered.reduce((sum, f) => sum + (f.size || 0), 0); console.log(`Speicher: ${formatSize(totalSize)}`); if (filtered.length === 0) continue; showTable(filtered, 0, 30); - const confirm = await ask(`\nTippe "LOESCHEN" um diese ${filtered.length.toLocaleString('de-DE')} Videos zu loeschen: `); + const confirm = await ask(`\nTippe "LOESCHEN" um diese ${filtered.length.toLocaleString('de-DE')} Videos zu löschen: `); if (confirm === 'LOESCHEN') { await deleteVideos(filtered); break; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a7af354 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,27 @@ +{ + "name": "byse-video-manager", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "byse-video-manager", + "version": "1.0.0", + "dependencies": { + "dotenv": "^16.4.7" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + } + } +}