sync
This commit is contained in:
@@ -22,10 +22,16 @@ except ImportError:
|
|||||||
FITBIT_LIBRARY = False
|
FITBIT_LIBRARY = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import garminconnect
|
import garth
|
||||||
GARMIN_LIBRARY = "garminconnect"
|
GARTH_LIBRARY = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
GARMIN_LIBRARY = None
|
GARTH_LIBRARY = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
import garminconnect
|
||||||
|
GARMINCONNECT_LIBRARY = True
|
||||||
|
except ImportError:
|
||||||
|
GARMINCONNECT_LIBRARY = False
|
||||||
|
|
||||||
import schedule
|
import schedule
|
||||||
|
|
||||||
@@ -555,34 +561,12 @@ class GarminClient:
|
|||||||
self.garminconnect = garminconnect
|
self.garminconnect = garminconnect
|
||||||
logger.info("Using garminconnect library")
|
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:
|
except ImportError:
|
||||||
logger.error("garminconnect library not installed. Install with: pip install garminconnect")
|
logger.error("garminconnect library not installed. Install with: pip install garminconnect")
|
||||||
raise ImportError("garminconnect library is required but not installed")
|
raise ImportError("garminconnect library is required but not installed")
|
||||||
|
|
||||||
async def authenticate(self) -> bool:
|
async def authenticate(self) -> bool:
|
||||||
"""Authenticate with Garmin Connect"""
|
"""Authenticate with Garmin Connect using garth"""
|
||||||
if self.read_only_mode:
|
if self.read_only_mode:
|
||||||
logger.info("Running in read-only mode - skipping Garmin authentication")
|
logger.info("Running in read-only mode - skipping Garmin authentication")
|
||||||
return True
|
return True
|
||||||
@@ -597,54 +581,24 @@ class GarminClient:
|
|||||||
if not self._setup_credentials():
|
if not self._setup_credentials():
|
||||||
return False
|
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.garmin_client = self.garminconnect.Garmin(
|
||||||
self.username,
|
self.username, self.password
|
||||||
self.password,
|
|
||||||
is_cn=self.is_china
|
|
||||||
)
|
)
|
||||||
|
self.garmin_client.login()
|
||||||
|
|
||||||
# Use garth to load the session if it exists
|
# Verify by getting the full name
|
||||||
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)
|
|
||||||
|
|
||||||
profile = self.garmin_client.get_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
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -653,6 +607,10 @@ class GarminClient:
|
|||||||
logger.error(f"Full traceback: {traceback.format_exc()}")
|
logger.error(f"Full traceback: {traceback.format_exc()}")
|
||||||
return False
|
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:
|
def _setup_credentials(self) -> bool:
|
||||||
"""Setup Garmin credentials interactively"""
|
"""Setup Garmin credentials interactively"""
|
||||||
print("\n🔑 Garmin Connect Credentials Setup")
|
print("\n🔑 Garmin Connect Credentials Setup")
|
||||||
|
|||||||
Reference in New Issue
Block a user