diff --git a/bambulab/auth.py b/bambulab/auth.py index 65ee135..6393ecd 100644 --- a/bambulab/auth.py +++ b/bambulab/auth.py @@ -12,6 +12,7 @@ from pathlib import Path +# TODO(alstr): Refactor this class for token/account region handling class TokenManager: """ Manages API token mappings and validation. @@ -247,13 +248,13 @@ def _handle_email_verification( """ # Send verification code to email send_payload = { - "email": email, + "phone" if self.region == "china" else "email": email, "type": "codeLogin" } try: response = self.session.post( - f"{self.base_url}/v1/user-service/user/sendemail/code", + f"{self.base_url}/v1/user-service/user/" + "sendsmscode" if self.region == "china" else "sendemail/code", json=send_payload, timeout=30 ) @@ -378,6 +379,7 @@ def save_token(self, token: str) -> None: # Don't fail if we can't save, just warn print(f"Warning: Could not save token to {self.token_file}: {e}") + #TODO(alstr): Refactor region handling for saved tokens def load_token(self) -> Optional[str]: """ Load saved token from file. diff --git a/bambulab/client.py b/bambulab/client.py index b4e346d..938881f 100644 --- a/bambulab/client.py +++ b/bambulab/client.py @@ -23,10 +23,12 @@ class BambuClient: Handles authentication, request formatting, and response parsing. """ - BASE_URL = "https://api.bambulab.com" + #TODO(alstr): It is better to refactor all servers and session token/account in separate configuration management module + BASE_URL_GLOBAL = "https://api.bambulab.com" + BASE_URL_CHINA = "https://api.bambulab.cn" DEFAULT_TIMEOUT = 30 - def __init__(self, token: str, timeout: int = None): + def __init__(self, token: str, timeout: int = None, region: Optional[str] = "global",): """ Initialize the Bambu API client. @@ -35,6 +37,8 @@ def __init__(self, token: str, timeout: int = None): timeout: Request timeout in seconds (default: 30) """ self.token = token + self.region = region + self.base_url = self.BASE_URL_CHINA if region == "china" else self.BASE_URL_GLOBAL self.timeout = timeout or self.DEFAULT_TIMEOUT self.session = requests.Session() @@ -69,7 +73,7 @@ def _request( Raises: BambuAPIError: If request fails """ - url = f"{self.BASE_URL}/{endpoint.lstrip('/')}" + url = f"{self.base_url}/{endpoint.lstrip('/')}" headers = self._get_headers() try: diff --git a/bambulab/mqtt.py b/bambulab/mqtt.py index d5b85c7..34bc462 100644 --- a/bambulab/mqtt.py +++ b/bambulab/mqtt.py @@ -32,7 +32,8 @@ class MQTTClient: Connects to Bambu Lab cloud MQTT broker and subscribes to device updates. """ - BROKER = "us.mqtt.bambulab.com" + BROKER_GLOBAL = "us.mqtt.bambulab.com" + BROKER_CHINA = "cn.mqtt.bambulab.com" PORT = 8883 def __init__( @@ -40,7 +41,8 @@ def __init__( username: str, access_token: str, device_id: str, - on_message: Optional[Callable] = None + on_message: Optional[Callable] = None, + region: Optional[str] = "global" ): """ Initialize MQTT client. @@ -56,6 +58,8 @@ def __init__( self.username = username self.access_token = access_token + self.region = region + self.broker = self.BROKER_CHINA if region == "china" else self.BROKER_GLOBAL self.device_id = device_id self.on_message_callback = on_message @@ -68,7 +72,7 @@ def _on_connect(self, client, userdata, flags, rc, properties=None): """Callback when connected to broker""" if rc == 0: self.connected = True - logger.info(f"Connected to MQTT broker: {self.BROKER}") + logger.info(f"Connected to MQTT broker: {self.broker}") # Subscribe to device report topic topic = f"device/{self.device_id}/report" @@ -126,8 +130,8 @@ def connect(self, blocking: bool = False): self.client.tls_set(cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLS) # Connect - logger.info(f"Connecting to {self.BROKER}:{self.PORT}...") - self.client.connect(self.BROKER, self.PORT, keepalive=60) + logger.info(f"Connecting to {self.broker}:{self.PORT}...") + self.client.connect(self.broker, self.PORT, keepalive=60) if blocking: self.client.loop_forever() diff --git a/cli_tools/camera_viewer.py b/cli_tools/camera_viewer.py index d6b830e..5f438d4 100644 --- a/cli_tools/camera_viewer.py +++ b/cli_tools/camera_viewer.py @@ -247,10 +247,10 @@ def view_rtsp_stream(stream): print() -def get_printer_info(token): +def get_printer_info(token, region='global'): """Get printer information from cloud API.""" try: - client = BambuClient(token) + client = BambuClient(token, region=region) devices = client.get_devices() if not devices: diff --git a/cli_tools/login.py b/cli_tools/login.py index 4400108..4d1463b 100755 --- a/cli_tools/login.py +++ b/cli_tools/login.py @@ -111,7 +111,7 @@ def main(): password = args.password if not username: - username = input("Email: ") + username = input("Phone: " if args.region == "china" else "Email: ") if not password: password = getpass.getpass("Password: ") diff --git a/cli_tools/monitor.py b/cli_tools/monitor.py index c6a1a13..ea3733d 100644 --- a/cli_tools/monitor.py +++ b/cli_tools/monitor.py @@ -10,6 +10,7 @@ import os import time from datetime import datetime +from typing import Optional # Add parent directory to path for bambulab import sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) @@ -21,7 +22,7 @@ class PrinterMonitor: """Monitor printer status with formatted output""" - def __init__(self, username: str, access_token: str, device_id: str): + def __init__(self, username: str, access_token: str, device_id: str, region: Optional[str] = "global"): self.device_id = device_id self.message_count = 0 self.last_update = None @@ -31,7 +32,8 @@ def __init__(self, username: str, access_token: str, device_id: str): username=username, access_token=access_token, device_id=device_id, - on_message=self.on_message + on_message=self.on_message, + region=region ) def on_message(self, device_id: str, data: dict): @@ -112,7 +114,7 @@ def start(self): print("Bambu Lab Printer Monitor") print("=" * 80) print(f"Device ID: {self.device_id}") - print(f"Broker: {self.client.BROKER}:{self.client.PORT}") + print(f"Broker: {self.client.broker}:{self.client.PORT}") print() print("Connecting to MQTT...") print("=" * 80) diff --git a/cli_tools/query.py b/cli_tools/query.py index 57cec30..b78a00a 100644 --- a/cli_tools/query.py +++ b/cli_tools/query.py @@ -49,6 +49,7 @@ def main(): print(" --firmware Show firmware info") print(" --json Output in JSON format") print(" --device Filter by device ID") + print(" --region region (global or china), default is global") print() print("Examples:") print(" python query.py AADBD2wZe_token...") @@ -72,12 +73,18 @@ def main(): if idx + 1 < len(args): device_filter = args[idx + 1] + region = 'global' + if '--region' in args: + idx = args.index('--region') + if idx + 1 < len(args): + region = args[idx + 1] + # Default to showing devices show_devices = not any([show_status, show_profile, show_projects, show_firmware]) # Create API client try: - client = BambuClient(access_token) + client = BambuClient(access_token, region=region) except Exception as e: print(f"Error: Failed to create API client: {e}") sys.exit(1) diff --git a/servers/proxy.py b/servers/proxy.py index abe2b8f..86e67be 100644 --- a/servers/proxy.py +++ b/servers/proxy.py @@ -25,6 +25,7 @@ from flask import Flask, request, jsonify, Response from flask_limiter import Limiter from flask_limiter.util import get_remote_address +from typing import Optional # Add parent directory to path for bambulab import sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) @@ -226,7 +227,7 @@ def cleanup_expired_mqtt_sessions(): print(f"[MQTT] Cleanup error: {e}") -def start_mqtt_session(device_id: str, real_token: str): +def start_mqtt_session(device_id: str, real_token: str, region: Optional[str] = "global"): """ Start MQTT monitoring session for a specific device. @@ -257,7 +258,7 @@ def start_mqtt_session(device_id: str, real_token: str): try: # Get user profile for username - client = BambuClient(real_token) + client = BambuClient(real_token, region=region) profile = client.get(f"v1/user-service/my/profile") username = profile.get('uid') or profile.get('user_id') @@ -399,7 +400,7 @@ def proxy_v1(endpoint): limiter.limit(RATE_LIMITS["default"])(lambda: None)() # Create client with real token - client = BambuClient(real_token) + client = BambuClient(real_token) #TODO(fix): handing region with TokenManager here # Get request body for write operations data = None