Skip to content

Implementation Details


Google Calendar Polling

The bot polls for upcoming meetings by navigating to meet.google.com/landing using Puppeteer (headless Chrome), rather than the Google Calendar API.

How it works: 1. scheduler.js calls fetchMeetLandingEvents() on a configurable interval (default: 5 minutes) 2. Puppeteer loads the Meet landing page (which shows upcoming meetings if authenticated) 3. The bot scans for meeting event cards using page.$$() to get live ElementHandle references 4. Each card is clicked; the bot then scans the full page HTML for a meet.google.com/xxx-xxxx-xxx URL pattern exposed in the detail panel 5. When a meeting is found within the join window (default: 1–2 minutes before start), its URL is passed to the joiner

Deduplication: a seenMeetCodes set prevents the same meeting from being processed twice per poll cycle.

Notification dismiss: before scanning, the bot clicks any visible "Not now" / "Close" / "Deny" button to dismiss the "Receive desktop notifications from Meet" dialog, which can cover event cards.


Meeting Join

Implemented in src/joiner.js using Puppeteer:

  1. Navigate to the Meet URL
  2. Wait for the pre-join lobby to load
  3. Mute microphone and camera (click the toggle buttons)
  4. Click the "Join now" / "Ask to join" button
  5. Wait for the in-meeting state using detectInMeeting():
  6. Checks that video[autoplay] (remote stream) is present
  7. Confirms that no pre-join join-button selectors remain
  8. Confirms the body text does not contain "join now" / "ask to join"
  9. Returns { success, page, screenshotPath }

A 6-second post-click settle delay prevents false-positive detection while the UI transitions from pre-join to in-meeting.


Audio Capture

Implemented in src/recorder.js:

  1. setupPulseAudio() creates a virtual PulseAudio null sink and a loopback module that routes Chrome's audio output through it
  2. FFmpeg spawns and records from the virtual sink output at 64 kbps mono MP3
  3. If FFmpeg exits immediately (PulseAudio not ready), the bot waits 3 seconds, re-runs setupPulseAudio(), and retries FFmpeg once
  4. If the retry also fails, an error is thrown (no silent missing audio file)
  5. Recording stops when the bot detects the meeting has ended
  6. Audio saved to /root/meet-transcriber/recordings/meeting_<id>_<timestamp>.mp3

Transcription

Implemented in src/transcriber.js:

  • The MP3 file is sent to OpenAI's Whisper API (/v1/audio/transcriptions)
  • Model: whisper-1
  • Response: plain text transcript
  • Transcript delivered to Telegram via notifier.js

Telegram Bot Commands

Command Action
/login Force re-authentication — opens browser, sends step-by-step screenshots to Telegram
/status Show current bot status (running, idle, in meeting)
/join <url> Manually join a specific Meet URL immediately
/iseeevents Debug snapshot — navigates to Meet landing, takes screenshot, sends body text; logs type: "error" event to API

Error Recovery

  • PM2 auto-restart: meet-transcriber restarts automatically on crash (max 10 restarts, 5s delay between restarts)
  • Browser crash recovery: when a CDP crash is detected in runScheduler, ensureGoogleSession() is called to reconnect before the next poll
  • Transient errors suppressed from Telegram: Protocol error, Connection closed, Navigation timeout, net::ERR_* and similar self-recovering errors are logged to PM2 only
  • error-watcher: separate PM2 process monitors logs for crash patterns and fires alerts

Session Persistence

  • Google authentication cookies are saved to google-cookies.json after each successful login
  • On startup, google-login.js injects the saved cookies into Puppeteer before navigation
  • areCookiesValid() checks google.com/account and meet.google.com/landing to verify session is active
  • If cookies are stale, the bot re-runs the full login flow and requests a backup code via Telegram if needed

PM2 Configuration

Defined in ecosystem.config.cjs:

Process Port Notes
meet-transcriber none Headless browser, runs as root
meet-status 3456 Express API + dashboard
error-watcher none Log monitor, fires on crash patterns

Environment: TZ=America/Chicago (ensures Chrome renders meeting times in Central time).