Meet Transcriber — Changelog¶
All notable changes are documented here.
[Unreleased] — Landing Page Debug Cleanup (Tasks #682–#691)¶
Fixed¶
Remove Body Preview from Telegram (Task #684–#687)¶
🔍 Landing page — 0 events foundTelegram caption no longer includesBody preview:section- Caption is now clean: URL only (
🔍 Landing page — 0 events found\nURL: ...) - Body text capped at 3000 chars inside
page.evaluateto limit CDP transfer size (was unbounded)
Debug Body Text Moved to API Events (Task #684–#687)¶
- Landing page body text now logged as
type: "debug"event in the API event log - Visible in dashboard at
https://recorder.nicnames.devand API athttps://api.recorder.nicnames.dev - Added
export function logDebugEvent(text)tosrc/notifier.js— wraps privateappendEvent('debug', text) - Available in default export object for use by other modules
Changed¶
src/scheduler.js— caption cleaned, bodyText bounded at 3000,logDebugEventcalled for API storagesrc/notifier.js—logDebugEventnamed export + added to default export
[Unreleased] — /iseeevents Debug Snapshot + Calendar Fallback + FFmpeg Retry (Tasks #550–#628)¶
Fixed¶
/iseeevents Debug Snapshot (Tasks #550–#582)¶
/iseeeventsTelegram command now triggers full debug snapshot:- Navigates to
meet.google.com/landing, takes screenshot - Sends screenshot to Telegram with
🚨 /iseeevents debug snapshotcaption - Sends page body text (up to 3800 chars) as follow-up
- Calls
sendErrorNotification→ logstype: "error"event in API captureDebugSnapshot()exported fromsrc/scheduler.jsglobalThis._captureDebugSnapshotregistered insrc/index.jsafter browser init/iseeeventshandler calls snapshot +appendEvent('error', ...)+ replies to user
Notification Dialog Dismiss (Tasks #570–#582)¶
- Added dismiss step before candidate scan in
fetchMeetLandingEvents - Clicks any visible button with dismiss-like text ("not now", "close", "deny", "block", "skip")
- 600ms pause after dismiss for dialog animation
- Root cause: "Receive desktop notifications from Meet" dialog covered event cards → 0 candidates found
Timezone Fix (Tasks #562–#568)¶
- TZ=America/Chicago added to .env (loaded via --env-file=.env by PM2)
- Process now runs in CDT — screenshots show consistent Central time
Calendar Event Click Fallback (Tasks #590–#628)¶
- chips.forEach now extracts _meetUrl from child anchor inside each chip
- Previously chips found via data-eventid had no _meetUrl — both primary click AND fallback failed
- Click re-query selector broadened: added data-eventchip alongside data-eventid/data-eventid-r
FFmpeg PulseAudio Race Condition (Tasks #596–#628)¶
- Added retry when FFmpeg exits immediately (PulseAudio sink not ready)
- On immediate exit: waits 3s, re-runs setupPulseAudio(), retries FFmpeg spawn
- If retry also fails: throws error (no silent missing audio file)
- Extracted spawnFfmpeg(outputPath) helper — eliminates duplicated spawn + event handler code
Changed¶
- src/scheduler.js — notification dismiss, captureDebugSnapshot export, calendar _meetUrl fallback
- src/notifier.js — /iseeevents command, help text updated
- src/index.js — globalThis._captureDebugSnapshot registration
- src/recorder.js — FFmpeg retry on immediate exit, spawnFfmpeg helper extracted
- .env — TZ=America/Chicago
[Unreleased] — PulseAudio Startup Fix + Landing Improvements (2025-05-23, Tasks #504–#525)¶
Fixed¶
PulseAudio Silent Startup (Task #522)¶
setupPulseAudio()now pre-checks if virtual sink/loopback already exist viapactl list short sinks/modules- If they exist: logs
Virtual sink already exists/Loopback already exists— no error Connection failure/Connection terminated/initialization failederrors suppressed as info (not errors)- Eliminates noisy error log on every bot restart when PulseAudio modules already loaded
Landing Page Date Header Filter (Task #504)¶
- Replaced bullet-regex date header filter with
titleLinerequirement - Meeting cards always have a title line (e.g. "Test call"); page clock headers never do — this is the reliable discriminator
TZ: 'America/Chicago'added to PM2 ecosystem so Chromium renders meeting times in CDT (user's timezone)- Landing 0-events debug message now sends a screenshot + body preview caption (not text-only)
- Debug body log throttled to once per 15 min via
_lastBodyDebug
Changed¶
src/recorder.js—setupPulseAudio()pre-checks before loading PulseAudio modulessrc/scheduler.js—titleLinerequirement replaces bullet regex; 0-events debug sends screenshot;TZin ecosystemecosystem.config.cjs— addedTZ: 'America/Chicago'
[Unreleased] — Date Header Filter Fix (2025-05-23, Tasks #493–#500)¶
Fixed¶
Landing Page — Date Header False Click (Task #495)¶
- Bot was clicking page clock elements like
"11:27 PM • Thu, Mar 26"as if they were meeting cards - Added regex filter in
fetchMeetLandingEvents: elements matchingHH:MM AM/PM • Word(bullet/dash separator after time) are now skipped - Meeting cards are multi-line (
time\ntitle); date headers are single-line with a bullet — this distinguishes them reliably - Covers separators:
•,·,-,–,—
File changed: src/scheduler.js
[Unreleased] — Explicit Mark-as-Read API (2025-05-23, Tasks #462–#468)¶
Added¶
Explicit Event Acknowledgment Flow¶
GET /api/events?show=new|all&limit=N—showparam (defaultnew); no longer auto-marks as readPOST /api/events/read— mark events as read by IDs:{"ids":["uuid",...]}POST /api/events/processing— claim events as in-progress by an agent; keeps them visible inshow=newstatusfield on all events:"new"|"processing"|"read"- Legacy events (without
status) auto-migrated on read viamigrateEvent() showfield echoed in API response for transparency- Typical agent flow: GET → POST /processing (claim) → work → POST /read (done)
Changed¶
GET /api/eventsno longer auto-marks events as read on retrieval (was: auto-mark on GET)notifier.js: new events written withstatus: "new"field
[Unreleased] — Status Website Fixes (2025-05-23, Tasks #452–#458)¶
Fixed¶
API Domain Separation¶
api.recorder.nicnames.devnow returns JSON only (no HTML)- Static file middleware skipped for
Host: api.*requests - Unknown routes return
{"error":"Not found","path":"..."}JSON 404 - Human dashboard (
recorder.nicnames.dev) unaffected — still serves HTML + SPA fallback
Screenshots Now Visible on Dashboard¶
- Bot screenshots were stored in
/tmpand deleted before the dashboard could serve them notifier.jsnow callspersistScreenshot()to copy each screenshot to/root/meet-transcriber/data/screenshots/before the/tmpfile is removed- Dashboard
mediaUrlfields now point to persistent paths that remain available - Screenshot directory auto-trimmed to last 50 files
/screenshotendpoint serves images from the persistent directory
Added¶
Automated Error Monitoring Cron¶
- OpenClaw cron
meet-transcriber:api-monitorregistered — fires every 10 minutes - Fetches
https://api.recorder.nicnames.dev/api/events - If
hasErrors: true→ analyzes errors and escalates to the fix team - If clean → silent (
HEARTBEAT_OK) - Events are marked as read on retrieval, so the cron never sees the same error twice
[Unreleased] — Status Website Addition (2025-05-23)¶
Added¶
Status Website — recorder.nicnames.dev (Task #442–#448)¶
Human Dashboard: https://recorder.nicnames.dev
- Dark-themed, mobile-friendly status page
- Shows last 20 bot events with colored type badges (error=red, screenshot=green, status=blue, etc.)
- Screenshot thumbnails embedded inline
- Auto-refreshes every 30 seconds via /api/events/all
Machine-Readable API: https://api.recorder.nicnames.dev
| Endpoint | Description |
|---|---|
GET /health |
{"status":"ok"} — liveness check |
GET /api/events |
Unread events from last 60 min; marks returned events as read (deduplicated per poll) |
GET /api/events/all |
Last 20 events regardless of read state |
GET /screenshot?path=<encoded> |
Serves screenshot images (path restricted to /tmp and /root/meet-transcriber) |
Event JSONL log: /root/meet-transcriber/data/events.jsonl
- Every Telegram notification now appended as a JSON line: {"id","ts","type","text","mediaUrl","read"}
- Event types: status, error, meeting_found, joined, transcript, screenshot
- Auto-trimmed to last 2000 lines when file exceeds 5MB
Infrastructure:
- meet-status PM2 process — Node/Express server on port 3456 (/root/meet-transcriber-status/)
- Nginx reverse proxy: both domains → 127.0.0.1:3456
- Let's Encrypt SSL active on both domains (auto-renews via certbot)
Changed¶
src/notifier.js:appendEvent()added — called from all 6 send functions to log events to JSONL
[Unreleased] — 2025-05-23¶
Added¶
Meet Landing Page Event Detection (Tasks #395–#415)¶
- DOM debug dump: when
fetchMeetLandingEventsreturns 0 events, sends a Telegram text message with full DOM diagnostic (links, headings, body preview). Throttled to once per 15 min. - Click-based event extraction: rewrote
fetchMeetLandingEventsto interact with Meet landing event cards. Cards have no anchor tags — the bot now usespage.$$()to get liveElementHandlerefs, clicks each card, and scans the full page HTML for ameet.google.com/xxx-xxxx-xxxpattern exposed in the in-page detail panel. - Dual URL extraction strategy: checks URL bar (Strategy A) and full page HTML scan (Strategy B) after each click.
seenMeetCodesdedup: prevents processing the same meeting twice per poll.- Escape to close panel between card clicks.
Join Flow Improvements (Tasks #417–#430)¶
- Screenshot with joined notification:
sendJoinedNotificationnow sends a photo (with caption) instead of text-only. Screenshot is taken inside the meeting after mic/camera mute. detectInMeetinghardened:- Added pre-join text guard: body text containing "join now" / "ask to join" / "ready to join" →
return false - Added DOM-level join-button guard: if any known join
jsnamebutton is still present →return false video[autoplay](remote stream) required for last-resort fallback instead of plain<video>(which fires on pre-join camera preview)- 6-second post-click settle delay before detection loop starts, preventing false-positive detection on pre-join state.
- Last-resort fallback hardened: requires
video[autoplay]+!isPreJoin+!hasJoinBtn— all three conditions must hold.
Error Handling (Tasks #433–#438)¶
- Browser crash errors suppressed from Telegram:
Protocol error,Connection closed,Target closed,Session closed,Navigation timeout,net::ERR_*,detached,Execution contexterrors are now logged to PM2 only (not sent to Telegram). These are transient self-recovering failures. - Browser recovery attempt on crash: when a CDP crash is detected in
runScheduler, callsensureGoogleSessionto attempt reconnect before next poll. - Eliminated "Unknown meeting" spam: scheduler-level errors with no active meeting no longer generate confusing Telegram error messages.
Changed¶
fetchMeetLandingEvents(src/scheduler.js): complete rewrite — no longer uses static selectors, uses live ElementHandle click + HTML scansendJoinedNotification(src/notifier.js): now accepts optionalscreenshotPathparam; sendssendPhotowith Markdown caption when providedjoinMeeting(src/joiner.js): returns{ success, page, screenshotPath }instead of sending screenshot directlyrunScheduler(src/index.js): three-tier error classification (login / browser-crash / genuine)
Fixed¶
- False-positive join detection: bot was declaring "Successfully joined" while still on the pre-join lobby screen
- "Unknown meeting" Telegram error notifications during browser crashes
- Click-based element finding: replaced fragile array-index re-query with live
ElementHandlerefs frompage.$$()
Previous Changes (pre-2025-05-23)¶
Session & Auth¶
- Auto re-login on Meet session expiry (
areCookiesValidchecks bothgoogle.com/accountandmeet.google.com/landing) /logincommand with step-by-step Telegram screenshots- CAPTCHA handoff flow via Telegram reply
- Backup code pool for 2FA automation
- CDP cookie capture/injection to persist auth across browser restarts
Recording & Transcription¶
- Audio capture via PulseAudio virtual sink
- MP3 recording at 64kbps mono
- OpenAI Whisper transcription
- Transcript delivered to Telegram on meeting end
Telegram Bot Commands¶
/login— force re-authentication/status— show bot status/join <url>— manual meeting join