Skip to content

Commit

Permalink
more stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
jeeftor committed Dec 2, 2023
1 parent c1ea12a commit 2dbdadc
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 42 deletions.
2 changes: 1 addition & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ In order to actually control the unit you will need to access the cloud in order

```python
cloud_api = IntelliFireAPICloud(use_http=True, verify_ssl=False)
await cloud_api.login(username=username, password=password)
await cloud_api.login_with_credentials(username=username, password=password)

# Once logged in you can pull out the api key for the default (first detected) fireplace
api_key = cloud_api.get_fireplace_api_key(cloud_api.default_fireplace)
Expand Down
42 changes: 21 additions & 21 deletions example_cloud_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@
import logging
import os

from httpx import Cookies
from rich import print
from rich.logging import RichHandler

from intellifire4py import UnifiedFireplace
from intellifire4py.cloud_interface import IntelliFireCloudInterface
from intellifire4py.const import IntelliFireApiMode

FORMAT = "%(message)s"
logging.basicConfig(
Expand Down Expand Up @@ -54,25 +53,26 @@ async def main() -> None:

cloud_api_interface = IntelliFireCloudInterface(use_http=True, verify_ssl=False)

user_json: str = os.getenv("USER_JSON") # type: ignore

cloud_api_interface.load_user_data(json_str=user_json)

# await cloud_api_interface.login(username=username, password=password)

user_data = cloud_api_interface.user_data
print(user_data.model_dump_json(indent=2))

fireplaces: UnifiedFireplace = (
await UnifiedFireplace.build_fireplaces_from_user_data(user_data)
)
fireplace = fireplaces[0]

await fireplace.set_read_mode(IntelliFireApiMode.LOCAL)
await fireplace.set_control_mode(IntelliFireApiMode.CLOUD)

print(fireplace.user_data_json)
exit(0)
await cloud_api_interface.login_with_cookie(cookie=Cookies())
# user_json: str = os.getenv("USER_JSON") # type: ignore
#
# cloud_api_interface.load_user_data(json_str=user_json)
#
# # await cloud_api_interface.login(username=username, password=password)
#
# user_data = cloud_api_interface.user_data
# print(user_data.model_dump_json(indent=2))
#
# fireplaces: UnifiedFireplace = (
# await UnifiedFireplace.build_fireplaces_from_user_data(user_data)
# )
# fireplace = fireplaces[0]
#
# await fireplace.set_read_mode(IntelliFireApiMode.LOCAL)
# await fireplace.set_control_mode(IntelliFireApiMode.CLOUD)
#
# print(fireplace.user_data_json)
# exit(0)

# fireplace[0].debug()
# await asyncio.sleep(10)
Expand Down
98 changes: 81 additions & 17 deletions src/intellifire4py/cloud_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,57 @@ def __init__(self, use_http: bool = False, verify_ssl: bool = True):
else:
self.prefix = "https"

async def login(self, *, username: str, password: str) -> None:
async def login_with_cookie_vars(
self, *, user_id: str, auth_cookie: str, web_client_id: str
) -> None:
"""Logs in using individual cookie components instead of a pre-formed cookie object.
This method constructs a cookie using the provided user_id, auth_cookie, and web_client_id,
then proceeds with the login process using this cookie. It's an alternative way to authenticate
when you have the cookie components instead of a full cookie string.
Args:
user_id (str): The user ID part of the cookie.
auth_cookie (str): The authentication token part of the cookie.
web_client_id (str): The web client ID part of the cookie.
Returns:
None: This method does not return anything. It sets the authenticated state internally.
"""
cookie = Cookies()
cookie.set("user", user_id)
cookie.set("auth_cookie", auth_cookie)
cookie.set("web_client_id", web_client_id)
return await self.login_with_cookie(cookie=cookie)

async def login_with_cookie(self, *, cookie: Cookies) -> None:
"""Uses a cookie 🍪️ to simulate the login flow, bypassing the need for username and password.
Sets the user as logged in if the cookie is valid and can successfully fetch user data.
"""
self._user_data.username = "UNSET"
self._user_data.password = "UNSET" # noqa: S105

self._cookie = cookie
self._user_data.parse_cookie(self._cookie)

# Must be set for future methods to pass -> if cookie is invalid will be set to false
self._is_logged_in = True

async with httpx.AsyncClient() as client:
try:
self._log.info("Using cookie data to poll IFTAPI")
await self._parse_user_data(client=client)
except httpx.HTTPError as http_err:
self._log.error(f"HTTP error occurred: {http_err}")
self._is_logged_in = False
# raise
except Exception as err:
self._log.error(f"An error occurred: {err}")
self._is_logged_in = False
raise

async def login_with_credentials(self, *, username: str, password: str) -> None:
"""Authenticates with the IntelliFire Cloud API using the provided username and password.
This method performs a login operation to the cloud API, storing the session cookies
Expand Down Expand Up @@ -174,44 +224,58 @@ async def _parse_user_data(self, client: httpx.AsyncClient) -> None:
async def _get_locations(self, client: httpx.AsyncClient) -> IntelliFireLocations:
"""Retrieves a list of locations accessible to the user from the cloud API.
This method makes an API call to gather details about locations that the user has access to.
The retrieved locations are used to discover fireplaces and their respective data.
Args:
client (httpx.AsyncClient): The HTTP client used for making the request.
Returns:
IntelliFireLocations: An object representing the locations accessible to the user.
Raises:
httpx.HTTPError: If there's an HTTP error during the request.
"""
await self._login_check()
response = await client.get(url=f"{self.prefix}://iftapi.net/a/enumlocations")
json_data = response.json()
locations = IntelliFireLocations(**json_data)
return locations

try:
response = await client.get(
url=f"{self.prefix}://iftapi.net/a/enumlocations"
)
response.raise_for_status() # Raises an HTTPError for 4xx/5xx responses
json_data = response.json()
locations = IntelliFireLocations(**json_data)
return locations
except httpx.HTTPError as e:
self._log.error(f"HTTP error occurred while fetching locations: {e}")
raise

async def _get_fireplaces(
self, client: httpx.AsyncClient, *, location_id: str
) -> IntelliFireFireplaces:
"""Retrieves a list of fireplaces associated with a given location.
This method queries the cloud API to obtain detailed information about fireplaces present
at a specific location identified by the location_id.
Args:
client (httpx.AsyncClient): The HTTP client used for making the request.
location_id (str): Identifier for the location whose fireplaces are to be retrieved.
Returns:
IntelliFireFireplaces: An object containing details of fireplaces at the specified location.
Raises:
httpx.HTTPError: If there's an HTTP error during the request.
"""
await self._login_check()
response = await client.get(
url=f"{self.prefix}://iftapi.net/a/enumfireplaces?location_id={location_id}"
)
json_data = response.json()

fireplaces = IntelliFireFireplaces(**json_data)
return fireplaces
try:
response = await client.get(
url=f"{self.prefix}://iftapi.net/a/enumfireplaces?location_id={location_id}"
)
response.raise_for_status() # Raises an HTTPError for 4xx/5xx responses
json_data = response.json()

fireplaces = IntelliFireFireplaces(**json_data)
return fireplaces
except httpx.HTTPError as e:
self._log.error(f"HTTP error occurred while fetching fireplaces: {e}")
raise

@property
def cloud_fireplaces(self) -> list[IntelliFireAPICloud]:
Expand Down
4 changes: 4 additions & 0 deletions src/intellifire4py/unified_fireplace.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ def __init__(
serial=self.serial, cookies=self._fireplace_data.cookies
)

async def perform_cloud_poll(self):
"""Perform a Cloud Poll - this should be used to validate the stored credentials."""
await self._cloud_api.poll()

@property
def dump_user_data_json(self) -> str:
"""Dump the internal _fireplace_data object to a JSON String."""
Expand Down
6 changes: 3 additions & 3 deletions tests/test_cloud_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ async def test_cloud_login(

cloud_interface = IntelliFireCloudInterface()
# cloud_api = IntelliFireAPICloud(serial="XXXXXE834CE109D849CBB15CDDBAFF381")
await cloud_interface.login(username=username, password=password)
await cloud_interface.login_with_credentials(username=username, password=password)
user_data = cloud_interface.user_data
fireplaces = await UnifiedFireplace.build_fireplaces_from_user_data(
user_data,
Expand Down Expand Up @@ -105,7 +105,7 @@ async def test_incorrect_login_credentials(httpx_mock: HTTPXMock) -> None:

cloud_api = IntelliFireCloudInterface()
with pytest.raises(LoginError):
await cloud_api.login(username=username, password=password)
await cloud_api.login_with_credentials(username=username, password=password)


@pytest.mark.asyncio
Expand Down Expand Up @@ -157,7 +157,7 @@ async def test_sending(
)

cloud_api = IntelliFireCloudInterface()
await cloud_api.login(username=username, password=password)
await cloud_api.login_with_credentials(username=username, password=password)
# Fake logged in state

# cloud_api = IntelliFireAPICloud()
Expand Down

0 comments on commit 2dbdadc

Please sign in to comment.