feat(myTischtennis): integrate Playwright for CAPTCHA handling and enhance login form functionality

- Added Playwright as a dependency to handle CAPTCHA challenges during login attempts.
- Implemented a new endpoint to retrieve the login form from myTischtennis, parsing necessary fields for user input.
- Enhanced the login process to utilize Playwright for browser automation when CAPTCHA is required.
- Updated the MyTischtennisDialog component to support local login form submission instead of using an iframe.
- Refactored the MyTischtennisController to include proxy functionality for serving resources and handling login submissions.
- Improved error handling and user feedback during login attempts, ensuring a smoother user experience.
This commit is contained in:
Torsten Schulz (local)
2026-02-27 17:15:20 +01:00
parent b2017b7365
commit 4e81a1c4a7
14 changed files with 917 additions and 258 deletions

View File

@@ -58,6 +58,15 @@ class MyTischtennisService {
// Login-Versuch bei myTischtennis
loginResult = await myTischtennisClient.login(email, password);
if (!loginResult.success && loginResult.requiresCaptcha) {
console.log('[myTischtennisService.upsertAccount] CAPTCHA-Fehler, versuche Playwright-Fallback...');
const playwrightResult = await myTischtennisClient.loginWithBrowserAutomation(email, password);
if (playwrightResult.success) {
loginResult = playwrightResult;
} else {
console.warn('[myTischtennisService.upsertAccount] Playwright-Fallback fehlgeschlagen:', playwrightResult.error);
}
}
if (!loginResult.success) {
const statusCode = loginResult.requiresCaptcha ? 400 : 401;
const errorMessage = loginResult.error || 'myTischtennis-Login fehlgeschlagen. Bitte überprüfen Sie Ihre Zugangsdaten.';
@@ -74,10 +83,14 @@ class MyTischtennisService {
const now = new Date();
if (account) {
const effectiveAutoUpdateRatings = autoUpdateRatings === undefined
? account.autoUpdateRatings
: Boolean(autoUpdateRatings);
// Update existing
account.email = email;
account.savePassword = savePassword;
account.autoUpdateRatings = autoUpdateRatings;
account.autoUpdateRatings = savePassword ? effectiveAutoUpdateRatings : false;
if (password && savePassword) {
account.setPassword(password);
@@ -115,7 +128,7 @@ class MyTischtennisService {
userId,
email,
savePassword,
autoUpdateRatings,
autoUpdateRatings: savePassword ? Boolean(autoUpdateRatings) : false,
lastLoginAttempt: password ? now : null,
lastLoginSuccess: loginResult?.success ? now : null
};
@@ -225,18 +238,48 @@ class MyTischtennisService {
// Login-Versuch mit Passwort
console.log('[myTischtennisService.verifyLogin] Attempting login for user:', account.email);
const loginResult = await myTischtennisClient.login(account.email, password);
console.log('[myTischtennisService.verifyLogin] Login result:', { success: loginResult.success, error: loginResult.error, requiresCaptcha: loginResult.requiresCaptcha });
let effectiveLoginResult = loginResult;
if (!effectiveLoginResult.success && effectiveLoginResult.requiresCaptcha) {
console.log('[myTischtennisService.verifyLogin] CAPTCHA-Fehler, versuche Playwright-Fallback...');
try {
const playwrightResult = await myTischtennisClient.loginWithBrowserAutomation(account.email, password);
if (playwrightResult.success) {
effectiveLoginResult = playwrightResult;
} else {
console.warn('[myTischtennisService.verifyLogin] Playwright-Fallback fehlgeschlagen:', playwrightResult.error);
effectiveLoginResult = {
success: false,
error: playwrightResult.error || 'Playwright-Fallback fehlgeschlagen',
requiresCaptcha: true,
status: 400
};
}
} catch (playwrightError) {
console.warn('[myTischtennisService.verifyLogin] Playwright-Fallback Exception:', playwrightError?.message || playwrightError);
effectiveLoginResult = {
success: false,
error: `Playwright-Fallback Exception: ${playwrightError?.message || 'Unbekannter Fehler'}`,
requiresCaptcha: true,
status: 400
};
}
}
console.log('[myTischtennisService.verifyLogin] Login result:', {
success: effectiveLoginResult.success,
error: effectiveLoginResult.error,
requiresCaptcha: effectiveLoginResult.requiresCaptcha
});
if (loginResult.success) {
if (effectiveLoginResult.success) {
account.lastLoginSuccess = now;
account.accessToken = loginResult.accessToken;
account.refreshToken = loginResult.refreshToken;
account.expiresAt = loginResult.expiresAt;
account.cookie = loginResult.cookie;
account.userData = loginResult.user;
account.accessToken = effectiveLoginResult.accessToken;
account.refreshToken = effectiveLoginResult.refreshToken;
account.expiresAt = effectiveLoginResult.expiresAt;
account.cookie = effectiveLoginResult.cookie;
account.userData = effectiveLoginResult.user;
// Hole Club-ID und Federation
const profileResult = await myTischtennisClient.getUserProfile(loginResult.cookie);
const profileResult = await myTischtennisClient.getUserProfile(effectiveLoginResult.cookie);
if (profileResult.success) {
account.clubId = profileResult.clubId || account.clubId;
@@ -250,25 +293,31 @@ class MyTischtennisService {
return {
success: true,
accessToken: loginResult.accessToken,
refreshToken: loginResult.refreshToken,
expiresAt: loginResult.expiresAt,
user: loginResult.user,
accessToken: effectiveLoginResult.accessToken,
refreshToken: effectiveLoginResult.refreshToken,
expiresAt: effectiveLoginResult.expiresAt,
user: effectiveLoginResult.user,
clubId: account.clubId,
clubName: account.clubName
};
} else {
// Prevent stale "success" from previously valid sessions after an explicit failed login attempt.
account.accessToken = null;
account.refreshToken = null;
account.cookie = null;
account.expiresAt = null;
account.userData = null;
await account.save(); // Save lastLoginAttempt
const errorMessage = loginResult.error || 'myTischtennis-Login fehlgeschlagen';
const errorMessage = effectiveLoginResult.error || 'myTischtennis-Login fehlgeschlagen';
// Verwende den Status-Code vom myTischtennisClient, falls vorhanden, sonst 401
// Wenn CAPTCHA erforderlich ist, verwende 400 statt 401
const statusCode = loginResult.requiresCaptcha
const statusCode = effectiveLoginResult.requiresCaptcha
? 400
: (loginResult.status && loginResult.status >= 400 && loginResult.status < 600
? loginResult.status
: (effectiveLoginResult.status && effectiveLoginResult.status >= 400 && effectiveLoginResult.status < 600
? effectiveLoginResult.status
: 401);
console.error('[myTischtennisService.verifyLogin] Login failed:', errorMessage, `(Status: ${statusCode})`, loginResult.requiresCaptcha ? '(CAPTCHA erforderlich)' : '');
if (loginResult.requiresCaptcha) {
console.error('[myTischtennisService.verifyLogin] Login failed:', errorMessage, `(Status: ${statusCode})`, effectiveLoginResult.requiresCaptcha ? '(CAPTCHA erforderlich)' : '');
if (effectiveLoginResult.requiresCaptcha) {
throw new HttpError({ code: 'ERROR_MYTISCHTENNIS_CAPTCHA_REQUIRED', params: { message: errorMessage } }, statusCode);
}
throw new HttpError(errorMessage, statusCode);

View File

@@ -115,7 +115,6 @@ class SchedulerService {
/**
* Manually trigger rating updates (for testing)
* HINWEIS: Deaktiviert - automatische MyTischtennis-Abrufe sind nicht mehr verfügbar
*/
async triggerRatingUpdates() {
devLog('[Scheduler] Manual rating updates trigger called');
@@ -124,7 +123,6 @@ class SchedulerService {
/**
* Manually trigger match results fetch (for testing)
* HINWEIS: Deaktiviert - automatische MyTischtennis-Abrufe sind nicht mehr verfügbar
*/
async triggerMatchResultsFetch() {
devLog('[Scheduler] Manual match results fetch trigger called');