Skip to content

Commit

Permalink
fix api changes
Browse files Browse the repository at this point in the history
  • Loading branch information
Guy293 committed Jun 8, 2024
1 parent 28b006a commit ff1ae51
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 77 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Store the ``refresh_token`` somewhere safe, you will use it to authenticate with

```py
edge = Tami4EdgeAPI(refresh_token)
print(f"Bar Name: {edge.device.name}, Firmware Version: {edge.device.device_firmware}")
print(f"Bar Name: {edge.device_metadata.name}, Firmware Version: {edge.device_metadata.device_firmware}")
```

### Boil Water
Expand All @@ -44,8 +44,8 @@ edge.boil_water()

### Get User Drinks
```py
drinks = edge.get_drinks()
for drink in drinks:
device = edge.get_device()
for drink in device.drinks:
print(drink.name)
```

Expand All @@ -56,9 +56,9 @@ edge.prepare_drink(drink)

### Get Filter / UV Information
```py
water_quality = edge.get_water_quality()
water_quality = device.water_quality
water_quality.uv.last_replacement
water_quality.uv.upcoming_replacement
water_quality.uv.status
water_quality.uv.installed
water_quality.filter.milli_litters_passed
```
124 changes: 69 additions & 55 deletions Tami4EdgeAPI/Tami4EdgeAPI.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import datetime
import json
import logging
from json import JSONDecodeError
from typing import Callable, Optional

import jwt
import requests
from pypasser import reCaptchaV3
from requests.auth import AuthBase
from requests.exceptions import JSONDecodeError, RequestException
from requests.exceptions import RequestException
from requests.models import PreparedRequest

from Tami4EdgeAPI import exceptions
from Tami4EdgeAPI.device import Device
from Tami4EdgeAPI.device_metadata import DeviceMetadata
from Tami4EdgeAPI.drink import Drink
from Tami4EdgeAPI.token import Token
from Tami4EdgeAPI.water_quality import UV, Filter, WaterQuality
Expand Down Expand Up @@ -44,6 +49,7 @@ class Tami4EdgeAPI:
"""Tami4Edge API Interface."""

ENDPOINT = "https://swelcustomers.strauss-water.com"
AUTH_ENDPOINT = "https://authentication-prod.strauss-group.com"
ANCHOR_URL = "https://www.google.com/recaptcha/enterprise/anchor?ar=1&k=6Lf-jYgUAAAAAEQiRRXezC9dfIQoxofIhqBnGisq&co=aHR0cHM6Ly93d3cudGFtaTQuY28uaWw6NDQz&hl=en&v=gWN_U6xTIPevg0vuq7g1hct0&size=invisible&cb=ji0lh9higcza"

def __init__(self, refresh_token: str) -> None:
Expand All @@ -58,7 +64,7 @@ def __init__(self, refresh_token: str) -> None:
# but /v2/ only supports one device.
# Also, the app doesn't seem to support multiple devices at all.
# So for now, we'll use the first device we get from the API.
self.device = self._get_devices()[0]
self.device_metadata = self._get_devices_metadata()[0]

def get_access_token(self) -> str:
"""Get the access token, refreshing it if necessary."""
Expand All @@ -72,8 +78,9 @@ def get_access_token(self) -> str:
def _request_new_token(self):
response = request_wrapper(
"POST",
f"{Tami4EdgeAPI.ENDPOINT}/public/token/refresh",
json={"token": self._token.refresh_token},
f"{Tami4EdgeAPI.AUTH_ENDPOINT}/api/v1/auth/token/refresh",
headers={"X-Api-Key": "96787682-rrzh-0995-v9sz-cfdad9ac7072"},
json={"refreshToken": self._token.refresh_token},
exception=exceptions.TokenRefreshFailedException("Token Refresh Failed"),
)

Expand All @@ -85,21 +92,30 @@ def _request_new_token(self):
except JSONDecodeError as ex:
raise exceptions.TokenRefreshFailedException("Token Refresh Failed") from ex

if "access_token" not in response_json:
if "accessToken" not in response_json or "refreshToken" not in response_json:
logging.error("Token Refresh Failed, response: %s", response_json)
raise exceptions.TokenRefreshFailedException(
"Token Refresh Failed: No access_token key"
"Token Refresh Failed: No accessToken key"
)

access_token = jwt.decode(
response_json["accessToken"], options={"verify_signature": False}
)

logging.debug("Token Refresh Successful")

self._token = Token(
refresh_token=response_json["refresh_token"],
access_token=response_json["access_token"],
expires_in=response_json["expires_in"],
# Replace refresh token only if we get a new one
refresh_token=(
response_json["refreshToken"]
if response_json["refreshToken"] is not None
else self._token.refresh_token
),
access_token=response_json["accessToken"],
expires_at=datetime.datetime.fromtimestamp(access_token["exp"]),
)

def _get_devices(self) -> list[Device]:
def _get_devices_metadata(self) -> list[DeviceMetadata]:
response = request_wrapper(
"GET",
f"{self.ENDPOINT}/api/v1/device",
Expand All @@ -108,7 +124,7 @@ def _get_devices(self) -> list[Device]:
)

return [
Device(
DeviceMetadata(
id=d["id"],
name=d["name"],
connected=d["connected"],
Expand All @@ -119,17 +135,19 @@ def _get_devices(self) -> list[Device]:
for d in response.json()
]

def get_drinks(self) -> list[Drink]:
"""Fetch the drinks."""
def get_device(self) -> Device:
"""Fetch device data."""

response = request_wrapper(
"GET",
f"{self.ENDPOINT}/api/v1/customer/drink",
f"{self.ENDPOINT}/api/v3/customer/mainPage/{self.device_metadata.psn}",
session=self._session,
exception=exceptions.APIRequestFailedException("Drink Request Failed"),
)
exception=exceptions.APIRequestFailedException(
"Water Quality Request Failed"
),
).json()

return [
drinks = [
Drink(
id=d["id"],
name=d["name"],
Expand All @@ -138,34 +156,28 @@ def get_drinks(self) -> list[Drink]:
include_in_customer_statistics=d["includeInCustomerStatistics"],
default_drink=d["defaultDrink"],
)
for d in response.json()["drinks"]
for d in response["drinks"]
]

def get_water_quality(self) -> WaterQuality:
"""Fetch the water quality."""

response = request_wrapper(
"GET",
f"{self.ENDPOINT}/api/v2/customer/waterQuality",
session=self._session,
exception=exceptions.APIRequestFailedException(
"Water Quality Request Failed"
),
).json()

_filter = response["filterInfo"]
uv = response["uvInfo"]
return WaterQuality(
uv=UV(
last_replacement=uv["lastReplacement"],
upcoming_replacement=uv["upcomingReplacement"],
status=uv["status"],
),
filter=Filter(
last_replacement=_filter["lastReplacement"],
upcoming_replacement=_filter["upcomingReplacement"],
status=_filter["status"],
milli_litters_passed=_filter["milliLittersPassed"],
dynamic_data = response["dynamicData"]
_filter = dynamic_data["filterInfo"]
uv = dynamic_data["uvInfo"]

return Device(
device_metadata=self.device_metadata,
drinks=drinks,
water_quality=WaterQuality(
uv=UV(
# last_replacement=uv["lastReplacement"],
upcoming_replacement=uv["upcomingReplacement"],
installed=uv["installed"],
),
filter=Filter(
# last_replacement=_filter["lastReplacement"],
upcoming_replacement=_filter["upcomingReplacement"],
milli_litters_passed=_filter["milliLittersPassed"],
installed=_filter["installed"],
),
),
)

Expand All @@ -174,7 +186,7 @@ def prepare_drink(self, drink: Drink) -> None:

request_wrapper(
"POST",
f"{self.ENDPOINT}/api/v1/device/{self.device.id}/prepareDrink/{drink.id}",
f"{self.ENDPOINT}/api/v1/device/{self.device_metadata.id}/prepareDrink/{drink.id}",
session=self._session,
exception=exceptions.APIRequestFailedException(
"Drink Prepare Request Failed"
Expand All @@ -186,7 +198,7 @@ def boil_water(self) -> None:

response = request_wrapper(
"POST",
f"{self.ENDPOINT}/api/v1/device/{self.device.id}/startBoiling",
f"{self.ENDPOINT}/api/v1/device/{self.device_metadata.id}/startBoiling",
session=self._session,
exception=exceptions.APIRequestFailedException("Boil Water Request Failed"),
)
Expand All @@ -204,16 +216,17 @@ def request_otp(phone_number: str) -> None:

response = request_wrapper(
"POST",
f"{Tami4EdgeAPI.ENDPOINT}/public/phone/generateOTP",
f"{Tami4EdgeAPI.AUTH_ENDPOINT}/api/v1/auth/otp/request",
headers={"X-Api-Key": "96787682-rrzh-0995-v9sz-cfdad9ac7072"},
json={
"phoneNumber": phone_number,
"phone": phone_number,
"reCaptchaToken": Tami4EdgeAPI._get_recaptcha_token(),
},
exception=exceptions.OTPFailedException("OTP Request Failed"),
).json()
)

if not response["success"]:
logging.error("OTP Request Failed, response: %s", response)
if response.status_code != 200:
logging.error("OTP Request Failed, response: %s", response.content)
raise exceptions.OTPFailedException("OTP Request Failed")

logging.info("OTP Request Successful")
Expand All @@ -224,19 +237,20 @@ def submit_otp(phone_number: str, otp: int) -> str:

response = request_wrapper(
"POST",
f"{Tami4EdgeAPI.ENDPOINT}/public/phone/submitOTP",
f"{Tami4EdgeAPI.AUTH_ENDPOINT}/api/v1/auth/otp/submit",
headers={"X-Api-Key": "96787682-rrzh-0995-v9sz-cfdad9ac7072"},
json={
"phoneNumber": phone_number,
"code": otp,
"phone": phone_number,
"otp": otp,
"reCaptchaToken": Tami4EdgeAPI._get_recaptcha_token(),
},
exception=exceptions.OTPFailedException("OTP Submission Failed"),
).json()

if "access_token" not in response:
if "accessToken" not in response:
logging.error("OTP Submission Failed, response: %s", response)
raise exceptions.OTPFailedException("OTP Submission Failed")

logging.info("OTP Submission Successful")

return response["refresh_token"]
return response["refreshToken"]
12 changes: 6 additions & 6 deletions Tami4EdgeAPI/device.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from dataclasses import dataclass

from Tami4EdgeAPI import device_metadata, water_quality
from Tami4EdgeAPI.drink import Drink


@dataclass
class Device(object):
id: int
name: str
connected: bool
psn: str
type: str
device_firmware: str
device_metadata: device_metadata.DeviceMetadata
water_quality: water_quality.WaterQuality
drinks: list[Drink]
11 changes: 11 additions & 0 deletions Tami4EdgeAPI/device_metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from dataclasses import dataclass


@dataclass
class DeviceMetadata(object):
id: int
name: str
connected: bool
psn: str
type: str
device_firmware: str
20 changes: 13 additions & 7 deletions Tami4EdgeAPI/token.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
from datetime import datetime, timedelta
from typing import Optional


class Token(object):
access_token: str
refresh_token: str
expires_at: datetime
access_token: str
expires_at: Optional[datetime]

def __init__(
self, refresh_token: str, access_token: str = None, expires_in: int = None
self,
refresh_token: str,
access_token: Optional[str] = None,
expires_at: Optional[datetime] = None,
):
self.refresh_token = refresh_token
self.access_token = access_token if access_token else None
self.expires_at = (
datetime.now() + timedelta(seconds=expires_in) if expires_in else None
)
self.expires_at = expires_at

@property
def is_valid(self):
return self.access_token is not None and self.expires_at > datetime.now()
return (
self.access_token is not None
and self.expires_at is not None
and self.expires_at > datetime.now()
)
6 changes: 3 additions & 3 deletions Tami4EdgeAPI/water_quality.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@

@dataclass
class QualityInfo:
last_replacement: date
# last_replacement: date
upcoming_replacement: date
status: str
installed: bool

def __post_init__(self) -> None:
self.last_replacement = date.fromtimestamp(self.last_replacement / 1000)
# self.last_replacement = date.fromtimestamp(self.last_replacement / 1000)
self.upcoming_replacement = date.fromtimestamp(self.upcoming_replacement / 1000)


Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
long_description_content_type="text/markdown",
url="https://github.com/Guy293/Tami4EdgeAPI",
packages=setuptools.find_packages(),
install_requires=["requests", "pypasser"],
install_requires=["requests", "pypasser", "pyjwt"],
)

0 comments on commit ff1ae51

Please sign in to comment.