ZoomBot.

A pool of headless Chrome bots auto-joining Zoom meetings, driven over an HTTP API and self-healing on crash — Python + Selenium + Flask, one Chrome per bot, per-bot locks, thread-safe.

Year2025
RoleSolo · system design + python
Timeline8 weeks
Status● Live · self-hosted

01.Overview

The big picture

Zoom-bot started from a very simple question from a friend: "Is it possible to have multiple accounts join Zoom at the same time?" From there I began experimenting with Chrome automation to simulate multiple participants joining the same meeting. After a lot of tuning around browser profiles, resource usage, and network stability, the system can run 20 bots simultaneously on a single machine — as long as the network holds up.

Everything is packaged into a single Python process: main.py spawns N bots (each with its own Chrome instance and a temporary user-data-dir), a health loop daemon running every 20 seconds to automatically restart any bot that dies, and a Flask API for remote control. Version v0.4 runs stably for hours at a time with 20 bots in parallel, each consuming around ~100MB RAM.

02.Features

What it does
  • 01
    Isolated Chrome poolOne Chrome · one tempdir · one lock

    Each bot is an independent webdriver.Chrome instance with --user-data-dir pointing to its own temporary directory from tempfile.mkdtemp — completely isolated browser profiles, nothing shared between bots. Each bot holds its own RLock, ensuring 2 concurrent requests to the same bot queue up sequentially rather than racing on the driver. One Chrome crashing takes nothing else down with it.

  • 02
    HTTP control planeFlask · chat · chat-all · mic · video · leave · rejoin

    Flask 3 runs in threaded mode, serving the full set of control endpoints: POST /chat sends a message to a specific bot by name, POST /chat-all broadcasts in parallel to the entire pool via ThreadPoolExecutor. POST /bots/<name>/mic and /video toggle the mic and camera on individual bots, /leave and /rejoin control meeting presence. GET /bots returns the full bot list with current status, /health confirms the system is alive.

  • 03
    Health check & auto-restart20s loop · rate-limit · backoff

    A daemon thread runs in the background, scanning every slot every 20 seconds to check each bot's health by reading driver.title and current_url. If a bot stops responding or returns unexpected values, _restart_slot is called — the old Chrome is killed, a new instance spins up, and the bot rejoins the meeting. To prevent an infinite restart loop when a bot keeps crashing, the system uses a 1-hour sliding window: once MAX_RESTART_PER_HOUR=5 is exceeded, the slot is flagged disabled=true and halted entirely until manual intervention.

  • 04
    Random mic/video per botmicro_video_random · per-instance prefs

    Enabling micro_video_random=true activates a more realistic simulation mode: every time a bot spawns — including restarts after a crash — _pick_av_state() independently randomises the mic and video state for that bot. Chrome prefs ALLOW/BLOCK are built separately per instance, never shared across bots. The result is a meeting with a natural mix of participants — some with mic on, some off, some with camera on, some off — closer to the behaviour of a real group of users than a uniform bot pool.

  • 05
    Polling dashboardVanilla HTML · poll /bots · per-bot controls

    A single static dashboard.html file, no build step required, polls /bots and /health every few seconds to display the full pool status in real time. Each bot shows its current state — alive / disabled / manual_offline — along with mic and video status, and the number of restarts in the past hour. A row of buttons per bot allows toggling mic, toggling video, leaving, or rejoining directly from the page. Open a browser and it works, nothing else to install.

03.Tech stack

Tools used
LanguagePython 3.8+ · type hints · dataclasses (BotSlot)
BrowserSelenium 4.6+ · Chrome headless=new · Selenium Manager (auto-managed chromedriver) · per-bot temp --user-data-dir
APIFlask 3 (threaded=True) · JSON in / out · ThreadPoolExecutor for /chat-all
Concurrencymain thread (entry + hotkey) · daemon health loop · Flask worker threads · per-bot RLock · stop_flag (Event) for orderly shutdown
ReliabilityHealth probe (driver.title + current_url) · auto-restart · sliding-window 5/h rate-limit · screenshot + DOM dump on join failure
Dev toolskeyboard (Alt+Ctrl+Shift+E exit hotkey) · logging (console INFO + file DEBUG) · dump_after_join to refresh selectors

04.How it works

Architecture

The threading model is lean but the order has to be right: main thread runs a hotkey loop waiting for an exit signal; a daemon health-loop scans all slots every 20 seconds; a daemon Flask serves HTTP with threaded=True. Every Selenium operation goes through ZoomBot.lock — an RLock per bot — ensuring 2 concurrent /chat requests to the same bot queue up sequentially. /chat-all fires in parallel via ThreadPoolExecutor and stays safe because each worker acquires only its own bot's lock.

The trickiest part is setting mic and video state after joining: Zoom Web Client encodes state in the button's aria-labelmute my microphone means mic is ON, unmute my microphone means it's OFF. Matching requires substring logic and checking 'unmute' before 'mute' — because 'unmute' contains 'mute' as a substring. This bug caused mic to always appear enabled regardless of config in v0.3, fixed in v0.4. When WebElement.click() fails to trigger the React handler headlessly, the fallback is a JS click.

Design decision

The most time-consuming part of building ZoomBot wasn't spawning Chrome instances or wiring up the Flask API — it was correctly reading mic state from an aria-label. Zoom Web Client exposes no clean state attribute, no data attribute, no class toggle — just a text string in the label that changes depending on language and version. The v0.3 bug lived exactly there: checking 'mute' before 'unmute' meant mic state was always read as ON. One prefix string, a few hours of debugging.

05.Comments

Leave a few words

No comments yet.

Xem tiep
hotel preview
H
Hotel management

Hotel Management is an internal hotel administration system built for front desk staff and operations managers. The entire core workflow runs within a single system: accepting reservations from walk-in guests or online channels, handling check-in and check-out, recording ancillary services incurred during the stay, and automatically generating invoices at checkout. Permissions are scoped down to individual actions — receptionists, cashiers, and managers each have different screens and access levels. Every change in the system is written to a full audit log, with enough detail to trace back anything when needed.

trading-signals preview
T
Trading Signals

TradingSignals is a personal buy/sell signal tool for crypto and equities — not a mass copy-trading bot, but a system built for one person, backtested against 2–5 years of real market data. NestJS scans simultaneously across multiple timeframes from 15 minutes to 1 month, running in parallel through 4 independent engines: candlestick pattern, indicator, price action and volume analysis. A signal is only recorded when multiple timeframes converge — cutting through noise and surfacing only the entry points genuinely worth attention. After 10 days, the system looks back at each signal — right or wrong — gradually sharpening its accuracy the longer it runs. Results are pushed directly to a personal Telegram the moment a signal fires, with patterns rendered in real time on a TradingView chart embedded in Next.js.

python-ocr preview
O
Captcha OCR

CaptchaOCR is a FastAPI micro-service that accepts a 4-digit captcha image in base64 and returns the corresponding integer. The ddddocr pretrained model is loaded once at process startup — every subsequent request is pure inference, with latency in the range of a few tens of milliseconds per image. The service is deployed via PM2 with auto-restart and a hard RAM cap, keeping it stable over long periods without manual oversight.

random-tools preview
R
Random Tools

RandomKit is a lightweight collection of randomisation tools that runs entirely client-side — no server, no data sent anywhere. It includes the tools people actually reach for: a customisable spinner with fair probabilities and transparent percentage display, duck racing, a draw lots picker, coin flip, dice roller, random number generator, password generator, and a random location picker on a map. The interface supports both Vietnamese and English, with clean SEO structure so each individual tool is independently discoverable.