diff --git a/backend/services/clickTtTournamentRegistrationService.js b/backend/services/clickTtTournamentRegistrationService.js index b30d344f..5f23b62d 100644 --- a/backend/services/clickTtTournamentRegistrationService.js +++ b/backend/services/clickTtTournamentRegistrationService.js @@ -152,11 +152,20 @@ class ClickTtTournamentRegistrationService { trace }); await clickTtPlayerRegistrationService._selectClubContext(page, associationMemberNumber, trace); + await this._openTournamentSearch(page, trace); + await this._filterTournamentSearch(page, tournament, trace); + const resultsPageUrl = page.url(); const processedCompetitions = []; for (const group of competitionGroups) { - await this._openTournamentSearch(page, trace); - await this._filterTournamentSearch(page, tournament, trace); + if (page.url() !== resultsPageUrl) { + clickTtPlayerRegistrationService._trace(trace, 'step', { + name: 'open-search-results', + url: resultsPageUrl + }); + await page.goto(resultsPageUrl, { waitUntil: 'domcontentloaded' }); + await clickTtPlayerRegistrationService._dismissConsentOverlays(page, trace); + } await this._openCompetitionRegistration(page, tournament, group.competition, trace); await this._selectCompetitionMembers(page, group.entries, trace); await this._openControlStep(page, trace); @@ -244,79 +253,8 @@ class ClickTtTournamentRegistrationService { } const titleProfile = getTitleSearchProfile(tournament.title || ''); - const contentRoot = page.locator('#content-row1, #content, #container').first(); - const tournamentHref = await contentRoot.locator('a').evaluateAll((anchors, criteria) => { - const normalize = (value) => String(value || '') - .normalize('NFKC') - .replace(/\s+/g, ' ') - .trim() - .toLowerCase(); - const tokenize = (value) => normalize(value) - .split(/[^a-z0-9äöüß]+/i) - .map((token) => token.trim()) - .filter((token) => token.length >= 3) - .filter((token) => !new Set([ - 'und', 'der', 'die', 'das', 'des', 'den', 'dem', 'von', 'für', 'mit', - 'ein', 'eine', 'einer', 'eines', 'zum', 'zur', 'im', 'am', 'an' - ]).has(token)); - - const wantedTitle = normalize(criteria.tournamentTitle); - const wantedTokens = Array.isArray(criteria.tournamentTokens) ? criteria.tournamentTokens : []; - const wantedTailTokens = Array.isArray(criteria.tournamentTailTokens) ? criteria.tournamentTailTokens : []; - - let bestHref = null; - let bestScore = -1; - - for (const anchor of anchors) { - const href = anchor.getAttribute('href'); - if (!href) continue; - if (/dataProtection|legalNotice|logout|contact/i.test(href)) continue; - - const text = normalize(anchor.textContent || ''); - const contextText = normalize(anchor.closest('tr, li, div, td')?.textContent || ''); - const combinedText = `${text} ${contextText}`.trim(); - const combinedTokens = new Set(tokenize(combinedText)); - - let score = 0; - if (wantedTitle && combinedText.includes(wantedTitle)) score += 100; - if (text && wantedTitle && text.includes(wantedTitle)) score += 30; - - for (const token of wantedTokens) { - if (combinedTokens.has(token)) score += 3; - } - - for (const token of wantedTailTokens) { - if (combinedTokens.has(token)) score += 8; - } - - if (/turnier|meisterschaft|rangliste|pokal/i.test(combinedText)) score += 10; - if (/anmelden|meldung|teilnehmer/i.test(combinedText)) score += 5; - - if (score > bestScore) { - bestScore = score; - bestHref = href; - } - } - - return bestHref; - }, { - tournamentTitle: tournament.title || '', - tournamentTokens: titleProfile.tokens, - tournamentTailTokens: titleProfile.tailTokens - }); - - if (tournamentHref) { - clickTtPlayerRegistrationService._trace(trace, 'step', { - name: 'click', - label: tournament.title || 'Turnier', - selector: `a[href="${tournamentHref}"]` - }); - await contentRoot.locator(`a[href="${tournamentHref}"]`).first().click(); - await page.waitForLoadState('domcontentloaded'); - } - - const href = await page.locator('#content-row1 a, #content a, #container a').evaluateAll((anchors, criteria) => { + const href = await page.locator('#content-row1 tr, #content tr, #container tr').evaluateAll((rows, criteria) => { const normalize = (value) => String(value || '') .normalize('NFKC') .replace(/\s+/g, ' ') @@ -339,19 +277,26 @@ class ClickTtTournamentRegistrationService { let bestHref = null; let bestScore = -1; - for (const anchor of anchors) { + for (const row of rows) { + const rowText = normalize(row.textContent || ''); + if (!rowText.includes(wantedCompetition)) continue; + if (wantedTournament && !rowText.includes(wantedTournament)) continue; + + const anchor = Array.from(row.querySelectorAll('a[href]')).find((candidate) => + /anmeldung|anmelden|melden/i.test(normalize(candidate.textContent || '')) + ); + if (!anchor) continue; const href = anchor.getAttribute('href'); if (!href) continue; if (/dataProtection|legalNotice|logout|contact/i.test(href)) continue; - const text = normalize(anchor.textContent || ''); - if (!text.includes(wantedCompetition)) continue; - const contextText = normalize(anchor.closest('tr, li, div, td')?.textContent || ''); - const combinedText = `${text} ${contextText}`.trim(); + const linkText = normalize(anchor.textContent || ''); + const combinedText = `${rowText} ${linkText}`.trim(); const combinedTokens = new Set(tokenize(combinedText)); let score = 0; if (wantedTournament && combinedText.includes(wantedTournament)) score += 100; + if (/anmeldung|anmelden|melden/.test(linkText)) score += 20; for (const token of wantedTournamentTokens) { if (combinedTokens.has(token)) score += 3;