πŸŒ‰
BridgeBot β€” System Architecture

1 Β· What BridgeBot is

A multi-platform bridge: it connects a contact on one messenger to a contact on another so they can talk without switching apps. It also proxies content (YouTube, Instagram, X) for networks where those sites are blocked, and exposes a web app, a public web-chat, and wizard bots on Telegram & Bale.

Platforms bridged
Telegram Β· Bale Β· Instagram Β· WhatsApp Β· Rubika Β· Eitaa
Interfaces
Web app Β· Web-chat Β· TG bot Β· Bale bot Β· WA commands Β· Admin dashboard
Runtime
Python async hub + Node WhatsApp sidecar + optional Redis + workers

2 Β· Top-level topology

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ HUB (python main.py) ──────────────────────────┐ β”‚ Flask API :8000 (admin) Flask API :8001 (user/webchat) β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ TG client β”‚ β”‚ Bale clnt β”‚ β”‚ IG poller β”‚ β”‚ Router (group_mapping) β”‚ β”‚ β”‚ β”‚ TG bot β”‚ β”‚ Bale bot β”‚ β”‚ Rubika β”‚ β”‚ Worker pools (scalable) β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ webhook /internal/wa-message β”‚ q:<platform>.out β”Œβ”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ WhatsApp sidecarβ”‚ (Node, whatsapp-web.js, :3001) β”‚ BROKER (Redis / mem) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ pull jobs β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ worker.py bale / telegram / ig … β”‚ β”‚ (on THIS or ANOTHER server) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The hub owns the bridge logic and the single source of truth (group_mapping). Sending is a seam (deliver()) that puts jobs on the broker; workers β€” local or remote β€” drain the queues and do the actual send.

3 Β· How a message crosses platforms

Inbound β†’ routed β†’ Outbound
  1. 1
    A message arrives on a platform (e.g. a WhatsApp DM hits /internal/wa-message, or a Bale group message hits the Bale client).
  2. 2
    The bridge ensures a paired group exists (get_or_create_contact_group) and records it in group_mapping.
  3. 3
    The router looks up the mapping and decides the destination platform + target id.
  4. 4
    deliver(platform, to, text) puts a job on q:<platform>.out.
  5. 5
    A worker in that platform's pool (any server) drains the queue and performs the real send.
Two broker roads
Queues (load-balanced)
q:inbound, q:telegram.out, q:bale.out … each job taken by exactly one worker β†’ parallelism & scaling.
Channels (fan-out pub/sub)
events, log, status β€” every subscriber (dashboards) gets each message.
Backend swap
No REDIS_URL β†’ in-memory (single machine). Set REDIS_URL β†’ Redis (many servers share the same queues). Identical interface.

4 Β· Each platform

PlatformLibrary / transportLoginMulti-accountModule
✈️ TelegramTelethon (MTProto)phone + codeworkers/sessionstg/client.py
🟒 Baleaiobale + bot RESTphone + codeworkers/sessionsbale/client.py · bale/api.py
πŸ“Έ Instagraminstagrapi (private API)sessionid cookieβœ“ rotating pool + proxyinstagram/bridge.py Β· features/instagram.py
πŸ’¬ WhatsAppwhatsapp-web.js (Node sidecar)QR scanone linked device / sidecarwa-bridge/index.js
🟠 Rubikarubpyphone + codeworkers/sessionsrubika/client.py
🟑 Eitaaeitaayar bot APIbot tokentokenseita/__init__.py
Bridge lifecycle

User picks a contact in the bot/web wizard β†’ hub creates a paired group on each side (or a web-chat token) β†’ stores both directions in group_mapping β†’ sends an invitation (group link, or the web-chat link for Instagram). Afterwards every message flows both ways via the router.

Instagram web-chat (blacklist-safe)

Because IG DMs are fragile from a datacenter IP, the IG bridge sends the contact a one-time greeting DM containing a web-chat link (/chat/<token>). The real conversation happens in our web-chat, bridged to the paired TG group β€” so no ongoing IG DM traffic is needed.

5 Β· Running platforms on other servers

The rule

main.py = the hub (one server). worker.py <platform> = a worker (any server). Both point at the same REDIS_URL.

# hub server set REDIS_URL=redis://10.0.0.1:6379/0 python main.py # instagram on a clean-IP server set REDIS_URL=redis://10.0.0.1:6379/0 python login.py instagram --sessionid <cookie> --proxy socks5://127.0.0.1:1080 python worker.py instagram
Scaling threads (live)

Raise or lower workers per platform without a restart:

GET /admin/api/workers POST /admin/api/workers {"platform":"bale","count":5}

Or run worker.py bale on more servers β€” they all pull from q:bale.out and split the load. That's horizontal scale + multi-account (a different account per worker).

6 Β· Code map

Shared foundation
  • clients.py β€” live client objects (tg, bale, bots)
  • state.py β€” the one shared state dict + WS broadcast
  • db.py β€” SQLite schema + all persistence (mappings, messages, prefs, names, subs)
  • config.py β€” env vars
Broker + workers
  • broker/queue.py β€” work queues (in-mem / Redis)
  • broker/bus.py β€” pub/sub channels
  • broker/route.py β€” inboundβ†’outbound routing rules
  • workers.py β€” scalable per-platform pools
  • worker.py β€” standalone per-server entrypoint
Platforms
  • tg/client.py Β· bale/client.py Β· bale/api.py
  • instagram/bridge.py Β· rubika/client.py Β· eita/
  • wa-bridge/index.js (Node)
Features (shared by bots + web)
  • features/youtube.py Β· tools.py Β· instagram.py
  • features/telegram.py Β· subscriptions.py Β· flags.py
HTTP routes (Flask blueprints)
  • api/youtube.py β€” public /watch proxy + player API
  • (more route groups migrating out of main.py incrementally)
Login & ops
  • login.py β€” per-server multi-account login
  • broadcast.py β€” channel broadcast/advertisement
  • service_manager.py β€” PyQt desktop control panel
Refactor status

Every platform now lives in its own folder and runs individually. The shared foundation (clients/state/db) and broker/workers were extracted first, then each platform's connection loop + inbound path moved out behind a dependency-injection seam: start_telegram + its 4 event handlers β†’ tg/client.py; start_bale (the ~1,000-line aiobale WS loop + binary frame parser) β†’ bale/client.py; start_rubika β†’ rubika/client.py; start_instagram β†’ instagram/bridge.py; WhatsApp sidecar + inbound β†’ whatsapp/sidecar.py & whatsapp/inbound.py. The hub keeps only thin wrappers that inject deliver/submit_inbound as callables (no module imports main). Both PolBot onboarding wizards also moved out β€” the Telegram wizard (start_tg_bot + bridge creation + media cache) β†’ tg/bot.py (6 injected seams), and the Bale wizard (start_bale_bot + _handle_bale_bot_update REST poller) β†’ bale/bot.py (4 injected seams). The dispatch core (deliver/deliver_media/submit_inbound + inbound router + worker-pool setup) then moved to a self-contained dispatch.py β€” the keystone that lets route blueprints call deliver without importing main. main.py shrank from 10,091 β†’ ~4,220 lines. Route blueprints have started landing on top of that seam: api/otp.py (OTP service) and api/x.py (X/Twitter explore) join api/youtube.py, using the shared app.config['ASYNC_LOOP'] convention. The remaining /me/api and /admin/api route groups migrate the same way. Every module is documented file-by-file in docs/CODE_REFERENCE.md.

7 Β· Where you interact