Last active
May 14, 2026 10:05
-
-
Save nickalexej/30bed76bb33856fc55ec2cf923f51719 to your computer and use it in GitHub Desktop.
Entfernt Automatisch erstellte Workouts aus Garmin Connect Web #vibecoded --- 2026-02-11: Fix - Anpassungen im HTML von Garmin angepasst. Verbesserte Log Ausgabe im Terminal 2026-03-11: Fix -für Gamin Webanpassungen 2026-05-11: Fix - Garmin änderte Ellipsis-Button-Selector & Dropdown-Menü-Klasse
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| function deleteWorkouts() { | |
| // Suche alle Zeilen in der Workout-Tabelle | |
| const workoutRows = document.querySelectorAll('tr.SortableTableLayout_tableRow__iHLrR'); | |
| if (workoutRows.length === 0) { | |
| console.log('Keine Workout-Zeilen gefunden.'); | |
| return; | |
| } | |
| // Finde das erste Workout mit "Bei Predictive Fitness bearbeiten" | |
| let targetRow = null; | |
| for (const row of workoutRows) { | |
| // Prüfe die "Bearbeitet"-Spalte | |
| const editedCell = row.querySelector('td[data-title="Bearbeitet"]'); | |
| if (editedCell) { | |
| const text = editedCell.textContent.trim(); | |
| if (text === 'Bei Predictive Fitness bearbeiten') { | |
| const workoutName = row.querySelector('td[data-title="Name"]')?.textContent; | |
| targetRow = row; | |
| console.log('✓ Predictive Fitness Workout gefunden:', workoutName); | |
| break; | |
| } | |
| } | |
| } | |
| // Wenn kein passendes Training gefunden wurde → fertig | |
| if (!targetRow) { | |
| console.log('✓✓✓ Keine Predictive Fitness Workouts mehr gefunden. Fertig!'); | |
| return; | |
| } | |
| // Finde den Drei-Punkte-Button (Ellipsis) | |
| const ellipsisButton = targetRow.querySelector('.WorkoutsList_ellipsisMenu__4RZ\\+p button') | |
| || targetRow.querySelector('button[aria-label="Optionen"]') | |
| || targetRow.querySelector('button[aria-label="Optionen"] i.icon-ellipsis-vertical')?.closest('button'); | |
| if (!ellipsisButton) { | |
| console.log('⚠ Drei-Punkte-Menü nicht gefunden. Überspringe...'); | |
| setTimeout(deleteWorkouts, 1000); | |
| return; | |
| } | |
| // Klicke auf den Button | |
| ellipsisButton.click(); | |
| console.log('→ Menü geöffnet...'); | |
| setTimeout(() => { | |
| findAndClickDelete(targetRow); | |
| }, 500); | |
| } | |
| function clickLöschenInPopup(callback) { | |
| // Primär: ActionMenu-Button direkt per Klasse | |
| let found = null; | |
| const actionMenuItems = document.querySelectorAll('.ActionMenuItem_actionMenuItem__kUf7T'); | |
| for (const el of actionMenuItems) { | |
| const text = el.textContent.trim().toLowerCase(); | |
| if (text === 'löschen' || text === 'delete') { | |
| found = el; | |
| console.log('✓ ActionMenu-Löschen gefunden'); | |
| break; | |
| } | |
| } | |
| // Fallback: sichtbares "Löschen"-Element außerhalb von <tr> und Confirm-Dialog | |
| if (!found) { | |
| const candidates = document.querySelectorAll('button, li, [role="menuitem"], [role="option"], a, span'); | |
| for (const el of candidates) { | |
| if (el.closest('tr')) continue; | |
| if (el.closest('.Dialog_dialog__fiyk-')) continue; | |
| const text = el.textContent.trim().toLowerCase(); | |
| if (el.offsetParent !== null && (text === 'löschen' || text === 'delete')) { | |
| found = el; | |
| console.log('✓ Fallback-Löschen gefunden:', el.tagName, el.className); | |
| break; | |
| } | |
| } | |
| } | |
| if (found) { | |
| found.click(); | |
| setTimeout(callback, 800); | |
| } else { | |
| console.log('→ Kein Popup-Löschen gefunden, gehe weiter...'); | |
| callback(); | |
| } | |
| } | |
| function findAndClickDelete(row) { | |
| // Schritt 1: Löschen im Dropdown-Menü klicken | |
| // Schritt 2: falls Zwischen-Popup erscheint, dort nochmal Löschen klicken | |
| // Schritt 3: finaler Confirm-Dialog | |
| clickLöschenInPopup(() => { | |
| clickLöschenInPopup(() => { | |
| confirmDelete(); | |
| }); | |
| }); | |
| } | |
| function confirmDelete() { | |
| // Suche nach ALLEN Dialogen | |
| const allDialogs = document.querySelectorAll('.Dialog_dialog__fiyk-'); | |
| console.log('→ Gefundene Dialoge:', allDialogs.length); | |
| // Finde den Dialog mit "Training löschen" im Header (OHNE h3!) | |
| let deleteDialog = null; | |
| for (const dialog of allDialogs) { | |
| // WICHTIG: Der Header ist ein DIV, kein H3! | |
| const header = dialog.querySelector('.Dialog_dialogHeader__Guxgt'); | |
| if (header) { | |
| const headerText = header.textContent.trim(); | |
| console.log(' → Dialog-Titel: "' + headerText + '"'); | |
| if (headerText === 'Training löschen' || headerText.toLowerCase().includes('training') && headerText.toLowerCase().includes('löschen')) { | |
| deleteDialog = dialog; | |
| console.log('✓ "Training löschen"-Dialog gefunden!'); | |
| break; | |
| } | |
| } | |
| } | |
| if (!deleteDialog) { | |
| console.log('⚠ "Training löschen"-Dialog nicht gefunden! Warte länger...'); | |
| // Zähle Wiederholungen | |
| if (!confirmDelete.retryCount) confirmDelete.retryCount = 0; | |
| confirmDelete.retryCount++; | |
| if (confirmDelete.retryCount < 5) { | |
| setTimeout(confirmDelete, 500); | |
| } else { | |
| console.log('⚠ Dialog erscheint nicht. Schließe alle Dialoge und versuche nächstes Workout...'); | |
| confirmDelete.retryCount = 0; | |
| // Schließe alle Dialoge | |
| const closeBtns = document.querySelectorAll('.Dialog_dialogCloseBtn__EtPd-'); | |
| closeBtns.forEach(btn => btn.click()); | |
| setTimeout(deleteWorkouts, 2000); | |
| } | |
| return; | |
| } | |
| // Reset retry counter | |
| confirmDelete.retryCount = 0; | |
| // Suche den Footer in diesem spezifischen Dialog | |
| const dialogFooter = deleteDialog.querySelector('.Dialog_dialogFooter__W9xGT'); | |
| if (!dialogFooter) { | |
| console.log('⚠ Dialog-Footer nicht gefunden in diesem Dialog!'); | |
| setTimeout(confirmDelete, 500); | |
| return; | |
| } | |
| console.log('✓ Dialog-Footer im richtigen Dialog gefunden!'); | |
| // Suche alle Buttons im Footer dieses spezifischen Dialogs | |
| const footerButtons = dialogFooter.querySelectorAll('button'); | |
| console.log('→ Gefundene Footer-Buttons im Lösch-Dialog:', footerButtons.length); | |
| let confirmButton = null; | |
| for (const btn of footerButtons) { | |
| const text = btn.textContent.toLowerCase().trim(); | |
| const classes = btn.className; | |
| console.log(' → Button-Text: "' + text + '", Klassen:', classes); | |
| // Suche nach dem "Löschen" Button | |
| if (text === 'löschen' || text === 'delete') { | |
| confirmButton = btn; | |
| console.log('✓✓ Löschen-Button gefunden!'); | |
| break; | |
| } | |
| } | |
| if (!confirmButton) { | |
| // Fallback: Suche nach danger-Button | |
| confirmButton = dialogFooter.querySelector('button.Button_danger__hwLq8, button[class*="danger"]'); | |
| if (confirmButton) { | |
| console.log('✓✓ Löschen-Button via Danger-Klasse gefunden!'); | |
| } | |
| } | |
| if (!confirmButton) { | |
| console.log('⚠ Kein Löschen-Button gefunden. Schließe Dialog...'); | |
| const closeBtn = deleteDialog.querySelector('.Dialog_dialogCloseBtn__EtPd-'); | |
| if (closeBtn) { | |
| closeBtn.click(); | |
| console.log('→ Dialog geschlossen, versuche nächstes Workout...'); | |
| } | |
| setTimeout(deleteWorkouts, 2000); | |
| return; | |
| } | |
| console.log('→ Klicke Löschen-Button...'); | |
| confirmButton.click(); | |
| console.log('✓✓✓ Workout gelöscht!\n'); | |
| // Nächstes Workout nach kurzer Pause | |
| setTimeout(deleteWorkouts, 2000); | |
| } | |
| // Start | |
| console.log('╔════════════════════════════════════════════════════╗'); | |
| console.log('║ Predictive Fitness Workout Bulk-Löscher ║'); | |
| console.log('╚════════════════════════════════════════════════════╝'); | |
| console.log('Kriterium: "Bei Predictive Fitness bearbeiten"\n'); | |
| deleteWorkouts(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment