fix: Umlaute in README und Konsolenausgaben (ä/ö/ü statt ae/oe/ue)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c6cca124a6
commit
fa0aa4736b
44
README.md
44
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**
|
||||
|
||||
44
index.js
44
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;
|
||||
|
||||
27
package-lock.json
generated
Normal file
27
package-lock.json
generated
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user