Skip to content

Commit

Permalink
Update app.py
Browse files Browse the repository at this point in the history
  • Loading branch information
antoinebou12 authored Mar 12, 2024
1 parent 749a479 commit be0e205
Showing 1 changed file with 133 additions and 107 deletions.
240 changes: 133 additions & 107 deletions api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ class RenphoWeight:
user_id (str, optional): The ID of the user for whom weight data should be fetched.
"""

def __init__(self, email, password, user_id=None, refresh=60):
def __init__(self, email, password, user_id=None, refresh=60, proxy=None):
"""Initialize a new RenphoWeight instance."""
self.public_key: str = CONF_PUBLIC_KEY
self.email: str = email
Expand Down Expand Up @@ -379,6 +379,7 @@ def __init__(self, email, password, user_id=None, refresh=60):
self._last_updated_growth_record = None
self.auth_in_progress = False
self.is_polling_active = False
self.proxy = proxy

@staticmethod
def get_timestamp() -> int:
Expand Down Expand Up @@ -411,6 +412,20 @@ async def open_session(self):
headers={"Content-Type": "application/json", "Accept": "application/json"},
)

async def check_proxy(self):
"""
Checks if the proxy is working by making a request to httpbin.org.
"""
test_url = 'https://renpho.qnclouds.com/api/v3/girths/list_girth.json?app_id=Renpho&terminal_user_session_key='
try:
connector = ProxyConnector.from_url(self.proxy) if self.proxy else None
async with aiohttp.ClientSession(connector=connector) as session:
async with session.get(test_url) as response:
return True
except Exception as e:
_LOGGER.error(f"Proxy connection failed: {e}")
return False


async def _request(self, method: str, url: str, retries: int = 3, skip_auth=False, **kwargs):
"""
Expand All @@ -426,44 +441,45 @@ async def _request(self, method: str, url: str, retries: int = 3, skip_auth=Fals
Returns:
Union[Dict, List]: The parsed JSON response from the API request.
"""
token = self.token
if not await self.check_proxy():
_LOGGER.error("Proxy check failed. Aborting authentication.")
raise APIError("Proxy check failed. Aborting authentication.")
while retries > 0:
session = aiohttp.ClientSession(
headers={"Content-Type": "application/json", "Accept": "application/json",
"User-Agent": "Renpho/2.1.0 (iPhone; iOS 14.4; Scale/2.1.0; en-US)"
}
)

if not token and not url.endswith("sign_in.json") and not skip_auth:
auth_success = await self.auth()
token = self.token
if not auth_success:
raise AuthenticationError("Authentication failed. Unable to proceed with the request.")

kwargs = self.prepare_data(kwargs)

try:
async with session.request(method, url, **kwargs) as response:
response.raise_for_status()
parsed_response = await response.json()

if parsed_response.get("status_code") == "40302":
token = None
skip_auth = False
retries -= 1
await session.close()
continue # Retry the request
if parsed_response.get("status_code") == "50000":
raise APIError(f"Internal server error: {parsed_response.get('status_message')}")
if parsed_response.get("status_code") == "20000" and parsed_response.get("status_message") == "ok":
return parsed_response
else:
raise APIError(f"API request failed {method} {url}: {parsed_response.get('status_message')}")
except (aiohttp.ClientResponseError, aiohttp.ClientConnectionError) as e:
_LOGGER.error(f"Client error: {e}")
raise APIError(f"API request failed {method} {url}") from e
finally:
await session.close()
connector = ProxyConnector.from_url(self.proxy) if self.proxy else None
async with aiohttp.ClientSession(connector=connector, headers={
"Content-Type": "application/json",
"Accept": "application/json",
"User-Agent": "Renpho/2.1.0 (iPhone; iOS 14.4; Scale/2.1.0; en-US)"
}, timeout=ClientTimeout(total=60)) as session:

if not self.token and not url.endswith("sign_in.json") or not skip_auth:
auth_success = await self.auth()
if not auth_success:
raise AuthenticationError("Authentication failed. Unable to proceed with the request.")

kwargs = self.prepare_data(kwargs)

try:
async with session.request(method, url, **kwargs) as response:
response.raise_for_status()
parsed_response = await response.json()

if parsed_response.get("status_code") == "40302":
skip_auth = False
auth_success = await self.auth()
if not auth_success:
raise AuthenticationError("Authentication failed. Unable to proceed with the request.")
retries -= 1
continue # Retry the request
if parsed_response.get("status_code") == "50000":
raise APIError(f"Internal server error: {parsed_response.get('status_message')}")
if parsed_response.get("status_code") == "20000" and parsed_response.get("status_message") == "ok":
return parsed_response
else:
raise APIError(f"API request failed {method} {url}: {parsed_response.get('status_message')}")
except (aiohttp.ClientResponseError, aiohttp.ClientConnectionError) as e:
_LOGGER.error(f"Client error: {e}")
raise APIError(f"API request failed {method} {url}") from e

@staticmethod
def encrypt_password(public_key_str, password):
Expand Down Expand Up @@ -510,53 +526,64 @@ async def auth(self):
data = self.prepare_data({"secure_flag": "1", "email": self.email,
"password": encrypted_password})

try:
self.token = None
session = aiohttp.ClientSession(
headers={"Content-Type": "application/json", "Accept": "application/json", "User-Agent": "Renpho/2.1.0 (iPhone; iOS 14.4; Scale/2.1.0; en-US)"},
)

async with session.request("POST", API_AUTH_URL, json=data) as response:
response.raise_for_status()
parsed = await response.json()

if parsed is None:
_LOGGER.error("Authentication failed. No response received.")
raise AuthenticationError("Authentication failed. No response received.")

if parsed.get("status_code") == "50000" and parsed.get("status_message") == "Email was not registered":
_LOGGER.warning("Email was not registered.")
raise AuthenticationError("Email was not registered.")

if parsed.get("status_code") == "500" and parsed.get("status_message") == "Internal Server Error":
_LOGGER.warning("Bad Password or Internal Server Error.")
raise AuthenticationError("Bad Password or Internal Server Error.")

if "terminal_user_session_key" not in parsed:
_LOGGER.error(
"'terminal_user_session_key' not found in parsed object.")
raise AuthenticationError(f"Authentication failed: {parsed}")

if parsed.get("status_code") == "20000" and parsed.get("status_message") == "ok":
if 'terminal_user_session_key' in parsed:
self.token = parsed["terminal_user_session_key"]
else:
self.token = None
raise AuthenticationError("Session key not found in response.")
if 'device_binds_ary' in parsed:
parsed['device_binds_ary'] = [DeviceBind(**device) for device in parsed['device_binds_ary']]
else:
parsed['device_binds_ary'] = []
self.login_data = UserResponse(**parsed)
if self.user_id is None:
self.user_id = self.login_data.get("id", None)
return True
except Exception as e:
_LOGGER.error(f"Authentication failed: {e}")
raise AuthenticationError("Authentication failed due to an error. {e}") from e
finally:
self.auth_in_progress = False
await session.close()
for attempt in range(3):
try:
self.token = None
if not await self.check_proxy():
_LOGGER.error("Proxy check failed. Aborting authentication.")
raise APIError("Proxy check failed. Aborting authentication.")

connector = ProxyConnector.from_url(self.proxy) if self.proxy else None
async with aiohttp.ClientSession(connector=connector, headers={
"Content-Type": "application/json",
"Accept": "application/json",
"User-Agent": "Renpho/2.1.0 (iPhone; iOS 14.4; Scale/2.1.0; en-US)"
}, timeout=ClientTimeout(total=60)) as session:

async with session.request("POST", API_AUTH_URL, json=data) as response:
response.raise_for_status()
parsed = await response.json()

if parsed is None:
_LOGGER.error("Authentication failed. No response received.")
raise AuthenticationError("Authentication failed. No response received.")

if parsed.get("status_code") == "50000" and parsed.get("status_message") == "Email was not registered":
_LOGGER.warning("Email was not registered.")
raise AuthenticationError("Email was not registered.")

if parsed.get("status_code") == "500" and parsed.get("status_message") == "Internal Server Error":
_LOGGER.warning("Bad Password or Internal Server Error.")
raise AuthenticationError("Bad Password or Internal Server Error.")

if "terminal_user_session_key" not in parsed:
_LOGGER.error(
"'terminal_user_session_key' not found in parsed object.")
raise AuthenticationError(f"Authentication failed: {parsed}")

if parsed.get("status_code") == "20000" and parsed.get("status_message") == "ok":
if 'terminal_user_session_key' in parsed:
self.token = parsed["terminal_user_session_key"]
else:
self.token = None
raise AuthenticationError("Session key not found in response.")
if 'device_binds_ary' in parsed:
parsed['device_binds_ary'] = [DeviceBind(**device) for device in parsed['device_binds_ary']]
else:
parsed['device_binds_ary'] = []
self.login_data = UserResponse(**parsed)
self.token = parsed["terminal_user_session_key"]
if self.user_id is None:
self.user_id = self.login_data.get("id", None)
return True
except (aiohttp.ClientResponseError, aiohttp.ClientConnectionError) as e:
_LOGGER.error(f"Authentication failed: {e}")
if attempt < 3 - 1:
await asyncio.sleep(5) # Wait before retrying
else:
raise AuthenticationError(f"Authentication failed after retries. {e}") from e
finally:
self.auth_in_progress = False

async def get_scale_users(self):
"""
Expand Down Expand Up @@ -629,7 +656,6 @@ async def get_measurements(self):
_LOGGER.error(f"Failed to fetch weight measurements: {e}")
return None


async def get_measurements_history(self):
"""
Fetch the most recent weight measurements_history for the user.
Expand Down Expand Up @@ -735,6 +761,7 @@ async def list_girth(self):

if "status_code" in parsed and parsed["status_code"] == "20000":
response = GirthResponse(**parsed)
self._last_updated_girth = time.time()
self.girth_info = response.girths
return self.girth_info
else:
Expand Down Expand Up @@ -870,31 +897,30 @@ async def get_specific_metric(self, metric_type: str, metric: str, user_id: Opti

try:
if metric_type == METRIC_TYPE_WEIGHT:
if self._last_updated_weight is None or time.time() - self._last_updated_weight > self.refresh:
last_measurement = await self.get_weight()
if last_measurement and self.weight is not None:
return last_measurement[1].get(metric, None) if last_measurement[1] else None
if self._last_updated_weight is None or self.weight:
if self.weight_info is not None:
return self.weight_info.get(metric, None)
return self.weight_info.get(metric, None) if self.weight_info else None
elif metric_type == METRIC_TYPE_GIRTH:
if self._last_updated_girth is None or time.time() - self._last_updated_girth > self.refresh:
if self._last_updated_girth is None or self.girth_info is None:
await self.list_girth()
for girth_entry in self.girth_info:
if hasattr(girth_entry, f"{metric}_value"):
return getattr(girth_entry, f"{metric}_value", None)
if self.girth_info:
valid_girths = sorted([g for g in self.girth_info if getattr(g, f"{metric}_value", 0) not in (None, 0.0)], key=lambda x: x.time_stamp, reverse=True)
for girth in valid_girths:
value = getattr(girth, f"{metric}_value", None)
if value not in (None, 0.0):
return value
return None
elif metric_type == METRIC_TYPE_GIRTH_GOAL:
if self._last_updated_girth_goal is None or time.time() - self._last_updated_girth_goal > self.refresh:
if self._last_updated_girth_goal is None or self.girth_goal is None:
await self.list_girth_goal()
for goal in self.girth_goal:
if goal.girth_type == metric:
return goal.goal_value
elif metric_type == METRIC_TYPE_GROWTH_RECORD:
if self._last_updated_growth_record is None or time.time() - self._last_updated_growth_record > self.refresh:
last_measurement = (
self.growth_record.get("growths", [])[0]
if self.growth_record.get("growths")
else None
)
return last_measurement.get(metric, None) if last_measurement else None
if self.girth_goal:
valid_goals = sorted([g for g in self.girth_goal if g.girth_type == metric and g.goal_value not in (None, 0.0)], key=lambda x: x.setup_goal_at, reverse=True)
# Iterate to find the first valid goal
for goal in valid_goals:
if goal.goal_value not in (None, 0.0):
return goal.goal_value
return None
else:
_LOGGER.error(f"Invalid metric type: {metric_type}")
return None
Expand Down Expand Up @@ -1152,4 +1178,4 @@ async def message_list(request: Request, renpho: RenphoWeight = Depends(get_curr
raise HTTPException(status_code=404, detail="Message list not found")
except Exception as e:
_LOGGER.error(f"Error fetching message list: {e}")
return APIResponse(status="error", message=str(e))
return APIResponse(status="error", message=str(e))

0 comments on commit be0e205

Please sign in to comment.