diff --git a/src/bot/joinProcedure.ts b/src/bot/joinProcedure.ts index 062ea38..f10cf24 100644 --- a/src/bot/joinProcedure.ts +++ b/src/bot/joinProcedure.ts @@ -203,26 +203,33 @@ export class JoinProcedure { // First, dismiss any "no audio/video" modal that may be blocking await this._dismissNoAudioVideoModal(); - // Primary selector - confirmed working by Recall.ai (Jan 2025) - const primarySelector = 'button:has-text("Join now")'; + // Teams v2 uses stable IDs for the join button. Wait with multiple selectors. + // The button may take time to render in the Teams v2 SPA after auth redirect. + const joinSelectors = [ + '#prejoin-join-button', // Teams v2 stable ID + 'button[data-tid="prejoin-join-button"]', // Teams v2 data-tid + 'button:has-text("Join now")', // Text-based (light-meetings) + 'button:has-text("Join meeting")', // Alternative text + ]; + + const combinedSelector = joinSelectors.join(', '); try { - await this._page.waitForSelector(primarySelector, { timeout: 15000 }); - await this._page.click(primarySelector); - this._logger.info('Clicked "Join now" button'); - - // After clicking Join, Teams may show the modal again. Dismiss if present. - await this._page.waitForTimeout(2000); - await this._dismissNoAudioVideoModal(); - return; + await this._page.waitForSelector(combinedSelector, { timeout: 20000, state: 'visible' }); + const button = await this._page.$(combinedSelector); + if (button) { + await button.click(); + this._logger.info('Clicked "Join now" button'); + await this._page.waitForTimeout(2000); + await this._dismissNoAudioVideoModal(); + return; + } } catch { - this._logger.info('Primary join button selector not found, trying fallbacks...'); + this._logger.info('Join button not found with combined selectors, trying text fallbacks...'); } - // Fallback selectors + // Last resort fallback: any button with "Join" text const fallbackSelectors = [ - 'button[data-tid="prejoin-join-button"]', - 'button:has-text("Join meeting")', 'button:has-text("Join")', '[data-tid="joinButton"]', ]; diff --git a/src/bot/orchestrator.ts b/src/bot/orchestrator.ts index 38ce7a2..29ee879 100644 --- a/src/bot/orchestrator.ts +++ b/src/bot/orchestrator.ts @@ -218,22 +218,55 @@ export class BotOrchestrator { const postAuthUrl = this._page!.url(); this._logger.info(`Post-auth URL: ${postAuthUrl.substring(0, 100)}`); - // After login, Microsoft may redirect to M365/Office instead of back to the meeting. - // We need to navigate back to the meeting URL -- now with the auth session active. - // This time, Teams should recognize the auth and show the authenticated pre-join page. - if (!postAuthUrl.includes('teams.microsoft.com/v2') || !postAuthUrl.includes('meet')) { - this._logger.info('Not on Teams meeting page after auth - navigating back to meeting URL...'); - await this._page!.goto(this._meetingUrl, { - waitUntil: 'domcontentloaded', - timeout: 30000, - }); - - // Handle launcher dialog if it appears again - await this._joinProcedure!.handleLauncherIfPresent(); - await this._page!.waitForTimeout(5000); - - const meetingPageUrl = this._page!.url(); - this._logger.info(`After re-navigation to meeting: ${meetingPageUrl.substring(0, 100)}`); + // After login, Microsoft redirects to M365 (m365.cloud.microsoft/chat/). + // The "Continue on this browser" launcher ALWAYS leads to anonymous mode. + // Instead, navigate to teams.cloud.microsoft and use the meeting join from there. + // teams.cloud.microsoft is the new Teams domain where the auth session lives. + const { parseMeetingUrl } = await import('./meetingUrlParser'); + const parsed = parseMeetingUrl(this._meetingUrl); + const meetingId = parsed.meetingId || ''; + const passcode = parsed.passcode || ''; + + // Navigate to Teams on the cloud.microsoft domain (where auth cookies are) + // and try to join the meeting from within the authenticated app + const teamsCloudUrls = [ + // Direct meeting URL on new Teams domain + `https://teams.cloud.microsoft/meet/${meetingId}${passcode ? '?p=' + passcode : ''}`, + // Original meeting URL (teams.microsoft.com) + this._meetingUrl, + ]; + + for (const url of teamsCloudUrls) { + this._logger.info(`Trying authenticated meeting URL: ${url.substring(0, 80)}`); + try { + await this._page!.goto(url, { + waitUntil: 'domcontentloaded', + timeout: 20000, + }); + await this._page!.waitForTimeout(5000); + + const resultUrl = this._page!.url(); + const pageContent = await this._page!.evaluate(() => document.body?.innerText?.substring(0, 500) || ''); + this._logger.info(`Result URL: ${resultUrl.substring(0, 100)}`); + this._logger.info(`Page content: ${pageContent.substring(0, 200)}`); + + // Check if we have the authenticated pre-join (no "Type your name", has "Join now") + if (pageContent.includes('Join now') && !pageContent.includes('Type your name') && !pageContent.includes('Enter the name')) { + this._logger.info('Found authenticated pre-join page!'); + break; + } + + // If launcher appears, DON'T click "Continue on this browser" (leads to anon) + // Instead try the next URL + if (pageContent.includes('Continue on this browser')) { + this._logger.info('Launcher appeared - skipping (would lead to anonymous)'); + continue; + } + + } catch (navErr) { + this._logger.warn(`Navigation to ${url.substring(0, 60)} failed: ${navErr}`); + continue; + } } // Verify we're on the authenticated pre-join page