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:
- Navigate to the Meet URL
- Wait for the pre-join lobby to load
- Mute microphone and camera (click the toggle buttons)
- Click the "Join now" / "Ask to join" button
- Wait for the in-meeting state using
detectInMeeting(): - Checks that
video[autoplay](remote stream) is present - Confirms that no pre-join join-button selectors remain
- Confirms the body text does not contain "join now" / "ask to join"
- 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:
setupPulseAudio()creates a virtual PulseAudio null sink and a loopback module that routes Chrome's audio output through it- FFmpeg spawns and records from the virtual sink output at 64 kbps mono MP3
- If FFmpeg exits immediately (PulseAudio not ready), the bot waits 3 seconds, re-runs
setupPulseAudio(), and retries FFmpeg once - If the retry also fails, an error is thrown (no silent missing audio file)
- Recording stops when the bot detects the meeting has ended
- 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-transcriberrestarts 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.jsonafter each successful login - On startup,
google-login.jsinjects the saved cookies into Puppeteer before navigation areCookiesValid()checksgoogle.com/accountandmeet.google.com/landingto 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).