This commit is contained in:
2025-12-14 06:08:30 -08:00
parent 3a4563a34d
commit 399132006c

View File

@@ -22,10 +22,16 @@ except ImportError:
FITBIT_LIBRARY = False
try:
import garminconnect
GARMIN_LIBRARY = "garminconnect"
import garth
GARTH_LIBRARY = True
except ImportError:
GARMIN_LIBRARY = None
GARTH_LIBRARY = False
try:
import garminconnect
GARMINCONNECT_LIBRARY = True
except ImportError:
GARMINCONNECT_LIBRARY = False
import schedule
@@ -555,34 +561,12 @@ class GarminClient:
self.garminconnect = garminconnect
logger.info("Using garminconnect library")
# Monkey patch the login method to handle garth compatibility issue
original_login = self.garminconnect.Garmin.login
def patched_login(self):
"""Patched login method that handles garth returning None"""
try:
result = original_login(self)
return result
except TypeError as e:
if "cannot unpack non-iterable NoneType object" in str(e):
# Check if we have valid tokens despite the None return
if (self.garth.oauth1_token and self.garth.oauth2_token):
logger.info("Login successful (handled garth None return)")
return True
else:
raise
else:
raise
# Apply the patch
self.garminconnect.Garmin.login = patched_login
except ImportError:
logger.error("garminconnect library not installed. Install with: pip install garminconnect")
raise ImportError("garminconnect library is required but not installed")
async def authenticate(self) -> bool:
"""Authenticate with Garmin Connect"""
"""Authenticate with Garmin Connect using garth"""
if self.read_only_mode:
logger.info("Running in read-only mode - skipping Garmin authentication")
return True
@@ -597,54 +581,24 @@ class GarminClient:
if not self._setup_credentials():
return False
logger.info("Initializing Garmin client...")
# Set session file path for garminconnect library
os.environ['GARMINTOKENS'] = str(self.session_file)
# Configure garth for domain if using Garmin China
if self.is_china:
garth.configure(domain="garmin.cn")
# Initialize garminconnect.Garmin with credentials.
# It will use garth library for authentication and session management.
self.garmin_client = self.garminconnect.Garmin(
self.username,
self.password,
is_cn=self.is_china
self.username, self.password
)
self.garmin_client.login()
# Use garth to load the session if it exists
if os.path.exists(self.session_file):
try:
logger.info(f"Attempting to load session from {self.session_file}")
self.garmin_client.garth.load(self.session_file)
logger.info("Loaded existing session from file.")
# Log garth state after loading
logger.info(f"Garth tokens after load: oauth1={bool(self.garmin_client.garth.oauth1_token)}, oauth2={bool(self.garmin_client.garth.oauth2_token)}")
except Exception as e:
logger.warning(f"Could not load session file: {e}. Performing fresh login.")
# Login (will use loaded session or perform a fresh auth)
logger.info("Calling garmin_client.login()...")
try:
# Handle garth API compatibility issue - newer versions return None
# when using existing sessions, but garminconnect expects a tuple
login_result = self.garmin_client.login()
# Check if login returned None (new garth behavior with existing sessions)
if login_result is None:
# Verify that we actually have valid tokens after login
if (self.garmin_client.garth.oauth1_token and
self.garmin_client.garth.oauth2_token):
logger.info("Login successful (garth returned None but tokens are valid)")
else:
logger.error("Login failed - garth returned None and no valid tokens")
raise Exception("Garmin login failed: No valid tokens after authentication")
else:
logger.info("Login successful")
except Exception as e:
logger.error(f"Login failed with exception: {e}")
# Log garth state before re-raising
logger.info(f"Garth tokens before failure: oauth1={bool(self.garmin_client.garth.oauth1_token)}, oauth2={bool(self.garmin_client.garth.oauth2_token)}")
raise
# Save the session using garth's dump method
self.garmin_client.garth.dump(self.session_file)
# Verify by getting the full name
profile = self.garmin_client.get_full_name()
logger.info(f"Successfully authenticated and saved session for user: {profile}")
logger.info(f"Successfully authenticated with Garmin for user: {profile}")
return True
except Exception as e:
@@ -653,6 +607,10 @@ class GarminClient:
logger.error(f"Full traceback: {traceback.format_exc()}")
return False
def _mfa_handler(self, _) -> str:
"""Handle MFA code input from the user."""
return input("Enter Garmin MFA code: ")
def _setup_credentials(self) -> bool:
"""Setup Garmin credentials interactively"""
print("\n🔑 Garmin Connect Credentials Setup")