From 286b9316f3d268473f5dc982b6d58f5ce8b646b8 Mon Sep 17 00:00:00 2001 From: 5ila5 <5ila5@users.noreply.github.com> Date: Fri, 27 Sep 2024 23:25:39 +0200 Subject: [PATCH] fix elmbridge_gov_uk --- .../source/elmbridge_gov_uk.py | 176 ++++++++---------- doc/source/elmbridge_gov_uk.md | 48 +---- 2 files changed, 83 insertions(+), 141 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/elmbridge_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/elmbridge_gov_uk.py index 0431d9233..dfc2cda2f 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/elmbridge_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/elmbridge_gov_uk.py @@ -1,8 +1,8 @@ -import logging -from datetime import datetime, timedelta +from datetime import datetime +from typing import TypedDict import requests -from bs4 import BeautifulSoup +from dateutil.parser import parse from waste_collection_schedule import Collection TITLE = "Elmbridge Borough Council" @@ -16,107 +16,95 @@ "Test_005": {"uprn": 100062372553}, } -API_URLS = { - "session": "https://emaps.elmbridge.gov.uk/myElmbridge.aspx", - "search": "https://emaps.elmbridge.gov.uk/myElmbridge.aspx?action=SetAddress&UniqueId={}", - "schedule": "https://emaps.elmbridge.gov.uk/myElmbridge.aspx?tab=0#Refuse_&_Recycling", -} -OFFSETS = { - "Monday": 0, - "Tuesday": 1, - "Wednesday": 2, - "Thursday": 3, - "Friday": 4, - "Saturday": 5, - "Sunday": 6, -} +BASE_URL = "https://elmbridge-self.achieveservice.com" +INTIAL_URL = f"{BASE_URL}/service/Your_bin_collection_days" +AUTH_URL = f"{BASE_URL}/authapi/isauthenticated" +AUTH_TEST = f"{BASE_URL}/apibroker/domain/elmbridge-self.achieveservice.com" +API_URL = f"{BASE_URL}/apibroker/runLookup" -ICON_MAP = { - "REFUSE": "mdi:trash-can", - "RECYCLING": "mdi:recycle", - "FOOD": "mdi:food", - "GARDEN": "mdi:leaf", -} -HEADERS = { - "user-agent": "Mozilla/5.0", +ICON_MAP = { + "Domestic Waste": "mdi:trash-can", + "Domestic Recycling": "mdi:recycle", + "Food Waste": "mdi:food", + "Textiles and Small WEEE": "mdi:tshirt-crew", } -_LOGGER = logging.getLogger(__name__) +class CollectionResult(TypedDict): + Date: str + Service1: str + Service2: str + Service3: str class Source: - def __init__(self, uprn: str = None): - self._uprn = str(uprn) - - def fetch(self): - # API's do not return the year, nor the date of the collection. - # They return a list of dates for the beginning of a week, and the day of the week the collection is on. - # This script assumes the week-commencing dates are for the current year. - # This'll cause problems in December as upcoming January collections will have been assigned dates in the past. - # Some clunky logic can deal with this: - # If a date in less than 1 month in the past, it doesn't matter as the collection will have recently occurred. - # If a date is more than 1 month in the past, assume it's an incorrectly assigned date and increment the year by 1. - # Once that's been done, offset the week-commencing dates to match day of the week each waste collection type is scheduled. - # If you have a better way of doing this, feel free to update via a Pull Request! - - # Get current date and year in format consistent with API result - today = datetime.now() - today = today.replace(hour=0, minute=0, second=0, microsecond=0) - year = today.year - - s = requests.Session() - - r0 = s.get(API_URLS["session"], headers=HEADERS) - r0.raise_for_status() - r1 = s.get(API_URLS["search"].format(self._uprn), headers=HEADERS) - r1.raise_for_status() - r2 = s.get(API_URLS["schedule"], headers=HEADERS) - r2.raise_for_status() - - responseContent = r2.content - soup = BeautifulSoup(responseContent, "html.parser") + def __init__(self, uprn: str | int): + self._uprn = str(uprn).strip() + self._session = requests.Session() + + def _init_session(self) -> str: + self._session = requests.Session() + r = self._session.get(INTIAL_URL) + r.raise_for_status() + params: dict[str, str | int] = { + "uri": r.url, + "hostname": "elmbridge-self.achieveservice.com", + "withCredentials": "true", + } + r = self._session.get(AUTH_URL, params=params) + r.raise_for_status() + data = r.json() + session_key = data["auth-session"] + + params = { + "sid": session_key, + "_": int(datetime.now().timestamp() * 1000), + } + r = self._session.get(AUTH_TEST, params=params) + r.raise_for_status() + + return session_key + + def get_collections(self, session_key: str) -> list[Collection]: + params: dict[str, int | str] = { + "id": "663b557cdaece", + "repeat_against": "", + "noRetry": "false", + "getOnlyTokens": "undefined", + "log_id": "", + "app_name": "AF-Renderer::Self", + "_": int(datetime.now().timestamp() * 1000), + "sid": session_key, + } + payload = { + "formValues": { + "Section 1": { + "UPRN": {"value": self._uprn}, + } + } + } + r = self._session.post(API_URL, params=params, json=payload) + r.raise_for_status() + return list(r.json()["integration"]["transformed"]["rows_data"].values()) + + def fetch(self) -> list[Collection]: + session_key = self._init_session() + collections = self.get_collections(session_key) entries = [] - - notice = soup.find("div", {"class": "atPanelContent atFirst atAlt0"}) - notices = notice.text.replace( - "\nRefuse and recycling collection days\n", "" - ).split(".") - notices.pop(-1) # Remove superfluous element - frame = soup.find("div", {"class": "atPanelContent atAlt1 atLast"}) - table = frame.find("table") - - for tr in table.find_all("tr"): - row = [] - for td in tr.find_all("td"): - row.append(td.text.strip()) - row.pop(1) # removes superfluous element - dt = row[0] + " " + str(year) - dt = datetime.strptime(dt, "%d %b %Y") - - # Amend year, if necessary - if (dt - today) < timedelta(days=-31): - dt = dt.replace(year=dt.year + 1) - row[0] = dt - - # Separate out same-day waste collections - wastetypes = row[1].split(" + ") - - # Sort out date offsets for each collection type - for waste in wastetypes: - for day, offset in OFFSETS.items(): - for sentence in notices: - if (waste in sentence) and (day in sentence): - new_date = row[0] + timedelta(days=offset) - entries.append( - Collection( - date=new_date.date(), - t=waste + " bin", - icon=ICON_MAP.get(waste.upper()), - ) - ) + for collection in collections: + date = parse(collection["Date"], dayfirst=True).date() + for service in [ + collection["Service1"], + collection["Service2"], + collection["Service3"], + ]: + if not service: + continue + service = service.removesuffix(" Collection Service") + icon = ICON_MAP.get(service) + entries.append(Collection(date=date, t=service, icon=icon)) return entries diff --git a/doc/source/elmbridge_gov_uk.md b/doc/source/elmbridge_gov_uk.md index 845cc547b..f4b3a3d0e 100644 --- a/doc/source/elmbridge_gov_uk.md +++ b/doc/source/elmbridge_gov_uk.md @@ -1,6 +1,6 @@ # Elmbridge Borough Council -Support for schedules provided by [Elmbridge Borough Council](https://emaps.elmbridge.gov.uk/myElmbridge.aspx?tab=0#Refuse_&_Recycling), serving Elmbridge, UK. +Support for schedules provided by [Elmbridge Borough Council](http://elmbridge-self.achieveservice.com/service/Your_bin_collection_days), serving Elmbridge, UK. ## Configuration via configuration.yaml @@ -33,49 +33,3 @@ waste_collection_schedule: ## How to find your `UPRN` An easy way to find your Unique Property Reference Number (UPRN) is by going to https://www.findmyaddress.co.uk/ and entering in your address details. - -#### Notes: -The Elmbridge web site does not show a list of collection dates. It describes the day of the week your waste collection(s) happen on, and lists the start date of the weeks this applies to. The format also differs depending on your collection schedule, for example: - - -``` -Refuse and recycling collection days - -Your collection day for refuse is Wednesday in the weeks indicated below. - -Your collection day for recycling is Wednesday in the weeks indicated below. - -Your collection day for garden waste (if you subscribe) is Tuesday in the weeks indicated below. - -Recycling and garden waste collections for weeks commencing Monday -10 Oct - refuse + garden -17 Oct - refuse + recycling -24 Oct - refuse + garden -31 Oct - refuse + recycling -``` - -``` -Refuse and recycling collection days - -Your collection day for refuse and food waste or, recycling and food waste is Tuesday in the weeks indicated below. - -Your collection day for garden waste (if you subscribe) is Tuesday in the weeks indicated below. - -Recycling and garden waste collections for weeks commencing Monday -10 Oct - refuse + food + garden -17 Oct - recycling + food -24 Oct - refuse + food + garden -31 Oct - recycling + food -``` - -Trying to convert all this into a schedule of dates for each specific waste collections is a bit fiddly. By way of explanaion of what the the script tries to do: -* It assumes the week-commencing dates are for the current year. -* This'll cause problems in December as upcoming January collections will have been assigned dates in the past. -* Some clunky logic can deal with this: - * If a date in less than 1 month in the past, it doesn't matter as the collection will have recently occurred. - * If a date is more than 1 month in the past, assume it's an incorrectly assigned date and increments the year by 1. -* Once that's been done, offset the week-commencing dates to match day of the week indicated for each waste collection type. - -So, plenty of scope for things to go wrong! - -If you have a better way of doing this, feel free to update all the elmbridge_gov_uk files via a pull request! \ No newline at end of file