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.
2 Β· Top-level topology
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
- 1A message arrives on a platform (e.g. a WhatsApp DM hits /internal/wa-message, or a Bale group message hits the Bale client).
- 2The bridge ensures a paired group exists (get_or_create_contact_group) and records it in group_mapping.
- 3The router looks up the mapping and decides the destination platform + target id.
- 4deliver(platform, to, text) puts a job on q:<platform>.out.
- 5A worker in that platform's pool (any server) drains the queue and performs the real send.
4 Β· Each platform
| Platform | Library / transport | Login | Multi-account | Module |
|---|---|---|---|---|
| βοΈ Telegram | Telethon (MTProto) | phone + code | workers/sessions | tg/client.py |
| π’ Bale | aiobale + bot REST | phone + code | workers/sessions | bale/client.py Β· bale/api.py |
| πΈ Instagram | instagrapi (private API) | sessionid cookie | β rotating pool + proxy | instagram/bridge.py Β· features/instagram.py |
| π¬ WhatsApp | whatsapp-web.js (Node sidecar) | QR scan | one linked device / sidecar | wa-bridge/index.js |
| π Rubika | rubpy | phone + code | workers/sessions | rubika/client.py |
| π‘ Eitaa | eitaayar bot API | bot token | tokens | eita/__init__.py |
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.
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
main.py = the hub (one server). worker.py <platform> = a worker (any server). Both point at the same REDIS_URL.
Raise or lower workers per platform without a restart:
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
- 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/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
- tg/client.py Β· bale/client.py Β· bale/api.py
- instagram/bridge.py Β· rubika/client.py Β· eita/
- wa-bridge/index.js (Node)
- features/youtube.py Β· tools.py Β· instagram.py
- features/telegram.py Β· subscriptions.py Β· flags.py
- api/youtube.py β public /watch proxy + player API
- (more route groups migrating out of main.py incrementally)
- login.py β per-server multi-account login
- broadcast.py β channel broadcast/advertisement
- service_manager.py β PyQt desktop control panel
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.