stage-13: WhatsApp bridge with Baileys + Python adapter
Node.js bridge (bridge/whatsapp/): Baileys client with Express HTTP API on localhost:8098 — QR code linking, message queue, reconnection logic. Python adapter (src/adapters/whatsapp.py): polls bridge every 2s, routes through router.py, separate whatsapp.owner/admins auth, security logging. Integrated in main.py alongside Discord + Telegram via asyncio.gather. CLI: echo whatsapp status/qr. 442 tests pass (32 new, zero failures). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
90
cli.py
90
cli.py
@@ -522,6 +522,86 @@ def cmd_heartbeat(args):
|
||||
print(run_heartbeat())
|
||||
|
||||
|
||||
def cmd_whatsapp(args):
|
||||
"""Handle whatsapp subcommand."""
|
||||
if args.whatsapp_action == "status":
|
||||
_whatsapp_status()
|
||||
elif args.whatsapp_action == "qr":
|
||||
_whatsapp_qr()
|
||||
|
||||
|
||||
def _whatsapp_status():
|
||||
"""Check WhatsApp bridge connection status."""
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
cfg_file = CONFIG_FILE
|
||||
bridge_url = "http://127.0.0.1:8098"
|
||||
try:
|
||||
text = cfg_file.read_text(encoding="utf-8")
|
||||
cfg = json.loads(text)
|
||||
bridge_url = cfg.get("whatsapp", {}).get("bridge_url", bridge_url)
|
||||
except (FileNotFoundError, json.JSONDecodeError, OSError):
|
||||
pass
|
||||
|
||||
try:
|
||||
req = urllib.request.urlopen(f"{bridge_url}/status", timeout=5)
|
||||
data = json.loads(req.read().decode())
|
||||
except (urllib.error.URLError, OSError) as e:
|
||||
print(f"Bridge not reachable at {bridge_url}")
|
||||
print(f" Error: {e}")
|
||||
return
|
||||
|
||||
connected = data.get("connected", False)
|
||||
phone = data.get("phone", "unknown")
|
||||
has_qr = data.get("qr", False)
|
||||
|
||||
if connected:
|
||||
print(f"Status: CONNECTED")
|
||||
print(f"Phone: {phone}")
|
||||
elif has_qr:
|
||||
print(f"Status: WAITING FOR QR SCAN")
|
||||
print(f"Run 'echo whatsapp qr' for QR code instructions.")
|
||||
else:
|
||||
print(f"Status: DISCONNECTED")
|
||||
print(f"Start the bridge and scan the QR code to connect.")
|
||||
|
||||
|
||||
def _whatsapp_qr():
|
||||
"""Show QR code instructions from the bridge."""
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
cfg_file = CONFIG_FILE
|
||||
bridge_url = "http://127.0.0.1:8098"
|
||||
try:
|
||||
text = cfg_file.read_text(encoding="utf-8")
|
||||
cfg = json.loads(text)
|
||||
bridge_url = cfg.get("whatsapp", {}).get("bridge_url", bridge_url)
|
||||
except (FileNotFoundError, json.JSONDecodeError, OSError):
|
||||
pass
|
||||
|
||||
try:
|
||||
req = urllib.request.urlopen(f"{bridge_url}/qr", timeout=5)
|
||||
data = json.loads(req.read().decode())
|
||||
except (urllib.error.URLError, OSError) as e:
|
||||
print(f"Bridge not reachable at {bridge_url}")
|
||||
print(f" Error: {e}")
|
||||
return
|
||||
|
||||
qr = data.get("qr")
|
||||
if not qr:
|
||||
if data.get("connected"):
|
||||
print("Already connected — no QR code needed.")
|
||||
else:
|
||||
print("No QR code available yet. Wait for the bridge to initialize.")
|
||||
return
|
||||
|
||||
print("QR code is available at the bridge.")
|
||||
print(f"Open {bridge_url}/qr in a browser to scan,")
|
||||
print("or check the bridge terminal output for the QR code.")
|
||||
|
||||
|
||||
def cmd_secrets(args):
|
||||
"""Handle secrets subcommand."""
|
||||
if args.secrets_action == "set":
|
||||
@@ -664,6 +744,13 @@ def main():
|
||||
cron_disable_p = cron_sub.add_parser("disable", help="Disable a job")
|
||||
cron_disable_p.add_argument("name", help="Job name")
|
||||
|
||||
# whatsapp
|
||||
whatsapp_parser = sub.add_parser("whatsapp", help="WhatsApp bridge commands")
|
||||
whatsapp_sub = whatsapp_parser.add_subparsers(dest="whatsapp_action")
|
||||
|
||||
whatsapp_sub.add_parser("status", help="Check bridge connection status")
|
||||
whatsapp_sub.add_parser("qr", help="Show QR code instructions")
|
||||
|
||||
# Parse and dispatch
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -693,6 +780,9 @@ def main():
|
||||
"secrets": lambda a: (
|
||||
cmd_secrets(a) if a.secrets_action else (secrets_parser.print_help() or sys.exit(0))
|
||||
),
|
||||
"whatsapp": lambda a: (
|
||||
cmd_whatsapp(a) if a.whatsapp_action else (whatsapp_parser.print_help() or sys.exit(0))
|
||||
),
|
||||
}
|
||||
|
||||
handler = dispatch.get(args.command)
|
||||
|
||||
Reference in New Issue
Block a user