-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor to work with a config_flow (#67)
* feat: add config_flow to replace manual configuration * feat: no need for local python script or docker container * feat: add coordinator for polling data * feat: add DE translation * refactor: ... hm ... everything?
- Loading branch information
Showing
12 changed files
with
481 additions
and
305 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,63 @@ | ||
"""The tgtg component.""" | ||
|
||
import logging | ||
|
||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.const import CONF_EMAIL, Platform | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator | ||
|
||
from .client import Client | ||
from .const import ( | ||
DOMAIN, | ||
CONF_ACCESS_TOKEN, | ||
CONF_REFRESH_TOKEN, | ||
CONF_USER_ID, | ||
CONF_COOKIE, | ||
TGTG_NAME, | ||
TGTG_CLIENT, | ||
TGTG_COORDINATOR, | ||
DEFAULT_SCAN_INTERVAL | ||
) | ||
|
||
LOGGER = logging.getLogger(__name__) | ||
|
||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): | ||
"""Set up TGTG as config entry.""" | ||
|
||
LOGGER.info(f"Initializing {DOMAIN}") | ||
|
||
# Load values from settings | ||
email = entry.data.get(CONF_EMAIL) | ||
access_token = entry.data.get(CONF_ACCESS_TOKEN) | ||
refresh_token = entry.data.get(CONF_REFRESH_TOKEN) | ||
user_id = entry.data.get(CONF_USER_ID) | ||
cookie = entry.data.get(CONF_COOKIE) | ||
|
||
# Log in with tokens | ||
tgtg_client = Client(hass=hass, email=email, access_token=access_token, refresh_token=refresh_token, user_id=user_id, cookie=cookie) | ||
|
||
tgtg_coordinator = DataUpdateCoordinator( | ||
hass, | ||
LOGGER, | ||
name=f"{DOMAIN} Coordinator for {user_id}", | ||
update_method=tgtg_client.update, | ||
update_interval=DEFAULT_SCAN_INTERVAL, | ||
) | ||
|
||
# Fetch initial data so we have data when entities subscribe | ||
await tgtg_coordinator.async_refresh() | ||
|
||
# Save the data | ||
tgtg_hass_data = hass.data.setdefault(DOMAIN, {}) | ||
tgtg_hass_data[entry.entry_id] = { | ||
TGTG_CLIENT: tgtg_client, | ||
TGTG_COORDINATOR: tgtg_coordinator, | ||
TGTG_NAME: user_id, | ||
} | ||
|
||
hass.async_create_task( | ||
hass.config_entries.async_forward_entry_setup(entry, Platform.SENSOR) | ||
) | ||
|
||
return True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import logging | ||
import datetime | ||
|
||
from tgtg import TgtgClient | ||
|
||
from .const import CONF_NEXT_SALES_WINDOW, CONF_ITEM, CONF_ITEM_ID | ||
|
||
LOGGER = logging.getLogger(__name__) | ||
|
||
class Client: | ||
updateCycle = None | ||
items = None | ||
|
||
def __init__(self, hass, access_token=None, refresh_token=None, user_id=None, user_agent=None, email=None, cookie=None): | ||
self.hass = hass | ||
|
||
if email != "": | ||
self.email = email | ||
|
||
if access_token != "": | ||
self.access_token = access_token | ||
|
||
if refresh_token != "": | ||
self.refresh_token = refresh_token | ||
|
||
if user_id != "": | ||
self.user_id = user_id | ||
|
||
if user_agent != "": | ||
self.user_agent = user_agent | ||
|
||
if cookie != "": | ||
self.cookie = cookie | ||
|
||
if((self.access_token and self.refresh_token and self.user_id and self.user_agent) or self.email): | ||
self.tgtg = TgtgClient( | ||
email=self.email, | ||
access_token=self.access_token, | ||
refresh_token=self.refresh_token, | ||
user_id=self.user_id, | ||
user_agent=self.user_agent, | ||
cookie=self.cookie | ||
) | ||
|
||
async def fetch_credentials(self): | ||
return await self.hass.async_add_executor_job(self.tgtg.get_credentials) | ||
|
||
async def fetch_favourites(self): | ||
return await self.hass.async_add_executor_job(self.tgtg.get_favourites) | ||
|
||
async def fetch_items(self): | ||
return await self.hass.async_add_executor_job(self.tgtg.get_items) | ||
|
||
async def fetch_item(self, item_id): | ||
return await self.hass.async_add_executor_job(self.tgtg.get_item, item_id) | ||
|
||
def get_item(self, item_id): | ||
return self.items.get(item_id) | ||
|
||
async def update_item_details(self, item_id): | ||
LOGGER.debug('Updating item details: %s', item_id) | ||
item = await self.fetch_item(item_id) | ||
# merge data | ||
self.items[item_id] = {**self.items[item_id], **item} | ||
|
||
async def update(self): | ||
# update all favourites every 15 minutes | ||
if self.updateCycle is None or self.updateCycle % 5 == 0: | ||
LOGGER.debug('Updating TGTG favourites ...') | ||
items = await self.fetch_items() | ||
self.items = {d[CONF_ITEM][CONF_ITEM_ID]: d for d in items} | ||
|
||
# update details of each item | ||
for item_id in self.items: | ||
# update item more often if item is in saleswindow | ||
if self.is_during_sales_window(self.items[item_id], 10): | ||
LOGGER.debug('Updating item details because in saleswindow...') | ||
self.update_item_details(item_id) | ||
# fetch item in detail to get more data (but not so often) = 40 * DEFAULT_SCAN_INTERVAL | ||
elif self.updateCycle is None or self.updateCycle >= 40: | ||
await self.update_item_details(item_id) | ||
|
||
# reset updateCycle | ||
if self.updateCycle is None or self.updateCycle >= 40: | ||
self.updateCycle = 0 | ||
|
||
self.updateCycle += 1 | ||
|
||
def is_during_sales_window(self, item, salesWindowMinutes): | ||
if CONF_NEXT_SALES_WINDOW in item: | ||
current_datetime = None | ||
sales_window = None | ||
|
||
try: | ||
current_datetime = datetime.datetime.now(datetime.timezone.utc) | ||
except ValueError as e: | ||
LOGGER.error('Current Date Error: %s', e) | ||
|
||
try: | ||
sales_window = datetime.datetime.strptime(item[CONF_NEXT_SALES_WINDOW], '%Y-%m-%dT%H:%M:%S%z') | ||
except ValueError as e: | ||
LOGGER.error('Current SalesWindow Date Error: %s', e) | ||
|
||
return sales_window <= current_datetime <= sales_window + datetime.timedelta(minutes=salesWindowMinutes) | ||
|
||
return False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import voluptuous as vol | ||
import logging | ||
|
||
from homeassistant import config_entries | ||
from homeassistant.const import CONF_EMAIL | ||
|
||
from .const import DOMAIN, CONF_USER_ID, CONF_COOKIE, CONF_ACCESS_TOKEN, CONF_REFRESH_TOKEN | ||
from .client import Client | ||
|
||
LOGGER = logging.getLogger(__name__) | ||
|
||
class TGTGConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): | ||
"""TGTG config flow.""" | ||
|
||
VERSION = 1 | ||
|
||
async def async_step_user(self, user_input=None): | ||
errors = {} | ||
if user_input is not None: | ||
email = user_input[CONF_EMAIL] | ||
|
||
# use email as unique_id | ||
await self.async_set_unique_id(email) | ||
self._abort_if_unique_id_configured() | ||
|
||
try: | ||
# Get credentials | ||
tgtg = Client(hass=self.hass, email=email) | ||
data = await tgtg.fetch_credentials() | ||
|
||
config = { | ||
CONF_EMAIL: email, | ||
CONF_ACCESS_TOKEN: data["access_token"], | ||
CONF_REFRESH_TOKEN: data["refresh_token"], | ||
CONF_USER_ID: data["user_id"], | ||
CONF_COOKIE: data["cookie"] | ||
} | ||
|
||
return self.async_create_entry(title=f"TGTG {email}", data=config) | ||
|
||
except Exception as e: | ||
errors["base"] = "Error: {}".format(e) | ||
return self.async_show_form( | ||
step_id="user", | ||
data_schema=vol.Schema({ | ||
vol.Required(CONF_EMAIL): str | ||
}), | ||
errors=errors | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
"""Constants for the Divera integration.""" | ||
from datetime import timedelta | ||
from typing import Final | ||
|
||
DOMAIN: Final = "tgtg" | ||
CONF_FAVOURITES: Final = "favourites" | ||
CONF_ITEM: Final = "item" | ||
CONF_ITEM_ID: Final = "item_id" | ||
CONF_PRICE_INCL_TAX: Final = "price_including_taxes" | ||
CONF_VALUE_INCL_TAX: Final = "value_including_taxes" | ||
CONF_ITEM_START: Final = "start" | ||
CONF_ITEM_END: Final = "end" | ||
CONF_ITEM_LOGO_PICTURE: Final = "logo_picture" | ||
CONF_PICKUP_INTERVAL: Final = "pickup_interval" | ||
CONF_SOLD_OUT_AT: Final = "sold_out_at" | ||
CONF_NEXT_SALES_WINDOW: Final = "next_sales_window_purchase_start" | ||
CONF_ACCESS_TOKEN: Final = "access_token" | ||
CONF_REFRESH_TOKEN: Final = "refresh_token" | ||
CONF_USER_EMAIL: Final = "email" | ||
CONF_USER_ID: Final = "user_id" | ||
CONF_COOKIE: Final = "cookie" | ||
CONF_USER_AGENT: Final = "user_agent" | ||
CONF_STORE: Final = "store" | ||
CONF_STORE_ID: Final = "store_id" | ||
ATTR_ITEM_ID: Final = "item_id" | ||
ATTR_ITEM_ID_URL: Final = "item_url" | ||
ATTR_STORE_ID: Final = "store_id" | ||
ATTR_PRICE: Final = "price" | ||
ATTR_VALUE: Final = "value" | ||
ATTR_PICKUP_START: Final = "pickup_start" | ||
ATTR_PICKUP_STOP: Final = "pickup_stop" | ||
ATTR_SOLDOUT_DATE: Final = "soldout_date" | ||
ATTR_NEXT_SALES_WINDOW_DATE: Final = "next_saleswindow" | ||
ATTR_LOGO_PICTURE:Final = "store_logo_url" | ||
|
||
TGTG_NAME = "tgtg_name" | ||
TGTG_CLIENT = "tgtg_client" | ||
TGTG_COORDINATOR = "tgtg_coordinator" | ||
|
||
DEFAULT_SHORT_NAME = "TGTG Store" | ||
|
||
DEFAULT_SCAN_INTERVAL = timedelta(minutes=3) |
Oops, something went wrong.