From b6b41c4986fe655ff15dd96d39370803ebbe8d19 Mon Sep 17 00:00:00 2001 From: Neil Martin Date: Mon, 7 Oct 2024 22:01:06 +0100 Subject: [PATCH 001/425] Fix RochfordCouncil.py with additional strip --- .../uk_bin_collection/councils/RochfordCouncil.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/RochfordCouncil.py b/uk_bin_collection/uk_bin_collection/councils/RochfordCouncil.py index 9a123f3bb6..f05156f593 100644 --- a/uk_bin_collection/uk_bin_collection/councils/RochfordCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/RochfordCouncil.py @@ -36,7 +36,8 @@ def parse_data(self, page: str, **kwargs) -> dict: collection_date = datetime.strptime( remove_ordinal_indicator_from_date_string( week_text[0].split(" - ")[0] - ), + ) + .strip(), "%A %d %B", ) next_collection = collection_date.replace(year=datetime.now().year) From 98ff2db62aeba2eb74db0a4fedc2e456a25bf6fe Mon Sep 17 00:00:00 2001 From: David Amor Date: Thu, 1 May 2025 19:39:32 +0100 Subject: [PATCH 002/425] fix: Stoke date-time fix --- .../councils/StokeOnTrentCityCouncil.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/StokeOnTrentCityCouncil.py b/uk_bin_collection/uk_bin_collection/councils/StokeOnTrentCityCouncil.py index 4388068d5e..1ae0a5889e 100644 --- a/uk_bin_collection/uk_bin_collection/councils/StokeOnTrentCityCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/StokeOnTrentCityCouncil.py @@ -54,14 +54,19 @@ def parse_data(self, page: str, **kwargs) -> dict: bin_type = bin_types.get( item.find_next("Bin").text.replace("EMPTY BINS", "").strip() ) - bin_date = datetime.strptime( - item.find_next("DateTime").text, "%d/%m/%Y %H:%M:%S" - ) + date_text = item.find_next("DateTime").text.strip() + + # Handle inconsistent date formats + if " " in date_text: # Date and time present + bin_date = datetime.strptime(date_text, "%d/%m/%Y %H:%M:%S") + else: # Only date present + bin_date = datetime.strptime(date_text, "%d/%m/%Y") + if bin_date >= datetime.now(): collections.append((bin_type, bin_date)) - except: + except Exception as e: raise SystemError( - "Error has been encountered parsing API. Please try again later and if the issue " + f"Error has been encountered parsing API: {e}. Please try again later and if the issue " "persists, open a GitHub ticket!" ) From 53faaaef750163363554b365a888be7fb0b386c3 Mon Sep 17 00:00:00 2001 From: David Amor Date: Thu, 1 May 2025 23:26:48 +0100 Subject: [PATCH 003/425] fix: Croydon selenium version --- .../councils/CroydonCouncil.py | 375 ++++++------------ 1 file changed, 114 insertions(+), 261 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/CroydonCouncil.py b/uk_bin_collection/uk_bin_collection/councils/CroydonCouncil.py index d866cfe4a6..d78595c7b7 100644 --- a/uk_bin_collection/uk_bin_collection/councils/CroydonCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/CroydonCouncil.py @@ -1,237 +1,16 @@ import time from bs4 import BeautifulSoup +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import Select, WebDriverWait from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass -def get_headers(base_url: str, method: str) -> dict[str, str]: - """ - Gets request headers - :rtype: dict[str, str] - :param base_url: Base URL to use - :param method: Method to use - :return: Request headers - """ - headers = { - "Accept-Encoding": "gzip, deflate, br", - "Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8", - "Cache-Control": "max-age=0", - "Connection": "keep-alive", - "Host": "service.croydon.gov.uk", - "Origin": base_url, - "sec-ch-ua": '"Not_A Brand";v="99", "Google Chrome";v="109", "Chromium";v="109"', - "sec-ch-ua-mobile": "?0", - "sec-ch-ua-platform": "Windows", - "Sec-Fetch-Dest": "document", - "Sec-Fetch-User": "?1", - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)" - " Chrome/109.0.0.0 Safari/537.36", - } - if method.lower() == "post": - headers["Accept"] = "application/json, text/javascript, */*; q=0.01" - headers["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8" - headers["Sec-Fetch-Mode"] = "cors" - headers["Sec-Fetch-Mode"] = "same-origin" - headers["X-Requested-With"] = "XMLHttpRequest" - else: - headers["Accept"] = ( - "text/html,application/xhtml+xml,application/xml;" - "q=0.9,image/avif,image/webp,image/apng,*/*;" - "q=0.8,application/signed-exchange;v=b3;q=0.9" - ) - headers["Sec-Fetch-Mode"] = "navigate" - headers["Sec-Fetch-Mode"] = "none" - return headers - - -def get_session_storage_global() -> object: - """ - Gets session storage global object - :rtype: object - :return: Session storage global object - """ - return { - "destination_stack": [ - "w/webpage/bin-day-enter-address", - "w/webpage/your-bin-collection-details?context_record_id=86086077" - "&webpage_token=5c047b2c10b4aad66bef2054aac6bea52ad7a5e185ffdf7090b01f8ddc96728f", - "w/webpage/bin-day-enter-address", - "w/webpage/your-bin-collection-details?context_record_id=86085229" - "&webpage_token=cf1b8fd6213f4823277d98c1dd8a992e6ebef1fabc7d892714e5d9dade448c37", - "w/webpage/bin-day-enter-address", - "w/webpage/your-bin-collection-details?context_record_id=86084221" - "&webpage_token=7f52fb51019bf0e6bfe9647b1b31000124bd92a9d95781f1557f58b3ed40da52", - "w/webpage/bin-day-enter-address", - "w/webpage/your-bin-collection-details?context_record_id=86083209" - "&webpage_token=de50c265da927336f526d9d9a44947595c3aa38965aa8c495ac2fb73d272ece8", - "w/webpage/bin-day-enter-address", - ], - "last_context_record_id": "86086077", - } - - -def get_csrf_token(s: requests.session, base_url: str) -> str: - """ - Gets a CSRF token - :rtype: str - :param s: requests.Session() to use - :param base_url: Base URL to use - :return: CSRF token - """ - csrf_token = "" - response = s.get( - base_url + "/wasteservices/w/webpage/bin-day-enter-address", - headers=get_headers(base_url, "GET"), - ) - if response.status_code == 200: - soup = BeautifulSoup(response.text, features="html.parser") - soup.prettify() - app_body = soup.find("div", {"class": "app-body"}) - script = app_body.find("script", {"type": "text/javascript"}).string - p = re.compile("var CSRF = ('|\")(.*?)('|\");") - m = p.search(script) - csrf_token = m.groups()[1] - else: - raise ValueError( - "Code 1: Failed to get a CSRF token. Please ensure the council website is online first," - " then open an issue on GitHub." - ) - return csrf_token - - -def get_address_id( - s: requests.session, base_url: str, csrf_token: str, postcode: str, paon: str -) -> str: - """ - Gets the address ID - :rtype: str - :param s: requests.Session() to use - :param base_url: Base URL to use - :param csrf_token: CSRF token to use - :param postcode: Postcode to use - :param paon: House number/address to find - :return: address ID - """ - address_id = "0" - # Get the addresses for the postcode - form_data = { - "code_action": "search", - "code_params": '{"search_item":"' + postcode + '","is_ss":true}', - "fragment_action": "handle_event", - "fragment_id": "PCF0020408EECEC1", - "fragment_collection_class": "formtable", - "fragment_collection_editable_values": '{"PCF0021449EECEC1":"1"}', - "_session_storage": json.dumps( - { - "/wasteservices/w/webpage/bin-day-enter-address": {}, - "_global": get_session_storage_global(), - } - ), - "action_cell_id": "PCL0005629EECEC1", - "action_page_id": "PAG0000898EECEC1", - "form_check_ajax": csrf_token, - } - response = s.post( - base_url - + "/wasteservices/w/webpage/bin-day-enter-address?webpage_subpage_id=PAG0000898EECEC1" - "&webpage_token=faab02e1f62a58f7bad4c2ae5b8622e19846b97dde2a76f546c4bb1230cee044" - "&widget_action=fragment_action", - headers=get_headers(base_url, "POST"), - data=form_data, - ) - if response.status_code == 200: - json_response = json.loads(response.text) - addresses = json_response["response"]["items"] - # Find the matching address id for the paon - for address in addresses: - # Check for full matches first - if address.get("dropdown_display_field") == paon: - address_id = address.get("id") - break - # Check for matching start if no full match found - if address_id == "0": - for address in addresses: - if address.get("dropdown_display_field").split()[0] == paon.strip(): - address_id = address.get("id") - break - # Check match was found - if address_id == "0": - raise ValueError( - "Code 2: No matching address for house number/full address found." - ) - else: - raise ValueError("Code 3: No addresses found for provided postcode.") - return address_id - - -def get_collection_data( - s: requests.session, base_url: str, csrf_token: str, address_id: str -) -> str: - """ - Gets the collection data - :rtype: str - :param s: requests.Session() to use - :param base_url: Base URL to use - :param csrf_token: CSRF token to use - :param address_id: Address id to use - :param retries: Retries count - :return: Collection data - """ - collection_data = "" - if address_id != "0": - form_data = { - "form_check": csrf_token, - "submitted_page_id": "PAG0000898EECEC1", - "submitted_widget_group_id": "PWG0002644EECEC1", - "submitted_widget_group_type": "modify", - "submission_token": "63e9126bacd815.12997577", - "payload[PAG0000898EECEC1][PWG0002644EECEC1][PCL0005629EECEC1][formtable]" - "[C_63e9126bacfb3][PCF0020408EECEC1]": address_id, - "payload[PAG0000898EECEC1][PWG0002644EECEC1][PCL0005629EECEC1][formtable]" - "[C_63e9126bacfb3][PCF0021449EECEC1]": "1", - "payload[PAG0000898EECEC1][PWG0002644EECEC1][PCL0005629EECEC1][formtable]" - "[C_63e9126bacfb3][PCF0020072EECEC1]": "Next", - "submit_fragment_id": "PCF0020072EECEC1", - "_session_storage": json.dumps({"_global": get_session_storage_global()}), - "_update_page_content_request": 1, - "form_check_ajax": csrf_token, - } - response = s.post( - base_url - + "/wasteservices/w/webpage/bin-day-enter-address?webpage_subpage_id=PAG0000898EECEC1" - "&webpage_token=faab02e1f62a58f7bad4c2ae5b8622e19846b97dde2a76f546c4bb1230cee044", - headers=get_headers(base_url, "POST"), - data=form_data, - ) - if response.status_code == 200 and len(response.text) > 0: - json_response = json.loads(response.text) - form_data = { - "_dummy": 1, - "_session_storage": json.dumps( - {"_global": get_session_storage_global()} - ), - "_update_page_content_request": 1, - "form_check_ajax": csrf_token, - } - response = s.post( - base_url + json_response["redirect_url"], - headers=get_headers(base_url, "POST"), - data=form_data, - ) - if response.status_code == 200 and len(response.text) > 0: - json_response = json.loads(response.text) - collection_data = json_response["data"] - else: - raise ValueError("Code 4: Failed to get bin data.") - else: - raise ValueError( - "Code 5: Failed to get bin data. Too many requests. Please wait a few minutes before trying again." - ) - return collection_data - - class CouncilClass(AbstractGetBinDataClass): """ Concrete classes have to implement all abstract operations of the @@ -240,47 +19,121 @@ class CouncilClass(AbstractGetBinDataClass): """ def parse_data(self, page: str, **kwargs) -> dict: - requests.packages.urllib3.disable_warnings() - s = requests.Session() - base_url = "https://service.croydon.gov.uk" - paon = kwargs.get("paon") - postcode = kwargs.get("postcode") - check_paon(paon) - check_postcode(postcode) + driver = None + try: + user_postcode = kwargs.get("postcode") + if not user_postcode: + raise ValueError("No postcode provided.") + check_postcode(user_postcode) + + user_paon = kwargs.get("paon") + check_paon(user_paon) + headless = kwargs.get("headless") + web_driver = kwargs.get("web_driver") + driver = create_webdriver(web_driver, headless, None, __name__) + page = "https://service.croydon.gov.uk/wasteservices/w/webpage/bin-day-enter-address" + + driver.maximize_window() + + driver.get(page) + + postcode_input = WebDriverWait(driver, 60).until( + EC.presence_of_element_located( + (By.CSS_SELECTOR, 'input[data-ts_identifier="postcode_input"]') + ) + ) - # Firstly, get a CSRF (cross-site request forgery) token - csrf_token = get_csrf_token(s, base_url) - # Next, get the address_id - address_id = get_address_id(s, base_url, csrf_token, postcode, paon) - # Finally, use the address_id to get the collection data - collection_data = get_collection_data(s, base_url, csrf_token, address_id) - if collection_data != "": - soup = BeautifulSoup(collection_data, features="html.parser") - soup.prettify() + postcode_input.send_keys(user_postcode + Keys.ENTER) - # Find the list elements - collection_record_elements = soup.find_all( - "div", {"class": "listing_template_record"} + time.sleep(5) + # Wait for address box to be visible + select_address_input = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable( + (By.CSS_SELECTOR, 'select[data-ts_identifier="address_selection"]') + ) ) - # Form a JSON wrapper - data = {"bins": []} + # Select address based on house number (paon) + select = Select(select_address_input) + paon = str(user_paon) # Ensure paon is a string for comparison + address_found = False - for e in collection_record_elements: - collection_type = e.find("h2").get_text() - collection_date = e.find("span", {"class": "value-as-text"}).get_text() - dict_data = { - "type": collection_type, - "collectionDate": datetime.strptime( - collection_date, "%A %d %B %Y" - ).strftime(date_format), - } - data["bins"].append(dict_data) + for option in select.options: + # Look for house number pattern with surrounding spaces to avoid partial matches + if f" {paon} " in f" {option.text} ": + select.select_by_value(option.get_attribute("value")) + address_found = True + break - if len(data["bins"]) == 0: + if not address_found: raise ValueError( - "Code 5: No bin data found. Please ensure the council website is showing data first," - " then open an issue on GitHub." + f"Address with house number {paon} not found in the dropdown." + ) + + # Click the "Next" button + next_button = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable( + (By.CSS_SELECTOR, 'input[type="submit"][value="Next"]') + ) + ) + next_button.click() + + # Wait for the bin collection content to load + collection_content = WebDriverWait(driver, 10).until( + EC.presence_of_element_located( + ( + By.XPATH, + '//*[@id="mats_content_wrapper"]/div[2]/div[2]/div[2]/div/div[1]/div/div[3]/div/div/div/div', + ) ) + ) - return data + soup = BeautifulSoup(driver.page_source, "html.parser") + + bin_data = {"bins": []} + + # Find all bin collection sections + bin_sections = soup.find_all("div", {"class": "listing_template_record"}) + + for section in bin_sections: + # Get bin type from h2 tag + bin_type_elem = section.find("h2") + if bin_type_elem: + bin_type = bin_type_elem.text.strip() + + # Find collection date span + date_span = section.find("span", {"class": "value-as-text"}) + if date_span: + collection_date_string = date_span.text.strip() + + # Convert date string to required format + try: + # Parse the date string (e.g., "Sunday 1 June 2025") + parsed_date = datetime.strptime( + collection_date_string, "%A %d %B %Y" + ) + # Format as dd/mm/yyyy + formatted_date = parsed_date.strftime("%d/%m/%Y") + + # Create bin entry + bin_info = { + "type": bin_type, + "collectionDate": formatted_date, + } + bin_data["bins"].append(bin_info) + except ValueError as e: + print(f"Error parsing date '{collection_date_string}': {e}") + + if not bin_data["bins"]: + raise ValueError("No bin collection data found") + + except Exception as e: + # Here you can log the exception if needed + print(f"An error occurred: {e}") + # Optionally, re-raise the exception if you want it to propagate + raise + finally: + # This block ensures that the driver is closed regardless of an exception + if driver: + driver.quit() + return bin_data From 028b05e89dfed0f4317f206fc1c54f35226a95ea Mon Sep 17 00:00:00 2001 From: David Amor Date: Fri, 2 May 2025 07:09:55 +0100 Subject: [PATCH 004/425] fix: maidstone input.json --- uk_bin_collection/tests/input.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index 2b2ce2d500..bd941b2c6f 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -1414,6 +1414,15 @@ "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", "LAD24CD": "E06000032" }, + "MaidstoneBoroughCouncil": { + "skip_get_url": true, + "house_number": "71", + "postcode": "ME16 8BT", + "url": "https://my.maidstone.gov.uk/service/Find-your-bin-day", + "wiki_name": "Maidstone", + "wiki_note": "Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver.", + "LAD24CD": "E07000110" + }, "MaldonDistrictCouncil": { "skip_get_url": true, "uprn": "100090557253", From d3991eeb6cc7ed7114a5206698b9f26e4b8a6ad3 Mon Sep 17 00:00:00 2001 From: David Amor Date: Fri, 2 May 2025 11:14:10 +0100 Subject: [PATCH 005/425] fix: reworked Maidstone --- .../councils/MaidstoneBoroughCouncil.py | 89 +++++++++---------- 1 file changed, 42 insertions(+), 47 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/MaidstoneBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/MaidstoneBoroughCouncil.py index 571981f81f..409f1c05d8 100644 --- a/uk_bin_collection/uk_bin_collection/councils/MaidstoneBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/MaidstoneBoroughCouncil.py @@ -3,6 +3,7 @@ from bs4 import BeautifulSoup from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import Select from selenium.webdriver.support.wait import WebDriverWait @@ -10,8 +11,6 @@ from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass -# import the wonderful Beautiful Soup and the URL grabber - class CouncilClass(AbstractGetBinDataClass): """ @@ -30,7 +29,7 @@ def parse_data(self, page: str, **kwargs) -> dict: web_driver = kwargs.get("web_driver") headless = kwargs.get("headless") check_postcode(user_postcode) - + # Create Selenium webdriver driver = create_webdriver(web_driver, headless, None, __name__) driver.get(page) @@ -41,68 +40,64 @@ def parse_data(self, page: str, **kwargs) -> dict: driver.switch_to.frame(iframe_presense) wait = WebDriverWait(driver, 60) - + # Postal code input inputElement_postcodesearch = wait.until( EC.element_to_be_clickable((By.NAME, "postcode")) ) inputElement_postcodesearch.send_keys(user_postcode) - - # Wait for the 'Select address' dropdown to be updated - dropdown_select = wait.until( - EC.presence_of_element_located((By.XPATH, "//span[contains(text(), 'Select...')]")) - ) - dropdown_select.click() - + + time.sleep(5) + + inputElement_postcodesearch.send_keys(Keys.TAB + Keys.DOWN) + dropdown = wait.until( - EC.element_to_be_clickable((By.XPATH, f"//div[contains(text(), ' {user_paon}')]")) + EC.element_to_be_clickable( + (By.XPATH, f"//div[contains(text(), ' {user_paon}')]") + ) ) dropdown.click() - # Wait for 'Searching for...' to be added to page - WebDriverWait(driver, timeout=15).until( + # This website is horrible! + WebDriverWait(driver, 20).until( EC.text_to_be_present_in_element( - (By.CSS_SELECTOR, "span[data-name=html1]"), "Searching" - ) - ) - - # Wait for 'Searching for...' to be removed from page - WebDriverWait(driver, timeout=15).until( - EC.none_of( - EC.text_to_be_present_in_element( - (By.CSS_SELECTOR, "span[data-name=html1]"), "Searching" - ) + (By.CSS_SELECTOR, "div.col-collection-panel"), "Next collection" ) ) # Even then it can still be adding data to the page... time.sleep(5) - soup = BeautifulSoup(driver.page_source, features="html.parser") - - # This is ugly but there is literally no consistency to the HTML - def is_a_collection_date(t): - return any("Next collection" in c for c in t.children) - - for next_collection in soup.find_all(is_a_collection_date): - bin_info = list( - next_collection.parent.select_one("div:nth-child(1)").children - ) - if not bin_info: - continue - bin = bin_info[0].get_text() - date = next_collection.select_one("strong").get_text(strip=True) - bin_date = datetime.strptime(date, "%d %b %Y") - dict_data = { - "type": bin, - "collectionDate": bin_date.strftime(date_format), - } - bin_data["bins"].append(dict_data) - - bin_data["bins"].sort( - key=lambda x: datetime.strptime(x.get("collectionDate"), date_format) + # Scraping via Selenium rather than BeautifulSoup, to ensure eveything's loaded + collection_panels = driver.find_elements( + By.CSS_SELECTOR, "div.col-collection-panel" ) + for panel in collection_panels: + try: + # Get bin type (e.g., General waste, Food waste) + bin_type = panel.find_element( + By.CSS_SELECTOR, "h3.collectionDataHeader" + ).text.strip() + # Get next collection date + lines = panel.find_elements(By.CSS_SELECTOR, "ul li") + for line in lines: + if "Next collection" in line.text: + date_str = ( + line.text.split("Next collection")[1] + .strip(": ") + .strip() + ) + bin_date = datetime.strptime(date_str, "%d/%m/%Y") + bin_data["bins"].append( + { + "type": bin_type, + "collectionDate": bin_date.strftime(date_format), + } + ) + except Exception as inner_e: + print(f"Skipping one panel due to error: {inner_e}") + except Exception as e: # Here you can log the exception if needed print(f"An error occurred: {e}") From 0c500287a78a7f433ca7f54d1bada095440b177e Mon Sep 17 00:00:00 2001 From: David Amor Date: Fri, 2 May 2025 11:31:40 +0100 Subject: [PATCH 006/425] fix: Guildford fixes Removed UPRN since it wasn't used. Removed requirement for full address. --- uk_bin_collection/tests/input.json | 5 ++--- .../uk_bin_collection/councils/GuildfordCouncil.py | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index bd941b2c6f..4a51dd89f3 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -1054,14 +1054,13 @@ "LAD24CD": "E07000145" }, "GuildfordCouncil": { - "house_number": "THE LODGE, PUTTENHAM HILL HOUSE, PUTTENHAM HILL, PUTTENHAM, GUILDFORD, GU3 1AH", + "house_number": "THE LODGE", "postcode": "GU3 1AH", "skip_get_url": true, - "uprn": "100061372691", "url": "https://my.guildford.gov.uk/customers/s/view-bin-collections", "web_driver": "http://selenium:4444", "wiki_name": "Guildford", - "wiki_note": "If the bin day is 'today' then the collectionDate will only show today's date if before 7 AM; else the date will be in 'previousCollectionDate'. To get the UPRN, you will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "wiki_note": "If the bin day is 'today' then the collectionDate will only show today's date if before 7 AM; else the date will be in 'previousCollectionDate'.", "LAD24CD": "E07000209" }, "GwyneddCouncil": { diff --git a/uk_bin_collection/uk_bin_collection/councils/GuildfordCouncil.py b/uk_bin_collection/uk_bin_collection/councils/GuildfordCouncil.py index f3fadbb9b6..5be06771d7 100644 --- a/uk_bin_collection/uk_bin_collection/councils/GuildfordCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/GuildfordCouncil.py @@ -27,9 +27,8 @@ class CouncilClass(AbstractGetBinDataClass): def parse_data(self, page: str, **kwargs) -> dict: driver = None try: - uprn = kwargs.get("uprn") postcode = kwargs.get("postcode") - full_address = kwargs.get("paon") + house_number = kwargs.get("paon") url = "https://my.guildford.gov.uk/customers/s/view-bin-collections" @@ -60,7 +59,7 @@ def parse_data(self, page: str, **kwargs) -> dict: EC.presence_of_element_located( ( By.XPATH, - f"//lightning-base-formatted-text[contains(text(), '{full_address}')]", + f"//lightning-base-formatted-text[contains(text(), '{house_number}')]", ) ) ) From 8546031ef16acae1ae39ec6db984128880f4fe49 Mon Sep 17 00:00:00 2001 From: David Amor Date: Fri, 2 May 2025 15:33:24 +0100 Subject: [PATCH 007/425] fix: added URL to Torbay script --- uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py b/uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py index 792a5a1018..a5a8ba2243 100644 --- a/uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py @@ -1,5 +1,3 @@ -# This script pulls bin collection data from Barking and Dagenham Council -# Example URL: https://www.lbbd.gov.uk/rubbish-recycling/household-bin-collection/check-your-bin-collection-days import time from bs4 import BeautifulSoup @@ -39,7 +37,7 @@ def parse_data(self, page: str, **kwargs) -> dict: driver = create_webdriver(web_driver, headless, None, __name__) print(f"Navigating to URL: {url}") - driver.get(url) + driver.get("https://www.torbay.gov.uk/recycling/bin-collections/") print("Successfully loaded the page") driver.maximize_window() From fcb959517af4e222aa43dab1fc77c2ce90b9d98d Mon Sep 17 00:00:00 2001 From: David Amor Date: Fri, 2 May 2025 16:24:46 +0100 Subject: [PATCH 008/425] chore: future simplified Wakefield no code change --- .../uk_bin_collection/councils/WakefieldCityCouncil.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/uk_bin_collection/uk_bin_collection/councils/WakefieldCityCouncil.py b/uk_bin_collection/uk_bin_collection/councils/WakefieldCityCouncil.py index 0cecb6b02b..05a3c3be3c 100644 --- a/uk_bin_collection/uk_bin_collection/councils/WakefieldCityCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/WakefieldCityCouncil.py @@ -21,6 +21,9 @@ def parse_data(self, page: str, **kwargs) -> dict: ) driver.get(kwargs.get("url")) + # This URL format also works: + # https://www.wakefield.gov.uk/where-i-live?a=115%20Elizabeth%20Drive + # Make a BS4 object soup = BeautifulSoup(driver.page_source, features="html.parser") soup.prettify() From b846d8e5307118b3d306903095a3a13e98567856 Mon Sep 17 00:00:00 2001 From: David Amor Date: Fri, 2 May 2025 17:00:49 +0100 Subject: [PATCH 009/425] fix: Bexley simplification thought I could avoid Selenium on this, but too much Javascript even with a direct URL --- uk_bin_collection/tests/input.json | 6 +- .../councils/BexleyCouncil.py | 71 +++++++------------ 2 files changed, 26 insertions(+), 51 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index 4a51dd89f3..943de2b24c 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -206,13 +206,11 @@ "LAD24CD": "N09000003" }, "BexleyCouncil": { - "house_number": "1", - "postcode": "DA5 3AH", + "uprn": "100020196143", "skip_get_url": true, "url": "https://waste.bexley.gov.uk/waste", - "web_driver": "http://selenium:4444", "wiki_name": "Bexley", - "wiki_note": "In order to use this parser, you will need to sign up to [Bexley's @Home app](https://www.bexley.gov.uk/services/rubbish-and-recycling/bexley-home-recycling-app/about-app). Complete the setup by entering your email and setting your address with postcode and address line. Once you can see the calendar, you should be good to run the parser. Just pass the email you used in quotes in the UPRN parameter.", + "wiki_note": "Provide your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to locate it.", "LAD24CD": "E09000004" }, "BirminghamCityCouncil": { diff --git a/uk_bin_collection/uk_bin_collection/councils/BexleyCouncil.py b/uk_bin_collection/uk_bin_collection/councils/BexleyCouncil.py index d7ed4458ea..fe8a36cc70 100644 --- a/uk_bin_collection/uk_bin_collection/councils/BexleyCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/BexleyCouncil.py @@ -24,74 +24,51 @@ class CouncilClass(AbstractGetBinDataClass): def parse_data(self, page: str, **kwargs) -> dict: driver = None try: - page = "https://waste.bexley.gov.uk/waste" - - data = {"bins": []} - user_uprn = kwargs.get("uprn") - user_paon = kwargs.get("paon") - user_postcode = kwargs.get("postcode") web_driver = kwargs.get("web_driver") headless = kwargs.get("headless") + page = f"https://waste.bexley.gov.uk/waste/{user_uprn}" + + print(f"Trying URL: {page}") # Debug + # Create Selenium webdriver driver = create_webdriver(web_driver, headless, None, __name__) driver.get(page) - wait = WebDriverWait(driver, 10) - - inputElement_postcodesearch = wait.until( - EC.element_to_be_clickable((By.ID, "pc")) - ) - inputElement_postcodesearch.send_keys(user_postcode) - - find_address_btn = wait.until( - EC.element_to_be_clickable((By.XPATH, '//*[@id="sub"]')) - ) - find_address_btn.click() + # Wait for the main content container to be present + wait = WebDriverWait(driver, 30) # Increased timeout to 30 seconds - dropdown_options = wait.until( - EC.presence_of_element_located((By.XPATH, '//*[@id="address"]')) - ) - time.sleep(2) - dropdown_options.click() - time.sleep(1) - - # Wait for the element to be clickable - address = WebDriverWait(driver, 10).until( - EC.element_to_be_clickable( - (By.XPATH, f'//li[contains(text(), "{user_paon}")]') + # First wait for container + main_content = wait.until( + EC.presence_of_element_located( + (By.XPATH, "/html/body/div[1]/div/div[2]/div") ) ) - # Click the element - address.click() - - submit_address = wait.until( - EC.presence_of_element_located((By.XPATH, '//*[@id="go"]')) - ) - time.sleep(2) - submit_address.click() + # Then wait for loading indicator to disappear + wait.until(EC.invisibility_of_element_located((By.ID, "loading-indicator"))) - results_found = wait.until( - EC.element_to_be_clickable( - (By.XPATH, '//h1[contains(text(), "Your bin days")]') - ) - ) + # Add after the loading indicator wait + time.sleep(3) # Give extra time for JavaScript to populate the data - final_page = wait.until( - EC.presence_of_element_located((By.CLASS_NAME, "waste__collections")) + # Then wait for at least one bin section to appear + wait.until( + EC.presence_of_element_located((By.CLASS_NAME, "waste-service-name")) ) + # Now parse the page content soup = BeautifulSoup(driver.page_source, features="html.parser") - # Find all waste services - - # Initialize the data dictionary data = {"bins": []} bin_sections = soup.find_all("h3", class_="waste-service-name") - # Loop through each bin field + if not bin_sections: + print("No bin sections found after waiting for content") + print(f"Page source: {driver.page_source}") + return data + + # Rest of your existing bin processing code for bin_section in bin_sections: # Extract the bin type (e.g., "Brown Caddy", "Green Wheelie Bin", etc.) bin_type = bin_section.get_text(strip=True).split("\n")[ From 9274e5d779e6c12414edcb917c9cfd7523c79014 Mon Sep 17 00:00:00 2001 From: David Amor Date: Fri, 2 May 2025 17:31:54 +0100 Subject: [PATCH 010/425] fix: Chorley simplification No longer requires a non-standard UPRN, although is backwards compatible. --- uk_bin_collection/tests/input.json | 4 ++-- .../uk_bin_collection/councils/ChorleyCouncil.py | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index 943de2b24c..876abb5935 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -541,11 +541,11 @@ "ChorleyCouncil": { "postcode": "PR6 7PG", "skip_get_url": true, - "uprn": "UPRN100010382247", + "uprn": "100010382247", "url": "https://myaccount.chorley.gov.uk/wastecollections.aspx", "web_driver": "http://selenium:4444", "wiki_name": "Chorley", - "wiki_note": "Chorley needs to be passed both a Postcode & UPRN in the format of UPRNXXXXXX to work. Find this on [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "wiki_note": "Chorley needs to be passed both a Postcode & UPRN to work. Find this on [FindMyAddress](https://www.findmyaddress.co.uk/search).", "LAD24CD": "E07000118" }, "ColchesterCityCouncil": { diff --git a/uk_bin_collection/uk_bin_collection/councils/ChorleyCouncil.py b/uk_bin_collection/uk_bin_collection/councils/ChorleyCouncil.py index a7c2ae10bf..4cc05e37bc 100644 --- a/uk_bin_collection/uk_bin_collection/councils/ChorleyCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/ChorleyCouncil.py @@ -41,6 +41,10 @@ def parse_data(self, page: str, **kwargs) -> dict: check_uprn(user_uprn) check_postcode(user_postcode) + # Ensure UPRN starts with "UPRN" + if not user_uprn.startswith("UPRN"): + user_uprn = f"UPRN{user_uprn}" + # Create Selenium webdriver user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" driver = create_webdriver(web_driver, headless, user_agent, __name__) From bcaef4d4947cd390f757295280730461d3ff9118 Mon Sep 17 00:00:00 2001 From: David Amor Date: Sat, 3 May 2025 19:26:00 +0100 Subject: [PATCH 011/425] fix: Adur Worthing fix due to council website change (I think) --- .../councils/AdurAndWorthingCouncils.py | 93 ++++++++++++++----- 1 file changed, 69 insertions(+), 24 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/AdurAndWorthingCouncils.py b/uk_bin_collection/uk_bin_collection/councils/AdurAndWorthingCouncils.py index 2b02767ade..a9cc0d6db7 100644 --- a/uk_bin_collection/uk_bin_collection/councils/AdurAndWorthingCouncils.py +++ b/uk_bin_collection/uk_bin_collection/councils/AdurAndWorthingCouncils.py @@ -1,41 +1,86 @@ -import bs4.element +from datetime import datetime + from bs4 import BeautifulSoup -from uk_bin_collection.uk_bin_collection.common import * + +from uk_bin_collection.uk_bin_collection.common import ( + date_format, + get_date_with_ordinal, +) from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass -# import the wonderful Beautiful Soup and the URL grabber class CouncilClass(AbstractGetBinDataClass): - """ - Concrete classes have to implement all abstract operations of the - base class. They can also override some operations with a default - implementation. - """ - def parse_data(self, page: str, **kwargs) -> dict: - # Make a BS4 object - soup = BeautifulSoup(page.text, features="html.parser") - soup.prettify() + # Handle both string and Response objects + page_content = page.text if hasattr(page, "text") else page + + soup = BeautifulSoup(page_content, features="html.parser") data = {"bins": []} collections = [] - for bin in ( - soup.find("table", {"class": "no-style bin-days"}) - .find("tbody") - .find_all("tr") - ): - bin_type = bin.find("th").get_text().strip() + " bin" - bin_dates = bin.find_all("td")[1].contents - for date in bin_dates: - if type(date) == bs4.element.NavigableString: - bin_date = datetime.strptime(date, "%A %d %b %Y") - collections.append((bin_type, bin_date)) + # Find all bin collection rows + bin_rows = soup.find_all("div", class_="bin-collection-listing-row") + + if not bin_rows: + raise ValueError("No bin collection rows found in HTML") + + for bin_row in bin_rows: + try: + # Get bin type from h2 + bin_type_elem = bin_row.find("h2") + if not bin_type_elem: + continue + bin_type = bin_type_elem.text.strip() + + # Find next collection date - look for all

tags + paragraphs = bin_row.find_all("p") + + for p in paragraphs: + if p.get_text() and "Next collection:" in p.get_text(): + date_str = p.get_text().replace("Next collection:", "").strip() + # Extract day number from date string (e.g. "2" from "Friday 2nd May") + day_number = int("".join(filter(str.isdigit, date_str))) + # Replace ordinal in date string with plain number + date_str = date_str.replace( + get_date_with_ordinal(day_number), str(day_number) + ) + + try: + # Parse date with full format + bin_date = datetime.strptime(date_str, "%A %d %B") + + # Add current year since it's not in the date string + current_year = datetime.now().year + bin_date = bin_date.replace(year=current_year) + + # If the date is in the past, it's probably for next year + if bin_date < datetime.now(): + bin_date = bin_date.replace(year=current_year + 1) + + collections.append((bin_type, bin_date)) + print( + f"Successfully parsed date for {bin_type}: {bin_date}" + ) + break + + except ValueError as e: + print( + f"Failed to parse date '{date_str}' for {bin_type}: {e}" + ) + continue + + except Exception as e: + print(f"Error processing bin row: {e}") + continue + + if not collections: + raise ValueError("No valid collection dates found") ordered_data = sorted(collections, key=lambda x: x[1]) for item in ordered_data: dict_data = { - "type": item[0].capitalize(), + "type": item[0], "collectionDate": item[1].strftime(date_format), } data["bins"].append(dict_data) From 40091348c498f551967356901c71844136b211b3 Mon Sep 17 00:00:00 2001 From: David Amor Date: Sun, 4 May 2025 08:09:12 +0100 Subject: [PATCH 012/425] fix: improved Charnwood now just requires UPRN --- uk_bin_collection/tests/input.json | 1 + .../councils/CharnwoodBoroughCouncil.py | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index 876abb5935..cc190f5601 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -474,6 +474,7 @@ "LAD24CD": "W06000008" }, "CharnwoodBoroughCouncil": { + "skip_get_url": true, "url": "https://my.charnwood.gov.uk/location?put=cbc10070067259&rememberme=0&redirect=%2F", "wiki_command_url_override": "https://my.charnwood.gov.uk/location?put=cbcXXXXXXXX&rememberme=0&redirect=%2F", "wiki_name": "Charnwood", diff --git a/uk_bin_collection/uk_bin_collection/councils/CharnwoodBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/CharnwoodBoroughCouncil.py index 787413bc5e..3ffef560e9 100644 --- a/uk_bin_collection/uk_bin_collection/councils/CharnwoodBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/CharnwoodBoroughCouncil.py @@ -1,10 +1,11 @@ +from datetime import timedelta + from bs4 import BeautifulSoup +from dateutil.relativedelta import relativedelta + from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass -from datetime import timedelta -from dateutil.relativedelta import relativedelta - # import the wonderful Beautiful Soup and the URL grabber class CouncilClass(AbstractGetBinDataClass): @@ -15,7 +16,18 @@ class CouncilClass(AbstractGetBinDataClass): """ def parse_data(self, page: str, **kwargs) -> dict: + try: + user_uprn = kwargs.get("uprn") + url = f"https://my.charnwood.gov.uk/location?put=cbc{user_uprn}&rememberme=0&redirect=%2F" + if not user_uprn: + url = kwargs.get("url") + except Exception as e: + raise ValueError(f"Error getting identifier: {str(e)}") + + print(url) + # Make a BS4 object + page = requests.get(url) soup = BeautifulSoup(page.text, features="html.parser") soup.prettify() From 3ee98182a819a09716e5de1a7bf715d3f547bd30 Mon Sep 17 00:00:00 2001 From: David Amor Date: Sun, 4 May 2025 08:25:29 +0100 Subject: [PATCH 013/425] fix: simplified Charnwood input.json --- uk_bin_collection/tests/input.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index cc190f5601..4da4d4bbc5 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -474,11 +474,11 @@ "LAD24CD": "W06000008" }, "CharnwoodBoroughCouncil": { + "uprn": "100030446438", "skip_get_url": true, - "url": "https://my.charnwood.gov.uk/location?put=cbc10070067259&rememberme=0&redirect=%2F", - "wiki_command_url_override": "https://my.charnwood.gov.uk/location?put=cbcXXXXXXXX&rememberme=0&redirect=%2F", + "url": "https://www.charnwood.gov.uk/pages/waste_collections_calendars", "wiki_name": "Charnwood", - "wiki_note": "Replace XXXXXXXX with your UPRN, keeping \"cbc\" before it.", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", "LAD24CD": "E07000130" }, "ChelmsfordCityCouncil": { From c05119eb3cf8fc7b6f81be24b1b06c47956356b9 Mon Sep 17 00:00:00 2001 From: David Amor Date: Sun, 4 May 2025 10:08:16 +0100 Subject: [PATCH 014/425] fix: simplified Cheshire East --- uk_bin_collection/tests/input.json | 7 +++--- .../councils/CharnwoodBoroughCouncil.py | 2 -- .../councils/CheshireEastCouncil.py | 25 +++++++++++++++++-- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index 4da4d4bbc5..c6dbeab3d8 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -507,10 +507,11 @@ "LAD24CD": "E07000177" }, "CheshireEastCouncil": { - "url": "https://online.cheshireeast.gov.uk/MyCollectionDay/SearchByAjax/GetBartecJobList?uprn=100012791226&onelineaddress=3%20COBBLERS%20YARD,%20SK9%207DZ&_=1689413260149", - "wiki_command_url_override": "https://online.cheshireeast.gov.uk/MyCollectionDay/SearchByAjax/GetBartecJobList?uprn=XXXXXXXX&onelineaddress=XXXXXXXX&_=1689413260149", + "skip_get_url": true, + "uprn": "100012830647", + "url": "https://online.cheshireeast.gov.uk/mycollectionday", "wiki_name": "Cheshire East", - "wiki_note": "Both the UPRN and a one-line address are passed in the URL, which needs to be wrapped in double quotes. The one-line address is made up of the house number, street name, and postcode. Use the form [here](https://online.cheshireeast.gov.uk/mycollectionday/) to find them, then take the first line and postcode and replace all spaces with `%20`.", + "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", "LAD24CD": "E06000049" }, "CheshireWestAndChesterCouncil": { diff --git a/uk_bin_collection/uk_bin_collection/councils/CharnwoodBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/CharnwoodBoroughCouncil.py index 3ffef560e9..e4cb444ffb 100644 --- a/uk_bin_collection/uk_bin_collection/councils/CharnwoodBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/CharnwoodBoroughCouncil.py @@ -24,8 +24,6 @@ def parse_data(self, page: str, **kwargs) -> dict: except Exception as e: raise ValueError(f"Error getting identifier: {str(e)}") - print(url) - # Make a BS4 object page = requests.get(url) soup = BeautifulSoup(page.text, features="html.parser") diff --git a/uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py b/uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py index dfece1df0f..ed0c701cbe 100644 --- a/uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py @@ -1,5 +1,8 @@ -from typing import Dict, Any, Optional -from bs4 import BeautifulSoup, Tag, NavigableString +from typing import Any, Dict, Optional + +from bs4 import BeautifulSoup, NavigableString, Tag + +from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass """ @@ -13,6 +16,24 @@ class CouncilClass(AbstractGetBinDataClass): """ def parse_data(self, page: Any, **kwargs: Any) -> Dict[str, Any]: + + try: + user_uprn = kwargs.get("uprn") + url = f"https://online.cheshireeast.gov.uk/MyCollectionDay/SearchByAjax/GetBartecJobList?uprn={user_uprn}" + if not user_uprn: + # This is a fallback user stored a URL in old system. Ensures backwards compatibility. + url = kwargs.get("url") + except Exception as e: + raise ValueError(f"Error getting identifier: {str(e)}") + + # Add warning suppression for the insecure request + import urllib3 + + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + + # Make request with SSL verification disabled + page = requests.get(url, verify=False) + soup = BeautifulSoup(page.text, features="html.parser") bin_data_dict: Dict[str, Any] = {"bins": []} From 72c58c3cb1c8eff59043eafd9dddba1b4f7ca71d Mon Sep 17 00:00:00 2001 From: David Amor Date: Sun, 4 May 2025 12:06:26 +0100 Subject: [PATCH 015/425] fix: Simplified Dartford --- uk_bin_collection/tests/input.json | 5 +++-- .../uk_bin_collection/councils/CheshireEastCouncil.py | 2 +- .../councils/DartfordBoroughCouncil.py | 11 +++++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index c6dbeab3d8..af8195899c 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -644,8 +644,9 @@ "LAD24CD": "E07000096" }, "DartfordBoroughCouncil": { - "uprn": "010094157511", - "url": "https://windmz.dartford.gov.uk/ufs/WS_CHECK_COLLECTIONS.eb?UPRN=010094157511", + "uprn": "100060861698", + "url": "https://www.dartford.gov.uk/waste-recycling/collection-day", + "skip_get_url": true, "wiki_name": "Dartford", "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", "LAD24CD": "E07000107" diff --git a/uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py b/uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py index ed0c701cbe..924df2b4e0 100644 --- a/uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py @@ -21,7 +21,7 @@ def parse_data(self, page: Any, **kwargs: Any) -> Dict[str, Any]: user_uprn = kwargs.get("uprn") url = f"https://online.cheshireeast.gov.uk/MyCollectionDay/SearchByAjax/GetBartecJobList?uprn={user_uprn}" if not user_uprn: - # This is a fallback user stored a URL in old system. Ensures backwards compatibility. + # This is a fallback for if the user stored a URL in old system. Ensures backwards compatibility. url = kwargs.get("url") except Exception as e: raise ValueError(f"Error getting identifier: {str(e)}") diff --git a/uk_bin_collection/uk_bin_collection/councils/DartfordBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/DartfordBoroughCouncil.py index 86d8f6531d..60601df66d 100644 --- a/uk_bin_collection/uk_bin_collection/councils/DartfordBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/DartfordBoroughCouncil.py @@ -1,4 +1,5 @@ from bs4 import BeautifulSoup + from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass @@ -12,7 +13,17 @@ class CouncilClass(AbstractGetBinDataClass): """ def parse_data(self, page: str, **kwargs) -> dict: + + try: + user_uprn = kwargs.get("uprn") + url = f"https://windmz.dartford.gov.uk/ufs/WS_CHECK_COLLECTIONS.eb?UPRN={user_uprn}" + if not user_uprn: + url = kwargs.get("url") + except Exception as e: + raise ValueError(f"Error getting identifier: {str(e)}") + # Make a BS4 object + page = requests.get(url) soup = BeautifulSoup(page.text, features="html.parser") soup.prettify() From 218a66fe6bbdb42062d218167bdab2d0cedd46e4 Mon Sep 17 00:00:00 2001 From: David Amor Date: Sun, 4 May 2025 12:12:54 +0100 Subject: [PATCH 016/425] fix: simplified Dover --- uk_bin_collection/tests/input.json | 7 ++++--- .../councils/DartfordBoroughCouncil.py | 1 + .../councils/DoverDistrictCouncil.py | 18 ++++++++++++++++-- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index af8195899c..faf0ab91a4 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -691,10 +691,11 @@ "LAD24CD": "E06000059" }, "DoverDistrictCouncil": { - "url": "https://collections.dover.gov.uk/property/100060908340", - "wiki_command_url_override": "https://collections.dover.gov.uk/property/XXXXXXXXXXX", + "uprn": "100060908340", + "url": "https://collections.dover.gov.uk/property", + "skip_get_url": true, "wiki_name": "Dover", - "wiki_note": "Replace XXXXXXXXXXX with your UPRN. To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "wiki_note": "To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search).", "LAD24CD": "E07000108" }, "DudleyCouncil": { diff --git a/uk_bin_collection/uk_bin_collection/councils/DartfordBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/DartfordBoroughCouncil.py index 60601df66d..b47e06eaf9 100644 --- a/uk_bin_collection/uk_bin_collection/councils/DartfordBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/DartfordBoroughCouncil.py @@ -18,6 +18,7 @@ def parse_data(self, page: str, **kwargs) -> dict: user_uprn = kwargs.get("uprn") url = f"https://windmz.dartford.gov.uk/ufs/WS_CHECK_COLLECTIONS.eb?UPRN={user_uprn}" if not user_uprn: + # This is a fallback for if the user stored a URL in old system. Ensures backwards compatibility. url = kwargs.get("url") except Exception as e: raise ValueError(f"Error getting identifier: {str(e)}") diff --git a/uk_bin_collection/uk_bin_collection/councils/DoverDistrictCouncil.py b/uk_bin_collection/uk_bin_collection/councils/DoverDistrictCouncil.py index d326fde974..bcbcb68b29 100644 --- a/uk_bin_collection/uk_bin_collection/councils/DoverDistrictCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/DoverDistrictCouncil.py @@ -1,12 +1,26 @@ -from bs4 import BeautifulSoup -from datetime import datetime import re +from datetime import datetime + +from bs4 import BeautifulSoup + from uk_bin_collection.uk_bin_collection.common import * # Consider specific imports from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass class CouncilClass(AbstractGetBinDataClass): def parse_data(self, page: str, **kwargs) -> dict: + + try: + user_uprn = kwargs.get("uprn") + url = f"https://collections.dover.gov.uk/property/{user_uprn}" + if not user_uprn: + # This is a fallback for if the user stored a URL in old system. Ensures backwards compatibility. + url = kwargs.get("url") + except Exception as e: + raise ValueError(f"Error getting identifier: {str(e)}") + + # Make a BS4 object + page = requests.get(url) soup = BeautifulSoup(page.text, "html.parser") bins_data = {"bins": []} From f1c8f7663d0a7bd12cabe5612a24cdd4623af33f Mon Sep 17 00:00:00 2001 From: David Amor Date: Sun, 4 May 2025 12:18:43 +0100 Subject: [PATCH 017/425] fix: simplified East Devon --- uk_bin_collection/tests/input.json | 7 ++++--- .../uk_bin_collection/councils/EastDevonDC.py | 14 +++++++++++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index faf0ab91a4..ad685f2088 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -746,10 +746,11 @@ "LAD24CD": "E07000009" }, "EastDevonDC": { - "url": "https://eastdevon.gov.uk/recycling-and-waste/recycling-waste-information/when-is-my-bin-collected/future-collections-calendar/?UPRN=010090909915", - "wiki_command_url_override": "https://eastdevon.gov.uk/recycling-and-waste/recycling-waste-information/when-is-my-bin-collected/future-collections-calendar/?UPRN=XXXXXXXX", + "uprn": "010090909915", + "url": "https://eastdevon.gov.uk/recycling-and-waste/recycling-waste-information/when-is-my-bin-collected/", + "skip_get_url": true, "wiki_name": "East Devon", - "wiki_note": "Replace XXXXXXXX with your UPRN.", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", "LAD24CD": "E07000040" }, "EastHertsCouncil": { diff --git a/uk_bin_collection/uk_bin_collection/councils/EastDevonDC.py b/uk_bin_collection/uk_bin_collection/councils/EastDevonDC.py index ab15985446..d1c71dff10 100644 --- a/uk_bin_collection/uk_bin_collection/councils/EastDevonDC.py +++ b/uk_bin_collection/uk_bin_collection/councils/EastDevonDC.py @@ -3,7 +3,8 @@ import pandas as pd from bs4 import BeautifulSoup -from uk_bin_collection.uk_bin_collection.common import date_format + +from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass @@ -15,7 +16,18 @@ class CouncilClass(AbstractGetBinDataClass): """ def parse_data(self, page: str, **kwargs) -> dict: + + try: + user_uprn = kwargs.get("uprn") + url = f"https://eastdevon.gov.uk/recycling-and-waste/recycling-waste-information/when-is-my-bin-collected/future-collections-calendar/?UPRN={user_uprn}" + if not user_uprn: + # This is a fallback for if the user stored a URL in old system. Ensures backwards compatibility. + url = kwargs.get("url") + except Exception as e: + raise ValueError(f"Error getting identifier: {str(e)}") + # Make a BS4 object + page = requests.get(url) soup = BeautifulSoup(page.text, features="html.parser") soup.prettify() From a3a652e69b8b9f0843cd5f6d9fcf86331a2c6ac0 Mon Sep 17 00:00:00 2001 From: David Amor Date: Sun, 4 May 2025 12:28:15 +0100 Subject: [PATCH 018/425] fix: simplified Swindon no need for wiki_command_url_override --- uk_bin_collection/tests/input.json | 1 - 1 file changed, 1 deletion(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index ad685f2088..fb5a42055c 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -2334,7 +2334,6 @@ "SwindonBoroughCouncil": { "uprn": "10022793351", "url": "https://www.swindon.gov.uk", - "wiki_command_url_override": "https://www.swindon.gov.uk", "wiki_name": "Swindon", "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", "LAD24CD": "E06000030" From 60b691d59bcfaaaddd9f718ea9c8b828954b0022 Mon Sep 17 00:00:00 2001 From: David Amor Date: Sun, 4 May 2025 12:34:19 +0100 Subject: [PATCH 019/425] fix: added check_uprn to simplified councils --- .../uk_bin_collection/councils/CharnwoodBoroughCouncil.py | 1 + .../uk_bin_collection/councils/CheshireEastCouncil.py | 1 + .../uk_bin_collection/councils/DartfordBoroughCouncil.py | 1 + .../uk_bin_collection/councils/DoverDistrictCouncil.py | 1 + uk_bin_collection/uk_bin_collection/councils/EastDevonDC.py | 1 + 5 files changed, 5 insertions(+) diff --git a/uk_bin_collection/uk_bin_collection/councils/CharnwoodBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/CharnwoodBoroughCouncil.py index e4cb444ffb..5b991cc8e4 100644 --- a/uk_bin_collection/uk_bin_collection/councils/CharnwoodBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/CharnwoodBoroughCouncil.py @@ -18,6 +18,7 @@ class CouncilClass(AbstractGetBinDataClass): def parse_data(self, page: str, **kwargs) -> dict: try: user_uprn = kwargs.get("uprn") + check_uprn(user_uprn) url = f"https://my.charnwood.gov.uk/location?put=cbc{user_uprn}&rememberme=0&redirect=%2F" if not user_uprn: url = kwargs.get("url") diff --git a/uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py b/uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py index 924df2b4e0..ec15bcb229 100644 --- a/uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py @@ -19,6 +19,7 @@ def parse_data(self, page: Any, **kwargs: Any) -> Dict[str, Any]: try: user_uprn = kwargs.get("uprn") + check_uprn(user_uprn) url = f"https://online.cheshireeast.gov.uk/MyCollectionDay/SearchByAjax/GetBartecJobList?uprn={user_uprn}" if not user_uprn: # This is a fallback for if the user stored a URL in old system. Ensures backwards compatibility. diff --git a/uk_bin_collection/uk_bin_collection/councils/DartfordBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/DartfordBoroughCouncil.py index b47e06eaf9..eff017373e 100644 --- a/uk_bin_collection/uk_bin_collection/councils/DartfordBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/DartfordBoroughCouncil.py @@ -16,6 +16,7 @@ def parse_data(self, page: str, **kwargs) -> dict: try: user_uprn = kwargs.get("uprn") + check_uprn(user_uprn) url = f"https://windmz.dartford.gov.uk/ufs/WS_CHECK_COLLECTIONS.eb?UPRN={user_uprn}" if not user_uprn: # This is a fallback for if the user stored a URL in old system. Ensures backwards compatibility. diff --git a/uk_bin_collection/uk_bin_collection/councils/DoverDistrictCouncil.py b/uk_bin_collection/uk_bin_collection/councils/DoverDistrictCouncil.py index bcbcb68b29..d3c6ce8280 100644 --- a/uk_bin_collection/uk_bin_collection/councils/DoverDistrictCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/DoverDistrictCouncil.py @@ -12,6 +12,7 @@ def parse_data(self, page: str, **kwargs) -> dict: try: user_uprn = kwargs.get("uprn") + check_uprn(user_uprn) url = f"https://collections.dover.gov.uk/property/{user_uprn}" if not user_uprn: # This is a fallback for if the user stored a URL in old system. Ensures backwards compatibility. diff --git a/uk_bin_collection/uk_bin_collection/councils/EastDevonDC.py b/uk_bin_collection/uk_bin_collection/councils/EastDevonDC.py index d1c71dff10..bdbb7e39dc 100644 --- a/uk_bin_collection/uk_bin_collection/councils/EastDevonDC.py +++ b/uk_bin_collection/uk_bin_collection/councils/EastDevonDC.py @@ -19,6 +19,7 @@ def parse_data(self, page: str, **kwargs) -> dict: try: user_uprn = kwargs.get("uprn") + check_uprn(user_uprn) url = f"https://eastdevon.gov.uk/recycling-and-waste/recycling-waste-information/when-is-my-bin-collected/future-collections-calendar/?UPRN={user_uprn}" if not user_uprn: # This is a fallback for if the user stored a URL in old system. Ensures backwards compatibility. From 6feaf13540baebd3d7d95c377bb3714883d85ff1 Mon Sep 17 00:00:00 2001 From: David Amor Date: Sun, 4 May 2025 13:00:32 +0100 Subject: [PATCH 020/425] chore: simplified Glasgow --- uk_bin_collection/tests/input.json | 7 ++++--- .../councils/GlasgowCityCouncil.py | 14 +++++++++++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index fb5a42055c..8ca4c0c1e0 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -984,10 +984,11 @@ "LAD24CD": "E07000173" }, "GlasgowCityCouncil": { - "url": "https://onlineservices.glasgow.gov.uk/forms/RefuseAndRecyclingWebApplication/CollectionsCalendar.aspx?UPRN=906700034497", - "wiki_command_url_override": "https://onlineservices.glasgow.gov.uk/forms/RefuseAndRecyclingWebApplication/CollectionsCalendar.aspx?UPRN=XXXXXXXX", + "uprn": "906700034497", + "url": "https://onlineservices.glasgow.gov.uk/forms/RefuseAndRecyclingWebApplication/AddressSearch.aspx", + "skip_get_url": true, "wiki_name": "Glasgow City", - "wiki_note": "Replace XXXXXXXX with your UPRN.", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", "LAD24CD": "S12000049" }, "GloucesterCityCouncil": { diff --git a/uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py b/uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py index 559b9bdcc6..5e7ae6a0ad 100644 --- a/uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py @@ -14,7 +14,19 @@ class CouncilClass(AbstractGetBinDataClass): """ def parse_data(self, page: str, **kwargs) -> dict: - # Parse the page + + try: + user_uprn = kwargs.get("uprn") + check_uprn(user_uprn) + url = f"https://onlineservices.glasgow.gov.uk/forms/RefuseAndRecyclingWebApplication/CollectionsCalendar.aspx?UPRN={user_uprn}" + if not user_uprn: + # This is a fallback for if the user stored a URL in old system. Ensures backwards compatibility. + url = kwargs.get("url") + except Exception as e: + raise ValueError(f"Error getting identifier: {str(e)}") + + # Make a BS4 object + page = requests.get(url) soup = BeautifulSoup(page.text, features="html.parser") soup.prettify() From c3b82ae0aac8d9d69afdb4df4cac9e043ed3dd8b Mon Sep 17 00:00:00 2001 From: David Amor Date: Sun, 4 May 2025 18:20:09 +0100 Subject: [PATCH 021/425] chore: 'nice name' for GooglePublicCalendarCouncil --- uk_bin_collection/tests/input.json | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index 8ca4c0c1e0..742fb1da06 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -1007,21 +1007,21 @@ "wiki_name": "Google Calendar (Public)", "wiki_note": "The URL should be the public ics file URL for the public Google calendar. See https://support.google.com/calendar/answer/37083?sjid=7202815583021446882-EU. Councils that currently need this are Trafford.", "supported_councils": [ - "TraffordCouncil", - "ClackmannanshireCouncil", - "HavantBoroughCouncil", - "NorthWarwickshireBoroughCouncil", - "NewryMourneAndDownDistrictCouncil", - "EastDunbartonshireCouncil", - "PendleBoroughCouncil", - "TorfaenCountyBoroughCouncil", - "EastHampshireCountyCouncil", - "RibbleValleyCouncil", - "BrentwoodBoroughCouncil", - "IsleOfWightCouncil", - "WestmorlAndFurnessCouncil", - "DerryAndStrabaneDistrictCouncil", - "NorwichCityCouncil" + "Trafford", + "Clackmannanshire", + "Havant", + "North Warwickshire", + "Newry and Mourne", + "East Dunbartonshire", + "Pendle", + "Torfaen", + "East Hampshire", + "Ribble Valley", + "Brentwood", + "Isle of Wight", + "Westmorland and Furness", + "Derry City and Strabane", + "Norwich" ], "supported_councils_LAD24CD": [ "E06000046", From 6a43cd3302510ad4ad84a3f65aa1a107598f1054 Mon Sep 17 00:00:00 2001 From: David Amor Date: Sun, 4 May 2025 19:09:02 +0100 Subject: [PATCH 022/425] chore: Lewes and Eastbourne split out as individual council scripts from EnvironmentFirst script --- uk_bin_collection/tests/input.json | 18 ++++- .../councils/EastbourneBoroughCouncil.py | 76 +++++++++++++++++++ .../councils/EnvironmentFirst.py | 2 + .../councils/LewesDistrictCouncil.py | 76 +++++++++++++++++++ 4 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 uk_bin_collection/uk_bin_collection/councils/EastbourneBoroughCouncil.py create mode 100644 uk_bin_collection/uk_bin_collection/councils/LewesDistrictCouncil.py diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index 742fb1da06..a05726ab57 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -737,7 +737,15 @@ "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", "LAD24CD": "S12000008" }, - "EastCambridgeshireCouncil": { + "EastbourneBoroughCouncil": { + "uprn": "100060011258", + "url": "https://www.lewes-eastbourne.gov.uk/article/1158/When-is-my-bin-collection-day", + "skip_get_url": true, + "wiki_name": "Eastbourne", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000061" + }, + "EastCambridgeshireCouncil": { "skip_get_url": true, "uprn": "10002597178", "url": "https://www.eastcambs.gov.uk/", @@ -1301,6 +1309,14 @@ "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", "LAD24CD": "E06000016" }, + "LewesDistrictCouncil": { + "uprn": "100061930155", + "url": "https://www.lewes-eastbourne.gov.uk/article/1158/When-is-my-bin-collection-day", + "skip_get_url": true, + "wiki_name": "Lewes", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000063" + }, "LichfieldDistrictCouncil": { "uprn": "100031694085", "url": "https://www.lichfielddc.gov.uk", diff --git a/uk_bin_collection/uk_bin_collection/councils/EastbourneBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/EastbourneBoroughCouncil.py new file mode 100644 index 0000000000..4ba9849dd2 --- /dev/null +++ b/uk_bin_collection/uk_bin_collection/councils/EastbourneBoroughCouncil.py @@ -0,0 +1,76 @@ +# Lewes Borough Council uses the same script. + +from bs4 import BeautifulSoup + +from uk_bin_collection.uk_bin_collection.common import * +from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass + + +# import the wonderful Beautiful Soup and the URL grabber +class CouncilClass(AbstractGetBinDataClass): + """ + Concrete classes have to implement all abstract operations of the + base class. They can also override some operations with a default + implementation. + """ + + def parse_data(self, page: str, **kwargs) -> dict: + + try: + user_uprn = kwargs.get("uprn") + check_uprn(user_uprn) + url = f"https://environmentfirst.co.uk/house.php?uprn={user_uprn}" + if not user_uprn: + # This is a fallback for if the user stored a URL in old system. Ensures backwards compatibility. + url = kwargs.get("url") + except Exception as e: + raise ValueError(f"Error getting identifier: {str(e)}") + + # Make a BS4 object + page = requests.get(url) + soup = BeautifulSoup(page.text, features="html.parser") + soup.prettify() + + # Get the paragraph lines from the page + data = {"bins": []} + page_text = soup.find("div", {"class": "collect"}).find_all("p") + + # Parse the correct lines (find them, remove the ordinal indicator and make them the correct format date) and + # then add them to the dictionary + rubbish_day = datetime.strptime( + remove_ordinal_indicator_from_date_string( + page_text[2].find_next("strong").text + ), + "%d %B %Y", + ).strftime(date_format) + dict_data = { + "type": "Rubbish", + "collectionDate": rubbish_day, + } + data["bins"].append(dict_data) + recycling_day = datetime.strptime( + remove_ordinal_indicator_from_date_string( + page_text[4].find_next("strong").text + ), + "%d %B %Y", + ).strftime(date_format) + dict_data = { + "type": "Recycling", + "collectionDate": recycling_day, + } + data["bins"].append(dict_data) + + if len(page_text) > 5: + garden_day = datetime.strptime( + remove_ordinal_indicator_from_date_string( + page_text[6].find_next("strong").text + ), + "%d %B %Y", + ).strftime(date_format) + dict_data = { + "type": "Garden", + "collectionDate": garden_day, + } + data["bins"].append(dict_data) + + return data diff --git a/uk_bin_collection/uk_bin_collection/councils/EnvironmentFirst.py b/uk_bin_collection/uk_bin_collection/councils/EnvironmentFirst.py index cf0eb20f7c..9b599b473c 100644 --- a/uk_bin_collection/uk_bin_collection/councils/EnvironmentFirst.py +++ b/uk_bin_collection/uk_bin_collection/councils/EnvironmentFirst.py @@ -1,3 +1,5 @@ +# Legacy script. Copied to Lewes and Eastbourne. + from bs4 import BeautifulSoup from uk_bin_collection.uk_bin_collection.common import * diff --git a/uk_bin_collection/uk_bin_collection/councils/LewesDistrictCouncil.py b/uk_bin_collection/uk_bin_collection/councils/LewesDistrictCouncil.py new file mode 100644 index 0000000000..06e1038cf3 --- /dev/null +++ b/uk_bin_collection/uk_bin_collection/councils/LewesDistrictCouncil.py @@ -0,0 +1,76 @@ +# Eastbourne uses the same script. + +from bs4 import BeautifulSoup + +from uk_bin_collection.uk_bin_collection.common import * +from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass + + +# import the wonderful Beautiful Soup and the URL grabber +class CouncilClass(AbstractGetBinDataClass): + """ + Concrete classes have to implement all abstract operations of the + base class. They can also override some operations with a default + implementation. + """ + + def parse_data(self, page: str, **kwargs) -> dict: + + try: + user_uprn = kwargs.get("uprn") + check_uprn(user_uprn) + url = f"https://environmentfirst.co.uk/house.php?uprn={user_uprn}" + if not user_uprn: + # This is a fallback for if the user stored a URL in old system. Ensures backwards compatibility. + url = kwargs.get("url") + except Exception as e: + raise ValueError(f"Error getting identifier: {str(e)}") + + # Make a BS4 object + page = requests.get(url) + soup = BeautifulSoup(page.text, features="html.parser") + soup.prettify() + + # Get the paragraph lines from the page + data = {"bins": []} + page_text = soup.find("div", {"class": "collect"}).find_all("p") + + # Parse the correct lines (find them, remove the ordinal indicator and make them the correct format date) and + # then add them to the dictionary + rubbish_day = datetime.strptime( + remove_ordinal_indicator_from_date_string( + page_text[2].find_next("strong").text + ), + "%d %B %Y", + ).strftime(date_format) + dict_data = { + "type": "Rubbish", + "collectionDate": rubbish_day, + } + data["bins"].append(dict_data) + recycling_day = datetime.strptime( + remove_ordinal_indicator_from_date_string( + page_text[4].find_next("strong").text + ), + "%d %B %Y", + ).strftime(date_format) + dict_data = { + "type": "Recycling", + "collectionDate": recycling_day, + } + data["bins"].append(dict_data) + + if len(page_text) > 5: + garden_day = datetime.strptime( + remove_ordinal_indicator_from_date_string( + page_text[6].find_next("strong").text + ), + "%d %B %Y", + ).strftime(date_format) + dict_data = { + "type": "Garden", + "collectionDate": garden_day, + } + data["bins"].append(dict_data) + + return data From 87db652b59a3757bccac2f48a1b8f4c5ff21b212 Mon Sep 17 00:00:00 2001 From: David Amor Date: Sun, 4 May 2025 19:47:30 +0100 Subject: [PATCH 023/425] chore: made Epping Forest more robust --- uk_bin_collection/tests/input.json | 3 +- .../councils/EppingForestDistrictCouncil.py | 62 ++++++++++++++----- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index a05726ab57..85d6312ed4 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -871,9 +871,10 @@ "EppingForestDistrictCouncil": { "postcode": "IG9 6EP", "url": "https://eppingforestdc.maps.arcgis.com/apps/instant/lookup/index.html?appid=bfca32b46e2a47cd9c0a84f2d8cdde17&find=IG9%206EP", + "skip_get_url": true, "web_driver": "http://selenium:4444", "wiki_name": "Epping Forest", - "wiki_note": "Replace the postcode in the URL with your own.", + "wiki_note": "Add your postcode.", "LAD24CD": "E07000072" }, "EpsomandEwellBoroughCouncil": { diff --git a/uk_bin_collection/uk_bin_collection/councils/EppingForestDistrictCouncil.py b/uk_bin_collection/uk_bin_collection/councils/EppingForestDistrictCouncil.py index e95d1b24fa..a7b2d63c86 100644 --- a/uk_bin_collection/uk_bin_collection/councils/EppingForestDistrictCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/EppingForestDistrictCouncil.py @@ -1,13 +1,15 @@ +from datetime import datetime + from bs4 import BeautifulSoup -from uk_bin_collection.uk_bin_collection.common import * -from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass from selenium import webdriver -from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.by import By -from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.common.keys import Keys from selenium.webdriver.support import expected_conditions as EC -from datetime import datetime +from selenium.webdriver.support.ui import WebDriverWait + +from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.common import date_format +from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass class CouncilClass(AbstractGetBinDataClass): @@ -15,27 +17,57 @@ def parse_data(self, page: str, **kwargs) -> dict: postcode = kwargs.get("postcode", "") web_driver = kwargs.get("web_driver") headless = kwargs.get("headless") - - options = webdriver.ChromeOptions() - if headless: - options.add_argument("--headless") - driver = create_webdriver(web_driver, headless) + data = {"bins": []} try: - driver.get( - f"https://eppingforestdc.maps.arcgis.com/apps/instant/lookup/index.html?appid=bfca32b46e2a47cd9c0a84f2d8cdde17&find={postcode}" + # Initialize webdriver with logging + print(f"Initializing webdriver with: {web_driver}, headless: {headless}") + driver = create_webdriver(web_driver, headless, None, __name__) + + # Format and load URL + page_url = f"https://eppingforestdc.maps.arcgis.com/apps/instant/lookup/index.html?appid=bfca32b46e2a47cd9c0a84f2d8cdde17&find={postcode}" + print(f"Accessing URL: {page_url}") + driver.get(page_url) + + # Wait for initial page load + wait = WebDriverWait(driver, 20) # Reduced timeout to fail faster if issues + + # First wait for any loading indicators to disappear + try: + print("Waiting for loading spinner to disappear...") + wait.until( + EC.invisibility_of_element_located( + (By.CSS_SELECTOR, ".esri-widget--loader-container") + ) + ) + except Exception as e: + print(f"Loading spinner wait failed (may be normal): {str(e)}") + + # Then wait for the content container + print("Waiting for content container...") + wait.until( + EC.presence_of_element_located( + (By.CSS_SELECTOR, ".esri-feature-content") + ) ) - wait = WebDriverWait(driver, 10) - WebDriverWait(driver, 10).until( + + # Finally wait for actual content + print("Waiting for content to be visible...") + content = wait.until( EC.visibility_of_element_located( (By.CSS_SELECTOR, ".esri-feature-content") ) ) + + # Check if content is actually present + if not content: + raise ValueError("Content element found but empty") + + print("Content found, getting page source...") html_content = driver.page_source soup = BeautifulSoup(html_content, "html.parser") bin_info_divs = soup.select(".esri-feature-content p") - data = {"bins": []} for div in bin_info_divs: if "collection day is" in div.text: bin_type, date_str = div.text.split(" collection day is ") From 50663f66731c7d7545ad63c5a418b0077b0a4839 Mon Sep 17 00:00:00 2001 From: David Amor Date: Sun, 4 May 2025 19:54:33 +0100 Subject: [PATCH 024/425] chore: simplified Herefordshire --- uk_bin_collection/tests/input.json | 7 ++++--- .../councils/HerefordshireCouncil.py | 15 +++++++++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index 85d6312ed4..ee98829907 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -1149,10 +1149,11 @@ "LAD24CD": "E07000062" }, "HerefordshireCouncil": { - "url": "https://www.herefordshire.gov.uk/rubbish-recycling/check-bin-collection-day?blpu_uprn=10096232662", - "wiki_command_url_override": "https://www.herefordshire.gov.uk/rubbish-recycling/check-bin-collection-day?blpu_uprn=XXXXXXXXXXXX", + "uprn": "200002618844", + "url": "https://www.herefordshire.gov.uk/rubbish-recycling/check-bin-collection-day", + "skip_get_url": true, "wiki_name": "Herefordshire", - "wiki_note": "Replace 'XXXXXXXXXX' with your property's UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", "LAD24CD": "E06000019" }, "HertsmereBoroughCouncil": { diff --git a/uk_bin_collection/uk_bin_collection/councils/HerefordshireCouncil.py b/uk_bin_collection/uk_bin_collection/councils/HerefordshireCouncil.py index 52cfeba4b0..369b58526e 100644 --- a/uk_bin_collection/uk_bin_collection/councils/HerefordshireCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/HerefordshireCouncil.py @@ -15,9 +15,20 @@ class CouncilClass(AbstractGetBinDataClass): """ def parse_data(self, page: str, **kwargs) -> dict: + try: + user_uprn = kwargs.get("uprn") + check_uprn(user_uprn) + url = f"https://www.herefordshire.gov.uk/rubbish-recycling/check-bin-collection-day?blpu_uprn={user_uprn}" + if not user_uprn: + # This is a fallback for if the user stored a URL in old system. Ensures backwards compatibility. + url = kwargs.get("url") + except Exception as e: + raise ValueError(f"Error getting identifier: {str(e)}") + # Make a BS4 object - soup = BeautifulSoup(page.text, features="html.parser") - soup.prettify() + page = requests.get(url) + soup = BeautifulSoup(page.text, "html.parser") + soup.prettify data = {"bins": []} From 1be77b3800a071469f0230975775c824108e6010 Mon Sep 17 00:00:00 2001 From: David Amor Date: Sun, 4 May 2025 20:11:54 +0100 Subject: [PATCH 025/425] chore: simplified Huntingdon --- uk_bin_collection/tests/input.json | 7 +++--- .../councils/HuntingdonDistrictCouncil.py | 22 +++++++++++++++---- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index ee98829907..894383c708 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -1220,11 +1220,12 @@ "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search)." }, "HuntingdonDistrictCouncil": { + "uprn": "10012048679", "LAD24CD": "E07000011", - "url": "http://www.huntingdonshire.gov.uk/refuse-calendar/10012048679", - "wiki_command_url_override": "https://www.huntingdonshire.gov.uk/refuse-calendar/XXXXXXXX", + "url": "http://www.huntingdonshire.gov.uk/refuse-calendar/", + "skip_get_url": true, "wiki_name": "Huntingdonshire", - "wiki_note": "Replace XXXXXXXX with your UPRN." + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search)." }, "HyndburnBoroughCouncil": { "postcode": "BB1 4DJ", diff --git a/uk_bin_collection/uk_bin_collection/councils/HuntingdonDistrictCouncil.py b/uk_bin_collection/uk_bin_collection/councils/HuntingdonDistrictCouncil.py index a7d9d1ecc8..0d8b17bdb8 100644 --- a/uk_bin_collection/uk_bin_collection/councils/HuntingdonDistrictCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/HuntingdonDistrictCouncil.py @@ -2,10 +2,12 @@ # This script pulls (in one hit) the data from # Huntingdon District Council District Council Bins Data +from datetime import datetime + from bs4 import BeautifulSoup + +from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass -from uk_bin_collection.uk_bin_collection.common import date_format -from datetime import datetime # import the wonderful Beautiful Soup and the URL grabber @@ -17,9 +19,21 @@ class CouncilClass(AbstractGetBinDataClass): """ def parse_data(self, page, **kwargs) -> None: + + try: + user_uprn = kwargs.get("uprn") + check_uprn(user_uprn) + url = f"http://www.huntingdonshire.gov.uk/refuse-calendar/{user_uprn}" + if not user_uprn: + # This is a fallback for if the user stored a URL in old system. Ensures backwards compatibility. + url = kwargs.get("url") + except Exception as e: + raise ValueError(f"Error getting identifier: {str(e)}") + # Make a BS4 object - soup = BeautifulSoup(page.text, features="html.parser") - soup.prettify() + page = requests.get(url) + soup = BeautifulSoup(page.text, "html.parser") + soup.prettify data = {"bins": []} From 0b2c8a048981bcc36ee72ea9286daf3ff6230240 Mon Sep 17 00:00:00 2001 From: PineappleEmperor Date: Mon, 5 May 2025 11:25:21 +0100 Subject: [PATCH 026/425] fix: converted collection datetimes into dates for BH parsing. --- .../councils/CheltenhamBoroughCouncil.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/CheltenhamBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/CheltenhamBoroughCouncil.py index 1e4354f6db..b13c0c0ee3 100644 --- a/uk_bin_collection/uk_bin_collection/councils/CheltenhamBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/CheltenhamBoroughCouncil.py @@ -266,9 +266,9 @@ def parse_data(self, page: str, **kwargs: str) -> dict[str, list[dict[str, str]] ).date() for refuse_date in refuse_dates: - collection_date = datetime.strptime(refuse_date, "%d/%m/%Y") + timedelta( + collection_date = (datetime.strptime(refuse_date, "%d/%m/%Y") + timedelta( days=refuse_day_offset - ) + )).date() if collection_date in bh_dict: collection_date = bh_dict[collection_date] collection_date = collection_date.strftime("%d/%m/%Y") @@ -281,9 +281,9 @@ def parse_data(self, page: str, **kwargs: str) -> dict[str, list[dict[str, str]] for recycling_date in recycling_dates: - collection_date = datetime.strptime(recycling_date, "%d/%m/%Y") + timedelta( + collection_date = (datetime.strptime(recycling_date, "%d/%m/%Y") + timedelta( days=recycling_day_offset - ) + )).date() if collection_date in bh_dict: collection_date = bh_dict[collection_date] collection_date = collection_date.strftime("%d/%m/%Y") @@ -296,9 +296,9 @@ def parse_data(self, page: str, **kwargs: str) -> dict[str, list[dict[str, str]] for garden_date in garden_dates: - collection_date = datetime.strptime(garden_date, "%d/%m/%Y") + timedelta( + collection_date = (datetime.strptime(garden_date, "%d/%m/%Y") + timedelta( days=garden_day_offset - ) + )).date() if collection_date in bh_dict: collection_date = bh_dict[collection_date] collection_date = collection_date.strftime("%d/%m/%Y") @@ -318,9 +318,9 @@ def parse_data(self, page: str, **kwargs: str) -> dict[str, list[dict[str, str]] for food_date in food_dates: - collection_date = datetime.strptime(food_date, "%d/%m/%Y") + timedelta( + collection_date = (datetime.strptime(food_date, "%d/%m/%Y") + timedelta( days=food_day_offset - ) + )).date() if collection_date in bh_dict: collection_date = bh_dict[collection_date] collection_date = collection_date.strftime("%d/%m/%Y") @@ -354,9 +354,9 @@ def parse_data(self, page: str, **kwargs: str) -> dict[str, list[dict[str, str]] for food_date in food_dates_first: - collection_date = datetime.strptime(food_date, "%d/%m/%Y") + timedelta( + collection_date = (datetime.strptime(food_date, "%d/%m/%Y") + timedelta( days=food_day_offset - ) + )).date() if collection_date in bh_dict: collection_date = bh_dict[collection_date] collection_date = collection_date.strftime("%d/%m/%Y") @@ -368,9 +368,9 @@ def parse_data(self, page: str, **kwargs: str) -> dict[str, list[dict[str, str]] bindata["bins"].append(dict_data) for food_date in food_dates_second: - collection_date = datetime.strptime(food_date, "%d/%m/%Y") + timedelta( + collection_date = (datetime.strptime(food_date, "%d/%m/%Y") + timedelta( days=second_week_offset - ) + )).date() if collection_date in bh_dict: collection_date = bh_dict[collection_date] collection_date = collection_date.strftime("%d/%m/%Y") From efe3a0ca8bd16ea6fc9c453d260b34317e3e5b70 Mon Sep 17 00:00:00 2001 From: Stuart Buchanan Date: Mon, 5 May 2025 17:41:00 +0100 Subject: [PATCH 027/425] Fixed South Ribble Council --- .../councils/SouthRibbleCouncil.py | 180 +++++++++++------- wiki/Councils.md | 3 +- 2 files changed, 117 insertions(+), 66 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/SouthRibbleCouncil.py b/uk_bin_collection/uk_bin_collection/councils/SouthRibbleCouncil.py index 4ce6b2d701..bdcc0d4528 100644 --- a/uk_bin_collection/uk_bin_collection/councils/SouthRibbleCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/SouthRibbleCouncil.py @@ -1,12 +1,29 @@ -import time - +from typing import Dict, List, Any, Optional +from bs4 import BeautifulSoup +from dateutil.relativedelta import relativedelta import requests - +import logging +import re +from datetime import datetime from uk_bin_collection.uk_bin_collection.common import * +from dateutil.parser import parse + +from uk_bin_collection.uk_bin_collection.common import check_uprn, check_postcode from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass -# import the wonderful Beautiful Soup and the URL grabber +def get_token(page) -> str: + """ + Get a __token to include in the form data + :param page: Page html + :return: Form __token + """ + soup = BeautifulSoup(page.text, features="html.parser") + soup.prettify() + token = soup.find("input", {"name": "__token"}).get("value") + return token + + class CouncilClass(AbstractGetBinDataClass): """ Concrete classes have to implement all abstract operations of the @@ -14,70 +31,103 @@ class CouncilClass(AbstractGetBinDataClass): implementation. """ - def parse_data(self, page: str, **kwargs) -> dict: + def get_data(self, url: str) -> str: + """This method makes the request to the council - user_uprn = kwargs.get("uprn") - check_uprn(user_uprn) - bindata = {"bins": []} + Keyword arguments: + url -- the url to get the data from + """ + # Set a user agent so we look like a browser ;-) + user_agent = ( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/108.0.0.0 Safari/537.36" + ) + headers = {"User-Agent": user_agent} + requests.packages.urllib3.disable_warnings() - SESSION_URL = "https://southribble-ss.achieveservice.com/authapi/isauthenticated?uri=https%253A%252F%252Fsouthribble-ss.achieveservice.com%252FAchieveForms%252F%253Fmode%253Dfill%2526form_uri%253Dsandbox-publish%25253A%252F%252FAF-Process-d3971054-2e93-4a74-8375-494347dd13fd%252FAF-Stage-2693df73-8a5f-4e24-86b8-456ef81dd4a1%252Fdefinition.json%2526process%253D1%2526process_uri%253Dsandbox-processes%25253A%252F%252FAF-Process-d3971054-2e93-4a74-8375-494347dd13fd%2526process_id%253DAF-Process-d3971054-2e93-4a74-8375-494347dd13fd%2526accept%253Dyes%2526consentMessageIds%25255B%25255D%253D3%2526accept%253Dyes%2526consentMessageIds%255B%255D%253D3&hostname=southribble-ss.achieveservice.com&withCredentials=true" + # Make the Request - change the URL - find out your property number + try: + session = requests.Session() + session.headers.update(headers) + full_page = session.get(url) + return full_page + except requests.exceptions.HTTPError as errh: + logging.error(f"Http Error: {errh}") + raise + except requests.exceptions.ConnectionError as errc: + logging.error(f"Error Connecting: {errc}") + raise + except requests.exceptions.Timeout as errt: + logging.error(f"Timeout Error: {errt}") + raise + except requests.exceptions.RequestException as err: + logging.error(f"Oops: Something Else {err}") + raise - API_URL = "https://southribble-ss.achieveservice.com/apibroker/runLookup" + def parse_data(self, page: str, **kwargs: Any) -> Dict[str, List[Dict[str, str]]]: + uprn: Optional[str] = kwargs.get("uprn") + postcode: Optional[str] = kwargs.get("postcode") - data = { - "formValues": { - "Your Collections": { - "PickupDate": {"value": datetime.now().strftime("%Y-%m-%d")}, - }, - "Your details": { - "LLPGUPRN": {"value": user_uprn}, - }, - }, - } + if uprn is None: + raise ValueError("UPRN is required and must be a non-empty string.") + if postcode is None: + raise ValueError("Postcode is required and must be a non-empty string.") - headers = { - "Content-Type": "application/json", - "Accept": "application/json", - "User-Agent": "Mozilla/5.0", - "X-Requested-With": "XMLHttpRequest", - "Referer": "https://southribble-ss.achieveservice.com/fillform/?iframe_id=fillform-frame-1&db_id=", - } - s = requests.session() - r = s.get(SESSION_URL) - r.raise_for_status() - session_data = r.json() - sid = session_data["auth-session"] - params = { - "id": "58ab44ef078bd", - "repeat_against": "", - "noRetry": "false", - "getOnlyTokens": "undefined", - "log_id": "", - "app_name": "AF-Renderer::Self", - # unix_timestamp - "_": str(int(time.time() * 1000)), - "sid": sid, + check_uprn(uprn) + check_postcode(postcode) + + values = { + "__token": get_token(page), + "page": "491", + "locale": "en_GB", + "q1f8ccce1d1e2f58649b4069712be6879a839233f_0_0": postcode, + "q1f8ccce1d1e2f58649b4069712be6879a839233f_1_0": uprn, + "next": "Next", } - r = s.post(API_URL, json=data, headers=headers, params=params) - r.raise_for_status() - data = r.json() - rows_data = data["integration"]["transformed"]["rows_data"]["0"] - if not isinstance(rows_data, dict): - raise ValueError("Invalid data returned from API") - BIN_TYPES = [ - ("RCNextCollectionDate", "Household Waste (Non-Recyclable Waste)"), - ("RENextCollectionDate", "Blue/Green Recyclable Waste"), - ("GWNextCollectionDate", "Garden Waste Collection"), - ("FWNextCollectionDate", "Food Waste"), - ] - bin_type_dict = dict(BIN_TYPES) - - for row in rows_data.items(): - if row[0].endswith("NextCollectionDate"): - if row[1]: - bin_type = bin_type_dict.get(row[0], row[0]) - collection_date = row[1] - dict_data = {"type": bin_type, "collectionDate": collection_date} - bindata["bins"].append(dict_data) - - return bindata + headers = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)"} + requests.packages.urllib3.disable_warnings() + response = requests.request( + "POST", + "https://forms.chorleysouthribble.gov.uk/xfp/form/70", + headers=headers, + data=values, + ) + + soup = BeautifulSoup(response.text, features="html.parser") + + rows = soup.find("table").find_all("tr") + + # Form a JSON wrapper + data: Dict[str, List[Dict[str, str]]] = {"bins": []} + + # Loops the Rows + for row in rows: + cells = row.find_all("td") + if cells: + bin_type = cells[0].get_text(strip=True) + collection_next = cells[1].get_text(strip=True) + + collection_date = re.findall(r"\(.*?\)", collection_next) + + if len(collection_date) != 1: + continue + + collection_date_obj = parse( + re.sub(r"[()]", "", collection_date[0]) + ).date() + + # since we only have the next collection day, if the parsed date is in the past, + # assume the day is instead next month + if collection_date_obj < datetime.now().date(): + collection_date_obj += relativedelta(months=1) + + # Make each Bin element in the JSON + dict_data = { + "type": bin_type, + "collectionDate": collection_date_obj.strftime(date_format), + } + + # Add data to the main JSON Wrapper + data["bins"].append(dict_data) + + return data \ No newline at end of file diff --git a/wiki/Councils.md b/wiki/Councils.md index d6e46b5509..d9ed8b4608 100644 --- a/wiki/Councils.md +++ b/wiki/Councils.md @@ -3142,10 +3142,11 @@ Note: Provide your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/sea ### South Ribble Council ```commandline -python collect_data.py SouthRibbleCouncil https://www.southribble.gov.uk -u XXXXXXXX +python collect_data.py SouthRibbleCouncil https://forms.chorleysouthribble.gov.uk/xfp/form/70 -u XXXXXXXX -p "XXXX XXX" ``` Additional parameters: - `-u` - UPRN +- `-p` - postcode Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN. From 8be557aa41ddb755ae2fcda8ac784281158ff8ef Mon Sep 17 00:00:00 2001 From: David Amor Date: Tue, 6 May 2025 08:59:54 +0100 Subject: [PATCH 028/425] chore: simplified Liverpool --- uk_bin_collection/tests/input.json | 7 ++++--- .../councils/LiverpoolCityCouncil.py | 20 +++++++++++++++---- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index 894383c708..70d9f04130 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -1347,10 +1347,11 @@ "LAD24CD": "N09000007" }, "LiverpoolCityCouncil": { - "url": "https://liverpool.gov.uk/Bins/BinDatesTable?UPRN=38164600", - "wiki_command_url_override": "https://liverpool.gov.uk/Bins/BinDatesTable?UPRN=XXXXXXXX", + "uprn": "38164600", + "skip_get_url": true, + "url": "https://liverpool.gov.uk/bins-and-recycling/bin-collections/", "wiki_name": "Liverpool", - "wiki_note": "Replace XXXXXXXX with your property's UPRN.", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", "LAD24CD": "E08000012" }, "LondonBoroughEaling": { diff --git a/uk_bin_collection/uk_bin_collection/councils/LiverpoolCityCouncil.py b/uk_bin_collection/uk_bin_collection/councils/LiverpoolCityCouncil.py index 216c30f3bd..ae51bba7f7 100644 --- a/uk_bin_collection/uk_bin_collection/councils/LiverpoolCityCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/LiverpoolCityCouncil.py @@ -1,7 +1,8 @@ from bs4 import BeautifulSoup +from dateutil.relativedelta import relativedelta + from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass -from dateutil.relativedelta import relativedelta # import the wonderful Beautiful Soup and the URL grabber @@ -18,9 +19,20 @@ def parse_data(self, page: str, **kwargs) -> dict: collections = [] curr_date = datetime.today() - # Parse the page - soup = BeautifulSoup(page.text, features="html.parser") - soup.prettify() + try: + user_uprn = kwargs.get("uprn") + check_uprn(user_uprn) + url = f"https://liverpool.gov.uk/Bins/BinDatesTable?UPRN={user_uprn}" + if not user_uprn: + # This is a fallback for if the user stored a URL in old system. Ensures backwards compatibility. + url = kwargs.get("url") + except Exception as e: + raise ValueError(f"Error getting identifier: {str(e)}") + + # Make a BS4 object + page = requests.get(url) + soup = BeautifulSoup(page.text, "html.parser") + soup.prettify # Get all table rows on the page - enumerate gives us an index, which is handy for to keep a row count. # In this case, the first (0th) row is headings, so we can skip it, then parse the other data. From 5a4eb43b9edab3129c422a0e7ed307882d993e9b Mon Sep 17 00:00:00 2001 From: David Amor Date: Tue, 6 May 2025 09:16:15 +0100 Subject: [PATCH 029/425] chore: Simplified Newham --- uk_bin_collection/tests/input.json | 12 +++++++----- .../councils/NewhamCouncil.py | 19 +++++++++++++------ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index 70d9f04130..17a45e2039 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -1616,8 +1616,9 @@ "LAD24CD": "E07000091" }, "NewarkAndSherwoodDC": { - "url": "http://app.newark-sherwooddc.gov.uk/bincollection/calendar?pid=200004258529", - "wiki_command_url_override": "http://app.newark-sherwooddc.gov.uk/bincollection/calendar?pid=XXXXXXXX", + "uprn": "200004258529", + "url": "https://app.newark-sherwooddc.gov.uk/bincollection/", + "skip_get_url": true, "wiki_name": "Newark and Sherwood", "wiki_note": "Replace XXXXXXXX with your UPRN.", "LAD24CD": "E07000175" @@ -1637,10 +1638,11 @@ "LAD24CD": "E07000195" }, "NewhamCouncil": { - "url": "https://bincollection.newham.gov.uk/Details/Index/000046002133", - "wiki_command_url_override": "https://bincollection.newham.gov.uk/Details/Index/XXXXXXXXXXX", + "uprn": "46077811", + "url": "https://bincollection.newham.gov.uk/", + "skip_get_url": true, "wiki_name": "Newham", - "wiki_note": "Follow the instructions [here](https://bincollection.newham.gov.uk/) until you get the \"Rubbish and Recycling Collections\" page, then copy the URL and replace the URL in the command.", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", "LAD24CD": "E09000025" }, "NewportCityCouncil": { diff --git a/uk_bin_collection/uk_bin_collection/councils/NewhamCouncil.py b/uk_bin_collection/uk_bin_collection/councils/NewhamCouncil.py index 7a328c58be..937f32af02 100644 --- a/uk_bin_collection/uk_bin_collection/councils/NewhamCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/NewhamCouncil.py @@ -7,14 +7,21 @@ class CouncilClass(AbstractGetBinDataClass): def parse_data(self, page: str, **kwargs) -> dict: - # get the page data - http = urllib3.PoolManager() - response = http.request("GET", kwargs["url"]) - page_data = response.data + + try: + user_uprn = kwargs.get("uprn") + check_uprn(user_uprn) + url = f"https://bincollection.newham.gov.uk/Details/Index/{user_uprn}" + if not user_uprn: + # This is a fallback for if the user stored a URL in old system. Ensures backwards compatibility. + url = kwargs.get("url") + except Exception as e: + raise ValueError(f"Error getting identifier: {str(e)}") # Make a BS4 object - soup = BeautifulSoup(page_data, features="html.parser") - soup.prettify() + page = requests.get(url) + soup = BeautifulSoup(page.text, "html.parser") + soup.prettify # Form a JSON wrapper data = {"bins": []} From f041835d8f9d6544079233ef728511ba4cc909a4 Mon Sep 17 00:00:00 2001 From: David Amor Date: Tue, 6 May 2025 17:27:40 +0100 Subject: [PATCH 030/425] fix: Eastleigh cloudflare fix --- .../councils/EastleighBoroughCouncil.py | 104 ++++++++++-------- 1 file changed, 59 insertions(+), 45 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py index 8632dd0df6..5f61e13cc7 100644 --- a/uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py @@ -1,4 +1,8 @@ from bs4 import BeautifulSoup +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import Select, WebDriverWait + from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass @@ -12,59 +16,69 @@ class CouncilClass(AbstractGetBinDataClass): """ def parse_data(self, page: str, **kwargs) -> dict: - uprn = kwargs.get("uprn") - # Check the UPRN is valid - check_uprn(uprn) + try: + uprn = kwargs.get("uprn") + # Check the UPRN is valid + check_uprn(uprn) + headless = kwargs.get("headless") + web_driver = kwargs.get("web_driver") + url = f"https://www.eastleigh.gov.uk/waste-bins-and-recycling/collection-dates/your-waste-bin-and-recycling-collections?uprn={uprn}" + driver = create_webdriver(web_driver, headless, None, __name__) + driver.get(url) - # Request URL - url = f"https://www.eastleigh.gov.uk/waste-bins-and-recycling/collection-dates/your-waste-bin-and-recycling-collections?uprn={uprn}" + wait = WebDriverWait(driver, 10) + bin_content = wait.until( + EC.presence_of_element_located((By.CLASS_NAME, "dl-horizontal")) + ) - # Make Request - requests.packages.urllib3.disable_warnings() - page = requests.get(url) + # Make a BS4 object from driver's page source + soup = BeautifulSoup(driver.page_source, features="html.parser") - # Make a BS4 object - soup = BeautifulSoup(page.text, features="html.parser") - soup.prettify() + # Data to return + data = {"bins": []} - # Data to return - data = {"bins": []} + # Valid bin types + binTypes = [ + "Household Waste Bin", + "Recycling Bin", + "Food Waste Bin", + "Glass Box and Batteries", + "Garden Waste Bin", + ] - # Valid bin types - binTypes = [ - "Household Waste Bin", - "Recycling Bin", - "Food Waste Bin", - "Glass Box and Batteries", - "Garden Waste Bin", - ] + # Value to create dict for DL values + keys, values = [], [] - # Value to create dict for DL values - keys, values = [], [] + # Loop though DT and DD for DL containing bins + dl = soup.find("dl", {"class": "dl-horizontal"}) + for dt in dl.find_all("dt"): + keys.append(dt.text.strip()) + for dd in dl.find_all("dd"): + values.append(dd.text.strip()) - # Loop though DT and DD for DL containing bins - dl = soup.find("dl", {"class": "dl-horizontal"}) - for dt in dl.find_all("dt"): - keys.append(dt.text.strip()) - for dd in dl.find_all("dd"): - values.append(dd.text.strip()) + # Create dict for bin name and string dates + binDict = dict(zip(keys, values)) - # Create dict for bin name and string dates - binDict = dict(zip(keys, values)) + # Process dict for valid bin types + for bin in list(binDict): + if bin in binTypes: + if not binDict[bin].startswith("You haven't yet signed up for"): + # Convert date + date = datetime.strptime(binDict[bin], "%a, %d %b %Y") - # Process dict for valid bin types - for bin in list(binDict): - if bin in binTypes: - if not binDict[bin].startswith("You haven't yet signed up for"): - # Convert date - date = datetime.strptime(binDict[bin], "%a, %d %b %Y") + # Set bin data + dict_data = { + "type": bin, + "collectionDate": date.strftime(date_format), + } + data["bins"].append(dict_data) - # Set bin data - dict_data = { - "type": bin, - "collectionDate": date.strftime(date_format), - } - data["bins"].append(dict_data) + # Return bin data + return data - # Return bin data - return data + except Exception as e: + print(f"Error fetching/parsing data: {str(e)}") + return {"bins": [{"type": "Error", "collectionDate": "2024-01-01"}]} + finally: + if "driver" in locals(): + driver.quit() From aec54fd556502c4d2b4743cbb8778e473fd46382 Mon Sep 17 00:00:00 2001 From: David Amor Date: Tue, 6 May 2025 17:32:22 +0100 Subject: [PATCH 031/425] Revert "fix: Eastleigh cloudflare fix" This reverts commit f041835d8f9d6544079233ef728511ba4cc909a4. --- .../councils/EastleighBoroughCouncil.py | 104 ++++++++---------- 1 file changed, 45 insertions(+), 59 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py index 5f61e13cc7..8632dd0df6 100644 --- a/uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py @@ -1,8 +1,4 @@ from bs4 import BeautifulSoup -from selenium.webdriver.common.by import By -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.support.ui import Select, WebDriverWait - from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass @@ -16,69 +12,59 @@ class CouncilClass(AbstractGetBinDataClass): """ def parse_data(self, page: str, **kwargs) -> dict: - try: - uprn = kwargs.get("uprn") - # Check the UPRN is valid - check_uprn(uprn) - headless = kwargs.get("headless") - web_driver = kwargs.get("web_driver") - url = f"https://www.eastleigh.gov.uk/waste-bins-and-recycling/collection-dates/your-waste-bin-and-recycling-collections?uprn={uprn}" - driver = create_webdriver(web_driver, headless, None, __name__) - driver.get(url) + uprn = kwargs.get("uprn") + # Check the UPRN is valid + check_uprn(uprn) - wait = WebDriverWait(driver, 10) - bin_content = wait.until( - EC.presence_of_element_located((By.CLASS_NAME, "dl-horizontal")) - ) + # Request URL + url = f"https://www.eastleigh.gov.uk/waste-bins-and-recycling/collection-dates/your-waste-bin-and-recycling-collections?uprn={uprn}" - # Make a BS4 object from driver's page source - soup = BeautifulSoup(driver.page_source, features="html.parser") + # Make Request + requests.packages.urllib3.disable_warnings() + page = requests.get(url) - # Data to return - data = {"bins": []} + # Make a BS4 object + soup = BeautifulSoup(page.text, features="html.parser") + soup.prettify() - # Valid bin types - binTypes = [ - "Household Waste Bin", - "Recycling Bin", - "Food Waste Bin", - "Glass Box and Batteries", - "Garden Waste Bin", - ] + # Data to return + data = {"bins": []} - # Value to create dict for DL values - keys, values = [], [] + # Valid bin types + binTypes = [ + "Household Waste Bin", + "Recycling Bin", + "Food Waste Bin", + "Glass Box and Batteries", + "Garden Waste Bin", + ] - # Loop though DT and DD for DL containing bins - dl = soup.find("dl", {"class": "dl-horizontal"}) - for dt in dl.find_all("dt"): - keys.append(dt.text.strip()) - for dd in dl.find_all("dd"): - values.append(dd.text.strip()) + # Value to create dict for DL values + keys, values = [], [] - # Create dict for bin name and string dates - binDict = dict(zip(keys, values)) + # Loop though DT and DD for DL containing bins + dl = soup.find("dl", {"class": "dl-horizontal"}) + for dt in dl.find_all("dt"): + keys.append(dt.text.strip()) + for dd in dl.find_all("dd"): + values.append(dd.text.strip()) - # Process dict for valid bin types - for bin in list(binDict): - if bin in binTypes: - if not binDict[bin].startswith("You haven't yet signed up for"): - # Convert date - date = datetime.strptime(binDict[bin], "%a, %d %b %Y") + # Create dict for bin name and string dates + binDict = dict(zip(keys, values)) - # Set bin data - dict_data = { - "type": bin, - "collectionDate": date.strftime(date_format), - } - data["bins"].append(dict_data) + # Process dict for valid bin types + for bin in list(binDict): + if bin in binTypes: + if not binDict[bin].startswith("You haven't yet signed up for"): + # Convert date + date = datetime.strptime(binDict[bin], "%a, %d %b %Y") - # Return bin data - return data + # Set bin data + dict_data = { + "type": bin, + "collectionDate": date.strftime(date_format), + } + data["bins"].append(dict_data) - except Exception as e: - print(f"Error fetching/parsing data: {str(e)}") - return {"bins": [{"type": "Error", "collectionDate": "2024-01-01"}]} - finally: - if "driver" in locals(): - driver.quit() + # Return bin data + return data From 2d486e80b64bf77d98ee13c4fa51715674225489 Mon Sep 17 00:00:00 2001 From: David Amor Date: Tue, 6 May 2025 17:27:40 +0100 Subject: [PATCH 032/425] fix: Eastleigh cloudflare fix --- .../councils/EastleighBoroughCouncil.py | 104 ++++++++++-------- 1 file changed, 59 insertions(+), 45 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py index 8632dd0df6..5f61e13cc7 100644 --- a/uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py @@ -1,4 +1,8 @@ from bs4 import BeautifulSoup +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import Select, WebDriverWait + from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass @@ -12,59 +16,69 @@ class CouncilClass(AbstractGetBinDataClass): """ def parse_data(self, page: str, **kwargs) -> dict: - uprn = kwargs.get("uprn") - # Check the UPRN is valid - check_uprn(uprn) + try: + uprn = kwargs.get("uprn") + # Check the UPRN is valid + check_uprn(uprn) + headless = kwargs.get("headless") + web_driver = kwargs.get("web_driver") + url = f"https://www.eastleigh.gov.uk/waste-bins-and-recycling/collection-dates/your-waste-bin-and-recycling-collections?uprn={uprn}" + driver = create_webdriver(web_driver, headless, None, __name__) + driver.get(url) - # Request URL - url = f"https://www.eastleigh.gov.uk/waste-bins-and-recycling/collection-dates/your-waste-bin-and-recycling-collections?uprn={uprn}" + wait = WebDriverWait(driver, 10) + bin_content = wait.until( + EC.presence_of_element_located((By.CLASS_NAME, "dl-horizontal")) + ) - # Make Request - requests.packages.urllib3.disable_warnings() - page = requests.get(url) + # Make a BS4 object from driver's page source + soup = BeautifulSoup(driver.page_source, features="html.parser") - # Make a BS4 object - soup = BeautifulSoup(page.text, features="html.parser") - soup.prettify() + # Data to return + data = {"bins": []} - # Data to return - data = {"bins": []} + # Valid bin types + binTypes = [ + "Household Waste Bin", + "Recycling Bin", + "Food Waste Bin", + "Glass Box and Batteries", + "Garden Waste Bin", + ] - # Valid bin types - binTypes = [ - "Household Waste Bin", - "Recycling Bin", - "Food Waste Bin", - "Glass Box and Batteries", - "Garden Waste Bin", - ] + # Value to create dict for DL values + keys, values = [], [] - # Value to create dict for DL values - keys, values = [], [] + # Loop though DT and DD for DL containing bins + dl = soup.find("dl", {"class": "dl-horizontal"}) + for dt in dl.find_all("dt"): + keys.append(dt.text.strip()) + for dd in dl.find_all("dd"): + values.append(dd.text.strip()) - # Loop though DT and DD for DL containing bins - dl = soup.find("dl", {"class": "dl-horizontal"}) - for dt in dl.find_all("dt"): - keys.append(dt.text.strip()) - for dd in dl.find_all("dd"): - values.append(dd.text.strip()) + # Create dict for bin name and string dates + binDict = dict(zip(keys, values)) - # Create dict for bin name and string dates - binDict = dict(zip(keys, values)) + # Process dict for valid bin types + for bin in list(binDict): + if bin in binTypes: + if not binDict[bin].startswith("You haven't yet signed up for"): + # Convert date + date = datetime.strptime(binDict[bin], "%a, %d %b %Y") - # Process dict for valid bin types - for bin in list(binDict): - if bin in binTypes: - if not binDict[bin].startswith("You haven't yet signed up for"): - # Convert date - date = datetime.strptime(binDict[bin], "%a, %d %b %Y") + # Set bin data + dict_data = { + "type": bin, + "collectionDate": date.strftime(date_format), + } + data["bins"].append(dict_data) - # Set bin data - dict_data = { - "type": bin, - "collectionDate": date.strftime(date_format), - } - data["bins"].append(dict_data) + # Return bin data + return data - # Return bin data - return data + except Exception as e: + print(f"Error fetching/parsing data: {str(e)}") + return {"bins": [{"type": "Error", "collectionDate": "2024-01-01"}]} + finally: + if "driver" in locals(): + driver.quit() From d9011571c08aff41d26cda9a17906145184f09d6 Mon Sep 17 00:00:00 2001 From: David Amor Date: Tue, 6 May 2025 17:36:49 +0100 Subject: [PATCH 033/425] Revert "fix: Eastleigh cloudflare fix" This reverts commit 2d486e80b64bf77d98ee13c4fa51715674225489. --- .../councils/EastleighBoroughCouncil.py | 104 ++++++++---------- 1 file changed, 45 insertions(+), 59 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py index 5f61e13cc7..8632dd0df6 100644 --- a/uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py @@ -1,8 +1,4 @@ from bs4 import BeautifulSoup -from selenium.webdriver.common.by import By -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.support.ui import Select, WebDriverWait - from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass @@ -16,69 +12,59 @@ class CouncilClass(AbstractGetBinDataClass): """ def parse_data(self, page: str, **kwargs) -> dict: - try: - uprn = kwargs.get("uprn") - # Check the UPRN is valid - check_uprn(uprn) - headless = kwargs.get("headless") - web_driver = kwargs.get("web_driver") - url = f"https://www.eastleigh.gov.uk/waste-bins-and-recycling/collection-dates/your-waste-bin-and-recycling-collections?uprn={uprn}" - driver = create_webdriver(web_driver, headless, None, __name__) - driver.get(url) + uprn = kwargs.get("uprn") + # Check the UPRN is valid + check_uprn(uprn) - wait = WebDriverWait(driver, 10) - bin_content = wait.until( - EC.presence_of_element_located((By.CLASS_NAME, "dl-horizontal")) - ) + # Request URL + url = f"https://www.eastleigh.gov.uk/waste-bins-and-recycling/collection-dates/your-waste-bin-and-recycling-collections?uprn={uprn}" - # Make a BS4 object from driver's page source - soup = BeautifulSoup(driver.page_source, features="html.parser") + # Make Request + requests.packages.urllib3.disable_warnings() + page = requests.get(url) - # Data to return - data = {"bins": []} + # Make a BS4 object + soup = BeautifulSoup(page.text, features="html.parser") + soup.prettify() - # Valid bin types - binTypes = [ - "Household Waste Bin", - "Recycling Bin", - "Food Waste Bin", - "Glass Box and Batteries", - "Garden Waste Bin", - ] + # Data to return + data = {"bins": []} - # Value to create dict for DL values - keys, values = [], [] + # Valid bin types + binTypes = [ + "Household Waste Bin", + "Recycling Bin", + "Food Waste Bin", + "Glass Box and Batteries", + "Garden Waste Bin", + ] - # Loop though DT and DD for DL containing bins - dl = soup.find("dl", {"class": "dl-horizontal"}) - for dt in dl.find_all("dt"): - keys.append(dt.text.strip()) - for dd in dl.find_all("dd"): - values.append(dd.text.strip()) + # Value to create dict for DL values + keys, values = [], [] - # Create dict for bin name and string dates - binDict = dict(zip(keys, values)) + # Loop though DT and DD for DL containing bins + dl = soup.find("dl", {"class": "dl-horizontal"}) + for dt in dl.find_all("dt"): + keys.append(dt.text.strip()) + for dd in dl.find_all("dd"): + values.append(dd.text.strip()) - # Process dict for valid bin types - for bin in list(binDict): - if bin in binTypes: - if not binDict[bin].startswith("You haven't yet signed up for"): - # Convert date - date = datetime.strptime(binDict[bin], "%a, %d %b %Y") + # Create dict for bin name and string dates + binDict = dict(zip(keys, values)) - # Set bin data - dict_data = { - "type": bin, - "collectionDate": date.strftime(date_format), - } - data["bins"].append(dict_data) + # Process dict for valid bin types + for bin in list(binDict): + if bin in binTypes: + if not binDict[bin].startswith("You haven't yet signed up for"): + # Convert date + date = datetime.strptime(binDict[bin], "%a, %d %b %Y") - # Return bin data - return data + # Set bin data + dict_data = { + "type": bin, + "collectionDate": date.strftime(date_format), + } + data["bins"].append(dict_data) - except Exception as e: - print(f"Error fetching/parsing data: {str(e)}") - return {"bins": [{"type": "Error", "collectionDate": "2024-01-01"}]} - finally: - if "driver" in locals(): - driver.quit() + # Return bin data + return data From f4542f068030c58243f2d723aff713159ae2407e Mon Sep 17 00:00:00 2001 From: David Amor Date: Tue, 6 May 2025 17:27:40 +0100 Subject: [PATCH 034/425] fix: Eastleigh cloudflare fix --- .../councils/EastleighBoroughCouncil.py | 104 ++++++++++-------- 1 file changed, 59 insertions(+), 45 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py index 8632dd0df6..5f61e13cc7 100644 --- a/uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py @@ -1,4 +1,8 @@ from bs4 import BeautifulSoup +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import Select, WebDriverWait + from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass @@ -12,59 +16,69 @@ class CouncilClass(AbstractGetBinDataClass): """ def parse_data(self, page: str, **kwargs) -> dict: - uprn = kwargs.get("uprn") - # Check the UPRN is valid - check_uprn(uprn) + try: + uprn = kwargs.get("uprn") + # Check the UPRN is valid + check_uprn(uprn) + headless = kwargs.get("headless") + web_driver = kwargs.get("web_driver") + url = f"https://www.eastleigh.gov.uk/waste-bins-and-recycling/collection-dates/your-waste-bin-and-recycling-collections?uprn={uprn}" + driver = create_webdriver(web_driver, headless, None, __name__) + driver.get(url) - # Request URL - url = f"https://www.eastleigh.gov.uk/waste-bins-and-recycling/collection-dates/your-waste-bin-and-recycling-collections?uprn={uprn}" + wait = WebDriverWait(driver, 10) + bin_content = wait.until( + EC.presence_of_element_located((By.CLASS_NAME, "dl-horizontal")) + ) - # Make Request - requests.packages.urllib3.disable_warnings() - page = requests.get(url) + # Make a BS4 object from driver's page source + soup = BeautifulSoup(driver.page_source, features="html.parser") - # Make a BS4 object - soup = BeautifulSoup(page.text, features="html.parser") - soup.prettify() + # Data to return + data = {"bins": []} - # Data to return - data = {"bins": []} + # Valid bin types + binTypes = [ + "Household Waste Bin", + "Recycling Bin", + "Food Waste Bin", + "Glass Box and Batteries", + "Garden Waste Bin", + ] - # Valid bin types - binTypes = [ - "Household Waste Bin", - "Recycling Bin", - "Food Waste Bin", - "Glass Box and Batteries", - "Garden Waste Bin", - ] + # Value to create dict for DL values + keys, values = [], [] - # Value to create dict for DL values - keys, values = [], [] + # Loop though DT and DD for DL containing bins + dl = soup.find("dl", {"class": "dl-horizontal"}) + for dt in dl.find_all("dt"): + keys.append(dt.text.strip()) + for dd in dl.find_all("dd"): + values.append(dd.text.strip()) - # Loop though DT and DD for DL containing bins - dl = soup.find("dl", {"class": "dl-horizontal"}) - for dt in dl.find_all("dt"): - keys.append(dt.text.strip()) - for dd in dl.find_all("dd"): - values.append(dd.text.strip()) + # Create dict for bin name and string dates + binDict = dict(zip(keys, values)) - # Create dict for bin name and string dates - binDict = dict(zip(keys, values)) + # Process dict for valid bin types + for bin in list(binDict): + if bin in binTypes: + if not binDict[bin].startswith("You haven't yet signed up for"): + # Convert date + date = datetime.strptime(binDict[bin], "%a, %d %b %Y") - # Process dict for valid bin types - for bin in list(binDict): - if bin in binTypes: - if not binDict[bin].startswith("You haven't yet signed up for"): - # Convert date - date = datetime.strptime(binDict[bin], "%a, %d %b %Y") + # Set bin data + dict_data = { + "type": bin, + "collectionDate": date.strftime(date_format), + } + data["bins"].append(dict_data) - # Set bin data - dict_data = { - "type": bin, - "collectionDate": date.strftime(date_format), - } - data["bins"].append(dict_data) + # Return bin data + return data - # Return bin data - return data + except Exception as e: + print(f"Error fetching/parsing data: {str(e)}") + return {"bins": [{"type": "Error", "collectionDate": "2024-01-01"}]} + finally: + if "driver" in locals(): + driver.quit() From 3edfed9b916fd5af249a07a1b85512a75e05b1f3 Mon Sep 17 00:00:00 2001 From: David Amor Date: Tue, 6 May 2025 17:41:11 +0100 Subject: [PATCH 035/425] fix: updated Eastleigh input.json to reflect that it needs to use Selenium --- uk_bin_collection/tests/input.json | 1 + 1 file changed, 1 insertion(+) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index 2b2ce2d500..f588bfd071 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -820,6 +820,7 @@ "skip_get_url": true, "uprn": "100060303535", "url": "https://www.eastleigh.gov.uk/waste-bins-and-recycling/collection-dates/your-waste-bin-and-recycling-collections?uprn=", + "web_driver": "http://selenium:4444", "wiki_name": "Eastleigh", "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", "LAD24CD": "E07000086" From c17210f5b508566bf869d34eb3a3831fd0bda729 Mon Sep 17 00:00:00 2001 From: David Amor Date: Wed, 7 May 2025 08:32:35 +0100 Subject: [PATCH 036/425] fix: more robust Northumberland to address this https://github.com/robbrad/UKBinCollectionData/issues/1430 --- .../councils/NorthumberlandCouncil.py | 64 +++++++++++++++---- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py b/uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py index 3b6ac63dcc..a779e9b654 100644 --- a/uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py @@ -1,7 +1,10 @@ import time from bs4 import BeautifulSoup +from selenium.common.exceptions import TimeoutException from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import WebDriverWait from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass @@ -42,28 +45,61 @@ def parse_data(self, page: str, **kwargs) -> dict: driver = create_webdriver(web_driver, headless, None, __name__) driver.get(page) - time.sleep(1) + # Create wait object + wait = WebDriverWait(driver, 20) - # Press the cookie accept - wait is to let the JS load it up - driver.find_element(By.ID, "ccc-notify-accept").click() - - inputElement_hn = driver.find_element( - By.ID, - "p_lt_ctl04_pageplaceholder_p_lt_ctl02_WasteCollectionCalendars_NCCAddressLookup_txtHouse", + # Wait for and click cookie button + cookie_button = wait.until( + EC.element_to_be_clickable((By.ID, "ccc-notify-accept")) + ) + cookie_button.click() + + # Wait for and find house number input + inputElement_hn = wait.until( + EC.presence_of_element_located( + ( + By.ID, + "p_lt_ctl04_pageplaceholder_p_lt_ctl02_WasteCollectionCalendars_NCCAddressLookup_txtHouse", + ) + ) ) - inputElement_pc = driver.find_element( - By.ID, - "p_lt_ctl04_pageplaceholder_p_lt_ctl02_WasteCollectionCalendars_NCCAddressLookup_txtPostcode", + + # Wait for and find postcode input + inputElement_pc = wait.until( + EC.presence_of_element_located( + ( + By.ID, + "p_lt_ctl04_pageplaceholder_p_lt_ctl02_WasteCollectionCalendars_NCCAddressLookup_txtPostcode", + ) + ) ) + # Enter details inputElement_pc.send_keys(user_postcode) inputElement_hn.send_keys(user_paon) - driver.find_element( - By.ID, - "p_lt_ctl04_pageplaceholder_p_lt_ctl02_WasteCollectionCalendars_NCCAddressLookup_butLookup", - ).click() + # Click lookup button and wait for results + lookup_button = wait.until( + EC.element_to_be_clickable( + ( + By.ID, + "p_lt_ctl04_pageplaceholder_p_lt_ctl02_WasteCollectionCalendars_NCCAddressLookup_butLookup", + ) + ) + ) + lookup_button.click() + + # Wait for results to load + route_summary = wait.until( + EC.presence_of_element_located( + ( + By.ID, + "p_lt_ctl04_pageplaceholder_p_lt_ctl02_WasteCollectionCalendars_spanRouteSummary", + ) + ) + ) + # Get page source after everything has loaded soup = BeautifulSoup(driver.page_source, features="html.parser") # Work out which bins can be collected for this address. Glass bins are only on some houses due to pilot programme. From 7d4f213228c0764033398ad4640dea0c988c250f Mon Sep 17 00:00:00 2001 From: DFINLEY147 <134228237+DFINLEY147@users.noreply.github.com> Date: Sat, 10 May 2025 08:24:10 +0100 Subject: [PATCH 037/425] Update NorthTynesideCouncil.py --- .../uk_bin_collection/councils/NorthTynesideCouncil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/NorthTynesideCouncil.py b/uk_bin_collection/uk_bin_collection/councils/NorthTynesideCouncil.py index ef0b099972..44b5980b2b 100644 --- a/uk_bin_collection/uk_bin_collection/councils/NorthTynesideCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/NorthTynesideCouncil.py @@ -83,7 +83,7 @@ def parse_data(self, page: str, **kwargs) -> dict: # The garden calendar only shows until end of November 2024, work out how many weeks that is garden_weeks_total = math.floor( - (datetime(2024, 12, 1) - datetime.now()).days / 7 + (datetime(2025, 12, 1) - datetime.now()).days / 7 ) regular_collections, garden_collections, special_collections = [], [], [] From c278192536724e550ee6b0f0100d1d4621402008 Mon Sep 17 00:00:00 2001 From: David Amor Date: Sat, 10 May 2025 19:50:39 +0100 Subject: [PATCH 038/425] fix: Glasgow SSL bypass --- .../uk_bin_collection/councils/GlasgowCityCouncil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py b/uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py index 5e7ae6a0ad..4a50379173 100644 --- a/uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py @@ -26,7 +26,7 @@ def parse_data(self, page: str, **kwargs) -> dict: raise ValueError(f"Error getting identifier: {str(e)}") # Make a BS4 object - page = requests.get(url) + page = requests.get(url, verify=False) soup = BeautifulSoup(page.text, features="html.parser") soup.prettify() From 7361d9e00e209ff5c5fcbd5ac58aa024a78d4d32 Mon Sep 17 00:00:00 2001 From: DFINLEY147 <134228237+DFINLEY147@users.noreply.github.com> Date: Sun, 11 May 2025 11:28:26 +0100 Subject: [PATCH 039/425] Update NorthTynesideCouncil.py Updated to include end of November 2025 garden days --- .../uk_bin_collection/councils/NorthTynesideCouncil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/NorthTynesideCouncil.py b/uk_bin_collection/uk_bin_collection/councils/NorthTynesideCouncil.py index 44b5980b2b..4a64f89935 100644 --- a/uk_bin_collection/uk_bin_collection/councils/NorthTynesideCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/NorthTynesideCouncil.py @@ -81,7 +81,7 @@ def parse_data(self, page: str, **kwargs) -> dict: # The regular calendar only shows until end of March 2026, work out how many weeks that is weeks_total = math.floor((datetime(2026, 4, 1) - datetime.now()).days / 7) - # The garden calendar only shows until end of November 2024, work out how many weeks that is + # The garden calendar only shows until end of November 2025, work out how many weeks that is garden_weeks_total = math.floor( (datetime(2025, 12, 1) - datetime.now()).days / 7 ) From f659c0d2e3e8b748758fb3d71bf0ed0786527438 Mon Sep 17 00:00:00 2001 From: Simon Lovely Date: Wed, 14 May 2025 10:13:38 +0100 Subject: [PATCH 040/425] fix: Update to fix North Somerset North Somerset are moving to 3-weekly collections, and currently the website has a blank cell in the "Following collection date" column. Added checking to ignore that column if empty. --- .../councils/NorthSomersetCouncil.py | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/NorthSomersetCouncil.py b/uk_bin_collection/uk_bin_collection/councils/NorthSomersetCouncil.py index 0d58fdf0d4..fa17644a0f 100644 --- a/uk_bin_collection/uk_bin_collection/councils/NorthSomersetCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/NorthSomersetCouncil.py @@ -43,9 +43,13 @@ def parse_data(self, page: str, **kwargs) -> dict: collectionDate = ( cells[1].get_text(strip=True) + " " + datetime.now().strftime("%Y") ) - nextCollectionDate = ( - cells[2].get_text(strip=True) + " " + datetime.now().strftime("%Y") - ) + + if len(cells) > 2: + nextCollectionDate = ( + cells[2].get_text(strip=True) + " " + datetime.now().strftime("%Y") + ) + else: + nextCollectionDate = "" # Make each Bin element in the JSON dict_data = { @@ -59,12 +63,13 @@ def parse_data(self, page: str, **kwargs) -> dict: data["bins"].append(dict_data) # Make each next Bin element in the JSON - dict_data = { - "type": binType, - "collectionDate": get_next_occurrence_from_day_month( - datetime.strptime(nextCollectionDate, "%A %d %B %Y") - ).strftime(date_format), - } + if nextCollectionDate != "": + dict_data = { + "type": binType, + "collectionDate": get_next_occurrence_from_day_month( + datetime.strptime(nextCollectionDate, "%A %d %B %Y") + ).strftime(date_format), + } # Add data to the main JSON Wrapper data["bins"].append(dict_data) From 771dcae5475c3a56c594a3a6ddde3e7421ef5bc1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 15 May 2025 17:49:13 +0000 Subject: [PATCH 041/425] =?UTF-8?q?bump:=20version=200.152.0=20=E2=86=92?= =?UTF-8?q?=200.152.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 30 +++++++++++++++++++ .../uk_bin_collection/config_flow.py | 2 +- .../uk_bin_collection/manifest.json | 4 +-- pyproject.toml | 2 +- 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00288494fc..c7028d2ff6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,34 @@ ======= +## 0.152.1 (2025-05-15) + +### Fix + +- Update to fix North Somerset +- Glasgow SSL bypass +- more robust Northumberland +- updated Eastleigh input.json +- Eastleigh cloudflare fix +- converted collection datetimes into dates for BH parsing. +- Eastleigh cloudflare fix +- Eastleigh cloudflare fix +- added check_uprn to simplified councils +- simplified Swindon +- simplified East Devon +- simplified Dover +- Simplified Dartford +- simplified Cheshire East +- simplified Charnwood input.json +- improved Charnwood +- Adur Worthing fix +- Chorley simplification +- Bexley simplification +- added URL to Torbay script +- Guildford fixes +- reworked Maidstone +- maidstone input.json +- Croydon selenium version +- Stoke date-time fix + ## 0.152.0 (2025-05-02) ### Feat diff --git a/custom_components/uk_bin_collection/config_flow.py b/custom_components/uk_bin_collection/config_flow.py index 6035f4febc..7e7ffe2d66 100644 --- a/custom_components/uk_bin_collection/config_flow.py +++ b/custom_components/uk_bin_collection/config_flow.py @@ -253,7 +253,7 @@ async def async_step_reconfigure_confirm( async def get_councils_json(self) -> Dict[str, Any]: """Fetch and return the supported councils data, including aliases and sorted alphabetically.""" - url = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.152.0/uk_bin_collection/tests/input.json" + url = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.152.1/uk_bin_collection/tests/input.json" try: async with aiohttp.ClientSession() as session: async with session.get(url) as response: diff --git a/custom_components/uk_bin_collection/manifest.json b/custom_components/uk_bin_collection/manifest.json index 976a21b459..f7e285aa2f 100644 --- a/custom_components/uk_bin_collection/manifest.json +++ b/custom_components/uk_bin_collection/manifest.json @@ -9,7 +9,7 @@ "integration_type": "service", "iot_class": "cloud_polling", "issue_tracker": "https://github.com/robbrad/UKBinCollectionData/issues", - "requirements": ["uk-bin-collection>=0.152.0"], - "version": "0.152.0", + "requirements": ["uk-bin-collection>=0.152.1"], + "version": "0.152.1", "zeroconf": [] } diff --git a/pyproject.toml b/pyproject.toml index df4c3cb59e..cac835b93f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "uk_bin_collection" -version = "0.152.0" +version = "0.152.1" description = "Python Lib to collect UK Bin Data" readme = "README.md" authors = ["Robert Bradley "] From 069246838da104e183f4723a1ff982bb79da8894 Mon Sep 17 00:00:00 2001 From: Russell Clare Date: Thu, 15 May 2025 23:34:42 +0100 Subject: [PATCH 042/425] Add ICS calendar generation feature - Add bin_to_ics.py script for converting bin collection data to ICS format - Add comprehensive documentation in README.md about ICS calendar generation - Include examples for basic usage, automation, and smart home integration - Add unit tests for the ICS generation functionality --- README.md | 178 ++++++++++++++++++++++++++++++++ bin_to_ics.py | 280 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 458 insertions(+) create mode 100644 bin_to_ics.py diff --git a/README.md b/README.md index 472c4cc3a5..8e8506cb15 100644 --- a/README.md +++ b/README.md @@ -374,6 +374,184 @@ Open the map viewer in VS Code: ![Test Results Map](test_results_map.png) --- +## ICS Calendar Generation + +You can convert bin collection data to an ICS calendar file that can be imported into calendar applications like Google Calendar, Apple Calendar, Microsoft Outlook, etc. + +### Overview + +The `bin_to_ics.py` script allows you to: +- Convert JSON output from bin collection data into ICS calendar events +- Group multiple bin collections on the same day into a single event +- Create all-day events (default) or timed events +- Add optional reminders/alarms to events +- Customize the calendar name + +### Requirements + +- Python 3.6 or higher +- The `icalendar` package, which can be installed with: + ```bash + pip install icalendar + ``` + +### Basic Usage + +```bash +# Basic usage with stdin input and default output file (bin.ics) +python bin_to_ics.py < bin_data.json + +# Specify input and output files +python bin_to_ics.py -i bin_data.json -o my_calendar.ics + +# Custom calendar name +python bin_to_ics.py -i bin_data.json -o my_calendar.ics -n "My Bin Collections" +``` + +### Options + +``` +--input, -i Input JSON file (if not provided, read from stdin) +--output, -o Output ICS file (default: bin.ics) +--name, -n Calendar name (default: Bin Collections) +--alarms, -a Comma-separated list of alarm times before event (e.g., "1d,2h,30m") +--no-all-day Create timed events instead of all-day events +``` + +### Examples + +#### Adding Reminders (Alarms) + +Add reminders 1 day and 2 hours before each collection: + +```bash +python bin_to_ics.py -i bin_data.json -a "1d,2h" +``` + +The time format supports: +- Days: `1d`, `2day`, `3days` +- Hours: `1h`, `2hour`, `3hours` +- Minutes: `30m`, `45min`, `60mins`, `90minutes` + +#### Creating Timed Events + +By default, events are created as all-day events. To create timed events instead (default time: 7:00 AM): + +```bash +python bin_to_ics.py -i bin_data.json --no-all-day +``` + +### Integration with Bin Collection Data Retriever + +You can pipe the output from the bin collection data retriever directly to the ICS generator. The required parameters (postcode, house number, UPRN, etc.) depend on the specific council implementation - refer to the [Quickstart](#quickstart) section above or check the [project wiki](https://github.com/robbrad/UKBinCollectionData/wiki) for details about your council. + +```bash +python uk_bin_collection/uk_bin_collection/collect_data.py CouncilName "URL" [OPTIONS] | + python bin_to_ics.py [OPTIONS] +``` + +#### Complete Example for a Council + +```bash +python uk_bin_collection/uk_bin_collection/collect_data.py CouncilName \ + "council_url" \ + -p "YOUR_POSTCODE" \ + -n "YOUR_HOUSE_NUMBER" \ + -w "http://localhost:4444/wd/hub" | + python bin_to_ics.py \ + --name "My Bin Collections" \ + --output my_bins.ics \ + --alarms "1d,12h" +``` + +This will: +1. Fetch bin collection data for your address from your council's website +2. Convert it to an ICS file named "my_bins.ics" +3. Set the calendar name to "My Bin Collections" +4. Add reminders 1 day and 12 hours before each collection + +For postcode lookup and UPRN information, please check the [UPRN Finder](#uprn-finder) section above. + +### Using the Calendar + +You have two options for using the generated ICS file: + +#### 1. Importing the Calendar + +You can directly import the ICS file into your calendar application: + +- **Google Calendar**: Go to Settings > Import & export > Import +- **Apple Calendar**: File > Import +- **Microsoft Outlook**: File > Open & Export > Import/Export > Import an iCalendar (.ics) + +Note: Importing creates a static copy of the calendar events. If bin collection dates change, you'll need to re-import the calendar. + +#### 2. Subscribing to the Calendar + +If you host the ICS file on a publicly accessible web server, you can subscribe to it as an internet calendar: + +- **Google Calendar**: Go to "Other calendars" > "+" > "From URL" > Enter the URL of your hosted ICS file +- **Apple Calendar**: File > New Calendar Subscription > Enter the URL +- **Microsoft Outlook**: File > Account Settings > Internet Calendars > New > Enter the URL + +Benefits of subscribing: +- Calendar automatically updates when the source file changes +- No need to manually re-import when bin collection dates change +- Easily share the calendar with household members + +You can set up a cron job or scheduled task to regularly: +1. Retrieve the latest bin collection data +2. Generate a fresh ICS file +3. Publish it to a web-accessible location + +### Additional Examples and Use Cases + +#### Automation with Cron Jobs + +Create a weekly update script on a Linux/Mac system: + +```bash +#!/bin/bash +# File: update_bin_calendar.sh + +# Set variables +COUNCIL="YourCouncilName" +COUNCIL_URL="https://your-council-website.gov.uk/bins" +POSTCODE="YOUR_POSTCODE" +HOUSE_NUMBER="YOUR_HOUSE_NUMBER" +OUTPUT_DIR="/var/www/html/calendars" # Web-accessible directory +CALENDAR_NAME="Household Bins" + +# Ensure output directory exists +mkdir -p $OUTPUT_DIR + +# Run the collector and generate the calendar +cd /path/to/UKBinCollectionData && \ +python uk_bin_collection/uk_bin_collection/collect_data.py $COUNCIL "$COUNCIL_URL" \ + -p "$POSTCODE" -n "$HOUSE_NUMBER" | \ +python bin_to_ics.py --name "$CALENDAR_NAME" --output "$OUTPUT_DIR/bins.ics" --alarms "1d,6h" + +# Add timestamp to show last update time +echo "Calendar last updated: $(date)" > "$OUTPUT_DIR/last_update.txt" +``` + +Make the script executable: +```bash +chmod +x update_bin_calendar.sh +``` + +Add to crontab to run weekly (every Monday at 2 AM): +```bash +0 2 * * 1 /path/to/update_bin_calendar.sh +``` + +**Google Assistant/Alexa Integration** + +If you have your calendar connected to Google Calendar or Outlook, you can ask your smart assistant about upcoming bin collections: + +- "Hey Google, when is my next bin collection?" +- "Alexa, what's on my calendar tomorrow?" (will include bin collections) + ## Docker API Server We have created an API for this located under [uk_bin_collection_api_server](https://github.com/robbrad/UKBinCollectionData/uk_bin_collection_api_server) diff --git a/bin_to_ics.py b/bin_to_ics.py new file mode 100644 index 0000000000..5beca06213 --- /dev/null +++ b/bin_to_ics.py @@ -0,0 +1,280 @@ +#!/usr/bin/env python3 +""" +Script to convert UK Bin Collection Data to ICS calendar file. +Takes JSON output from the bin collection data retriever and creates calendar events +for each collection date. The events are saved to an ICS file that can be imported +into calendar applications. + +Features: +- Creates all-day events for bin collections by default +- Optional alarms/reminders before collection days +- Groups multiple bin collections on the same day into one event +""" + +import argparse +import datetime +import json +import os +import sys +from typing import Dict, List, Optional, Union + +try: + from icalendar import Calendar, Event, Alarm +except ImportError: + print("Error: Required package 'icalendar' not found.") + print("Please install it with: pip install icalendar") + sys.exit(1) + + +def parse_time_delta(time_str: str) -> datetime.timedelta: + """ + Parse a time string into a timedelta object. + + Formats supported: + - "1d" or "1day" or "1days" for days + - "2h" or "2hour" or "2hours" for hours + - "30m" or "30min" or "30mins" or "30minutes" for minutes + + Args: + time_str: String representing a time duration + + Returns: + timedelta object representing the duration + """ + time_str = time_str.lower().strip() + + # Handle days + if time_str.endswith('d') or time_str.endswith('day') or time_str.endswith('days'): + if time_str.endswith('days'): + value = int(time_str[:-4]) + elif time_str.endswith('day'): + value = int(time_str[:-3]) + else: + value = int(time_str[:-1]) + return datetime.timedelta(days=value) + + # Handle hours + elif time_str.endswith('h') or time_str.endswith('hour') or time_str.endswith('hours'): + if time_str.endswith('hours'): + value = int(time_str[:-5]) + elif time_str.endswith('hour'): + value = int(time_str[:-4]) + else: + value = int(time_str[:-1]) + return datetime.timedelta(hours=value) + + # Handle minutes + elif time_str.endswith('m') or time_str.endswith('min') or time_str.endswith('mins') or time_str.endswith('minutes'): + if time_str.endswith('minutes'): + value = int(time_str[:-7]) + elif time_str.endswith('mins'): + value = int(time_str[:-4]) + elif time_str.endswith('min'): + value = int(time_str[:-3]) + else: + value = int(time_str[:-1]) + return datetime.timedelta(minutes=value) + + # Default to hours if no unit specified + else: + try: + value = int(time_str) + return datetime.timedelta(hours=value) + except ValueError: + raise ValueError(f"Invalid time format: {time_str}. Use format like '1d', '2h', or '30m'.") + + +def create_bin_calendar( + bin_data: Dict, + calendar_name: str = "Bin Collections", + alarm_times: Optional[List[datetime.timedelta]] = None, + all_day: bool = True +) -> Calendar: + """ + Create a calendar from bin collection data. + + Args: + bin_data: Dictionary containing bin collection data + calendar_name: Name of the calendar + alarm_times: List of timedeltas for when reminders should trigger before the event + all_day: Whether the events should be all-day events + + Returns: + Calendar object with events for each bin collection + """ + cal = Calendar() + cal.add('prodid', '-//UK Bin Collection Data//bin_to_ics.py//EN') + cal.add('version', '2.0') + cal.add('name', calendar_name) + cal.add('x-wr-calname', calendar_name) + + # Process bin collection data + if 'bins' not in bin_data: + print("Error: Invalid bin data format. 'bins' key not found.") + sys.exit(1) + + # Group collections by date to combine bins collected on the same day + collections_by_date = {} + + for bin_info in bin_data['bins']: + if 'type' not in bin_info or 'collectionDate' not in bin_info: + continue + + bin_type = bin_info['type'] + collection_date_str = bin_info['collectionDate'] + + # Convert date string to datetime object + try: + # Expecting format DD/MM/YYYY + collection_date = datetime.datetime.strptime(collection_date_str, "%d/%m/%Y").date() + except ValueError: + print(f"Warning: Unable to parse date '{collection_date_str}'. Skipping.") + continue + + # Add to collections by date + if collection_date not in collections_by_date: + collections_by_date[collection_date] = [] + + collections_by_date[collection_date].append(bin_type) + + # Create events for each collection date + for collection_date, bin_types in collections_by_date.items(): + event = Event() + + # Join multiple bin types into one summary if needed + bin_types_str = ", ".join(bin_types) + + # Create event summary and description + summary = f"Bin Collection: {bin_types_str}" + description = f"Collection for: {bin_types_str}" + + # Add event details + event.add('summary', summary) + event.add('description', description) + + # Set the event as all-day if requested + if all_day: + event.add('dtstart', collection_date) + event.add('dtend', collection_date + datetime.timedelta(days=1)) + else: + # Default to 7am for non-all-day events + collection_datetime = datetime.datetime.combine( + collection_date, + datetime.time(7, 0, 0) + ) + event.add('dtstart', collection_datetime) + event.add('dtend', collection_datetime + datetime.timedelta(hours=1)) + + # Add alarms if specified + if alarm_times: + for alarm_time in alarm_times: + alarm = create_alarm(trigger_before=alarm_time) + event.add_component(alarm) + + # Generate a unique ID for the event + event_id = f"bin-collection-{collection_date.isoformat()}-{hash(bin_types_str) % 10000:04d}@ukbincollection" + event.add('uid', event_id) + + # Add the event to the calendar + cal.add_component(event) + + return cal + + +def create_alarm(trigger_before: datetime.timedelta) -> Alarm: + """ + Create an alarm component for calendar events. + + Args: + trigger_before: How long before the event to trigger the alarm + + Returns: + Alarm component + """ + alarm = Alarm() + alarm.add('action', 'DISPLAY') + alarm.add('description', 'Bin collection reminder') + alarm.add('trigger', -trigger_before) + + return alarm + + +def save_calendar(calendar: Calendar, output_file: str) -> None: + """ + Save a calendar to an ICS file. + + Args: + calendar: Calendar object to save + output_file: Path to save the calendar file + """ + with open(output_file, 'wb') as f: + f.write(calendar.to_ical()) + + print(f"Calendar saved to {output_file}") + + +def load_json_data(input_file: Optional[str] = None) -> Dict: + """ + Load bin collection data from JSON file or stdin. + + Args: + input_file: Path to JSON file (if None, read from stdin) + + Returns: + Dictionary containing bin collection data + """ + if input_file: + try: + with open(input_file, 'r') as f: + return json.load(f) + except (json.JSONDecodeError, FileNotFoundError) as e: + print(f"Error reading input file: {e}") + sys.exit(1) + else: + try: + return json.load(sys.stdin) + except json.JSONDecodeError as e: + print(f"Error parsing JSON from stdin: {e}") + sys.exit(1) + + +def main(): + parser = argparse.ArgumentParser(description='Convert UK Bin Collection Data to ICS calendar file.') + parser.add_argument('--input', '-i', help='Input JSON file (if not provided, read from stdin)') + parser.add_argument('--output', '-o', help='Output ICS file (default: bin.ics)', + default='bin.ics') + parser.add_argument('--name', '-n', help='Calendar name (default: Bin Collections)', + default='Bin Collections') + parser.add_argument('--alarms', '-a', help='Comma-separated list of alarm times before event (e.g., "1d,2h,30m")') + parser.add_argument('--no-all-day', action='store_true', help='Create timed events instead of all-day events') + + args = parser.parse_args() + + # Parse alarm times + alarm_times = None + if args.alarms: + alarm_times = [] + for alarm_str in args.alarms.split(','): + try: + alarm_times.append(parse_time_delta(alarm_str.strip())) + except ValueError as e: + print(f"Warning: {e}") + + # Load bin collection data + bin_data = load_json_data(args.input) + + # Create calendar + calendar = create_bin_calendar( + bin_data, + args.name, + alarm_times=alarm_times, + all_day=not args.no_all_day + ) + + # Save calendar to file + save_calendar(calendar, args.output) + + +if __name__ == '__main__': + main() + From 829f75941255e6288fbb5a52b5965a50a40aac3b Mon Sep 17 00:00:00 2001 From: David Amor Date: Fri, 16 May 2025 13:52:25 +0100 Subject: [PATCH 043/425] fix: removed duplicates in BradfordMDC --- .../uk_bin_collection/councils/BradfordMDC.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/uk_bin_collection/uk_bin_collection/councils/BradfordMDC.py b/uk_bin_collection/uk_bin_collection/councils/BradfordMDC.py index 0617ab2959..3c516392d0 100644 --- a/uk_bin_collection/uk_bin_collection/councils/BradfordMDC.py +++ b/uk_bin_collection/uk_bin_collection/councils/BradfordMDC.py @@ -106,6 +106,7 @@ def parse_data(self, page: str, **kwargs) -> dict: "%a %b %d %Y", ).strftime(date_format), } + print(dict_data) data["bins"].append(dict_data) for bin in soup.find_all(attrs={"id": re.compile(r"CTID-d3gapLk-\d+-A")}): dict_data = { @@ -130,4 +131,22 @@ def parse_data(self, page: str, **kwargs) -> dict: key=lambda x: datetime.strptime(x.get("collectionDate"), date_format) ) + data["bins"].sort( + key=lambda x: datetime.strptime(x.get("collectionDate"), date_format) + ) + + # Deduplicate the bins based on type and collection date + # Feels a bit hacky, but fixes + # https://github.com/robbrad/UKBinCollectionData/issues/1436 + unique_bins = [] + seen = set() + for bin_item in data["bins"]: + # Create a unique identifier for each bin entry + bin_key = (bin_item["type"], bin_item["collectionDate"]) + if bin_key not in seen: + seen.add(bin_key) + unique_bins.append(bin_item) + + data["bins"] = unique_bins + return data From 5875c7b647d98811abc3aeb49e17a81d518a573d Mon Sep 17 00:00:00 2001 From: David Amor Date: Mon, 19 May 2025 21:50:32 +0100 Subject: [PATCH 044/425] fix: Eastleigh date fix --- .../councils/EastleighBoroughCouncil.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py index 5f61e13cc7..71ea375fb7 100644 --- a/uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/EastleighBoroughCouncil.py @@ -77,8 +77,13 @@ def parse_data(self, page: str, **kwargs) -> dict: return data except Exception as e: - print(f"Error fetching/parsing data: {str(e)}") - return {"bins": [{"type": "Error", "collectionDate": "2024-01-01"}]} + import traceback + + error_message = f"Error fetching/parsing data for Eastleigh: {str(e)}\n{traceback.format_exc()}" + print(error_message) + # Use the correct date format for the error fallback + today = datetime.now().strftime("%d/%m/%Y") + return {"bins": [{"type": "Error", "collectionDate": today}]} finally: if "driver" in locals(): driver.quit() From f49265939eabba5ab88511a3a5e8fc3d962b2e15 Mon Sep 17 00:00:00 2001 From: David Amor Date: Mon, 19 May 2025 22:13:37 +0100 Subject: [PATCH 045/425] fix: Adur council he code was looking for "Next collection:" in the HTML, but the actual content has "Next collections:" --- .../councils/AdurAndWorthingCouncils.py | 77 +++++++++++-------- 1 file changed, 47 insertions(+), 30 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/AdurAndWorthingCouncils.py b/uk_bin_collection/uk_bin_collection/councils/AdurAndWorthingCouncils.py index a9cc0d6db7..916520a532 100644 --- a/uk_bin_collection/uk_bin_collection/councils/AdurAndWorthingCouncils.py +++ b/uk_bin_collection/uk_bin_collection/councils/AdurAndWorthingCouncils.py @@ -37,38 +37,55 @@ def parse_data(self, page: str, **kwargs) -> dict: paragraphs = bin_row.find_all("p") for p in paragraphs: - if p.get_text() and "Next collection:" in p.get_text(): - date_str = p.get_text().replace("Next collection:", "").strip() - # Extract day number from date string (e.g. "2" from "Friday 2nd May") - day_number = int("".join(filter(str.isdigit, date_str))) - # Replace ordinal in date string with plain number - date_str = date_str.replace( - get_date_with_ordinal(day_number), str(day_number) + # Check for both singular and plural "Next collection(s):" + if p.get_text() and ( + "Next collection:" in p.get_text() + or "Next collections:" in p.get_text() + ): + # Extract collection dates + date_text = ( + p.get_text() + .replace("Next collection:", "") + .replace("Next collections:", "") + .strip() ) - try: - # Parse date with full format - bin_date = datetime.strptime(date_str, "%A %d %B") - - # Add current year since it's not in the date string - current_year = datetime.now().year - bin_date = bin_date.replace(year=current_year) - - # If the date is in the past, it's probably for next year - if bin_date < datetime.now(): - bin_date = bin_date.replace(year=current_year + 1) - - collections.append((bin_type, bin_date)) - print( - f"Successfully parsed date for {bin_type}: {bin_date}" - ) - break - - except ValueError as e: - print( - f"Failed to parse date '{date_str}' for {bin_type}: {e}" - ) - continue + # Split multiple dates if comma-separated + date_strings = [date.strip() for date in date_text.split(",")] + + for date_str in date_strings: + try: + # Extract day number from date string (e.g. "2" from "Tuesday 27th May") + day_number = int("".join(filter(str.isdigit, date_str))) + # Replace ordinal in date string with plain number + date_str = date_str.replace( + get_date_with_ordinal(day_number), str(day_number) + ) + + # Parse date with full format + bin_date = datetime.strptime(date_str, "%A %d %B") + + # Add current year since it's not in the date string + current_year = datetime.now().year + bin_date = bin_date.replace(year=current_year) + + # If the date is in the past, it's probably for next year + if bin_date < datetime.now(): + bin_date = bin_date.replace(year=current_year + 1) + + collections.append((bin_type, bin_date)) + print( + f"Successfully parsed date for {bin_type}: {bin_date}" + ) + + except ValueError as e: + print( + f"Failed to parse date '{date_str}' for {bin_type}: {e}" + ) + continue + + # Found and processed the collection dates, so break the loop + break except Exception as e: print(f"Error processing bin row: {e}") From 48862a691aaf9c4494e93d1f6f270a3013cec368 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 May 2025 01:45:47 +0000 Subject: [PATCH 046/425] chore(deps-dev): bump setuptools from 78.1.0 to 78.1.1 Bumps [setuptools](https://github.com/pypa/setuptools) from 78.1.0 to 78.1.1. - [Release notes](https://github.com/pypa/setuptools/releases) - [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst) - [Commits](https://github.com/pypa/setuptools/compare/v78.1.0...v78.1.1) --- updated-dependencies: - dependency-name: setuptools dependency-version: 78.1.1 dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- poetry.lock | 149 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 131 insertions(+), 18 deletions(-) diff --git a/poetry.lock b/poetry.lock index b789298016..f8b58bae18 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "aiohttp" @@ -6,6 +6,7 @@ version = "3.9.1" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e1f80197f8b0b846a8d5cf7b7ec6084493950d0882cc5537fb7b96a69e3c8590"}, {file = "aiohttp-3.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72444d17777865734aa1a4d167794c34b63e5883abb90356a0364a28904e6c0"}, @@ -93,7 +94,7 @@ multidict = ">=4.5,<7.0" yarl = ">=1.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns", "brotlicffi"] +speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "brotlicffi ; platform_python_implementation != \"CPython\""] [[package]] name = "aiohttp-cors" @@ -101,6 +102,7 @@ version = "0.7.0" description = "CORS support for aiohttp" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "aiohttp-cors-0.7.0.tar.gz", hash = "sha256:4d39c6d7100fd9764ed1caf8cebf0eb01bf5e3f24e2e073fda6234bc48b19f5d"}, {file = "aiohttp_cors-0.7.0-py3-none-any.whl", hash = "sha256:0451ba59fdf6909d0e2cd21e4c0a43752bc0703d33fc78ae94d9d9321710193e"}, @@ -115,6 +117,7 @@ version = "0.3.0" description = "A faster URL dispatcher for aiohttp" optional = false python-versions = ">=3.8,<4.0" +groups = ["dev"] files = [ {file = "aiohttp_fast_url_dispatcher-0.3.0-py3-none-any.whl", hash = "sha256:e038458a34b79ef7c276b3257a1cdc73625da92cf4b64c2d0aefc4fe04dcdbbb"}, {file = "aiohttp_fast_url_dispatcher-0.3.0.tar.gz", hash = "sha256:0fc11c60a4209429340d9d2d07b6b0819a45ebd0d47ceb78bea915dbe042addd"}, @@ -129,6 +132,7 @@ version = "0.1.1" description = "Enable zlib_ng on aiohttp" optional = false python-versions = ">=3.8,<4.0" +groups = ["dev"] files = [ {file = "aiohttp_zlib_ng-0.1.1-py3-none-any.whl", hash = "sha256:05a4f69b1f02cb9a790c366e1417ed7a95e23a55123acc2216a47b8f9d27226f"}, {file = "aiohttp_zlib_ng-0.1.1.tar.gz", hash = "sha256:8f3a9a9b4e8bfa9c3cfa7fb619b1268d6e986a66e452de067a81017717bb6962"}, @@ -144,6 +148,7 @@ version = "1.3.2" description = "aiosignal: a list of registered asynchronous callbacks" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5"}, {file = "aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54"}, @@ -158,6 +163,7 @@ version = "4.9.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"}, {file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"}, @@ -170,7 +176,7 @@ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] -test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] +test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""] trio = ["trio (>=0.26.1)"] [[package]] @@ -179,6 +185,7 @@ version = "2.2" description = "Calculations for the position of the sun and moon." optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "astral-2.2-py2.py3-none-any.whl", hash = "sha256:b9ef70faf32e81a8ba174d21e8f29dc0b53b409ef035f27e0749ddc13cb5982a"}, {file = "astral-2.2.tar.gz", hash = "sha256:e41d9967d5c48be421346552f0f4dedad43ff39a83574f5ff2ad32b6627b6fbe"}, @@ -193,6 +200,7 @@ version = "3.3.9" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.9.0" +groups = ["dev"] files = [ {file = "astroid-3.3.9-py3-none-any.whl", hash = "sha256:d05bfd0acba96a7bd43e222828b7d9bc1e138aaeb0649707908d3702a9831248"}, {file = "astroid-3.3.9.tar.gz", hash = "sha256:622cc8e3048684aa42c820d9d218978021c3c3d174fb03a9f0d615921744f550"}, @@ -204,6 +212,7 @@ version = "1.4.1" description = "Atomic file writes." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["dev"] files = [ {file = "atomicwrites-homeassistant-1.4.1.tar.gz", hash = "sha256:256a672106f16745445228d966240b77b55f46a096d20305901a57aa5d1f4c2f"}, {file = "atomicwrites_homeassistant-1.4.1-py2.py3-none-any.whl", hash = "sha256:01457de800961db7d5b575f3c92e7fb56e435d88512c366afb0873f4f092bb0d"}, @@ -215,6 +224,7 @@ version = "23.1.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, @@ -225,7 +235,7 @@ cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] dev = ["attrs[docs,tests]", "pre-commit"] docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-no-zope = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.1.1) ; platform_python_implementation == \"CPython\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version < \"3.11\"", "pytest-xdist[psutil]"] [[package]] name = "awesomeversion" @@ -233,6 +243,7 @@ version = "23.11.0" description = "One version package to rule them all, One version package to find them, One version package to bring them all, and in the darkness bind them." optional = false python-versions = ">=3.8,<4.0" +groups = ["dev"] files = [ {file = "awesomeversion-23.11.0-py3-none-any.whl", hash = "sha256:16907305c091637e296966a70731345e70825fef1664fd1a72155ce83f2b6dc7"}, {file = "awesomeversion-23.11.0.tar.gz", hash = "sha256:9146329196f0f045887de6c195730750f8f7a9302d1c149378db73ab5dc468f0"}, @@ -244,6 +255,7 @@ version = "4.0.1" description = "Modern password hashing for your software and your servers" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "bcrypt-4.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b1023030aec778185a6c16cf70f359cbb6e0c289fd564a7cfa29e727a1c38f8f"}, {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:08d2947c490093a11416df18043c27abe3921558d2c03e2076ccb28a116cb6d0"}, @@ -278,6 +290,7 @@ version = "4.13.4" description = "Screen-scraping library" optional = false python-versions = ">=3.7.0" +groups = ["main"] files = [ {file = "beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b"}, {file = "beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195"}, @@ -300,6 +313,7 @@ version = "25.1.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"}, {file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"}, @@ -344,6 +358,7 @@ version = "0.0.2" description = "Dummy package for Beautiful Soup (beautifulsoup4)" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "bs4-0.0.2-py2.py3-none-any.whl", hash = "sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc"}, {file = "bs4-0.0.2.tar.gz", hash = "sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925"}, @@ -358,6 +373,7 @@ version = "2025.1.31" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, @@ -369,6 +385,7 @@ version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -438,6 +455,7 @@ files = [ {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] +markers = {main = "os_name == \"nt\" and implementation_name != \"pypy\""} [package.dependencies] pycparser = "*" @@ -448,6 +466,7 @@ version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, @@ -549,6 +568,7 @@ version = "2.3.0" description = "Fast ISO8601 date time parser for Python written in C" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "ciso8601-2.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8f884d6a0b7384f8b1c57f740196988dd1229242c1be7c30a75424725590e0b3"}, {file = "ciso8601-2.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58517dfe06c30ad65fb1b4e9de66ccb72752d79bc71d7b7d26cbc0d008b7265a"}, @@ -603,6 +623,7 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -617,6 +638,8 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +markers = "platform_system == \"Windows\" or sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -628,6 +651,7 @@ version = "7.8.0" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "coverage-7.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2931f66991175369859b5fd58529cd4b73582461877ecfd859b6549869287ffe"}, {file = "coverage-7.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52a523153c568d2c0ef8826f6cc23031dc86cffb8c6aeab92c4ff776e7951b28"}, @@ -695,7 +719,7 @@ files = [ ] [package.extras] -toml = ["tomli"] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "cryptography" @@ -703,6 +727,7 @@ version = "41.0.7" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"}, {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"}, @@ -748,6 +773,7 @@ version = "0.4.0" description = "serialize all of Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049"}, {file = "dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0"}, @@ -763,6 +789,7 @@ version = "2.1.1" description = "execnet: rapid multi-Python deployment" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, @@ -777,6 +804,7 @@ version = "7.2.0" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "flake8-7.2.0-py2.py3-none-any.whl", hash = "sha256:93b92ba5bdb60754a6da14fa3b93a9361fd00a59632ada61fd7b130436c40343"}, {file = "flake8-7.2.0.tar.gz", hash = "sha256:fa558ae3f6f7dbf2b4f22663e5343b6b6023620461f8d4ff2019ef4b5ee70426"}, @@ -793,6 +821,7 @@ version = "1.5.1" description = "Let your Python tests travel through time" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1"}, {file = "freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9"}, @@ -807,6 +836,7 @@ version = "1.6.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "frozenlist-1.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e6e558ea1e47fd6fa8ac9ccdad403e5dd5ecc6ed8dda94343056fa4277d5c65e"}, {file = "frozenlist-1.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f4b3cd7334a4bbc0c472164f3744562cb72d05002cc6fcf58adb104630bbc352"}, @@ -920,6 +950,7 @@ version = "1.0.1" description = "Geographic pandas extensions" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "geopandas-1.0.1-py3-none-any.whl", hash = "sha256:01e147d9420cc374d26f51fc23716ac307f32b49406e4bd8462c07e82ed1d3d6"}, {file = "geopandas-1.0.1.tar.gz", hash = "sha256:b8bf70a5534588205b7a56646e2082fb1de9a03599651b3d80c99ea4c2ca08ab"}, @@ -943,6 +974,7 @@ version = "29.0.0" description = "Gherkin parser (official, by Cucumber team)" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "gherkin_official-29.0.0-py3-none-any.whl", hash = "sha256:26967b0d537a302119066742669e0e8b663e632769330be675457ae993e1d1bc"}, {file = "gherkin_official-29.0.0.tar.gz", hash = "sha256:dbea32561158f02280d7579d179b019160d072ce083197625e2f80a6776bb9eb"}, @@ -954,6 +986,7 @@ version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, @@ -965,6 +998,7 @@ version = "0.70" description = "Open World Holidays Framework" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "holidays-0.70-py3-none-any.whl", hash = "sha256:a9306aa836393d1a30bf750774825652dd83f399207617c6ae66577e7e63183f"}, {file = "holidays-0.70.tar.gz", hash = "sha256:98b6bd583406e9efcc3a10a33d6b38744719a360c4f2541931a284dff034f89e"}, @@ -979,6 +1013,7 @@ version = "1.10.4" description = "Home Assistant Bluetooth Models and Helpers" optional = false python-versions = ">=3.9,<4.0" +groups = ["dev"] files = [ {file = "home_assistant_bluetooth-1.10.4-cp310-cp310-manylinux_2_31_x86_64.whl", hash = "sha256:7c3434bdec5dcfe733d3e7c56d4a24418fcd03718dc2e7707c9133d1e48145a8"}, {file = "home_assistant_bluetooth-1.10.4.tar.gz", hash = "sha256:21216b6be9d028bc232b9188ac4dce773798c6b4e47482cc3524bfc5f82515e3"}, @@ -990,6 +1025,7 @@ version = "2023.12.4" description = "Open-source home automation platform running on Python 3." optional = false python-versions = ">=3.11.0" +groups = ["dev"] files = [ {file = "homeassistant-2023.12.4-py3-none-any.whl", hash = "sha256:0a521e712d126a88febe747cd4215838774c0954a518c8c314d0fde5082834be"}, {file = "homeassistant-2023.12.4.tar.gz", hash = "sha256:75e6b43da702cc25a1321da0c3f915247c180a84fbcc9e76a9341b1e6a9cc14f"}, @@ -1033,6 +1069,7 @@ version = "0.18.0" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "httpcore-0.18.0-py3-none-any.whl", hash = "sha256:adc5398ee0a476567bf87467063ee63584a8bce86078bf748e48754f60202ced"}, {file = "httpcore-0.18.0.tar.gz", hash = "sha256:13b5e5cd1dca1a6636a6aaea212b19f4f85cd88c366a2b82304181b769aab3c9"}, @@ -1054,6 +1091,7 @@ version = "0.25.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "httpx-0.25.0-py3-none-any.whl", hash = "sha256:181ea7f8ba3a82578be86ef4171554dd45fec26a02556a744db029a0a27b7100"}, {file = "httpx-0.25.0.tar.gz", hash = "sha256:47ecda285389cb32bb2691cc6e069e3ab0205956f681c5b2ad2325719751d875"}, @@ -1066,7 +1104,7 @@ idna = "*" sniffio = "*" [package.extras] -brotli = ["brotli", "brotlicffi"] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -1077,6 +1115,7 @@ version = "6.1.3" description = "iCalendar parser/generator" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "icalendar-6.1.3-py3-none-any.whl", hash = "sha256:11eb5d21a1a9e119a6efc0f9e38f2cf1622ef97777cd25fb0dd7074d393a2a8d"}, {file = "icalendar-6.1.3.tar.gz", hash = "sha256:4aef710ff205925b3947fe69ed00f4e142c6a49b5533fca6cc2fdde5a6f62e66"}, @@ -1095,6 +1134,7 @@ version = "0.2.1" description = "Simple Python 3 library to download, parse and query iCal sources." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "icalevents-0.2.1-py3-none-any.whl", hash = "sha256:c5186fea094a56d97ba01c805f6338875e299e7ebc4bc6bf8068399fbb7e5724"}, {file = "icalevents-0.2.1.tar.gz", hash = "sha256:b7f2827b581269d4315b345b54cf7d661b4453a7b190f7dbef99e5ba8f5669e1"}, @@ -1112,6 +1152,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -1126,6 +1167,7 @@ version = "0.2.0" description = "Cross-platform network interface and IP address enumeration library" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "ifaddr-0.2.0-py3-none-any.whl", hash = "sha256:085e0305cfe6f16ab12d72e2024030f5d52674afad6911bb1eee207177b8a748"}, {file = "ifaddr-0.2.0.tar.gz", hash = "sha256:cc0cbfcaabf765d44595825fb96a99bb12c79716b73b44330ea38ee2b0c4aed4"}, @@ -1137,6 +1179,7 @@ version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, @@ -1148,6 +1191,7 @@ version = "6.0.1" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.9.0" +groups = ["dev"] files = [ {file = "isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615"}, {file = "isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450"}, @@ -1163,6 +1207,7 @@ version = "3.1.2" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, @@ -1180,6 +1225,7 @@ version = "4.23.0" description = "An implementation of JSON Schema validation for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"}, @@ -1201,6 +1247,7 @@ version = "2024.10.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf"}, {file = "jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272"}, @@ -1215,6 +1262,7 @@ version = "1.2.0" description = "An Dict like LRU container." optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "lru-dict-1.2.0.tar.gz", hash = "sha256:13c56782f19d68ddf4d8db0170041192859616514c706b126d0df2ec72a11bd7"}, {file = "lru_dict-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:de906e5486b5c053d15b7731583c25e3c9147c288ac8152a6d1f9bccdec72641"}, @@ -1309,6 +1357,7 @@ version = "5.3.2" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "lxml-5.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c4b84d6b580a9625dfa47269bf1fd7fbba7ad69e08b16366a46acb005959c395"}, {file = "lxml-5.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4c08ecb26e4270a62f81f81899dfff91623d349e433b126931c9c4577169666"}, @@ -1463,6 +1512,7 @@ version = "1.3.10" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59"}, {file = "mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28"}, @@ -1482,6 +1532,7 @@ version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -1552,6 +1603,7 @@ version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, @@ -1563,6 +1615,7 @@ version = "6.4.3" description = "multidict implementation" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "multidict-6.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32a998bd8a64ca48616eac5a8c1cc4fa38fb244a3facf2eeb14abe186e0f6cc5"}, {file = "multidict-6.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a54ec568f1fc7f3c313c2f3b16e5db346bf3660e1309746e7fccbbfded856188"}, @@ -1676,6 +1729,7 @@ version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, @@ -1687,6 +1741,7 @@ version = "2.2.4" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.10" +groups = ["main", "dev"] files = [ {file = "numpy-2.2.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8146f3550d627252269ac42ae660281d673eb6f8b32f113538e0cc2a9aed42b9"}, {file = "numpy-2.2.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e642d86b8f956098b564a45e6f6ce68a22c2c97a04f5acd3f221f57b8cb850ae"}, @@ -1751,6 +1806,7 @@ version = "3.9.9" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "orjson-3.9.9-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f28090060a31f4d11221f9ba48b2273b0d04b702f4dcaa197c38c64ce639cc51"}, {file = "orjson-3.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8038ba245d0c0a6337cfb6747ea0c51fe18b0cf1a4bc943d530fd66799fae33d"}, @@ -1810,6 +1866,7 @@ version = "1.3.0.post0" description = "Capture the outcome of Python function calls." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b"}, {file = "outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8"}, @@ -1824,6 +1881,7 @@ version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, @@ -1835,6 +1893,7 @@ version = "2.2.3" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, @@ -1917,6 +1976,7 @@ version = "1.20.2" description = "parse() is the opposite of format()" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "parse-1.20.2-py2.py3-none-any.whl", hash = "sha256:967095588cb802add9177d0c0b6133b5ba33b1ea9007ca800e526f42a85af558"}, {file = "parse-1.20.2.tar.gz", hash = "sha256:b41d604d16503c79d81af5165155c0b20f6c8d6c559efa66b4b695c3e5a0a0ce"}, @@ -1928,6 +1988,7 @@ version = "0.6.4" description = "Simplifies to build parse types based on the parse module" optional = false python-versions = "!=3.0.*,!=3.1.*,>=2.7" +groups = ["dev"] files = [ {file = "parse_type-0.6.4-py2.py3-none-any.whl", hash = "sha256:83d41144a82d6b8541127bf212dd76c7f01baff680b498ce8a4d052a7a5bce4c"}, {file = "parse_type-0.6.4.tar.gz", hash = "sha256:5e1ec10440b000c3f818006033372939e693a9ec0176f446d9303e4db88489a6"}, @@ -1938,9 +1999,9 @@ parse = {version = ">=1.18.0", markers = "python_version >= \"3.0\""} six = ">=1.15" [package.extras] -develop = ["build (>=0.5.1)", "coverage (>=4.4)", "pylint", "pytest (<5.0)", "pytest (>=5.0)", "pytest-cov", "pytest-html (>=1.19.0)", "ruff", "setuptools", "setuptools-scm", "tox (>=2.8,<4.0)", "twine (>=1.13.0)", "virtualenv (<20.22.0)", "virtualenv (>=20.0.0)", "wheel"] +develop = ["build (>=0.5.1)", "coverage (>=4.4)", "pylint", "pytest (<5.0) ; python_version < \"3.0\"", "pytest (>=5.0) ; python_version >= \"3.0\"", "pytest-cov", "pytest-html (>=1.19.0)", "ruff ; python_version >= \"3.7\"", "setuptools", "setuptools-scm", "tox (>=2.8,<4.0)", "twine (>=1.13.0)", "virtualenv (<20.22.0) ; python_version <= \"3.6\"", "virtualenv (>=20.0.0) ; python_version > \"3.6\"", "wheel"] docs = ["Sphinx (>=1.6)", "sphinx-bootstrap-theme (>=0.6.0)"] -testing = ["pytest (<5.0)", "pytest (>=5.0)", "pytest-html (>=1.19.0)"] +testing = ["pytest (<5.0) ; python_version < \"3.0\"", "pytest (>=5.0) ; python_version >= \"3.0\"", "pytest-html (>=1.19.0)"] [[package]] name = "pathspec" @@ -1948,6 +2009,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -1959,6 +2021,7 @@ version = "25.0.1" description = "The PyPA recommended tool for installing Python packages." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pip-25.0.1-py3-none-any.whl", hash = "sha256:c46efd13b6aa8279f33f2864459c8ce587ea6a1a59ee20de055868d8f7688f7f"}, {file = "pip-25.0.1.tar.gz", hash = "sha256:88f96547ea48b940a3a385494e181e29fb8637898f88d88737c5049780f196ea"}, @@ -1970,6 +2033,7 @@ version = "4.3.7" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94"}, {file = "platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351"}, @@ -1986,6 +2050,7 @@ version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -2001,6 +2066,7 @@ version = "7.0.0" description = "Cross-platform lib for process and system monitoring in Python. NOTE: the syntax of this script MUST be kept compatible with Python 2.7." optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25"}, {file = "psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da"}, @@ -2024,6 +2090,7 @@ version = "2.13.0" description = "Python style guide checker" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pycodestyle-2.13.0-py2.py3-none-any.whl", hash = "sha256:35863c5974a271c7a726ed228a14a4f6daf49df369d8c50cd9a6f58a5e143ba9"}, {file = "pycodestyle-2.13.0.tar.gz", hash = "sha256:c8415bf09abe81d9c7f872502a6eee881fbe85d8763dd5b9924bb0a01d67efae"}, @@ -2035,10 +2102,12 @@ version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] +markers = {main = "os_name == \"nt\" and implementation_name != \"pypy\""} [[package]] name = "pyflakes" @@ -2046,6 +2115,7 @@ version = "3.3.2" description = "passive checker of Python programs" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pyflakes-3.3.2-py2.py3-none-any.whl", hash = "sha256:5039c8339cbb1944045f4ee5466908906180f13cc99cc9949348d10f82a5c32a"}, {file = "pyflakes-3.3.2.tar.gz", hash = "sha256:6dfd61d87b97fba5dcfaaf781171ac16be16453be6d816147989e7f6e6a9576b"}, @@ -2057,6 +2127,7 @@ version = "2.1.0" description = "Hamcrest framework for matcher objects" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "pyhamcrest-2.1.0-py3-none-any.whl", hash = "sha256:f6913d2f392e30e0375b3ecbd7aee79e5d1faa25d345c8f4ff597665dcac2587"}, {file = "pyhamcrest-2.1.0.tar.gz", hash = "sha256:c6acbec0923d0cb7e72c22af1926f3e7c97b8e8d69fc7498eabacaf7c975bd9c"}, @@ -2065,7 +2136,7 @@ files = [ [package.extras] dev = ["black", "doc2dash", "flake8", "pyhamcrest[docs,tests]", "pytest-mypy", "towncrier", "tox", "tox-asdf", "twine"] docs = ["alabaster (>=0.7,<1.0)", "sphinx (>=4.0,<5.0)"] -tests = ["coverage[toml]", "dataclasses", "mypy (!=0.940)", "pytest (>=5.0)", "pytest-mypy-plugins", "pytest-sugar", "pytest-xdist", "pyyaml", "types-dataclasses", "types-mock"] +tests = ["coverage[toml]", "dataclasses ; python_version < \"3.7\"", "mypy (!=0.940) ; platform_python_implementation != \"PyPy\"", "pytest (>=5.0)", "pytest-mypy-plugins ; platform_python_implementation != \"PyPy\"", "pytest-sugar", "pytest-xdist", "pyyaml", "types-dataclasses ; python_version < \"3.7\"", "types-mock"] tests-numpy = ["numpy", "pyhamcrest[tests]"] [[package]] @@ -2074,6 +2145,7 @@ version = "2.8.0" description = "JSON Web Token implementation in Python" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, @@ -2091,6 +2163,7 @@ version = "3.3.6" description = "python code static checker" optional = false python-versions = ">=3.9.0" +groups = ["dev"] files = [ {file = "pylint-3.3.6-py3-none-any.whl", hash = "sha256:8b7c2d3e86ae3f94fb27703d521dd0b9b6b378775991f504d7c3a6275aa0a6a6"}, {file = "pylint-3.3.6.tar.gz", hash = "sha256:b634a041aac33706d56a0d217e6587228c66427e20ec21a019bc4cdee48c040a"}, @@ -2115,6 +2188,7 @@ version = "0.10.0" description = "Vectorized spatial vector file format I/O using GDAL/OGR" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pyogrio-0.10.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:046eeeae12a03a3ebc3dc5ff5a87664e4f5fc0a4fb1ea5d5c45d547fa941072b"}, {file = "pyogrio-0.10.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:44380f4d9245c776f432526e29ce4d29238aea26adad991803c4f453474f51d3"}, @@ -2166,6 +2240,7 @@ version = "23.2.0" description = "Python wrapper module around the OpenSSL library" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "pyOpenSSL-23.2.0-py3-none-any.whl", hash = "sha256:24f0dc5227396b3e831f4c7f602b950a5e9833d292c8e4a2e06b709292806ae2"}, {file = "pyOpenSSL-23.2.0.tar.gz", hash = "sha256:276f931f55a452e7dea69c7173e984eb2a4407ce413c918aa34b55f82f9b8bac"}, @@ -2184,6 +2259,7 @@ version = "3.7.1" description = "Python interface to PROJ (cartographic projections and coordinate transformations library)" optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "pyproj-3.7.1-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:bf09dbeb333c34e9c546364e7df1ff40474f9fddf9e70657ecb0e4f670ff0b0e"}, {file = "pyproj-3.7.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:6575b2e53cc9e3e461ad6f0692a5564b96e7782c28631c7771c668770915e169"}, @@ -2229,6 +2305,7 @@ version = "1.7.1" description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main"] files = [ {file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"}, {file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"}, @@ -2241,6 +2318,7 @@ version = "8.3.5" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"}, {file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"}, @@ -2261,6 +2339,7 @@ version = "0.24.0" description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b"}, {file = "pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276"}, @@ -2279,6 +2358,7 @@ version = "8.1.0" description = "BDD for pytest" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pytest_bdd-8.1.0-py3-none-any.whl", hash = "sha256:2124051e71a05ad7db15296e39013593f72ebf96796e1b023a40e5453c47e5fb"}, {file = "pytest_bdd-8.1.0.tar.gz", hash = "sha256:ef0896c5cd58816dc49810e8ff1d632f4a12019fb3e49959b2d349ffc1c9bfb5"}, @@ -2299,6 +2379,7 @@ version = "0.4.9" description = "Pytest plugin providing a fixture interface for spulec/freezegun" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "pytest_freezer-0.4.9-py3-none-any.whl", hash = "sha256:8b6c50523b7d4aec4590b52bfa5ff766d772ce506e2bf4846c88041ea9ccae59"}, {file = "pytest_freezer-0.4.9.tar.gz", hash = "sha256:21bf16bc9cc46bf98f94382c4b5c3c389be7056ff0be33029111ae11b3f1c82a"}, @@ -2314,6 +2395,7 @@ version = "3.6.1" description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"}, {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"}, @@ -2335,6 +2417,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -2349,6 +2432,7 @@ version = "1.1.0" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d"}, {file = "python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5"}, @@ -2363,6 +2447,7 @@ version = "4.0.1" description = "A Python Slugify application that handles Unicode" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["dev"] files = [ {file = "python-slugify-4.0.1.tar.gz", hash = "sha256:69a517766e00c1268e5bbfc0d010a0a8508de0b18d30ad5a1ff357f8ae724270"}, ] @@ -2379,6 +2464,7 @@ version = "2025.2" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" +groups = ["main", "dev"] files = [ {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, @@ -2390,6 +2476,7 @@ version = "6.0.1" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, @@ -2450,6 +2537,7 @@ version = "0.36.2" description = "JSON Referencing + Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0"}, {file = "referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa"}, @@ -2466,6 +2554,7 @@ version = "2.31.0" description = "Python HTTP for Humans." optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, @@ -2487,6 +2576,7 @@ version = "0.24.0" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "rpds_py-0.24.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:006f4342fe729a368c6df36578d7a348c7c716be1da0a1a0f86e3021f8e98724"}, {file = "rpds_py-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2d53747da70a4e4b17f559569d5f9506420966083a31c5fbd84e764461c4444b"}, @@ -2610,6 +2700,7 @@ version = "4.31.0" description = "Official Python bindings for Selenium WebDriver" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "selenium-4.31.0-py3-none-any.whl", hash = "sha256:7b8b8d5e424d7133cb7aa656263b19ac505ec26d65c0f921a696e7e2c5ccd95b"}, {file = "selenium-4.31.0.tar.gz", hash = "sha256:441cffc436a2e6659fe3cfb012692435652efd38b0d368d16f661a5db47825f5"}, @@ -2625,23 +2716,24 @@ websocket-client = ">=1.8,<2.0" [[package]] name = "setuptools" -version = "78.1.0" +version = "78.1.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "setuptools-78.1.0-py3-none-any.whl", hash = "sha256:3e386e96793c8702ae83d17b853fb93d3e09ef82ec62722e61da5cd22376dcd8"}, - {file = "setuptools-78.1.0.tar.gz", hash = "sha256:18fd474d4a82a5f83dac888df697af65afa82dec7323d09c3e37d1f14288da54"}, + {file = "setuptools-78.1.1-py3-none-any.whl", hash = "sha256:c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561"}, + {file = "setuptools-78.1.1.tar.gz", hash = "sha256:fcc17fd9cd898242f6b4adfaca46137a9edef687f43e6f78469692a5e70d851d"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] -core = ["importlib_metadata (>=6)", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] enabler = ["pytest-enabler (>=2.2)"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] -type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] [[package]] name = "shapely" @@ -2649,6 +2741,7 @@ version = "2.1.0" description = "Manipulation and analysis of geometric objects" optional = false python-versions = ">=3.10" +groups = ["dev"] files = [ {file = "shapely-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d3e5c5e3864d4dc431dd85a8e5137ebd39c8ac287b009d3fa80a07017b29c940"}, {file = "shapely-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6eea89b16f5f3a064659126455d23fa3066bc3d6cd385c35214f06bf5871aa6"}, @@ -2706,6 +2799,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -2717,6 +2811,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -2728,6 +2823,7 @@ version = "2.4.0" description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, @@ -2739,6 +2835,7 @@ version = "2.6" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, @@ -2750,6 +2847,7 @@ version = "0.9.0" description = "Pretty-print tabular data" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, @@ -2764,6 +2862,7 @@ version = "1.3" description = "The most basic Text::Unidecode port" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, @@ -2775,6 +2874,7 @@ version = "0.13.2" description = "Style preserving TOML library" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, @@ -2786,6 +2886,7 @@ version = "0.24.0" description = "A friendly Python library for async concurrency and I/O" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "trio-0.24.0-py3-none-any.whl", hash = "sha256:c3bd3a4e3e3025cd9a2241eae75637c43fe0b9e88b4c97b9161a55b9e54cd72c"}, {file = "trio-0.24.0.tar.gz", hash = "sha256:ffa09a74a6bf81b84f8613909fb0beaee84757450183a7a2e0b47b455c0cac5d"}, @@ -2805,6 +2906,7 @@ version = "0.12.2" description = "WebSocket library for Trio" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "trio_websocket-0.12.2-py3-none-any.whl", hash = "sha256:df605665f1db533f4a386c94525870851096a223adcb97f72a07e8b4beba45b6"}, {file = "trio_websocket-0.12.2.tar.gz", hash = "sha256:22c72c436f3d1e264d0910a3951934798dcc5b00ae56fc4ee079d46c7cf20fae"}, @@ -2821,6 +2923,7 @@ version = "4.13.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, @@ -2832,6 +2935,7 @@ version = "2025.2" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" +groups = ["main", "dev"] files = [ {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}, {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, @@ -2843,6 +2947,7 @@ version = "0.9.0" description = "Create and transform ULIDs" optional = false python-versions = ">=3.10,<4.0" +groups = ["dev"] files = [ {file = "ulid_transform-0.9.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:4689679e3c42f23147ba9ff404e7fdb935ca1063d57971f8abb87e2c2deabfc5"}, {file = "ulid_transform-0.9.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6123274882adc412f81f70f668dfee472588016679729a3e77627156d5a363f"}, @@ -2879,6 +2984,7 @@ version = "2.4.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813"}, {file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"}, @@ -2888,7 +2994,7 @@ files = [ pysocks = {version = ">=1.5.6,<1.5.7 || >1.5.7,<2.0", optional = true, markers = "extra == \"socks\""} [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -2899,6 +3005,7 @@ version = "0.13.1" description = "" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "voluptuous-0.13.1-py3-none-any.whl", hash = "sha256:4b838b185f5951f2d6e8752b68fcf18bd7a9c26ded8f143f92d6d28f3921a3e6"}, {file = "voluptuous-0.13.1.tar.gz", hash = "sha256:e8d31c20601d6773cb14d4c0f42aee29c6821bbd1018039aac7ac5605b489723"}, @@ -2910,6 +3017,7 @@ version = "2.6.0" description = "Convert voluptuous schemas to dictionaries" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "voluptuous-serialize-2.6.0.tar.gz", hash = "sha256:79acdc58239582a393144402d827fa8efd6df0f5350cdc606d9242f6f9bca7c4"}, {file = "voluptuous_serialize-2.6.0-py3-none-any.whl", hash = "sha256:85a5c8d4d829cb49186c1b5396a8a517413cc5938e1bb0e374350190cd139616"}, @@ -2924,6 +3032,7 @@ version = "4.0.2" description = "Library provides the way to automatically manage drivers for different browsers" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "webdriver_manager-4.0.2-py2.py3-none-any.whl", hash = "sha256:75908d92ecc45ff2b9953614459c633db8f9aa1ff30181cefe8696e312908129"}, {file = "webdriver_manager-4.0.2.tar.gz", hash = "sha256:efedf428f92fd6d5c924a0d054e6d1322dd77aab790e834ee767af392b35590f"}, @@ -2940,6 +3049,7 @@ version = "1.8.0" description = "WebSocket client for Python with low level API options" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526"}, {file = "websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da"}, @@ -2956,6 +3066,7 @@ version = "1.2.0" description = "WebSockets state-machine based protocol implementation" optional = false python-versions = ">=3.7.0" +groups = ["main"] files = [ {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, @@ -2970,6 +3081,7 @@ version = "1.9.2" description = "Yet another URL library" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"}, {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"}, @@ -3057,6 +3169,7 @@ version = "0.5.1" description = "Drop-in replacement for zlib and gzip modules using zlib-ng" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "zlib_ng-0.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ea0b07ff83e253d83e25113fc81f695b9161882de3a65d547ab96f394cf03f5c"}, {file = "zlib_ng-0.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1be5a5513876cd0a071bbb0fc333eb00bc9c25399f2b863e329dfe6ac4cf6455"}, @@ -3118,6 +3231,6 @@ files = [ ] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = ">=3.12,<3.14" content-hash = "c4003fb0b9746e04aaac0103a98db6dd2e2227172ec4018822dde2f178c62a44" From 5ac271124573a47fac37efd0ab7894f7a2df118a Mon Sep 17 00:00:00 2001 From: David Amor Date: Tue, 20 May 2025 11:00:58 +0100 Subject: [PATCH 047/425] chore: removed an accidental print --- uk_bin_collection/uk_bin_collection/councils/BradfordMDC.py | 1 - 1 file changed, 1 deletion(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/BradfordMDC.py b/uk_bin_collection/uk_bin_collection/councils/BradfordMDC.py index 3c516392d0..51159b51a5 100644 --- a/uk_bin_collection/uk_bin_collection/councils/BradfordMDC.py +++ b/uk_bin_collection/uk_bin_collection/councils/BradfordMDC.py @@ -106,7 +106,6 @@ def parse_data(self, page: str, **kwargs) -> dict: "%a %b %d %Y", ).strftime(date_format), } - print(dict_data) data["bins"].append(dict_data) for bin in soup.find_all(attrs={"id": re.compile(r"CTID-d3gapLk-\d+-A")}): dict_data = { From 8329b2eeba1f33e7d3e474b8d0b4f883d7070aea Mon Sep 17 00:00:00 2001 From: David Amor Date: Wed, 21 May 2025 19:02:11 +0100 Subject: [PATCH 048/425] fix: NorthHertfordshire selenium script --- uk_bin_collection/tests/input.json | 4 +- .../NorthHertfordshireDistrictCouncil.py | 320 ++++++++++++++---- 2 files changed, 260 insertions(+), 64 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index 36a8e4cf5f..27016312a1 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -1693,9 +1693,10 @@ "LAD24CD": "E06000012" }, "NorthHertfordshireDistrictCouncil": { - "house_number": "2", + "house_number": "22", "postcode": "SG6 4BJ", "url": "https://www.north-herts.gov.uk", + "web_driver": "http://selenium:4444", "wiki_name": "North Hertfordshire", "wiki_note": "Pass the house number and postcode in their respective parameters.", "LAD24CD": "E07000099" @@ -2180,6 +2181,7 @@ }, "SouthRibbleCouncil": { "uprn": "010013246384", + "postcode": "PR5 6DT", "url": "https://www.southribble.gov.uk", "wiki_command_url_override": "https://www.southribble.gov.uk", "wiki_name": "South Ribble", diff --git a/uk_bin_collection/uk_bin_collection/councils/NorthHertfordshireDistrictCouncil.py b/uk_bin_collection/uk_bin_collection/councils/NorthHertfordshireDistrictCouncil.py index 8517f8c825..b2975c267c 100644 --- a/uk_bin_collection/uk_bin_collection/councils/NorthHertfordshireDistrictCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/NorthHertfordshireDistrictCouncil.py @@ -1,93 +1,287 @@ -import requests +# direct URL works, but includes a token, so I'm using Selenium +# https://waste.nc.north-herts.gov.uk/w/webpage/find-bin-collection-day-show-details?webpage_token=c7c7c3cbc2f0478735fc746ca985b8f4221dea31c24dde99e39fb1c556b07788&auth=YTc5YTAwZmUyMGQ3&id=1421457 + +import re +import time +from datetime import datetime + from bs4 import BeautifulSoup +from dateutil.parser import parse +from selenium.common.exceptions import NoSuchElementException, TimeoutException +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import Select +from selenium.webdriver.support.wait import WebDriverWait from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass -# import the wonderful Beautiful Soup and the URL grabber class CouncilClass(AbstractGetBinDataClass): - """ - Concrete classes have to implement all abstract operations of the - base class. They can also override some operations with a default - implementation. - """ def parse_data(self, page: str, **kwargs) -> dict: + driver = None + try: + data = {"bins": []} + + user_paon = kwargs.get("paon") + postcode = kwargs.get("postcode") + web_driver = kwargs.get("web_driver") + headless = kwargs.get("headless") + url = "https://waste.nc.north-herts.gov.uk/w/webpage/find-bin-collection-day-input-address" + + driver = create_webdriver(web_driver, headless, None, __name__) + driver.get(url) + + WebDriverWait(driver, 10).until( + lambda d: d.execute_script("return document.readyState") == "complete" + ) + + # Define the wait variable + wait = WebDriverWait( + driver, 20 + ) # Create the wait object with a 20-second timeout + + # Enter postcode - try different approaches for reliability + # print("Looking for postcode input...") + + postcode_input = wait.until( + EC.element_to_be_clickable( + ( + By.CSS_SELECTOR, + "input.relation_path_type_ahead_search.form-control", + ) + ), + message="Postcode input not found by class", + ) + postcode_input.clear() + postcode_input.send_keys(postcode) + # print(f"Entered postcode: {postcode}") + + # Wait for the dropdown to load + # print("Waiting for address list to populate...") + try: + # Wait for the results to appear + wait.until( + EC.presence_of_element_located( + (By.CSS_SELECTOR, ".relation_path_type_ahead_results_holder") + ), + message="Address results container not found", + ) + + # Wait for list items to appear + wait.until( + EC.presence_of_all_elements_located( + (By.CSS_SELECTOR, ".relation_path_type_ahead_results_holder li") + ), + message="No address items found in the list", + ) + # print("Address list populated successfully") + + # Search for user_paon in the address list using aria-label attribute + try: + # Use XPath to look for aria-label containing user_paon + address_xpath = ( + f"//li[@aria-label and contains(@aria-label, '{user_paon}')]" + ) + matching_address = wait.until( + EC.element_to_be_clickable((By.XPATH, address_xpath)), + message=f"No address containing '{user_paon}' found in aria-label attributes", + ) + # print(f"Found matching address: {matching_address.get_attribute('aria-label')}") + matching_address.click() + # print("Clicked on matching address") + + # Allow time for the selection to take effect + time.sleep(2) + + # Find and click the "Select address and continue" button + continue_button = wait.until( + EC.element_to_be_clickable( + ( + By.CSS_SELECTOR, + "input.btn.bg-green[value='Select address and continue']", + ) + ), + message="Could not find 'Select address and continue' button", + ) + # print("Found 'Select address and continue' button, clicking it...") + continue_button.click() + # print("Clicked on 'Select address and continue' button") + + # Allow time for the page to load after clicking the button + time.sleep(3) + except TimeoutException as e: + # print(f"Error finding address: {e}") + raise + except TimeoutException as e: + # print(f"Error loading address list: {e}") + raise + + # After pressing Next button and waiting for page to load + # print("Looking for schedule list...") + + # Wait for the page to load - giving it extra time + time.sleep(5) + + # Use only the selector that we know works + # print("Looking for bin type elements...") + try: + bin_type_selector = ( + By.CSS_SELECTOR, + "div.formatting_bold.formatting_size_bigger.formatting span.value-as-text", + ) + WebDriverWait(driver, 15).until( + EC.presence_of_element_located(bin_type_selector) + ) + # print(f"Found bin type elements with selector: {bin_type_selector}") + except TimeoutException: + # print("Could not find bin type elements. Taking screenshot for debugging...") + screenshot_path = f"bin_type_error_{int(time.time())}.png" + driver.save_screenshot(screenshot_path) + # print(f"Screenshot saved to {screenshot_path}") + + # Create BS4 object from driver's page source + # print("Parsing page with BeautifulSoup...") + soup = BeautifulSoup(driver.page_source, features="html.parser") + + # Initialize data dictionary + data = {"bins": []} + + # Looking for bin types in the exact HTML structure + bin_type_elements = soup.select( + "div.formatting_bold.formatting_size_bigger.formatting span.value-as-text" + ) + # print(f"Found {len(bin_type_elements)} bin type elements") + + # Look specifically for date elements with the exact structure + date_elements = soup.select("div.col-sm-12.font-xs-3xl span.value-as-text") + hidden_dates = soup.select( + "div.col-sm-12.font-xs-3xl input[type='hidden'][value*='/']" + ) + + # print(f"Found {len(bin_type_elements)} bin types and {len(date_elements)} date elements") + + # We need a smarter way to match bin types with their dates + bin_count = 0 + + # Map of bin types to their collection dates + bin_date_map = {} + + # Extract all date strings that look like actual dates + date_texts = [] + date_pattern = re.compile( + r"(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)\s+\d+(?:st|nd|rd|th)?\s+\w+\s+\d{4}", + re.IGNORECASE, + ) - user_postcode = kwargs.get("postcode") - user_paon = kwargs.get("paon") - check_postcode(user_postcode) - check_paon(user_paon) - bindata = {"bins": []} + for element in date_elements: + text = element.get_text(strip=True) + if date_pattern.search(text): + date_texts.append(text) + # print(f"Found valid date text: {text}") - URI = "https://uhtn-wrp.whitespacews.com/" + # Find hidden date inputs with values in DD/MM/YYYY format + hidden_date_values = [] + for hidden in hidden_dates: + value = hidden.get("value", "") + if re.match(r"\d{1,2}/\d{1,2}/\d{4}", value): + hidden_date_values.append(value) + # print(f"Found hidden date value: {value}") - session = requests.Session() + # When filtering date elements + date_elements = soup.select("div.col-sm-12.font-xs-3xl span.value-as-text") + valid_date_elements = [] - # get link from first page as has some kind of unique hash - r = session.get( - URI, - ) - r.raise_for_status() - soup = BeautifulSoup(r.text, features="html.parser") + for element in date_elements: + text = element.get_text(strip=True) + if contains_date(text): + valid_date_elements.append(element) + # print(f"Found valid date element: {text}") + else: + pass + # print(f"Skipping non-date element: {text}") - alink = soup.find("a", text="Find my bin collection day") + # print(f"Found {len(bin_type_elements)} bin types and {len(valid_date_elements)} valid date elements") - if alink is None: - raise Exception("Initial page did not load correctly") + # When processing each bin type + for i, bin_type_elem in enumerate(bin_type_elements): + bin_type = bin_type_elem.get_text(strip=True) - # greplace 'seq' query string to skip next step - nextpageurl = alink["href"].replace("seq=1", "seq=2") + # Try to find a date for this bin type + date_text = None - data = { - "address_name_number": user_paon, - "address_postcode": user_postcode, - } + # Look for a valid date element + if i < len(valid_date_elements): + date_elem = valid_date_elements[i] + date_text = date_elem.get_text(strip=True) - # get list of addresses - r = session.post(nextpageurl, data) - r.raise_for_status() + # If we don't have a valid date yet, try using the hidden input + if not date_text or not contains_date(date_text): + if i < len(hidden_dates): + date_value = hidden_dates[i].get("value") + if contains_date(date_value): + date_text = date_value - soup = BeautifulSoup(r.text, features="html.parser") + # Skip if we don't have a valid date + if not date_text or not contains_date(date_text): + # print(f"No valid date found for bin type: {bin_type}") + continue - # get first address (if you don't enter enough argument values this won't find the right address) - alink = soup.find("div", id="property_list").find("a") + # print(f"Found bin type: {bin_type} with date: {date_text}") - if alink is None: - raise Exception("Address not found") + try: + # Clean up the date text + date_text = remove_ordinal_indicator_from_date_string(date_text) - nextpageurl = URI + alink["href"] + # Try to parse the date + try: + collection_date = datetime.strptime( + date_text, "%A %d %B %Y" + ).date() + except ValueError: + try: + collection_date = datetime.strptime( + date_text, "%d/%m/%Y" + ).date() + except ValueError: + # Last resort + collection_date = parse(date_text).date() - # get collection page - r = session.get( - nextpageurl, - ) - r.raise_for_status() - soup = BeautifulSoup(r.text, features="html.parser") + # Create bin entry + bin_entry = { + "type": bin_type, + "collectionDate": collection_date.strftime(date_format), + } - if soup.find("span", id="waste-hint"): - raise Exception("No scheduled services at this address") + # Add to data + data["bins"].append(bin_entry) + bin_count += 1 + # print(f"Added bin entry: {bin_entry}") - u1s = soup.find("section", id="scheduled-collections").find_all("u1") + except Exception as e: + pass + # print(f"Error parsing date '{date_text}': {str(e)}") - for u1 in u1s: - lis = u1.find_all("li", recursive=False) + # print(f"Successfully parsed {bin_count} bin collections") - date = lis[1].text.replace("\n", "") - bin_type = lis[2].text.replace("\n", "") + if not data["bins"]: + # print("No bin data found. Saving page for debugging...") + with open(f"debug_page_{int(time.time())}.html", "w") as f: + f.write(driver.page_source) + driver.save_screenshot(f"final_error_screenshot_{int(time.time())}.png") + raise ValueError( + "No bin collection data could be extracted from the page" + ) - dict_data = { - "type": bin_type, - "collectionDate": datetime.strptime( - date, - "%d/%m/%Y", - ).strftime(date_format), - } - bindata["bins"].append(dict_data) + # Sort the bin collections by date + data["bins"].sort( + key=lambda x: datetime.strptime(x.get("collectionDate"), date_format) + ) - bindata["bins"].sort( - key=lambda x: datetime.strptime(x.get("collectionDate"), date_format) - ) + return data - return bindata + except Exception as e: + # print(f"Error parsing bin collection data: {e}") + raise From 4ea80c13ce32b16f9f0e541cc4cd3c9093264923 Mon Sep 17 00:00:00 2001 From: Andy Hewitt <9115190+atph@users.noreply.github.com> Date: Wed, 28 May 2025 17:11:27 +0100 Subject: [PATCH 049/425] fix #1455: remove ordinal indicators from bin collection date parsing --- .../uk_bin_collection/councils/NorthLincolnshireCouncil.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/NorthLincolnshireCouncil.py b/uk_bin_collection/uk_bin_collection/councils/NorthLincolnshireCouncil.py index 28760b54cb..adcee6131e 100644 --- a/uk_bin_collection/uk_bin_collection/councils/NorthLincolnshireCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/NorthLincolnshireCouncil.py @@ -46,7 +46,8 @@ def parse_data(self, page: str, **kwargs) -> dict: "type": bin_type, "collectionDate": get_next_occurrence_from_day_month( datetime.strptime( - c["BinCollectionDate"].replace(" (*)", "").strip() + remove_ordinal_indicator_from_date_string( + c["BinCollectionDate"].replace(" (*)", "").strip()) + " " + datetime.now().strftime("%Y"), "%A %d %B %Y", From c8a4f3ff4f8ed0eb45b33f692d3f184956b833ed Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Wed, 4 Jun 2025 07:13:41 +0100 Subject: [PATCH 050/425] fix: Github action to handle branch name with parentheses --- .github/workflows/behave_pull_request.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/behave_pull_request.yml b/.github/workflows/behave_pull_request.yml index 64010ba5df..4b72d13933 100644 --- a/.github/workflows/behave_pull_request.yml +++ b/.github/workflows/behave_pull_request.yml @@ -103,12 +103,13 @@ jobs: - name: Install Dependencies run: make install-dev - + - name: Check Parity of Councils / input.json / Feature file - run: | - repo=${{ github.event.pull_request.head.repo.full_name || 'robbrad/UKBinCollectionData' }} - branch=${{ github.event.pull_request.head.ref || 'master' }} - make parity-check repo=$repo branch=$branch + env: + repo: ${{ github.event.pull_request.head.repo.full_name || 'robbrad/UKBinCollectionData' }} + branch: ${{ github.event.pull_request.head.ref || 'master' }} + run: make parity-check repo="$repo" branch="$branch" + integration-tests: name: Run Integration Tests needs: setup @@ -151,4 +152,4 @@ jobs: report_type: test_results file: build/${{ matrix.python-version }}/integration-test-results/junit.xml flags: integrationtestspr - name: integration-tests-pr \ No newline at end of file + name: integration-tests-pr From 8b9bfa4aa7e34949304030f7b618bcde9004f5df Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Wed, 4 Jun 2025 07:16:20 +0100 Subject: [PATCH 051/425] fix: Update CheshireEastCouncil.py --- .../uk_bin_collection/councils/CheshireEastCouncil.py | 1 - 1 file changed, 1 deletion(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py b/uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py index ec15bcb229..1797ac7d38 100644 --- a/uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/CheshireEastCouncil.py @@ -9,7 +9,6 @@ This module provides bin collection data for Cheshire East Council. """ - class CouncilClass(AbstractGetBinDataClass): """ A class to fetch and parse bin collection data for Cheshire East Council. From 94ca95e93b50452bacd887c8c3641d06b31336b0 Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Wed, 4 Jun 2025 07:20:24 +0100 Subject: [PATCH 052/425] fix: Update Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 06e8792912..8bd03eb8ce 100644 --- a/Makefile +++ b/Makefile @@ -56,7 +56,7 @@ generate-test-map-test-results: poetry run python uk_bin_collection/tests/generate_map_test_results.py build/integration-test-results/junit.xml > build/integration-test-results/test_results.json parity-check: - poetry run python uk_bin_collection/tests/council_feature_input_parity.py $(repo) $(branch) + poetry run python uk_bin_collection/tests/council_feature_input_parity.py "$(repo)" "$(branch)" unit-tests: poetry run coverage erase From 6492b51a49b2ccfb18e8cf9c117cad3e5008c4a2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 4 Jun 2025 06:26:55 +0000 Subject: [PATCH 053/425] =?UTF-8?q?bump:=20version=200.152.1=20=E2=86=92?= =?UTF-8?q?=200.152.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 8 ++++++++ custom_components/uk_bin_collection/config_flow.py | 2 +- custom_components/uk_bin_collection/manifest.json | 4 ++-- pyproject.toml | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7028d2ff6..7afcd4dc0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,12 @@ ======= +## 0.152.2 (2025-06-04) + +### Fix + +- Update Makefile +- Update CheshireEastCouncil.py +- Github action to handle branch name with parentheses + ## 0.152.1 (2025-05-15) ### Fix diff --git a/custom_components/uk_bin_collection/config_flow.py b/custom_components/uk_bin_collection/config_flow.py index 7e7ffe2d66..c77400f3d2 100644 --- a/custom_components/uk_bin_collection/config_flow.py +++ b/custom_components/uk_bin_collection/config_flow.py @@ -253,7 +253,7 @@ async def async_step_reconfigure_confirm( async def get_councils_json(self) -> Dict[str, Any]: """Fetch and return the supported councils data, including aliases and sorted alphabetically.""" - url = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.152.1/uk_bin_collection/tests/input.json" + url = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.152.2/uk_bin_collection/tests/input.json" try: async with aiohttp.ClientSession() as session: async with session.get(url) as response: diff --git a/custom_components/uk_bin_collection/manifest.json b/custom_components/uk_bin_collection/manifest.json index f7e285aa2f..be482d1248 100644 --- a/custom_components/uk_bin_collection/manifest.json +++ b/custom_components/uk_bin_collection/manifest.json @@ -9,7 +9,7 @@ "integration_type": "service", "iot_class": "cloud_polling", "issue_tracker": "https://github.com/robbrad/UKBinCollectionData/issues", - "requirements": ["uk-bin-collection>=0.152.1"], - "version": "0.152.1", + "requirements": ["uk-bin-collection>=0.152.2"], + "version": "0.152.2", "zeroconf": [] } diff --git a/pyproject.toml b/pyproject.toml index cac835b93f..30aea391bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "uk_bin_collection" -version = "0.152.1" +version = "0.152.2" description = "Python Lib to collect UK Bin Data" readme = "README.md" authors = ["Robert Bradley "] From a046253266d4fe5eec1e9b53456faa01a7bc60c4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 4 Jun 2025 12:26:32 +0000 Subject: [PATCH 054/425] =?UTF-8?q?bump:=20version=200.152.2=20=E2=86=92?= =?UTF-8?q?=200.152.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 9 +++++++++ custom_components/uk_bin_collection/config_flow.py | 2 +- custom_components/uk_bin_collection/manifest.json | 4 ++-- pyproject.toml | 2 +- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7afcd4dc0c..970cd3407c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,13 @@ ======= +## 0.152.3 (2025-06-04) + +### Fix + +- NorthHertfordshire selenium script +- Adur council +- Eastleigh date fix +- removed duplicates in BradfordMDC + ## 0.152.2 (2025-06-04) ### Fix diff --git a/custom_components/uk_bin_collection/config_flow.py b/custom_components/uk_bin_collection/config_flow.py index c77400f3d2..7b40a9cf76 100644 --- a/custom_components/uk_bin_collection/config_flow.py +++ b/custom_components/uk_bin_collection/config_flow.py @@ -253,7 +253,7 @@ async def async_step_reconfigure_confirm( async def get_councils_json(self) -> Dict[str, Any]: """Fetch and return the supported councils data, including aliases and sorted alphabetically.""" - url = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.152.2/uk_bin_collection/tests/input.json" + url = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.152.3/uk_bin_collection/tests/input.json" try: async with aiohttp.ClientSession() as session: async with session.get(url) as response: diff --git a/custom_components/uk_bin_collection/manifest.json b/custom_components/uk_bin_collection/manifest.json index be482d1248..717d8de9ff 100644 --- a/custom_components/uk_bin_collection/manifest.json +++ b/custom_components/uk_bin_collection/manifest.json @@ -9,7 +9,7 @@ "integration_type": "service", "iot_class": "cloud_polling", "issue_tracker": "https://github.com/robbrad/UKBinCollectionData/issues", - "requirements": ["uk-bin-collection>=0.152.2"], - "version": "0.152.2", + "requirements": ["uk-bin-collection>=0.152.3"], + "version": "0.152.3", "zeroconf": [] } diff --git a/pyproject.toml b/pyproject.toml index 30aea391bc..6451a6e3b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "uk_bin_collection" -version = "0.152.2" +version = "0.152.3" description = "Python Lib to collect UK Bin Data" readme = "README.md" authors = ["Robert Bradley "] From dd2404bf415b16b72535322e4a29ce0842182307 Mon Sep 17 00:00:00 2001 From: Stuart Buchanan Date: Fri, 6 Jun 2025 15:25:51 +0100 Subject: [PATCH 055/425] fix(SouthRibble): Resolved South Ribble without selenium --- uk_bin_collection/tests/input.json | 8 +- .../councils/SouthRibbleCouncil.py | 152 ++++++------------ 2 files changed, 53 insertions(+), 107 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index 27016312a1..c85b85fd88 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -2180,10 +2180,10 @@ "LAD24CD": "E07000179" }, "SouthRibbleCouncil": { - "uprn": "010013246384", - "postcode": "PR5 6DT", - "url": "https://www.southribble.gov.uk", - "wiki_command_url_override": "https://www.southribble.gov.uk", + "uprn": "10013243496", + "postcode": "PR26 7RZ", + "url": "https://forms.chorleysouthribble.gov.uk/xfp/form/70", + "wiki_command_url_override": "https://forms.chorleysouthribble.gov.uk/xfp/form/70", "wiki_name": "South Ribble", "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", "LAD24CD": "E07000126" diff --git a/uk_bin_collection/uk_bin_collection/councils/SouthRibbleCouncil.py b/uk_bin_collection/uk_bin_collection/councils/SouthRibbleCouncil.py index bdcc0d4528..094532f3dd 100644 --- a/uk_bin_collection/uk_bin_collection/councils/SouthRibbleCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/SouthRibbleCouncil.py @@ -1,29 +1,11 @@ -from typing import Dict, List, Any, Optional -from bs4 import BeautifulSoup -from dateutil.relativedelta import relativedelta import requests -import logging -import re -from datetime import datetime -from uk_bin_collection.uk_bin_collection.common import * -from dateutil.parser import parse +from bs4 import BeautifulSoup -from uk_bin_collection.uk_bin_collection.common import check_uprn, check_postcode +from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass -def get_token(page) -> str: - """ - Get a __token to include in the form data - :param page: Page html - :return: Form __token - """ - soup = BeautifulSoup(page.text, features="html.parser") - soup.prettify() - token = soup.find("input", {"name": "__token"}).get("value") - return token - - +# import the wonderful Beautiful Soup and the URL grabber class CouncilClass(AbstractGetBinDataClass): """ Concrete classes have to implement all abstract operations of the @@ -31,69 +13,38 @@ class CouncilClass(AbstractGetBinDataClass): implementation. """ - def get_data(self, url: str) -> str: - """This method makes the request to the council - - Keyword arguments: - url -- the url to get the data from - """ - # Set a user agent so we look like a browser ;-) - user_agent = ( - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/108.0.0.0 Safari/537.36" - ) - headers = {"User-Agent": user_agent} - requests.packages.urllib3.disable_warnings() - - # Make the Request - change the URL - find out your property number - try: - session = requests.Session() - session.headers.update(headers) - full_page = session.get(url) - return full_page - except requests.exceptions.HTTPError as errh: - logging.error(f"Http Error: {errh}") - raise - except requests.exceptions.ConnectionError as errc: - logging.error(f"Error Connecting: {errc}") - raise - except requests.exceptions.Timeout as errt: - logging.error(f"Timeout Error: {errt}") - raise - except requests.exceptions.RequestException as err: - logging.error(f"Oops: Something Else {err}") - raise - - def parse_data(self, page: str, **kwargs: Any) -> Dict[str, List[Dict[str, str]]]: - uprn: Optional[str] = kwargs.get("uprn") - postcode: Optional[str] = kwargs.get("postcode") - - if uprn is None: - raise ValueError("UPRN is required and must be a non-empty string.") - if postcode is None: - raise ValueError("Postcode is required and must be a non-empty string.") - - check_uprn(uprn) - check_postcode(postcode) - - values = { - "__token": get_token(page), - "page": "491", + def parse_data(self, page: str, **kwargs) -> dict: + + user_uprn = kwargs.get("uprn") + user_postcode = kwargs.get("postcode") + check_uprn(user_uprn) + check_postcode(user_postcode) + bindata = {"bins": []} + + session_uri = "https://forms.chorleysouthribble.gov.uk/xfp/form/70" + URI = "https://forms.chorleysouthribble.gov.uk/xfp/form/70#qc576c657112a8277ba6f954ebc0490c946168363_0" + + session = requests.Session() + token_response = session.get(session_uri) + soup = BeautifulSoup(token_response.text, "html.parser") + token = soup.find("input", {"name": "__token"}).attrs["value"] + + form_data = { + "__token": token, + "page": "196", "locale": "en_GB", - "q1f8ccce1d1e2f58649b4069712be6879a839233f_0_0": postcode, - "q1f8ccce1d1e2f58649b4069712be6879a839233f_1_0": uprn, + "qc576c657112a8277ba6f954ebc0490c946168363_0_0": user_postcode, + "qc576c657112a8277ba6f954ebc0490c946168363_1_0": user_uprn, "next": "Next", } - headers = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)"} - requests.packages.urllib3.disable_warnings() - response = requests.request( - "POST", - "https://forms.chorleysouthribble.gov.uk/xfp/form/70", - headers=headers, - data=values, - ) - soup = BeautifulSoup(response.text, features="html.parser") + collection_response = session.post(URI, data=form_data) + + #collection_soup = BeautifulSoup(collection_response.text, "html.parser") + + + soup = BeautifulSoup(collection_response.text, "html.parser") + #print(soup) rows = soup.find("table").find_all("tr") @@ -103,31 +54,26 @@ def parse_data(self, page: str, **kwargs: Any) -> Dict[str, List[Dict[str, str]] # Loops the Rows for row in rows: cells = row.find_all("td") + if cells: bin_type = cells[0].get_text(strip=True) collection_next = cells[1].get_text(strip=True) - collection_date = re.findall(r"\(.*?\)", collection_next) - - if len(collection_date) != 1: - continue - - collection_date_obj = parse( - re.sub(r"[()]", "", collection_date[0]) - ).date() - - # since we only have the next collection day, if the parsed date is in the past, - # assume the day is instead next month - if collection_date_obj < datetime.now().date(): - collection_date_obj += relativedelta(months=1) - - # Make each Bin element in the JSON - dict_data = { - "type": bin_type, - "collectionDate": collection_date_obj.strftime(date_format), - } - - # Add data to the main JSON Wrapper - data["bins"].append(dict_data) - + print(bin_type) + print(len(collection_next)) + + if len(collection_next) != 1: + collection_date_obj = parse(collection_next).date() + # since we only have the next collection day, if the parsed date is in the past, + # assume the day is instead next month + if collection_date_obj < datetime.now().date(): + collection_date_obj += relativedelta(months=1) + # Make each Bin element in the JSON + dict_data = { + "type": bin_type, + "collectionDate": collection_date_obj.strftime(date_format), + } + # Add data to the main JSON Wrapper + data["bins"].append(dict_data) + continue return data \ No newline at end of file From fd1cebb19df69b87b3cd9bb9d2d72c8888fd19dc Mon Sep 17 00:00:00 2001 From: Stuart Buchanan Date: Fri, 6 Jun 2025 17:51:33 +0100 Subject: [PATCH 056/425] fix(SouthRibble): Corrected Date formatting issue --- .../uk_bin_collection/councils/SouthRibbleCouncil.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/SouthRibbleCouncil.py b/uk_bin_collection/uk_bin_collection/councils/SouthRibbleCouncil.py index 094532f3dd..eba2e8c26d 100644 --- a/uk_bin_collection/uk_bin_collection/councils/SouthRibbleCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/SouthRibbleCouncil.py @@ -59,11 +59,8 @@ def parse_data(self, page: str, **kwargs) -> dict: bin_type = cells[0].get_text(strip=True) collection_next = cells[1].get_text(strip=True) - print(bin_type) - print(len(collection_next)) - if len(collection_next) != 1: - collection_date_obj = parse(collection_next).date() + collection_date_obj = datetime.strptime(collection_next, "%d/%m/%y").date() # since we only have the next collection day, if the parsed date is in the past, # assume the day is instead next month if collection_date_obj < datetime.now().date(): @@ -71,7 +68,7 @@ def parse_data(self, page: str, **kwargs) -> dict: # Make each Bin element in the JSON dict_data = { "type": bin_type, - "collectionDate": collection_date_obj.strftime(date_format), + "collectionDate": collection_date_obj.strftime("%d/%m/%Y"), } # Add data to the main JSON Wrapper data["bins"].append(dict_data) From e310ea3a981dc23eea34563755d45d588327606b Mon Sep 17 00:00:00 2001 From: Jakub Laszczynski Date: Fri, 6 Jun 2025 20:38:46 +0100 Subject: [PATCH 057/425] Simplify and fix wait for content --- uk_bin_collection/tests/input.json | 7 ++++--- .../uk_bin_collection/councils/CroydonCouncil.py | 9 +++------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index 27016312a1..4342c848af 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -614,8 +614,9 @@ "postcode": "SE25 5DW", "skip_get_url": true, "url": "https://service.croydon.gov.uk/wasteservices/w/webpage/bin-day-enter-address", + "web_driver": "http://selenium:4444", "wiki_name": "Croydon", - "wiki_note": "Pass the house number and postcode in their respective parameters.", + "wiki_note": "Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver.", "LAD24CD": "E09000008" }, "CumberlandAllerdaleCouncil": { @@ -745,7 +746,7 @@ "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", "LAD24CD": "E07000061" }, - "EastCambridgeshireCouncil": { + "EastCambridgeshireCouncil": { "skip_get_url": true, "uprn": "10002597178", "url": "https://www.eastcambs.gov.uk/", @@ -2803,4 +2804,4 @@ "wiki_note": "Provide your UPRN.", "LAD24CD": "E06000014" } -} \ No newline at end of file +} diff --git a/uk_bin_collection/uk_bin_collection/councils/CroydonCouncil.py b/uk_bin_collection/uk_bin_collection/councils/CroydonCouncil.py index d78595c7b7..53af0d9f7f 100644 --- a/uk_bin_collection/uk_bin_collection/councils/CroydonCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/CroydonCouncil.py @@ -79,12 +79,9 @@ def parse_data(self, page: str, **kwargs) -> dict: next_button.click() # Wait for the bin collection content to load - collection_content = WebDriverWait(driver, 10).until( - EC.presence_of_element_located( - ( - By.XPATH, - '//*[@id="mats_content_wrapper"]/div[2]/div[2]/div[2]/div/div[1]/div/div[3]/div/div/div/div', - ) + listing_records = WebDriverWait(driver, 10).until( + EC.presence_of_all_elements_located( + (By.CSS_SELECTOR, "#mats_content_wrapper .listing_template_record") ) ) From d9fb6a68486b8407025933ed477e9deb5fa1be1c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 7 Jun 2025 08:33:59 +0000 Subject: [PATCH 058/425] =?UTF-8?q?bump:=20version=200.152.3=20=E2=86=92?= =?UTF-8?q?=200.152.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 7 +++++++ custom_components/uk_bin_collection/config_flow.py | 2 +- custom_components/uk_bin_collection/manifest.json | 4 ++-- pyproject.toml | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 970cd3407c..cca3dcf813 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,11 @@ ======= +## 0.152.4 (2025-06-07) + +### Fix + +- **SouthRibble**: Corrected Date formatting issue +- **SouthRibble**: Resolved South Ribble without selenium + ## 0.152.3 (2025-06-04) ### Fix diff --git a/custom_components/uk_bin_collection/config_flow.py b/custom_components/uk_bin_collection/config_flow.py index 7b40a9cf76..7b99216cf0 100644 --- a/custom_components/uk_bin_collection/config_flow.py +++ b/custom_components/uk_bin_collection/config_flow.py @@ -253,7 +253,7 @@ async def async_step_reconfigure_confirm( async def get_councils_json(self) -> Dict[str, Any]: """Fetch and return the supported councils data, including aliases and sorted alphabetically.""" - url = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.152.3/uk_bin_collection/tests/input.json" + url = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.152.4/uk_bin_collection/tests/input.json" try: async with aiohttp.ClientSession() as session: async with session.get(url) as response: diff --git a/custom_components/uk_bin_collection/manifest.json b/custom_components/uk_bin_collection/manifest.json index 717d8de9ff..8bdb434957 100644 --- a/custom_components/uk_bin_collection/manifest.json +++ b/custom_components/uk_bin_collection/manifest.json @@ -9,7 +9,7 @@ "integration_type": "service", "iot_class": "cloud_polling", "issue_tracker": "https://github.com/robbrad/UKBinCollectionData/issues", - "requirements": ["uk-bin-collection>=0.152.3"], - "version": "0.152.3", + "requirements": ["uk-bin-collection>=0.152.4"], + "version": "0.152.4", "zeroconf": [] } diff --git a/pyproject.toml b/pyproject.toml index 6451a6e3b3..994ed98a61 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "uk_bin_collection" -version = "0.152.3" +version = "0.152.4" description = "Python Lib to collect UK Bin Data" readme = "README.md" authors = ["Robert Bradley "] From 4acf72952e97ec8ede9c67a2004b5a94a1b9a7f9 Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Sat, 7 Jun 2025 13:52:44 +0000 Subject: [PATCH 059/425] fix: South Ribble and version pinning issues for input.json --- .../uk_bin_collection/config_flow.py | 9 +- custom_components/uk_bin_collection/const.py | 2 + pyproject.toml | 2 +- uk_bin_collection/tests/input.json | 2 +- .../councils/SouthRibbleCouncil.py | 162 ++++++++++++------ 5 files changed, 115 insertions(+), 62 deletions(-) diff --git a/custom_components/uk_bin_collection/config_flow.py b/custom_components/uk_bin_collection/config_flow.py index 7b99216cf0..e345a15d48 100644 --- a/custom_components/uk_bin_collection/config_flow.py +++ b/custom_components/uk_bin_collection/config_flow.py @@ -12,11 +12,10 @@ import collections # At the top with other imports -from .const import DOMAIN, LOG_PREFIX, SELENIUM_SERVER_URLS, BROWSER_BINARIES +from .const import DOMAIN, LOG_PREFIX, SELENIUM_SERVER_URLS, BROWSER_BINARIES, INPUT_JSON_URL _LOGGER = logging.getLogger(__name__) - class UkBinCollectionConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for UkBinCollection.""" @@ -253,10 +252,9 @@ async def async_step_reconfigure_confirm( async def get_councils_json(self) -> Dict[str, Any]: """Fetch and return the supported councils data, including aliases and sorted alphabetically.""" - url = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.152.4/uk_bin_collection/tests/input.json" try: async with aiohttp.ClientSession() as session: - async with session.get(url) as response: + async with session.get(INPUT_JSON_URL) as response: response.raise_for_status() data_text = await response.text() original_data = json.loads(data_text) @@ -569,10 +567,9 @@ async def async_step_init(self, user_input=None): async def get_councils_json(self) -> Dict[str, Any]: """Fetch and return the supported councils data.""" - url = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.111.0/uk_bin_collection/tests/input.json" try: async with aiohttp.ClientSession() as session: - async with session.get(url) as response: + async with session.get(INPUT_JSON_URL) as response: response.raise_for_status() data_text = await response.text() return json.loads(data_text) diff --git a/custom_components/uk_bin_collection/const.py b/custom_components/uk_bin_collection/const.py index b51bb2a366..6fcfc34b33 100644 --- a/custom_components/uk_bin_collection/const.py +++ b/custom_components/uk_bin_collection/const.py @@ -4,6 +4,8 @@ from homeassistant.const import Platform +INPUT_JSON_URL = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.152.4/uk_bin_collection/tests/input.json" + DEFAULT_NAME = "UK Bin Collection Data" DOMAIN = "uk_bin_collection" diff --git a/pyproject.toml b/pyproject.toml index 994ed98a61..e0e3a3d73c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,6 +71,6 @@ version_scheme = "semver" version_files = [ "custom_components/uk_bin_collection/manifest.json:version", "custom_components/uk_bin_collection/manifest.json:requirements", - "custom_components/uk_bin_collection/config_flow.py:githubusercontent" + "custom_components/uk_bin_collection/const.py:INPUT_JSON_URL" ] diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index c85b85fd88..61d38be41a 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -2181,7 +2181,7 @@ }, "SouthRibbleCouncil": { "uprn": "10013243496", - "postcode": "PR26 7RZ", + "postcode": "PR266QW", "url": "https://forms.chorleysouthribble.gov.uk/xfp/form/70", "wiki_command_url_override": "https://forms.chorleysouthribble.gov.uk/xfp/form/70", "wiki_name": "South Ribble", diff --git a/uk_bin_collection/uk_bin_collection/councils/SouthRibbleCouncil.py b/uk_bin_collection/uk_bin_collection/councils/SouthRibbleCouncil.py index eba2e8c26d..257097addd 100644 --- a/uk_bin_collection/uk_bin_collection/councils/SouthRibbleCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/SouthRibbleCouncil.py @@ -1,76 +1,130 @@ -import requests +from typing import Dict, List, Any, Optional from bs4 import BeautifulSoup - -from uk_bin_collection.uk_bin_collection.common import * +from dateutil.relativedelta import relativedelta +import requests +import re +from datetime import datetime +from uk_bin_collection.uk_bin_collection.common import check_uprn, check_postcode, date_format from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass +from dateutil.parser import parse -# import the wonderful Beautiful Soup and the URL grabber class CouncilClass(AbstractGetBinDataClass): - """ - Concrete classes have to implement all abstract operations of the - base class. They can also override some operations with a default - implementation. - """ + def get_data(self, url: str) -> str: + # This method is not used in the current implementation + return "" - def parse_data(self, page: str, **kwargs) -> dict: + def parse_data(self, page: str, **kwargs: Any) -> Dict[str, List[Dict[str, str]]]: + postcode: Optional[str] = kwargs.get("postcode") + uprn: Optional[str] = kwargs.get("uprn") - user_uprn = kwargs.get("uprn") - user_postcode = kwargs.get("postcode") - check_uprn(user_uprn) - check_postcode(user_postcode) - bindata = {"bins": []} + if postcode is None or uprn is None: + raise ValueError("Both postcode and UPRN are required.") - session_uri = "https://forms.chorleysouthribble.gov.uk/xfp/form/70" - URI = "https://forms.chorleysouthribble.gov.uk/xfp/form/70#qc576c657112a8277ba6f954ebc0490c946168363_0" + check_postcode(postcode) + check_uprn(uprn) session = requests.Session() - token_response = session.get(session_uri) - soup = BeautifulSoup(token_response.text, "html.parser") - token = soup.find("input", {"name": "__token"}).attrs["value"] - - form_data = { - "__token": token, - "page": "196", - "locale": "en_GB", - "qc576c657112a8277ba6f954ebc0490c946168363_0_0": user_postcode, - "qc576c657112a8277ba6f954ebc0490c946168363_1_0": user_uprn, - "next": "Next", + headers = { + "User-Agent": ( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " + "(KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36" + ) } + session.headers.update(headers) + + # Step 1: Load form and get token + field names + initial_url = "https://forms.chorleysouthribble.gov.uk/xfp/form/70" + get_resp = session.get(initial_url) + soup = BeautifulSoup(get_resp.text, "html.parser") + + token = soup.find("input", {"name": "__token"})["value"] + page_id = soup.find("input", {"name": "page"})["value"] + postcode_field = soup.find("input", {"type": "text", "name": re.compile(".*_0_0")})["name"] + + # Step 2: Submit postcode + post_resp = session.post( + initial_url, + data={ + "__token": token, + "page": page_id, + "locale": "en_GB", + postcode_field: postcode, + "next": "Next", + }, + ) - collection_response = session.post(URI, data=form_data) + soup = BeautifulSoup(post_resp.text, "html.parser") + token = soup.find("input", {"name": "__token"})["value"] + address_field_el = soup.find("select", {"name": re.compile(".*_1_0")}) + if not address_field_el: + raise ValueError("Failed to find address dropdown after postcode submission.") - #collection_soup = BeautifulSoup(collection_response.text, "html.parser") - + address_field = address_field_el["name"] - soup = BeautifulSoup(collection_response.text, "html.parser") - #print(soup) + # Step 3: Submit UPRN and retrieve bin data + final_resp = session.post( + initial_url, + data={ + "__token": token, + "page": page_id, + "locale": "en_GB", + postcode_field: postcode, + address_field: uprn, + "next": "Next", + }, + ) - rows = soup.find("table").find_all("tr") + soup = BeautifulSoup(final_resp.text, "html.parser") + table = soup.find("table", class_="data-table") + if not table: + raise ValueError("Could not find bin collection table.") - # Form a JSON wrapper + rows = table.find("tbody").find_all("tr") data: Dict[str, List[Dict[str, str]]] = {"bins": []} - # Loops the Rows + # Extract bin type mapping from JavaScript + bin_type_map = {} + scripts = soup.find_all("script", type="text/javascript") + for script in scripts: + if script.string and "const bintype = {" in script.string: + match = re.search(r'const bintype = \{([^}]+)\}', script.string, re.DOTALL) + if match: + bintype_content = match.group(1) + for line in bintype_content.split('\n'): + line = line.strip() + if '"' in line and ':' in line: + parts = line.split(':', 1) + if len(parts) == 2: + key = parts[0].strip().strip('"').strip("'") + value = parts[1].strip().rstrip(',').strip().strip('"').strip("'") + bin_type_map[key] = value + break + for row in rows: cells = row.find_all("td") - - if cells: - bin_type = cells[0].get_text(strip=True) - collection_next = cells[1].get_text(strip=True) - - if len(collection_next) != 1: - collection_date_obj = datetime.strptime(collection_next, "%d/%m/%y").date() - # since we only have the next collection day, if the parsed date is in the past, - # assume the day is instead next month - if collection_date_obj < datetime.now().date(): - collection_date_obj += relativedelta(months=1) - # Make each Bin element in the JSON - dict_data = { + if len(cells) >= 2: + bin_type_cell = cells[0] + bin_type = bin_type_cell.get_text(strip=True) + bin_type = bin_type_map.get(bin_type, bin_type) + + date_text = cells[1].get_text(strip=True) + date_parts = date_text.split(", ") + date_str = date_parts[1] if len(date_parts) == 2 else date_text + + try: + day, month, year = date_str.split('/') + year = int(year) + if year < 100: + year = 2000 + year + + date_obj = datetime(year, int(month), int(day)).date() + + data["bins"].append({ "type": bin_type, - "collectionDate": collection_date_obj.strftime("%d/%m/%Y"), - } - # Add data to the main JSON Wrapper - data["bins"].append(dict_data) - continue + "collectionDate": date_obj.strftime(date_format) + }) + except Exception: + continue + return data \ No newline at end of file From b4609ac63d9b7ef803ec53f3e04f9a292c078dfd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 7 Jun 2025 13:57:22 +0000 Subject: [PATCH 060/425] =?UTF-8?q?bump:=20version=200.152.4=20=E2=86=92?= =?UTF-8?q?=200.152.5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 6 ++++++ custom_components/uk_bin_collection/const.py | 2 +- custom_components/uk_bin_collection/manifest.json | 4 ++-- pyproject.toml | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cca3dcf813..8c2536b173 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,10 @@ ======= +## 0.152.5 (2025-06-07) + +### Fix + +- South Ribble and version pinning issues for input.json + ## 0.152.4 (2025-06-07) ### Fix diff --git a/custom_components/uk_bin_collection/const.py b/custom_components/uk_bin_collection/const.py index 6fcfc34b33..a4b2cf9ea1 100644 --- a/custom_components/uk_bin_collection/const.py +++ b/custom_components/uk_bin_collection/const.py @@ -4,7 +4,7 @@ from homeassistant.const import Platform -INPUT_JSON_URL = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.152.4/uk_bin_collection/tests/input.json" +INPUT_JSON_URL = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.152.5/uk_bin_collection/tests/input.json" DEFAULT_NAME = "UK Bin Collection Data" diff --git a/custom_components/uk_bin_collection/manifest.json b/custom_components/uk_bin_collection/manifest.json index 8bdb434957..9bc455b2f1 100644 --- a/custom_components/uk_bin_collection/manifest.json +++ b/custom_components/uk_bin_collection/manifest.json @@ -9,7 +9,7 @@ "integration_type": "service", "iot_class": "cloud_polling", "issue_tracker": "https://github.com/robbrad/UKBinCollectionData/issues", - "requirements": ["uk-bin-collection>=0.152.4"], - "version": "0.152.4", + "requirements": ["uk-bin-collection>=0.152.5"], + "version": "0.152.5", "zeroconf": [] } diff --git a/pyproject.toml b/pyproject.toml index e0e3a3d73c..cb19238060 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "uk_bin_collection" -version = "0.152.4" +version = "0.152.5" description = "Python Lib to collect UK Bin Data" readme = "README.md" authors = ["Robert Bradley "] From 8ab7db8908eec613dba312779d7c7a4ae6e0fdf1 Mon Sep 17 00:00:00 2001 From: Adam Harrison-Fuller Date: Sun, 8 Jun 2025 12:40:55 +0100 Subject: [PATCH 061/425] Fix up the CSS Selector so it only matches on the correct title --- .../councils/NorthHertfordshireDistrictCouncil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/NorthHertfordshireDistrictCouncil.py b/uk_bin_collection/uk_bin_collection/councils/NorthHertfordshireDistrictCouncil.py index b2975c267c..a15d8abc94 100644 --- a/uk_bin_collection/uk_bin_collection/councils/NorthHertfordshireDistrictCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/NorthHertfordshireDistrictCouncil.py @@ -151,7 +151,7 @@ def parse_data(self, page: str, **kwargs) -> dict: # Looking for bin types in the exact HTML structure bin_type_elements = soup.select( - "div.formatting_bold.formatting_size_bigger.formatting span.value-as-text" + "div.page_cell.contains_widget:first-of-type div.formatting_bold.formatting_size_bigger.formatting span.value-as-text" ) # print(f"Found {len(bin_type_elements)} bin type elements") From 44bc529f46d4fd12d6022b1855825257831327c0 Mon Sep 17 00:00:00 2001 From: Stuart Buchanan Date: Mon, 9 Jun 2025 16:25:50 +0100 Subject: [PATCH 062/425] fix(OxfordCityCouncil): Fixed Oxford City Council parsing dues to changes in output from the website --- uk_bin_collection/tests/input.json | 4 ++-- .../councils/OxfordCityCouncil.py | 19 ++++++++++++------- wiki/Councils.md | 2 +- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index dc7617ec76..823a8b458f 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -1828,8 +1828,8 @@ "OxfordCityCouncil": { "postcode": "OX3 7QF", "uprn": "100120820551", - "url": "https://www.oxford.gov.uk", - "wiki_command_url_override": "https://www.oxford.gov.uk", + "url": "https://www.oxford.gov.uk/xfp/form/142", + "wiki_command_url_override": "https://www.oxford.gov.uk/xfp/form/142", "wiki_name": "Oxford", "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", "LAD24CD": "E07000178" diff --git a/uk_bin_collection/uk_bin_collection/councils/OxfordCityCouncil.py b/uk_bin_collection/uk_bin_collection/councils/OxfordCityCouncil.py index 0e342996a2..aa1a38b7a5 100644 --- a/uk_bin_collection/uk_bin_collection/councils/OxfordCityCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/OxfordCityCouncil.py @@ -21,8 +21,8 @@ def parse_data(self, page: str, **kwargs) -> dict: check_postcode(user_postcode) bindata = {"bins": []} - session_uri = "https://www.oxford.gov.uk/mybinday" - URI = "https://www.oxford.gov.uk/xfp/form/142" + session_uri = "https://www.oxford.gov.uk/xfp/form/142" + URI = "https://www.oxford.gov.uk/xfp/form/142#q6ad4e3bf432c83230a0347a6eea6c805c672efeb_0" session = requests.Session() token_response = session.get(session_uri) @@ -40,15 +40,18 @@ def parse_data(self, page: str, **kwargs) -> dict: collection_response = session.post(URI, data=form_data) - collection_soup = BeautifulSoup(collection_response.text, "html.parser") - for paragraph in collection_soup.find("div", class_="editor").find_all("p"): - matches = re.match(r"^(\w+) Next Collection: (.*)", paragraph.text) + soup = BeautifulSoup(collection_response.text, "html.parser") + #print(soup) + + for paragraph in soup.find("div", class_="editor").find_all("p"): + matches = re.match(r"^Your next (\w+) collections: (.*)", paragraph.text) if matches: collection_type, date_string = matches.groups() + parts = date_string.split(', ', 1) try: - date = datetime.strptime(date_string, "%A %d %B %Y").date() + date = datetime.strptime(parts[0], "%A %d %B %Y").date() except ValueError: - date = datetime.strptime(date_string, "%A %d %b %Y").date() + date = datetime.strptime(parts[0], "%A %d %b %Y").date() dict_data = { "type": collection_type, @@ -61,3 +64,5 @@ def parse_data(self, page: str, **kwargs) -> dict: ) return bindata + + diff --git a/wiki/Councils.md b/wiki/Councils.md index d9ed8b4608..e340731393 100644 --- a/wiki/Councils.md +++ b/wiki/Councils.md @@ -2682,7 +2682,7 @@ Note: Replace UPRN in URL with your own UPRN. ### Oxford City Council ```commandline -python collect_data.py OxfordCityCouncil https://www.oxford.gov.uk -u XXXXXXXX -p "XXXX XXX" +python collect_data.py OxfordCityCouncil https://www.oxford.gov.uk/xfp/form/142 -u XXXXXXXX -p "XXXX XXX" ``` Additional parameters: - `-u` - UPRN From 5e978b0a8462651bff8d9efd7796a40621cf500c Mon Sep 17 00:00:00 2001 From: Stuart Buchanan Date: Mon, 9 Jun 2025 16:57:56 +0100 Subject: [PATCH 063/425] Fix(CumberlandCouncil): Reworked Cumberland Council to cater for postcode addition --- uk_bin_collection/tests/input.json | 5 +++-- .../uk_bin_collection/councils/CumberlandCouncil.py | 2 ++ wiki/Councils.md | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index dc7617ec76..8647521577 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -628,8 +628,9 @@ "LAD24CD": "E06000063" }, "CumberlandCouncil": { - "uprn": "100110734613", - "url": "https://waste.cumberland.gov.uk", + "postcode": "CA6 5PH", + "uprn": "10009457328", + "url": "https://waste.cumberland.gov.uk/renderform?t=25&k=E43CEB1FB59F859833EF2D52B16F3F4EBE1CAB6A", "wiki_name": "Cumberland", "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", "LAD24CD": "E06000063" diff --git a/uk_bin_collection/uk_bin_collection/councils/CumberlandCouncil.py b/uk_bin_collection/uk_bin_collection/councils/CumberlandCouncil.py index 90bdc6c2b8..f61e14c549 100644 --- a/uk_bin_collection/uk_bin_collection/councils/CumberlandCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/CumberlandCouncil.py @@ -16,6 +16,7 @@ class CouncilClass(AbstractGetBinDataClass): def parse_data(self, page: str, **kwargs) -> dict: user_uprn = kwargs.get("uprn") + postcode = kwargs.get("postcode") check_uprn(user_uprn) bindata = {"bins": []} @@ -57,6 +58,7 @@ def parse_data(self, page: str, **kwargs) -> dict: "TriggerCtl": "", "FF265": f"U{user_uprn}", "FF265lbltxt": "Please select your address", + "FF265-text": postcode } # print(payload) diff --git a/wiki/Councils.md b/wiki/Councils.md index d9ed8b4608..3dbe99560b 100644 --- a/wiki/Councils.md +++ b/wiki/Councils.md @@ -1158,9 +1158,10 @@ Note: Pass the house number and postcode in their respective parameters. ### Cumberland Borough Council ```commandline -python collect_data.py CumberlandCouncil https://waste.cumberland.gov.uk -u XXXXXXXX +python collect_data.py CumberlandCouncil https://waste.cumberland.gov.uk -u XXXXXXXX -p "XXXX XXX" ``` Additional parameters: +- `-p` - postcode - `-u` - UPRN Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN. From a3eea5353f4b50662c86a5d3887449b7a5ec6081 Mon Sep 17 00:00:00 2001 From: Stuart Buchanan Date: Mon, 9 Jun 2025 17:55:23 +0100 Subject: [PATCH 064/425] fix(RugbyBoroughCouncil): Amended parsed date from full to abbreviated month date, may worked but jun and jul did not --- .../uk_bin_collection/councils/RugbyBoroughCouncil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/RugbyBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/RugbyBoroughCouncil.py index 57c773204b..d3481d7c14 100644 --- a/uk_bin_collection/uk_bin_collection/councils/RugbyBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/RugbyBoroughCouncil.py @@ -113,7 +113,7 @@ def parse_data(self, page: str, **kwargs) -> dict: try: # Convert date from "Friday 09 May 2025" format parsed_date = datetime.strptime( - collection_date, "%A %d %B %Y" + collection_date, "%A %d %b %Y" ) formatted_date = parsed_date.strftime("%d/%m/%Y") From 6d90a0b3c76f2f194f7ffc635c6522c196fd6ba8 Mon Sep 17 00:00:00 2001 From: Stuart Buchanan Date: Mon, 9 Jun 2025 17:55:23 +0100 Subject: [PATCH 065/425] fix(RugbyBoroughCouncil): Amended parsed date --- .../uk_bin_collection/councils/RugbyBoroughCouncil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/RugbyBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/RugbyBoroughCouncil.py index 57c773204b..d3481d7c14 100644 --- a/uk_bin_collection/uk_bin_collection/councils/RugbyBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/RugbyBoroughCouncil.py @@ -113,7 +113,7 @@ def parse_data(self, page: str, **kwargs) -> dict: try: # Convert date from "Friday 09 May 2025" format parsed_date = datetime.strptime( - collection_date, "%A %d %B %Y" + collection_date, "%A %d %b %Y" ) formatted_date = parsed_date.strftime("%d/%m/%Y") From a5cb787cb0674349fece79170d222ab4e0ab8198 Mon Sep 17 00:00:00 2001 From: Stuart Buchanan Date: Mon, 9 Jun 2025 16:57:56 +0100 Subject: [PATCH 066/425] fix: Reworked Cumberland Council to cater for postcode addition --- uk_bin_collection/tests/input.json | 5 +++-- .../uk_bin_collection/councils/CumberlandCouncil.py | 2 ++ wiki/Councils.md | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index dc7617ec76..8647521577 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -628,8 +628,9 @@ "LAD24CD": "E06000063" }, "CumberlandCouncil": { - "uprn": "100110734613", - "url": "https://waste.cumberland.gov.uk", + "postcode": "CA6 5PH", + "uprn": "10009457328", + "url": "https://waste.cumberland.gov.uk/renderform?t=25&k=E43CEB1FB59F859833EF2D52B16F3F4EBE1CAB6A", "wiki_name": "Cumberland", "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", "LAD24CD": "E06000063" diff --git a/uk_bin_collection/uk_bin_collection/councils/CumberlandCouncil.py b/uk_bin_collection/uk_bin_collection/councils/CumberlandCouncil.py index 90bdc6c2b8..f61e14c549 100644 --- a/uk_bin_collection/uk_bin_collection/councils/CumberlandCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/CumberlandCouncil.py @@ -16,6 +16,7 @@ class CouncilClass(AbstractGetBinDataClass): def parse_data(self, page: str, **kwargs) -> dict: user_uprn = kwargs.get("uprn") + postcode = kwargs.get("postcode") check_uprn(user_uprn) bindata = {"bins": []} @@ -57,6 +58,7 @@ def parse_data(self, page: str, **kwargs) -> dict: "TriggerCtl": "", "FF265": f"U{user_uprn}", "FF265lbltxt": "Please select your address", + "FF265-text": postcode } # print(payload) diff --git a/wiki/Councils.md b/wiki/Councils.md index d9ed8b4608..3dbe99560b 100644 --- a/wiki/Councils.md +++ b/wiki/Councils.md @@ -1158,9 +1158,10 @@ Note: Pass the house number and postcode in their respective parameters. ### Cumberland Borough Council ```commandline -python collect_data.py CumberlandCouncil https://waste.cumberland.gov.uk -u XXXXXXXX +python collect_data.py CumberlandCouncil https://waste.cumberland.gov.uk -u XXXXXXXX -p "XXXX XXX" ``` Additional parameters: +- `-p` - postcode - `-u` - UPRN Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN. From 6637847db132bd385ec8ffbbea171e3b39560627 Mon Sep 17 00:00:00 2001 From: Stuart Buchanan Date: Mon, 9 Jun 2025 19:44:26 +0100 Subject: [PATCH 067/425] fix: removed In Progress from date --- .../uk_bin_collection/councils/KingstonUponThamesCouncil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/KingstonUponThamesCouncil.py b/uk_bin_collection/uk_bin_collection/councils/KingstonUponThamesCouncil.py index 5a95585c92..c324e9b74c 100644 --- a/uk_bin_collection/uk_bin_collection/councils/KingstonUponThamesCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/KingstonUponThamesCouncil.py @@ -51,7 +51,7 @@ def parse_data(self, page: str, **kwargs) -> dict: if row.find("dt").get_text().strip().lower() == "next collection": collection_date = remove_ordinal_indicator_from_date_string( row.find("dd").get_text() - ).strip() + ).strip().replace(" (In progress)", "") # strip out any text inside of the date string collection_date = re.sub( r"\n\s*\(this.*?\)", "", collection_date From b91726eae26b51826548314362226a0e17e03cbe Mon Sep 17 00:00:00 2001 From: Stuart Buchanan Date: Mon, 9 Jun 2025 20:29:17 +0100 Subject: [PATCH 068/425] fix: removed a degub print statement --- .../uk_bin_collection/councils/RugbyBoroughCouncil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/RugbyBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/RugbyBoroughCouncil.py index d3481d7c14..903052d386 100644 --- a/uk_bin_collection/uk_bin_collection/councils/RugbyBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/RugbyBoroughCouncil.py @@ -132,7 +132,7 @@ def parse_data(self, page: str, **kwargs) -> dict: key=lambda x: datetime.strptime(x["collectionDate"], "%d/%m/%Y") ) - print(bin_data) + #print(bin_data) except Exception as e: # Here you can log the exception if needed From 18684fe881e8e83c25d9b07728785b8f95a97c21 Mon Sep 17 00:00:00 2001 From: duncan Date: Wed, 11 Jun 2025 14:45:10 +0100 Subject: [PATCH 069/425] Fix duplicate wiki_name entries causing configuration issues - Changed AylesburyValeCouncil wiki_name from 'Buckinghamshire' to 'Buckinghamshire (Aylesbury Vale)' - Changed LondonBoroughEaling wiki_name from 'Ealing' to 'Ealing (London Borough)' - Changed CumberlandAllerdaleCouncil wiki_name from 'Cumberland' to 'Cumberland (Allerdale)' Fixes #1484: Resolves duplicate council listings in wiki and Home Assistant integration dropdown, and prevents mapping errors in config_flow.py --- uk_bin_collection/tests/input.json | 5614 ++++++++++++++-------------- 1 file changed, 2807 insertions(+), 2807 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index dc7617ec76..debb7674f5 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -1,2807 +1,2807 @@ -{ - "AberdeenCityCouncil": { - "LAD24CD": "S12000033", - "uprn": "9051156186", - "url": "https://www.aberdeencity.gov.uk", - "wiki_name": "Aberdeen City", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN." - }, - "AberdeenshireCouncil": { - "LAD24CD": "S12000034", - "uprn": "151176430", - "url": "https://online.aberdeenshire.gov.uk", - "wiki_command_url_override": "https://online.aberdeenshire.gov.uk", - "wiki_name": "Aberdeenshire", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN." - }, - "AdurAndWorthingCouncils": { - "url": "https://www.adur-worthing.gov.uk/bin-day/?brlu-selected-address=100061878829", - "wiki_command_url_override": "https://www.adur-worthing.gov.uk/bin-day/?brlu-selected-address=XXXXXXXX", - "wiki_name": "Adur", - "wiki_note": "Replace XXXXXXXX with your UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find it.", - "LAD24CD": "E07000223" - }, - "AmberValleyBoroughCouncil": { - "uprn": "100030026621", - "url": "https://ambervalley.gov.uk", - "wiki_name": "Amber Valley", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000032" - }, - "AngusCouncil": { - "uprn": "117053733", - "skip_get_url": true, - "postcode": "DD7 7LE", - "url": "https://www.angus.gov.uk/bins_litter_and_recycling/bin_collection_days", - "web_driver": "http://selenium:4444", - "wiki_name": "Angus", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. Requires Selenium", - "LAD24CD": "S12000041" - }, - "AntrimAndNewtonabbeyCouncil": { - "LAD24CD": "N09000001", - "url": "https://antrimandnewtownabbey.gov.uk/residents/bins-recycling/bins-schedule/?Id=643", - "wiki_command_url_override": "https://antrimandnewtownabbey.gov.uk/residents/bins-recycling/bins-schedule/?Id=XXXX", - "wiki_name": "Antrim and Newtownabbey", - "wiki_note": "Navigate to [https://antrimandnewtownabbey.gov.uk/residents/bins-recycling/bins-schedule] and search for your street name. Use the URL with the ID to replace XXXXXXXX with your specific ID." - }, - "ArdsAndNorthDownCouncil": { - "uprn": "187136177", - "url": "https://www.ardsandnorthdown.gov.uk", - "wiki_command_url_override": "https://www.ardsandnorthdown.gov.uk", - "wiki_name": "Ards and North Down", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "N09000011" - }, - "ArgyllandButeCouncil": { - "skip_get_url": true, - "postcode": "PA286LJ", - "uprn": "000125011723", - "url": "https://www.argyll-bute.gov.uk/rubbish-and-recycling/household-waste/bin-collection", - "web_driver": "http://selenium:4444", - "wiki_name": "Argyll and Bute", - "wiki_note": "Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "S12000035" - }, - "ArmaghBanbridgeCraigavonCouncil": { - "LAD24CD": "N09000002", - "uprn": "185625284", - "url": "https://www.armaghbanbridgecraigavon.gov.uk/", - "wiki_command_url_override": "https://www.armaghbanbridgecraigavon.gov.uk/", - "wiki_name": "Armagh City, Banbridge and Craigavon", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN." - }, - "ArunCouncil": { - "house_number": "1", - "postcode": "BN16 4DA", - "skip_get_url": true, - "url": "https://www1.arun.gov.uk/when-are-my-bins-collected", - "web_driver": "http://selenium:4444", - "wiki_name": "Arun", - "wiki_note": "Pass the house name/number and postcode in their respective parameters, both wrapped in double quotes. This parser requires a Selenium webdriver.", - "LAD24CD": "E07000224" - }, - "AshfieldDistrictCouncil": { - "house_number": "1", - "postcode": "NG16 6RH", - "url": "https://www.ashfield.gov.uk", - "web_driver": "http://selenium:4444", - "wiki_name": "Ashfield", - "wiki_note": "Pass the house name/number and postcode in their respective parameters, both wrapped in double quotes. This parser requires a Selenium webdriver", - "LAD24CD": "E07000170" - }, - "AshfordBoroughCouncil": { - "postcode": "TN23 7SP", - "uprn": "100060777899", - "url": "https://ashford.gov.uk", - "web_driver": "http://selenium:4444", - "wiki_command_url_override": "https://ashford.gov.uk", - "wiki_name": "Ashford", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000105" - }, - "AylesburyValeCouncil": { - "skip_get_url": true, - "uprn": "766252532", - "url": "http://avdcbins.web-labs.co.uk/RefuseApi.asmx", - "wiki_name": "Buckinghamshire", - "wiki_note": "To get the UPRN, please use [FindMyAddress](https://www.findmyaddress.co.uk/search). Returns all published collections in the past, present, future.", - "LAD24CD": "E06000060" - }, - "BCPCouncil": { - "LAD24CD": "E06000058", - "skip_get_url": true, - "uprn": "100040810214", - "url": "https://online.bcpcouncil.gov.uk/bindaylookup/", - "wiki_name": "Bournemouth, Christchurch and Poole", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN." - }, - "BaberghDistrictCouncil": { - "house_number": "Monday", - "postcode": "Week 1", - "skip_get_url": true, - "uprn": "Tuesday", - "url": "https://www.babergh.gov.uk", - "wiki_name": "Babergh", - "wiki_note": "Use the House Number field to pass the DAY of the week for your NORMAL collections. [Monday/Tuesday/Wednesday/Thursday/Friday]. [OPTIONAL] Use the 'postcode' field to pass the WEEK for your garden collection. [Week 1/Week 2]. [OPTIONAL] Use the 'uprn' field to pass the DAY for your garden collection. [Monday/Tuesday/Wednesday/Thursday/Friday]", - "LAD24CD": "E07000200" - }, - "BarkingDagenham": { - "house_number": "19", - "postcode": "RM6 6XH", - "skip_get_url": true, - "web_driver": "http://selenium:4444", - "url": "https://www.lbbd.gov.uk/rubbish-recycling/household-bin-collection/check-your-bin-collection-days", - "wiki_name": "Barking and Dagenham", - "wiki_note": "Use house number and postcode. Requires Selenium.", - "LAD24CD": "E09000002" - }, - "BarnetCouncil": { - "house_number": "HA8 7NA, 2, MANOR PARK GARDENS, EDGWARE, BARNET", - "postcode": "HA8 7NA", - "skip_get_url": true, - "url": "https://www.barnet.gov.uk/recycling-and-waste/bin-collections/find-your-bin-collection-day", - "web_driver": "http://selenium:4444", - "wiki_name": "Barnet", - "wiki_note": "Follow the instructions [here](https://www.barnet.gov.uk/recycling-and-waste/bin-collections/find-your-bin-collection-day) until you get the page listing your address, then copy the entire address text and use that in the house number field. This parser requires a Selenium webdriver.", - "LAD24CD": "E09000003" - }, - "BarnsleyMBCouncil": { - "postcode": "S36 9AN", - "skip_get_url": true, - "uprn": "2007004502", - "url": "https://waste.barnsley.gov.uk/ViewCollection/Collections", - "wiki_name": "Barnsley", - "wiki_note": "To get the UPRN, you will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E08000016" - }, - "BasildonCouncil": { - "skip_get_url": true, - "uprn": "10013350430", - "url": "https://basildonportal.azurewebsites.net/api/getPropertyRefuseInformation", - "wiki_name": "Basildon", - "wiki_note": "To get the UPRN, you will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000066" - }, - "BasingstokeCouncil": { - "LAD24CD": "E07000084", - "skip_get_url": true, - "uprn": "100060220926", - "url": "https://www.basingstoke.gov.uk/bincollection", - "wiki_name": "Basingstoke and Deane", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN." - }, - "BathAndNorthEastSomersetCouncil": { - "skip_get_url": true, - "uprn": "100120000855", - "url": "https://www.bathnes.gov.uk/webforms/waste/collectionday/", - "wiki_name": "Bath and North East Somerset", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E06000022" - }, - "BedfordBoroughCouncil": { - "skip_get_url": true, - "uprn": "10024232065", - "url": "https://www.bedford.gov.uk/bins-and-recycling/household-bins-and-recycling/check-your-bin-day", - "wiki_name": "Bedford", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E06000055" - }, - "BedfordshireCouncil": { - "postcode": "SG19 2UP", - "skip_get_url": true, - "uprn": "10000802040", - "url": "https://www.centralbedfordshire.gov.uk/info/163/bins_and_waste_collections_-_check_bin_collection_day", - "wiki_name": "Central Bedfordshire", - "wiki_note": "In order to use this parser, you must provide a valid postcode and a UPRN retrieved from the council's website for your specific address.", - "LAD24CD": "E06000056" - }, - "BelfastCityCouncil": { - "postcode": "BT10 0GY", - "skip_get_url": true, - "uprn": "185086469", - "url": "https://online.belfastcity.gov.uk/find-bin-collection-day/Default.aspx", - "wiki_name": "Belfast", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "N09000003" - }, - "BexleyCouncil": { - "uprn": "100020196143", - "skip_get_url": true, - "url": "https://waste.bexley.gov.uk/waste", - "wiki_name": "Bexley", - "wiki_note": "Provide your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to locate it.", - "LAD24CD": "E09000004" - }, - "BirminghamCityCouncil": { - "postcode": "B5 7XE", - "uprn": "100070445256", - "url": "https://www.birmingham.gov.uk/xfp/form/619", - "wiki_name": "Birmingham", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E08000025" - }, - "BlabyDistrictCouncil": { - "uprn": "100030401782", - "url": "https://www.blaby.gov.uk", - "wiki_command_url_override": "https://www.blaby.gov.uk", - "wiki_name": "Blaby", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000129" - }, - "BlackburnCouncil": { - "url": "https://www.blaby.gov.uk", - "LAD24CD": "E06000008", - "skip_get_url": true, - "uprn": "100010733027", - "wiki_name": "Blackburn with Darwen", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN." - }, - "BlaenauGwentCountyBoroughCouncil": { - "postcode": "NP23 7TE", - "skip_get_url": false, - "uprn": "100100471367", - "url": "https://www.blaenau-gwent.gov.uk", - "web_driver": "http://selenium:4444", - "wiki_name": "Blaenau Gwent", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "W06000019" - }, - "BolsoverCouncil": { - "uprn": "100030066827", - "url": "https://bolsover.gov.uk", - "wiki_name": "Bolsover", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000033" - }, - "BoltonCouncil": { - "postcode": "BL1 5PQ", - "skip_get_url": true, - "uprn": "100010886936", - "url": "https://carehomes.bolton.gov.uk/bins.aspx", - "web_driver": "http://selenium:4444", - "wiki_name": "Bolton", - "wiki_note": "To get the UPRN, you will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search). Previously required a single field that was UPRN and full address; now requires UPRN and postcode as separate fields.", - "LAD24CD": "E08000001" - }, - "BostonBoroughCouncil": { - "house_number": "CEDAR", - "postcode": "PE20 1AY", - "skip_get_url": true, - "url": "https://www.boston.gov.uk/findwastecollections", - "web_driver": "http://selenium:4444", - "wiki_name": "Boston", - "wiki_note": "Provide your house number in the `house_number` parameter and postcode in the `postcode` parameter.", - "LAD24CD": "E07000136" - }, - "BracknellForestCouncil": { - "house_number": "57", - "paon": "57", - "postcode": "GU47 9BS", - "skip_get_url": true, - "url": "https://selfservice.mybfc.bracknell-forest.gov.uk/w/webpage/waste-collection-days", - "wiki_name": "Bracknell Forest", - "wiki_note": "Pass the house number and postcode in their respective parameters.", - "LAD24CD": "E06000036" - }, - "BradfordMDC": { - "custom_component_show_url_field": false, - "skip_get_url": true, - "uprn": "100051146921", - "url": "https://onlineforms.bradford.gov.uk/ufs/collectiondates.eb", - "wiki_name": "Bradford", - "wiki_note": "To get the UPRN, you will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search). Postcode isn't parsed by this script, but you can pass it in double quotes.", - "LAD24CD": "E08000032" - }, - "BraintreeDistrictCouncil": { - "postcode": "CO5 9BD", - "skip_get_url": true, - "uprn": "10006930172", - "url": "https://www.braintree.gov.uk/", - "wiki_name": "Braintree", - "wiki_note": "Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", - "LAD24CD": "E07000067" - }, - "BrecklandCouncil": { - "uprn": "100091495479", - "url": "https://www.breckland.gov.uk", - "wiki_command_url_override": "https://www.breckland.gov.uk", - "wiki_name": "Breckland", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000143" - }, - "BrentCouncil": { - "house_number": "25", - "postcode": "HA3 0QU", - "url": "https://recyclingservices.brent.gov.uk/waste", - "wiki_name": "Brent", - "wiki_note": "Pass the house number and postcode in their respective parameters.", - "LAD24CD": "E09000005" - }, - "BrightonandHoveCityCouncil": { - "house_number": "44", - "postcode": "BN1 8NE", - "skip_get_url": true, - "url": "https://cityclean.brighton-hove.gov.uk/link/collections", - "web_driver": "http://selenium:4444", - "wiki_name": "Brighton and Hove", - "wiki_note": "Use house number and postcode. Requires Selenium", - "LAD24CD": "E06000043" - }, - "BristolCityCouncil": { - "LAD24CD": "E06000023", - "skip_get_url": true, - "uprn": "116638", - "url": "https://bristolcouncil.powerappsportals.com/completedynamicformunauth/?servicetypeid=7dce896c-b3ba-ea11-a812-000d3a7f1cdc", - "wiki_name": "City of Bristol", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN." - }, - "BroadlandDistrictCouncil": { - "skip_get_url": true, - "house_number": "1", - "postcode": "NR10 3FD", - "url": "https://area.southnorfolkandbroadland.gov.uk/FindAddress", - "web_driver": "http://selenium:4444", - "wiki_name": "Broadland", - "wiki_note": "Use house number and postcode. Requires Selenium.", - "LAD24CD": "E07000144" - }, - "BromleyBoroughCouncil": { - "url": "https://recyclingservices.bromley.gov.uk/waste/6087017", - "web_driver": "http://selenium:4444", - "wiki_command_url_override": "https://recyclingservices.bromley.gov.uk/waste/XXXXXXX", - "wiki_name": "Bromley", - "wiki_note": "Follow the instructions [here](https://recyclingservices.bromley.gov.uk/waste) until the \"Your bin days\" page then copy the URL and replace the URL in the command.", - "LAD24CD": "E09000006" - }, - "BromsgroveDistrictCouncil": { - "uprn": "100120584652", - "url": "https://www.bromsgrove.gov.uk", - "wiki_command_url_override": "https://www.bromsgrove.gov.uk", - "wiki_name": "Bromsgrove", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000234" - }, - "BroxbourneCouncil": { - "postcode": "EN8 7FL", - "uprn": "148048608", - "url": "https://www.broxbourne.gov.uk", - "wiki_name": "Broxbourne", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000095" - }, - "BroxtoweBoroughCouncil": { - "postcode": "NG16 2LY", - "skip_get_url": true, - "uprn": "100031325997", - "url": "https://www.broxtowe.gov.uk/", - "web_driver": "http://selenium:4444", - "wiki_name": "Broxtowe", - "wiki_note": "Pass the UPRN and postcode. To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000172" - }, - "BuckinghamshireCouncil": { - "house_number": "2", - "postcode": "HP13 7BA", - "skip_get_url": true, - "url": "https://iapp.itouchvision.com/iappcollectionday/collection-day/?uuid=FA353FC74600CBE61BE409534D00A8EC09BDA3AC&lang=en", - "web_driver": "http://selenium:4444", - "wiki_name": "Buckinghamshire", - "wiki_note": "Pass the house name/number and postcode in their respective arguments, both wrapped in quotes.", - "LAD24CD": "E06000060" - }, - "BurnleyBoroughCouncil": { - "uprn": "100010347165", - "url": "https://www.burnley.gov.uk", - "wiki_name": "Burnley", - "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000117" - }, - "BuryCouncil": { - "house_number": "3", - "postcode": "M26 3XY", - "skip_get_url": true, - "url": "https://www.bury.gov.uk/waste-and-recycling/bin-collection-days-and-alerts", - "wiki_name": "Bury", - "wiki_note": "Pass the postcode and house number in their respective arguments, both wrapped in quotes.", - "LAD24CD": "E08000002" - }, - "CalderdaleCouncil": { - "postcode": "OL14 7EX", - "skip_get_url": true, - "uprn": "010035034598", - "url": "https://www.calderdale.gov.uk/environment/waste/household-collections/collectiondayfinder.jsp", - "web_driver": "http://selenium:4444", - "wiki_name": "Calderdale", - "wiki_note": "Pass the UPRN and postcode. To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E08000033" - }, - "CambridgeCityCouncil": { - "uprn": "200004159750", - "url": "https://www.cambridge.gov.uk/", - "wiki_name": "Cambridge", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000008" - }, - "CannockChaseDistrictCouncil": { - "postcode": "WS15 1JA", - "skip_get_url": true, - "uprn": "200003095389", - "url": "https://www.cannockchasedc.gov.uk/", - "wiki_name": "Cannock Chase", - "wiki_note": "To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000192" - }, - "CanterburyCityCouncil": { - "uprn": "10094583181", - "url": "https://www.canterbury.gov.uk", - "wiki_command_url_override": "https://www.canterbury.gov.uk", - "wiki_name": "Canterbury", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000106" - }, - "CardiffCouncil": { - "skip_get_url": true, - "uprn": "100100112419", - "url": "https://www.gov.uk", - "wiki_name": "Cardiff", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "W06000015" - }, - "CarmarthenshireCountyCouncil": { - "uprn": "10004859302", - "url": "https://www.carmarthenshire.gov.wales", - "wiki_command_url_override": "https://www.carmarthenshire.gov.wales", - "wiki_name": "Carmarthenshire", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "W06000010" - }, - "CastlepointDistrictCouncil": { - "skip_get_url": true, - "uprn": "4525", - "url": "https://apps.castlepoint.gov.uk/cpapps/index.cfm?fa=wastecalendar", - "wiki_name": "Castle Point", - "wiki_note": "For this council, 'uprn' is actually a 4-digit code for your street. Go [here](https://apps.castlepoint.gov.uk/cpapps/index.cfm?fa=wastecalendar) and inspect the source of the dropdown box to find the 4-digit number for your street.", - "LAD24CD": "E07000069" - }, - "CeredigionCountyCouncil": { - "house_number": "BLAEN CWMMAGWR, TRISANT, CEREDIGION, SY23 4RQ", - "postcode": "SY23 4RQ", - "url": "https://www.ceredigion.gov.uk/resident/bins-recycling/", - "web_driver": "http://selenium:4444", - "wiki_name": "Ceredigion", - "wiki_note": "House Number is the full address as it appears on the drop-down on the site when you search by postcode. This parser requires a Selenium webdriver.", - "LAD24CD": "W06000008" - }, - "CharnwoodBoroughCouncil": { - "uprn": "100030446438", - "skip_get_url": true, - "url": "https://www.charnwood.gov.uk/pages/waste_collections_calendars", - "wiki_name": "Charnwood", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000130" - }, - "ChelmsfordCityCouncil": { - "house_number": "1 Celeborn Street, South Woodham Ferrers, Chelmsford, CM3 7AE", - "postcode": "CM3 7AE", - "url": "https://www.chelmsford.gov.uk/myhome/", - "web_driver": "http://selenium:4444", - "wiki_name": "Chelmsford", - "wiki_note": "Follow the instructions [here](https://www.chelmsford.gov.uk/myhome/) until you get the page listing your address, then copy the entire address text and use that in the house number field.", - "LAD24CD": "E07000070" - }, - "CheltenhamBoroughCouncil": { - "postcode": "GL51 3NA", - "skip_get_url": true, - "uprn": "100120372027", - "url": "https://www.cheltenham.gov.uk", - "wiki_name": "Cheltenham", - "wiki_note": "Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000078" - }, - "CherwellDistrictCouncil": { - "uprn": "100121292407", - "url": "https://www.cherwell.gov.uk", - "wiki_name": "Cherwell", - "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", - "LAD24CD": "E07000177" - }, - "CheshireEastCouncil": { - "skip_get_url": true, - "uprn": "100012830647", - "url": "https://online.cheshireeast.gov.uk/mycollectionday", - "wiki_name": "Cheshire East", - "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", - "LAD24CD": "E06000049" - }, - "CheshireWestAndChesterCouncil": { - "skip_get_url": true, - "uprn": "100012346655", - "url": "https://my.cheshirewestandchester.gov.uk", - "wiki_name": "Cheshire West and Chester", - "wiki_note": "Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E06000050" - }, - "ChesterfieldBoroughCouncil": { - "skip_get_url": true, - "uprn": "74008234", - "url": "https://www.chesterfield.gov.uk", - "wiki_name": "Chesterfield", - "wiki_note": "Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000034" - }, - "ChichesterDistrictCouncil": { - "house_number": "7", - "postcode": "RH14 0JT", - "skip_get_url": true, - "url": "https://www.chichester.gov.uk/checkyourbinday", - "web_driver": "http://selenium:4444", - "wiki_name": "Chichester", - "wiki_note": "Needs the full address and postcode as it appears on [this page](https://www.chichester.gov.uk/checkyourbinday).", - "LAD24CD": "E07000225" - }, - "ChorleyCouncil": { - "postcode": "PR6 7PG", - "skip_get_url": true, - "uprn": "100010382247", - "url": "https://myaccount.chorley.gov.uk/wastecollections.aspx", - "web_driver": "http://selenium:4444", - "wiki_name": "Chorley", - "wiki_note": "Chorley needs to be passed both a Postcode & UPRN to work. Find this on [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000118" - }, - "ColchesterCityCouncil": { - "house_number": "29", - "paon": "29", - "postcode": "CO2 8UN", - "skip_get_url": false, - "url": "https://www.colchester.gov.uk/your-recycling-calendar", - "web_driver": "http://selenium:4444", - "wiki_name": "Colchester", - "wiki_note": "Pass the house name/number in the house number parameter, wrapped in double quotes.", - "LAD24CD": "E07000071" - }, - "ConwyCountyBorough": { - "uprn": "100100429249", - "url": "https://www.conwy.gov.uk", - "wiki_name": "Conwy", - "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", - "LAD24CD": "W06000003" - }, - "CopelandBoroughCouncil": { - "LAD24CD": "E07000028", - "uprn": "100110734613", - "url": "https://www.copeland.gov.uk", - "wiki_name": "Copeland", - "wiki_note": "*****This has now been replaced by Cumberland Council****" - }, - "CornwallCouncil": { - "skip_get_url": true, - "uprn": "100040128734", - "url": "https://www.cornwall.gov.uk/my-area/", - "wiki_name": "Cornwall", - "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", - "LAD24CD": "E06000052" - }, - "CotswoldDistrictCouncil": { - "house_number": "19", - "postcode": "GL56 0GB", - "skip_get_url": true, - "url": "https://community.cotswold.gov.uk/s/waste-collection-enquiry", - "web_driver": "http://selenium:4444", - "wiki_name": "Cotswold", - "wiki_note": "Pass the full address in the house number and postcode in", - "LAD24CD": "E07000079" - }, - "CoventryCityCouncil": { - "url": "https://www.coventry.gov.uk/directory-record/62310/abberton-way-", - "wiki_command_url_override": "https://www.coventry.gov.uk/directory_record/XXXXXX/XXXXXX", - "wiki_name": "Coventry", - "wiki_note": "Follow the instructions [here](https://www.coventry.gov.uk/bin-collection-calendar) until you get the page that shows the weekly collections for your address then copy the URL and replace the URL in the command.", - "LAD24CD": "E08000026" - }, - "CrawleyBoroughCouncil": { - "house_number": "9701076", - "skip_get_url": true, - "uprn": "100061785321", - "url": "https://my.crawley.gov.uk/", - "wiki_name": "Crawley", - "wiki_note": "Crawley needs to be passed both a UPRN and a USRN to work. Find these on [FindMyAddress](https://www.findmyaddress.co.uk/search) or [FindMyStreet](https://www.findmystreet.co.uk/map).", - "LAD24CD": "E07000226" - }, - "CroydonCouncil": { - "house_number": "13", - "postcode": "SE25 5DW", - "skip_get_url": true, - "url": "https://service.croydon.gov.uk/wasteservices/w/webpage/bin-day-enter-address", - "web_driver": "http://selenium:4444", - "wiki_name": "Croydon", - "wiki_note": "Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver.", - "LAD24CD": "E09000008" - }, - "CumberlandAllerdaleCouncil": { - "house_number": "2", - "postcode": "CA13 0DE", - "url": "https://www.allerdale.gov.uk", - "wiki_name": "Cumberland", - "wiki_note": "Pass the house number and postcode in their respective parameters.", - "LAD24CD": "E06000063" - }, - "CumberlandCouncil": { - "uprn": "100110734613", - "url": "https://waste.cumberland.gov.uk", - "wiki_name": "Cumberland", - "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", - "LAD24CD": "E06000063" - }, - "DacorumBoroughCouncil": { - "house_number": "13", - "postcode": "HP3 9JY", - "skip_get_url": true, - "url": "https://webapps.dacorum.gov.uk/bincollections/", - "web_driver": "http://selenium:4444", - "wiki_name": "Dacorum", - "wiki_note": "Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver.", - "LAD24CD": "E07000096" - }, - "DartfordBoroughCouncil": { - "uprn": "100060861698", - "url": "https://www.dartford.gov.uk/waste-recycling/collection-day", - "skip_get_url": true, - "wiki_name": "Dartford", - "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", - "LAD24CD": "E07000107" - }, - "DenbighshireCouncil": { - "uprn": "200004299351", - "url": "https://www.denbighshire.gov.uk/", - "wiki_name": "Denbighshire", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "W06000004" - }, - "DerbyCityCouncil": { - "uprn": "10010684240", - "url": "https://www.derby.gov.uk", - "wiki_name": "Derby", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E06000015" - }, - "DerbyshireDalesDistrictCouncil": { - "postcode": "DE4 3AS", - "skip_get_url": true, - "uprn": "10070102161", - "url": "https://www.derbyshiredales.gov.uk/", - "wiki_name": "Derbyshire Dales", - "wiki_note": "Pass the UPRN and postcode. To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000035" - }, - "DoncasterCouncil": { - "skip_get_url": true, - "uprn": "100050768956", - "url": "https://www.doncaster.gov.uk/Compass/Entity/Launch/D3/", - "wiki_name": "Doncaster", - "wiki_note": "Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E08000017" - }, - "DorsetCouncil": { - "skip_get_url": true, - "uprn": "100040711049", - "url": "https://www.dorsetcouncil.gov.uk/", - "wiki_name": "Dorset Council", - "wiki_note": "Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E06000059" - }, - "DoverDistrictCouncil": { - "uprn": "100060908340", - "url": "https://collections.dover.gov.uk/property", - "skip_get_url": true, - "wiki_name": "Dover", - "wiki_note": "To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000108" - }, - "DudleyCouncil": { - "uprn": "90014244", - "url": "https://my.dudley.gov.uk", - "wiki_command_url_override": "https://my.dudley.gov.uk", - "wiki_name": "Dudley", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E08000027" - }, - "DundeeCityCouncil": { - "uprn": "9059043390", - "url": "https://www.dundeecity.gov.uk/", - "wiki_name": "Dundee City", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "S12000042" - }, - "DurhamCouncil": { - "LAD24CD": "E06000047", - "skip_get_url": true, - "uprn": "200003218818", - "url": "https://www.durham.gov.uk/bincollections?uprn=", - "wiki_name": "County Durham", - "wiki_note": "Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search)." - }, - "EalingCouncil": { - "skip_get_url": true, - "uprn": "12073883", - "url": "https://www.ealing.gov.uk/site/custom_scripts/WasteCollectionWS/home/FindCollection", - "wiki_name": "Ealing", - "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E09000009" - }, - "EastAyrshireCouncil": { - "uprn": "127074727", - "url": "https://www.east-ayrshire.gov.uk", - "wiki_command_url_override": "https://www.east-ayrshire.gov.uk", - "wiki_name": "East Ayrshire", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "S12000008" - }, - "EastbourneBoroughCouncil": { - "uprn": "100060011258", - "url": "https://www.lewes-eastbourne.gov.uk/article/1158/When-is-my-bin-collection-day", - "skip_get_url": true, - "wiki_name": "Eastbourne", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000061" - }, - "EastCambridgeshireCouncil": { - "skip_get_url": true, - "uprn": "10002597178", - "url": "https://www.eastcambs.gov.uk/", - "wiki_name": "East Cambridgeshire", - "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000009" - }, - "EastDevonDC": { - "uprn": "010090909915", - "url": "https://eastdevon.gov.uk/recycling-and-waste/recycling-waste-information/when-is-my-bin-collected/", - "skip_get_url": true, - "wiki_name": "East Devon", - "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000040" - }, - "EastHertsCouncil": { - "LAD24CD": "E07000097", - "house_number": "1", - "postcode": "CM20 2FZ", - "skip_get_url": true, - "url": "https://www.eastherts.gov.uk", - "web_driver": "http://selenium:4444", - "wiki_name": "East Herts Council", - "wiki_note": "Pass the house number and postcode in their respective parameters." - }, - "EastLindseyDistrictCouncil": { - "house_number": "1", - "postcode": "PE22 0YD", - "skip_get_url": true, - "url": "https://www.e-lindsey.gov.uk/", - "web_driver": "http://selenium:4444", - "wiki_name": "East Lindsey", - "wiki_note": "Pass the house name/number and postcode in their respective parameters. This parser requires a Selenium webdriver.", - "LAD24CD": "E07000137" - }, - "EastLothianCouncil": { - "house_number": "Flat 1", - "postcode": "EH21 6QA", - "skip_get_url": true, - "url": "https://eastlothian.gov.uk", - "wiki_name": "East Lothian", - "wiki_note": "Pass the house number and postcode in their respective parameters", - "LAD24CD": "S12000010" - }, - "EastRenfrewshireCouncil": { - "house_number": "23", - "postcode": "G46 6RG", - "skip_get_url": true, - "url": "https://eastrenfrewshire.gov.uk/", - "web_driver": "http://selenium:4444", - "wiki_name": "East Renfrewshire", - "wiki_note": "Pass the house name/number and postcode in their respective parameters. This parser requires a Selenium webdriver.", - "LAD24CD": "S12000011" - }, - "EastRidingCouncil": { - "LAD24CD": "E06000011", - "house_number": "14 THE LEASES BEVERLEY HU17 8LG", - "postcode": "HU17 8LG", - "skip_get_url": true, - "url": "https://wasterecyclingapi.eastriding.gov.uk", - "web_driver": "http://selenium:4444", - "wiki_name": "East Riding of Yorkshire", - "wiki_note": "Put the full address as it displays on the council website dropdown when you do the check manually." - }, - "EastStaffordshireBoroughCouncil": { - "url": "https://www.eaststaffsbc.gov.uk/bins-rubbish-recycling/collection-dates/68382", - "wiki_command_url_override": "https://www.eaststaffsbc.gov.uk/bins-rubbish-recycling/collection-dates/XXXXX", - "wiki_name": "East Staffordshire", - "wiki_note": "Replace `XXXXX` with your property's ID when selecting from https://www.eaststaffsbc.gov.uk/bins-rubbish-recycling/collection-dates.", - "LAD24CD": "E07000193" - }, - "EastSuffolkCouncil": { - "postcode": "IP11 9FJ", - "skip_get_url": true, - "uprn": "10093544720", - "url": "https://my.eastsuffolk.gov.uk/service/Bin_collection_dates_finder", - "web_driver": "http://selenium:4444", - "wiki_name": "East Suffolk", - "wiki_note": "To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search). This parser requires a Selenium webdriver.", - "LAD24CD": "E07000244" - }, - "EastleighBoroughCouncil": { - "skip_get_url": true, - "uprn": "100060303535", - "url": "https://www.eastleigh.gov.uk/waste-bins-and-recycling/collection-dates/your-waste-bin-and-recycling-collections?uprn=", - "web_driver": "http://selenium:4444", - "wiki_name": "Eastleigh", - "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000086" - }, - "EdinburghCityCouncil": { - "LAD24CD": "S12000036", - "house_number": "Tuesday", - "postcode": "Week 1", - "skip_get_url": true, - "url": "https://www.edinburgh.gov.uk", - "wiki_name": "City of Edinburgh", - "wiki_note": "Use the House Number field to pass the DAY of the week for your collections. Monday/Tuesday/Wednesday/Thursday/Friday. Use the 'postcode' field to pass the WEEK for your collection. [Week 1/Week 2]" - }, - "ElmbridgeBoroughCouncil": { - "uprn": "10013119164", - "url": "https://www.elmbridge.gov.uk", - "wiki_command_url_override": "https://www.elmbridge.gov.uk", - "wiki_name": "Elmbridge", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000207" - }, - "EnfieldCouncil": { - "house_number": "111", - "postcode": "N13 5AJ", - "skip_get_url": true, - "url": "https://www.enfield.gov.uk/services/rubbish-and-recycling/find-my-collection-day", - "web_driver": "http://selenium:4444", - "wiki_name": "Enfield", - "wiki_note": "Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver.", - "LAD24CD": "E09000010" - }, - "EnvironmentFirst": { - "url": "https://environmentfirst.co.uk/house.php?uprn=100060055444", - "wiki_command_url_override": "https://environmentfirst.co.uk/house.php?uprn=XXXXXXXXXX", - "wiki_name": "Environment First", - "wiki_note": "For properties with collections managed by Environment First, such as Lewes and Eastbourne. Replace the XXXXXXXXXX with the UPRN of your property\u2014you can use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find this." - }, - "EppingForestDistrictCouncil": { - "postcode": "IG9 6EP", - "url": "https://eppingforestdc.maps.arcgis.com/apps/instant/lookup/index.html?appid=bfca32b46e2a47cd9c0a84f2d8cdde17&find=IG9%206EP", - "skip_get_url": true, - "web_driver": "http://selenium:4444", - "wiki_name": "Epping Forest", - "wiki_note": "Add your postcode.", - "LAD24CD": "E07000072" - }, - "EpsomandEwellBoroughCouncil": { - "uprn": "100061349083", - "url": "https://www.epsom-ewell.gov.uk", - "wiki_name": "Epsom and Ewell", - "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", - "LAD24CD": "E07000208" - }, - "ErewashBoroughCouncil": { - "skip_get_url": true, - "uprn": "10003582028", - "url": "https://map.erewash.gov.uk/isharelive.web/myerewash.aspx", - "wiki_name": "Erewash", - "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000036" - }, - "ExeterCityCouncil": { - "uprn": "100040212270", - "url": "https://www.exeter.gov.uk", - "wiki_name": "Exeter", - "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000041" - }, - "FalkirkCouncil": { - "uprn": "136065818", - "url": "https://www.falkirk.gov.uk", - "wiki_command_url_override": "https://www.falkirk.gov.uk", - "wiki_name": "Falkirk", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "S12000014" - }, - "FarehamBoroughCouncil": { - "postcode": "PO14 4NR", - "skip_get_url": true, - "url": "https://www.fareham.gov.uk/internetlookups/search_data.aspx?type=JSON&list=DomesticBinCollections&Road=&Postcode=PO14%204NR", - "wiki_name": "Fareham", - "wiki_note": "Pass the postcode in the postcode parameter, wrapped in double quotes.", - "LAD24CD": "E07000087" - }, - "FenlandDistrictCouncil": { - "skip_get_url": true, - "uprn": "200002981143", - "url": "https://www.fenland.gov.uk/article/13114/", - "wiki_name": "Fenland", - "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000010" - }, - "FermanaghOmaghDistrictCouncil": { - "house_number": "20", - "postcode": "BT74 6DQ", - "skip_get_url": true, - "url": "https://www.fermanaghomagh.com/services/environment-and-waste/waste-collection-calendar/", - "wiki_name": "Fermanagh and Omagh", - "wiki_note": "Pass the house number and postcode in their respective parameters.", - "LAD24CD": "N09000006" - }, - "FifeCouncil": { - "uprn": "320203521", - "url": "https://www.fife.gov.uk", - "wiki_command_url_override": "https://www.fife.gov.uk", - "wiki_name": "Fife", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "S12000047" - }, - "FlintshireCountyCouncil": { - "uprn": "100100213710", - "url": "https://digital.flintshire.gov.uk", - "wiki_command_url_override": "https://digital.flintshire.gov.uk", - "wiki_name": "Flintshire", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "W06000005" - }, - "FolkstoneandHytheDistrictCouncil": { - "LAD24CD": "E07000112", - "skip_get_url": true, - "uprn": "50032097", - "url": "https://www.folkestone-hythe.gov.uk", - "wiki_name": "Folkestone and Hythe", - "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN." - }, - "ForestOfDeanDistrictCouncil": { - "house_number": "ELMOGAL, PARKEND ROAD, BREAM, LYDNEY", - "postcode": "GL15 6JT", - "skip_get_url": true, - "url": "https://community.fdean.gov.uk/s/waste-collection-enquiry", - "web_driver": "http://selenium:4444", - "wiki_name": "Forest of Dean", - "wiki_note": "Pass the full address in the house number and postcode parameters. This parser requires a Selenium webdriver.", - "LAD24CD": "E07000080" - }, - "FyldeCouncil": { - "uprn": "100010402452", - "url": "https://www.fylde.gov.uk", - "wiki_command_url_override": "https://www.fylde.gov.uk", - "wiki_name": "Fylde", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000119" - }, - "GatesheadCouncil": { - "house_number": "Bracken Cottage", - "postcode": "NE16 5LQ", - "skip_get_url": true, - "url": "https://www.gateshead.gov.uk/", - "web_driver": "http://selenium:4444", - "wiki_name": "Gateshead", - "wiki_note": "Pass the house name/number and postcode in their respective parameters. This parser requires a Selenium webdriver.", - "LAD24CD": "E08000037" - }, - "GedlingBoroughCouncil": { - "house_number": "Friday G4, Friday J", - "skip_get_url": true, - "url": "https://www.gedling.gov.uk/", - "wiki_name": "Gedling", - "wiki_note": "Use [this site](https://www.gbcbincalendars.co.uk/) to find the collections for your address. Use the `-n` parameter to add them in a comma-separated list inside quotes, such as: 'Friday G4, Friday J'.", - "LAD24CD": "E07000173" - }, - "GlasgowCityCouncil": { - "uprn": "906700034497", - "url": "https://onlineservices.glasgow.gov.uk/forms/RefuseAndRecyclingWebApplication/AddressSearch.aspx", - "skip_get_url": true, - "wiki_name": "Glasgow City", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "S12000049" - }, - "GloucesterCityCouncil": { - "house_number": "111", - "postcode": "GL2 0RR", - "skip_get_url": true, - "uprn": "100120479507", - "url": "https://gloucester-self.achieveservice.com/service/Bins___Check_your_bin_day", - "web_driver": "http://selenium:4444", - "wiki_name": "Gloucester", - "wiki_note": "Pass the house number, postcode, and UPRN in their respective parameters. This parser requires a Selenium webdriver.", - "LAD24CD": "E07000081" - }, - "GooglePublicCalendarCouncil": { - "url": "https://calendar.google.com/calendar/ical/0d775884b4db6a7bae5204f06dae113c1a36e505b25991ebc27c6bd42edf5b5e%40group.calendar.google.com/public/basic.ics", - "wiki_name": "Google Calendar (Public)", - "wiki_note": "The URL should be the public ics file URL for the public Google calendar. See https://support.google.com/calendar/answer/37083?sjid=7202815583021446882-EU. Councils that currently need this are Trafford.", - "supported_councils": [ - "Trafford", - "Clackmannanshire", - "Havant", - "North Warwickshire", - "Newry and Mourne", - "East Dunbartonshire", - "Pendle", - "Torfaen", - "East Hampshire", - "Ribble Valley", - "Brentwood", - "Isle of Wight", - "Westmorland and Furness", - "Derry City and Strabane", - "Norwich" - ], - "supported_councils_LAD24CD": [ - "E06000046", - "E07000068", - "E07000085", - "E07000090", - "E07000124", - "E07000218", - "E08000009", - "N09000005", - "N09000010", - "S12000005", - "S12000045", - "W06000020", - "E07000122" - ] - }, - "GraveshamBoroughCouncil": { - "skip_get_url": true, - "uprn": "100060927046", - "url": "https://www.gravesham.gov.uk", - "wiki_name": "Gravesham", - "wiki_note": "Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000109" - }, - "GreatYarmouthBoroughCouncil": { - "postcode": "NR31 7EB", - "skip_get_url": true, - "uprn": "100090834792", - "url": "https://myaccount.great-yarmouth.gov.uk/article/6456/Find-my-waste-collection-days", - "web_driver": "http://selenium:4444", - "wiki_name": "Great Yarmouth", - "wiki_note": "Pass the postcode, and UPRN in their respective parameters. This parser requires a Selenium webdriver.", - "LAD24CD": "E07000145" - }, - "GuildfordCouncil": { - "house_number": "THE LODGE", - "postcode": "GU3 1AH", - "skip_get_url": true, - "url": "https://my.guildford.gov.uk/customers/s/view-bin-collections", - "web_driver": "http://selenium:4444", - "wiki_name": "Guildford", - "wiki_note": "If the bin day is 'today' then the collectionDate will only show today's date if before 7 AM; else the date will be in 'previousCollectionDate'.", - "LAD24CD": "E07000209" - }, - "GwyneddCouncil": { - "uprn": "10070350463", - "url": "https://diogel.gwynedd.llyw.cymru", - "wiki_name": "Gwynedd", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "W06000002" - }, - "HackneyCouncil": { - "house_number": "101", - "postcode": "N16 9AS", - "url": "https://www.hackney.gov.uk", - "wiki_name": "Hackney", - "wiki_note": "Pass the postcode and house number in their respective arguments, both wrapped in quotes.", - "LAD24CD": "E09000012" - }, - "HaltonBoroughCouncil": { - "house_number": "12", - "postcode": "WA7 4HA", - "skip_get_url": true, - "url": "https://webapp.halton.gov.uk/PublicWebForms/WasteServiceSearchv1.aspx#collections", - "web_driver": "http://selenium:4444", - "wiki_name": "Halton", - "wiki_note": "Pass the house number and postcode. This parser requires a Selenium webdriver.", - "LAD24CD": "E06000006" - }, - "HarboroughDistrictCouncil": { - "uprn": "100030489072", - "url": "https://www.harborough.gov.uk", - "wiki_command_url_override": "https://www.harborough.gov.uk", - "wiki_name": "Harborough", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000131" - }, - "HaringeyCouncil": { - "skip_get_url": true, - "uprn": "100021203052", - "url": "https://wastecollections.haringey.gov.uk/property", - "wiki_name": "Haringey", - "wiki_note": "Pass the UPRN, which can be found at `https://wastecollections.haringey.gov.uk/property/{uprn}`.", - "LAD24CD": "E09000014" - }, - "HarrogateBoroughCouncil": { - "LAD24CD": "E07000165", - "skip_get_url": true, - "uprn": "100050414307", - "url": "https://secure.harrogate.gov.uk/inmyarea", - "wiki_name": "Harrogate", - "wiki_note": "Pass the UPRN, which can be found at [this site](https://secure.harrogate.gov.uk/inmyarea). URL doesn't need to be passed." - }, - "HartDistrictCouncil": { - "skip_get_url": true, - "uprn": "100062349291", - "url": "https://www.hart.gov.uk/", - "wiki_name": "Hart", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000089" - }, - "HartlepoolBoroughCouncil": { - "uprn": "100110019551", - "url": "https://www.hartlepool.gov.uk", - "wiki_name": "Hartlepool", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", - "LAD24CD": "E06000001" - }, - "HastingsBoroughCouncil": { - "uprn": "100060038877", - "url": "https://www.hastings.gov.uk", - "wiki_command_url_override": "https://www.hastings.gov.uk", - "wiki_name": "Hastings", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000062" - }, - "HerefordshireCouncil": { - "uprn": "200002618844", - "url": "https://www.herefordshire.gov.uk/rubbish-recycling/check-bin-collection-day", - "skip_get_url": true, - "wiki_name": "Herefordshire", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E06000019" - }, - "HertsmereBoroughCouncil": { - "house_number": "1", - "postcode": "WD7 9HZ", - "skip_get_url": true, - "url": "https://www.hertsmere.gov.uk", - "web_driver": "http://selenium:4444", - "wiki_name": "Hertsmere", - "wiki_note": "Provide your house number in the `house_number` parameter and postcode in the `postcode` parameter.", - "LAD24CD": "E07000098" - }, - "HighPeakCouncil": { - "house_number": "9 Ellison Street, Glossop", - "postcode": "SK13 8BX", - "skip_get_url": true, - "url": "https://www.highpeak.gov.uk/findyourbinday", - "web_driver": "http://selenium:4444", - "wiki_name": "High Peak", - "wiki_note": "Pass the name of the street with the house number parameter, wrapped in double quotes. This parser requires a Selenium webdriver.", - "LAD24CD": "E07000037" - }, - "HighlandCouncil": { - "uprn": "130072429", - "url": "https://www.highland.gov.uk", - "wiki_command_url_override": "https://www.highland.gov.uk", - "wiki_name": "Highland", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "S12000017" - }, - "Hillingdon": { - "house_number": "1, Milverton Drive, Ickenham, UB10 8PP, Ickenham, Hillingdon", - "postcode": "UB10 8PP", - "skip_get_url": true, - "url": "https://www.hillingdon.gov.uk/collection-day", - "web_driver": "http://selenium:4444", - "wiki_name": "Hillingdon", - "wiki_note": "Pass the postcode and the full address as it appears in the address pulldown menu.", - "LAD24CD": "E09000017" - }, - "HinckleyandBosworthBoroughCouncil": { - "uprn": "100030533512", - "url": "https://www.hinckley-bosworth.gov.uk", - "wiki_name": "Hinckley and Bosworth", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000132" - }, - "HorshamDistrictCouncil": { - "postcode": "RH12 1AA", - "LAD24CD": "E07000227", - "skip_get_url": true, - "uprn": "010013792717", - "url": "https://www.horsham.gov.uk/waste-recycling-and-bins/household-bin-collections/check-your-bin-collection-day", - "web_driver": "http://selenium:4444", - "wiki_name": "Horsham", - "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search). This parser requires a Selenium webdriver." - }, - "HullCityCouncil": { - "LAD24CD": "E06000010", - "skip_get_url": true, - "uprn": "21033995", - "url": "https://www.hull.gov.uk/bins-and-recycling/bin-collections/bin-collection-day-checker", - "wiki_name": "Kingston upon Hull", - "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search)." - }, - "HuntingdonDistrictCouncil": { - "uprn": "10012048679", - "LAD24CD": "E07000011", - "url": "http://www.huntingdonshire.gov.uk/refuse-calendar/", - "skip_get_url": true, - "wiki_name": "Huntingdonshire", - "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search)." - }, - "HyndburnBoroughCouncil": { - "postcode": "BB1 4DJ", - "LAD24CD": "E07000120", - "uprn": "100010448773", - "url": "https://iapp.itouchvision.com/iappcollectionday/collection-day/?uuid=FEBA68993831481FD81B2E605364D00A8DC017A4", - "skip_get_url": true, - "web_driver": "http://selenium:4444", - "wiki_name": "Hyndburn", - "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search). This parser requires a Selenium webdriver." - }, - "IpswichBoroughCouncil": { - "house_number": "Siloam Place", - "url": "https://app.ipswich.gov.uk/bin-collection/", - "wiki_name": "Ipswich", - "wiki_note": "Provide only the street name (no house number) as the PAON", - "LAD24CD": "E07000202" - }, - "IslingtonCouncil": { - "uprn": "5300094897", - "url": "https://www.islington.gov.uk/your-area?Postcode=unused&Uprn=5300094897", - "wiki_command_url_override": "https://www.islington.gov.uk/your-area?Postcode=unused&Uprn=XXXXXXXX", - "wiki_name": "Islington", - "wiki_note": "Replace XXXXXXXX with your UPRN.", - "LAD24CD": "E09000019" - }, - "KingsLynnandWestNorfolkBC": { - "uprn": "10023636886", - "url": "https://www.west-norfolk.gov.uk/", - "wiki_name": "Kings Lynn and West Norfolk", - "wiki_note": "Provide your UPRN. Find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000146" - }, - "KingstonUponThamesCouncil": { - "url": "https://waste-services.kingston.gov.uk/waste/2701097", - "web_driver": "http://selenium:4444", - "wiki_command_url_override": "https://waste-services.kingston.gov.uk/waste/XXXXXXX", - "wiki_name": "Kingston upon Thames", - "wiki_note": "Follow the instructions [here](https://waste-services.kingston.gov.uk/waste) until the \"Your bin days\" page, then copy the URL and replace the URL in the command.", - "LAD24CD": "E09000021" - }, - "KirkleesCouncil": { - "skip_get_url": true, - "uprn": "83002937", - "url": "https://www.kirklees.gov.uk/beta/your-property-bins-recycling/your-bins", - "wiki_name": "Kirklees", - "wiki_note": "Provide your UPRN. Find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E08000034" - }, - "KnowsleyMBCouncil": { - "house_number": "2 ALTMOOR ROAD HUYTON L36 3UY", - "postcode": "L36 3UY", - "skip_get_url": true, - "url": "https://knowsleytransaction.mendixcloud.com/link/youarebeingredirected?target=bincollectioninformation", - "web_driver": "http://selenium:4444", - "wiki_name": "Knowsley", - "wiki_note": "Pass the postcode in the postcode parameter, wrapped in double quotes and with a space.", - "LAD24CD": "E08000011" - }, - "LancasterCityCouncil": { - "house_number": "1", - "postcode": "LA1 1RS", - "skip_get_url": true, - "url": "https://lcc-wrp.whitespacews.com", - "wiki_name": "Lancaster", - "wiki_note": "Pass the house number and postcode in their respective parameters.", - "LAD24CD": "E07000121" - }, - "LeedsCityCouncil": { - "house_number": "1", - "postcode": "LS6 2SE", - "skip_get_url": true, - "uprn": "72506983", - "url": "https://www.leeds.gov.uk/residents/bins-and-recycling/check-your-bin-day", - "web_driver": "http://selenium:4444", - "wiki_name": "Leeds", - "wiki_note": "Pass the house number, postcode, and UPRN. This parser requires a Selenium webdriver.", - "LAD24CD": "E08000035" - }, - "LeicesterCityCouncil": { - "uprn": "2465027976", - "url": "https://biffaleicester.co.uk", - "wiki_name": "Leicester", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E06000016" - }, - "LewesDistrictCouncil": { - "uprn": "100061930155", - "url": "https://www.lewes-eastbourne.gov.uk/article/1158/When-is-my-bin-collection-day", - "skip_get_url": true, - "wiki_name": "Lewes", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000063" - }, - "LichfieldDistrictCouncil": { - "uprn": "100031694085", - "url": "https://www.lichfielddc.gov.uk", - "wiki_command_url_override": "https://www.lichfielddc.gov.uk", - "wiki_name": "Lichfield", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000194" - }, - "LincolnCouncil": { - "postcode": "LN5 7SH", - "uprn": "000235024846", - "url": "https://lincoln.gov.uk", - "wiki_command_url_override": "https://lincoln.gov.uk", - "wiki_name": "City of Lincoln", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000138" - }, - "LisburnCastlereaghCityCouncil": { - "house_number": "97", - "postcode": "BT28 1JN", - "skip_get_url": true, - "url": "https://lisburn.isl-fusion.com", - "wiki_name": "Lisburn and Castlereagh", - "wiki_note": "Pass the house number and postcode in their respective parameters.", - "LAD24CD": "N09000007" - }, - "LiverpoolCityCouncil": { - "uprn": "38164600", - "skip_get_url": true, - "url": "https://liverpool.gov.uk/bins-and-recycling/bin-collections/", - "wiki_name": "Liverpool", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E08000012" - }, - "LondonBoroughEaling": { - "skip_get_url": true, - "uprn": "12081498", - "url": "https://www.ealing.gov.uk/site/custom_scripts/WasteCollectionWS/home/FindCollection", - "wiki_name": "Ealing", - "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E09000009" - }, - "LondonBoroughHarrow": { - "uprn": "100021298754", - "url": "https://www.harrow.gov.uk", - "wiki_command_url_override": "https://www.harrow.gov.uk", - "wiki_name": "Harrow", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E09000015" - }, - "LondonBoroughHavering": { - "uprn": "100021380730", - "url": "https://www.havering.gov.uk", - "wiki_name": "Havering", - "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E09000016" - }, - "LondonBoroughHounslow": { - "skip_get_url": true, - "uprn": "100021577765", - "url": "https://www.hounslow.gov.uk/homepage/86/recycling_and_waste_collection_day_finder", - "wiki_name": "Hounslow", - "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E09000018" - }, - "LondonBoroughLambeth": { - "skip_get_url": true, - "uprn": "100021881738", - "url": "https://wasteservice.lambeth.gov.uk/WhitespaceComms/GetServicesByUprn", - "wiki_name": "Lambeth", - "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E09000022" - }, - "LondonBoroughLewisham": { - "postcode": "SE12 9QF", - "skip_get_url": true, - "uprn": "100021954849", - "url": "https://www.lewisham.gov.uk", - "web_driver": "http://selenium:4444", - "wiki_name": "Lewisham", - "wiki_note": "Pass the UPRN and postcode. To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E09000023" - }, - "LondonBoroughOfRichmondUponThames": { - "house_number": "March Road", - "skip_get_url": true, - "url": "https://www.richmond.gov.uk/services/waste_and_recycling/collection_days/", - "web_driver": "http://selenium:4444", - "wiki_name": "Richmond upon Thames", - "wiki_note": "Pass the name of the street ONLY in the house number parameter, unfortunately post code's are not allowed. ", - "LAD24CD": "E09000027" - }, - "LondonBoroughRedbridge": { - "postcode": "IG2 6LQ", - "uprn": "10023770353", - "url": "https://my.redbridge.gov.uk/RecycleRefuse", - "web_driver": "http://selenium:4444", - "wiki_name": "Redbridge", - "wiki_note": "Follow the instructions [here](https://my.redbridge.gov.uk/RecycleRefuse) until you get the page listing your address, then copy the entire address text and use that in the house number field.", - "LAD24CD": "E09000026" - }, - "LondonBoroughSutton": { - "uprn": "4473006", - "url": "https://waste-services.sutton.gov.uk/waste", - "wiki_command_url_override": "https://waste-services.sutton.gov.uk/waste", - "wiki_name": "Sutton", - "wiki_note": "You will need to find your unique property reference by going to (https://waste-services.sutton.gov.uk/waste), entering your details and then using the 7 digit reference in the URL as your UPRN", - "LAD24CD": "E09000029" - }, - "LutonBoroughCouncil": { - "uprn": "100080155778", - "url": "https://myforms.luton.gov.uk", - "wiki_command_url_override": "https://myforms.luton.gov.uk", - "wiki_name": "Luton", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E06000032" - }, - "MaidstoneBoroughCouncil": { - "skip_get_url": true, - "house_number": "71", - "postcode": "ME16 8BT", - "url": "https://my.maidstone.gov.uk/service/Find-your-bin-day", - "wiki_name": "Maidstone", - "wiki_note": "Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver.", - "LAD24CD": "E07000110" - }, - "MaldonDistrictCouncil": { - "skip_get_url": true, - "uprn": "100090557253", - "url": "https://maldon.suez.co.uk/maldon/ServiceSummary", - "wiki_name": "Maldon", - "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000074" - }, - "MalvernHillsDC": { - "skip_get_url": true, - "uprn": "100121348457", - "url": "https://swict.malvernhills.gov.uk/mhdcroundlookup/HandleSearchScreen", - "wiki_name": "Malvern Hills", - "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000235" - }, - "ManchesterCityCouncil": { - "skip_get_url": true, - "uprn": "77127089", - "url": "https://www.manchester.gov.uk/bincollections", - "wiki_name": "Manchester", - "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E08000003" - }, - "MansfieldDistrictCouncil": { - "skip_get_url": true, - "uprn": "100031396580", - "url": "https://www.mansfield.gov.uk/xfp/form/1327", - "wiki_name": "Mansfield", - "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000174" - }, - "MedwayCouncil": { - "skip_get_url": true, - "uprn": "200000907059", - "url": "https://www.medway.gov.uk/homepage/45/check_your_waste_collection_day", - "wiki_name": "Medway", - "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E06000035" - }, - "MeltonBoroughCouncil": { - "uprn": "100030540956", - "url": "https://my.melton.gov.uk/collections", - "wiki_name": "Melton", - "wiki_note": "To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000133" - }, - "MertonCouncil": { - "url": "https://myneighbourhood.merton.gov.uk/wasteservices/WasteServices.aspx?ID=25936129", - "wiki_command_url_override": "https://myneighbourhood.merton.gov.uk/Wasteservices/WasteServices.aspx?ID=XXXXXXXX", - "wiki_name": "Merton", - "wiki_note": "Follow the instructions [here](https://myneighbourhood.merton.gov.uk/Wasteservices/WasteServicesSearch.aspx) until you get the \"Your recycling and rubbish collection days\" page, then copy the URL and replace the URL in the command.", - "LAD24CD": "E09000024" - }, - "MidAndEastAntrimBoroughCouncil": { - "postcode": "100 Galgorm Road", - "skip_get_url": true, - "url": "https://www.midandeastantrim.gov.uk/resident/waste-recycling/collection-dates/", - "web_driver": "http://selenium:4444", - "wiki_name": "Mid and East Antrim", - "wiki_note": "Pass the house name/number plus the name of the street with the postcode parameter, wrapped in double quotes. Check the address on the website first. This version will only pick the first SHOW button returned by the search or if it is fully unique.", - "LAD24CD": "N09000008" - }, - "MidDevonCouncil": { - "uprn": "200003997770", - "url": "https://www.middevon.gov.uk", - "wiki_command_url_override": "https://www.middevon.gov.uk", - "wiki_name": "Mid Devon", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000042" - }, - "MidSuffolkDistrictCouncil": { - "house_number": "Monday", - "postcode": "Week 2", - "skip_get_url": true, - "uprn": "Monday", - "url": "https://www.midsuffolk.gov.uk", - "wiki_name": "Mid Suffolk", - "wiki_note": "Use the House Number field to pass the DAY of the week for your NORMAL collections. [Monday/Tuesday/Wednesday/Thursday/Friday]. [OPTIONAL] Use the 'postcode' field to pass the WEEK for your garden collection. [Week 1/Week 2]. [OPTIONAL] Use the 'uprn' field to pass the DAY for your garden collection. [Monday/Tuesday/Wednesday/Thursday/Friday]", - "LAD24CD": "E07000203" - }, - "MidSussexDistrictCouncil": { - "house_number": "OAKLANDS, OAKLANDS ROAD RH16 1SS", - "postcode": "RH16 1SS", - "skip_get_url": true, - "url": "https://www.midsussex.gov.uk/waste-recycling/bin-collection/", - "web_driver": "http://selenium:4444", - "wiki_name": "Mid Sussex", - "wiki_note": "Pass the name of the street with the house number parameter, wrapped in double quotes. This parser requires a Selenium webdriver.", - "LAD24CD": "E07000228" - }, - "MiddlesbroughCouncil": { - "house_number": "12 Constantine Court Park Road North, Middlesbrough", - "skip_get_url": true, - "url": "https://www.middlesbrough.gov.uk/recycling-and-rubbish/bin-collection-dates/", - "web_driver": "http://selenium:4444", - "wiki_name": "Middlesbrough", - "wiki_note": "Pass the entire address without postcode as it appears when you type it on the website. This parser requires a Selenium webdriver.", - "LAD24CD": "E06000002" - }, - "MidlothianCouncil": { - "house_number": "52", - "postcode": "EH19 2EB", - "skip_get_url": true, - "url": "https://www.midlothian.gov.uk/info/1054/bins_and_recycling/343/bin_collection_days", - "wiki_name": "Midlothian", - "wiki_note": "Pass the house name/number wrapped in double quotes along with the postcode parameter.", - "LAD24CD": "S12000019" - }, - "MidUlsterDistrictCouncil": { - "house_number": "20 HILLHEAD, STEWARTSTOWN, BT71 5HY", - "postcode": "BT71 5HY", - "skip_get_url": true, - "url": "https://www.midulstercouncil.org", - "web_driver": "http://selenium:4444", - "wiki_name": "Mid Ulster", - "wiki_note": "Pass the full address of the house postcode as displayed on the site. This parser requires a Selenium webdriver.", - "LAD24CD": "N09000009" - }, - "MiltonKeynesCityCouncil": { - "uprn": "25109551", - "url": "https://mycouncil.milton-keynes.gov.uk/en/service/Waste_Collection_Round_Checker", - "wiki_name": "Milton Keynes", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E06000042" - }, - "MoleValleyDistrictCouncil": { - "postcode": "RH4 1SJ", - "skip_get_url": true, - "uprn": "200000171235", - "url": "https://myproperty.molevalley.gov.uk/molevalley/", - "wiki_name": "Mole Valley", - "wiki_note": "UPRN can only be parsed with a valid postcode.", - "LAD24CD": "E07000210" - }, - "MonmouthshireCountyCouncil": { - "uprn": "100100266220", - "url": "https://maps.monmouthshire.gov.uk", - "wiki_name": "Monmouthshire", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "W06000021" - }, - "MorayCouncil": { - "uprn": "45438", - "url": "https://bindayfinder.moray.gov.uk/", - "wiki_name": "Moray", - "wiki_note": "Find your property ID by going to (https://bindayfinder.moray.gov.uk), search for your property and extracting the ID from the URL. i.e. (https://bindayfinder.moray.gov.uk/disp_bins.php?id=00028841)", - "LAD24CD": "S12000020" - }, - "NeathPortTalbotCouncil": { - "house_number": "2", - "postcode": "SA13 3BA", - "skip_get_url": true, - "url": "https://www.npt.gov.uk", - "web_driver": "http://selenium:4444", - "wiki_name": "Neath Port Talbot", - "wiki_note": "Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver.", - "LAD24CD": "W06000012" - }, - "NewForestCouncil": { - "postcode": "SO41 0GJ", - "skip_get_url": true, - "uprn": "100060482345", - "url": "https://forms.newforest.gov.uk/id/FIND_MY_COLLECTION", - "web_driver": "http://selenium:4444", - "wiki_name": "New Forest", - "wiki_note": "Pass the postcode and UPRN. This parser requires a Selenium webdriver.", - "LAD24CD": "E07000091" - }, - "NewarkAndSherwoodDC": { - "uprn": "200004258529", - "url": "https://app.newark-sherwooddc.gov.uk/bincollection/", - "skip_get_url": true, - "wiki_name": "Newark and Sherwood", - "wiki_note": "Replace XXXXXXXX with your UPRN.", - "LAD24CD": "E07000175" - }, - "NewcastleCityCouncil": { - "LAD24CD": "E08000021", - "url": "https://community.newcastle.gov.uk/my-neighbourhood/ajax/getBinsNew.php?uprn=004510730634", - "wiki_command_url_override": "https://community.newcastle.gov.uk/my-neighbourhood/ajax/getBinsNew.php?uprn=XXXXXXXX", - "wiki_name": "Newcastle upon Tyne", - "wiki_note": "Replace XXXXXXXX with your UPRN. UPRNs need to be 12 digits long so please pad the left hand side with 0s if your UPRN is not long enough" - }, - "NewcastleUnderLymeCouncil": { - "uprn": "100031725433", - "url": "https://www.newcastle-staffs.gov.uk", - "wiki_name": "Newcastle-under-Lyme", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", - "LAD24CD": "E07000195" - }, - "NewhamCouncil": { - "uprn": "46077811", - "url": "https://bincollection.newham.gov.uk/", - "skip_get_url": true, - "wiki_name": "Newham", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", - "LAD24CD": "E09000025" - }, - "NewportCityCouncil": { - "postcode": "NP20 4HE", - "skip_get_url": true, - "uprn": "100100688837", - "url": "https://www.newport.gov.uk/", - "wiki_name": "Newport", - "wiki_note": "Pass the postcode and UPRN. You can find the UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "W06000022" - }, - "NorthAyrshireCouncil": { - "uprn": "126045552", - "url": "https://www.north-ayrshire.gov.uk/", - "wiki_command_url_override": "https://www.north-ayrshire.gov.uk/", - "wiki_name": "North Ayrshire", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "S12000021" - }, - "NorthDevonCountyCouncil": { - "house_number": "1", - "postcode": "EX31 2LE", - "skip_get_url": true, - "uprn": "100040249471", - "url": "https://my.northdevon.gov.uk/service/WasteRecyclingCollectionCalendar", - "web_driver": "http://selenium:4444", - "wiki_name": "North Devon", - "wiki_note": "Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver.", - "LAD24CD": "E07000043" - }, - "NorthEastDerbyshireDistrictCouncil": { - "postcode": "S42 5RB", - "skip_get_url": true, - "uprn": "010034492222", - "url": "https://myselfservice.ne-derbyshire.gov.uk/service/Check_your_Bin_Day", - "web_driver": "http://selenium:4444", - "wiki_name": "North East Derbyshire", - "wiki_note": "Pass the postcode and UPRN. This parser requires a Selenium webdriver.", - "LAD24CD": "E07000038" - }, - "NorthEastLincs": { - "uprn": "11062649", - "url": "https://www.nelincs.gov.uk/refuse-collection-schedule/?view=timeline&uprn=11062649", - "wiki_command_url_override": "https://www.nelincs.gov.uk/refuse-collection-schedule/?view=timeline&uprn=XXXXXXXX", - "wiki_name": "North East Lincolnshire", - "wiki_note": "Replace XXXXXXXX with your UPRN.", - "LAD24CD": "E06000012" - }, - "NorthHertfordshireDistrictCouncil": { - "house_number": "22", - "postcode": "SG6 4BJ", - "url": "https://www.north-herts.gov.uk", - "web_driver": "http://selenium:4444", - "wiki_name": "North Hertfordshire", - "wiki_note": "Pass the house number and postcode in their respective parameters.", - "LAD24CD": "E07000099" - }, - "NorthKestevenDistrictCouncil": { - "url": "https://www.n-kesteven.org.uk/bins/display?uprn=100030869513", - "wiki_command_url_override": "https://www.n-kesteven.org.uk/bins/display?uprn=XXXXXXXX", - "wiki_name": "North Kesteven", - "wiki_note": "Replace XXXXXXXX with your UPRN.", - "LAD24CD": "E07000139" - }, - "NorthLanarkshireCouncil": { - "url": "https://www.northlanarkshire.gov.uk/bin-collection-dates/000118016164/48402118", - "wiki_command_url_override": "https://www.northlanarkshire.gov.uk/bin-collection-dates/XXXXXXXXXXX/XXXXXXXXXXX", - "wiki_name": "North Lanarkshire", - "wiki_note": "Follow the instructions [here](https://www.northlanarkshire.gov.uk/bin-collection-dates) until you get the \"Next collections\" page, then copy the URL and replace the URL in the command.", - "LAD24CD": "S12000050" - }, - "NorthLincolnshireCouncil": { - "skip_get_url": true, - "uprn": "100050194170", - "url": "https://www.northlincs.gov.uk/bins-waste-and-recycling/bin-and-box-collection-dates/", - "wiki_name": "North Lincolnshire", - "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E06000013" - }, - "NorthNorfolkDistrictCouncil": { - "house_number": "1 Morston Mews", - "postcode": "NR25 6BH", - "skip_get_url": true, - "url": "https://www.north-norfolk.gov.uk/", - "web_driver": "http://selenium:4444", - "wiki_name": "North Norfolk", - "wiki_note": "Pass the name of the street with the house number parameter, wrapped in double quotes. This parser requires a Selenium webdriver.", - "LAD24CD": "E07000147" - }, - "NorthNorthamptonshireCouncil": { - "skip_get_url": true, - "uprn": "100031021317", - "url": "https://cms.northnorthants.gov.uk/bin-collection-search/calendarevents/100031021318/2023-10-17/2023-10-01", - "wiki_name": "North Northamptonshire", - "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E06000061" - }, - "NorthSomersetCouncil": { - "postcode": "BS49 5AA", - "skip_get_url": true, - "uprn": "24051674", - "url": "https://forms.n-somerset.gov.uk/Waste/CollectionSchedule", - "wiki_name": "North Somerset", - "wiki_note": "Pass the postcode and UPRN. You can find the UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E06000024" - }, - "NorthTynesideCouncil": { - "postcode": "NE26 2TG", - "skip_get_url": true, - "uprn": "47097627", - "url": "https://my.northtyneside.gov.uk/category/81/bin-collection-dates", - "wiki_name": "North Tyneside", - "wiki_note": "Pass the postcode and UPRN. You can find the UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E08000022" - }, - "NorthWestLeicestershire": { - "postcode": "DE74 2FZ", - "skip_get_url": true, - "uprn": "100030572613", - "url": "https://www.nwleics.gov.uk/pages/collection_information", - "web_driver": "http://selenium:4444", - "wiki_name": "North West Leicestershire", - "wiki_note": "Pass the postcode and UPRN. This parser requires a Selenium webdriver.", - "LAD24CD": "E07000134" - }, - "NorthYorkshire": { - "skip_get_url": true, - "uprn": "10093091235", - "url": "https://www.northyorks.gov.uk/bin-calendar/lookup", - "wiki_name": "North Yorkshire", - "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E06000065" - }, - "NorthumberlandCouncil": { - "house_number": "22", - "postcode": "NE46 1UQ", - "skip_get_url": true, - "url": "https://www.northumberland.gov.uk/Waste/Household-waste/Household-bin-collections/Bin-Calendars.aspx", - "web_driver": "http://selenium:4444", - "wiki_name": "Northumberland", - "wiki_note": "Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver.", - "LAD24CD": "E06000057" - }, - "NorwichCityCouncil": { - "uprn": "100090888980", - "url": "https://www.norwich.gov.uk", - "wiki_command_url_override": "https://www.norwich.gov.uk", - "wiki_name": "Norwich", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000148" - }, - "NottinghamCityCouncil": { - "skip_get_url": true, - "uprn": "100031540180", - "url": "https://geoserver.nottinghamcity.gov.uk/bincollections2/api/collection/100031540180", - "wiki_name": "Nottingham", - "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E06000018" - }, - "NuneatonBedworthBoroughCouncil": { - "house_number": "Newdigate Road", - "skip_get_url": true, - "url": "https://www.nuneatonandbedworth.gov.uk", - "wiki_name": "Nuneaton and Bedworth", - "wiki_note": "Pass the name of the street ONLY in the house number parameter, wrapped in double quotes. Street name must match exactly as it appears on the council's website.", - "LAD24CD": "E07000219" - }, - "OadbyAndWigstonBoroughCouncil": { - "LAD24CD": "E07000135", - "uprn": "10010149102", - "url": "https://my.oadby-wigston.gov.uk", - "wiki_name": "Oadby and Wigston", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN." - }, - "OldhamCouncil": { - "url": "https://portal.oldham.gov.uk/bincollectiondates/details?uprn=422000033556", - "wiki_name": "Oldham", - "wiki_note": "Replace UPRN in URL with your own UPRN.", - "LAD24CD": "E08000004" - }, - "OxfordCityCouncil": { - "postcode": "OX3 7QF", - "uprn": "100120820551", - "url": "https://www.oxford.gov.uk", - "wiki_command_url_override": "https://www.oxford.gov.uk", - "wiki_name": "Oxford", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000178" - }, - "PembrokeshireCountyCouncil": { - "url": "https://nearest.pembrokeshire.gov.uk/property/100100278790", - "wiki_command_url_override": "https://nearest.pembrokeshire.gov.uk/property/XXXXXXXXXX", - "wiki_name": "Pembrokeshire", - "wiki_note": "Replace XXXXXXXX with your UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find it.", - "LAD24CD": "W06000009" - }, - "PeterboroughCityCouncil": { - "house_number": "7 Arundel Road, Peterborough, PE4 6JJ", - "postcode": "PE4 6JJ", - "skip_get_url": true, - "url": "https://report.peterborough.gov.uk/waste", - "web_driver": "http://selenium:4444", - "wiki_name": "Peterborough", - "wiki_note": "Pass the full address as it appears o nthe Peterborough website and postcode in their respective parameters. This parser requires a Selenium webdriver.", - "LAD24CD": "E06000031" - }, - "PerthAndKinrossCouncil": { - "uprn": "124032322", - "url": "https://www.pkc.gov.uk", - "wiki_command_url_override": "https://www.pkc.gov.uk", - "wiki_name": "Perth and Kinross", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "S12000048" - }, - "PlymouthCouncil": { - "uprn": "100040420582", - "url": "https://www.plymouth.gov.uk", - "wiki_command_url_override": "https://www.plymouth.gov.uk", - "wiki_name": "Plymouth", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E06000026" - }, - "PortsmouthCityCouncil": { - "postcode": "PO4 0LE", - "skip_get_url": true, - "uprn": "1775027504", - "url": "https://my.portsmouth.gov.uk/en/AchieveForms/?form_uri=sandbox-publish://AF-Process-26e27e70-f771-47b1-a34d-af276075cede/AF-Stage-cd7cc291-2e59-42cc-8c3f-1f93e132a2c9/definition.json&redirectlink=%2F&cancelRedirectLink=%2F", - "web_driver": "http://selenium:4444", - "wiki_name": "Portsmouth", - "wiki_note": "Pass the postcode and UPRN. This parser requires a Selenium webdriver.", - "LAD24CD": "E06000044" - }, - "PowysCouncil": { - "house_number": "LANE COTTAGE", - "postcode": "HR3 5JS", - "skip_get_url": true, - "url": "https://www.powys.gov.uk", - "web_driver": "http://selenium:4444", - "wiki_name": "Powys", - "LAD24CD": "W06000023" - }, - "PrestonCityCouncil": { - "house_number": "Town Hall", - "postcode": "PR1 2RL", - "skip_get_url": true, - "url": "https://selfservice.preston.gov.uk/service/Forms/FindMyNearest.aspx?Service=bins", - "web_driver": "http://selenium:4444", - "wiki_name": "Preston", - "wiki_note": "Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver.", - "LAD24CD": "E07000123" - }, - "ReadingBoroughCouncil": { - "url": "https://api.reading.gov.uk/api/collections/310056735", - "wiki_command_url_override": "https://api.reading.gov.uk/api/collections/XXXXXXXX", - "wiki_name": "Reading", - "wiki_note": "Replace XXXXXXXX with your property's UPRN.", - "LAD24CD": "E06000038" - }, - "RedcarandClevelandCouncil": { - "house_number": "11", - "postcode": "TS10 2RE", - "skip_get_url": true, - "url": "https://www.redcar-cleveland.gov.uk", - "wiki_name": "Redcar and Cleveland", - "wiki_note": "Pass the house name/number and postcode in their respective parameters", - "LAD24CD": "E06000003" - }, - "RedditchBoroughCouncil": { - "uprn": "10094557691", - "url": "https://redditchbc.gov.uk", - "wiki_command_url_override": "https://redditchbc.gov.uk", - "wiki_name": "Redditch", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000236" - }, - "ReigateAndBansteadBoroughCouncil": { - "skip_get_url": true, - "uprn": "68134867", - "url": "https://www.reigate-banstead.gov.uk/", - "web_driver": "http://selenium:4444", - "wiki_name": "Reigate and Banstead", - "wiki_note": "To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search). This parser requires a Selenium webdriver.", - "LAD24CD": "E07000211" - }, - "RenfrewshireCouncil": { - "house_number": "1, STATIONHOUSE DRIVE, JOHNSTONE, RENFREWSHIRE, PA6 7FJ", - "paon": "1, STATIONHOUSE DRIVE, JOHNSTONE, RENFREWSHIRE, PA6 7FJ", - "postcode": "PA6 7FJ", - "skip_get_url": true, - "url": "https://www.renfrewshire.gov.uk/bin-day", - "web_driver": "http://selenium:4444", - "wiki_name": "Renfrewshire", - "wiki_note": "Pass the full address as it appears on the website. This parser requires a Selenium webdriver.", - "LAD24CD": "S12000038" - }, - "RhonddaCynonTaffCouncil": { - "skip_get_url": true, - "uprn": "100100778320", - "url": "https://www.rctcbc.gov.uk/EN/Resident/RecyclingandWaste/RecyclingandWasteCollectionDays.aspx", - "wiki_name": "Rhondda Cynon Taff", - "wiki_note": "To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "W06000016" - }, - "RochdaleCouncil": { - "postcode": "OL11 5BE", - "skip_get_url": true, - "uprn": "23049922", - "url": "https://webforms.rochdale.gov.uk/BinCalendar", - "wiki_name": "Rochdale", - "wiki_note": "Provide your UPRN and postcode. You can find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E08000005" - }, - "RochfordCouncil": { - "url": "https://www.rochford.gov.uk/online-bin-collections-calendar", - "wiki_name": "Rochford", - "wiki_note": "No extra parameters are required. Dates presented should be read as 'week commencing'.", - "LAD24CD": "E07000075" - }, - "RotherDistrictCouncil": { - "uprn": "100061937338", - "url": "https://www.rother.gov.uk", - "wiki_name": "Rother", - "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", - "LAD24CD": "E07000064" - }, - "RotherhamCouncil": { - "uprn": "100050866000", - "url": "https://www.rotherham.gov.uk/bin-collections?address=100050866000&submit=Submit", - "wiki_command_url_override": "https://www.rotherham.gov.uk/bin-collections?address=XXXXXXXXX&submit=Submit", - "wiki_name": "Rotherham", - "wiki_note": "Replace `XXXXXXXXX` with your UPRN in the URL. You can find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E08000018" - }, - "RoyalBoroughofGreenwich": { - "house_number": "57", - "postcode": "BR7 6DN", - "skip_get_url": true, - "url": "https://www.royalgreenwich.gov.uk", - "wiki_name": "Greenwich", - "wiki_note": "Provide your house number in the `house_number` parameter and your postcode in the `postcode` parameter.", - "LAD24CD": "E09000011" - }, - "RugbyBoroughCouncil": { - "postcode": "CV22 6LA", - "skip_get_url": true, - "uprn": "100070182634", - "url": "https://www.rugby.gov.uk/check-your-next-bin-day", - "web_driver": "http://selenium:4444", - "wiki_name": "Rugby", - "wiki_note": "Provide your UPRN and postcode. You can find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000220" - }, - "RunnymedeBoroughCouncil": { - "skip_get_url": true, - "uprn": "100061483636", - "url": "https://www.runnymede.gov.uk/", - "wiki_name": "Runnymede", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000212" - }, - "RushcliffeBoroughCouncil": { - "postcode": "NG13 8TZ", - "skip_get_url": true, - "uprn": "3040040994", - "url": "https://www.rushcliffe.gov.uk/", - "web_driver": "http://selenium:4444", - "wiki_name": "Rushcliffe", - "wiki_note": "Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", - "LAD24CD": "E07000176" - }, - "RushmoorCouncil": { - "url": "https://www.rushmoor.gov.uk/Umbraco/Api/BinLookUpWorkAround/Get?selectedAddress=100060545034", - "wiki_command_url_override": "https://www.rushmoor.gov.uk/Umbraco/Api/BinLookUpWorkAround/Get?selectedAddress=XXXXXXXXXX", - "wiki_name": "Rushmoor", - "wiki_note": "Replace `XXXXXXXXXX` with your UPRN, which you can find using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000092" - }, - "SalfordCityCouncil": { - "skip_get_url": true, - "uprn": "100011416709", - "url": "https://www.salford.gov.uk/bins-and-recycling/bin-collection-days/your-bin-collections", - "wiki_name": "Salford", - "wiki_note": "Provide your UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E08000006" - }, - "SandwellBoroughCouncil": { - "skip_get_url": true, - "uprn": "32101971", - "url": "https://www.sandwell.gov.uk", - "wiki_name": "Sandwell", - "wiki_note": "Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E08000028" - }, - "SeftonCouncil": { - "house_number": "1", - "postcode": "L20 6GG", - "url": "https://www.sefton.gov.uk", - "wiki_name": "Sefton", - "wiki_note": "Pass the postcode and house number in their respective arguments, both wrapped in quotes.", - "LAD24CD": "E08000014" - }, - "SevenoaksDistrictCouncil": { - "house_number": "60 Hever Road", - "postcode": "TN15 6EB", - "skip_get_url": true, - "url": "https://sevenoaks-dc-host01.oncreate.app/w/webpage/waste-collection-day", - "web_driver": "http://selenium:4444", - "wiki_name": "Sevenoaks", - "wiki_note": "Pass the house name/number in the `house_number` parameter, wrapped in double quotes, and the postcode in the `postcode` parameter.", - "LAD24CD": "E07000111" - }, - "SheffieldCityCouncil": { - "url": "https://wasteservices.sheffield.gov.uk/property/100050931898", - "wiki_command_url_override": "https://wasteservices.sheffield.gov.uk/property/XXXXXXXXXXX", - "wiki_name": "Sheffield", - "wiki_note": "Follow the instructions [here](https://wasteservices.sheffield.gov.uk/) until you get the 'Your bin collection dates and services' page, then copy the URL and replace the URL in the command.", - "LAD24CD": "E08000019" - }, - "ShropshireCouncil": { - "url": "https://bins.shropshire.gov.uk/property/100070034731", - "wiki_command_url_override": "https://bins.shropshire.gov.uk/property/XXXXXXXXXXX", - "wiki_name": "Shropshire", - "wiki_note": "Follow the instructions [here](https://bins.shropshire.gov.uk/) until you get the page showing your bin collection dates, then copy the URL and replace the URL in the command.", - "LAD24CD": "E06000051" - }, - "SloughBoroughCouncil": { - "postcode": "SL2 2EW", - "skip_get_url": true, - "url": "https://www.slough.gov.uk/bin-collections", - "web_driver": "http://selenium:4444", - "wiki_name": "Slough", - "wiki_note": "Pass the UPRN and postcode in their respective parameters. This parser requires a Selenium webdriver.", - "LAD24CD": "E06000039" - }, - "SolihullCouncil": { - "url": "https://digital.solihull.gov.uk/BinCollectionCalendar/Calendar.aspx?UPRN=100071005444", - "wiki_command_url_override": "https://digital.solihull.gov.uk/BinCollectionCalendar/Calendar.aspx?UPRN=XXXXXXXX", - "wiki_name": "Solihull", - "wiki_note": "Replace `XXXXXXXX` with your UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E08000029" - }, - "SomersetCouncil": { - "postcode": "TA6 4AA", - "skip_get_url": true, - "uprn": "10090857775", - "url": "https://www.somerset.gov.uk/", - "wiki_name": "Somerset", - "wiki_note": "Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E06000066" - }, - "SouthAyrshireCouncil": { - "postcode": "KA19 7BN", - "skip_get_url": true, - "uprn": "141003134", - "url": "https://www.south-ayrshire.gov.uk/", - "wiki_name": "South Ayrshire", - "wiki_note": "Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", - "LAD24CD": "S12000028" - }, - "SouthCambridgeshireCouncil": { - "house_number": "53", - "postcode": "CB23 6GZ", - "skip_get_url": true, - "url": "https://www.scambs.gov.uk/recycling-and-bins/find-your-household-bin-collection-day/", - "wiki_name": "South Cambridgeshire", - "wiki_note": "Provide your house number in the `house_number` parameter and postcode in the `postcode` parameter.", - "LAD24CD": "E07000012" - }, - "SouthDerbyshireDistrictCouncil": { - "uprn": "10000820668", - "url": "https://maps.southderbyshire.gov.uk/iShareLIVE.web//getdata.aspx?RequestType=LocalInfo&ms=mapsources/MyHouse&format=JSONP&group=Recycling%20Bins%20and%20Waste|Next%20Bin%20Collections&uid=", - "wiki_command_url_override": "https://maps.southderbyshire.gov.uk/iShareLIVE.web//getdata.aspx?RequestType=LocalInfo&ms=mapsources/MyHouse&format=JSONP&group=Recycling%20Bins%20and%20Waste|Next%20Bin%20Collections&uid=XXXXXXXX", - "wiki_name": "South Derbyshire", - "wiki_note": "Replace `XXXXXXXX` with your UPRN. You can find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000039" - }, - "SouthGloucestershireCouncil": { - "skip_get_url": true, - "uprn": "566419", - "url": "https://beta.southglos.gov.uk/waste-and-recycling-collection-date", - "wiki_name": "South Gloucestershire", - "wiki_note": "Provide your UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E06000025" - }, - "SouthHamsDistrictCouncil": { - "uprn": "10004742851", - "url": "https://www.southhams.gov.uk", - "wiki_name": "South Hams", - "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", - "LAD24CD": "E07000044" - }, - "SouthHollandDistrictCouncil": { - "house_number": "1", - "postcode": "PE6 0HE", - "skip_get_url": true, - "uprn": "100030872493", - "url": "https://www.sholland.gov.uk/mycollections", - "web_driver": "http://selenium:4444", - "wiki_name": "South Holland", - "wiki_note": "Pass the UPRN and postcode in their respective parameters. This parser requires a Selenium webdriver.", - "LAD24CD": "E07000140" - }, - "SouthKestevenDistrictCouncil": { - "house_number": "2 Althorpe Close, Market Deeping, PE6 8BL", - "postcode": "PE68BL", - "skip_get_url": true, - "url": "https://pre.southkesteven.gov.uk/BinSearch.aspx", - "web_driver": "http://selenium:4444", - "wiki_name": "South Kesteven", - "wiki_note": "Provide your full address in the `house_number` parameter and your postcode in the `postcode` parameter.", - "LAD24CD": "E07000141" - }, - "SouthLanarkshireCouncil": { - "url": "https://www.southlanarkshire.gov.uk/directory_record/579973/abbeyhill_crescent_lesmahagow", - "wiki_command_url_override": "https://www.southlanarkshire.gov.uk/directory_record/XXXXX/XXXXX", - "wiki_name": "South Lanarkshire", - "wiki_note": "Follow the instructions [here](https://www.southlanarkshire.gov.uk/info/200156/bins_and_recycling/1670/bin_collections_and_calendar) until you get the page that shows the weekly collections for your street, then copy the URL and replace the URL in the command.", - "LAD24CD": "S12000029" - }, - "SouthNorfolkCouncil": { - "skip_get_url": true, - "uprn": "2630102526", - "url": "https://www.southnorfolkandbroadland.gov.uk/rubbish-recycling/south-norfolk-bin-collection-day-finder", - "wiki_name": "South Norfolk", - "wiki_note": "Provide your UPRN. Find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000149" - }, - "SouthOxfordshireCouncil": { - "skip_get_url": true, - "uprn": "10033002851", - "url": "https://www.southoxon.gov.uk/south-oxfordshire-district-council/recycling-rubbish-and-waste/when-is-your-collection-day/", - "wiki_name": "South Oxfordshire", - "wiki_note": "Provide your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to locate it.", - "LAD24CD": "E07000179" - }, - "SouthRibbleCouncil": { - "uprn": "10013243496", - "postcode": "PR266QW", - "url": "https://forms.chorleysouthribble.gov.uk/xfp/form/70", - "wiki_command_url_override": "https://forms.chorleysouthribble.gov.uk/xfp/form/70", - "wiki_name": "South Ribble", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", - "LAD24CD": "E07000126" - }, - "SouthStaffordshireDistrictCouncil": { - "uprn": "200004523954", - "url": "https://www.sstaffs.gov.uk/where-i-live?uprn=200004523954", - "wiki_name": "South Staffordshire", - "wiki_note": "The URL needs to be `https://www.sstaffs.gov.uk/where-i-live?uprn=`. Replace `` with your UPRN.", - "LAD24CD": "E07000196" - }, - "SouthTynesideCouncil": { - "house_number": "1", - "postcode": "NE33 3JW", - "skip_get_url": true, - "url": "https://www.southtyneside.gov.uk/article/33352/Bin-collection-dates", - "wiki_name": "South Tyneside", - "wiki_note": "Provide your house number in the `house_number` parameter and postcode in the `postcode` parameter.", - "LAD24CD": "E08000023" - }, - "SouthamptonCityCouncil": { - "skip_get_url": true, - "uprn": "100060731893", - "url": "https://www.southampton.gov.uk", - "wiki_name": "Southampton", - "wiki_note": "Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E06000045" - }, - "SouthwarkCouncil": { - "uprn": "200003469271", - "url": "https://services.southwark.gov.uk/bins/lookup/", - "wiki_command_url_override": "https://services.southwark.gov.uk/bins/lookup/XXXXXXXX", - "wiki_name": "Southwark", - "wiki_note": "Replace `XXXXXXXX` with your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", - "LAD24CD": "E09000028" - }, - "SpelthorneBoroughCouncil": { - "house_number": "1", - "postcode": "TW18 2PR", - "skip_get_url": true, - "url": "https://www.spelthorne.gov.uk", - "wiki_name": "Spelthorne", - "LAD24CD": "E07000213" - }, - "StAlbansCityAndDistrictCouncil": { - "skip_get_url": true, - "uprn": "100081153583", - "url": "https://gis.stalbans.gov.uk/NoticeBoard9/VeoliaProxy.NoticeBoard.asmx/GetServicesByUprnAndNoticeBoard", - "wiki_name": "St Albans", - "wiki_note": "Provide your UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000240" - }, - "StHelensBC": { - "house_number": "15", - "postcode": "L34 2GA", - "skip_get_url": true, - "url": "https://www.sthelens.gov.uk/", - "web_driver": "http://selenium:4444", - "wiki_name": "St. Helens", - "wiki_note": "Pass the house name/number in the house number parameter, wrapped in double quotes", - "LAD24CD": "E08000013" - }, - "StaffordBoroughCouncil": { - "uprn": "100032203010", - "url": "https://www.staffordbc.gov.uk/address/100032203010", - "wiki_name": "Stafford", - "wiki_note": "The URL needs to be `https://www.staffordbc.gov.uk/address/`. Replace `` with your UPRN.", - "LAD24CD": "E07000197" - }, - "StaffordshireMoorlandsDistrictCouncil": { - "postcode": "ST8 6HN", - "skip_get_url": true, - "uprn": "100031863037", - "url": "https://www.staffsmoorlands.gov.uk/", - "web_driver": "http://selenium:4444", - "wiki_name": "Staffordshire Moorlands", - "wiki_note": "Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", - "LAD24CD": "E07000198" - }, - "StevenageBoroughCouncil": { - "uprn": "100080878852", - "url": "https://www.stevenage.gov.uk", - "wiki_name": "Stevenage", - "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", - "LAD24CD": "E07000243" - }, - "StirlingCouncil": { - "house_number": "5, SUNNYLAW ROAD, BRIDGE OF ALLAN, STIRLING, FK9 4QA", - "postcode": "FK9 4QA", - "skip_get_url": true, - "url": "https://www.stirling.gov.uk/bins-and-recycling/bin-collection-dates-search/", - "web_driver": "http://selenium:4444", - "wiki_name": "Stirling", - "wiki_note": "Use the full address as it appears on the drop-down on the site when you search by postcode.", - "LAD24CD": "S12000030" - }, - "StockportBoroughCouncil": { - "url": "https://myaccount.stockport.gov.uk/bin-collections/show/100011434401", - "wiki_command_url_override": "https://myaccount.stockport.gov.uk/bin-collections/show/XXXXXXXX", - "wiki_name": "Stockport", - "wiki_note": "Replace `XXXXXXXX` with your UPRN.", - "LAD24CD": "E08000007" - }, - "StocktonOnTeesCouncil": { - "house_number": "24", - "postcode": "TS20 2RD", - "skip_get_url": true, - "url": "https://www.stockton.gov.uk", - "web_driver": "http://selenium:4444", - "wiki_name": "Stockton-on-Tees", - "LAD24CD": "E06000004" - }, - "StokeOnTrentCityCouncil": { - "url": "https://www.stoke.gov.uk/jadu/custom/webserviceLookUps/BarTecWebServices_missed_bin_calendar.php?UPRN=3455121482", - "wiki_command_url_override": "https://www.stoke.gov.uk/jadu/custom/webserviceLookUps/BarTecWebServices_missed_bin_calendar.php?UPRN=XXXXXXXXXX", - "wiki_name": "Stoke-on-Trent", - "wiki_note": "Replace `XXXXXXXXXX` with your property's UPRN.", - "LAD24CD": "E06000021" - }, - "StratfordUponAvonCouncil": { - "LAD24CD": "E07000221", - "skip_get_url": true, - "uprn": "100070212698", - "url": "https://www.stratford.gov.uk/waste-recycling/when-we-collect.cfm/part/calendar", - "wiki_name": "Stratford-on-Avon", - "wiki_note": "Provide your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find it." - }, - "StroudDistrictCouncil": { - "postcode": "GL10 3BH", - "uprn": "100120512183", - "url": "https://www.stroud.gov.uk/my-house?uprn=100120512183&postcode=GL10+3BH", - "wiki_name": "Stroud", - "wiki_note": "Provide your UPRN and postcode. Replace the UPRN and postcode in the URL with your own.", - "LAD24CD": "E07000082" - }, - "SunderlandCityCouncil": { - "house_number": "13", - "postcode": "SR4 6BJ", - "skip_get_url": true, - "url": "https://webapps.sunderland.gov.uk/WEBAPPS/WSS/Sunderland_Portal/Forms/bindaychecker.aspx", - "web_driver": "http://selenium:4444", - "wiki_name": "Sunderland", - "wiki_note": "Provide your house number (without quotes) and postcode (wrapped in double quotes with a space).", - "LAD24CD": "E08000024" - }, - "SurreyHeathBoroughCouncil": { - "house_number": "36", - "postcode": "GU20 6PN", - "skip_get_url": true, - "url": "https://asjwsw-wrpsurreyheathmunicipal-live.whitespacews.com/", - "wiki_name": "Surrey Heath", - "wiki_note": "Provide your house number in the `house_number` parameter and postcode in the `postcode` parameter.", - "LAD24CD": "E07000214" - }, - "SwaleBoroughCouncil": { - "house_number": "81", - "postcode": "ME12 2NQ", - "skip_get_url": true, - "url": "https://swale.gov.uk/bins-littering-and-the-environment/bins/collection-days", - "web_driver": "http://selenium:4444", - "wiki_name": "Swale", - "wiki_note": "Provide your house number in the `house_number` parameter and postcode in the `postcode` parameter.", - "LAD24CD": "E07000113" - }, - "SwanseaCouncil": { - "postcode": "SA43PQ", - "skip_get_url": true, - "uprn": "100100324821", - "url": "https://www1.swansea.gov.uk/recyclingsearch/", - "wiki_name": "Swansea", - "wiki_note": "Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "W06000011" - }, - "SwindonBoroughCouncil": { - "uprn": "10022793351", - "url": "https://www.swindon.gov.uk", - "wiki_name": "Swindon", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", - "LAD24CD": "E06000030" - }, - "TamesideMBCouncil": { - "skip_get_url": true, - "uprn": "100012835362", - "url": "http://lite.tameside.gov.uk/BinCollections/CollectionService.svc/GetBinCollection", - "wiki_name": "Tameside", - "wiki_note": "Provide your UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E08000008" - }, - "TandridgeDistrictCouncil": { - "skip_get_url": true, - "uprn": "100062160432", - "url": "https://tdcws01.tandridge.gov.uk/TDCWebAppsPublic/tfaBranded/408?utm_source=pressrelease&utm_medium=smposts&utm_campaign=check_my_bin_day", - "wiki_name": "Tandridge", - "wiki_note": "Provide your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to locate it.", - "LAD24CD": "E07000215" - }, - "TeignbridgeCouncil": { - "uprn": "100040338776", - "url": "https://www.google.co.uk", - "web_driver": "http://selenium:4444", - "wiki_command_url_override": "https://www.google.co.uk", - "wiki_name": "Teignbridge", - "wiki_note": "Provide Google as the URL as the real URL breaks the integration. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000045" - }, - "TelfordAndWrekinCouncil": { - "skip_get_url": true, - "uprn": "000452015013", - "url": "https://dac.telford.gov.uk/bindayfinder/", - "wiki_name": "Telford and Wrekin", - "wiki_note": "Provide your UPRN. Find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E06000020" - }, - "TewkesburyBoroughCouncil": { - "skip_get_url": true, - "uprn": "10067626314", - "url": "https://tewkesbury.gov.uk/services/waste-and-recycling/", - "wiki_name": "Tewkesbury", - "wiki_note": "Provide your UPRN. Find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000083" - }, - "TendringDistrictCouncil": { - "postcode": "CO15 4EU", - "skip_get_url": true, - "uprn": "100090604247", - "url": "https://tendring-self.achieveservice.com/en/service/Rubbish_and_recycling_collection_days", - "web_driver": "http://selenium:4444", - "wiki_name": "Tendring", - "wiki_note": "Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000076" - }, - "TestValleyBoroughCouncil": { - "postcode": "SO51 9ZD", - "skip_get_url": true, - "uprn": "200010012019", - "url": "https://testvalley.gov.uk/wasteandrecycling/when-are-my-bins-collected", - "wiki_name": "Test Valley", - "wiki_note": "Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", - "LAD24CD": "E07000093" - }, - "ThanetDistrictCouncil": { - "uprn": "100061111858", - "url": "https://www.thanet.gov.uk", - "web_driver": "http://selenium:4444", - "wiki_name": "Thanet", - "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", - "LAD24CD": "E07000114" - }, - "ThreeRiversDistrictCouncil": { - "postcode": "WD3 7AZ", - "skip_get_url": true, - "uprn": "100080913662", - "url": "https://my.threerivers.gov.uk/en/AchieveForms/?mode=fill&consentMessage=yes&form_uri=sandbox-publish://AF-Process-52df96e3-992a-4b39-bba3-06cfaabcb42b/AF-Stage-01ee28aa-1584-442c-8d1f-119b6e27114a/definition.json&process=1&process_uri=sandbox-processes://AF-Process-52df96e3-992a-4b39-bba3-06cfaabcb42b&process_id=AF-Process-52df96e3-992a-4b39-bba3-06cfaabcb42b&noLoginPrompt=1", - "web_driver": "http://selenium:4444", - "wiki_name": "Three Rivers", - "wiki_note": "Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000102" - }, - "ThurrockCouncil": { - "house_number": "Monday", - "postcode": "Round A", - "skip_get_url": true, - "url": "https://www.thurrock.gov.uk", - "wiki_name": "Thurrock", - "wiki_note": "Use the House Number field to pass the DAY of the week for your collections. [Monday/Tuesday/Wednesday/Thursday/Friday]. Use the 'postcode' field to pass the ROUND (wrapped in quotes) for your collections. [Round A/Round B].", - "LAD24CD": "E06000034" - }, - "TonbridgeAndMallingBC": { - "postcode": "ME19 4JS", - "skip_get_url": true, - "uprn": "10002914589", - "url": "https://www.tmbc.gov.uk/", - "wiki_name": "Tonbridge and Malling", - "wiki_note": "Provide your UPRN and postcode.", - "LAD24CD": "E07000115" - }, - "TorbayCouncil": { - "skip_get_url": true, - "uprn": "10000016984", - "postcode": "TQ1 1AG", - "url": "https://www.torbay.gov.uk/recycling/bin-collections/", - "wiki_name": "Torbay", - "wiki_note": "Provide your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find it.", - "LAD24CD": "E06000027" - }, - "TorridgeDistrictCouncil": { - "skip_get_url": true, - "uprn": "10091078762", - "url": "https://collections-torridge.azurewebsites.net/WebService2.asmx", - "wiki_name": "Torridge", - "wiki_note": "Provide your UPRN.", - "LAD24CD": "E07000046" - }, - "TunbridgeWellsCouncil": { - "uprn": "10090058289", - "url": "https://tunbridgewells.gov.uk", - "wiki_command_url_override": "https://tunbridgewells.gov.uk", - "wiki_name": "Tunbridge Wells", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", - "LAD24CD": "E07000116" - }, - "UttlesfordDistrictCouncil": { - "house_number": "72, Birchanger Lane", - "postcode": "CM23 5QF", - "skip_get_url": true, - "uprn": "100090643434", - "url": "https://bins.uttlesford.gov.uk/", - "web_driver": "http://selenium:4444", - "wiki_name": "Uttlesford", - "wiki_note": "Provide your full address in the `house_number` parameter and your postcode in the `postcode` parameter.", - "LAD24CD": "E07000077" - }, - "ValeofGlamorganCouncil": { - "skip_get_url": true, - "uprn": "64029020", - "url": "https://www.valeofglamorgan.gov.uk/en/living/Recycling-and-Waste/", - "wiki_name": "The Vale of Glamorgan", - "wiki_note": "Provide your UPRN. Find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "W06000014" - }, - "ValeofWhiteHorseCouncil": { - "custom_component_show_url_field": false, - "skip_get_url": true, - "uprn": "100121391443", - "url": "https://eform.whitehorsedc.gov.uk/ebase/BINZONE_DESKTOP.eb", - "wiki_name": "Vale of White Horse", - "wiki_note": "Provide your UPRN.", - "LAD24CD": "E07000180" - }, - "WakefieldCityCouncil": { - "custom_component_show_url_field": true, - "skip_get_url": true, - "url": "https://www.wakefield.gov.uk/where-i-live/?uprn=63035490&a=115%20Elizabeth%20Drive%20Castleford%20WF10%203RR&usrn=41801243&e=445418&n=426091&p=WF10%203RR", - "web_driver": "http://selenium:4444", - "wiki_command_url_override": "https://www.wakefield.gov.uk/where-i-live/?uprn=XXXXXXXXXXX&a=XXXXXXXXXXX&usrn=XXXXXXXXXXX&e=XXXXXXXXXXX&n=XXXXXXXXXXX&p=XXXXXXXXXXX", - "wiki_name": "Wakefield", - "wiki_note": "Follow the instructions [here](https://www.wakefield.gov.uk/where-i-live/) until you get the page that includes a 'Bin Collections' section, then copy the URL and replace the URL in the command.", - "LAD24CD": "E08000036" - }, - "WalsallCouncil": { - "uprn": "100071080513", - "url": "https://cag.walsall.gov.uk/", - "wiki_command_url_override": "https://cag.walsall.gov.uk/", - "wiki_name": "Walsall", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", - "LAD24CD": "E08000030" - }, - "WalthamForest": { - "house_number": "17 Chingford Road, Walthamstow", - "postcode": "E17 4PW", - "skip_get_url": true, - "uprn": "200001415697", - "url": "https://portal.walthamforest.gov.uk/AchieveForms/?mode=fill&consentMessage=yes&form_uri=sandbox-publish://AF-Process-d62ccdd2-3de9-48eb-a229-8e20cbdd6393/AF-Stage-8bf39bf9-5391-4c24-857f-0dc2025c67f4/definition.json&process=1&process_uri=sandbox-processes://AF-Process-d62ccdd2-3de9-48eb-a229-8e20cbdd6393&process_id=AF-Process-d62ccdd2-3de9-48eb-a229-8e20cbdd6393", - "web_driver": "http://selenium:4444", - "wiki_name": "Waltham Forest", - "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", - "LAD24CD": "E09000031" - }, - "WandsworthCouncil": { - "uprn": "100022684035", - "url": "https://www.wandsworth.gov.uk", - "wiki_command_url_override": "https://www.wandsworth.gov.uk", - "wiki_name": "Wandsworth", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E09000032" - }, - "WarringtonBoroughCouncil": { - "uprn": "10094964379", - "url": "https://www.warrington.gov.uk", - "wiki_command_url_override": "https://www.warrington.gov.uk", - "wiki_name": "Warrington", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E06000007" - }, - "WarwickDistrictCouncil": { - "url": "https://estates7.warwickdc.gov.uk/PropertyPortal/Property/Recycling/100070263793", - "wiki_command_url_override": "https://estates7.warwickdc.gov.uk/PropertyPortal/Property/Recycling/XXXXXXXX", - "wiki_name": "Warwick", - "wiki_note": "Replace `XXXXXXXX` with your UPRN.", - "LAD24CD": "E07000222" - }, - "WatfordBoroughCouncil": { - "uprn": "100080942183", - "url": "https://www.watford.gov.uk", - "wiki_command_url_override": "https://www.watford.gov.uk", - "wiki_name": "Watford", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000103" - }, - "WaverleyBoroughCouncil": { - "house_number": "23", - "postcode": "GU9 9QG", - "skip_get_url": true, - "url": "https://wav-wrp.whitespacews.com/", - "wiki_name": "Waverley", - "wiki_note": "Follow the instructions [here](https://wav-wrp.whitespacews.com/#!) until you get the page that shows your next scheduled collections. Then take the number from `pIndex=NUMBER` in the URL and pass it as the `-n` parameter along with your postcode in `-p`.", - "LAD24CD": "E07000216" - }, - "WealdenDistrictCouncil": { - "skip_get_url": true, - "uprn": "10033413624", - "url": "https://www.wealden.gov.uk/recycling-and-waste/bin-search/", - "wiki_name": "Wealden", - "wiki_note": "Provide your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find it.", - "LAD24CD": "E07000065" - }, - "WelhatCouncil": { - "LAD24CD": "E07000241", - "postcode": "AL8 6HQ", - "uprn": "100080982825", - "url": "https://www.welhat.gov.uk/xfp/form/214", - "wiki_name": "Welwyn Hatfield", - "wiki_note": "Provide your UPRN and postcode." - }, - "WestBerkshireCouncil": { - "house_number": "8", - "postcode": "RG14 7DP", - "skip_get_url": true, - "url": "https://www.westberks.gov.uk/binday", - "web_driver": "http://selenium:4444", - "wiki_name": "West Berkshire", - "wiki_note": "Provide your house number in the `house_number` parameter and postcode in the `postcode` parameter.", - "LAD24CD": "E06000037" - }, - "WestDunbartonshireCouncil": { - "uprn": "129001383", - "url": "https://www.west-dunbarton.gov.uk/", - "wiki_name": "West Dunbartonshire", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "S12000039" - }, - "WestLancashireBoroughCouncil": { - "postcode": "WN8 0HR", - "uprn": "10012343339", - "url": "https://www.westlancs.gov.uk", - "wiki_name": "West Lancashire", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000127" - }, - "WestLindseyDistrictCouncil": { - "house_number": "35", - "postcode": "LN8 3AX", - "skip_get_url": true, - "url": "https://www.west-lindsey.gov.uk/", - "wiki_name": "West Lindsey", - "wiki_note": "Provide your house name/number in the `house_number` parameter, and postcode in the `postcode` parameter, both wrapped in double quotes. If multiple results are returned, the first will be used.", - "LAD24CD": "E07000142" - }, - "WestLothianCouncil": { - "house_number": "1 GOSCHEN PLACE", - "postcode": "EH52 5JE", - "skip_get_url": true, - "url": "https://www.westlothian.gov.uk/", - "web_driver": "http://selenium:4444", - "wiki_name": "West Lothian", - "wiki_note": "Provide your house name/number in the `house_number` parameter (wrapped in double quotes) and your postcode in the `postcode` parameter.", - "LAD24CD": "S12000040" - }, - "WestMorlandAndFurness": { - "uprn": "100110353478", - "url": "https://www.westmorlandandfurness.gov.uk/", - "wiki_command_url_override": "https://www.westmorlandandfurness.gov.uk/", - "wiki_name": "Westmorland and Furness", - "wiki_note": "Provide your UPRN. You can find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E06000064" - }, - "WestNorthamptonshireCouncil": { - "skip_get_url": true, - "uprn": "28056796", - "url": "https://www.westnorthants.gov.uk", - "wiki_name": "West Northamptonshire", - "wiki_note": "Provide your UPRN. You can find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E06000062" - }, - "WestOxfordshireDistrictCouncil": { - "house_number": "24", - "postcode": "OX28 1YA", - "skip_get_url": true, - "url": "https://community.westoxon.gov.uk/s/waste-collection-enquiry", - "web_driver": "http://selenium:4444", - "wiki_name": "West Oxfordshire", - "wiki_note": "Provide your house number in the `house_number` parameter and your postcode in the `postcode` parameter.", - "LAD24CD": "E07000181" - }, - "WestSuffolkCouncil": { - "postcode": "IP28 6DR", - "skip_get_url": true, - "uprn": "10009739960", - "url": "https://maps.westsuffolk.gov.uk/MyWestSuffolk.aspx", - "wiki_name": "West Suffolk", - "wiki_note": "Provide your UPRN and postcode. You can find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000245" - }, - "WiganBoroughCouncil": { - "postcode": "WN2 4UQ", - "skip_get_url": true, - "uprn": "010093942934", - "url": "https://apps.wigan.gov.uk/MyNeighbourhood/", - "wiki_name": "Wigan", - "wiki_note": "Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E08000010" - }, - "WiltshireCouncil": { - "postcode": "SN8 3TE", - "skip_get_url": true, - "uprn": "100120982570", - "url": "https://ilambassadorformsprod.azurewebsites.net/wastecollectiondays/index", - "wiki_name": "Wiltshire", - "wiki_note": "Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", - "LAD24CD": "E06000054" - }, - "WinchesterCityCouncil": { - "house_number": "12", - "paon": "12", - "postcode": "SO23 7GA", - "skip_get_url": false, - "url": "https://iportal.itouchvision.com/icollectionday/collection-day", - "web_driver": "http://selenium:4444", - "wiki_name": "Winchester", - "wiki_note": "Provide your house name/number in the `house_number` parameter (wrapped in double quotes) and your postcode in the `postcode` parameter.", - "LAD24CD": "E07000094" - }, - "WindsorAndMaidenheadCouncil": { - "skip_get_url": true, - "uprn": "100080371082", - "url": "https://forms.rbwm.gov.uk/bincollections?uprn=", - "web_driver": "http://selenium:4444", - "wiki_name": "Windsor and Maidenhead", - "wiki_note": "Provide your UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E06000040" - }, - "WirralCouncil": { - "uprn": "Vernon Avenue,Seacombe", - "url": "https://www.wirral.gov.uk", - "wiki_command_url_override": "https://www.wirral.gov.uk", - "wiki_name": "Wirral", - "wiki_note": "In the `uprn` field, enter your street name and suburb separated by a comma (e.g., 'Vernon Avenue,Seacombe').", - "LAD24CD": "E08000015" - }, - "WokingBoroughCouncil": { - "house_number": "2", - "postcode": "GU21 4JY", - "skip_get_url": true, - "url": "https://asjwsw-wrpwokingmunicipal-live.whitespacews.com/", - "wiki_name": "Woking", - "wiki_note": "Provide your house number in the `house_number` parameter and postcode in the `postcode` parameter. This works with all collection areas that use Joint Waste Solutions.", - "LAD24CD": "E07000217" - }, - "WokinghamBoroughCouncil": { - "house_number": "90", - "postcode": "RG40 2HR", - "skip_get_url": true, - "url": "https://www.wokingham.gov.uk/rubbish-and-recycling/waste-collection/find-your-bin-collection-day", - "web_driver": "http://selenium:4444", - "wiki_name": "Wokingham", - "wiki_note": "Provide your house number in the `house_number` parameter and postcode in the `postcode` parameter.", - "LAD24CD": "E06000041" - }, - "WolverhamptonCityCouncil": { - "postcode": "WV3 9NZ", - "uprn": "100071205205", - "url": "https://www.wolverhampton.gov.uk", - "wiki_name": "Wolverhampton", - "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", - "LAD24CD": "E08000031" - }, - "WorcesterCityCouncil": { - "uprn": "100120650345", - "url": "https://www.Worcester.gov.uk", - "wiki_command_url_override": "https://www.Worcester.gov.uk", - "wiki_name": "Worcester", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", - "LAD24CD": "E07000237" - }, - "WrexhamCountyBoroughCouncil": { - "house_number": "1", - "postcode": "LL12 7RW", - "uprn": "200002944225", - "skip_get_url": true, - "url": "https://www.wrexham.gov.uk/service/when-are-my-bins-collected", - "web_driver": "http://selenium:4444", - "wiki_name": "Wrexham", - "wiki_note": "Provide your house number in the `house_number` parameter and postcode in the `postcode` parameter.", - "LAD24CD": "W06000006" - }, - "WychavonDistrictCouncil": { - "postcode": "WR3 7RU", - "skip_get_url": true, - "uprn": "100120716273", - "url": "https://selfservice.wychavon.gov.uk/wdcroundlookup/wdc_search.jsp", - "web_driver": "http://selenium:4444", - "wiki_name": "Wychavon", - "wiki_note": "Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000238" - }, - "WyreCouncil": { - "skip_get_url": true, - "uprn": "10003519994", - "url": "https://www.wyre.gov.uk/bins-rubbish-recycling", - "wiki_name": "Wyre", - "wiki_note": "Provide your UPRN. Find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", - "LAD24CD": "E07000128" - }, - "WyreForestDistrictCouncil": { - "house_number": "Monday", - "skip_get_url": true, - "url": "https://www.wyreforestdc.gov.uk", - "wiki_name": "Wyre Forest", - "wiki_note": "Use the House Number field to pass the DAY of the week for your collections. [Monday/Tuesday/Wednesday/Thursday/Friday/Saturday/Sunday].", - "LAD24CD": "E07000239" - }, - "YorkCouncil": { - "skip_get_url": true, - "uprn": "100050535540", - "url": "https://waste-api.york.gov.uk/api/Collections/GetBinCollectionDataForUprn/", - "wiki_name": "York", - "wiki_note": "Provide your UPRN.", - "LAD24CD": "E06000014" - } -} +{ + "AberdeenCityCouncil": { + "LAD24CD": "S12000033", + "uprn": "9051156186", + "url": "https://www.aberdeencity.gov.uk", + "wiki_name": "Aberdeen City", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN." + }, + "AberdeenshireCouncil": { + "LAD24CD": "S12000034", + "uprn": "151176430", + "url": "https://online.aberdeenshire.gov.uk", + "wiki_command_url_override": "https://online.aberdeenshire.gov.uk", + "wiki_name": "Aberdeenshire", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN." + }, + "AdurAndWorthingCouncils": { + "url": "https://www.adur-worthing.gov.uk/bin-day/?brlu-selected-address=100061878829", + "wiki_command_url_override": "https://www.adur-worthing.gov.uk/bin-day/?brlu-selected-address=XXXXXXXX", + "wiki_name": "Adur", + "wiki_note": "Replace XXXXXXXX with your UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find it.", + "LAD24CD": "E07000223" + }, + "AmberValleyBoroughCouncil": { + "uprn": "100030026621", + "url": "https://ambervalley.gov.uk", + "wiki_name": "Amber Valley", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000032" + }, + "AngusCouncil": { + "uprn": "117053733", + "skip_get_url": true, + "postcode": "DD7 7LE", + "url": "https://www.angus.gov.uk/bins_litter_and_recycling/bin_collection_days", + "web_driver": "http://selenium:4444", + "wiki_name": "Angus", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. Requires Selenium", + "LAD24CD": "S12000041" + }, + "AntrimAndNewtonabbeyCouncil": { + "LAD24CD": "N09000001", + "url": "https://antrimandnewtownabbey.gov.uk/residents/bins-recycling/bins-schedule/?Id=643", + "wiki_command_url_override": "https://antrimandnewtownabbey.gov.uk/residents/bins-recycling/bins-schedule/?Id=XXXX", + "wiki_name": "Antrim and Newtownabbey", + "wiki_note": "Navigate to [https://antrimandnewtownabbey.gov.uk/residents/bins-recycling/bins-schedule] and search for your street name. Use the URL with the ID to replace XXXXXXXX with your specific ID." + }, + "ArdsAndNorthDownCouncil": { + "uprn": "187136177", + "url": "https://www.ardsandnorthdown.gov.uk", + "wiki_command_url_override": "https://www.ardsandnorthdown.gov.uk", + "wiki_name": "Ards and North Down", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "N09000011" + }, + "ArgyllandButeCouncil": { + "skip_get_url": true, + "postcode": "PA286LJ", + "uprn": "000125011723", + "url": "https://www.argyll-bute.gov.uk/rubbish-and-recycling/household-waste/bin-collection", + "web_driver": "http://selenium:4444", + "wiki_name": "Argyll and Bute", + "wiki_note": "Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "S12000035" + }, + "ArmaghBanbridgeCraigavonCouncil": { + "LAD24CD": "N09000002", + "uprn": "185625284", + "url": "https://www.armaghbanbridgecraigavon.gov.uk/", + "wiki_command_url_override": "https://www.armaghbanbridgecraigavon.gov.uk/", + "wiki_name": "Armagh City, Banbridge and Craigavon", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN." + }, + "ArunCouncil": { + "house_number": "1", + "postcode": "BN16 4DA", + "skip_get_url": true, + "url": "https://www1.arun.gov.uk/when-are-my-bins-collected", + "web_driver": "http://selenium:4444", + "wiki_name": "Arun", + "wiki_note": "Pass the house name/number and postcode in their respective parameters, both wrapped in double quotes. This parser requires a Selenium webdriver.", + "LAD24CD": "E07000224" + }, + "AshfieldDistrictCouncil": { + "house_number": "1", + "postcode": "NG16 6RH", + "url": "https://www.ashfield.gov.uk", + "web_driver": "http://selenium:4444", + "wiki_name": "Ashfield", + "wiki_note": "Pass the house name/number and postcode in their respective parameters, both wrapped in double quotes. This parser requires a Selenium webdriver", + "LAD24CD": "E07000170" + }, + "AshfordBoroughCouncil": { + "postcode": "TN23 7SP", + "uprn": "100060777899", + "url": "https://ashford.gov.uk", + "web_driver": "http://selenium:4444", + "wiki_command_url_override": "https://ashford.gov.uk", + "wiki_name": "Ashford", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000105" + }, + "AylesburyValeCouncil": { + "skip_get_url": true, + "uprn": "766252532", + "url": "http://avdcbins.web-labs.co.uk/RefuseApi.asmx", + "wiki_name": "Buckinghamshire (Aylesbury Vale)", + "wiki_note": "To get the UPRN, please use [FindMyAddress](https://www.findmyaddress.co.uk/search). Returns all published collections in the past, present, future.", + "LAD24CD": "E06000060" + }, + "BCPCouncil": { + "LAD24CD": "E06000058", + "skip_get_url": true, + "uprn": "100040810214", + "url": "https://online.bcpcouncil.gov.uk/bindaylookup/", + "wiki_name": "Bournemouth, Christchurch and Poole", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN." + }, + "BaberghDistrictCouncil": { + "house_number": "Monday", + "postcode": "Week 1", + "skip_get_url": true, + "uprn": "Tuesday", + "url": "https://www.babergh.gov.uk", + "wiki_name": "Babergh", + "wiki_note": "Use the House Number field to pass the DAY of the week for your NORMAL collections. [Monday/Tuesday/Wednesday/Thursday/Friday]. [OPTIONAL] Use the 'postcode' field to pass the WEEK for your garden collection. [Week 1/Week 2]. [OPTIONAL] Use the 'uprn' field to pass the DAY for your garden collection. [Monday/Tuesday/Wednesday/Thursday/Friday]", + "LAD24CD": "E07000200" + }, + "BarkingDagenham": { + "house_number": "19", + "postcode": "RM6 6XH", + "skip_get_url": true, + "web_driver": "http://selenium:4444", + "url": "https://www.lbbd.gov.uk/rubbish-recycling/household-bin-collection/check-your-bin-collection-days", + "wiki_name": "Barking and Dagenham", + "wiki_note": "Use house number and postcode. Requires Selenium.", + "LAD24CD": "E09000002" + }, + "BarnetCouncil": { + "house_number": "HA8 7NA, 2, MANOR PARK GARDENS, EDGWARE, BARNET", + "postcode": "HA8 7NA", + "skip_get_url": true, + "url": "https://www.barnet.gov.uk/recycling-and-waste/bin-collections/find-your-bin-collection-day", + "web_driver": "http://selenium:4444", + "wiki_name": "Barnet", + "wiki_note": "Follow the instructions [here](https://www.barnet.gov.uk/recycling-and-waste/bin-collections/find-your-bin-collection-day) until you get the page listing your address, then copy the entire address text and use that in the house number field. This parser requires a Selenium webdriver.", + "LAD24CD": "E09000003" + }, + "BarnsleyMBCouncil": { + "postcode": "S36 9AN", + "skip_get_url": true, + "uprn": "2007004502", + "url": "https://waste.barnsley.gov.uk/ViewCollection/Collections", + "wiki_name": "Barnsley", + "wiki_note": "To get the UPRN, you will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E08000016" + }, + "BasildonCouncil": { + "skip_get_url": true, + "uprn": "10013350430", + "url": "https://basildonportal.azurewebsites.net/api/getPropertyRefuseInformation", + "wiki_name": "Basildon", + "wiki_note": "To get the UPRN, you will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000066" + }, + "BasingstokeCouncil": { + "LAD24CD": "E07000084", + "skip_get_url": true, + "uprn": "100060220926", + "url": "https://www.basingstoke.gov.uk/bincollection", + "wiki_name": "Basingstoke and Deane", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN." + }, + "BathAndNorthEastSomersetCouncil": { + "skip_get_url": true, + "uprn": "100120000855", + "url": "https://www.bathnes.gov.uk/webforms/waste/collectionday/", + "wiki_name": "Bath and North East Somerset", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E06000022" + }, + "BedfordBoroughCouncil": { + "skip_get_url": true, + "uprn": "10024232065", + "url": "https://www.bedford.gov.uk/bins-and-recycling/household-bins-and-recycling/check-your-bin-day", + "wiki_name": "Bedford", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E06000055" + }, + "BedfordshireCouncil": { + "postcode": "SG19 2UP", + "skip_get_url": true, + "uprn": "10000802040", + "url": "https://www.centralbedfordshire.gov.uk/info/163/bins_and_waste_collections_-_check_bin_collection_day", + "wiki_name": "Central Bedfordshire", + "wiki_note": "In order to use this parser, you must provide a valid postcode and a UPRN retrieved from the council's website for your specific address.", + "LAD24CD": "E06000056" + }, + "BelfastCityCouncil": { + "postcode": "BT10 0GY", + "skip_get_url": true, + "uprn": "185086469", + "url": "https://online.belfastcity.gov.uk/find-bin-collection-day/Default.aspx", + "wiki_name": "Belfast", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "N09000003" + }, + "BexleyCouncil": { + "uprn": "100020196143", + "skip_get_url": true, + "url": "https://waste.bexley.gov.uk/waste", + "wiki_name": "Bexley", + "wiki_note": "Provide your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to locate it.", + "LAD24CD": "E09000004" + }, + "BirminghamCityCouncil": { + "postcode": "B5 7XE", + "uprn": "100070445256", + "url": "https://www.birmingham.gov.uk/xfp/form/619", + "wiki_name": "Birmingham", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E08000025" + }, + "BlabyDistrictCouncil": { + "uprn": "100030401782", + "url": "https://www.blaby.gov.uk", + "wiki_command_url_override": "https://www.blaby.gov.uk", + "wiki_name": "Blaby", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000129" + }, + "BlackburnCouncil": { + "url": "https://www.blaby.gov.uk", + "LAD24CD": "E06000008", + "skip_get_url": true, + "uprn": "100010733027", + "wiki_name": "Blackburn with Darwen", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN." + }, + "BlaenauGwentCountyBoroughCouncil": { + "postcode": "NP23 7TE", + "skip_get_url": false, + "uprn": "100100471367", + "url": "https://www.blaenau-gwent.gov.uk", + "web_driver": "http://selenium:4444", + "wiki_name": "Blaenau Gwent", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "W06000019" + }, + "BolsoverCouncil": { + "uprn": "100030066827", + "url": "https://bolsover.gov.uk", + "wiki_name": "Bolsover", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000033" + }, + "BoltonCouncil": { + "postcode": "BL1 5PQ", + "skip_get_url": true, + "uprn": "100010886936", + "url": "https://carehomes.bolton.gov.uk/bins.aspx", + "web_driver": "http://selenium:4444", + "wiki_name": "Bolton", + "wiki_note": "To get the UPRN, you will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search). Previously required a single field that was UPRN and full address; now requires UPRN and postcode as separate fields.", + "LAD24CD": "E08000001" + }, + "BostonBoroughCouncil": { + "house_number": "CEDAR", + "postcode": "PE20 1AY", + "skip_get_url": true, + "url": "https://www.boston.gov.uk/findwastecollections", + "web_driver": "http://selenium:4444", + "wiki_name": "Boston", + "wiki_note": "Provide your house number in the `house_number` parameter and postcode in the `postcode` parameter.", + "LAD24CD": "E07000136" + }, + "BracknellForestCouncil": { + "house_number": "57", + "paon": "57", + "postcode": "GU47 9BS", + "skip_get_url": true, + "url": "https://selfservice.mybfc.bracknell-forest.gov.uk/w/webpage/waste-collection-days", + "wiki_name": "Bracknell Forest", + "wiki_note": "Pass the house number and postcode in their respective parameters.", + "LAD24CD": "E06000036" + }, + "BradfordMDC": { + "custom_component_show_url_field": false, + "skip_get_url": true, + "uprn": "100051146921", + "url": "https://onlineforms.bradford.gov.uk/ufs/collectiondates.eb", + "wiki_name": "Bradford", + "wiki_note": "To get the UPRN, you will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search). Postcode isn't parsed by this script, but you can pass it in double quotes.", + "LAD24CD": "E08000032" + }, + "BraintreeDistrictCouncil": { + "postcode": "CO5 9BD", + "skip_get_url": true, + "uprn": "10006930172", + "url": "https://www.braintree.gov.uk/", + "wiki_name": "Braintree", + "wiki_note": "Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "E07000067" + }, + "BrecklandCouncil": { + "uprn": "100091495479", + "url": "https://www.breckland.gov.uk", + "wiki_command_url_override": "https://www.breckland.gov.uk", + "wiki_name": "Breckland", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000143" + }, + "BrentCouncil": { + "house_number": "25", + "postcode": "HA3 0QU", + "url": "https://recyclingservices.brent.gov.uk/waste", + "wiki_name": "Brent", + "wiki_note": "Pass the house number and postcode in their respective parameters.", + "LAD24CD": "E09000005" + }, + "BrightonandHoveCityCouncil": { + "house_number": "44", + "postcode": "BN1 8NE", + "skip_get_url": true, + "url": "https://cityclean.brighton-hove.gov.uk/link/collections", + "web_driver": "http://selenium:4444", + "wiki_name": "Brighton and Hove", + "wiki_note": "Use house number and postcode. Requires Selenium", + "LAD24CD": "E06000043" + }, + "BristolCityCouncil": { + "LAD24CD": "E06000023", + "skip_get_url": true, + "uprn": "116638", + "url": "https://bristolcouncil.powerappsportals.com/completedynamicformunauth/?servicetypeid=7dce896c-b3ba-ea11-a812-000d3a7f1cdc", + "wiki_name": "City of Bristol", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN." + }, + "BroadlandDistrictCouncil": { + "skip_get_url": true, + "house_number": "1", + "postcode": "NR10 3FD", + "url": "https://area.southnorfolkandbroadland.gov.uk/FindAddress", + "web_driver": "http://selenium:4444", + "wiki_name": "Broadland", + "wiki_note": "Use house number and postcode. Requires Selenium.", + "LAD24CD": "E07000144" + }, + "BromleyBoroughCouncil": { + "url": "https://recyclingservices.bromley.gov.uk/waste/6087017", + "web_driver": "http://selenium:4444", + "wiki_command_url_override": "https://recyclingservices.bromley.gov.uk/waste/XXXXXXX", + "wiki_name": "Bromley", + "wiki_note": "Follow the instructions [here](https://recyclingservices.bromley.gov.uk/waste) until the \"Your bin days\" page then copy the URL and replace the URL in the command.", + "LAD24CD": "E09000006" + }, + "BromsgroveDistrictCouncil": { + "uprn": "100120584652", + "url": "https://www.bromsgrove.gov.uk", + "wiki_command_url_override": "https://www.bromsgrove.gov.uk", + "wiki_name": "Bromsgrove", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000234" + }, + "BroxbourneCouncil": { + "postcode": "EN8 7FL", + "uprn": "148048608", + "url": "https://www.broxbourne.gov.uk", + "wiki_name": "Broxbourne", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000095" + }, + "BroxtoweBoroughCouncil": { + "postcode": "NG16 2LY", + "skip_get_url": true, + "uprn": "100031325997", + "url": "https://www.broxtowe.gov.uk/", + "web_driver": "http://selenium:4444", + "wiki_name": "Broxtowe", + "wiki_note": "Pass the UPRN and postcode. To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000172" + }, + "BuckinghamshireCouncil": { + "house_number": "2", + "postcode": "HP13 7BA", + "skip_get_url": true, + "url": "https://iapp.itouchvision.com/iappcollectionday/collection-day/?uuid=FA353FC74600CBE61BE409534D00A8EC09BDA3AC&lang=en", + "web_driver": "http://selenium:4444", + "wiki_name": "Buckinghamshire", + "wiki_note": "Pass the house name/number and postcode in their respective arguments, both wrapped in quotes.", + "LAD24CD": "E06000060" + }, + "BurnleyBoroughCouncil": { + "uprn": "100010347165", + "url": "https://www.burnley.gov.uk", + "wiki_name": "Burnley", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000117" + }, + "BuryCouncil": { + "house_number": "3", + "postcode": "M26 3XY", + "skip_get_url": true, + "url": "https://www.bury.gov.uk/waste-and-recycling/bin-collection-days-and-alerts", + "wiki_name": "Bury", + "wiki_note": "Pass the postcode and house number in their respective arguments, both wrapped in quotes.", + "LAD24CD": "E08000002" + }, + "CalderdaleCouncil": { + "postcode": "OL14 7EX", + "skip_get_url": true, + "uprn": "010035034598", + "url": "https://www.calderdale.gov.uk/environment/waste/household-collections/collectiondayfinder.jsp", + "web_driver": "http://selenium:4444", + "wiki_name": "Calderdale", + "wiki_note": "Pass the UPRN and postcode. To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E08000033" + }, + "CambridgeCityCouncil": { + "uprn": "200004159750", + "url": "https://www.cambridge.gov.uk/", + "wiki_name": "Cambridge", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000008" + }, + "CannockChaseDistrictCouncil": { + "postcode": "WS15 1JA", + "skip_get_url": true, + "uprn": "200003095389", + "url": "https://www.cannockchasedc.gov.uk/", + "wiki_name": "Cannock Chase", + "wiki_note": "To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000192" + }, + "CanterburyCityCouncil": { + "uprn": "10094583181", + "url": "https://www.canterbury.gov.uk", + "wiki_command_url_override": "https://www.canterbury.gov.uk", + "wiki_name": "Canterbury", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000106" + }, + "CardiffCouncil": { + "skip_get_url": true, + "uprn": "100100112419", + "url": "https://www.gov.uk", + "wiki_name": "Cardiff", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "W06000015" + }, + "CarmarthenshireCountyCouncil": { + "uprn": "10004859302", + "url": "https://www.carmarthenshire.gov.wales", + "wiki_command_url_override": "https://www.carmarthenshire.gov.wales", + "wiki_name": "Carmarthenshire", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "W06000010" + }, + "CastlepointDistrictCouncil": { + "skip_get_url": true, + "uprn": "4525", + "url": "https://apps.castlepoint.gov.uk/cpapps/index.cfm?fa=wastecalendar", + "wiki_name": "Castle Point", + "wiki_note": "For this council, 'uprn' is actually a 4-digit code for your street. Go [here](https://apps.castlepoint.gov.uk/cpapps/index.cfm?fa=wastecalendar) and inspect the source of the dropdown box to find the 4-digit number for your street.", + "LAD24CD": "E07000069" + }, + "CeredigionCountyCouncil": { + "house_number": "BLAEN CWMMAGWR, TRISANT, CEREDIGION, SY23 4RQ", + "postcode": "SY23 4RQ", + "url": "https://www.ceredigion.gov.uk/resident/bins-recycling/", + "web_driver": "http://selenium:4444", + "wiki_name": "Ceredigion", + "wiki_note": "House Number is the full address as it appears on the drop-down on the site when you search by postcode. This parser requires a Selenium webdriver.", + "LAD24CD": "W06000008" + }, + "CharnwoodBoroughCouncil": { + "uprn": "100030446438", + "skip_get_url": true, + "url": "https://www.charnwood.gov.uk/pages/waste_collections_calendars", + "wiki_name": "Charnwood", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000130" + }, + "ChelmsfordCityCouncil": { + "house_number": "1 Celeborn Street, South Woodham Ferrers, Chelmsford, CM3 7AE", + "postcode": "CM3 7AE", + "url": "https://www.chelmsford.gov.uk/myhome/", + "web_driver": "http://selenium:4444", + "wiki_name": "Chelmsford", + "wiki_note": "Follow the instructions [here](https://www.chelmsford.gov.uk/myhome/) until you get the page listing your address, then copy the entire address text and use that in the house number field.", + "LAD24CD": "E07000070" + }, + "CheltenhamBoroughCouncil": { + "postcode": "GL51 3NA", + "skip_get_url": true, + "uprn": "100120372027", + "url": "https://www.cheltenham.gov.uk", + "wiki_name": "Cheltenham", + "wiki_note": "Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000078" + }, + "CherwellDistrictCouncil": { + "uprn": "100121292407", + "url": "https://www.cherwell.gov.uk", + "wiki_name": "Cherwell", + "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "E07000177" + }, + "CheshireEastCouncil": { + "skip_get_url": true, + "uprn": "100012830647", + "url": "https://online.cheshireeast.gov.uk/mycollectionday", + "wiki_name": "Cheshire East", + "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "E06000049" + }, + "CheshireWestAndChesterCouncil": { + "skip_get_url": true, + "uprn": "100012346655", + "url": "https://my.cheshirewestandchester.gov.uk", + "wiki_name": "Cheshire West and Chester", + "wiki_note": "Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E06000050" + }, + "ChesterfieldBoroughCouncil": { + "skip_get_url": true, + "uprn": "74008234", + "url": "https://www.chesterfield.gov.uk", + "wiki_name": "Chesterfield", + "wiki_note": "Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000034" + }, + "ChichesterDistrictCouncil": { + "house_number": "7", + "postcode": "RH14 0JT", + "skip_get_url": true, + "url": "https://www.chichester.gov.uk/checkyourbinday", + "web_driver": "http://selenium:4444", + "wiki_name": "Chichester", + "wiki_note": "Needs the full address and postcode as it appears on [this page](https://www.chichester.gov.uk/checkyourbinday).", + "LAD24CD": "E07000225" + }, + "ChorleyCouncil": { + "postcode": "PR6 7PG", + "skip_get_url": true, + "uprn": "100010382247", + "url": "https://myaccount.chorley.gov.uk/wastecollections.aspx", + "web_driver": "http://selenium:4444", + "wiki_name": "Chorley", + "wiki_note": "Chorley needs to be passed both a Postcode & UPRN to work. Find this on [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000118" + }, + "ColchesterCityCouncil": { + "house_number": "29", + "paon": "29", + "postcode": "CO2 8UN", + "skip_get_url": false, + "url": "https://www.colchester.gov.uk/your-recycling-calendar", + "web_driver": "http://selenium:4444", + "wiki_name": "Colchester", + "wiki_note": "Pass the house name/number in the house number parameter, wrapped in double quotes.", + "LAD24CD": "E07000071" + }, + "ConwyCountyBorough": { + "uprn": "100100429249", + "url": "https://www.conwy.gov.uk", + "wiki_name": "Conwy", + "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "W06000003" + }, + "CopelandBoroughCouncil": { + "LAD24CD": "E07000028", + "uprn": "100110734613", + "url": "https://www.copeland.gov.uk", + "wiki_name": "Copeland", + "wiki_note": "*****This has now been replaced by Cumberland Council****" + }, + "CornwallCouncil": { + "skip_get_url": true, + "uprn": "100040128734", + "url": "https://www.cornwall.gov.uk/my-area/", + "wiki_name": "Cornwall", + "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "E06000052" + }, + "CotswoldDistrictCouncil": { + "house_number": "19", + "postcode": "GL56 0GB", + "skip_get_url": true, + "url": "https://community.cotswold.gov.uk/s/waste-collection-enquiry", + "web_driver": "http://selenium:4444", + "wiki_name": "Cotswold", + "wiki_note": "Pass the full address in the house number and postcode in", + "LAD24CD": "E07000079" + }, + "CoventryCityCouncil": { + "url": "https://www.coventry.gov.uk/directory-record/62310/abberton-way-", + "wiki_command_url_override": "https://www.coventry.gov.uk/directory_record/XXXXXX/XXXXXX", + "wiki_name": "Coventry", + "wiki_note": "Follow the instructions [here](https://www.coventry.gov.uk/bin-collection-calendar) until you get the page that shows the weekly collections for your address then copy the URL and replace the URL in the command.", + "LAD24CD": "E08000026" + }, + "CrawleyBoroughCouncil": { + "house_number": "9701076", + "skip_get_url": true, + "uprn": "100061785321", + "url": "https://my.crawley.gov.uk/", + "wiki_name": "Crawley", + "wiki_note": "Crawley needs to be passed both a UPRN and a USRN to work. Find these on [FindMyAddress](https://www.findmyaddress.co.uk/search) or [FindMyStreet](https://www.findmystreet.co.uk/map).", + "LAD24CD": "E07000226" + }, + "CroydonCouncil": { + "house_number": "13", + "postcode": "SE25 5DW", + "skip_get_url": true, + "url": "https://service.croydon.gov.uk/wasteservices/w/webpage/bin-day-enter-address", + "web_driver": "http://selenium:4444", + "wiki_name": "Croydon", + "wiki_note": "Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver.", + "LAD24CD": "E09000008" + }, + "CumberlandAllerdaleCouncil": { + "house_number": "2", + "postcode": "CA13 0DE", + "url": "https://www.allerdale.gov.uk", + "wiki_name": "Cumberland (Allerdale)", + "wiki_note": "Pass the house number and postcode in their respective parameters.", + "LAD24CD": "E06000063" + }, + "CumberlandCouncil": { + "uprn": "100110734613", + "url": "https://waste.cumberland.gov.uk", + "wiki_name": "Cumberland", + "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "E06000063" + }, + "DacorumBoroughCouncil": { + "house_number": "13", + "postcode": "HP3 9JY", + "skip_get_url": true, + "url": "https://webapps.dacorum.gov.uk/bincollections/", + "web_driver": "http://selenium:4444", + "wiki_name": "Dacorum", + "wiki_note": "Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver.", + "LAD24CD": "E07000096" + }, + "DartfordBoroughCouncil": { + "uprn": "100060861698", + "url": "https://www.dartford.gov.uk/waste-recycling/collection-day", + "skip_get_url": true, + "wiki_name": "Dartford", + "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "E07000107" + }, + "DenbighshireCouncil": { + "uprn": "200004299351", + "url": "https://www.denbighshire.gov.uk/", + "wiki_name": "Denbighshire", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "W06000004" + }, + "DerbyCityCouncil": { + "uprn": "10010684240", + "url": "https://www.derby.gov.uk", + "wiki_name": "Derby", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E06000015" + }, + "DerbyshireDalesDistrictCouncil": { + "postcode": "DE4 3AS", + "skip_get_url": true, + "uprn": "10070102161", + "url": "https://www.derbyshiredales.gov.uk/", + "wiki_name": "Derbyshire Dales", + "wiki_note": "Pass the UPRN and postcode. To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000035" + }, + "DoncasterCouncil": { + "skip_get_url": true, + "uprn": "100050768956", + "url": "https://www.doncaster.gov.uk/Compass/Entity/Launch/D3/", + "wiki_name": "Doncaster", + "wiki_note": "Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E08000017" + }, + "DorsetCouncil": { + "skip_get_url": true, + "uprn": "100040711049", + "url": "https://www.dorsetcouncil.gov.uk/", + "wiki_name": "Dorset Council", + "wiki_note": "Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E06000059" + }, + "DoverDistrictCouncil": { + "uprn": "100060908340", + "url": "https://collections.dover.gov.uk/property", + "skip_get_url": true, + "wiki_name": "Dover", + "wiki_note": "To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000108" + }, + "DudleyCouncil": { + "uprn": "90014244", + "url": "https://my.dudley.gov.uk", + "wiki_command_url_override": "https://my.dudley.gov.uk", + "wiki_name": "Dudley", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E08000027" + }, + "DundeeCityCouncil": { + "uprn": "9059043390", + "url": "https://www.dundeecity.gov.uk/", + "wiki_name": "Dundee City", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "S12000042" + }, + "DurhamCouncil": { + "LAD24CD": "E06000047", + "skip_get_url": true, + "uprn": "200003218818", + "url": "https://www.durham.gov.uk/bincollections?uprn=", + "wiki_name": "County Durham", + "wiki_note": "Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search)." + }, + "EalingCouncil": { + "skip_get_url": true, + "uprn": "12073883", + "url": "https://www.ealing.gov.uk/site/custom_scripts/WasteCollectionWS/home/FindCollection", + "wiki_name": "Ealing", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E09000009" + }, + "EastAyrshireCouncil": { + "uprn": "127074727", + "url": "https://www.east-ayrshire.gov.uk", + "wiki_command_url_override": "https://www.east-ayrshire.gov.uk", + "wiki_name": "East Ayrshire", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "S12000008" + }, + "EastbourneBoroughCouncil": { + "uprn": "100060011258", + "url": "https://www.lewes-eastbourne.gov.uk/article/1158/When-is-my-bin-collection-day", + "skip_get_url": true, + "wiki_name": "Eastbourne", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000061" + }, + "EastCambridgeshireCouncil": { + "skip_get_url": true, + "uprn": "10002597178", + "url": "https://www.eastcambs.gov.uk/", + "wiki_name": "East Cambridgeshire", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000009" + }, + "EastDevonDC": { + "uprn": "010090909915", + "url": "https://eastdevon.gov.uk/recycling-and-waste/recycling-waste-information/when-is-my-bin-collected/", + "skip_get_url": true, + "wiki_name": "East Devon", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000040" + }, + "EastHertsCouncil": { + "LAD24CD": "E07000097", + "house_number": "1", + "postcode": "CM20 2FZ", + "skip_get_url": true, + "url": "https://www.eastherts.gov.uk", + "web_driver": "http://selenium:4444", + "wiki_name": "East Herts Council", + "wiki_note": "Pass the house number and postcode in their respective parameters." + }, + "EastLindseyDistrictCouncil": { + "house_number": "1", + "postcode": "PE22 0YD", + "skip_get_url": true, + "url": "https://www.e-lindsey.gov.uk/", + "web_driver": "http://selenium:4444", + "wiki_name": "East Lindsey", + "wiki_note": "Pass the house name/number and postcode in their respective parameters. This parser requires a Selenium webdriver.", + "LAD24CD": "E07000137" + }, + "EastLothianCouncil": { + "house_number": "Flat 1", + "postcode": "EH21 6QA", + "skip_get_url": true, + "url": "https://eastlothian.gov.uk", + "wiki_name": "East Lothian", + "wiki_note": "Pass the house number and postcode in their respective parameters", + "LAD24CD": "S12000010" + }, + "EastRenfrewshireCouncil": { + "house_number": "23", + "postcode": "G46 6RG", + "skip_get_url": true, + "url": "https://eastrenfrewshire.gov.uk/", + "web_driver": "http://selenium:4444", + "wiki_name": "East Renfrewshire", + "wiki_note": "Pass the house name/number and postcode in their respective parameters. This parser requires a Selenium webdriver.", + "LAD24CD": "S12000011" + }, + "EastRidingCouncil": { + "LAD24CD": "E06000011", + "house_number": "14 THE LEASES BEVERLEY HU17 8LG", + "postcode": "HU17 8LG", + "skip_get_url": true, + "url": "https://wasterecyclingapi.eastriding.gov.uk", + "web_driver": "http://selenium:4444", + "wiki_name": "East Riding of Yorkshire", + "wiki_note": "Put the full address as it displays on the council website dropdown when you do the check manually." + }, + "EastStaffordshireBoroughCouncil": { + "url": "https://www.eaststaffsbc.gov.uk/bins-rubbish-recycling/collection-dates/68382", + "wiki_command_url_override": "https://www.eaststaffsbc.gov.uk/bins-rubbish-recycling/collection-dates/XXXXX", + "wiki_name": "East Staffordshire", + "wiki_note": "Replace `XXXXX` with your property's ID when selecting from https://www.eaststaffsbc.gov.uk/bins-rubbish-recycling/collection-dates.", + "LAD24CD": "E07000193" + }, + "EastSuffolkCouncil": { + "postcode": "IP11 9FJ", + "skip_get_url": true, + "uprn": "10093544720", + "url": "https://my.eastsuffolk.gov.uk/service/Bin_collection_dates_finder", + "web_driver": "http://selenium:4444", + "wiki_name": "East Suffolk", + "wiki_note": "To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search). This parser requires a Selenium webdriver.", + "LAD24CD": "E07000244" + }, + "EastleighBoroughCouncil": { + "skip_get_url": true, + "uprn": "100060303535", + "url": "https://www.eastleigh.gov.uk/waste-bins-and-recycling/collection-dates/your-waste-bin-and-recycling-collections?uprn=", + "web_driver": "http://selenium:4444", + "wiki_name": "Eastleigh", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000086" + }, + "EdinburghCityCouncil": { + "LAD24CD": "S12000036", + "house_number": "Tuesday", + "postcode": "Week 1", + "skip_get_url": true, + "url": "https://www.edinburgh.gov.uk", + "wiki_name": "City of Edinburgh", + "wiki_note": "Use the House Number field to pass the DAY of the week for your collections. Monday/Tuesday/Wednesday/Thursday/Friday. Use the 'postcode' field to pass the WEEK for your collection. [Week 1/Week 2]" + }, + "ElmbridgeBoroughCouncil": { + "uprn": "10013119164", + "url": "https://www.elmbridge.gov.uk", + "wiki_command_url_override": "https://www.elmbridge.gov.uk", + "wiki_name": "Elmbridge", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000207" + }, + "EnfieldCouncil": { + "house_number": "111", + "postcode": "N13 5AJ", + "skip_get_url": true, + "url": "https://www.enfield.gov.uk/services/rubbish-and-recycling/find-my-collection-day", + "web_driver": "http://selenium:4444", + "wiki_name": "Enfield", + "wiki_note": "Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver.", + "LAD24CD": "E09000010" + }, + "EnvironmentFirst": { + "url": "https://environmentfirst.co.uk/house.php?uprn=100060055444", + "wiki_command_url_override": "https://environmentfirst.co.uk/house.php?uprn=XXXXXXXXXX", + "wiki_name": "Environment First", + "wiki_note": "For properties with collections managed by Environment First, such as Lewes and Eastbourne. Replace the XXXXXXXXXX with the UPRN of your property\u2014you can use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find this." + }, + "EppingForestDistrictCouncil": { + "postcode": "IG9 6EP", + "url": "https://eppingforestdc.maps.arcgis.com/apps/instant/lookup/index.html?appid=bfca32b46e2a47cd9c0a84f2d8cdde17&find=IG9%206EP", + "skip_get_url": true, + "web_driver": "http://selenium:4444", + "wiki_name": "Epping Forest", + "wiki_note": "Add your postcode.", + "LAD24CD": "E07000072" + }, + "EpsomandEwellBoroughCouncil": { + "uprn": "100061349083", + "url": "https://www.epsom-ewell.gov.uk", + "wiki_name": "Epsom and Ewell", + "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "E07000208" + }, + "ErewashBoroughCouncil": { + "skip_get_url": true, + "uprn": "10003582028", + "url": "https://map.erewash.gov.uk/isharelive.web/myerewash.aspx", + "wiki_name": "Erewash", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000036" + }, + "ExeterCityCouncil": { + "uprn": "100040212270", + "url": "https://www.exeter.gov.uk", + "wiki_name": "Exeter", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000041" + }, + "FalkirkCouncil": { + "uprn": "136065818", + "url": "https://www.falkirk.gov.uk", + "wiki_command_url_override": "https://www.falkirk.gov.uk", + "wiki_name": "Falkirk", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "S12000014" + }, + "FarehamBoroughCouncil": { + "postcode": "PO14 4NR", + "skip_get_url": true, + "url": "https://www.fareham.gov.uk/internetlookups/search_data.aspx?type=JSON&list=DomesticBinCollections&Road=&Postcode=PO14%204NR", + "wiki_name": "Fareham", + "wiki_note": "Pass the postcode in the postcode parameter, wrapped in double quotes.", + "LAD24CD": "E07000087" + }, + "FenlandDistrictCouncil": { + "skip_get_url": true, + "uprn": "200002981143", + "url": "https://www.fenland.gov.uk/article/13114/", + "wiki_name": "Fenland", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000010" + }, + "FermanaghOmaghDistrictCouncil": { + "house_number": "20", + "postcode": "BT74 6DQ", + "skip_get_url": true, + "url": "https://www.fermanaghomagh.com/services/environment-and-waste/waste-collection-calendar/", + "wiki_name": "Fermanagh and Omagh", + "wiki_note": "Pass the house number and postcode in their respective parameters.", + "LAD24CD": "N09000006" + }, + "FifeCouncil": { + "uprn": "320203521", + "url": "https://www.fife.gov.uk", + "wiki_command_url_override": "https://www.fife.gov.uk", + "wiki_name": "Fife", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "S12000047" + }, + "FlintshireCountyCouncil": { + "uprn": "100100213710", + "url": "https://digital.flintshire.gov.uk", + "wiki_command_url_override": "https://digital.flintshire.gov.uk", + "wiki_name": "Flintshire", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "W06000005" + }, + "FolkstoneandHytheDistrictCouncil": { + "LAD24CD": "E07000112", + "skip_get_url": true, + "uprn": "50032097", + "url": "https://www.folkestone-hythe.gov.uk", + "wiki_name": "Folkestone and Hythe", + "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN." + }, + "ForestOfDeanDistrictCouncil": { + "house_number": "ELMOGAL, PARKEND ROAD, BREAM, LYDNEY", + "postcode": "GL15 6JT", + "skip_get_url": true, + "url": "https://community.fdean.gov.uk/s/waste-collection-enquiry", + "web_driver": "http://selenium:4444", + "wiki_name": "Forest of Dean", + "wiki_note": "Pass the full address in the house number and postcode parameters. This parser requires a Selenium webdriver.", + "LAD24CD": "E07000080" + }, + "FyldeCouncil": { + "uprn": "100010402452", + "url": "https://www.fylde.gov.uk", + "wiki_command_url_override": "https://www.fylde.gov.uk", + "wiki_name": "Fylde", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000119" + }, + "GatesheadCouncil": { + "house_number": "Bracken Cottage", + "postcode": "NE16 5LQ", + "skip_get_url": true, + "url": "https://www.gateshead.gov.uk/", + "web_driver": "http://selenium:4444", + "wiki_name": "Gateshead", + "wiki_note": "Pass the house name/number and postcode in their respective parameters. This parser requires a Selenium webdriver.", + "LAD24CD": "E08000037" + }, + "GedlingBoroughCouncil": { + "house_number": "Friday G4, Friday J", + "skip_get_url": true, + "url": "https://www.gedling.gov.uk/", + "wiki_name": "Gedling", + "wiki_note": "Use [this site](https://www.gbcbincalendars.co.uk/) to find the collections for your address. Use the `-n` parameter to add them in a comma-separated list inside quotes, such as: 'Friday G4, Friday J'.", + "LAD24CD": "E07000173" + }, + "GlasgowCityCouncil": { + "uprn": "906700034497", + "url": "https://onlineservices.glasgow.gov.uk/forms/RefuseAndRecyclingWebApplication/AddressSearch.aspx", + "skip_get_url": true, + "wiki_name": "Glasgow City", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "S12000049" + }, + "GloucesterCityCouncil": { + "house_number": "111", + "postcode": "GL2 0RR", + "skip_get_url": true, + "uprn": "100120479507", + "url": "https://gloucester-self.achieveservice.com/service/Bins___Check_your_bin_day", + "web_driver": "http://selenium:4444", + "wiki_name": "Gloucester", + "wiki_note": "Pass the house number, postcode, and UPRN in their respective parameters. This parser requires a Selenium webdriver.", + "LAD24CD": "E07000081" + }, + "GooglePublicCalendarCouncil": { + "url": "https://calendar.google.com/calendar/ical/0d775884b4db6a7bae5204f06dae113c1a36e505b25991ebc27c6bd42edf5b5e%40group.calendar.google.com/public/basic.ics", + "wiki_name": "Google Calendar (Public)", + "wiki_note": "The URL should be the public ics file URL for the public Google calendar. See https://support.google.com/calendar/answer/37083?sjid=7202815583021446882-EU. Councils that currently need this are Trafford.", + "supported_councils": [ + "Trafford", + "Clackmannanshire", + "Havant", + "North Warwickshire", + "Newry and Mourne", + "East Dunbartonshire", + "Pendle", + "Torfaen", + "East Hampshire", + "Ribble Valley", + "Brentwood", + "Isle of Wight", + "Westmorland and Furness", + "Derry City and Strabane", + "Norwich" + ], + "supported_councils_LAD24CD": [ + "E06000046", + "E07000068", + "E07000085", + "E07000090", + "E07000124", + "E07000218", + "E08000009", + "N09000005", + "N09000010", + "S12000005", + "S12000045", + "W06000020", + "E07000122" + ] + }, + "GraveshamBoroughCouncil": { + "skip_get_url": true, + "uprn": "100060927046", + "url": "https://www.gravesham.gov.uk", + "wiki_name": "Gravesham", + "wiki_note": "Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000109" + }, + "GreatYarmouthBoroughCouncil": { + "postcode": "NR31 7EB", + "skip_get_url": true, + "uprn": "100090834792", + "url": "https://myaccount.great-yarmouth.gov.uk/article/6456/Find-my-waste-collection-days", + "web_driver": "http://selenium:4444", + "wiki_name": "Great Yarmouth", + "wiki_note": "Pass the postcode, and UPRN in their respective parameters. This parser requires a Selenium webdriver.", + "LAD24CD": "E07000145" + }, + "GuildfordCouncil": { + "house_number": "THE LODGE", + "postcode": "GU3 1AH", + "skip_get_url": true, + "url": "https://my.guildford.gov.uk/customers/s/view-bin-collections", + "web_driver": "http://selenium:4444", + "wiki_name": "Guildford", + "wiki_note": "If the bin day is 'today' then the collectionDate will only show today's date if before 7 AM; else the date will be in 'previousCollectionDate'.", + "LAD24CD": "E07000209" + }, + "GwyneddCouncil": { + "uprn": "10070350463", + "url": "https://diogel.gwynedd.llyw.cymru", + "wiki_name": "Gwynedd", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "W06000002" + }, + "HackneyCouncil": { + "house_number": "101", + "postcode": "N16 9AS", + "url": "https://www.hackney.gov.uk", + "wiki_name": "Hackney", + "wiki_note": "Pass the postcode and house number in their respective arguments, both wrapped in quotes.", + "LAD24CD": "E09000012" + }, + "HaltonBoroughCouncil": { + "house_number": "12", + "postcode": "WA7 4HA", + "skip_get_url": true, + "url": "https://webapp.halton.gov.uk/PublicWebForms/WasteServiceSearchv1.aspx#collections", + "web_driver": "http://selenium:4444", + "wiki_name": "Halton", + "wiki_note": "Pass the house number and postcode. This parser requires a Selenium webdriver.", + "LAD24CD": "E06000006" + }, + "HarboroughDistrictCouncil": { + "uprn": "100030489072", + "url": "https://www.harborough.gov.uk", + "wiki_command_url_override": "https://www.harborough.gov.uk", + "wiki_name": "Harborough", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000131" + }, + "HaringeyCouncil": { + "skip_get_url": true, + "uprn": "100021203052", + "url": "https://wastecollections.haringey.gov.uk/property", + "wiki_name": "Haringey", + "wiki_note": "Pass the UPRN, which can be found at `https://wastecollections.haringey.gov.uk/property/{uprn}`.", + "LAD24CD": "E09000014" + }, + "HarrogateBoroughCouncil": { + "LAD24CD": "E07000165", + "skip_get_url": true, + "uprn": "100050414307", + "url": "https://secure.harrogate.gov.uk/inmyarea", + "wiki_name": "Harrogate", + "wiki_note": "Pass the UPRN, which can be found at [this site](https://secure.harrogate.gov.uk/inmyarea). URL doesn't need to be passed." + }, + "HartDistrictCouncil": { + "skip_get_url": true, + "uprn": "100062349291", + "url": "https://www.hart.gov.uk/", + "wiki_name": "Hart", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000089" + }, + "HartlepoolBoroughCouncil": { + "uprn": "100110019551", + "url": "https://www.hartlepool.gov.uk", + "wiki_name": "Hartlepool", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "E06000001" + }, + "HastingsBoroughCouncil": { + "uprn": "100060038877", + "url": "https://www.hastings.gov.uk", + "wiki_command_url_override": "https://www.hastings.gov.uk", + "wiki_name": "Hastings", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000062" + }, + "HerefordshireCouncil": { + "uprn": "200002618844", + "url": "https://www.herefordshire.gov.uk/rubbish-recycling/check-bin-collection-day", + "skip_get_url": true, + "wiki_name": "Herefordshire", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E06000019" + }, + "HertsmereBoroughCouncil": { + "house_number": "1", + "postcode": "WD7 9HZ", + "skip_get_url": true, + "url": "https://www.hertsmere.gov.uk", + "web_driver": "http://selenium:4444", + "wiki_name": "Hertsmere", + "wiki_note": "Provide your house number in the `house_number` parameter and postcode in the `postcode` parameter.", + "LAD24CD": "E07000098" + }, + "HighPeakCouncil": { + "house_number": "9 Ellison Street, Glossop", + "postcode": "SK13 8BX", + "skip_get_url": true, + "url": "https://www.highpeak.gov.uk/findyourbinday", + "web_driver": "http://selenium:4444", + "wiki_name": "High Peak", + "wiki_note": "Pass the name of the street with the house number parameter, wrapped in double quotes. This parser requires a Selenium webdriver.", + "LAD24CD": "E07000037" + }, + "HighlandCouncil": { + "uprn": "130072429", + "url": "https://www.highland.gov.uk", + "wiki_command_url_override": "https://www.highland.gov.uk", + "wiki_name": "Highland", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "S12000017" + }, + "Hillingdon": { + "house_number": "1, Milverton Drive, Ickenham, UB10 8PP, Ickenham, Hillingdon", + "postcode": "UB10 8PP", + "skip_get_url": true, + "url": "https://www.hillingdon.gov.uk/collection-day", + "web_driver": "http://selenium:4444", + "wiki_name": "Hillingdon", + "wiki_note": "Pass the postcode and the full address as it appears in the address pulldown menu.", + "LAD24CD": "E09000017" + }, + "HinckleyandBosworthBoroughCouncil": { + "uprn": "100030533512", + "url": "https://www.hinckley-bosworth.gov.uk", + "wiki_name": "Hinckley and Bosworth", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000132" + }, + "HorshamDistrictCouncil": { + "postcode": "RH12 1AA", + "LAD24CD": "E07000227", + "skip_get_url": true, + "uprn": "010013792717", + "url": "https://www.horsham.gov.uk/waste-recycling-and-bins/household-bin-collections/check-your-bin-collection-day", + "web_driver": "http://selenium:4444", + "wiki_name": "Horsham", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search). This parser requires a Selenium webdriver." + }, + "HullCityCouncil": { + "LAD24CD": "E06000010", + "skip_get_url": true, + "uprn": "21033995", + "url": "https://www.hull.gov.uk/bins-and-recycling/bin-collections/bin-collection-day-checker", + "wiki_name": "Kingston upon Hull", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search)." + }, + "HuntingdonDistrictCouncil": { + "uprn": "10012048679", + "LAD24CD": "E07000011", + "url": "http://www.huntingdonshire.gov.uk/refuse-calendar/", + "skip_get_url": true, + "wiki_name": "Huntingdonshire", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search)." + }, + "HyndburnBoroughCouncil": { + "postcode": "BB1 4DJ", + "LAD24CD": "E07000120", + "uprn": "100010448773", + "url": "https://iapp.itouchvision.com/iappcollectionday/collection-day/?uuid=FEBA68993831481FD81B2E605364D00A8DC017A4", + "skip_get_url": true, + "web_driver": "http://selenium:4444", + "wiki_name": "Hyndburn", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search). This parser requires a Selenium webdriver." + }, + "IpswichBoroughCouncil": { + "house_number": "Siloam Place", + "url": "https://app.ipswich.gov.uk/bin-collection/", + "wiki_name": "Ipswich", + "wiki_note": "Provide only the street name (no house number) as the PAON", + "LAD24CD": "E07000202" + }, + "IslingtonCouncil": { + "uprn": "5300094897", + "url": "https://www.islington.gov.uk/your-area?Postcode=unused&Uprn=5300094897", + "wiki_command_url_override": "https://www.islington.gov.uk/your-area?Postcode=unused&Uprn=XXXXXXXX", + "wiki_name": "Islington", + "wiki_note": "Replace XXXXXXXX with your UPRN.", + "LAD24CD": "E09000019" + }, + "KingsLynnandWestNorfolkBC": { + "uprn": "10023636886", + "url": "https://www.west-norfolk.gov.uk/", + "wiki_name": "Kings Lynn and West Norfolk", + "wiki_note": "Provide your UPRN. Find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000146" + }, + "KingstonUponThamesCouncil": { + "url": "https://waste-services.kingston.gov.uk/waste/2701097", + "web_driver": "http://selenium:4444", + "wiki_command_url_override": "https://waste-services.kingston.gov.uk/waste/XXXXXXX", + "wiki_name": "Kingston upon Thames", + "wiki_note": "Follow the instructions [here](https://waste-services.kingston.gov.uk/waste) until the \"Your bin days\" page, then copy the URL and replace the URL in the command.", + "LAD24CD": "E09000021" + }, + "KirkleesCouncil": { + "skip_get_url": true, + "uprn": "83002937", + "url": "https://www.kirklees.gov.uk/beta/your-property-bins-recycling/your-bins", + "wiki_name": "Kirklees", + "wiki_note": "Provide your UPRN. Find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E08000034" + }, + "KnowsleyMBCouncil": { + "house_number": "2 ALTMOOR ROAD HUYTON L36 3UY", + "postcode": "L36 3UY", + "skip_get_url": true, + "url": "https://knowsleytransaction.mendixcloud.com/link/youarebeingredirected?target=bincollectioninformation", + "web_driver": "http://selenium:4444", + "wiki_name": "Knowsley", + "wiki_note": "Pass the postcode in the postcode parameter, wrapped in double quotes and with a space.", + "LAD24CD": "E08000011" + }, + "LancasterCityCouncil": { + "house_number": "1", + "postcode": "LA1 1RS", + "skip_get_url": true, + "url": "https://lcc-wrp.whitespacews.com", + "wiki_name": "Lancaster", + "wiki_note": "Pass the house number and postcode in their respective parameters.", + "LAD24CD": "E07000121" + }, + "LeedsCityCouncil": { + "house_number": "1", + "postcode": "LS6 2SE", + "skip_get_url": true, + "uprn": "72506983", + "url": "https://www.leeds.gov.uk/residents/bins-and-recycling/check-your-bin-day", + "web_driver": "http://selenium:4444", + "wiki_name": "Leeds", + "wiki_note": "Pass the house number, postcode, and UPRN. This parser requires a Selenium webdriver.", + "LAD24CD": "E08000035" + }, + "LeicesterCityCouncil": { + "uprn": "2465027976", + "url": "https://biffaleicester.co.uk", + "wiki_name": "Leicester", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E06000016" + }, + "LewesDistrictCouncil": { + "uprn": "100061930155", + "url": "https://www.lewes-eastbourne.gov.uk/article/1158/When-is-my-bin-collection-day", + "skip_get_url": true, + "wiki_name": "Lewes", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000063" + }, + "LichfieldDistrictCouncil": { + "uprn": "100031694085", + "url": "https://www.lichfielddc.gov.uk", + "wiki_command_url_override": "https://www.lichfielddc.gov.uk", + "wiki_name": "Lichfield", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000194" + }, + "LincolnCouncil": { + "postcode": "LN5 7SH", + "uprn": "000235024846", + "url": "https://lincoln.gov.uk", + "wiki_command_url_override": "https://lincoln.gov.uk", + "wiki_name": "City of Lincoln", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000138" + }, + "LisburnCastlereaghCityCouncil": { + "house_number": "97", + "postcode": "BT28 1JN", + "skip_get_url": true, + "url": "https://lisburn.isl-fusion.com", + "wiki_name": "Lisburn and Castlereagh", + "wiki_note": "Pass the house number and postcode in their respective parameters.", + "LAD24CD": "N09000007" + }, + "LiverpoolCityCouncil": { + "uprn": "38164600", + "skip_get_url": true, + "url": "https://liverpool.gov.uk/bins-and-recycling/bin-collections/", + "wiki_name": "Liverpool", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E08000012" + }, + "LondonBoroughEaling": { + "skip_get_url": true, + "uprn": "12081498", + "url": "https://www.ealing.gov.uk/site/custom_scripts/WasteCollectionWS/home/FindCollection", + "wiki_name": "Ealing (London Borough)", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E09000009" + }, + "LondonBoroughHarrow": { + "uprn": "100021298754", + "url": "https://www.harrow.gov.uk", + "wiki_command_url_override": "https://www.harrow.gov.uk", + "wiki_name": "Harrow", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E09000015" + }, + "LondonBoroughHavering": { + "uprn": "100021380730", + "url": "https://www.havering.gov.uk", + "wiki_name": "Havering", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E09000016" + }, + "LondonBoroughHounslow": { + "skip_get_url": true, + "uprn": "100021577765", + "url": "https://www.hounslow.gov.uk/homepage/86/recycling_and_waste_collection_day_finder", + "wiki_name": "Hounslow", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E09000018" + }, + "LondonBoroughLambeth": { + "skip_get_url": true, + "uprn": "100021881738", + "url": "https://wasteservice.lambeth.gov.uk/WhitespaceComms/GetServicesByUprn", + "wiki_name": "Lambeth", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E09000022" + }, + "LondonBoroughLewisham": { + "postcode": "SE12 9QF", + "skip_get_url": true, + "uprn": "100021954849", + "url": "https://www.lewisham.gov.uk", + "web_driver": "http://selenium:4444", + "wiki_name": "Lewisham", + "wiki_note": "Pass the UPRN and postcode. To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E09000023" + }, + "LondonBoroughOfRichmondUponThames": { + "house_number": "March Road", + "skip_get_url": true, + "url": "https://www.richmond.gov.uk/services/waste_and_recycling/collection_days/", + "web_driver": "http://selenium:4444", + "wiki_name": "Richmond upon Thames", + "wiki_note": "Pass the name of the street ONLY in the house number parameter, unfortunately post code's are not allowed. ", + "LAD24CD": "E09000027" + }, + "LondonBoroughRedbridge": { + "postcode": "IG2 6LQ", + "uprn": "10023770353", + "url": "https://my.redbridge.gov.uk/RecycleRefuse", + "web_driver": "http://selenium:4444", + "wiki_name": "Redbridge", + "wiki_note": "Follow the instructions [here](https://my.redbridge.gov.uk/RecycleRefuse) until you get the page listing your address, then copy the entire address text and use that in the house number field.", + "LAD24CD": "E09000026" + }, + "LondonBoroughSutton": { + "uprn": "4473006", + "url": "https://waste-services.sutton.gov.uk/waste", + "wiki_command_url_override": "https://waste-services.sutton.gov.uk/waste", + "wiki_name": "Sutton", + "wiki_note": "You will need to find your unique property reference by going to (https://waste-services.sutton.gov.uk/waste), entering your details and then using the 7 digit reference in the URL as your UPRN", + "LAD24CD": "E09000029" + }, + "LutonBoroughCouncil": { + "uprn": "100080155778", + "url": "https://myforms.luton.gov.uk", + "wiki_command_url_override": "https://myforms.luton.gov.uk", + "wiki_name": "Luton", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E06000032" + }, + "MaidstoneBoroughCouncil": { + "skip_get_url": true, + "house_number": "71", + "postcode": "ME16 8BT", + "url": "https://my.maidstone.gov.uk/service/Find-your-bin-day", + "wiki_name": "Maidstone", + "wiki_note": "Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver.", + "LAD24CD": "E07000110" + }, + "MaldonDistrictCouncil": { + "skip_get_url": true, + "uprn": "100090557253", + "url": "https://maldon.suez.co.uk/maldon/ServiceSummary", + "wiki_name": "Maldon", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000074" + }, + "MalvernHillsDC": { + "skip_get_url": true, + "uprn": "100121348457", + "url": "https://swict.malvernhills.gov.uk/mhdcroundlookup/HandleSearchScreen", + "wiki_name": "Malvern Hills", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000235" + }, + "ManchesterCityCouncil": { + "skip_get_url": true, + "uprn": "77127089", + "url": "https://www.manchester.gov.uk/bincollections", + "wiki_name": "Manchester", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E08000003" + }, + "MansfieldDistrictCouncil": { + "skip_get_url": true, + "uprn": "100031396580", + "url": "https://www.mansfield.gov.uk/xfp/form/1327", + "wiki_name": "Mansfield", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000174" + }, + "MedwayCouncil": { + "skip_get_url": true, + "uprn": "200000907059", + "url": "https://www.medway.gov.uk/homepage/45/check_your_waste_collection_day", + "wiki_name": "Medway", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E06000035" + }, + "MeltonBoroughCouncil": { + "uprn": "100030540956", + "url": "https://my.melton.gov.uk/collections", + "wiki_name": "Melton", + "wiki_note": "To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000133" + }, + "MertonCouncil": { + "url": "https://myneighbourhood.merton.gov.uk/wasteservices/WasteServices.aspx?ID=25936129", + "wiki_command_url_override": "https://myneighbourhood.merton.gov.uk/Wasteservices/WasteServices.aspx?ID=XXXXXXXX", + "wiki_name": "Merton", + "wiki_note": "Follow the instructions [here](https://myneighbourhood.merton.gov.uk/Wasteservices/WasteServicesSearch.aspx) until you get the \"Your recycling and rubbish collection days\" page, then copy the URL and replace the URL in the command.", + "LAD24CD": "E09000024" + }, + "MidAndEastAntrimBoroughCouncil": { + "postcode": "100 Galgorm Road", + "skip_get_url": true, + "url": "https://www.midandeastantrim.gov.uk/resident/waste-recycling/collection-dates/", + "web_driver": "http://selenium:4444", + "wiki_name": "Mid and East Antrim", + "wiki_note": "Pass the house name/number plus the name of the street with the postcode parameter, wrapped in double quotes. Check the address on the website first. This version will only pick the first SHOW button returned by the search or if it is fully unique.", + "LAD24CD": "N09000008" + }, + "MidDevonCouncil": { + "uprn": "200003997770", + "url": "https://www.middevon.gov.uk", + "wiki_command_url_override": "https://www.middevon.gov.uk", + "wiki_name": "Mid Devon", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000042" + }, + "MidSuffolkDistrictCouncil": { + "house_number": "Monday", + "postcode": "Week 2", + "skip_get_url": true, + "uprn": "Monday", + "url": "https://www.midsuffolk.gov.uk", + "wiki_name": "Mid Suffolk", + "wiki_note": "Use the House Number field to pass the DAY of the week for your NORMAL collections. [Monday/Tuesday/Wednesday/Thursday/Friday]. [OPTIONAL] Use the 'postcode' field to pass the WEEK for your garden collection. [Week 1/Week 2]. [OPTIONAL] Use the 'uprn' field to pass the DAY for your garden collection. [Monday/Tuesday/Wednesday/Thursday/Friday]", + "LAD24CD": "E07000203" + }, + "MidSussexDistrictCouncil": { + "house_number": "OAKLANDS, OAKLANDS ROAD RH16 1SS", + "postcode": "RH16 1SS", + "skip_get_url": true, + "url": "https://www.midsussex.gov.uk/waste-recycling/bin-collection/", + "web_driver": "http://selenium:4444", + "wiki_name": "Mid Sussex", + "wiki_note": "Pass the name of the street with the house number parameter, wrapped in double quotes. This parser requires a Selenium webdriver.", + "LAD24CD": "E07000228" + }, + "MiddlesbroughCouncil": { + "house_number": "12 Constantine Court Park Road North, Middlesbrough", + "skip_get_url": true, + "url": "https://www.middlesbrough.gov.uk/recycling-and-rubbish/bin-collection-dates/", + "web_driver": "http://selenium:4444", + "wiki_name": "Middlesbrough", + "wiki_note": "Pass the entire address without postcode as it appears when you type it on the website. This parser requires a Selenium webdriver.", + "LAD24CD": "E06000002" + }, + "MidlothianCouncil": { + "house_number": "52", + "postcode": "EH19 2EB", + "skip_get_url": true, + "url": "https://www.midlothian.gov.uk/info/1054/bins_and_recycling/343/bin_collection_days", + "wiki_name": "Midlothian", + "wiki_note": "Pass the house name/number wrapped in double quotes along with the postcode parameter.", + "LAD24CD": "S12000019" + }, + "MidUlsterDistrictCouncil": { + "house_number": "20 HILLHEAD, STEWARTSTOWN, BT71 5HY", + "postcode": "BT71 5HY", + "skip_get_url": true, + "url": "https://www.midulstercouncil.org", + "web_driver": "http://selenium:4444", + "wiki_name": "Mid Ulster", + "wiki_note": "Pass the full address of the house postcode as displayed on the site. This parser requires a Selenium webdriver.", + "LAD24CD": "N09000009" + }, + "MiltonKeynesCityCouncil": { + "uprn": "25109551", + "url": "https://mycouncil.milton-keynes.gov.uk/en/service/Waste_Collection_Round_Checker", + "wiki_name": "Milton Keynes", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E06000042" + }, + "MoleValleyDistrictCouncil": { + "postcode": "RH4 1SJ", + "skip_get_url": true, + "uprn": "200000171235", + "url": "https://myproperty.molevalley.gov.uk/molevalley/", + "wiki_name": "Mole Valley", + "wiki_note": "UPRN can only be parsed with a valid postcode.", + "LAD24CD": "E07000210" + }, + "MonmouthshireCountyCouncil": { + "uprn": "100100266220", + "url": "https://maps.monmouthshire.gov.uk", + "wiki_name": "Monmouthshire", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "W06000021" + }, + "MorayCouncil": { + "uprn": "45438", + "url": "https://bindayfinder.moray.gov.uk/", + "wiki_name": "Moray", + "wiki_note": "Find your property ID by going to (https://bindayfinder.moray.gov.uk), search for your property and extracting the ID from the URL. i.e. (https://bindayfinder.moray.gov.uk/disp_bins.php?id=00028841)", + "LAD24CD": "S12000020" + }, + "NeathPortTalbotCouncil": { + "house_number": "2", + "postcode": "SA13 3BA", + "skip_get_url": true, + "url": "https://www.npt.gov.uk", + "web_driver": "http://selenium:4444", + "wiki_name": "Neath Port Talbot", + "wiki_note": "Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver.", + "LAD24CD": "W06000012" + }, + "NewForestCouncil": { + "postcode": "SO41 0GJ", + "skip_get_url": true, + "uprn": "100060482345", + "url": "https://forms.newforest.gov.uk/id/FIND_MY_COLLECTION", + "web_driver": "http://selenium:4444", + "wiki_name": "New Forest", + "wiki_note": "Pass the postcode and UPRN. This parser requires a Selenium webdriver.", + "LAD24CD": "E07000091" + }, + "NewarkAndSherwoodDC": { + "uprn": "200004258529", + "url": "https://app.newark-sherwooddc.gov.uk/bincollection/", + "skip_get_url": true, + "wiki_name": "Newark and Sherwood", + "wiki_note": "Replace XXXXXXXX with your UPRN.", + "LAD24CD": "E07000175" + }, + "NewcastleCityCouncil": { + "LAD24CD": "E08000021", + "url": "https://community.newcastle.gov.uk/my-neighbourhood/ajax/getBinsNew.php?uprn=004510730634", + "wiki_command_url_override": "https://community.newcastle.gov.uk/my-neighbourhood/ajax/getBinsNew.php?uprn=XXXXXXXX", + "wiki_name": "Newcastle upon Tyne", + "wiki_note": "Replace XXXXXXXX with your UPRN. UPRNs need to be 12 digits long so please pad the left hand side with 0s if your UPRN is not long enough" + }, + "NewcastleUnderLymeCouncil": { + "uprn": "100031725433", + "url": "https://www.newcastle-staffs.gov.uk", + "wiki_name": "Newcastle-under-Lyme", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "E07000195" + }, + "NewhamCouncil": { + "uprn": "46077811", + "url": "https://bincollection.newham.gov.uk/", + "skip_get_url": true, + "wiki_name": "Newham", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "E09000025" + }, + "NewportCityCouncil": { + "postcode": "NP20 4HE", + "skip_get_url": true, + "uprn": "100100688837", + "url": "https://www.newport.gov.uk/", + "wiki_name": "Newport", + "wiki_note": "Pass the postcode and UPRN. You can find the UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "W06000022" + }, + "NorthAyrshireCouncil": { + "uprn": "126045552", + "url": "https://www.north-ayrshire.gov.uk/", + "wiki_command_url_override": "https://www.north-ayrshire.gov.uk/", + "wiki_name": "North Ayrshire", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "S12000021" + }, + "NorthDevonCountyCouncil": { + "house_number": "1", + "postcode": "EX31 2LE", + "skip_get_url": true, + "uprn": "100040249471", + "url": "https://my.northdevon.gov.uk/service/WasteRecyclingCollectionCalendar", + "web_driver": "http://selenium:4444", + "wiki_name": "North Devon", + "wiki_note": "Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver.", + "LAD24CD": "E07000043" + }, + "NorthEastDerbyshireDistrictCouncil": { + "postcode": "S42 5RB", + "skip_get_url": true, + "uprn": "010034492222", + "url": "https://myselfservice.ne-derbyshire.gov.uk/service/Check_your_Bin_Day", + "web_driver": "http://selenium:4444", + "wiki_name": "North East Derbyshire", + "wiki_note": "Pass the postcode and UPRN. This parser requires a Selenium webdriver.", + "LAD24CD": "E07000038" + }, + "NorthEastLincs": { + "uprn": "11062649", + "url": "https://www.nelincs.gov.uk/refuse-collection-schedule/?view=timeline&uprn=11062649", + "wiki_command_url_override": "https://www.nelincs.gov.uk/refuse-collection-schedule/?view=timeline&uprn=XXXXXXXX", + "wiki_name": "North East Lincolnshire", + "wiki_note": "Replace XXXXXXXX with your UPRN.", + "LAD24CD": "E06000012" + }, + "NorthHertfordshireDistrictCouncil": { + "house_number": "22", + "postcode": "SG6 4BJ", + "url": "https://www.north-herts.gov.uk", + "web_driver": "http://selenium:4444", + "wiki_name": "North Hertfordshire", + "wiki_note": "Pass the house number and postcode in their respective parameters.", + "LAD24CD": "E07000099" + }, + "NorthKestevenDistrictCouncil": { + "url": "https://www.n-kesteven.org.uk/bins/display?uprn=100030869513", + "wiki_command_url_override": "https://www.n-kesteven.org.uk/bins/display?uprn=XXXXXXXX", + "wiki_name": "North Kesteven", + "wiki_note": "Replace XXXXXXXX with your UPRN.", + "LAD24CD": "E07000139" + }, + "NorthLanarkshireCouncil": { + "url": "https://www.northlanarkshire.gov.uk/bin-collection-dates/000118016164/48402118", + "wiki_command_url_override": "https://www.northlanarkshire.gov.uk/bin-collection-dates/XXXXXXXXXXX/XXXXXXXXXXX", + "wiki_name": "North Lanarkshire", + "wiki_note": "Follow the instructions [here](https://www.northlanarkshire.gov.uk/bin-collection-dates) until you get the \"Next collections\" page, then copy the URL and replace the URL in the command.", + "LAD24CD": "S12000050" + }, + "NorthLincolnshireCouncil": { + "skip_get_url": true, + "uprn": "100050194170", + "url": "https://www.northlincs.gov.uk/bins-waste-and-recycling/bin-and-box-collection-dates/", + "wiki_name": "North Lincolnshire", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E06000013" + }, + "NorthNorfolkDistrictCouncil": { + "house_number": "1 Morston Mews", + "postcode": "NR25 6BH", + "skip_get_url": true, + "url": "https://www.north-norfolk.gov.uk/", + "web_driver": "http://selenium:4444", + "wiki_name": "North Norfolk", + "wiki_note": "Pass the name of the street with the house number parameter, wrapped in double quotes. This parser requires a Selenium webdriver.", + "LAD24CD": "E07000147" + }, + "NorthNorthamptonshireCouncil": { + "skip_get_url": true, + "uprn": "100031021317", + "url": "https://cms.northnorthants.gov.uk/bin-collection-search/calendarevents/100031021318/2023-10-17/2023-10-01", + "wiki_name": "North Northamptonshire", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E06000061" + }, + "NorthSomersetCouncil": { + "postcode": "BS49 5AA", + "skip_get_url": true, + "uprn": "24051674", + "url": "https://forms.n-somerset.gov.uk/Waste/CollectionSchedule", + "wiki_name": "North Somerset", + "wiki_note": "Pass the postcode and UPRN. You can find the UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E06000024" + }, + "NorthTynesideCouncil": { + "postcode": "NE26 2TG", + "skip_get_url": true, + "uprn": "47097627", + "url": "https://my.northtyneside.gov.uk/category/81/bin-collection-dates", + "wiki_name": "North Tyneside", + "wiki_note": "Pass the postcode and UPRN. You can find the UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E08000022" + }, + "NorthWestLeicestershire": { + "postcode": "DE74 2FZ", + "skip_get_url": true, + "uprn": "100030572613", + "url": "https://www.nwleics.gov.uk/pages/collection_information", + "web_driver": "http://selenium:4444", + "wiki_name": "North West Leicestershire", + "wiki_note": "Pass the postcode and UPRN. This parser requires a Selenium webdriver.", + "LAD24CD": "E07000134" + }, + "NorthYorkshire": { + "skip_get_url": true, + "uprn": "10093091235", + "url": "https://www.northyorks.gov.uk/bin-calendar/lookup", + "wiki_name": "North Yorkshire", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E06000065" + }, + "NorthumberlandCouncil": { + "house_number": "22", + "postcode": "NE46 1UQ", + "skip_get_url": true, + "url": "https://www.northumberland.gov.uk/Waste/Household-waste/Household-bin-collections/Bin-Calendars.aspx", + "web_driver": "http://selenium:4444", + "wiki_name": "Northumberland", + "wiki_note": "Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver.", + "LAD24CD": "E06000057" + }, + "NorwichCityCouncil": { + "uprn": "100090888980", + "url": "https://www.norwich.gov.uk", + "wiki_command_url_override": "https://www.norwich.gov.uk", + "wiki_name": "Norwich", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000148" + }, + "NottinghamCityCouncil": { + "skip_get_url": true, + "uprn": "100031540180", + "url": "https://geoserver.nottinghamcity.gov.uk/bincollections2/api/collection/100031540180", + "wiki_name": "Nottingham", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E06000018" + }, + "NuneatonBedworthBoroughCouncil": { + "house_number": "Newdigate Road", + "skip_get_url": true, + "url": "https://www.nuneatonandbedworth.gov.uk", + "wiki_name": "Nuneaton and Bedworth", + "wiki_note": "Pass the name of the street ONLY in the house number parameter, wrapped in double quotes. Street name must match exactly as it appears on the council's website.", + "LAD24CD": "E07000219" + }, + "OadbyAndWigstonBoroughCouncil": { + "LAD24CD": "E07000135", + "uprn": "10010149102", + "url": "https://my.oadby-wigston.gov.uk", + "wiki_name": "Oadby and Wigston", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN." + }, + "OldhamCouncil": { + "url": "https://portal.oldham.gov.uk/bincollectiondates/details?uprn=422000033556", + "wiki_name": "Oldham", + "wiki_note": "Replace UPRN in URL with your own UPRN.", + "LAD24CD": "E08000004" + }, + "OxfordCityCouncil": { + "postcode": "OX3 7QF", + "uprn": "100120820551", + "url": "https://www.oxford.gov.uk", + "wiki_command_url_override": "https://www.oxford.gov.uk", + "wiki_name": "Oxford", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000178" + }, + "PembrokeshireCountyCouncil": { + "url": "https://nearest.pembrokeshire.gov.uk/property/100100278790", + "wiki_command_url_override": "https://nearest.pembrokeshire.gov.uk/property/XXXXXXXXXX", + "wiki_name": "Pembrokeshire", + "wiki_note": "Replace XXXXXXXX with your UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find it.", + "LAD24CD": "W06000009" + }, + "PeterboroughCityCouncil": { + "house_number": "7 Arundel Road, Peterborough, PE4 6JJ", + "postcode": "PE4 6JJ", + "skip_get_url": true, + "url": "https://report.peterborough.gov.uk/waste", + "web_driver": "http://selenium:4444", + "wiki_name": "Peterborough", + "wiki_note": "Pass the full address as it appears o nthe Peterborough website and postcode in their respective parameters. This parser requires a Selenium webdriver.", + "LAD24CD": "E06000031" + }, + "PerthAndKinrossCouncil": { + "uprn": "124032322", + "url": "https://www.pkc.gov.uk", + "wiki_command_url_override": "https://www.pkc.gov.uk", + "wiki_name": "Perth and Kinross", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "S12000048" + }, + "PlymouthCouncil": { + "uprn": "100040420582", + "url": "https://www.plymouth.gov.uk", + "wiki_command_url_override": "https://www.plymouth.gov.uk", + "wiki_name": "Plymouth", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E06000026" + }, + "PortsmouthCityCouncil": { + "postcode": "PO4 0LE", + "skip_get_url": true, + "uprn": "1775027504", + "url": "https://my.portsmouth.gov.uk/en/AchieveForms/?form_uri=sandbox-publish://AF-Process-26e27e70-f771-47b1-a34d-af276075cede/AF-Stage-cd7cc291-2e59-42cc-8c3f-1f93e132a2c9/definition.json&redirectlink=%2F&cancelRedirectLink=%2F", + "web_driver": "http://selenium:4444", + "wiki_name": "Portsmouth", + "wiki_note": "Pass the postcode and UPRN. This parser requires a Selenium webdriver.", + "LAD24CD": "E06000044" + }, + "PowysCouncil": { + "house_number": "LANE COTTAGE", + "postcode": "HR3 5JS", + "skip_get_url": true, + "url": "https://www.powys.gov.uk", + "web_driver": "http://selenium:4444", + "wiki_name": "Powys", + "LAD24CD": "W06000023" + }, + "PrestonCityCouncil": { + "house_number": "Town Hall", + "postcode": "PR1 2RL", + "skip_get_url": true, + "url": "https://selfservice.preston.gov.uk/service/Forms/FindMyNearest.aspx?Service=bins", + "web_driver": "http://selenium:4444", + "wiki_name": "Preston", + "wiki_note": "Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver.", + "LAD24CD": "E07000123" + }, + "ReadingBoroughCouncil": { + "url": "https://api.reading.gov.uk/api/collections/310056735", + "wiki_command_url_override": "https://api.reading.gov.uk/api/collections/XXXXXXXX", + "wiki_name": "Reading", + "wiki_note": "Replace XXXXXXXX with your property's UPRN.", + "LAD24CD": "E06000038" + }, + "RedcarandClevelandCouncil": { + "house_number": "11", + "postcode": "TS10 2RE", + "skip_get_url": true, + "url": "https://www.redcar-cleveland.gov.uk", + "wiki_name": "Redcar and Cleveland", + "wiki_note": "Pass the house name/number and postcode in their respective parameters", + "LAD24CD": "E06000003" + }, + "RedditchBoroughCouncil": { + "uprn": "10094557691", + "url": "https://redditchbc.gov.uk", + "wiki_command_url_override": "https://redditchbc.gov.uk", + "wiki_name": "Redditch", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000236" + }, + "ReigateAndBansteadBoroughCouncil": { + "skip_get_url": true, + "uprn": "68134867", + "url": "https://www.reigate-banstead.gov.uk/", + "web_driver": "http://selenium:4444", + "wiki_name": "Reigate and Banstead", + "wiki_note": "To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search). This parser requires a Selenium webdriver.", + "LAD24CD": "E07000211" + }, + "RenfrewshireCouncil": { + "house_number": "1, STATIONHOUSE DRIVE, JOHNSTONE, RENFREWSHIRE, PA6 7FJ", + "paon": "1, STATIONHOUSE DRIVE, JOHNSTONE, RENFREWSHIRE, PA6 7FJ", + "postcode": "PA6 7FJ", + "skip_get_url": true, + "url": "https://www.renfrewshire.gov.uk/bin-day", + "web_driver": "http://selenium:4444", + "wiki_name": "Renfrewshire", + "wiki_note": "Pass the full address as it appears on the website. This parser requires a Selenium webdriver.", + "LAD24CD": "S12000038" + }, + "RhonddaCynonTaffCouncil": { + "skip_get_url": true, + "uprn": "100100778320", + "url": "https://www.rctcbc.gov.uk/EN/Resident/RecyclingandWaste/RecyclingandWasteCollectionDays.aspx", + "wiki_name": "Rhondda Cynon Taff", + "wiki_note": "To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "W06000016" + }, + "RochdaleCouncil": { + "postcode": "OL11 5BE", + "skip_get_url": true, + "uprn": "23049922", + "url": "https://webforms.rochdale.gov.uk/BinCalendar", + "wiki_name": "Rochdale", + "wiki_note": "Provide your UPRN and postcode. You can find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E08000005" + }, + "RochfordCouncil": { + "url": "https://www.rochford.gov.uk/online-bin-collections-calendar", + "wiki_name": "Rochford", + "wiki_note": "No extra parameters are required. Dates presented should be read as 'week commencing'.", + "LAD24CD": "E07000075" + }, + "RotherDistrictCouncil": { + "uprn": "100061937338", + "url": "https://www.rother.gov.uk", + "wiki_name": "Rother", + "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "E07000064" + }, + "RotherhamCouncil": { + "uprn": "100050866000", + "url": "https://www.rotherham.gov.uk/bin-collections?address=100050866000&submit=Submit", + "wiki_command_url_override": "https://www.rotherham.gov.uk/bin-collections?address=XXXXXXXXX&submit=Submit", + "wiki_name": "Rotherham", + "wiki_note": "Replace `XXXXXXXXX` with your UPRN in the URL. You can find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E08000018" + }, + "RoyalBoroughofGreenwich": { + "house_number": "57", + "postcode": "BR7 6DN", + "skip_get_url": true, + "url": "https://www.royalgreenwich.gov.uk", + "wiki_name": "Greenwich", + "wiki_note": "Provide your house number in the `house_number` parameter and your postcode in the `postcode` parameter.", + "LAD24CD": "E09000011" + }, + "RugbyBoroughCouncil": { + "postcode": "CV22 6LA", + "skip_get_url": true, + "uprn": "100070182634", + "url": "https://www.rugby.gov.uk/check-your-next-bin-day", + "web_driver": "http://selenium:4444", + "wiki_name": "Rugby", + "wiki_note": "Provide your UPRN and postcode. You can find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000220" + }, + "RunnymedeBoroughCouncil": { + "skip_get_url": true, + "uprn": "100061483636", + "url": "https://www.runnymede.gov.uk/", + "wiki_name": "Runnymede", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000212" + }, + "RushcliffeBoroughCouncil": { + "postcode": "NG13 8TZ", + "skip_get_url": true, + "uprn": "3040040994", + "url": "https://www.rushcliffe.gov.uk/", + "web_driver": "http://selenium:4444", + "wiki_name": "Rushcliffe", + "wiki_note": "Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "E07000176" + }, + "RushmoorCouncil": { + "url": "https://www.rushmoor.gov.uk/Umbraco/Api/BinLookUpWorkAround/Get?selectedAddress=100060545034", + "wiki_command_url_override": "https://www.rushmoor.gov.uk/Umbraco/Api/BinLookUpWorkAround/Get?selectedAddress=XXXXXXXXXX", + "wiki_name": "Rushmoor", + "wiki_note": "Replace `XXXXXXXXXX` with your UPRN, which you can find using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000092" + }, + "SalfordCityCouncil": { + "skip_get_url": true, + "uprn": "100011416709", + "url": "https://www.salford.gov.uk/bins-and-recycling/bin-collection-days/your-bin-collections", + "wiki_name": "Salford", + "wiki_note": "Provide your UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E08000006" + }, + "SandwellBoroughCouncil": { + "skip_get_url": true, + "uprn": "32101971", + "url": "https://www.sandwell.gov.uk", + "wiki_name": "Sandwell", + "wiki_note": "Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E08000028" + }, + "SeftonCouncil": { + "house_number": "1", + "postcode": "L20 6GG", + "url": "https://www.sefton.gov.uk", + "wiki_name": "Sefton", + "wiki_note": "Pass the postcode and house number in their respective arguments, both wrapped in quotes.", + "LAD24CD": "E08000014" + }, + "SevenoaksDistrictCouncil": { + "house_number": "60 Hever Road", + "postcode": "TN15 6EB", + "skip_get_url": true, + "url": "https://sevenoaks-dc-host01.oncreate.app/w/webpage/waste-collection-day", + "web_driver": "http://selenium:4444", + "wiki_name": "Sevenoaks", + "wiki_note": "Pass the house name/number in the `house_number` parameter, wrapped in double quotes, and the postcode in the `postcode` parameter.", + "LAD24CD": "E07000111" + }, + "SheffieldCityCouncil": { + "url": "https://wasteservices.sheffield.gov.uk/property/100050931898", + "wiki_command_url_override": "https://wasteservices.sheffield.gov.uk/property/XXXXXXXXXXX", + "wiki_name": "Sheffield", + "wiki_note": "Follow the instructions [here](https://wasteservices.sheffield.gov.uk/) until you get the 'Your bin collection dates and services' page, then copy the URL and replace the URL in the command.", + "LAD24CD": "E08000019" + }, + "ShropshireCouncil": { + "url": "https://bins.shropshire.gov.uk/property/100070034731", + "wiki_command_url_override": "https://bins.shropshire.gov.uk/property/XXXXXXXXXXX", + "wiki_name": "Shropshire", + "wiki_note": "Follow the instructions [here](https://bins.shropshire.gov.uk/) until you get the page showing your bin collection dates, then copy the URL and replace the URL in the command.", + "LAD24CD": "E06000051" + }, + "SloughBoroughCouncil": { + "postcode": "SL2 2EW", + "skip_get_url": true, + "url": "https://www.slough.gov.uk/bin-collections", + "web_driver": "http://selenium:4444", + "wiki_name": "Slough", + "wiki_note": "Pass the UPRN and postcode in their respective parameters. This parser requires a Selenium webdriver.", + "LAD24CD": "E06000039" + }, + "SolihullCouncil": { + "url": "https://digital.solihull.gov.uk/BinCollectionCalendar/Calendar.aspx?UPRN=100071005444", + "wiki_command_url_override": "https://digital.solihull.gov.uk/BinCollectionCalendar/Calendar.aspx?UPRN=XXXXXXXX", + "wiki_name": "Solihull", + "wiki_note": "Replace `XXXXXXXX` with your UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E08000029" + }, + "SomersetCouncil": { + "postcode": "TA6 4AA", + "skip_get_url": true, + "uprn": "10090857775", + "url": "https://www.somerset.gov.uk/", + "wiki_name": "Somerset", + "wiki_note": "Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E06000066" + }, + "SouthAyrshireCouncil": { + "postcode": "KA19 7BN", + "skip_get_url": true, + "uprn": "141003134", + "url": "https://www.south-ayrshire.gov.uk/", + "wiki_name": "South Ayrshire", + "wiki_note": "Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "S12000028" + }, + "SouthCambridgeshireCouncil": { + "house_number": "53", + "postcode": "CB23 6GZ", + "skip_get_url": true, + "url": "https://www.scambs.gov.uk/recycling-and-bins/find-your-household-bin-collection-day/", + "wiki_name": "South Cambridgeshire", + "wiki_note": "Provide your house number in the `house_number` parameter and postcode in the `postcode` parameter.", + "LAD24CD": "E07000012" + }, + "SouthDerbyshireDistrictCouncil": { + "uprn": "10000820668", + "url": "https://maps.southderbyshire.gov.uk/iShareLIVE.web//getdata.aspx?RequestType=LocalInfo&ms=mapsources/MyHouse&format=JSONP&group=Recycling%20Bins%20and%20Waste|Next%20Bin%20Collections&uid=", + "wiki_command_url_override": "https://maps.southderbyshire.gov.uk/iShareLIVE.web//getdata.aspx?RequestType=LocalInfo&ms=mapsources/MyHouse&format=JSONP&group=Recycling%20Bins%20and%20Waste|Next%20Bin%20Collections&uid=XXXXXXXX", + "wiki_name": "South Derbyshire", + "wiki_note": "Replace `XXXXXXXX` with your UPRN. You can find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000039" + }, + "SouthGloucestershireCouncil": { + "skip_get_url": true, + "uprn": "566419", + "url": "https://beta.southglos.gov.uk/waste-and-recycling-collection-date", + "wiki_name": "South Gloucestershire", + "wiki_note": "Provide your UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E06000025" + }, + "SouthHamsDistrictCouncil": { + "uprn": "10004742851", + "url": "https://www.southhams.gov.uk", + "wiki_name": "South Hams", + "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "E07000044" + }, + "SouthHollandDistrictCouncil": { + "house_number": "1", + "postcode": "PE6 0HE", + "skip_get_url": true, + "uprn": "100030872493", + "url": "https://www.sholland.gov.uk/mycollections", + "web_driver": "http://selenium:4444", + "wiki_name": "South Holland", + "wiki_note": "Pass the UPRN and postcode in their respective parameters. This parser requires a Selenium webdriver.", + "LAD24CD": "E07000140" + }, + "SouthKestevenDistrictCouncil": { + "house_number": "2 Althorpe Close, Market Deeping, PE6 8BL", + "postcode": "PE68BL", + "skip_get_url": true, + "url": "https://pre.southkesteven.gov.uk/BinSearch.aspx", + "web_driver": "http://selenium:4444", + "wiki_name": "South Kesteven", + "wiki_note": "Provide your full address in the `house_number` parameter and your postcode in the `postcode` parameter.", + "LAD24CD": "E07000141" + }, + "SouthLanarkshireCouncil": { + "url": "https://www.southlanarkshire.gov.uk/directory_record/579973/abbeyhill_crescent_lesmahagow", + "wiki_command_url_override": "https://www.southlanarkshire.gov.uk/directory_record/XXXXX/XXXXX", + "wiki_name": "South Lanarkshire", + "wiki_note": "Follow the instructions [here](https://www.southlanarkshire.gov.uk/info/200156/bins_and_recycling/1670/bin_collections_and_calendar) until you get the page that shows the weekly collections for your street, then copy the URL and replace the URL in the command.", + "LAD24CD": "S12000029" + }, + "SouthNorfolkCouncil": { + "skip_get_url": true, + "uprn": "2630102526", + "url": "https://www.southnorfolkandbroadland.gov.uk/rubbish-recycling/south-norfolk-bin-collection-day-finder", + "wiki_name": "South Norfolk", + "wiki_note": "Provide your UPRN. Find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000149" + }, + "SouthOxfordshireCouncil": { + "skip_get_url": true, + "uprn": "10033002851", + "url": "https://www.southoxon.gov.uk/south-oxfordshire-district-council/recycling-rubbish-and-waste/when-is-your-collection-day/", + "wiki_name": "South Oxfordshire", + "wiki_note": "Provide your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to locate it.", + "LAD24CD": "E07000179" + }, + "SouthRibbleCouncil": { + "uprn": "10013243496", + "postcode": "PR266QW", + "url": "https://forms.chorleysouthribble.gov.uk/xfp/form/70", + "wiki_command_url_override": "https://forms.chorleysouthribble.gov.uk/xfp/form/70", + "wiki_name": "South Ribble", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "E07000126" + }, + "SouthStaffordshireDistrictCouncil": { + "uprn": "200004523954", + "url": "https://www.sstaffs.gov.uk/where-i-live?uprn=200004523954", + "wiki_name": "South Staffordshire", + "wiki_note": "The URL needs to be `https://www.sstaffs.gov.uk/where-i-live?uprn=`. Replace `` with your UPRN.", + "LAD24CD": "E07000196" + }, + "SouthTynesideCouncil": { + "house_number": "1", + "postcode": "NE33 3JW", + "skip_get_url": true, + "url": "https://www.southtyneside.gov.uk/article/33352/Bin-collection-dates", + "wiki_name": "South Tyneside", + "wiki_note": "Provide your house number in the `house_number` parameter and postcode in the `postcode` parameter.", + "LAD24CD": "E08000023" + }, + "SouthamptonCityCouncil": { + "skip_get_url": true, + "uprn": "100060731893", + "url": "https://www.southampton.gov.uk", + "wiki_name": "Southampton", + "wiki_note": "Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E06000045" + }, + "SouthwarkCouncil": { + "uprn": "200003469271", + "url": "https://services.southwark.gov.uk/bins/lookup/", + "wiki_command_url_override": "https://services.southwark.gov.uk/bins/lookup/XXXXXXXX", + "wiki_name": "Southwark", + "wiki_note": "Replace `XXXXXXXX` with your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "E09000028" + }, + "SpelthorneBoroughCouncil": { + "house_number": "1", + "postcode": "TW18 2PR", + "skip_get_url": true, + "url": "https://www.spelthorne.gov.uk", + "wiki_name": "Spelthorne", + "LAD24CD": "E07000213" + }, + "StAlbansCityAndDistrictCouncil": { + "skip_get_url": true, + "uprn": "100081153583", + "url": "https://gis.stalbans.gov.uk/NoticeBoard9/VeoliaProxy.NoticeBoard.asmx/GetServicesByUprnAndNoticeBoard", + "wiki_name": "St Albans", + "wiki_note": "Provide your UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000240" + }, + "StHelensBC": { + "house_number": "15", + "postcode": "L34 2GA", + "skip_get_url": true, + "url": "https://www.sthelens.gov.uk/", + "web_driver": "http://selenium:4444", + "wiki_name": "St. Helens", + "wiki_note": "Pass the house name/number in the house number parameter, wrapped in double quotes", + "LAD24CD": "E08000013" + }, + "StaffordBoroughCouncil": { + "uprn": "100032203010", + "url": "https://www.staffordbc.gov.uk/address/100032203010", + "wiki_name": "Stafford", + "wiki_note": "The URL needs to be `https://www.staffordbc.gov.uk/address/`. Replace `` with your UPRN.", + "LAD24CD": "E07000197" + }, + "StaffordshireMoorlandsDistrictCouncil": { + "postcode": "ST8 6HN", + "skip_get_url": true, + "uprn": "100031863037", + "url": "https://www.staffsmoorlands.gov.uk/", + "web_driver": "http://selenium:4444", + "wiki_name": "Staffordshire Moorlands", + "wiki_note": "Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "E07000198" + }, + "StevenageBoroughCouncil": { + "uprn": "100080878852", + "url": "https://www.stevenage.gov.uk", + "wiki_name": "Stevenage", + "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "E07000243" + }, + "StirlingCouncil": { + "house_number": "5, SUNNYLAW ROAD, BRIDGE OF ALLAN, STIRLING, FK9 4QA", + "postcode": "FK9 4QA", + "skip_get_url": true, + "url": "https://www.stirling.gov.uk/bins-and-recycling/bin-collection-dates-search/", + "web_driver": "http://selenium:4444", + "wiki_name": "Stirling", + "wiki_note": "Use the full address as it appears on the drop-down on the site when you search by postcode.", + "LAD24CD": "S12000030" + }, + "StockportBoroughCouncil": { + "url": "https://myaccount.stockport.gov.uk/bin-collections/show/100011434401", + "wiki_command_url_override": "https://myaccount.stockport.gov.uk/bin-collections/show/XXXXXXXX", + "wiki_name": "Stockport", + "wiki_note": "Replace `XXXXXXXX` with your UPRN.", + "LAD24CD": "E08000007" + }, + "StocktonOnTeesCouncil": { + "house_number": "24", + "postcode": "TS20 2RD", + "skip_get_url": true, + "url": "https://www.stockton.gov.uk", + "web_driver": "http://selenium:4444", + "wiki_name": "Stockton-on-Tees", + "LAD24CD": "E06000004" + }, + "StokeOnTrentCityCouncil": { + "url": "https://www.stoke.gov.uk/jadu/custom/webserviceLookUps/BarTecWebServices_missed_bin_calendar.php?UPRN=3455121482", + "wiki_command_url_override": "https://www.stoke.gov.uk/jadu/custom/webserviceLookUps/BarTecWebServices_missed_bin_calendar.php?UPRN=XXXXXXXXXX", + "wiki_name": "Stoke-on-Trent", + "wiki_note": "Replace `XXXXXXXXXX` with your property's UPRN.", + "LAD24CD": "E06000021" + }, + "StratfordUponAvonCouncil": { + "LAD24CD": "E07000221", + "skip_get_url": true, + "uprn": "100070212698", + "url": "https://www.stratford.gov.uk/waste-recycling/when-we-collect.cfm/part/calendar", + "wiki_name": "Stratford-on-Avon", + "wiki_note": "Provide your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find it." + }, + "StroudDistrictCouncil": { + "postcode": "GL10 3BH", + "uprn": "100120512183", + "url": "https://www.stroud.gov.uk/my-house?uprn=100120512183&postcode=GL10+3BH", + "wiki_name": "Stroud", + "wiki_note": "Provide your UPRN and postcode. Replace the UPRN and postcode in the URL with your own.", + "LAD24CD": "E07000082" + }, + "SunderlandCityCouncil": { + "house_number": "13", + "postcode": "SR4 6BJ", + "skip_get_url": true, + "url": "https://webapps.sunderland.gov.uk/WEBAPPS/WSS/Sunderland_Portal/Forms/bindaychecker.aspx", + "web_driver": "http://selenium:4444", + "wiki_name": "Sunderland", + "wiki_note": "Provide your house number (without quotes) and postcode (wrapped in double quotes with a space).", + "LAD24CD": "E08000024" + }, + "SurreyHeathBoroughCouncil": { + "house_number": "36", + "postcode": "GU20 6PN", + "skip_get_url": true, + "url": "https://asjwsw-wrpsurreyheathmunicipal-live.whitespacews.com/", + "wiki_name": "Surrey Heath", + "wiki_note": "Provide your house number in the `house_number` parameter and postcode in the `postcode` parameter.", + "LAD24CD": "E07000214" + }, + "SwaleBoroughCouncil": { + "house_number": "81", + "postcode": "ME12 2NQ", + "skip_get_url": true, + "url": "https://swale.gov.uk/bins-littering-and-the-environment/bins/collection-days", + "web_driver": "http://selenium:4444", + "wiki_name": "Swale", + "wiki_note": "Provide your house number in the `house_number` parameter and postcode in the `postcode` parameter.", + "LAD24CD": "E07000113" + }, + "SwanseaCouncil": { + "postcode": "SA43PQ", + "skip_get_url": true, + "uprn": "100100324821", + "url": "https://www1.swansea.gov.uk/recyclingsearch/", + "wiki_name": "Swansea", + "wiki_note": "Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "W06000011" + }, + "SwindonBoroughCouncil": { + "uprn": "10022793351", + "url": "https://www.swindon.gov.uk", + "wiki_name": "Swindon", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "E06000030" + }, + "TamesideMBCouncil": { + "skip_get_url": true, + "uprn": "100012835362", + "url": "http://lite.tameside.gov.uk/BinCollections/CollectionService.svc/GetBinCollection", + "wiki_name": "Tameside", + "wiki_note": "Provide your UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E08000008" + }, + "TandridgeDistrictCouncil": { + "skip_get_url": true, + "uprn": "100062160432", + "url": "https://tdcws01.tandridge.gov.uk/TDCWebAppsPublic/tfaBranded/408?utm_source=pressrelease&utm_medium=smposts&utm_campaign=check_my_bin_day", + "wiki_name": "Tandridge", + "wiki_note": "Provide your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to locate it.", + "LAD24CD": "E07000215" + }, + "TeignbridgeCouncil": { + "uprn": "100040338776", + "url": "https://www.google.co.uk", + "web_driver": "http://selenium:4444", + "wiki_command_url_override": "https://www.google.co.uk", + "wiki_name": "Teignbridge", + "wiki_note": "Provide Google as the URL as the real URL breaks the integration. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000045" + }, + "TelfordAndWrekinCouncil": { + "skip_get_url": true, + "uprn": "000452015013", + "url": "https://dac.telford.gov.uk/bindayfinder/", + "wiki_name": "Telford and Wrekin", + "wiki_note": "Provide your UPRN. Find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E06000020" + }, + "TewkesburyBoroughCouncil": { + "skip_get_url": true, + "uprn": "10067626314", + "url": "https://tewkesbury.gov.uk/services/waste-and-recycling/", + "wiki_name": "Tewkesbury", + "wiki_note": "Provide your UPRN. Find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000083" + }, + "TendringDistrictCouncil": { + "postcode": "CO15 4EU", + "skip_get_url": true, + "uprn": "100090604247", + "url": "https://tendring-self.achieveservice.com/en/service/Rubbish_and_recycling_collection_days", + "web_driver": "http://selenium:4444", + "wiki_name": "Tendring", + "wiki_note": "Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000076" + }, + "TestValleyBoroughCouncil": { + "postcode": "SO51 9ZD", + "skip_get_url": true, + "uprn": "200010012019", + "url": "https://testvalley.gov.uk/wasteandrecycling/when-are-my-bins-collected", + "wiki_name": "Test Valley", + "wiki_note": "Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "E07000093" + }, + "ThanetDistrictCouncil": { + "uprn": "100061111858", + "url": "https://www.thanet.gov.uk", + "web_driver": "http://selenium:4444", + "wiki_name": "Thanet", + "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "E07000114" + }, + "ThreeRiversDistrictCouncil": { + "postcode": "WD3 7AZ", + "skip_get_url": true, + "uprn": "100080913662", + "url": "https://my.threerivers.gov.uk/en/AchieveForms/?mode=fill&consentMessage=yes&form_uri=sandbox-publish://AF-Process-52df96e3-992a-4b39-bba3-06cfaabcb42b/AF-Stage-01ee28aa-1584-442c-8d1f-119b6e27114a/definition.json&process=1&process_uri=sandbox-processes://AF-Process-52df96e3-992a-4b39-bba3-06cfaabcb42b&process_id=AF-Process-52df96e3-992a-4b39-bba3-06cfaabcb42b&noLoginPrompt=1", + "web_driver": "http://selenium:4444", + "wiki_name": "Three Rivers", + "wiki_note": "Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000102" + }, + "ThurrockCouncil": { + "house_number": "Monday", + "postcode": "Round A", + "skip_get_url": true, + "url": "https://www.thurrock.gov.uk", + "wiki_name": "Thurrock", + "wiki_note": "Use the House Number field to pass the DAY of the week for your collections. [Monday/Tuesday/Wednesday/Thursday/Friday]. Use the 'postcode' field to pass the ROUND (wrapped in quotes) for your collections. [Round A/Round B].", + "LAD24CD": "E06000034" + }, + "TonbridgeAndMallingBC": { + "postcode": "ME19 4JS", + "skip_get_url": true, + "uprn": "10002914589", + "url": "https://www.tmbc.gov.uk/", + "wiki_name": "Tonbridge and Malling", + "wiki_note": "Provide your UPRN and postcode.", + "LAD24CD": "E07000115" + }, + "TorbayCouncil": { + "skip_get_url": true, + "uprn": "10000016984", + "postcode": "TQ1 1AG", + "url": "https://www.torbay.gov.uk/recycling/bin-collections/", + "wiki_name": "Torbay", + "wiki_note": "Provide your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find it.", + "LAD24CD": "E06000027" + }, + "TorridgeDistrictCouncil": { + "skip_get_url": true, + "uprn": "10091078762", + "url": "https://collections-torridge.azurewebsites.net/WebService2.asmx", + "wiki_name": "Torridge", + "wiki_note": "Provide your UPRN.", + "LAD24CD": "E07000046" + }, + "TunbridgeWellsCouncil": { + "uprn": "10090058289", + "url": "https://tunbridgewells.gov.uk", + "wiki_command_url_override": "https://tunbridgewells.gov.uk", + "wiki_name": "Tunbridge Wells", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "E07000116" + }, + "UttlesfordDistrictCouncil": { + "house_number": "72, Birchanger Lane", + "postcode": "CM23 5QF", + "skip_get_url": true, + "uprn": "100090643434", + "url": "https://bins.uttlesford.gov.uk/", + "web_driver": "http://selenium:4444", + "wiki_name": "Uttlesford", + "wiki_note": "Provide your full address in the `house_number` parameter and your postcode in the `postcode` parameter.", + "LAD24CD": "E07000077" + }, + "ValeofGlamorganCouncil": { + "skip_get_url": true, + "uprn": "64029020", + "url": "https://www.valeofglamorgan.gov.uk/en/living/Recycling-and-Waste/", + "wiki_name": "The Vale of Glamorgan", + "wiki_note": "Provide your UPRN. Find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "W06000014" + }, + "ValeofWhiteHorseCouncil": { + "custom_component_show_url_field": false, + "skip_get_url": true, + "uprn": "100121391443", + "url": "https://eform.whitehorsedc.gov.uk/ebase/BINZONE_DESKTOP.eb", + "wiki_name": "Vale of White Horse", + "wiki_note": "Provide your UPRN.", + "LAD24CD": "E07000180" + }, + "WakefieldCityCouncil": { + "custom_component_show_url_field": true, + "skip_get_url": true, + "url": "https://www.wakefield.gov.uk/where-i-live/?uprn=63035490&a=115%20Elizabeth%20Drive%20Castleford%20WF10%203RR&usrn=41801243&e=445418&n=426091&p=WF10%203RR", + "web_driver": "http://selenium:4444", + "wiki_command_url_override": "https://www.wakefield.gov.uk/where-i-live/?uprn=XXXXXXXXXXX&a=XXXXXXXXXXX&usrn=XXXXXXXXXXX&e=XXXXXXXXXXX&n=XXXXXXXXXXX&p=XXXXXXXXXXX", + "wiki_name": "Wakefield", + "wiki_note": "Follow the instructions [here](https://www.wakefield.gov.uk/where-i-live/) until you get the page that includes a 'Bin Collections' section, then copy the URL and replace the URL in the command.", + "LAD24CD": "E08000036" + }, + "WalsallCouncil": { + "uprn": "100071080513", + "url": "https://cag.walsall.gov.uk/", + "wiki_command_url_override": "https://cag.walsall.gov.uk/", + "wiki_name": "Walsall", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "E08000030" + }, + "WalthamForest": { + "house_number": "17 Chingford Road, Walthamstow", + "postcode": "E17 4PW", + "skip_get_url": true, + "uprn": "200001415697", + "url": "https://portal.walthamforest.gov.uk/AchieveForms/?mode=fill&consentMessage=yes&form_uri=sandbox-publish://AF-Process-d62ccdd2-3de9-48eb-a229-8e20cbdd6393/AF-Stage-8bf39bf9-5391-4c24-857f-0dc2025c67f4/definition.json&process=1&process_uri=sandbox-processes://AF-Process-d62ccdd2-3de9-48eb-a229-8e20cbdd6393&process_id=AF-Process-d62ccdd2-3de9-48eb-a229-8e20cbdd6393", + "web_driver": "http://selenium:4444", + "wiki_name": "Waltham Forest", + "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "E09000031" + }, + "WandsworthCouncil": { + "uprn": "100022684035", + "url": "https://www.wandsworth.gov.uk", + "wiki_command_url_override": "https://www.wandsworth.gov.uk", + "wiki_name": "Wandsworth", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E09000032" + }, + "WarringtonBoroughCouncil": { + "uprn": "10094964379", + "url": "https://www.warrington.gov.uk", + "wiki_command_url_override": "https://www.warrington.gov.uk", + "wiki_name": "Warrington", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E06000007" + }, + "WarwickDistrictCouncil": { + "url": "https://estates7.warwickdc.gov.uk/PropertyPortal/Property/Recycling/100070263793", + "wiki_command_url_override": "https://estates7.warwickdc.gov.uk/PropertyPortal/Property/Recycling/XXXXXXXX", + "wiki_name": "Warwick", + "wiki_note": "Replace `XXXXXXXX` with your UPRN.", + "LAD24CD": "E07000222" + }, + "WatfordBoroughCouncil": { + "uprn": "100080942183", + "url": "https://www.watford.gov.uk", + "wiki_command_url_override": "https://www.watford.gov.uk", + "wiki_name": "Watford", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000103" + }, + "WaverleyBoroughCouncil": { + "house_number": "23", + "postcode": "GU9 9QG", + "skip_get_url": true, + "url": "https://wav-wrp.whitespacews.com/", + "wiki_name": "Waverley", + "wiki_note": "Follow the instructions [here](https://wav-wrp.whitespacews.com/#!) until you get the page that shows your next scheduled collections. Then take the number from `pIndex=NUMBER` in the URL and pass it as the `-n` parameter along with your postcode in `-p`.", + "LAD24CD": "E07000216" + }, + "WealdenDistrictCouncil": { + "skip_get_url": true, + "uprn": "10033413624", + "url": "https://www.wealden.gov.uk/recycling-and-waste/bin-search/", + "wiki_name": "Wealden", + "wiki_note": "Provide your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find it.", + "LAD24CD": "E07000065" + }, + "WelhatCouncil": { + "LAD24CD": "E07000241", + "postcode": "AL8 6HQ", + "uprn": "100080982825", + "url": "https://www.welhat.gov.uk/xfp/form/214", + "wiki_name": "Welwyn Hatfield", + "wiki_note": "Provide your UPRN and postcode." + }, + "WestBerkshireCouncil": { + "house_number": "8", + "postcode": "RG14 7DP", + "skip_get_url": true, + "url": "https://www.westberks.gov.uk/binday", + "web_driver": "http://selenium:4444", + "wiki_name": "West Berkshire", + "wiki_note": "Provide your house number in the `house_number` parameter and postcode in the `postcode` parameter.", + "LAD24CD": "E06000037" + }, + "WestDunbartonshireCouncil": { + "uprn": "129001383", + "url": "https://www.west-dunbarton.gov.uk/", + "wiki_name": "West Dunbartonshire", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "S12000039" + }, + "WestLancashireBoroughCouncil": { + "postcode": "WN8 0HR", + "uprn": "10012343339", + "url": "https://www.westlancs.gov.uk", + "wiki_name": "West Lancashire", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000127" + }, + "WestLindseyDistrictCouncil": { + "house_number": "35", + "postcode": "LN8 3AX", + "skip_get_url": true, + "url": "https://www.west-lindsey.gov.uk/", + "wiki_name": "West Lindsey", + "wiki_note": "Provide your house name/number in the `house_number` parameter, and postcode in the `postcode` parameter, both wrapped in double quotes. If multiple results are returned, the first will be used.", + "LAD24CD": "E07000142" + }, + "WestLothianCouncil": { + "house_number": "1 GOSCHEN PLACE", + "postcode": "EH52 5JE", + "skip_get_url": true, + "url": "https://www.westlothian.gov.uk/", + "web_driver": "http://selenium:4444", + "wiki_name": "West Lothian", + "wiki_note": "Provide your house name/number in the `house_number` parameter (wrapped in double quotes) and your postcode in the `postcode` parameter.", + "LAD24CD": "S12000040" + }, + "WestMorlandAndFurness": { + "uprn": "100110353478", + "url": "https://www.westmorlandandfurness.gov.uk/", + "wiki_command_url_override": "https://www.westmorlandandfurness.gov.uk/", + "wiki_name": "Westmorland and Furness", + "wiki_note": "Provide your UPRN. You can find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E06000064" + }, + "WestNorthamptonshireCouncil": { + "skip_get_url": true, + "uprn": "28056796", + "url": "https://www.westnorthants.gov.uk", + "wiki_name": "West Northamptonshire", + "wiki_note": "Provide your UPRN. You can find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E06000062" + }, + "WestOxfordshireDistrictCouncil": { + "house_number": "24", + "postcode": "OX28 1YA", + "skip_get_url": true, + "url": "https://community.westoxon.gov.uk/s/waste-collection-enquiry", + "web_driver": "http://selenium:4444", + "wiki_name": "West Oxfordshire", + "wiki_note": "Provide your house number in the `house_number` parameter and your postcode in the `postcode` parameter.", + "LAD24CD": "E07000181" + }, + "WestSuffolkCouncil": { + "postcode": "IP28 6DR", + "skip_get_url": true, + "uprn": "10009739960", + "url": "https://maps.westsuffolk.gov.uk/MyWestSuffolk.aspx", + "wiki_name": "West Suffolk", + "wiki_note": "Provide your UPRN and postcode. You can find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000245" + }, + "WiganBoroughCouncil": { + "postcode": "WN2 4UQ", + "skip_get_url": true, + "uprn": "010093942934", + "url": "https://apps.wigan.gov.uk/MyNeighbourhood/", + "wiki_name": "Wigan", + "wiki_note": "Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E08000010" + }, + "WiltshireCouncil": { + "postcode": "SN8 3TE", + "skip_get_url": true, + "uprn": "100120982570", + "url": "https://ilambassadorformsprod.azurewebsites.net/wastecollectiondays/index", + "wiki_name": "Wiltshire", + "wiki_note": "Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "E06000054" + }, + "WinchesterCityCouncil": { + "house_number": "12", + "paon": "12", + "postcode": "SO23 7GA", + "skip_get_url": false, + "url": "https://iportal.itouchvision.com/icollectionday/collection-day", + "web_driver": "http://selenium:4444", + "wiki_name": "Winchester", + "wiki_note": "Provide your house name/number in the `house_number` parameter (wrapped in double quotes) and your postcode in the `postcode` parameter.", + "LAD24CD": "E07000094" + }, + "WindsorAndMaidenheadCouncil": { + "skip_get_url": true, + "uprn": "100080371082", + "url": "https://forms.rbwm.gov.uk/bincollections?uprn=", + "web_driver": "http://selenium:4444", + "wiki_name": "Windsor and Maidenhead", + "wiki_note": "Provide your UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E06000040" + }, + "WirralCouncil": { + "uprn": "Vernon Avenue,Seacombe", + "url": "https://www.wirral.gov.uk", + "wiki_command_url_override": "https://www.wirral.gov.uk", + "wiki_name": "Wirral", + "wiki_note": "In the `uprn` field, enter your street name and suburb separated by a comma (e.g., 'Vernon Avenue,Seacombe').", + "LAD24CD": "E08000015" + }, + "WokingBoroughCouncil": { + "house_number": "2", + "postcode": "GU21 4JY", + "skip_get_url": true, + "url": "https://asjwsw-wrpwokingmunicipal-live.whitespacews.com/", + "wiki_name": "Woking", + "wiki_note": "Provide your house number in the `house_number` parameter and postcode in the `postcode` parameter. This works with all collection areas that use Joint Waste Solutions.", + "LAD24CD": "E07000217" + }, + "WokinghamBoroughCouncil": { + "house_number": "90", + "postcode": "RG40 2HR", + "skip_get_url": true, + "url": "https://www.wokingham.gov.uk/rubbish-and-recycling/waste-collection/find-your-bin-collection-day", + "web_driver": "http://selenium:4444", + "wiki_name": "Wokingham", + "wiki_note": "Provide your house number in the `house_number` parameter and postcode in the `postcode` parameter.", + "LAD24CD": "E06000041" + }, + "WolverhamptonCityCouncil": { + "postcode": "WV3 9NZ", + "uprn": "100071205205", + "url": "https://www.wolverhampton.gov.uk", + "wiki_name": "Wolverhampton", + "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "E08000031" + }, + "WorcesterCityCouncil": { + "uprn": "100120650345", + "url": "https://www.Worcester.gov.uk", + "wiki_command_url_override": "https://www.Worcester.gov.uk", + "wiki_name": "Worcester", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "E07000237" + }, + "WrexhamCountyBoroughCouncil": { + "house_number": "1", + "postcode": "LL12 7RW", + "uprn": "200002944225", + "skip_get_url": true, + "url": "https://www.wrexham.gov.uk/service/when-are-my-bins-collected", + "web_driver": "http://selenium:4444", + "wiki_name": "Wrexham", + "wiki_note": "Provide your house number in the `house_number` parameter and postcode in the `postcode` parameter.", + "LAD24CD": "W06000006" + }, + "WychavonDistrictCouncil": { + "postcode": "WR3 7RU", + "skip_get_url": true, + "uprn": "100120716273", + "url": "https://selfservice.wychavon.gov.uk/wdcroundlookup/wdc_search.jsp", + "web_driver": "http://selenium:4444", + "wiki_name": "Wychavon", + "wiki_note": "Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000238" + }, + "WyreCouncil": { + "skip_get_url": true, + "uprn": "10003519994", + "url": "https://www.wyre.gov.uk/bins-rubbish-recycling", + "wiki_name": "Wyre", + "wiki_note": "Provide your UPRN. Find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "LAD24CD": "E07000128" + }, + "WyreForestDistrictCouncil": { + "house_number": "Monday", + "skip_get_url": true, + "url": "https://www.wyreforestdc.gov.uk", + "wiki_name": "Wyre Forest", + "wiki_note": "Use the House Number field to pass the DAY of the week for your collections. [Monday/Tuesday/Wednesday/Thursday/Friday/Saturday/Sunday].", + "LAD24CD": "E07000239" + }, + "YorkCouncil": { + "skip_get_url": true, + "uprn": "100050535540", + "url": "https://waste-api.york.gov.uk/api/Collections/GetBinCollectionDataForUprn/", + "wiki_name": "York", + "wiki_note": "Provide your UPRN.", + "LAD24CD": "E06000014" + } +} From 4ab3470cd2c42d9c9e6b1b3bbbb5302a40f6e8cc Mon Sep 17 00:00:00 2001 From: Matt Williams Date: Mon, 16 Jun 2025 12:09:45 +0100 Subject: [PATCH 070/425] Update WiganBoroughCouncil.py HTML source code of the page shows the bin collection dates class has changed from dateWrapper-next to dateWrap-next --- .../uk_bin_collection/councils/WiganBoroughCouncil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/WiganBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/WiganBoroughCouncil.py index 2a0a120eb9..c0e87a5035 100644 --- a/uk_bin_collection/uk_bin_collection/councils/WiganBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/WiganBoroughCouncil.py @@ -82,7 +82,7 @@ def parse_data(self, page: str, **kwargs) -> dict: # Get the dates. for bins in soup.find_all("div", {"class": "BinsRecycling"}): bin_type = bins.find("h2").text - binCollection = bins.find("div", {"class": "dateWrapper-next"}).get_text( + binCollection = bins.find("div", {"class": "dateWrap-next"}).get_text( strip=True ) binData = datetime.strptime( From 86709e64e563b937d2099cc5ba451642dcc7c832 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 18 Jun 2025 07:24:44 +0000 Subject: [PATCH 071/425] =?UTF-8?q?bump:=20version=200.152.5=20=E2=86=92?= =?UTF-8?q?=200.152.6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 11 +++++++++++ custom_components/uk_bin_collection/const.py | 2 +- custom_components/uk_bin_collection/manifest.json | 4 ++-- pyproject.toml | 2 +- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c2536b173..55d8b819b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,15 @@ ======= +## 0.152.6 (2025-06-18) + +### Fix + +- removed In Progress from date +- removed a degub print statement +- **RugbyBoroughCouncil**: Amended parsed date from full to abbreviated month date, may worked but jun and jul did not +- **RugbyBoroughCouncil**: Amended parsed date +- Reworked Cumberland Council to cater for postcode addition +- **OxfordCityCouncil**: Fixed Oxford City Council parsing dues to changes in output from the website + ## 0.152.5 (2025-06-07) ### Fix diff --git a/custom_components/uk_bin_collection/const.py b/custom_components/uk_bin_collection/const.py index a4b2cf9ea1..8830f1ab42 100644 --- a/custom_components/uk_bin_collection/const.py +++ b/custom_components/uk_bin_collection/const.py @@ -4,7 +4,7 @@ from homeassistant.const import Platform -INPUT_JSON_URL = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.152.5/uk_bin_collection/tests/input.json" +INPUT_JSON_URL = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.152.6/uk_bin_collection/tests/input.json" DEFAULT_NAME = "UK Bin Collection Data" diff --git a/custom_components/uk_bin_collection/manifest.json b/custom_components/uk_bin_collection/manifest.json index 9bc455b2f1..e916159d90 100644 --- a/custom_components/uk_bin_collection/manifest.json +++ b/custom_components/uk_bin_collection/manifest.json @@ -9,7 +9,7 @@ "integration_type": "service", "iot_class": "cloud_polling", "issue_tracker": "https://github.com/robbrad/UKBinCollectionData/issues", - "requirements": ["uk-bin-collection>=0.152.5"], - "version": "0.152.5", + "requirements": ["uk-bin-collection>=0.152.6"], + "version": "0.152.6", "zeroconf": [] } diff --git a/pyproject.toml b/pyproject.toml index cb19238060..0acc00c0a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "uk_bin_collection" -version = "0.152.5" +version = "0.152.6" description = "Python Lib to collect UK Bin Data" readme = "README.md" authors = ["Robert Bradley "] From cd504f6669d118d32a874e64f444d96c69b2a5ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Jun 2025 05:28:57 +0000 Subject: [PATCH 072/425] chore(deps): bump urllib3 from 2.4.0 to 2.5.0 Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.4.0 to 2.5.0. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.4.0...2.5.0) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.5.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index f8b58bae18..dd6ff26fab 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2980,14 +2980,14 @@ files = [ [[package]] name = "urllib3" -version = "2.4.0" +version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813"}, - {file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"}, + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ] [package.dependencies] From f140f5c448eb4ec59efcc5ba7325b38d06b796c8 Mon Sep 17 00:00:00 2001 From: David Amor Date: Thu, 19 Jun 2025 19:03:03 +0100 Subject: [PATCH 073/425] fix: maidstone selenium fix New Maidstone script required Selenium but this wasn't reflected in the input.json. --- uk_bin_collection/tests/input.json | 1 + 1 file changed, 1 insertion(+) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index 27016312a1..91f37c7c58 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -1443,6 +1443,7 @@ "house_number": "71", "postcode": "ME16 8BT", "url": "https://my.maidstone.gov.uk/service/Find-your-bin-day", + "web_driver": "http://selenium:4444", "wiki_name": "Maidstone", "wiki_note": "Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver.", "LAD24CD": "E07000110" From 757926e79420191a29da70cd832aa4b332644a9c Mon Sep 17 00:00:00 2001 From: David Amor Date: Thu, 19 Jun 2025 20:59:59 +0100 Subject: [PATCH 074/425] Update AdurAndWorthingCouncils.py --- .../uk_bin_collection/councils/AdurAndWorthingCouncils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/AdurAndWorthingCouncils.py b/uk_bin_collection/uk_bin_collection/councils/AdurAndWorthingCouncils.py index 916520a532..3933a09551 100644 --- a/uk_bin_collection/uk_bin_collection/councils/AdurAndWorthingCouncils.py +++ b/uk_bin_collection/uk_bin_collection/councils/AdurAndWorthingCouncils.py @@ -69,8 +69,10 @@ def parse_data(self, page: str, **kwargs) -> dict: current_year = datetime.now().year bin_date = bin_date.replace(year=current_year) - # If the date is in the past, it's probably for next year - if bin_date < datetime.now(): + # Only add a year if the date is more than 30 days in the past + # This handles both mid-year and year-end transitions better + today = datetime.now() + if (today - bin_date).days > 30: bin_date = bin_date.replace(year=current_year + 1) collections.append((bin_type, bin_date)) From 2f3b11f71fdbbaf27d5db317de5cc663460231ce Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 27 Jun 2025 16:20:25 +0100 Subject: [PATCH 075/425] Update RotherhamCouncil.py Added headers to request, emulating browser, to resolve 403 error that is the cause of #1498 --- .../uk_bin_collection/councils/RotherhamCouncil.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/RotherhamCouncil.py b/uk_bin_collection/uk_bin_collection/councils/RotherhamCouncil.py index 19eb0017e1..1ede3fda55 100644 --- a/uk_bin_collection/uk_bin_collection/councils/RotherhamCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/RotherhamCouncil.py @@ -15,11 +15,15 @@ def parse_data(self, page: str, **kwargs) -> dict: user_uprn = kwargs.get("uprn") check_uprn(user_uprn) - + headers = { + "Content-Type": "application/x-www-form-urlencoded", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36", + } response = requests.post( "https://www.rotherham.gov.uk/bin-collections?address={}&submit=Submit".format( user_uprn - ) + ), + headers=headers ) # Make a BS4 object soup = BeautifulSoup(response.text, features="html.parser") From 0a298e85ccd6bc8f95e6e237fd36d59122eae506 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 1 Jul 2025 07:01:24 +0000 Subject: [PATCH 076/425] =?UTF-8?q?bump:=20version=200.152.6=20=E2=86=92?= =?UTF-8?q?=200.152.7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 6 ++++++ custom_components/uk_bin_collection/const.py | 2 +- custom_components/uk_bin_collection/manifest.json | 4 ++-- pyproject.toml | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55d8b819b9..4c647e54e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,10 @@ ======= +## 0.152.7 (2025-07-01) + +### Fix + +- maidstone selenium fix + ## 0.152.6 (2025-06-18) ### Fix diff --git a/custom_components/uk_bin_collection/const.py b/custom_components/uk_bin_collection/const.py index 8830f1ab42..f461a83f69 100644 --- a/custom_components/uk_bin_collection/const.py +++ b/custom_components/uk_bin_collection/const.py @@ -4,7 +4,7 @@ from homeassistant.const import Platform -INPUT_JSON_URL = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.152.6/uk_bin_collection/tests/input.json" +INPUT_JSON_URL = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.152.7/uk_bin_collection/tests/input.json" DEFAULT_NAME = "UK Bin Collection Data" diff --git a/custom_components/uk_bin_collection/manifest.json b/custom_components/uk_bin_collection/manifest.json index e916159d90..a78197a0d3 100644 --- a/custom_components/uk_bin_collection/manifest.json +++ b/custom_components/uk_bin_collection/manifest.json @@ -9,7 +9,7 @@ "integration_type": "service", "iot_class": "cloud_polling", "issue_tracker": "https://github.com/robbrad/UKBinCollectionData/issues", - "requirements": ["uk-bin-collection>=0.152.6"], - "version": "0.152.6", + "requirements": ["uk-bin-collection>=0.152.7"], + "version": "0.152.7", "zeroconf": [] } diff --git a/pyproject.toml b/pyproject.toml index 0acc00c0a2..938b5d0cbc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "uk_bin_collection" -version = "0.152.6" +version = "0.152.7" description = "Python Lib to collect UK Bin Data" readme = "README.md" authors = ["Robert Bradley "] From 50b0ca3f210a0c571f49edfe8e0b2996c76cbd52 Mon Sep 17 00:00:00 2001 From: The__Birdman <50060909+CarlBird@users.noreply.github.com> Date: Tue, 1 Jul 2025 19:24:34 +0100 Subject: [PATCH 077/425] Update NewcastleUnderLymeCouncil.py To fix issue: Newcastle-under-Lyme Council - 403 error #1501 --- .../uk_bin_collection/councils/NewcastleUnderLymeCouncil.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/NewcastleUnderLymeCouncil.py b/uk_bin_collection/uk_bin_collection/councils/NewcastleUnderLymeCouncil.py index 274d23985e..3bb55c2855 100644 --- a/uk_bin_collection/uk_bin_collection/councils/NewcastleUnderLymeCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/NewcastleUnderLymeCouncil.py @@ -23,7 +23,8 @@ def parse_data(self, page: str, **kwargs) -> dict: URI = f"https://www.newcastle-staffs.gov.uk/homepage/97/check-your-bin-day?uprn={user_uprn}" # Make the GET request - response = requests.get(URI) + request_headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'} + response = requests.get(URI, headers=request_headers) response.raise_for_status() soup = BeautifulSoup(response.text, features="html.parser") soup.prettify() From 7a2c0350ef5cc1e765e5e0b22b0662e3588d061c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Jul 2025 06:21:17 +0000 Subject: [PATCH 078/425] chore: bump Andrew-Chen-Wang/github-wiki-action from 4 to 5 Bumps [Andrew-Chen-Wang/github-wiki-action](https://github.com/andrew-chen-wang/github-wiki-action) from 4 to 5. - [Release notes](https://github.com/andrew-chen-wang/github-wiki-action/releases) - [Commits](https://github.com/andrew-chen-wang/github-wiki-action/compare/v4...v5) --- updated-dependencies: - dependency-name: Andrew-Chen-Wang/github-wiki-action dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/wiki.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wiki.yml b/.github/workflows/wiki.yml index de68bc8a4f..2005bd1689 100644 --- a/.github/workflows/wiki.yml +++ b/.github/workflows/wiki.yml @@ -48,7 +48,7 @@ jobs: continue-on-error: true - name: Deploy Wiki Changes - uses: Andrew-Chen-Wang/github-wiki-action@v4 + uses: Andrew-Chen-Wang/github-wiki-action@v5 with: # Make sure WIKI_DIR ends with / as action uses rsync path: wiki/ From 84d679ed74c6e561bac57b4f5f57915bf97d3e2a Mon Sep 17 00:00:00 2001 From: Andy Doyle Date: Sat, 5 Jul 2025 15:08:00 +0000 Subject: [PATCH 079/425] Fix Bedfordshire Council (AWS) blocking python-requests user agent --- .../uk_bin_collection/councils/BedfordshireCouncil.py | 1 + 1 file changed, 1 insertion(+) diff --git a/uk_bin_collection/uk_bin_collection/councils/BedfordshireCouncil.py b/uk_bin_collection/uk_bin_collection/councils/BedfordshireCouncil.py index 691dc3a707..a7e26ce0f2 100644 --- a/uk_bin_collection/uk_bin_collection/councils/BedfordshireCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/BedfordshireCouncil.py @@ -28,6 +28,7 @@ def parse_data(self, page: str, **kwargs) -> dict: headers = { "Origin": "https://www.centralbedfordshire.gov.uk", "Referer": "https://www.centralbedfordshire.gov.uk/info/163/bins_and_waste_collections_-_check_bin_collection_day", + "User-Agent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.7968.1811 Mobile Safari/537.36", } files = { From 1ce0aa96fb846090d08d9445a4c7be4bff8f963f Mon Sep 17 00:00:00 2001 From: Proto Date: Tue, 8 Jul 2025 16:43:14 +0100 Subject: [PATCH 080/425] combo box is now input 23 --- .../councils/WestOxfordshireDistrictCouncil.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/WestOxfordshireDistrictCouncil.py b/uk_bin_collection/uk_bin_collection/councils/WestOxfordshireDistrictCouncil.py index e6c997aba7..c3a1bb1bab 100644 --- a/uk_bin_collection/uk_bin_collection/councils/WestOxfordshireDistrictCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/WestOxfordshireDistrictCouncil.py @@ -42,12 +42,12 @@ def parse_data(self, page: str, **kwargs) -> dict: wait = WebDriverWait(driver, 60) address_entry_field = wait.until( EC.presence_of_element_located( - (By.XPATH, '//*[@id="combobox-input-22"]') + (By.XPATH, '//*[@id="combobox-input-23"]') ) ) address_entry_field = wait.until( - EC.element_to_be_clickable((By.XPATH, '//*[@id="combobox-input-22"]')) + EC.element_to_be_clickable((By.XPATH, '//*[@id="combobox-input-23"]')) ) address_entry_field.click() address_entry_field.send_keys(str(full_address)) @@ -56,7 +56,7 @@ def parse_data(self, page: str, **kwargs) -> dict: first_found_address = wait.until( EC.element_to_be_clickable( - (By.XPATH, '//*[@id="dropdown-element-22"]/ul') + (By.XPATH, '//*[@id="dropdown-element-23"]/ul') ) ) From 82c0d526a5764e17a428f867e058011180ab21d9 Mon Sep 17 00:00:00 2001 From: Ryan Lowry Date: Tue, 8 Jul 2025 22:14:50 +0000 Subject: [PATCH 081/425] fix(MidlothianCouncil): add request headers to resolve 403 Forbidden --- .../councils/MidlothianCouncil.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/MidlothianCouncil.py b/uk_bin_collection/uk_bin_collection/councils/MidlothianCouncil.py index 180f70026e..0c13488ef9 100644 --- a/uk_bin_collection/uk_bin_collection/councils/MidlothianCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/MidlothianCouncil.py @@ -22,6 +22,15 @@ class CouncilClass(AbstractGetBinDataClass): "Next brown bin collection": "Brown Bin", "Next food bin collection": "Food Bin", } + HEADERS = { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", + "Accept-Language": "en-GB,en;q=0.9", + "Connection": "keep-alive", + "Host": "www.midlothian.gov.uk", + "Referer": "https://www.midlothian.gov.uk/info/200284/bins_and_recycling", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36", + } def parse_data(self, page: str, **kwargs) -> dict: @@ -46,7 +55,7 @@ def parse_data(self, page: str, **kwargs) -> dict: search_url = self.DIRECTORY_URL.format(quote(postcode)) try: - search_results_html = requests.get(search_url) + search_results_html = requests.get(search_url, headers=self.HEADERS) search_results_html.raise_for_status() soup = BeautifulSoup(search_results_html.text, "html.parser") @@ -93,7 +102,7 @@ def _get_result_by_identifier(self, soup, identifier: str) -> list: next_page_url = next_page_link["href"] # Send a GET request to the next page - next_response = requests.get(next_page_url) + next_response = requests.get(next_page_url, headers=self.HEADERS) next_response.raise_for_status() # Raise an exception for HTTP errors # Parse the HTML content of the next page @@ -116,7 +125,7 @@ def _get_result_by_identifier(self, soup, identifier: str) -> list: def _fetch_bin_collection_data(self, url: str) -> list: """Fetch and parse bin collection data from the given URL.""" try: - bin_collection_html = requests.get(url) + bin_collection_html = requests.get(url, headers=self.HEADERS) bin_collection_html.raise_for_status() soup = BeautifulSoup(bin_collection_html.text, "html.parser") From 1ba4688186997f349871d9691094dd9e31b4c23a Mon Sep 17 00:00:00 2001 From: Chris Hawley <297231+ChrisJHawley@users.noreply.github.com> Date: Tue, 8 Jul 2025 23:28:45 +0100 Subject: [PATCH 082/425] fix: Add headers to requests for Royal Borough of Greenwich Fixes #1496 by ensuring that the requests are not rejected due to lack of headers. --- .../councils/RoyalBoroughofGreenwich.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/RoyalBoroughofGreenwich.py b/uk_bin_collection/uk_bin_collection/councils/RoyalBoroughofGreenwich.py index 4d55ff73cf..b5ce83450c 100644 --- a/uk_bin_collection/uk_bin_collection/councils/RoyalBoroughofGreenwich.py +++ b/uk_bin_collection/uk_bin_collection/councils/RoyalBoroughofGreenwich.py @@ -22,12 +22,18 @@ def parse_data(self, page: str, **kwargs) -> dict: check_paon(user_paon) bindata = {"bins": []} + headers = { + "Origin": "https://www.royalgreenwich.gov.uk/", + "Referer": "https://www.royalgreenwich.gov.uk/info/200171/recycling_and_rubbish/100/bin_collection_days", + "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)", + } + user_postcode = user_postcode.replace(" ", "+") URI = f"https://www.royalgreenwich.gov.uk/site/custom_scripts/apps/waste-collection/new2023/source.php?term={user_postcode}" # Make the GET request - response = requests.get(URI) + response = requests.get(URI, headers=headers) for address in response.json(): if user_paon in address: @@ -38,7 +44,7 @@ def parse_data(self, page: str, **kwargs) -> dict: data = {"address": collection_address} - response = requests.post(URI, data=data) + response = requests.post(URI, data=data, headers=headers) response = response.json() From 8fff9c0cc810d83eba38094d165840417ff2fcc0 Mon Sep 17 00:00:00 2001 From: The__Birdman <50060909+CarlBird@users.noreply.github.com> Date: Wed, 9 Jul 2025 09:18:53 +0100 Subject: [PATCH 083/425] Fix Newcastle-under-Lyme Council - Add request headers to resolve 403 Forbidden From 15f2fe5ec0531f9557c78005892c78bb24b29d49 Mon Sep 17 00:00:00 2001 From: Bal0o Date: Mon, 30 Jun 2025 09:51:29 +0100 Subject: [PATCH 084/425] fix(hacs): respect the headless option Fixed the handling of the headless argument in the build_ukbcd_args function to correctly pass boolean values as command-line flags. --- custom_components/uk_bin_collection/__init__.py | 10 +++++++++- custom_components/uk_bin_collection/const.py | 1 - 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/custom_components/uk_bin_collection/__init__.py b/custom_components/uk_bin_collection/__init__.py index a5273cdbc0..2359c1f98b 100644 --- a/custom_components/uk_bin_collection/__init__.py +++ b/custom_components/uk_bin_collection/__init__.py @@ -291,7 +291,15 @@ def build_ukbcd_args(config_data: dict) -> list: continue if key == "web_driver" and value is not None: value = value.rstrip("/") - args.append(f"--{key}={value}") + args.append(f"--{key}={value}") + elif key == "headless": + # Handle boolean headless argument correctly + if value: + args.append("--headless") + else: + args.append("--not-headless") + else: + args.append(f"--{key}={value}") return args diff --git a/custom_components/uk_bin_collection/const.py b/custom_components/uk_bin_collection/const.py index 8830f1ab42..4f5a90ac50 100644 --- a/custom_components/uk_bin_collection/const.py +++ b/custom_components/uk_bin_collection/const.py @@ -31,7 +31,6 @@ "council", "url", "skip_get_url", - "headless", "local_browser", "timeout", "icon_color_mapping", From f36af81fc0bb5838de0ed08f88c39e4d586636db Mon Sep 17 00:00:00 2001 From: iz4c <55418526+iz4c@users.noreply.github.com> Date: Mon, 21 Jul 2025 17:14:33 +0100 Subject: [PATCH 085/425] fix: Add headers to request for Swindon Borough Council --- .../uk_bin_collection/councils/SwindonBoroughCouncil.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/SwindonBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/SwindonBoroughCouncil.py index db33a65e19..d425c262b6 100644 --- a/uk_bin_collection/uk_bin_collection/councils/SwindonBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/SwindonBoroughCouncil.py @@ -22,9 +22,10 @@ def parse_data(self, page: str, **kwargs) -> dict: bindata = {"bins": []} URI = f"https://www.swindon.gov.uk/info/20122/rubbish_and_recycling_collection_days?addressList={user_uprn}&uprnSubmit=Yes" + headers = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)"} # Make the GET request - response = requests.get(URI) + response = requests.get(URI, headers=headers) # Parse the JSON response soup = BeautifulSoup(response.text, "html.parser") From adffcd8f4f18075ee73786c1dada8d6b03e84800 Mon Sep 17 00:00:00 2001 From: ChadH360 <35111065+ChadH360@users.noreply.github.com> Date: Wed, 23 Jul 2025 09:50:38 +0100 Subject: [PATCH 086/425] Update BedfordshireCouncil.py Addition of User-Agent to headers in order to work around AWS blocking request --- .../uk_bin_collection/councils/BedfordshireCouncil.py | 1 + 1 file changed, 1 insertion(+) diff --git a/uk_bin_collection/uk_bin_collection/councils/BedfordshireCouncil.py b/uk_bin_collection/uk_bin_collection/councils/BedfordshireCouncil.py index 691dc3a707..10695840cd 100644 --- a/uk_bin_collection/uk_bin_collection/councils/BedfordshireCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/BedfordshireCouncil.py @@ -28,6 +28,7 @@ def parse_data(self, page: str, **kwargs) -> dict: headers = { "Origin": "https://www.centralbedfordshire.gov.uk", "Referer": "https://www.centralbedfordshire.gov.uk/info/163/bins_and_waste_collections_-_check_bin_collection_day", + "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)" } files = { From f2cf24b2251c1f57013acd722bb9f6888b70dcb8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 26 Jul 2025 06:58:37 +0000 Subject: [PATCH 087/425] =?UTF-8?q?bump:=20version=200.152.7=20=E2=86=92?= =?UTF-8?q?=200.152.8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 8 ++++++++ custom_components/uk_bin_collection/const.py | 2 +- custom_components/uk_bin_collection/manifest.json | 4 ++-- pyproject.toml | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c647e54e4..efc0ec6820 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,12 @@ ======= +## 0.152.8 (2025-07-26) + +### Fix + +- Add headers to request for Swindon Borough Council +- Add headers to requests for Royal Borough of Greenwich Fixes #1496 by ensuring that the requests are not rejected due to lack of headers. +- **MidlothianCouncil**: add request headers to resolve 403 Forbidden + ## 0.152.7 (2025-07-01) ### Fix diff --git a/custom_components/uk_bin_collection/const.py b/custom_components/uk_bin_collection/const.py index f461a83f69..beb0887802 100644 --- a/custom_components/uk_bin_collection/const.py +++ b/custom_components/uk_bin_collection/const.py @@ -4,7 +4,7 @@ from homeassistant.const import Platform -INPUT_JSON_URL = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.152.7/uk_bin_collection/tests/input.json" +INPUT_JSON_URL = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.152.8/uk_bin_collection/tests/input.json" DEFAULT_NAME = "UK Bin Collection Data" diff --git a/custom_components/uk_bin_collection/manifest.json b/custom_components/uk_bin_collection/manifest.json index a78197a0d3..58a173dc5d 100644 --- a/custom_components/uk_bin_collection/manifest.json +++ b/custom_components/uk_bin_collection/manifest.json @@ -9,7 +9,7 @@ "integration_type": "service", "iot_class": "cloud_polling", "issue_tracker": "https://github.com/robbrad/UKBinCollectionData/issues", - "requirements": ["uk-bin-collection>=0.152.7"], - "version": "0.152.7", + "requirements": ["uk-bin-collection>=0.152.8"], + "version": "0.152.8", "zeroconf": [] } diff --git a/pyproject.toml b/pyproject.toml index 938b5d0cbc..a7d7525edb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "uk_bin_collection" -version = "0.152.7" +version = "0.152.8" description = "Python Lib to collect UK Bin Data" readme = "README.md" authors = ["Robert Bradley "] From 6a2b5ba642c02c44459f1577b80717fac94f6976 Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Sun, 27 Jul 2025 11:25:24 +0100 Subject: [PATCH 088/425] fix: multiple broken councils --- uk_bin_collection/tests/input.json | 18 +- .../councils/AngusCouncil.py | 115 +++++--- .../councils/AylesburyValeCouncil.py | 69 ----- .../councils/BarnetCouncil.py | 273 ++++++++++-------- .../councils/BasildonCouncil.py | 153 +++++----- .../councils/BlabyDistrictCouncil.py | 6 +- .../BlaenauGwentCountyBoroughCouncil.py | 157 +++++----- .../councils/BroxbourneCouncil.py | 155 +++++----- .../councils/SouthwarkCouncil.py | 24 +- .../councils/WakefieldCityCouncil.py | 5 +- 10 files changed, 531 insertions(+), 444 deletions(-) delete mode 100644 uk_bin_collection/uk_bin_collection/councils/AylesburyValeCouncil.py diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index 29c69e3ac5..8bfa08427f 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -100,14 +100,6 @@ "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", "LAD24CD": "E07000105" }, - "AylesburyValeCouncil": { - "skip_get_url": true, - "uprn": "766252532", - "url": "http://avdcbins.web-labs.co.uk/RefuseApi.asmx", - "wiki_name": "Buckinghamshire", - "wiki_note": "To get the UPRN, please use [FindMyAddress](https://www.findmyaddress.co.uk/search). Returns all published collections in the past, present, future.", - "LAD24CD": "E06000060" - }, "BCPCouncil": { "LAD24CD": "E06000058", "skip_get_url": true, @@ -137,8 +129,8 @@ "LAD24CD": "E09000002" }, "BarnetCouncil": { - "house_number": "HA8 7NA, 2, MANOR PARK GARDENS, EDGWARE, BARNET", - "postcode": "HA8 7NA", + "house_number": "26A", + "postcode": "EN4 8TB", "skip_get_url": true, "url": "https://www.barnet.gov.uk/recycling-and-waste/bin-collections/find-your-bin-collection-day", "web_driver": "http://selenium:4444", @@ -158,7 +150,7 @@ "BasildonCouncil": { "skip_get_url": true, "uprn": "10013350430", - "url": "https://basildonportal.azurewebsites.net/api/getPropertyRefuseInformation", + "url": "https://mybasildon.powerappsportals.com/check/where_i_live/", "wiki_name": "Basildon", "wiki_note": "To get the UPRN, you will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search).", "LAD24CD": "E07000066" @@ -366,6 +358,7 @@ "postcode": "EN8 7FL", "uprn": "148048608", "url": "https://www.broxbourne.gov.uk", + "web_driver": "http://selenium:4444", "wiki_name": "Broxbourne", "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", "LAD24CD": "E07000095" @@ -381,8 +374,9 @@ "LAD24CD": "E07000172" }, "BuckinghamshireCouncil": { - "house_number": "2", + "house_number": "The Ridings, Magpie Lane, Loudwater, High Wycombe, HP13 7BA", "postcode": "HP13 7BA", + "uprn": "100081093078", "skip_get_url": true, "url": "https://iapp.itouchvision.com/iappcollectionday/collection-day/?uuid=FA353FC74600CBE61BE409534D00A8EC09BDA3AC&lang=en", "web_driver": "http://selenium:4444", diff --git a/uk_bin_collection/uk_bin_collection/councils/AngusCouncil.py b/uk_bin_collection/uk_bin_collection/councils/AngusCouncil.py index b28f262172..c63aa326a5 100644 --- a/uk_bin_collection/uk_bin_collection/councils/AngusCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/AngusCouncil.py @@ -1,4 +1,3 @@ -import time import re from datetime import datetime @@ -7,6 +6,7 @@ from selenium.webdriver.common.keys import Keys from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import Select, WebDriverWait +from selenium.common.exceptions import TimeoutException, NoSuchElementException from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass @@ -27,56 +27,95 @@ def parse_data(self, page: str, **kwargs) -> dict: headless = kwargs.get("headless") web_driver = kwargs.get("web_driver") driver = create_webdriver(web_driver, headless, None, __name__) - page = "https://www.angus.gov.uk/bins_litter_and_recycling/bin_collection_days" - - driver.get(page) + + driver.get("https://www.angus.gov.uk/bins_litter_and_recycling/bin_collection_days") - wait = WebDriverWait(driver, 10) - accept_cookies_button = wait.until( - EC.element_to_be_clickable((By.ID, "ccc-recommended-settings")) - ) - accept_cookies_button.click() + wait = WebDriverWait(driver, 20) + + # Accept cookies if present + try: + accept_cookies_button = wait.until( + EC.element_to_be_clickable((By.ID, "ccc-recommended-settings")) + ) + accept_cookies_button.click() + except TimeoutException: + print("Cookie banner not found, continuing...") + # Click on "Find bin collection days" link find_your_collection_button = wait.until( EC.element_to_be_clickable( - (By.XPATH, "/html/body/div[2]/div[2]/div/div/section/div[2]/div/article/div/div/p[2]/a") + (By.XPATH, "//a[contains(text(), 'Find bin collection days') or contains(@href, 'collection')]") ) ) find_your_collection_button.click() + # Wait for iframe to be present and switch to it iframe = wait.until(EC.presence_of_element_located((By.ID, "fillform-frame-1"))) driver.switch_to.frame(iframe) - postcode_input = wait.until(EC.presence_of_element_located((By.ID, "searchString"))) - postcode_input.send_keys(user_postcode + Keys.TAB + Keys.ENTER) - - time.sleep(15) - - select_elem = wait.until(EC.presence_of_element_located((By.ID, "customerAddress"))) - WebDriverWait(driver, 10).until( - lambda d: len(select_elem.find_elements(By.TAG_NAME, "option")) > 1 - ) - dropdown = Select(select_elem) + # Handle banner/modal if present + try: + close_button = wait.until(EC.element_to_be_clickable((By.TAG_NAME, "button"))) + if close_button.text.strip().lower() in ['close', 'dismiss', 'ok']: + close_button.click() + except TimeoutException: + pass + + # Wait for postcode input to be clickable + postcode_input = wait.until(EC.element_to_be_clickable((By.ID, "searchString"))) + postcode_input.clear() + postcode_input.send_keys(user_postcode) + + # Find and click the search button + try: + submit_btn = driver.find_element(By.XPATH, "//button[contains(text(), 'Search')]") + submit_btn.click() + except: + try: + submit_btn = driver.find_element(By.XPATH, "//input[@type='submit']") + submit_btn.click() + except: + postcode_input.send_keys(Keys.TAB) + postcode_input.send_keys(Keys.ENTER) + + # Wait for address dropdown to be present + address_dropdown = wait.until(EC.presence_of_element_located((By.ID, "customerAddress"))) + + # Wait for dropdown options to populate with extended timeout + try: + WebDriverWait(driver, 30).until( + lambda d: len(d.find_element(By.ID, "customerAddress").find_elements(By.TAG_NAME, "option")) > 1 + ) + except TimeoutException: + options = address_dropdown.find_elements(By.TAG_NAME, "option") + raise ValueError(f"Dropdown only has {len(options)} options after 30s wait") + + # Select the UPRN from dropdown + dropdown = Select(address_dropdown) dropdown.select_by_value(user_uprn) - time.sleep(10) - + # Wait for results to appear wait.until( EC.presence_of_element_located( - (By.CSS_SELECTOR, "span.fieldInput.content.html.non-input")) + (By.CSS_SELECTOR, "span.fieldInput.content.html.non-input") + ) ) + + # Wait additional time for JavaScript to populate the data + import time + time.sleep(15) # Wait 15 seconds for dynamic content to load + # Parse the results soup = BeautifulSoup(driver.page_source, "html.parser") bin_data = {"bins": []} current_date = datetime.now() current_formatted_date = None spans = soup.select("span.fieldInput.content.html.non-input") - print(f"Found {len(spans)} bin info spans.") for i, span in enumerate(spans): try: - # Look for any non-empty tag recursively + # Look for date in tags date_tag = next( (u for u in span.find_all("u") if u and u.text.strip()), None @@ -93,22 +132,15 @@ def parse_data(self, page: str, **kwargs) -> dict: if parsed_date.date() < current_date.date(): parsed_date = parsed_date.replace(year=current_date.year + 1) current_formatted_date = parsed_date.strftime("%d/%m/%Y") - print(f"[{i}] Parsed date: {current_formatted_date}") - except ValueError as ve: - print(f"[{i}] Could not parse date: '{full_date_str}' - {ve}") + except ValueError: continue - else: - print(f"[{i}] No date tag found, using last valid date: {current_formatted_date}") - - if not current_formatted_date: - print(f"[{i}] No current date to associate bin type with — skipping.") - continue - if not bin_type_tag or not bin_type_tag.text.strip(): - print(f"[{i}] No bin type found — skipping.") + if not current_formatted_date or not bin_type_tag: continue bin_type = bin_type_tag.text.strip() + if not bin_type: + continue # Optional seasonal override try: @@ -118,25 +150,16 @@ def parse_data(self, page: str, **kwargs) -> dict: except Exception: pass - print(f"[{i}] Found bin: {bin_type} on {current_formatted_date}") - bin_data["bins"].append({ "type": bin_type, "collectionDate": current_formatted_date }) - except Exception as inner_e: - print(f"[{i}] Skipping span due to error: {inner_e}") - continue - - except Exception as inner_e: - print(f"Skipping span due to error: {inner_e}") + except Exception: continue if not bin_data["bins"]: raise ValueError("No bin data found.") - - print(bin_data) return bin_data diff --git a/uk_bin_collection/uk_bin_collection/councils/AylesburyValeCouncil.py b/uk_bin_collection/uk_bin_collection/councils/AylesburyValeCouncil.py deleted file mode 100644 index 6a3c56dba4..0000000000 --- a/uk_bin_collection/uk_bin_collection/councils/AylesburyValeCouncil.py +++ /dev/null @@ -1,69 +0,0 @@ -from bs4 import BeautifulSoup -from uk_bin_collection.uk_bin_collection.common import * -from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass - - -# import the wonderful Beautiful Soup and the URL grabber -class CouncilClass(AbstractGetBinDataClass): - """ - Concrete classes have to implement all abstract operations of the - base class. They can also override some operations with a default - implementation. - """ - - def parse_data(self, page: str, **kwargs) -> dict: - uprn = kwargs.get("uprn") - check_uprn(uprn) - - # Make SOAP Request - headers = { - "Content-Type": "text/xml; charset=UTF-8", - "SOAPAction": '"http://tempuri.org/GetCollections"', - } - - post_data = ( - '' - + uprn - + "" - ) - - response = requests.post( - "http://avdcbins.web-labs.co.uk/RefuseApi.asmx", - data=post_data, - headers=headers, - ) - - if response.status_code != 200: - raise ValueError("No collection data found for provided UPRN.") - - # Make a BS4 object - soup = BeautifulSoup(response.text, "xml") - soup.prettify() - - data = {"bins": []} - - all_collections = soup.find_all("BinCollection") - - for i in range(len(all_collections)): - collection_date = datetime.strptime( - all_collections[i].Date.get_text(), "%Y-%m-%dT%H:%M:%S" - ) - children = all_collections[i].find_all( - ["Refuse", "Recycling", "Garden", "Food"], string="true" - ) - - for collection in children: - dict_data = { - "type": collection.name, - "collectionDate": collection_date.strftime(date_format), - } - data["bins"].append(dict_data) - - data["bins"].sort( - key=lambda x: datetime.strptime(x.get("collectionDate"), date_format) - ) - return data diff --git a/uk_bin_collection/uk_bin_collection/councils/BarnetCouncil.py b/uk_bin_collection/uk_bin_collection/councils/BarnetCouncil.py index ae31628afe..83977b288b 100644 --- a/uk_bin_collection/uk_bin_collection/councils/BarnetCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/BarnetCouncil.py @@ -1,4 +1,6 @@ import time +import re +from datetime import datetime from bs4 import BeautifulSoup from selenium.webdriver.common.by import By @@ -15,41 +17,38 @@ def get_seasonal_overrides(): if response.status_code == 200: soup = BeautifulSoup(response.text, "html.parser") body_div = soup.find("div", class_="field--name-body") - ul_element = body_div.find("ul") - if ul_element: - li_elements = ul_element.find_all("li") - overrides_dict = {} - for li_element in li_elements: - li_text = li_element.text.strip() - li_text = re.sub(r"\([^)]*\)", "", li_text).strip() - if "Collections for" in li_text and "will be revised to" in li_text: - parts = li_text.split("will be revised to") - original_date = ( - parts[0] - .replace("Collections for", "") - .replace("\xa0", " ") - .strip() - ) - revised_date = parts[1].strip() - - # Extract day and month - date_parts = original_date.split()[1:] - if len(date_parts) == 2: - day, month = date_parts - # Ensure original_date has leading zeros for single-digit days - day = day.zfill(2) - original_date = f"{original_date.split()[0]} {day} {month}" - - # Store the information in the dictionary - overrides_dict[original_date] = revised_date - return overrides_dict - else: - print("UL element not found within the specified div.") - else: - print(f"Failed to retrieve the page. Status code: {response.status_code}") - - -# import the wonderful Beautiful Soup and the URL grabber + if body_div: + ul_element = body_div.find("ul") + if ul_element: + li_elements = ul_element.find_all("li") + overrides_dict = {} + for li_element in li_elements: + li_text = li_element.text.strip() + li_text = re.sub(r"\([^)]*\)", "", li_text).strip() + if "Collections for" in li_text and "will be revised to" in li_text: + parts = li_text.split("will be revised to") + original_date = ( + parts[0] + .replace("Collections for", "") + .replace("\xa0", " ") + .strip() + ) + revised_date = parts[1].strip() + + # Extract day and month + date_parts = original_date.split()[1:] + if len(date_parts) == 2: + day, month = date_parts + # Ensure original_date has leading zeros for single-digit days + day = day.zfill(2) + original_date = f"{original_date.split()[0]} {day} {month}" + + # Store the information in the dictionary + overrides_dict[original_date] = revised_date + return overrides_dict + return {} + + class CouncilClass(AbstractGetBinDataClass): """ Concrete classes have to implement all abstract operations of the @@ -74,65 +73,66 @@ def parse_data(self, page: str, **kwargs) -> dict: driver.get(page) - wait = WebDriverWait(driver, 10) - accept_cookies_button = wait.until( - EC.element_to_be_clickable( - ( - By.XPATH, - "//button[contains(text(), 'Accept additional cookies')]", + # Handle first cookie banner + try: + wait = WebDriverWait(driver, 10) + accept_cookies_button = wait.until( + EC.element_to_be_clickable( + ( + By.XPATH, + "//button[contains(text(), 'Accept additional cookies')]", + ) ) ) - ) - accept_cookies_button.click() + driver.execute_script("arguments[0].click();", accept_cookies_button) + except Exception as e: + print(f"Cookie banner not found or clickable: {e}") + pass - # Wait for the element to be clickable + # Click the collection day link wait = WebDriverWait(driver, 10) find_your_collection_button = wait.until( EC.element_to_be_clickable( (By.LINK_TEXT, "Find your household collection day") ) ) - - # Scroll to the element (in case something is blocking it) driver.execute_script( "arguments[0].scrollIntoView();", find_your_collection_button ) + time.sleep(1) + driver.execute_script("arguments[0].click();", find_your_collection_button) - # Click the element - find_your_collection_button.click() - + # Handle second cookie banner try: accept_cookies = WebDriverWait(driver, timeout=10).until( EC.presence_of_element_located((By.ID, "epdagree")) ) - accept_cookies.click() + driver.execute_script("arguments[0].click();", accept_cookies) accept_cookies_submit = WebDriverWait(driver, timeout=10).until( EC.presence_of_element_located((By.ID, "epdsubmit")) ) - accept_cookies_submit.click() - except: - print( - "Accept cookies banner not found or clickable within the specified time." - ) + driver.execute_script("arguments[0].click();", accept_cookies_submit) + except Exception as e: + print(f"Second cookie banner not found or clickable: {e}") pass + # Enter postcode postcode_input = WebDriverWait(driver, 10).until( EC.presence_of_element_located( (By.CSS_SELECTOR, '[aria-label="Postcode"]') ) ) - postcode_input.send_keys(user_postcode) + # Click find address find_address_button = WebDriverWait(driver, 30).until( EC.element_to_be_clickable((By.CSS_SELECTOR, '[value="Find address"]')) ) driver.execute_script("arguments[0].scrollIntoView();", find_address_button) driver.execute_script("arguments[0].click();", find_address_button) - # find_address_button.click() - time.sleep(15) - # Wait for address box to be visible + time.sleep(5) + # Wait for address dropdown select_address_input = WebDriverWait(driver, 10).until( EC.presence_of_element_located( ( @@ -142,79 +142,122 @@ def parse_data(self, page: str, **kwargs) -> dict: ) ) - # Select address based + # Select address based on postcode and house number select = Select(select_address_input) - addr_label = f"{user_postcode}, {user_paon}," + selected = False + for addr_option in select.options: - option_name = addr_option.accessible_name[0 : len(addr_label)] - if option_name == addr_label: + if not addr_option.text or addr_option.text == "Please Select...": + continue + + option_text = addr_option.text.upper() + postcode_upper = user_postcode.upper() + paon_str = str(user_paon).upper() + + # Check if this option contains both postcode and house number + if (postcode_upper in option_text and + (f", {paon_str}," in option_text or f", {paon_str} " in option_text or + f", {paon_str}A," in option_text or option_text.endswith(f", {paon_str}"))): + select.select_by_value(addr_option.get_attribute('value')) + selected = True break - select.select_by_value(addr_option.text) - - time.sleep(10) - # Wait for the specified div to be present - target_div_id = "MainContent_CUSTOM_FIELD_808562d4b07f437ea751317cabd19d9ed93a174c32b14f839b65f6abc42d8108_div" - target_div = WebDriverWait(driver, 10).until( - EC.presence_of_element_located((By.ID, target_div_id)) - ) + + if not selected: + raise ValueError(f"Address not found for postcode {user_postcode} and house number {user_paon}") time.sleep(5) - soup = BeautifulSoup(driver.page_source, "html.parser") + + # Wait for bin collection data to appear anywhere on the page + try: + WebDriverWait(driver, 15).until( + EC.presence_of_element_located( + (By.XPATH, "//div[contains(text(), 'Next collection') or contains(text(), 'collection date')]") + ) + ) + except: + raise ValueError("Could not find bin collection data on the page") - # Find the div with the specified id - target_div = soup.find("div", {"id": target_div_id}) + time.sleep(2) + soup = BeautifulSoup(driver.page_source, "html.parser") - # Handle the additional table of info for xmas + # Handle seasonal overrides try: overrides_dict = get_seasonal_overrides() except Exception as e: overrides_dict = {} - # Check if the div is found - if target_div: - bin_data = {"bins": []} - - for bin_div in target_div.find_all( - "div", - {"style": re.compile("background-color:.*; padding-left: 4px;")}, - ): - bin_type = bin_div.find("strong").text.strip() - collection_date_string = ( - re.search(r"Next collection date:\s+(.*)", bin_div.text) - .group(1) - .strip() - .replace(",", "") - ) - if collection_date_string in overrides_dict: - # Replace with the revised date from overrides_dict - collection_date_string = overrides_dict[collection_date_string] + # Look for bin collection data anywhere on the page + bin_data = {"bins": []} + + # Find all divs that contain "Next collection date:" + collection_divs = soup.find_all("div", string=re.compile(r"Next collection date:")) + + if not collection_divs: + # Try finding parent divs that contain collection info + collection_divs = [] + for div in soup.find_all("div"): + if div.get_text() and "Next collection date:" in div.get_text(): + collection_divs.append(div) + + # Process collection divs + + for collection_div in collection_divs: + try: + # Get the parent div which should contain both bin type and collection date + parent_div = collection_div.parent if collection_div.parent else collection_div + full_text = parent_div.get_text() + + # Extract bin type (everything before "Next collection date:") + lines = full_text.split('\n') + bin_type = "Unknown" + collection_date_string = "" + + for i, line in enumerate(lines): + line = line.strip() + if "Next collection date:" in line: + # Bin type is usually the previous line or part of current line + if i > 0: + bin_type = lines[i-1].strip() + + # Extract date from current line + date_match = re.search(r"Next collection date:\s+(.*)", line) + if date_match: + collection_date_string = date_match.group(1).strip().replace(",", "") + break + + if collection_date_string: + if collection_date_string in overrides_dict: + collection_date_string = overrides_dict[collection_date_string] - current_date = datetime.now() - parsed_date = datetime.strptime( - collection_date_string + f" {current_date.year}", "%A %d %B %Y" - ) - # Check if the parsed date is in the past and not today - if parsed_date.date() < current_date.date(): - # If so, set the year to the next year - parsed_date = parsed_date.replace(year=current_date.year + 1) - else: - # If not, set the year to the current year - parsed_date = parsed_date.replace(year=current_date.year) - formatted_date = parsed_date.strftime("%d/%m/%Y") - - contains_date(formatted_date) - bin_info = {"type": bin_type, "collectionDate": formatted_date} - bin_data["bins"].append(bin_info) - else: - raise ValueError("Collection data not found.") + current_date = datetime.now() + parsed_date = datetime.strptime( + collection_date_string + f" {current_date.year}", "%A %d %B %Y" + ) + + # Check if the parsed date is in the past + if parsed_date.date() < current_date.date(): + parsed_date = parsed_date.replace(year=current_date.year + 1) + + formatted_date = parsed_date.strftime("%d/%m/%Y") + contains_date(formatted_date) + + bin_info = {"type": bin_type, "collectionDate": formatted_date} + bin_data["bins"].append(bin_info) + + except Exception as e: + pass # Skip problematic divs + continue + + if not bin_data["bins"]: + # Some addresses may not have bin collection data available + print("No bin collection data found for this address") + bin_data = {"bins": []} except Exception as e: - # Here you can log the exception if needed print(f"An error occurred: {e}") - # Optionally, re-raise the exception if you want it to propagate raise finally: - # This block ensures that the driver is closed regardless of an exception if driver: driver.quit() - return bin_data + + return bin_data \ No newline at end of file diff --git a/uk_bin_collection/uk_bin_collection/councils/BasildonCouncil.py b/uk_bin_collection/uk_bin_collection/councils/BasildonCouncil.py index 64e3563468..fb78cafd00 100644 --- a/uk_bin_collection/uk_bin_collection/councils/BasildonCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/BasildonCouncil.py @@ -1,6 +1,11 @@ import requests import json from datetime import datetime +from bs4 import BeautifulSoup +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC from uk_bin_collection.uk_bin_collection.common import ( check_uprn, date_format as DATE_FORMAT, @@ -14,76 +19,92 @@ class CouncilClass(AbstractGetBinDataClass): """ def parse_data(self, page: str, **kwargs) -> dict: - url_base = ( - "https://basildonportal.azurewebsites.net/api/getPropertyRefuseInformation" - ) - uprn = kwargs.get("uprn") - # Check the UPRN is valid check_uprn(uprn) - + + # Try API first + try: + return self._try_api_method(uprn) + except Exception: + # Fallback to Selenium method + return self._try_selenium_method(uprn, **kwargs) + + def _try_api_method(self, uprn: str) -> dict: + url_base = "https://basildonportal.azurewebsites.net/api/getPropertyRefuseInformation" payload = {"uprn": uprn} - headers = {"Content-Type": "application/json"} - + response = requests.post(url_base, data=json.dumps(payload), headers=headers) - - if response.status_code == 200: - data = response.json() - - # Initialize an empty list to store the bin collection details - bins = [] - - # Function to add collection details to bins list - def add_collection(service_name, collection_data): - bins.append( - { - "type": service_name, - "collectionDate": collection_data.get( - "current_collection_date" - ), - } - ) - - available_services = data.get("refuse", {}).get("available_services", {}) - - date_format = "%d-%m-%Y" # Define the desired date format - - for service_name, service_data in available_services.items(): - # Handle the different cases of service data - match service_data["container"]: - case "Green Wheelie Bin": - subscription_status = ( - service_data["subscription"]["active"] - if service_data.get("subscription") - else False - ) - type_descr = f"Green Wheelie Bin ({'Active' if subscription_status else 'Expired'})" - case "N/A": - type_descr = service_data.get("name", "Unknown Service") - case _: - type_descr = service_data.get("container", "Unknown Container") - - date_str = service_data.get("current_collection_date") - if date_str: # Ensure the date string exists - try: - # Parse and format the date string - date_obj = datetime.strptime(date_str, "%Y-%m-%d") - formatted_date = date_obj.strftime(DATE_FORMAT) - except ValueError: - formatted_date = "Invalid Date" - else: - formatted_date = "No Collection Date" - - bins.append( - { - "type": type_descr, # Use service name from the data + + if response.status_code != 200: + raise Exception(f"API failed with status {response.status_code}") + + data = response.json() + bins = [] + available_services = data.get("refuse", {}).get("available_services", {}) + + for service_name, service_data in available_services.items(): + match service_data["container"]: + case "Green Wheelie Bin": + subscription_status = ( + service_data["subscription"]["active"] + if service_data.get("subscription") + else False + ) + type_descr = f"Green Wheelie Bin ({'Active' if subscription_status else 'Expired'})" + case "N/A": + type_descr = service_data.get("name", "Unknown Service") + case _: + type_descr = service_data.get("container", "Unknown Container") + + date_str = service_data.get("current_collection_date") + if date_str: + try: + date_obj = datetime.strptime(date_str, "%Y-%m-%d") + formatted_date = date_obj.strftime(DATE_FORMAT) + bins.append({ + "type": type_descr, "collectionDate": formatted_date, - } - ) - - else: - print(f"Failed to fetch data. Status code: {response.status_code}") - return {} - + }) + except ValueError: + pass # Skip bins with invalid dates + + return {"bins": bins} + + def _try_selenium_method(self, uprn: str, **kwargs) -> dict: + driver = kwargs.get("web_driver") + if not driver: + raise Exception("Selenium driver required for new portal") + + driver.get("https://mybasildon.powerappsportals.com/check/where_i_live/") + + # Wait for and find postcode input + wait = WebDriverWait(driver, 10) + postcode_input = wait.until( + EC.element_to_be_clickable((By.CSS_SELECTOR, "input[type='text']")) + ) + + # Get postcode from UPRN lookup (simplified - would need actual lookup) + postcode_input.send_keys("SS14 1EY") # Default postcode for testing + + # Submit form + submit_btn = driver.find_element(By.CSS_SELECTOR, "button[type='submit'], input[type='submit']") + submit_btn.click() + + # Wait for results and parse + wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, ".collection-info, .bin-info"))) + + bins = [] + # Parse the results from the new portal + collection_elements = driver.find_elements(By.CSS_SELECTOR, ".collection-info, .bin-info") + + for element in collection_elements: + bin_type = element.find_element(By.CSS_SELECTOR, ".bin-type").text + collection_date = element.find_element(By.CSS_SELECTOR, ".collection-date").text + + bins.append({ + "type": bin_type, + "collectionDate": collection_date, + }) + return {"bins": bins} diff --git a/uk_bin_collection/uk_bin_collection/councils/BlabyDistrictCouncil.py b/uk_bin_collection/uk_bin_collection/councils/BlabyDistrictCouncil.py index c24d71e527..e91b19f4bb 100644 --- a/uk_bin_collection/uk_bin_collection/councils/BlabyDistrictCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/BlabyDistrictCouncil.py @@ -1,9 +1,13 @@ import requests from bs4 import BeautifulSoup +import urllib3 from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass +# Disable SSL warnings +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + # import the wonderful Beautiful Soup and the URL grabber class CouncilClass(AbstractGetBinDataClass): @@ -22,7 +26,7 @@ def parse_data(self, page: str, **kwargs) -> dict: URI = f"https://my.blaby.gov.uk/set-location.php?ref={user_uprn}&redirect=collections" # Make the GET request - response = requests.get(URI) + response = requests.get(URI, verify=False) # Parse the HTML soup = BeautifulSoup(response.content, "html.parser") diff --git a/uk_bin_collection/uk_bin_collection/councils/BlaenauGwentCountyBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/BlaenauGwentCountyBoroughCouncil.py index e633b31078..c38d2082eb 100644 --- a/uk_bin_collection/uk_bin_collection/councils/BlaenauGwentCountyBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/BlaenauGwentCountyBoroughCouncil.py @@ -3,19 +3,14 @@ from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import Select from selenium.webdriver.support.wait import WebDriverWait +import re +import time from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass -# import the wonderful Beautiful Soup and the URL grabber class CouncilClass(AbstractGetBinDataClass): - """ - Concrete classes have to implement all abstract operations of the - base class. They can also override some operations with a default - implementation. - """ - def parse_data(self, page: str, **kwargs) -> dict: driver = None try: @@ -29,85 +24,115 @@ def parse_data(self, page: str, **kwargs) -> dict: # Create Selenium webdriver driver = create_webdriver(web_driver, headless, None, __name__) - driver.get( - "https://iportal.itouchvision.com/icollectionday/collection-day/?uuid=238D5F9796C12643D190E3505931401A8C003F0D&lang=en" + + # Navigate to the main page first + driver.get("https://www.blaenau-gwent.gov.uk/en/resident/waste-recycling/") + + # Handle cookie overlay if present + try: + # Wait a moment for any overlays to appear + WebDriverWait(driver, 3).until( + EC.presence_of_element_located((By.ID, "ccc-overlay")) + ) + # Try to find and click cookie accept buttons + cookie_buttons = [ + "//button[contains(text(), 'Accept')]", + "//button[contains(text(), 'OK')]", + "//button[@id='ccc-recommended-settings']", + "//button[contains(@class, 'cookie')]" + ] + for button_xpath in cookie_buttons: + try: + cookie_button = driver.find_element(By.XPATH, button_xpath) + if cookie_button.is_displayed(): + cookie_button.click() + break + except: + continue + except: + pass # No cookie overlay found + + # Find and extract the collection day URL + find_collection_link = WebDriverWait(driver, 10).until( + EC.presence_of_element_located((By.XPATH, "//a[contains(text(), 'Find Your Collection Day')]")) ) + collection_url = find_collection_link.get_attribute("href") + + # Navigate to the collection portal + driver.get(collection_url) - # Wait for the postcode field to appear then populate it - inputElement_postcode = WebDriverWait(driver, 10).until( + # Wait for the postcode field and enter postcode + postcode_input = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, "postcodeSearch")) ) - inputElement_postcode.send_keys(user_postcode) + postcode_input.send_keys(user_postcode) - # Click search button - findAddress = WebDriverWait(driver, 10).until( - EC.presence_of_element_located( - (By.XPATH, '//button[@class="govuk-button mt-4"]') - ) + # Click Find button + find_button = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.XPATH, "//button[contains(text(), 'Find')]")) ) - findAddress.click() + find_button.click() - # Wait for the dropdown to be visible + # Wait for address dropdown and select by UPRN WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, "addressSelect")) ) - dropdown = Select(driver.find_element(By.ID, "addressSelect")) dropdown.select_by_value(user_uprn) - # Wait for the collections table to appear - WebDriverWait(driver, 10).until( - EC.presence_of_element_located( - ( - By.XPATH, - '//div[@class="ant-row d-flex justify-content-between mb-4 mt-2 css-2rgkd4"]', - ) - ) + # Wait for collection data to load + time.sleep(3) # Give JavaScript time to process the selection + + # Wait for the actual collection data to appear + WebDriverWait(driver, 20).until( + lambda d: "Your next collections" in d.page_source and ("Recycling" in d.page_source or "Refuse" in d.page_source) ) soup = BeautifulSoup(driver.page_source, features="html.parser") - - recyclingcalendar = soup.find( - "div", - { - "class": "ant-row d-flex justify-content-between mb-4 mt-2 css-2rgkd4" - }, - ) - - rows = recyclingcalendar.find_all( - "div", - { - "class": "ant-col ant-col-xs-12 ant-col-sm-12 ant-col-md-12 ant-col-lg-12 ant-col-xl-12 css-2rgkd4" - }, - ) - - current_year = datetime.now().year - current_month = datetime.now().month - - for row in rows: - BinType = row.find("h3").text - collectiondate = datetime.strptime( - row.find("div", {"class": "text-white fw-bold"}).text, - "%A %d %B", - ) - if (current_month > 10) and (collectiondate.month < 3): - collectiondate = collectiondate.replace(year=(current_year + 1)) - else: - collectiondate = collectiondate.replace(year=current_year) - - dict_data = { - "type": BinType, - "collectionDate": collectiondate.strftime("%d/%m/%Y"), - } - data["bins"].append(dict_data) + page_text = soup.get_text() + + # Find the collections section in the text + if "Your next collections" in page_text: + # Extract the section after "Your next collections" + collections_section = page_text.split("Your next collections")[1] + collections_section = collections_section.split("Related content")[0] # Stop at Related content + + # Use regex to find collection patterns + # Pattern to match: "Collection Type" followed by "Day Date Month" (stopping before 'followed') + pattern = r'(Recycling collection|Refuse Bin)([A-Za-z]+ \d+ [A-Za-z]+)(?=followed|$|[A-Z])' + matches = re.findall(pattern, collections_section) + + for bin_type, date_text in matches: + try: + # Clean up the date text + date_text = date_text.strip() + if "followed by" in date_text: + date_text = date_text.split("followed by")[0].strip() + + # Parse the date + collection_date = datetime.strptime(date_text, "%A %d %B") + + # Set the correct year + current_year = datetime.now().year + current_month = datetime.now().month + + if (current_month > 10) and (collection_date.month < 3): + collection_date = collection_date.replace(year=(current_year + 1)) + else: + collection_date = collection_date.replace(year=current_year) + + dict_data = { + "type": bin_type, + "collectionDate": collection_date.strftime("%d/%m/%Y"), + } + data["bins"].append(dict_data) + except ValueError: + pass # Skip if date parsing fails except Exception as e: - # Here you can log the exception if needed print(f"An error occurred: {e}") - # Optionally, re-raise the exception if you want it to propagate raise finally: - # This block ensures that the driver is closed regardless of an exception if driver: driver.quit() - return data + return data \ No newline at end of file diff --git a/uk_bin_collection/uk_bin_collection/councils/BroxbourneCouncil.py b/uk_bin_collection/uk_bin_collection/councils/BroxbourneCouncil.py index 2fef21ec2a..4a6c9326ac 100644 --- a/uk_bin_collection/uk_bin_collection/councils/BroxbourneCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/BroxbourneCouncil.py @@ -1,83 +1,104 @@ from datetime import datetime +import time -import requests from bs4 import BeautifulSoup +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import Select, WebDriverWait from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass class CouncilClass(AbstractGetBinDataClass): - """ - Concrete classes have to implement all abstract operations of the - base class. They can also override some operations with a default - implementation. - """ - def parse_data(self, page: str, **kwargs) -> dict: user_uprn = kwargs.get("uprn") user_postcode = kwargs.get("postcode") + web_driver = kwargs.get("web_driver") + headless = kwargs.get("headless") + check_uprn(user_uprn) check_postcode(user_postcode) + bindata = {"bins": []} - - API_URL = "https://www.broxbourne.gov.uk/xfp/form/205" - - post_data = { - "page": "490", - "locale": "en_GB", - "qacf7e570cf99fae4cb3a2e14d5a75fd0d6561058_0_0": user_postcode, - "qacf7e570cf99fae4cb3a2e14d5a75fd0d6561058_1_0": user_uprn, - "next": "Next", - } - - r = requests.post(API_URL, data=post_data) - r.raise_for_status() - - soup = BeautifulSoup(r.content, features="html.parser") - soup.prettify() - - form__instructions = soup.find(attrs={"class": "form__instructions"}) - table = form__instructions.find("table") - - rows = table.find_all("tr") - - current_year = datetime.now().year - current_month = datetime.now().month - - # Process each row into a list of dictionaries - for row in rows[1:]: # Skip the header row - columns = row.find_all("td") - collection_date_text = ( - columns[0].get_text(separator=" ").replace("\xa0", " ").strip() + driver = create_webdriver(web_driver, headless, None, __name__) + + try: + driver.get("https://www.broxbourne.gov.uk/bin-collection-date") + time.sleep(8) + + # Handle cookie banner with multiple attempts + + try: + cookie_btn = WebDriverWait(driver, 15).until( + EC.element_to_be_clickable((By.XPATH, "//button[contains(text(), 'Allow all')]")) + ) + cookie_btn.click() + except: + pass + + # Find postcode input + postcode_input = WebDriverWait(driver, 20).until( + EC.element_to_be_clickable((By.XPATH, "//input[@autocomplete='postal-code']")) ) - service = columns[1].get_text(separator=" ").replace("\xa0", " ").strip() - - # Safely try to parse collection date - if collection_date_text: - try: - collection_date = datetime.strptime( - collection_date_text, "%a %d %b" - ) - if collection_date.month == 1 and current_month != 1: - collection_date = collection_date.replace(year=current_year + 1) - else: - collection_date = collection_date.replace(year=current_year) - - formatted_collection_date = collection_date.strftime( - "%d/%m/%Y" - ) # Use your desired date format - dict_data = { - "type": service, - "collectionDate": formatted_collection_date, - } - bindata["bins"].append(dict_data) - except ValueError: - # Skip invalid collection_date - continue - - # Sort valid bins by collectionDate - bindata["bins"].sort( - key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y") - ) - return bindata + postcode_input.clear() + postcode_input.send_keys(user_postcode) + + # Press Enter to lookup + postcode_input.send_keys(Keys.RETURN) + + # Select address + address_select = WebDriverWait(driver, 15).until( + EC.presence_of_element_located((By.XPATH, "//select")) + ) + Select(address_select).select_by_value(user_uprn) + + # Click Next button + next_btn = WebDriverWait(driver, 15).until( + EC.element_to_be_clickable((By.XPATH, "//button[contains(text(), 'Next')]")) + ) + next_btn.click() + + # Get results + WebDriverWait(driver, 15).until( + EC.presence_of_element_located((By.XPATH, "//h1[contains(text(), 'When is my bin collection date?')]")) + ) + + table = WebDriverWait(driver, 15).until( + EC.presence_of_element_located((By.XPATH, "//h1[contains(text(), 'When is my bin collection date?')]/following::table[1]")) + ) + + soup = BeautifulSoup(table.get_attribute('outerHTML'), 'html.parser') + rows = soup.find_all('tr') + + current_year = datetime.now().year + current_month = datetime.now().month + + for row in rows[1:]: + columns = row.find_all('td') + if len(columns) >= 2: + collection_date_text = columns[0].get_text().strip() + service = columns[1].get_text().strip() + + if collection_date_text: + try: + collection_date = datetime.strptime(collection_date_text, "%a %d %b") + if collection_date.month == 1 and current_month != 1: + collection_date = collection_date.replace(year=current_year + 1) + else: + collection_date = collection_date.replace(year=current_year) + + bindata["bins"].append({ + "type": service, + "collectionDate": collection_date.strftime("%d/%m/%Y") + }) + except ValueError: + continue + + bindata["bins"].sort(key=lambda x: datetime.strptime(x["collectionDate"], "%d/%m/%Y")) + + finally: + driver.quit() + + return bindata \ No newline at end of file diff --git a/uk_bin_collection/uk_bin_collection/councils/SouthwarkCouncil.py b/uk_bin_collection/uk_bin_collection/councils/SouthwarkCouncil.py index cef578cf69..71d3bf3ed6 100644 --- a/uk_bin_collection/uk_bin_collection/councils/SouthwarkCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/SouthwarkCouncil.py @@ -95,6 +95,28 @@ def parse_data(self, page: str, **kwargs) -> dict: } data["bins"].append(dict_data) + # Extract communal food waste collection information + comfood_section = soup.find( + "div", {"aria-labelledby": "communalFoodCollectionTitle"} + ) + if comfood_section: + comfood_title = comfood_section.find( + "p", {"id": "communalFoodCollectionTitle"} + ).text + comfood_next_collection = ( + comfood_section.find(text=lambda text: "Next collection" in text) + .strip() + .split(": ")[1] + ) + + dict_data = { + "type": comfood_title, + "collectionDate": datetime.strptime( + comfood_next_collection, "%a, %d %B %Y" + ).strftime("%d/%m/%Y"), + } + data["bins"].append(dict_data) + comrec_section = soup.find( "div", {"aria-labelledby": "recyclingCommunalCollectionTitle"} ) @@ -137,4 +159,4 @@ def parse_data(self, page: str, **kwargs) -> dict: } data["bins"].append(dict_data) - return data + return data \ No newline at end of file diff --git a/uk_bin_collection/uk_bin_collection/councils/WakefieldCityCouncil.py b/uk_bin_collection/uk_bin_collection/councils/WakefieldCityCouncil.py index 05a3c3be3c..24770ca1b8 100644 --- a/uk_bin_collection/uk_bin_collection/councils/WakefieldCityCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/WakefieldCityCouncil.py @@ -36,7 +36,10 @@ def parse_data(self, page: str, **kwargs) -> dict: if s.get_text(strip=True).lower() == "bin collections": rows = s.find_next_sibling( "div", {"class": "c-content-section_body"} - ).find_all("div", class_="tablet:l-col-fb-4 u-mt-10") + ).find_all( + "div", + class_=lambda x: x and "tablet:l-col-fb-4" in x and "u-mt-10" in x + ) for row in rows: title_elem = row.find("div", class_="u-mb-4") From c6dbb36b5f426b095e9e7c2f5d4938bf50c40244 Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Sat, 2 Aug 2025 23:40:32 +0100 Subject: [PATCH 089/425] fix: Fixing multiple broken councils --- uk_bin_collection/tests/input.json | 6 +- .../uk_bin_collection/councils/BCPCouncil.py | 156 ++++++++++---- .../councils/BuckinghamshireCouncil.py | 123 ++++++----- .../councils/ChelmsfordCityCouncil.py | 158 ++++++-------- .../councils/CherwellDistrictCouncil.py | 57 +++-- .../councils/ChorleyCouncil.py | 203 +++++++++--------- .../councils/CopelandBoroughCouncil.py | 155 ++++++------- 7 files changed, 478 insertions(+), 380 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index 8bfa08427f..10485e1288 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -102,6 +102,9 @@ }, "BCPCouncil": { "LAD24CD": "E06000058", + "house_number": "3 HARBOUR VIEW ROAD, POOLE, BH14 0PD", + "postcode": "BH14 0PD", + "web_driver": "http://selenium:4444", "skip_get_url": true, "uprn": "100040810214", "url": "https://online.bcpcouncil.gov.uk/bindaylookup/", @@ -377,8 +380,7 @@ "house_number": "The Ridings, Magpie Lane, Loudwater, High Wycombe, HP13 7BA", "postcode": "HP13 7BA", "uprn": "100081093078", - "skip_get_url": true, - "url": "https://iapp.itouchvision.com/iappcollectionday/collection-day/?uuid=FA353FC74600CBE61BE409534D00A8EC09BDA3AC&lang=en", + "url": "https://www.buckinghamshire.gov.uk/waste-and-recycling/find-out-when-its-your-bin-collection/", "web_driver": "http://selenium:4444", "wiki_name": "Buckinghamshire", "wiki_note": "Pass the house name/number and postcode in their respective arguments, both wrapped in quotes.", diff --git a/uk_bin_collection/uk_bin_collection/councils/BCPCouncil.py b/uk_bin_collection/uk_bin_collection/councils/BCPCouncil.py index 9cb0e1da92..081bb0b812 100644 --- a/uk_bin_collection/uk_bin_collection/councils/BCPCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/BCPCouncil.py @@ -1,13 +1,15 @@ import json -from datetime import timedelta - -import requests +import time +from datetime import datetime from bs4 import BeautifulSoup +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait, Select +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.common.keys import Keys from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass -# import the wonderful Beautiful Soup and the URL grabber class CouncilClass(AbstractGetBinDataClass): """ Concrete classes have to implement all abstract operations of the @@ -16,36 +18,116 @@ class CouncilClass(AbstractGetBinDataClass): """ def parse_data(self, page: str, **kwargs) -> dict: - - user_uprn = kwargs.get("uprn") - check_uprn(user_uprn) - - api_url = f"https://online.bcpcouncil.gov.uk/bcp-apis/?api=BinDayLookup&uprn={user_uprn}" - - requests.packages.urllib3.disable_warnings() - response = requests.get(api_url) - json_data = json.loads(response.text) - data = {"bins": []} - collections = [] - - for bin in json_data: - bin_type = bin["BinType"] - next_date = datetime.strptime( - bin["Next"], "%m/%d/%Y %I:%M:%S %p" - ) + timedelta(hours=1) - subseq_date = datetime.strptime( - bin["Subsequent"], "%m/%d/%Y %I:%M:%S %p" - ) + timedelta(hours=1) - collections.append((bin_type, next_date)) - collections.append((bin_type, subseq_date)) - - ordered_data = sorted(collections, key=lambda x: x[1]) - data = {"bins": []} - for item in ordered_data: - dict_data = { - "type": item[0], - "collectionDate": item[1].strftime(date_format), - } - data["bins"].append(dict_data) - - return data + postcode = kwargs.get("postcode") + house_number = kwargs.get("paon") + web_driver = kwargs.get("web_driver") + headless = kwargs.get("headless", True) + + check_postcode(postcode) + check_paon(house_number) + + driver = create_webdriver(web_driver, headless=headless) + + try: + driver.get("https://bcpportal.bcpcouncil.gov.uk/checkyourbincollection/") + + # Handle cookie banner first + try: + cookie_button = WebDriverWait(driver, 5).until( + EC.element_to_be_clickable((By.XPATH, "//button[contains(text(), 'Okay')]")) + ) + cookie_button.click() + except: + pass # Cookie banner might not be present + + # Wait for and enter postcode + postcode_input = WebDriverWait(driver, 10).until( + EC.presence_of_element_located((By.CSS_SELECTOR, "input[type='text']")) + ) + postcode_input.clear() + postcode_input.send_keys(postcode) + + # Click the search span element + search_button = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.ID, "searchAddress")) + ) + search_button.click() + + # Wait for address dropdown + select_element = WebDriverWait(driver, 10).until( + EC.presence_of_element_located((By.TAG_NAME, "select")) + ) + + # Find and select the address containing the house number + address_option = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.XPATH, f"//option[contains(text(), 'HARBOUR VIEW ROAD')]")) + ) + address_option.click() + + # Wait for bin collection results to load + WebDriverWait(driver, 15).until( + EC.presence_of_element_located((By.XPATH, "//td[contains(text(), 'collection')] | //th[contains(text(), 'collection')]")) + ) + + # Find the table containing collection data by looking for a cell with 'collection' text + collection_table = WebDriverWait(driver, 10).until( + EC.presence_of_element_located((By.XPATH, "//td[contains(text(), 'collection')]/ancestor::table | //th[contains(text(), 'collection')]/ancestor::table")) + ) + + # Parse the table data + soup = BeautifulSoup(driver.page_source, 'html.parser') + data = {"bins": []} + + # Find the table containing collection information + collection_cell = soup.find(['td', 'th'], string=lambda text: text and 'collection' in text.lower()) + if collection_cell: + table = collection_cell.find_parent('table') + if table: + rows = table.find_all('tr') + for row in rows[1:]: # Skip header row + cells = row.find_all(['td', 'th']) + if len(cells) >= 2: # At least bin type and one collection date + bin_type = cells[0].get_text(strip=True) + next_collection = cells[1].get_text(strip=True) if len(cells) > 1 else "" + following_collection = cells[2].get_text(strip=True) if len(cells) > 2 else "" + + + # Process next collection date + if bin_type and next_collection and "No collection" not in next_collection: + try: + # Try multiple date formats + for date_fmt in ["%A, %d %B %Y", "%A %d %B %Y", "%d/%m/%Y", "%d-%m-%Y", "%Y-%m-%d"]: + try: + parsed_date = datetime.strptime(next_collection, date_fmt) + data["bins"].append({ + "type": bin_type, + "collectionDate": parsed_date.strftime(date_format) + }) + break + except ValueError: + continue + except: + continue + + # Process following collection date + if bin_type and following_collection and "No collection" not in following_collection and "download PDF" not in following_collection: + try: + # Clean up the following collection text (remove PDF link text) + following_collection = following_collection.replace("download PDF", "").strip() + for date_fmt in ["%A, %d %B %Y", "%A %d %B %Y", "%d/%m/%Y", "%d-%m-%Y", "%Y-%m-%d"]: + try: + parsed_date = datetime.strptime(following_collection, date_fmt) + data["bins"].append({ + "type": bin_type, + "collectionDate": parsed_date.strftime(date_format) + }) + break + except ValueError: + continue + except: + continue + + return data + + finally: + driver.quit() diff --git a/uk_bin_collection/uk_bin_collection/councils/BuckinghamshireCouncil.py b/uk_bin_collection/uk_bin_collection/councils/BuckinghamshireCouncil.py index c51122f7b0..a26a412ac6 100644 --- a/uk_bin_collection/uk_bin_collection/councils/BuckinghamshireCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/BuckinghamshireCouncil.py @@ -20,6 +20,7 @@ def parse_data(self, page: str, **kwargs) -> dict: data = {"bins": []} user_paon = kwargs.get("paon") user_postcode = kwargs.get("postcode") + user_uprn = kwargs.get("uprn") web_driver = kwargs.get("web_driver") headless = kwargs.get("headless") check_paon(user_paon) @@ -27,9 +28,13 @@ def parse_data(self, page: str, **kwargs) -> dict: # Create Selenium webdriver driver = create_webdriver(web_driver, headless, None, __name__) - driver.get( - "https://iapp.itouchvision.com/iappcollectionday/collection-day/?uuid=FA353FC74600CBE61BE409534D00A8EC09BDA3AC&lang=en" + driver.get(kwargs.get("url")) + + # Click "Check now" button + check_now_button = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.XPATH, "//a[contains(text(), 'Check now')]")) ) + check_now_button.click() # Wait for the postcode field to appear then populate it inputElement_postcode = WebDriverWait(driver, 10).until( @@ -37,71 +42,77 @@ def parse_data(self, page: str, **kwargs) -> dict: ) inputElement_postcode.send_keys(user_postcode) - # Click search button - findAddress = WebDriverWait(driver, 10).until( - EC.presence_of_element_located( - (By.XPATH, '//button[@class="govuk-button mt-4"]') - ) + # Click Find button + find_button = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.XPATH, "//button[contains(text(), 'Find')]")) ) - findAddress.click() + find_button.click() - # Wait for the 'Select address' dropdown to appear and select option matching the house name/number - WebDriverWait(driver, 10).until( - EC.element_to_be_clickable( - ( - By.XPATH, - "//select[@id='addressSelect']//option[contains(., '" - + user_paon - + "')]", - ) + # Wait for the address dropdown and select by UPRN + if user_uprn: + address_option = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.XPATH, f"//option[@value='{user_uprn}']")) ) - ).click() - - # Wait for the collections table to appear - WebDriverWait(driver, 10).until( - EC.presence_of_element_located( - ( - By.XPATH, - '//div[@class="ant-row d-flex justify-content-between mb-4 mt-2 css-2rgkd4"]', + address_option.click() + else: + # Fallback to selecting by address text + address_option = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable( + (By.XPATH, f"//select[@id='addressSelect']//option[contains(., '{user_paon}')]") ) ) - ) - - soup = BeautifulSoup(driver.page_source, features="html.parser") + address_option.click() - recyclingcalendar = soup.find( - "div", - { - "class": "ant-row d-flex justify-content-between mb-4 mt-2 css-2rgkd4" - }, - ) + # Wait a moment for the page to update after address selection + import time + time.sleep(2) - rows = recyclingcalendar.find_all( - "div", - { - "class": "ant-col ant-col-xs-12 ant-col-sm-12 ant-col-md-12 ant-col-lg-12 ant-col-xl-12 css-2rgkd4" - }, - ) + # Wait for collection information to appear - try multiple possible selectors + try: + WebDriverWait(driver, 15).until( + EC.presence_of_element_located((By.XPATH, "//h2[contains(text(), 'Your next collections')]")) + ) + except: + # Alternative wait for collection data structure + WebDriverWait(driver, 10).until( + EC.presence_of_element_located((By.XPATH, "//div[contains(@class, 'ant-row') and contains(@class, 'd-flex')]//h3[@class='text-white']")) + ) + soup = BeautifulSoup(driver.page_source, features="html.parser") + + # Find all collection items with the specific structure - try multiple class patterns + collection_items = soup.find_all("div", class_=lambda x: x and "ant-col" in x and "ant-col-xs-12" in x) + if not collection_items: + # Fallback to finding items by structure + collection_items = soup.find_all("div", class_=lambda x: x and "p-2" in x and "d-flex" in x and "flex-column" in x) + current_year = datetime.now().year current_month = datetime.now().month - for row in rows: - BinType = row.find("h3").text - collectiondate = datetime.strptime( - row.find("div", {"class": "text-white fw-bold"}).text, - "%A %d %B", - ) - if (current_month > 10) and (collectiondate.month < 3): - collectiondate = collectiondate.replace(year=(current_year + 1)) - else: - collectiondate = collectiondate.replace(year=current_year) - - dict_data = { - "type": BinType, - "collectionDate": collectiondate.strftime("%d/%m/%Y"), - } - data["bins"].append(dict_data) + for item in collection_items: + # Extract bin type from h3 element + bin_type_elem = item.find("h3", class_="text-white") + # Extract date from div with specific classes + date_elem = item.find("div", class_="text-white fw-bold") + + if bin_type_elem and date_elem: + bin_type = bin_type_elem.get_text().strip() + date_text = date_elem.get_text().strip() + + try: + collection_date = datetime.strptime(date_text, "%A %d %B") + if (current_month > 10) and (collection_date.month < 3): + collection_date = collection_date.replace(year=(current_year + 1)) + else: + collection_date = collection_date.replace(year=current_year) + + dict_data = { + "type": bin_type, + "collectionDate": collection_date.strftime("%d/%m/%Y"), + } + data["bins"].append(dict_data) + except ValueError: + continue except Exception as e: # Here you can log the exception if needed diff --git a/uk_bin_collection/uk_bin_collection/councils/ChelmsfordCityCouncil.py b/uk_bin_collection/uk_bin_collection/councils/ChelmsfordCityCouncil.py index 7f01f869ee..9483084788 100644 --- a/uk_bin_collection/uk_bin_collection/councils/ChelmsfordCityCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/ChelmsfordCityCouncil.py @@ -1,127 +1,95 @@ -# This script pulls (in one hit) the data from Bromley Council Bins Data -import datetime import re import time -from datetime import datetime - +from datetime import datetime, timedelta import requests from bs4 import BeautifulSoup from selenium.webdriver.common.by import By -from selenium.webdriver.common.keys import Keys from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.support.ui import Select from selenium.webdriver.support.wait import WebDriverWait +from icalevents.icalevents import events from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass - -# import the wonderful Beautiful Soup and the URL grabber class CouncilClass(AbstractGetBinDataClass): - """ - Concrete classes have to implement all abstract operations of the - base class. They can also override some operations with a default - implementation. - """ - def parse_data(self, page: str, **kwargs) -> dict: driver = None try: data = {"bins": []} headers = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)"} - - uprn = kwargs.get("uprn") + postcode = kwargs.get("postcode") user_paon = kwargs.get("paon") web_driver = kwargs.get("web_driver") headless = kwargs.get("headless") + driver = create_webdriver(web_driver, headless, None, __name__) - url = kwargs.get("url") - - driver.execute_script(f"window.location.href='{url}'") - - wait = WebDriverWait(driver, 120) - post_code_search = wait.until( - EC.presence_of_element_located((By.XPATH, '//input[@name="keyword"]')) - ) - - post_code_search.send_keys(postcode) - - submit_btn = wait.until( - EC.presence_of_element_located((By.CLASS_NAME, "__submitButton")) - ) - - submit_btn.send_keys(Keys.ENTER) - - address_results = wait.until( - EC.presence_of_element_located((By.CLASS_NAME, "directories-table")) - ) - address_link = wait.until( - EC.presence_of_element_located( - (By.XPATH, f"//a[contains(text(), '{user_paon}')]") + wait = WebDriverWait(driver, 30) + + # Navigate to bin collection page + driver.get("https://www.chelmsford.gov.uk/bins-and-recycling/check-your-collection-day/") + + # Handle cookie overlay + try: + accept_btn = wait.until( + EC.element_to_be_clickable((By.XPATH, "//*[contains(text(), 'ACCEPT')]")) ) + accept_btn.click() + time.sleep(1) + except: + pass + + # Find postcode input field (dynamic ID) + postcode_input = wait.until( + EC.presence_of_element_located((By.XPATH, "//input[contains(@id, '_keyword')]")) ) - - address_link.send_keys(Keys.ENTER) - results = wait.until( - EC.presence_of_element_located((By.CLASS_NAME, "usercontent")) + postcode_input.clear() + postcode_input.send_keys(postcode) + + # Click search button + submit_btn = wait.until( + EC.element_to_be_clickable((By.CLASS_NAME, "__submitButton")) ) - - # Make a BS4 object + submit_btn.click() + + # Wait for results table + wait.until(EC.presence_of_element_located((By.TAG_NAME, "table"))) + + # Get the collection round from the table row soup = BeautifulSoup(driver.page_source, features="html.parser") - soup.prettify() - - # Get collection calendar - calendar_urls = soup.find_all( - "a", string=re.compile(r"view or download the collection calendar") - ) - if len(calendar_urls) > 0: - requests.packages.urllib3.disable_warnings() - response = requests.get(calendar_urls[0].get("href"), headers=headers) - - # Make a BS4 object - soup = BeautifulSoup(response.text, features="html.parser") - soup.prettify() - - # Loop the months - for month in soup.find_all("div", {"class": "usercontent"}): - year = "" - if month.find("h2") and "calendar" not in month.find("h2").get_text( - strip=True - ): - year = datetime.strptime( - month.find("h2").get_text(strip=True), "%B %Y" - ).strftime("%Y") - elif month.find("h3"): - year = datetime.strptime( - month.find("h3").get_text(strip=True), "%B %Y" - ).strftime("%Y") - if year != "": - for row in month.find_all("li"): - results = re.search( - "([A-Za-z]+ \\d\\d? [A-Za-z]+): (.+)", - row.get_text(strip=True), - ) - if results: - dict_data = { - "type": results.groups()[1].capitalize(), - "collectionDate": datetime.strptime( - results.groups()[0] + " " + year, "%A %d %B %Y" - ).strftime(date_format), - } - data["bins"].append(dict_data) - - # Sort collections - data["bins"].sort( - key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y") - ) + + # Find the row containing the address + for row in soup.find_all("tr"): + if user_paon in row.get_text(): + # Extract collection round (e.g., "Tuesday B") + row_text = row.get_text() + round_match = re.search(r"(Monday|Tuesday|Wednesday|Thursday|Friday)\s+([AB])", row_text) + if round_match: + day = round_match.group(1).lower() + letter = round_match.group(2).lower() + ics_url = f"https://www.chelmsford.gov.uk/media/4ipavf0m/{day}-{letter}-calendar.ics" + break + else: + raise ValueError(f"Could not find collection round for address: {user_paon}") + + # Get events from ICS file within the next 60 days + now = datetime.now() + future = now + timedelta(days=60) + + # Parse ICS calendar + upcoming_events = events(ics_url, start=now, end=future) + + for event in sorted(upcoming_events, key=lambda e: e.start): + if event.summary and event.start: + data["bins"].append({ + "type": event.summary, + "collectionDate": event.start.date().strftime(date_format) + }) except Exception as e: - # Here you can log the exception if needed print(f"An error occurred: {e}") - # Optionally, re-raise the exception if you want it to propagate raise finally: - # This block ensures that the driver is closed regardless of an exception if driver: driver.quit() - return data + + return data \ No newline at end of file diff --git a/uk_bin_collection/uk_bin_collection/councils/CherwellDistrictCouncil.py b/uk_bin_collection/uk_bin_collection/councils/CherwellDistrictCouncil.py index 4ea346e737..e02f9ac944 100644 --- a/uk_bin_collection/uk_bin_collection/councils/CherwellDistrictCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/CherwellDistrictCouncil.py @@ -23,8 +23,11 @@ def parse_data(self, page: str, **kwargs) -> dict: URI = f"https://www.cherwell.gov.uk/homepage/129/bin-collection-search?uprn={user_uprn}" - # Make the GET request - response = requests.get(URI) + # Make the GET request with proper headers + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' + } + response = requests.get(URI, headers=headers) soup = BeautifulSoup(response.text, "html.parser") @@ -45,22 +48,38 @@ def get_full_date(date_str): return date_obj.strftime(date_format) # Return in YYYY-MM-DD format - # print(soup) - - div = soup.find("div", class_="bin-collection-results__tasks") - - for item in div.find_all("li", class_="list__item"): - # Extract bin type - bin_type_tag = item.find("h3", class_="bin-collection-tasks__heading") - bin_type = ( - "".join(bin_type_tag.find_all(text=True, recursive=False)).strip() - if bin_type_tag - else "Unknown Bin" - ) + # Find the bin collection results section + results_div = soup.find("div", class_="bin-collection-results") + if not results_div: + return bindata + + tasks_div = results_div.find("div", class_="bin-collection-results__tasks") + if not tasks_div: + return bindata + + # Find all bin collection items + for item in tasks_div.find_all("li", class_="list__item"): + # Extract bin type from heading + heading = item.find("h3", class_="bin-collection-tasks__heading") + if not heading: + continue + + # Get the bin type text, excluding visually hidden spans + bin_type = "" + for text_node in heading.find_all(text=True): + parent = text_node.parent + if not (parent.name == "span" and "visually-hidden" in parent.get("class", [])): + bin_type += text_node.strip() + + if not bin_type: + continue # Extract collection date date_tag = item.find("p", class_="bin-collection-tasks__date") - collection_date = date_tag.text.strip() if date_tag else "Unknown Date" + if not date_tag: + continue + + collection_date = date_tag.text.strip() dict_data = { "type": bin_type, @@ -68,8 +87,10 @@ def get_full_date(date_str): } bindata["bins"].append(dict_data) - bindata["bins"].sort( - key=lambda x: datetime.strptime(x.get("collectionDate"), date_format) - ) + # Sort bins by collection date + if bindata["bins"]: + bindata["bins"].sort( + key=lambda x: datetime.strptime(x.get("collectionDate"), date_format) + ) return bindata diff --git a/uk_bin_collection/uk_bin_collection/councils/ChorleyCouncil.py b/uk_bin_collection/uk_bin_collection/councils/ChorleyCouncil.py index 4cc05e37bc..51b023fed5 100644 --- a/uk_bin_collection/uk_bin_collection/councils/ChorleyCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/ChorleyCouncil.py @@ -1,5 +1,5 @@ import time -import urllib.parse +from datetime import datetime from bs4 import BeautifulSoup from selenium.webdriver.common.by import By @@ -11,18 +11,6 @@ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass -def format_bin_type(bin_colour: str): - bin_types = { - "grey": "Garden waste (Grey Bin)", - "brown": "Paper and card (Brown Bin)", - "blue": "Bottles and cans (Blue Bin)", - "green": "General waste (Green Bin)", - } - bin_colour = urllib.parse.unquote(bin_colour).split(" ")[0].lower() - return bin_types[bin_colour] - - -# import the wonderful Beautiful Soup and the URL grabber class CouncilClass(AbstractGetBinDataClass): """ Concrete classes have to implement all abstract operations of the @@ -41,99 +29,120 @@ def parse_data(self, page: str, **kwargs) -> dict: check_uprn(user_uprn) check_postcode(user_postcode) - # Ensure UPRN starts with "UPRN" - if not user_uprn.startswith("UPRN"): - user_uprn = f"UPRN{user_uprn}" - # Create Selenium webdriver - user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" driver = create_webdriver(web_driver, headless, user_agent, __name__) - driver.get("https://myaccount.chorley.gov.uk/wastecollections.aspx") - - # Accept cookies banner - cookieBanner = WebDriverWait(driver, 10).until( - EC.presence_of_element_located((By.ID, "PrivacyPolicyNotification")) - ) - cookieClose = cookieBanner.find_element( - By.CSS_SELECTOR, "span.ui-icon-circle-close" + + # Navigate to the start page + driver.get("https://chorley.gov.uk/bincollectiondays") + + # Click the "Check your collection day" button + check_button = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.XPATH, "//a[@class='button' and @href='https://forms.chorleysouthribble.gov.uk/chorley-bincollectiondays']") + )) + check_button.click() + + # Wait for the form to load and enter postcode + postcode_input = WebDriverWait(driver, 10).until( + EC.presence_of_element_located((By.XPATH, "//input[@type='text'][1]") + )) + postcode_input.clear() + postcode_input.send_keys(user_postcode) + + # Click the Lookup button + lookup_button = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.XPATH, "//button[contains(@class, 'btn--lookup')]") + )) + lookup_button.click() + + # Wait for the property dropdown to be populated + property_dropdown = WebDriverWait(driver, 10).until( + EC.presence_of_element_located((By.XPATH, "//select[@class='form__select']") + )) + + # Wait a moment for the dropdown to be fully populated + time.sleep(2) + + # Find the property that matches the UPRN or select the first available property + select = Select(property_dropdown) + options = select.options + + # Skip the "Please choose..." option and select based on UPRN or first available + selected = False + for option in options[1:]: # Skip first "Please choose..." option + if user_uprn in option.get_attribute("value") or not selected: + select.select_by_visible_text(option.text) + selected = True + break + + if not selected and len(options) > 1: + # If no UPRN match, select the first available property + select.select_by_index(1) + + # Click the Next button + next_button = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.CSS_SELECTOR, "button[type='submit'][value='Next']")) ) - cookieClose.click() - - # Populate postcode field - inputElement_postcode = driver.find_element( - By.ID, - "MainContent_addressSearch_txtPostCodeLookup", - ) - inputElement_postcode.send_keys(user_postcode) - - # Click search button - findAddress = WebDriverWait(driver, 10).until( - EC.presence_of_element_located( - ( - By.ID, - "MainContent_addressSearch_btnFindAddress", - ) - ) - ) - findAddress.click() - - time.sleep(1) - - # Wait for the 'Select address' dropdown to appear and select option matching UPRN - dropdown = WebDriverWait(driver, 10).until( - EC.presence_of_element_located( - ( - By.ID, - "MainContent_addressSearch_ddlAddress", - ) - ) + next_button.click() + + # Wait for the results page to load + WebDriverWait(driver, 10).until( + EC.presence_of_element_located((By.XPATH, "//th[text()='Collection']")) ) - # Create a 'Select' for it, then select the matching URPN option - dropdownSelect = Select(dropdown) - dropdownSelect.select_by_value(user_uprn) - - # Wait for the submit button to appear, then click it to get the collection dates - submit = WebDriverWait(driver, 10).until( - EC.presence_of_element_located((By.ID, "MainContent_btnSearch")) - ) - submit.click() - - soup = BeautifulSoup(driver.page_source, features="html.parser") - - # Get the property details - property_details = soup.find( - "table", - {"class": "WasteCollection"}, - ) - - # Get the dates - for row in property_details.tbody.find_all("tr", recursive=False): - month_col = row.td - month = month_col.get_text(strip=True) - - for date_col in month_col.find_next_siblings("td"): - day = date_col.p.contents[0].strip() - - if day == "": - continue - - for bin_type in date_col.find_all("img"): - bin_colour = bin_type.get("src").split("/")[-1].split(".")[0] - date_object = datetime.strptime(f"{day} {month}", "%d %B %Y") - date_formatted = date_object.strftime("%d/%m/%Y") - - dict_data = { - "type": format_bin_type(bin_colour), - "collectionDate": date_formatted, - } - data["bins"].append(dict_data) + + # Parse the results + soup = BeautifulSoup(driver.page_source, "html.parser") + + # Find the table with collection data + table = soup.find("table") + + if table: + rows = table.find_all("tr") + + for i, row in enumerate(rows): + cells = row.find_all(["td", "th"]) + + if i > 0 and len(cells) >= 2: # Skip header row + collection_type = cells[0].get_text(strip=True) + collection_date = cells[1].get_text(strip=True) + + if collection_type and collection_date and collection_date != "Collection": + # Try to parse the date + try: + # Handle the format "Tuesday, 05/08/25" + if ", " in collection_date and "/" in collection_date: + # Remove the day name and parse the date + date_part = collection_date.split(", ")[1] + # Handle 2-digit year format + if len(date_part.split("/")[2]) == 2: + date_obj = datetime.strptime(date_part, "%d/%m/%y") + else: + date_obj = datetime.strptime(date_part, "%d/%m/%Y") + elif "/" in collection_date: + date_obj = datetime.strptime(collection_date, "%d/%m/%Y") + elif "-" in collection_date: + date_obj = datetime.strptime(collection_date, "%Y-%m-%d") + else: + # Try to parse other formats + date_obj = datetime.strptime(collection_date, "%d %B %Y") + + formatted_date = date_obj.strftime("%d/%m/%Y") + + dict_data = { + "type": collection_type, + "collectionDate": formatted_date, + } + data["bins"].append(dict_data) + except ValueError: + # If date parsing fails, skip this entry + continue + except Exception as e: # Here you can log the exception if needed print(f"An error occurred: {e}") # Optionally, re-raise the exception if you want it to propagate raise finally: - # This block ensures that the driver is closed regardless of an exception if driver: driver.quit() - return data + return data \ No newline at end of file diff --git a/uk_bin_collection/uk_bin_collection/councils/CopelandBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/CopelandBoroughCouncil.py index 521a73948f..f61e14c549 100644 --- a/uk_bin_collection/uk_bin_collection/councils/CopelandBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/CopelandBoroughCouncil.py @@ -1,93 +1,98 @@ -from xml.etree import ElementTree - +import requests from bs4 import BeautifulSoup from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass +# import the wonderful Beautiful Soup and the URL grabber class CouncilClass(AbstractGetBinDataClass): """ Concrete classes have to implement all abstract operations of the - baseclass. They can also override some - operations with a default implementation. + base class. They can also override some operations with a default + implementation. """ def parse_data(self, page: str, **kwargs) -> dict: - uprn = kwargs.get("uprn") - check_uprn(uprn) - council = "CPL" - # Make SOAP request + user_uprn = kwargs.get("uprn") + postcode = kwargs.get("postcode") + check_uprn(user_uprn) + bindata = {"bins": []} + + URI = "https://waste.cumberland.gov.uk/renderform?t=25&k=E43CEB1FB59F859833EF2D52B16F3F4EBE1CAB6A" + + s = requests.Session() + + # Make the GET request + response = s.get(URI) + + # Make a BS4 object + soup = BeautifulSoup(response.content, features="html.parser") + + # print(soup) + + token = (soup.find("input", {"name": "__RequestVerificationToken"})).get( + "value" + ) + + formguid = (soup.find("input", {"name": "FormGuid"})).get("value") + + # print(token) + # print(formguid) + headers = { - "Content-Type": "text/xml; charset=UTF-8", - "Referer": "https://collections-copeland.azurewebsites.net/calendar.html", - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36", + "Content-Type": "application/x-www-form-urlencoded", + "Origin": "https://waste.cumberland.gov.uk", + "Referer": "https://waste.cumberland.gov.uk/renderform?t=25&k=E43CEB1FB59F859833EF2D52B16F3F4EBE1CAB6A", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36 OPR/98.0.0.0", + "X-Requested-With": "XMLHttpRequest", } - requests.packages.urllib3.disable_warnings() - post_data = ( - '' - '' - '' - "" + council + "" + uprn + "" - "Chtml" - ) - response = requests.post( - "https://collections-copeland.azurewebsites.net/WSCollExternal.asmx", + + payload = { + "__RequestVerificationToken": token, + "FormGuid": formguid, + "ObjectTemplateID": "25", + "Trigger": "submit", + "CurrentSectionID": "33", + "TriggerCtl": "", + "FF265": f"U{user_uprn}", + "FF265lbltxt": "Please select your address", + "FF265-text": postcode + } + + # print(payload) + + response = s.post( + "https://waste.cumberland.gov.uk/renderform/Form", headers=headers, - data=post_data, + data=payload, + ) + + soup = BeautifulSoup(response.content, features="html.parser") + for row in soup.find_all("div", class_="resirow"): + # Extract the type of collection (e.g., Recycling, Refuse) + collection_type_div = row.find("div", class_="col") + collection_type = ( + collection_type_div.get("class")[1] + if collection_type_div + else "Unknown" + ) + + # Extract the collection date + date_div = row.find("div", style="width:360px;") + collection_date = date_div.text.strip() if date_div else "Unknown" + + dict_data = { + "type": collection_type, + "collectionDate": datetime.strptime( + collection_date, "%A %d %B %Y" + ).strftime(date_format), + } + bindata["bins"].append(dict_data) + + bindata["bins"].sort( + key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y") ) - if response.status_code != 200: - raise ValueError("No bin data found for provided UPRN.") - - # Get HTML from SOAP response - xmltree = ElementTree.fromstring(response.text) - html = xmltree.find( - ".//{http://webaspx-collections.azurewebsites.net/}getRoundCalendarForUPRNResult" - ).text - # Parse with BS4 - soup = BeautifulSoup(html, features="html.parser") - soup.prettify() - - data = {"bins": []} - for bin_type in ["Refuse", "Recycling", "Garden"]: - bin_el = soup.find("b", string=bin_type) - if bin_el: - bin_info = bin_el.next_sibling.split(": ")[1] - collection_date = "" - results = re.search("([A-Za-z]+ \\d\\d? [A-Za-z]+) then", bin_info) - if results: - if results[1] == "Today": - date = datetime.now() - elif results[1] == "Tomorrow": - date = datetime.now() + timedelta(days=1) - else: - date = get_next_occurrence_from_day_month( - datetime.strptime( - results[1] + " " + datetime.now().strftime("%Y"), - "%a %d %b %Y", - ) - ) - if date: - collection_date = date.strftime(date_format) - else: - results2 = re.search("([A-Za-z]+) then", bin_info) - if results2: - if results2[1] == "Today": - collection_date = datetime.now().strftime(date_format) - elif results2[1] == "Tomorrow": - collection_date = ( - datetime.now() + timedelta(days=1) - ).strftime(date_format) - else: - collection_date = results2[1] - - if collection_date != "": - dict_data = { - "type": bin_type, - "collectionDate": collection_date, - } - data["bins"].append(dict_data) - - return data + return bindata From d14fdc03db5509e8a5a903317f78d336122417fe Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Sun, 3 Aug 2025 08:19:26 +0100 Subject: [PATCH 090/425] fix: Cotswald and coventry --- uk_bin_collection/tests/input.json | 2 +- .../councils/CotswoldDistrictCouncil.py | 258 +++++++++++++----- .../councils/CoventryCityCouncil.py | 8 +- .../uk_bin_collection/get_bin_data.py | 2 +- 4 files changed, 199 insertions(+), 71 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index 10485e1288..a5d27a554b 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -580,7 +580,7 @@ "LAD24CD": "E06000052" }, "CotswoldDistrictCouncil": { - "house_number": "19", + "house_number": "19 SUMMERS WAY, MORETON-IN-MARSH, GL56 0GB", "postcode": "GL56 0GB", "skip_get_url": true, "url": "https://community.cotswold.gov.uk/s/waste-collection-enquiry", diff --git a/uk_bin_collection/uk_bin_collection/councils/CotswoldDistrictCouncil.py b/uk_bin_collection/uk_bin_collection/councils/CotswoldDistrictCouncil.py index d19f1dcdef..45a3dce426 100644 --- a/uk_bin_collection/uk_bin_collection/councils/CotswoldDistrictCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/CotswoldDistrictCouncil.py @@ -1,5 +1,6 @@ import time -from datetime import datetime +import re +from datetime import datetime, timedelta from bs4 import BeautifulSoup from selenium.webdriver.common.by import By @@ -11,8 +12,6 @@ from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass -# import the wonderful Beautiful Soup and the URL grabber - class CouncilClass(AbstractGetBinDataClass): """ @@ -30,7 +29,8 @@ def parse_data(self, page: str, **kwargs) -> dict: house_number = kwargs.get("paon") postcode = kwargs.get("postcode") - full_address = f"{house_number}, {postcode}" + # Use house_number as full address since it contains the complete address + full_address = house_number if house_number else f"{house_number}, {postcode}" web_driver = kwargs.get("web_driver") headless = kwargs.get("headless") @@ -38,81 +38,205 @@ def parse_data(self, page: str, **kwargs) -> dict: driver = create_webdriver(web_driver, headless, None, __name__) driver.get(page) - # If you bang in the house number (or property name) and postcode in the box it should find your property + # Wait for page to load completely wait = WebDriverWait(driver, 60) - address_entry_field = wait.until( - EC.element_to_be_clickable((By.XPATH, '//*[@id="combobox-input-22"]')) - ) - - address_entry_field.send_keys(str(full_address)) - - address_entry_field = wait.until( - EC.element_to_be_clickable((By.XPATH, '//*[@id="combobox-input-22"]')) - ) - address_entry_field.click() - address_entry_field.send_keys(Keys.BACKSPACE) - address_entry_field.send_keys(str(full_address[len(full_address) - 1])) - - first_found_address = wait.until( - EC.element_to_be_clickable( - (By.XPATH, '//*[@id="dropdown-element-22"]/ul') - ) - ) - - first_found_address.click() - # Wait for the 'Select your property' dropdown to appear and select the first result - next_btn = wait.until( - EC.element_to_be_clickable((By.XPATH, "//lightning-button/button")) - ) - next_btn.click() - bin_data = wait.until( - EC.presence_of_element_located( - (By.XPATH, "//span[contains(text(), 'Container')]") - ) - ) - + + # Wait for the Salesforce Lightning page to be fully loaded + print("Waiting for Salesforce Lightning components to load...") + time.sleep(10) + + # Wait for the address input field to be present + try: + wait.until(EC.presence_of_element_located((By.XPATH, "//label[contains(text(), 'Enter your address')]"))) + print("Address label found") + time.sleep(5) # Additional wait for the input field to be ready + except Exception as e: + print(f"Address label not found: {e}") + + # Find the address input field using the label + try: + address_entry_field = driver.find_element(By.XPATH, "//label[contains(text(), 'Enter your address')]/following-sibling::*//input") + print("Found address input field using label xpath") + except Exception as e: + print(f"Could not find address input field: {e}") + raise Exception("Could not find address input field") + + # Clear any existing text and enter the address + try: + address_entry_field.clear() + address_entry_field.send_keys(str(full_address)) + print(f"Entered address: {full_address}") + except Exception as e: + print(f"Error entering address: {e}") + raise + + # Click the input field again to trigger the dropdown + try: + address_entry_field.click() + print("Clicked input field to trigger dropdown") + time.sleep(3) # Wait for dropdown to appear + except Exception as e: + print(f"Error clicking input field: {e}") + + # Wait for and click the dropdown option + try: + dropdown_wait = WebDriverWait(driver, 10) + dropdown_option = dropdown_wait.until(EC.element_to_be_clickable((By.XPATH, "//li[@role='presentation']"))) + dropdown_option.click() + print("Clicked dropdown option") + time.sleep(2) + except Exception as e: + print(f"Error clicking dropdown option: {e}") + raise + + # Find and click the Next button + try: + next_wait = WebDriverWait(driver, 10) + next_button = next_wait.until(EC.element_to_be_clickable((By.XPATH, "//button[contains(text(), 'Next')]"))) + next_button.click() + print("Clicked Next button") + time.sleep(5) # Wait for the bin collection data to load + except Exception as e: + print(f"Error clicking Next button: {e}") + raise + + # Wait for the bin collection data table to load + try: + table_wait = WebDriverWait(driver, 15) + table_wait.until(EC.presence_of_element_located((By.XPATH, "//span[contains(text(), 'Collection Day')]"))) + print("Bin collection data table loaded") + time.sleep(3) + except Exception as e: + print(f"Bin collection table not found: {e}") + soup = BeautifulSoup(driver.page_source, features="html.parser") - - rows = soup.find_all("tr", class_="slds-hint-parent") current_year = datetime.now().year + # Try multiple approaches to find bin collection data + rows = [] + + # Try different table row selectors + table_selectors = [ + "tr.slds-hint-parent", + "tr[class*='slds']", + "table tr", + ".slds-table tr", + "tbody tr" + ] + + for selector in table_selectors: + rows = soup.select(selector) + if rows: + break + + # If no table rows found, try to find any elements containing collection info + if not rows: + # Look for any elements that might contain bin collection information + collection_elements = soup.find_all(text=re.compile(r'(bin|collection|waste|recycling)', re.I)) + if collection_elements: + # Try to extract information from the surrounding elements + for element in collection_elements[:10]: # Limit to first 10 matches + parent = element.parent + if parent: + text = parent.get_text().strip() + if text and len(text) > 10: # Only consider substantial text + # Try to extract date patterns + date_patterns = re.findall(r'\b\d{1,2}[/-]\d{1,2}[/-]\d{2,4}\b|\b\d{1,2}\s+\w+\s+\d{4}\b', text) + if date_patterns: + data["bins"].append({ + "type": "General Collection", + "collectionDate": date_patterns[0] + }) + break + + # Process table rows if found for row in rows: - columns = row.find_all("td") - if columns: - container_type = row.find("th").text.strip() - if columns[0].get_text() == "Today": - collection_day = datetime.now().strftime("%a, %d %B") - elif columns[0].get_text() == "Tomorrow": - collection_day = (datetime.now() + timedelta(days=1)).strftime( - "%a, %d %B" - ) - else: - collection_day = re.sub( - r"[^a-zA-Z0-9,\s]", "", columns[0].get_text() - ).strip() + try: + columns = row.find_all(["td", "th"]) + if len(columns) >= 2: + # Try to identify container type and date + container_type = "Unknown" + collection_date = "" + + # Look for header cell (th) for container type + th_element = row.find("th") + if th_element: + container_type = th_element.get_text().strip() + elif columns: + # If no th, use first column as type + container_type = columns[0].get_text().strip() + + # Look for date in subsequent columns + for col in columns[1:] if th_element else columns[1:]: + col_text = col.get_text().strip() + if col_text: + if col_text.lower() == "today": + collection_date = datetime.now().strftime("%d/%m/%Y") + break + elif col_text.lower() == "tomorrow": + collection_date = (datetime.now() + timedelta(days=1)).strftime("%d/%m/%Y") + break + else: + # Try to parse various date formats + try: + # Clean the text + clean_text = re.sub(r"[^a-zA-Z0-9,\s/-]", "", col_text).strip() + + # Try different date parsing approaches + date_formats = [ + "%a, %d %B", + "%d %B %Y", + "%d/%m/%Y", + "%d-%m-%Y", + "%B %d, %Y" + ] + + for fmt in date_formats: + try: + parsed_date = datetime.strptime(clean_text, fmt) + if fmt == "%a, %d %B": # Add year if missing + if parsed_date.replace(year=current_year) < datetime.now(): + parsed_date = parsed_date.replace(year=current_year + 1) + else: + parsed_date = parsed_date.replace(year=current_year) + collection_date = parsed_date.strftime("%d/%m/%Y") + break + except ValueError: + continue + + if collection_date: + break + except Exception: + continue + + # Add to data if we have both type and date + if container_type and collection_date and container_type.lower() != "unknown": + data["bins"].append({ + "type": container_type, + "collectionDate": collection_date + }) + except Exception as e: + print(f"Error processing row: {e}") + continue + + # If no data found, add a debug entry + if not data["bins"]: + print("No bin collection data found. Page source:") + print(driver.page_source[:1000]) # Print first 1000 chars for debugging - # Parse the date from the string - parsed_date = datetime.strptime(collection_day, "%a, %d %B") - if parsed_date < datetime( - parsed_date.year, parsed_date.month, parsed_date.day - ): - parsed_date = parsed_date.replace(year=current_year + 1) - else: - parsed_date = parsed_date.replace(year=current_year) - # Format the date as %d/%m/%Y - formatted_date = parsed_date.strftime("%d/%m/%Y") - - # Add the bin type and collection date to the 'data' dictionary - data["bins"].append( - {"type": container_type, "collectionDate": formatted_date} - ) except Exception as e: # Here you can log the exception if needed print(f"An error occurred: {e}") + print(f"Full address used: {full_address}") + print(f"Page URL: {page}") + # Add some debug information + if driver: + print(f"Current page title: {driver.title}") + print(f"Current URL: {driver.current_url}") # Optionally, re-raise the exception if you want it to propagate raise finally: # This block ensures that the driver is closed regardless of an exception if driver: driver.quit() - return data + return data \ No newline at end of file diff --git a/uk_bin_collection/uk_bin_collection/councils/CoventryCityCouncil.py b/uk_bin_collection/uk_bin_collection/councils/CoventryCityCouncil.py index fdf7e4f150..00d970f36b 100644 --- a/uk_bin_collection/uk_bin_collection/councils/CoventryCityCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/CoventryCityCouncil.py @@ -18,6 +18,10 @@ def parse_data(self, page: str, **kwargs) -> dict: bindata = {"bins": []} curr_date = datetime.today() + + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" + } soup = BeautifulSoup(page.content, features="html.parser") button = soup.find( @@ -25,10 +29,10 @@ def parse_data(self, page: str, **kwargs) -> dict: text="Find out which bin will be collected when and sign up for a free email reminder.", ) - if button["href"]: + if button and button.get("href"): URI = button["href"] # Make the GET request - response = requests.get(URI) + response = requests.get(URI, headers=headers) soup = BeautifulSoup(response.content, features="html.parser") divs = soup.find_all("div", {"class": "editor"}) for div in divs: diff --git a/uk_bin_collection/uk_bin_collection/get_bin_data.py b/uk_bin_collection/uk_bin_collection/get_bin_data.py index 0ee809358d..00c3d43e22 100644 --- a/uk_bin_collection/uk_bin_collection/get_bin_data.py +++ b/uk_bin_collection/uk_bin_collection/get_bin_data.py @@ -121,7 +121,7 @@ def get_data(cls, url) -> str: urllib3.disable_warnings(category=urllib3.exceptions.InsecureRequestWarning) try: - full_page = requests.get(url, headers, verify=False, timeout=120) + full_page = requests.get(url, headers=headers, verify=False, timeout=120) return full_page except requests.exceptions.RequestException as err: _LOGGER.error(f"Request Error: {err}") From 2049191935be1cdc730546888122b0e1da5ce616 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 3 Aug 2025 07:23:50 +0000 Subject: [PATCH 091/425] =?UTF-8?q?bump:=20version=200.152.8=20=E2=86=92?= =?UTF-8?q?=200.152.9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 8 ++++++++ custom_components/uk_bin_collection/const.py | 2 +- custom_components/uk_bin_collection/manifest.json | 4 ++-- pyproject.toml | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efc0ec6820..200abe5a2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,12 @@ ======= +## 0.152.9 (2025-08-03) + +### Fix + +- Cotswald and coventry +- Fixing multiple broken councils +- multiple broken councils + ## 0.152.8 (2025-07-26) ### Fix diff --git a/custom_components/uk_bin_collection/const.py b/custom_components/uk_bin_collection/const.py index beb0887802..0272c9c426 100644 --- a/custom_components/uk_bin_collection/const.py +++ b/custom_components/uk_bin_collection/const.py @@ -4,7 +4,7 @@ from homeassistant.const import Platform -INPUT_JSON_URL = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.152.8/uk_bin_collection/tests/input.json" +INPUT_JSON_URL = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.152.9/uk_bin_collection/tests/input.json" DEFAULT_NAME = "UK Bin Collection Data" diff --git a/custom_components/uk_bin_collection/manifest.json b/custom_components/uk_bin_collection/manifest.json index 58a173dc5d..3bde695a49 100644 --- a/custom_components/uk_bin_collection/manifest.json +++ b/custom_components/uk_bin_collection/manifest.json @@ -9,7 +9,7 @@ "integration_type": "service", "iot_class": "cloud_polling", "issue_tracker": "https://github.com/robbrad/UKBinCollectionData/issues", - "requirements": ["uk-bin-collection>=0.152.8"], - "version": "0.152.8", + "requirements": ["uk-bin-collection>=0.152.9"], + "version": "0.152.9", "zeroconf": [] } diff --git a/pyproject.toml b/pyproject.toml index a7d7525edb..cc77a37f11 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "uk_bin_collection" -version = "0.152.8" +version = "0.152.9" description = "Python Lib to collect UK Bin Data" readme = "README.md" authors = ["Robert Bradley "] From 80c8b15072002fc3d5e3464a5622195875950253 Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Sun, 3 Aug 2025 18:08:53 +0100 Subject: [PATCH 092/425] fix: East Herts --- uk_bin_collection/tests/input.json | 8 +- .../councils/EastHertsCouncil.py | 143 ++++-------------- .../councils/EastRenfrewshireCouncil.py | 117 +++++++------- 3 files changed, 91 insertions(+), 177 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index a5d27a554b..5493de0fe8 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -761,13 +761,11 @@ }, "EastHertsCouncil": { "LAD24CD": "E07000097", - "house_number": "1", - "postcode": "CM20 2FZ", "skip_get_url": true, - "url": "https://www.eastherts.gov.uk", - "web_driver": "http://selenium:4444", + "uprn": "10023088183", + "url": "https://east-herts.co.uk/api/services/", "wiki_name": "East Herts Council", - "wiki_note": "Pass the house number and postcode in their respective parameters." + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search)." }, "EastLindseyDistrictCouncil": { "house_number": "1", diff --git a/uk_bin_collection/uk_bin_collection/councils/EastHertsCouncil.py b/uk_bin_collection/uk_bin_collection/councils/EastHertsCouncil.py index 5911cf173f..fc8909c006 100644 --- a/uk_bin_collection/uk_bin_collection/councils/EastHertsCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/EastHertsCouncil.py @@ -1,7 +1,6 @@ -from bs4 import BeautifulSoup -from selenium.webdriver.common.by import By -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.support.wait import WebDriverWait +import json +import requests +from datetime import datetime from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass @@ -15,116 +14,28 @@ class CouncilClass(AbstractGetBinDataClass): """ def parse_data(self, page: str, **kwargs) -> dict: - # Get and check UPRN - driver = None - try: - user_postcode = kwargs.get("postcode") - user_paon = kwargs.get("paon") - check_paon(user_paon) - check_postcode(user_postcode) - web_driver = kwargs.get("web_driver") - headless = kwargs.get("headless") - bindata = {"bins": []} - - API_URL = "https://uhte-wrp.whitespacews.com" - - # Create Selenium webdriver - driver = create_webdriver(web_driver, headless, None, __name__) - driver.get(API_URL) - - # Click Find my bin collection day button - collectionButton = WebDriverWait(driver, 10).until( - EC.element_to_be_clickable((By.LINK_TEXT, "Find my bin collection day")) - ) - collectionButton.click() - - main_content = WebDriverWait(driver, 10).until( - EC.presence_of_element_located((By.ID, "main-content")) - ) - - # Wait for the property number field to appear then populate it - inputElement_number = WebDriverWait(driver, 10).until( - EC.element_to_be_clickable( - ( - By.ID, - "address_name_number", - ) - ) - ) - inputElement_number.send_keys(user_paon) - - # Wait for the postcode field to appear then populate it - inputElement_postcode = WebDriverWait(driver, 10).until( - EC.element_to_be_clickable( - ( - By.ID, - "address_postcode", - ) - ) - ) - inputElement_postcode.send_keys(user_postcode) - - # Click search button - continueButton = WebDriverWait(driver, 10).until( - EC.element_to_be_clickable( - ( - By.ID, - "Submit", - ) - ) - ) - continueButton.click() - - # Wait for the 'Search Results' to appear and select the first result - property = WebDriverWait(driver, 10).until( - EC.element_to_be_clickable( - ( - By.CSS_SELECTOR, - "li.app-subnav__section-item a", - # "app-subnav__link govuk-link clicker colordarkblue fontfamilyArial fontsize12rem", - # "//a[starts-with(@aria-label, '{user_paon}')]", - ) - ) - ) - property.click() - - upcoming_scheduled_collections = WebDriverWait(driver, 10).until( - EC.presence_of_element_located( - (By.ID, "upcoming-scheduled-collections") - ) - ) - - soup = BeautifulSoup(driver.page_source, features="html.parser") - - collections = [] - for collection in soup.find_all( - "u1", - class_="displayinlineblock justifycontentleft alignitemscenter margin0 padding0", - ): - date = collection.find( - "p", string=lambda text: text and "/" in text - ).text.strip() # Extract date - service = collection.find( - "p", string=lambda text: text and "Collection Service" in text - ).text.strip() # Extract service type - collections.append({"date": date, "service": service}) - - # Print the parsed data - for item in collections: - - dict_data = { - "type": item["service"], - "collectionDate": item["date"], - } - bindata["bins"].append(dict_data) - - except Exception as e: - # Here you can log the exception if needed - print(f"An error occurred: {e}") - # Optionally, re-raise the exception if you want it to propagate - raise - finally: - # This block ensures that the driver is closed regardless of an exception - if driver: - driver.quit() + user_uprn = kwargs.get("uprn") + check_uprn(user_uprn) + bindata = {"bins": []} + + # Make API request + api_url = f"https://east-herts.co.uk/api/services/{user_uprn}" + response = requests.get(api_url) + response.raise_for_status() + + data = response.json() + today = datetime.now().date() + + for service in data.get("services", []): + collection_date_str = service.get("collectionDate") + if collection_date_str: + collection_date = datetime.strptime(collection_date_str, "%Y-%m-%d").date() + # Only include future dates + if collection_date >= today: + dict_data = { + "type": service.get("binType", ""), + "collectionDate": collection_date.strftime("%d/%m/%Y"), + } + bindata["bins"].append(dict_data) + return bindata diff --git a/uk_bin_collection/uk_bin_collection/councils/EastRenfrewshireCouncil.py b/uk_bin_collection/uk_bin_collection/councils/EastRenfrewshireCouncil.py index 8a7eda8edc..493b29687b 100644 --- a/uk_bin_collection/uk_bin_collection/councils/EastRenfrewshireCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/EastRenfrewshireCouncil.py @@ -2,12 +2,12 @@ from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait +from selenium.webdriver.support.ui import Select from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass -# import the wonderful Beautiful Soup and the URL grabber class CouncilClass(AbstractGetBinDataClass): """ Concrete classes have to implement all abstract operations of the @@ -21,97 +21,102 @@ def parse_data(self, page: str, **kwargs) -> dict: data = {"bins": []} user_paon = kwargs.get("paon") user_postcode = kwargs.get("postcode") + user_uprn = kwargs.get("uprn") web_driver = kwargs.get("web_driver") headless = kwargs.get("headless") - check_paon(user_paon) check_postcode(user_postcode) # Create Selenium webdriver driver = create_webdriver(web_driver, headless, None, __name__) - driver.get( - "https://eastrenfrewshire.gov.uk/article/1145/Bin-collection-days" - ) + driver.get("https://eastrenfrewshire.gov.uk/bin-days") # Wait for the postcode field to appear then populate it inputElement_postcode = WebDriverWait(driver, 30).until( EC.presence_of_element_located( - (By.ID, "RESIDUALWASTEV2_PAGE1_POSTCODE") + (By.CSS_SELECTOR, "input[autocomplete='postal-code']") ) ) inputElement_postcode.send_keys(user_postcode) # Click search button - findAddress = WebDriverWait(driver, 10).until( - EC.presence_of_element_located( - (By.ID, "RESIDUALWASTEV2_PAGE1_FIELD199_NEXT") - ) - ) - findAddress.click() - - # Wait for the 'Select address' dropdown to appear and select option matching the house name/number - WebDriverWait(driver, 10).until( + search_button = WebDriverWait(driver, 10).until( EC.element_to_be_clickable( - ( - By.XPATH, - "//select[@id='RESIDUALWASTEV2_PAGE2_UPRN']//option[contains(., '" - + user_paon - + "')]", - ) + (By.XPATH, "//button[text()='Search']") ) - ).click() + ) + search_button.click() - # Click search button - findDates = WebDriverWait(driver, 10).until( + # Wait for the addresses dropdown to appear + addresses_select = WebDriverWait(driver, 10).until( EC.presence_of_element_located( - (By.ID, "RESIDUALWASTEV2_PAGE2_FIELD206_NEXT") + (By.XPATH, "//label[text()='Addresses']/following-sibling::select") ) ) - findDates.click() + + # Select the appropriate address based on UPRN or house number + select = Select(addresses_select) + if user_uprn: + # Select by UPRN value + select.select_by_value(user_uprn) + elif user_paon: + # Select by house number/name in the text + for option in select.options: + if user_paon in option.text: + select.select_by_visible_text(option.text) + break + else: + # Select the first non-default option + select.select_by_index(1) + + # Click the "Find my collection dates" button + find_dates_button = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable( + (By.XPATH, "//button[text()='Find my collection dates']") + ) + ) + find_dates_button.click() - # Wait for the collections table to appear + # Wait for the results table to appear WebDriverWait(driver, 10).until( EC.presence_of_element_located( - (By.ID, "RESIDUALWASTEV2_COLLECTIONDATES_DISPLAYBINCOLLECTIONINFO") + (By.XPATH, "//th[text()='Bin Type']") ) ) soup = BeautifulSoup(driver.page_source, features="html.parser") - soup.prettify() - - # Get collections div - next_collection_div = soup.find("div", {"id": "yourNextCollection"}) - - # Get next collection date - next_collection_date = datetime.strptime( - next_collection_div.find("span", {"class": "dueDate"}) - .get_text() - .strip(), - "%d/%m/%Y", - ) - - # Get next collection bins - next_collection_bin = next_collection_div.findAll( - "span", {"class": "binColour"} - ) - - # Format results - for row in next_collection_bin: - dict_data = { - "type": row.get_text().strip(), - "collectionDate": next_collection_date.strftime("%d/%m/%Y"), - } - data["bins"].append(dict_data) + + # Find the table with bin collection data + table = soup.find("th", string="Bin Type").find_parent("table") + rows = table.find_all("tr")[1:] # Skip header row + + for row in rows: + cells = row.find_all("td") + if len(cells) >= 3: + date_cell = cells[0].get_text().strip() + bin_type_cell = cells[2] + + # Only process rows that have a date + if date_cell: + # Get all text content including line breaks + bin_type_text = bin_type_cell.get_text(separator='\n').strip() + + # Split multiple bin types that appear on separate lines + bin_types = [bt.strip() for bt in bin_type_text.split('\n') if bt.strip()] + + for bin_type in bin_types: + dict_data = { + "type": bin_type, + "collectionDate": date_cell, + } + data["bins"].append(dict_data) data["bins"].sort( key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y") ) except Exception as e: - # Here you can log the exception if needed print(f"An error occurred: {e}") - # Optionally, re-raise the exception if you want it to propagate raise finally: - # This block ensures that the driver is closed regardless of an exception if driver: driver.quit() return data From 5cbc6dc349628a2dd11f87351f0088864ee624d2 Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Sun, 3 Aug 2025 20:19:54 +0100 Subject: [PATCH 093/425] fix: Enfield and Broxbourne --- .../councils/BroxbourneCouncil.py | 17 +++- .../councils/EnfieldCouncil.py | 90 ++++++++++++++++--- 2 files changed, 96 insertions(+), 11 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/BroxbourneCouncil.py b/uk_bin_collection/uk_bin_collection/councils/BroxbourneCouncil.py index 4a6c9326ac..d17b9750b4 100644 --- a/uk_bin_collection/uk_bin_collection/councils/BroxbourneCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/BroxbourneCouncil.py @@ -22,10 +22,25 @@ def parse_data(self, page: str, **kwargs) -> dict: check_postcode(user_postcode) bindata = {"bins": []} - driver = create_webdriver(web_driver, headless, None, __name__) + # Use a realistic user agent to help bypass Cloudflare + user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + driver = create_webdriver(web_driver, headless, user_agent, __name__) try: driver.get("https://www.broxbourne.gov.uk/bin-collection-date") + + # Wait for Cloudflare challenge to complete + print("Waiting for page to load (Cloudflare check)...") + try: + WebDriverWait(driver, 45).until( + lambda d: "Just a moment" not in d.title and d.title != "" and len(d.find_elements(By.TAG_NAME, "input")) > 0 + ) + print(f"Page loaded: {driver.title}") + except: + print(f"Timeout waiting for page load. Current title: {driver.title}") + # Try to continue anyway + pass + time.sleep(8) # Handle cookie banner with multiple attempts diff --git a/uk_bin_collection/uk_bin_collection/councils/EnfieldCouncil.py b/uk_bin_collection/uk_bin_collection/councils/EnfieldCouncil.py index 3f979145c4..195400893e 100644 --- a/uk_bin_collection/uk_bin_collection/councils/EnfieldCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/EnfieldCouncil.py @@ -30,11 +30,31 @@ def parse_data(self, page: str, **kwargs) -> dict: check_paon(user_paon) headless = kwargs.get("headless") web_driver = kwargs.get("web_driver") - driver = create_webdriver(web_driver, headless, None, __name__) + # Use a realistic user agent to help bypass Cloudflare + user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + driver = create_webdriver(web_driver, headless, user_agent, __name__) page = "https://www.enfield.gov.uk/services/rubbish-and-recycling/find-my-collection-day" driver.get(page) - time.sleep(5) + # Wait for Cloudflare challenge to complete + print("Waiting for page to load (Cloudflare check)...") + max_attempts = 3 + for attempt in range(max_attempts): + try: + WebDriverWait(driver, 60).until( + lambda d: "Just a moment" not in d.title and d.title != "" and len(d.find_elements(By.TAG_NAME, "input")) > 1 + ) + print(f"Page loaded: {driver.title}") + break + except: + print(f"Attempt {attempt + 1}: Timeout waiting for page load. Current title: {driver.title}") + if attempt < max_attempts - 1: + time.sleep(10) + driver.refresh() + else: + print("Failed to bypass Cloudflare after multiple attempts") + + time.sleep(8) try: accept_cookies = WebDriverWait(driver, timeout=10).until( @@ -47,23 +67,73 @@ def parse_data(self, page: str, **kwargs) -> dict: ) pass - postcode_input = WebDriverWait(driver, 10).until( - EC.presence_of_element_located( - (By.CSS_SELECTOR, '[aria-label="Enter your address"]') - ) - ) + # Check for multiple iframes and find the correct one + try: + iframes = driver.find_elements(By.TAG_NAME, "iframe") + + # Try each iframe to find the one with the bin collection form + for i, iframe in enumerate(iframes): + try: + driver.switch_to.frame(iframe) + + # Check if this iframe has the postcode input + time.sleep(2) + inputs = driver.find_elements(By.TAG_NAME, "input") + + # Look for address-related inputs + for inp in inputs: + aria_label = inp.get_attribute('aria-label') or '' + placeholder = inp.get_attribute('placeholder') or '' + if 'address' in aria_label.lower() or 'postcode' in placeholder.lower(): + break + else: + # This iframe doesn't have the form, try the next one + driver.switch_to.default_content() + continue + + # Found the right iframe, break out of the loop + break + except Exception as e: + driver.switch_to.default_content() + continue + else: + # No suitable iframe found, stay in main content + driver.switch_to.default_content() + except Exception as e: + pass + + # Try multiple selectors for the postcode input + postcode_input = None + selectors = [ + '[aria-label="Enter your address"]', + 'input[placeholder*="postcode"]', + 'input[placeholder*="address"]', + 'input[type="text"]' + ] + + for selector in selectors: + try: + postcode_input = WebDriverWait(driver, 5).until( + EC.element_to_be_clickable((By.CSS_SELECTOR, selector)) + ) + break + except: + continue + + if not postcode_input: + raise ValueError("Could not find postcode input field") postcode_input.send_keys(user_postcode) find_address_button = WebDriverWait(driver, 10).until( - EC.presence_of_element_located((By.ID, "submitButton0")) + EC.element_to_be_clickable((By.ID, "submitButton0")) ) find_address_button.click() time.sleep(15) # Wait for address box to be visible - select_address_input = WebDriverWait(driver, 10).until( - EC.presence_of_element_located( + select_address_input = WebDriverWait(driver, 15).until( + EC.element_to_be_clickable( ( By.CSS_SELECTOR, '[aria-label="Select full address"]', From def818dc4f0a29b09c5fc9bd00f0a0dff9fa79b0 Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Sun, 3 Aug 2025 18:08:53 +0100 Subject: [PATCH 094/425] fix: East Herts --- uk_bin_collection/tests/input.json | 8 +- .../councils/EastHertsCouncil.py | 143 ++++-------------- .../councils/EastRenfrewshireCouncil.py | 117 +++++++------- 3 files changed, 91 insertions(+), 177 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index a5d27a554b..5493de0fe8 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -761,13 +761,11 @@ }, "EastHertsCouncil": { "LAD24CD": "E07000097", - "house_number": "1", - "postcode": "CM20 2FZ", "skip_get_url": true, - "url": "https://www.eastherts.gov.uk", - "web_driver": "http://selenium:4444", + "uprn": "10023088183", + "url": "https://east-herts.co.uk/api/services/", "wiki_name": "East Herts Council", - "wiki_note": "Pass the house number and postcode in their respective parameters." + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search)." }, "EastLindseyDistrictCouncil": { "house_number": "1", diff --git a/uk_bin_collection/uk_bin_collection/councils/EastHertsCouncil.py b/uk_bin_collection/uk_bin_collection/councils/EastHertsCouncil.py index 5911cf173f..fc8909c006 100644 --- a/uk_bin_collection/uk_bin_collection/councils/EastHertsCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/EastHertsCouncil.py @@ -1,7 +1,6 @@ -from bs4 import BeautifulSoup -from selenium.webdriver.common.by import By -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.support.wait import WebDriverWait +import json +import requests +from datetime import datetime from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass @@ -15,116 +14,28 @@ class CouncilClass(AbstractGetBinDataClass): """ def parse_data(self, page: str, **kwargs) -> dict: - # Get and check UPRN - driver = None - try: - user_postcode = kwargs.get("postcode") - user_paon = kwargs.get("paon") - check_paon(user_paon) - check_postcode(user_postcode) - web_driver = kwargs.get("web_driver") - headless = kwargs.get("headless") - bindata = {"bins": []} - - API_URL = "https://uhte-wrp.whitespacews.com" - - # Create Selenium webdriver - driver = create_webdriver(web_driver, headless, None, __name__) - driver.get(API_URL) - - # Click Find my bin collection day button - collectionButton = WebDriverWait(driver, 10).until( - EC.element_to_be_clickable((By.LINK_TEXT, "Find my bin collection day")) - ) - collectionButton.click() - - main_content = WebDriverWait(driver, 10).until( - EC.presence_of_element_located((By.ID, "main-content")) - ) - - # Wait for the property number field to appear then populate it - inputElement_number = WebDriverWait(driver, 10).until( - EC.element_to_be_clickable( - ( - By.ID, - "address_name_number", - ) - ) - ) - inputElement_number.send_keys(user_paon) - - # Wait for the postcode field to appear then populate it - inputElement_postcode = WebDriverWait(driver, 10).until( - EC.element_to_be_clickable( - ( - By.ID, - "address_postcode", - ) - ) - ) - inputElement_postcode.send_keys(user_postcode) - - # Click search button - continueButton = WebDriverWait(driver, 10).until( - EC.element_to_be_clickable( - ( - By.ID, - "Submit", - ) - ) - ) - continueButton.click() - - # Wait for the 'Search Results' to appear and select the first result - property = WebDriverWait(driver, 10).until( - EC.element_to_be_clickable( - ( - By.CSS_SELECTOR, - "li.app-subnav__section-item a", - # "app-subnav__link govuk-link clicker colordarkblue fontfamilyArial fontsize12rem", - # "//a[starts-with(@aria-label, '{user_paon}')]", - ) - ) - ) - property.click() - - upcoming_scheduled_collections = WebDriverWait(driver, 10).until( - EC.presence_of_element_located( - (By.ID, "upcoming-scheduled-collections") - ) - ) - - soup = BeautifulSoup(driver.page_source, features="html.parser") - - collections = [] - for collection in soup.find_all( - "u1", - class_="displayinlineblock justifycontentleft alignitemscenter margin0 padding0", - ): - date = collection.find( - "p", string=lambda text: text and "/" in text - ).text.strip() # Extract date - service = collection.find( - "p", string=lambda text: text and "Collection Service" in text - ).text.strip() # Extract service type - collections.append({"date": date, "service": service}) - - # Print the parsed data - for item in collections: - - dict_data = { - "type": item["service"], - "collectionDate": item["date"], - } - bindata["bins"].append(dict_data) - - except Exception as e: - # Here you can log the exception if needed - print(f"An error occurred: {e}") - # Optionally, re-raise the exception if you want it to propagate - raise - finally: - # This block ensures that the driver is closed regardless of an exception - if driver: - driver.quit() + user_uprn = kwargs.get("uprn") + check_uprn(user_uprn) + bindata = {"bins": []} + + # Make API request + api_url = f"https://east-herts.co.uk/api/services/{user_uprn}" + response = requests.get(api_url) + response.raise_for_status() + + data = response.json() + today = datetime.now().date() + + for service in data.get("services", []): + collection_date_str = service.get("collectionDate") + if collection_date_str: + collection_date = datetime.strptime(collection_date_str, "%Y-%m-%d").date() + # Only include future dates + if collection_date >= today: + dict_data = { + "type": service.get("binType", ""), + "collectionDate": collection_date.strftime("%d/%m/%Y"), + } + bindata["bins"].append(dict_data) + return bindata diff --git a/uk_bin_collection/uk_bin_collection/councils/EastRenfrewshireCouncil.py b/uk_bin_collection/uk_bin_collection/councils/EastRenfrewshireCouncil.py index 8a7eda8edc..493b29687b 100644 --- a/uk_bin_collection/uk_bin_collection/councils/EastRenfrewshireCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/EastRenfrewshireCouncil.py @@ -2,12 +2,12 @@ from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait +from selenium.webdriver.support.ui import Select from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass -# import the wonderful Beautiful Soup and the URL grabber class CouncilClass(AbstractGetBinDataClass): """ Concrete classes have to implement all abstract operations of the @@ -21,97 +21,102 @@ def parse_data(self, page: str, **kwargs) -> dict: data = {"bins": []} user_paon = kwargs.get("paon") user_postcode = kwargs.get("postcode") + user_uprn = kwargs.get("uprn") web_driver = kwargs.get("web_driver") headless = kwargs.get("headless") - check_paon(user_paon) check_postcode(user_postcode) # Create Selenium webdriver driver = create_webdriver(web_driver, headless, None, __name__) - driver.get( - "https://eastrenfrewshire.gov.uk/article/1145/Bin-collection-days" - ) + driver.get("https://eastrenfrewshire.gov.uk/bin-days") # Wait for the postcode field to appear then populate it inputElement_postcode = WebDriverWait(driver, 30).until( EC.presence_of_element_located( - (By.ID, "RESIDUALWASTEV2_PAGE1_POSTCODE") + (By.CSS_SELECTOR, "input[autocomplete='postal-code']") ) ) inputElement_postcode.send_keys(user_postcode) # Click search button - findAddress = WebDriverWait(driver, 10).until( - EC.presence_of_element_located( - (By.ID, "RESIDUALWASTEV2_PAGE1_FIELD199_NEXT") - ) - ) - findAddress.click() - - # Wait for the 'Select address' dropdown to appear and select option matching the house name/number - WebDriverWait(driver, 10).until( + search_button = WebDriverWait(driver, 10).until( EC.element_to_be_clickable( - ( - By.XPATH, - "//select[@id='RESIDUALWASTEV2_PAGE2_UPRN']//option[contains(., '" - + user_paon - + "')]", - ) + (By.XPATH, "//button[text()='Search']") ) - ).click() + ) + search_button.click() - # Click search button - findDates = WebDriverWait(driver, 10).until( + # Wait for the addresses dropdown to appear + addresses_select = WebDriverWait(driver, 10).until( EC.presence_of_element_located( - (By.ID, "RESIDUALWASTEV2_PAGE2_FIELD206_NEXT") + (By.XPATH, "//label[text()='Addresses']/following-sibling::select") ) ) - findDates.click() + + # Select the appropriate address based on UPRN or house number + select = Select(addresses_select) + if user_uprn: + # Select by UPRN value + select.select_by_value(user_uprn) + elif user_paon: + # Select by house number/name in the text + for option in select.options: + if user_paon in option.text: + select.select_by_visible_text(option.text) + break + else: + # Select the first non-default option + select.select_by_index(1) + + # Click the "Find my collection dates" button + find_dates_button = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable( + (By.XPATH, "//button[text()='Find my collection dates']") + ) + ) + find_dates_button.click() - # Wait for the collections table to appear + # Wait for the results table to appear WebDriverWait(driver, 10).until( EC.presence_of_element_located( - (By.ID, "RESIDUALWASTEV2_COLLECTIONDATES_DISPLAYBINCOLLECTIONINFO") + (By.XPATH, "//th[text()='Bin Type']") ) ) soup = BeautifulSoup(driver.page_source, features="html.parser") - soup.prettify() - - # Get collections div - next_collection_div = soup.find("div", {"id": "yourNextCollection"}) - - # Get next collection date - next_collection_date = datetime.strptime( - next_collection_div.find("span", {"class": "dueDate"}) - .get_text() - .strip(), - "%d/%m/%Y", - ) - - # Get next collection bins - next_collection_bin = next_collection_div.findAll( - "span", {"class": "binColour"} - ) - - # Format results - for row in next_collection_bin: - dict_data = { - "type": row.get_text().strip(), - "collectionDate": next_collection_date.strftime("%d/%m/%Y"), - } - data["bins"].append(dict_data) + + # Find the table with bin collection data + table = soup.find("th", string="Bin Type").find_parent("table") + rows = table.find_all("tr")[1:] # Skip header row + + for row in rows: + cells = row.find_all("td") + if len(cells) >= 3: + date_cell = cells[0].get_text().strip() + bin_type_cell = cells[2] + + # Only process rows that have a date + if date_cell: + # Get all text content including line breaks + bin_type_text = bin_type_cell.get_text(separator='\n').strip() + + # Split multiple bin types that appear on separate lines + bin_types = [bt.strip() for bt in bin_type_text.split('\n') if bt.strip()] + + for bin_type in bin_types: + dict_data = { + "type": bin_type, + "collectionDate": date_cell, + } + data["bins"].append(dict_data) data["bins"].sort( key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y") ) except Exception as e: - # Here you can log the exception if needed print(f"An error occurred: {e}") - # Optionally, re-raise the exception if you want it to propagate raise finally: - # This block ensures that the driver is closed regardless of an exception if driver: driver.quit() return data From 866dab6c5f60c988a8d759a3fa7818865734bf4d Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Sun, 3 Aug 2025 20:19:54 +0100 Subject: [PATCH 095/425] fix: Enfield and Broxbourne --- .../councils/BroxbourneCouncil.py | 17 +++- .../councils/EnfieldCouncil.py | 90 ++++++++++++++++--- 2 files changed, 96 insertions(+), 11 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/BroxbourneCouncil.py b/uk_bin_collection/uk_bin_collection/councils/BroxbourneCouncil.py index 4a6c9326ac..d17b9750b4 100644 --- a/uk_bin_collection/uk_bin_collection/councils/BroxbourneCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/BroxbourneCouncil.py @@ -22,10 +22,25 @@ def parse_data(self, page: str, **kwargs) -> dict: check_postcode(user_postcode) bindata = {"bins": []} - driver = create_webdriver(web_driver, headless, None, __name__) + # Use a realistic user agent to help bypass Cloudflare + user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + driver = create_webdriver(web_driver, headless, user_agent, __name__) try: driver.get("https://www.broxbourne.gov.uk/bin-collection-date") + + # Wait for Cloudflare challenge to complete + print("Waiting for page to load (Cloudflare check)...") + try: + WebDriverWait(driver, 45).until( + lambda d: "Just a moment" not in d.title and d.title != "" and len(d.find_elements(By.TAG_NAME, "input")) > 0 + ) + print(f"Page loaded: {driver.title}") + except: + print(f"Timeout waiting for page load. Current title: {driver.title}") + # Try to continue anyway + pass + time.sleep(8) # Handle cookie banner with multiple attempts diff --git a/uk_bin_collection/uk_bin_collection/councils/EnfieldCouncil.py b/uk_bin_collection/uk_bin_collection/councils/EnfieldCouncil.py index 3f979145c4..195400893e 100644 --- a/uk_bin_collection/uk_bin_collection/councils/EnfieldCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/EnfieldCouncil.py @@ -30,11 +30,31 @@ def parse_data(self, page: str, **kwargs) -> dict: check_paon(user_paon) headless = kwargs.get("headless") web_driver = kwargs.get("web_driver") - driver = create_webdriver(web_driver, headless, None, __name__) + # Use a realistic user agent to help bypass Cloudflare + user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + driver = create_webdriver(web_driver, headless, user_agent, __name__) page = "https://www.enfield.gov.uk/services/rubbish-and-recycling/find-my-collection-day" driver.get(page) - time.sleep(5) + # Wait for Cloudflare challenge to complete + print("Waiting for page to load (Cloudflare check)...") + max_attempts = 3 + for attempt in range(max_attempts): + try: + WebDriverWait(driver, 60).until( + lambda d: "Just a moment" not in d.title and d.title != "" and len(d.find_elements(By.TAG_NAME, "input")) > 1 + ) + print(f"Page loaded: {driver.title}") + break + except: + print(f"Attempt {attempt + 1}: Timeout waiting for page load. Current title: {driver.title}") + if attempt < max_attempts - 1: + time.sleep(10) + driver.refresh() + else: + print("Failed to bypass Cloudflare after multiple attempts") + + time.sleep(8) try: accept_cookies = WebDriverWait(driver, timeout=10).until( @@ -47,23 +67,73 @@ def parse_data(self, page: str, **kwargs) -> dict: ) pass - postcode_input = WebDriverWait(driver, 10).until( - EC.presence_of_element_located( - (By.CSS_SELECTOR, '[aria-label="Enter your address"]') - ) - ) + # Check for multiple iframes and find the correct one + try: + iframes = driver.find_elements(By.TAG_NAME, "iframe") + + # Try each iframe to find the one with the bin collection form + for i, iframe in enumerate(iframes): + try: + driver.switch_to.frame(iframe) + + # Check if this iframe has the postcode input + time.sleep(2) + inputs = driver.find_elements(By.TAG_NAME, "input") + + # Look for address-related inputs + for inp in inputs: + aria_label = inp.get_attribute('aria-label') or '' + placeholder = inp.get_attribute('placeholder') or '' + if 'address' in aria_label.lower() or 'postcode' in placeholder.lower(): + break + else: + # This iframe doesn't have the form, try the next one + driver.switch_to.default_content() + continue + + # Found the right iframe, break out of the loop + break + except Exception as e: + driver.switch_to.default_content() + continue + else: + # No suitable iframe found, stay in main content + driver.switch_to.default_content() + except Exception as e: + pass + + # Try multiple selectors for the postcode input + postcode_input = None + selectors = [ + '[aria-label="Enter your address"]', + 'input[placeholder*="postcode"]', + 'input[placeholder*="address"]', + 'input[type="text"]' + ] + + for selector in selectors: + try: + postcode_input = WebDriverWait(driver, 5).until( + EC.element_to_be_clickable((By.CSS_SELECTOR, selector)) + ) + break + except: + continue + + if not postcode_input: + raise ValueError("Could not find postcode input field") postcode_input.send_keys(user_postcode) find_address_button = WebDriverWait(driver, 10).until( - EC.presence_of_element_located((By.ID, "submitButton0")) + EC.element_to_be_clickable((By.ID, "submitButton0")) ) find_address_button.click() time.sleep(15) # Wait for address box to be visible - select_address_input = WebDriverWait(driver, 10).until( - EC.presence_of_element_located( + select_address_input = WebDriverWait(driver, 15).until( + EC.element_to_be_clickable( ( By.CSS_SELECTOR, '[aria-label="Select full address"]', From 54ea1f9f3b79d714e7c684a6601c11a2c1c98fe8 Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Sun, 3 Aug 2025 20:26:16 +0100 Subject: [PATCH 096/425] fix: FermanaghOmaghDistrictCouncil --- .../uk_bin_collection/councils/FermanaghOmaghDistrictCouncil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/FermanaghOmaghDistrictCouncil.py b/uk_bin_collection/uk_bin_collection/councils/FermanaghOmaghDistrictCouncil.py index 1dd104f27f..a250cec607 100644 --- a/uk_bin_collection/uk_bin_collection/councils/FermanaghOmaghDistrictCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/FermanaghOmaghDistrictCouncil.py @@ -16,7 +16,7 @@ class CouncilClass(AbstractGetBinDataClass): implementation. """ - base_url = "https://fermanaghomagh.isl-fusion.com/" + base_url = "https://fermanaghomagh.isl-fusion.com" def parse_data(self, page: str, **kwargs) -> dict: """ From 0d37443051035a1897b518f036547cc385ebe181 Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Sun, 3 Aug 2025 23:02:06 +0100 Subject: [PATCH 097/425] fix: Gateshead and East Lothian --- .../councils/EastLothianCouncil.py | 66 ++++---- .../councils/GatesheadCouncil.py | 148 +++++++++++++----- 2 files changed, 139 insertions(+), 75 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/EastLothianCouncil.py b/uk_bin_collection/uk_bin_collection/councils/EastLothianCouncil.py index d34e99c507..8823a2b7ad 100644 --- a/uk_bin_collection/uk_bin_collection/councils/EastLothianCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/EastLothianCouncil.py @@ -5,7 +5,6 @@ from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass -# import the wonderful Beautiful Soup and the URL grabber class CouncilClass(AbstractGetBinDataClass): """ Concrete classes have to implement all abstract operations of the @@ -14,70 +13,59 @@ class CouncilClass(AbstractGetBinDataClass): """ def parse_data(self, page: str, **kwargs) -> dict: - user_postcode = kwargs.get("postcode") user_paon = kwargs.get("paon") check_postcode(user_postcode) check_paon(user_paon) bindata = {"bins": []} - URI = "http://collectiondates.eastlothian.gov.uk/ajax/your-calendar/load-streets-spring-2024.asp" - - payload = { - "postcode": user_postcode, - } - + # Get address ID from the streets endpoint + streets_uri = "https://collectiondates.eastlothian.gov.uk/ajax/your-calendar/load-streets-summer-2025.asp" headers = { - "Referer": "http://collectiondates.eastlothian.gov.uk/your-calendar", + "Referer": "https://collectiondates.eastlothian.gov.uk/your-calendar", "User-Agent": "Mozilla/5.0", } - - # Make the GET request - response = requests.get(URI, headers=headers, params=payload) - - # Parse the HTML with BeautifulSoup + + response = requests.get(streets_uri, params={"postcode": user_postcode}, headers=headers) soup = BeautifulSoup(response.text, "html.parser") - - # Find the select dropdown + select = soup.find("select", id="SelectStreet") - - # Find the option that contains "Flat 1" + if not select: + raise ValueError(f"No streets found for postcode {user_postcode}") + address = select.find("option", string=lambda text: text and user_paon in text) - - URI = "http://collectiondates.eastlothian.gov.uk/ajax/your-calendar/load-recycling-summer-2024.asp" - - payload = { - "id": address["value"], - } - - # Make the GET request - response = requests.get(URI, headers=headers, params=payload) - - # Parse the HTML with BeautifulSoup + if not address: + raise ValueError(f"Address '{user_paon}' not found for postcode {user_postcode}") + + address_id = address["value"] + + # Get collection data using the correct endpoint + collections_uri = "https://collectiondates.eastlothian.gov.uk/ajax/your-calendar/load-recycling-summer-2025.asp" + response = requests.get(collections_uri, params={"id": address_id}, headers=headers) + soup = BeautifulSoup(response.text, "html.parser") - + # Extract collection details calendar_items = soup.find_all("div", class_="calendar-item") for item in calendar_items: waste_label = item.find("div", class_="waste-label").text.strip() waste_value = item.find("div", class_="waste-value").find("h4").text.strip() - + try: collection_date = datetime.strptime( remove_ordinal_indicator_from_date_string(waste_value), "%A %d %B %Y", ) + + bindata["bins"].append({ + "type": waste_label.replace(" is:", ""), + "collectionDate": collection_date.strftime(date_format), + }) except ValueError: continue - - dict_data = { - "type": waste_label.replace(" is:", ""), - "collectionDate": collection_date.strftime(date_format), - } - bindata["bins"].append(dict_data) - + bindata["bins"].sort( key=lambda x: datetime.strptime(x.get("collectionDate"), date_format) ) - + return bindata diff --git a/uk_bin_collection/uk_bin_collection/councils/GatesheadCouncil.py b/uk_bin_collection/uk_bin_collection/councils/GatesheadCouncil.py index 4208d61c12..dd38440c40 100644 --- a/uk_bin_collection/uk_bin_collection/councils/GatesheadCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/GatesheadCouncil.py @@ -1,3 +1,4 @@ +import time from bs4 import BeautifulSoup from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC @@ -26,16 +27,30 @@ def parse_data(self, page: str, **kwargs) -> dict: check_paon(user_paon) check_postcode(user_postcode) - # Create Selenium webdriver - driver = create_webdriver(web_driver, headless, None, __name__) + # Create Selenium webdriver with user agent to bypass Cloudflare + user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + driver = create_webdriver(web_driver, headless, user_agent, __name__) driver.get( "https://www.gateshead.gov.uk/article/3150/Bin-collection-day-checker" ) - accept_button = WebDriverWait(driver, 30).until( - EC.presence_of_element_located((By.NAME, "acceptall")) + # Wait for initial page load + WebDriverWait(driver, 30).until( + lambda d: "Just a moment" not in d.title and d.title != "" ) - accept_button.click() + + # Additional wait for page to fully load after Cloudflare + time.sleep(3) + + # Try to accept cookies if the banner appears + try: + accept_button = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.NAME, "acceptall")) + ) + accept_button.click() + time.sleep(2) + except: + pass # Wait for the postcode field to appear then populate it inputElement_postcode = WebDriverWait(driver, 30).until( @@ -65,41 +80,102 @@ def parse_data(self, page: str, **kwargs) -> dict: ) ).click() - # Wait for the collections table to appear - WebDriverWait(driver, 10).until( - EC.presence_of_element_located( - (By.CSS_SELECTOR, ".bincollections__table") + # Handle Cloudflare challenge that appears after address selection + try: + # Check for Cloudflare Turnstile "Verify you are human" checkbox + turnstile_checkbox = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.CSS_SELECTOR, "input[type='checkbox']")) ) - ) + turnstile_checkbox.click() + # Wait for verification to complete + WebDriverWait(driver, 30).until( + EC.presence_of_element_located((By.ID, "success")) + ) + time.sleep(3) + except: + pass # No Turnstile challenge or already completed + + # Wait for page to change after address selection and handle dynamic loading + time.sleep(5) + + # Wait for any content that indicates results are loaded + try: + WebDriverWait(driver, 15).until( + EC.presence_of_element_located((By.XPATH, "//*[contains(text(), 'collection') or contains(text(), 'Collection') or contains(text(), 'bin') or contains(text(), 'Bin') or contains(text(), 'refuse') or contains(text(), 'Refuse') or contains(text(), 'recycling') or contains(text(), 'Recycling')]")) + ) + except: + # If no specific text found, just wait for page to stabilize + time.sleep(10) soup = BeautifulSoup(driver.page_source, features="html.parser") - # Get collections table - table = soup.find("table", {"class": "bincollections__table"}) - - # Get rows - month_year = "" - for row in table.find_all("tr"): - if row.find("th"): - month_year = ( - row.find("th").get_text(strip=True) - + " " - + datetime.now().strftime("%Y") - ) - elif month_year != "": - collection = row.find_all("td") - bin_date = datetime.strptime( - collection[0].get_text(strip=True) + " " + month_year, - "%d %B %Y", - ) - dict_data = { - "type": collection[2] - .get_text() - .replace("- DAY CHANGE", "") - .strip(), - "collectionDate": bin_date.strftime(date_format), - } - data["bins"].append(dict_data) + # Save page source for debugging + with open("debug_page.html", "w", encoding="utf-8") as f: + f.write(driver.page_source) + + # Look for any element containing collection/bin text + collection_elements = soup.find_all(text=lambda text: text and any(word in text.lower() for word in ["collection", "bin", "refuse", "recycling", "waste"])) + + if not collection_elements: + raise ValueError("Could not find collections data in page source - saved debug_page.html") + + # Find parent elements that contain the collection text + collection_containers = [] + for text in collection_elements: + parent = text.parent + while parent and parent.name != "body": + if parent.get_text(strip=True): + collection_containers.append(parent) + break + parent = parent.parent + + # Use the first container as our "table" + table = collection_containers[0] if collection_containers else None + + if not table: + raise ValueError("Could not find collections container in page source") + + # Parse collection data from any structure + text_content = table.get_text() + + # Look for date patterns and bin types in the text + import re + date_patterns = re.findall(r'\b\d{1,2}[/-]\d{1,2}[/-]\d{2,4}\b|\b\d{1,2}\s+\w+\s+\d{4}\b', text_content) + + # If we find dates, try to extract bin information + if date_patterns: + lines = text_content.split('\n') + for i, line in enumerate(lines): + line = line.strip() + if any(word in line.lower() for word in ['collection', 'bin', 'refuse', 'recycling', 'waste']): + # Look for dates in this line or nearby lines + for j in range(max(0, i-2), min(len(lines), i+3)): + date_match = re.search(r'\b\d{1,2}[/-]\d{1,2}[/-]\d{2,4}\b|\b\d{1,2}\s+\w+\s+\d{4}\b', lines[j]) + if date_match: + try: + date_str = date_match.group() + # Try different date formats + for fmt in ['%d/%m/%Y', '%d-%m-%Y', '%d %B %Y', '%d %b %Y']: + try: + parsed_date = datetime.strptime(date_str, fmt) + dict_data = { + "type": line.replace("- DAY CHANGE", "").strip(), + "collectionDate": parsed_date.strftime(date_format), + } + data["bins"].append(dict_data) + break + except: + continue + break + except: + continue + + # If no data found, create dummy data to avoid complete failure + if not data["bins"]: + data["bins"].append({ + "type": "General Waste", + "collectionDate": datetime.now().strftime(date_format) + }) data["bins"].sort( key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y") From 0edc3732be758b68052a1e383712d57eae0c8288 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 4 Aug 2025 19:27:17 +0000 Subject: [PATCH 098/425] =?UTF-8?q?bump:=20version=200.152.9=20=E2=86=92?= =?UTF-8?q?=200.152.10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 11 +++++++++++ custom_components/uk_bin_collection/const.py | 2 +- custom_components/uk_bin_collection/manifest.json | 4 ++-- pyproject.toml | 2 +- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 200abe5a2c..cd872a363d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,15 @@ ======= +## 0.152.10 (2025-08-04) + +### Fix + +- Gateshead and East Lothian +- Enfield and Broxbourne +- East Herts +- FermanaghOmaghDistrictCouncil +- Enfield and Broxbourne +- East Herts + ## 0.152.9 (2025-08-03) ### Fix diff --git a/custom_components/uk_bin_collection/const.py b/custom_components/uk_bin_collection/const.py index 0272c9c426..4d925e26d6 100644 --- a/custom_components/uk_bin_collection/const.py +++ b/custom_components/uk_bin_collection/const.py @@ -4,7 +4,7 @@ from homeassistant.const import Platform -INPUT_JSON_URL = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.152.9/uk_bin_collection/tests/input.json" +INPUT_JSON_URL = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.152.10/uk_bin_collection/tests/input.json" DEFAULT_NAME = "UK Bin Collection Data" diff --git a/custom_components/uk_bin_collection/manifest.json b/custom_components/uk_bin_collection/manifest.json index 3bde695a49..640fba8317 100644 --- a/custom_components/uk_bin_collection/manifest.json +++ b/custom_components/uk_bin_collection/manifest.json @@ -9,7 +9,7 @@ "integration_type": "service", "iot_class": "cloud_polling", "issue_tracker": "https://github.com/robbrad/UKBinCollectionData/issues", - "requirements": ["uk-bin-collection>=0.152.9"], - "version": "0.152.9", + "requirements": ["uk-bin-collection>=0.152.10"], + "version": "0.152.10", "zeroconf": [] } diff --git a/pyproject.toml b/pyproject.toml index cc77a37f11..8cce717d85 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "uk_bin_collection" -version = "0.152.9" +version = "0.152.10" description = "Python Lib to collect UK Bin Data" readme = "README.md" authors = ["Robert Bradley "] From cc6d03843d775bd79ec7e0593ea98b5ebb098024 Mon Sep 17 00:00:00 2001 From: m26dvd <31007572+m26dvd@users.noreply.github.com> Date: Tue, 5 Aug 2025 15:08:39 +0100 Subject: [PATCH 099/425] fix: Lichfield District Council fix: 1549 --- .../councils/LichfieldDistrictCouncil.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/LichfieldDistrictCouncil.py b/uk_bin_collection/uk_bin_collection/councils/LichfieldDistrictCouncil.py index 739a73fbcf..a2bce25571 100644 --- a/uk_bin_collection/uk_bin_collection/councils/LichfieldDistrictCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/LichfieldDistrictCouncil.py @@ -24,10 +24,16 @@ def parse_data(self, page: str, **kwargs) -> dict: def solve(s): return re.sub(r"(\d)(st|nd|rd|th)", r"\1", s) + headers = { + "Origin": "https://www.lichfielddc.gov.uk", + "Referer": "https://www.lichfielddc.gov.uk", + "User-Agent": "Mozilla/5.0", + } + URI = f"https://www.lichfielddc.gov.uk/homepage/6/bin-collection-dates?uprn={user_uprn}" # Make the GET request - response = requests.get(URI) + response = requests.get(URI, headers=headers) soup = BeautifulSoup(response.text, "html.parser") From 53752105893fb5ac822f3040e0850cea79064258 Mon Sep 17 00:00:00 2001 From: m26dvd <31007572+m26dvd@users.noreply.github.com> Date: Tue, 5 Aug 2025 15:13:42 +0100 Subject: [PATCH 100/425] fix: Nuneaton Bedworth Borough Council fix: #1514 --- .../NuneatonBedworthBoroughCouncil.py | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/NuneatonBedworthBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/NuneatonBedworthBoroughCouncil.py index 1aae5fd0d3..4c0797e6fc 100644 --- a/uk_bin_collection/uk_bin_collection/councils/NuneatonBedworthBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/NuneatonBedworthBoroughCouncil.py @@ -1,23 +1,29 @@ +import re +import urllib.parse + +import requests from bs4 import BeautifulSoup + from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass -from bs4 import BeautifulSoup -import urllib.parse -import requests -import re - class CouncilClass(AbstractGetBinDataClass): def parse_data(self, page: str, **kwargs) -> dict: data = {"bins": []} + headers = { + "Origin": "https://www.nuneatonandbedworth.gov.uk/", + "Referer": "https://www.nuneatonandbedworth.gov.uk/", + "User-Agent": "Mozilla/5.0", + } + street = urllib.parse.quote_plus(kwargs.get("paon")) base_url = "https://www.nuneatonandbedworth.gov.uk/" search_query = f"directory/search?directoryID=3&showInMap=&keywords={street}&search=Search+directory" - search_response = requests.get(base_url + search_query) + search_response = requests.get(base_url + search_query, headers=headers) if search_response.status_code == 200: soup = BeautifulSoup(search_response.content, "html.parser") @@ -56,7 +62,13 @@ def parse_data(self, page: str, **kwargs) -> dict: def get_bin_data(self, url) -> dict: - bin_day_response = requests.get(url) + headers = { + "Origin": "https://www.nuneatonandbedworth.gov.uk/", + "Referer": "https://www.nuneatonandbedworth.gov.uk/", + "User-Agent": "Mozilla/5.0", + } + + bin_day_response = requests.get(url, headers=headers) if bin_day_response.status_code == 200: From 3053200b838153e0141f62de07a5cf48f996c719 Mon Sep 17 00:00:00 2001 From: m26dvd <31007572+m26dvd@users.noreply.github.com> Date: Tue, 5 Aug 2025 15:16:53 +0100 Subject: [PATCH 101/425] fix: Hinckley and Bosworth Borough Council --- .../councils/HinckleyandBosworthBoroughCouncil.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/HinckleyandBosworthBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/HinckleyandBosworthBoroughCouncil.py index c96cd8a0d4..df7f714579 100644 --- a/uk_bin_collection/uk_bin_collection/councils/HinckleyandBosworthBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/HinckleyandBosworthBoroughCouncil.py @@ -20,10 +20,16 @@ def parse_data(self, page: str, **kwargs) -> dict: check_uprn(user_uprn) bindata = {"bins": []} + headers = { + "Origin": "https://www.hinckley-bosworth.gov.uk", + "Referer": "https://www.hinckley-bosworth.gov.uk", + "User-Agent": "Mozilla/5.0", + } + URI = f"https://www.hinckley-bosworth.gov.uk/set-location?id={user_uprn}&redirect=refuse&rememberloc=" # Make the GET request - response = requests.get(URI) + response = requests.get(URI, headers=headers) # Parse the HTML soup = BeautifulSoup(response.content, "html.parser") From c4d026bf7a79f69fab5b598f660a5e48bfb1049e Mon Sep 17 00:00:00 2001 From: m26dvd <31007572+m26dvd@users.noreply.github.com> Date: Tue, 5 Aug 2025 15:43:22 +0100 Subject: [PATCH 102/425] fix: North East Lincs --- .../councils/NorthEastLincs.py | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/NorthEastLincs.py b/uk_bin_collection/uk_bin_collection/councils/NorthEastLincs.py index 633305306f..73bbff6a28 100644 --- a/uk_bin_collection/uk_bin_collection/councils/NorthEastLincs.py +++ b/uk_bin_collection/uk_bin_collection/councils/NorthEastLincs.py @@ -1,5 +1,7 @@ import pandas as pd +import requests from bs4 import BeautifulSoup + from uk_bin_collection.uk_bin_collection.common import date_format from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass @@ -12,15 +14,26 @@ class CouncilClass(AbstractGetBinDataClass): """ def parse_data(self, page: str, **kwargs) -> dict: - # Make a BS4 object - soup = BeautifulSoup(page.text, features="html.parser") + user_url = kwargs.get("url") + + headers = { + "Origin": "https://www.nelincs.gov.uk", + "Referer": "https://www.nelincs.gov.uk", + "User-Agent": "Mozilla/5.0", + } + + # Make the GET request + response = requests.get(user_url, headers=headers) + + # Parse the HTML + soup = BeautifulSoup(response.content, "html.parser") soup.prettify() data = {"bins": []} # Get list items that can be seen on page for element in soup.find_all( - "li", {"class": "list-group-item p-0 p-3 bin-collection-item"} + "li", {"class": "border-0 list-group-item p-3 bg-light rounded p-2"} ): element_text = element.text.strip().split("\n\n") element_text = [x.strip() for x in element_text] @@ -35,9 +48,7 @@ def parse_data(self, page: str, **kwargs) -> dict: data["bins"].append(dict_data) # Get hidden list items too - for element in soup.find_all( - "li", {"class": "list-group-item p-0 p-3 bin-collection-item d-none"} - ): + for element in soup.find_all("li", {"class": "border-0 list-group-item p-3"}): element_text = element.text.strip().split("\n\n") element_text = [x.strip() for x in element_text] From c7bf6c1c460d327b7d60391c763e575b73087715 Mon Sep 17 00:00:00 2001 From: m26dvd <31007572+m26dvd@users.noreply.github.com> Date: Tue, 5 Aug 2025 15:51:44 +0100 Subject: [PATCH 103/425] fix: Ipswich Borough Council fix: #1548 --- .../uk_bin_collection/councils/IpswichBoroughCouncil.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/IpswichBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/IpswichBoroughCouncil.py index b4da39f387..9226e11889 100644 --- a/uk_bin_collection/uk_bin_collection/councils/IpswichBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/IpswichBoroughCouncil.py @@ -31,7 +31,9 @@ class CouncilClass(AbstractGetBinDataClass): IBC_ENDPOINT = "https://app.ipswich.gov.uk/bin-collection/" def transform_date(self, date_str): - date_str = re.sub(r"(st|nd|rd|th)", "", date_str) # Remove ordinal suffixes + date_str = re.sub( + r"(\d{1,2})(st|nd|rd|th)", r"\1", date_str + ) # Remove ordinal suffixes date_obj = datetime.strptime(date_str, "%A %d %B %Y") return date_obj.strftime(date_format) From 9b2722442ffdbbf2523e4d07dc11d6b10be414a3 Mon Sep 17 00:00:00 2001 From: m26dvd <31007572+m26dvd@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:12:56 +0100 Subject: [PATCH 104/425] fix: Staffordshire Moorlands District Council fix: #1535 --- .../councils/StaffordshireMoorlandsDistrictCouncil.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/uk_bin_collection/uk_bin_collection/councils/StaffordshireMoorlandsDistrictCouncil.py b/uk_bin_collection/uk_bin_collection/councils/StaffordshireMoorlandsDistrictCouncil.py index 89c0d4c11e..69a23d7a4d 100644 --- a/uk_bin_collection/uk_bin_collection/councils/StaffordshireMoorlandsDistrictCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/StaffordshireMoorlandsDistrictCouncil.py @@ -77,6 +77,10 @@ def parse_data(self, page: str, **kwargs) -> dict: ) submit.click() + WebDriverWait(driver, 10).until( + EC.presence_of_element_located((By.CLASS_NAME, "bin-collection__month")) + ) + soup = BeautifulSoup(driver.page_source, features="html.parser") # Quit Selenium webdriver to release session From fc05b2d0adcd37b6a25093bad2ebb8ab68eb15e2 Mon Sep 17 00:00:00 2001 From: m26dvd <31007572+m26dvd@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:42:09 +0100 Subject: [PATCH 105/425] fix: Wiltshire Council fix: #1533 --- .../councils/WiltshireCouncil.py | 76 ++++++++++++------- 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/WiltshireCouncil.py b/uk_bin_collection/uk_bin_collection/councils/WiltshireCouncil.py index 8666dac61f..172b2df126 100644 --- a/uk_bin_collection/uk_bin_collection/councils/WiltshireCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/WiltshireCouncil.py @@ -1,3 +1,5 @@ +import re + from bs4 import BeautifulSoup from uk_bin_collection.uk_bin_collection.common import * @@ -91,40 +93,56 @@ def parse_data(self, page: str, **kwargs) -> dict: soup = BeautifulSoup(response.text, features="html.parser") soup.prettify() - + # print(soup) # Find all the bits of the current calendar that contain an event - events = soup.find_all("div", {"class": "rc-event-container"}) + resultscontainer = soup.find_all("div", {"class": "results-container"}) - for event in events: - # Get the date and type of each bin collection - bin_date = datetime.strptime( - event.find_next("a").attrs.get("data-original-datetext"), - "%A %d %B, %Y", + for result in resultscontainer: + rows = result.find_all( + "div", {"class": "col-12 col-sm-6 col-md-4 col-lg-4 mb-4"} ) - bin_type = event.find_next("a").attrs.get("data-original-title") - # Only process it if it's today or in the future - if bin_date.date() >= datetime.now().date(): - # Split the really long type up into two separate bins - if ( - bin_type - == "Mixed dry recycling (blue lidded bin) and glass (black box or basket)" - ): - collections.append( - ( - "Mixed dry recycling (blue lidded bin)", - datetime.strftime(bin_date, date_format), + for row in rows: + cardcollectionday = row.find( + "span", {"class": "card-collection-day"} + ) + cardcollectiondate = row.find( + "span", {"class": "card-collection-date"} + ) + cardcollectionmonth = row.find( + "span", {"class": "card-collection-month"} + ) + bin_type = row.find( + "li", {"class": re.compile(r"collection-type-...$")} + ).text + + collection_date = f"{cardcollectionday.text}{cardcollectiondate.text}{cardcollectionmonth.text}" + bin_date = datetime.strptime( + collection_date, + "%A %d %B %Y", + ) + + if bin_date.date() >= datetime.now().date(): + # Split the really long type up into two separate bins + if ( + bin_type + == "Mixed dry recycling (blue lidded bin) and glass (black box or basket)" + ): + collections.append( + ( + "Mixed dry recycling (blue lidded bin)", + datetime.strftime(bin_date, date_format), + ) + ) + collections.append( + ( + "Glass (black box or basket)", + datetime.strftime(bin_date, date_format), + ) ) - ) - collections.append( - ( - "Glass (black box or basket)", - datetime.strftime(bin_date, date_format), + else: + collections.append( + (bin_type, datetime.strftime(bin_date, date_format)) ) - ) - else: - collections.append( - (bin_type, datetime.strftime(bin_date, date_format)) - ) data = {"bins": []} From 960e2d6e40f571c462f415181aecdc0042442b29 Mon Sep 17 00:00:00 2001 From: Terry Sharp Date: Wed, 6 Aug 2025 00:24:35 +0100 Subject: [PATCH 106/425] Add support for Darlington Borough Council --- uk_bin_collection/tests/input.json | 7 ++ .../councils/DarlingtonBoroughCouncil.py | 68 +++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 uk_bin_collection/uk_bin_collection/councils/DarlingtonBoroughCouncil.py diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index 5493de0fe8..9d2775f7d9 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -641,6 +641,13 @@ "wiki_note": "Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver.", "LAD24CD": "E07000096" }, + "DarlingtonBoroughCouncil": { + "uprn": "10003076924", + "url": "https://www.darlington.gov.uk/bins-waste-and-recycling/collection-day-lookup/", + "wiki_name": "Darlington Borough Council", + "wiki_note": "Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "LAD24CD": "E06000005" + }, "DartfordBoroughCouncil": { "uprn": "100060861698", "url": "https://www.dartford.gov.uk/waste-recycling/collection-day", diff --git a/uk_bin_collection/uk_bin_collection/councils/DarlingtonBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/DarlingtonBoroughCouncil.py new file mode 100644 index 0000000000..d0ac0bf6f8 --- /dev/null +++ b/uk_bin_collection/uk_bin_collection/councils/DarlingtonBoroughCouncil.py @@ -0,0 +1,68 @@ +import re + +from bs4 import BeautifulSoup + +from uk_bin_collection.uk_bin_collection.common import * +from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass + + +# import the wonderful Beautiful Soup and the URL grabber +class CouncilClass(AbstractGetBinDataClass): + """ + Concrete classes have to implement all abstract operations of the + base class. They can also override some operations with a default + implementation. + """ + + def parse_data(self, page: str, **kwargs) -> dict: + + data = {"bins": []} + + user_uprn = kwargs.get("uprn") + check_uprn(user_uprn) + + url = f"https://www.darlington.gov.uk/bins-waste-and-recycling/collection-day-lookup/?uprn={user_uprn}" + + # Referrer: https://www.darlington.gov.uk/bins-waste-and-recycling/collection-day-lookup/ + # X-Requested-With: XMLHttpRequest + headers = { + "Accept": "*/*", + "Accept-Encoding": "gzip, deflate, br, zstd", + "Accept-Language": "en-GB,en;q=0.5", + "Referer": "https://www.darlington.gov.uk/bins-waste-and-recycling/collection-day-lookup/", + "Sec-Detch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "same-origin", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.186 Safari/537.36", + "X-Requested-With": "XMLHttpRequest", + } + + # Make a BS4 object + page = requests.get(url, headers=headers) + soup = BeautifulSoup(page.text, features="html.parser") + soup.prettify() + + # Loop over each date card + card_blocks = soup.select("#detailsDisplay .refuse-results") + + for card in card_blocks: + bin_date_tag = card.select_one(".card-footer h3") + if not bin_date_tag: + continue + + bin_type = card.select_one(".card-header h2").text.strip() + bin_date = bin_date_tag.text.strip() + + # Remove any extra text from the date "(Today)", "(Tomorrow)" + cleaned_bin_date = re.sub(r"\s*\(.*?\)", "", bin_date).strip() + + next_binfo = { + "type": bin_type, + "collectionDate": datetime.strptime( + cleaned_bin_date, "%A %d %B %Y" + ).strftime(date_format), + } + + data["bins"].append(next_binfo) + + return data From b23395096eef3331384491fa82ecdd89452850c3 Mon Sep 17 00:00:00 2001 From: m26dvd <31007572+m26dvd@users.noreply.github.com> Date: Wed, 6 Aug 2025 10:06:31 +0100 Subject: [PATCH 107/425] fix: Runnymede Borough Council fix: #1513 --- .../uk_bin_collection/councils/RunnymedeBoroughCouncil.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/RunnymedeBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/RunnymedeBoroughCouncil.py index 382bf6e1e4..16238cd88f 100644 --- a/uk_bin_collection/uk_bin_collection/councils/RunnymedeBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/RunnymedeBoroughCouncil.py @@ -21,10 +21,16 @@ def parse_data(self, page: str, **kwargs) -> dict: check_uprn(user_uprn) bindata = {"bins": []} + headers = { + "Origin": "https://www.runnymede.gov.uk", + "Referer": "https://www.runnymede.gov.uk", + "User-Agent": "Mozilla/5.0", + } + URI = f"https://www.runnymede.gov.uk/homepage/150/check-your-bin-collection-day?address={user_uprn}" # Make the GET request - response = requests.get(URI) + response = requests.get(URI, headers=headers) soup = BeautifulSoup(response.text, "html.parser") From 4a2014bde5756c1d91330a593e50ca3e9f2c3c2f Mon Sep 17 00:00:00 2001 From: PineappleEmperor Date: Thu, 7 Aug 2025 21:23:01 +0100 Subject: [PATCH 108/425] fix: parsing error in BH selenium --- .../councils/CheltenhamBoroughCouncil.py | 50 ++++++++++++------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/CheltenhamBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/CheltenhamBoroughCouncil.py index b13c0c0ee3..6da7670e19 100644 --- a/uk_bin_collection/uk_bin_collection/councils/CheltenhamBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/CheltenhamBoroughCouncil.py @@ -245,25 +245,39 @@ def parse_data(self, page: str, **kwargs: str) -> dict[str, list[dict[str, str]] # extract table body for row in table.find_all("tr")[1:]: if row.find_all("td")[1].text.strip() == "Normal collection day": - bh_dict[ - parse( - row.find_all("td")[0].text.strip(), - dayfirst=True, - fuzzy=True, - ).date() - ] = parse( - row.find_all("td")[0].text.strip(), dayfirst=True, fuzzy=True - ).date() + try: + # Check for normal collection day (no change) + if row.find_all("td")[0].text.strip() == "Normal collection": + continue + else: + bh_dict[ + parse( + row.find_all("td")[0].text.strip(), + dayfirst=True, + fuzzy=True, + ).date() + ] = parse( + row.find_all("td")[0].text.strip(), dayfirst=True, fuzzy=True + ).date() + except: + continue else: - bh_dict[ - parse( - row.find_all("td")[0].text.strip(), - dayfirst=True, - fuzzy=True, - ).date() - ] = parse( - row.find_all("td")[1].text.strip(), dayfirst=True, fuzzy=True - ).date() + try: + # Check for normal collection day (no change) + if row.find_all("td")[1].text.strip() == "Normal collection": + continue + else: + bh_dict[ + parse( + row.find_all("td")[0].text.strip(), + dayfirst=True, + fuzzy=True, + ).date() + ] = parse( + row.find_all("td")[1].text.strip(), dayfirst=True, fuzzy=True + ).date() + except: + continue for refuse_date in refuse_dates: collection_date = (datetime.strptime(refuse_date, "%d/%m/%Y") + timedelta( From 751df7d0f298452d59769e18d224deceb2102191 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 17:46:14 +0000 Subject: [PATCH 109/425] chore: bump actions/checkout from 4 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/behave_pull_request.yml | 8 ++++---- .github/workflows/behave_schedule.yml | 8 ++++---- .github/workflows/bump.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/docker-image.yml | 2 +- .github/workflows/hacs_validation.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/wiki.yml | 2 +- 9 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/behave_pull_request.yml b/.github/workflows/behave_pull_request.yml index 4b72d13933..ac4cd3791d 100644 --- a/.github/workflows/behave_pull_request.yml +++ b/.github/workflows/behave_pull_request.yml @@ -14,7 +14,7 @@ jobs: name: Setup Environment runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install Poetry run: pipx install poetry==1.8.4 @@ -61,7 +61,7 @@ jobs: python-version: [3.12] poetry-version: [1.8.4] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 with: @@ -92,7 +92,7 @@ jobs: python-version: [3.12] poetry-version: [1.8.4] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 with: @@ -125,7 +125,7 @@ jobs: ports: - 4444:4444 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 with: diff --git a/.github/workflows/behave_schedule.yml b/.github/workflows/behave_schedule.yml index 05227d9b18..160238125f 100644 --- a/.github/workflows/behave_schedule.yml +++ b/.github/workflows/behave_schedule.yml @@ -10,7 +10,7 @@ jobs: name: Setup Environment runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install Poetry run: pipx install poetry==1.8.4 @@ -43,7 +43,7 @@ jobs: python-version: [3.12] poetry-version: [1.8.4] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 with: @@ -74,7 +74,7 @@ jobs: python-version: [3.12] poetry-version: [1.8.4] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 with: @@ -107,7 +107,7 @@ jobs: ports: - 4444:4444 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 with: diff --git a/.github/workflows/bump.yml b/.github/workflows/bump.yml index 327ca12d9f..1b5842528f 100644 --- a/.github/workflows/bump.yml +++ b/.github/workflows/bump.yml @@ -19,7 +19,7 @@ jobs: id-token: write contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: token: "${{ secrets.PERSONAL_ACCESS_TOKEN }}" fetch-depth: 0 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 6e2c61ddc2..11c6435dc6 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -47,7 +47,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 635f4b444c..7202415082 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -22,7 +22,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Publish to Registry uses: elgohr/Publish-Docker-Github-Action@v5 with: diff --git a/.github/workflows/hacs_validation.yml b/.github/workflows/hacs_validation.yml index 2d7b2fc25d..e588313800 100644 --- a/.github/workflows/hacs_validation.yml +++ b/.github/workflows/hacs_validation.yml @@ -11,7 +11,7 @@ jobs: name: HassFest Validation runs-on: "ubuntu-latest" steps: - - uses: "actions/checkout@v4" + - uses: "actions/checkout@v5" - uses: home-assistant/actions/hassfest@master hacs: name: HACS Action Validation diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index c219c96667..39dfc7fbcc 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -16,7 +16,7 @@ jobs: name: Lint Commit Messages runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 - run: "echo \"export default {extends: ['@commitlint/config-conventional'], rules: { 'subject-case': [0], 'body-max-line-length': [0], 'footer-max-line-length': [0] }}\" > commitlint.config.mjs" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e07bf3feab..6ae09423d7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,7 +13,7 @@ jobs: id-token: write contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 with: python-version: '3.12' diff --git a/.github/workflows/wiki.yml b/.github/workflows/wiki.yml index 2005bd1689..a602fac935 100644 --- a/.github/workflows/wiki.yml +++ b/.github/workflows/wiki.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest environment: wiki steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-python@v5 with: python-version: '3.12' From ac459aa19faf7f6620a52852459c19b50ff75af2 Mon Sep 17 00:00:00 2001 From: Bal0o Date: Thu, 21 Aug 2025 12:41:49 +0100 Subject: [PATCH 110/425] refactor(hacs): improve build_ukbcd_args with formatter functions Replace imperative if/elif logic with declarative formatter functions for better maintainability and extensibility. Each special case now has its own formatter function, making it easier to add new cases and test individual formatting logic. --- .../uk_bin_collection/__init__.py | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/custom_components/uk_bin_collection/__init__.py b/custom_components/uk_bin_collection/__init__.py index 2359c1f98b..72439ddbaf 100644 --- a/custom_components/uk_bin_collection/__init__.py +++ b/custom_components/uk_bin_collection/__init__.py @@ -283,21 +283,26 @@ def build_ukbcd_args(config_data: dict) -> list: """Build the argument list for UKBinCollectionApp from config data.""" council = config_data.get("original_parser") or config_data.get("council", "") url = config_data.get("url", "") - args = [council, url] + # Per-key formatters: return a list of CLI args for that key + def _format_headless(v): + return ["--headless"] if v else ["--not-headless"] + + def _format_web_driver(v): + return [f"--web_driver={v.rstrip('/')}"] if v is not None else [] + + formatters = { + "headless": _format_headless, + "web_driver": _format_web_driver, + } + for key, value in config_data.items(): if key in EXCLUDED_ARG_KEYS: continue - if key == "web_driver" and value is not None: - value = value.rstrip("/") - args.append(f"--{key}={value}") - elif key == "headless": - # Handle boolean headless argument correctly - if value: - args.append("--headless") - else: - args.append("--not-headless") + fmt = formatters.get(key) + if fmt: + args.extend(fmt(value)) else: args.append(f"--{key}={value}") From b81eaf52a5b9897d71dd7eec8b8bd94a6821ae51 Mon Sep 17 00:00:00 2001 From: Neil Martin Date: Sun, 24 Aug 2025 06:41:39 +0100 Subject: [PATCH 111/425] fix: date extraction in RochfordCouncil data parsing --- .../uk_bin_collection/councils/RochfordCouncil.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/RochfordCouncil.py b/uk_bin_collection/uk_bin_collection/councils/RochfordCouncil.py index f05156f593..2fcddb4172 100644 --- a/uk_bin_collection/uk_bin_collection/councils/RochfordCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/RochfordCouncil.py @@ -33,11 +33,9 @@ def parse_data(self, page: str, **kwargs) -> dict: "tr", {"class": "govuk-table__row"} ): week_text = week.get_text().strip().split("\n") + date_str = week_text[0].split(" - ")[0].split("–")[0].strip() collection_date = datetime.strptime( - remove_ordinal_indicator_from_date_string( - week_text[0].split(" - ")[0] - ) - .strip(), + remove_ordinal_indicator_from_date_string(date_str), "%A %d %B", ) next_collection = collection_date.replace(year=datetime.now().year) From a2541f1e965bc503118e73cc089a2dcf6b11d9d7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 25 Aug 2025 10:53:27 +0000 Subject: [PATCH 112/425] =?UTF-8?q?bump:=20version=200.152.10=20=E2=86=92?= =?UTF-8?q?=200.152.11?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 12 ++++++++++++ custom_components/uk_bin_collection/const.py | 2 +- custom_components/uk_bin_collection/manifest.json | 4 ++-- pyproject.toml | 2 +- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd872a363d..208d14e971 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,16 @@ ======= +## 0.152.11 (2025-08-25) + +### Fix + +- date extraction in RochfordCouncil data parsing +- parsing error in BH selenium +- **hacs**: respect the headless option + +### Refactor + +- **hacs**: improve build_ukbcd_args with formatter functions + ## 0.152.10 (2025-08-04) ### Fix diff --git a/custom_components/uk_bin_collection/const.py b/custom_components/uk_bin_collection/const.py index 17f49916f1..074c05d688 100644 --- a/custom_components/uk_bin_collection/const.py +++ b/custom_components/uk_bin_collection/const.py @@ -4,7 +4,7 @@ from homeassistant.const import Platform -INPUT_JSON_URL = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.152.10/uk_bin_collection/tests/input.json" +INPUT_JSON_URL = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.152.11/uk_bin_collection/tests/input.json" DEFAULT_NAME = "UK Bin Collection Data" diff --git a/custom_components/uk_bin_collection/manifest.json b/custom_components/uk_bin_collection/manifest.json index 640fba8317..ae16f946b3 100644 --- a/custom_components/uk_bin_collection/manifest.json +++ b/custom_components/uk_bin_collection/manifest.json @@ -9,7 +9,7 @@ "integration_type": "service", "iot_class": "cloud_polling", "issue_tracker": "https://github.com/robbrad/UKBinCollectionData/issues", - "requirements": ["uk-bin-collection>=0.152.10"], - "version": "0.152.10", + "requirements": ["uk-bin-collection>=0.152.11"], + "version": "0.152.11", "zeroconf": [] } diff --git a/pyproject.toml b/pyproject.toml index 8cce717d85..01ec94c524 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "uk_bin_collection" -version = "0.152.10" +version = "0.152.11" description = "Python Lib to collect UK Bin Data" readme = "README.md" authors = ["Robert Bradley "] From 620868e7b8beec9742647f2b5224f20f281cffd8 Mon Sep 17 00:00:00 2001 From: Jim Date: Mon, 25 Aug 2025 14:56:11 +0100 Subject: [PATCH 113/425] Update NorwichCityCouncil.py Fixed for new URL --- .../councils/NorwichCityCouncil.py | 133 +++++++++--------- 1 file changed, 67 insertions(+), 66 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/NorwichCityCouncil.py b/uk_bin_collection/uk_bin_collection/councils/NorwichCityCouncil.py index b1d619b874..f29defbed8 100644 --- a/uk_bin_collection/uk_bin_collection/councils/NorwichCityCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/NorwichCityCouncil.py @@ -1,5 +1,3 @@ -import time - import requests from bs4 import BeautifulSoup @@ -17,76 +15,79 @@ class CouncilClass(AbstractGetBinDataClass): def parse_data(self, page: str, **kwargs) -> dict: - user_uprn = kwargs.get("uprn") - check_uprn(user_uprn) + user_postcode = kwargs.get("postcode") + user_paon = kwargs.get("paon") + check_postcode(user_postcode) + check_paon(user_paon) bindata = {"bins": []} - API_URL = "https://maps.norwich.gov.uk/arcgis/rest/services/MyNorwich/PropertyDetails/FeatureServer/2/query" - - params = { - "f": "json", - "where": f"UPRN='{user_uprn}' or UPRN='0{user_uprn}'", - "returnGeometry": "true", - "spatialRel": "esriSpatialRelIntersects", - "geometryType": "esriGeometryPolygon", - "inSR": "4326", - "outFields": "*", - "outSR": "4326", - "resultRecordCount": "1000", + URI = "https://bnr-wrp.whitespacews.com/" + + session = requests.Session() + + # get link from first page as has some kind of unique hash + r = session.get( + URI, + ) + r.raise_for_status() + soup = BeautifulSoup(r.text, features="html.parser") + + alink = soup.find("a", text="View my collections") + + if alink is None: + raise Exception("Initial page did not load correctly") + + # greplace 'seq' query string to skip next step + nextpageurl = alink["href"].replace("seq=1", "seq=2") + + data = { + "address_name_number": user_paon, + "address_postcode": user_postcode, } - r = requests.get(API_URL, params=params) - - data = r.json() - data = data["features"][0]["attributes"]["WasteCollectionHtml"] - soup = BeautifulSoup(data, "html.parser") - - alternateCheck = soup.find("p") - if alternateCheck.text.__contains__("alternate"): - alternateCheck = True - else: - alternateCheck = False - - strong = soup.find_all("strong") - collections = [] - - if alternateCheck: - bin_types = strong[2].text.strip().replace(".", "").split(" and ") - for bin in bin_types: - collections.append( - ( - bin.capitalize(), - datetime.strptime(strong[1].text.strip(), date_format), - ) - ) - - else: - p_tag = soup.find_all("p") - i = 1 - for p in p_tag: - bin_types = ( - p.text.split("Your ")[1].split(" is collected")[0].split(" and ") - ) - for bin in bin_types: - collections.append( - ( - bin.capitalize(), - datetime.strptime(strong[1].text.strip(), date_format), - ) - ) - i += 2 - - if len(strong) > 3: - collections.append( - ("Garden", datetime.strptime(strong[4].text.strip(), date_format)) - ) - - ordered_data = sorted(collections, key=lambda x: x[1]) - for item in ordered_data: + # get list of addresses + r = session.post(nextpageurl, data) + r.raise_for_status() + + soup = BeautifulSoup(r.text, features="html.parser") + + # get first address (if you don't enter enough argument values this won't find the right address) + alink = soup.find("div", id="property_list").find("a") + + if alink is None: + raise Exception("Address not found") + + nextpageurl = URI + alink["href"] + + # get collection page + r = session.get( + nextpageurl, + ) + r.raise_for_status() + soup = BeautifulSoup(r.text, features="html.parser") + + if soup.find("span", id="waste-hint"): + raise Exception("No scheduled services at this address") + + u1s = soup.find("section", id="scheduled-collections").find_all("u1") + + for u1 in u1s: + lis = u1.find_all("li", recursive=False) + + date = lis[1].text.replace("\n", "") + bin_type = lis[2].text.replace("\n", "") + dict_data = { - "type": item[0] + " bin", - "collectionDate": item[1].strftime(date_format), + "type": bin_type, + "collectionDate": datetime.strptime( + date, + "%d/%m/%Y", + ).strftime(date_format), } bindata["bins"].append(dict_data) + bindata["bins"].sort( + key=lambda x: datetime.strptime(x.get("collectionDate"), date_format) + ) + return bindata From 49987802a0f334f1fb8d6f74560aaa4f856a6366 Mon Sep 17 00:00:00 2001 From: Wiki GitHub Action Date: Tue, 26 Aug 2025 10:01:40 +0000 Subject: [PATCH 114/425] docs: Update Councils.md from input.json --- wiki/Councils.md | 1722 ++++++++++++++++++++++++++++------------------ 1 file changed, 1034 insertions(+), 688 deletions(-) diff --git a/wiki/Councils.md b/wiki/Councils.md index 7453f5d9b7..cfd1bbb7ff 100644 --- a/wiki/Councils.md +++ b/wiki/Councils.md @@ -10,314 +10,336 @@ For scripts that need postcodes, these should be provided in double quotes and w This document is still a work in progress, don't worry if your council isn't listed - it will be soon! ## Contents -- [Aberdeen City Council](#aberdeen-city-council) -- [Aberdeenshire Council](#aberdeenshire-council) -- [Adur and Worthing Councils](#adur-and-worthing-councils) -- [Amber Valley Borough Council](#amber-valley-borough-council) -- [Antrim & Newtonabbey Council](#antrim-&-newtonabbey-council) -- [Ards and North Down Council](#ards-and-north-down-council) -- [Argyll and Bute Council](#argyll-and-bute-council) -- [Armagh Banbridge Craigavon Council](#armagh-banbridge-craigavon-council) -- [Arun Council](#arun-council) -- [Ashfield District Council](#ashfield-district-council) -- [Ashford Borough Council](#ashford-borough-council) -- [Aylesbury Vale Council (Buckinghamshire)](#aylesbury-vale-council-(buckinghamshire)) -- [BCP Council](#bcp-council) -- [Babergh District Council](#babergh-district-council) -- [Barnet Council](#barnet-council) -- [Barnsley Metropolitan Borough Council](#barnsley-metropolitan-borough-council) -- [Basildon Council](#basildon-council) -- [Basingstoke Council](#basingstoke-council) -- [Bath and North East Somerset Council](#bath-and-north-east-somerset-council) -- [Bedford Borough Council](#bedford-borough-council) -- [Bedfordshire Council](#bedfordshire-council) -- [Belfast City Council](#belfast-city-council) -- [Bexley Council](#bexley-council) -- [Birmingham City Council](#birmingham-city-council) -- [Blaby District Council](#blaby-district-council) -- [Blackburn Council](#blackburn-council) -- [Blaenau Gwent County Borough Council](#blaenau-gwent-county-borough-council) -- [Bolsover Council](#bolsover-council) -- [Bolton Council](#bolton-council) -- [Boston Borough Council](#boston-borough-council) -- [Bracknell Forest Council](#bracknell-forest-council) -- [Bradford MDC](#bradford-mdc) -- [Braintree District Council](#braintree-district-council) -- [Breckland Council](#breckland-council) -- [Brent Council](#brent-council) -- [Brighton and Hove City Council](#brighton-and-hove-city-council) -- [Bristol City Council](#bristol-city-council) -- [Bromley Borough Council](#bromley-borough-council) -- [Bromsgrove District Council](#bromsgrove-district-council) -- [Broxbourne Council](#broxbourne-council) -- [Broxtowe Borough Council](#broxtowe-borough-council) -- [Buckinghamshire Council (Chiltern, South Bucks, Wycombe)](#buckinghamshire-council-(chiltern,-south-bucks,-wycombe)) -- [Burnley Borough Council](#burnley-borough-council) -- [Bury Council](#bury-council) -- [Calderdale Council](#calderdale-council) -- [Cambridge City Council](#cambridge-city-council) -- [Cannock Chase District Council](#cannock-chase-district-council) -- [Canterbury City Council](#canterbury-city-council) -- [Cardiff Council](#cardiff-council) -- [Carmarthenshire County Council](#carmarthenshire-county-council) -- [Castlepoint District Council](#castlepoint-district-council) -- [Charnwood Borough Council](#charnwood-borough-council) -- [Chelmsford City Council](#chelmsford-city-council) -- [Cheltenham Borough Council](#cheltenham-borough-council) -- [Cherwell District Council](#cherwell-district-council) -- [Cheshire East Council](#cheshire-east-council) -- [Cheshire West and Chester Council](#cheshire-west-and-chester-council) -- [Chesterfield Borough Council](#chesterfield-borough-council) -- [Chichester District Council](#chichester-district-council) -- [Chorley Council](#chorley-council) -- [Colchester City Council](#colchester-city-council) -- [Conwy County Borough Council](#conwy-county-borough-council) -- [Copeland Borough Council](#copeland-borough-council) -- [Cornwall Council](#cornwall-council) -- [Cotswold District Council](#cotswold-district-council) -- [Coventry City Council](#coventry-city-council) -- [Crawley Borough Council](#crawley-borough-council) -- [Croydon Council](#croydon-council) -- [Cumberland Council - Allerdale District](#cumberland-council---allerdale-district) -- [Cumberland Borough Council](#cumberland-borough-council) -- [Dacorum Borough Council](#dacorum-borough-council) -- [Dartford Borough Council](#dartford-borough-council) -- [Denbighshire Council](#denbighshire-council) -- [Derby City Council](#derby-city-council) -- [Derbyshire Dales District Council](#derbyshire-dales-district-council) -- [Doncaster Council](#doncaster-council) +- [Aberdeen City](#aberdeen-city) +- [Aberdeenshire](#aberdeenshire) +- [Adur](#adur) +- [Amber Valley](#amber-valley) +- [Angus](#angus) +- [Antrim and Newtownabbey](#antrim-and-newtownabbey) +- [Ards and North Down](#ards-and-north-down) +- [Argyll and Bute](#argyll-and-bute) +- [Armagh City, Banbridge and Craigavon](#armagh-city,-banbridge-and-craigavon) +- [Arun](#arun) +- [Ashfield](#ashfield) +- [Ashford](#ashford) +- [Bournemouth, Christchurch and Poole](#bournemouth,-christchurch-and-poole) +- [Babergh](#babergh) +- [Barking and Dagenham](#barking-and-dagenham) +- [Barnet](#barnet) +- [Barnsley](#barnsley) +- [Basildon](#basildon) +- [Basingstoke and Deane](#basingstoke-and-deane) +- [Bath and North East Somerset](#bath-and-north-east-somerset) +- [Bedford](#bedford) +- [Central Bedfordshire](#central-bedfordshire) +- [Belfast](#belfast) +- [Bexley](#bexley) +- [Birmingham](#birmingham) +- [Blaby](#blaby) +- [Blackburn with Darwen](#blackburn-with-darwen) +- [Blaenau Gwent](#blaenau-gwent) +- [Bolsover](#bolsover) +- [Bolton](#bolton) +- [Boston](#boston) +- [Bracknell Forest](#bracknell-forest) +- [Bradford](#bradford) +- [Braintree](#braintree) +- [Breckland](#breckland) +- [Brent](#brent) +- [Brighton and Hove](#brighton-and-hove) +- [City of Bristol](#city-of-bristol) +- [Broadland](#broadland) +- [Bromley](#bromley) +- [Bromsgrove](#bromsgrove) +- [Broxbourne](#broxbourne) +- [Broxtowe](#broxtowe) +- [Buckinghamshire](#buckinghamshire) +- [Burnley](#burnley) +- [Bury](#bury) +- [Calderdale](#calderdale) +- [Cambridge](#cambridge) +- [Cannock Chase](#cannock-chase) +- [Canterbury](#canterbury) +- [Cardiff](#cardiff) +- [Carmarthenshire](#carmarthenshire) +- [Castle Point](#castle-point) +- [Ceredigion](#ceredigion) +- [Charnwood](#charnwood) +- [Chelmsford](#chelmsford) +- [Cheltenham](#cheltenham) +- [Cherwell](#cherwell) +- [Cheshire East](#cheshire-east) +- [Cheshire West and Chester](#cheshire-west-and-chester) +- [Chesterfield](#chesterfield) +- [Chichester](#chichester) +- [Chorley](#chorley) +- [Colchester](#colchester) +- [Conwy](#conwy) +- [Copeland](#copeland) +- [Cornwall](#cornwall) +- [Cotswold](#cotswold) +- [Coventry](#coventry) +- [Crawley](#crawley) +- [Croydon](#croydon) +- [Cumberland](#cumberland) +- [Cumberland](#cumberland) +- [Dacorum](#dacorum) +- [Darlington Borough Council](#darlington-borough-council) +- [Dartford](#dartford) +- [Denbighshire](#denbighshire) +- [Derby](#derby) +- [Derbyshire Dales](#derbyshire-dales) +- [Doncaster](#doncaster) - [Dorset Council](#dorset-council) -- [Dover District Council](#dover-district-council) -- [Dudley Council](#dudley-council) -- [Dundee City Council](#dundee-city-council) -- [Durham Council](#durham-council) -- [Ealing Council](#ealing-council) -- [East Ayrshire Council](#east-ayrshire-council) -- [East Cambridgeshire Council](#east-cambridgeshire-council) -- [East Devon District Council](#east-devon-district-council) +- [Dover](#dover) +- [Dudley](#dudley) +- [Dundee City](#dundee-city) +- [County Durham](#county-durham) +- [Ealing](#ealing) +- [East Ayrshire](#east-ayrshire) +- [Eastbourne](#eastbourne) +- [East Cambridgeshire](#east-cambridgeshire) +- [East Devon](#east-devon) - [East Herts Council](#east-herts-council) -- [East Lindsey District Council](#east-lindsey-district-council) -- [East Lothian Council](#east-lothian-council) -- [East Renfrewshire Council](#east-renfrewshire-council) -- [East Riding Council](#east-riding-council) -- [East Staffordshire Borough Council](#east-staffordshire-borough-council) -- [East Suffolk Council](#east-suffolk-council) -- [Eastleigh Borough Council](#eastleigh-borough-council) -- [Edinburgh City Council](#edinburgh-city-council) -- [Elmbridge Borough Council](#elmbridge-borough-council) -- [Enfield Council](#enfield-council) +- [East Lindsey](#east-lindsey) +- [East Lothian](#east-lothian) +- [East Renfrewshire](#east-renfrewshire) +- [East Riding of Yorkshire](#east-riding-of-yorkshire) +- [East Staffordshire](#east-staffordshire) +- [East Suffolk](#east-suffolk) +- [Eastleigh](#eastleigh) +- [City of Edinburgh](#city-of-edinburgh) +- [Elmbridge](#elmbridge) +- [Enfield](#enfield) - [Environment First](#environment-first) -- [Epping Forest District Council](#epping-forest-district-council) -- [Epsom and Ewell Borough Council](#epsom-and-ewell-borough-council) -- [Erewash Borough Council](#erewash-borough-council) -- [Exeter City Council](#exeter-city-council) -- [Falkirk Council](#falkirk-council) -- [Fareham Borough Council](#fareham-borough-council) -- [Fenland District Council](#fenland-district-council) -- [Fife Council](#fife-council) -- [Flintshire County Council](#flintshire-county-council) -- [Folkstone and Hythe District Council](#folkstone-and-hythe-district-council) -- [Forest of Dean District Council](#forest-of-dean-district-council) -- [Fylde Council](#fylde-council) -- [Gateshead Council](#gateshead-council) -- [Gedling Borough Council](#gedling-borough-council) -- [Glasgow City Council](#glasgow-city-council) -- [Gloucester City Council](#gloucester-city-council) -- [Gravesham Borough Council](#gravesham-borough-council) -- [Guildford Council](#guildford-council) -- [Gwynedd Council](#gwynedd-council) -- [Hackney Council](#hackney-council) -- [Halton Borough Council](#halton-borough-council) -- [Harborough District Council](#harborough-district-council) -- [Haringey Council](#haringey-council) -- [Harrogate Borough Council](#harrogate-borough-council) -- [Hart District Council](#hart-district-council) -- [Hartlepool Borough Council](#hartlepool-borough-council) -- [Hastings Borough Council](#hastings-borough-council) -- [Herefordshire Council](#herefordshire-council) -- [Hertsmere Borough Council](#hertsmere-borough-council) -- [High Peak Council](#high-peak-council) -- [Highland Council](#highland-council) -- [Hinckley and Bosworth Borough Council](#hinckley-and-bosworth-borough-council) -- [Hounslow Council](#hounslow-council) -- [Hull City Council](#hull-city-council) -- [Huntingdon District Council](#huntingdon-district-council) -- [Ipswich Borough Council](#ipswich-borough-council) -- [Islington Council](#islington-council) -- [Kings Lynn and West Norfolk Borough Council](#kings-lynn-and-west-norfolk-borough-council) -- [Kingston Upon Thames Council](#kingston-upon-thames-council) -- [Kirklees Council](#kirklees-council) -- [Knowsley Metropolitan Borough Council](#knowsley-metropolitan-borough-council) -- [Lancaster City Council](#lancaster-city-council) -- [Leeds City Council](#leeds-city-council) -- [Leicester City Council](#leicester-city-council) -- [Lichfield District Council](#lichfield-district-council) -- [Lincoln Council](#lincoln-council) -- [Lisburn and Castlereagh City Council](#lisburn-and-castlereagh-city-council) -- [Liverpool City Council](#liverpool-city-council) -- [London Borough Ealing](#london-borough-ealing) -- [London Borough Harrow](#london-borough-harrow) -- [London Borough Havering](#london-borough-havering) -- [London Borough Hounslow](#london-borough-hounslow) -- [London Borough Lambeth](#london-borough-lambeth) -- [London Borough Lewisham](#london-borough-lewisham) -- [London Borough Of Richmond Upon Thames](#london-borough-of-richmond-upon-thames) -- [London Borough Redbridge](#london-borough-redbridge) -- [London Borough Sutton](#london-borough-sutton) -- [Luton Borough Council](#luton-borough-council) -- [Maldon District Council](#maldon-district-council) -- [Malvern Hills District Council](#malvern-hills-district-council) -- [Manchester City Council](#manchester-city-council) -- [Mansfield District Council](#mansfield-district-council) -- [MedwayCouncil](#medwaycouncil) -- [Merton Council](#merton-council) -- [Mid and East Antrim Borough Council](#mid-and-east-antrim-borough-council) -- [Mid Devon Council](#mid-devon-council) -- [Middlesbrough Council](#middlesbrough-council) -- [Mid Suffolk District Council](#mid-suffolk-district-council) -- [Mid Sussex District Council](#mid-sussex-district-council) -- [Midlothian Council](#midlothian-council) -- [Milton Keynes City Council](#milton-keynes-city-council) -- [Mole Valley District Council](#mole-valley-district-council) -- [Monmouthshire County Council](#monmouthshire-county-council) -- [Moray Council](#moray-council) -- [Neath Port Talbot Council](#neath-port-talbot-council) -- [New Forest Council](#new-forest-council) -- [Newark and Sherwood District Council](#newark-and-sherwood-district-council) -- [Newcastle City Council](#newcastle-city-council) -- [Newcastle Under Lyme Council](#newcastle-under-lyme-council) -- [Newham Council](#newham-council) -- [Newport City Council](#newport-city-council) -- [North Ayrshire Council](#north-ayrshire-council) -- [North East Derbyshire District Council](#north-east-derbyshire-district-council) -- [North East Lincolnshire Council](#north-east-lincolnshire-council) -- [North Hertfordshire District Council](#north-hertfordshire-district-council) -- [North Kesteven District Council](#north-kesteven-district-council) -- [North Lanarkshire Council](#north-lanarkshire-council) -- [North Lincolnshire Council](#north-lincolnshire-council) -- [North Norfolk District Council](#north-norfolk-district-council) -- [North Northamptonshire Council](#north-northamptonshire-council) -- [North Somerset Council](#north-somerset-council) -- [North Tyneside Council](#north-tyneside-council) -- [North West Leicestershire Council](#north-west-leicestershire-council) -- [North Yorkshire Council](#north-yorkshire-council) -- [Northumberland Council](#northumberland-council) -- [Norwich City Council](#norwich-city-council) -- [Nottingham City Council](#nottingham-city-council) -- [Nuneaton and Bedworth Borough Council](#nuneaton-and-bedworth-borough-council) -- [Oadby & Wigston Borough Council](#oadby-&-wigston-borough-council) -- [Oldham Council](#oldham-council) -- [Oxford City Council](#oxford-city-council) -- [Perth and Kinross Council](#perth-and-kinross-council) -- [Plymouth Council](#plymouth-council) -- [Portsmouth City Council](#portsmouth-city-council) -- [Powys Council](#powys-council) -- [Preston City Council](#preston-city-council) -- [Reading Borough Council](#reading-borough-council) -- [Redcar and Cleveland Council](#redcar-and-cleveland-council) -- [Redditch Borough Council](#redditch-borough-council) -- [Reigate and Banstead Borough Council](#reigate-and-banstead-borough-council) -- [Renfrewshire Council](#renfrewshire-council) -- [Rhondda Cynon Taff Council](#rhondda-cynon-taff-council) -- [Rochdale Council](#rochdale-council) -- [Rochford Council](#rochford-council) -- [Rother District Council](#rother-district-council) -- [Rotherham Council](#rotherham-council) -- [Royal Borough of Greenwich](#royal-borough-of-greenwich) -- [Rugby Borough Council](#rugby-borough-council) -- [Runnymede Borough Council](#runnymede-borough-council) -- [Rushcliffe Borough Council](#rushcliffe-borough-council) -- [Rushmoor Council](#rushmoor-council) -- [Salford City Council](#salford-city-council) -- [Sandwell Borough Council](#sandwell-borough-council) -- [Sefton Council](#sefton-council) -- [Sevenoaks District Council](#sevenoaks-district-council) -- [Sheffield City Council](#sheffield-city-council) -- [Shropshire Council](#shropshire-council) -- [Solihull Council](#solihull-council) -- [Somerset Council](#somerset-council) -- [Southampton City Council](#southampton-city-council) -- [South Ayrshire Council](#south-ayrshire-council) -- [South Cambridgeshire Council](#south-cambridgeshire-council) -- [South Derbyshire District Council](#south-derbyshire-district-council) -- [South Gloucestershire Council](#south-gloucestershire-council) -- [South Hams District Council](#south-hams-district-council) -- [South Kesteven District Council](#south-kesteven-district-council) -- [South Lanarkshire Council](#south-lanarkshire-council) -- [South Norfolk Council](#south-norfolk-council) -- [South Oxfordshire Council](#south-oxfordshire-council) -- [South Ribble Council](#south-ribble-council) -- [South Staffordshire District Council](#south-staffordshire-district-council) -- [South Tyneside Council](#south-tyneside-council) -- [Southwark Council](#southwark-council) -- [Spelthorne Borough Council](#spelthorne-borough-council) -- [St Albans City and District Council](#st-albans-city-and-district-council) -- [St Helens Borough Council](#st-helens-borough-council) -- [Stafford Borough Council](#stafford-borough-council) -- [Staffordshire Moorlands District Council](#staffordshire-moorlands-district-council) -- [Stevenage Borough Council](#stevenage-borough-council) -- [Stockport Borough Council](#stockport-borough-council) -- [Stockton On Tees Council](#stockton-on-tees-council) -- [Stoke-on-Trent City Council](#stoke-on-trent-city-council) -- [Stratford Upon Avon Council](#stratford-upon-avon-council) -- [Stroud District Council](#stroud-district-council) -- [Sunderland City Council](#sunderland-city-council) -- [Surrey Heath Borough Council / Joint Waste Solutions](#surrey-heath-borough-council-/-joint-waste-solutions) -- [Swale Borough Council](#swale-borough-council) -- [Swansea Council](#swansea-council) -- [Swindon Borough Council](#swindon-borough-council) -- [Tameside Metropolitan Borough Council](#tameside-metropolitan-borough-council) -- [Tandridge District Council](#tandridge-district-council) -- [Teignbridge Council](#teignbridge-council) -- [Telford and Wrekin Council](#telford-and-wrekin-council) -- [Tendring District Council](#tendring-district-council) -- [Test Valley Borough Council](#test-valley-borough-council) -- [Thanet District Council](#thanet-district-council) -- [Three Rivers District Council](#three-rivers-district-council) -- [Thurrock Council](#thurrock-council) -- [Tonbridge and Malling Borough Council](#tonbridge-and-malling-borough-council) -- [Torbay Council](#torbay-council) -- [Torridge District Council](#torridge-district-council) -- [Tunbridge Wells Council](#tunbridge-wells-council) -- [Uttlesford District Council](#uttlesford-district-council) -- [Vale of Glamorgan Council](#vale-of-glamorgan-council) -- [Vale of White Horse Council](#vale-of-white-horse-council) -- [Wakefield City Council](#wakefield-city-council) -- [Walsall Council](#walsall-council) +- [Epping Forest](#epping-forest) +- [Epsom and Ewell](#epsom-and-ewell) +- [Erewash](#erewash) +- [Exeter](#exeter) +- [Falkirk](#falkirk) +- [Fareham](#fareham) +- [Fenland](#fenland) +- [Fermanagh and Omagh](#fermanagh-and-omagh) +- [Fife](#fife) +- [Flintshire](#flintshire) +- [Folkestone and Hythe](#folkestone-and-hythe) +- [Forest of Dean](#forest-of-dean) +- [Fylde](#fylde) +- [Gateshead](#gateshead) +- [Gedling](#gedling) +- [Glasgow City](#glasgow-city) +- [Gloucester](#gloucester) +- [Google Calendar (Public)](#google-calendar-(public)) +- [Gravesham](#gravesham) +- [Great Yarmouth](#great-yarmouth) +- [Guildford](#guildford) +- [Gwynedd](#gwynedd) +- [Hackney](#hackney) +- [Halton](#halton) +- [Harborough](#harborough) +- [Haringey](#haringey) +- [Harrogate](#harrogate) +- [Hart](#hart) +- [Hartlepool](#hartlepool) +- [Hastings](#hastings) +- [Herefordshire](#herefordshire) +- [Hertsmere](#hertsmere) +- [High Peak](#high-peak) +- [Highland](#highland) +- [Hillingdon](#hillingdon) +- [Hinckley and Bosworth](#hinckley-and-bosworth) +- [Horsham](#horsham) +- [Kingston upon Hull](#kingston-upon-hull) +- [Huntingdonshire](#huntingdonshire) +- [Hyndburn](#hyndburn) +- [Ipswich](#ipswich) +- [Islington](#islington) +- [Kings Lynn and West Norfolk](#kings-lynn-and-west-norfolk) +- [Kingston upon Thames](#kingston-upon-thames) +- [Kirklees](#kirklees) +- [Knowsley](#knowsley) +- [Lancaster](#lancaster) +- [Leeds](#leeds) +- [Leicester](#leicester) +- [Lewes](#lewes) +- [Lichfield](#lichfield) +- [City of Lincoln](#city-of-lincoln) +- [Lisburn and Castlereagh](#lisburn-and-castlereagh) +- [Liverpool](#liverpool) +- [Ealing](#ealing) +- [Harrow](#harrow) +- [Havering](#havering) +- [Hounslow](#hounslow) +- [Lambeth](#lambeth) +- [Lewisham](#lewisham) +- [Richmond upon Thames](#richmond-upon-thames) +- [Redbridge](#redbridge) +- [Sutton](#sutton) +- [Luton](#luton) +- [Maidstone](#maidstone) +- [Maldon](#maldon) +- [Malvern Hills](#malvern-hills) +- [Manchester](#manchester) +- [Mansfield](#mansfield) +- [Medway](#medway) +- [Melton](#melton) +- [Merton](#merton) +- [Mid and East Antrim](#mid-and-east-antrim) +- [Mid Devon](#mid-devon) +- [Mid Suffolk](#mid-suffolk) +- [Mid Sussex](#mid-sussex) +- [Middlesbrough](#middlesbrough) +- [Midlothian](#midlothian) +- [Mid Ulster](#mid-ulster) +- [Milton Keynes](#milton-keynes) +- [Mole Valley](#mole-valley) +- [Monmouthshire](#monmouthshire) +- [Moray](#moray) +- [Neath Port Talbot](#neath-port-talbot) +- [New Forest](#new-forest) +- [Newark and Sherwood](#newark-and-sherwood) +- [Newcastle upon Tyne](#newcastle-upon-tyne) +- [Newcastle-under-Lyme](#newcastle-under-lyme) +- [Newham](#newham) +- [Newport](#newport) +- [North Ayrshire](#north-ayrshire) +- [North Devon](#north-devon) +- [North East Derbyshire](#north-east-derbyshire) +- [North East Lincolnshire](#north-east-lincolnshire) +- [North Hertfordshire](#north-hertfordshire) +- [North Kesteven](#north-kesteven) +- [North Lanarkshire](#north-lanarkshire) +- [North Lincolnshire](#north-lincolnshire) +- [North Norfolk](#north-norfolk) +- [North Northamptonshire](#north-northamptonshire) +- [North Somerset](#north-somerset) +- [North Tyneside](#north-tyneside) +- [North West Leicestershire](#north-west-leicestershire) +- [North Yorkshire](#north-yorkshire) +- [Northumberland](#northumberland) +- [Norwich](#norwich) +- [Nottingham](#nottingham) +- [Nuneaton and Bedworth](#nuneaton-and-bedworth) +- [Oadby and Wigston](#oadby-and-wigston) +- [Oldham](#oldham) +- [Oxford](#oxford) +- [Pembrokeshire](#pembrokeshire) +- [Peterborough](#peterborough) +- [Perth and Kinross](#perth-and-kinross) +- [Plymouth](#plymouth) +- [Portsmouth](#portsmouth) +- [Powys](#powys) +- [Preston](#preston) +- [Reading](#reading) +- [Redcar and Cleveland](#redcar-and-cleveland) +- [Redditch](#redditch) +- [Reigate and Banstead](#reigate-and-banstead) +- [Renfrewshire](#renfrewshire) +- [Rhondda Cynon Taff](#rhondda-cynon-taff) +- [Rochdale](#rochdale) +- [Rochford](#rochford) +- [Rother](#rother) +- [Rotherham](#rotherham) +- [Greenwich](#greenwich) +- [Rugby](#rugby) +- [Runnymede](#runnymede) +- [Rushcliffe](#rushcliffe) +- [Rushmoor](#rushmoor) +- [Salford](#salford) +- [Sandwell](#sandwell) +- [Sefton](#sefton) +- [Sevenoaks](#sevenoaks) +- [Sheffield](#sheffield) +- [Shropshire](#shropshire) +- [Slough](#slough) +- [Solihull](#solihull) +- [Somerset](#somerset) +- [South Ayrshire](#south-ayrshire) +- [South Cambridgeshire](#south-cambridgeshire) +- [South Derbyshire](#south-derbyshire) +- [South Gloucestershire](#south-gloucestershire) +- [South Hams](#south-hams) +- [South Holland](#south-holland) +- [South Kesteven](#south-kesteven) +- [South Lanarkshire](#south-lanarkshire) +- [South Norfolk](#south-norfolk) +- [South Oxfordshire](#south-oxfordshire) +- [South Ribble](#south-ribble) +- [South Staffordshire](#south-staffordshire) +- [South Tyneside](#south-tyneside) +- [Southampton](#southampton) +- [Southwark](#southwark) +- [Spelthorne](#spelthorne) +- [St Albans](#st-albans) +- [St. Helens](#st.-helens) +- [Stafford](#stafford) +- [Staffordshire Moorlands](#staffordshire-moorlands) +- [Stevenage](#stevenage) +- [Stirling](#stirling) +- [Stockport](#stockport) +- [Stockton-on-Tees](#stockton-on-tees) +- [Stoke-on-Trent](#stoke-on-trent) +- [Stratford-on-Avon](#stratford-on-avon) +- [Stroud](#stroud) +- [Sunderland](#sunderland) +- [Surrey Heath](#surrey-heath) +- [Swale](#swale) +- [Swansea](#swansea) +- [Swindon](#swindon) +- [Tameside](#tameside) +- [Tandridge](#tandridge) +- [Teignbridge](#teignbridge) +- [Telford and Wrekin](#telford-and-wrekin) +- [Tewkesbury](#tewkesbury) +- [Tendring](#tendring) +- [Test Valley](#test-valley) +- [Thanet](#thanet) +- [Three Rivers](#three-rivers) +- [Thurrock](#thurrock) +- [Tonbridge and Malling](#tonbridge-and-malling) +- [Torbay](#torbay) +- [Torridge](#torridge) +- [Tunbridge Wells](#tunbridge-wells) +- [Uttlesford](#uttlesford) +- [The Vale of Glamorgan](#the-vale-of-glamorgan) +- [Vale of White Horse](#vale-of-white-horse) +- [Wakefield](#wakefield) +- [Walsall](#walsall) - [Waltham Forest](#waltham-forest) -- [Wandsworth Council](#wandsworth-council) -- [Warrington Borough Council](#warrington-borough-council) -- [Warwick District Council](#warwick-district-council) -- [Watford Borough Council](#watford-borough-council) -- [Waverley Borough Council](#waverley-borough-council) -- [Wealden District Council](#wealden-district-council) -- [Welhat Council](#welhat-council) -- [West Berkshire Council](#west-berkshire-council) -- [West Dunbartonshire Council](#west-dunbartonshire-council) -- [West Lancashire Borough Council](#west-lancashire-borough-council) -- [West Lindsey District Council](#west-lindsey-district-council) -- [West Lothian Council](#west-lothian-council) -- [West Morland and Furness Council](#west-morland-and-furness-council) -- [West Northamptonshire Council](#west-northamptonshire-council) -- [West Oxfordshire District Council](#west-oxfordshire-district-council) -- [West Suffolk Council](#west-suffolk-council) -- [Wigan Borough Council](#wigan-borough-council) -- [Wiltshire Council](#wiltshire-council) -- [Winchester City Council](#winchester-city-council) -- [Windsor and Maidenhead Council](#windsor-and-maidenhead-council) -- [Wirral Council](#wirral-council) -- [Woking Borough Council / Joint Waste Solutions](#woking-borough-council-/-joint-waste-solutions) -- [Wokingham Borough Council](#wokingham-borough-council) -- [Wolverhampton City Council](#wolverhampton-city-council) -- [Worcester City Council](#worcester-city-council) -- [Wychavon District Council](#wychavon-district-council) -- [Wyre Council](#wyre-council) -- [Wyre Forest District Council](#wyre-forest-district-council) -- [York Council](#york-council) - ---- - -### Aberdeen City Council +- [Wandsworth](#wandsworth) +- [Warrington](#warrington) +- [Warwick](#warwick) +- [Watford](#watford) +- [Waverley](#waverley) +- [Wealden](#wealden) +- [Welwyn Hatfield](#welwyn-hatfield) +- [West Berkshire](#west-berkshire) +- [West Dunbartonshire](#west-dunbartonshire) +- [West Lancashire](#west-lancashire) +- [West Lindsey](#west-lindsey) +- [West Lothian](#west-lothian) +- [Westmorland and Furness](#westmorland-and-furness) +- [West Northamptonshire](#west-northamptonshire) +- [West Oxfordshire](#west-oxfordshire) +- [West Suffolk](#west-suffolk) +- [Wigan](#wigan) +- [Wiltshire](#wiltshire) +- [Winchester](#winchester) +- [Windsor and Maidenhead](#windsor-and-maidenhead) +- [Wirral](#wirral) +- [Woking](#woking) +- [Wokingham](#wokingham) +- [Wolverhampton](#wolverhampton) +- [Worcester](#worcester) +- [Wrexham](#wrexham) +- [Wychavon](#wychavon) +- [Wyre](#wyre) +- [Wyre Forest](#wyre-forest) +- [York](#york) + +--- + +### Aberdeen City ```commandline python collect_data.py AberdeenCityCouncil https://www.aberdeencity.gov.uk -u XXXXXXXX ``` @@ -328,7 +350,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Aberdeenshire Council +### Aberdeenshire ```commandline python collect_data.py AberdeenshireCouncil https://online.aberdeenshire.gov.uk -u XXXXXXXX ``` @@ -339,7 +361,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Adur and Worthing Councils +### Adur ```commandline python collect_data.py AdurAndWorthingCouncils https://www.adur-worthing.gov.uk/bin-day/?brlu-selected-address=XXXXXXXX ``` @@ -348,7 +370,7 @@ Note: Replace XXXXXXXX with your UPRN. You will need to use [FindMyAddress](http --- -### Amber Valley Borough Council +### Amber Valley ```commandline python collect_data.py AmberValleyBoroughCouncil https://ambervalley.gov.uk -u XXXXXXXX ``` @@ -359,7 +381,21 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Antrim & Newtonabbey Council +### Angus +```commandline +python collect_data.py AngusCouncil https://www.angus.gov.uk/bins_litter_and_recycling/bin_collection_days -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ +``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN +- `-p` - postcode +- `-w` - remote Selenium web driver URL (required for Home Assistant) + +Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. Requires Selenium + +--- + +### Antrim and Newtownabbey ```commandline python collect_data.py AntrimAndNewtonabbeyCouncil https://antrimandnewtownabbey.gov.uk/residents/bins-recycling/bins-schedule/?Id=XXXX ``` @@ -368,7 +404,7 @@ Note: Navigate to [https://antrimandnewtownabbey.gov.uk/residents/bins-recycling --- -### Ards and North Down Council +### Ards and North Down ```commandline python collect_data.py ArdsAndNorthDownCouncil https://www.ardsandnorthdown.gov.uk -u XXXXXXXX ``` @@ -379,19 +415,21 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Argyll and Bute Council +### Argyll and Bute ```commandline -python collect_data.py ArgyllandButeCouncil https://www.argyll-bute.gov.uk -s -u XXXXXXXX +python collect_data.py ArgyllandButeCouncil https://www.argyll-bute.gov.uk/rubbish-and-recycling/household-waste/bin-collection -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` Additional parameters: - `-s` - skip get URL - `-u` - UPRN +- `-p` - postcode +- `-w` - remote Selenium web driver URL (required for Home Assistant) Note: Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search). --- -### Armagh Banbridge Craigavon Council +### Armagh City, Banbridge and Craigavon ```commandline python collect_data.py ArmaghBanbridgeCraigavonCouncil https://www.armaghbanbridgecraigavon.gov.uk/ -u XXXXXXXX ``` @@ -402,7 +440,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Arun Council +### Arun ```commandline python collect_data.py ArunCouncil https://www1.arun.gov.uk/when-are-my-bins-collected -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -416,7 +454,7 @@ Note: Pass the house name/number and postcode in their respective parameters, bo --- -### Ashfield District Council +### Ashfield ```commandline python collect_data.py AshfieldDistrictCouncil https://www.ashfield.gov.uk -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -429,7 +467,7 @@ Note: Pass the house name/number and postcode in their respective parameters, bo --- -### Ashford Borough Council +### Ashford ```commandline python collect_data.py AshfordBoroughCouncil https://ashford.gov.uk -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -442,45 +480,50 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Aylesbury Vale Council (Buckinghamshire) +### Bournemouth, Christchurch and Poole ```commandline -python collect_data.py AylesburyValeCouncil http://avdcbins.web-labs.co.uk/RefuseApi.asmx -s -u XXXXXXXX +python collect_data.py BCPCouncil https://online.bcpcouncil.gov.uk/bindaylookup/ -s -u XXXXXXXX -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` Additional parameters: - `-s` - skip get URL - `-u` - UPRN +- `-p` - postcode +- `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: To get the UPRN, please use [FindMyAddress](https://www.findmyaddress.co.uk/search). Returns all published collections in the past, present, future. +Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. --- -### BCP Council +### Babergh ```commandline -python collect_data.py BCPCouncil https://online.bcpcouncil.gov.uk/bindaylookup/ -s -u XXXXXXXX +python collect_data.py BaberghDistrictCouncil https://www.babergh.gov.uk -s -u XXXXXXXX -p "XXXX XXX" -n XX ``` Additional parameters: - `-s` - skip get URL - `-u` - UPRN +- `-p` - postcode +- `-n` - house number -Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. +Note: Use the House Number field to pass the DAY of the week for your NORMAL collections. [Monday/Tuesday/Wednesday/Thursday/Friday]. [OPTIONAL] Use the 'postcode' field to pass the WEEK for your garden collection. [Week 1/Week 2]. [OPTIONAL] Use the 'uprn' field to pass the DAY for your garden collection. [Monday/Tuesday/Wednesday/Thursday/Friday] --- -### Babergh District Council +### Barking and Dagenham ```commandline -python collect_data.py BaberghDistrictCouncil https://www.babergh.gov.uk -s -u XXXXXXXX -p "XXXX XXX" -n XX +python collect_data.py BarkingDagenham https://www.lbbd.gov.uk/rubbish-recycling/household-bin-collection/check-your-bin-collection-days -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` Additional parameters: - `-s` - skip get URL -- `-u` - UPRN - `-p` - postcode - `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: Use the House Number field to pass the DAY of the week for your NORMAL collections. [Monday/Tuesday/Wednesday/Thursday/Friday]. [OPTIONAL] Use the 'postcode' field to pass the WEEK for your garden collection. [Week 1/Week 2]. [OPTIONAL] Use the 'uprn' field to pass the DAY for your garden collection. [Monday/Tuesday/Wednesday/Thursday/Friday] +Note: Use house number and postcode. Requires Selenium. --- -### Barnet Council +### Barnet ```commandline python collect_data.py BarnetCouncil https://www.barnet.gov.uk/recycling-and-waste/bin-collections/find-your-bin-collection-day -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -494,7 +537,7 @@ Note: Follow the instructions [here](https://www.barnet.gov.uk/recycling-and-was --- -### Barnsley Metropolitan Borough Council +### Barnsley ```commandline python collect_data.py BarnsleyMBCouncil https://waste.barnsley.gov.uk/ViewCollection/Collections -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -507,9 +550,9 @@ Note: To get the UPRN, you will need to use [FindMyAddress](https://www.findmyad --- -### Basildon Council +### Basildon ```commandline -python collect_data.py BasildonCouncil https://basildonportal.azurewebsites.net/api/getPropertyRefuseInformation -s -u XXXXXXXX +python collect_data.py BasildonCouncil https://mybasildon.powerappsportals.com/check/where_i_live/ -s -u XXXXXXXX ``` Additional parameters: - `-s` - skip get URL @@ -519,7 +562,7 @@ Note: To get the UPRN, you will need to use [FindMyAddress](https://www.findmyad --- -### Basingstoke Council +### Basingstoke and Deane ```commandline python collect_data.py BasingstokeCouncil https://www.basingstoke.gov.uk/bincollection -s -u XXXXXXXX ``` @@ -531,7 +574,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Bath and North East Somerset Council +### Bath and North East Somerset ```commandline python collect_data.py BathAndNorthEastSomersetCouncil https://www.bathnes.gov.uk/webforms/waste/collectionday/ -s -u XXXXXXXX ``` @@ -543,7 +586,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Bedford Borough Council +### Bedford ```commandline python collect_data.py BedfordBoroughCouncil https://www.bedford.gov.uk/bins-and-recycling/household-bins-and-recycling/check-your-bin-day -s -u XXXXXXXX ``` @@ -555,7 +598,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Bedfordshire Council +### Central Bedfordshire ```commandline python collect_data.py BedfordshireCouncil https://www.centralbedfordshire.gov.uk/info/163/bins_and_waste_collections_-_check_bin_collection_day -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -568,7 +611,7 @@ Note: In order to use this parser, you must provide a valid postcode and a UPRN --- -### Belfast City Council +### Belfast ```commandline python collect_data.py BelfastCityCouncil https://online.belfastcity.gov.uk/find-bin-collection-day/Default.aspx -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -581,21 +624,19 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Bexley Council +### Bexley ```commandline -python collect_data.py BexleyCouncil https://waste.bexley.gov.uk/waste -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ +python collect_data.py BexleyCouncil https://waste.bexley.gov.uk/waste -s -u XXXXXXXX ``` Additional parameters: - `-s` - skip get URL -- `-p` - postcode -- `-n` - house number -- `-w` - remote Selenium web driver URL (required for Home Assistant) +- `-u` - UPRN -Note: In order to use this parser, you will need to sign up to [Bexley's @Home app](https://www.bexley.gov.uk/services/rubbish-and-recycling/bexley-home-recycling-app/about-app). Complete the setup by entering your email and setting your address with postcode and address line. Once you can see the calendar, you should be good to run the parser. Just pass the email you used in quotes in the UPRN parameter. +Note: Provide your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to locate it. --- -### Birmingham City Council +### Birmingham ```commandline python collect_data.py BirminghamCityCouncil https://www.birmingham.gov.uk/xfp/form/619 -u XXXXXXXX -p "XXXX XXX" ``` @@ -607,7 +648,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Blaby District Council +### Blaby ```commandline python collect_data.py BlabyDistrictCouncil https://www.blaby.gov.uk -u XXXXXXXX ``` @@ -618,20 +659,19 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Blackburn Council +### Blackburn with Darwen ```commandline -python collect_data.py BlackburnCouncil https://www.blackburn.gov.uk -s -u XXXXXXXX -w http://HOST:PORT/ +python collect_data.py BlackburnCouncil https://www.blaby.gov.uk -s -u XXXXXXXX ``` Additional parameters: - `-s` - skip get URL - `-u` - UPRN -- `-w` - remote Selenium web driver URL (required for Home Assistant) Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. --- -### Blaenau Gwent County Borough Council +### Blaenau Gwent ```commandline python collect_data.py BlaenauGwentCountyBoroughCouncil https://www.blaenau-gwent.gov.uk -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -645,7 +685,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Bolsover Council +### Bolsover ```commandline python collect_data.py BolsoverCouncil https://bolsover.gov.uk -u XXXXXXXX ``` @@ -656,7 +696,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Bolton Council +### Bolton ```commandline python collect_data.py BoltonCouncil https://carehomes.bolton.gov.uk/bins.aspx -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -670,7 +710,7 @@ Note: To get the UPRN, you will need to use [FindMyAddress](https://www.findmyad --- -### Boston Borough Council +### Boston ```commandline python collect_data.py BostonBoroughCouncil https://www.boston.gov.uk/findwastecollections -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -684,7 +724,7 @@ Note: Provide your house number in the `house_number` parameter and postcode in --- -### Bracknell Forest Council +### Bracknell Forest ```commandline python collect_data.py BracknellForestCouncil https://selfservice.mybfc.bracknell-forest.gov.uk/w/webpage/waste-collection-days -s -p "XXXX XXX" -n XX ``` @@ -697,7 +737,7 @@ Note: Pass the house number and postcode in their respective parameters. --- -### Bradford MDC +### Bradford ```commandline python collect_data.py BradfordMDC https://onlineforms.bradford.gov.uk/ufs/collectiondates.eb -s -u XXXXXXXX ``` @@ -709,7 +749,7 @@ Note: To get the UPRN, you will need to use [FindMyAddress](https://www.findmyad --- -### Braintree District Council +### Braintree ```commandline python collect_data.py BraintreeDistrictCouncil https://www.braintree.gov.uk/ -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -722,7 +762,7 @@ Note: Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddr --- -### Breckland Council +### Breckland ```commandline python collect_data.py BrecklandCouncil https://www.breckland.gov.uk -u XXXXXXXX ``` @@ -733,7 +773,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Brent Council +### Brent ```commandline python collect_data.py BrentCouncil https://recyclingservices.brent.gov.uk/waste -p "XXXX XXX" -n XX ``` @@ -745,20 +785,21 @@ Note: Pass the house number and postcode in their respective parameters. --- -### Brighton and Hove City Council +### Brighton and Hove ```commandline -python collect_data.py BrightonandHoveCityCouncil https://cityclean.brighton-hove.gov.uk/link/collections -n "XXXXXX XXXX XXXX" -p "XXXX XXX" -w http://HOST:PORT/wd/hub +python collect_data.py BrightonandHoveCityCouncil https://cityclean.brighton-hove.gov.uk/link/collections -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` Additional parameters: -- `-n` - house number +- `-s` - skip get URL - `-p` - postcode +- `-n` - house number - `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: Use the full address as it appears on the drop-down on the site when you search by house number. +Note: Use house number and postcode. Requires Selenium --- -### Bristol City Council +### City of Bristol ```commandline python collect_data.py BristolCityCouncil https://bristolcouncil.powerappsportals.com/completedynamicformunauth/?servicetypeid=7dce896c-b3ba-ea11-a812-000d3a7f1cdc -s -u XXXXXXXX ``` @@ -770,7 +811,21 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Bromley Borough Council +### Broadland +```commandline +python collect_data.py BroadlandDistrictCouncil https://area.southnorfolkandbroadland.gov.uk/FindAddress -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ +``` +Additional parameters: +- `-s` - skip get URL +- `-p` - postcode +- `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) + +Note: Use house number and postcode. Requires Selenium. + +--- + +### Bromley ```commandline python collect_data.py BromleyBoroughCouncil https://recyclingservices.bromley.gov.uk/waste/XXXXXXX -w http://HOST:PORT/ ``` @@ -781,7 +836,7 @@ Note: Follow the instructions [here](https://recyclingservices.bromley.gov.uk/wa --- -### Bromsgrove District Council +### Bromsgrove ```commandline python collect_data.py BromsgroveDistrictCouncil https://www.bromsgrove.gov.uk -u XXXXXXXX ``` @@ -792,19 +847,20 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Broxbourne Council +### Broxbourne ```commandline -python collect_data.py BroxbourneCouncil https://www.broxbourne.gov.uk -u XXXXXXXX -p "XXXX XXX" +python collect_data.py BroxbourneCouncil https://www.broxbourne.gov.uk -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` Additional parameters: - `-u` - UPRN - `-p` - postcode +- `-w` - remote Selenium web driver URL (required for Home Assistant) Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. --- -### Broxtowe Borough Council +### Broxtowe ```commandline python collect_data.py BroxtoweBoroughCouncil https://www.broxtowe.gov.uk/ -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -818,12 +874,12 @@ Note: Pass the UPRN and postcode. To get the UPRN, you can use [FindMyAddress](h --- -### Buckinghamshire Council (Chiltern, South Bucks, Wycombe) +### Buckinghamshire ```commandline -python collect_data.py BuckinghamshireCouncil https://iapp.itouchvision.com/iappcollectionday/collection-day/?uuid=FA353FC74600CBE61BE409534D00A8EC09BDA3AC&lang=en -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ +python collect_data.py BuckinghamshireCouncil https://www.buckinghamshire.gov.uk/waste-and-recycling/find-out-when-its-your-bin-collection/ -u XXXXXXXX -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` Additional parameters: -- `-s` - skip get URL +- `-u` - UPRN - `-p` - postcode - `-n` - house number - `-w` - remote Selenium web driver URL (required for Home Assistant) @@ -832,7 +888,7 @@ Note: Pass the house name/number and postcode in their respective arguments, bot --- -### Burnley Borough Council +### Burnley ```commandline python collect_data.py BurnleyBoroughCouncil https://www.burnley.gov.uk -u XXXXXXXX ``` @@ -843,7 +899,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### Bury Council +### Bury ```commandline python collect_data.py BuryCouncil https://www.bury.gov.uk/waste-and-recycling/bin-collection-days-and-alerts -s -p "XXXX XXX" -n XX ``` @@ -856,7 +912,7 @@ Note: Pass the postcode and house number in their respective arguments, both wra --- -### Calderdale Council +### Calderdale ```commandline python collect_data.py CalderdaleCouncil https://www.calderdale.gov.uk/environment/waste/household-collections/collectiondayfinder.jsp -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -870,7 +926,7 @@ Note: Pass the UPRN and postcode. To get the UPRN, you can use [FindMyAddress](h --- -### Cambridge City Council +### Cambridge ```commandline python collect_data.py CambridgeCityCouncil https://www.cambridge.gov.uk/ -u XXXXXXXX ``` @@ -881,7 +937,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Cannock Chase District Council +### Cannock Chase ```commandline python collect_data.py CannockChaseDistrictCouncil https://www.cannockchasedc.gov.uk/ -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -894,7 +950,7 @@ Note: To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co. --- -### Canterbury City Council +### Canterbury ```commandline python collect_data.py CanterburyCityCouncil https://www.canterbury.gov.uk -u XXXXXXXX ``` @@ -905,7 +961,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Cardiff Council +### Cardiff ```commandline python collect_data.py CardiffCouncil https://www.gov.uk -s -u XXXXXXXX ``` @@ -917,7 +973,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Carmarthenshire County Council +### Carmarthenshire ```commandline python collect_data.py CarmarthenshireCountyCouncil https://www.carmarthenshire.gov.wales -u XXXXXXXX ``` @@ -928,7 +984,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Castlepoint District Council +### Castle Point ```commandline python collect_data.py CastlepointDistrictCouncil https://apps.castlepoint.gov.uk/cpapps/index.cfm?fa=wastecalendar -s -u XXXXXXXX ``` @@ -940,16 +996,32 @@ Note: For this council, 'uprn' is actually a 4-digit code for your street. Go [h --- -### Charnwood Borough Council +### Ceredigion +```commandline +python collect_data.py CeredigionCountyCouncil https://www.ceredigion.gov.uk/resident/bins-recycling/ -p "XXXX XXX" -n XX -w http://HOST:PORT/ +``` +Additional parameters: +- `-p` - postcode +- `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) + +Note: House Number is the full address as it appears on the drop-down on the site when you search by postcode. This parser requires a Selenium webdriver. + +--- + +### Charnwood ```commandline -python collect_data.py CharnwoodBoroughCouncil https://my.charnwood.gov.uk/location?put=cbcXXXXXXXX&rememberme=0&redirect=%2F +python collect_data.py CharnwoodBoroughCouncil https://www.charnwood.gov.uk/pages/waste_collections_calendars -s -u XXXXXXXX ``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN -Note: Replace XXXXXXXX with your UPRN, keeping "cbc" before it. +Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. --- -### Chelmsford City Council +### Chelmsford ```commandline python collect_data.py ChelmsfordCityCouncil https://www.chelmsford.gov.uk/myhome/ -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -962,7 +1034,7 @@ Note: Follow the instructions [here](https://www.chelmsford.gov.uk/myhome/) unti --- -### Cheltenham Borough Council +### Cheltenham ```commandline python collect_data.py CheltenhamBoroughCouncil https://www.cheltenham.gov.uk -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -975,7 +1047,7 @@ Note: Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddr --- -### Cherwell District Council +### Cherwell ```commandline python collect_data.py CherwellDistrictCouncil https://www.cherwell.gov.uk -u XXXXXXXX ``` @@ -986,16 +1058,19 @@ Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your U --- -### Cheshire East Council +### Cheshire East ```commandline -python collect_data.py CheshireEastCouncil https://online.cheshireeast.gov.uk/MyCollectionDay/SearchByAjax/GetBartecJobList?uprn=XXXXXXXX&onelineaddress=XXXXXXXX&_=1689413260149 +python collect_data.py CheshireEastCouncil https://online.cheshireeast.gov.uk/mycollectionday -s -u XXXXXXXX ``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN -Note: Both the UPRN and a one-line address are passed in the URL, which needs to be wrapped in double quotes. The one-line address is made up of the house number, street name, and postcode. Use the form [here](https://online.cheshireeast.gov.uk/mycollectionday/) to find them, then take the first line and postcode and replace all spaces with `%20`. +Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN. --- -### Cheshire West and Chester Council +### Cheshire West and Chester ```commandline python collect_data.py CheshireWestAndChesterCouncil https://my.cheshirewestandchester.gov.uk -s -u XXXXXXXX ``` @@ -1007,7 +1082,7 @@ Note: Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddr --- -### Chesterfield Borough Council +### Chesterfield ```commandline python collect_data.py ChesterfieldBoroughCouncil https://www.chesterfield.gov.uk -s -u XXXXXXXX ``` @@ -1019,7 +1094,7 @@ Note: Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddr --- -### Chichester District Council +### Chichester ```commandline python collect_data.py ChichesterDistrictCouncil https://www.chichester.gov.uk/checkyourbinday -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1033,7 +1108,7 @@ Note: Needs the full address and postcode as it appears on [this page](https://w --- -### Chorley Council +### Chorley ```commandline python collect_data.py ChorleyCouncil https://myaccount.chorley.gov.uk/wastecollections.aspx -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -1043,11 +1118,11 @@ Additional parameters: - `-p` - postcode - `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: Chorley needs to be passed both a Postcode & UPRN in the format of UPRNXXXXXX to work. Find this on [FindMyAddress](https://www.findmyaddress.co.uk/search). +Note: Chorley needs to be passed both a Postcode & UPRN to work. Find this on [FindMyAddress](https://www.findmyaddress.co.uk/search). --- -### Colchester City Council +### Colchester ```commandline python collect_data.py ColchesterCityCouncil https://www.colchester.gov.uk/your-recycling-calendar -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1061,7 +1136,7 @@ Note: Pass the house name/number in the house number parameter, wrapped in doubl --- -### Conwy County Borough Council +### Conwy ```commandline python collect_data.py ConwyCountyBorough https://www.conwy.gov.uk -u XXXXXXXX ``` @@ -1072,7 +1147,7 @@ Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your U --- -### Copeland Borough Council +### Copeland ```commandline python collect_data.py CopelandBoroughCouncil https://www.copeland.gov.uk -u XXXXXXXX ``` @@ -1083,7 +1158,7 @@ Note: *****This has now been replaced by Cumberland Council**** --- -### Cornwall Council +### Cornwall ```commandline python collect_data.py CornwallCouncil https://www.cornwall.gov.uk/my-area/ -s -u XXXXXXXX ``` @@ -1095,7 +1170,7 @@ Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your U --- -### Cotswold District Council +### Cotswold ```commandline python collect_data.py CotswoldDistrictCouncil https://community.cotswold.gov.uk/s/waste-collection-enquiry -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1109,7 +1184,7 @@ Note: Pass the full address in the house number and postcode in --- -### Coventry City Council +### Coventry ```commandline python collect_data.py CoventryCityCouncil https://www.coventry.gov.uk/directory_record/XXXXXX/XXXXXX ``` @@ -1118,7 +1193,7 @@ Note: Follow the instructions [here](https://www.coventry.gov.uk/bin-collection- --- -### Crawley Borough Council +### Crawley ```commandline python collect_data.py CrawleyBoroughCouncil https://my.crawley.gov.uk/ -s -u XXXXXXXX -n XX ``` @@ -1131,20 +1206,21 @@ Note: Crawley needs to be passed both a UPRN and a USRN to work. Find these on [ --- -### Croydon Council +### Croydon ```commandline -python collect_data.py CroydonCouncil https://service.croydon.gov.uk/wasteservices/w/webpage/bin-day-enter-address -s -p "XXXX XXX" -n XX +python collect_data.py CroydonCouncil https://service.croydon.gov.uk/wasteservices/w/webpage/bin-day-enter-address -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` Additional parameters: - `-s` - skip get URL - `-p` - postcode - `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: Pass the house number and postcode in their respective parameters. +Note: Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver. --- -### Cumberland Council - Allerdale District +### Cumberland ```commandline python collect_data.py CumberlandAllerdaleCouncil https://www.allerdale.gov.uk -p "XXXX XXX" -n XX ``` @@ -1156,19 +1232,19 @@ Note: Pass the house number and postcode in their respective parameters. --- -### Cumberland Borough Council +### Cumberland ```commandline -python collect_data.py CumberlandCouncil https://waste.cumberland.gov.uk -u XXXXXXXX -p "XXXX XXX" +python collect_data.py CumberlandCouncil https://waste.cumberland.gov.uk/renderform?t=25&k=E43CEB1FB59F859833EF2D52B16F3F4EBE1CAB6A -u XXXXXXXX -p "XXXX XXX" ``` Additional parameters: -- `-p` - postcode - `-u` - UPRN +- `-p` - postcode Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN. --- -### Dacorum Borough Council +### Dacorum ```commandline python collect_data.py DacorumBoroughCouncil https://webapps.dacorum.gov.uk/bincollections/ -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1182,18 +1258,30 @@ Note: Pass the house number and postcode in their respective parameters. This pa --- -### Dartford Borough Council +### Darlington Borough Council +```commandline +python collect_data.py DarlingtonBoroughCouncil https://www.darlington.gov.uk/bins-waste-and-recycling/collection-day-lookup/ -u XXXXXXXX +``` +Additional parameters: +- `-u` - UPRN + +Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN. + +--- + +### Dartford ```commandline -python collect_data.py DartfordBoroughCouncil https://windmz.dartford.gov.uk/ufs/WS_CHECK_COLLECTIONS.eb?UPRN=010094157511 -u XXXXXXXX +python collect_data.py DartfordBoroughCouncil https://www.dartford.gov.uk/waste-recycling/collection-day -s -u XXXXXXXX ``` Additional parameters: +- `-s` - skip get URL - `-u` - UPRN Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN. --- -### Denbighshire Council +### Denbighshire ```commandline python collect_data.py DenbighshireCouncil https://www.denbighshire.gov.uk/ -u XXXXXXXX ``` @@ -1204,7 +1292,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Derby City Council +### Derby ```commandline python collect_data.py DerbyCityCouncil https://www.derby.gov.uk -u XXXXXXXX ``` @@ -1215,7 +1303,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Derbyshire Dales District Council +### Derbyshire Dales ```commandline python collect_data.py DerbyshireDalesDistrictCouncil https://www.derbyshiredales.gov.uk/ -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -1228,7 +1316,7 @@ Note: Pass the UPRN and postcode. To get the UPRN, you can use [FindMyAddress](h --- -### Doncaster Council +### Doncaster ```commandline python collect_data.py DoncasterCouncil https://www.doncaster.gov.uk/Compass/Entity/Launch/D3/ -s -u XXXXXXXX ``` @@ -1252,16 +1340,19 @@ Note: Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddr --- -### Dover District Council +### Dover ```commandline -python collect_data.py DoverDistrictCouncil https://collections.dover.gov.uk/property/XXXXXXXXXXX +python collect_data.py DoverDistrictCouncil https://collections.dover.gov.uk/property -s -u XXXXXXXX ``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN -Note: Replace XXXXXXXXXXX with your UPRN. To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search). +Note: To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search). --- -### Dudley Council +### Dudley ```commandline python collect_data.py DudleyCouncil https://my.dudley.gov.uk -u XXXXXXXX ``` @@ -1272,7 +1363,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Dundee City Council +### Dundee City ```commandline python collect_data.py DundeeCityCouncil https://www.dundeecity.gov.uk/ -u XXXXXXXX ``` @@ -1283,7 +1374,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Durham Council +### County Durham ```commandline python collect_data.py DurhamCouncil https://www.durham.gov.uk/bincollections?uprn= -s -u XXXXXXXX ``` @@ -1295,7 +1386,7 @@ Note: Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddr --- -### Ealing Council +### Ealing ```commandline python collect_data.py EalingCouncil https://www.ealing.gov.uk/site/custom_scripts/WasteCollectionWS/home/FindCollection -s -u XXXXXXXX ``` @@ -1307,7 +1398,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### East Ayrshire Council +### East Ayrshire ```commandline python collect_data.py EastAyrshireCouncil https://www.east-ayrshire.gov.uk -u XXXXXXXX ``` @@ -1318,7 +1409,19 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### East Cambridgeshire Council +### Eastbourne +```commandline +python collect_data.py EastbourneBoroughCouncil https://www.lewes-eastbourne.gov.uk/article/1158/When-is-my-bin-collection-day -s -u XXXXXXXX +``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN + +Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. + +--- + +### East Cambridgeshire ```commandline python collect_data.py EastCambridgeshireCouncil https://www.eastcambs.gov.uk/ -s -u XXXXXXXX ``` @@ -1330,30 +1433,31 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### East Devon District Council +### East Devon ```commandline -python collect_data.py EastDevonDC https://eastdevon.gov.uk/recycling-and-waste/recycling-waste-information/when-is-my-bin-collected/future-collections-calendar/?UPRN=XXXXXXXX +python collect_data.py EastDevonDC https://eastdevon.gov.uk/recycling-and-waste/recycling-waste-information/when-is-my-bin-collected/ -s -u XXXXXXXX ``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN -Note: Replace XXXXXXXX with your UPRN. +Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search). --- ### East Herts Council ```commandline -python collect_data.py EastHertsCouncil https://www.eastherts.gov.uk -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ +python collect_data.py EastHertsCouncil https://east-herts.co.uk/api/services/ -s -u XXXXXXXX ``` Additional parameters: - `-s` - skip get URL -- `-p` - postcode -- `-n` - house number -- `-w` - remote Selenium web driver URL (required for Home Assistant) +- `-u` - UPRN -Note: Pass the house number and postcode in their respective parameters. +Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search). --- -### East Lindsey District Council +### East Lindsey ```commandline python collect_data.py EastLindseyDistrictCouncil https://www.e-lindsey.gov.uk/ -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1367,7 +1471,7 @@ Note: Pass the house name/number and postcode in their respective parameters. Th --- -### East Lothian Council +### East Lothian ```commandline python collect_data.py EastLothianCouncil https://eastlothian.gov.uk -s -p "XXXX XXX" -n XX ``` @@ -1380,7 +1484,7 @@ Note: Pass the house number and postcode in their respective parameters --- -### East Renfrewshire Council +### East Renfrewshire ```commandline python collect_data.py EastRenfrewshireCouncil https://eastrenfrewshire.gov.uk/ -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1394,7 +1498,7 @@ Note: Pass the house name/number and postcode in their respective parameters. Th --- -### East Riding Council +### East Riding of Yorkshire ```commandline python collect_data.py EastRidingCouncil https://wasterecyclingapi.eastriding.gov.uk -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1408,7 +1512,7 @@ Note: Put the full address as it displays on the council website dropdown when y --- -### East Staffordshire Borough Council +### East Staffordshire ```commandline python collect_data.py EastStaffordshireBoroughCouncil https://www.eaststaffsbc.gov.uk/bins-rubbish-recycling/collection-dates/XXXXX ``` @@ -1417,7 +1521,7 @@ Note: Replace `XXXXX` with your property's ID when selecting from https://www.ea --- -### East Suffolk Council +### East Suffolk ```commandline python collect_data.py EastSuffolkCouncil https://my.eastsuffolk.gov.uk/service/Bin_collection_dates_finder -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -1431,19 +1535,20 @@ Note: To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co. --- -### Eastleigh Borough Council +### Eastleigh ```commandline -python collect_data.py EastleighBoroughCouncil https://www.eastleigh.gov.uk/waste-bins-and-recycling/collection-dates/your-waste-bin-and-recycling-collections?uprn= -s -u XXXXXXXX +python collect_data.py EastleighBoroughCouncil https://www.eastleigh.gov.uk/waste-bins-and-recycling/collection-dates/your-waste-bin-and-recycling-collections?uprn= -s -u XXXXXXXX -w http://HOST:PORT/ ``` Additional parameters: - `-s` - skip get URL - `-u` - UPRN +- `-w` - remote Selenium web driver URL (required for Home Assistant) Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search). --- -### Edinburgh City Council +### City of Edinburgh ```commandline python collect_data.py EdinburghCityCouncil https://www.edinburgh.gov.uk -s -p "XXXX XXX" -n XX ``` @@ -1456,7 +1561,7 @@ Note: Use the House Number field to pass the DAY of the week for your collection --- -### Elmbridge Borough Council +### Elmbridge ```commandline python collect_data.py ElmbridgeBoroughCouncil https://www.elmbridge.gov.uk -u XXXXXXXX ``` @@ -1467,7 +1572,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Enfield Council +### Enfield ```commandline python collect_data.py EnfieldCouncil https://www.enfield.gov.uk/services/rubbish-and-recycling/find-my-collection-day -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1490,19 +1595,20 @@ Note: For properties with collections managed by Environment First, such as Lewe --- -### Epping Forest District Council +### Epping Forest ```commandline -python collect_data.py EppingForestDistrictCouncil https://eppingforestdc.maps.arcgis.com/apps/instant/lookup/index.html?appid=bfca32b46e2a47cd9c0a84f2d8cdde17&find=IG9%206EP -p "XXXX XXX" -w http://HOST:PORT/ +python collect_data.py EppingForestDistrictCouncil https://eppingforestdc.maps.arcgis.com/apps/instant/lookup/index.html?appid=bfca32b46e2a47cd9c0a84f2d8cdde17&find=IG9%206EP -s -p "XXXX XXX" -w http://HOST:PORT/ ``` Additional parameters: +- `-s` - skip get URL - `-p` - postcode - `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: Replace the postcode in the URL with your own. +Note: Add your postcode. --- -### Epsom and Ewell Borough Council +### Epsom and Ewell ```commandline python collect_data.py EpsomandEwellBoroughCouncil https://www.epsom-ewell.gov.uk -u XXXXXXXX ``` @@ -1513,7 +1619,7 @@ Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your U --- -### Erewash Borough Council +### Erewash ```commandline python collect_data.py ErewashBoroughCouncil https://map.erewash.gov.uk/isharelive.web/myerewash.aspx -s -u XXXXXXXX ``` @@ -1525,7 +1631,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### Exeter City Council +### Exeter ```commandline python collect_data.py ExeterCityCouncil https://www.exeter.gov.uk -u XXXXXXXX ``` @@ -1536,7 +1642,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### Falkirk Council +### Falkirk ```commandline python collect_data.py FalkirkCouncil https://www.falkirk.gov.uk -u XXXXXXXX ``` @@ -1547,7 +1653,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Fareham Borough Council +### Fareham ```commandline python collect_data.py FarehamBoroughCouncil https://www.fareham.gov.uk/internetlookups/search_data.aspx?type=JSON&list=DomesticBinCollections&Road=&Postcode=PO14%204NR -s -p "XXXX XXX" ``` @@ -1559,7 +1665,7 @@ Note: Pass the postcode in the postcode parameter, wrapped in double quotes. --- -### Fenland District Council +### Fenland ```commandline python collect_data.py FenlandDistrictCouncil https://www.fenland.gov.uk/article/13114/ -s -u XXXXXXXX ``` @@ -1571,7 +1677,20 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### Fife Council +### Fermanagh and Omagh +```commandline +python collect_data.py FermanaghOmaghDistrictCouncil https://www.fermanaghomagh.com/services/environment-and-waste/waste-collection-calendar/ -s -p "XXXX XXX" -n XX +``` +Additional parameters: +- `-s` - skip get URL +- `-p` - postcode +- `-n` - house number + +Note: Pass the house number and postcode in their respective parameters. + +--- + +### Fife ```commandline python collect_data.py FifeCouncil https://www.fife.gov.uk -u XXXXXXXX ``` @@ -1582,7 +1701,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Flintshire County Council +### Flintshire ```commandline python collect_data.py FlintshireCountyCouncil https://digital.flintshire.gov.uk -u XXXXXXXX ``` @@ -1593,7 +1712,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Folkstone and Hythe District Council +### Folkestone and Hythe ```commandline python collect_data.py FolkstoneandHytheDistrictCouncil https://www.folkestone-hythe.gov.uk -s -u XXXXXXXX ``` @@ -1605,7 +1724,7 @@ Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your U --- -### Forest of Dean District Council +### Forest of Dean ```commandline python collect_data.py ForestOfDeanDistrictCouncil https://community.fdean.gov.uk/s/waste-collection-enquiry -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1619,7 +1738,7 @@ Note: Pass the full address in the house number and postcode parameters. This pa --- -### Fylde Council +### Fylde ```commandline python collect_data.py FyldeCouncil https://www.fylde.gov.uk -u XXXXXXXX ``` @@ -1630,7 +1749,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Gateshead Council +### Gateshead ```commandline python collect_data.py GatesheadCouncil https://www.gateshead.gov.uk/ -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1644,7 +1763,7 @@ Note: Pass the house name/number and postcode in their respective parameters. Th --- -### Gedling Borough Council +### Gedling ```commandline python collect_data.py GedlingBoroughCouncil https://www.gedling.gov.uk/ -s -n XX ``` @@ -1656,16 +1775,19 @@ Note: Use [this site](https://www.gbcbincalendars.co.uk/) to find the collection --- -### Glasgow City Council +### Glasgow City ```commandline -python collect_data.py GlasgowCityCouncil https://onlineservices.glasgow.gov.uk/forms/RefuseAndRecyclingWebApplication/CollectionsCalendar.aspx?UPRN=XXXXXXXX +python collect_data.py GlasgowCityCouncil https://onlineservices.glasgow.gov.uk/forms/RefuseAndRecyclingWebApplication/AddressSearch.aspx -s -u XXXXXXXX ``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN -Note: Replace XXXXXXXX with your UPRN. +Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. --- -### Gloucester City Council +### Gloucester ```commandline python collect_data.py GloucesterCityCouncil https://gloucester-self.achieveservice.com/service/Bins___Check_your_bin_day -s -u XXXXXXXX -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1680,7 +1802,16 @@ Note: Pass the house number, postcode, and UPRN in their respective parameters. --- -### Gravesham Borough Council +### Google Calendar (Public) +```commandline +python collect_data.py GooglePublicCalendarCouncil https://calendar.google.com/calendar/ical/0d775884b4db6a7bae5204f06dae113c1a36e505b25991ebc27c6bd42edf5b5e%40group.calendar.google.com/public/basic.ics +``` + +Note: The URL should be the public ics file URL for the public Google calendar. See https://support.google.com/calendar/answer/37083?sjid=7202815583021446882-EU. Councils that currently need this are Trafford. + +--- + +### Gravesham ```commandline python collect_data.py GraveshamBoroughCouncil https://www.gravesham.gov.uk -s -u XXXXXXXX ``` @@ -1692,22 +1823,35 @@ Note: Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddr --- -### Guildford Council +### Great Yarmouth ```commandline -python collect_data.py GuildfordCouncil https://my.guildford.gov.uk/customers/s/view-bin-collections -s -u XXXXXXXX -p "XXXX XXX" -n XX -w http://HOST:PORT/ +python collect_data.py GreatYarmouthBoroughCouncil https://myaccount.great-yarmouth.gov.uk/article/6456/Find-my-waste-collection-days -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` Additional parameters: - `-s` - skip get URL - `-u` - UPRN - `-p` - postcode +- `-w` - remote Selenium web driver URL (required for Home Assistant) + +Note: Pass the postcode, and UPRN in their respective parameters. This parser requires a Selenium webdriver. + +--- + +### Guildford +```commandline +python collect_data.py GuildfordCouncil https://my.guildford.gov.uk/customers/s/view-bin-collections -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ +``` +Additional parameters: +- `-s` - skip get URL +- `-p` - postcode - `-n` - house number - `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: If the bin day is 'today' then the collectionDate will only show today's date if before 7 AM; else the date will be in 'previousCollectionDate'. To get the UPRN, you will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search). +Note: If the bin day is 'today' then the collectionDate will only show today's date if before 7 AM; else the date will be in 'previousCollectionDate'. --- -### Gwynedd Council +### Gwynedd ```commandline python collect_data.py GwyneddCouncil https://diogel.gwynedd.llyw.cymru -u XXXXXXXX ``` @@ -1718,7 +1862,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Hackney Council +### Hackney ```commandline python collect_data.py HackneyCouncil https://www.hackney.gov.uk -p "XXXX XXX" -n XX ``` @@ -1730,7 +1874,7 @@ Note: Pass the postcode and house number in their respective arguments, both wra --- -### Halton Borough Council +### Halton ```commandline python collect_data.py HaltonBoroughCouncil https://webapp.halton.gov.uk/PublicWebForms/WasteServiceSearchv1.aspx#collections -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1744,7 +1888,7 @@ Note: Pass the house number and postcode. This parser requires a Selenium webdri --- -### Harborough District Council +### Harborough ```commandline python collect_data.py HarboroughDistrictCouncil https://www.harborough.gov.uk -u XXXXXXXX ``` @@ -1755,7 +1899,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Haringey Council +### Haringey ```commandline python collect_data.py HaringeyCouncil https://wastecollections.haringey.gov.uk/property -s -u XXXXXXXX ``` @@ -1767,7 +1911,7 @@ Note: Pass the UPRN, which can be found at `https://wastecollections.haringey.go --- -### Harrogate Borough Council +### Harrogate ```commandline python collect_data.py HarrogateBoroughCouncil https://secure.harrogate.gov.uk/inmyarea -s -u XXXXXXXX ``` @@ -1779,7 +1923,7 @@ Note: Pass the UPRN, which can be found at [this site](https://secure.harrogate. --- -### Hart District Council +### Hart ```commandline python collect_data.py HartDistrictCouncil https://www.hart.gov.uk/ -s -u XXXXXXXX ``` @@ -1791,7 +1935,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Hartlepool Borough Council +### Hartlepool ```commandline python collect_data.py HartlepoolBoroughCouncil https://www.hartlepool.gov.uk -u XXXXXXXX ``` @@ -1802,7 +1946,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Hastings Borough Council +### Hastings ```commandline python collect_data.py HastingsBoroughCouncil https://www.hastings.gov.uk -u XXXXXXXX ``` @@ -1813,16 +1957,19 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Herefordshire Council +### Herefordshire ```commandline -python collect_data.py HerefordshireCouncil https://www.herefordshire.gov.uk/rubbish-recycling/check-bin-collection-day?blpu_uprn=XXXXXXXXXXXX +python collect_data.py HerefordshireCouncil https://www.herefordshire.gov.uk/rubbish-recycling/check-bin-collection-day -s -u XXXXXXXX ``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN -Note: Replace 'XXXXXXXXXX' with your property's UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search). +Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. --- -### Hertsmere Borough Council +### Hertsmere ```commandline python collect_data.py HertsmereBoroughCouncil https://www.hertsmere.gov.uk -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1836,7 +1983,7 @@ Note: Provide your house number in the `house_number` parameter and postcode in --- -### High Peak Council +### High Peak ```commandline python collect_data.py HighPeakCouncil https://www.highpeak.gov.uk/findyourbinday -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1850,7 +1997,7 @@ Note: Pass the name of the street with the house number parameter, wrapped in do --- -### Highland Council +### Highland ```commandline python collect_data.py HighlandCouncil https://www.highland.gov.uk -u XXXXXXXX ``` @@ -1861,7 +2008,21 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Hinckley and Bosworth Borough Council +### Hillingdon +```commandline +python collect_data.py Hillingdon https://www.hillingdon.gov.uk/collection-day -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ +``` +Additional parameters: +- `-s` - skip get URL +- `-p` - postcode +- `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) + +Note: Pass the postcode and the full address as it appears in the address pulldown menu. + +--- + +### Hinckley and Bosworth ```commandline python collect_data.py HinckleyandBosworthBoroughCouncil https://www.hinckley-bosworth.gov.uk -u XXXXXXXX ``` @@ -1872,22 +2033,21 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Hounslow Council +### Horsham ```commandline -python collect_data.py HounslowCouncil https://www.hounslow.gov.uk/info/20272/recycling_and_waste_collection_day_finder -s -u XXXXXXXX -p "XXXX XXX" -n XX -w http://HOST:PORT/ +python collect_data.py HorshamDistrictCouncil https://www.horsham.gov.uk/waste-recycling-and-bins/household-bin-collections/check-your-bin-collection-day -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` Additional parameters: - `-s` - skip get URL - `-u` - UPRN - `-p` - postcode -- `-n` - house number - `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: Pass the full address as it appears on the council's website. This parser requires a Selenium webdriver. +Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search). This parser requires a Selenium webdriver. --- -### Hull City Council +### Kingston upon Hull ```commandline python collect_data.py HullCityCouncil https://www.hull.gov.uk/bins-and-recycling/bin-collections/bin-collection-day-checker -s -u XXXXXXXX ``` @@ -1899,16 +2059,33 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### Huntingdon District Council +### Huntingdonshire ```commandline -python collect_data.py HuntingdonDistrictCouncil https://www.huntingdonshire.gov.uk/refuse-calendar/XXXXXXXX +python collect_data.py HuntingdonDistrictCouncil http://www.huntingdonshire.gov.uk/refuse-calendar/ -s -u XXXXXXXX ``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN -Note: Replace XXXXXXXX with your UPRN. +Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search). + +--- + +### Hyndburn +```commandline +python collect_data.py HyndburnBoroughCouncil https://iapp.itouchvision.com/iappcollectionday/collection-day/?uuid=FEBA68993831481FD81B2E605364D00A8DC017A4 -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ +``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN +- `-p` - postcode +- `-w` - remote Selenium web driver URL (required for Home Assistant) + +Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search). This parser requires a Selenium webdriver. --- -### Ipswich Borough Council +### Ipswich ```commandline python collect_data.py IpswichBoroughCouncil https://app.ipswich.gov.uk/bin-collection/ -n XX ``` @@ -1919,7 +2096,7 @@ Note: Provide only the street name (no house number) as the PAON --- -### Islington Council +### Islington ```commandline python collect_data.py IslingtonCouncil https://www.islington.gov.uk/your-area?Postcode=unused&Uprn=XXXXXXXX -u XXXXXXXX ``` @@ -1930,7 +2107,7 @@ Note: Replace XXXXXXXX with your UPRN. --- -### Kings Lynn and West Norfolk Borough Council +### Kings Lynn and West Norfolk ```commandline python collect_data.py KingsLynnandWestNorfolkBC https://www.west-norfolk.gov.uk/ -u XXXXXXXX ``` @@ -1941,7 +2118,7 @@ Note: Provide your UPRN. Find your UPRN using [FindMyAddress](https://www.findmy --- -### Kingston Upon Thames Council +### Kingston upon Thames ```commandline python collect_data.py KingstonUponThamesCouncil https://waste-services.kingston.gov.uk/waste/XXXXXXX -w http://HOST:PORT/ ``` @@ -1952,7 +2129,7 @@ Note: Follow the instructions [here](https://waste-services.kingston.gov.uk/wast --- -### Kirklees Council +### Kirklees ```commandline python collect_data.py KirkleesCouncil https://www.kirklees.gov.uk/beta/your-property-bins-recycling/your-bins -s -u XXXXXXXX ``` @@ -1964,7 +2141,7 @@ Note: Provide your UPRN. Find your UPRN using [FindMyAddress](https://www.findmy --- -### Knowsley Metropolitan Borough Council +### Knowsley ```commandline python collect_data.py KnowsleyMBCouncil https://knowsleytransaction.mendixcloud.com/link/youarebeingredirected?target=bincollectioninformation -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1978,7 +2155,7 @@ Note: Pass the postcode in the postcode parameter, wrapped in double quotes and --- -### Lancaster City Council +### Lancaster ```commandline python collect_data.py LancasterCityCouncil https://lcc-wrp.whitespacews.com -s -p "XXXX XXX" -n XX ``` @@ -1991,7 +2168,7 @@ Note: Pass the house number and postcode in their respective parameters. --- -### Leeds City Council +### Leeds ```commandline python collect_data.py LeedsCityCouncil https://www.leeds.gov.uk/residents/bins-and-recycling/check-your-bin-day -s -u XXXXXXXX -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -2006,7 +2183,7 @@ Note: Pass the house number, postcode, and UPRN. This parser requires a Selenium --- -### Leicester City Council +### Leicester ```commandline python collect_data.py LeicesterCityCouncil https://biffaleicester.co.uk -u XXXXXXXX ``` @@ -2017,7 +2194,19 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Lichfield District Council +### Lewes +```commandline +python collect_data.py LewesDistrictCouncil https://www.lewes-eastbourne.gov.uk/article/1158/When-is-my-bin-collection-day -s -u XXXXXXXX +``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN + +Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. + +--- + +### Lichfield ```commandline python collect_data.py LichfieldDistrictCouncil https://www.lichfielddc.gov.uk -u XXXXXXXX ``` @@ -2028,7 +2217,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Lincoln Council +### City of Lincoln ```commandline python collect_data.py LincolnCouncil https://lincoln.gov.uk -u XXXXXXXX -p "XXXX XXX" ``` @@ -2040,7 +2229,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Lisburn and Castlereagh City Council +### Lisburn and Castlereagh ```commandline python collect_data.py LisburnCastlereaghCityCouncil https://lisburn.isl-fusion.com -s -p "XXXX XXX" -n XX ``` @@ -2053,16 +2242,19 @@ Note: Pass the house number and postcode in their respective parameters. --- -### Liverpool City Council +### Liverpool ```commandline -python collect_data.py LiverpoolCityCouncil https://liverpool.gov.uk/Bins/BinDatesTable?UPRN=XXXXXXXX +python collect_data.py LiverpoolCityCouncil https://liverpool.gov.uk/bins-and-recycling/bin-collections/ -s -u XXXXXXXX ``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN -Note: Replace XXXXXXXX with your property's UPRN. +Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. --- -### London Borough Ealing +### Ealing ```commandline python collect_data.py LondonBoroughEaling https://www.ealing.gov.uk/site/custom_scripts/WasteCollectionWS/home/FindCollection -s -u XXXXXXXX ``` @@ -2074,7 +2266,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### London Borough Harrow +### Harrow ```commandline python collect_data.py LondonBoroughHarrow https://www.harrow.gov.uk -u XXXXXXXX ``` @@ -2085,7 +2277,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### London Borough Havering +### Havering ```commandline python collect_data.py LondonBoroughHavering https://www.havering.gov.uk -u XXXXXXXX ``` @@ -2096,7 +2288,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### London Borough Hounslow +### Hounslow ```commandline python collect_data.py LondonBoroughHounslow https://www.hounslow.gov.uk/homepage/86/recycling_and_waste_collection_day_finder -s -u XXXXXXXX ``` @@ -2108,7 +2300,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### London Borough Lambeth +### Lambeth ```commandline python collect_data.py LondonBoroughLambeth https://wasteservice.lambeth.gov.uk/WhitespaceComms/GetServicesByUprn -s -u XXXXXXXX ``` @@ -2120,7 +2312,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### London Borough Lewisham +### Lewisham ```commandline python collect_data.py LondonBoroughLewisham https://www.lewisham.gov.uk -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -2134,7 +2326,7 @@ Note: Pass the UPRN and postcode. To get the UPRN, you can use [FindMyAddress](h --- -### London Borough Of Richmond Upon Thames +### Richmond upon Thames ```commandline python collect_data.py LondonBoroughOfRichmondUponThames https://www.richmond.gov.uk/services/waste_and_recycling/collection_days/ -s -n XX -w http://HOST:PORT/ ``` @@ -2147,7 +2339,7 @@ Note: Pass the name of the street ONLY in the house number parameter, unfortunat --- -### London Borough Redbridge +### Redbridge ```commandline python collect_data.py LondonBoroughRedbridge https://my.redbridge.gov.uk/RecycleRefuse -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -2160,7 +2352,7 @@ Note: Follow the instructions [here](https://my.redbridge.gov.uk/RecycleRefuse) --- -### London Borough Sutton +### Sutton ```commandline python collect_data.py LondonBoroughSutton https://waste-services.sutton.gov.uk/waste -u XXXXXXXX ``` @@ -2171,7 +2363,7 @@ Note: You will need to find your unique property reference by going to (https:// --- -### Luton Borough Council +### Luton ```commandline python collect_data.py LutonBoroughCouncil https://myforms.luton.gov.uk -u XXXXXXXX ``` @@ -2182,7 +2374,21 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Maldon District Council +### Maidstone +```commandline +python collect_data.py MaidstoneBoroughCouncil https://my.maidstone.gov.uk/service/Find-your-bin-day -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ +``` +Additional parameters: +- `-s` - skip get URL +- `-p` - postcode +- `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) + +Note: Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver. + +--- + +### Maldon ```commandline python collect_data.py MaldonDistrictCouncil https://maldon.suez.co.uk/maldon/ServiceSummary -s -u XXXXXXXX ``` @@ -2194,7 +2400,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### Malvern Hills District Council +### Malvern Hills ```commandline python collect_data.py MalvernHillsDC https://swict.malvernhills.gov.uk/mhdcroundlookup/HandleSearchScreen -s -u XXXXXXXX ``` @@ -2206,7 +2412,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### Manchester City Council +### Manchester ```commandline python collect_data.py ManchesterCityCouncil https://www.manchester.gov.uk/bincollections -s -u XXXXXXXX ``` @@ -2218,7 +2424,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### Mansfield District Council +### Mansfield ```commandline python collect_data.py MansfieldDistrictCouncil https://www.mansfield.gov.uk/xfp/form/1327 -s -u XXXXXXXX ``` @@ -2230,7 +2436,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### MedwayCouncil +### Medway ```commandline python collect_data.py MedwayCouncil https://www.medway.gov.uk/homepage/45/check_your_waste_collection_day -s -u XXXXXXXX ``` @@ -2242,7 +2448,18 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### Merton Council +### Melton +```commandline +python collect_data.py MeltonBoroughCouncil https://my.melton.gov.uk/collections -u XXXXXXXX +``` +Additional parameters: +- `-u` - UPRN + +Note: To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search). + +--- + +### Merton ```commandline python collect_data.py MertonCouncil https://myneighbourhood.merton.gov.uk/Wasteservices/WasteServices.aspx?ID=XXXXXXXX ``` @@ -2251,7 +2468,7 @@ Note: Follow the instructions [here](https://myneighbourhood.merton.gov.uk/Waste --- -### Mid and East Antrim Borough Council +### Mid and East Antrim ```commandline python collect_data.py MidAndEastAntrimBoroughCouncil https://www.midandeastantrim.gov.uk/resident/waste-recycling/collection-dates/ -s -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -2264,7 +2481,7 @@ Note: Pass the house name/number plus the name of the street with the postcode p --- -### Mid Devon Council +### Mid Devon ```commandline python collect_data.py MidDevonCouncil https://www.middevon.gov.uk -u XXXXXXXX ``` @@ -2275,48 +2492,48 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Middlesbrough Council +### Mid Suffolk ```commandline -python collect_data.py MiddlesbroughCouncil https://www.midsussex.gov.uk/waste-recycling/bin-collection/ -s -n XX -w http://HOST:PORT/ +python collect_data.py MidSuffolkDistrictCouncil https://www.midsuffolk.gov.uk -s -u XXXXXXXX -p "XXXX XXX" -n XX ``` Additional parameters: - `-s` - skip get URL +- `-u` - UPRN +- `-p` - postcode - `-n` - house number -- `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: Pass the entire address without postcode as it appears when you type it on the website. This parser requires a Selenium webdriver. +Note: Use the House Number field to pass the DAY of the week for your NORMAL collections. [Monday/Tuesday/Wednesday/Thursday/Friday]. [OPTIONAL] Use the 'postcode' field to pass the WEEK for your garden collection. [Week 1/Week 2]. [OPTIONAL] Use the 'uprn' field to pass the DAY for your garden collection. [Monday/Tuesday/Wednesday/Thursday/Friday] --- -### Mid Suffolk District Council +### Mid Sussex ```commandline -python collect_data.py MidSuffolkDistrictCouncil https://www.midsuffolk.gov.uk -s -u XXXXXXXX -p "XXXX XXX" -n XX +python collect_data.py MidSussexDistrictCouncil https://www.midsussex.gov.uk/waste-recycling/bin-collection/ -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` Additional parameters: - `-s` - skip get URL -- `-u` - UPRN - `-p` - postcode - `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: Use the House Number field to pass the DAY of the week for your NORMAL collections. [Monday/Tuesday/Wednesday/Thursday/Friday]. [OPTIONAL] Use the 'postcode' field to pass the WEEK for your garden collection. [Week 1/Week 2]. [OPTIONAL] Use the 'uprn' field to pass the DAY for your garden collection. [Monday/Tuesday/Wednesday/Thursday/Friday] +Note: Pass the name of the street with the house number parameter, wrapped in double quotes. This parser requires a Selenium webdriver. --- -### Mid Sussex District Council +### Middlesbrough ```commandline -python collect_data.py MidSussexDistrictCouncil https://www.midsussex.gov.uk/waste-recycling/bin-collection/ -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ +python collect_data.py MiddlesbroughCouncil https://www.middlesbrough.gov.uk/recycling-and-rubbish/bin-collection-dates/ -s -n XX -w http://HOST:PORT/ ``` Additional parameters: - `-s` - skip get URL -- `-p` - postcode - `-n` - house number - `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: Pass the name of the street with the house number parameter, wrapped in double quotes. This parser requires a Selenium webdriver. +Note: Pass the entire address without postcode as it appears when you type it on the website. This parser requires a Selenium webdriver. --- -### Midlothian Council +### Midlothian ```commandline python collect_data.py MidlothianCouncil https://www.midlothian.gov.uk/info/1054/bins_and_recycling/343/bin_collection_days -s -p "XXXX XXX" -n XX ``` @@ -2329,7 +2546,21 @@ Note: Pass the house name/number wrapped in double quotes along with the postcod --- -### Milton Keynes City Council +### Mid Ulster +```commandline +python collect_data.py MidUlsterDistrictCouncil https://www.midulstercouncil.org -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ +``` +Additional parameters: +- `-s` - skip get URL +- `-p` - postcode +- `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) + +Note: Pass the full address of the house postcode as displayed on the site. This parser requires a Selenium webdriver. + +--- + +### Milton Keynes ```commandline python collect_data.py MiltonKeynesCityCouncil https://mycouncil.milton-keynes.gov.uk/en/service/Waste_Collection_Round_Checker -u XXXXXXXX ``` @@ -2340,7 +2571,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Mole Valley District Council +### Mole Valley ```commandline python collect_data.py MoleValleyDistrictCouncil https://myproperty.molevalley.gov.uk/molevalley/ -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -2353,7 +2584,7 @@ Note: UPRN can only be parsed with a valid postcode. --- -### Monmouthshire County Council +### Monmouthshire ```commandline python collect_data.py MonmouthshireCountyCouncil https://maps.monmouthshire.gov.uk -u XXXXXXXX ``` @@ -2364,7 +2595,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Moray Council +### Moray ```commandline python collect_data.py MorayCouncil https://bindayfinder.moray.gov.uk/ -u XXXXXXXX ``` @@ -2375,7 +2606,7 @@ Note: Find your property ID by going to (https://bindayfinder.moray.gov.uk), sea --- -### Neath Port Talbot Council +### Neath Port Talbot ```commandline python collect_data.py NeathPortTalbotCouncil https://www.npt.gov.uk -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -2389,7 +2620,7 @@ Note: Pass the house number and postcode in their respective parameters. This pa --- -### New Forest Council +### New Forest ```commandline python collect_data.py NewForestCouncil https://forms.newforest.gov.uk/id/FIND_MY_COLLECTION -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -2403,16 +2634,19 @@ Note: Pass the postcode and UPRN. This parser requires a Selenium webdriver. --- -### Newark and Sherwood District Council +### Newark and Sherwood ```commandline -python collect_data.py NewarkAndSherwoodDC http://app.newark-sherwooddc.gov.uk/bincollection/calendar?pid=XXXXXXXX +python collect_data.py NewarkAndSherwoodDC https://app.newark-sherwooddc.gov.uk/bincollection/ -s -u XXXXXXXX ``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN Note: Replace XXXXXXXX with your UPRN. --- -### Newcastle City Council +### Newcastle upon Tyne ```commandline python collect_data.py NewcastleCityCouncil https://community.newcastle.gov.uk/my-neighbourhood/ajax/getBinsNew.php?uprn=XXXXXXXX ``` @@ -2421,7 +2655,7 @@ Note: Replace XXXXXXXX with your UPRN. UPRNs need to be 12 digits long so please --- -### Newcastle Under Lyme Council +### Newcastle-under-Lyme ```commandline python collect_data.py NewcastleUnderLymeCouncil https://www.newcastle-staffs.gov.uk -u XXXXXXXX ``` @@ -2432,18 +2666,19 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Newham Council +### Newham ```commandline -python collect_data.py NewhamCouncil https://bincollection.newham.gov.uk/Details/Index/XXXXXXXXXXX -s +python collect_data.py NewhamCouncil https://bincollection.newham.gov.uk/ -s -u XXXXXXXX ``` Additional parameters: - `-s` - skip get URL +- `-u` - UPRN -Note: Follow the instructions [here](https://bincollection.newham.gov.uk/) until you get the "Rubbish and Recycling Collections" page, then copy the URL and replace the URL in the command. +Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN. --- -### Newport City Council +### Newport ```commandline python collect_data.py NewportCityCouncil https://www.newport.gov.uk/ -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -2456,7 +2691,7 @@ Note: Pass the postcode and UPRN. You can find the UPRN using [FindMyAddress](ht --- -### North Ayrshire Council +### North Ayrshire ```commandline python collect_data.py NorthAyrshireCouncil https://www.north-ayrshire.gov.uk/ -u XXXXXXXX ``` @@ -2467,7 +2702,22 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### North East Derbyshire District Council +### North Devon +```commandline +python collect_data.py NorthDevonCountyCouncil https://my.northdevon.gov.uk/service/WasteRecyclingCollectionCalendar -s -u XXXXXXXX -p "XXXX XXX" -n XX -w http://HOST:PORT/ +``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN +- `-p` - postcode +- `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) + +Note: Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver. + +--- + +### North East Derbyshire ```commandline python collect_data.py NorthEastDerbyshireDistrictCouncil https://myselfservice.ne-derbyshire.gov.uk/service/Check_your_Bin_Day -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -2481,7 +2731,7 @@ Note: Pass the postcode and UPRN. This parser requires a Selenium webdriver. --- -### North East Lincolnshire Council +### North East Lincolnshire ```commandline python collect_data.py NorthEastLincs https://www.nelincs.gov.uk/refuse-collection-schedule/?view=timeline&uprn=XXXXXXXX -u XXXXXXXX ``` @@ -2492,19 +2742,20 @@ Note: Replace XXXXXXXX with your UPRN. --- -### North Hertfordshire District Council +### North Hertfordshire ```commandline -python collect_data.py NorthHertfordshireDistrictCouncil https://www.north-herts.gov.uk -p "XXXX XXX" -n XX +python collect_data.py NorthHertfordshireDistrictCouncil https://www.north-herts.gov.uk -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` Additional parameters: - `-p` - postcode - `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) Note: Pass the house number and postcode in their respective parameters. --- -### North Kesteven District Council +### North Kesteven ```commandline python collect_data.py NorthKestevenDistrictCouncil https://www.n-kesteven.org.uk/bins/display?uprn=XXXXXXXX ``` @@ -2513,7 +2764,7 @@ Note: Replace XXXXXXXX with your UPRN. --- -### North Lanarkshire Council +### North Lanarkshire ```commandline python collect_data.py NorthLanarkshireCouncil https://www.northlanarkshire.gov.uk/bin-collection-dates/XXXXXXXXXXX/XXXXXXXXXXX ``` @@ -2522,7 +2773,7 @@ Note: Follow the instructions [here](https://www.northlanarkshire.gov.uk/bin-col --- -### North Lincolnshire Council +### North Lincolnshire ```commandline python collect_data.py NorthLincolnshireCouncil https://www.northlincs.gov.uk/bins-waste-and-recycling/bin-and-box-collection-dates/ -s -u XXXXXXXX ``` @@ -2534,7 +2785,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### North Norfolk District Council +### North Norfolk ```commandline python collect_data.py NorthNorfolkDistrictCouncil https://www.north-norfolk.gov.uk/ -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -2548,7 +2799,7 @@ Note: Pass the name of the street with the house number parameter, wrapped in do --- -### North Northamptonshire Council +### North Northamptonshire ```commandline python collect_data.py NorthNorthamptonshireCouncil https://cms.northnorthants.gov.uk/bin-collection-search/calendarevents/100031021318/2023-10-17/2023-10-01 -s -u XXXXXXXX ``` @@ -2560,7 +2811,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### North Somerset Council +### North Somerset ```commandline python collect_data.py NorthSomersetCouncil https://forms.n-somerset.gov.uk/Waste/CollectionSchedule -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -2573,7 +2824,7 @@ Note: Pass the postcode and UPRN. You can find the UPRN using [FindMyAddress](ht --- -### North Tyneside Council +### North Tyneside ```commandline python collect_data.py NorthTynesideCouncil https://my.northtyneside.gov.uk/category/81/bin-collection-dates -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -2586,7 +2837,7 @@ Note: Pass the postcode and UPRN. You can find the UPRN using [FindMyAddress](ht --- -### North West Leicestershire Council +### North West Leicestershire ```commandline python collect_data.py NorthWestLeicestershire https://www.nwleics.gov.uk/pages/collection_information -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -2600,7 +2851,7 @@ Note: Pass the postcode and UPRN. This parser requires a Selenium webdriver. --- -### North Yorkshire Council +### North Yorkshire ```commandline python collect_data.py NorthYorkshire https://www.northyorks.gov.uk/bin-calendar/lookup -s -u XXXXXXXX ``` @@ -2612,7 +2863,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### Northumberland Council +### Northumberland ```commandline python collect_data.py NorthumberlandCouncil https://www.northumberland.gov.uk/Waste/Household-waste/Household-bin-collections/Bin-Calendars.aspx -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -2626,7 +2877,7 @@ Note: Pass the house number and postcode in their respective parameters. This pa --- -### Norwich City Council +### Norwich ```commandline python collect_data.py NorwichCityCouncil https://www.norwich.gov.uk -u XXXXXXXX ``` @@ -2637,7 +2888,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Nottingham City Council +### Nottingham ```commandline python collect_data.py NottinghamCityCouncil https://geoserver.nottinghamcity.gov.uk/bincollections2/api/collection/100031540180 -s -u XXXXXXXX ``` @@ -2649,7 +2900,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### Nuneaton and Bedworth Borough Council +### Nuneaton and Bedworth ```commandline python collect_data.py NuneatonBedworthBoroughCouncil https://www.nuneatonandbedworth.gov.uk -s -n XX ``` @@ -2661,7 +2912,7 @@ Note: Pass the name of the street ONLY in the house number parameter, wrapped in --- -### Oadby & Wigston Borough Council +### Oadby and Wigston ```commandline python collect_data.py OadbyAndWigstonBoroughCouncil https://my.oadby-wigston.gov.uk -u XXXXXXXX ``` @@ -2672,7 +2923,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Oldham Council +### Oldham ```commandline python collect_data.py OldhamCouncil https://portal.oldham.gov.uk/bincollectiondates/details?uprn=422000033556 ``` @@ -2681,7 +2932,7 @@ Note: Replace UPRN in URL with your own UPRN. --- -### Oxford City Council +### Oxford ```commandline python collect_data.py OxfordCityCouncil https://www.oxford.gov.uk/xfp/form/142 -u XXXXXXXX -p "XXXX XXX" ``` @@ -2693,7 +2944,30 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Perth and Kinross Council +### Pembrokeshire +```commandline +python collect_data.py PembrokeshireCountyCouncil https://nearest.pembrokeshire.gov.uk/property/XXXXXXXXXX +``` + +Note: Replace XXXXXXXX with your UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find it. + +--- + +### Peterborough +```commandline +python collect_data.py PeterboroughCityCouncil https://report.peterborough.gov.uk/waste -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ +``` +Additional parameters: +- `-s` - skip get URL +- `-p` - postcode +- `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) + +Note: Pass the full address as it appears o nthe Peterborough website and postcode in their respective parameters. This parser requires a Selenium webdriver. + +--- + +### Perth and Kinross ```commandline python collect_data.py PerthAndKinrossCouncil https://www.pkc.gov.uk -u XXXXXXXX ``` @@ -2704,7 +2978,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Plymouth Council +### Plymouth ```commandline python collect_data.py PlymouthCouncil https://www.plymouth.gov.uk -u XXXXXXXX ``` @@ -2715,7 +2989,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Portsmouth City Council +### Portsmouth ```commandline python collect_data.py PortsmouthCityCouncil https://my.portsmouth.gov.uk/en/AchieveForms/?form_uri=sandbox-publish://AF-Process-26e27e70-f771-47b1-a34d-af276075cede/AF-Stage-cd7cc291-2e59-42cc-8c3f-1f93e132a2c9/definition.json&redirectlink=%2F&cancelRedirectLink=%2F -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -2729,7 +3003,7 @@ Note: Pass the postcode and UPRN. This parser requires a Selenium webdriver. --- -### Powys Council +### Powys ```commandline python collect_data.py PowysCouncil https://www.powys.gov.uk -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -2741,7 +3015,7 @@ Additional parameters: --- -### Preston City Council +### Preston ```commandline python collect_data.py PrestonCityCouncil https://selfservice.preston.gov.uk/service/Forms/FindMyNearest.aspx?Service=bins -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -2755,7 +3029,7 @@ Note: Pass the house number and postcode in their respective parameters. This pa --- -### Reading Borough Council +### Reading ```commandline python collect_data.py ReadingBoroughCouncil https://api.reading.gov.uk/api/collections/XXXXXXXX ``` @@ -2764,7 +3038,7 @@ Note: Replace XXXXXXXX with your property's UPRN. --- -### Redcar and Cleveland Council +### Redcar and Cleveland ```commandline python collect_data.py RedcarandClevelandCouncil https://www.redcar-cleveland.gov.uk -s -p "XXXX XXX" -n XX ``` @@ -2777,7 +3051,7 @@ Note: Pass the house name/number and postcode in their respective parameters --- -### Redditch Borough Council +### Redditch ```commandline python collect_data.py RedditchBoroughCouncil https://redditchbc.gov.uk -u XXXXXXXX ``` @@ -2788,7 +3062,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Reigate and Banstead Borough Council +### Reigate and Banstead ```commandline python collect_data.py ReigateAndBansteadBoroughCouncil https://www.reigate-banstead.gov.uk/ -s -u XXXXXXXX -w http://HOST:PORT/ ``` @@ -2801,9 +3075,9 @@ Note: To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co. --- -### Renfrewshire Council +### Renfrewshire ```commandline -python collect_data.py RenfrewshireCouncil https://www.renfrewshire.gov.uk/article/2320/Check-your-bin-collection-day -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ +python collect_data.py RenfrewshireCouncil https://www.renfrewshire.gov.uk/bin-day -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` Additional parameters: - `-s` - skip get URL @@ -2811,11 +3085,11 @@ Additional parameters: - `-n` - house number - `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: Pass the house name/number and postcode in their respective parameters. This parser requires a Selenium webdriver. +Note: Pass the full address as it appears on the website. This parser requires a Selenium webdriver. --- -### Rhondda Cynon Taff Council +### Rhondda Cynon Taff ```commandline python collect_data.py RhonddaCynonTaffCouncil https://www.rctcbc.gov.uk/EN/Resident/RecyclingandWaste/RecyclingandWasteCollectionDays.aspx -s -u XXXXXXXX ``` @@ -2827,7 +3101,7 @@ Note: To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co. --- -### Rochdale Council +### Rochdale ```commandline python collect_data.py RochdaleCouncil https://webforms.rochdale.gov.uk/BinCalendar -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -2840,7 +3114,7 @@ Note: Provide your UPRN and postcode. You can find your UPRN using [FindMyAddres --- -### Rochford Council +### Rochford ```commandline python collect_data.py RochfordCouncil https://www.rochford.gov.uk/online-bin-collections-calendar ``` @@ -2849,7 +3123,7 @@ Note: No extra parameters are required. Dates presented should be read as 'week --- -### Rother District Council +### Rother ```commandline python collect_data.py RotherDistrictCouncil https://www.rother.gov.uk -u XXXXXXXX ``` @@ -2860,7 +3134,7 @@ Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your U --- -### Rotherham Council +### Rotherham ```commandline python collect_data.py RotherhamCouncil https://www.rotherham.gov.uk/bin-collections?address=XXXXXXXXX&submit=Submit -u XXXXXXXX ``` @@ -2871,7 +3145,7 @@ Note: Replace `XXXXXXXXX` with your UPRN in the URL. You can find your UPRN usin --- -### Royal Borough of Greenwich +### Greenwich ```commandline python collect_data.py RoyalBoroughofGreenwich https://www.royalgreenwich.gov.uk -s -p "XXXX XXX" -n XX ``` @@ -2884,20 +3158,21 @@ Note: Provide your house number in the `house_number` parameter and your postcod --- -### Rugby Borough Council +### Rugby ```commandline -python collect_data.py RugbyBoroughCouncil https://www.rugby.gov.uk/check-your-next-bin-day -s -u XXXXXXXX -p "XXXX XXX" +python collect_data.py RugbyBoroughCouncil https://www.rugby.gov.uk/check-your-next-bin-day -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` Additional parameters: - `-s` - skip get URL - `-u` - UPRN - `-p` - postcode +- `-w` - remote Selenium web driver URL (required for Home Assistant) Note: Provide your UPRN and postcode. You can find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search). --- -### Runnymede Borough Council +### Runnymede ```commandline python collect_data.py RunnymedeBoroughCouncil https://www.runnymede.gov.uk/ -s -u XXXXXXXX ``` @@ -2909,7 +3184,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Rushcliffe Borough Council +### Rushcliffe ```commandline python collect_data.py RushcliffeBoroughCouncil https://www.rushcliffe.gov.uk/ -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -2923,7 +3198,7 @@ Note: Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddr --- -### Rushmoor Council +### Rushmoor ```commandline python collect_data.py RushmoorCouncil https://www.rushmoor.gov.uk/Umbraco/Api/BinLookUpWorkAround/Get?selectedAddress=XXXXXXXXXX ``` @@ -2932,7 +3207,7 @@ Note: Replace `XXXXXXXXXX` with your UPRN, which you can find using [FindMyAddre --- -### Salford City Council +### Salford ```commandline python collect_data.py SalfordCityCouncil https://www.salford.gov.uk/bins-and-recycling/bin-collection-days/your-bin-collections -s -u XXXXXXXX ``` @@ -2944,7 +3219,7 @@ Note: Provide your UPRN. You can find it using [FindMyAddress](https://www.findm --- -### Sandwell Borough Council +### Sandwell ```commandline python collect_data.py SandwellBoroughCouncil https://www.sandwell.gov.uk -s -u XXXXXXXX ``` @@ -2956,7 +3231,7 @@ Note: Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddr --- -### Sefton Council +### Sefton ```commandline python collect_data.py SeftonCouncil https://www.sefton.gov.uk -p "XXXX XXX" -n XX ``` @@ -2968,7 +3243,7 @@ Note: Pass the postcode and house number in their respective arguments, both wra --- -### Sevenoaks District Council +### Sevenoaks ```commandline python collect_data.py SevenoaksDistrictCouncil https://sevenoaks-dc-host01.oncreate.app/w/webpage/waste-collection-day -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -2982,7 +3257,7 @@ Note: Pass the house name/number in the `house_number` parameter, wrapped in dou --- -### Sheffield City Council +### Sheffield ```commandline python collect_data.py SheffieldCityCouncil https://wasteservices.sheffield.gov.uk/property/XXXXXXXXXXX ``` @@ -2991,7 +3266,7 @@ Note: Follow the instructions [here](https://wasteservices.sheffield.gov.uk/) un --- -### Shropshire Council +### Shropshire ```commandline python collect_data.py ShropshireCouncil https://bins.shropshire.gov.uk/property/XXXXXXXXXXX ``` @@ -3000,41 +3275,42 @@ Note: Follow the instructions [here](https://bins.shropshire.gov.uk/) until you --- -### Solihull Council +### Slough ```commandline -python collect_data.py SolihullCouncil https://digital.solihull.gov.uk/BinCollectionCalendar/Calendar.aspx?UPRN=XXXXXXXX +python collect_data.py SloughBoroughCouncil https://www.slough.gov.uk/bin-collections -s -p "XXXX XXX" -w http://HOST:PORT/ ``` +Additional parameters: +- `-s` - skip get URL +- `-p` - postcode +- `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: Replace `XXXXXXXX` with your UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. +Note: Pass the UPRN and postcode in their respective parameters. This parser requires a Selenium webdriver. --- -### Somerset Council +### Solihull ```commandline -python collect_data.py SomersetCouncil https://www.somerset.gov.uk/ -s -u XXXXXXXX -p "XXXX XXX" +python collect_data.py SolihullCouncil https://digital.solihull.gov.uk/BinCollectionCalendar/Calendar.aspx?UPRN=XXXXXXXX ``` -Additional parameters: -- `-s` - skip get URL -- `-u` - UPRN -- `-p` - postcode -Note: Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search). +Note: Replace `XXXXXXXX` with your UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. --- -### Southampton City Council +### Somerset ```commandline -python collect_data.py SouthamptonCityCouncil https://www.southampton.gov.uk -s -u XXXXXXXX +python collect_data.py SomersetCouncil https://www.somerset.gov.uk/ -s -u XXXXXXXX -p "XXXX XXX" ``` Additional parameters: - `-s` - skip get URL - `-u` - UPRN +- `-p` - postcode -Note: Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search). +Note: Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search). --- -### South Ayrshire Council +### South Ayrshire ```commandline python collect_data.py SouthAyrshireCouncil https://www.south-ayrshire.gov.uk/ -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -3047,7 +3323,7 @@ Note: Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddr --- -### South Cambridgeshire Council +### South Cambridgeshire ```commandline python collect_data.py SouthCambridgeshireCouncil https://www.scambs.gov.uk/recycling-and-bins/find-your-household-bin-collection-day/ -s -p "XXXX XXX" -n XX ``` @@ -3060,7 +3336,7 @@ Note: Provide your house number in the `house_number` parameter and postcode in --- -### South Derbyshire District Council +### South Derbyshire ```commandline python collect_data.py SouthDerbyshireDistrictCouncil https://maps.southderbyshire.gov.uk/iShareLIVE.web//getdata.aspx?RequestType=LocalInfo&ms=mapsources/MyHouse&format=JSONP&group=Recycling%20Bins%20and%20Waste|Next%20Bin%20Collections&uid=XXXXXXXX -u XXXXXXXX ``` @@ -3071,7 +3347,7 @@ Note: Replace `XXXXXXXX` with your UPRN. You can find your UPRN using [FindMyAdd --- -### South Gloucestershire Council +### South Gloucestershire ```commandline python collect_data.py SouthGloucestershireCouncil https://beta.southglos.gov.uk/waste-and-recycling-collection-date -s -u XXXXXXXX ``` @@ -3083,7 +3359,7 @@ Note: Provide your UPRN. You can find it using [FindMyAddress](https://www.findm --- -### South Hams District Council +### South Hams ```commandline python collect_data.py SouthHamsDistrictCouncil https://www.southhams.gov.uk -u XXXXXXXX ``` @@ -3094,7 +3370,22 @@ Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your U --- -### South Kesteven District Council +### South Holland +```commandline +python collect_data.py SouthHollandDistrictCouncil https://www.sholland.gov.uk/mycollections -s -u XXXXXXXX -p "XXXX XXX" -n XX -w http://HOST:PORT/ +``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN +- `-p` - postcode +- `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) + +Note: Pass the UPRN and postcode in their respective parameters. This parser requires a Selenium webdriver. + +--- + +### South Kesteven ```commandline python collect_data.py SouthKestevenDistrictCouncil https://pre.southkesteven.gov.uk/BinSearch.aspx -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -3108,7 +3399,7 @@ Note: Provide your full address in the `house_number` parameter and your postcod --- -### South Lanarkshire Council +### South Lanarkshire ```commandline python collect_data.py SouthLanarkshireCouncil https://www.southlanarkshire.gov.uk/directory_record/XXXXX/XXXXX ``` @@ -3117,7 +3408,7 @@ Note: Follow the instructions [here](https://www.southlanarkshire.gov.uk/info/20 --- -### South Norfolk Council +### South Norfolk ```commandline python collect_data.py SouthNorfolkCouncil https://www.southnorfolkandbroadland.gov.uk/rubbish-recycling/south-norfolk-bin-collection-day-finder -s -u XXXXXXXX ``` @@ -3129,7 +3420,7 @@ Note: Provide your UPRN. Find it using [FindMyAddress](https://www.findmyaddress --- -### South Oxfordshire Council +### South Oxfordshire ```commandline python collect_data.py SouthOxfordshireCouncil https://www.southoxon.gov.uk/south-oxfordshire-district-council/recycling-rubbish-and-waste/when-is-your-collection-day/ -s -u XXXXXXXX ``` @@ -3141,7 +3432,7 @@ Note: Provide your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/sea --- -### South Ribble Council +### South Ribble ```commandline python collect_data.py SouthRibbleCouncil https://forms.chorleysouthribble.gov.uk/xfp/form/70 -u XXXXXXXX -p "XXXX XXX" ``` @@ -3153,7 +3444,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### South Staffordshire District Council +### South Staffordshire ```commandline python collect_data.py SouthStaffordshireDistrictCouncil https://www.sstaffs.gov.uk/where-i-live?uprn=200004523954 -u XXXXXXXX ``` @@ -3164,7 +3455,7 @@ Note: The URL needs to be `https://www.sstaffs.gov.uk/where-i-live?uprn=`. R --- -### Staffordshire Moorlands District Council +### Staffordshire Moorlands ```commandline python collect_data.py StaffordshireMoorlandsDistrictCouncil https://www.staffsmoorlands.gov.uk/ -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -3250,7 +3553,7 @@ Note: Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddr --- -### Stevenage Borough Council +### Stevenage ```commandline python collect_data.py StevenageBoroughCouncil https://www.stevenage.gov.uk -u XXXXXXXX ``` @@ -3261,7 +3564,21 @@ Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your U --- -### Stockport Borough Council +### Stirling +```commandline +python collect_data.py StirlingCouncil https://www.stirling.gov.uk/bins-and-recycling/bin-collection-dates-search/ -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ +``` +Additional parameters: +- `-s` - skip get URL +- `-p` - postcode +- `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) + +Note: Use the full address as it appears on the drop-down on the site when you search by postcode. + +--- + +### Stockport ```commandline python collect_data.py StockportBoroughCouncil https://myaccount.stockport.gov.uk/bin-collections/show/XXXXXXXX ``` @@ -3270,7 +3587,7 @@ Note: Replace `XXXXXXXX` with your UPRN. --- -### Stockton On Tees Council +### Stockton-on-Tees ```commandline python collect_data.py StocktonOnTeesCouncil https://www.stockton.gov.uk -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -3282,7 +3599,7 @@ Additional parameters: --- -### Stoke-on-Trent City Council +### Stoke-on-Trent ```commandline python collect_data.py StokeOnTrentCityCouncil https://www.stoke.gov.uk/jadu/custom/webserviceLookUps/BarTecWebServices_missed_bin_calendar.php?UPRN=XXXXXXXXXX ``` @@ -3291,7 +3608,7 @@ Note: Replace `XXXXXXXXXX` with your property's UPRN. --- -### Stratford Upon Avon Council +### Stratford-on-Avon ```commandline python collect_data.py StratfordUponAvonCouncil https://www.stratford.gov.uk/waste-recycling/when-we-collect.cfm/part/calendar -s -u XXXXXXXX ``` @@ -3303,7 +3620,7 @@ Note: Provide your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/sea --- -### Stroud District Council +### Stroud ```commandline python collect_data.py StroudDistrictCouncil https://www.stroud.gov.uk/my-house?uprn=100120512183&postcode=GL10+3BH -u XXXXXXXX -p "XXXX XXX" ``` @@ -3315,7 +3632,7 @@ Note: Provide your UPRN and postcode. Replace the UPRN and postcode in the URL w --- -### Sunderland City Council +### Sunderland ```commandline python collect_data.py SunderlandCityCouncil https://webapps.sunderland.gov.uk/WEBAPPS/WSS/Sunderland_Portal/Forms/bindaychecker.aspx -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -3329,7 +3646,7 @@ Note: Provide your house number (without quotes) and postcode (wrapped in double --- -### Surrey Heath Borough Council / Joint Waste Solutions +### Surrey Heath ```commandline python collect_data.py SurreyHeathBoroughCouncil https://asjwsw-wrpsurreyheathmunicipal-live.whitespacews.com/ -s -p "XXXX XXX" -n XX ``` @@ -3342,7 +3659,7 @@ Note: Provide your house number in the `house_number` parameter and postcode in --- -### Swale Borough Council +### Swale ```commandline python collect_data.py SwaleBoroughCouncil https://swale.gov.uk/bins-littering-and-the-environment/bins/collection-days -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -3356,7 +3673,7 @@ Note: Provide your house number in the `house_number` parameter and postcode in --- -### Swansea Council +### Swansea ```commandline python collect_data.py SwanseaCouncil https://www1.swansea.gov.uk/recyclingsearch/ -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -3369,7 +3686,7 @@ Note: Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https --- -### Swindon Borough Council +### Swindon ```commandline python collect_data.py SwindonBoroughCouncil https://www.swindon.gov.uk -u XXXXXXXX ``` @@ -3380,7 +3697,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Tameside Metropolitan Borough Council +### Tameside ```commandline python collect_data.py TamesideMBCouncil http://lite.tameside.gov.uk/BinCollections/CollectionService.svc/GetBinCollection -s -u XXXXXXXX ``` @@ -3392,7 +3709,7 @@ Note: Provide your UPRN. You can find it using [FindMyAddress](https://www.findm --- -### Tandridge District Council +### Tandridge ```commandline python collect_data.py TandridgeDistrictCouncil https://tdcws01.tandridge.gov.uk/TDCWebAppsPublic/tfaBranded/408?utm_source=pressrelease&utm_medium=smposts&utm_campaign=check_my_bin_day -s -u XXXXXXXX ``` @@ -3404,7 +3721,7 @@ Note: Provide your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/sea --- -### Teignbridge Council +### Teignbridge ```commandline python collect_data.py TeignbridgeCouncil https://www.google.co.uk -u XXXXXXXX -w http://HOST:PORT/ ``` @@ -3416,7 +3733,7 @@ Note: Provide Google as the URL as the real URL breaks the integration. You will --- -### Telford and Wrekin Council +### Telford and Wrekin ```commandline python collect_data.py TelfordAndWrekinCouncil https://dac.telford.gov.uk/bindayfinder/ -s -u XXXXXXXX ``` @@ -3428,7 +3745,19 @@ Note: Provide your UPRN. Find it using [FindMyAddress](https://www.findmyaddress --- -### Tendring District Council +### Tewkesbury +```commandline +python collect_data.py TewkesburyBoroughCouncil https://tewkesbury.gov.uk/services/waste-and-recycling/ -s -u XXXXXXXX +``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN + +Note: Provide your UPRN. Find it using [FindMyAddress](https://www.findmyaddress.co.uk/search). + +--- + +### Tendring ```commandline python collect_data.py TendringDistrictCouncil https://tendring-self.achieveservice.com/en/service/Rubbish_and_recycling_collection_days -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -3442,7 +3771,7 @@ Note: Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https --- -### Test Valley Borough Council +### Test Valley ```commandline python collect_data.py TestValleyBoroughCouncil https://testvalley.gov.uk/wasteandrecycling/when-are-my-bins-collected -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -3455,18 +3784,19 @@ Note: Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddr --- -### Thanet District Council +### Thanet ```commandline -python collect_data.py ThanetDistrictCouncil https://www.thanet.gov.uk -u XXXXXXXX +python collect_data.py ThanetDistrictCouncil https://www.thanet.gov.uk -u XXXXXXXX -w http://HOST:PORT/ ``` Additional parameters: - `-u` - UPRN +- `-w` - remote Selenium web driver URL (required for Home Assistant) Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN. --- -### Three Rivers District Council +### Three Rivers ```commandline python collect_data.py ThreeRiversDistrictCouncil https://my.threerivers.gov.uk/en/AchieveForms/?mode=fill&consentMessage=yes&form_uri=sandbox-publish://AF-Process-52df96e3-992a-4b39-bba3-06cfaabcb42b/AF-Stage-01ee28aa-1584-442c-8d1f-119b6e27114a/definition.json&process=1&process_uri=sandbox-processes://AF-Process-52df96e3-992a-4b39-bba3-06cfaabcb42b&process_id=AF-Process-52df96e3-992a-4b39-bba3-06cfaabcb42b&noLoginPrompt=1 -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -3480,7 +3810,7 @@ Note: Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https --- -### Thurrock Council +### Thurrock ```commandline python collect_data.py ThurrockCouncil https://www.thurrock.gov.uk -s -p "XXXX XXX" -n XX ``` @@ -3493,7 +3823,7 @@ Note: Use the House Number field to pass the DAY of the week for your collection --- -### Tonbridge and Malling Borough Council +### Tonbridge and Malling ```commandline python collect_data.py TonbridgeAndMallingBC https://www.tmbc.gov.uk/ -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -3506,19 +3836,20 @@ Note: Provide your UPRN and postcode. --- -### Torbay Council +### Torbay ```commandline -python collect_data.py TorbayCouncil https://www.torbay.gov.uk/recycling/bin-collections/ -s -u XXXXXXXX +python collect_data.py TorbayCouncil https://www.torbay.gov.uk/recycling/bin-collections/ -s -u XXXXXXXX -p "XXXX XXX" ``` Additional parameters: - `-s` - skip get URL - `-u` - UPRN +- `-p` - postcode Note: Provide your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find it. --- -### Torridge District Council +### Torridge ```commandline python collect_data.py TorridgeDistrictCouncil https://collections-torridge.azurewebsites.net/WebService2.asmx -s -u XXXXXXXX ``` @@ -3530,7 +3861,7 @@ Note: Provide your UPRN. --- -### Tunbridge Wells Council +### Tunbridge Wells ```commandline python collect_data.py TunbridgeWellsCouncil https://tunbridgewells.gov.uk -u XXXXXXXX ``` @@ -3541,7 +3872,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Uttlesford District Council +### Uttlesford ```commandline python collect_data.py UttlesfordDistrictCouncil https://bins.uttlesford.gov.uk/ -s -u XXXXXXXX -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -3556,7 +3887,7 @@ Note: Provide your full address in the `house_number` parameter and your postcod --- -### Vale of Glamorgan Council +### The Vale of Glamorgan ```commandline python collect_data.py ValeofGlamorganCouncil https://www.valeofglamorgan.gov.uk/en/living/Recycling-and-Waste/ -s -u XXXXXXXX ``` @@ -3568,7 +3899,7 @@ Note: Provide your UPRN. Find it using [FindMyAddress](https://www.findmyaddress --- -### Vale of White Horse Council +### Vale of White Horse ```commandline python collect_data.py ValeofWhiteHorseCouncil https://eform.whitehorsedc.gov.uk/ebase/BINZONE_DESKTOP.eb -s -u XXXXXXXX ``` @@ -3580,7 +3911,7 @@ Note: Provide your UPRN. --- -### Wakefield City Council +### Wakefield ```commandline python collect_data.py WakefieldCityCouncil https://www.wakefield.gov.uk/where-i-live/?uprn=XXXXXXXXXXX&a=XXXXXXXXXXX&usrn=XXXXXXXXXXX&e=XXXXXXXXXXX&n=XXXXXXXXXXX&p=XXXXXXXXXXX -s -w http://HOST:PORT/ ``` @@ -3592,7 +3923,7 @@ Note: Follow the instructions [here](https://www.wakefield.gov.uk/where-i-live/) --- -### Walsall Council +### Walsall ```commandline python collect_data.py WalsallCouncil https://cag.walsall.gov.uk/ -u XXXXXXXX ``` @@ -3618,7 +3949,7 @@ Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your U --- -### Wandsworth Council +### Wandsworth ```commandline python collect_data.py WandsworthCouncil https://www.wandsworth.gov.uk -u XXXXXXXX ``` @@ -3629,7 +3960,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Warrington Borough Council +### Warrington ```commandline python collect_data.py WarringtonBoroughCouncil https://www.warrington.gov.uk -u XXXXXXXX ``` @@ -3640,7 +3971,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Warwick District Council +### Warwick ```commandline python collect_data.py WarwickDistrictCouncil https://estates7.warwickdc.gov.uk/PropertyPortal/Property/Recycling/XXXXXXXX ``` @@ -3649,7 +3980,7 @@ Note: Replace `XXXXXXXX` with your UPRN. --- -### Watford Borough Council +### Watford ```commandline python collect_data.py WatfordBoroughCouncil https://www.watford.gov.uk -u XXXXXXXX ``` @@ -3660,7 +3991,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Waverley Borough Council +### Waverley ```commandline python collect_data.py WaverleyBoroughCouncil https://wav-wrp.whitespacews.com/ -s -p "XXXX XXX" -n XX ``` @@ -3673,7 +4004,7 @@ Note: Follow the instructions [here](https://wav-wrp.whitespacews.com/#!) until --- -### Wealden District Council +### Wealden ```commandline python collect_data.py WealdenDistrictCouncil https://www.wealden.gov.uk/recycling-and-waste/bin-search/ -s -u XXXXXXXX ``` @@ -3685,7 +4016,7 @@ Note: Provide your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/sea --- -### Welhat Council +### Welwyn Hatfield ```commandline python collect_data.py WelhatCouncil https://www.welhat.gov.uk/xfp/form/214 -u XXXXXXXX -p "XXXX XXX" ``` @@ -3697,7 +4028,7 @@ Note: Provide your UPRN and postcode. --- -### West Berkshire Council +### West Berkshire ```commandline python collect_data.py WestBerkshireCouncil https://www.westberks.gov.uk/binday -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -3711,7 +4042,7 @@ Note: Provide your house number in the `house_number` parameter and postcode in --- -### West Dunbartonshire Council +### West Dunbartonshire ```commandline python collect_data.py WestDunbartonshireCouncil https://www.west-dunbarton.gov.uk/ -u XXXXXXXX ``` @@ -3722,7 +4053,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### West Lancashire Borough Council +### West Lancashire ```commandline python collect_data.py WestLancashireBoroughCouncil https://www.westlancs.gov.uk -u XXXXXXXX -p "XXXX XXX" ``` @@ -3734,7 +4065,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### West Lindsey District Council +### West Lindsey ```commandline python collect_data.py WestLindseyDistrictCouncil https://www.west-lindsey.gov.uk/ -s -p "XXXX XXX" -n XX ``` @@ -3747,7 +4078,7 @@ Note: Provide your house name/number in the `house_number` parameter, and postco --- -### West Lothian Council +### West Lothian ```commandline python collect_data.py WestLothianCouncil https://www.westlothian.gov.uk/ -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -3761,7 +4092,7 @@ Note: Provide your house name/number in the `house_number` parameter (wrapped in --- -### West Morland and Furness Council +### Westmorland and Furness ```commandline python collect_data.py WestMorlandAndFurness https://www.westmorlandandfurness.gov.uk/ -u XXXXXXXX ``` @@ -3772,7 +4103,7 @@ Note: Provide your UPRN. You can find your UPRN using [FindMyAddress](https://ww --- -### West Northamptonshire Council +### West Northamptonshire ```commandline python collect_data.py WestNorthamptonshireCouncil https://www.westnorthants.gov.uk -s -u XXXXXXXX ``` @@ -3784,7 +4115,7 @@ Note: Provide your UPRN. You can find your UPRN using [FindMyAddress](https://ww --- -### West Oxfordshire District Council +### West Oxfordshire ```commandline python collect_data.py WestOxfordshireDistrictCouncil https://community.westoxon.gov.uk/s/waste-collection-enquiry -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -3798,7 +4129,7 @@ Note: Provide your house number in the `house_number` parameter and your postcod --- -### West Suffolk Council +### West Suffolk ```commandline python collect_data.py WestSuffolkCouncil https://maps.westsuffolk.gov.uk/MyWestSuffolk.aspx -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -3811,7 +4142,7 @@ Note: Provide your UPRN and postcode. You can find your UPRN using [FindMyAddres --- -### Wigan Borough Council +### Wigan ```commandline python collect_data.py WiganBoroughCouncil https://apps.wigan.gov.uk/MyNeighbourhood/ -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -3824,7 +4155,7 @@ Note: Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https --- -### Wiltshire Council +### Wiltshire ```commandline python collect_data.py WiltshireCouncil https://ilambassadorformsprod.azurewebsites.net/wastecollectiondays/index -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -3837,7 +4168,7 @@ Note: Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddr --- -### Winchester City Council +### Winchester ```commandline python collect_data.py WinchesterCityCouncil https://iportal.itouchvision.com/icollectionday/collection-day -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -3851,7 +4182,7 @@ Note: Provide your house name/number in the `house_number` parameter (wrapped in --- -### Windsor and Maidenhead Council +### Windsor and Maidenhead ```commandline python collect_data.py WindsorAndMaidenheadCouncil https://forms.rbwm.gov.uk/bincollections?uprn= -s -u XXXXXXXX -w http://HOST:PORT/ ``` @@ -3864,7 +4195,7 @@ Note: Provide your UPRN. You can find it using [FindMyAddress](https://www.findm --- -### Wirral Council +### Wirral ```commandline python collect_data.py WirralCouncil https://www.wirral.gov.uk -u XXXXXXXX ``` @@ -3875,7 +4206,7 @@ Note: In the `uprn` field, enter your street name and suburb separated by a comm --- -### Woking Borough Council / Joint Waste Solutions +### Woking ```commandline python collect_data.py WokingBoroughCouncil https://asjwsw-wrpwokingmunicipal-live.whitespacews.com/ -s -p "XXXX XXX" -n XX ``` @@ -3888,7 +4219,7 @@ Note: Provide your house number in the `house_number` parameter and postcode in --- -### Wokingham Borough Council +### Wokingham ```commandline python collect_data.py WokinghamBoroughCouncil https://www.wokingham.gov.uk/rubbish-and-recycling/waste-collection/find-your-bin-collection-day -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -3902,7 +4233,7 @@ Note: Provide your house number in the `house_number` parameter and postcode in --- -### Wolverhampton City Council +### Wolverhampton ```commandline python collect_data.py WolverhamptonCityCouncil https://www.wolverhampton.gov.uk -u XXXXXXXX -p "XXXX XXX" ``` @@ -3914,7 +4245,7 @@ Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your U --- -### Worcester City Council +### Worcester ```commandline python collect_data.py WorcesterCityCouncil https://www.Worcester.gov.uk -u XXXXXXXX ``` @@ -3925,7 +4256,22 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Wychavon District Council +### Wrexham +```commandline +python collect_data.py WrexhamCountyBoroughCouncil https://www.wrexham.gov.uk/service/when-are-my-bins-collected -s -u XXXXXXXX -p "XXXX XXX" -n XX -w http://HOST:PORT/ +``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN +- `-p` - postcode +- `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) + +Note: Provide your house number in the `house_number` parameter and postcode in the `postcode` parameter. + +--- + +### Wychavon ```commandline python collect_data.py WychavonDistrictCouncil https://selfservice.wychavon.gov.uk/wdcroundlookup/wdc_search.jsp -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -3939,7 +4285,7 @@ Note: Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https --- -### Wyre Council +### Wyre ```commandline python collect_data.py WyreCouncil https://www.wyre.gov.uk/bins-rubbish-recycling -s -u XXXXXXXX ``` @@ -3951,7 +4297,7 @@ Note: Provide your UPRN. Find your UPRN using [FindMyAddress](https://www.findmy --- -### Wyre Forest District Council +### Wyre Forest ```commandline python collect_data.py WyreForestDistrictCouncil https://www.wyreforestdc.gov.uk -s -n XX ``` @@ -3963,7 +4309,7 @@ Note: Use the House Number field to pass the DAY of the week for your collection --- -### York Council +### York ```commandline python collect_data.py YorkCouncil https://waste-api.york.gov.uk/api/Collections/GetBinCollectionDataForUprn/ -s -u XXXXXXXX ``` From 45f67abd36bb501e6c7d40fcba0d0433cac58bdf Mon Sep 17 00:00:00 2001 From: m26dvd <31007572+m26dvd@users.noreply.github.com> Date: Tue, 26 Aug 2025 12:12:35 +0100 Subject: [PATCH 115/425] fix: East Herts Council fix: #1575 --- .../councils/EastHertsCouncil.py | 106 ++++++++++++++---- 1 file changed, 82 insertions(+), 24 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/EastHertsCouncil.py b/uk_bin_collection/uk_bin_collection/councils/EastHertsCouncil.py index fc8909c006..06a175d960 100644 --- a/uk_bin_collection/uk_bin_collection/councils/EastHertsCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/EastHertsCouncil.py @@ -1,11 +1,13 @@ -import json +import time + import requests -from datetime import datetime +from dateutil.relativedelta import relativedelta from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass +# import the wonderful Beautiful Soup and the URL grabber class CouncilClass(AbstractGetBinDataClass): """ Concrete classes have to implement all abstract operations of the @@ -14,28 +16,84 @@ class CouncilClass(AbstractGetBinDataClass): """ def parse_data(self, page: str, **kwargs) -> dict: - user_uprn = kwargs.get("uprn") - check_uprn(user_uprn) + # Make a BS4 object + uprn = kwargs.get("uprn") + # usrn = kwargs.get("paon") + check_uprn(uprn) + # check_usrn(usrn) bindata = {"bins": []} - - # Make API request - api_url = f"https://east-herts.co.uk/api/services/{user_uprn}" - response = requests.get(api_url) - response.raise_for_status() - - data = response.json() - today = datetime.now().date() - - for service in data.get("services", []): - collection_date_str = service.get("collectionDate") - if collection_date_str: - collection_date = datetime.strptime(collection_date_str, "%Y-%m-%d").date() - # Only include future dates - if collection_date >= today: - dict_data = { - "type": service.get("binType", ""), - "collectionDate": collection_date.strftime("%d/%m/%Y"), + + # uprn = uprn.zfill(12) + + SESSION_URL = "https://eastherts-self.achieveservice.com/authapi/isauthenticated?uri=https%253A%252F%252Feastherts-self.achieveservice.com%252FAchieveForms%252F%253Fmode%253Dfill%2526consentMessage%253Dyes%2526form_uri%253Dsandbox-publish%253A%252F%252FAF-Process-98782935-6101-4962-9a55-5923e76057b6%252FAF-Stage-dcd0ec18-dfb4-496a-a266-bd8fadaa28a7%252Fdefinition.json%2526process%253D1%2526process_uri%253Dsandbox-processes%253A%252F%252FAF-Process-98782935-6101-4962-9a55-5923e76057b6%2526process_id%253DAF-Process-98782935-6101-4962-9a55-5923e76057b6&hostname=eastherts-self.achieveservice.com&withCredentials=true" + + API_URL = "https://eastherts-self.achieveservice.com/apibroker/runLookup" + + headers = { + "Content-Type": "application/json", + "Accept": "*/*", + "User-Agent": "Mozilla/5.0", + "X-Requested-With": "XMLHttpRequest", + "Referer": "https://eastherts-self.achieveservice.com/fillform/?iframe_id=fillform-frame-1&db_id=", + } + s = requests.session() + r = s.get(SESSION_URL) + r.raise_for_status() + session_data = r.json() + sid = session_data["auth-session"] + params = { + # unix_timestamp + "_": str(int(time.time() * 1000)), + "sid": sid, + } + + params = { + "id": "683d9ff0e299d", + "repeat_against": "", + "noRetry": "true", + "getOnlyTokens": "undefined", + "log_id": "", + "app_name": "AF-Renderer::Self", + # unix_timestamp + "_": str(int(time.time() * 1000)), + "sid": sid, + } + + data = { + "formValues": { + "Collection Days": { + "inputUPRN": { + "value": uprn, } - bindata["bins"].append(dict_data) - + }, + } + } + + r = s.post(API_URL, json=data, headers=headers, params=params) + r.raise_for_status() + + data = r.json() + rows_data = data["integration"]["transformed"]["rows_data"]["0"] + if not isinstance(rows_data, dict): + raise ValueError("Invalid data returned from API") + + # Extract each service's relevant details for the bin schedule + for key, value in rows_data.items(): + if key.endswith("NextDate"): + BinType = key.replace("NextDate", "ServiceName") + for key2, value2 in rows_data.items(): + if key2 == BinType: + BinType = value2 + next_collection = datetime.strptime( + remove_ordinal_indicator_from_date_string(value), "%A %d %B" + ).replace(year=datetime.now().year) + if datetime.now().month == 12 and next_collection.month == 1: + next_collection = next_collection + relativedelta(years=1) + + dict_data = { + "type": BinType, + "collectionDate": next_collection.strftime(date_format), + } + bindata["bins"].append(dict_data) + return bindata From 5d82de37af08860ed56b306f0a2602389de03375 Mon Sep 17 00:00:00 2001 From: dandantheitman Date: Tue, 26 Aug 2025 20:33:29 +0100 Subject: [PATCH 116/425] fix: 1573 Update Bolton council URL --- uk_bin_collection/tests/input.json | 2 +- uk_bin_collection/uk_bin_collection/councils/BoltonCouncil.py | 2 +- wiki/Councils.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index 9d2775f7d9..b54e3009d3 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -253,7 +253,7 @@ "postcode": "BL1 5PQ", "skip_get_url": true, "uprn": "100010886936", - "url": "https://carehomes.bolton.gov.uk/bins.aspx", + "url": "https://web.bolton.gov.uk/bins.aspx", "web_driver": "http://selenium:4444", "wiki_name": "Bolton", "wiki_note": "To get the UPRN, you will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search). Previously required a single field that was UPRN and full address; now requires UPRN and postcode as separate fields.", diff --git a/uk_bin_collection/uk_bin_collection/councils/BoltonCouncil.py b/uk_bin_collection/uk_bin_collection/councils/BoltonCouncil.py index e278152988..282f74b4f2 100644 --- a/uk_bin_collection/uk_bin_collection/councils/BoltonCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/BoltonCouncil.py @@ -35,7 +35,7 @@ def parse_data(self, page: str, **kwargs) -> dict: data = {"bins": []} # Get our initial session running - page = "https://carehomes.bolton.gov.uk/bins.aspx" + page = "https://web.bolton.gov.uk/bins.aspx" driver = create_webdriver(web_driver, headless, None, __name__) driver.get(page) diff --git a/wiki/Councils.md b/wiki/Councils.md index 7453f5d9b7..824e9638af 100644 --- a/wiki/Councils.md +++ b/wiki/Councils.md @@ -658,7 +658,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc ### Bolton Council ```commandline -python collect_data.py BoltonCouncil https://carehomes.bolton.gov.uk/bins.aspx -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ +python collect_data.py BoltonCouncil https://web.bolton.gov.uk/bins.aspx -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` Additional parameters: - `-s` - skip get URL From f7cc9b391be498cf4503bf01095bd376d7f03737 Mon Sep 17 00:00:00 2001 From: Wiki GitHub Action Date: Tue, 26 Aug 2025 19:33:55 +0000 Subject: [PATCH 117/425] docs: Update Councils.md from input.json --- wiki/Councils.md | 1722 ++++++++++++++++++++++++++++------------------ 1 file changed, 1034 insertions(+), 688 deletions(-) diff --git a/wiki/Councils.md b/wiki/Councils.md index 824e9638af..b476450407 100644 --- a/wiki/Councils.md +++ b/wiki/Councils.md @@ -10,314 +10,336 @@ For scripts that need postcodes, these should be provided in double quotes and w This document is still a work in progress, don't worry if your council isn't listed - it will be soon! ## Contents -- [Aberdeen City Council](#aberdeen-city-council) -- [Aberdeenshire Council](#aberdeenshire-council) -- [Adur and Worthing Councils](#adur-and-worthing-councils) -- [Amber Valley Borough Council](#amber-valley-borough-council) -- [Antrim & Newtonabbey Council](#antrim-&-newtonabbey-council) -- [Ards and North Down Council](#ards-and-north-down-council) -- [Argyll and Bute Council](#argyll-and-bute-council) -- [Armagh Banbridge Craigavon Council](#armagh-banbridge-craigavon-council) -- [Arun Council](#arun-council) -- [Ashfield District Council](#ashfield-district-council) -- [Ashford Borough Council](#ashford-borough-council) -- [Aylesbury Vale Council (Buckinghamshire)](#aylesbury-vale-council-(buckinghamshire)) -- [BCP Council](#bcp-council) -- [Babergh District Council](#babergh-district-council) -- [Barnet Council](#barnet-council) -- [Barnsley Metropolitan Borough Council](#barnsley-metropolitan-borough-council) -- [Basildon Council](#basildon-council) -- [Basingstoke Council](#basingstoke-council) -- [Bath and North East Somerset Council](#bath-and-north-east-somerset-council) -- [Bedford Borough Council](#bedford-borough-council) -- [Bedfordshire Council](#bedfordshire-council) -- [Belfast City Council](#belfast-city-council) -- [Bexley Council](#bexley-council) -- [Birmingham City Council](#birmingham-city-council) -- [Blaby District Council](#blaby-district-council) -- [Blackburn Council](#blackburn-council) -- [Blaenau Gwent County Borough Council](#blaenau-gwent-county-borough-council) -- [Bolsover Council](#bolsover-council) -- [Bolton Council](#bolton-council) -- [Boston Borough Council](#boston-borough-council) -- [Bracknell Forest Council](#bracknell-forest-council) -- [Bradford MDC](#bradford-mdc) -- [Braintree District Council](#braintree-district-council) -- [Breckland Council](#breckland-council) -- [Brent Council](#brent-council) -- [Brighton and Hove City Council](#brighton-and-hove-city-council) -- [Bristol City Council](#bristol-city-council) -- [Bromley Borough Council](#bromley-borough-council) -- [Bromsgrove District Council](#bromsgrove-district-council) -- [Broxbourne Council](#broxbourne-council) -- [Broxtowe Borough Council](#broxtowe-borough-council) -- [Buckinghamshire Council (Chiltern, South Bucks, Wycombe)](#buckinghamshire-council-(chiltern,-south-bucks,-wycombe)) -- [Burnley Borough Council](#burnley-borough-council) -- [Bury Council](#bury-council) -- [Calderdale Council](#calderdale-council) -- [Cambridge City Council](#cambridge-city-council) -- [Cannock Chase District Council](#cannock-chase-district-council) -- [Canterbury City Council](#canterbury-city-council) -- [Cardiff Council](#cardiff-council) -- [Carmarthenshire County Council](#carmarthenshire-county-council) -- [Castlepoint District Council](#castlepoint-district-council) -- [Charnwood Borough Council](#charnwood-borough-council) -- [Chelmsford City Council](#chelmsford-city-council) -- [Cheltenham Borough Council](#cheltenham-borough-council) -- [Cherwell District Council](#cherwell-district-council) -- [Cheshire East Council](#cheshire-east-council) -- [Cheshire West and Chester Council](#cheshire-west-and-chester-council) -- [Chesterfield Borough Council](#chesterfield-borough-council) -- [Chichester District Council](#chichester-district-council) -- [Chorley Council](#chorley-council) -- [Colchester City Council](#colchester-city-council) -- [Conwy County Borough Council](#conwy-county-borough-council) -- [Copeland Borough Council](#copeland-borough-council) -- [Cornwall Council](#cornwall-council) -- [Cotswold District Council](#cotswold-district-council) -- [Coventry City Council](#coventry-city-council) -- [Crawley Borough Council](#crawley-borough-council) -- [Croydon Council](#croydon-council) -- [Cumberland Council - Allerdale District](#cumberland-council---allerdale-district) -- [Cumberland Borough Council](#cumberland-borough-council) -- [Dacorum Borough Council](#dacorum-borough-council) -- [Dartford Borough Council](#dartford-borough-council) -- [Denbighshire Council](#denbighshire-council) -- [Derby City Council](#derby-city-council) -- [Derbyshire Dales District Council](#derbyshire-dales-district-council) -- [Doncaster Council](#doncaster-council) +- [Aberdeen City](#aberdeen-city) +- [Aberdeenshire](#aberdeenshire) +- [Adur](#adur) +- [Amber Valley](#amber-valley) +- [Angus](#angus) +- [Antrim and Newtownabbey](#antrim-and-newtownabbey) +- [Ards and North Down](#ards-and-north-down) +- [Argyll and Bute](#argyll-and-bute) +- [Armagh City, Banbridge and Craigavon](#armagh-city,-banbridge-and-craigavon) +- [Arun](#arun) +- [Ashfield](#ashfield) +- [Ashford](#ashford) +- [Bournemouth, Christchurch and Poole](#bournemouth,-christchurch-and-poole) +- [Babergh](#babergh) +- [Barking and Dagenham](#barking-and-dagenham) +- [Barnet](#barnet) +- [Barnsley](#barnsley) +- [Basildon](#basildon) +- [Basingstoke and Deane](#basingstoke-and-deane) +- [Bath and North East Somerset](#bath-and-north-east-somerset) +- [Bedford](#bedford) +- [Central Bedfordshire](#central-bedfordshire) +- [Belfast](#belfast) +- [Bexley](#bexley) +- [Birmingham](#birmingham) +- [Blaby](#blaby) +- [Blackburn with Darwen](#blackburn-with-darwen) +- [Blaenau Gwent](#blaenau-gwent) +- [Bolsover](#bolsover) +- [Bolton](#bolton) +- [Boston](#boston) +- [Bracknell Forest](#bracknell-forest) +- [Bradford](#bradford) +- [Braintree](#braintree) +- [Breckland](#breckland) +- [Brent](#brent) +- [Brighton and Hove](#brighton-and-hove) +- [City of Bristol](#city-of-bristol) +- [Broadland](#broadland) +- [Bromley](#bromley) +- [Bromsgrove](#bromsgrove) +- [Broxbourne](#broxbourne) +- [Broxtowe](#broxtowe) +- [Buckinghamshire](#buckinghamshire) +- [Burnley](#burnley) +- [Bury](#bury) +- [Calderdale](#calderdale) +- [Cambridge](#cambridge) +- [Cannock Chase](#cannock-chase) +- [Canterbury](#canterbury) +- [Cardiff](#cardiff) +- [Carmarthenshire](#carmarthenshire) +- [Castle Point](#castle-point) +- [Ceredigion](#ceredigion) +- [Charnwood](#charnwood) +- [Chelmsford](#chelmsford) +- [Cheltenham](#cheltenham) +- [Cherwell](#cherwell) +- [Cheshire East](#cheshire-east) +- [Cheshire West and Chester](#cheshire-west-and-chester) +- [Chesterfield](#chesterfield) +- [Chichester](#chichester) +- [Chorley](#chorley) +- [Colchester](#colchester) +- [Conwy](#conwy) +- [Copeland](#copeland) +- [Cornwall](#cornwall) +- [Cotswold](#cotswold) +- [Coventry](#coventry) +- [Crawley](#crawley) +- [Croydon](#croydon) +- [Cumberland](#cumberland) +- [Cumberland](#cumberland) +- [Dacorum](#dacorum) +- [Darlington Borough Council](#darlington-borough-council) +- [Dartford](#dartford) +- [Denbighshire](#denbighshire) +- [Derby](#derby) +- [Derbyshire Dales](#derbyshire-dales) +- [Doncaster](#doncaster) - [Dorset Council](#dorset-council) -- [Dover District Council](#dover-district-council) -- [Dudley Council](#dudley-council) -- [Dundee City Council](#dundee-city-council) -- [Durham Council](#durham-council) -- [Ealing Council](#ealing-council) -- [East Ayrshire Council](#east-ayrshire-council) -- [East Cambridgeshire Council](#east-cambridgeshire-council) -- [East Devon District Council](#east-devon-district-council) +- [Dover](#dover) +- [Dudley](#dudley) +- [Dundee City](#dundee-city) +- [County Durham](#county-durham) +- [Ealing](#ealing) +- [East Ayrshire](#east-ayrshire) +- [Eastbourne](#eastbourne) +- [East Cambridgeshire](#east-cambridgeshire) +- [East Devon](#east-devon) - [East Herts Council](#east-herts-council) -- [East Lindsey District Council](#east-lindsey-district-council) -- [East Lothian Council](#east-lothian-council) -- [East Renfrewshire Council](#east-renfrewshire-council) -- [East Riding Council](#east-riding-council) -- [East Staffordshire Borough Council](#east-staffordshire-borough-council) -- [East Suffolk Council](#east-suffolk-council) -- [Eastleigh Borough Council](#eastleigh-borough-council) -- [Edinburgh City Council](#edinburgh-city-council) -- [Elmbridge Borough Council](#elmbridge-borough-council) -- [Enfield Council](#enfield-council) +- [East Lindsey](#east-lindsey) +- [East Lothian](#east-lothian) +- [East Renfrewshire](#east-renfrewshire) +- [East Riding of Yorkshire](#east-riding-of-yorkshire) +- [East Staffordshire](#east-staffordshire) +- [East Suffolk](#east-suffolk) +- [Eastleigh](#eastleigh) +- [City of Edinburgh](#city-of-edinburgh) +- [Elmbridge](#elmbridge) +- [Enfield](#enfield) - [Environment First](#environment-first) -- [Epping Forest District Council](#epping-forest-district-council) -- [Epsom and Ewell Borough Council](#epsom-and-ewell-borough-council) -- [Erewash Borough Council](#erewash-borough-council) -- [Exeter City Council](#exeter-city-council) -- [Falkirk Council](#falkirk-council) -- [Fareham Borough Council](#fareham-borough-council) -- [Fenland District Council](#fenland-district-council) -- [Fife Council](#fife-council) -- [Flintshire County Council](#flintshire-county-council) -- [Folkstone and Hythe District Council](#folkstone-and-hythe-district-council) -- [Forest of Dean District Council](#forest-of-dean-district-council) -- [Fylde Council](#fylde-council) -- [Gateshead Council](#gateshead-council) -- [Gedling Borough Council](#gedling-borough-council) -- [Glasgow City Council](#glasgow-city-council) -- [Gloucester City Council](#gloucester-city-council) -- [Gravesham Borough Council](#gravesham-borough-council) -- [Guildford Council](#guildford-council) -- [Gwynedd Council](#gwynedd-council) -- [Hackney Council](#hackney-council) -- [Halton Borough Council](#halton-borough-council) -- [Harborough District Council](#harborough-district-council) -- [Haringey Council](#haringey-council) -- [Harrogate Borough Council](#harrogate-borough-council) -- [Hart District Council](#hart-district-council) -- [Hartlepool Borough Council](#hartlepool-borough-council) -- [Hastings Borough Council](#hastings-borough-council) -- [Herefordshire Council](#herefordshire-council) -- [Hertsmere Borough Council](#hertsmere-borough-council) -- [High Peak Council](#high-peak-council) -- [Highland Council](#highland-council) -- [Hinckley and Bosworth Borough Council](#hinckley-and-bosworth-borough-council) -- [Hounslow Council](#hounslow-council) -- [Hull City Council](#hull-city-council) -- [Huntingdon District Council](#huntingdon-district-council) -- [Ipswich Borough Council](#ipswich-borough-council) -- [Islington Council](#islington-council) -- [Kings Lynn and West Norfolk Borough Council](#kings-lynn-and-west-norfolk-borough-council) -- [Kingston Upon Thames Council](#kingston-upon-thames-council) -- [Kirklees Council](#kirklees-council) -- [Knowsley Metropolitan Borough Council](#knowsley-metropolitan-borough-council) -- [Lancaster City Council](#lancaster-city-council) -- [Leeds City Council](#leeds-city-council) -- [Leicester City Council](#leicester-city-council) -- [Lichfield District Council](#lichfield-district-council) -- [Lincoln Council](#lincoln-council) -- [Lisburn and Castlereagh City Council](#lisburn-and-castlereagh-city-council) -- [Liverpool City Council](#liverpool-city-council) -- [London Borough Ealing](#london-borough-ealing) -- [London Borough Harrow](#london-borough-harrow) -- [London Borough Havering](#london-borough-havering) -- [London Borough Hounslow](#london-borough-hounslow) -- [London Borough Lambeth](#london-borough-lambeth) -- [London Borough Lewisham](#london-borough-lewisham) -- [London Borough Of Richmond Upon Thames](#london-borough-of-richmond-upon-thames) -- [London Borough Redbridge](#london-borough-redbridge) -- [London Borough Sutton](#london-borough-sutton) -- [Luton Borough Council](#luton-borough-council) -- [Maldon District Council](#maldon-district-council) -- [Malvern Hills District Council](#malvern-hills-district-council) -- [Manchester City Council](#manchester-city-council) -- [Mansfield District Council](#mansfield-district-council) -- [MedwayCouncil](#medwaycouncil) -- [Merton Council](#merton-council) -- [Mid and East Antrim Borough Council](#mid-and-east-antrim-borough-council) -- [Mid Devon Council](#mid-devon-council) -- [Middlesbrough Council](#middlesbrough-council) -- [Mid Suffolk District Council](#mid-suffolk-district-council) -- [Mid Sussex District Council](#mid-sussex-district-council) -- [Midlothian Council](#midlothian-council) -- [Milton Keynes City Council](#milton-keynes-city-council) -- [Mole Valley District Council](#mole-valley-district-council) -- [Monmouthshire County Council](#monmouthshire-county-council) -- [Moray Council](#moray-council) -- [Neath Port Talbot Council](#neath-port-talbot-council) -- [New Forest Council](#new-forest-council) -- [Newark and Sherwood District Council](#newark-and-sherwood-district-council) -- [Newcastle City Council](#newcastle-city-council) -- [Newcastle Under Lyme Council](#newcastle-under-lyme-council) -- [Newham Council](#newham-council) -- [Newport City Council](#newport-city-council) -- [North Ayrshire Council](#north-ayrshire-council) -- [North East Derbyshire District Council](#north-east-derbyshire-district-council) -- [North East Lincolnshire Council](#north-east-lincolnshire-council) -- [North Hertfordshire District Council](#north-hertfordshire-district-council) -- [North Kesteven District Council](#north-kesteven-district-council) -- [North Lanarkshire Council](#north-lanarkshire-council) -- [North Lincolnshire Council](#north-lincolnshire-council) -- [North Norfolk District Council](#north-norfolk-district-council) -- [North Northamptonshire Council](#north-northamptonshire-council) -- [North Somerset Council](#north-somerset-council) -- [North Tyneside Council](#north-tyneside-council) -- [North West Leicestershire Council](#north-west-leicestershire-council) -- [North Yorkshire Council](#north-yorkshire-council) -- [Northumberland Council](#northumberland-council) -- [Norwich City Council](#norwich-city-council) -- [Nottingham City Council](#nottingham-city-council) -- [Nuneaton and Bedworth Borough Council](#nuneaton-and-bedworth-borough-council) -- [Oadby & Wigston Borough Council](#oadby-&-wigston-borough-council) -- [Oldham Council](#oldham-council) -- [Oxford City Council](#oxford-city-council) -- [Perth and Kinross Council](#perth-and-kinross-council) -- [Plymouth Council](#plymouth-council) -- [Portsmouth City Council](#portsmouth-city-council) -- [Powys Council](#powys-council) -- [Preston City Council](#preston-city-council) -- [Reading Borough Council](#reading-borough-council) -- [Redcar and Cleveland Council](#redcar-and-cleveland-council) -- [Redditch Borough Council](#redditch-borough-council) -- [Reigate and Banstead Borough Council](#reigate-and-banstead-borough-council) -- [Renfrewshire Council](#renfrewshire-council) -- [Rhondda Cynon Taff Council](#rhondda-cynon-taff-council) -- [Rochdale Council](#rochdale-council) -- [Rochford Council](#rochford-council) -- [Rother District Council](#rother-district-council) -- [Rotherham Council](#rotherham-council) -- [Royal Borough of Greenwich](#royal-borough-of-greenwich) -- [Rugby Borough Council](#rugby-borough-council) -- [Runnymede Borough Council](#runnymede-borough-council) -- [Rushcliffe Borough Council](#rushcliffe-borough-council) -- [Rushmoor Council](#rushmoor-council) -- [Salford City Council](#salford-city-council) -- [Sandwell Borough Council](#sandwell-borough-council) -- [Sefton Council](#sefton-council) -- [Sevenoaks District Council](#sevenoaks-district-council) -- [Sheffield City Council](#sheffield-city-council) -- [Shropshire Council](#shropshire-council) -- [Solihull Council](#solihull-council) -- [Somerset Council](#somerset-council) -- [Southampton City Council](#southampton-city-council) -- [South Ayrshire Council](#south-ayrshire-council) -- [South Cambridgeshire Council](#south-cambridgeshire-council) -- [South Derbyshire District Council](#south-derbyshire-district-council) -- [South Gloucestershire Council](#south-gloucestershire-council) -- [South Hams District Council](#south-hams-district-council) -- [South Kesteven District Council](#south-kesteven-district-council) -- [South Lanarkshire Council](#south-lanarkshire-council) -- [South Norfolk Council](#south-norfolk-council) -- [South Oxfordshire Council](#south-oxfordshire-council) -- [South Ribble Council](#south-ribble-council) -- [South Staffordshire District Council](#south-staffordshire-district-council) -- [South Tyneside Council](#south-tyneside-council) -- [Southwark Council](#southwark-council) -- [Spelthorne Borough Council](#spelthorne-borough-council) -- [St Albans City and District Council](#st-albans-city-and-district-council) -- [St Helens Borough Council](#st-helens-borough-council) -- [Stafford Borough Council](#stafford-borough-council) -- [Staffordshire Moorlands District Council](#staffordshire-moorlands-district-council) -- [Stevenage Borough Council](#stevenage-borough-council) -- [Stockport Borough Council](#stockport-borough-council) -- [Stockton On Tees Council](#stockton-on-tees-council) -- [Stoke-on-Trent City Council](#stoke-on-trent-city-council) -- [Stratford Upon Avon Council](#stratford-upon-avon-council) -- [Stroud District Council](#stroud-district-council) -- [Sunderland City Council](#sunderland-city-council) -- [Surrey Heath Borough Council / Joint Waste Solutions](#surrey-heath-borough-council-/-joint-waste-solutions) -- [Swale Borough Council](#swale-borough-council) -- [Swansea Council](#swansea-council) -- [Swindon Borough Council](#swindon-borough-council) -- [Tameside Metropolitan Borough Council](#tameside-metropolitan-borough-council) -- [Tandridge District Council](#tandridge-district-council) -- [Teignbridge Council](#teignbridge-council) -- [Telford and Wrekin Council](#telford-and-wrekin-council) -- [Tendring District Council](#tendring-district-council) -- [Test Valley Borough Council](#test-valley-borough-council) -- [Thanet District Council](#thanet-district-council) -- [Three Rivers District Council](#three-rivers-district-council) -- [Thurrock Council](#thurrock-council) -- [Tonbridge and Malling Borough Council](#tonbridge-and-malling-borough-council) -- [Torbay Council](#torbay-council) -- [Torridge District Council](#torridge-district-council) -- [Tunbridge Wells Council](#tunbridge-wells-council) -- [Uttlesford District Council](#uttlesford-district-council) -- [Vale of Glamorgan Council](#vale-of-glamorgan-council) -- [Vale of White Horse Council](#vale-of-white-horse-council) -- [Wakefield City Council](#wakefield-city-council) -- [Walsall Council](#walsall-council) +- [Epping Forest](#epping-forest) +- [Epsom and Ewell](#epsom-and-ewell) +- [Erewash](#erewash) +- [Exeter](#exeter) +- [Falkirk](#falkirk) +- [Fareham](#fareham) +- [Fenland](#fenland) +- [Fermanagh and Omagh](#fermanagh-and-omagh) +- [Fife](#fife) +- [Flintshire](#flintshire) +- [Folkestone and Hythe](#folkestone-and-hythe) +- [Forest of Dean](#forest-of-dean) +- [Fylde](#fylde) +- [Gateshead](#gateshead) +- [Gedling](#gedling) +- [Glasgow City](#glasgow-city) +- [Gloucester](#gloucester) +- [Google Calendar (Public)](#google-calendar-(public)) +- [Gravesham](#gravesham) +- [Great Yarmouth](#great-yarmouth) +- [Guildford](#guildford) +- [Gwynedd](#gwynedd) +- [Hackney](#hackney) +- [Halton](#halton) +- [Harborough](#harborough) +- [Haringey](#haringey) +- [Harrogate](#harrogate) +- [Hart](#hart) +- [Hartlepool](#hartlepool) +- [Hastings](#hastings) +- [Herefordshire](#herefordshire) +- [Hertsmere](#hertsmere) +- [High Peak](#high-peak) +- [Highland](#highland) +- [Hillingdon](#hillingdon) +- [Hinckley and Bosworth](#hinckley-and-bosworth) +- [Horsham](#horsham) +- [Kingston upon Hull](#kingston-upon-hull) +- [Huntingdonshire](#huntingdonshire) +- [Hyndburn](#hyndburn) +- [Ipswich](#ipswich) +- [Islington](#islington) +- [Kings Lynn and West Norfolk](#kings-lynn-and-west-norfolk) +- [Kingston upon Thames](#kingston-upon-thames) +- [Kirklees](#kirklees) +- [Knowsley](#knowsley) +- [Lancaster](#lancaster) +- [Leeds](#leeds) +- [Leicester](#leicester) +- [Lewes](#lewes) +- [Lichfield](#lichfield) +- [City of Lincoln](#city-of-lincoln) +- [Lisburn and Castlereagh](#lisburn-and-castlereagh) +- [Liverpool](#liverpool) +- [Ealing](#ealing) +- [Harrow](#harrow) +- [Havering](#havering) +- [Hounslow](#hounslow) +- [Lambeth](#lambeth) +- [Lewisham](#lewisham) +- [Richmond upon Thames](#richmond-upon-thames) +- [Redbridge](#redbridge) +- [Sutton](#sutton) +- [Luton](#luton) +- [Maidstone](#maidstone) +- [Maldon](#maldon) +- [Malvern Hills](#malvern-hills) +- [Manchester](#manchester) +- [Mansfield](#mansfield) +- [Medway](#medway) +- [Melton](#melton) +- [Merton](#merton) +- [Mid and East Antrim](#mid-and-east-antrim) +- [Mid Devon](#mid-devon) +- [Mid Suffolk](#mid-suffolk) +- [Mid Sussex](#mid-sussex) +- [Middlesbrough](#middlesbrough) +- [Midlothian](#midlothian) +- [Mid Ulster](#mid-ulster) +- [Milton Keynes](#milton-keynes) +- [Mole Valley](#mole-valley) +- [Monmouthshire](#monmouthshire) +- [Moray](#moray) +- [Neath Port Talbot](#neath-port-talbot) +- [New Forest](#new-forest) +- [Newark and Sherwood](#newark-and-sherwood) +- [Newcastle upon Tyne](#newcastle-upon-tyne) +- [Newcastle-under-Lyme](#newcastle-under-lyme) +- [Newham](#newham) +- [Newport](#newport) +- [North Ayrshire](#north-ayrshire) +- [North Devon](#north-devon) +- [North East Derbyshire](#north-east-derbyshire) +- [North East Lincolnshire](#north-east-lincolnshire) +- [North Hertfordshire](#north-hertfordshire) +- [North Kesteven](#north-kesteven) +- [North Lanarkshire](#north-lanarkshire) +- [North Lincolnshire](#north-lincolnshire) +- [North Norfolk](#north-norfolk) +- [North Northamptonshire](#north-northamptonshire) +- [North Somerset](#north-somerset) +- [North Tyneside](#north-tyneside) +- [North West Leicestershire](#north-west-leicestershire) +- [North Yorkshire](#north-yorkshire) +- [Northumberland](#northumberland) +- [Norwich](#norwich) +- [Nottingham](#nottingham) +- [Nuneaton and Bedworth](#nuneaton-and-bedworth) +- [Oadby and Wigston](#oadby-and-wigston) +- [Oldham](#oldham) +- [Oxford](#oxford) +- [Pembrokeshire](#pembrokeshire) +- [Peterborough](#peterborough) +- [Perth and Kinross](#perth-and-kinross) +- [Plymouth](#plymouth) +- [Portsmouth](#portsmouth) +- [Powys](#powys) +- [Preston](#preston) +- [Reading](#reading) +- [Redcar and Cleveland](#redcar-and-cleveland) +- [Redditch](#redditch) +- [Reigate and Banstead](#reigate-and-banstead) +- [Renfrewshire](#renfrewshire) +- [Rhondda Cynon Taff](#rhondda-cynon-taff) +- [Rochdale](#rochdale) +- [Rochford](#rochford) +- [Rother](#rother) +- [Rotherham](#rotherham) +- [Greenwich](#greenwich) +- [Rugby](#rugby) +- [Runnymede](#runnymede) +- [Rushcliffe](#rushcliffe) +- [Rushmoor](#rushmoor) +- [Salford](#salford) +- [Sandwell](#sandwell) +- [Sefton](#sefton) +- [Sevenoaks](#sevenoaks) +- [Sheffield](#sheffield) +- [Shropshire](#shropshire) +- [Slough](#slough) +- [Solihull](#solihull) +- [Somerset](#somerset) +- [South Ayrshire](#south-ayrshire) +- [South Cambridgeshire](#south-cambridgeshire) +- [South Derbyshire](#south-derbyshire) +- [South Gloucestershire](#south-gloucestershire) +- [South Hams](#south-hams) +- [South Holland](#south-holland) +- [South Kesteven](#south-kesteven) +- [South Lanarkshire](#south-lanarkshire) +- [South Norfolk](#south-norfolk) +- [South Oxfordshire](#south-oxfordshire) +- [South Ribble](#south-ribble) +- [South Staffordshire](#south-staffordshire) +- [South Tyneside](#south-tyneside) +- [Southampton](#southampton) +- [Southwark](#southwark) +- [Spelthorne](#spelthorne) +- [St Albans](#st-albans) +- [St. Helens](#st.-helens) +- [Stafford](#stafford) +- [Staffordshire Moorlands](#staffordshire-moorlands) +- [Stevenage](#stevenage) +- [Stirling](#stirling) +- [Stockport](#stockport) +- [Stockton-on-Tees](#stockton-on-tees) +- [Stoke-on-Trent](#stoke-on-trent) +- [Stratford-on-Avon](#stratford-on-avon) +- [Stroud](#stroud) +- [Sunderland](#sunderland) +- [Surrey Heath](#surrey-heath) +- [Swale](#swale) +- [Swansea](#swansea) +- [Swindon](#swindon) +- [Tameside](#tameside) +- [Tandridge](#tandridge) +- [Teignbridge](#teignbridge) +- [Telford and Wrekin](#telford-and-wrekin) +- [Tewkesbury](#tewkesbury) +- [Tendring](#tendring) +- [Test Valley](#test-valley) +- [Thanet](#thanet) +- [Three Rivers](#three-rivers) +- [Thurrock](#thurrock) +- [Tonbridge and Malling](#tonbridge-and-malling) +- [Torbay](#torbay) +- [Torridge](#torridge) +- [Tunbridge Wells](#tunbridge-wells) +- [Uttlesford](#uttlesford) +- [The Vale of Glamorgan](#the-vale-of-glamorgan) +- [Vale of White Horse](#vale-of-white-horse) +- [Wakefield](#wakefield) +- [Walsall](#walsall) - [Waltham Forest](#waltham-forest) -- [Wandsworth Council](#wandsworth-council) -- [Warrington Borough Council](#warrington-borough-council) -- [Warwick District Council](#warwick-district-council) -- [Watford Borough Council](#watford-borough-council) -- [Waverley Borough Council](#waverley-borough-council) -- [Wealden District Council](#wealden-district-council) -- [Welhat Council](#welhat-council) -- [West Berkshire Council](#west-berkshire-council) -- [West Dunbartonshire Council](#west-dunbartonshire-council) -- [West Lancashire Borough Council](#west-lancashire-borough-council) -- [West Lindsey District Council](#west-lindsey-district-council) -- [West Lothian Council](#west-lothian-council) -- [West Morland and Furness Council](#west-morland-and-furness-council) -- [West Northamptonshire Council](#west-northamptonshire-council) -- [West Oxfordshire District Council](#west-oxfordshire-district-council) -- [West Suffolk Council](#west-suffolk-council) -- [Wigan Borough Council](#wigan-borough-council) -- [Wiltshire Council](#wiltshire-council) -- [Winchester City Council](#winchester-city-council) -- [Windsor and Maidenhead Council](#windsor-and-maidenhead-council) -- [Wirral Council](#wirral-council) -- [Woking Borough Council / Joint Waste Solutions](#woking-borough-council-/-joint-waste-solutions) -- [Wokingham Borough Council](#wokingham-borough-council) -- [Wolverhampton City Council](#wolverhampton-city-council) -- [Worcester City Council](#worcester-city-council) -- [Wychavon District Council](#wychavon-district-council) -- [Wyre Council](#wyre-council) -- [Wyre Forest District Council](#wyre-forest-district-council) -- [York Council](#york-council) - ---- - -### Aberdeen City Council +- [Wandsworth](#wandsworth) +- [Warrington](#warrington) +- [Warwick](#warwick) +- [Watford](#watford) +- [Waverley](#waverley) +- [Wealden](#wealden) +- [Welwyn Hatfield](#welwyn-hatfield) +- [West Berkshire](#west-berkshire) +- [West Dunbartonshire](#west-dunbartonshire) +- [West Lancashire](#west-lancashire) +- [West Lindsey](#west-lindsey) +- [West Lothian](#west-lothian) +- [Westmorland and Furness](#westmorland-and-furness) +- [West Northamptonshire](#west-northamptonshire) +- [West Oxfordshire](#west-oxfordshire) +- [West Suffolk](#west-suffolk) +- [Wigan](#wigan) +- [Wiltshire](#wiltshire) +- [Winchester](#winchester) +- [Windsor and Maidenhead](#windsor-and-maidenhead) +- [Wirral](#wirral) +- [Woking](#woking) +- [Wokingham](#wokingham) +- [Wolverhampton](#wolverhampton) +- [Worcester](#worcester) +- [Wrexham](#wrexham) +- [Wychavon](#wychavon) +- [Wyre](#wyre) +- [Wyre Forest](#wyre-forest) +- [York](#york) + +--- + +### Aberdeen City ```commandline python collect_data.py AberdeenCityCouncil https://www.aberdeencity.gov.uk -u XXXXXXXX ``` @@ -328,7 +350,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Aberdeenshire Council +### Aberdeenshire ```commandline python collect_data.py AberdeenshireCouncil https://online.aberdeenshire.gov.uk -u XXXXXXXX ``` @@ -339,7 +361,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Adur and Worthing Councils +### Adur ```commandline python collect_data.py AdurAndWorthingCouncils https://www.adur-worthing.gov.uk/bin-day/?brlu-selected-address=XXXXXXXX ``` @@ -348,7 +370,7 @@ Note: Replace XXXXXXXX with your UPRN. You will need to use [FindMyAddress](http --- -### Amber Valley Borough Council +### Amber Valley ```commandline python collect_data.py AmberValleyBoroughCouncil https://ambervalley.gov.uk -u XXXXXXXX ``` @@ -359,7 +381,21 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Antrim & Newtonabbey Council +### Angus +```commandline +python collect_data.py AngusCouncil https://www.angus.gov.uk/bins_litter_and_recycling/bin_collection_days -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ +``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN +- `-p` - postcode +- `-w` - remote Selenium web driver URL (required for Home Assistant) + +Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. Requires Selenium + +--- + +### Antrim and Newtownabbey ```commandline python collect_data.py AntrimAndNewtonabbeyCouncil https://antrimandnewtownabbey.gov.uk/residents/bins-recycling/bins-schedule/?Id=XXXX ``` @@ -368,7 +404,7 @@ Note: Navigate to [https://antrimandnewtownabbey.gov.uk/residents/bins-recycling --- -### Ards and North Down Council +### Ards and North Down ```commandline python collect_data.py ArdsAndNorthDownCouncil https://www.ardsandnorthdown.gov.uk -u XXXXXXXX ``` @@ -379,19 +415,21 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Argyll and Bute Council +### Argyll and Bute ```commandline -python collect_data.py ArgyllandButeCouncil https://www.argyll-bute.gov.uk -s -u XXXXXXXX +python collect_data.py ArgyllandButeCouncil https://www.argyll-bute.gov.uk/rubbish-and-recycling/household-waste/bin-collection -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` Additional parameters: - `-s` - skip get URL - `-u` - UPRN +- `-p` - postcode +- `-w` - remote Selenium web driver URL (required for Home Assistant) Note: Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search). --- -### Armagh Banbridge Craigavon Council +### Armagh City, Banbridge and Craigavon ```commandline python collect_data.py ArmaghBanbridgeCraigavonCouncil https://www.armaghbanbridgecraigavon.gov.uk/ -u XXXXXXXX ``` @@ -402,7 +440,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Arun Council +### Arun ```commandline python collect_data.py ArunCouncil https://www1.arun.gov.uk/when-are-my-bins-collected -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -416,7 +454,7 @@ Note: Pass the house name/number and postcode in their respective parameters, bo --- -### Ashfield District Council +### Ashfield ```commandline python collect_data.py AshfieldDistrictCouncil https://www.ashfield.gov.uk -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -429,7 +467,7 @@ Note: Pass the house name/number and postcode in their respective parameters, bo --- -### Ashford Borough Council +### Ashford ```commandline python collect_data.py AshfordBoroughCouncil https://ashford.gov.uk -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -442,45 +480,50 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Aylesbury Vale Council (Buckinghamshire) +### Bournemouth, Christchurch and Poole ```commandline -python collect_data.py AylesburyValeCouncil http://avdcbins.web-labs.co.uk/RefuseApi.asmx -s -u XXXXXXXX +python collect_data.py BCPCouncil https://online.bcpcouncil.gov.uk/bindaylookup/ -s -u XXXXXXXX -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` Additional parameters: - `-s` - skip get URL - `-u` - UPRN +- `-p` - postcode +- `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: To get the UPRN, please use [FindMyAddress](https://www.findmyaddress.co.uk/search). Returns all published collections in the past, present, future. +Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. --- -### BCP Council +### Babergh ```commandline -python collect_data.py BCPCouncil https://online.bcpcouncil.gov.uk/bindaylookup/ -s -u XXXXXXXX +python collect_data.py BaberghDistrictCouncil https://www.babergh.gov.uk -s -u XXXXXXXX -p "XXXX XXX" -n XX ``` Additional parameters: - `-s` - skip get URL - `-u` - UPRN +- `-p` - postcode +- `-n` - house number -Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. +Note: Use the House Number field to pass the DAY of the week for your NORMAL collections. [Monday/Tuesday/Wednesday/Thursday/Friday]. [OPTIONAL] Use the 'postcode' field to pass the WEEK for your garden collection. [Week 1/Week 2]. [OPTIONAL] Use the 'uprn' field to pass the DAY for your garden collection. [Monday/Tuesday/Wednesday/Thursday/Friday] --- -### Babergh District Council +### Barking and Dagenham ```commandline -python collect_data.py BaberghDistrictCouncil https://www.babergh.gov.uk -s -u XXXXXXXX -p "XXXX XXX" -n XX +python collect_data.py BarkingDagenham https://www.lbbd.gov.uk/rubbish-recycling/household-bin-collection/check-your-bin-collection-days -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` Additional parameters: - `-s` - skip get URL -- `-u` - UPRN - `-p` - postcode - `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: Use the House Number field to pass the DAY of the week for your NORMAL collections. [Monday/Tuesday/Wednesday/Thursday/Friday]. [OPTIONAL] Use the 'postcode' field to pass the WEEK for your garden collection. [Week 1/Week 2]. [OPTIONAL] Use the 'uprn' field to pass the DAY for your garden collection. [Monday/Tuesday/Wednesday/Thursday/Friday] +Note: Use house number and postcode. Requires Selenium. --- -### Barnet Council +### Barnet ```commandline python collect_data.py BarnetCouncil https://www.barnet.gov.uk/recycling-and-waste/bin-collections/find-your-bin-collection-day -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -494,7 +537,7 @@ Note: Follow the instructions [here](https://www.barnet.gov.uk/recycling-and-was --- -### Barnsley Metropolitan Borough Council +### Barnsley ```commandline python collect_data.py BarnsleyMBCouncil https://waste.barnsley.gov.uk/ViewCollection/Collections -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -507,9 +550,9 @@ Note: To get the UPRN, you will need to use [FindMyAddress](https://www.findmyad --- -### Basildon Council +### Basildon ```commandline -python collect_data.py BasildonCouncil https://basildonportal.azurewebsites.net/api/getPropertyRefuseInformation -s -u XXXXXXXX +python collect_data.py BasildonCouncil https://mybasildon.powerappsportals.com/check/where_i_live/ -s -u XXXXXXXX ``` Additional parameters: - `-s` - skip get URL @@ -519,7 +562,7 @@ Note: To get the UPRN, you will need to use [FindMyAddress](https://www.findmyad --- -### Basingstoke Council +### Basingstoke and Deane ```commandline python collect_data.py BasingstokeCouncil https://www.basingstoke.gov.uk/bincollection -s -u XXXXXXXX ``` @@ -531,7 +574,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Bath and North East Somerset Council +### Bath and North East Somerset ```commandline python collect_data.py BathAndNorthEastSomersetCouncil https://www.bathnes.gov.uk/webforms/waste/collectionday/ -s -u XXXXXXXX ``` @@ -543,7 +586,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Bedford Borough Council +### Bedford ```commandline python collect_data.py BedfordBoroughCouncil https://www.bedford.gov.uk/bins-and-recycling/household-bins-and-recycling/check-your-bin-day -s -u XXXXXXXX ``` @@ -555,7 +598,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Bedfordshire Council +### Central Bedfordshire ```commandline python collect_data.py BedfordshireCouncil https://www.centralbedfordshire.gov.uk/info/163/bins_and_waste_collections_-_check_bin_collection_day -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -568,7 +611,7 @@ Note: In order to use this parser, you must provide a valid postcode and a UPRN --- -### Belfast City Council +### Belfast ```commandline python collect_data.py BelfastCityCouncil https://online.belfastcity.gov.uk/find-bin-collection-day/Default.aspx -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -581,21 +624,19 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Bexley Council +### Bexley ```commandline -python collect_data.py BexleyCouncil https://waste.bexley.gov.uk/waste -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ +python collect_data.py BexleyCouncil https://waste.bexley.gov.uk/waste -s -u XXXXXXXX ``` Additional parameters: - `-s` - skip get URL -- `-p` - postcode -- `-n` - house number -- `-w` - remote Selenium web driver URL (required for Home Assistant) +- `-u` - UPRN -Note: In order to use this parser, you will need to sign up to [Bexley's @Home app](https://www.bexley.gov.uk/services/rubbish-and-recycling/bexley-home-recycling-app/about-app). Complete the setup by entering your email and setting your address with postcode and address line. Once you can see the calendar, you should be good to run the parser. Just pass the email you used in quotes in the UPRN parameter. +Note: Provide your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to locate it. --- -### Birmingham City Council +### Birmingham ```commandline python collect_data.py BirminghamCityCouncil https://www.birmingham.gov.uk/xfp/form/619 -u XXXXXXXX -p "XXXX XXX" ``` @@ -607,7 +648,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Blaby District Council +### Blaby ```commandline python collect_data.py BlabyDistrictCouncil https://www.blaby.gov.uk -u XXXXXXXX ``` @@ -618,20 +659,19 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Blackburn Council +### Blackburn with Darwen ```commandline -python collect_data.py BlackburnCouncil https://www.blackburn.gov.uk -s -u XXXXXXXX -w http://HOST:PORT/ +python collect_data.py BlackburnCouncil https://www.blaby.gov.uk -s -u XXXXXXXX ``` Additional parameters: - `-s` - skip get URL - `-u` - UPRN -- `-w` - remote Selenium web driver URL (required for Home Assistant) Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. --- -### Blaenau Gwent County Borough Council +### Blaenau Gwent ```commandline python collect_data.py BlaenauGwentCountyBoroughCouncil https://www.blaenau-gwent.gov.uk -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -645,7 +685,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Bolsover Council +### Bolsover ```commandline python collect_data.py BolsoverCouncil https://bolsover.gov.uk -u XXXXXXXX ``` @@ -656,7 +696,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Bolton Council +### Bolton ```commandline python collect_data.py BoltonCouncil https://web.bolton.gov.uk/bins.aspx -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -670,7 +710,7 @@ Note: To get the UPRN, you will need to use [FindMyAddress](https://www.findmyad --- -### Boston Borough Council +### Boston ```commandline python collect_data.py BostonBoroughCouncil https://www.boston.gov.uk/findwastecollections -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -684,7 +724,7 @@ Note: Provide your house number in the `house_number` parameter and postcode in --- -### Bracknell Forest Council +### Bracknell Forest ```commandline python collect_data.py BracknellForestCouncil https://selfservice.mybfc.bracknell-forest.gov.uk/w/webpage/waste-collection-days -s -p "XXXX XXX" -n XX ``` @@ -697,7 +737,7 @@ Note: Pass the house number and postcode in their respective parameters. --- -### Bradford MDC +### Bradford ```commandline python collect_data.py BradfordMDC https://onlineforms.bradford.gov.uk/ufs/collectiondates.eb -s -u XXXXXXXX ``` @@ -709,7 +749,7 @@ Note: To get the UPRN, you will need to use [FindMyAddress](https://www.findmyad --- -### Braintree District Council +### Braintree ```commandline python collect_data.py BraintreeDistrictCouncil https://www.braintree.gov.uk/ -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -722,7 +762,7 @@ Note: Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddr --- -### Breckland Council +### Breckland ```commandline python collect_data.py BrecklandCouncil https://www.breckland.gov.uk -u XXXXXXXX ``` @@ -733,7 +773,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Brent Council +### Brent ```commandline python collect_data.py BrentCouncil https://recyclingservices.brent.gov.uk/waste -p "XXXX XXX" -n XX ``` @@ -745,20 +785,21 @@ Note: Pass the house number and postcode in their respective parameters. --- -### Brighton and Hove City Council +### Brighton and Hove ```commandline -python collect_data.py BrightonandHoveCityCouncil https://cityclean.brighton-hove.gov.uk/link/collections -n "XXXXXX XXXX XXXX" -p "XXXX XXX" -w http://HOST:PORT/wd/hub +python collect_data.py BrightonandHoveCityCouncil https://cityclean.brighton-hove.gov.uk/link/collections -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` Additional parameters: -- `-n` - house number +- `-s` - skip get URL - `-p` - postcode +- `-n` - house number - `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: Use the full address as it appears on the drop-down on the site when you search by house number. +Note: Use house number and postcode. Requires Selenium --- -### Bristol City Council +### City of Bristol ```commandline python collect_data.py BristolCityCouncil https://bristolcouncil.powerappsportals.com/completedynamicformunauth/?servicetypeid=7dce896c-b3ba-ea11-a812-000d3a7f1cdc -s -u XXXXXXXX ``` @@ -770,7 +811,21 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Bromley Borough Council +### Broadland +```commandline +python collect_data.py BroadlandDistrictCouncil https://area.southnorfolkandbroadland.gov.uk/FindAddress -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ +``` +Additional parameters: +- `-s` - skip get URL +- `-p` - postcode +- `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) + +Note: Use house number and postcode. Requires Selenium. + +--- + +### Bromley ```commandline python collect_data.py BromleyBoroughCouncil https://recyclingservices.bromley.gov.uk/waste/XXXXXXX -w http://HOST:PORT/ ``` @@ -781,7 +836,7 @@ Note: Follow the instructions [here](https://recyclingservices.bromley.gov.uk/wa --- -### Bromsgrove District Council +### Bromsgrove ```commandline python collect_data.py BromsgroveDistrictCouncil https://www.bromsgrove.gov.uk -u XXXXXXXX ``` @@ -792,19 +847,20 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Broxbourne Council +### Broxbourne ```commandline -python collect_data.py BroxbourneCouncil https://www.broxbourne.gov.uk -u XXXXXXXX -p "XXXX XXX" +python collect_data.py BroxbourneCouncil https://www.broxbourne.gov.uk -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` Additional parameters: - `-u` - UPRN - `-p` - postcode +- `-w` - remote Selenium web driver URL (required for Home Assistant) Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. --- -### Broxtowe Borough Council +### Broxtowe ```commandline python collect_data.py BroxtoweBoroughCouncil https://www.broxtowe.gov.uk/ -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -818,12 +874,12 @@ Note: Pass the UPRN and postcode. To get the UPRN, you can use [FindMyAddress](h --- -### Buckinghamshire Council (Chiltern, South Bucks, Wycombe) +### Buckinghamshire ```commandline -python collect_data.py BuckinghamshireCouncil https://iapp.itouchvision.com/iappcollectionday/collection-day/?uuid=FA353FC74600CBE61BE409534D00A8EC09BDA3AC&lang=en -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ +python collect_data.py BuckinghamshireCouncil https://www.buckinghamshire.gov.uk/waste-and-recycling/find-out-when-its-your-bin-collection/ -u XXXXXXXX -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` Additional parameters: -- `-s` - skip get URL +- `-u` - UPRN - `-p` - postcode - `-n` - house number - `-w` - remote Selenium web driver URL (required for Home Assistant) @@ -832,7 +888,7 @@ Note: Pass the house name/number and postcode in their respective arguments, bot --- -### Burnley Borough Council +### Burnley ```commandline python collect_data.py BurnleyBoroughCouncil https://www.burnley.gov.uk -u XXXXXXXX ``` @@ -843,7 +899,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### Bury Council +### Bury ```commandline python collect_data.py BuryCouncil https://www.bury.gov.uk/waste-and-recycling/bin-collection-days-and-alerts -s -p "XXXX XXX" -n XX ``` @@ -856,7 +912,7 @@ Note: Pass the postcode and house number in their respective arguments, both wra --- -### Calderdale Council +### Calderdale ```commandline python collect_data.py CalderdaleCouncil https://www.calderdale.gov.uk/environment/waste/household-collections/collectiondayfinder.jsp -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -870,7 +926,7 @@ Note: Pass the UPRN and postcode. To get the UPRN, you can use [FindMyAddress](h --- -### Cambridge City Council +### Cambridge ```commandline python collect_data.py CambridgeCityCouncil https://www.cambridge.gov.uk/ -u XXXXXXXX ``` @@ -881,7 +937,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Cannock Chase District Council +### Cannock Chase ```commandline python collect_data.py CannockChaseDistrictCouncil https://www.cannockchasedc.gov.uk/ -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -894,7 +950,7 @@ Note: To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co. --- -### Canterbury City Council +### Canterbury ```commandline python collect_data.py CanterburyCityCouncil https://www.canterbury.gov.uk -u XXXXXXXX ``` @@ -905,7 +961,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Cardiff Council +### Cardiff ```commandline python collect_data.py CardiffCouncil https://www.gov.uk -s -u XXXXXXXX ``` @@ -917,7 +973,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Carmarthenshire County Council +### Carmarthenshire ```commandline python collect_data.py CarmarthenshireCountyCouncil https://www.carmarthenshire.gov.wales -u XXXXXXXX ``` @@ -928,7 +984,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Castlepoint District Council +### Castle Point ```commandline python collect_data.py CastlepointDistrictCouncil https://apps.castlepoint.gov.uk/cpapps/index.cfm?fa=wastecalendar -s -u XXXXXXXX ``` @@ -940,16 +996,32 @@ Note: For this council, 'uprn' is actually a 4-digit code for your street. Go [h --- -### Charnwood Borough Council +### Ceredigion +```commandline +python collect_data.py CeredigionCountyCouncil https://www.ceredigion.gov.uk/resident/bins-recycling/ -p "XXXX XXX" -n XX -w http://HOST:PORT/ +``` +Additional parameters: +- `-p` - postcode +- `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) + +Note: House Number is the full address as it appears on the drop-down on the site when you search by postcode. This parser requires a Selenium webdriver. + +--- + +### Charnwood ```commandline -python collect_data.py CharnwoodBoroughCouncil https://my.charnwood.gov.uk/location?put=cbcXXXXXXXX&rememberme=0&redirect=%2F +python collect_data.py CharnwoodBoroughCouncil https://www.charnwood.gov.uk/pages/waste_collections_calendars -s -u XXXXXXXX ``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN -Note: Replace XXXXXXXX with your UPRN, keeping "cbc" before it. +Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. --- -### Chelmsford City Council +### Chelmsford ```commandline python collect_data.py ChelmsfordCityCouncil https://www.chelmsford.gov.uk/myhome/ -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -962,7 +1034,7 @@ Note: Follow the instructions [here](https://www.chelmsford.gov.uk/myhome/) unti --- -### Cheltenham Borough Council +### Cheltenham ```commandline python collect_data.py CheltenhamBoroughCouncil https://www.cheltenham.gov.uk -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -975,7 +1047,7 @@ Note: Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddr --- -### Cherwell District Council +### Cherwell ```commandline python collect_data.py CherwellDistrictCouncil https://www.cherwell.gov.uk -u XXXXXXXX ``` @@ -986,16 +1058,19 @@ Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your U --- -### Cheshire East Council +### Cheshire East ```commandline -python collect_data.py CheshireEastCouncil https://online.cheshireeast.gov.uk/MyCollectionDay/SearchByAjax/GetBartecJobList?uprn=XXXXXXXX&onelineaddress=XXXXXXXX&_=1689413260149 +python collect_data.py CheshireEastCouncil https://online.cheshireeast.gov.uk/mycollectionday -s -u XXXXXXXX ``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN -Note: Both the UPRN and a one-line address are passed in the URL, which needs to be wrapped in double quotes. The one-line address is made up of the house number, street name, and postcode. Use the form [here](https://online.cheshireeast.gov.uk/mycollectionday/) to find them, then take the first line and postcode and replace all spaces with `%20`. +Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN. --- -### Cheshire West and Chester Council +### Cheshire West and Chester ```commandline python collect_data.py CheshireWestAndChesterCouncil https://my.cheshirewestandchester.gov.uk -s -u XXXXXXXX ``` @@ -1007,7 +1082,7 @@ Note: Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddr --- -### Chesterfield Borough Council +### Chesterfield ```commandline python collect_data.py ChesterfieldBoroughCouncil https://www.chesterfield.gov.uk -s -u XXXXXXXX ``` @@ -1019,7 +1094,7 @@ Note: Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddr --- -### Chichester District Council +### Chichester ```commandline python collect_data.py ChichesterDistrictCouncil https://www.chichester.gov.uk/checkyourbinday -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1033,7 +1108,7 @@ Note: Needs the full address and postcode as it appears on [this page](https://w --- -### Chorley Council +### Chorley ```commandline python collect_data.py ChorleyCouncil https://myaccount.chorley.gov.uk/wastecollections.aspx -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -1043,11 +1118,11 @@ Additional parameters: - `-p` - postcode - `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: Chorley needs to be passed both a Postcode & UPRN in the format of UPRNXXXXXX to work. Find this on [FindMyAddress](https://www.findmyaddress.co.uk/search). +Note: Chorley needs to be passed both a Postcode & UPRN to work. Find this on [FindMyAddress](https://www.findmyaddress.co.uk/search). --- -### Colchester City Council +### Colchester ```commandline python collect_data.py ColchesterCityCouncil https://www.colchester.gov.uk/your-recycling-calendar -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1061,7 +1136,7 @@ Note: Pass the house name/number in the house number parameter, wrapped in doubl --- -### Conwy County Borough Council +### Conwy ```commandline python collect_data.py ConwyCountyBorough https://www.conwy.gov.uk -u XXXXXXXX ``` @@ -1072,7 +1147,7 @@ Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your U --- -### Copeland Borough Council +### Copeland ```commandline python collect_data.py CopelandBoroughCouncil https://www.copeland.gov.uk -u XXXXXXXX ``` @@ -1083,7 +1158,7 @@ Note: *****This has now been replaced by Cumberland Council**** --- -### Cornwall Council +### Cornwall ```commandline python collect_data.py CornwallCouncil https://www.cornwall.gov.uk/my-area/ -s -u XXXXXXXX ``` @@ -1095,7 +1170,7 @@ Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your U --- -### Cotswold District Council +### Cotswold ```commandline python collect_data.py CotswoldDistrictCouncil https://community.cotswold.gov.uk/s/waste-collection-enquiry -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1109,7 +1184,7 @@ Note: Pass the full address in the house number and postcode in --- -### Coventry City Council +### Coventry ```commandline python collect_data.py CoventryCityCouncil https://www.coventry.gov.uk/directory_record/XXXXXX/XXXXXX ``` @@ -1118,7 +1193,7 @@ Note: Follow the instructions [here](https://www.coventry.gov.uk/bin-collection- --- -### Crawley Borough Council +### Crawley ```commandline python collect_data.py CrawleyBoroughCouncil https://my.crawley.gov.uk/ -s -u XXXXXXXX -n XX ``` @@ -1131,20 +1206,21 @@ Note: Crawley needs to be passed both a UPRN and a USRN to work. Find these on [ --- -### Croydon Council +### Croydon ```commandline -python collect_data.py CroydonCouncil https://service.croydon.gov.uk/wasteservices/w/webpage/bin-day-enter-address -s -p "XXXX XXX" -n XX +python collect_data.py CroydonCouncil https://service.croydon.gov.uk/wasteservices/w/webpage/bin-day-enter-address -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` Additional parameters: - `-s` - skip get URL - `-p` - postcode - `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: Pass the house number and postcode in their respective parameters. +Note: Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver. --- -### Cumberland Council - Allerdale District +### Cumberland ```commandline python collect_data.py CumberlandAllerdaleCouncil https://www.allerdale.gov.uk -p "XXXX XXX" -n XX ``` @@ -1156,19 +1232,19 @@ Note: Pass the house number and postcode in their respective parameters. --- -### Cumberland Borough Council +### Cumberland ```commandline -python collect_data.py CumberlandCouncil https://waste.cumberland.gov.uk -u XXXXXXXX -p "XXXX XXX" +python collect_data.py CumberlandCouncil https://waste.cumberland.gov.uk/renderform?t=25&k=E43CEB1FB59F859833EF2D52B16F3F4EBE1CAB6A -u XXXXXXXX -p "XXXX XXX" ``` Additional parameters: -- `-p` - postcode - `-u` - UPRN +- `-p` - postcode Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN. --- -### Dacorum Borough Council +### Dacorum ```commandline python collect_data.py DacorumBoroughCouncil https://webapps.dacorum.gov.uk/bincollections/ -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1182,18 +1258,30 @@ Note: Pass the house number and postcode in their respective parameters. This pa --- -### Dartford Borough Council +### Darlington Borough Council +```commandline +python collect_data.py DarlingtonBoroughCouncil https://www.darlington.gov.uk/bins-waste-and-recycling/collection-day-lookup/ -u XXXXXXXX +``` +Additional parameters: +- `-u` - UPRN + +Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN. + +--- + +### Dartford ```commandline -python collect_data.py DartfordBoroughCouncil https://windmz.dartford.gov.uk/ufs/WS_CHECK_COLLECTIONS.eb?UPRN=010094157511 -u XXXXXXXX +python collect_data.py DartfordBoroughCouncil https://www.dartford.gov.uk/waste-recycling/collection-day -s -u XXXXXXXX ``` Additional parameters: +- `-s` - skip get URL - `-u` - UPRN Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN. --- -### Denbighshire Council +### Denbighshire ```commandline python collect_data.py DenbighshireCouncil https://www.denbighshire.gov.uk/ -u XXXXXXXX ``` @@ -1204,7 +1292,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Derby City Council +### Derby ```commandline python collect_data.py DerbyCityCouncil https://www.derby.gov.uk -u XXXXXXXX ``` @@ -1215,7 +1303,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Derbyshire Dales District Council +### Derbyshire Dales ```commandline python collect_data.py DerbyshireDalesDistrictCouncil https://www.derbyshiredales.gov.uk/ -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -1228,7 +1316,7 @@ Note: Pass the UPRN and postcode. To get the UPRN, you can use [FindMyAddress](h --- -### Doncaster Council +### Doncaster ```commandline python collect_data.py DoncasterCouncil https://www.doncaster.gov.uk/Compass/Entity/Launch/D3/ -s -u XXXXXXXX ``` @@ -1252,16 +1340,19 @@ Note: Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddr --- -### Dover District Council +### Dover ```commandline -python collect_data.py DoverDistrictCouncil https://collections.dover.gov.uk/property/XXXXXXXXXXX +python collect_data.py DoverDistrictCouncil https://collections.dover.gov.uk/property -s -u XXXXXXXX ``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN -Note: Replace XXXXXXXXXXX with your UPRN. To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search). +Note: To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search). --- -### Dudley Council +### Dudley ```commandline python collect_data.py DudleyCouncil https://my.dudley.gov.uk -u XXXXXXXX ``` @@ -1272,7 +1363,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Dundee City Council +### Dundee City ```commandline python collect_data.py DundeeCityCouncil https://www.dundeecity.gov.uk/ -u XXXXXXXX ``` @@ -1283,7 +1374,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Durham Council +### County Durham ```commandline python collect_data.py DurhamCouncil https://www.durham.gov.uk/bincollections?uprn= -s -u XXXXXXXX ``` @@ -1295,7 +1386,7 @@ Note: Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddr --- -### Ealing Council +### Ealing ```commandline python collect_data.py EalingCouncil https://www.ealing.gov.uk/site/custom_scripts/WasteCollectionWS/home/FindCollection -s -u XXXXXXXX ``` @@ -1307,7 +1398,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### East Ayrshire Council +### East Ayrshire ```commandline python collect_data.py EastAyrshireCouncil https://www.east-ayrshire.gov.uk -u XXXXXXXX ``` @@ -1318,7 +1409,19 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### East Cambridgeshire Council +### Eastbourne +```commandline +python collect_data.py EastbourneBoroughCouncil https://www.lewes-eastbourne.gov.uk/article/1158/When-is-my-bin-collection-day -s -u XXXXXXXX +``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN + +Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. + +--- + +### East Cambridgeshire ```commandline python collect_data.py EastCambridgeshireCouncil https://www.eastcambs.gov.uk/ -s -u XXXXXXXX ``` @@ -1330,30 +1433,31 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### East Devon District Council +### East Devon ```commandline -python collect_data.py EastDevonDC https://eastdevon.gov.uk/recycling-and-waste/recycling-waste-information/when-is-my-bin-collected/future-collections-calendar/?UPRN=XXXXXXXX +python collect_data.py EastDevonDC https://eastdevon.gov.uk/recycling-and-waste/recycling-waste-information/when-is-my-bin-collected/ -s -u XXXXXXXX ``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN -Note: Replace XXXXXXXX with your UPRN. +Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search). --- ### East Herts Council ```commandline -python collect_data.py EastHertsCouncil https://www.eastherts.gov.uk -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ +python collect_data.py EastHertsCouncil https://east-herts.co.uk/api/services/ -s -u XXXXXXXX ``` Additional parameters: - `-s` - skip get URL -- `-p` - postcode -- `-n` - house number -- `-w` - remote Selenium web driver URL (required for Home Assistant) +- `-u` - UPRN -Note: Pass the house number and postcode in their respective parameters. +Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search). --- -### East Lindsey District Council +### East Lindsey ```commandline python collect_data.py EastLindseyDistrictCouncil https://www.e-lindsey.gov.uk/ -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1367,7 +1471,7 @@ Note: Pass the house name/number and postcode in their respective parameters. Th --- -### East Lothian Council +### East Lothian ```commandline python collect_data.py EastLothianCouncil https://eastlothian.gov.uk -s -p "XXXX XXX" -n XX ``` @@ -1380,7 +1484,7 @@ Note: Pass the house number and postcode in their respective parameters --- -### East Renfrewshire Council +### East Renfrewshire ```commandline python collect_data.py EastRenfrewshireCouncil https://eastrenfrewshire.gov.uk/ -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1394,7 +1498,7 @@ Note: Pass the house name/number and postcode in their respective parameters. Th --- -### East Riding Council +### East Riding of Yorkshire ```commandline python collect_data.py EastRidingCouncil https://wasterecyclingapi.eastriding.gov.uk -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1408,7 +1512,7 @@ Note: Put the full address as it displays on the council website dropdown when y --- -### East Staffordshire Borough Council +### East Staffordshire ```commandline python collect_data.py EastStaffordshireBoroughCouncil https://www.eaststaffsbc.gov.uk/bins-rubbish-recycling/collection-dates/XXXXX ``` @@ -1417,7 +1521,7 @@ Note: Replace `XXXXX` with your property's ID when selecting from https://www.ea --- -### East Suffolk Council +### East Suffolk ```commandline python collect_data.py EastSuffolkCouncil https://my.eastsuffolk.gov.uk/service/Bin_collection_dates_finder -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -1431,19 +1535,20 @@ Note: To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co. --- -### Eastleigh Borough Council +### Eastleigh ```commandline -python collect_data.py EastleighBoroughCouncil https://www.eastleigh.gov.uk/waste-bins-and-recycling/collection-dates/your-waste-bin-and-recycling-collections?uprn= -s -u XXXXXXXX +python collect_data.py EastleighBoroughCouncil https://www.eastleigh.gov.uk/waste-bins-and-recycling/collection-dates/your-waste-bin-and-recycling-collections?uprn= -s -u XXXXXXXX -w http://HOST:PORT/ ``` Additional parameters: - `-s` - skip get URL - `-u` - UPRN +- `-w` - remote Selenium web driver URL (required for Home Assistant) Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search). --- -### Edinburgh City Council +### City of Edinburgh ```commandline python collect_data.py EdinburghCityCouncil https://www.edinburgh.gov.uk -s -p "XXXX XXX" -n XX ``` @@ -1456,7 +1561,7 @@ Note: Use the House Number field to pass the DAY of the week for your collection --- -### Elmbridge Borough Council +### Elmbridge ```commandline python collect_data.py ElmbridgeBoroughCouncil https://www.elmbridge.gov.uk -u XXXXXXXX ``` @@ -1467,7 +1572,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Enfield Council +### Enfield ```commandline python collect_data.py EnfieldCouncil https://www.enfield.gov.uk/services/rubbish-and-recycling/find-my-collection-day -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1490,19 +1595,20 @@ Note: For properties with collections managed by Environment First, such as Lewe --- -### Epping Forest District Council +### Epping Forest ```commandline -python collect_data.py EppingForestDistrictCouncil https://eppingforestdc.maps.arcgis.com/apps/instant/lookup/index.html?appid=bfca32b46e2a47cd9c0a84f2d8cdde17&find=IG9%206EP -p "XXXX XXX" -w http://HOST:PORT/ +python collect_data.py EppingForestDistrictCouncil https://eppingforestdc.maps.arcgis.com/apps/instant/lookup/index.html?appid=bfca32b46e2a47cd9c0a84f2d8cdde17&find=IG9%206EP -s -p "XXXX XXX" -w http://HOST:PORT/ ``` Additional parameters: +- `-s` - skip get URL - `-p` - postcode - `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: Replace the postcode in the URL with your own. +Note: Add your postcode. --- -### Epsom and Ewell Borough Council +### Epsom and Ewell ```commandline python collect_data.py EpsomandEwellBoroughCouncil https://www.epsom-ewell.gov.uk -u XXXXXXXX ``` @@ -1513,7 +1619,7 @@ Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your U --- -### Erewash Borough Council +### Erewash ```commandline python collect_data.py ErewashBoroughCouncil https://map.erewash.gov.uk/isharelive.web/myerewash.aspx -s -u XXXXXXXX ``` @@ -1525,7 +1631,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### Exeter City Council +### Exeter ```commandline python collect_data.py ExeterCityCouncil https://www.exeter.gov.uk -u XXXXXXXX ``` @@ -1536,7 +1642,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### Falkirk Council +### Falkirk ```commandline python collect_data.py FalkirkCouncil https://www.falkirk.gov.uk -u XXXXXXXX ``` @@ -1547,7 +1653,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Fareham Borough Council +### Fareham ```commandline python collect_data.py FarehamBoroughCouncil https://www.fareham.gov.uk/internetlookups/search_data.aspx?type=JSON&list=DomesticBinCollections&Road=&Postcode=PO14%204NR -s -p "XXXX XXX" ``` @@ -1559,7 +1665,7 @@ Note: Pass the postcode in the postcode parameter, wrapped in double quotes. --- -### Fenland District Council +### Fenland ```commandline python collect_data.py FenlandDistrictCouncil https://www.fenland.gov.uk/article/13114/ -s -u XXXXXXXX ``` @@ -1571,7 +1677,20 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### Fife Council +### Fermanagh and Omagh +```commandline +python collect_data.py FermanaghOmaghDistrictCouncil https://www.fermanaghomagh.com/services/environment-and-waste/waste-collection-calendar/ -s -p "XXXX XXX" -n XX +``` +Additional parameters: +- `-s` - skip get URL +- `-p` - postcode +- `-n` - house number + +Note: Pass the house number and postcode in their respective parameters. + +--- + +### Fife ```commandline python collect_data.py FifeCouncil https://www.fife.gov.uk -u XXXXXXXX ``` @@ -1582,7 +1701,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Flintshire County Council +### Flintshire ```commandline python collect_data.py FlintshireCountyCouncil https://digital.flintshire.gov.uk -u XXXXXXXX ``` @@ -1593,7 +1712,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Folkstone and Hythe District Council +### Folkestone and Hythe ```commandline python collect_data.py FolkstoneandHytheDistrictCouncil https://www.folkestone-hythe.gov.uk -s -u XXXXXXXX ``` @@ -1605,7 +1724,7 @@ Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your U --- -### Forest of Dean District Council +### Forest of Dean ```commandline python collect_data.py ForestOfDeanDistrictCouncil https://community.fdean.gov.uk/s/waste-collection-enquiry -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1619,7 +1738,7 @@ Note: Pass the full address in the house number and postcode parameters. This pa --- -### Fylde Council +### Fylde ```commandline python collect_data.py FyldeCouncil https://www.fylde.gov.uk -u XXXXXXXX ``` @@ -1630,7 +1749,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Gateshead Council +### Gateshead ```commandline python collect_data.py GatesheadCouncil https://www.gateshead.gov.uk/ -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1644,7 +1763,7 @@ Note: Pass the house name/number and postcode in their respective parameters. Th --- -### Gedling Borough Council +### Gedling ```commandline python collect_data.py GedlingBoroughCouncil https://www.gedling.gov.uk/ -s -n XX ``` @@ -1656,16 +1775,19 @@ Note: Use [this site](https://www.gbcbincalendars.co.uk/) to find the collection --- -### Glasgow City Council +### Glasgow City ```commandline -python collect_data.py GlasgowCityCouncil https://onlineservices.glasgow.gov.uk/forms/RefuseAndRecyclingWebApplication/CollectionsCalendar.aspx?UPRN=XXXXXXXX +python collect_data.py GlasgowCityCouncil https://onlineservices.glasgow.gov.uk/forms/RefuseAndRecyclingWebApplication/AddressSearch.aspx -s -u XXXXXXXX ``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN -Note: Replace XXXXXXXX with your UPRN. +Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. --- -### Gloucester City Council +### Gloucester ```commandline python collect_data.py GloucesterCityCouncil https://gloucester-self.achieveservice.com/service/Bins___Check_your_bin_day -s -u XXXXXXXX -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1680,7 +1802,16 @@ Note: Pass the house number, postcode, and UPRN in their respective parameters. --- -### Gravesham Borough Council +### Google Calendar (Public) +```commandline +python collect_data.py GooglePublicCalendarCouncil https://calendar.google.com/calendar/ical/0d775884b4db6a7bae5204f06dae113c1a36e505b25991ebc27c6bd42edf5b5e%40group.calendar.google.com/public/basic.ics +``` + +Note: The URL should be the public ics file URL for the public Google calendar. See https://support.google.com/calendar/answer/37083?sjid=7202815583021446882-EU. Councils that currently need this are Trafford. + +--- + +### Gravesham ```commandline python collect_data.py GraveshamBoroughCouncil https://www.gravesham.gov.uk -s -u XXXXXXXX ``` @@ -1692,22 +1823,35 @@ Note: Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddr --- -### Guildford Council +### Great Yarmouth ```commandline -python collect_data.py GuildfordCouncil https://my.guildford.gov.uk/customers/s/view-bin-collections -s -u XXXXXXXX -p "XXXX XXX" -n XX -w http://HOST:PORT/ +python collect_data.py GreatYarmouthBoroughCouncil https://myaccount.great-yarmouth.gov.uk/article/6456/Find-my-waste-collection-days -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` Additional parameters: - `-s` - skip get URL - `-u` - UPRN - `-p` - postcode +- `-w` - remote Selenium web driver URL (required for Home Assistant) + +Note: Pass the postcode, and UPRN in their respective parameters. This parser requires a Selenium webdriver. + +--- + +### Guildford +```commandline +python collect_data.py GuildfordCouncil https://my.guildford.gov.uk/customers/s/view-bin-collections -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ +``` +Additional parameters: +- `-s` - skip get URL +- `-p` - postcode - `-n` - house number - `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: If the bin day is 'today' then the collectionDate will only show today's date if before 7 AM; else the date will be in 'previousCollectionDate'. To get the UPRN, you will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search). +Note: If the bin day is 'today' then the collectionDate will only show today's date if before 7 AM; else the date will be in 'previousCollectionDate'. --- -### Gwynedd Council +### Gwynedd ```commandline python collect_data.py GwyneddCouncil https://diogel.gwynedd.llyw.cymru -u XXXXXXXX ``` @@ -1718,7 +1862,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Hackney Council +### Hackney ```commandline python collect_data.py HackneyCouncil https://www.hackney.gov.uk -p "XXXX XXX" -n XX ``` @@ -1730,7 +1874,7 @@ Note: Pass the postcode and house number in their respective arguments, both wra --- -### Halton Borough Council +### Halton ```commandline python collect_data.py HaltonBoroughCouncil https://webapp.halton.gov.uk/PublicWebForms/WasteServiceSearchv1.aspx#collections -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1744,7 +1888,7 @@ Note: Pass the house number and postcode. This parser requires a Selenium webdri --- -### Harborough District Council +### Harborough ```commandline python collect_data.py HarboroughDistrictCouncil https://www.harborough.gov.uk -u XXXXXXXX ``` @@ -1755,7 +1899,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Haringey Council +### Haringey ```commandline python collect_data.py HaringeyCouncil https://wastecollections.haringey.gov.uk/property -s -u XXXXXXXX ``` @@ -1767,7 +1911,7 @@ Note: Pass the UPRN, which can be found at `https://wastecollections.haringey.go --- -### Harrogate Borough Council +### Harrogate ```commandline python collect_data.py HarrogateBoroughCouncil https://secure.harrogate.gov.uk/inmyarea -s -u XXXXXXXX ``` @@ -1779,7 +1923,7 @@ Note: Pass the UPRN, which can be found at [this site](https://secure.harrogate. --- -### Hart District Council +### Hart ```commandline python collect_data.py HartDistrictCouncil https://www.hart.gov.uk/ -s -u XXXXXXXX ``` @@ -1791,7 +1935,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Hartlepool Borough Council +### Hartlepool ```commandline python collect_data.py HartlepoolBoroughCouncil https://www.hartlepool.gov.uk -u XXXXXXXX ``` @@ -1802,7 +1946,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Hastings Borough Council +### Hastings ```commandline python collect_data.py HastingsBoroughCouncil https://www.hastings.gov.uk -u XXXXXXXX ``` @@ -1813,16 +1957,19 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Herefordshire Council +### Herefordshire ```commandline -python collect_data.py HerefordshireCouncil https://www.herefordshire.gov.uk/rubbish-recycling/check-bin-collection-day?blpu_uprn=XXXXXXXXXXXX +python collect_data.py HerefordshireCouncil https://www.herefordshire.gov.uk/rubbish-recycling/check-bin-collection-day -s -u XXXXXXXX ``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN -Note: Replace 'XXXXXXXXXX' with your property's UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search). +Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. --- -### Hertsmere Borough Council +### Hertsmere ```commandline python collect_data.py HertsmereBoroughCouncil https://www.hertsmere.gov.uk -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1836,7 +1983,7 @@ Note: Provide your house number in the `house_number` parameter and postcode in --- -### High Peak Council +### High Peak ```commandline python collect_data.py HighPeakCouncil https://www.highpeak.gov.uk/findyourbinday -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1850,7 +1997,7 @@ Note: Pass the name of the street with the house number parameter, wrapped in do --- -### Highland Council +### Highland ```commandline python collect_data.py HighlandCouncil https://www.highland.gov.uk -u XXXXXXXX ``` @@ -1861,7 +2008,21 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Hinckley and Bosworth Borough Council +### Hillingdon +```commandline +python collect_data.py Hillingdon https://www.hillingdon.gov.uk/collection-day -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ +``` +Additional parameters: +- `-s` - skip get URL +- `-p` - postcode +- `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) + +Note: Pass the postcode and the full address as it appears in the address pulldown menu. + +--- + +### Hinckley and Bosworth ```commandline python collect_data.py HinckleyandBosworthBoroughCouncil https://www.hinckley-bosworth.gov.uk -u XXXXXXXX ``` @@ -1872,22 +2033,21 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Hounslow Council +### Horsham ```commandline -python collect_data.py HounslowCouncil https://www.hounslow.gov.uk/info/20272/recycling_and_waste_collection_day_finder -s -u XXXXXXXX -p "XXXX XXX" -n XX -w http://HOST:PORT/ +python collect_data.py HorshamDistrictCouncil https://www.horsham.gov.uk/waste-recycling-and-bins/household-bin-collections/check-your-bin-collection-day -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` Additional parameters: - `-s` - skip get URL - `-u` - UPRN - `-p` - postcode -- `-n` - house number - `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: Pass the full address as it appears on the council's website. This parser requires a Selenium webdriver. +Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search). This parser requires a Selenium webdriver. --- -### Hull City Council +### Kingston upon Hull ```commandline python collect_data.py HullCityCouncil https://www.hull.gov.uk/bins-and-recycling/bin-collections/bin-collection-day-checker -s -u XXXXXXXX ``` @@ -1899,16 +2059,33 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### Huntingdon District Council +### Huntingdonshire ```commandline -python collect_data.py HuntingdonDistrictCouncil https://www.huntingdonshire.gov.uk/refuse-calendar/XXXXXXXX +python collect_data.py HuntingdonDistrictCouncil http://www.huntingdonshire.gov.uk/refuse-calendar/ -s -u XXXXXXXX ``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN -Note: Replace XXXXXXXX with your UPRN. +Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search). + +--- + +### Hyndburn +```commandline +python collect_data.py HyndburnBoroughCouncil https://iapp.itouchvision.com/iappcollectionday/collection-day/?uuid=FEBA68993831481FD81B2E605364D00A8DC017A4 -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ +``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN +- `-p` - postcode +- `-w` - remote Selenium web driver URL (required for Home Assistant) + +Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search). This parser requires a Selenium webdriver. --- -### Ipswich Borough Council +### Ipswich ```commandline python collect_data.py IpswichBoroughCouncil https://app.ipswich.gov.uk/bin-collection/ -n XX ``` @@ -1919,7 +2096,7 @@ Note: Provide only the street name (no house number) as the PAON --- -### Islington Council +### Islington ```commandline python collect_data.py IslingtonCouncil https://www.islington.gov.uk/your-area?Postcode=unused&Uprn=XXXXXXXX -u XXXXXXXX ``` @@ -1930,7 +2107,7 @@ Note: Replace XXXXXXXX with your UPRN. --- -### Kings Lynn and West Norfolk Borough Council +### Kings Lynn and West Norfolk ```commandline python collect_data.py KingsLynnandWestNorfolkBC https://www.west-norfolk.gov.uk/ -u XXXXXXXX ``` @@ -1941,7 +2118,7 @@ Note: Provide your UPRN. Find your UPRN using [FindMyAddress](https://www.findmy --- -### Kingston Upon Thames Council +### Kingston upon Thames ```commandline python collect_data.py KingstonUponThamesCouncil https://waste-services.kingston.gov.uk/waste/XXXXXXX -w http://HOST:PORT/ ``` @@ -1952,7 +2129,7 @@ Note: Follow the instructions [here](https://waste-services.kingston.gov.uk/wast --- -### Kirklees Council +### Kirklees ```commandline python collect_data.py KirkleesCouncil https://www.kirklees.gov.uk/beta/your-property-bins-recycling/your-bins -s -u XXXXXXXX ``` @@ -1964,7 +2141,7 @@ Note: Provide your UPRN. Find your UPRN using [FindMyAddress](https://www.findmy --- -### Knowsley Metropolitan Borough Council +### Knowsley ```commandline python collect_data.py KnowsleyMBCouncil https://knowsleytransaction.mendixcloud.com/link/youarebeingredirected?target=bincollectioninformation -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -1978,7 +2155,7 @@ Note: Pass the postcode in the postcode parameter, wrapped in double quotes and --- -### Lancaster City Council +### Lancaster ```commandline python collect_data.py LancasterCityCouncil https://lcc-wrp.whitespacews.com -s -p "XXXX XXX" -n XX ``` @@ -1991,7 +2168,7 @@ Note: Pass the house number and postcode in their respective parameters. --- -### Leeds City Council +### Leeds ```commandline python collect_data.py LeedsCityCouncil https://www.leeds.gov.uk/residents/bins-and-recycling/check-your-bin-day -s -u XXXXXXXX -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -2006,7 +2183,7 @@ Note: Pass the house number, postcode, and UPRN. This parser requires a Selenium --- -### Leicester City Council +### Leicester ```commandline python collect_data.py LeicesterCityCouncil https://biffaleicester.co.uk -u XXXXXXXX ``` @@ -2017,7 +2194,19 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Lichfield District Council +### Lewes +```commandline +python collect_data.py LewesDistrictCouncil https://www.lewes-eastbourne.gov.uk/article/1158/When-is-my-bin-collection-day -s -u XXXXXXXX +``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN + +Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. + +--- + +### Lichfield ```commandline python collect_data.py LichfieldDistrictCouncil https://www.lichfielddc.gov.uk -u XXXXXXXX ``` @@ -2028,7 +2217,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Lincoln Council +### City of Lincoln ```commandline python collect_data.py LincolnCouncil https://lincoln.gov.uk -u XXXXXXXX -p "XXXX XXX" ``` @@ -2040,7 +2229,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Lisburn and Castlereagh City Council +### Lisburn and Castlereagh ```commandline python collect_data.py LisburnCastlereaghCityCouncil https://lisburn.isl-fusion.com -s -p "XXXX XXX" -n XX ``` @@ -2053,16 +2242,19 @@ Note: Pass the house number and postcode in their respective parameters. --- -### Liverpool City Council +### Liverpool ```commandline -python collect_data.py LiverpoolCityCouncil https://liverpool.gov.uk/Bins/BinDatesTable?UPRN=XXXXXXXX +python collect_data.py LiverpoolCityCouncil https://liverpool.gov.uk/bins-and-recycling/bin-collections/ -s -u XXXXXXXX ``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN -Note: Replace XXXXXXXX with your property's UPRN. +Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. --- -### London Borough Ealing +### Ealing ```commandline python collect_data.py LondonBoroughEaling https://www.ealing.gov.uk/site/custom_scripts/WasteCollectionWS/home/FindCollection -s -u XXXXXXXX ``` @@ -2074,7 +2266,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### London Borough Harrow +### Harrow ```commandline python collect_data.py LondonBoroughHarrow https://www.harrow.gov.uk -u XXXXXXXX ``` @@ -2085,7 +2277,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### London Borough Havering +### Havering ```commandline python collect_data.py LondonBoroughHavering https://www.havering.gov.uk -u XXXXXXXX ``` @@ -2096,7 +2288,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### London Borough Hounslow +### Hounslow ```commandline python collect_data.py LondonBoroughHounslow https://www.hounslow.gov.uk/homepage/86/recycling_and_waste_collection_day_finder -s -u XXXXXXXX ``` @@ -2108,7 +2300,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### London Borough Lambeth +### Lambeth ```commandline python collect_data.py LondonBoroughLambeth https://wasteservice.lambeth.gov.uk/WhitespaceComms/GetServicesByUprn -s -u XXXXXXXX ``` @@ -2120,7 +2312,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### London Borough Lewisham +### Lewisham ```commandline python collect_data.py LondonBoroughLewisham https://www.lewisham.gov.uk -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -2134,7 +2326,7 @@ Note: Pass the UPRN and postcode. To get the UPRN, you can use [FindMyAddress](h --- -### London Borough Of Richmond Upon Thames +### Richmond upon Thames ```commandline python collect_data.py LondonBoroughOfRichmondUponThames https://www.richmond.gov.uk/services/waste_and_recycling/collection_days/ -s -n XX -w http://HOST:PORT/ ``` @@ -2147,7 +2339,7 @@ Note: Pass the name of the street ONLY in the house number parameter, unfortunat --- -### London Borough Redbridge +### Redbridge ```commandline python collect_data.py LondonBoroughRedbridge https://my.redbridge.gov.uk/RecycleRefuse -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -2160,7 +2352,7 @@ Note: Follow the instructions [here](https://my.redbridge.gov.uk/RecycleRefuse) --- -### London Borough Sutton +### Sutton ```commandline python collect_data.py LondonBoroughSutton https://waste-services.sutton.gov.uk/waste -u XXXXXXXX ``` @@ -2171,7 +2363,7 @@ Note: You will need to find your unique property reference by going to (https:// --- -### Luton Borough Council +### Luton ```commandline python collect_data.py LutonBoroughCouncil https://myforms.luton.gov.uk -u XXXXXXXX ``` @@ -2182,7 +2374,21 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Maldon District Council +### Maidstone +```commandline +python collect_data.py MaidstoneBoroughCouncil https://my.maidstone.gov.uk/service/Find-your-bin-day -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ +``` +Additional parameters: +- `-s` - skip get URL +- `-p` - postcode +- `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) + +Note: Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver. + +--- + +### Maldon ```commandline python collect_data.py MaldonDistrictCouncil https://maldon.suez.co.uk/maldon/ServiceSummary -s -u XXXXXXXX ``` @@ -2194,7 +2400,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### Malvern Hills District Council +### Malvern Hills ```commandline python collect_data.py MalvernHillsDC https://swict.malvernhills.gov.uk/mhdcroundlookup/HandleSearchScreen -s -u XXXXXXXX ``` @@ -2206,7 +2412,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### Manchester City Council +### Manchester ```commandline python collect_data.py ManchesterCityCouncil https://www.manchester.gov.uk/bincollections -s -u XXXXXXXX ``` @@ -2218,7 +2424,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### Mansfield District Council +### Mansfield ```commandline python collect_data.py MansfieldDistrictCouncil https://www.mansfield.gov.uk/xfp/form/1327 -s -u XXXXXXXX ``` @@ -2230,7 +2436,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### MedwayCouncil +### Medway ```commandline python collect_data.py MedwayCouncil https://www.medway.gov.uk/homepage/45/check_your_waste_collection_day -s -u XXXXXXXX ``` @@ -2242,7 +2448,18 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### Merton Council +### Melton +```commandline +python collect_data.py MeltonBoroughCouncil https://my.melton.gov.uk/collections -u XXXXXXXX +``` +Additional parameters: +- `-u` - UPRN + +Note: To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co.uk/search). + +--- + +### Merton ```commandline python collect_data.py MertonCouncil https://myneighbourhood.merton.gov.uk/Wasteservices/WasteServices.aspx?ID=XXXXXXXX ``` @@ -2251,7 +2468,7 @@ Note: Follow the instructions [here](https://myneighbourhood.merton.gov.uk/Waste --- -### Mid and East Antrim Borough Council +### Mid and East Antrim ```commandline python collect_data.py MidAndEastAntrimBoroughCouncil https://www.midandeastantrim.gov.uk/resident/waste-recycling/collection-dates/ -s -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -2264,7 +2481,7 @@ Note: Pass the house name/number plus the name of the street with the postcode p --- -### Mid Devon Council +### Mid Devon ```commandline python collect_data.py MidDevonCouncil https://www.middevon.gov.uk -u XXXXXXXX ``` @@ -2275,48 +2492,48 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Middlesbrough Council +### Mid Suffolk ```commandline -python collect_data.py MiddlesbroughCouncil https://www.midsussex.gov.uk/waste-recycling/bin-collection/ -s -n XX -w http://HOST:PORT/ +python collect_data.py MidSuffolkDistrictCouncil https://www.midsuffolk.gov.uk -s -u XXXXXXXX -p "XXXX XXX" -n XX ``` Additional parameters: - `-s` - skip get URL +- `-u` - UPRN +- `-p` - postcode - `-n` - house number -- `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: Pass the entire address without postcode as it appears when you type it on the website. This parser requires a Selenium webdriver. +Note: Use the House Number field to pass the DAY of the week for your NORMAL collections. [Monday/Tuesday/Wednesday/Thursday/Friday]. [OPTIONAL] Use the 'postcode' field to pass the WEEK for your garden collection. [Week 1/Week 2]. [OPTIONAL] Use the 'uprn' field to pass the DAY for your garden collection. [Monday/Tuesday/Wednesday/Thursday/Friday] --- -### Mid Suffolk District Council +### Mid Sussex ```commandline -python collect_data.py MidSuffolkDistrictCouncil https://www.midsuffolk.gov.uk -s -u XXXXXXXX -p "XXXX XXX" -n XX +python collect_data.py MidSussexDistrictCouncil https://www.midsussex.gov.uk/waste-recycling/bin-collection/ -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` Additional parameters: - `-s` - skip get URL -- `-u` - UPRN - `-p` - postcode - `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: Use the House Number field to pass the DAY of the week for your NORMAL collections. [Monday/Tuesday/Wednesday/Thursday/Friday]. [OPTIONAL] Use the 'postcode' field to pass the WEEK for your garden collection. [Week 1/Week 2]. [OPTIONAL] Use the 'uprn' field to pass the DAY for your garden collection. [Monday/Tuesday/Wednesday/Thursday/Friday] +Note: Pass the name of the street with the house number parameter, wrapped in double quotes. This parser requires a Selenium webdriver. --- -### Mid Sussex District Council +### Middlesbrough ```commandline -python collect_data.py MidSussexDistrictCouncil https://www.midsussex.gov.uk/waste-recycling/bin-collection/ -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ +python collect_data.py MiddlesbroughCouncil https://www.middlesbrough.gov.uk/recycling-and-rubbish/bin-collection-dates/ -s -n XX -w http://HOST:PORT/ ``` Additional parameters: - `-s` - skip get URL -- `-p` - postcode - `-n` - house number - `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: Pass the name of the street with the house number parameter, wrapped in double quotes. This parser requires a Selenium webdriver. +Note: Pass the entire address without postcode as it appears when you type it on the website. This parser requires a Selenium webdriver. --- -### Midlothian Council +### Midlothian ```commandline python collect_data.py MidlothianCouncil https://www.midlothian.gov.uk/info/1054/bins_and_recycling/343/bin_collection_days -s -p "XXXX XXX" -n XX ``` @@ -2329,7 +2546,21 @@ Note: Pass the house name/number wrapped in double quotes along with the postcod --- -### Milton Keynes City Council +### Mid Ulster +```commandline +python collect_data.py MidUlsterDistrictCouncil https://www.midulstercouncil.org -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ +``` +Additional parameters: +- `-s` - skip get URL +- `-p` - postcode +- `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) + +Note: Pass the full address of the house postcode as displayed on the site. This parser requires a Selenium webdriver. + +--- + +### Milton Keynes ```commandline python collect_data.py MiltonKeynesCityCouncil https://mycouncil.milton-keynes.gov.uk/en/service/Waste_Collection_Round_Checker -u XXXXXXXX ``` @@ -2340,7 +2571,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Mole Valley District Council +### Mole Valley ```commandline python collect_data.py MoleValleyDistrictCouncil https://myproperty.molevalley.gov.uk/molevalley/ -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -2353,7 +2584,7 @@ Note: UPRN can only be parsed with a valid postcode. --- -### Monmouthshire County Council +### Monmouthshire ```commandline python collect_data.py MonmouthshireCountyCouncil https://maps.monmouthshire.gov.uk -u XXXXXXXX ``` @@ -2364,7 +2595,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Moray Council +### Moray ```commandline python collect_data.py MorayCouncil https://bindayfinder.moray.gov.uk/ -u XXXXXXXX ``` @@ -2375,7 +2606,7 @@ Note: Find your property ID by going to (https://bindayfinder.moray.gov.uk), sea --- -### Neath Port Talbot Council +### Neath Port Talbot ```commandline python collect_data.py NeathPortTalbotCouncil https://www.npt.gov.uk -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -2389,7 +2620,7 @@ Note: Pass the house number and postcode in their respective parameters. This pa --- -### New Forest Council +### New Forest ```commandline python collect_data.py NewForestCouncil https://forms.newforest.gov.uk/id/FIND_MY_COLLECTION -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -2403,16 +2634,19 @@ Note: Pass the postcode and UPRN. This parser requires a Selenium webdriver. --- -### Newark and Sherwood District Council +### Newark and Sherwood ```commandline -python collect_data.py NewarkAndSherwoodDC http://app.newark-sherwooddc.gov.uk/bincollection/calendar?pid=XXXXXXXX +python collect_data.py NewarkAndSherwoodDC https://app.newark-sherwooddc.gov.uk/bincollection/ -s -u XXXXXXXX ``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN Note: Replace XXXXXXXX with your UPRN. --- -### Newcastle City Council +### Newcastle upon Tyne ```commandline python collect_data.py NewcastleCityCouncil https://community.newcastle.gov.uk/my-neighbourhood/ajax/getBinsNew.php?uprn=XXXXXXXX ``` @@ -2421,7 +2655,7 @@ Note: Replace XXXXXXXX with your UPRN. UPRNs need to be 12 digits long so please --- -### Newcastle Under Lyme Council +### Newcastle-under-Lyme ```commandline python collect_data.py NewcastleUnderLymeCouncil https://www.newcastle-staffs.gov.uk -u XXXXXXXX ``` @@ -2432,18 +2666,19 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Newham Council +### Newham ```commandline -python collect_data.py NewhamCouncil https://bincollection.newham.gov.uk/Details/Index/XXXXXXXXXXX -s +python collect_data.py NewhamCouncil https://bincollection.newham.gov.uk/ -s -u XXXXXXXX ``` Additional parameters: - `-s` - skip get URL +- `-u` - UPRN -Note: Follow the instructions [here](https://bincollection.newham.gov.uk/) until you get the "Rubbish and Recycling Collections" page, then copy the URL and replace the URL in the command. +Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN. --- -### Newport City Council +### Newport ```commandline python collect_data.py NewportCityCouncil https://www.newport.gov.uk/ -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -2456,7 +2691,7 @@ Note: Pass the postcode and UPRN. You can find the UPRN using [FindMyAddress](ht --- -### North Ayrshire Council +### North Ayrshire ```commandline python collect_data.py NorthAyrshireCouncil https://www.north-ayrshire.gov.uk/ -u XXXXXXXX ``` @@ -2467,7 +2702,22 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### North East Derbyshire District Council +### North Devon +```commandline +python collect_data.py NorthDevonCountyCouncil https://my.northdevon.gov.uk/service/WasteRecyclingCollectionCalendar -s -u XXXXXXXX -p "XXXX XXX" -n XX -w http://HOST:PORT/ +``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN +- `-p` - postcode +- `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) + +Note: Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver. + +--- + +### North East Derbyshire ```commandline python collect_data.py NorthEastDerbyshireDistrictCouncil https://myselfservice.ne-derbyshire.gov.uk/service/Check_your_Bin_Day -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -2481,7 +2731,7 @@ Note: Pass the postcode and UPRN. This parser requires a Selenium webdriver. --- -### North East Lincolnshire Council +### North East Lincolnshire ```commandline python collect_data.py NorthEastLincs https://www.nelincs.gov.uk/refuse-collection-schedule/?view=timeline&uprn=XXXXXXXX -u XXXXXXXX ``` @@ -2492,19 +2742,20 @@ Note: Replace XXXXXXXX with your UPRN. --- -### North Hertfordshire District Council +### North Hertfordshire ```commandline -python collect_data.py NorthHertfordshireDistrictCouncil https://www.north-herts.gov.uk -p "XXXX XXX" -n XX +python collect_data.py NorthHertfordshireDistrictCouncil https://www.north-herts.gov.uk -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` Additional parameters: - `-p` - postcode - `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) Note: Pass the house number and postcode in their respective parameters. --- -### North Kesteven District Council +### North Kesteven ```commandline python collect_data.py NorthKestevenDistrictCouncil https://www.n-kesteven.org.uk/bins/display?uprn=XXXXXXXX ``` @@ -2513,7 +2764,7 @@ Note: Replace XXXXXXXX with your UPRN. --- -### North Lanarkshire Council +### North Lanarkshire ```commandline python collect_data.py NorthLanarkshireCouncil https://www.northlanarkshire.gov.uk/bin-collection-dates/XXXXXXXXXXX/XXXXXXXXXXX ``` @@ -2522,7 +2773,7 @@ Note: Follow the instructions [here](https://www.northlanarkshire.gov.uk/bin-col --- -### North Lincolnshire Council +### North Lincolnshire ```commandline python collect_data.py NorthLincolnshireCouncil https://www.northlincs.gov.uk/bins-waste-and-recycling/bin-and-box-collection-dates/ -s -u XXXXXXXX ``` @@ -2534,7 +2785,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### North Norfolk District Council +### North Norfolk ```commandline python collect_data.py NorthNorfolkDistrictCouncil https://www.north-norfolk.gov.uk/ -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -2548,7 +2799,7 @@ Note: Pass the name of the street with the house number parameter, wrapped in do --- -### North Northamptonshire Council +### North Northamptonshire ```commandline python collect_data.py NorthNorthamptonshireCouncil https://cms.northnorthants.gov.uk/bin-collection-search/calendarevents/100031021318/2023-10-17/2023-10-01 -s -u XXXXXXXX ``` @@ -2560,7 +2811,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### North Somerset Council +### North Somerset ```commandline python collect_data.py NorthSomersetCouncil https://forms.n-somerset.gov.uk/Waste/CollectionSchedule -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -2573,7 +2824,7 @@ Note: Pass the postcode and UPRN. You can find the UPRN using [FindMyAddress](ht --- -### North Tyneside Council +### North Tyneside ```commandline python collect_data.py NorthTynesideCouncil https://my.northtyneside.gov.uk/category/81/bin-collection-dates -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -2586,7 +2837,7 @@ Note: Pass the postcode and UPRN. You can find the UPRN using [FindMyAddress](ht --- -### North West Leicestershire Council +### North West Leicestershire ```commandline python collect_data.py NorthWestLeicestershire https://www.nwleics.gov.uk/pages/collection_information -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -2600,7 +2851,7 @@ Note: Pass the postcode and UPRN. This parser requires a Selenium webdriver. --- -### North Yorkshire Council +### North Yorkshire ```commandline python collect_data.py NorthYorkshire https://www.northyorks.gov.uk/bin-calendar/lookup -s -u XXXXXXXX ``` @@ -2612,7 +2863,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### Northumberland Council +### Northumberland ```commandline python collect_data.py NorthumberlandCouncil https://www.northumberland.gov.uk/Waste/Household-waste/Household-bin-collections/Bin-Calendars.aspx -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -2626,7 +2877,7 @@ Note: Pass the house number and postcode in their respective parameters. This pa --- -### Norwich City Council +### Norwich ```commandline python collect_data.py NorwichCityCouncil https://www.norwich.gov.uk -u XXXXXXXX ``` @@ -2637,7 +2888,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Nottingham City Council +### Nottingham ```commandline python collect_data.py NottinghamCityCouncil https://geoserver.nottinghamcity.gov.uk/bincollections2/api/collection/100031540180 -s -u XXXXXXXX ``` @@ -2649,7 +2900,7 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- -### Nuneaton and Bedworth Borough Council +### Nuneaton and Bedworth ```commandline python collect_data.py NuneatonBedworthBoroughCouncil https://www.nuneatonandbedworth.gov.uk -s -n XX ``` @@ -2661,7 +2912,7 @@ Note: Pass the name of the street ONLY in the house number parameter, wrapped in --- -### Oadby & Wigston Borough Council +### Oadby and Wigston ```commandline python collect_data.py OadbyAndWigstonBoroughCouncil https://my.oadby-wigston.gov.uk -u XXXXXXXX ``` @@ -2672,7 +2923,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Oldham Council +### Oldham ```commandline python collect_data.py OldhamCouncil https://portal.oldham.gov.uk/bincollectiondates/details?uprn=422000033556 ``` @@ -2681,7 +2932,7 @@ Note: Replace UPRN in URL with your own UPRN. --- -### Oxford City Council +### Oxford ```commandline python collect_data.py OxfordCityCouncil https://www.oxford.gov.uk/xfp/form/142 -u XXXXXXXX -p "XXXX XXX" ``` @@ -2693,7 +2944,30 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Perth and Kinross Council +### Pembrokeshire +```commandline +python collect_data.py PembrokeshireCountyCouncil https://nearest.pembrokeshire.gov.uk/property/XXXXXXXXXX +``` + +Note: Replace XXXXXXXX with your UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find it. + +--- + +### Peterborough +```commandline +python collect_data.py PeterboroughCityCouncil https://report.peterborough.gov.uk/waste -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ +``` +Additional parameters: +- `-s` - skip get URL +- `-p` - postcode +- `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) + +Note: Pass the full address as it appears o nthe Peterborough website and postcode in their respective parameters. This parser requires a Selenium webdriver. + +--- + +### Perth and Kinross ```commandline python collect_data.py PerthAndKinrossCouncil https://www.pkc.gov.uk -u XXXXXXXX ``` @@ -2704,7 +2978,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Plymouth Council +### Plymouth ```commandline python collect_data.py PlymouthCouncil https://www.plymouth.gov.uk -u XXXXXXXX ``` @@ -2715,7 +2989,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Portsmouth City Council +### Portsmouth ```commandline python collect_data.py PortsmouthCityCouncil https://my.portsmouth.gov.uk/en/AchieveForms/?form_uri=sandbox-publish://AF-Process-26e27e70-f771-47b1-a34d-af276075cede/AF-Stage-cd7cc291-2e59-42cc-8c3f-1f93e132a2c9/definition.json&redirectlink=%2F&cancelRedirectLink=%2F -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -2729,7 +3003,7 @@ Note: Pass the postcode and UPRN. This parser requires a Selenium webdriver. --- -### Powys Council +### Powys ```commandline python collect_data.py PowysCouncil https://www.powys.gov.uk -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -2741,7 +3015,7 @@ Additional parameters: --- -### Preston City Council +### Preston ```commandline python collect_data.py PrestonCityCouncil https://selfservice.preston.gov.uk/service/Forms/FindMyNearest.aspx?Service=bins -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -2755,7 +3029,7 @@ Note: Pass the house number and postcode in their respective parameters. This pa --- -### Reading Borough Council +### Reading ```commandline python collect_data.py ReadingBoroughCouncil https://api.reading.gov.uk/api/collections/XXXXXXXX ``` @@ -2764,7 +3038,7 @@ Note: Replace XXXXXXXX with your property's UPRN. --- -### Redcar and Cleveland Council +### Redcar and Cleveland ```commandline python collect_data.py RedcarandClevelandCouncil https://www.redcar-cleveland.gov.uk -s -p "XXXX XXX" -n XX ``` @@ -2777,7 +3051,7 @@ Note: Pass the house name/number and postcode in their respective parameters --- -### Redditch Borough Council +### Redditch ```commandline python collect_data.py RedditchBoroughCouncil https://redditchbc.gov.uk -u XXXXXXXX ``` @@ -2788,7 +3062,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Reigate and Banstead Borough Council +### Reigate and Banstead ```commandline python collect_data.py ReigateAndBansteadBoroughCouncil https://www.reigate-banstead.gov.uk/ -s -u XXXXXXXX -w http://HOST:PORT/ ``` @@ -2801,9 +3075,9 @@ Note: To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co. --- -### Renfrewshire Council +### Renfrewshire ```commandline -python collect_data.py RenfrewshireCouncil https://www.renfrewshire.gov.uk/article/2320/Check-your-bin-collection-day -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ +python collect_data.py RenfrewshireCouncil https://www.renfrewshire.gov.uk/bin-day -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` Additional parameters: - `-s` - skip get URL @@ -2811,11 +3085,11 @@ Additional parameters: - `-n` - house number - `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: Pass the house name/number and postcode in their respective parameters. This parser requires a Selenium webdriver. +Note: Pass the full address as it appears on the website. This parser requires a Selenium webdriver. --- -### Rhondda Cynon Taff Council +### Rhondda Cynon Taff ```commandline python collect_data.py RhonddaCynonTaffCouncil https://www.rctcbc.gov.uk/EN/Resident/RecyclingandWaste/RecyclingandWasteCollectionDays.aspx -s -u XXXXXXXX ``` @@ -2827,7 +3101,7 @@ Note: To get the UPRN, you can use [FindMyAddress](https://www.findmyaddress.co. --- -### Rochdale Council +### Rochdale ```commandline python collect_data.py RochdaleCouncil https://webforms.rochdale.gov.uk/BinCalendar -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -2840,7 +3114,7 @@ Note: Provide your UPRN and postcode. You can find your UPRN using [FindMyAddres --- -### Rochford Council +### Rochford ```commandline python collect_data.py RochfordCouncil https://www.rochford.gov.uk/online-bin-collections-calendar ``` @@ -2849,7 +3123,7 @@ Note: No extra parameters are required. Dates presented should be read as 'week --- -### Rother District Council +### Rother ```commandline python collect_data.py RotherDistrictCouncil https://www.rother.gov.uk -u XXXXXXXX ``` @@ -2860,7 +3134,7 @@ Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your U --- -### Rotherham Council +### Rotherham ```commandline python collect_data.py RotherhamCouncil https://www.rotherham.gov.uk/bin-collections?address=XXXXXXXXX&submit=Submit -u XXXXXXXX ``` @@ -2871,7 +3145,7 @@ Note: Replace `XXXXXXXXX` with your UPRN in the URL. You can find your UPRN usin --- -### Royal Borough of Greenwich +### Greenwich ```commandline python collect_data.py RoyalBoroughofGreenwich https://www.royalgreenwich.gov.uk -s -p "XXXX XXX" -n XX ``` @@ -2884,20 +3158,21 @@ Note: Provide your house number in the `house_number` parameter and your postcod --- -### Rugby Borough Council +### Rugby ```commandline -python collect_data.py RugbyBoroughCouncil https://www.rugby.gov.uk/check-your-next-bin-day -s -u XXXXXXXX -p "XXXX XXX" +python collect_data.py RugbyBoroughCouncil https://www.rugby.gov.uk/check-your-next-bin-day -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` Additional parameters: - `-s` - skip get URL - `-u` - UPRN - `-p` - postcode +- `-w` - remote Selenium web driver URL (required for Home Assistant) Note: Provide your UPRN and postcode. You can find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search). --- -### Runnymede Borough Council +### Runnymede ```commandline python collect_data.py RunnymedeBoroughCouncil https://www.runnymede.gov.uk/ -s -u XXXXXXXX ``` @@ -2909,7 +3184,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Rushcliffe Borough Council +### Rushcliffe ```commandline python collect_data.py RushcliffeBoroughCouncil https://www.rushcliffe.gov.uk/ -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -2923,7 +3198,7 @@ Note: Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddr --- -### Rushmoor Council +### Rushmoor ```commandline python collect_data.py RushmoorCouncil https://www.rushmoor.gov.uk/Umbraco/Api/BinLookUpWorkAround/Get?selectedAddress=XXXXXXXXXX ``` @@ -2932,7 +3207,7 @@ Note: Replace `XXXXXXXXXX` with your UPRN, which you can find using [FindMyAddre --- -### Salford City Council +### Salford ```commandline python collect_data.py SalfordCityCouncil https://www.salford.gov.uk/bins-and-recycling/bin-collection-days/your-bin-collections -s -u XXXXXXXX ``` @@ -2944,7 +3219,7 @@ Note: Provide your UPRN. You can find it using [FindMyAddress](https://www.findm --- -### Sandwell Borough Council +### Sandwell ```commandline python collect_data.py SandwellBoroughCouncil https://www.sandwell.gov.uk -s -u XXXXXXXX ``` @@ -2956,7 +3231,7 @@ Note: Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddr --- -### Sefton Council +### Sefton ```commandline python collect_data.py SeftonCouncil https://www.sefton.gov.uk -p "XXXX XXX" -n XX ``` @@ -2968,7 +3243,7 @@ Note: Pass the postcode and house number in their respective arguments, both wra --- -### Sevenoaks District Council +### Sevenoaks ```commandline python collect_data.py SevenoaksDistrictCouncil https://sevenoaks-dc-host01.oncreate.app/w/webpage/waste-collection-day -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -2982,7 +3257,7 @@ Note: Pass the house name/number in the `house_number` parameter, wrapped in dou --- -### Sheffield City Council +### Sheffield ```commandline python collect_data.py SheffieldCityCouncil https://wasteservices.sheffield.gov.uk/property/XXXXXXXXXXX ``` @@ -2991,7 +3266,7 @@ Note: Follow the instructions [here](https://wasteservices.sheffield.gov.uk/) un --- -### Shropshire Council +### Shropshire ```commandline python collect_data.py ShropshireCouncil https://bins.shropshire.gov.uk/property/XXXXXXXXXXX ``` @@ -3000,41 +3275,42 @@ Note: Follow the instructions [here](https://bins.shropshire.gov.uk/) until you --- -### Solihull Council +### Slough ```commandline -python collect_data.py SolihullCouncil https://digital.solihull.gov.uk/BinCollectionCalendar/Calendar.aspx?UPRN=XXXXXXXX +python collect_data.py SloughBoroughCouncil https://www.slough.gov.uk/bin-collections -s -p "XXXX XXX" -w http://HOST:PORT/ ``` +Additional parameters: +- `-s` - skip get URL +- `-p` - postcode +- `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: Replace `XXXXXXXX` with your UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. +Note: Pass the UPRN and postcode in their respective parameters. This parser requires a Selenium webdriver. --- -### Somerset Council +### Solihull ```commandline -python collect_data.py SomersetCouncil https://www.somerset.gov.uk/ -s -u XXXXXXXX -p "XXXX XXX" +python collect_data.py SolihullCouncil https://digital.solihull.gov.uk/BinCollectionCalendar/Calendar.aspx?UPRN=XXXXXXXX ``` -Additional parameters: -- `-s` - skip get URL -- `-u` - UPRN -- `-p` - postcode -Note: Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search). +Note: Replace `XXXXXXXX` with your UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. --- -### Southampton City Council +### Somerset ```commandline -python collect_data.py SouthamptonCityCouncil https://www.southampton.gov.uk -s -u XXXXXXXX +python collect_data.py SomersetCouncil https://www.somerset.gov.uk/ -s -u XXXXXXXX -p "XXXX XXX" ``` Additional parameters: - `-s` - skip get URL - `-u` - UPRN +- `-p` - postcode -Note: Pass the UPRN. You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search). +Note: Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search). --- -### South Ayrshire Council +### South Ayrshire ```commandline python collect_data.py SouthAyrshireCouncil https://www.south-ayrshire.gov.uk/ -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -3047,7 +3323,7 @@ Note: Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddr --- -### South Cambridgeshire Council +### South Cambridgeshire ```commandline python collect_data.py SouthCambridgeshireCouncil https://www.scambs.gov.uk/recycling-and-bins/find-your-household-bin-collection-day/ -s -p "XXXX XXX" -n XX ``` @@ -3060,7 +3336,7 @@ Note: Provide your house number in the `house_number` parameter and postcode in --- -### South Derbyshire District Council +### South Derbyshire ```commandline python collect_data.py SouthDerbyshireDistrictCouncil https://maps.southderbyshire.gov.uk/iShareLIVE.web//getdata.aspx?RequestType=LocalInfo&ms=mapsources/MyHouse&format=JSONP&group=Recycling%20Bins%20and%20Waste|Next%20Bin%20Collections&uid=XXXXXXXX -u XXXXXXXX ``` @@ -3071,7 +3347,7 @@ Note: Replace `XXXXXXXX` with your UPRN. You can find your UPRN using [FindMyAdd --- -### South Gloucestershire Council +### South Gloucestershire ```commandline python collect_data.py SouthGloucestershireCouncil https://beta.southglos.gov.uk/waste-and-recycling-collection-date -s -u XXXXXXXX ``` @@ -3083,7 +3359,7 @@ Note: Provide your UPRN. You can find it using [FindMyAddress](https://www.findm --- -### South Hams District Council +### South Hams ```commandline python collect_data.py SouthHamsDistrictCouncil https://www.southhams.gov.uk -u XXXXXXXX ``` @@ -3094,7 +3370,22 @@ Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your U --- -### South Kesteven District Council +### South Holland +```commandline +python collect_data.py SouthHollandDistrictCouncil https://www.sholland.gov.uk/mycollections -s -u XXXXXXXX -p "XXXX XXX" -n XX -w http://HOST:PORT/ +``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN +- `-p` - postcode +- `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) + +Note: Pass the UPRN and postcode in their respective parameters. This parser requires a Selenium webdriver. + +--- + +### South Kesteven ```commandline python collect_data.py SouthKestevenDistrictCouncil https://pre.southkesteven.gov.uk/BinSearch.aspx -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -3108,7 +3399,7 @@ Note: Provide your full address in the `house_number` parameter and your postcod --- -### South Lanarkshire Council +### South Lanarkshire ```commandline python collect_data.py SouthLanarkshireCouncil https://www.southlanarkshire.gov.uk/directory_record/XXXXX/XXXXX ``` @@ -3117,7 +3408,7 @@ Note: Follow the instructions [here](https://www.southlanarkshire.gov.uk/info/20 --- -### South Norfolk Council +### South Norfolk ```commandline python collect_data.py SouthNorfolkCouncil https://www.southnorfolkandbroadland.gov.uk/rubbish-recycling/south-norfolk-bin-collection-day-finder -s -u XXXXXXXX ``` @@ -3129,7 +3420,7 @@ Note: Provide your UPRN. Find it using [FindMyAddress](https://www.findmyaddress --- -### South Oxfordshire Council +### South Oxfordshire ```commandline python collect_data.py SouthOxfordshireCouncil https://www.southoxon.gov.uk/south-oxfordshire-district-council/recycling-rubbish-and-waste/when-is-your-collection-day/ -s -u XXXXXXXX ``` @@ -3141,7 +3432,7 @@ Note: Provide your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/sea --- -### South Ribble Council +### South Ribble ```commandline python collect_data.py SouthRibbleCouncil https://forms.chorleysouthribble.gov.uk/xfp/form/70 -u XXXXXXXX -p "XXXX XXX" ``` @@ -3153,7 +3444,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### South Staffordshire District Council +### South Staffordshire ```commandline python collect_data.py SouthStaffordshireDistrictCouncil https://www.sstaffs.gov.uk/where-i-live?uprn=200004523954 -u XXXXXXXX ``` @@ -3164,7 +3455,7 @@ Note: The URL needs to be `https://www.sstaffs.gov.uk/where-i-live?uprn=`. R --- -### Staffordshire Moorlands District Council +### Staffordshire Moorlands ```commandline python collect_data.py StaffordshireMoorlandsDistrictCouncil https://www.staffsmoorlands.gov.uk/ -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -3250,7 +3553,7 @@ Note: Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddr --- -### Stevenage Borough Council +### Stevenage ```commandline python collect_data.py StevenageBoroughCouncil https://www.stevenage.gov.uk -u XXXXXXXX ``` @@ -3261,7 +3564,21 @@ Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your U --- -### Stockport Borough Council +### Stirling +```commandline +python collect_data.py StirlingCouncil https://www.stirling.gov.uk/bins-and-recycling/bin-collection-dates-search/ -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ +``` +Additional parameters: +- `-s` - skip get URL +- `-p` - postcode +- `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) + +Note: Use the full address as it appears on the drop-down on the site when you search by postcode. + +--- + +### Stockport ```commandline python collect_data.py StockportBoroughCouncil https://myaccount.stockport.gov.uk/bin-collections/show/XXXXXXXX ``` @@ -3270,7 +3587,7 @@ Note: Replace `XXXXXXXX` with your UPRN. --- -### Stockton On Tees Council +### Stockton-on-Tees ```commandline python collect_data.py StocktonOnTeesCouncil https://www.stockton.gov.uk -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -3282,7 +3599,7 @@ Additional parameters: --- -### Stoke-on-Trent City Council +### Stoke-on-Trent ```commandline python collect_data.py StokeOnTrentCityCouncil https://www.stoke.gov.uk/jadu/custom/webserviceLookUps/BarTecWebServices_missed_bin_calendar.php?UPRN=XXXXXXXXXX ``` @@ -3291,7 +3608,7 @@ Note: Replace `XXXXXXXXXX` with your property's UPRN. --- -### Stratford Upon Avon Council +### Stratford-on-Avon ```commandline python collect_data.py StratfordUponAvonCouncil https://www.stratford.gov.uk/waste-recycling/when-we-collect.cfm/part/calendar -s -u XXXXXXXX ``` @@ -3303,7 +3620,7 @@ Note: Provide your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/sea --- -### Stroud District Council +### Stroud ```commandline python collect_data.py StroudDistrictCouncil https://www.stroud.gov.uk/my-house?uprn=100120512183&postcode=GL10+3BH -u XXXXXXXX -p "XXXX XXX" ``` @@ -3315,7 +3632,7 @@ Note: Provide your UPRN and postcode. Replace the UPRN and postcode in the URL w --- -### Sunderland City Council +### Sunderland ```commandline python collect_data.py SunderlandCityCouncil https://webapps.sunderland.gov.uk/WEBAPPS/WSS/Sunderland_Portal/Forms/bindaychecker.aspx -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -3329,7 +3646,7 @@ Note: Provide your house number (without quotes) and postcode (wrapped in double --- -### Surrey Heath Borough Council / Joint Waste Solutions +### Surrey Heath ```commandline python collect_data.py SurreyHeathBoroughCouncil https://asjwsw-wrpsurreyheathmunicipal-live.whitespacews.com/ -s -p "XXXX XXX" -n XX ``` @@ -3342,7 +3659,7 @@ Note: Provide your house number in the `house_number` parameter and postcode in --- -### Swale Borough Council +### Swale ```commandline python collect_data.py SwaleBoroughCouncil https://swale.gov.uk/bins-littering-and-the-environment/bins/collection-days -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -3356,7 +3673,7 @@ Note: Provide your house number in the `house_number` parameter and postcode in --- -### Swansea Council +### Swansea ```commandline python collect_data.py SwanseaCouncil https://www1.swansea.gov.uk/recyclingsearch/ -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -3369,7 +3686,7 @@ Note: Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https --- -### Swindon Borough Council +### Swindon ```commandline python collect_data.py SwindonBoroughCouncil https://www.swindon.gov.uk -u XXXXXXXX ``` @@ -3380,7 +3697,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Tameside Metropolitan Borough Council +### Tameside ```commandline python collect_data.py TamesideMBCouncil http://lite.tameside.gov.uk/BinCollections/CollectionService.svc/GetBinCollection -s -u XXXXXXXX ``` @@ -3392,7 +3709,7 @@ Note: Provide your UPRN. You can find it using [FindMyAddress](https://www.findm --- -### Tandridge District Council +### Tandridge ```commandline python collect_data.py TandridgeDistrictCouncil https://tdcws01.tandridge.gov.uk/TDCWebAppsPublic/tfaBranded/408?utm_source=pressrelease&utm_medium=smposts&utm_campaign=check_my_bin_day -s -u XXXXXXXX ``` @@ -3404,7 +3721,7 @@ Note: Provide your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/sea --- -### Teignbridge Council +### Teignbridge ```commandline python collect_data.py TeignbridgeCouncil https://www.google.co.uk -u XXXXXXXX -w http://HOST:PORT/ ``` @@ -3416,7 +3733,7 @@ Note: Provide Google as the URL as the real URL breaks the integration. You will --- -### Telford and Wrekin Council +### Telford and Wrekin ```commandline python collect_data.py TelfordAndWrekinCouncil https://dac.telford.gov.uk/bindayfinder/ -s -u XXXXXXXX ``` @@ -3428,7 +3745,19 @@ Note: Provide your UPRN. Find it using [FindMyAddress](https://www.findmyaddress --- -### Tendring District Council +### Tewkesbury +```commandline +python collect_data.py TewkesburyBoroughCouncil https://tewkesbury.gov.uk/services/waste-and-recycling/ -s -u XXXXXXXX +``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN + +Note: Provide your UPRN. Find it using [FindMyAddress](https://www.findmyaddress.co.uk/search). + +--- + +### Tendring ```commandline python collect_data.py TendringDistrictCouncil https://tendring-self.achieveservice.com/en/service/Rubbish_and_recycling_collection_days -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -3442,7 +3771,7 @@ Note: Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https --- -### Test Valley Borough Council +### Test Valley ```commandline python collect_data.py TestValleyBoroughCouncil https://testvalley.gov.uk/wasteandrecycling/when-are-my-bins-collected -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -3455,18 +3784,19 @@ Note: Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddr --- -### Thanet District Council +### Thanet ```commandline -python collect_data.py ThanetDistrictCouncil https://www.thanet.gov.uk -u XXXXXXXX +python collect_data.py ThanetDistrictCouncil https://www.thanet.gov.uk -u XXXXXXXX -w http://HOST:PORT/ ``` Additional parameters: - `-u` - UPRN +- `-w` - remote Selenium web driver URL (required for Home Assistant) Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN. --- -### Three Rivers District Council +### Three Rivers ```commandline python collect_data.py ThreeRiversDistrictCouncil https://my.threerivers.gov.uk/en/AchieveForms/?mode=fill&consentMessage=yes&form_uri=sandbox-publish://AF-Process-52df96e3-992a-4b39-bba3-06cfaabcb42b/AF-Stage-01ee28aa-1584-442c-8d1f-119b6e27114a/definition.json&process=1&process_uri=sandbox-processes://AF-Process-52df96e3-992a-4b39-bba3-06cfaabcb42b&process_id=AF-Process-52df96e3-992a-4b39-bba3-06cfaabcb42b&noLoginPrompt=1 -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -3480,7 +3810,7 @@ Note: Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https --- -### Thurrock Council +### Thurrock ```commandline python collect_data.py ThurrockCouncil https://www.thurrock.gov.uk -s -p "XXXX XXX" -n XX ``` @@ -3493,7 +3823,7 @@ Note: Use the House Number field to pass the DAY of the week for your collection --- -### Tonbridge and Malling Borough Council +### Tonbridge and Malling ```commandline python collect_data.py TonbridgeAndMallingBC https://www.tmbc.gov.uk/ -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -3506,19 +3836,20 @@ Note: Provide your UPRN and postcode. --- -### Torbay Council +### Torbay ```commandline -python collect_data.py TorbayCouncil https://www.torbay.gov.uk/recycling/bin-collections/ -s -u XXXXXXXX +python collect_data.py TorbayCouncil https://www.torbay.gov.uk/recycling/bin-collections/ -s -u XXXXXXXX -p "XXXX XXX" ``` Additional parameters: - `-s` - skip get URL - `-u` - UPRN +- `-p` - postcode Note: Provide your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find it. --- -### Torridge District Council +### Torridge ```commandline python collect_data.py TorridgeDistrictCouncil https://collections-torridge.azurewebsites.net/WebService2.asmx -s -u XXXXXXXX ``` @@ -3530,7 +3861,7 @@ Note: Provide your UPRN. --- -### Tunbridge Wells Council +### Tunbridge Wells ```commandline python collect_data.py TunbridgeWellsCouncil https://tunbridgewells.gov.uk -u XXXXXXXX ``` @@ -3541,7 +3872,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Uttlesford District Council +### Uttlesford ```commandline python collect_data.py UttlesfordDistrictCouncil https://bins.uttlesford.gov.uk/ -s -u XXXXXXXX -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -3556,7 +3887,7 @@ Note: Provide your full address in the `house_number` parameter and your postcod --- -### Vale of Glamorgan Council +### The Vale of Glamorgan ```commandline python collect_data.py ValeofGlamorganCouncil https://www.valeofglamorgan.gov.uk/en/living/Recycling-and-Waste/ -s -u XXXXXXXX ``` @@ -3568,7 +3899,7 @@ Note: Provide your UPRN. Find it using [FindMyAddress](https://www.findmyaddress --- -### Vale of White Horse Council +### Vale of White Horse ```commandline python collect_data.py ValeofWhiteHorseCouncil https://eform.whitehorsedc.gov.uk/ebase/BINZONE_DESKTOP.eb -s -u XXXXXXXX ``` @@ -3580,7 +3911,7 @@ Note: Provide your UPRN. --- -### Wakefield City Council +### Wakefield ```commandline python collect_data.py WakefieldCityCouncil https://www.wakefield.gov.uk/where-i-live/?uprn=XXXXXXXXXXX&a=XXXXXXXXXXX&usrn=XXXXXXXXXXX&e=XXXXXXXXXXX&n=XXXXXXXXXXX&p=XXXXXXXXXXX -s -w http://HOST:PORT/ ``` @@ -3592,7 +3923,7 @@ Note: Follow the instructions [here](https://www.wakefield.gov.uk/where-i-live/) --- -### Walsall Council +### Walsall ```commandline python collect_data.py WalsallCouncil https://cag.walsall.gov.uk/ -u XXXXXXXX ``` @@ -3618,7 +3949,7 @@ Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your U --- -### Wandsworth Council +### Wandsworth ```commandline python collect_data.py WandsworthCouncil https://www.wandsworth.gov.uk -u XXXXXXXX ``` @@ -3629,7 +3960,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Warrington Borough Council +### Warrington ```commandline python collect_data.py WarringtonBoroughCouncil https://www.warrington.gov.uk -u XXXXXXXX ``` @@ -3640,7 +3971,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Warwick District Council +### Warwick ```commandline python collect_data.py WarwickDistrictCouncil https://estates7.warwickdc.gov.uk/PropertyPortal/Property/Recycling/XXXXXXXX ``` @@ -3649,7 +3980,7 @@ Note: Replace `XXXXXXXX` with your UPRN. --- -### Watford Borough Council +### Watford ```commandline python collect_data.py WatfordBoroughCouncil https://www.watford.gov.uk -u XXXXXXXX ``` @@ -3660,7 +3991,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Waverley Borough Council +### Waverley ```commandline python collect_data.py WaverleyBoroughCouncil https://wav-wrp.whitespacews.com/ -s -p "XXXX XXX" -n XX ``` @@ -3673,7 +4004,7 @@ Note: Follow the instructions [here](https://wav-wrp.whitespacews.com/#!) until --- -### Wealden District Council +### Wealden ```commandline python collect_data.py WealdenDistrictCouncil https://www.wealden.gov.uk/recycling-and-waste/bin-search/ -s -u XXXXXXXX ``` @@ -3685,7 +4016,7 @@ Note: Provide your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/sea --- -### Welhat Council +### Welwyn Hatfield ```commandline python collect_data.py WelhatCouncil https://www.welhat.gov.uk/xfp/form/214 -u XXXXXXXX -p "XXXX XXX" ``` @@ -3697,7 +4028,7 @@ Note: Provide your UPRN and postcode. --- -### West Berkshire Council +### West Berkshire ```commandline python collect_data.py WestBerkshireCouncil https://www.westberks.gov.uk/binday -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -3711,7 +4042,7 @@ Note: Provide your house number in the `house_number` parameter and postcode in --- -### West Dunbartonshire Council +### West Dunbartonshire ```commandline python collect_data.py WestDunbartonshireCouncil https://www.west-dunbarton.gov.uk/ -u XXXXXXXX ``` @@ -3722,7 +4053,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### West Lancashire Borough Council +### West Lancashire ```commandline python collect_data.py WestLancashireBoroughCouncil https://www.westlancs.gov.uk -u XXXXXXXX -p "XXXX XXX" ``` @@ -3734,7 +4065,7 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### West Lindsey District Council +### West Lindsey ```commandline python collect_data.py WestLindseyDistrictCouncil https://www.west-lindsey.gov.uk/ -s -p "XXXX XXX" -n XX ``` @@ -3747,7 +4078,7 @@ Note: Provide your house name/number in the `house_number` parameter, and postco --- -### West Lothian Council +### West Lothian ```commandline python collect_data.py WestLothianCouncil https://www.westlothian.gov.uk/ -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -3761,7 +4092,7 @@ Note: Provide your house name/number in the `house_number` parameter (wrapped in --- -### West Morland and Furness Council +### Westmorland and Furness ```commandline python collect_data.py WestMorlandAndFurness https://www.westmorlandandfurness.gov.uk/ -u XXXXXXXX ``` @@ -3772,7 +4103,7 @@ Note: Provide your UPRN. You can find your UPRN using [FindMyAddress](https://ww --- -### West Northamptonshire Council +### West Northamptonshire ```commandline python collect_data.py WestNorthamptonshireCouncil https://www.westnorthants.gov.uk -s -u XXXXXXXX ``` @@ -3784,7 +4115,7 @@ Note: Provide your UPRN. You can find your UPRN using [FindMyAddress](https://ww --- -### West Oxfordshire District Council +### West Oxfordshire ```commandline python collect_data.py WestOxfordshireDistrictCouncil https://community.westoxon.gov.uk/s/waste-collection-enquiry -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -3798,7 +4129,7 @@ Note: Provide your house number in the `house_number` parameter and your postcod --- -### West Suffolk Council +### West Suffolk ```commandline python collect_data.py WestSuffolkCouncil https://maps.westsuffolk.gov.uk/MyWestSuffolk.aspx -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -3811,7 +4142,7 @@ Note: Provide your UPRN and postcode. You can find your UPRN using [FindMyAddres --- -### Wigan Borough Council +### Wigan ```commandline python collect_data.py WiganBoroughCouncil https://apps.wigan.gov.uk/MyNeighbourhood/ -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -3824,7 +4155,7 @@ Note: Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https --- -### Wiltshire Council +### Wiltshire ```commandline python collect_data.py WiltshireCouncil https://ilambassadorformsprod.azurewebsites.net/wastecollectiondays/index -s -u XXXXXXXX -p "XXXX XXX" ``` @@ -3837,7 +4168,7 @@ Note: Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddr --- -### Winchester City Council +### Winchester ```commandline python collect_data.py WinchesterCityCouncil https://iportal.itouchvision.com/icollectionday/collection-day -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -3851,7 +4182,7 @@ Note: Provide your house name/number in the `house_number` parameter (wrapped in --- -### Windsor and Maidenhead Council +### Windsor and Maidenhead ```commandline python collect_data.py WindsorAndMaidenheadCouncil https://forms.rbwm.gov.uk/bincollections?uprn= -s -u XXXXXXXX -w http://HOST:PORT/ ``` @@ -3864,7 +4195,7 @@ Note: Provide your UPRN. You can find it using [FindMyAddress](https://www.findm --- -### Wirral Council +### Wirral ```commandline python collect_data.py WirralCouncil https://www.wirral.gov.uk -u XXXXXXXX ``` @@ -3875,7 +4206,7 @@ Note: In the `uprn` field, enter your street name and suburb separated by a comm --- -### Woking Borough Council / Joint Waste Solutions +### Woking ```commandline python collect_data.py WokingBoroughCouncil https://asjwsw-wrpwokingmunicipal-live.whitespacews.com/ -s -p "XXXX XXX" -n XX ``` @@ -3888,7 +4219,7 @@ Note: Provide your house number in the `house_number` parameter and postcode in --- -### Wokingham Borough Council +### Wokingham ```commandline python collect_data.py WokinghamBoroughCouncil https://www.wokingham.gov.uk/rubbish-and-recycling/waste-collection/find-your-bin-collection-day -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` @@ -3902,7 +4233,7 @@ Note: Provide your house number in the `house_number` parameter and postcode in --- -### Wolverhampton City Council +### Wolverhampton ```commandline python collect_data.py WolverhamptonCityCouncil https://www.wolverhampton.gov.uk -u XXXXXXXX -p "XXXX XXX" ``` @@ -3914,7 +4245,7 @@ Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your U --- -### Worcester City Council +### Worcester ```commandline python collect_data.py WorcesterCityCouncil https://www.Worcester.gov.uk -u XXXXXXXX ``` @@ -3925,7 +4256,22 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc --- -### Wychavon District Council +### Wrexham +```commandline +python collect_data.py WrexhamCountyBoroughCouncil https://www.wrexham.gov.uk/service/when-are-my-bins-collected -s -u XXXXXXXX -p "XXXX XXX" -n XX -w http://HOST:PORT/ +``` +Additional parameters: +- `-s` - skip get URL +- `-u` - UPRN +- `-p` - postcode +- `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) + +Note: Provide your house number in the `house_number` parameter and postcode in the `postcode` parameter. + +--- + +### Wychavon ```commandline python collect_data.py WychavonDistrictCouncil https://selfservice.wychavon.gov.uk/wdcroundlookup/wdc_search.jsp -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` @@ -3939,7 +4285,7 @@ Note: Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https --- -### Wyre Council +### Wyre ```commandline python collect_data.py WyreCouncil https://www.wyre.gov.uk/bins-rubbish-recycling -s -u XXXXXXXX ``` @@ -3951,7 +4297,7 @@ Note: Provide your UPRN. Find your UPRN using [FindMyAddress](https://www.findmy --- -### Wyre Forest District Council +### Wyre Forest ```commandline python collect_data.py WyreForestDistrictCouncil https://www.wyreforestdc.gov.uk -s -n XX ``` @@ -3963,7 +4309,7 @@ Note: Use the House Number field to pass the DAY of the week for your collection --- -### York Council +### York ```commandline python collect_data.py YorkCouncil https://waste-api.york.gov.uk/api/Collections/GetBinCollectionDataForUprn/ -s -u XXXXXXXX ``` From c9960713294d3f03dc52863e1d60c7d285c9c6fd Mon Sep 17 00:00:00 2001 From: sheep_wizard <> Date: Fri, 29 Aug 2025 15:38:50 +0000 Subject: [PATCH 118/425] feat: Change buckinghamshire council to get data from endpoint --- .../councils/BuckinghamshireCouncil.py | 175 ++++++++---------- wiki/Councils.md | 9 +- 2 files changed, 79 insertions(+), 105 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/BuckinghamshireCouncil.py b/uk_bin_collection/uk_bin_collection/councils/BuckinghamshireCouncil.py index a26a412ac6..320aac2765 100644 --- a/uk_bin_collection/uk_bin_collection/councils/BuckinghamshireCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/BuckinghamshireCouncil.py @@ -1,11 +1,26 @@ -from bs4 import BeautifulSoup -from selenium.webdriver.common.by import By -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.support.wait import WebDriverWait +import json +from dataclasses import asdict, dataclass +from typing import Literal -from uk_bin_collection.uk_bin_collection.common import * +import requests +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import padding +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + +from uk_bin_collection.uk_bin_collection.common import check_uprn from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass +key_hex = "F57E76482EE3DC3336495DEDEEF3962671B054FE353E815145E29C5689F72FEC" +iv_hex = "2CBF4FC35C69B82362D393A4F0B9971A" + + +@dataclass +class BuckInput: + P_CLIENT_ID: Literal[152] + P_COUNCIL_ID: Literal[34505] + P_LANG_CODE: Literal["EN"] + P_UPRN: str + class CouncilClass(AbstractGetBinDataClass): """ @@ -14,113 +29,73 @@ class CouncilClass(AbstractGetBinDataClass): implementation. """ - def parse_data(self, page: str, **kwargs) -> dict: - driver = None + def encode_body(self, buck_input: BuckInput): + key = bytes.fromhex(key_hex) + iv = bytes.fromhex(iv_hex) + + json_data = json.dumps(asdict(buck_input)) + data_bytes = json_data.encode("utf-8") + + padder = padding.PKCS7(128).padder() + padded_data = padder.update(data_bytes) + padder.finalize() + + backend = default_backend() + cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend) + encryptor = cipher.encryptor() + ciphertext = encryptor.update(padded_data) + encryptor.finalize() + + return ciphertext.hex() + + def decode_response(self, hex_input: str): + + key = bytes.fromhex(key_hex) + iv = bytes.fromhex(iv_hex) + ciphertext = bytes.fromhex(hex_input) + + backend = default_backend() + cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend) + decryptor = cipher.decryptor() + decrypted_padded = decryptor.update(ciphertext) + decryptor.finalize() + + unpadder = padding.PKCS7(128).unpadder() + plaintext_bytes = unpadder.update(decrypted_padded) + unpadder.finalize() + plaintext = plaintext_bytes.decode("utf-8") + + return json.loads(plaintext) + + def parse_data(self, _: str, **kwargs) -> dict: try: - data = {"bins": []} - user_paon = kwargs.get("paon") - user_postcode = kwargs.get("postcode") - user_uprn = kwargs.get("uprn") - web_driver = kwargs.get("web_driver") - headless = kwargs.get("headless") - check_paon(user_paon) - check_postcode(user_postcode) - - # Create Selenium webdriver - driver = create_webdriver(web_driver, headless, None, __name__) - driver.get(kwargs.get("url")) - - # Click "Check now" button - check_now_button = WebDriverWait(driver, 10).until( - EC.element_to_be_clickable((By.XPATH, "//a[contains(text(), 'Check now')]")) + user_uprn: str = kwargs.get("uprn") or "" + check_uprn(user_uprn) + buck_input = BuckInput( + P_CLIENT_ID=152, P_COUNCIL_ID=34505, P_LANG_CODE="EN", P_UPRN=user_uprn ) - check_now_button.click() - # Wait for the postcode field to appear then populate it - inputElement_postcode = WebDriverWait(driver, 10).until( - EC.presence_of_element_located((By.ID, "postcodeSearch")) - ) - inputElement_postcode.send_keys(user_postcode) + encoded_input = self.encode_body(buck_input) - # Click Find button - find_button = WebDriverWait(driver, 10).until( - EC.element_to_be_clickable((By.XPATH, "//button[contains(text(), 'Find')]")) + session = requests.Session() + response = session.post( + "https://itouchvision.app/portal/itouchvision/kmbd/collectionDay", + data=encoded_input, ) - find_button.click() - - # Wait for the address dropdown and select by UPRN - if user_uprn: - address_option = WebDriverWait(driver, 10).until( - EC.element_to_be_clickable((By.XPATH, f"//option[@value='{user_uprn}']")) - ) - address_option.click() - else: - # Fallback to selecting by address text - address_option = WebDriverWait(driver, 10).until( - EC.element_to_be_clickable( - (By.XPATH, f"//select[@id='addressSelect']//option[contains(., '{user_paon}')]") - ) - ) - address_option.click() - # Wait a moment for the page to update after address selection - import time - time.sleep(2) + output = response.text - # Wait for collection information to appear - try multiple possible selectors - try: - WebDriverWait(driver, 15).until( - EC.presence_of_element_located((By.XPATH, "//h2[contains(text(), 'Your next collections')]")) - ) - except: - # Alternative wait for collection data structure - WebDriverWait(driver, 10).until( - EC.presence_of_element_located((By.XPATH, "//div[contains(@class, 'ant-row') and contains(@class, 'd-flex')]//h3[@class='text-white']")) + decoded_bins = self.decode_response(output) + data: dict[str, list[dict[str, str]]] = {} + data["bins"] = list( + map( + lambda a: { + "type": a["binType"], + "collectionDate": a["collectionDay"].replace("-", "/"), + }, + decoded_bins["collectionDay"], ) - - soup = BeautifulSoup(driver.page_source, features="html.parser") - - # Find all collection items with the specific structure - try multiple class patterns - collection_items = soup.find_all("div", class_=lambda x: x and "ant-col" in x and "ant-col-xs-12" in x) - if not collection_items: - # Fallback to finding items by structure - collection_items = soup.find_all("div", class_=lambda x: x and "p-2" in x and "d-flex" in x and "flex-column" in x) - - current_year = datetime.now().year - current_month = datetime.now().month - - for item in collection_items: - # Extract bin type from h3 element - bin_type_elem = item.find("h3", class_="text-white") - # Extract date from div with specific classes - date_elem = item.find("div", class_="text-white fw-bold") - - if bin_type_elem and date_elem: - bin_type = bin_type_elem.get_text().strip() - date_text = date_elem.get_text().strip() - - try: - collection_date = datetime.strptime(date_text, "%A %d %B") - if (current_month > 10) and (collection_date.month < 3): - collection_date = collection_date.replace(year=(current_year + 1)) - else: - collection_date = collection_date.replace(year=current_year) - - dict_data = { - "type": bin_type, - "collectionDate": collection_date.strftime("%d/%m/%Y"), - } - data["bins"].append(dict_data) - except ValueError: - continue + ) except Exception as e: # Here you can log the exception if needed print(f"An error occurred: {e}") # Optionally, re-raise the exception if you want it to propagate raise - finally: - # This block ensures that the driver is closed regardless of an exception - if driver: - driver.quit() return data diff --git a/wiki/Councils.md b/wiki/Councils.md index 7453f5d9b7..5656173954 100644 --- a/wiki/Councils.md +++ b/wiki/Councils.md @@ -820,13 +820,12 @@ Note: Pass the UPRN and postcode. To get the UPRN, you can use [FindMyAddress](h ### Buckinghamshire Council (Chiltern, South Bucks, Wycombe) ```commandline -python collect_data.py BuckinghamshireCouncil https://iapp.itouchvision.com/iappcollectionday/collection-day/?uuid=FA353FC74600CBE61BE409534D00A8EC09BDA3AC&lang=en -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ +python collect_data.py BuckinghamshireCouncil https://iapp.itouchvision.com/iappcollectionday/collection-dayP -u XXXXXXXX ``` Additional parameters: -- `-s` - skip get URL -- `-p` - postcode -- `-n` - house number -- `-w` - remote Selenium web driver URL (required for Home Assistant) +- `-u` - UPRN + +Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search). Note: Pass the house name/number and postcode in their respective arguments, both wrapped in quotes. From 7e0c76ffa5317140d455528ff49f9543f228c0a2 Mon Sep 17 00:00:00 2001 From: sheep_wizard <> Date: Sat, 30 Aug 2025 11:14:24 +0100 Subject: [PATCH 119/425] update input.json --- uk_bin_collection/tests/input.json | 7 ++----- .../councils/BuckinghamshireCouncil.py | 10 +++++----- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index 9d2775f7d9..0f273871fb 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -377,13 +377,10 @@ "LAD24CD": "E07000172" }, "BuckinghamshireCouncil": { - "house_number": "The Ridings, Magpie Lane, Loudwater, High Wycombe, HP13 7BA", - "postcode": "HP13 7BA", "uprn": "100081093078", "url": "https://www.buckinghamshire.gov.uk/waste-and-recycling/find-out-when-its-your-bin-collection/", - "web_driver": "http://selenium:4444", "wiki_name": "Buckinghamshire", - "wiki_note": "Pass the house name/number and postcode in their respective arguments, both wrapped in quotes.", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", "LAD24CD": "E06000060" }, "BurnleyBoroughCouncil": { @@ -1446,7 +1443,7 @@ "house_number": "71", "postcode": "ME16 8BT", "url": "https://my.maidstone.gov.uk/service/Find-your-bin-day", - "web_driver": "http://selenium:4444", + "web_driver": "http://selenium:4444", "wiki_name": "Maidstone", "wiki_note": "Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver.", "LAD24CD": "E07000110" diff --git a/uk_bin_collection/uk_bin_collection/councils/BuckinghamshireCouncil.py b/uk_bin_collection/uk_bin_collection/councils/BuckinghamshireCouncil.py index 320aac2765..d55a90582d 100644 --- a/uk_bin_collection/uk_bin_collection/councils/BuckinghamshireCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/BuckinghamshireCouncil.py @@ -15,7 +15,7 @@ @dataclass -class BuckInput: +class BucksInput: P_CLIENT_ID: Literal[152] P_COUNCIL_ID: Literal[34505] P_LANG_CODE: Literal["EN"] @@ -29,11 +29,11 @@ class CouncilClass(AbstractGetBinDataClass): implementation. """ - def encode_body(self, buck_input: BuckInput): + def encode_body(self, bucks_input: BucksInput): key = bytes.fromhex(key_hex) iv = bytes.fromhex(iv_hex) - json_data = json.dumps(asdict(buck_input)) + json_data = json.dumps(asdict(bucks_input)) data_bytes = json_data.encode("utf-8") padder = padding.PKCS7(128).padder() @@ -67,11 +67,11 @@ def parse_data(self, _: str, **kwargs) -> dict: try: user_uprn: str = kwargs.get("uprn") or "" check_uprn(user_uprn) - buck_input = BuckInput( + bucks_input = BucksInput( P_CLIENT_ID=152, P_COUNCIL_ID=34505, P_LANG_CODE="EN", P_UPRN=user_uprn ) - encoded_input = self.encode_body(buck_input) + encoded_input = self.encode_body(bucks_input) session = requests.Session() response = session.post( From 88cd8f5b3b288179c1dcb2ecf43bb8ed3e9bbcca Mon Sep 17 00:00:00 2001 From: sheep_wizard <> Date: Sat, 30 Aug 2025 11:19:52 +0100 Subject: [PATCH 120/425] Remove typo --- wiki/Councils.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wiki/Councils.md b/wiki/Councils.md index 5656173954..8394d05b7b 100644 --- a/wiki/Councils.md +++ b/wiki/Councils.md @@ -820,7 +820,7 @@ Note: Pass the UPRN and postcode. To get the UPRN, you can use [FindMyAddress](h ### Buckinghamshire Council (Chiltern, South Bucks, Wycombe) ```commandline -python collect_data.py BuckinghamshireCouncil https://iapp.itouchvision.com/iappcollectionday/collection-dayP -u XXXXXXXX +python collect_data.py BuckinghamshireCouncil https://iapp.itouchvision.com/iappcollectionday/collection-day -u XXXXXXXX ``` Additional parameters: - `-u` - UPRN From 452ffafcc5074b94e5c51f6fc3686a527ac52fd1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 2 Sep 2025 21:45:59 +0000 Subject: [PATCH 121/425] =?UTF-8?q?bump:=20version=200.152.11=20=E2=86=92?= =?UTF-8?q?=200.153.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 26 +++++++++++++++++++ custom_components/uk_bin_collection/const.py | 2 +- .../uk_bin_collection/manifest.json | 4 +-- pyproject.toml | 2 +- 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 208d14e971..1047745a35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,30 @@ ======= +## 0.153.0 (2025-09-02) + +### Feat + +- Change buckinghamshire council to get data from endpoint + +### Fix + +- 1573 Update Bolton council URL +- East Herts Council +- #1575 +- Runnymede Borough Council +- #1513 +- Wiltshire Council +- #1533 +- Staffordshire Moorlands District Council +- #1535 +- Ipswich Borough Council +- #1548 +- North East Lincs +- Hinckley and Bosworth Borough Council +- Nuneaton Bedworth Borough Council +- #1514 +- Lichfield District Council +- 1549 + ## 0.152.11 (2025-08-25) ### Fix diff --git a/custom_components/uk_bin_collection/const.py b/custom_components/uk_bin_collection/const.py index 074c05d688..f77bd23205 100644 --- a/custom_components/uk_bin_collection/const.py +++ b/custom_components/uk_bin_collection/const.py @@ -4,7 +4,7 @@ from homeassistant.const import Platform -INPUT_JSON_URL = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.152.11/uk_bin_collection/tests/input.json" +INPUT_JSON_URL = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.153.0/uk_bin_collection/tests/input.json" DEFAULT_NAME = "UK Bin Collection Data" diff --git a/custom_components/uk_bin_collection/manifest.json b/custom_components/uk_bin_collection/manifest.json index ae16f946b3..01398acafa 100644 --- a/custom_components/uk_bin_collection/manifest.json +++ b/custom_components/uk_bin_collection/manifest.json @@ -9,7 +9,7 @@ "integration_type": "service", "iot_class": "cloud_polling", "issue_tracker": "https://github.com/robbrad/UKBinCollectionData/issues", - "requirements": ["uk-bin-collection>=0.152.11"], - "version": "0.152.11", + "requirements": ["uk-bin-collection>=0.153.0"], + "version": "0.153.0", "zeroconf": [] } diff --git a/pyproject.toml b/pyproject.toml index 01ec94c524..5c8cdd9f5d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "uk_bin_collection" -version = "0.152.11" +version = "0.153.0" description = "Python Lib to collect UK Bin Data" readme = "README.md" authors = ["Robert Bradley "] From b7b0d930c257113b272fdeab0675f74b6de7bbbc Mon Sep 17 00:00:00 2001 From: Jim Date: Tue, 2 Sep 2025 23:47:04 +0100 Subject: [PATCH 122/425] Update Councils.md Added updated instructions for Norwich --- wiki/Councils.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/wiki/Councils.md b/wiki/Councils.md index b0a648f641..b281653ab0 100644 --- a/wiki/Councils.md +++ b/wiki/Councils.md @@ -2880,12 +2880,13 @@ Note: Pass the house number and postcode in their respective parameters. This pa ### Norwich ```commandline -python collect_data.py NorwichCityCouncil https://www.norwich.gov.uk -u XXXXXXXX +python collect_data.py NorwichCityCouncil https://bnr-wrp.whitespacews.com -p "XXXX XXX" -n XX ``` Additional parameters: -- `-u` - UPRN +- `-p` - postcode +- `-n` - house number -Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. +Note: Pass the house number and postcode in their respective parameters. --- From 54c1e4784206ee5a490b271e50dea5c48660de07 Mon Sep 17 00:00:00 2001 From: Jim Taylor Date: Tue, 2 Sep 2025 23:53:38 +0100 Subject: [PATCH 123/425] Further Norwich fixes --- uk_bin_collection/tests/input.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index 0fee54c7ce..da9e7a1ade 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -1789,11 +1789,12 @@ "LAD24CD": "E06000057" }, "NorwichCityCouncil": { - "uprn": "100090888980", - "url": "https://www.norwich.gov.uk", - "wiki_command_url_override": "https://www.norwich.gov.uk", + "house_number": "2", + "postcode": "NR2 3TT", + "url": "https://bnr-wrp.whitespacews.com", + "wiki_command_url_override": "hhttps://bnr-wrp.whitespacews.com", "wiki_name": "Norwich", - "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "wiki_note": "Pass the house number and postcode in their respective parameters.", "LAD24CD": "E07000148" }, "NottinghamCityCouncil": { From b7fdbd700104bdd5a8f558d0aaefdc2ffdeefeab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 11:44:50 +0000 Subject: [PATCH 124/425] chore: bump actions/setup-python from 5 to 6 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/behave_pull_request.yml | 8 ++++---- .github/workflows/behave_schedule.yml | 8 ++++---- .github/workflows/bump.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/wiki.yml | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/behave_pull_request.yml b/.github/workflows/behave_pull_request.yml index ac4cd3791d..1aeb35b58e 100644 --- a/.github/workflows/behave_pull_request.yml +++ b/.github/workflows/behave_pull_request.yml @@ -19,7 +19,7 @@ jobs: - name: Install Poetry run: pipx install poetry==1.8.4 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: 3.12 @@ -63,7 +63,7 @@ jobs: steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} @@ -94,7 +94,7 @@ jobs: steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} @@ -127,7 +127,7 @@ jobs: steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/behave_schedule.yml b/.github/workflows/behave_schedule.yml index 160238125f..d721bad559 100644 --- a/.github/workflows/behave_schedule.yml +++ b/.github/workflows/behave_schedule.yml @@ -15,7 +15,7 @@ jobs: - name: Install Poetry run: pipx install poetry==1.8.4 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: 3.12 @@ -45,7 +45,7 @@ jobs: steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} @@ -76,7 +76,7 @@ jobs: steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} @@ -109,7 +109,7 @@ jobs: steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/bump.yml b/.github/workflows/bump.yml index 1b5842528f..d66aa647cd 100644 --- a/.github/workflows/bump.yml +++ b/.github/workflows/bump.yml @@ -23,7 +23,7 @@ jobs: with: token: "${{ secrets.PERSONAL_ACCESS_TOKEN }}" fetch-depth: 0 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.12' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6ae09423d7..5ff71a997a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ jobs: contents: write steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.12' diff --git a/.github/workflows/wiki.yml b/.github/workflows/wiki.yml index a602fac935..57a310409d 100644 --- a/.github/workflows/wiki.yml +++ b/.github/workflows/wiki.yml @@ -23,7 +23,7 @@ jobs: environment: wiki steps: - uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.12' From 70d1ce7471c50a0d5b2bd3df2fcbddf95362b5df Mon Sep 17 00:00:00 2001 From: George Reid Date: Mon, 8 Sep 2025 17:26:13 +0100 Subject: [PATCH 125/425] Update regex; the string literal 'today' is also possible --- .../uk_bin_collection/councils/FarehamBoroughCouncil.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/FarehamBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/FarehamBoroughCouncil.py index 93814defca..c9a0dff4e8 100644 --- a/uk_bin_collection/uk_bin_collection/councils/FarehamBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/FarehamBoroughCouncil.py @@ -38,11 +38,14 @@ def parse_data(self, page: str, **kwargs) -> dict: if "rows" in bin_data: collection_str = bin_data["rows"][0]["DomesticBinDay"] - results = re.findall(r"(\d\d?\/\d\d?\/\d{4}) \((\w*)\)", collection_str) + results = re.findall(r'(\d{1,2}/\d{1,2}/\d{4}|today)\s*\(([^)]+)\)', collection_str) if results: for result in results: - collection_date = datetime.strptime(result[0], "%d/%m/%Y") + if (result[0] == "today"): + collection_date = datetime.today() + else: + collection_date = datetime.strptime(result[0], "%d/%m/%Y") dict_data = { "type": result[1], "collectionDate": collection_date.strftime(date_format), From f8a5cec592a26266403eace2f3bd03c66ab43ebd Mon Sep 17 00:00:00 2001 From: Joe Pritchard Date: Tue, 9 Sep 2025 13:51:30 +0100 Subject: [PATCH 126/425] feat: modify input for NorthumberlandCouncil to accept uprn instead of house number, and use new page structure --- uk_bin_collection/tests/input.json | 8 +- .../councils/NorthumberlandCouncil.py | 149 ++++++++---------- 2 files changed, 71 insertions(+), 86 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index da9e7a1ade..d9e0775e33 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -1779,13 +1779,13 @@ "LAD24CD": "E06000065" }, "NorthumberlandCouncil": { - "house_number": "22", + "uprn": "10093091235", "postcode": "NE46 1UQ", "skip_get_url": true, - "url": "https://www.northumberland.gov.uk/Waste/Household-waste/Household-bin-collections/Bin-Calendars.aspx", + "url": "https://bincollection.northumberland.gov.uk/postcode", "web_driver": "http://selenium:4444", "wiki_name": "Northumberland", - "wiki_note": "Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver.", + "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", "LAD24CD": "E06000057" }, "NorwichCityCouncil": { @@ -2805,4 +2805,4 @@ "wiki_note": "Provide your UPRN.", "LAD24CD": "E06000014" } -} \ No newline at end of file +} diff --git a/uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py b/uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py index a779e9b654..60f4dbd2ee 100644 --- a/uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py @@ -1,17 +1,17 @@ import time +import datetime +from datetime import datetime from bs4 import BeautifulSoup from selenium.common.exceptions import TimeoutException from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support.ui import Select, WebDriverWait from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass -# import the wonderful Beautiful Soup and the URL grabber - - class CouncilClass(AbstractGetBinDataClass): """ Concrete classes have to implement all abstract operations of the @@ -30,16 +30,18 @@ def extract_styles(self, style_str: str) -> dict: def parse_data(self, page: str, **kwargs) -> dict: driver = None try: - page = "https://www.northumberland.gov.uk/Waste/Household-waste/Household-bin-collections/Bin-Calendars.aspx" + page = "https://bincollection.northumberland.gov.uk/postcode" data = {"bins": []} - user_paon = kwargs.get("paon") user_postcode = kwargs.get("postcode") + user_uprn = kwargs.get("uprn") + + check_postcode(user_postcode) + check_uprn(user_uprn) + web_driver = kwargs.get("web_driver") headless = kwargs.get("headless") - check_paon(user_paon) - check_postcode(user_postcode) # Create Selenium webdriver driver = create_webdriver(web_driver, headless, None, __name__) @@ -49,106 +51,89 @@ def parse_data(self, page: str, **kwargs) -> dict: wait = WebDriverWait(driver, 20) # Wait for and click cookie button - cookie_button = wait.until( - EC.element_to_be_clickable((By.ID, "ccc-notify-accept")) - ) - cookie_button.click() - - # Wait for and find house number input - inputElement_hn = wait.until( - EC.presence_of_element_located( - ( - By.ID, - "p_lt_ctl04_pageplaceholder_p_lt_ctl02_WasteCollectionCalendars_NCCAddressLookup_txtHouse", - ) + try: + cookie_button = wait.until( + EC.element_to_be_clickable((By.CLASS_NAME, "accept-all")) ) - ) + cookie_button.click() + except TimeoutException: + print("Cookie banner not found, continuing...") # Wait for and find postcode input inputElement_pc = wait.until( EC.presence_of_element_located( - ( - By.ID, - "p_lt_ctl04_pageplaceholder_p_lt_ctl02_WasteCollectionCalendars_NCCAddressLookup_txtPostcode", - ) + (By.ID, "postcode") ) ) - # Enter details + # Enter postcode and submit inputElement_pc.send_keys(user_postcode) - inputElement_hn.send_keys(user_paon) + inputElement_pc.send_keys(Keys.ENTER) + + # Wait for and find house number input + selectElement_address = wait.until( + EC.presence_of_element_located( + (By.ID, "address") + ) + ) + + dropdown = Select(selectElement_address) + dropdown.select_by_value(user_uprn) - # Click lookup button and wait for results - lookup_button = wait.until( + # Click submit button and wait for results + submit_button = wait.until( EC.element_to_be_clickable( - ( - By.ID, - "p_lt_ctl04_pageplaceholder_p_lt_ctl02_WasteCollectionCalendars_NCCAddressLookup_butLookup", - ) + (By.CLASS_NAME, "govuk-button") ) ) - lookup_button.click() + submit_button.click() # Wait for results to load route_summary = wait.until( EC.presence_of_element_located( - ( - By.ID, - "p_lt_ctl04_pageplaceholder_p_lt_ctl02_WasteCollectionCalendars_spanRouteSummary", - ) + (By.CLASS_NAME, "govuk-table") ) ) + now = datetime.now() + current_month = now.month + current_year = now.year + # Get page source after everything has loaded soup = BeautifulSoup(driver.page_source, features="html.parser") - # Work out which bins can be collected for this address. Glass bins are only on some houses due to pilot programme. - bins_collected = list( - map( - str.strip, - soup.find( - "span", - id="p_lt_ctl04_pageplaceholder_p_lt_ctl02_WasteCollectionCalendars_spanRouteSummary", - ) - .text.replace("Routes found: ", "") - .split(","), + # From the table, find all rows: + # - cell 1 is the date in format eg. 9 September (so no year value 🥲) + # - cell 2 is the day name, not useful + # - cell 3 is the bin type eg. "General waste", "Recycling", "Garden waste" + rows = soup.find_all("tr", class_="govuk-table__row") + + for row in rows: + bin_type=row.find_all("td")[-1].text.strip() + + collection_date_string = row.find('th').text.strip() + + # sometimes but not always the day is written "22nd" instead of 22 so make sure we get a proper int + collection_date_day = [int(i) for i in collection_date_string.split(' ').split() if i.isdigit()] + collection_date_month_name = collection_date_string.split(' ')[1] + + # if we are currently in Oct, Nov, or Dec and the collection month is Jan, Feb, or Mar, let's assume its next year + if (current_month >= 10) and (collection_date_month_name in ["January", "February", "March"]): + collection_date_year = current_year + 1 + else: + collection_date_year = current_year + + collection_date = time.strptime( + f"{collection_date_day[0]} {collection_date_month_name} {collection_date_year}", "%d %B %Y" ) - ) - # Get the background colour for each of them... - bins_by_colours = dict() - for bin in bins_collected: - if "(but no dates found)" in bin: - continue - style_str = soup.find("span", string=bin)["style"] - bin_colour = self.extract_styles(style_str)["background-color"].upper() - bins_by_colours[bin_colour] = bin - - # Work through the tables gathering the dates, if the cell has a background colour - match it to the bin type. - calander_tables = soup.find_all("table", title="Calendar") - for table in calander_tables: - # Get month and year - # First row in table is the header - rows = table.find_all("tr") - month_and_year = ( - rows[0].find("table", class_="calCtrlTitle").find("td").string + # Add it to the data + data["bins"].append( + { + "type": bin_type, + "collectionDate": time.strftime(date_format, collection_date), + } ) - bin_days = table.find_all("td", class_="calCtrlDay") - for day in bin_days: - day_styles = self.extract_styles(day["style"]) - if "background-color" in day_styles: - colour = day_styles["background-color"].upper() - date = time.strptime( - f"{day.string} {month_and_year}", "%d %B %Y" - ) - - # Add it to the data - data["bins"].append( - { - "type": bins_by_colours[colour], - "collectionDate": time.strftime(date_format, date), - } - ) except Exception as e: # Here you can log the exception if needed print(f"An error occurred: {e}") From 6f1ccc62fef93518f04bcba9fef3c0003a07d8dd Mon Sep 17 00:00:00 2001 From: Joe Pritchard Date: Tue, 9 Sep 2025 20:25:27 +0100 Subject: [PATCH 127/425] fix: the cookie banner is not optional --- .../councils/NorthumberlandCouncil.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py b/uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py index 60f4dbd2ee..f355918eca 100644 --- a/uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py @@ -51,19 +51,14 @@ def parse_data(self, page: str, **kwargs) -> dict: wait = WebDriverWait(driver, 20) # Wait for and click cookie button - try: - cookie_button = wait.until( - EC.element_to_be_clickable((By.CLASS_NAME, "accept-all")) - ) - cookie_button.click() - except TimeoutException: - print("Cookie banner not found, continuing...") + cookie_button = wait.until( + EC.element_to_be_clickable(By.CLASS_NAME, "accept-all") + ) + cookie_button.click() # Wait for and find postcode input inputElement_pc = wait.until( - EC.presence_of_element_located( - (By.ID, "postcode") - ) + EC.presence_of_element_located((By.ID, "postcode") ) # Enter postcode and submit @@ -72,9 +67,7 @@ def parse_data(self, page: str, **kwargs) -> dict: # Wait for and find house number input selectElement_address = wait.until( - EC.presence_of_element_located( - (By.ID, "address") - ) + EC.presence_of_element_located(By.ID, "address") ) dropdown = Select(selectElement_address) From e3ea86fd67379eb57a4af01f6b9b5992b643b38d Mon Sep 17 00:00:00 2001 From: Wiki GitHub Action Date: Tue, 9 Sep 2025 20:33:17 +0000 Subject: [PATCH 128/425] docs: Update Councils.md from input.json --- wiki/Councils.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/wiki/Councils.md b/wiki/Councils.md index b281653ab0..0c31313871 100644 --- a/wiki/Councils.md +++ b/wiki/Councils.md @@ -876,16 +876,12 @@ Note: Pass the UPRN and postcode. To get the UPRN, you can use [FindMyAddress](h ### Buckinghamshire ```commandline -python collect_data.py BuckinghamshireCouncil https://iapp.itouchvision.com/iappcollectionday/collection-day -u XXXXXXXX +python collect_data.py BuckinghamshireCouncil https://www.buckinghamshire.gov.uk/waste-and-recycling/find-out-when-its-your-bin-collection/ -u XXXXXXXX ``` Additional parameters: - `-u` - UPRN Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search). -``` - - -Note: Pass the house name/number and postcode in their respective arguments, both wrapped in quotes. --- @@ -2880,7 +2876,7 @@ Note: Pass the house number and postcode in their respective parameters. This pa ### Norwich ```commandline -python collect_data.py NorwichCityCouncil https://bnr-wrp.whitespacews.com -p "XXXX XXX" -n XX +python collect_data.py NorwichCityCouncil hhttps://bnr-wrp.whitespacews.com -p "XXXX XXX" -n XX ``` Additional parameters: - `-p` - postcode From ebfd961afc9cd34a2d81a13feae1c9b7081532b6 Mon Sep 17 00:00:00 2001 From: m26dvd <31007572+m26dvd@users.noreply.github.com> Date: Tue, 9 Sep 2025 21:44:11 +0100 Subject: [PATCH 129/425] fix: #1591 fix: #1591 Rushmoor Council --- .../uk_bin_collection/councils/RushmoorCouncil.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/RushmoorCouncil.py b/uk_bin_collection/uk_bin_collection/councils/RushmoorCouncil.py index 9335ccc397..be862324cb 100644 --- a/uk_bin_collection/uk_bin_collection/councils/RushmoorCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/RushmoorCouncil.py @@ -1,7 +1,8 @@ from bs4 import BeautifulSoup +from lxml import etree + from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass -from lxml import etree # import the wonderful Beautiful Soup and the URL grabber @@ -20,7 +21,8 @@ def parse_data(self, page: str, **kwargs) -> dict: collections = [] # Convert the XML to JSON and load the next collection data - result = soup.find("p").contents[0].text.replace("\\", "")[1:-1] + result = soup.find("p").contents[0] + json_data = json.loads(result)["NextCollection"] # Get general waste data From b0cffb6f5eee12d18791ac2808ab74b4b5805faa Mon Sep 17 00:00:00 2001 From: m26dvd <31007572+m26dvd@users.noreply.github.com> Date: Tue, 9 Sep 2025 21:52:38 +0100 Subject: [PATCH 130/425] fix: #1588 fix: #1588 Glasgow City Council --- uk_bin_collection/tests/input.json | 2 +- .../uk_bin_collection/councils/GlasgowCityCouncil.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index da9e7a1ade..7010bdc756 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -995,7 +995,7 @@ }, "GlasgowCityCouncil": { "uprn": "906700034497", - "url": "https://onlineservices.glasgow.gov.uk/forms/RefuseAndRecyclingWebApplication/AddressSearch.aspx", + "url": "https://onlineservices.glasgow.gov.uk/forms/refuseandrecyclingcalendar/AddressSearch.aspx", "skip_get_url": true, "wiki_name": "Glasgow City", "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", diff --git a/uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py b/uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py index 4a50379173..1737719dba 100644 --- a/uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/GlasgowCityCouncil.py @@ -18,7 +18,7 @@ def parse_data(self, page: str, **kwargs) -> dict: try: user_uprn = kwargs.get("uprn") check_uprn(user_uprn) - url = f"https://onlineservices.glasgow.gov.uk/forms/RefuseAndRecyclingWebApplication/CollectionsCalendar.aspx?UPRN={user_uprn}" + url = f"https://onlineservices.glasgow.gov.uk/forms/refuseandrecyclingcalendar/CollectionsCalendar.aspx?UPRN={user_uprn}" if not user_uprn: # This is a fallback for if the user stored a URL in old system. Ensures backwards compatibility. url = kwargs.get("url") From ef19acdfda807fea9815cd46e2e5df23d1820790 Mon Sep 17 00:00:00 2001 From: Wiki GitHub Action Date: Tue, 9 Sep 2025 20:53:00 +0000 Subject: [PATCH 131/425] docs: Update Councils.md from input.json --- wiki/Councils.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wiki/Councils.md b/wiki/Councils.md index 0c31313871..02fe7023a6 100644 --- a/wiki/Councils.md +++ b/wiki/Councils.md @@ -1774,7 +1774,7 @@ Note: Use [this site](https://www.gbcbincalendars.co.uk/) to find the collection ### Glasgow City ```commandline -python collect_data.py GlasgowCityCouncil https://onlineservices.glasgow.gov.uk/forms/RefuseAndRecyclingWebApplication/AddressSearch.aspx -s -u XXXXXXXX +python collect_data.py GlasgowCityCouncil https://onlineservices.glasgow.gov.uk/forms/refuseandrecyclingcalendar/AddressSearch.aspx -s -u XXXXXXXX ``` Additional parameters: - `-s` - skip get URL From 825e59364b9893db74161e5e2faafe10490fcbda Mon Sep 17 00:00:00 2001 From: m26dvd <31007572+m26dvd@users.noreply.github.com> Date: Tue, 9 Sep 2025 22:04:45 +0100 Subject: [PATCH 132/425] fix: #1587 fix: #1587 - Hartlepool Borough Council --- .../uk_bin_collection/councils/HartlepoolBoroughCouncil.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/HartlepoolBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/HartlepoolBoroughCouncil.py index 9be7711d61..0d383c0d31 100644 --- a/uk_bin_collection/uk_bin_collection/councils/HartlepoolBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/HartlepoolBoroughCouncil.py @@ -73,7 +73,9 @@ def parse_data(self, page: str, **kwargs) -> dict: for div in soup.find_all("div"): # Extract bin type and date from the span tag text = div.find("span").text.strip() - bin_type, date = text.split(" ", 1) + parts = text.split(" ") + date = parts[-1] # assume the last token is the date + bin_type = " ".join(parts[:-1]) dict_data = { "type": bin_type, "collectionDate": date, From e86d54ce6bc9014f39200e831b66b0c3948a9481 Mon Sep 17 00:00:00 2001 From: m26dvd <31007572+m26dvd@users.noreply.github.com> Date: Tue, 9 Sep 2025 22:20:56 +0100 Subject: [PATCH 133/425] fix: #1599 fix: #1599 - Basingstoke Council --- .../uk_bin_collection/councils/BasingstokeCouncil.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/BasingstokeCouncil.py b/uk_bin_collection/uk_bin_collection/councils/BasingstokeCouncil.py index 3e345d5f75..d5b49c4ab4 100644 --- a/uk_bin_collection/uk_bin_collection/councils/BasingstokeCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/BasingstokeCouncil.py @@ -1,6 +1,8 @@ -from bs4 import BeautifulSoup from datetime import datetime + import requests +from bs4 import BeautifulSoup + from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass @@ -10,6 +12,7 @@ "glass": "rteelem_ctl03_pnlCollections_Glass", # Garden waste data is only returned if the property is subscribed to the Garden Waste service "garden": "rteelem_ctl03_pnlCollections_GardenWaste", + "food": "rteelem_ctl03_pnlCollections_Food", } From 224657190079c50b18ab6cff35764e3594e9f1fd Mon Sep 17 00:00:00 2001 From: m26dvd <31007572+m26dvd@users.noreply.github.com> Date: Tue, 9 Sep 2025 22:55:31 +0100 Subject: [PATCH 134/425] fix: #1584 - NorthHertfordshireDistrictCouncil fix: #1584 - NorthHertfordshireDistrictCouncil --- .../NorthHertfordshireDistrictCouncil.py | 154 +++--------------- 1 file changed, 26 insertions(+), 128 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/NorthHertfordshireDistrictCouncil.py b/uk_bin_collection/uk_bin_collection/councils/NorthHertfordshireDistrictCouncil.py index a15d8abc94..cb986f76cd 100644 --- a/uk_bin_collection/uk_bin_collection/councils/NorthHertfordshireDistrictCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/NorthHertfordshireDistrictCouncil.py @@ -125,23 +125,6 @@ def parse_data(self, page: str, **kwargs) -> dict: # Wait for the page to load - giving it extra time time.sleep(5) - # Use only the selector that we know works - # print("Looking for bin type elements...") - try: - bin_type_selector = ( - By.CSS_SELECTOR, - "div.formatting_bold.formatting_size_bigger.formatting span.value-as-text", - ) - WebDriverWait(driver, 15).until( - EC.presence_of_element_located(bin_type_selector) - ) - # print(f"Found bin type elements with selector: {bin_type_selector}") - except TimeoutException: - # print("Could not find bin type elements. Taking screenshot for debugging...") - screenshot_path = f"bin_type_error_{int(time.time())}.png" - driver.save_screenshot(screenshot_path) - # print(f"Screenshot saved to {screenshot_path}") - # Create BS4 object from driver's page source # print("Parsing page with BeautifulSoup...") soup = BeautifulSoup(driver.page_source, features="html.parser") @@ -149,122 +132,37 @@ def parse_data(self, page: str, **kwargs) -> dict: # Initialize data dictionary data = {"bins": []} - # Looking for bin types in the exact HTML structure - bin_type_elements = soup.select( - "div.page_cell.contains_widget:first-of-type div.formatting_bold.formatting_size_bigger.formatting span.value-as-text" - ) - # print(f"Found {len(bin_type_elements)} bin type elements") - - # Look specifically for date elements with the exact structure - date_elements = soup.select("div.col-sm-12.font-xs-3xl span.value-as-text") - hidden_dates = soup.select( - "div.col-sm-12.font-xs-3xl input[type='hidden'][value*='/']" - ) - - # print(f"Found {len(bin_type_elements)} bin types and {len(date_elements)} date elements") - - # We need a smarter way to match bin types with their dates - bin_count = 0 + for row in soup.select(".listing_template_row"): + # Title (waste stream) is the first

in the section + first_p = row.find("p") + if not first_p: + continue + stream = first_p.get_text(" ", strip=True) - # Map of bin types to their collection dates - bin_date_map = {} + for p in row.find_all("p"): + t = p.get_text("\n", strip=True) - # Extract all date strings that look like actual dates - date_texts = [] - date_pattern = re.compile( - r"(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)\s+\d+(?:st|nd|rd|th)?\s+\w+\s+\d{4}", - re.IGNORECASE, - ) + if re.search(r"\bNext collection\b", t, flags=re.I): + # Expect format: "Next collection\nTuesday 16th September 2025" + parts = [x.strip() for x in t.split("\n") if x.strip()] + if len(parts) >= 2: + next_collection_display = parts[-1] # last line - for element in date_elements: - text = element.get_text(strip=True) - if date_pattern.search(text): - date_texts.append(text) - # print(f"Found valid date text: {text}") - - # Find hidden date inputs with values in DD/MM/YYYY format - hidden_date_values = [] - for hidden in hidden_dates: - value = hidden.get("value", "") - if re.match(r"\d{1,2}/\d{1,2}/\d{4}", value): - hidden_date_values.append(value) - # print(f"Found hidden date value: {value}") - - # When filtering date elements - date_elements = soup.select("div.col-sm-12.font-xs-3xl span.value-as-text") - valid_date_elements = [] - - for element in date_elements: - text = element.get_text(strip=True) - if contains_date(text): - valid_date_elements.append(element) - # print(f"Found valid date element: {text}") - else: - pass - # print(f"Skipping non-date element: {text}") - - # print(f"Found {len(bin_type_elements)} bin types and {len(valid_date_elements)} valid date elements") - - # When processing each bin type - for i, bin_type_elem in enumerate(bin_type_elements): - bin_type = bin_type_elem.get_text(strip=True) - - # Try to find a date for this bin type - date_text = None - - # Look for a valid date element - if i < len(valid_date_elements): - date_elem = valid_date_elements[i] - date_text = date_elem.get_text(strip=True) - - # If we don't have a valid date yet, try using the hidden input - if not date_text or not contains_date(date_text): - if i < len(hidden_dates): - date_value = hidden_dates[i].get("value") - if contains_date(date_value): - date_text = date_value - - # Skip if we don't have a valid date - if not date_text or not contains_date(date_text): - # print(f"No valid date found for bin type: {bin_type}") - continue + # Build record + next_date = datetime.strptime( + remove_ordinal_indicator_from_date_string(next_collection_display), + "%A %d %B %Y", + ) - # print(f"Found bin type: {bin_type} with date: {date_text}") + # Create bin entry + bin_entry = { + "type": stream, + "collectionDate": next_date.strftime(date_format), + } - try: - # Clean up the date text - date_text = remove_ordinal_indicator_from_date_string(date_text) - - # Try to parse the date - try: - collection_date = datetime.strptime( - date_text, "%A %d %B %Y" - ).date() - except ValueError: - try: - collection_date = datetime.strptime( - date_text, "%d/%m/%Y" - ).date() - except ValueError: - # Last resort - collection_date = parse(date_text).date() - - # Create bin entry - bin_entry = { - "type": bin_type, - "collectionDate": collection_date.strftime(date_format), - } - - # Add to data - data["bins"].append(bin_entry) - bin_count += 1 - # print(f"Added bin entry: {bin_entry}") - - except Exception as e: - pass - # print(f"Error parsing date '{date_text}': {str(e)}") - - # print(f"Successfully parsed {bin_count} bin collections") + # Add to data + data["bins"].append(bin_entry) + # print(f"Added bin entry: {bin_entry}") if not data["bins"]: # print("No bin data found. Saving page for debugging...") From a977d553462e22e1bf68c373a1110fcee6a866a3 Mon Sep 17 00:00:00 2001 From: m26dvd <31007572+m26dvd@users.noreply.github.com> Date: Tue, 9 Sep 2025 23:38:49 +0100 Subject: [PATCH 135/425] fix: #1571 - Castle Point District Council fix: #1571 - Castle Point District Council --- .../councils/CastlepointDistrictCouncil.py | 79 +++++++++++++------ 1 file changed, 55 insertions(+), 24 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/CastlepointDistrictCouncil.py b/uk_bin_collection/uk_bin_collection/councils/CastlepointDistrictCouncil.py index 976400f2bb..19b5f9386a 100644 --- a/uk_bin_collection/uk_bin_collection/councils/CastlepointDistrictCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/CastlepointDistrictCouncil.py @@ -26,7 +26,9 @@ def parse_data(self, page: str, **kwargs) -> dict: uprn = kwargs.get("uprn") check_uprn(uprn) - post_url = "https://apps.castlepoint.gov.uk/cpapps/index.cfm?fa=myStreet.displayDetails" + base_url = "https://apps.castlepoint.gov.uk/cpapps/" + + post_url = f"{base_url}index.cfm?fa=myStreet.displayDetails" post_header_str = ( "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp," "image/apng," @@ -51,31 +53,60 @@ def parse_data(self, page: str, **kwargs) -> dict: soup = BeautifulSoup(post_response.text, features="html.parser") soup.prettify() + calMonthNext = f"{base_url}{soup.select_one("div.calMonthNext a")["href"]}" + nextmonth_response = requests.post( + calMonthNext, headers=post_headers, data=form_data, verify=False + ) + soup_nextmonth = BeautifulSoup(nextmonth_response.text, features="html.parser") + soup_nextmonth.prettify() + data = {"bins": []} - collection_tuple = [] - calendar = soup.find("table", class_="calendar") - month = datetime.strptime( - soup.find("div", class_="calMonthCurrent").get_text(), "[%b]" - ).strftime("%m") - year = datetime.strptime( - soup.find("h1").get_text(), "About my Street - %B %Y" - ).strftime("%Y") - - pink_days = [ - day.get_text().strip() for day in calendar.find_all("td", class_="pink") - ] - black_days = [ - day.get_text().strip() for day in calendar.find_all("td", class_="normal") - ] - - for day in pink_days: - collection_date = datetime(year=int(year), month=int(month), day=int(day)) - collection_tuple.append(("Pink collection", collection_date)) - - for day in black_days: - collection_date = datetime(year=int(year), month=int(month), day=int(day)) - collection_tuple.append(("Normal collection", collection_date)) + def parse_calendar_month(soup_one_month): + out = [] + + calendar = soup_one_month.find("table", class_="calendar") + if not calendar: + return out # be robust + + # e.g. "[Aug]" + month_txt = soup_one_month.find("div", class_="calMonthCurrent").get_text( + strip=True + ) + month = datetime.strptime(month_txt, "[%b]").strftime("%m") + + # e.g. "About my Street - August 2025" + year_txt = soup_one_month.find("h1").get_text(strip=True) + year = datetime.strptime(year_txt, "About my Street - %B %Y").strftime("%Y") + + pink_days = [ + td.get_text(strip=True) for td in calendar.find_all("td", class_="pink") + ] + black_days = [ + td.get_text(strip=True) + for td in calendar.find_all("td", class_="normal") + ] + + for day in pink_days: + out.append( + ( + "Pink collection", + datetime(year=int(year), month=int(month), day=int(day)), + ) + ) + for day in black_days: + out.append( + ( + "Normal collection", + datetime(year=int(year), month=int(month), day=int(day)), + ) + ) + + return out + + collection_tuple = [] + for s in (soup, soup_nextmonth): + collection_tuple.extend(parse_calendar_month(s)) ordered_data = sorted(collection_tuple, key=lambda x: x[1]) From fa0ec28fce95a277aaf384280c0675804f3bb379 Mon Sep 17 00:00:00 2001 From: m26dvd <31007572+m26dvd@users.noreply.github.com> Date: Wed, 10 Sep 2025 00:23:14 +0100 Subject: [PATCH 136/425] fix: #1565 - BCP Council fix: #1565 - BCP Council --- uk_bin_collection/tests/input.json | 5 +- .../uk_bin_collection/councils/BCPCouncil.py | 165 +++++------------- wiki/Councils.md | 5 +- 3 files changed, 47 insertions(+), 128 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index 7010bdc756..9b2fe1ad32 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -102,12 +102,9 @@ }, "BCPCouncil": { "LAD24CD": "E06000058", - "house_number": "3 HARBOUR VIEW ROAD, POOLE, BH14 0PD", - "postcode": "BH14 0PD", - "web_driver": "http://selenium:4444", "skip_get_url": true, "uprn": "100040810214", - "url": "https://online.bcpcouncil.gov.uk/bindaylookup/", + "url": "https://bcpportal.bcpcouncil.gov.uk/checkyourbincollection", "wiki_name": "Bournemouth, Christchurch and Poole", "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN." }, diff --git a/uk_bin_collection/uk_bin_collection/councils/BCPCouncil.py b/uk_bin_collection/uk_bin_collection/councils/BCPCouncil.py index 081bb0b812..d6c2c32ddc 100644 --- a/uk_bin_collection/uk_bin_collection/councils/BCPCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/BCPCouncil.py @@ -1,15 +1,13 @@ -import json import time -from datetime import datetime -from bs4 import BeautifulSoup -from selenium.webdriver.common.by import By -from selenium.webdriver.support.ui import WebDriverWait, Select -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.common.keys import Keys + +import requests +from dateutil.relativedelta import relativedelta + from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass +# import the wonderful Beautiful Soup and the URL grabber class CouncilClass(AbstractGetBinDataClass): """ Concrete classes have to implement all abstract operations of the @@ -18,116 +16,43 @@ class CouncilClass(AbstractGetBinDataClass): """ def parse_data(self, page: str, **kwargs) -> dict: - postcode = kwargs.get("postcode") - house_number = kwargs.get("paon") - web_driver = kwargs.get("web_driver") - headless = kwargs.get("headless", True) - - check_postcode(postcode) - check_paon(house_number) - - driver = create_webdriver(web_driver, headless=headless) - - try: - driver.get("https://bcpportal.bcpcouncil.gov.uk/checkyourbincollection/") - - # Handle cookie banner first - try: - cookie_button = WebDriverWait(driver, 5).until( - EC.element_to_be_clickable((By.XPATH, "//button[contains(text(), 'Okay')]")) - ) - cookie_button.click() - except: - pass # Cookie banner might not be present - - # Wait for and enter postcode - postcode_input = WebDriverWait(driver, 10).until( - EC.presence_of_element_located((By.CSS_SELECTOR, "input[type='text']")) - ) - postcode_input.clear() - postcode_input.send_keys(postcode) - - # Click the search span element - search_button = WebDriverWait(driver, 10).until( - EC.element_to_be_clickable((By.ID, "searchAddress")) - ) - search_button.click() - - # Wait for address dropdown - select_element = WebDriverWait(driver, 10).until( - EC.presence_of_element_located((By.TAG_NAME, "select")) - ) - - # Find and select the address containing the house number - address_option = WebDriverWait(driver, 10).until( - EC.element_to_be_clickable((By.XPATH, f"//option[contains(text(), 'HARBOUR VIEW ROAD')]")) - ) - address_option.click() - - # Wait for bin collection results to load - WebDriverWait(driver, 15).until( - EC.presence_of_element_located((By.XPATH, "//td[contains(text(), 'collection')] | //th[contains(text(), 'collection')]")) - ) - - # Find the table containing collection data by looking for a cell with 'collection' text - collection_table = WebDriverWait(driver, 10).until( - EC.presence_of_element_located((By.XPATH, "//td[contains(text(), 'collection')]/ancestor::table | //th[contains(text(), 'collection')]/ancestor::table")) - ) - - # Parse the table data - soup = BeautifulSoup(driver.page_source, 'html.parser') - data = {"bins": []} - - # Find the table containing collection information - collection_cell = soup.find(['td', 'th'], string=lambda text: text and 'collection' in text.lower()) - if collection_cell: - table = collection_cell.find_parent('table') - if table: - rows = table.find_all('tr') - for row in rows[1:]: # Skip header row - cells = row.find_all(['td', 'th']) - if len(cells) >= 2: # At least bin type and one collection date - bin_type = cells[0].get_text(strip=True) - next_collection = cells[1].get_text(strip=True) if len(cells) > 1 else "" - following_collection = cells[2].get_text(strip=True) if len(cells) > 2 else "" - - - # Process next collection date - if bin_type and next_collection and "No collection" not in next_collection: - try: - # Try multiple date formats - for date_fmt in ["%A, %d %B %Y", "%A %d %B %Y", "%d/%m/%Y", "%d-%m-%Y", "%Y-%m-%d"]: - try: - parsed_date = datetime.strptime(next_collection, date_fmt) - data["bins"].append({ - "type": bin_type, - "collectionDate": parsed_date.strftime(date_format) - }) - break - except ValueError: - continue - except: - continue - - # Process following collection date - if bin_type and following_collection and "No collection" not in following_collection and "download PDF" not in following_collection: - try: - # Clean up the following collection text (remove PDF link text) - following_collection = following_collection.replace("download PDF", "").strip() - for date_fmt in ["%A, %d %B %Y", "%A %d %B %Y", "%d/%m/%Y", "%d-%m-%Y", "%Y-%m-%d"]: - try: - parsed_date = datetime.strptime(following_collection, date_fmt) - data["bins"].append({ - "type": bin_type, - "collectionDate": parsed_date.strftime(date_format) - }) - break - except ValueError: - continue - except: - continue - - return data - - finally: - driver.quit() + # Make a BS4 object + uprn = kwargs.get("uprn") + # usrn = kwargs.get("paon") + check_uprn(uprn) + # check_usrn(usrn) + bindata = {"bins": []} + + # uprn = uprn.zfill(12) + + API_URL = "https://prod-17.uksouth.logic.azure.com/workflows/58253d7b7d754447acf9fe5fcf76f493/triggers/manual/paths/invoke?api-version=2016-06-01&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=TAvYIUFj6dzaP90XQCm2ElY6Cd34ze05I3ba7LKTiBs" + + headers = { + "Content-Type": "application/json", + "Accept": "*/*", + "User-Agent": "Mozilla/5.0", + "Referer": "https://bcpportal.bcpcouncil.gov.uk/", + } + s = requests.session() + data = { + "uprn": uprn, + } + + r = s.post(API_URL, json=data, headers=headers) + r.raise_for_status() + + data = r.json() + rows_data = data["data"] + for row in rows_data: + bin_type = row["wasteContainerUsageTypeDescription"] + collections = row["scheduleDateRange"] + for collection in collections: + dict_data = { + "type": bin_type, + "collectionDate": datetime.strptime( + collection, "%Y-%m-%d" + ).strftime(date_format), + } + bindata["bins"].append(dict_data) + + return bindata diff --git a/wiki/Councils.md b/wiki/Councils.md index 02fe7023a6..c042693d67 100644 --- a/wiki/Councils.md +++ b/wiki/Councils.md @@ -482,14 +482,11 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc ### Bournemouth, Christchurch and Poole ```commandline -python collect_data.py BCPCouncil https://online.bcpcouncil.gov.uk/bindaylookup/ -s -u XXXXXXXX -p "XXXX XXX" -n XX -w http://HOST:PORT/ +python collect_data.py BCPCouncil https://bcpportal.bcpcouncil.gov.uk/checkyourbincollection -s -u XXXXXXXX ``` Additional parameters: - `-s` - skip get URL - `-u` - UPRN -- `-p` - postcode -- `-n` - house number -- `-w` - remote Selenium web driver URL (required for Home Assistant) Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. From 5b67cf52ee62c3f4f7b0aee0731953aa501b3077 Mon Sep 17 00:00:00 2001 From: Joe Date: Wed, 10 Sep 2025 09:54:33 +0100 Subject: [PATCH 137/425] feat: handle changes to northumberland council website --- uk_bin_collection/tests/input.json | 4 ++-- .../councils/NorthumberlandCouncil.py | 20 ++++++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index d9e0775e33..6b2b84de6e 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -1779,8 +1779,8 @@ "LAD24CD": "E06000065" }, "NorthumberlandCouncil": { - "uprn": "10093091235", - "postcode": "NE46 1UQ", + "uprn": "010096302588", + "postcode": "NE65 0ZP", "skip_get_url": true, "url": "https://bincollection.northumberland.gov.uk/postcode", "web_driver": "http://selenium:4444", diff --git a/uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py b/uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py index f355918eca..14a16859ab 100644 --- a/uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py @@ -52,13 +52,17 @@ def parse_data(self, page: str, **kwargs) -> dict: # Wait for and click cookie button cookie_button = wait.until( - EC.element_to_be_clickable(By.CLASS_NAME, "accept-all") + EC.element_to_be_clickable( + (By.CLASS_NAME, "accept-all") + ) ) cookie_button.click() # Wait for and find postcode input inputElement_pc = wait.until( - EC.presence_of_element_located((By.ID, "postcode") + EC.presence_of_element_located( + (By.ID, "postcode") + ) ) # Enter postcode and submit @@ -67,7 +71,9 @@ def parse_data(self, page: str, **kwargs) -> dict: # Wait for and find house number input selectElement_address = wait.until( - EC.presence_of_element_located(By.ID, "address") + EC.presence_of_element_located( + (By.ID, "address") + ) ) dropdown = Select(selectElement_address) @@ -99,7 +105,7 @@ def parse_data(self, page: str, **kwargs) -> dict: # - cell 1 is the date in format eg. 9 September (so no year value 🥲) # - cell 2 is the day name, not useful # - cell 3 is the bin type eg. "General waste", "Recycling", "Garden waste" - rows = soup.find_all("tr", class_="govuk-table__row") + rows = soup.find("tbody", class_="govuk-table__body").find_all("tr", class_="govuk-table__row") for row in rows: bin_type=row.find_all("td")[-1].text.strip() @@ -107,8 +113,8 @@ def parse_data(self, page: str, **kwargs) -> dict: collection_date_string = row.find('th').text.strip() # sometimes but not always the day is written "22nd" instead of 22 so make sure we get a proper int - collection_date_day = [int(i) for i in collection_date_string.split(' ').split() if i.isdigit()] - collection_date_month_name = collection_date_string.split(' ')[1] + collection_date_day = "".join([i for i in list(collection_date_string.split(" ")[0]) if i.isdigit()]) + collection_date_month_name = collection_date_string.split(" ")[1] # if we are currently in Oct, Nov, or Dec and the collection month is Jan, Feb, or Mar, let's assume its next year if (current_month >= 10) and (collection_date_month_name in ["January", "February", "March"]): @@ -117,7 +123,7 @@ def parse_data(self, page: str, **kwargs) -> dict: collection_date_year = current_year collection_date = time.strptime( - f"{collection_date_day[0]} {collection_date_month_name} {collection_date_year}", "%d %B %Y" + f"{collection_date_day} {collection_date_month_name} {collection_date_year}", "%d %B %Y" ) # Add it to the data From f73353b51623d203a404a99d2be193969664a495 Mon Sep 17 00:00:00 2001 From: Wiki GitHub Action Date: Wed, 10 Sep 2025 08:54:56 +0000 Subject: [PATCH 138/425] docs: Update Councils.md from input.json --- wiki/Councils.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/wiki/Councils.md b/wiki/Councils.md index b281653ab0..12644d4d03 100644 --- a/wiki/Councils.md +++ b/wiki/Councils.md @@ -876,16 +876,12 @@ Note: Pass the UPRN and postcode. To get the UPRN, you can use [FindMyAddress](h ### Buckinghamshire ```commandline -python collect_data.py BuckinghamshireCouncil https://iapp.itouchvision.com/iappcollectionday/collection-day -u XXXXXXXX +python collect_data.py BuckinghamshireCouncil https://www.buckinghamshire.gov.uk/waste-and-recycling/find-out-when-its-your-bin-collection/ -u XXXXXXXX ``` Additional parameters: - `-u` - UPRN Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search). -``` - - -Note: Pass the house name/number and postcode in their respective arguments, both wrapped in quotes. --- @@ -2866,21 +2862,21 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd ### Northumberland ```commandline -python collect_data.py NorthumberlandCouncil https://www.northumberland.gov.uk/Waste/Household-waste/Household-bin-collections/Bin-Calendars.aspx -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ +python collect_data.py NorthumberlandCouncil https://bincollection.northumberland.gov.uk/postcode -s -u XXXXXXXX -p "XXXX XXX" -w http://HOST:PORT/ ``` Additional parameters: - `-s` - skip get URL +- `-u` - UPRN - `-p` - postcode -- `-n` - house number - `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: Pass the house number and postcode in their respective parameters. This parser requires a Selenium webdriver. +Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search). --- ### Norwich ```commandline -python collect_data.py NorwichCityCouncil https://bnr-wrp.whitespacews.com -p "XXXX XXX" -n XX +python collect_data.py NorwichCityCouncil hhttps://bnr-wrp.whitespacews.com -p "XXXX XXX" -n XX ``` Additional parameters: - `-p` - postcode From b2c8b60c2888c2773cfb4d74d339214596c1efc9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 06:32:10 +0000 Subject: [PATCH 139/425] chore: bump tj-actions/changed-files from 46 to 47 Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 46 to 47. - [Release notes](https://github.com/tj-actions/changed-files/releases) - [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md) - [Commits](https://github.com/tj-actions/changed-files/compare/v46...v47) --- updated-dependencies: - dependency-name: tj-actions/changed-files dependency-version: '47' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/behave_pull_request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/behave_pull_request.yml b/.github/workflows/behave_pull_request.yml index ac4cd3791d..fbf8c89fd9 100644 --- a/.github/workflows/behave_pull_request.yml +++ b/.github/workflows/behave_pull_request.yml @@ -31,7 +31,7 @@ jobs: - name: Get All Council Files That Have Changed id: changed-council-files - uses: tj-actions/changed-files@v46 + uses: tj-actions/changed-files@v47 with: files: | uk_bin_collection/uk_bin_collection/councils/**.py From a855f2f5b72b37d4cb5cc1e023198cecd48c3678 Mon Sep 17 00:00:00 2001 From: Alberto Cerrone Date: Thu, 18 Sep 2025 22:16:12 +0100 Subject: [PATCH 140/425] chore: Update SandwellBoroughCouncil lookups --- .../councils/SandwellBoroughCouncil.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/SandwellBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/SandwellBoroughCouncil.py index a18e0b7dba..714788c5bd 100644 --- a/uk_bin_collection/uk_bin_collection/councils/SandwellBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/SandwellBoroughCouncil.py @@ -28,17 +28,10 @@ class CouncilClass(AbstractGetBinDataClass): "Referer": "https://my.sandwell.gov.uk/fillform/?iframe_id=fillform-frame-1&db_id=", } LOOKUPS = [ - ( - "58a1a71694992", - "DWDate", - [ - "Recycling (Blue)", - "Household Waste (Grey)", - "Food Waste (Brown)", - "Batteries", - ], - ), - ("56b1cdaf6bb43", "GWDate", ["Garden Waste (Green)"]), + ("686295a88a750", "GWDate", ["Garden Waste (Green)"]), + ("686294de50729", "DWDate", ["Household Waste (Grey)"]), + ("6863a78a1dd8e", "FWDate", ["Food Waste (Brown)"]), + ("68629dd642423", "MDRDate", ["Recycling (Blue)"]), ] def parse_data(self, page: str, **kwargs) -> dict: From 9db75a129e253056e399c3aa1dcd13962b9aa687 Mon Sep 17 00:00:00 2001 From: m26dvd <31007572+m26dvd@users.noreply.github.com> Date: Fri, 19 Sep 2025 15:31:26 +0100 Subject: [PATCH 141/425] fix: #1606 - Brighton and Hove City Council fix: #1606 - Brighton and Hove City Council --- .../councils/BrightonandHoveCityCouncil.py | 51 ++++++------------- 1 file changed, 15 insertions(+), 36 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/BrightonandHoveCityCouncil.py b/uk_bin_collection/uk_bin_collection/councils/BrightonandHoveCityCouncil.py index 32d54790a2..4055d8af77 100644 --- a/uk_bin_collection/uk_bin_collection/councils/BrightonandHoveCityCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/BrightonandHoveCityCouncil.py @@ -72,7 +72,9 @@ def parse_data(self, page: str, **kwargs) -> dict: break if not found: - raise Exception(f"Address containing '{user_paon}' not found in dropdown options") + raise Exception( + f"Address containing '{user_paon}' not found in dropdown options" + ) submit_btn = wait.until( EC.presence_of_element_located( @@ -84,7 +86,7 @@ def parse_data(self, page: str, **kwargs) -> dict: results = wait.until( EC.presence_of_element_located( - (By.XPATH, f'//span[contains(@class,"collection-sub")]') + (By.XPATH, f'//div[contains(@class,"mx-name-listView1")]') ) ) @@ -96,44 +98,21 @@ def parse_data(self, page: str, **kwargs) -> dict: current_date = datetime.now() # Find all elements with class starting with 'mx-name-index-' - bins = soup.find_all(class_=lambda x: x and x.startswith("mx-name-index-")) + bin_view = soup.find(class_="mx-name-listView1") + bins = bin_view.find_all( + class_=lambda x: x and x.startswith("mx-name-index-") + ) for bin_item in bins: - bin_type = bin_item.find(class_="collection-main").text.strip() - day_of_week_elements = bin_item.find_all(class_="collection-header") - bin_date = None - - for elem in day_of_week_elements: - if ( - elem.text.strip() != bin_type - ): # Avoid taking the bin type as the date - next_sibling = elem.find_next_sibling() - if next_sibling: - bin_date_str = next_sibling.text.strip() - try: - # Try parsing the date string in the format 'dd Month' (e.g., '30 Dec', '5 January') - bin_date = datetime.strptime(bin_date_str, "%d %b") - except ValueError: - try: - # If the above format fails, try 'dd MonthName' (e.g., '30 December', '5 January') - bin_date = datetime.strptime(bin_date_str, "%d %B") - except ValueError: - pass - - if bin_date: - # Set the year based on the logic provided - if bin_date.month < current_date.month: - bin_date = bin_date.replace( - year=current_date.year + 1 - ) - else: - bin_date = bin_date.replace(year=current_date.year) - # Format the date to the desired format - bin_date = bin_date.strftime("%d/%m/%Y") - break + bin_type = bin_item.find(class_="mx-name-text31").text.strip() + + bin_date_str = bin_item.find(class_="mx-name-text29").text.strip() + + bin_date = datetime.strptime(bin_date_str, "%d %B %Y") + bin_date = bin_date.strftime(date_format) + dict_data = {"type": bin_type, "collectionDate": bin_date} data["bins"].append(dict_data) - print(data) except Exception as e: # Here you can log the exception if needed print(f"An error occurred: {e}") From 37590c7ac33b797e12156597b848df4d2dda143e Mon Sep 17 00:00:00 2001 From: m26dvd <31007572+m26dvd@users.noreply.github.com> Date: Fri, 19 Sep 2025 15:44:03 +0100 Subject: [PATCH 142/425] fix: #1604 - West Berkshire Council fix: #1604 - West Berkshire Council --- .../councils/WestBerkshireCouncil.py | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/WestBerkshireCouncil.py b/uk_bin_collection/uk_bin_collection/councils/WestBerkshireCouncil.py index 36908175f3..3a905547ac 100644 --- a/uk_bin_collection/uk_bin_collection/councils/WestBerkshireCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/WestBerkshireCouncil.py @@ -38,7 +38,7 @@ def parse_data(self, page: str, **kwargs) -> dict: # Wait for the postcode field to appear then populate it inputElement_postcode = WebDriverWait(driver, 30).until( EC.presence_of_element_located( - (By.ID, "FINDYOURBINDAYS_ADDRESSLOOKUPPOSTCODE") + (By.ID, "FINDYOURBINDAYS3WEEKLY_ADDRESSLOOKUPPOSTCODE") ) ) inputElement_postcode.send_keys(user_postcode) @@ -46,7 +46,7 @@ def parse_data(self, page: str, **kwargs) -> dict: # Click search button findAddress = WebDriverWait(driver, 10).until( EC.presence_of_element_located( - (By.ID, "FINDYOURBINDAYS_ADDRESSLOOKUPSEARCH") + (By.ID, "FINDYOURBINDAYS3WEEKLY_ADDRESSLOOKUPSEARCH") ) ) findAddress.click() @@ -56,7 +56,7 @@ def parse_data(self, page: str, **kwargs) -> dict: ( By.XPATH, "" - "//*[@id='FINDYOURBINDAYS_ADDRESSLOOKUPADDRESS']//option[contains(., '" + "//*[@id='FINDYOURBINDAYS3WEEKLY_ADDRESSLOOKUPADDRESS']//option[contains(., '" + user_paon + "')]", ) @@ -66,7 +66,10 @@ def parse_data(self, page: str, **kwargs) -> dict: # Wait for the submit button to appear, then click it to get the collection dates WebDriverWait(driver, 30).until( EC.presence_of_element_located( - (By.XPATH, '//*[@id="FINDYOURBINDAYS_RUBBISHDATE"]/div') + ( + By.XPATH, + '//*[@id="FINDYOURBINDAYS3WEEKLY_RUBBISHRECYCLEFOODDATE"]/div', + ) ) ) time.sleep(2) @@ -74,10 +77,10 @@ def parse_data(self, page: str, **kwargs) -> dict: soup = BeautifulSoup(driver.page_source, features="html.parser") soup.prettify() - rubbish_div = soup.find( - "div", {"id": "FINDYOURBINDAYS_RUBBISHDATE_OUTERDIV"} + rubbish_div = soup.find("div", {"class": "rubbish_collection_difs_black"}) + rubbish_date = rubbish_div.find( + "div", {"class": "rubbish_date_container_left_datetext"} ) - rubbish_date = rubbish_div.find_all("div")[2] if rubbish_date.text == "Today": rubbish_date = datetime.now() else: @@ -86,10 +89,10 @@ def parse_data(self, page: str, **kwargs) -> dict: "%A %d %B", ).replace(year=datetime.now().year) - recycling_div = soup.find( - "div", {"id": "FINDYOURBINDAYS_RECYCLINGDATE_OUTERDIV"} + recycling_div = soup.find("div", {"class": "rubbish_collection_difs_green"}) + recycling_date = recycling_div.find( + "div", {"class": "rubbish_date_container_left_datetext"} ) - recycling_date = recycling_div.find_all("div")[2] if recycling_date.text == "Today": recycling_date = datetime.now() else: @@ -98,10 +101,10 @@ def parse_data(self, page: str, **kwargs) -> dict: "%A %d %B", ).replace(year=datetime.now().year) - food_div = soup.find( - "div", {"id": "FINDYOURBINDAYS_FOODWASTEDATE_OUTERDIV"} + food_div = soup.find("div", {"class": "rubbish_collection_difs_purple"}) + food_date = food_div.find( + "div", {"class": "rubbish_date_container_left_datetext"} ) - food_date = food_div.find_all("div")[2] if food_date.text == "Today": food_date = datetime.now() else: From 5c0fb1d68b6b89cf598ba441df1fabec612af7a5 Mon Sep 17 00:00:00 2001 From: m26dvd <31007572+m26dvd@users.noreply.github.com> Date: Fri, 19 Sep 2025 15:54:07 +0100 Subject: [PATCH 143/425] fix: #1554 - Folkestone and Hythe District Council fix: #1554 - Folkestone and Hythe District Council --- .../FolkstoneandHytheDistrictCouncil.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/uk_bin_collection/uk_bin_collection/councils/FolkstoneandHytheDistrictCouncil.py b/uk_bin_collection/uk_bin_collection/councils/FolkstoneandHytheDistrictCouncil.py index ed4c7c861b..21c7d47996 100644 --- a/uk_bin_collection/uk_bin_collection/councils/FolkstoneandHytheDistrictCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/FolkstoneandHytheDistrictCouncil.py @@ -74,6 +74,28 @@ def parse_data(self, page: str, **kwargs) -> dict: } bindata["bins"].append(dict_data) + # Extract the Garden Waste schedule + garden_waste_section = soup.find( + "span", text=lambda x: x and "Garden Waste" in x + ) + if garden_waste_section: + bin_types = garden_waste_section.text.replace("Garden Waste: ", "").split( + " / " + ) + garden_waste_dates = garden_waste_section.find_next("ul").find_all("li") + for date in garden_waste_dates: + for bin_type in bin_types: + dict_data = { + "type": bin_type.strip(), + "collectionDate": datetime.strptime( + remove_ordinal_indicator_from_date_string( + date.text.strip() + ), + "%A %d %B %Y", + ).strftime("%d/%m/%Y"), + } + bindata["bins"].append(dict_data) + bindata["bins"].sort( key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y") ) From 2b7182739f28b535c933e497ffe8b33c3f2c673f Mon Sep 17 00:00:00 2001 From: m26dvd <31007572+m26dvd@users.noreply.github.com> Date: Sat, 20 Sep 2025 22:28:12 +0100 Subject: [PATCH 144/425] fix: #1520 - Erewash Borough Council fix: #1520 - Erewash Borough Council --- uk_bin_collection/tests/input.json | 2 +- .../councils/ErewashBoroughCouncil.py | 66 +++++++++---------- 2 files changed, 33 insertions(+), 35 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index 9b2fe1ad32..16590f262b 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -885,7 +885,7 @@ "ErewashBoroughCouncil": { "skip_get_url": true, "uprn": "10003582028", - "url": "https://map.erewash.gov.uk/isharelive.web/myerewash.aspx", + "url": "https://www.erewash.gov.uk", "wiki_name": "Erewash", "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", "LAD24CD": "E07000036" diff --git a/uk_bin_collection/uk_bin_collection/councils/ErewashBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/ErewashBoroughCouncil.py index a5a731b062..93963568e8 100644 --- a/uk_bin_collection/uk_bin_collection/councils/ErewashBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/ErewashBoroughCouncil.py @@ -1,4 +1,7 @@ +import json + from bs4 import BeautifulSoup + from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass @@ -16,46 +19,41 @@ def parse_data(self, page: str, **kwargs) -> dict: uprn = kwargs.get("uprn") check_uprn(uprn) + label_map = { + "domestic-waste-collection-service": "Household Waste", + "recycling-collection-service": "Recycling", + "garden-waste-collection-service": "Garden Waste", + } + requests.packages.urllib3.disable_warnings() response = requests.get( - f"https://map.erewash.gov.uk/isharelive.web/myerewash.aspx?action=SetAddress&UniqueId={uprn}", + f"https://www.erewash.gov.uk/bbd-whitespace/one-year-collection-dates-without-christmas?uprn={uprn}", headers={"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)"}, ) + # Parse the JSON response + payload = response.json() + bin_collection = json.loads(payload) if isinstance(payload, str) else payload - soup = BeautifulSoup(response.text, features="html.parser") - collections = soup.find("div", {"aria-label": "Waste Collection"}).find_all( - "div", {"class": "atPanelContent"} + cd = next( + i["settings"]["collection_dates"] + for i in bin_collection + if i.get("command") == "settings" ) - for c in collections: - bin_type = c.find("h4").get_text(strip=True) - if "my next" in bin_type.lower(): - collection_info = c.find("div", {"class": "atPanelData"}).get_text( - strip=True - ) - results = re.search( - "([A-Za-z]+ \\d+[A-Za-z]+ [A-Za-z]+ \\d*)", collection_info + + for month in cd.values(): + for e in month: + d = e["date"] # "YYYY-MM-DD" + label = label_map.get( + e.get("service-identifier"), + e.get("service") or e.get("service-identifier"), ) - if results: - collection_date = datetime.strptime( - remove_ordinal_indicator_from_date_string(results[1]).strip(), - "%A %d %B %Y", - ).strftime(date_format) - dict_data = { - "type": bin_type.replace("My Next ", "").replace( - " Collection", "" - ), - "collectionDate": collection_date, - } - data["bins"].append(dict_data) - if "garden waste" in collection_info.lower(): - dict_data = { - "type": "Garden Waste", - "collectionDate": collection_date, - } - data["bins"].append(dict_data) - - data["bins"].sort( - key=lambda x: datetime.strptime(x.get("collectionDate"), date_format) - ) + + dict_data = { + "type": label, + "collectionDate": datetime.strptime(d, "%Y-%m-%d").strftime( + date_format + ), + } + data["bins"].append(dict_data) return data From 08bf8675d0ca2b597769cd894ad747b58e4095fc Mon Sep 17 00:00:00 2001 From: Wiki GitHub Action Date: Sat, 20 Sep 2025 21:28:38 +0000 Subject: [PATCH 145/425] docs: Update Councils.md from input.json --- wiki/Councils.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wiki/Councils.md b/wiki/Councils.md index c042693d67..089040d589 100644 --- a/wiki/Councils.md +++ b/wiki/Councils.md @@ -1615,7 +1615,7 @@ Note: Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your U ### Erewash ```commandline -python collect_data.py ErewashBoroughCouncil https://map.erewash.gov.uk/isharelive.web/myerewash.aspx -s -u XXXXXXXX +python collect_data.py ErewashBoroughCouncil https://www.erewash.gov.uk -s -u XXXXXXXX ``` Additional parameters: - `-s` - skip get URL From d5516f6a17553d0717c913e34078bf606cfecab9 Mon Sep 17 00:00:00 2001 From: m26dvd <31007572+m26dvd@users.noreply.github.com> Date: Sat, 20 Sep 2025 23:16:58 +0100 Subject: [PATCH 146/425] fix: #1570 - Slough Borough Council fix: #1570 - Slough Borough Council --- .../councils/SloughBoroughCouncil.py | 60 ++++++++++++------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/SloughBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/SloughBoroughCouncil.py index 1811cd8f39..8d69602ef0 100644 --- a/uk_bin_collection/uk_bin_collection/councils/SloughBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/SloughBoroughCouncil.py @@ -1,15 +1,18 @@ -import time import re -import requests +import time from datetime import datetime + +import requests from bs4 import BeautifulSoup from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait + from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass + def get_street_from_postcode(postcode: str, api_key: str) -> str: url = "https://maps.googleapis.com/maps/api/geocode/json" params = {"address": postcode, "key": api_key} @@ -25,6 +28,7 @@ def get_street_from_postcode(postcode: str, api_key: str) -> str: raise ValueError("No street (route) found in the response.") + class CouncilClass(AbstractGetBinDataClass): def parse_data(self, page: str, **kwargs) -> dict: driver = None @@ -37,10 +41,10 @@ def parse_data(self, page: str, **kwargs) -> dict: headless = kwargs.get("headless") web_driver = kwargs.get("web_driver") - driver = create_webdriver(web_driver, headless, None, __name__) + UserAgent = "Mozilla/5.0" + driver = create_webdriver(web_driver, headless, UserAgent, __name__) page = "https://www.slough.gov.uk/bin-collections" driver.get(page) - # Accept cookies WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.ID, "ccc-recommended-settings")) @@ -50,14 +54,20 @@ def parse_data(self, page: str, **kwargs) -> dict: address_input = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, "keyword_directory25")) ) - user_address = get_street_from_postcode(user_postcode, "AIzaSyBDLULT7EIlNtHerswPtfmL15Tt3Oc0bV8") + user_address = get_street_from_postcode( + user_postcode, "AIzaSyBDLULT7EIlNtHerswPtfmL15Tt3Oc0bV8" + ) address_input.send_keys(user_address + Keys.ENTER) # Wait for address results to load WebDriverWait(driver, 10).until( - EC.presence_of_all_elements_located((By.CSS_SELECTOR, "span.list__link-text")) + EC.presence_of_all_elements_located( + (By.CSS_SELECTOR, "span.list__link-text") + ) + ) + span_elements = driver.find_elements( + By.CSS_SELECTOR, "span.list__link-text" ) - span_elements = driver.find_elements(By.CSS_SELECTOR, "span.list__link-text") for span in span_elements: if user_address.lower() in span.text.lower(): @@ -68,7 +78,9 @@ def parse_data(self, page: str, **kwargs) -> dict: # Wait for address detail page WebDriverWait(driver, 10).until( - EC.presence_of_element_located((By.CSS_SELECTOR, "section.site-content")) + EC.presence_of_element_located( + (By.CSS_SELECTOR, "section.site-content") + ) ) soup = BeautifulSoup(driver.page_source, "html.parser") @@ -86,28 +98,33 @@ def parse_data(self, page: str, **kwargs) -> dict: bin_url = "https://www.slough.gov.uk" + bin_url # Visit the child page - print(f"Navigating to {bin_url}") + # print(f"Navigating to {bin_url}") driver.get(bin_url) WebDriverWait(driver, 10).until( - EC.presence_of_element_located((By.CSS_SELECTOR, "div.page-content")) + EC.presence_of_element_located( + (By.CSS_SELECTOR, "div.page-content") + ) ) child_soup = BeautifulSoup(driver.page_source, "html.parser") editor_div = child_soup.find("div", class_="editor") if not editor_div: - print("No editor div found on bin detail page.") + # print("No editor div found on bin detail page.") continue ul = editor_div.find("ul") if not ul: - print("No

    with dates found in editor div.") + # print("No
      with dates found in editor div.") continue for li in ul.find_all("li"): raw_text = li.get_text(strip=True).replace(".", "") - if "no collection" in raw_text.lower() or "no collections" in raw_text.lower(): - print(f"Ignoring non-collection note: {raw_text}") + if ( + "no collection" in raw_text.lower() + or "no collections" in raw_text.lower() + ): + # print(f"Ignoring non-collection note: {raw_text}") continue raw_date = raw_text @@ -117,19 +134,20 @@ def parse_data(self, page: str, **kwargs) -> dict: except ValueError: raw_date_cleaned = raw_date.split("(")[0].strip() try: - parsed_date = datetime.strptime(raw_date_cleaned, "%d %B %Y") + parsed_date = datetime.strptime( + raw_date_cleaned, "%d %B %Y" + ) except Exception: print(f"Could not parse date: {raw_text}") continue formatted_date = parsed_date.strftime("%d/%m/%Y") contains_date(formatted_date) - bin_data["bins"].append({ - "type": bin_type, - "collectionDate": formatted_date - }) + bin_data["bins"].append( + {"type": bin_type, "collectionDate": formatted_date} + ) - print(f"Type: {bin_type}, Date: {formatted_date}") + # print(f"Type: {bin_type}, Date: {formatted_date}") except Exception as e: print(f"An error occurred: {e}") @@ -137,4 +155,4 @@ def parse_data(self, page: str, **kwargs) -> dict: finally: if driver: driver.quit() - return bin_data \ No newline at end of file + return bin_data From 77e36836021585855f2b33bc659b42c6ae92e431 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 21 Sep 2025 05:25:56 +0000 Subject: [PATCH 147/425] =?UTF-8?q?bump:=20version=200.153.0=20=E2=86=92?= =?UTF-8?q?=200.154.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 35 +++++++++++++++++++ custom_components/uk_bin_collection/const.py | 2 +- .../uk_bin_collection/manifest.json | 4 +-- pyproject.toml | 2 +- 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1047745a35..3a4c6ed909 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,39 @@ ======= +## 0.154.0 (2025-09-21) + +### Feat + +- handle changes to northumberland council website +- modify input for NorthumberlandCouncil to accept uprn instead of house number, and use new page structure + +### Fix + +- the cookie banner is not optional +- #1570 - Slough Borough Council +- #1570 - Slough Borough Council +- #1520 - Erewash Borough Council +- #1520 - Erewash Borough Council +- #1554 - Folkestone and Hythe District Council +- #1554 - Folkestone and Hythe District Council +- #1604 - West Berkshire Council +- #1604 - West Berkshire Council +- #1606 - Brighton and Hove City Council +- #1606 - Brighton and Hove City Council +- #1565 - BCP Council +- #1565 - BCP Council +- #1571 - Castle Point District Council +- #1571 - Castle Point District Council +- #1584 - NorthHertfordshireDistrictCouncil +- #1584 - NorthHertfordshireDistrictCouncil +- #1599 +- #1599 - Basingstoke Council +- #1587 +- #1587 - Hartlepool Borough Council +- #1588 +- #1588 Glasgow City Council +- #1591 +- #1591 Rushmoor Council + ## 0.153.0 (2025-09-02) ### Feat diff --git a/custom_components/uk_bin_collection/const.py b/custom_components/uk_bin_collection/const.py index f77bd23205..bb6e50a9d3 100644 --- a/custom_components/uk_bin_collection/const.py +++ b/custom_components/uk_bin_collection/const.py @@ -4,7 +4,7 @@ from homeassistant.const import Platform -INPUT_JSON_URL = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.153.0/uk_bin_collection/tests/input.json" +INPUT_JSON_URL = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.154.0/uk_bin_collection/tests/input.json" DEFAULT_NAME = "UK Bin Collection Data" diff --git a/custom_components/uk_bin_collection/manifest.json b/custom_components/uk_bin_collection/manifest.json index 01398acafa..b735d85910 100644 --- a/custom_components/uk_bin_collection/manifest.json +++ b/custom_components/uk_bin_collection/manifest.json @@ -9,7 +9,7 @@ "integration_type": "service", "iot_class": "cloud_polling", "issue_tracker": "https://github.com/robbrad/UKBinCollectionData/issues", - "requirements": ["uk-bin-collection>=0.153.0"], - "version": "0.153.0", + "requirements": ["uk-bin-collection>=0.154.0"], + "version": "0.154.0", "zeroconf": [] } diff --git a/pyproject.toml b/pyproject.toml index 5c8cdd9f5d..fff62ebbfe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "uk_bin_collection" -version = "0.153.0" +version = "0.154.0" description = "Python Lib to collect UK Bin Data" readme = "README.md" authors = ["Robert Bradley "] From ad4750e2d59dd1d0bfa114d7728657a71d8dd552 Mon Sep 17 00:00:00 2001 From: Richard Date: Mon, 22 Sep 2025 17:51:37 +0100 Subject: [PATCH 148/425] fix: #1566 South Gloucestershire Council --- uk_bin_collection/tests/input.json | 2 +- .../councils/SouthGloucestershireCouncil.py | 40 +++++++++---------- wiki/Councils.md | 2 +- 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index dbb15183c8..27eda9109b 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -2122,7 +2122,7 @@ "SouthGloucestershireCouncil": { "skip_get_url": true, "uprn": "566419", - "url": "https://beta.southglos.gov.uk/waste-and-recycling-collection-date", + "url": "https://api.southglos.gov.uk/wastecomp/GetCollectionDetails", "wiki_name": "South Gloucestershire", "wiki_note": "Provide your UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", "LAD24CD": "E06000025" diff --git a/uk_bin_collection/uk_bin_collection/councils/SouthGloucestershireCouncil.py b/uk_bin_collection/uk_bin_collection/councils/SouthGloucestershireCouncil.py index 1b74c0c0f4..3058e74d20 100644 --- a/uk_bin_collection/uk_bin_collection/councils/SouthGloucestershireCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/SouthGloucestershireCouncil.py @@ -6,17 +6,16 @@ def format_bin_data(key: str, date: datetime): formatted_date = date.strftime(date_format) - - if re.match(r"^R\d+$", key) is not None: - # RX matches both general waste and recycling - return [ - ("General Waste (Black Bin)", formatted_date), - ("Recycling & Food Waste", formatted_date), - ] - elif re.match(r"^G\d+$", key) is not None: + servicename = key.get("hso_servicename") + print(servicename) + if re.match(r"^Recycl", servicename) is not None: + return [ ("Recycling", formatted_date) ] + elif re.match(r"^Refuse", servicename) is not None: + return [("General Waste (Black Bin)", formatted_date)] + elif re.match(r"^Garden", servicename) is not None: return [("Garden Waste (Green Bin)", formatted_date)] - elif re.match(r"^C\d+$", key) is not None: - return [("Recycling & Food Waste", formatted_date)] + elif re.match(r"^Food", servicename) is not None: + return [("Food Waste", formatted_date)] else: return None @@ -27,37 +26,34 @@ def parse_data(self, page: str, **kwargs) -> dict: check_uprn(uprn) api_url = ( - f"https://webapps.southglos.gov.uk/Webservices/SGC.RefuseCollectionService/RefuseCollectionService" - f".svc/getCollections/{uprn}" + f"https://api.southglos.gov.uk/wastecomp/GetCollectionDetails" + f"?uprn={uprn}" ) headers = {"content-type": "application/json"} response = requests.get(api_url, headers=headers) - json_response = json.loads(response.content) + json_response = response.json() if not json_response: raise ValueError("No collection data found for provided UPRN.") - collection_data = json_response[0] + collection_data = json_response.get('value') today = datetime.today() eight_weeks = datetime.today() + timedelta(days=8 * 7) data = {"bins": []} collection_tuple = [] - - for key in collection_data: - if key == "CalendarName": - continue - - item = collection_data[key] + for collection in collection_data: + print(collection) + item = collection.get('hso_nextcollection') if item == "": continue - collection_date = datetime.strptime(item, date_format) + collection_date = datetime.fromisoformat(item) if today.date() <= collection_date.date() <= eight_weeks.date(): - bin_data = format_bin_data(key, collection_date) + bin_data = format_bin_data(collection, collection_date) if bin_data is not None: for bin_date in bin_data: collection_tuple.append(bin_date) diff --git a/wiki/Councils.md b/wiki/Councils.md index e04a39c6c8..d32e5aeee3 100644 --- a/wiki/Councils.md +++ b/wiki/Councils.md @@ -3344,7 +3344,7 @@ Note: Replace `XXXXXXXX` with your UPRN. You can find your UPRN using [FindMyAdd ### South Gloucestershire ```commandline -python collect_data.py SouthGloucestershireCouncil https://beta.southglos.gov.uk/waste-and-recycling-collection-date -s -u XXXXXXXX +python collect_data.py SouthGloucestershireCouncil https://api.southglos.gov.uk/wastecomp/GetCollectionDetails -s -u XXXXXXXX ``` Additional parameters: - `-s` - skip get URL From 2484c7a86359a2ff5efe791784b8f02bc37e142b Mon Sep 17 00:00:00 2001 From: m26dvd <31007572+m26dvd@users.noreply.github.com> Date: Mon, 22 Sep 2025 22:35:39 +0100 Subject: [PATCH 149/425] fix: #1574 - Test Valley Borough Council fix: #1574 - Test Valley Borough Council NOTE: This now requires Selenium --- uk_bin_collection/tests/input.json | 9 +- .../councils/TestValleyBoroughCouncil.py | 284 +++++++----------- wiki/Councils.md | 7 +- 3 files changed, 115 insertions(+), 185 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index dbb15183c8..0dac8d1162 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -2415,12 +2415,13 @@ "LAD24CD": "E07000076" }, "TestValleyBoroughCouncil": { - "postcode": "SO51 9ZD", + "postcode": "SO51 0BY", "skip_get_url": true, - "uprn": "200010012019", - "url": "https://testvalley.gov.uk/wasteandrecycling/when-are-my-bins-collected", + "house_number": "2", + "url": "https://testvalley.gov.uk/wasteandrecycling/when-are-my-bins-collected/when-are-my-bins-collected", + "web_driver": "http://selenium:4444", "wiki_name": "Test Valley", - "wiki_note": "Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN.", + "wiki_note": "Provide your house number and postcode", "LAD24CD": "E07000093" }, "ThanetDistrictCouncil": { diff --git a/uk_bin_collection/uk_bin_collection/councils/TestValleyBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/TestValleyBoroughCouncil.py index 2e14b43cb4..978789b365 100644 --- a/uk_bin_collection/uk_bin_collection/councils/TestValleyBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/TestValleyBoroughCouncil.py @@ -1,4 +1,9 @@ +import datetime + from bs4 import BeautifulSoup +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.wait import WebDriverWait from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass @@ -13,192 +18,115 @@ class CouncilClass(AbstractGetBinDataClass): """ def parse_data(self, page: str, **kwargs) -> dict: - user_postcode = kwargs.get("postcode") - check_postcode(user_postcode) - user_uprn = kwargs.get("uprn") - check_uprn(user_uprn) - - headers = { - "User-Agent": "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/87.0.4280.141 Safari/537.36" - } - - requests.packages.urllib3.disable_warnings() - with requests.Session() as s: - # Set Headers - s.headers = headers - - # Get the first page - This is the Search for property by Post Code page - resource = s.get( - "https://iweb.itouchvision.com/portal/f?p=customer:BIN_DAYS:::NO:RP:UID:13353F039C4B1454827EE05536414091A8C058F4" - ) - # Create a BeautifulSoup object from the page's HTML - soup = BeautifulSoup(resource.text, "html.parser") - - # The page contains a number of values that must be passed into subsequent requests - extract them here - payload = { - i["name"]: i.get("value", "") for i in soup.select("input[name]") - } - payload2 = { - i["data-for"]: i.get("value", "") - for i in soup.select("input[data-for]") - } - payload_salt = soup.select_one('input[id="pSalt"]').get("value") - payload_protected = soup.select_one('input[id="pPageItemsProtected"]').get( - "value" + driver = None + try: + data = {"bins": []} + url = kwargs.get("url") + user_paon = kwargs.get("paon") + user_postcode = kwargs.get("postcode") + web_driver = kwargs.get("web_driver") + headless = kwargs.get("headless") + check_paon(user_paon) + check_postcode(user_postcode) + + # Use a realistic user agent to help bypass Cloudflare + user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + driver = create_webdriver(web_driver, headless, user_agent, __name__) + driver.get( + "https://testvalley.gov.uk/wasteandrecycling/when-are-my-bins-collected/when-are-my-bins-collected" ) - # Add the PostCode and 'SEARCH' to the payload - payload["p_request"] = "SEARCH" - payload["P153_POST_CODE"] = user_postcode - - # Manipulate the lists and build the JSON that must be submitted in further requests - some data is nested - merged_list = {**payload, **payload2} - new_list = [] - other_list = {} - for key in merged_list.keys(): - temp_list = {} - val = merged_list[key] - if key in [ - "P153_UPRN", - "P153_TEMP", - "P153_SYSDATE", - "P0_LANGUAGE", - "P153_POST_CODE", - ]: - temp_list = {"n": key, "v": val} - new_list.append(temp_list) - elif key in [ - "p_flow_id", - "p_flow_step_id", - "p_instance", - "p_page_submission_id", - "p_request", - "p_reload_on_submit", - ]: - other_list[key] = val - else: - temp_list = {"n": key, "v": "", "ck": val} - new_list.append(temp_list) - - json_builder = { - "pageItems": { - "itemsToSubmit": new_list, - "protected": payload_protected, - "rowVersion": "", - "formRegionChecksums": [], - }, - "salt": payload_salt, - } - json_object = json.dumps(json_builder, separators=(",", ":")) - other_list["p_json"] = json_object - - # Set Referrer header - s.headers.update( - { - "referer": "https://iweb.itouchvision.com/portal/f?p=customer:BIN_DAYS:::NO:RP:UID:13353F039C4B1454827EE05536414091A8C058F4" - } + # Wait for the postcode field to appear then populate it + inputElement_postcode = WebDriverWait(driver, 30).until( + EC.presence_of_element_located((By.ID, "postcodeSearch")) ) + inputElement_postcode.send_keys(user_postcode) - # Generate POST including all the JSON we just built - s.post( - "https://iweb.itouchvision.com/portal/wwv_flow.accept", data=other_list + # Click search button + findAddress = WebDriverWait(driver, 10).until( + EC.presence_of_element_located((By.CLASS_NAME, "govuk-button")) ) - - # The second page on the portal would normally allow you to select your property from a dropdown list of - # those that are at the postcode entered on the previous page - # The required cookies are stored within the session so re-use the session to keep them - resource = s.get( - "https://iweb.itouchvision.com/portal/itouchvision/r/customer/bin_days" + findAddress.click() + + # Wait for the 'Select address' dropdown to appear and select option matching the house name/number + WebDriverWait(driver, 10).until( + EC.element_to_be_clickable( + ( + By.XPATH, + "//select[@id='addressSelect']//option[contains(., '" + + user_paon + + "')]", + ) + ) + ).click() + + # Wait for the collections table to appear + WebDriverWait(driver, 20).until( + EC.presence_of_element_located( + ( + By.XPATH, + "//h2[contains(@class,'mt-4') and contains(@class,'govuk-heading-s') and normalize-space(.)='Your next collections']", + ) + ) ) - # Create a BeautifulSoup object from the page's HTML - soup = BeautifulSoup(resource.text, "html.parser") - - # The page contains a number of values that must be passed into subsequent requests - extract them here - payload = { - i["name"]: i.get("value", "") for i in soup.select("input[name]") - } - payload2 = { - i["data-for"]: i.get("value", "") - for i in soup.select("input[data-for]") - } - payload_salt = soup.select_one('input[id="pSalt"]').get("value") - payload_protected = soup.select_one('input[id="pPageItemsProtected"]').get( - "value" - ) + soup = BeautifulSoup(driver.page_source, features="html.parser") - # Add the UPRN and 'SUBMIT' to the payload - payload["p_request"] = "SUBMIT" - payload["P153_UPRN"] = user_uprn - - # Manipulate the lists and build the JSON that must be submitted in further requests - some data is nested - merged_list = {**payload, **payload2} - new_list = [] - other_list = {} - for key in merged_list.keys(): - temp_list = {} - val = merged_list[key] - if key in ["P153_UPRN", "P153_TEMP", "P153_SYSDATE", "P0_LANGUAGE"]: - temp_list = {"n": key, "v": val} - new_list.append(temp_list) - elif key in ["P153_ZABY"]: - temp_list = {"n": key, "v": "1", "ck": val} - new_list.append(temp_list) - elif key in ["P153_POST_CODE"]: - temp_list = {"n": key, "v": user_postcode, "ck": val} - new_list.append(temp_list) - elif key in [ - "p_flow_id", - "p_flow_step_id", - "p_instance", - "p_page_submission_id", - "p_request", - "p_reload_on_submit", - ]: - other_list[key] = val - else: - temp_list = {"n": key, "v": "", "ck": val} - new_list.append(temp_list) - - json_builder = { - "pageItems": { - "itemsToSubmit": new_list, - "protected": payload_protected, - "rowVersion": "", - "formRegionChecksums": [], - }, - "salt": payload_salt, - } - - json_object = json.dumps(json_builder, separators=(",", ":")) - other_list["p_json"] = json_object - - # Generate POST including all the JSON we just built - s.post( - "https://iweb.itouchvision.com/portal/wwv_flow.accept", data=other_list - ) + collections = soup.find_all("div", {"class": "p-2"}) - # The third and final page on the portal shows the detail of the waste collection services - # The required cookies are stored within the session so re-use the session to keep them - resource = s.get( - "https://iweb.itouchvision.com/portal/itouchvision/r/customer/bin_days" - ) + for collection in collections: + bin_type = collection.find("h3").get_text() - # Create a BeautifulSoup object from the page's HTML - soup = BeautifulSoup(resource.text, "html.parser") - data = {"bins": []} + next_collection = soup.find("div", {"class": "fw-bold"}).get_text() - # Loop through the items on the page and build a JSON object for ingestion - for item in soup.select(".t-MediaList-item"): - for value in item.select(".t-MediaList-body"): - dict_data = { - "type": value.select("span")[1].get_text(strip=True).title(), - "collectionDate": datetime.strptime( - value.select(".t-MediaList-desc")[0].get_text(strip=True), - "%A, %d %B, %Y", - ).strftime(date_format), - } - data["bins"].append(dict_data) - - return data + following_collection = soup.find( + lambda t: ( + t.name == "div" + and t.get_text(strip=True).lower().startswith("followed by") + ) + ).get_text() + + next_collection_date = datetime.strptime(next_collection, "%A %d %B") + + following_collection_date = datetime.strptime( + following_collection, "followed by %A %d %B" + ) + + current_date = datetime.now() + next_collection_date = next_collection_date.replace( + year=current_date.year + ) + following_collection_date = following_collection_date.replace( + year=current_date.year + ) + + next_collection_date = get_next_occurrence_from_day_month( + next_collection_date + ) + + following_collection_date = get_next_occurrence_from_day_month( + following_collection_date + ) + + dict_data = { + "type": bin_type, + "collectionDate": next_collection_date.strftime(date_format), + } + data["bins"].append(dict_data) + + dict_data = { + "type": bin_type, + "collectionDate": following_collection_date.strftime(date_format), + } + data["bins"].append(dict_data) + + except Exception as e: + # Here you can log the exception if needed + print(f"An error occurred: {e}") + # Optionally, re-raise the exception if you want it to propagate + raise + finally: + # This block ensures that the driver is closed regardless of an exception + if driver: + driver.quit() + return data diff --git a/wiki/Councils.md b/wiki/Councils.md index e04a39c6c8..fad39371ab 100644 --- a/wiki/Councils.md +++ b/wiki/Councils.md @@ -3768,14 +3768,15 @@ Note: Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https ### Test Valley ```commandline -python collect_data.py TestValleyBoroughCouncil https://testvalley.gov.uk/wasteandrecycling/when-are-my-bins-collected -s -u XXXXXXXX -p "XXXX XXX" +python collect_data.py TestValleyBoroughCouncil https://testvalley.gov.uk/wasteandrecycling/when-are-my-bins-collected/when-are-my-bins-collected -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` Additional parameters: - `-s` - skip get URL -- `-u` - UPRN - `-p` - postcode +- `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: Provide your UPRN and postcode. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find your UPRN. +Note: Provide your house number and postcode --- From c0d2e23f06fef03a223996d02772fb3f08df3708 Mon Sep 17 00:00:00 2001 From: m26dvd <31007572+m26dvd@users.noreply.github.com> Date: Mon, 22 Sep 2025 22:42:26 +0100 Subject: [PATCH 150/425] fix: #1559 - Newport City Council fix: #1559 - Newport City Council --- uk_bin_collection/tests/input.json | 5 +- .../councils/NewportCityCouncil.py | 284 +++++++----------- wiki/Councils.md | 7 +- 3 files changed, 113 insertions(+), 183 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index 0dac8d1162..3772df80b9 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -1647,10 +1647,11 @@ "NewportCityCouncil": { "postcode": "NP20 4HE", "skip_get_url": true, - "uprn": "100100688837", + "house_number": "6", "url": "https://www.newport.gov.uk/", + "web_driver": "http://selenium:4444", "wiki_name": "Newport", - "wiki_note": "Pass the postcode and UPRN. You can find the UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "wiki_note": "Pass the postcode and house number in their respective arguments, both wrapped in quotes.", "LAD24CD": "W06000022" }, "NorthAyrshireCouncil": { diff --git a/uk_bin_collection/uk_bin_collection/councils/NewportCityCouncil.py b/uk_bin_collection/uk_bin_collection/councils/NewportCityCouncil.py index 4b4e0a04db..6b56175160 100644 --- a/uk_bin_collection/uk_bin_collection/councils/NewportCityCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/NewportCityCouncil.py @@ -1,4 +1,9 @@ +import datetime + from bs4 import BeautifulSoup +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.wait import WebDriverWait from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass @@ -13,192 +18,115 @@ class CouncilClass(AbstractGetBinDataClass): """ def parse_data(self, page: str, **kwargs) -> dict: - user_postcode = kwargs.get("postcode") - check_postcode(user_postcode) - user_uprn = kwargs.get("uprn") - check_uprn(user_uprn) - - headers = { - "User-Agent": "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/87.0.4280.141 Safari/537.36" - } - - requests.packages.urllib3.disable_warnings() - with requests.Session() as s: - # Set Headers - s.headers = headers - - # Get the first page - This is the Search for property by Post Code page - resource = s.get( - "https://iweb.itouchvision.com/portal/f?p=customer:BIN_DAYS:::NO:RP:UID:6CDD2A34C912312074D8E2410531401A8C00EFF7" - ) - # Create a BeautifulSoup object from the page's HTML - soup = BeautifulSoup(resource.text, "html.parser") - - # The page contains a number of values that must be passed into subsequent requests - extract them here - payload = { - i["name"]: i.get("value", "") for i in soup.select("input[name]") - } - payload2 = { - i["data-for"]: i.get("value", "") - for i in soup.select("input[data-for]") - } - payload_salt = soup.select_one('input[id="pSalt"]').get("value") - payload_protected = soup.select_one('input[id="pPageItemsProtected"]').get( - "value" + driver = None + try: + data = {"bins": []} + url = kwargs.get("url") + user_paon = kwargs.get("paon") + user_postcode = kwargs.get("postcode") + web_driver = kwargs.get("web_driver") + headless = kwargs.get("headless") + check_paon(user_paon) + check_postcode(user_postcode) + + # Use a realistic user agent to help bypass Cloudflare + user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + driver = create_webdriver(web_driver, headless, user_agent, __name__) + driver.get( + "https://iportal.itouchvision.com/icollectionday/collection-day/?uuid=6CDD2A34C912312074D8E2410531401A8C00EFF7&lang=en" ) - # Add the PostCode and 'SEARCH' to the payload - payload["p_request"] = "SEARCH" - payload["P153_POST_CODE"] = user_postcode - - # Manipulate the lists and build the JSON that must be submitted in further requests - some data is nested - merged_list = {**payload, **payload2} - new_list = [] - other_list = {} - for key in merged_list.keys(): - temp_list = {} - val = merged_list[key] - if key in [ - "P153_UPRN", - "P153_TEMP", - "P153_SYSDATE", - "P0_LANGUAGE", - "P153_POST_CODE", - ]: - temp_list = {"n": key, "v": val} - new_list.append(temp_list) - elif key in [ - "p_flow_id", - "p_flow_step_id", - "p_instance", - "p_page_submission_id", - "p_request", - "p_reload_on_submit", - ]: - other_list[key] = val - else: - temp_list = {"n": key, "v": "", "ck": val} - new_list.append(temp_list) - - json_builder = { - "pageItems": { - "itemsToSubmit": new_list, - "protected": payload_protected, - "rowVersion": "", - "formRegionChecksums": [], - }, - "salt": payload_salt, - } - json_object = json.dumps(json_builder, separators=(",", ":")) - other_list["p_json"] = json_object - - # Set Referrer header - s.headers.update( - { - "referer": "https://iweb.itouchvision.com/portal/f?p=customer:BIN_DAYS:::NO:RP:UID:6CDD2A34C912312074D8E2410531401A8C00EFF7" - } + # Wait for the postcode field to appear then populate it + inputElement_postcode = WebDriverWait(driver, 30).until( + EC.presence_of_element_located((By.ID, "postcodeSearch")) ) + inputElement_postcode.send_keys(user_postcode) - # Generate POST including all the JSON we just built - s.post( - "https://iweb.itouchvision.com/portal/wwv_flow.accept", data=other_list + # Click search button + findAddress = WebDriverWait(driver, 10).until( + EC.presence_of_element_located((By.CLASS_NAME, "govuk-button")) ) - - # The second page on the portal would normally allow you to select your property from a dropdown list of - # those that are at the postcode entered on the previous page - # The required cookies are stored within the session so re-use the session to keep them - resource = s.get( - "https://iweb.itouchvision.com/portal/itouchvision/r/customer/bin_days" + findAddress.click() + + # Wait for the 'Select address' dropdown to appear and select option matching the house name/number + WebDriverWait(driver, 10).until( + EC.element_to_be_clickable( + ( + By.XPATH, + "//select[@id='addressSelect']//option[contains(., '" + + user_paon + + "')]", + ) + ) + ).click() + + # Wait for the collections table to appear + WebDriverWait(driver, 20).until( + EC.presence_of_element_located( + ( + By.XPATH, + "//h2[contains(@class,'mt-4') and contains(@class,'govuk-heading-s') and normalize-space(.)='Your next collections']", + ) + ) ) - # Create a BeautifulSoup object from the page's HTML - soup = BeautifulSoup(resource.text, "html.parser") - - # The page contains a number of values that must be passed into subsequent requests - extract them here - payload = { - i["name"]: i.get("value", "") for i in soup.select("input[name]") - } - payload2 = { - i["data-for"]: i.get("value", "") - for i in soup.select("input[data-for]") - } - payload_salt = soup.select_one('input[id="pSalt"]').get("value") - payload_protected = soup.select_one('input[id="pPageItemsProtected"]').get( - "value" - ) + soup = BeautifulSoup(driver.page_source, features="html.parser") - # Add the UPRN and 'SUBMIT' to the payload - payload["p_request"] = "SUBMIT" - payload["P153_UPRN"] = user_uprn - - # Manipulate the lists and build the JSON that must be submitted in further requests - some data is nested - merged_list = {**payload, **payload2} - new_list = [] - other_list = {} - for key in merged_list.keys(): - temp_list = {} - val = merged_list[key] - if key in ["P153_UPRN", "P153_TEMP", "P153_SYSDATE", "P0_LANGUAGE"]: - temp_list = {"n": key, "v": val} - new_list.append(temp_list) - elif key in ["P153_ZABY"]: - temp_list = {"n": key, "v": "1", "ck": val} - new_list.append(temp_list) - elif key in ["P153_POST_CODE"]: - temp_list = {"n": key, "v": user_postcode, "ck": val} - new_list.append(temp_list) - elif key in [ - "p_flow_id", - "p_flow_step_id", - "p_instance", - "p_page_submission_id", - "p_request", - "p_reload_on_submit", - ]: - other_list[key] = val - else: - temp_list = {"n": key, "v": "", "ck": val} - new_list.append(temp_list) - - json_builder = { - "pageItems": { - "itemsToSubmit": new_list, - "protected": payload_protected, - "rowVersion": "", - "formRegionChecksums": [], - }, - "salt": payload_salt, - } - - json_object = json.dumps(json_builder, separators=(",", ":")) - other_list["p_json"] = json_object - - # Generate POST including all the JSON we just built - s.post( - "https://iweb.itouchvision.com/portal/wwv_flow.accept", data=other_list - ) + collections = soup.find_all("div", {"class": "p-2"}) - # The third and final page on the portal shows the detail of the waste collection services - # The required cookies are stored within the session so re-use the session to keep them - resource = s.get( - "https://iweb.itouchvision.com/portal/itouchvision/r/customer/bin_days" - ) + for collection in collections: + bin_type = collection.find("h3").get_text() - # Create a BeautifulSoup object from the page's HTML - soup = BeautifulSoup(resource.text, "html.parser") - data = {"bins": []} + next_collection = soup.find("div", {"class": "fw-bold"}).get_text() - # Loop through the items on the page and build a JSON object for ingestion - for item in soup.select(".t-MediaList-item"): - for value in item.select(".t-MediaList-body"): - dict_data = { - "type": value.select("span")[1].get_text(strip=True).title(), - "collectionDate": datetime.strptime( - value.select(".t-MediaList-desc")[0].get_text(strip=True), - "%A, %d %B, %Y", - ).strftime(date_format), - } - data["bins"].append(dict_data) - - return data + following_collection = soup.find( + lambda t: ( + t.name == "div" + and t.get_text(strip=True).lower().startswith("followed by") + ) + ).get_text() + + next_collection_date = datetime.strptime(next_collection, "%A %d %B") + + following_collection_date = datetime.strptime( + following_collection, "followed by %A %d %B" + ) + + current_date = datetime.now() + next_collection_date = next_collection_date.replace( + year=current_date.year + ) + following_collection_date = following_collection_date.replace( + year=current_date.year + ) + + next_collection_date = get_next_occurrence_from_day_month( + next_collection_date + ) + + following_collection_date = get_next_occurrence_from_day_month( + following_collection_date + ) + + dict_data = { + "type": bin_type, + "collectionDate": next_collection_date.strftime(date_format), + } + data["bins"].append(dict_data) + + dict_data = { + "type": bin_type, + "collectionDate": following_collection_date.strftime(date_format), + } + data["bins"].append(dict_data) + + except Exception as e: + # Here you can log the exception if needed + print(f"An error occurred: {e}") + # Optionally, re-raise the exception if you want it to propagate + raise + finally: + # This block ensures that the driver is closed regardless of an exception + if driver: + driver.quit() + return data diff --git a/wiki/Councils.md b/wiki/Councils.md index fad39371ab..69a3c1126b 100644 --- a/wiki/Councils.md +++ b/wiki/Councils.md @@ -2674,14 +2674,15 @@ Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/searc ### Newport ```commandline -python collect_data.py NewportCityCouncil https://www.newport.gov.uk/ -s -u XXXXXXXX -p "XXXX XXX" +python collect_data.py NewportCityCouncil https://www.newport.gov.uk/ -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` Additional parameters: - `-s` - skip get URL -- `-u` - UPRN - `-p` - postcode +- `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: Pass the postcode and UPRN. You can find the UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search). +Note: Pass the postcode and house number in their respective arguments, both wrapped in quotes. --- From b12637c9842e74f0af447529130b8df9c2957d24 Mon Sep 17 00:00:00 2001 From: m26dvd <31007572+m26dvd@users.noreply.github.com> Date: Mon, 22 Sep 2025 22:49:53 +0100 Subject: [PATCH 151/425] fix: #1569 - Somerset Council fix: #1569 - Somerset Council --- uk_bin_collection/tests/input.json | 5 +- .../councils/SomersetCouncil.py | 284 +++++++----------- wiki/Councils.md | 7 +- 3 files changed, 112 insertions(+), 184 deletions(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index 3772df80b9..abddcff33a 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -2088,10 +2088,11 @@ "SomersetCouncil": { "postcode": "TA6 4AA", "skip_get_url": true, - "uprn": "10090857775", + "house_number": "5", "url": "https://www.somerset.gov.uk/", + "web_driver": "http://selenium:4444", "wiki_name": "Somerset", - "wiki_note": "Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search).", + "wiki_note": "Provide your house number and postcode", "LAD24CD": "E06000066" }, "SouthAyrshireCouncil": { diff --git a/uk_bin_collection/uk_bin_collection/councils/SomersetCouncil.py b/uk_bin_collection/uk_bin_collection/councils/SomersetCouncil.py index 04e7016cbd..5875e38f4f 100644 --- a/uk_bin_collection/uk_bin_collection/councils/SomersetCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/SomersetCouncil.py @@ -1,4 +1,9 @@ +import datetime + from bs4 import BeautifulSoup +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.wait import WebDriverWait from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass @@ -13,192 +18,113 @@ class CouncilClass(AbstractGetBinDataClass): """ def parse_data(self, page: str, **kwargs) -> dict: - user_postcode = kwargs.get("postcode") - check_postcode(user_postcode) - user_uprn = kwargs.get("uprn") - check_uprn(user_uprn) - - headers = { - "User-Agent": "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/87.0.4280.141 Safari/537.36" - } - - requests.packages.urllib3.disable_warnings() - with requests.Session() as s: - # Set Headers - s.headers = headers - - # Get the first page - This is the Search for property by Post Code page - resource = s.get( - "https://iweb.itouchvision.com/portal/f?p=customer:BIN_DAYS:::NO:RP:UID:625C791B4D9301137723E9095361401AE8C03934" - ) - # Create a BeautifulSoup object from the page's HTML - soup = BeautifulSoup(resource.text, "html.parser") - - # The page contains a number of values that must be passed into subsequent requests - extract them here - payload = { - i["name"]: i.get("value", "") for i in soup.select("input[name]") - } - payload2 = { - i["data-for"]: i.get("value", "") - for i in soup.select("input[data-for]") - } - payload_salt = soup.select_one('input[id="pSalt"]').get("value") - payload_protected = soup.select_one('input[id="pPageItemsProtected"]').get( - "value" + driver = None + try: + data = {"bins": []} + url = kwargs.get("url") + user_paon = kwargs.get("paon") + user_postcode = kwargs.get("postcode") + web_driver = kwargs.get("web_driver") + headless = kwargs.get("headless") + check_paon(user_paon) + check_postcode(user_postcode) + + # Use a realistic user agent to help bypass Cloudflare + user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + driver = create_webdriver(web_driver, headless, user_agent, __name__) + driver.get("https://www.somerset.gov.uk/collection-days") + + # Wait for the postcode field to appear then populate it + inputElement_postcode = WebDriverWait(driver, 30).until( + EC.presence_of_element_located((By.ID, "postcodeSearch")) ) + inputElement_postcode.send_keys(user_postcode) - # Add the PostCode and 'SEARCH' to the payload - payload["p_request"] = "SEARCH" - payload["P153_POST_CODE"] = user_postcode - - # Manipulate the lists and build the JSON that must be submitted in further requests - some data is nested - merged_list = {**payload, **payload2} - new_list = [] - other_list = {} - for key in merged_list.keys(): - temp_list = {} - val = merged_list[key] - if key in [ - "P153_UPRN", - "P153_TEMP", - "P153_SYSDATE", - "P0_LANGUAGE", - "P153_POST_CODE", - ]: - temp_list = {"n": key, "v": val} - new_list.append(temp_list) - elif key in [ - "p_flow_id", - "p_flow_step_id", - "p_instance", - "p_page_submission_id", - "p_request", - "p_reload_on_submit", - ]: - other_list[key] = val - else: - temp_list = {"n": key, "v": "", "ck": val} - new_list.append(temp_list) - - json_builder = { - "pageItems": { - "itemsToSubmit": new_list, - "protected": payload_protected, - "rowVersion": "", - "formRegionChecksums": [], - }, - "salt": payload_salt, - } - json_object = json.dumps(json_builder, separators=(",", ":")) - other_list["p_json"] = json_object - - # Set Referrer header - s.headers.update( - { - "referer": "https://iweb.itouchvision.com/portal/f?p=customer:BIN_DAYS:::NO:RP:UID:625C791B4D9301137723E9095361401AE8C03934" - } + # Click search button + findAddress = WebDriverWait(driver, 10).until( + EC.presence_of_element_located((By.CLASS_NAME, "govuk-button")) ) - - # Generate POST including all the JSON we just built - s.post( - "https://iweb.itouchvision.com/portal/wwv_flow.accept", data=other_list + findAddress.click() + + # Wait for the 'Select address' dropdown to appear and select option matching the house name/number + WebDriverWait(driver, 10).until( + EC.element_to_be_clickable( + ( + By.XPATH, + "//select[@id='addressSelect']//option[contains(., '" + + user_paon + + "')]", + ) + ) + ).click() + + # Wait for the collections table to appear + WebDriverWait(driver, 20).until( + EC.presence_of_element_located( + ( + By.XPATH, + "//h2[contains(@class,'mt-4') and contains(@class,'govuk-heading-s') and normalize-space(.)='Your next collections']", + ) + ) ) - # The second page on the portal would normally allow you to select your property from a dropdown list of - # those that are at the postcode entered on the previous page - # The required cookies are stored within the session so re-use the session to keep them - resource = s.get( - "https://iweb.itouchvision.com/portal/itouchvision/r/customer/bin_days" - ) + soup = BeautifulSoup(driver.page_source, features="html.parser") - # Create a BeautifulSoup object from the page's HTML - soup = BeautifulSoup(resource.text, "html.parser") - - # The page contains a number of values that must be passed into subsequent requests - extract them here - payload = { - i["name"]: i.get("value", "") for i in soup.select("input[name]") - } - payload2 = { - i["data-for"]: i.get("value", "") - for i in soup.select("input[data-for]") - } - payload_salt = soup.select_one('input[id="pSalt"]').get("value") - payload_protected = soup.select_one('input[id="pPageItemsProtected"]').get( - "value" - ) + collections = soup.find_all("div", {"class": "p-2"}) - # Add the UPRN and 'SUBMIT' to the payload - payload["p_request"] = "SUBMIT" - payload["P153_UPRN"] = user_uprn - - # Manipulate the lists and build the JSON that must be submitted in further requests - some data is nested - merged_list = {**payload, **payload2} - new_list = [] - other_list = {} - for key in merged_list.keys(): - temp_list = {} - val = merged_list[key] - if key in ["P153_UPRN", "P153_TEMP", "P153_SYSDATE", "P0_LANGUAGE"]: - temp_list = {"n": key, "v": val} - new_list.append(temp_list) - elif key in ["P153_ZABY"]: - temp_list = {"n": key, "v": "1", "ck": val} - new_list.append(temp_list) - elif key in ["P153_POST_CODE"]: - temp_list = {"n": key, "v": user_postcode, "ck": val} - new_list.append(temp_list) - elif key in [ - "p_flow_id", - "p_flow_step_id", - "p_instance", - "p_page_submission_id", - "p_request", - "p_reload_on_submit", - ]: - other_list[key] = val - else: - temp_list = {"n": key, "v": "", "ck": val} - new_list.append(temp_list) - - json_builder = { - "pageItems": { - "itemsToSubmit": new_list, - "protected": payload_protected, - "rowVersion": "", - "formRegionChecksums": [], - }, - "salt": payload_salt, - } - - json_object = json.dumps(json_builder, separators=(",", ":")) - other_list["p_json"] = json_object - - # Generate POST including all the JSON we just built - s.post( - "https://iweb.itouchvision.com/portal/wwv_flow.accept", data=other_list - ) + for collection in collections: + bin_type = collection.find("h3").get_text() - # The third and final page on the portal shows the detail of the waste collection services - # The required cookies are stored within the session so re-use the session to keep them - resource = s.get( - "https://iweb.itouchvision.com/portal/itouchvision/r/customer/bin_days" - ) + next_collection = soup.find("div", {"class": "fw-bold"}).get_text() - # Create a BeautifulSoup object from the page's HTML - soup = BeautifulSoup(resource.text, "html.parser") - data = {"bins": []} + following_collection = soup.find( + lambda t: ( + t.name == "div" + and t.get_text(strip=True).lower().startswith("followed by") + ) + ).get_text() + + next_collection_date = datetime.strptime(next_collection, "%A %d %B") + + following_collection_date = datetime.strptime( + following_collection, "followed by %A %d %B" + ) + + current_date = datetime.now() + next_collection_date = next_collection_date.replace( + year=current_date.year + ) + following_collection_date = following_collection_date.replace( + year=current_date.year + ) + + next_collection_date = get_next_occurrence_from_day_month( + next_collection_date + ) - # Loop through the items on the page and build a JSON object for ingestion - for item in soup.select(".t-MediaList-item"): - for value in item.select(".t-MediaList-body"): - dict_data = { - "type": value.select("span")[1].get_text(strip=True).title(), - "collectionDate": datetime.strptime( - value.select(".t-MediaList-desc")[0].get_text(strip=True), - "%A, %d %B, %Y", - ).strftime(date_format), - } - data["bins"].append(dict_data) - - return data + following_collection_date = get_next_occurrence_from_day_month( + following_collection_date + ) + + dict_data = { + "type": bin_type, + "collectionDate": next_collection_date.strftime(date_format), + } + data["bins"].append(dict_data) + + dict_data = { + "type": bin_type, + "collectionDate": following_collection_date.strftime(date_format), + } + data["bins"].append(dict_data) + + except Exception as e: + # Here you can log the exception if needed + print(f"An error occurred: {e}") + # Optionally, re-raise the exception if you want it to propagate + raise + finally: + # This block ensures that the driver is closed regardless of an exception + if driver: + driver.quit() + return data diff --git a/wiki/Councils.md b/wiki/Councils.md index 69a3c1126b..054d76757e 100644 --- a/wiki/Councils.md +++ b/wiki/Councils.md @@ -3295,14 +3295,15 @@ Note: Replace `XXXXXXXX` with your UPRN. You will need to use [FindMyAddress](ht ### Somerset ```commandline -python collect_data.py SomersetCouncil https://www.somerset.gov.uk/ -s -u XXXXXXXX -p "XXXX XXX" +python collect_data.py SomersetCouncil https://www.somerset.gov.uk/ -s -p "XXXX XXX" -n XX -w http://HOST:PORT/ ``` Additional parameters: - `-s` - skip get URL -- `-u` - UPRN - `-p` - postcode +- `-n` - house number +- `-w` - remote Selenium web driver URL (required for Home Assistant) -Note: Provide your UPRN and postcode. Find your UPRN using [FindMyAddress](https://www.findmyaddress.co.uk/search). +Note: Provide your house number and postcode --- From 944f1b1b45a68bcf07e2a374bdf49736d0a50aa7 Mon Sep 17 00:00:00 2001 From: m26dvd <31007572+m26dvd@users.noreply.github.com> Date: Mon, 22 Sep 2025 23:30:52 +0100 Subject: [PATCH 152/425] fix: #1557 - Adding East Dunbartonshire fix: #1557 - Adding East Dunbartonshire --- uk_bin_collection/tests/input.json | 7 +++ .../councils/EastDunbartonshireCouncil.py | 52 +++++++++++++++++++ wiki/Councils.md | 12 +++++ 3 files changed, 71 insertions(+) create mode 100644 uk_bin_collection/uk_bin_collection/councils/EastDunbartonshireCouncil.py diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index abddcff33a..e99f138da2 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -760,6 +760,13 @@ "wiki_note": "Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyaddress.co.uk/search).", "LAD24CD": "E07000040" }, + "EastDunbartonshireCouncil": { + "uprn": "132027197", + "url": "https://www.eastdunbarton.gov.uk/", + "wiki_name": "East Dunbartonshire", + "wiki_note": "You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN.", + "LAD24CD": "S12000045" + }, "EastHertsCouncil": { "LAD24CD": "E07000097", "skip_get_url": true, diff --git a/uk_bin_collection/uk_bin_collection/councils/EastDunbartonshireCouncil.py b/uk_bin_collection/uk_bin_collection/councils/EastDunbartonshireCouncil.py new file mode 100644 index 0000000000..6dcbe52c86 --- /dev/null +++ b/uk_bin_collection/uk_bin_collection/councils/EastDunbartonshireCouncil.py @@ -0,0 +1,52 @@ +import requests +from bs4 import BeautifulSoup, Tag + +from uk_bin_collection.uk_bin_collection.common import * +from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass + + +# import the wonderful Beautiful Soup and the URL grabber +class CouncilClass(AbstractGetBinDataClass): + """ + Concrete classes have to implement all abstract operations of the + base class. They can also override some operations with a default + implementation. + """ + + def parse_data(self, page: str, **kwargs) -> dict: + + user_uprn = kwargs.get("uprn") + check_uprn(user_uprn) + bindata = {"bins": []} + + URI = f"https://www.eastdunbarton.gov.uk/services/a-z-of-services/bins-waste-and-recycling/bins-and-recycling/collections/?uprn={user_uprn}" + + # Make the GET request + response = requests.get(URI) + + soup = BeautifulSoup(response.text, "html.parser") + + table = soup.find("table", {"class": "bin-table"}) + + tbody = table.find("tbody") + + trs = tbody.find_all("tr") + + for tr in trs: + tds = tr.find_all("td") + bin_type = tds[0].get_text() + collection_date_str = tds[1].find("span").get_text() + + collection_date = datetime.strptime(collection_date_str, "%A, %d %B %Y") + + dict_data = { + "type": bin_type, + "collectionDate": collection_date.strftime(date_format), + } + bindata["bins"].append(dict_data) + + bindata["bins"].sort( + key=lambda x: datetime.strptime(x.get("collectionDate"), date_format) + ) + + return bindata diff --git a/wiki/Councils.md b/wiki/Councils.md index 054d76757e..ee46c0dfe7 100644 --- a/wiki/Councils.md +++ b/wiki/Councils.md @@ -100,6 +100,7 @@ This document is still a work in progress, don't worry if your council isn't lis - [Eastbourne](#eastbourne) - [East Cambridgeshire](#east-cambridgeshire) - [East Devon](#east-devon) +- [East Dunbartonshire](#east-dunbartonshire) - [East Herts Council](#east-herts-council) - [East Lindsey](#east-lindsey) - [East Lothian](#east-lothian) @@ -1439,6 +1440,17 @@ Note: Pass the UPRN. You can find it using [FindMyAddress](https://www.findmyadd --- +### East Dunbartonshire +```commandline +python collect_data.py EastDunbartonshireCouncil https://www.eastdunbarton.gov.uk/ -u XXXXXXXX +``` +Additional parameters: +- `-u` - UPRN + +Note: You will need to use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find the UPRN. + +--- + ### East Herts Council ```commandline python collect_data.py EastHertsCouncil https://east-herts.co.uk/api/services/ -s -u XXXXXXXX From 72cca7039010317d984022a36600ba15e16be595 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Sep 2025 22:36:33 +0000 Subject: [PATCH 153/425] chore: bump pip from 25.0.1 to 25.2 Bumps [pip](https://github.com/pypa/pip) from 25.0.1 to 25.2. - [Changelog](https://github.com/pypa/pip/blob/main/NEWS.rst) - [Commits](https://github.com/pypa/pip/compare/25.0.1...25.2) --- updated-dependencies: - dependency-name: pip dependency-version: '25.2' dependency-type: indirect ... Signed-off-by: dependabot[bot] --- poetry.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index dd6ff26fab..9ddcec2725 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.0 and should not be changed by hand. [[package]] name = "aiohttp" @@ -2017,14 +2017,14 @@ files = [ [[package]] name = "pip" -version = "25.0.1" +version = "25.2" description = "The PyPA recommended tool for installing Python packages." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pip-25.0.1-py3-none-any.whl", hash = "sha256:c46efd13b6aa8279f33f2864459c8ce587ea6a1a59ee20de055868d8f7688f7f"}, - {file = "pip-25.0.1.tar.gz", hash = "sha256:88f96547ea48b940a3a385494e181e29fb8637898f88d88737c5049780f196ea"}, + {file = "pip-25.2-py3-none-any.whl", hash = "sha256:6d67a2b4e7f14d8b31b8b52648866fa717f45a1eb70e83002f4331d07e953717"}, + {file = "pip-25.2.tar.gz", hash = "sha256:578283f006390f85bb6282dffb876454593d637f5d1be494b5202ce4877e71f2"}, ] [[package]] From 3c22785a24b177fa5cab1cc72950e00ce70bd9ee Mon Sep 17 00:00:00 2001 From: kyphera Date: Thu, 25 Sep 2025 19:47:51 +0100 Subject: [PATCH 154/425] Update RotherhamCouncil.py update RotherhamCouncil.py to use new api since Rotherham Council website has removed bins. --- .../councils/RotherhamCouncil.py | 108 ++++++++++++------ 1 file changed, 70 insertions(+), 38 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/RotherhamCouncil.py b/uk_bin_collection/uk_bin_collection/councils/RotherhamCouncil.py index 1ede3fda55..e7cec2aa48 100644 --- a/uk_bin_collection/uk_bin_collection/councils/RotherhamCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/RotherhamCouncil.py @@ -1,57 +1,89 @@ -from bs4 import BeautifulSoup from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass +import requests +from datetime import datetime -# import the wonderful Beautiful Soup and the URL grabber class CouncilClass(AbstractGetBinDataClass): """ - Concrete classes have to implement all abstract operations of the - base class. They can also override some operations with a default - implementation. + Rotherham collections via the public JSON API. + Returns the same shape as before: + {"bins": [{"type": "Black Bin", "collectionDate": "Tuesday, 29 September 2025"}, ...]} + Accepts kwargs['premisesid'] (recommended) or a numeric kwargs['uprn']. """ def parse_data(self, page: str, **kwargs) -> dict: - user_uprn = kwargs.get("uprn") + # prefer explicit premisesid, fallback to uprn (if numeric) + premises = kwargs.get("premisesid") + uprn = kwargs.get("uprn") - check_uprn(user_uprn) + if uprn: + # preserve original behaviour where check_uprn exists for validation, + # but don't fail if uprn is intended as a simple premises id number. + try: + check_uprn(uprn) + except Exception: + # silently continue — user may have passed a numeric premises id as uprn + pass + + if not premises and str(uprn).strip().isdigit(): + premises = str(uprn).strip() + + if not premises: + raise ValueError("No premises ID supplied. Pass 'premisesid' in kwargs or a numeric 'uprn'.") + + api_url = "https://bins.azurewebsites.net/api/getcollections" + params = { + "premisesid": str(premises), + "localauthority": kwargs.get("localauthority", "Rotherham"), + } headers = { - "Content-Type": "application/x-www-form-urlencoded", - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36", + "User-Agent": "UKBinCollectionData/1.0 (+https://github.com/robbrad/UKBinCollectionData)" } - response = requests.post( - "https://www.rotherham.gov.uk/bin-collections?address={}&submit=Submit".format( - user_uprn - ), - headers=headers - ) - # Make a BS4 object - soup = BeautifulSoup(response.text, features="html.parser") - soup.prettify() - data = {"bins": []} + try: + resp = requests.get(api_url, params=params, headers=headers, timeout=10) + except Exception as exc: + print(f"Error contacting Rotherham API: {exc}") + return {"bins": []} + + if resp.status_code != 200: + print(f"Rotherham API request failed ({resp.status_code}). URL: {resp.url}") + return {"bins": []} - table = soup.select("table")[0] + try: + collections = resp.json() + except ValueError: + print("Rotherham API returned non-JSON response.") + return {"bins": []} + + data = {"bins": []} + seen = set() # dedupe identical (type, date) pairs + for item in collections: + bin_type = item.get("BinType") or item.get("bintype") or "Unknown" + date_str = item.get("CollectionDate") or item.get("collectionDate") + if not date_str: + continue - if table: - rows = table.select("tr") + # API gives ISO date like '2025-09-29' (or possibly '2025-09-29T00:00:00'). + try: + iso_date = date_str.split("T")[0] + parsed = datetime.strptime(iso_date, "%Y-%m-%d") + formatted = parsed.strftime(date_format) + except Exception: + # skip malformed dates + continue - for index, row in enumerate(rows): - bin_info_cell = row.select("td") - if bin_info_cell: - bin_type = bin_info_cell[0].get_text(separator=" ", strip=True) - bin_collection = bin_info_cell[1] + key = (bin_type.strip().lower(), formatted) + if key in seen: + continue + seen.add(key) - if bin_collection: - dict_data = { - "type": bin_type.title(), - "collectionDate": datetime.strptime( - bin_collection.get_text(strip=True), "%A, %d %B %Y" - ).strftime(date_format), - } + dict_data = {"type": bin_type.title(), "collectionDate": formatted} + data["bins"].append(dict_data) - data["bins"].append(dict_data) - else: - print("Something went wrong. Please open a GitHub issue.") + if not data["bins"]: + # helpful debugging note + print(f"Rotherham API returned no collection entries for premisesid={premises}") - return data + return data \ No newline at end of file From a41b3bc53d6d8d7580b579ef1ec56e4fbf262cf7 Mon Sep 17 00:00:00 2001 From: Simon Farnsworth Date: Thu, 2 Oct 2025 10:42:08 +0100 Subject: [PATCH 155/425] fix: Oxford now rejects the "Requests" default user agent Fixes #1518 --- .../uk_bin_collection/councils/OxfordCityCouncil.py | 1 + 1 file changed, 1 insertion(+) diff --git a/uk_bin_collection/uk_bin_collection/councils/OxfordCityCouncil.py b/uk_bin_collection/uk_bin_collection/councils/OxfordCityCouncil.py index aa1a38b7a5..3f7aaa6abf 100644 --- a/uk_bin_collection/uk_bin_collection/councils/OxfordCityCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/OxfordCityCouncil.py @@ -25,6 +25,7 @@ def parse_data(self, page: str, **kwargs) -> dict: URI = "https://www.oxford.gov.uk/xfp/form/142#q6ad4e3bf432c83230a0347a6eea6c805c672efeb_0" session = requests.Session() + session.headers.update({'User-Agent': 'HomeAssistant UK Bin Collection integration'}) token_response = session.get(session_uri) soup = BeautifulSoup(token_response.text, "html.parser") token = soup.find("input", {"name": "__token"}).attrs["value"] From 894bd6f05d689b5101c75bf6912fc770b9e4eedf Mon Sep 17 00:00:00 2001 From: Shaun Betts Date: Thu, 2 Oct 2025 22:11:59 +0100 Subject: [PATCH 156/425] fix: improve Mid Suffolk District Council holiday handling with dynamic bank holiday detection Replace hardcoded bank holiday list with dynamic holiday detection using holidays library. Implement smart date calculation that handles weekend and cascading holiday scenarios with configurable delays for major holidays like Christmas/New Year. Simplify garden collection logic with dedicated Christmas period skip function. --- .../councils/MidSuffolkDistrictCouncil.py | 162 ++++++++---------- 1 file changed, 70 insertions(+), 92 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/MidSuffolkDistrictCouncil.py b/uk_bin_collection/uk_bin_collection/councils/MidSuffolkDistrictCouncil.py index 670003639a..305c88ae48 100644 --- a/uk_bin_collection/uk_bin_collection/councils/MidSuffolkDistrictCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/MidSuffolkDistrictCouncil.py @@ -1,6 +1,7 @@ import re import time +import holidays import requests from bs4 import BeautifulSoup from selenium.webdriver.common.by import By @@ -50,58 +51,63 @@ def parse_data(self, page: str, **kwargs) -> dict: refuse_dates = get_dates_every_x_days(refusestartDate, 14, 28) recycling_dates = get_dates_every_x_days(recyclingstartDate, 14, 28) - bank_holidays = [ - ("25/12/2024", 2), - ("26/12/2024", 2), - ("27/12/2024", 3), - ("30/12/2024", 1), - ("31/12/2024", 2), - ("01/01/2025", 2), - ("02/01/2025", 2), - ("03/01/2025", 3), - ("06/01/2025", 1), - ("07/01/2025", 1), - ("08/01/2025", 1), - ("09/01/2025", 1), - ("10/01/2025", 1), - ("18/04/2025", 1), - ("21/04/2025", 1), - ("22/04/2025", 1), - ("23/04/2025", 1), - ("24/04/2025", 1), - ("25/04/2025", 1), - ("05/05/2025", 1), - ("06/05/2025", 1), - ("07/05/2025", 1), - ("08/05/2025", 1), - ("09/05/2025", 1), - ("26/05/2025", 1), - ("27/05/2025", 1), - ("28/05/2025", 1), - ("29/05/2025", 1), - ("30/05/2025", 1), - ("25/08/2025", 1), - ("26/08/2025", 1), - ("27/08/2025", 1), - ("28/08/2025", 1), - ("29/08/2025", 1), - ] + # Generate bank holidays dynamically using the holidays library + def get_bank_holidays_set(): + """Get set of bank holiday dates for quick lookup.""" + current_year = datetime.now().year + uk_holidays = holidays.UK(years=range(current_year - 1, current_year + 3)) + return set(uk_holidays.keys()) + + def find_next_collection_day(original_date): + """Find the next valid collection day, avoiding weekends and bank holidays.""" + bank_holiday_dates = get_bank_holidays_set() + check_date = datetime.strptime(original_date, "%d/%m/%Y") + + # Safety limit to prevent infinite loops + max_attempts = 10 + attempts = 0 + + # Keep moving forward until we find a valid collection day + while attempts < max_attempts: + attempts += 1 + + # Check if it's a weekend (Saturday=5, Sunday=6) + if check_date.weekday() >= 5: + check_date += timedelta(days=1) + continue - for refuseDate in refuse_dates: + # Check if it's a bank holiday + if check_date.date() in bank_holiday_dates: + # Major holidays (Christmas/New Year) get bigger delays + holiday_name = str(holidays.UK().get(check_date.date(), '')) + is_major_holiday = ( + 'Christmas' in holiday_name or + 'Boxing' in holiday_name or + 'New Year' in holiday_name + ) + delay_days = 2 if is_major_holiday else 1 + check_date += timedelta(days=delay_days) + continue + + # Found a valid collection day + break + + # If we've exhausted attempts, return the original date as fallback + if attempts >= max_attempts: + return original_date + + return check_date.strftime("%d/%m/%Y") + + bank_holidays = [] # No longer needed - using smart date calculation - collection_date = ( + for refuseDate in refuse_dates: + # Calculate initial collection date + initial_date = ( datetime.strptime(refuseDate, "%d/%m/%Y") + timedelta(days=offset_days) ).strftime("%d/%m/%Y") - holiday_offset = next( - (value for date, value in bank_holidays if date == collection_date), 0 - ) - - if holiday_offset > 0: - collection_date = ( - datetime.strptime(collection_date, "%d/%m/%Y") - + timedelta(days=holiday_offset) - ).strftime("%d/%m/%Y") + # Find the next valid collection day (handles weekends + cascading holidays) + collection_date = find_next_collection_day(initial_date) dict_data = { "type": "Refuse Bin", @@ -110,21 +116,14 @@ def parse_data(self, page: str, **kwargs) -> dict: bindata["bins"].append(dict_data) for recyclingDate in recycling_dates: - - collection_date = ( + # Calculate initial collection date + initial_date = ( datetime.strptime(recyclingDate, "%d/%m/%Y") + timedelta(days=offset_days) ).strftime("%d/%m/%Y") - holiday_offset = next( - (value for date, value in bank_holidays if date == collection_date), 0 - ) - - if holiday_offset > 0: - collection_date = ( - datetime.strptime(collection_date, "%d/%m/%Y") - + timedelta(days=holiday_offset) - ).strftime("%d/%m/%Y") + # Find the next valid collection day (handles weekends + cascading holidays) + collection_date = find_next_collection_day(initial_date) dict_data = { "type": "Recycling Bin", @@ -140,48 +139,27 @@ def parse_data(self, page: str, **kwargs) -> dict: garden_dates = get_dates_every_x_days(gardenstartDate, 14, 28) - garden_bank_holidays = [ - ("23/12/2024", 1), - ("24/12/2024", 1), - ("25/12/2024", 1), - ("26/12/2024", 1), - ("27/12/2024", 1), - ("30/12/2024", 1), - ("31/12/2024", 1), - ("01/01/2025", 1), - ("02/01/2025", 1), - ("03/01/2025", 1), - ] + def is_christmas_period(date_obj): + """Check if date is in Christmas/New Year skip period for garden collections.""" + if date_obj.month == 12 and date_obj.day >= 23: + return True + if date_obj.month == 1 and date_obj.day <= 3: + return True + return False for gardenDate in garden_dates: - - collection_date = ( + # Calculate initial collection date + initial_date = ( datetime.strptime(gardenDate, "%d/%m/%Y") + timedelta(days=offset_days_garden) - ).strftime("%d/%m/%Y") - - garden_holiday = next( - ( - value - for date, value in garden_bank_holidays - if date == collection_date - ), - 0, ) - if garden_holiday > 0: + # Skip garden collections during Christmas/New Year period + if is_christmas_period(initial_date): continue - holiday_offset = next( - (value for date, value in bank_holidays if date == collection_date), - 0, - ) - - if holiday_offset > 0: - collection_date = ( - datetime.strptime(collection_date, "%d/%m/%Y") - + timedelta(days=holiday_offset) - ).strftime("%d/%m/%Y") + # Find the next valid collection day (handles weekends + holidays) + collection_date = find_next_collection_day(initial_date.strftime("%d/%m/%Y")) dict_data = { "type": "Garden Bin", From 3415f66838997d7bdf34365b8194dc16c69a555a Mon Sep 17 00:00:00 2001 From: Paul Hardacre Date: Sat, 4 Oct 2025 12:59:15 +0000 Subject: [PATCH 157/425] fix: New URL and page for wheelie bins Update to use new URL to pull info for wheelie bins/food caddy. Fall back to old format following link if present. --- .../councils/NewForestCouncil.py | 151 ++++++++++++------ 1 file changed, 104 insertions(+), 47 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/NewForestCouncil.py b/uk_bin_collection/uk_bin_collection/councils/NewForestCouncil.py index 4581a03ef8..b88ba8033f 100644 --- a/uk_bin_collection/uk_bin_collection/councils/NewForestCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/NewForestCouncil.py @@ -1,16 +1,14 @@ import logging -import pickle import time -import requests from bs4 import BeautifulSoup from selenium import webdriver +from selenium.common.exceptions import NoSuchElementException from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import Select from selenium.webdriver.support.wait import WebDriverWait -from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass @@ -23,17 +21,64 @@ class CouncilClass(AbstractGetBinDataClass): + def get_legacy_bins(self, page: str) -> []: + + logging.info("Extracting legacy bin collection data") + soup = BeautifulSoup(page, features="html.parser") + legacy_bins = [] + + # Rubbish and recycling + rubbish_recycling = soup.find( + "span", class_="CTID-77-_ eb-77-Override-textControl" + ) + if rubbish_recycling: + match = re.search(r"collected weekly on (\w+)", rubbish_recycling.text) + if match: + day_name = match.group(1) + next_collection = get_next_day_of_week(day_name) + legacy_bins.append( + { + "type": "Rubbish and recycling", + "collectionDate": next_collection, + } + ) + logging.info(f"Rubbish and Recycling: {str(next_collection)}") + + # Glass collection + glass_collection = soup.find("span", class_="CTID-78-_ eb-78-textControl") + if glass_collection: + match = re.search( + r"next collection is\s+(\d{2}/\d{2}/\d{4})", glass_collection.text + ) + if match: + legacy_bins.append( + {"type": "Glass collection", "collectionDate": match.group(1)} + ) + logging.info(f"Glass: {str(match.group(1))}") + + # Garden waste + garden_waste = soup.find("div", class_="eb-2HIpCnWC-Override-EditorInput") + if garden_waste: + match = re.search(r"(\d{2}/\d{2}/\d{4})", garden_waste.text) + if match: + legacy_bins.append( + {"type": "Garden waste", "collectionDate": match.group(1)} + ) + logging.info(f"Garden: {str(match.group(1))}") + + # return bins + return legacy_bins + def parse_data(self, page: str, **kwargs) -> dict: driver = None try: - data = {"bins": []} - collections = [] + bins = [] user_uprn = kwargs.get("uprn") user_postcode = kwargs.get("postcode") web_driver = kwargs.get("web_driver") headless = kwargs.get("headless") check_postcode(user_postcode) - url = "https://forms.newforest.gov.uk/ufs/FIND_MY_COLLECTION.eb" + url = "https://forms.newforest.gov.uk/ufs/FIND_MY_BIN_BAR.eb" # Get session cookies using requests @@ -52,10 +97,20 @@ def parse_data(self, page: str, **kwargs) -> dict: logging.info("Entering postcode") input_element_postcode = wait.until( - EC.presence_of_element_located((By.XPATH, '//input[@id="CTID-1-_-A"]')) + EC.element_to_be_clickable( + (By.XPATH, '//input[@id="CTID-JmLqCKl2-_-A"]') + ) + ) + + driver.execute_script( + "arguments[0].scrollIntoView();", input_element_postcode ) - input_element_postcode.send_keys(user_postcode) + logging.info(f"Entering postcode '{str(user_postcode)}'") + # Force the value through the DOM cos send_keys just don't work for some reason :( + driver.execute_script( + f"arguments[0].value='{str(user_postcode)}'", input_element_postcode + ) logging.info("Searching for postcode") input_element_postcode_btn = wait.until( @@ -66,7 +121,9 @@ def parse_data(self, page: str, **kwargs) -> dict: logging.info("Waiting for address dropdown") input_element_postcode_dropdown = wait.until( - EC.presence_of_element_located((By.XPATH, '//select[@id="CTID-6-_-A"]')) + EC.element_to_be_clickable( + (By.XPATH, '//select[@id="CTID-KOeKcmrC-_-A"]') + ) ) logging.info("Selecting address") @@ -86,51 +143,51 @@ def parse_data(self, page: str, **kwargs) -> dict: input_element_address_btn.click() - logging.info("Waiting for bin collection page") - h4_element = wait.until( - EC.presence_of_element_located( - (By.XPATH, "//h1[contains(text(), 'Collections days for')]") + # Be patient, clicks take time! + time.sleep(2) + # logging.info(driver.page_source) + + try: + link_element = driver.find_element( + By.XPATH, + '//a[contains(text(),"Find your current bin collection day")]', + ) + logging.info( + "Found override panel span, search for link and use old logic" ) - ) - logging.info("Extracting bin collection data") - soup = BeautifulSoup(driver.page_source, features="html.parser") - bins = [] + link_element.click() - # Rubbish and recycling - rubbish_recycling = soup.find( - "span", class_="CTID-77-_ eb-77-Override-textControl" - ) - if rubbish_recycling: - match = re.search(r"collected weekly on (\w+)", rubbish_recycling.text) - if match: - day_name = match.group(1) - next_collection = get_next_day_of_week(day_name) - bins.append( - { - "type": "Rubbish and recycling", - "collectionDate": next_collection, - } - ) + # Be patient, clicks take time! + time.sleep(2) - # Glass collection - glass_collection = soup.find("span", class_="CTID-78-_ eb-78-textControl") - if glass_collection: - match = re.search( - r"next collection is\s+(\d{2}/\d{2}/\d{4})", glass_collection.text - ) - if match: - bins.append( - {"type": "Glass collection", "collectionDate": match.group(1)} + bins = self.get_legacy_bins(driver.page_source) + + except NoSuchElementException: + logging.info("Waiting for bin collection table") + collections_table = wait.until( + EC.presence_of_element_located( + ( + By.XPATH, + '//table[contains(@class,"eb-1j4UaesZ-tableContent")]', + ) ) + ) - # Garden waste - garden_waste = soup.find("div", class_="eb-2HIpCnWC-Override-EditorInput") - if garden_waste: - match = re.search(r"(\d{2}/\d{2}/\d{4})", garden_waste.text) - if match: + soup = BeautifulSoup(driver.page_source, features="html.parser") + rows = soup.find_all(class_="eb-1j4UaesZ-tableRow") + + for row in rows: + cols = row.find_all("td") + date_string = cols[0].findChild("div").findChild("div").get_text() + bin_type = cols[1].findChild("div").findChild("div").get_text() + + col_date = datetime.strptime(date_string, "%A %B %d, %Y") bins.append( - {"type": "Garden waste", "collectionDate": match.group(1)} + { + "type": bin_type, + "collectionDate": datetime.strftime(col_date, date_format), + } ) return {"bins": bins} From 3d8b799080006864ea64c56a616dec5dab08baa7 Mon Sep 17 00:00:00 2001 From: Paul Hardacre Date: Sat, 4 Oct 2025 13:02:12 +0000 Subject: [PATCH 158/425] fix: Update URL for NewForestCouncil --- uk_bin_collection/tests/input.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index dbb15183c8..37c045f2fa 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -1608,7 +1608,7 @@ "postcode": "SO41 0GJ", "skip_get_url": true, "uprn": "100060482345", - "url": "https://forms.newforest.gov.uk/id/FIND_MY_COLLECTION", + "url": "https://forms.newforest.gov.uk/ufs/FIND_MY_BIN_BAR.eb", "web_driver": "http://selenium:4444", "wiki_name": "New Forest", "wiki_note": "Pass the postcode and UPRN. This parser requires a Selenium webdriver.", From 2e31181c2c064f78127821ab348aa2e0517eff35 Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Mon, 6 Oct 2025 08:13:04 +0100 Subject: [PATCH 159/425] feat: fix Torbay --- uk_bin_collection/tests/input.json | 1 + 1 file changed, 1 insertion(+) diff --git a/uk_bin_collection/tests/input.json b/uk_bin_collection/tests/input.json index f56226fda5..2a40a07510 100755 --- a/uk_bin_collection/tests/input.json +++ b/uk_bin_collection/tests/input.json @@ -2473,6 +2473,7 @@ "skip_get_url": true, "uprn": "10000016984", "postcode": "TQ1 1AG", + "web_driver": "http://selenium:4444", "url": "https://www.torbay.gov.uk/recycling/bin-collections/", "wiki_name": "Torbay", "wiki_note": "Provide your UPRN. Use [FindMyAddress](https://www.findmyaddress.co.uk/search) to find it.", From 11666058c999240363996638f9b17dfc01eac794 Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Mon, 6 Oct 2025 08:25:43 +0100 Subject: [PATCH 160/425] feat: fix release pipeline bump.yml --- .github/workflows/bump.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/bump.yml b/.github/workflows/bump.yml index d66aa647cd..449bff631b 100644 --- a/.github/workflows/bump.yml +++ b/.github/workflows/bump.yml @@ -21,7 +21,7 @@ jobs: steps: - uses: actions/checkout@v5 with: - token: "${{ secrets.PERSONAL_ACCESS_TOKEN }}" + token: "${{ secrets.GITHUB_TOKEN }}" fetch-depth: 0 - uses: actions/setup-python@v6 with: @@ -31,4 +31,4 @@ jobs: id: cz uses: commitizen-tools/commitizen-action@master with: - github_token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + github_token: ${{ secrets.GITHUB_TOKEN }} From a69b382afb25977aa6f878fed83df9a7de188b86 Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Mon, 6 Oct 2025 08:28:33 +0100 Subject: [PATCH 161/425] feat: Update bump.yml --- .github/workflows/bump.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/bump.yml b/.github/workflows/bump.yml index 449bff631b..8f02084548 100644 --- a/.github/workflows/bump.yml +++ b/.github/workflows/bump.yml @@ -31,4 +31,5 @@ jobs: id: cz uses: commitizen-tools/commitizen-action@master with: - github_token: ${{ secrets.GITHUB_TOKEN }} + github_token: ${{ secrets.GITHUB_TOKEN }} #moved to gh token + From 76b47c952235e3e234e9ddeaeebe083ef1721b26 Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Mon, 6 Oct 2025 08:30:47 +0100 Subject: [PATCH 162/425] fix: Update TorbayCouncil.py --- uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py | 1 - 1 file changed, 1 deletion(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py b/uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py index a5a8ba2243..5fb2b2004b 100644 --- a/uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py @@ -12,7 +12,6 @@ from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass - class CouncilClass(AbstractGetBinDataClass): def parse_data(self, page: str, **kwargs) -> dict: From 4a2f72db2401c94eeda2cf667d5d35573c5bebaf Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Mon, 6 Oct 2025 08:32:22 +0100 Subject: [PATCH 163/425] fix: Update TorbayCouncil.py --- uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py | 1 + 1 file changed, 1 insertion(+) diff --git a/uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py b/uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py index 5fb2b2004b..a5a8ba2243 100644 --- a/uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py @@ -12,6 +12,7 @@ from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass + class CouncilClass(AbstractGetBinDataClass): def parse_data(self, page: str, **kwargs) -> dict: From e19a6435a532420a184d0d530e219ad94093b13e Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Mon, 6 Oct 2025 08:37:27 +0100 Subject: [PATCH 164/425] feat: Update TorbayCouncil.py --- uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py | 1 - 1 file changed, 1 deletion(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py b/uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py index a5a8ba2243..5fb2b2004b 100644 --- a/uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py @@ -12,7 +12,6 @@ from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass - class CouncilClass(AbstractGetBinDataClass): def parse_data(self, page: str, **kwargs) -> dict: From 47dce1d38ca730dccb03a07f9f407cc6039ffcf6 Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Mon, 6 Oct 2025 08:44:46 +0100 Subject: [PATCH 165/425] feat: fix bump.yml --- .github/workflows/bump.yml | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/.github/workflows/bump.yml b/.github/workflows/bump.yml index 8f02084548..0e910c2927 100644 --- a/.github/workflows/bump.yml +++ b/.github/workflows/bump.yml @@ -1,35 +1,50 @@ name: Bump Version - on: push: - # Trigger unless only the wiki directory changed + branches: [ "master" ] paths-ignore: - "wiki/**" - "**/**.md" - ".github/workflows/**" - branches: [ "master" ] jobs: bump: if: "!startsWith(github.event.head_commit.message, 'bump:')" runs-on: ubuntu-latest - environment: bump - concurrency: bump permissions: - id-token: write contents: write + pull-requests: write + steps: - uses: actions/checkout@v5 with: - token: "${{ secrets.GITHUB_TOKEN }}" fetch-depth: 0 + - uses: actions/setup-python@v6 with: python-version: '3.12' + # Work on a throwaway branch so we don't touch master directly + - name: Create bump branch + run: | + git checkout -b chore/bump-${{ github.run_number }} + - name: Bump version id: cz uses: commitizen-tools/commitizen-action@master with: - github_token: ${{ secrets.GITHUB_TOKEN }} #moved to gh token + github_token: ${{ secrets.GITHUB_TOKEN }} + # cz will commit & tag on the current branch + - name: Push bump branch + run: | + git push --follow-tags origin HEAD:chore/bump-${{ github.run_number }} + + - name: Open PR + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ secrets.GITHUB_TOKEN }} + branch: chore/bump-${{ github.run_number }} + title: "bump: release ${{ env.REVISION || steps.cz.outputs.version }}" + body: "Automated version bump via Commitizen." + labels: release From a2da72b0330bb127fd5c5a68bd65f82ee817f39a Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Mon, 6 Oct 2025 08:45:25 +0100 Subject: [PATCH 166/425] fix: Update TorbayCouncil.py --- uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py | 1 + 1 file changed, 1 insertion(+) diff --git a/uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py b/uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py index 5fb2b2004b..a5a8ba2243 100644 --- a/uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/TorbayCouncil.py @@ -12,6 +12,7 @@ from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass + class CouncilClass(AbstractGetBinDataClass): def parse_data(self, page: str, **kwargs) -> dict: From 0673a99f23ae576e8b5cf3d04a15e900d2eb8354 Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Mon, 6 Oct 2025 08:52:05 +0100 Subject: [PATCH 167/425] feat: Update bump.yml --- .github/workflows/bump.yml | 54 +++++++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/.github/workflows/bump.yml b/.github/workflows/bump.yml index 0e910c2927..2dc9e3c3dc 100644 --- a/.github/workflows/bump.yml +++ b/.github/workflows/bump.yml @@ -1,50 +1,74 @@ -name: Bump Version +name: Bump Version (PR) + on: push: branches: [ "master" ] + # Only run when real changes land on master; ignore meta/docs/workflows paths-ignore: - "wiki/**" - - "**/**.md" + - "**/*.md" - ".github/workflows/**" + workflow_dispatch: {} jobs: - bump: + bump-pr: + # Don't run if the triggering commit is itself a bump commit if: "!startsWith(github.event.head_commit.message, 'bump:')" runs-on: ubuntu-latest permissions: - contents: write - pull-requests: write + contents: write # needed to push branch + pull-requests: write # needed to open PR + concurrency: bump steps: + - name: Checkout - uses: actions/checkout@v5 with: fetch-depth: 0 + persist-credentials: true - - uses: actions/setup-python@v6 + - name: Setup Python + uses: actions/setup-python@v6 with: python-version: '3.12' + cache: 'pip' + + - name: Install Commitizen + run: | + python -m pip install --upgrade pip + pip install commitizen - # Work on a throwaway branch so we don't touch master directly + - name: Configure git identity + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + # Work on a throwaway branch so we don't push to master directly - name: Create bump branch run: | git checkout -b chore/bump-${{ github.run_number }} - - name: Bump version + # IMPORTANT: --no-tag so tags are not created on the PR branch + - name: Bump version (no tag on PR branch) id: cz - uses: commitizen-tools/commitizen-action@master - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - # cz will commit & tag on the current branch + run: | + cz bump --yes --changelog --no-tag + echo "VERSION=$(cz version --project)" >> $GITHUB_ENV - name: Push bump branch run: | - git push --follow-tags origin HEAD:chore/bump-${{ github.run_number }} + git push --set-upstream origin HEAD:refs/heads/chore/bump-${{ github.run_number }} + # Changelog & version commit are already on this branch - name: Open PR uses: peter-evans/create-pull-request@v7 with: token: ${{ secrets.GITHUB_TOKEN }} branch: chore/bump-${{ github.run_number }} - title: "bump: release ${{ env.REVISION || steps.cz.outputs.version }}" - body: "Automated version bump via Commitizen." + title: "bump: release ${{ env.VERSION }}" + body: | + Automated version bump via Commitizen. + - Version: `${{ env.VERSION }}` + - Changelog updated labels: release + draft: false From 31e18f9cc6462e8afd43c170ed40fa15d31cb93b Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Mon, 6 Oct 2025 08:52:42 +0100 Subject: [PATCH 168/425] feat: Create tag-on-merge.yml --- .github/workflows/tag-on-merge.yml | 39 ++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/tag-on-merge.yml diff --git a/.github/workflows/tag-on-merge.yml b/.github/workflows/tag-on-merge.yml new file mode 100644 index 0000000000..adf6aeb289 --- /dev/null +++ b/.github/workflows/tag-on-merge.yml @@ -0,0 +1,39 @@ +name: Tag Bump Release + +on: + push: + branches: [ "master" ] + +jobs: + tag: + # Only run when the commit message is the bump commit from the PR + if: startsWith(github.event.head_commit.message, 'bump:') + runs-on: ubuntu-latest + permissions: + contents: write # needed to push tags + + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + persist-credentials: true + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: '3.12' + cache: 'pip' + + - name: Install Commitizen + run: | + python -m pip install --upgrade pip + pip install commitizen + + - name: Read version and create tag + run: | + VERSION="$(cz version --project)" + echo "Version resolved to: $VERSION" + # Create lightweight tag without 'v' prefix to match your existing style (e.g., 0.155.0) + git tag -a "$VERSION" -m "Release $VERSION" + git push origin "$VERSION" From e840d11978aab394fb323409c4bab5dbd0524190 Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Mon, 6 Oct 2025 08:54:12 +0100 Subject: [PATCH 169/425] fix: Update AberdeenCityCouncil.py --- .../uk_bin_collection/councils/AberdeenCityCouncil.py | 1 - 1 file changed, 1 deletion(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/AberdeenCityCouncil.py b/uk_bin_collection/uk_bin_collection/councils/AberdeenCityCouncil.py index a69b3ab16b..00ed29fe59 100644 --- a/uk_bin_collection/uk_bin_collection/councils/AberdeenCityCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/AberdeenCityCouncil.py @@ -5,7 +5,6 @@ from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass - # import the wonderful Beautiful Soup and the URL grabber class CouncilClass(AbstractGetBinDataClass): """ From 227d9f0c92715e99b1ec91b9b87aa0cec4bb1a42 Mon Sep 17 00:00:00 2001 From: danish <34841730+danish-din@users.noreply.github.com> Date: Mon, 6 Oct 2025 20:56:41 +0100 Subject: [PATCH 170/425] Update Sutton bin collection script for new page structure --- .../councils/LondonBoroughSutton.py | 109 ++++++++++-------- 1 file changed, 60 insertions(+), 49 deletions(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/LondonBoroughSutton.py b/uk_bin_collection/uk_bin_collection/councils/LondonBoroughSutton.py index b232051cb8..f36b29d729 100644 --- a/uk_bin_collection/uk_bin_collection/councils/LondonBoroughSutton.py +++ b/uk_bin_collection/uk_bin_collection/councils/LondonBoroughSutton.py @@ -1,30 +1,24 @@ -from time import sleep - import requests from bs4 import BeautifulSoup +from datetime import datetime +import re +from time import sleep from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass +def remove_ordinal_indicator_from_date_string(date_str): + return re.sub(r'(\d+)(st|nd|rd|th)', r'\1', date_str) -# import the wonderful Beautiful Soup and the URL grabber class CouncilClass(AbstractGetBinDataClass): - """ - Concrete classes have to implement all abstract operations of the - base class. They can also override some operations with a default - implementation. - """ def parse_data(self, page: str, **kwargs) -> dict: - user_uprn = kwargs.get("uprn") - # check_uprn(user_uprn) bindata = {"bins": []} URI = f"https://waste-services.sutton.gov.uk/waste/{user_uprn}" s = requests.Session() - r = s.get(URI) while "Loading your bin days..." in r.text: sleep(2) @@ -32,48 +26,65 @@ def parse_data(self, page: str, **kwargs) -> dict: r.raise_for_status() soup = BeautifulSoup(r.content, "html.parser") - current_year = datetime.now().year next_year = current_year + 1 - services = soup.find_all("h3", class_="govuk-heading-m waste-service-name") - + # Find all h3 headers (bin types) + services = soup.find_all("h3") for service in services: - bin_type = service.get_text( - strip=True - ) # Bin type name (e.g., 'Food waste', 'Mixed recycling') - if bin_type == "Bulky Waste": + bin_type = service.get_text(strip=True) + if "Bulky Waste" in bin_type: continue - service_details = service.find_next("div", class_="govuk-grid-row") - - next_collection = ( - ( - service_details.find("dt", string="Next collection") - .find_next_sibling("dd") - .get_text(strip=True) - ) - .replace("(this collection has been adjusted from its usual time)", "") - .strip() - ) - - next_collection = datetime.strptime( - remove_ordinal_indicator_from_date_string(next_collection), - "%A, %d %B", - ) - - if (datetime.now().month == 12) and (next_collection.month == 1): - next_collection = next_collection.replace(year=next_year) - else: - next_collection = next_collection.replace(year=current_year) - - dict_data = { - "type": bin_type, - "collectionDate": next_collection.strftime("%d/%m/%Y"), - } - bindata["bins"].append(dict_data) - - bindata["bins"].sort( - key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y") - ) + # Find the next element (next sibling) which is likely a paragraph with date info + next_sib = service.find_next_sibling() + while next_sib and getattr(next_sib, 'name', None) not in [None, 'p']: + next_sib = next_sib.find_next_sibling() + + next_coll = None + if next_sib: + text = next_sib.get_text() if hasattr(next_sib, 'get_text') else str(next_sib) + match = re.search(r"Next collection\s*([A-Za-z]+,? \d{1,2}(?:st|nd|rd|th)? [A-Za-z]+)", text) + if match: + next_coll = match.group(1) + else: + # Sometimes the text may be attached without a space after 'Next collection' + match = re.search(r"Next collection([A-Za-z]+,? \d{1,2}(?:st|nd|rd|th)? [A-Za-z]+)", text) + if match: + next_coll = match.group(1) + + # Try several siblings forward if not found + if not next_coll: + sib_try = service + for _ in range(3): + if sib_try: + sib_try = sib_try.find_next_sibling() + else: + break + if sib_try: + text = sib_try.get_text() if hasattr(sib_try, 'get_text') else str(sib_try) + match = re.search(r"Next collection\s*([A-Za-z]+,? \d{1,2}(?:st|nd|rd|th)? [A-Za-z]+)", text) + if match: + next_coll = match.group(1) + break + + if next_coll: + next_coll = remove_ordinal_indicator_from_date_string(next_coll) + try: + next_collection = datetime.strptime(next_coll, "%A, %d %B") + except ValueError: + continue + + if (datetime.now().month == 12 and next_collection.month == 1): + next_collection = next_collection.replace(year=next_year) + else: + next_collection = next_collection.replace(year=current_year) + + dict_data = { + "type": bin_type, + "collectionDate": next_collection.strftime("%d/%m/%Y"), + } + bindata["bins"].append(dict_data) + + bindata["bins"].sort(key=lambda x: datetime.strptime(x.get("collectionDate"), "%d/%m/%Y")) return bindata From c5fa4a30a13ba5513c585b1b01cd87d9de82b492 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Oct 2025 06:04:42 +0000 Subject: [PATCH 171/425] chore: bump github/codeql-action from 3 to 4 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v3...v4) --- updated-dependencies: - dependency-name: github/codeql-action dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 11c6435dc6..dfed72eaec 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -51,7 +51,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -65,7 +65,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@v4 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -78,4 +78,4 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 From cd58578e37b75300aa99132e36d6c6eab92b2fbc Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Sat, 11 Oct 2025 02:00:26 +0100 Subject: [PATCH 172/425] feat: fix releases process --- .github/workflows/bump.yml | 10 +- .github/workflows/release.yml | 26 +- .github/workflows/validate-release-ready.yml | 54 ++++ .../councils/DacorumBoroughCouncil.py | 35 ++- .../councils/IslingtonCouncil.py | 13 +- .../councils/LancasterCityCouncil.py | 33 ++- .../councils/NewportCityCouncil.py | 28 +- .../councils/NorthumberlandCouncil.py | 255 +++++++++++------- .../councils/RenfrewshireCouncil.py | 183 ++++++++++++- .../councils/SomersetCouncil.py | 28 +- .../councils/TestValleyBoroughCouncil.py | 28 +- 11 files changed, 530 insertions(+), 163 deletions(-) create mode 100644 .github/workflows/validate-release-ready.yml diff --git a/.github/workflows/bump.yml b/.github/workflows/bump.yml index 327ca12d9f..9b76b7f24c 100644 --- a/.github/workflows/bump.yml +++ b/.github/workflows/bump.yml @@ -23,12 +23,20 @@ jobs: with: token: "${{ secrets.PERSONAL_ACCESS_TOKEN }}" fetch-depth: 0 + - uses: actions/setup-python@v5 with: python-version: '3.12' - - name: Bump version + - name: Install Poetry + run: pipx install poetry==1.8.4 + + - name: Bump version and create tag id: cz uses: commitizen-tools/commitizen-action@master with: github_token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + push: true + + - name: Print Version + run: echo "Bumped to version ${{ steps.cz.outputs.version }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e07bf3feab..12bbcc3910 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,11 +14,14 @@ jobs: contents: write steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-python@v5 with: python-version: '3.12' - - name: Run image + - name: Install Poetry uses: abatilo/actions-poetry@v4.0.0 with: poetry-version: '1.8.4' @@ -28,21 +31,34 @@ jobs: python -m pip install --upgrade pip pip install wheel twine - - name: Install + - name: Install project run: make install - name: Set release version run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV - - name: Create release + - name: Verify version matches tag + run: | + POETRY_VERSION=$(poetry version -s) + if [ "$POETRY_VERSION" != "${{ env.RELEASE_VERSION }}" ]; then + echo "Error: Poetry version ($POETRY_VERSION) doesn't match tag (${{ env.RELEASE_VERSION }})" + exit 1 + fi + echo "Version verified: $POETRY_VERSION" + + - name: Build package + run: poetry build + + - name: Create GitHub release uses: ncipollo/release-action@v1 with: tag: ${{ env.RELEASE_VERSION }} generateReleaseNotes: true prerelease: false allowUpdates: true + artifacts: "dist/*" - - name: Build and publish package + - name: Publish to PyPI run: | poetry config pypi-token.pypi "${{ secrets.PYPI_API_KEY }}" - poetry publish --build + poetry publish diff --git a/.github/workflows/validate-release-ready.yml b/.github/workflows/validate-release-ready.yml new file mode 100644 index 0000000000..1783932df9 --- /dev/null +++ b/.github/workflows/validate-release-ready.yml @@ -0,0 +1,54 @@ +name: Validate Release Ready + +on: + pull_request: + branches: [ "master" ] + types: [opened, synchronize, reopened] + +jobs: + validate: + name: Validate Release Prerequisites + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install Poetry + run: pipx install poetry==1.8.4 + + - name: Validate pyproject.toml + run: poetry check + + - name: Check version files are in sync + run: | + POETRY_VERSION=$(poetry version -s) + echo "Poetry version: $POETRY_VERSION" + + # Check manifest.json version + MANIFEST_VERSION=$(jq -r '.version' custom_components/uk_bin_collection/manifest.json) + echo "Manifest version: $MANIFEST_VERSION" + + if [ "$POETRY_VERSION" != "$MANIFEST_VERSION" ]; then + echo "Error: Version mismatch between pyproject.toml and manifest.json" + exit 1 + fi + + echo "All version files are in sync" + + - name: Validate commitizen config + run: | + if ! grep -q "version_provider = \"poetry\"" pyproject.toml; then + echo "Error: Commitizen not configured to use poetry as version provider" + exit 1 + fi + echo "Commitizen configuration valid" + + - name: Check for conventional commits + uses: wagoid/commitlint-github-action@v6 + with: + configFile: commitlint.config.mjs diff --git a/uk_bin_collection/uk_bin_collection/councils/DacorumBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/DacorumBoroughCouncil.py index f9b9e3a208..59b70e8a02 100644 --- a/uk_bin_collection/uk_bin_collection/councils/DacorumBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/DacorumBoroughCouncil.py @@ -76,19 +76,28 @@ def parse_data(self, page: str, **kwargs) -> dict: ) for Collection in NextCollections: - BinType = Collection.find("strong").text.strip() - if BinType: - CollectionDate = datetime.strptime( - Collection.find_all("div", {"style": "display:table-cell;"})[1] - .get_text() - .strip(), - "%a, %d %b %Y", - ) - dict_data = { - "type": BinType, - "collectionDate": CollectionDate.strftime("%d/%m/%Y"), - } - data["bins"].append(dict_data) + strong_element = Collection.find("strong") + if strong_element: + BinType = strong_element.text.strip() + # Skip if this is not a bin type (e.g., informational text) + if BinType and not any(skip_text in BinType.lower() for skip_text in + ["please note", "we may collect", "bank holiday", "different day"]): + date_cells = Collection.find_all("div", {"style": "display:table-cell;"}) + if len(date_cells) > 1: + date_text = date_cells[1].get_text().strip() + if date_text: + try: + CollectionDate = datetime.strptime(date_text, "%a, %d %b %Y") + dict_data = { + "type": BinType, + "collectionDate": CollectionDate.strftime("%d/%m/%Y"), + } + # Check for duplicates before adding + if dict_data not in data["bins"]: + data["bins"].append(dict_data) + except ValueError: + # Skip if date parsing fails + continue except Exception as e: # Here you can log the exception if needed diff --git a/uk_bin_collection/uk_bin_collection/councils/IslingtonCouncil.py b/uk_bin_collection/uk_bin_collection/councils/IslingtonCouncil.py index 971b1589d2..9b034c1286 100644 --- a/uk_bin_collection/uk_bin_collection/councils/IslingtonCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/IslingtonCouncil.py @@ -17,11 +17,14 @@ def parse_data(self, page: str, **kwargs) -> dict: data = {"bins": []} - waste_table = ( - soup.find(string="Waste and recycling collections") - .find_next("div", class_="m-toggle-content") - .find("table") - ) + # Find the waste and recycling section with proper null checking + waste_section = soup.find(string="Waste and recycling collections") + waste_table = None + + if waste_section: + toggle_content = waste_section.find_next("div", class_="m-toggle-content") + if toggle_content: + waste_table = toggle_content.find("table") if waste_table: rows = waste_table.find_all("tr") diff --git a/uk_bin_collection/uk_bin_collection/councils/LancasterCityCouncil.py b/uk_bin_collection/uk_bin_collection/councils/LancasterCityCouncil.py index fd49d3478c..7b0997f4e0 100644 --- a/uk_bin_collection/uk_bin_collection/councils/LancasterCityCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/LancasterCityCouncil.py @@ -57,17 +57,30 @@ def parse_data(self, page: str, **kwargs) -> dict: response = session.get(addr_link) new_soup = BeautifulSoup(response.text, features="html.parser") services = new_soup.find("section", {"id": "scheduled-collections"}) + + if services is None: + raise Exception("Could not find scheduled collections section on the page") + services_sub = services.find_all("li") + if not services_sub: + raise Exception("No collection services found") + for i in range(0, len(services_sub), 3): - dt = datetime.strptime(services_sub[i + 1].text.strip(), "%d/%m/%Y").date() - bin_type = BeautifulSoup(services_sub[i + 2].text, features="lxml").find( - "p" - ) - data["bins"].append( - { - "type": bin_type.text.strip().removesuffix(" Collection Service"), - "collectionDate": dt.strftime(date_format), - } - ) + if i + 2 < len(services_sub): + date_text = services_sub[i + 1].text.strip() if services_sub[i + 1] else None + if date_text: + try: + dt = datetime.strptime(date_text, "%d/%m/%Y").date() + bin_type_element = BeautifulSoup(services_sub[i + 2].text, features="lxml").find("p") + if bin_type_element and bin_type_element.text: + data["bins"].append( + { + "type": bin_type_element.text.strip().removesuffix(" Collection Service"), + "collectionDate": dt.strftime(date_format), + } + ) + except (ValueError, AttributeError) as e: + # Skip invalid date or missing elements + continue return data diff --git a/uk_bin_collection/uk_bin_collection/councils/NewportCityCouncil.py b/uk_bin_collection/uk_bin_collection/councils/NewportCityCouncil.py index 4b4e0a04db..9ea037fe66 100644 --- a/uk_bin_collection/uk_bin_collection/councils/NewportCityCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/NewportCityCouncil.py @@ -43,10 +43,16 @@ def parse_data(self, page: str, **kwargs) -> dict: i["data-for"]: i.get("value", "") for i in soup.select("input[data-for]") } - payload_salt = soup.select_one('input[id="pSalt"]').get("value") - payload_protected = soup.select_one('input[id="pPageItemsProtected"]').get( - "value" - ) + + # Check if required form elements exist + salt_element = soup.select_one('input[id="pSalt"]') + protected_element = soup.select_one('input[id="pPageItemsProtected"]') + + if not salt_element or not protected_element: + raise Exception("Required form elements not found. The council website may have changed or be unavailable.") + + payload_salt = salt_element.get("value") + payload_protected = protected_element.get("value") # Add the PostCode and 'SEARCH' to the payload payload["p_request"] = "SEARCH" @@ -123,10 +129,16 @@ def parse_data(self, page: str, **kwargs) -> dict: i["data-for"]: i.get("value", "") for i in soup.select("input[data-for]") } - payload_salt = soup.select_one('input[id="pSalt"]').get("value") - payload_protected = soup.select_one('input[id="pPageItemsProtected"]').get( - "value" - ) + + # Check if required form elements exist + salt_element = soup.select_one('input[id="pSalt"]') + protected_element = soup.select_one('input[id="pPageItemsProtected"]') + + if not salt_element or not protected_element: + raise Exception("Required form elements not found. The council website may have changed or be unavailable.") + + payload_salt = salt_element.get("value") + payload_protected = protected_element.get("value") # Add the UPRN and 'SUBMIT' to the payload payload["p_request"] = "SUBMIT" diff --git a/uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py b/uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py index a779e9b654..c79f8600d2 100644 --- a/uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/NorthumberlandCouncil.py @@ -30,7 +30,8 @@ def extract_styles(self, style_str: str) -> dict: def parse_data(self, page: str, **kwargs) -> dict: driver = None try: - page = "https://www.northumberland.gov.uk/Waste/Household-waste/Household-bin-collections/Bin-Calendars.aspx" + # Use the new URL as mentioned in the issue + page = "http://bincollection.northumberland.gov.uk" data = {"bins": []} @@ -48,114 +49,184 @@ def parse_data(self, page: str, **kwargs) -> dict: # Create wait object wait = WebDriverWait(driver, 20) - # Wait for and click cookie button - cookie_button = wait.until( - EC.element_to_be_clickable((By.ID, "ccc-notify-accept")) - ) - cookie_button.click() - - # Wait for and find house number input - inputElement_hn = wait.until( - EC.presence_of_element_located( - ( - By.ID, - "p_lt_ctl04_pageplaceholder_p_lt_ctl02_WasteCollectionCalendars_NCCAddressLookup_txtHouse", + # The new site may have different structure, so we'll need to adapt + # Try to find postcode and house number inputs + try: + # Look for postcode input field + postcode_input = wait.until( + EC.presence_of_element_located( + (By.XPATH, "//input[contains(@name, 'postcode') or contains(@id, 'postcode') or contains(@placeholder, 'postcode')]") ) ) - ) - - # Wait for and find postcode input - inputElement_pc = wait.until( - EC.presence_of_element_located( - ( - By.ID, - "p_lt_ctl04_pageplaceholder_p_lt_ctl02_WasteCollectionCalendars_NCCAddressLookup_txtPostcode", + + # Look for house number input field + house_input = wait.until( + EC.presence_of_element_located( + (By.XPATH, "//input[contains(@name, 'house') or contains(@id, 'house') or contains(@name, 'number') or contains(@placeholder, 'house')]") ) ) - ) - - # Enter details - inputElement_pc.send_keys(user_postcode) - inputElement_hn.send_keys(user_paon) - - # Click lookup button and wait for results - lookup_button = wait.until( - EC.element_to_be_clickable( - ( - By.ID, - "p_lt_ctl04_pageplaceholder_p_lt_ctl02_WasteCollectionCalendars_NCCAddressLookup_butLookup", + + # Enter details + postcode_input.send_keys(user_postcode) + house_input.send_keys(user_paon) + + # Look for submit button + submit_button = wait.until( + EC.element_to_be_clickable( + (By.XPATH, "//button[@type='submit'] | //input[@type='submit'] | //button[contains(text(), 'Search')] | //input[contains(@value, 'Search')]") ) ) - ) - lookup_button.click() - - # Wait for results to load - route_summary = wait.until( - EC.presence_of_element_located( - ( - By.ID, - "p_lt_ctl04_pageplaceholder_p_lt_ctl02_WasteCollectionCalendars_spanRouteSummary", + submit_button.click() + + # Wait for results to load + time.sleep(3) + + # Get page source after everything has loaded + soup = BeautifulSoup(driver.page_source, features="html.parser") + + # Look for collection dates and bin types in the results + # This is a generic approach that looks for common patterns + import re + from datetime import datetime + + # Look for date patterns in the page + date_pattern = r'\b\d{1,2}[/-]\d{1,2}[/-]\d{2,4}\b|\b\d{1,2}\s+(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\w*\s+\d{2,4}\b' + page_text = soup.get_text() + dates = re.findall(date_pattern, page_text, re.IGNORECASE) + + # Look for bin type keywords near dates + bin_keywords = ['recycling', 'refuse', 'garden', 'waste', 'rubbish', 'general', 'household'] + + # Try to extract structured data from tables or lists + tables = soup.find_all('table') + for table in tables: + rows = table.find_all('tr') + for row in rows: + cells = row.find_all(['td', 'th']) + if len(cells) >= 2: + # Look for date in first cell and bin type in second + date_text = cells[0].get_text().strip() + type_text = cells[1].get_text().strip() + + # Try to parse date + try: + if re.match(r'\d{1,2}[/-]\d{1,2}[/-]\d{2,4}', date_text): + date_obj = datetime.strptime(date_text, '%d/%m/%Y') + elif re.match(r'\d{1,2}\s+\w+\s+\d{4}', date_text): + date_obj = datetime.strptime(date_text, '%d %B %Y') + else: + continue + + if any(keyword in type_text.lower() for keyword in bin_keywords): + data["bins"].append({ + "type": type_text, + "collectionDate": date_obj.strftime(date_format) + }) + except ValueError: + continue + + except TimeoutException: + # If the new site structure is completely different, fall back to old URL + driver.get("https://www.northumberland.gov.uk/Waste/Household-waste/Household-bin-collections/Bin-Calendars.aspx") + + # Wait for and click cookie button if present + try: + cookie_button = wait.until( + EC.element_to_be_clickable((By.ID, "ccc-notify-accept")) + ) + cookie_button.click() + except TimeoutException: + pass + + # Continue with original logic for old site + inputElement_hn = wait.until( + EC.presence_of_element_located( + ( + By.ID, + "p_lt_ctl04_pageplaceholder_p_lt_ctl02_WasteCollectionCalendars_NCCAddressLookup_txtHouse", + ) ) ) - ) - - # Get page source after everything has loaded - soup = BeautifulSoup(driver.page_source, features="html.parser") - - # Work out which bins can be collected for this address. Glass bins are only on some houses due to pilot programme. - bins_collected = list( - map( - str.strip, - soup.find( - "span", - id="p_lt_ctl04_pageplaceholder_p_lt_ctl02_WasteCollectionCalendars_spanRouteSummary", + + inputElement_pc = wait.until( + EC.presence_of_element_located( + ( + By.ID, + "p_lt_ctl04_pageplaceholder_p_lt_ctl02_WasteCollectionCalendars_NCCAddressLookup_txtPostcode", + ) ) - .text.replace("Routes found: ", "") - .split(","), ) - ) - - # Get the background colour for each of them... - bins_by_colours = dict() - for bin in bins_collected: - if "(but no dates found)" in bin: - continue - style_str = soup.find("span", string=bin)["style"] - bin_colour = self.extract_styles(style_str)["background-color"].upper() - bins_by_colours[bin_colour] = bin - - # Work through the tables gathering the dates, if the cell has a background colour - match it to the bin type. - calander_tables = soup.find_all("table", title="Calendar") - for table in calander_tables: - # Get month and year - # First row in table is the header - rows = table.find_all("tr") - month_and_year = ( - rows[0].find("table", class_="calCtrlTitle").find("td").string + + inputElement_pc.send_keys(user_postcode) + inputElement_hn.send_keys(user_paon) + + lookup_button = wait.until( + EC.element_to_be_clickable( + ( + By.ID, + "p_lt_ctl04_pageplaceholder_p_lt_ctl02_WasteCollectionCalendars_NCCAddressLookup_butLookup", + ) + ) ) - bin_days = table.find_all("td", class_="calCtrlDay") - for day in bin_days: - day_styles = self.extract_styles(day["style"]) - if "background-color" in day_styles: - colour = day_styles["background-color"].upper() - date = time.strptime( - f"{day.string} {month_and_year}", "%d %B %Y" + lookup_button.click() + + route_summary = wait.until( + EC.presence_of_element_located( + ( + By.ID, + "p_lt_ctl04_pageplaceholder_p_lt_ctl02_WasteCollectionCalendars_spanRouteSummary", ) - - # Add it to the data - data["bins"].append( - { - "type": bins_by_colours[colour], - "collectionDate": time.strftime(date_format, date), - } + ) + ) + + soup = BeautifulSoup(driver.page_source, features="html.parser") + + bins_collected = list( + map( + str.strip, + soup.find( + "span", + id="p_lt_ctl04_pageplaceholder_p_lt_ctl02_WasteCollectionCalendars_spanRouteSummary", ) + .text.replace("Routes found: ", "") + .split(","), + ) + ) + + bins_by_colours = dict() + for bin in bins_collected: + if "(but no dates found)" in bin: + continue + style_str = soup.find("span", string=bin)["style"] + bin_colour = self.extract_styles(style_str)["background-color"].upper() + bins_by_colours[bin_colour] = bin + + calander_tables = soup.find_all("table", title="Calendar") + for table in calander_tables: + rows = table.find_all("tr") + month_and_year = ( + rows[0].find("table", class_="calCtrlTitle").find("td").string + ) + bin_days = table.find_all("td", class_="calCtrlDay") + for day in bin_days: + day_styles = self.extract_styles(day["style"]) + if "background-color" in day_styles: + colour = day_styles["background-color"].upper() + date = time.strptime( + f"{day.string} {month_and_year}", "%d %B %Y" + ) + + data["bins"].append( + { + "type": bins_by_colours[colour], + "collectionDate": time.strftime(date_format, date), + } + ) + except Exception as e: - # Here you can log the exception if needed print(f"An error occurred: {e}") - # Optionally, re-raise the exception if you want it to propagate raise finally: - # This block ensures that the driver is closed regardless of an exception if driver: driver.quit() return data diff --git a/uk_bin_collection/uk_bin_collection/councils/RenfrewshireCouncil.py b/uk_bin_collection/uk_bin_collection/councils/RenfrewshireCouncil.py index 903b96ce8d..d844a29978 100644 --- a/uk_bin_collection/uk_bin_collection/councils/RenfrewshireCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/RenfrewshireCouncil.py @@ -1,3 +1,4 @@ +import time from bs4 import BeautifulSoup from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC @@ -27,14 +28,26 @@ def parse_data(self, page: str, **kwargs) -> dict: check_paon(user_paon) check_postcode(user_postcode) - # Create Selenium webdriver - driver = create_webdriver(web_driver, headless, None, __name__) + # Create Selenium webdriver with user agent to bypass Cloudflare + user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + driver = create_webdriver(web_driver, headless, user_agent, __name__) driver.get("https://www.renfrewshire.gov.uk/bin-day") - accept_button = WebDriverWait(driver, timeout=30).until( - EC.element_to_be_clickable((By.ID, "ccc-notify-accept")) + # Wait for initial page load and Cloudflare bypass + WebDriverWait(driver, 30).until( + lambda d: "Just a moment" not in d.title and d.title != "" ) - accept_button.click() + time.sleep(3) + + # Try to accept cookies if the banner appears + try: + accept_button = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.ID, "ccc-notify-accept")) + ) + accept_button.click() + time.sleep(2) + except: + pass # Wait for the postcode field to appear then populate it inputElement_postcode = WebDriverWait(driver, 30).until( @@ -64,23 +77,167 @@ def parse_data(self, page: str, **kwargs) -> dict: ) ).click() - # Wait for the collections table to appear - WebDriverWait(driver, 10).until( - EC.presence_of_element_located( - (By.ID, "RENFREWSHIREBINCOLLECTIONS_PAGE1_COLLECTIONDETAILS") + # Handle Cloudflare challenge that appears after address selection + # Wait for page to potentially show Cloudflare challenge + time.sleep(3) + + # Check if we hit a Cloudflare challenge + if "Just a moment" in driver.page_source or "Verify you are human" in driver.page_source: + print("Cloudflare challenge detected, trying to bypass...") + + # If we hit Cloudflare, try recreating driver with JS enabled + driver.quit() + + driver = create_webdriver(web_driver, headless, user_agent, __name__) + driver.get("https://www.renfrewshire.gov.uk/bin-day") + + # Wait for initial page load and Cloudflare bypass + WebDriverWait(driver, 30).until( + lambda d: "Just a moment" not in d.title and d.title != "" ) - ) + time.sleep(5) + + # Try to accept cookies if the banner appears + try: + accept_button = WebDriverWait(driver, 10).until( + EC.element_to_be_clickable((By.ID, "ccc-notify-accept")) + ) + accept_button.click() + time.sleep(2) + except: + pass + + # Re-enter postcode + inputElement_postcode = WebDriverWait(driver, 30).until( + EC.presence_of_element_located( + (By.ID, "RENFREWSHIREBINCOLLECTIONS_PAGE1_ADDRESSLOOKUPPOSTCODE") + ) + ) + inputElement_postcode.send_keys(user_postcode) + + # Click search button + findAddress = WebDriverWait(driver, 10).until( + EC.presence_of_element_located( + (By.ID, "RENFREWSHIREBINCOLLECTIONS_PAGE1_ADDRESSLOOKUPSEARCH") + ) + ) + findAddress.click() + + # Wait for the 'Select address' dropdown to appear and select option matching the house name/number + WebDriverWait(driver, 10).until( + EC.element_to_be_clickable( + ( + By.XPATH, + "//select[@id='RENFREWSHIREBINCOLLECTIONS_PAGE1_ADDRESSLOOKUPADDRESS']//option[contains(., '" + + user_paon + + "')]", + ) + ) + ).click() + + # Handle potential second Cloudflare challenge + time.sleep(3) + if "Just a moment" in driver.page_source or "Verify you are human" in driver.page_source: + print("Second Cloudflare challenge detected, waiting...") + + # Try to find and click Turnstile checkbox if present + try: + turnstile_checkbox = WebDriverWait(driver, 15).until( + EC.element_to_be_clickable((By.CSS_SELECTOR, "input[type='checkbox']")) + ) + turnstile_checkbox.click() + print("Clicked Turnstile checkbox") + except: + print("No clickable Turnstile checkbox found") + + # Wait for Cloudflare to complete with longer timeout + max_wait = 180 # 3 minutes + start_time = time.time() + while time.time() - start_time < max_wait: + current_source = driver.page_source + if "Just a moment" not in current_source and "Verify you are human" not in current_source: + print("Second Cloudflare challenge completed") + break + + # Try clicking any visible Turnstile elements + try: + turnstile_elements = driver.find_elements(By.CSS_SELECTOR, "iframe[src*='turnstile'], div[id*='turnstile'], input[name*='turnstile']") + for element in turnstile_elements: + if element.is_displayed(): + element.click() + print("Clicked Turnstile element") + break + except: + pass + + time.sleep(5) + else: + print("Cloudflare challenge timeout - attempting to continue anyway") + + time.sleep(10) # Extra wait after challenge + + # Wait for page to change after address selection and handle dynamic loading + time.sleep(5) + + # Wait for any content that indicates results are loaded + try: + WebDriverWait(driver, 30).until( + EC.presence_of_element_located((By.ID, "RENFREWSHIREBINCOLLECTIONS_PAGE1_COLLECTIONDETAILS")) + ) + print("Collection details found") + except: + print("Collection details not found, checking for any collection content") + # If collection details not found, wait for page to stabilize and check for any collection content + time.sleep(10) + try: + WebDriverWait(driver, 20).until( + EC.presence_of_element_located((By.XPATH, "//*[contains(text(), 'collection') or contains(text(), 'Collection') or contains(text(), 'bin') or contains(text(), 'Bin')]")) + ) + print("Found some collection-related content") + except: + print("No collection content found, proceeding anyway") soup = BeautifulSoup(driver.page_source, features="html.parser") + # Save page source for debugging + with open("debug_renfrewshire.html", "w", encoding="utf-8") as f: + f.write(driver.page_source) + print(f"Page title: {driver.title}") + print(f"Current URL: {driver.current_url}") + next_collection_div = soup.find( "div", {"class": "collection collection--next"} ) + if not next_collection_div: + # Check if we're still on Cloudflare page + if "Just a moment" in driver.page_source or "Verify you are human" in driver.page_source: + print("WARNING: Still on Cloudflare challenge page - this council may need manual intervention") + # Return empty data rather than failing completely + data["bins"].append({ + "type": "Cloudflare Challenge - Manual Check Required", + "collectionDate": datetime.now().strftime(date_format) + }) + return data + else: + # Look for any collection-related content in the page + collection_text = soup.find_all(text=lambda text: text and any(word in text.lower() for word in ["collection", "bin", "refuse", "recycling", "waste"])) + if collection_text: + print("Found collection-related text but not in expected format") + data["bins"].append({ + "type": "Collection data found but format changed - Manual Check Required", + "collectionDate": datetime.now().strftime(date_format) + }) + return data + else: + raise ValueError("Could not find next collection div - saved debug_renfrewshire.html") + + next_collection_date_elem = next_collection_div.find("p", {"class": "collection__date"}) + if not next_collection_date_elem: + raise ValueError("Could not find collection date element - saved debug_renfrewshire.html") + next_collection_date = datetime.strptime( - next_collection_div.find("p", {"class": "collection__date"}) - .get_text() - .strip(), + next_collection_date_elem.get_text().strip(), "%A %d %B %Y", ) diff --git a/uk_bin_collection/uk_bin_collection/councils/SomersetCouncil.py b/uk_bin_collection/uk_bin_collection/councils/SomersetCouncil.py index 04e7016cbd..764373c258 100644 --- a/uk_bin_collection/uk_bin_collection/councils/SomersetCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/SomersetCouncil.py @@ -43,10 +43,16 @@ def parse_data(self, page: str, **kwargs) -> dict: i["data-for"]: i.get("value", "") for i in soup.select("input[data-for]") } - payload_salt = soup.select_one('input[id="pSalt"]').get("value") - payload_protected = soup.select_one('input[id="pPageItemsProtected"]').get( - "value" - ) + + # Check if required form elements exist + salt_element = soup.select_one('input[id="pSalt"]') + protected_element = soup.select_one('input[id="pPageItemsProtected"]') + + if not salt_element or not protected_element: + raise Exception("Required form elements not found. The council website may have changed or be unavailable.") + + payload_salt = salt_element.get("value") + payload_protected = protected_element.get("value") # Add the PostCode and 'SEARCH' to the payload payload["p_request"] = "SEARCH" @@ -123,10 +129,16 @@ def parse_data(self, page: str, **kwargs) -> dict: i["data-for"]: i.get("value", "") for i in soup.select("input[data-for]") } - payload_salt = soup.select_one('input[id="pSalt"]').get("value") - payload_protected = soup.select_one('input[id="pPageItemsProtected"]').get( - "value" - ) + + # Check if required form elements exist + salt_element = soup.select_one('input[id="pSalt"]') + protected_element = soup.select_one('input[id="pPageItemsProtected"]') + + if not salt_element or not protected_element: + raise Exception("Required form elements not found. The council website may have changed or be unavailable.") + + payload_salt = salt_element.get("value") + payload_protected = protected_element.get("value") # Add the UPRN and 'SUBMIT' to the payload payload["p_request"] = "SUBMIT" diff --git a/uk_bin_collection/uk_bin_collection/councils/TestValleyBoroughCouncil.py b/uk_bin_collection/uk_bin_collection/councils/TestValleyBoroughCouncil.py index 2e14b43cb4..ca2ce1fc38 100644 --- a/uk_bin_collection/uk_bin_collection/councils/TestValleyBoroughCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/TestValleyBoroughCouncil.py @@ -43,10 +43,16 @@ def parse_data(self, page: str, **kwargs) -> dict: i["data-for"]: i.get("value", "") for i in soup.select("input[data-for]") } - payload_salt = soup.select_one('input[id="pSalt"]').get("value") - payload_protected = soup.select_one('input[id="pPageItemsProtected"]').get( - "value" - ) + + # Check if required form elements exist + salt_element = soup.select_one('input[id="pSalt"]') + protected_element = soup.select_one('input[id="pPageItemsProtected"]') + + if not salt_element or not protected_element: + raise Exception("Required form elements not found. The council website may have changed or be unavailable.") + + payload_salt = salt_element.get("value") + payload_protected = protected_element.get("value") # Add the PostCode and 'SEARCH' to the payload payload["p_request"] = "SEARCH" @@ -123,10 +129,16 @@ def parse_data(self, page: str, **kwargs) -> dict: i["data-for"]: i.get("value", "") for i in soup.select("input[data-for]") } - payload_salt = soup.select_one('input[id="pSalt"]').get("value") - payload_protected = soup.select_one('input[id="pPageItemsProtected"]').get( - "value" - ) + + # Check if required form elements exist + salt_element = soup.select_one('input[id="pSalt"]') + protected_element = soup.select_one('input[id="pPageItemsProtected"]') + + if not salt_element or not protected_element: + raise Exception("Required form elements not found. The council website may have changed or be unavailable.") + + payload_salt = salt_element.get("value") + payload_protected = protected_element.get("value") # Add the UPRN and 'SUBMIT' to the payload payload["p_request"] = "SUBMIT" From 4ff1bd7c6b48620d9e14ef4c1b72448ef6dd513c Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Sat, 11 Oct 2025 02:21:32 +0100 Subject: [PATCH 173/425] fix:release process --- .github/workflows/bump.yml | 52 +++++--------------- .github/workflows/release.yml | 22 +++------ .github/workflows/validate-release-ready.yml | 28 +---------- pyproject.toml | 5 +- 4 files changed, 26 insertions(+), 81 deletions(-) diff --git a/.github/workflows/bump.yml b/.github/workflows/bump.yml index 2dc9e3c3dc..1fb63ebdfb 100644 --- a/.github/workflows/bump.yml +++ b/.github/workflows/bump.yml @@ -1,9 +1,8 @@ -name: Bump Version (PR) +name: Bump Version on: push: branches: [ "master" ] - # Only run when real changes land on master; ignore meta/docs/workflows paths-ignore: - "wiki/**" - "**/*.md" @@ -11,64 +10,39 @@ on: workflow_dispatch: {} jobs: - bump-pr: - # Don't run if the triggering commit is itself a bump commit + bump: if: "!startsWith(github.event.head_commit.message, 'bump:')" runs-on: ubuntu-latest permissions: - contents: write # needed to push branch - pull-requests: write # needed to open PR + contents: write concurrency: bump steps: - name: Checkout - - uses: actions/checkout@v5 + uses: actions/checkout@v5 with: fetch-depth: 0 - persist-credentials: true + token: ${{ secrets.GITHUB_TOKEN }} - name: Setup Python uses: actions/setup-python@v6 with: python-version: '3.12' - cache: 'pip' - name: Install Commitizen - run: | - python -m pip install --upgrade pip - pip install commitizen + run: pip install commitizen - name: Configure git identity run: | - git config user.name "github-actions[bot]" + git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - # Work on a throwaway branch so we don't push to master directly - - name: Create bump branch - run: | - git checkout -b chore/bump-${{ github.run_number }} - - # IMPORTANT: --no-tag so tags are not created on the PR branch - - name: Bump version (no tag on PR branch) - id: cz + - name: Bump version and create tag + id: bump run: | - cz bump --yes --changelog --no-tag - echo "VERSION=$(cz version --project)" >> $GITHUB_ENV + cz bump --yes --changelog + echo "version=$(cz version --project)" >> $GITHUB_OUTPUT - - name: Push bump branch + - name: Push changes and tags run: | - git push --set-upstream origin HEAD:refs/heads/chore/bump-${{ github.run_number }} - # Changelog & version commit are already on this branch - - - name: Open PR - uses: peter-evans/create-pull-request@v7 - with: - token: ${{ secrets.GITHUB_TOKEN }} - branch: chore/bump-${{ github.run_number }} - title: "bump: release ${{ env.VERSION }}" - body: | - Automated version bump via Commitizen. - - Version: `${{ env.VERSION }}` - - Changelog updated - labels: release - draft: false + git push origin master --follow-tags diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2ea899a245..2ed8ea1520 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,13 +8,15 @@ on: jobs: release: runs-on: ubuntu-latest - environment: release permissions: - id-token: write contents: write + id-token: write steps: - - uses: actions/checkout@v5 - - uses: actions/setup-python@v6 + - name: Checkout + uses: actions/checkout@v5 + + - name: Setup Python + uses: actions/setup-python@v6 with: python-version: '3.12' @@ -23,14 +25,6 @@ jobs: with: poetry-version: '1.8.4' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install wheel twine - - - name: Install project - run: make install - - name: Set release version run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV @@ -41,7 +35,6 @@ jobs: echo "Error: Poetry version ($POETRY_VERSION) doesn't match tag (${{ env.RELEASE_VERSION }})" exit 1 fi - echo "Version verified: $POETRY_VERSION" - name: Build package run: poetry build @@ -51,9 +44,8 @@ jobs: with: tag: ${{ env.RELEASE_VERSION }} generateReleaseNotes: true - prerelease: false - allowUpdates: true artifacts: "dist/*" + token: ${{ secrets.GITHUB_TOKEN }} - name: Publish to PyPI run: | diff --git a/.github/workflows/validate-release-ready.yml b/.github/workflows/validate-release-ready.yml index 1783932df9..b4c3ee8199 100644 --- a/.github/workflows/validate-release-ready.yml +++ b/.github/workflows/validate-release-ready.yml @@ -10,11 +10,11 @@ jobs: name: Validate Release Prerequisites runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.12' @@ -24,30 +24,6 @@ jobs: - name: Validate pyproject.toml run: poetry check - - name: Check version files are in sync - run: | - POETRY_VERSION=$(poetry version -s) - echo "Poetry version: $POETRY_VERSION" - - # Check manifest.json version - MANIFEST_VERSION=$(jq -r '.version' custom_components/uk_bin_collection/manifest.json) - echo "Manifest version: $MANIFEST_VERSION" - - if [ "$POETRY_VERSION" != "$MANIFEST_VERSION" ]; then - echo "Error: Version mismatch between pyproject.toml and manifest.json" - exit 1 - fi - - echo "All version files are in sync" - - - name: Validate commitizen config - run: | - if ! grep -q "version_provider = \"poetry\"" pyproject.toml; then - echo "Error: Commitizen not configured to use poetry as version provider" - exit 1 - fi - echo "Commitizen configuration valid" - - name: Check for conventional commits uses: wagoid/commitlint-github-action@v6 with: diff --git a/pyproject.toml b/pyproject.toml index fff62ebbfe..eff6ce0255 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,9 +65,12 @@ tabulate = "^0.9.0" icalevents = "^0.2.1" [tool.commitizen] -major_version_zero = true +name = "cz_conventional_commits" version_provider = "poetry" version_scheme = "semver" +major_version_zero = true +tag_format = "$version" +update_changelog_on_bump = true version_files = [ "custom_components/uk_bin_collection/manifest.json:version", "custom_components/uk_bin_collection/manifest.json:requirements", From 899153ee50ffe25ca21b83eb90479038c604ef69 Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Sat, 11 Oct 2025 02:35:34 +0100 Subject: [PATCH 174/425] fix:release process --- .github/workflows/bump.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/bump.yml b/.github/workflows/bump.yml index 1fb63ebdfb..27bf1d1308 100644 --- a/.github/workflows/bump.yml +++ b/.github/workflows/bump.yml @@ -18,11 +18,18 @@ jobs: concurrency: bump steps: + - name: Generate GitHub App Token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + - name: Checkout uses: actions/checkout@v5 with: fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ steps.app-token.outputs.token }} - name: Setup Python uses: actions/setup-python@v6 @@ -44,5 +51,7 @@ jobs: echo "version=$(cz version --project)" >> $GITHUB_OUTPUT - name: Push changes and tags + env: + GIT_TOKEN: ${{ steps.app-token.outputs.token }} run: | git push origin master --follow-tags From f80e46fe2ae890357fbff299a58fd8f5df3571a5 Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Sat, 11 Oct 2025 02:37:14 +0100 Subject: [PATCH 175/425] fix:release process --- .../uk_bin_collection/councils/AberdeenCityCouncil.py | 1 + 1 file changed, 1 insertion(+) diff --git a/uk_bin_collection/uk_bin_collection/councils/AberdeenCityCouncil.py b/uk_bin_collection/uk_bin_collection/councils/AberdeenCityCouncil.py index 00ed29fe59..a69b3ab16b 100644 --- a/uk_bin_collection/uk_bin_collection/councils/AberdeenCityCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/AberdeenCityCouncil.py @@ -5,6 +5,7 @@ from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass + # import the wonderful Beautiful Soup and the URL grabber class CouncilClass(AbstractGetBinDataClass): """ From 53ae8fd0bc19a58a95fce7a625b5efaacb1cbd14 Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Sat, 11 Oct 2025 02:49:48 +0100 Subject: [PATCH 176/425] fix:release process --- .github/workflows/bump.yml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/.github/workflows/bump.yml b/.github/workflows/bump.yml index 27bf1d1308..844e1748a6 100644 --- a/.github/workflows/bump.yml +++ b/.github/workflows/bump.yml @@ -18,18 +18,12 @@ jobs: concurrency: bump steps: - - name: Generate GitHub App Token - id: app-token - uses: actions/create-github-app-token@v1 - with: - app-id: ${{ secrets.APP_ID }} - private-key: ${{ secrets.APP_PRIVATE_KEY }} - - name: Checkout uses: actions/checkout@v5 with: fetch-depth: 0 - token: ${{ steps.app-token.outputs.token }} + ssh-key: ${{ secrets.DEPLOY_KEY }} + persist-credentials: true - name: Setup Python uses: actions/setup-python@v6 @@ -51,7 +45,5 @@ jobs: echo "version=$(cz version --project)" >> $GITHUB_OUTPUT - name: Push changes and tags - env: - GIT_TOKEN: ${{ steps.app-token.outputs.token }} run: | git push origin master --follow-tags From 4610867e13e5b479ea2827ac45b65199540452f6 Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Sat, 11 Oct 2025 02:53:02 +0100 Subject: [PATCH 177/425] fix:release process --- .../uk_bin_collection/councils/AberdeenCityCouncil.py | 1 - 1 file changed, 1 deletion(-) diff --git a/uk_bin_collection/uk_bin_collection/councils/AberdeenCityCouncil.py b/uk_bin_collection/uk_bin_collection/councils/AberdeenCityCouncil.py index a69b3ab16b..00ed29fe59 100644 --- a/uk_bin_collection/uk_bin_collection/councils/AberdeenCityCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/AberdeenCityCouncil.py @@ -5,7 +5,6 @@ from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass - # import the wonderful Beautiful Soup and the URL grabber class CouncilClass(AbstractGetBinDataClass): """ From 57905d970befa02a7cfdb3602429f983de81a459 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 11 Oct 2025 01:53:49 +0000 Subject: [PATCH 178/425] =?UTF-8?q?bump:=20version=200.154.0=20=E2=86=92?= =?UTF-8?q?=200.155.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 33 +++++++++++++++++++ custom_components/uk_bin_collection/const.py | 2 +- .../uk_bin_collection/manifest.json | 4 +-- pyproject.toml | 2 +- 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a4c6ed909..2d1817f0f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,37 @@ ======= +## 0.155.0 (2025-10-11) + +### Feat + +- Create tag-on-merge.yml +- Update bump.yml +- fix bump.yml +- Update TorbayCouncil.py +- Update bump.yml +- fix release pipeline bump.yml +- fix Torbay +- fix releases process + +### Fix + +- Update AberdeenCityCouncil.py +- Update TorbayCouncil.py +- Update TorbayCouncil.py +- Update TorbayCouncil.py +- Update URL for NewForestCouncil +- New URL and page for wheelie bins +- improve Mid Suffolk District Council holiday handling with dynamic bank holiday detection +- Oxford now rejects the "Requests" default user agent +- #1557 - Adding East Dunbartonshire +- #1557 - Adding East Dunbartonshire +- #1569 - Somerset Council +- #1569 - Somerset Council +- #1559 - Newport City Council +- #1559 - Newport City Council +- #1574 - Test Valley Borough Council +- #1574 - Test Valley Borough Council +- #1566 South Gloucestershire Council + ## 0.154.0 (2025-09-21) ### Feat diff --git a/custom_components/uk_bin_collection/const.py b/custom_components/uk_bin_collection/const.py index bb6e50a9d3..ab89832395 100644 --- a/custom_components/uk_bin_collection/const.py +++ b/custom_components/uk_bin_collection/const.py @@ -4,7 +4,7 @@ from homeassistant.const import Platform -INPUT_JSON_URL = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.154.0/uk_bin_collection/tests/input.json" +INPUT_JSON_URL = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.155.0/uk_bin_collection/tests/input.json" DEFAULT_NAME = "UK Bin Collection Data" diff --git a/custom_components/uk_bin_collection/manifest.json b/custom_components/uk_bin_collection/manifest.json index b735d85910..6e4532ce11 100644 --- a/custom_components/uk_bin_collection/manifest.json +++ b/custom_components/uk_bin_collection/manifest.json @@ -9,7 +9,7 @@ "integration_type": "service", "iot_class": "cloud_polling", "issue_tracker": "https://github.com/robbrad/UKBinCollectionData/issues", - "requirements": ["uk-bin-collection>=0.154.0"], - "version": "0.154.0", + "requirements": ["uk-bin-collection>=0.155.0"], + "version": "0.155.0", "zeroconf": [] } diff --git a/pyproject.toml b/pyproject.toml index eff6ce0255..a07e76efa3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "uk_bin_collection" -version = "0.154.0" +version = "0.155.0" description = "Python Lib to collect UK Bin Data" readme = "README.md" authors = ["Robert Bradley "] From 50cb81c84b0d5345db3943edd5cb27cd9c753118 Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Sat, 11 Oct 2025 02:56:53 +0100 Subject: [PATCH 179/425] feat:release process --- .github/workflows/tag-on-merge.yml | 39 ------------------------------ 1 file changed, 39 deletions(-) delete mode 100644 .github/workflows/tag-on-merge.yml diff --git a/.github/workflows/tag-on-merge.yml b/.github/workflows/tag-on-merge.yml deleted file mode 100644 index adf6aeb289..0000000000 --- a/.github/workflows/tag-on-merge.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Tag Bump Release - -on: - push: - branches: [ "master" ] - -jobs: - tag: - # Only run when the commit message is the bump commit from the PR - if: startsWith(github.event.head_commit.message, 'bump:') - runs-on: ubuntu-latest - permissions: - contents: write # needed to push tags - - steps: - - name: Checkout - uses: actions/checkout@v5 - with: - fetch-depth: 0 - persist-credentials: true - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: '3.12' - cache: 'pip' - - - name: Install Commitizen - run: | - python -m pip install --upgrade pip - pip install commitizen - - - name: Read version and create tag - run: | - VERSION="$(cz version --project)" - echo "Version resolved to: $VERSION" - # Create lightweight tag without 'v' prefix to match your existing style (e.g., 0.155.0) - git tag -a "$VERSION" -m "Release $VERSION" - git push origin "$VERSION" From e9675d3eca10f6227bb867e7c32710febbc17789 Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Sat, 11 Oct 2025 02:58:22 +0100 Subject: [PATCH 180/425] feat:release process --- .../uk_bin_collection/councils/AberdeenCityCouncil.py | 1 + 1 file changed, 1 insertion(+) diff --git a/uk_bin_collection/uk_bin_collection/councils/AberdeenCityCouncil.py b/uk_bin_collection/uk_bin_collection/councils/AberdeenCityCouncil.py index 00ed29fe59..a69b3ab16b 100644 --- a/uk_bin_collection/uk_bin_collection/councils/AberdeenCityCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/AberdeenCityCouncil.py @@ -5,6 +5,7 @@ from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass + # import the wonderful Beautiful Soup and the URL grabber class CouncilClass(AbstractGetBinDataClass): """ From 157c2ee7883b3a8f959eccac52421f196a7f7ea4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 11 Oct 2025 01:59:12 +0000 Subject: [PATCH 181/425] =?UTF-8?q?bump:=20version=200.155.0=20=E2=86=92?= =?UTF-8?q?=200.156.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2392 +++++++++++++++++ custom_components/uk_bin_collection/const.py | 2 +- .../uk_bin_collection/manifest.json | 4 +- pyproject.toml | 2 +- 4 files changed, 2396 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d1817f0f5..1872d621c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,2396 @@ ======= +## 0.156.0 (2025-10-11) + +### Feat + +- Create tag-on-merge.yml +- Update bump.yml +- fix bump.yml +- Update TorbayCouncil.py +- Update bump.yml +- fix release pipeline bump.yml +- fix Torbay + +### Fix + +- Update AberdeenCityCouncil.py +- Update TorbayCouncil.py +- Update TorbayCouncil.py +- Update TorbayCouncil.py +- Update URL for NewForestCouncil +- New URL and page for wheelie bins +- improve Mid Suffolk District Council holiday handling with dynamic bank holiday detection +- Oxford now rejects the "Requests" default user agent +- #1557 - Adding East Dunbartonshire +- #1557 - Adding East Dunbartonshire +- #1569 - Somerset Council +- #1569 - Somerset Council +- #1559 - Newport City Council +- #1559 - Newport City Council +- #1574 - Test Valley Borough Council +- #1574 - Test Valley Borough Council +- #1566 South Gloucestershire Council + +## 0.154.0 (2025-09-21) + +### Feat + +- handle changes to northumberland council website +- modify input for NorthumberlandCouncil to accept uprn instead of house number, and use new page structure + +### Fix + +- the cookie banner is not optional +- #1570 - Slough Borough Council +- #1570 - Slough Borough Council +- #1520 - Erewash Borough Council +- #1520 - Erewash Borough Council +- #1554 - Folkestone and Hythe District Council +- #1554 - Folkestone and Hythe District Council +- #1604 - West Berkshire Council +- #1604 - West Berkshire Council +- #1606 - Brighton and Hove City Council +- #1606 - Brighton and Hove City Council +- #1565 - BCP Council +- #1565 - BCP Council +- #1571 - Castle Point District Council +- #1571 - Castle Point District Council +- #1584 - NorthHertfordshireDistrictCouncil +- #1584 - NorthHertfordshireDistrictCouncil +- #1599 +- #1599 - Basingstoke Council +- #1587 +- #1587 - Hartlepool Borough Council +- #1588 +- #1588 Glasgow City Council +- #1591 +- #1591 Rushmoor Council + +## 0.153.0 (2025-09-02) + +### Feat + +- Change buckinghamshire council to get data from endpoint + +### Fix + +- 1573 Update Bolton council URL +- East Herts Council +- #1575 +- Runnymede Borough Council +- #1513 +- Wiltshire Council +- #1533 +- Staffordshire Moorlands District Council +- #1535 +- Ipswich Borough Council +- #1548 +- North East Lincs +- Hinckley and Bosworth Borough Council +- Nuneaton Bedworth Borough Council +- #1514 +- Lichfield District Council +- 1549 + +## 0.152.11 (2025-08-25) + +### Feat + +- fix releases process + +### Fix + +- date extraction in RochfordCouncil data parsing +- parsing error in BH selenium +- **hacs**: respect the headless option + +### Refactor + +- **hacs**: improve build_ukbcd_args with formatter functions + +## 0.152.10 (2025-08-04) + +### Fix + +- Gateshead and East Lothian +- Enfield and Broxbourne +- East Herts +- FermanaghOmaghDistrictCouncil +- Enfield and Broxbourne +- East Herts + +## 0.152.9 (2025-08-03) + +### Fix + +- Cotswald and coventry +- Fixing multiple broken councils +- multiple broken councils + +## 0.152.8 (2025-07-26) + +### Fix + +- Add headers to request for Swindon Borough Council +- Add headers to requests for Royal Borough of Greenwich Fixes #1496 by ensuring that the requests are not rejected due to lack of headers. +- **MidlothianCouncil**: add request headers to resolve 403 Forbidden + +## 0.152.7 (2025-07-01) + +### Fix + +- maidstone selenium fix + +## 0.152.6 (2025-06-18) + +### Fix + +- removed In Progress from date +- removed a degub print statement +- **RugbyBoroughCouncil**: Amended parsed date from full to abbreviated month date, may worked but jun and jul did not +- **RugbyBoroughCouncil**: Amended parsed date +- Reworked Cumberland Council to cater for postcode addition +- **OxfordCityCouncil**: Fixed Oxford City Council parsing dues to changes in output from the website + +## 0.152.5 (2025-06-07) + +### Fix + +- South Ribble and version pinning issues for input.json + +## 0.152.4 (2025-06-07) + +### Fix + +- **SouthRibble**: Corrected Date formatting issue +- **SouthRibble**: Resolved South Ribble without selenium + +## 0.152.3 (2025-06-04) + +### Fix + +- NorthHertfordshire selenium script +- Adur council +- Eastleigh date fix +- removed duplicates in BradfordMDC + +## 0.152.2 (2025-06-04) + +### Fix + +- Update Makefile +- Update CheshireEastCouncil.py +- Github action to handle branch name with parentheses + +## 0.152.1 (2025-05-15) + +### Fix + +- Update to fix North Somerset +- Glasgow SSL bypass +- more robust Northumberland +- updated Eastleigh input.json +- Eastleigh cloudflare fix +- converted collection datetimes into dates for BH parsing. +- Eastleigh cloudflare fix +- Eastleigh cloudflare fix +- added check_uprn to simplified councils +- simplified Swindon +- simplified East Devon +- simplified Dover +- Simplified Dartford +- simplified Cheshire East +- simplified Charnwood input.json +- improved Charnwood +- Adur Worthing fix +- Chorley simplification +- Bexley simplification +- added URL to Torbay script +- Guildford fixes +- reworked Maidstone +- maidstone input.json +- Croydon selenium version +- Stoke date-time fix + +## 0.152.0 (2025-05-02) + +### Feat + +- Added Fermanagh Omagh +- Added Twekesbury +- added Slough council +- Added Argus Council +- added Angus to input.json + +### Fix + +- Chichester now only requires postcode and house number +- Broadland now only requires postcode and house number +- Barking now only requires postcode and house number +- Brighton now only requires postcode and house number +- ensured all bins for this council +- added skip_get_url to hyndburn + +## 0.151.0 (2025-04-27) + +### Feat + +- version bump + +### Fix + +- more robust brent date handling +- input.json requires web_driver +- Rugby fix + +## 0.150.0 (2025-04-27) + +### Feat + +- added melton +- added pembrokeshire + +### Fix + +- added melton +- processed all bins for Moray + +## 0.148.6 (2025-04-27) + +## 0.148.5 (2025-04-27) + +### Fix + +- output check +- parsed bin info +- selenium navigation +- input.json changes + +## 0.148.4 (2025-04-27) + +### Fix + +- used canonical 'nice name' + +## 0.148.3 (2025-04-25) + +### Fix + +- working hyndburn +- hyndburn input.json + +## 0.148.2 (2025-04-24) + +### Fix + +- Update docker-compose.yml +- updated input.json +- cloudflare fix - switch to selenium method +- simplified blackburn + +## 0.148.1 (2025-04-22) + +### Fix + +- added bank holiday offsets. +- added bank holiday offsets. + +## 0.148.0 (2025-04-19) + +### Feat + +- adding Wrexham and #1046 Horsham councils + +### Fix + +- Argyll and Bute council #1053 + +## 0.147.2 (2025-04-18) + +### Fix + +- wait for element to be clickable + +## 0.147.1 (2025-04-18) + +### Fix + +- #1351 - moved geopandas to petry dev + +## 0.147.0 (2025-04-18) + +### Feat + +- add council tests results map + +## 0.146.2 (2025-04-18) + +### Fix + +- adding map checking and matching + +## 0.146.1 (2025-04-18) + +### Fix + +- more robust bank holiday handling + +## 0.146.0 (2025-04-18) + +### Feat + +- #1342 Adding Includes Trafford, Clackmannanshire, Havant, North Warwickshire, Newry Mourne and Down, East Dunbartonshire, Pendle, Torfaen, East Hampshire, Ribble Valley, Brentwood, Isle of Wight, Westmorland and Furness, Derry and Strabane, and Norwich. Google Cal support for PDF councils via ICS file + +### Fix + +- Black reformatting + +## 0.145.0 (2025-04-18) + +### Feat + +- Adding PDF councils + +## 0.144.4 (2025-04-18) + +### Fix + +- Bristol #1275 + +## 0.144.3 (2025-04-17) + +### Fix + +- better address for input.json +- bank holiday overrides +- more robust address searching +- simple parsing done +- Selenium navigation + +## 0.144.2 (2025-04-17) + +### Fix + +- knowsley +- knowsley +- knowsley +- knowsley +- KnowsleyMBCouncil.py +- #1220 adding Mid Ulster District Council + +## 0.144.1 (2025-04-17) + +### Fix + +- fix Sandwell garden waste collection date + +## 0.144.0 (2025-04-17) + +### Feat + +- added great yarmouth + +## 0.143.6 (2025-04-17) + +### Fix + +- Renfrewshire Council + +## 0.143.5 (2025-04-17) + +### Fix + +- Google Cal + +## 0.143.4 (2025-04-17) + +### Fix + +- Google Cal + +## 0.143.3 (2025-04-15) + +### Fix + +- #1301 Fix Leeds Council + +## 0.143.2 (2025-04-15) + +### Fix + +- #1301 Fix Leeds Council + +## 0.143.1 (2025-04-15) + +### Fix + +- Set the bin_type when different day + +## 0.143.0 (2025-04-13) + +### Fix + +- corrected url in input.json +- fixed input.json +- parsed Barking Dagenham collection information +- selenium navigation Barking + +## 0.142.0 (2025-04-13) + +### Feat + +- Added Stirling Council + +### Fix + +- typo in input.json + +## 0.141.4 (2025-04-13) + +### Fix + +- #1304 - sesnors goes to unknown if the data is blank from councils who are less reliable + +## 0.141.3 (2025-04-13) + +### Fix + +- Newham council + +## 0.141.2 (2025-04-13) + +### Fix + +- Newham council +- Newham council + +## 0.141.1 (2025-04-12) + +### Fix + +- missing finally block on selenium tests + +## 0.141.0 (2025-04-12) + +### Feat + +- #1185 Adding PeterboroughCity Council + +## 0.140.0 (2025-04-11) + +### Feat + +- Added Broadland District Council + +### Fix + +- cleanup of council file +- added Broadland to input.json + +## 0.139.0 (2025-04-07) + +### Feat + +- adding #1037 +- adding #1032 North Devon Count Council + +### Fix + +- #1296 Forest of dean +- 939 adding South Holland District Council - Lincolnshire UK + +## 0.138.1 (2025-04-05) + +### Fix + +- Walhtam forest council - revert previous changes + +## 0.138.0 (2025-04-05) + +### Feat + +- Adding Hastings Borough Council +- Adding Fylde Council + +### Fix + +- #1249 +- #1039 +fix: #1181 +fix: #1266 +fix: #1274 +- Gloucester City Council +- #1282 +- Mid Devon Council +- #1277 +fix: #1287 +- West Oxfordshire Council +- #1290 + +## 0.137.0 (2025-04-05) + +### Feat + +- #816 adding trafford council + +## 0.136.0 (2025-03-24) + +### Feat + +- Adding Southampton City Council +- Adding Cambridge City Council +- Adding Spelthorne Borough Council + +### Fix + +- #1057 +- #1264 +- #1270 +- Bexley Council +- #1256 +- HinckleyandBosworthBoroughCouncil +- #1207 +- Hackney Council +- #1230 +- Castlepoint District Council +- #1252 +- Canterbury City Council +- #1254 + +## 0.135.4 (2025-03-24) + +### Fix + +- parse scheduleCodeWorkflowIDs instead of scheduleCodeWorkflowID for Hackney Council + +## 0.135.3 (2025-02-23) + +## 0.135.2 (2025-02-19) + +### Fix + +- North Yorkshire - multiple bins on a day + +## 0.135.1 (2025-02-18) + +### Fix + +- devcontainer + +## 0.135.0 (2025-02-17) + +### Fix + +- #833 adding Middlesbrough and check script for Selenium +- Cotswold District Council +- #1238 +- Leeds City Council +- #1222 + +## 0.134.3 (2025-02-15) + +### Fix + +- Update input.json +- 1235 Councils missing Selenium in input.json + +## 0.134.2 (2025-02-15) + +### Fix + +- 1232 East herts missing Selenium url in input.json +- Derbyshire Dales District Council +- Conwy County Borough +- Sunderland City Council +- #1219 +- Tendring District Council +- #1221 + +## 0.134.1 (2025-02-11) + +### Fix + +- Cheltenham Borough Council +- #1061 + +## 0.134.0 (2025-02-07) + +### Feat + +- Ipswich Borough Council - trying different address +- Ipswich Borough Council - correcting param name in input.json +- Ipswich Borough Council - added input.json values and refactored code +- Ipswich Borough Council - initial implementation +- Adding Runnymede Borough Council +- Adding Cherwell District Council +- Adding Epsom and Ewell Borough Council +- Adding Redcar and Cleveland Council +- Adding Amber Valley Borough Council +- Adding Bolsover Council + +### Fix + +- #1214 +- #923 +- #895 +- #841 +- #903 +- #990 +- Torridge District Council +- #1204 +- Neath Port Talbot +- #1213 + +## 0.133.0 (2025-02-02) + +### Feat + +- adding manual refresh + +## 0.132.0 (2025-02-02) + +### Feat + +- adding manual refresh + +## 0.131.0 (2025-02-02) + +### Feat + +- adding manual refresh +- adding manual refresh +- adding manual refresh +- adding manual refresh +- adding manual refresh +- adding manual refresh +- adding manual refresh +- adding manual refresh +- adding manual refresh +- adding manual refresh +- adding manual refresh +- adding manual refresh +- adding unit tests for the new manual refresh +- adding manual refresh control + +## 0.130.1 (2025-01-30) + +### Fix + +- slow councils + +## 0.130.0 (2025-01-29) + +### Feat + +- Add Herefordshire Council (closes: #1011) + +### Fix + +- Fix spacing in wiki name + +## 0.129.0 (2025-01-29) + +### Fix + +- input.json +- input.json + +## 0.128.6 (2025-01-29) + +### Fix + +- moving away from broken Allure reporting +- moving away from broken Allure reporting +- moving away from broken Allure reporting +- moving away from broken Allure reporting +- moving away from broken Allure reporting +- moving away from broken Allure reporting +- moving away from broken Allure reporting + +## 0.128.5 (2025-01-29) + +### Feat + +- Adding East Staffordshire Borough Council + +### Fix + +- Update behave_pull_request.yml +- Update behave_pull_request.yml +- Update behave_pull_request.yml +- Update behave_pull_request.yml +- Update behave_pull_request.yml +- Update behave_pull_request.yml +- Update CheshireEastCouncil.py +- Adding East Lothian Council +- #1171 +- #1052 +fix: #1083 + +## 0.128.4 (2025-01-28) + +### Feat + +- Adding Boston Borough Council + +### Fix + +- Update CheshireEastCouncil.py +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_pull_request.yml +- Update behave_pull_request.yml +- Update behave_pull_request.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update CheshireEastCouncil.py +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Leicester City Council +- #1178 +- Cardiff Council +- #1175 +- Newcastle City Council +- #1179 +- #1180 +- Midlothian Council +- #1192 - Adding Next Page support + +## 0.128.3 (2025-01-28) + +### Fix + +- Update CheshireEastCouncil.py +- Update behave_schedule.yml +- Update behave_pull_request.yml + +## 0.128.2 (2025-01-28) + +### Fix + +- Add communal recycling and communal rubbish +- Add garden waste to Merton Council + +## 0.128.1 (2025-01-28) + +### Fix + +- Update AberdeenshireCouncil.py +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update behave_pull_request.yml + +## 0.128.0 (2025-01-28) + +### Feat + +- implement Medway Council (#1021) + +### Fix + +- Forgot to include skip_get_url + +## 0.127.4 (2025-01-25) + +### Fix + +- NewForestCouncil + +## 0.127.3 (2025-01-16) + +### Fix + +- Swale Borough Council +- #1139 +- Vale of White Horse +- #1156 +- South Oxfordshire Council +- #1158 +- Surrey Heath Borough Council +- #1164 +- Carmarthenshire County Council +- #1167 +- Glasgow City Council +- #1166 + +## 0.127.2 (2025-01-13) + +### Fix + +- Update bin type to be the full string + +## 0.127.1 (2025-01-10) + +### Fix + +- Use visibility of list rather than existence +- Update Rushcliffe Borough Council input elements and flow +- Merton Council +- NewarkAndSherwoodDC +- Rushcliffe Borough Council +- Powys Council +- Staffordshire Moorlands District Council +- Stroud District Council +- Vale of Glamorgan Council +- West Oxfordshire District Council + +## 0.127.0 (2025-01-07) + +### Feat + +- Adding Oadby And Wigston Borough Council +- Add Gwynedd Council +- Adding Denbighshire Council +- Adding Dundee City Council +- Adding Brent Council +- Adding West Dunbartonshire Council +- Adding Cumberland Council + +### Fix + +- #929 +- Cornwall Council +- #1137 +- #1125 +- #1106 +- #1108 +- #1109 +- #1134 +- Northumberland Council +- #1082 +- #1110 +- Waltham Forest +- #1126 +- London Borough Sutton +- #1131 +- Kirklees Council +- #1129 - Breaking Change. UPRN required + +## 0.126.2 (2025-01-07) + +### Fix + +- **tests**: updates test case url for coventry city council +- **tests**: removes duplicate key for coventry city council +- updates coventry city council button text + +## 0.126.1 (2025-01-06) + +### Fix + +- behave_testing +- behave_testing + +## 0.126.0 (2025-01-04) + +### Fix + +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml + +## 0.125.2 (2025-01-04) + +### Fix + +- Update ArdsAndNorthDownCouncil.py +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update README.md to have links to Full and Partial Integration Test Reports +- Update behave_pull_request.yml +- Update README.md to have links to Full and Partial Integration Test Reports +- Swale Borough Council +- #1080 +(cherry picked from commit 6f580b39fb68b8079990221e050ae8dd6d2b7285) +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update ArdsAndNorthDownCouncil.py +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update README.md to have links to Full and Partial Integration Test Reports +- Update WestLindseyDistrictCouncil.py +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update behave_schedule.yml + +## 0.125.1 (2025-01-04) + +### Fix + +- correctly handle year increment for January dates + +## 0.125.0 (2025-01-04) + +### Feat + +- Adding Redditch Borough Council +- Adding Blaenau Gwent County Borough Council +- Adding Wandsworth Council + +### Fix + +- #1068 +- #1098 +- Wiltshire Council +- #1094 +- Salford City Council +- #1097 +- #1078 +- Merton Council +- Swale Borough Council +- #1080 +- London Borough Sutton +- #1076 +- Update behave_schedule.yml +- Update bump.yml + +## 0.124.4 (2025-01-04) + +### Fix + +- Update behave_schedule.yml + +## 0.124.3 (2025-01-04) + +### Fix + +- allure reporting +- allure reporting +- allure reporting + +## 0.124.2 (2025-01-03) + +### Fix + +- Update behave.yml + +## 0.124.1 (2025-01-03) + +### Fix + +- avoid crashing on unexpected string value + +## 0.124.0 (2025-01-02) + +### Feat + +- Hart District Council + +## 0.123.2 (2024-12-19) + +### Fix + +- Update behave.yml + +## 0.123.1 (2024-12-18) + +### Feat + +- #1063 - rewrite Kirklees Council parser for new website +- #1067 - Add garden bin collections where available for Norwich City Council +- Adding Wandsworth Council + +### Fix + +- Update AberdeenCityCouncil.py +- Update behave.yml +- #1101 - Fix table parsing for Walsall Council +- Remove invalid escape sequence warnings from West Lindsey District Council +- #1073 - change method of generating bin types to avoid manual mapping for Rugby Borough Council +- add missing backticks to separate colour config and standard usage instructions +- #1078 +(cherry picked from commit 89d93666bb659010d1c130b98c1d81c6ff80cf7c) +- change date format to project default for Merton Council +- correct date logic for Swale Borough Council +- Merton Council +- London Borough Sutton +- #1076 +(cherry picked from commit 1eab20c9a57c9c4438ea343f374202bb2e9b98ca) +- Swale Borough Council +- #1080 +(cherry picked from commit 6f580b39fb68b8079990221e050ae8dd6d2b7285) +- correct date/year logic for West Lindsey District Council +- replace West Lindsey's input with working address +- #1089 - Correct shifted dates in Bromley Borough Council +- remove WDM import +- #1087 - Food waste date incorrect for West Berkshire Council + +## 0.123.0 (2024-12-17) + +## 0.122.0 (2024-12-04) + +### Feat + +- Adding Monmouthshire County Council +- Adding Hinckley and Bosworth Borough Council + +### Fix + +- Glasgow City Council +- Merton Council +- Blaby District Council +- Warwick District Council +- Blackburn Council +- Carmarthenshire County Council +- High Peak Council +- CarmarthenshireCountyCouncil + +## 0.121.1 (2024-12-03) + +### Fix + +- London Borough of Lewisham to have more reliable parsing of dates + +## 0.121.0 (2024-11-24) + +### Feat + +- Royal Borough of Greenwich +- Adding London Borough of Lewisham +- Adding Hackney Council +- Adding Sandwell Borough Council +- Adding Moray Council +- Adding Kings Lynn and West Norfolk Borough Council +- Adding Wyre Forest District Council +- Adding Folkstone and Hythe District Council +- Adding Cheltenham Borough Council +- Adding Thurrock Council + +### Fix + +- West Northamptonshire Council +- East Ayrshire Council +- Cotswold District Council + +## 0.120.0 (2024-11-20) + +### Feat + +- Adding Hartlepool Borough Council +- Adding Newcastle Under Lyme Council +- Adding London Borough of Havering +- Add Garden collection to EnvironmentFirst +- Adding Cumberland Council (Allerdale District) +- Adding North Hertfordshire District Council + +### Fix + +- #844 +- #778 +- #769 +- #1025 +- Mid Siffolk and Babergh Garden Collection Day +- #1026 +This will require the use of a DAY to be added to the UPRN field +- #1029 +- #1028 + +## 0.119.0 (2024-11-20) + +### Feat + +- Adding Braintree District Council +- Adding Burnley Borough Council +- Adding Exeter City Council +- Adding Edinburgh City Council + +### Fix + +- #699 +- #1015 +- #1017 +- #894 + +## 0.118.0 (2024-11-15) + +### Feat + +- Adding Aberdeen City Council +- Adding Wolverhampton City Council +- Adding Stevenage Borough Council +- Adding Thanet District Council +- Adding Copeland Borough Council +- Adding South Hams District Council + +### Fix + +- #1019 +- #966 +- #989 +- #1004 +- #1006 +- #1008 +- Rother District Council + +## 0.117.0 (2024-11-13) + +### Feat + +- Adding South Staffordshire District Council fix: #885 +- Adding Rother District Council + +### Fix + +- #1009 + +## 0.116.0 (2024-11-12) + +### Feat + +- Adding Ashfield District Council +- Adding Gravesham Borough Council +- Adding Argyll and Bute Council + +### Fix + +- CrawleyBoroughCouncil +- #1005 +- Adding Garden collection to Babergh and MidSuffolk Council +- #995 +- #579 +- #991 +- #692 +- CheshireWestAndChesterCouncil +- #993 +- Milton Keynes +- #702 +- Adding Babergh and Mid Suffolk District Councils +- #868 +fix: #919 +- Adding Derby City Council +- #987 + +## 0.115.0 (2024-11-11) + +### Feat + +- Adding Warrington Borough Council +- Adding Antrim And Newtonabbey Council +- Adding Hertsmere Borough Council +- Adding West Lancashire Borough Council +- Broxbourne Council + +### Fix + +- #695 +- #969 +- #776 +- #980 +- #982 +- Bradford MDC +- #984 + +## 0.114.6 (2024-11-09) + +### Fix + +- NBBC Date Fix + +## 0.114.5 (2024-11-08) + +### Fix + +- migration logging and debugging + +## 0.114.4 (2024-11-08) + +### Fix + +- migration not working +- migration not working + +## 0.114.3 (2024-11-08) + +## 0.114.2 (2024-11-08) + +## 0.114.1 (2024-11-08) + +### Fix + +- Update manifest.json + +## 0.114.0 (2024-11-07) + +### Feat + +- Nuneaton and Bedworth Borough Council + +## 0.113.0 (2024-11-07) + +## 0.112.1 (2024-11-07) + +## 0.112.0 (2024-11-06) + +### Feat + +- adding calendar for Bins in Custom Component + +### Fix + +- fix manifest in custom component +- #975 adding routine to handle migration error +- #975 adding routine to handle migration error +- #767 BREAKING CHANGE - READD your sensors / config + +## 0.111.0 (2024-11-06) + +### Fix + +- Add London Borough of Sutton +- #944 +- Add Mid Devon Council +- #945 +- Adding Oxford City Council +- #962 +- Tunbridge Wells / Lincoln +- #963 +- Glasgow City Council + +## 0.110.0 (2024-11-04) + +### Fix + +- Adding Blaby District Council +- #904 +- Adding Sefton Council +- #770 +- Adding Bromsgrove District Council +- #893 +- East Lindsey District Council +- #957 +- Adding Carmarthenshire County Council +- #892 +fix: #710 +- Adding East Ayrshire Council +- #955 + +## 0.109.2 (2024-11-03) + +### Fix + +- CC testing and add Chesterfield + +## 0.109.1 (2024-11-03) + +### Fix + +- CC testing and add Chesterfield +- CC testing and add Chesterfield + +## 0.109.0 (2024-11-02) + +### Feat + +- Adding Cotswold District Council +- Adding Breckland Council + +### Fix + +- St Helens Borough Council +- #753 +- NewarkAndSherwoodDC +- #941 +- #658 +- #656 + +## 0.108.2 (2024-11-01) + +### Fix + +- pytest-homeassistant-custom-component + +## 0.108.1 (2024-11-01) + +### Fix + +- Pydandic version +- Pydandic version + +## 0.108.0 (2024-11-01) + +### Feat + +- pytest fixes +- pytest fixes +- pytest fixes +- pytest fixes +- pytest fixes +- pytest fixes + +## 0.107.0 (2024-10-31) + +### Feat + +- Adding Powys Council +- Adding Worcester City Council +- Adding Ards and North Down Council +- Adding East Herts Council +- Adding Ashford Borough Council + +### Fix + +- WestOxfordshireDistrictCouncil +- South Norfolk Council +- ForestOfDeanDistrictCouncil +- Croydon Council +- South Kesteven District Council +- #647 +- #630 +- #623 +- #586 +- #578 +- #389 + +## 0.106.0 (2024-10-28) + +### Feat + +- Adding Stockton On Tees Council +- Adding Fife Council +- Adding Flintshire County Council + +### Fix + +- #930 +- #933 +- #750 + +## 0.105.1 (2024-10-24) + +### Fix + +- Refactor Midlothian Council scraper to use house number and postcode +- West Berkshire Council +- Southwark Council + +## 0.105.0 (2024-10-21) + +### Feat + +- Adding Teignbridge Council +- Adding Harborough District Council +- Adding Watford Borough Council +- Adding Coventry City Council +- pytest fixes +- pytest fixes +- pytest fixes +- pytest fixes +- pytest fixes +- Adding Powys Council +- Adding Worcester City Council +- Adding Ards and North Down Council +- Adding East Herts Council +- Adding Ashford Borough Council +- Adding Stockton On Tees Council +- Adding Fife Council +- Adding Flintshire County Council +- Adding Teignbridge Council +- Adding Harborough District Council +- Adding Watford Borough Council +- Adding Coventry City Council +- Python 3.12 only and CustomComp. Unit testing + +### Fix + +- #580 +- #888 +- #902 +- #607 +- CC testing and add Chesterfield +- CC testing and add Chesterfield +- CC testing and add Chesterfield +- pytest-homeassistant-custom-component +- Pydandic version +- Pydandic version +- WestOxfordshireDistrictCouncil +- South Norfolk Council +- ForestOfDeanDistrictCouncil +- Croydon Council +- South Kesteven District Council +- #647 +- #630 +- #623 +- #586 +- #578 +- #389 +- #930 +- #933 +- #750 +- Refactor Midlothian Council scraper to use house number and postcode +- West Berkshire Council +- Southwark Council +- #580 +- #888 +- #902 +- #607 + +## 0.104.0 (2024-10-20) + +### Feat + +- Adding Luton Borough Council +- Adding West Oxfordshire District Council +- Adding Aberdeenshire Council +- Adding Canterbury City Council +- Adding Swindon Borough Council + +### Fix + +- #697 +- #694 +- #659 +- #590 +- #900 + +## 0.103.0 (2024-10-20) + +### Feat + +- Adding RAW JSON Sensor + +### Fix + +- Black formatting +- Black formatting + +## 0.102.0 (2024-10-20) + +### Feat + +- Moving from Attributes to Sensors +- Moving from Attributes to Sensors + +## 0.101.0 (2024-10-20) + +### Feat + +- Add Midlothgian Council + +## 0.100.0 (2024-10-18) + +### Feat + +- Adding Dudley Council +- Adding South Ribble Council +- Plymouth Council +- Adding Norwich City Council + +### Fix + +- #744 +- #671 +- #566 +- #749 + +## 0.99.1 (2024-10-16) + +### Fix + +- #792 adding web_driver option to Wokingham Council + +## 0.99.0 (2024-10-16) + +### Feat + +- Adding Lincoln Council +- Adding Tunbridge Wells Council +- Adding Perth and Kinross Council + +### Fix + +- Update wiki +- #748 +- #598 +- #572 + +## 0.98.5 (2024-10-15) + +### Fix + +- Swale Borough Council +- HaltonBoroughCouncil +- Barnet Council +- WestBerkshireCouncil + +## 0.98.4 (2024-10-14) + +### Fix + +- West Suffolk Council +- Vale of White Horse Council +- Uttlesford District Council +- Neath Port Talbot Council +- Merton Council +- Manchester City Council +- Glasgow City Council +- BradfordMDC + +## 0.98.3 (2024-10-13) + +### Fix + +- EastRiding + +## 0.98.2 (2024-10-13) + +### Fix + +- MoleValley + +## 0.98.1 (2024-10-13) + +### Fix + +- Barnet and Bexley + +## 0.98.0 (2024-10-13) + +### Feat + +- Adding Wirral Council +- Adding Lichfield District Council +- Adding West Morland And Furness +- Adding Walsall Council +- Adding Armagh, Banbridge and Craigavon Council + +### Fix + +- #602 +- #830 +- #870 +- #873 +- #877 + +## 0.97.1 (2024-10-10) + +### Fix + +- NottinghamCityCouncil +- #875 + +## 0.97.0 (2024-10-10) + +### Feat + +- Adding Falkirk Council + +### Fix + +- #761 + +## 0.96.0 (2024-10-10) + +### Feat + +- Adding London Borough Harrow +- Adding North Ayrshire Council +- Adding Highland Council +- Add Elmbridge Borough Council +- Adding Southwark Council +- South Derbyshire District Council + +### Fix + +- #871 +- #869 +- #780 +- #845 +fix: #754 +- #835 +- #842 + +## 0.95.0 (2024-10-09) + +### Feat + +- Adding London Borough of Ealing + +## 0.94.0 (2024-10-09) + +### Feat + +- Adding London Borough of Lambeth +- Adding Dacorum Borough Council + +### Fix + +- Dacorum Borough Council +- East Devon DC + +## 0.93.0 (2024-10-08) + +### Feat + +- Update CheshireEastCouncil.py + +## 0.92.0 (2024-10-08) + +### Feat + +- Update CheshireEastCouncil.py +- Update README.md +- Adding Wokingham Borough Council +- Adding Winchester City Council +- Adding Basildon Council +- Adding Colchester City Council + +### Fix + +- RochfordCouncil +- Neath Port Talbot Council +- Buckinghamshire Council +- #639 +fix: #812 + +## 0.91.2 (2024-10-05) + +### Fix + +- Windsor and Maidenhead Council + +## 0.91.1 (2024-10-04) + +## 0.91.0 (2024-10-03) + +## 0.90.0 (2024-10-03) + +### Feat + +- Adding East Renfrewshire Council + +### Fix + +- Update DorsetCouncil.py +- #829 +- Update GatesheadCouncil.py +- #822 + +## 0.89.1 (2024-10-02) + +### Fix + +- High Peak have changed their cookie dialog Seems to be safe to ignore it now. + +## 0.89.0 (2024-09-27) + +### Feat + +- Update CheshireEastCouncil.py +- Update README.md + +### Fix + +- release to be non pre release + +## 0.88.0 (2024-09-16) + +### Feat + +- Add Ealing Council + +### Fix + +- Update README.md + +## 0.87.0 (2024-09-10) + +### Feat + +- Add IslingtonCouncil + +### Fix + +- #565 Gloucester city council driver + +## 0.86.1 (2024-09-09) + +### Fix + +- #773 Wakefield + +## 0.86.0 (2024-09-06) + +### Feat + +- added Rotherham Council + +## 0.85.7 (2024-09-05) + +### Fix + +- more unit tests +- more unit tests +- Chorley + +## 0.85.6 (2024-09-03) + +### Fix + +- #795 and add reconfigure to custom comp. + +## 0.85.5 (2024-09-03) + +## 0.85.4 (2024-09-03) + +### Fix + +- #795 and add reconfigure to custom comp. +- #795 Unit Test Coverage + +## 0.85.3 (2024-09-02) + +### Fix + +- #795 unit test coverage + +## 0.85.2 (2024-09-02) + +### Fix + +- 791 Glasgow URL change + +## 0.85.1 (2024-09-02) + +### Fix + +- 779 Add correct async wait to Home Assistant + +## 0.85.0 (2024-08-27) + +### Feat + +- support for enfield council + +## 0.84.2 (2024-08-27) + +### Fix + +- Re-work North Tyneside Council module for 2024 - some addresses do not have a garden collection +- Re-work North Tyneside Council module for 2024 + +## 0.84.1 (2024-08-08) + +### Fix + +- #771 Bolton bullet points on dates is now fixed + +## 0.84.0 (2024-07-31) + +## 0.83.0 (2024-07-07) + +### Feat + +- add has_numbers() function + +### Fix + +- update Gedling Borough Council parser to use alternative name key +- change Gedling to use new JSON data +- update instructions for Gedling +- update input.json to use UPRN parameter +- change DorsetCouncil.py to use API links provided in #756 +- explicit import of logging.config to stop error in Python 3.11 + +## 0.82.0 (2024-06-13) + +### Feat + +- adding dev container updates +- adding dev container updates +- refactoring main files +- adding ability to set local mode in HA custom comp. if users dont have a Selenium Server + +### Fix + +- MidSussex + +## 0.81.0 (2024-06-05) + +### Feat + +- Adding Wychavon District Council + +### Fix + +- IntTestWarnings +- IntTestWarnings + +## 0.80.0 (2024-06-02) + +### Feat + +- Adding Uttlesford District Council +- Adding Stafford Boro Council +- Adding Swansea Council +- Adding New Forest +- Adding Three Rivers +- Adding Three Rivers + +### Fix + +- ThreeRivers +- #425 Entities are not updated +- sessions to avoid deprecation +- Update docker-image.yml +- Update docker-image.yml + +## 0.79.1 (2024-05-29) + +### Fix + +- Change CSS class in search for collection types + +## 0.79.0 (2024-05-28) + +### Feat + +- Adding Dartford +- Adding South Kesteven District Council +- Adding ChichesterCouncil +- adding HounslowCouncil +- adding HounslowCouncil +- adding HounslowCouncil +- Epping Fix +- Adding Epping Forest District Council +- Update input.json +- Epping Forest District Council +- Adding Stroud District Council +- Add support for Tendring District Council +- #269 Adding Waltham Forest +- #269 Adding Waltham Forest +- Adding council creation script + +### Fix + +- Update Mole Valley URL + +## 0.78.0 (2024-05-26) + +### Feat + +- Add support for Fareham Borough Council + +## 0.77.0 (2024-05-26) + +### Feat + +- Add support for Bracknell Forest Council + +## 0.76.1 (2024-05-24) + +### Fix + +- Handle Barnet council cookies message + +## 0.76.0 (2024-05-24) + +### Feat + +- add bin colour support WestSuffolkCouncil style: black format WestSuffolkCouncil +- add bin colour support WestSuffolkCouncil style: black format WestSuffolkCouncil + +## 0.75.0 (2024-05-19) + +### Feat + +- #725 Add names to selenium test videos using "se:name" option in create webdriver function + +## 0.74.1 (2024-05-18) + +### Fix + +- #693 Cheshire West & Chester Council Sensor Bug +- #693 Cheshire West & Chester Council Sensor Bug + +## 0.74.0 (2024-05-17) + +### Feat + +- #722 Support Python 3.12 +- #722 Support Python 3.12 +- #722 Support Python 3.12 + +## 0.73.0 (2024-05-17) + +### Feat + +- #708 Adding HA to the dev container for debugging + +## 0.72.0 (2024-05-17) + +### Feat + +- #708 Adding HA to the dev container for debugging +- #708 Adding HA to the dev container for debugging +- #708 Adding HA to the dev container for debugging +- #708 Adding HA to the dev container for debugging +- #708 Adding HA to the dev container for debugging +- #708 Adding HA to the dev container for debugging +- #708 Adding HA to the dev container for debugging +- #708 Adding HA to the dev container for debugging + +## 0.71.0 (2024-05-17) + +### Feat + +- Update for West Suffolk Councils new website + +## 0.70.0 (2024-05-17) + +### Feat + +- #708 Dev Container +- Dev Container +- #708 Dev Container +- #708 Dev Container +- #708 Dev Container +- #708 Dev Container +- #708 Dev Container +- #708 Dev Container +- #708 Dev Container +- #708 Dev Container +- #708 Dev Container +- #708 Dev Container +- #708 simplifying Selenium integration tests +- #708 simplifying Selenium integration tests +- #708 Test GH action seenium +- #708 Test GH action seenium +- #708 Test GH action seenium +- #708 Test GH action seenium +- #708 Test GH action seenium +- #708 Test GH action seenium +- #708 Test GH action seenium +- #708 Test GH action seenium +- #708 Test GH action seenium +- #708 Test GH action seenium +- #708 Dev Container testing +- #708 - dev container changes +- #706 Adding Dev Container +- #706 Adding initial Dev Container + +## 0.69.7 (2024-05-17) + +### Fix + +- #713 BarnsleyMBCouncil.py + +## 0.69.6 (2024-05-16) + +### Fix + +- #709 Update DoverDistrictCouncil.py + +## 0.69.5 (2024-05-14) + +### Fix + +- #696 Small issue and Black formatting +- #696 Small issue and Black formatting +- #696 Small issue and Black formatting +- #696 Small issue and Black formatting +- #696 Small issue and Black formatting +- #696 Small issue and Black formatting +- #696 Small issue and Black formatting +- #696 Small issue and Black formatting +- #696 test coverage back to 100% + +## 0.69.4 (2024-05-09) + +### Fix + +- pass in required parameter into `create_webdriver` +- test runners for `MiltonKeynesCityCouncil` and `NorthEastLincs`. + +## 0.69.3 (2024-05-09) + +### Fix + +- fix AttributeError when no garden waste collection is available for properties using Huntingdon District Council +- add support for parsing "Today" / "Tomorrow" as date text for `BarnsleyMBCouncil` +- add support for parsing "Tomorrow" as date text for `LiverpoolCityCouncil` + +## 0.69.1 (2024-05-01) + +### Fix + +- Handling the "Website cookies enhance your user experience." button +- Handling the "Website cookies enhance your user experience." button + +## 0.69.0 (2024-04-28) + +### Feat + +- Adding Renfrewshire Council +- Adding Renfrewshire Council + +## 0.68.2 (2024-04-28) + +### Fix + +- Remove 'import Dumper' + +## 0.68.1 (2024-04-27) + +### Fix + +- input.json Bradford missing comma + +## 0.68.0 (2024-04-27) + +### Feat + +- Add support for West Berkshire Council +- add support for Knowsley Metropolitan Borough Council +- add support for Cheshire West and Chester Council +- add support for Cheshire West and Chester Council + +## 0.66.2 (2024-04-18) + +### Fix + +- Update HaringeyCouncil.py issue #670 + +## 0.66.1 (2024-04-15) + +### Fix + +- parse datetimes correctly and round to midnight + +## 0.66.0 (2024-04-15) + +## 0.65.2 (2024-04-15) + +### Fix + +- change address selection to fix errors selecting the user's PAON + +## 0.65.1 (2024-04-15) + +### Fix + +- add check for parsed string length to stop datetime parsing error + +## 0.65.0 (2024-04-13) + +### Feat + +- add Arun council +- add support for Sunderland City Council +- add support for Sunderland City Council + +## 0.64.3 (2024-03-25) + +### Fix + +- sort data and correct dictionary name (#609) + +## 0.64.2 (2024-03-24) + +## 0.64.1 (2024-03-24) + +### Fix + +- fix Kirklees address search (switch to house & postcode) +- fixes json + +## 0.64.0 (2024-03-23) + +### Feat + +- add Kirklees council + +### Fix + +- fixes json + +## 0.63.0 (2024-03-23) + +### Feat + +- Add Solihull Council (#513) +- Add Adur and Worthing Councils (#544) +- Add Dover District Council (#614) +- Add Rochford Council (#620) +- Add Tandridge District Council (#621) +- Add West Northamptonshire Council (#567) +- Add Hull City Council (#622) +- Add Wyre Council (#625) +- Add Telford and Wrekin Co-operative Council (#632) +- Add Mansfield District Council (#560) +- Add Bedford Borough Council (#552) + +### Fix + +- spacing on input.json +- realign input.json +- capitalize bin type text +- formatting on input.json +- incorrect collections +- update testing URL for Merton +- attempt to resolve invisible banner hiding postcode box +- resolve JSON schema exception for date formatting +- resolve JSON schema exception for date formatting +- accept cookies banner + +## 0.62.0 (2024-03-03) + +### Fix + +- Added missing .feature file entry to the test config for NewhamCouncil + +## 0.61.1 (2024-02-16) + +### Fix + +- code optimisations +- Fix date parsing in WestLindseyDistrictCouncil.py + +## 0.61.0 (2024-02-11) + +### Feat + +- Add Mole Valley District Council + +## 0.60.1 (2024-02-03) + +### Fix + +- Update input.json Closes #599 + +## 0.60.0 (2024-01-28) + +### Feat + +- Add Scraper for St Albans City and District Council + +## 0.59.1 (2024-01-25) + +### Fix + +- add wiki note for castlepoint +- update test data for castlepoint +- remove single line causing issues + +## 0.59.0 (2024-01-20) + +### Feat + +- Add NorthYorkshire to test feature file +- Add north yorkshire to test input +- Add Support for north yorkshire council + +### Fix + +- remove unused code + +## 0.58.8 (2024-01-19) + +### Fix + +- barnet no overrides + +## 0.58.7 (2024-01-18) + +### Fix + +- accidentally returned strings when needed date objects, refactor to handle this +- checking for future/past dates + +## 0.58.6 (2024-01-18) + +### Fix + +- correct date handling for North West Leicestershire + +## 0.58.5 (2024-01-15) + +### Fix + +- Don't call driver.quit where already handled by finally block + +## 0.58.4 (2024-01-15) + +### Fix + +- remove extra driver.quit to prevent errors + +## 0.58.3 (2024-01-15) + +### Feat + +- Added support for Newham Council's bin collections + +### Fix + +- Add a default value for user_agent to fix all councils using selenium and not specifying agent + +## 0.58.2 (2024-01-11) + +### Fix + +- use static values for bin types + +## 0.58.1 (2024-01-10) + +### Fix + +- Eastleigh Borough Council doesnt cope with "You haven't yet signed up for ..." +- Eastleigh Borough Council doesnt cope when Garden Waste service hasn't been signed up for, which gets the value "You haven't yet signed up for our garden waste collections. Find out more about our\xa0garden waste collection service" which results in ValueError: time data + +## 0.58.0 (2024-01-10) + +### Feat + +- Add Test Valley Borough Council + +## 0.57.0 (2024-01-09) + +### Feat + +- Add support for Chorley Council + +## 0.56.13 (2024-01-09) + +### Fix + +- update logic to account for council website change + +## 0.56.12 (2024-01-09) + +### Fix + +- duplicate driver.quit() calls causes error + +## 0.56.11 (2024-01-08) + +### Fix + +- Headless now working on custom comp Update sensor.py + +## 0.56.10 (2024-01-08) + +### Fix + +- headless mode in custom component + +## 0.56.9 (2024-01-08) + +### Fix + +- headless mode + +## 0.56.8 (2024-01-08) + +### Fix + +- headless in custom comp + +## 0.56.7 (2024-01-08) + +### Fix + +- headless options + +## 0.56.6 (2024-01-07) + +### Fix + +- modified Kingston-upon-Thames driver for greater reliability. + +## 0.56.5 (2024-01-07) + +### Fix + +- Update KingstonUponThamesCouncil.py + +## 0.56.4 (2024-01-07) + +### Fix + +- Update KingstonUponThamesCouncil.py + +## 0.56.3 (2024-01-07) + +### Fix + +- headless options +- #542 - Selenium Grid Sessions must be terminated cleanly +- #542 - Selenium Grid Sessions must be terminated cleanly + +## 0.56.2 (2024-01-07) + +### Fix + +- Update strings.json +- Update en.json +- Update config_flow.py + +## 0.56.1 (2024-01-07) + +### Fix + +- Update common.py + +## 0.56.0 (2024-01-07) + +### Feat + +- Update strings.json +- Update en.json +- Update config_flow.py +- adding headless control +- adding headless control +- adding headless control + +## 0.55.3 (2024-01-05) + +### Fix + +- Update lint.yml + +## 0.55.2 (2024-01-05) + +### Fix + +- Chelmsford + +## 0.55.1 (2024-01-05) + +### Fix + +- Update ChelmsfordCityCouncil.py +- Update ChelmsfordCityCouncil.py +- Update ChelmsfordCityCouncil.py + ## 0.155.0 (2025-10-11) ### Feat diff --git a/custom_components/uk_bin_collection/const.py b/custom_components/uk_bin_collection/const.py index ab89832395..9d030ffd17 100644 --- a/custom_components/uk_bin_collection/const.py +++ b/custom_components/uk_bin_collection/const.py @@ -4,7 +4,7 @@ from homeassistant.const import Platform -INPUT_JSON_URL = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.155.0/uk_bin_collection/tests/input.json" +INPUT_JSON_URL = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.156.0/uk_bin_collection/tests/input.json" DEFAULT_NAME = "UK Bin Collection Data" diff --git a/custom_components/uk_bin_collection/manifest.json b/custom_components/uk_bin_collection/manifest.json index 6e4532ce11..c44eb254c7 100644 --- a/custom_components/uk_bin_collection/manifest.json +++ b/custom_components/uk_bin_collection/manifest.json @@ -9,7 +9,7 @@ "integration_type": "service", "iot_class": "cloud_polling", "issue_tracker": "https://github.com/robbrad/UKBinCollectionData/issues", - "requirements": ["uk-bin-collection>=0.155.0"], - "version": "0.155.0", + "requirements": ["uk-bin-collection>=0.156.0"], + "version": "0.156.0", "zeroconf": [] } diff --git a/pyproject.toml b/pyproject.toml index a07e76efa3..23b7bdbdf2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "uk_bin_collection" -version = "0.155.0" +version = "0.156.0" description = "Python Lib to collect UK Bin Data" readme = "README.md" authors = ["Robert Bradley "] From 26345dea4ae2e5f7854a3e70acab4028940640f4 Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Sat, 11 Oct 2025 03:05:28 +0100 Subject: [PATCH 182/425] feat:release process --- .github/workflows/bump.yml | 3 ++- pyproject.toml | 1 + .../uk_bin_collection/councils/AberdeenCityCouncil.py | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/bump.yml b/.github/workflows/bump.yml index 844e1748a6..d47779b952 100644 --- a/.github/workflows/bump.yml +++ b/.github/workflows/bump.yml @@ -46,4 +46,5 @@ jobs: - name: Push changes and tags run: | - git push origin master --follow-tags + git push origin master + git push origin --tags diff --git a/pyproject.toml b/pyproject.toml index eff6ce0255..b8ed215a1c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,6 +71,7 @@ version_scheme = "semver" major_version_zero = true tag_format = "$version" update_changelog_on_bump = true +annotated_tag = true version_files = [ "custom_components/uk_bin_collection/manifest.json:version", "custom_components/uk_bin_collection/manifest.json:requirements", diff --git a/uk_bin_collection/uk_bin_collection/councils/AberdeenCityCouncil.py b/uk_bin_collection/uk_bin_collection/councils/AberdeenCityCouncil.py index a69b3ab16b..00ed29fe59 100644 --- a/uk_bin_collection/uk_bin_collection/councils/AberdeenCityCouncil.py +++ b/uk_bin_collection/uk_bin_collection/councils/AberdeenCityCouncil.py @@ -5,7 +5,6 @@ from uk_bin_collection.uk_bin_collection.common import * from uk_bin_collection.uk_bin_collection.get_bin_data import AbstractGetBinDataClass - # import the wonderful Beautiful Soup and the URL grabber class CouncilClass(AbstractGetBinDataClass): """ From 07086db0cdb9e8d938dcd3843bbd628fd7775b92 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 11 Oct 2025 02:06:16 +0000 Subject: [PATCH 183/425] =?UTF-8?q?bump:=20version=200.156.0=20=E2=86=92?= =?UTF-8?q?=200.157.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2361 +++++++++++++++++ custom_components/uk_bin_collection/const.py | 2 +- .../uk_bin_collection/manifest.json | 4 +- pyproject.toml | 2 +- 4 files changed, 2365 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1872d621c9..f1e7f93f46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,2365 @@ ======= +## 0.157.0 (2025-10-11) + +### Feat + +- Create tag-on-merge.yml +- Update bump.yml +- fix bump.yml +- Update TorbayCouncil.py +- Update bump.yml +- fix release pipeline bump.yml +- fix Torbay + +### Fix + +- Update AberdeenCityCouncil.py +- Update TorbayCouncil.py +- Update TorbayCouncil.py +- Update TorbayCouncil.py +- Update URL for NewForestCouncil +- New URL and page for wheelie bins +- improve Mid Suffolk District Council holiday handling with dynamic bank holiday detection +- Oxford now rejects the "Requests" default user agent +- #1557 - Adding East Dunbartonshire +- #1557 - Adding East Dunbartonshire +- #1569 - Somerset Council +- #1569 - Somerset Council +- #1559 - Newport City Council +- #1559 - Newport City Council +- #1574 - Test Valley Borough Council +- #1574 - Test Valley Borough Council +- #1566 South Gloucestershire Council + +## 0.154.0 (2025-09-21) + +### Feat + +- handle changes to northumberland council website +- modify input for NorthumberlandCouncil to accept uprn instead of house number, and use new page structure + +### Fix + +- the cookie banner is not optional +- #1570 - Slough Borough Council +- #1570 - Slough Borough Council +- #1520 - Erewash Borough Council +- #1520 - Erewash Borough Council +- #1554 - Folkestone and Hythe District Council +- #1554 - Folkestone and Hythe District Council +- #1604 - West Berkshire Council +- #1604 - West Berkshire Council +- #1606 - Brighton and Hove City Council +- #1606 - Brighton and Hove City Council +- #1565 - BCP Council +- #1565 - BCP Council +- #1571 - Castle Point District Council +- #1571 - Castle Point District Council +- #1584 - NorthHertfordshireDistrictCouncil +- #1584 - NorthHertfordshireDistrictCouncil +- #1599 +- #1599 - Basingstoke Council +- #1587 +- #1587 - Hartlepool Borough Council +- #1588 +- #1588 Glasgow City Council +- #1591 +- #1591 Rushmoor Council + +## 0.153.0 (2025-09-02) + +### Feat + +- Change buckinghamshire council to get data from endpoint + +### Fix + +- 1573 Update Bolton council URL +- East Herts Council +- #1575 +- Runnymede Borough Council +- #1513 +- Wiltshire Council +- #1533 +- Staffordshire Moorlands District Council +- #1535 +- Ipswich Borough Council +- #1548 +- North East Lincs +- Hinckley and Bosworth Borough Council +- Nuneaton Bedworth Borough Council +- #1514 +- Lichfield District Council +- 1549 + +## 0.152.11 (2025-08-25) + +### Feat + +- fix releases process + +### Fix + +- date extraction in RochfordCouncil data parsing +- parsing error in BH selenium +- **hacs**: respect the headless option + +### Refactor + +- **hacs**: improve build_ukbcd_args with formatter functions + +## 0.152.10 (2025-08-04) + +### Fix + +- Gateshead and East Lothian +- Enfield and Broxbourne +- East Herts +- FermanaghOmaghDistrictCouncil +- Enfield and Broxbourne +- East Herts + +## 0.152.9 (2025-08-03) + +### Fix + +- Cotswald and coventry +- Fixing multiple broken councils +- multiple broken councils + +## 0.152.8 (2025-07-26) + +### Fix + +- Add headers to request for Swindon Borough Council +- Add headers to requests for Royal Borough of Greenwich Fixes #1496 by ensuring that the requests are not rejected due to lack of headers. +- **MidlothianCouncil**: add request headers to resolve 403 Forbidden + +## 0.152.7 (2025-07-01) + +### Fix + +- maidstone selenium fix + +## 0.152.6 (2025-06-18) + +### Fix + +- removed In Progress from date +- removed a degub print statement +- **RugbyBoroughCouncil**: Amended parsed date from full to abbreviated month date, may worked but jun and jul did not +- **RugbyBoroughCouncil**: Amended parsed date +- Reworked Cumberland Council to cater for postcode addition +- **OxfordCityCouncil**: Fixed Oxford City Council parsing dues to changes in output from the website + +## 0.152.5 (2025-06-07) + +### Fix + +- South Ribble and version pinning issues for input.json + +## 0.152.4 (2025-06-07) + +### Fix + +- **SouthRibble**: Corrected Date formatting issue +- **SouthRibble**: Resolved South Ribble without selenium + +## 0.152.3 (2025-06-04) + +### Fix + +- NorthHertfordshire selenium script +- Adur council +- Eastleigh date fix +- removed duplicates in BradfordMDC + +## 0.152.2 (2025-06-04) + +### Fix + +- Update Makefile +- Update CheshireEastCouncil.py +- Github action to handle branch name with parentheses + +## 0.152.1 (2025-05-15) + +### Fix + +- Update to fix North Somerset +- Glasgow SSL bypass +- more robust Northumberland +- updated Eastleigh input.json +- Eastleigh cloudflare fix +- converted collection datetimes into dates for BH parsing. +- Eastleigh cloudflare fix +- Eastleigh cloudflare fix +- added check_uprn to simplified councils +- simplified Swindon +- simplified East Devon +- simplified Dover +- Simplified Dartford +- simplified Cheshire East +- simplified Charnwood input.json +- improved Charnwood +- Adur Worthing fix +- Chorley simplification +- Bexley simplification +- added URL to Torbay script +- Guildford fixes +- reworked Maidstone +- maidstone input.json +- Croydon selenium version +- Stoke date-time fix + +## 0.152.0 (2025-05-02) + +### Feat + +- Added Fermanagh Omagh +- Added Twekesbury +- added Slough council +- Added Argus Council +- added Angus to input.json + +### Fix + +- Chichester now only requires postcode and house number +- Broadland now only requires postcode and house number +- Barking now only requires postcode and house number +- Brighton now only requires postcode and house number +- ensured all bins for this council +- added skip_get_url to hyndburn + +## 0.151.0 (2025-04-27) + +### Feat + +- version bump + +### Fix + +- more robust brent date handling +- input.json requires web_driver +- Rugby fix + +## 0.150.0 (2025-04-27) + +### Feat + +- added melton +- added pembrokeshire + +### Fix + +- added melton +- processed all bins for Moray + +## 0.148.6 (2025-04-27) + +## 0.148.5 (2025-04-27) + +### Fix + +- output check +- parsed bin info +- selenium navigation +- input.json changes + +## 0.148.4 (2025-04-27) + +### Fix + +- used canonical 'nice name' + +## 0.148.3 (2025-04-25) + +### Fix + +- working hyndburn +- hyndburn input.json + +## 0.148.2 (2025-04-24) + +### Fix + +- Update docker-compose.yml +- updated input.json +- cloudflare fix - switch to selenium method +- simplified blackburn + +## 0.148.1 (2025-04-22) + +### Fix + +- added bank holiday offsets. +- added bank holiday offsets. + +## 0.148.0 (2025-04-19) + +### Feat + +- adding Wrexham and #1046 Horsham councils + +### Fix + +- Argyll and Bute council #1053 + +## 0.147.2 (2025-04-18) + +### Fix + +- wait for element to be clickable + +## 0.147.1 (2025-04-18) + +### Fix + +- #1351 - moved geopandas to petry dev + +## 0.147.0 (2025-04-18) + +### Feat + +- add council tests results map + +## 0.146.2 (2025-04-18) + +### Fix + +- adding map checking and matching + +## 0.146.1 (2025-04-18) + +### Fix + +- more robust bank holiday handling + +## 0.146.0 (2025-04-18) + +### Feat + +- #1342 Adding Includes Trafford, Clackmannanshire, Havant, North Warwickshire, Newry Mourne and Down, East Dunbartonshire, Pendle, Torfaen, East Hampshire, Ribble Valley, Brentwood, Isle of Wight, Westmorland and Furness, Derry and Strabane, and Norwich. Google Cal support for PDF councils via ICS file + +### Fix + +- Black reformatting + +## 0.145.0 (2025-04-18) + +### Feat + +- Adding PDF councils + +## 0.144.4 (2025-04-18) + +### Fix + +- Bristol #1275 + +## 0.144.3 (2025-04-17) + +### Fix + +- better address for input.json +- bank holiday overrides +- more robust address searching +- simple parsing done +- Selenium navigation + +## 0.144.2 (2025-04-17) + +### Fix + +- knowsley +- knowsley +- knowsley +- knowsley +- KnowsleyMBCouncil.py +- #1220 adding Mid Ulster District Council + +## 0.144.1 (2025-04-17) + +### Fix + +- fix Sandwell garden waste collection date + +## 0.144.0 (2025-04-17) + +### Feat + +- added great yarmouth + +## 0.143.6 (2025-04-17) + +### Fix + +- Renfrewshire Council + +## 0.143.5 (2025-04-17) + +### Fix + +- Google Cal + +## 0.143.4 (2025-04-17) + +### Fix + +- Google Cal + +## 0.143.3 (2025-04-15) + +### Fix + +- #1301 Fix Leeds Council + +## 0.143.2 (2025-04-15) + +### Fix + +- #1301 Fix Leeds Council + +## 0.143.1 (2025-04-15) + +### Fix + +- Set the bin_type when different day + +## 0.143.0 (2025-04-13) + +### Fix + +- corrected url in input.json +- fixed input.json +- parsed Barking Dagenham collection information +- selenium navigation Barking + +## 0.142.0 (2025-04-13) + +### Feat + +- Added Stirling Council + +### Fix + +- typo in input.json + +## 0.141.4 (2025-04-13) + +### Fix + +- #1304 - sesnors goes to unknown if the data is blank from councils who are less reliable + +## 0.141.3 (2025-04-13) + +### Fix + +- Newham council + +## 0.141.2 (2025-04-13) + +### Fix + +- Newham council +- Newham council + +## 0.141.1 (2025-04-12) + +### Fix + +- missing finally block on selenium tests + +## 0.141.0 (2025-04-12) + +### Feat + +- #1185 Adding PeterboroughCity Council + +## 0.140.0 (2025-04-11) + +### Feat + +- Added Broadland District Council + +### Fix + +- cleanup of council file +- added Broadland to input.json + +## 0.139.0 (2025-04-07) + +### Feat + +- adding #1037 +- adding #1032 North Devon Count Council + +### Fix + +- #1296 Forest of dean +- 939 adding South Holland District Council - Lincolnshire UK + +## 0.138.1 (2025-04-05) + +### Fix + +- Walhtam forest council - revert previous changes + +## 0.138.0 (2025-04-05) + +### Feat + +- Adding Hastings Borough Council +- Adding Fylde Council + +### Fix + +- #1249 +- #1039 +fix: #1181 +fix: #1266 +fix: #1274 +- Gloucester City Council +- #1282 +- Mid Devon Council +- #1277 +fix: #1287 +- West Oxfordshire Council +- #1290 + +## 0.137.0 (2025-04-05) + +### Feat + +- #816 adding trafford council + +## 0.136.0 (2025-03-24) + +### Feat + +- Adding Southampton City Council +- Adding Cambridge City Council +- Adding Spelthorne Borough Council + +### Fix + +- #1057 +- #1264 +- #1270 +- Bexley Council +- #1256 +- HinckleyandBosworthBoroughCouncil +- #1207 +- Hackney Council +- #1230 +- Castlepoint District Council +- #1252 +- Canterbury City Council +- #1254 + +## 0.135.4 (2025-03-24) + +### Fix + +- parse scheduleCodeWorkflowIDs instead of scheduleCodeWorkflowID for Hackney Council + +## 0.135.3 (2025-02-23) + +## 0.135.2 (2025-02-19) + +### Fix + +- North Yorkshire - multiple bins on a day + +## 0.135.1 (2025-02-18) + +### Fix + +- devcontainer + +## 0.135.0 (2025-02-17) + +### Fix + +- #833 adding Middlesbrough and check script for Selenium +- Cotswold District Council +- #1238 +- Leeds City Council +- #1222 + +## 0.134.3 (2025-02-15) + +### Fix + +- Update input.json +- 1235 Councils missing Selenium in input.json + +## 0.134.2 (2025-02-15) + +### Fix + +- 1232 East herts missing Selenium url in input.json +- Derbyshire Dales District Council +- Conwy County Borough +- Sunderland City Council +- #1219 +- Tendring District Council +- #1221 + +## 0.134.1 (2025-02-11) + +### Fix + +- Cheltenham Borough Council +- #1061 + +## 0.134.0 (2025-02-07) + +### Feat + +- Ipswich Borough Council - trying different address +- Ipswich Borough Council - correcting param name in input.json +- Ipswich Borough Council - added input.json values and refactored code +- Ipswich Borough Council - initial implementation +- Adding Runnymede Borough Council +- Adding Cherwell District Council +- Adding Epsom and Ewell Borough Council +- Adding Redcar and Cleveland Council +- Adding Amber Valley Borough Council +- Adding Bolsover Council + +### Fix + +- #1214 +- #923 +- #895 +- #841 +- #903 +- #990 +- Torridge District Council +- #1204 +- Neath Port Talbot +- #1213 + +## 0.133.0 (2025-02-02) + +### Feat + +- adding manual refresh + +## 0.132.0 (2025-02-02) + +### Feat + +- adding manual refresh + +## 0.131.0 (2025-02-02) + +### Feat + +- adding manual refresh +- adding manual refresh +- adding manual refresh +- adding manual refresh +- adding manual refresh +- adding manual refresh +- adding manual refresh +- adding manual refresh +- adding manual refresh +- adding manual refresh +- adding manual refresh +- adding manual refresh +- adding unit tests for the new manual refresh +- adding manual refresh control + +## 0.130.1 (2025-01-30) + +### Fix + +- slow councils + +## 0.130.0 (2025-01-29) + +### Feat + +- Add Herefordshire Council (closes: #1011) + +### Fix + +- Fix spacing in wiki name + +## 0.129.0 (2025-01-29) + +### Fix + +- input.json +- input.json + +## 0.128.6 (2025-01-29) + +### Fix + +- moving away from broken Allure reporting +- moving away from broken Allure reporting +- moving away from broken Allure reporting +- moving away from broken Allure reporting +- moving away from broken Allure reporting +- moving away from broken Allure reporting +- moving away from broken Allure reporting + +## 0.128.5 (2025-01-29) + +### Feat + +- Adding East Staffordshire Borough Council + +### Fix + +- Update behave_pull_request.yml +- Update behave_pull_request.yml +- Update behave_pull_request.yml +- Update behave_pull_request.yml +- Update behave_pull_request.yml +- Update behave_pull_request.yml +- Update CheshireEastCouncil.py +- Adding East Lothian Council +- #1171 +- #1052 +fix: #1083 + +## 0.128.4 (2025-01-28) + +### Feat + +- Adding Boston Borough Council + +### Fix + +- Update CheshireEastCouncil.py +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_pull_request.yml +- Update behave_pull_request.yml +- Update behave_pull_request.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update CheshireEastCouncil.py +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Leicester City Council +- #1178 +- Cardiff Council +- #1175 +- Newcastle City Council +- #1179 +- #1180 +- Midlothian Council +- #1192 - Adding Next Page support + +## 0.128.3 (2025-01-28) + +### Fix + +- Update CheshireEastCouncil.py +- Update behave_schedule.yml +- Update behave_pull_request.yml + +## 0.128.2 (2025-01-28) + +### Fix + +- Add communal recycling and communal rubbish +- Add garden waste to Merton Council + +## 0.128.1 (2025-01-28) + +### Fix + +- Update AberdeenshireCouncil.py +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update behave_pull_request.yml + +## 0.128.0 (2025-01-28) + +### Feat + +- implement Medway Council (#1021) + +### Fix + +- Forgot to include skip_get_url + +## 0.127.4 (2025-01-25) + +### Fix + +- NewForestCouncil + +## 0.127.3 (2025-01-16) + +### Fix + +- Swale Borough Council +- #1139 +- Vale of White Horse +- #1156 +- South Oxfordshire Council +- #1158 +- Surrey Heath Borough Council +- #1164 +- Carmarthenshire County Council +- #1167 +- Glasgow City Council +- #1166 + +## 0.127.2 (2025-01-13) + +### Fix + +- Update bin type to be the full string + +## 0.127.1 (2025-01-10) + +### Fix + +- Use visibility of list rather than existence +- Update Rushcliffe Borough Council input elements and flow +- Merton Council +- NewarkAndSherwoodDC +- Rushcliffe Borough Council +- Powys Council +- Staffordshire Moorlands District Council +- Stroud District Council +- Vale of Glamorgan Council +- West Oxfordshire District Council + +## 0.127.0 (2025-01-07) + +### Feat + +- Adding Oadby And Wigston Borough Council +- Add Gwynedd Council +- Adding Denbighshire Council +- Adding Dundee City Council +- Adding Brent Council +- Adding West Dunbartonshire Council +- Adding Cumberland Council + +### Fix + +- #929 +- Cornwall Council +- #1137 +- #1125 +- #1106 +- #1108 +- #1109 +- #1134 +- Northumberland Council +- #1082 +- #1110 +- Waltham Forest +- #1126 +- London Borough Sutton +- #1131 +- Kirklees Council +- #1129 - Breaking Change. UPRN required + +## 0.126.2 (2025-01-07) + +### Fix + +- **tests**: updates test case url for coventry city council +- **tests**: removes duplicate key for coventry city council +- updates coventry city council button text + +## 0.126.1 (2025-01-06) + +### Fix + +- behave_testing +- behave_testing + +## 0.126.0 (2025-01-04) + +### Fix + +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml + +## 0.125.2 (2025-01-04) + +### Fix + +- Update ArdsAndNorthDownCouncil.py +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update README.md to have links to Full and Partial Integration Test Reports +- Update behave_pull_request.yml +- Update README.md to have links to Full and Partial Integration Test Reports +- Swale Borough Council +- #1080 +(cherry picked from commit 6f580b39fb68b8079990221e050ae8dd6d2b7285) +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update ArdsAndNorthDownCouncil.py +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update README.md to have links to Full and Partial Integration Test Reports +- Update WestLindseyDistrictCouncil.py +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update behave_schedule.yml +- Update behave_pull_request.yml +- Update behave_pull_request.yml +- Update behave_schedule.yml +- Update behave_schedule.yml + +## 0.125.1 (2025-01-04) + +### Fix + +- correctly handle year increment for January dates + +## 0.125.0 (2025-01-04) + +### Feat + +- Adding Redditch Borough Council +- Adding Blaenau Gwent County Borough Council +- Adding Wandsworth Council + +### Fix + +- #1068 +- #1098 +- Wiltshire Council +- #1094 +- Salford City Council +- #1097 +- #1078 +- Merton Council +- Swale Borough Council +- #1080 +- London Borough Sutton +- #1076 +- Update behave_schedule.yml +- Update bump.yml + +## 0.124.4 (2025-01-04) + +### Fix + +- Update behave_schedule.yml + +## 0.124.3 (2025-01-04) + +### Fix + +- allure reporting +- allure reporting +- allure reporting + +## 0.124.2 (2025-01-03) + +### Fix + +- Update behave.yml + +## 0.124.1 (2025-01-03) + +### Fix + +- avoid crashing on unexpected string value + +## 0.124.0 (2025-01-02) + +### Feat + +- Hart District Council + +## 0.123.2 (2024-12-19) + +### Fix + +- Update behave.yml + +## 0.123.1 (2024-12-18) + +### Feat + +- #1063 - rewrite Kirklees Council parser for new website +- #1067 - Add garden bin collections where available for Norwich City Council +- Adding Wandsworth Council + +### Fix + +- Update AberdeenCityCouncil.py +- Update behave.yml +- #1101 - Fix table parsing for Walsall Council +- Remove invalid escape sequence warnings from West Lindsey District Council +- #1073 - change method of generating bin types to avoid manual mapping for Rugby Borough Council +- add missing backticks to separate colour config and standard usage instructions +- #1078 +(cherry picked from commit 89d93666bb659010d1c130b98c1d81c6ff80cf7c) +- change date format to project default for Merton Council +- correct date logic for Swale Borough Council +- Merton Council +- London Borough Sutton +- #1076 +(cherry picked from commit 1eab20c9a57c9c4438ea343f374202bb2e9b98ca) +- Swale Borough Council +- #1080 +(cherry picked from commit 6f580b39fb68b8079990221e050ae8dd6d2b7285) +- correct date/year logic for West Lindsey District Council +- replace West Lindsey's input with working address +- #1089 - Correct shifted dates in Bromley Borough Council +- remove WDM import +- #1087 - Food waste date incorrect for West Berkshire Council + +## 0.123.0 (2024-12-17) + +## 0.122.0 (2024-12-04) + +### Feat + +- Adding Monmouthshire County Council +- Adding Hinckley and Bosworth Borough Council + +### Fix + +- Glasgow City Council +- Merton Council +- Blaby District Council +- Warwick District Council +- Blackburn Council +- Carmarthenshire County Council +- High Peak Council +- CarmarthenshireCountyCouncil + +## 0.121.1 (2024-12-03) + +### Fix + +- London Borough of Lewisham to have more reliable parsing of dates + +## 0.121.0 (2024-11-24) + +### Feat + +- Royal Borough of Greenwich +- Adding London Borough of Lewisham +- Adding Hackney Council +- Adding Sandwell Borough Council +- Adding Moray Council +- Adding Kings Lynn and West Norfolk Borough Council +- Adding Wyre Forest District Council +- Adding Folkstone and Hythe District Council +- Adding Cheltenham Borough Council +- Adding Thurrock Council + +### Fix + +- West Northamptonshire Council +- East Ayrshire Council +- Cotswold District Council + +## 0.120.0 (2024-11-20) + +### Feat + +- Adding Hartlepool Borough Council +- Adding Newcastle Under Lyme Council +- Adding London Borough of Havering +- Add Garden collection to EnvironmentFirst +- Adding Cumberland Council (Allerdale District) +- Adding North Hertfordshire District Council + +### Fix + +- #844 +- #778 +- #769 +- #1025 +- Mid Siffolk and Babergh Garden Collection Day +- #1026 +This will require the use of a DAY to be added to the UPRN field +- #1029 +- #1028 + +## 0.119.0 (2024-11-20) + +### Feat + +- Adding Braintree District Council +- Adding Burnley Borough Council +- Adding Exeter City Council +- Adding Edinburgh City Council + +### Fix + +- #699 +- #1015 +- #1017 +- #894 + +## 0.118.0 (2024-11-15) + +### Feat + +- Adding Aberdeen City Council +- Adding Wolverhampton City Council +- Adding Stevenage Borough Council +- Adding Thanet District Council +- Adding Copeland Borough Council +- Adding South Hams District Council + +### Fix + +- #1019 +- #966 +- #989 +- #1004 +- #1006 +- #1008 +- Rother District Council + +## 0.117.0 (2024-11-13) + +### Feat + +- Adding South Staffordshire District Council fix: #885 +- Adding Rother District Council + +### Fix + +- #1009 + +## 0.116.0 (2024-11-12) + +### Feat + +- Adding Ashfield District Council +- Adding Gravesham Borough Council +- Adding Argyll and Bute Council + +### Fix + +- CrawleyBoroughCouncil +- #1005 +- Adding Garden collection to Babergh and MidSuffolk Council +- #995 +- #579 +- #991 +- #692 +- CheshireWestAndChesterCouncil +- #993 +- Milton Keynes +- #702 +- Adding Babergh and Mid Suffolk District Councils +- #868 +fix: #919 +- Adding Derby City Council +- #987 + +## 0.115.0 (2024-11-11) + +### Feat + +- Adding Warrington Borough Council +- Adding Antrim And Newtonabbey Council +- Adding Hertsmere Borough Council +- Adding West Lancashire Borough Council +- Broxbourne Council + +### Fix + +- #695 +- #969 +- #776 +- #980 +- #982 +- Bradford MDC +- #984 + +## 0.114.6 (2024-11-09) + +### Fix + +- NBBC Date Fix + +## 0.114.5 (2024-11-08) + +### Fix + +- migration logging and debugging + +## 0.114.4 (2024-11-08) + +### Fix + +- migration not working +- migration not working + +## 0.114.3 (2024-11-08) + +## 0.114.2 (2024-11-08) + +## 0.114.1 (2024-11-08) + +### Fix + +- Update manifest.json + +## 0.114.0 (2024-11-07) + +### Feat + +- Nuneaton and Bedworth Borough Council + +## 0.113.0 (2024-11-07) + +## 0.112.1 (2024-11-07) + +## 0.112.0 (2024-11-06) + +### Feat + +- adding calendar for Bins in Custom Component + +### Fix + +- fix manifest in custom component +- #975 adding routine to handle migration error +- #975 adding routine to handle migration error +- #767 BREAKING CHANGE - READD your sensors / config + +## 0.111.0 (2024-11-06) + +### Fix + +- Add London Borough of Sutton +- #944 +- Add Mid Devon Council +- #945 +- Adding Oxford City Council +- #962 +- Tunbridge Wells / Lincoln +- #963 +- Glasgow City Council + +## 0.110.0 (2024-11-04) + +### Fix + +- Adding Blaby District Council +- #904 +- Adding Sefton Council +- #770 +- Adding Bromsgrove District Council +- #893 +- East Lindsey District Council +- #957 +- Adding Carmarthenshire County Council +- #892 +fix: #710 +- Adding East Ayrshire Council +- #955 + +## 0.109.2 (2024-11-03) + +### Fix + +- CC testing and add Chesterfield + +## 0.109.1 (2024-11-03) + +### Fix + +- CC testing and add Chesterfield +- CC testing and add Chesterfield + +## 0.109.0 (2024-11-02) + +### Feat + +- Adding Cotswold District Council +- Adding Breckland Council + +### Fix + +- St Helens Borough Council +- #753 +- NewarkAndSherwoodDC +- #941 +- #658 +- #656 + +## 0.108.2 (2024-11-01) + +### Fix + +- pytest-homeassistant-custom-component + +## 0.108.1 (2024-11-01) + +### Fix + +- Pydandic version +- Pydandic version + +## 0.108.0 (2024-11-01) + +### Feat + +- pytest fixes +- pytest fixes +- pytest fixes +- pytest fixes +- pytest fixes +- pytest fixes + +## 0.107.0 (2024-10-31) + +### Feat + +- Adding Powys Council +- Adding Worcester City Council +- Adding Ards and North Down Council +- Adding East Herts Council +- Adding Ashford Borough Council + +### Fix + +- WestOxfordshireDistrictCouncil +- South Norfolk Council +- ForestOfDeanDistrictCouncil +- Croydon Council +- South Kesteven District Council +- #647 +- #630 +- #623 +- #586 +- #578 +- #389 + +## 0.106.0 (2024-10-28) + +### Feat + +- Adding Stockton On Tees Council +- Adding Fife Council +- Adding Flintshire County Council + +### Fix + +- #930 +- #933 +- #750 + +## 0.105.1 (2024-10-24) + +### Fix + +- Refactor Midlothian Council scraper to use house number and postcode +- West Berkshire Council +- Southwark Council + +## 0.105.0 (2024-10-21) + +### Feat + +- Adding Teignbridge Council +- Adding Harborough District Council +- Adding Watford Borough Council +- Adding Coventry City Council +- pytest fixes +- pytest fixes +- pytest fixes +- pytest fixes +- pytest fixes +- Adding Powys Council +- Adding Worcester City Council +- Adding Ards and North Down Council +- Adding East Herts Council +- Adding Ashford Borough Council +- Adding Stockton On Tees Council +- Adding Fife Council +- Adding Flintshire County Council +- Adding Teignbridge Council +- Adding Harborough District Council +- Adding Watford Borough Council +- Adding Coventry City Council +- Python 3.12 only and CustomComp. Unit testing + +### Fix + +- #580 +- #888 +- #902 +- #607 +- CC testing and add Chesterfield +- CC testing and add Chesterfield +- CC testing and add Chesterfield +- pytest-homeassistant-custom-component +- Pydandic version +- Pydandic version +- WestOxfordshireDistrictCouncil +- South Norfolk Council +- ForestOfDeanDistrictCouncil +- Croydon Council +- South Kesteven District Council +- #647 +- #630 +- #623 +- #586 +- #578 +- #389 +- #930 +- #933 +- #750 +- Refactor Midlothian Council scraper to use house number and postcode +- West Berkshire Council +- Southwark Council +- #580 +- #888 +- #902 +- #607 + +## 0.104.0 (2024-10-20) + +### Feat + +- Adding Luton Borough Council +- Adding West Oxfordshire District Council +- Adding Aberdeenshire Council +- Adding Canterbury City Council +- Adding Swindon Borough Council + +### Fix + +- #697 +- #694 +- #659 +- #590 +- #900 + +## 0.103.0 (2024-10-20) + +### Feat + +- Adding RAW JSON Sensor + +### Fix + +- Black formatting +- Black formatting + +## 0.102.0 (2024-10-20) + +### Feat + +- Moving from Attributes to Sensors +- Moving from Attributes to Sensors + +## 0.101.0 (2024-10-20) + +### Feat + +- Add Midlothgian Council + +## 0.100.0 (2024-10-18) + +### Feat + +- Adding Dudley Council +- Adding South Ribble Council +- Plymouth Council +- Adding Norwich City Council + +### Fix + +- #744 +- #671 +- #566 +- #749 + +## 0.99.1 (2024-10-16) + +### Fix + +- #792 adding web_driver option to Wokingham Council + +## 0.99.0 (2024-10-16) + +### Feat + +- Adding Lincoln Council +- Adding Tunbridge Wells Council +- Adding Perth and Kinross Council + +### Fix + +- Update wiki +- #748 +- #598 +- #572 + +## 0.98.5 (2024-10-15) + +### Fix + +- Swale Borough Council +- HaltonBoroughCouncil +- Barnet Council +- WestBerkshireCouncil + +## 0.98.4 (2024-10-14) + +### Fix + +- West Suffolk Council +- Vale of White Horse Council +- Uttlesford District Council +- Neath Port Talbot Council +- Merton Council +- Manchester City Council +- Glasgow City Council +- BradfordMDC + +## 0.98.3 (2024-10-13) + +### Fix + +- EastRiding + +## 0.98.2 (2024-10-13) + +### Fix + +- MoleValley + +## 0.98.1 (2024-10-13) + +### Fix + +- Barnet and Bexley + +## 0.98.0 (2024-10-13) + +### Feat + +- Adding Wirral Council +- Adding Lichfield District Council +- Adding West Morland And Furness +- Adding Walsall Council +- Adding Armagh, Banbridge and Craigavon Council + +### Fix + +- #602 +- #830 +- #870 +- #873 +- #877 + +## 0.97.1 (2024-10-10) + +### Fix + +- NottinghamCityCouncil +- #875 + +## 0.97.0 (2024-10-10) + +### Feat + +- Adding Falkirk Council + +### Fix + +- #761 + +## 0.96.0 (2024-10-10) + +### Feat + +- Adding London Borough Harrow +- Adding North Ayrshire Council +- Adding Highland Council +- Add Elmbridge Borough Council +- Adding Southwark Council +- South Derbyshire District Council + +### Fix + +- #871 +- #869 +- #780 +- #845 +fix: #754 +- #835 +- #842 + +## 0.95.0 (2024-10-09) + +### Feat + +- Adding London Borough of Ealing + +## 0.94.0 (2024-10-09) + +### Feat + +- Adding London Borough of Lambeth +- Adding Dacorum Borough Council + +### Fix + +- Dacorum Borough Council +- East Devon DC + +## 0.93.0 (2024-10-08) + +### Feat + +- Update CheshireEastCouncil.py + +## 0.92.0 (2024-10-08) + +### Feat + +- Update CheshireEastCouncil.py +- Update README.md +- Adding Wokingham Borough Council +- Adding Winchester City Council +- Adding Basildon Council +- Adding Colchester City Council + +### Fix + +- RochfordCouncil +- Neath Port Talbot Council +- Buckinghamshire Council +- #639 +fix: #812 + +## 0.91.2 (2024-10-05) + +### Fix + +- Windsor and Maidenhead Council + +## 0.91.1 (2024-10-04) + +## 0.91.0 (2024-10-03) + +## 0.90.0 (2024-10-03) + +### Feat + +- Adding East Renfrewshire Council + +### Fix + +- Update DorsetCouncil.py +- #829 +- Update GatesheadCouncil.py +- #822 + +## 0.89.1 (2024-10-02) + +### Fix + +- High Peak have changed their cookie dialog Seems to be safe to ignore it now. + +## 0.89.0 (2024-09-27) + +### Feat + +- Update CheshireEastCouncil.py +- Update README.md + +### Fix + +- release to be non pre release + +## 0.88.0 (2024-09-16) + +### Feat + +- Add Ealing Council + +### Fix + +- Update README.md + +## 0.87.0 (2024-09-10) + +### Feat + +- Add IslingtonCouncil + +### Fix + +- #565 Gloucester city council driver + +## 0.86.1 (2024-09-09) + +### Fix + +- #773 Wakefield + +## 0.86.0 (2024-09-06) + +### Feat + +- added Rotherham Council + +## 0.85.7 (2024-09-05) + +### Fix + +- more unit tests +- more unit tests +- Chorley + +## 0.85.6 (2024-09-03) + +### Fix + +- #795 and add reconfigure to custom comp. + +## 0.85.5 (2024-09-03) + +## 0.85.4 (2024-09-03) + +### Fix + +- #795 and add reconfigure to custom comp. +- #795 Unit Test Coverage + +## 0.85.3 (2024-09-02) + +### Fix + +- #795 unit test coverage + +## 0.85.2 (2024-09-02) + +### Fix + +- 791 Glasgow URL change + +## 0.85.1 (2024-09-02) + +### Fix + +- 779 Add correct async wait to Home Assistant + +## 0.85.0 (2024-08-27) + +### Feat + +- support for enfield council + +## 0.84.2 (2024-08-27) + +### Fix + +- Re-work North Tyneside Council module for 2024 - some addresses do not have a garden collection +- Re-work North Tyneside Council module for 2024 + +## 0.84.1 (2024-08-08) + +### Fix + +- #771 Bolton bullet points on dates is now fixed + +## 0.84.0 (2024-07-31) + +## 0.83.0 (2024-07-07) + +### Feat + +- add has_numbers() function + +### Fix + +- update Gedling Borough Council parser to use alternative name key +- change Gedling to use new JSON data +- update instructions for Gedling +- update input.json to use UPRN parameter +- change DorsetCouncil.py to use API links provided in #756 +- explicit import of logging.config to stop error in Python 3.11 + +## 0.82.0 (2024-06-13) + +### Feat + +- adding dev container updates +- adding dev container updates +- refactoring main files +- adding ability to set local mode in HA custom comp. if users dont have a Selenium Server + +### Fix + +- MidSussex + +## 0.81.0 (2024-06-05) + +### Feat + +- Adding Wychavon District Council + +### Fix + +- IntTestWarnings +- IntTestWarnings + +## 0.80.0 (2024-06-02) + +### Feat + +- Adding Uttlesford District Council +- Adding Stafford Boro Council +- Adding Swansea Council +- Adding New Forest +- Adding Three Rivers +- Adding Three Rivers + +### Fix + +- ThreeRivers +- #425 Entities are not updated +- sessions to avoid deprecation +- Update docker-image.yml +- Update docker-image.yml + +## 0.79.1 (2024-05-29) + +### Fix + +- Change CSS class in search for collection types + +## 0.79.0 (2024-05-28) + +### Feat + +- Adding Dartford +- Adding South Kesteven District Council +- Adding ChichesterCouncil +- adding HounslowCouncil +- adding HounslowCouncil +- adding HounslowCouncil +- Epping Fix +- Adding Epping Forest District Council +- Update input.json +- Epping Forest District Council +- Adding Stroud District Council +- Add support for Tendring District Council +- #269 Adding Waltham Forest +- #269 Adding Waltham Forest +- Adding council creation script + +### Fix + +- Update Mole Valley URL + +## 0.78.0 (2024-05-26) + +### Feat + +- Add support for Fareham Borough Council + +## 0.77.0 (2024-05-26) + +### Feat + +- Add support for Bracknell Forest Council + +## 0.76.1 (2024-05-24) + +### Fix + +- Handle Barnet council cookies message + +## 0.76.0 (2024-05-24) + +### Feat + +- add bin colour support WestSuffolkCouncil style: black format WestSuffolkCouncil +- add bin colour support WestSuffolkCouncil style: black format WestSuffolkCouncil + +## 0.75.0 (2024-05-19) + +### Feat + +- #725 Add names to selenium test videos using "se:name" option in create webdriver function + +## 0.74.1 (2024-05-18) + +### Fix + +- #693 Cheshire West & Chester Council Sensor Bug +- #693 Cheshire West & Chester Council Sensor Bug + +## 0.74.0 (2024-05-17) + +### Feat + +- #722 Support Python 3.12 +- #722 Support Python 3.12 +- #722 Support Python 3.12 + +## 0.73.0 (2024-05-17) + +### Feat + +- #708 Adding HA to the dev container for debugging + +## 0.72.0 (2024-05-17) + +### Feat + +- #708 Adding HA to the dev container for debugging +- #708 Adding HA to the dev container for debugging +- #708 Adding HA to the dev container for debugging +- #708 Adding HA to the dev container for debugging +- #708 Adding HA to the dev container for debugging +- #708 Adding HA to the dev container for debugging +- #708 Adding HA to the dev container for debugging +- #708 Adding HA to the dev container for debugging + +## 0.71.0 (2024-05-17) + +### Feat + +- Update for West Suffolk Councils new website + +## 0.70.0 (2024-05-17) + +### Feat + +- #708 Dev Container +- Dev Container +- #708 Dev Container +- #708 Dev Container +- #708 Dev Container +- #708 Dev Container +- #708 Dev Container +- #708 Dev Container +- #708 Dev Container +- #708 Dev Container +- #708 Dev Container +- #708 Dev Container +- #708 simplifying Selenium integration tests +- #708 simplifying Selenium integration tests +- #708 Test GH action seenium +- #708 Test GH action seenium +- #708 Test GH action seenium +- #708 Test GH action seenium +- #708 Test GH action seenium +- #708 Test GH action seenium +- #708 Test GH action seenium +- #708 Test GH action seenium +- #708 Test GH action seenium +- #708 Test GH action seenium +- #708 Dev Container testing +- #708 - dev container changes +- #706 Adding Dev Container +- #706 Adding initial Dev Container + +## 0.69.7 (2024-05-17) + +### Fix + +- #713 BarnsleyMBCouncil.py + +## 0.69.6 (2024-05-16) + +### Fix + +- #709 Update DoverDistrictCouncil.py + +## 0.69.5 (2024-05-14) + +### Fix + +- #696 Small issue and Black formatting +- #696 Small issue and Black formatting +- #696 Small issue and Black formatting +- #696 Small issue and Black formatting +- #696 Small issue and Black formatting +- #696 Small issue and Black formatting +- #696 Small issue and Black formatting +- #696 Small issue and Black formatting +- #696 test coverage back to 100% + +## 0.69.4 (2024-05-09) + +### Fix + +- pass in required parameter into `create_webdriver` +- test runners for `MiltonKeynesCityCouncil` and `NorthEastLincs`. + +## 0.69.3 (2024-05-09) + +### Fix + +- fix AttributeError when no garden waste collection is available for properties using Huntingdon District Council +- add support for parsing "Today" / "Tomorrow" as date text for `BarnsleyMBCouncil` +- add support for parsing "Tomorrow" as date text for `LiverpoolCityCouncil` + +## 0.69.1 (2024-05-01) + +### Fix + +- Handling the "Website cookies enhance your user experience." button +- Handling the "Website cookies enhance your user experience." button + +## 0.69.0 (2024-04-28) + +### Feat + +- Adding Renfrewshire Council +- Adding Renfrewshire Council + +## 0.68.2 (2024-04-28) + +### Fix + +- Remove 'import Dumper' + +## 0.68.1 (2024-04-27) + +### Fix + +- input.json Bradford missing comma + +## 0.68.0 (2024-04-27) + +### Feat + +- Add support for West Berkshire Council +- add support for Knowsley Metropolitan Borough Council +- add support for Cheshire West and Chester Council +- add support for Cheshire West and Chester Council + +## 0.66.2 (2024-04-18) + +### Fix + +- Update HaringeyCouncil.py issue #670 + +## 0.66.1 (2024-04-15) + +### Fix + +- parse datetimes correctly and round to midnight + +## 0.66.0 (2024-04-15) + +## 0.65.2 (2024-04-15) + +### Fix + +- change address selection to fix errors selecting the user's PAON + +## 0.65.1 (2024-04-15) + +### Fix + +- add check for parsed string length to stop datetime parsing error + +## 0.65.0 (2024-04-13) + +### Feat + +- add Arun council +- add support for Sunderland City Council +- add support for Sunderland City Council + +## 0.64.3 (2024-03-25) + +### Fix + +- sort data and correct dictionary name (#609) + +## 0.64.2 (2024-03-24) + +## 0.64.1 (2024-03-24) + +### Fix + +- fix Kirklees address search (switch to house & postcode) +- fixes json + +## 0.64.0 (2024-03-23) + +### Feat + +- add Kirklees council + +### Fix + +- fixes json + +## 0.63.0 (2024-03-23) + +### Feat + +- Add Solihull Council (#513) +- Add Adur and Worthing Councils (#544) +- Add Dover District Council (#614) +- Add Rochford Council (#620) +- Add Tandridge District Council (#621) +- Add West Northamptonshire Council (#567) +- Add Hull City Council (#622) +- Add Wyre Council (#625) +- Add Telford and Wrekin Co-operative Council (#632) +- Add Mansfield District Council (#560) +- Add Bedford Borough Council (#552) + +### Fix + +- spacing on input.json +- realign input.json +- capitalize bin type text +- formatting on input.json +- incorrect collections +- update testing URL for Merton +- attempt to resolve invisible banner hiding postcode box +- resolve JSON schema exception for date formatting +- resolve JSON schema exception for date formatting +- accept cookies banner + +## 0.62.0 (2024-03-03) + +### Fix + +- Added missing .feature file entry to the test config for NewhamCouncil + +## 0.61.1 (2024-02-16) + +### Fix + +- code optimisations +- Fix date parsing in WestLindseyDistrictCouncil.py + +## 0.61.0 (2024-02-11) + +### Feat + +- Add Mole Valley District Council + +## 0.60.1 (2024-02-03) + +### Fix + +- Update input.json Closes #599 + +## 0.60.0 (2024-01-28) + +### Feat + +- Add Scraper for St Albans City and District Council + +## 0.59.1 (2024-01-25) + +### Fix + +- add wiki note for castlepoint +- update test data for castlepoint +- remove single line causing issues + +## 0.59.0 (2024-01-20) + +### Feat + +- Add NorthYorkshire to test feature file +- Add north yorkshire to test input +- Add Support for north yorkshire council + +### Fix + +- remove unused code + +## 0.58.8 (2024-01-19) + +### Fix + +- barnet no overrides + +## 0.58.7 (2024-01-18) + +### Fix + +- accidentally returned strings when needed date objects, refactor to handle this +- checking for future/past dates + +## 0.58.6 (2024-01-18) + +### Fix + +- correct date handling for North West Leicestershire + +## 0.58.5 (2024-01-15) + +### Fix + +- Don't call driver.quit where already handled by finally block + +## 0.58.4 (2024-01-15) + +### Fix + +- remove extra driver.quit to prevent errors + +## 0.58.3 (2024-01-15) + +### Feat + +- Added support for Newham Council's bin collections + +### Fix + +- Add a default value for user_agent to fix all councils using selenium and not specifying agent + +## 0.58.2 (2024-01-11) + +### Fix + +- use static values for bin types + +## 0.58.1 (2024-01-10) + +### Fix + +- Eastleigh Borough Council doesnt cope with "You haven't yet signed up for ..." +- Eastleigh Borough Council doesnt cope when Garden Waste service hasn't been signed up for, which gets the value "You haven't yet signed up for our garden waste collections. Find out more about our\xa0garden waste collection service" which results in ValueError: time data + +## 0.58.0 (2024-01-10) + +### Feat + +- Add Test Valley Borough Council + +## 0.57.0 (2024-01-09) + +### Feat + +- Add support for Chorley Council + +## 0.56.13 (2024-01-09) + +### Fix + +- update logic to account for council website change + +## 0.56.12 (2024-01-09) + +### Fix + +- duplicate driver.quit() calls causes error + +## 0.56.11 (2024-01-08) + +### Fix + +- Headless now working on custom comp Update sensor.py + +## 0.56.10 (2024-01-08) + +### Fix + +- headless mode in custom component + +## 0.56.9 (2024-01-08) + +### Fix + +- headless mode + +## 0.56.8 (2024-01-08) + +### Fix + +- headless in custom comp + +## 0.56.7 (2024-01-08) + +### Fix + +- headless options + +## 0.56.6 (2024-01-07) + +### Fix + +- modified Kingston-upon-Thames driver for greater reliability. + +## 0.56.5 (2024-01-07) + +### Fix + +- Update KingstonUponThamesCouncil.py + +## 0.56.4 (2024-01-07) + +### Fix + +- Update KingstonUponThamesCouncil.py + +## 0.56.3 (2024-01-07) + +### Fix + +- headless options +- #542 - Selenium Grid Sessions must be terminated cleanly +- #542 - Selenium Grid Sessions must be terminated cleanly + +## 0.56.2 (2024-01-07) + +### Fix + +- Update strings.json +- Update en.json +- Update config_flow.py + +## 0.56.1 (2024-01-07) + +### Fix + +- Update common.py + ## 0.156.0 (2025-10-11) ### Feat diff --git a/custom_components/uk_bin_collection/const.py b/custom_components/uk_bin_collection/const.py index 9d030ffd17..b17a5382e0 100644 --- a/custom_components/uk_bin_collection/const.py +++ b/custom_components/uk_bin_collection/const.py @@ -4,7 +4,7 @@ from homeassistant.const import Platform -INPUT_JSON_URL = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.156.0/uk_bin_collection/tests/input.json" +INPUT_JSON_URL = "https://raw.githubusercontent.com/robbrad/UKBinCollectionData/0.157.0/uk_bin_collection/tests/input.json" DEFAULT_NAME = "UK Bin Collection Data" diff --git a/custom_components/uk_bin_collection/manifest.json b/custom_components/uk_bin_collection/manifest.json index c44eb254c7..3b4cd690c9 100644 --- a/custom_components/uk_bin_collection/manifest.json +++ b/custom_components/uk_bin_collection/manifest.json @@ -9,7 +9,7 @@ "integration_type": "service", "iot_class": "cloud_polling", "issue_tracker": "https://github.com/robbrad/UKBinCollectionData/issues", - "requirements": ["uk-bin-collection>=0.156.0"], - "version": "0.156.0", + "requirements": ["uk-bin-collection>=0.157.0"], + "version": "0.157.0", "zeroconf": [] } diff --git a/pyproject.toml b/pyproject.toml index 4a989087f2..64640c400a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "uk_bin_collection" -version = "0.156.0" +version = "0.157.0" description = "Python Lib to collect UK Bin Data" readme = "README.md" authors = ["Robert Bradley "] From 692596ddd75e5dd80e79b02062db06d70143db65 Mon Sep 17 00:00:00 2001 From: Robert Bradley Date: Sat, 11 Oct 2025 03:26:14 +0100 Subject: [PATCH 184/425] feat: workflow overhaul --- .github/workflows/behave_pull_request.yml | 2 +- .github/workflows/behave_schedule.yml | 2 +- .github/workflows/bump.yml | 31 ++- .github/workflows/docker-image.yml | 2 +- .github/workflows/hacs_validation.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 36 ++- .github/workflows/rollback-release.yml | 117 +++++++++ .github/workflows/validate-release-ready.yml | 11 +- .github/workflows/wiki.yml | 2 +- .gitignore | 5 +- docs/RELEASE-SETUP-SUMMARY.md | 136 ++++++++++ docs/deploy-key-setup.md | 115 +++++++++ docs/example_council.md | 62 +++++ docs/github-app-setup.md | 173 +++++++++++++ docs/github-app-troubleshooting.md | 146 +++++++++++ docs/manual-tag-fix.md | 40 +++ docs/release-quick-reference.md | 95 +++++++ docs/release-workflow-branch-protection.md | 0 docs/release-workflow-diagram.md | 181 ++++++++++++++ docs/release-workflow-fixes.md | 201 +++++++++++++++ docs/release-workflow-migration.md | 177 +++++++++++++ docs/release-workflow-setup-checklist.md | 247 ++++++++++++++++++ docs/release-workflow.md | 202 +++++++++++++++ docs/rollback-release.md | 209 ++++++++++++++++ docs/utilities.md | 206 +++++++++++++++ docs/workflow-improvements-summary.md | 248 +++++++++++++++++++ 27 files changed, 2637 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/rollback-release.yml create mode 100644 docs/RELEASE-SETUP-SUMMARY.md create mode 100644 docs/deploy-key-setup.md create mode 100644 docs/example_council.md create mode 100644 docs/github-app-setup.md create mode 100644 docs/github-app-troubleshooting.md create mode 100644 docs/manual-tag-fix.md create mode 100644 docs/release-quick-reference.md create mode 100644 docs/release-workflow-branch-protection.md create mode 100644 docs/release-workflow-diagram.md create mode 100644 docs/release-workflow-fixes.md create mode 100644 docs/release-workflow-migration.md create mode 100644 docs/release-workflow-setup-checklist.md create mode 100644 docs/release-workflow.md create mode 100644 docs/rollback-release.md create mode 100644 docs/utilities.md create mode 100644 docs/workflow-improvements-summary.md diff --git a/.github/workflows/behave_pull_request.yml b/.github/workflows/behave_pull_request.yml index ec3f331629..94cf1afc28 100644 --- a/.github/workflows/behave_pull_request.yml +++ b/.github/workflows/behave_pull_request.yml @@ -1,4 +1,4 @@ -name: Test Councils (Pull Request Only) +name: PR - Test Councils on: workflow_dispatch: diff --git a/.github/workflows/behave_schedule.yml b/.github/workflows/behave_schedule.yml index d721bad559..376e8d0b2e 100644 --- a/.github/workflows/behave_schedule.yml +++ b/.github/workflows/behave_schedule.yml @@ -1,4 +1,4 @@ -name: Test Councils (Nightly Full Run) +name: Scheduled - Test All Councils on: workflow_dispatch: diff --git a/.github/workflows/bump.yml b/.github/workflows/bump.yml index d47779b952..9952bb1518 100644 --- a/.github/workflows/bump.yml +++ b/.github/workflows/bump.yml @@ -1,4 +1,4 @@ -name: Bump Version +name: Release - Bump Version on: push: @@ -29,6 +29,15 @@ jobs: uses: actions/setup-python@v6 with: python-version: '3.12' + cache: 'pip' + + - name: Cache Commitizen + uses: actions/cache@v4 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-commitizen-${{ hashFiles('**/pyproject.toml') }} + restore-keys: | + ${{ runner.os }}-pip-commitizen- - name: Install Commitizen run: pip install commitizen @@ -41,10 +50,30 @@ jobs: - name: Bump version and create tag id: bump run: | + # Check if there are commits to bump + if cz bump --yes --changelog --dry-run 2>&1 | grep -q "No commits found"; then + echo "No version bump needed - no conventional commits since last release" + echo "skip=true" >> $GITHUB_OUTPUT + exit 0 + fi cz bump --yes --changelog echo "version=$(cz version --project)" >> $GITHUB_OUTPUT + echo "skip=false" >> $GITHUB_OUTPUT - name: Push changes and tags + if: steps.bump.outputs.skip != 'true' run: | git push origin master git push origin --tags + + - name: Create workflow summary + if: always() + run: | + echo "## Bump Summary" >> $GITHUB_STEP_SUMMARY + if [ "${{ steps.bump.outputs.skip }}" == "true" ]; then + echo "- **Status**: ⏭️ Skipped (no conventional commits)" >> $GITHUB_STEP_SUMMARY + else + echo "- **Status**: ✅ Success" >> $GITHUB_STEP_SUMMARY + echo "- **New Version**: ${{ steps.bump.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "- **Tag Created**: ${{ steps.bump.outputs.version }}" >> $GITHUB_STEP_SUMMARY + fi diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 7202415082..5c835474dc 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -1,4 +1,4 @@ -name: Docker Image CI +name: Build - Docker Image on: push: diff --git a/.github/workflows/hacs_validation.yml b/.github/workflows/hacs_validation.yml index e588313800..ba3b7c84bc 100644 --- a/.github/workflows/hacs_validation.yml +++ b/.github/workflows/hacs_validation.yml @@ -1,4 +1,4 @@ -name: Validate with hassfest +name: PR - Validate HACS on: push: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 39dfc7fbcc..30385e4bad 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,4 +1,4 @@ -name: Lint Commit Message +name: PR - Lint Commit Messages on: push: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2ed8ea1520..67dda28ec5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -name: Publish Release +name: Release - Publish to PyPI on: push: @@ -19,12 +19,21 @@ jobs: uses: actions/setup-python@v6 with: python-version: '3.12' + cache: 'pip' - name: Install Poetry uses: abatilo/actions-poetry@v4.0.0 with: poetry-version: '1.8.4' + - name: Cache Poetry dependencies + uses: actions/cache@v4 + with: + path: ~/.cache/pypoetry + key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }} + restore-keys: | + ${{ runner.os }}-poetry- + - name: Set release version run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV @@ -48,6 +57,27 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} - name: Publish to PyPI + uses: nick-fields/retry@v3 + with: + timeout_minutes: 5 + max_attempts: 3 + retry_wait_seconds: 30 + command: | + poetry config pypi-token.pypi "${{ secrets.PYPI_API_KEY }}" + poetry publish + + - name: Create workflow summary + if: always() run: | - poetry config pypi-token.pypi "${{ secrets.PYPI_API_KEY }}" - poetry publish + echo "## Release Summary" >> $GITHUB_STEP_SUMMARY + echo "- **Version**: ${{ env.RELEASE_VERSION }}" >> $GITHUB_STEP_SUMMARY + echo "- **Status**: ${{ job.status }}" >> $GITHUB_STEP_SUMMARY + if [ "${{ job.status }}" == "success" ]; then + echo "- **PyPI**: https://pypi.org/project/uk-bin-collection/${{ env.RELEASE_VERSION }}/" >> $GITHUB_STEP_SUMMARY + echo "- **GitHub Release**: https://github.com/${{ github.repository }}/releases/tag/${{ env.RELEASE_VERSION }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "✅ Release published successfully!" >> $GITHUB_STEP_SUMMARY + else + echo "" >> $GITHUB_STEP_SUMMARY + echo "❌ Release failed - check logs above" >> $GITHUB_STEP_SUMMARY + fi diff --git a/.github/workflows/rollback-release.yml b/.github/workflows/rollback-release.yml new file mode 100644 index 0000000000..6509ee7e2e --- /dev/null +++ b/.github/workflows/rollback-release.yml @@ -0,0 +1,117 @@ +name: Release - Rollback + +on: + workflow_dispatch: + inputs: + version: + description: 'Version to rollback (e.g., 0.155.0)' + required: true + type: string + delete_pypi: + description: 'Also yank from PyPI? (cannot delete, only yank)' + required: false + type: boolean + default: false + +jobs: + rollback: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + ssh-key: ${{ secrets.DEPLOY_KEY }} + + - name: Validate version format + run: | + if ! [[ "${{ inputs.version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "❌ Invalid version format. Use X.Y.Z (e.g., 0.155.0)" + exit 1 + fi + echo "✅ Version format valid: ${{ inputs.version }}" + + - name: Check if release exists + id: check + run: | + if gh release view ${{ inputs.version }} > /dev/null 2>&1; then + echo "exists=true" >> $GITHUB_OUTPUT + echo "✅ Release ${{ inputs.version }} exists" + else + echo "exists=false" >> $GITHUB_OUTPUT + echo "⚠️ Release ${{ inputs.version }} not found" + fi + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Delete GitHub Release + if: steps.check.outputs.exists == 'true' + run: | + echo "🗑️ Deleting GitHub release ${{ inputs.version }}..." + gh release delete ${{ inputs.version }} --yes + echo "✅ GitHub release deleted" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Delete Git Tag + run: | + if git rev-parse ${{ inputs.version }} >/dev/null 2>&1; then + echo "🗑️ Deleting git tag ${{ inputs.version }}..." + git push origin :refs/tags/${{ inputs.version }} + echo "✅ Git tag deleted" + else + echo "⚠️ Tag ${{ inputs.version }} not found locally" + fi + + - name: Setup Python (if PyPI yank requested) + if: inputs.delete_pypi == true + uses: actions/setup-python@v6 + with: + python-version: '3.12' + + - name: Install Poetry (if PyPI yank requested) + if: inputs.delete_pypi == true + uses: abatilo/actions-poetry@v4.0.0 + with: + poetry-version: '1.8.4' + + - name: Yank from PyPI + if: inputs.delete_pypi == true + run: | + echo "⚠️ Yanking version ${{ inputs.version }} from PyPI..." + echo "Note: This marks the release as unsuitable for installation but doesn't delete it" + poetry config pypi-token.pypi "${{ secrets.PYPI_API_KEY }}" + # PyPI doesn't support yanking via poetry directly, need to use twine + pip install twine + # Note: You'll need to manually yank via PyPI web interface or use: + # twine upload --repository pypi --skip-existing dist/* + echo "⚠️ PyPI yanking must be done manually at: https://pypi.org/manage/project/uk-bin-collection/releases/" + echo "Go to the release and click 'Options' -> 'Yank release'" + + - name: Create workflow summary + if: always() + run: | + echo "## Rollback Summary" >> $GITHUB_STEP_SUMMARY + echo "- **Version**: ${{ inputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "- **GitHub Release**: ${{ steps.check.outputs.exists == 'true' && '✅ Deleted' || '⚠️ Not found' }}" >> $GITHUB_STEP_SUMMARY + echo "- **Git Tag**: Deleted from remote" >> $GITHUB_STEP_SUMMARY + if [ "${{ inputs.delete_pypi }}" == "true" ]; then + echo "- **PyPI**: ⚠️ Manual yank required" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Next Steps for PyPI" >> $GITHUB_STEP_SUMMARY + echo "1. Go to https://pypi.org/manage/project/uk-bin-collection/releases/" >> $GITHUB_STEP_SUMMARY + echo "2. Find version ${{ inputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "3. Click 'Options' -> 'Yank release'" >> $GITHUB_STEP_SUMMARY + fi + echo "" >> $GITHUB_STEP_SUMMARY + echo "### ⚠️ Important Notes" >> $GITHUB_STEP_SUMMARY + echo "- The version bump commit still exists in git history" >> $GITHUB_STEP_SUMMARY + echo "- To fully rollback, you may need to revert the bump commit" >> $GITHUB_STEP_SUMMARY + echo "- Users who already installed this version will keep it" >> $GITHUB_STEP_SUMMARY + + - name: Notify completion + run: | + echo "✅ Rollback completed for version ${{ inputs.version }}" + echo "Check the summary tab for details" diff --git a/.github/workflows/validate-release-ready.yml b/.github/workflows/validate-release-ready.yml index b4c3ee8199..088d62e114 100644 --- a/.github/workflows/validate-release-ready.yml +++ b/.github/workflows/validate-release-ready.yml @@ -1,4 +1,4 @@ -name: Validate Release Ready +name: PR - Validate Release Ready on: pull_request: @@ -17,6 +17,15 @@ jobs: - uses: actions/setup-python@v6 with: python-version: '3.12' + cache: 'pip' + + - name: Cache Poetry + uses: actions/cache@v4 + with: + path: ~/.local/pipx + key: ${{ runner.os }}-pipx-poetry-1.8.4 + restore-keys: | + ${{ runner.os }}-pipx-poetry- - name: Install Poetry run: pipx install poetry==1.8.4 diff --git a/.github/workflows/wiki.yml b/.github/workflows/wiki.yml index 57a310409d..b298ec4791 100644 --- a/.github/workflows/wiki.yml +++ b/.github/workflows/wiki.yml @@ -1,4 +1,4 @@ -name: Deploy Wiki +name: Deploy - Wiki on: push: diff --git a/.gitignore b/.gitignore index 193e15c91c..a2c3407e68 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ !pytest.ini # Or these folders... +!docs !.github !*.png !.github/ISSUE_TEMPLATE @@ -41,5 +42,5 @@ __pycache__ !TO_BE_CONVERTED !.devcontainer -uk_bin_collection/.DS_Store -uk_bin_collection/uk_bin_collection/.DS_Store +uk_bin_collection/.DS_Store +uk_bin_collection/uk_bin_collection/.DS_Store diff --git a/docs/RELEASE-SETUP-SUMMARY.md b/docs/RELEASE-SETUP-SUMMARY.md new file mode 100644 index 0000000000..0ccabd2c14 --- /dev/null +++ b/docs/RELEASE-SETUP-SUMMARY.md @@ -0,0 +1,136 @@ +# Release Workflow Setup Summary + +## What You Need to Do + +Your release workflow has been updated to use a GitHub App for secure, automated releases. Here's what you need to do to get it working: + +### 1. Create GitHub App (5 minutes) + +Follow the detailed guide: [GitHub App Setup Guide](./github-app-setup.md) + +**Quick steps:** +1. Go to https://github.com/settings/apps/new +2. Fill in: + - Name: `UKBinCollection Release Bot` (or similar unique name) + - Homepage: `https://github.com/robbrad/UKBinCollectionData` + - Uncheck "Webhook Active" + - Permissions: **Contents** = Read and write +3. Click "Create GitHub App" +4. Click "Install App" → Select your repository +5. Generate a private key (downloads a `.pem` file) + +### 2. Add Secrets to Repository (2 minutes) + +Go to: https://github.com/robbrad/UKBinCollectionData/settings/secrets/actions + +Add two secrets: + +**APP_ID** +- Value: The App ID shown at top of app settings (e.g., `123456`) + +**APP_PRIVATE_KEY** +- Value: Entire contents of the `.pem` file +- Include the `-----BEGIN RSA PRIVATE KEY-----` and `-----END RSA PRIVATE KEY-----` lines + +### 3. Test It (5 minutes) + +1. Create a test branch: + ```bash + git checkout -b test/release-workflow + ``` + +2. Make a small change with a conventional commit: + ```bash + echo "# Test" >> README.md + git add README.md + git commit -m "fix: test release workflow" + git push origin test/release-workflow + ``` + +3. Create and merge a PR on GitHub + +4. Watch the workflows run: + - Bump workflow should run and create a tag + - Release workflow should publish to PyPI + +5. Verify: + - Check https://github.com/robbrad/UKBinCollectionData/releases + - Check https://pypi.org/project/uk-bin-collection/ + +## What Changed + +### Workflows Updated +- ✅ `.github/workflows/bump.yml` - Now uses GitHub App token +- ✅ `.github/workflows/release.yml` - Cleaned up +- ✅ `.github/workflows/validate-release-ready.yml` - Simplified + +### Documentation Updated +- ✅ `docs/release-workflow.md` - Main documentation +- ✅ `docs/release-workflow-setup-checklist.md` - Setup checklist +- ✅ `docs/release-quick-reference.md` - Quick reference +- ✅ `docs/github-app-setup.md` - **NEW** - Detailed GitHub App guide +- ✅ `docs/release-workflow-migration.md` - Migration guide + +### Configuration Updated +- ✅ `pyproject.toml` - Enhanced Commitizen config + +## How It Works Now + +``` +1. Developer creates PR with conventional commits (feat:, fix:, etc.) +2. CI validates commits and runs tests +3. PR gets merged to master +4. Bump workflow automatically: + - Analyzes commits with Commitizen + - Updates version in all files + - Updates CHANGELOG.md + - Creates commit and tag + - Pushes to master (using GitHub App to bypass protection) +5. Release workflow automatically: + - Builds package + - Publishes to PyPI + - Creates GitHub release +``` + +**Everything is automated after merge!** + +## Benefits + +✅ **More secure** - GitHub App instead of personal token +✅ **No expiration** - Tokens auto-refresh +✅ **Fully automated** - No manual steps after PR merge +✅ **Version syncing** - Commitizen handles all version files +✅ **Better changelog** - Auto-generated from commits +✅ **Simpler** - Fewer secrets to manage + +## Troubleshooting + +### Bump workflow fails with "Bad credentials" +- Check that `APP_PRIVATE_KEY` includes the full key with BEGIN/END lines +- Verify no extra spaces or line breaks were added + +### Bump workflow fails with "Resource not accessible" +- Verify the GitHub App has "Contents: Read and write" permission +- Check that the app is installed on the repository + +### Release didn't trigger +- Check if the tag was created (look at bump workflow logs) +- Verify the tag follows the format `X.Y.Z` (no `v` prefix) + +## Need Help? + +- 📖 [Full Documentation](./release-workflow.md) +- 🔧 [GitHub App Setup Guide](./github-app-setup.md) +- ✅ [Setup Checklist](./release-workflow-setup-checklist.md) +- ⚡ [Quick Reference](./release-quick-reference.md) + +## Next Steps + +1. ✅ Complete the GitHub App setup +2. ✅ Add the secrets to your repository +3. ✅ Test with a small PR +4. ✅ Monitor the first few releases +5. ✅ Remove old `PERSONAL_ACCESS_TOKEN` if you had one +6. ✅ Update team documentation + +That's it! Your release workflow is now simplified and more secure. diff --git a/docs/deploy-key-setup.md b/docs/deploy-key-setup.md new file mode 100644 index 0000000000..01044df00a --- /dev/null +++ b/docs/deploy-key-setup.md @@ -0,0 +1,115 @@ +# Deploy Key Setup Guide + +Since the GitHub App bypass feature isn't available on your plan, we'll use a deploy key instead. This works on all GitHub plans and can bypass branch protection. + +## Step 1: Generate SSH Key Pair + +On your local machine (Windows), open PowerShell and run: + +```powershell +ssh-keygen -t ed25519 -C "github-actions-deploy-key" -f ukbcd-deploy-key -N "" +``` + +This creates two files: +- `ukbcd-deploy-key` (private key) +- `ukbcd-deploy-key.pub` (public key) + +## Step 2: Add Public Key to Repository + +1. Go to: https://github.com/robbrad/UKBinCollectionData/settings/keys + +2. Click **"Add deploy key"** + +3. Fill in: + - **Title**: `Release Workflow Deploy Key` + - **Key**: Paste the contents of `ukbcd-deploy-key.pub` + - **✅ Allow write access** - IMPORTANT: Check this box! + +4. Click **"Add key"** + +## Step 3: Add Private Key as Secret + +1. Go to: https://github.com/robbrad/UKBinCollectionData/settings/secrets/actions + +2. Click **"New repository secret"** + +3. Fill in: + - **Name**: `DEPLOY_KEY` + - **Value**: Paste the entire contents of `ukbcd-deploy-key` (the private key, not .pub) + +4. Click **"Add secret"** + +## Step 4: Update Branch Protection + +Deploy keys with write access can bypass branch protection automatically, but you need to ensure: + +1. Go to: https://github.com/robbrad/UKBinCollectionData/settings/branch_protection_rules + +2. Edit your master branch rule + +3. Make sure **"Do not allow bypassing the above settings"** is UNCHECKED + - Or if you see **"Include administrators"**, UNCHECK it + +4. Save changes + +## Step 5: Clean Up + +After adding the keys to GitHub, delete the local key files: + +```powershell +Remove-Item ukbcd-deploy-key +Remove-Item ukbcd-deploy-key.pub +``` + +## Step 6: Remove Old Secrets (Optional) + +Since we're not using the GitHub App anymore, you can remove: +- `APP_ID` +- `APP_PRIVATE_KEY` + +Or keep them for future use. + +## Test It + +1. Create a test PR with a conventional commit +2. Merge it +3. Watch the bump workflow run +4. It should now successfully push to master + +## How It Works + +- Deploy keys with write access can push to protected branches +- The SSH key authenticates the workflow +- No need for GitHub App or PAT +- Works on all GitHub plans (Free, Pro, Team, Enterprise) + +## Troubleshooting + +### "Permission denied (publickey)" +- Check that `DEPLOY_KEY` secret contains the private key (not the .pub file) +- Verify the deploy key is added to the repository with write access + +### Still getting "Protected branch update failed" +- Ensure "Allow write access" is checked on the deploy key +- Uncheck "Do not allow bypassing the above settings" in branch protection + +### "Host key verification failed" +- This shouldn't happen with GitHub, but if it does, the workflow will handle it automatically + +## Security Notes + +✅ Deploy key only has access to this one repository +✅ Can be revoked anytime from repository settings +✅ More secure than personal access tokens +✅ Doesn't expire + +## Alternative: Personal Access Token + +If deploy keys don't work, you can use a PAT: + +1. Create token at: https://github.com/settings/tokens/new +2. Select scope: `repo` +3. Add as secret: `PERSONAL_ACCESS_TOKEN` +4. Update workflow to use `token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}` + +But deploy keys are preferred for single-repository automation. diff --git a/docs/example_council.md b/docs/example_council.md new file mode 100644 index 0000000000..0dbae58dca --- /dev/null +++ b/docs/example_council.md @@ -0,0 +1,62 @@ +# Example Council Implementation + +This document shows how to implement a council class using the new utilities. + +## Basic Structure + +```python +from uk_bin_collection.uk_bin_collection.utils.retry import retry +from uk_bin_collection.uk_bin_collection.utils.cache import cached +from uk_bin_collection.uk_bin_collection.utils.http_client import get as http_get +from uk_bin_collection.uk_bin_collection.utils.logger import get_logger + +# Get a logger for this module +logger = get_logger(__name__) + +class ExampleCouncil: + """Example council implementation using the new utilities.""" + + # Required class variables + postcode_required = True + paon_required = True + + def __init__(self, url): + self.url = url + self.postcode = None + self.paon = None + + @cached(ttl=3600) # Cache for 1 hour + @retry(tries=3, delay=1, backoff=2) + def get_data(self): + """Get bin collection data for this council.""" + logger.info(f"Fetching data for postcode {self.postcode}") + + # Construct the URL with parameters + params = { + "postcode": self.postcode, + "house_number": self.paon + } + + # Make the request with automatic retry + response = http_get(self.url, params=params) + + # Process the response + # ... + + # Return the data in the standard format + return [ + { + "type": "General Waste", + "date": "01/01/2023" + }, + { + "type": "Recycling", + "date": "08/01/2023" + } + ] +``` + +## Complete Example + +For a complete example, see the updated council class template: +`/workspaces/UKBinCollectionData/uk_bin_collection/uk_bin_collection/councils/council_class_template/councilclasstemplate.py` \ No newline at end of file diff --git a/docs/github-app-setup.md b/docs/github-app-setup.md new file mode 100644 index 0000000000..0dda37cee5 --- /dev/null +++ b/docs/github-app-setup.md @@ -0,0 +1,173 @@ +# GitHub App Setup Guide + +This guide walks you through creating and configuring a GitHub App to allow the release workflow to bypass branch protection rules. + +## Why Use a GitHub App? + +✅ **More secure** - Fine-grained permissions, not tied to a user account +✅ **No expiration** - Tokens are automatically refreshed +✅ **Better audit trail** - Shows as the app, not a personal account +✅ **Team-friendly** - Won't break if someone leaves the team +✅ **Built-in bypass** - Can push to protected branches + +## Step-by-Step Setup + +### Step 1: Create the GitHub App + +1. **Navigate to GitHub App settings:** + - Personal account: https://github.com/settings/apps/new + - Organization: https://github.com/organizations/YOUR_ORG/settings/apps/new + +2. **Fill in the basic information:** + - **GitHub App name**: `UKBinCollection Release Bot` (must be globally unique) + - If taken, try: `UKBinCollection-Release-Bot-YourUsername` + - **Homepage URL**: `https://github.com/robbrad/UKBinCollectionData` + - **Description** (optional): `Automated release workflow for UK Bin Collection Data` + +3. **Configure webhook:** + - **Uncheck** "Active" under "Webhook" + - We don't need webhooks for this use case + +4. **Set repository permissions:** + - **Contents**: `Read and write` ✅ (Required - to push commits and tags) + - **Metadata**: `Read-only` (Automatically selected) + - **Pull requests**: `Read and write` (Optional - for future features) + +5. **Where can this GitHub App be installed?** + - Select: **"Only on this account"** + +6. **Click "Create GitHub App"** + +### Step 2: Install the App + +1. After creation, you'll see the app settings page +2. Click **"Install App"** in the left sidebar +3. Click **"Install"** next to your account/organization name +4. Choose installation scope: + - Select **"Only select repositories"** + - Check `UKBinCollectionData` +5. Click **"Install"** + +### Step 3: Generate Private Key + +1. Go back to the app settings page (Settings → Developer settings → GitHub Apps → Your App) +2. Scroll down to the **"Private keys"** section +3. Click **"Generate a private key"** +4. A `.pem` file will download automatically +5. **Save this file securely** - you'll need it in the next step + +### Step 4: Get Your App Credentials + +You need two pieces of information: + +#### App ID +- Found at the top of your app settings page +- Example: `123456` +- Copy this number + +#### Private Key +- Open the downloaded `.pem` file in a text editor +- Copy the **entire contents**, including: + ``` + -----BEGIN RSA PRIVATE KEY----- + [long string of characters] + -----END RSA PRIVATE KEY----- + ``` + +### Step 5: Add Secrets to Repository + +1. Go to your repository: https://github.com/robbrad/UKBinCollectionData +2. Navigate to: **Settings → Secrets and variables → Actions** +3. Click **"New repository secret"** + +#### Add APP_ID Secret +- **Name**: `APP_ID` +- **Value**: Your App ID (e.g., `123456`) +- Click "Add secret" + +#### Add APP_PRIVATE_KEY Secret +- **Name**: `APP_PRIVATE_KEY` +- **Value**: Paste the entire contents of the `.pem` file +- **Important**: Include the `-----BEGIN RSA PRIVATE KEY-----` and `-----END RSA PRIVATE KEY-----` lines +- Click "Add secret" + +### Step 6: Verify Setup + +1. The workflow is already configured to use these secrets +2. Test by merging a PR with a conventional commit message +3. Check the bump workflow logs to verify it runs successfully + +## Troubleshooting + +### "Bad credentials" error +- **Cause**: Private key not copied correctly +- **Solution**: Re-copy the entire `.pem` file contents, including BEGIN/END lines + +### "Resource not accessible by integration" error +- **Cause**: App doesn't have correct permissions +- **Solution**: + 1. Go to app settings + 2. Check "Contents" permission is set to "Read and write" + 3. Reinstall the app if needed + +### "App not installed" error +- **Cause**: App not installed on the repository +- **Solution**: Go to https://github.com/settings/installations and install it + +### Workflow still fails with permission error +- **Cause**: Secrets not set correctly +- **Solution**: + 1. Verify `APP_ID` is just the number (no quotes) + 2. Verify `APP_PRIVATE_KEY` includes the full key with headers + 3. Check for extra spaces or line breaks + +## Security Best Practices + +✅ **Never commit** the `.pem` file to your repository +✅ **Store the `.pem` file** securely (password manager or secure vault) +✅ **Rotate keys** if compromised (generate new private key) +✅ **Review app permissions** periodically +✅ **Monitor app activity** in audit logs + +## Managing the App + +### View App Activity +- Go to: Settings → Developer settings → GitHub Apps → Your App +- Click "Advanced" tab to see delivery logs + +### Regenerate Private Key +1. Go to app settings +2. Scroll to "Private keys" +3. Click "Generate a private key" +4. Update the `APP_PRIVATE_KEY` secret in your repository + +### Uninstall/Reinstall +- Go to: https://github.com/settings/installations +- Click "Configure" next to your app +- Adjust repository access or uninstall + +## Alternative: Using a Personal Access Token + +If you prefer not to use a GitHub App, you can use a Personal Access Token instead: + +1. Create a token at: https://github.com/settings/tokens/new +2. Select scope: `repo` (Full control of private repositories) +3. Add as secret: `BUMP_TOKEN` +4. Update workflow to use `${{ secrets.BUMP_TOKEN }}` instead of the app token + +However, GitHub Apps are recommended for production use. + +## Next Steps + +Once setup is complete: +1. ✅ Test the workflow with a small PR +2. ✅ Monitor the first few releases +3. ✅ Document the app for your team +4. ✅ Set up monitoring/alerts for workflow failures + +## Support + +If you encounter issues: +- Check the [troubleshooting section](#troubleshooting) above +- Review workflow logs in GitHub Actions +- See the [main workflow documentation](./release-workflow.md) diff --git a/docs/github-app-troubleshooting.md b/docs/github-app-troubleshooting.md new file mode 100644 index 0000000000..614da98a76 --- /dev/null +++ b/docs/github-app-troubleshooting.md @@ -0,0 +1,146 @@ +# GitHub App Troubleshooting - Branch Protection + +## Problem: "Protected branch update failed" + +If you're getting this error, the GitHub App isn't properly configured to bypass branch protection. + +## Solution Steps + +### Step 1: Verify App Installation + +1. Go to: https://github.com/settings/installations +2. Find your app (e.g., "UKBinCollection Release Bot") +3. Click "Configure" +4. Verify it's installed on `UKBinCollectionData` repository +5. Check that it has "Contents: Read and write" permission + +### Step 2: Configure Branch Protection + +Go to: https://github.com/robbrad/UKBinCollectionData/settings/branches + +Click "Edit" on your `master` branch protection rule. + +#### Option A: Allow App to Bypass (Recommended) + +Scroll to **"Allow specified actors to bypass required pull requests"**: +- Click "Add" +- Search for your app name +- Select it +- Click "Save changes" + +#### Option B: If App Doesn't Appear in Search + +The app might not show up in the bypass list. Instead: + +1. Temporarily disable "Require a pull request before merging" +2. Test the workflow +3. Re-enable after confirming it works + +OR + +1. Under "Restrict who can push to matching branches": + - Enable it + - Add your GitHub App + - This allows the app to push directly + +### Step 3: Verify App Permissions + +Go to your app settings: https://github.com/settings/apps + +1. Click on your app +2. Verify permissions: + - **Contents**: Read and write ✅ + - **Metadata**: Read-only ✅ +3. If permissions are wrong, update them +4. Go to https://github.com/settings/installations +5. Click "Configure" on your app +6. Click "Update" to refresh permissions + +### Step 4: Check Secrets + +Verify secrets are set correctly: + +1. Go to: https://github.com/robbrad/UKBinCollectionData/settings/secrets/actions +2. Verify `APP_ID` exists +3. Verify `APP_PRIVATE_KEY` exists +4. The private key should include: + ``` + -----BEGIN RSA PRIVATE KEY----- + [key content] + -----END RSA PRIVATE KEY----- + ``` + +### Step 5: Test the Workflow + +Create a test PR and merge it to see if it works. + +## Alternative: Temporary Workaround + +If you need to release immediately while fixing the app setup: + +### Manual Release + +```bash +# 1. Pull latest +git checkout master +git pull + +# 2. Bump version +cz bump --yes --changelog + +# 3. Push (you'll need to temporarily disable branch protection) +git push origin master --follow-tags +``` + +### Or Use Personal Access Token Temporarily + +Update `.github/workflows/bump.yml`: + +```yaml +- name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} +``` + +Then: +1. Create a PAT at: https://github.com/settings/tokens/new +2. Select scope: `repo` +3. Add as secret: `PERSONAL_ACCESS_TOKEN` +4. This will work until you fix the app setup + +## Common Issues + +### "Bad credentials" +- Private key not copied correctly +- Missing BEGIN/END lines +- Extra spaces or line breaks + +### "Resource not accessible by integration" +- App doesn't have correct permissions +- App not installed on repository +- Need to update app permissions + +### "App not found" +- APP_ID is incorrect +- App was deleted or renamed + +## Still Not Working? + +If none of the above works, you have two options: + +### Option 1: Use Personal Access Token (Quick Fix) +See "Alternative: Temporary Workaround" above + +### Option 2: Remove Branch Protection for Automation +1. Create a separate branch protection rule +2. Exclude `github-actions[bot]` from restrictions +3. This allows the workflow to push + +## Need More Help? + +Check the workflow logs for the exact error message and search for it in: +- GitHub Actions documentation +- GitHub App documentation +- Stack Overflow diff --git a/docs/manual-tag-fix.md b/docs/manual-tag-fix.md new file mode 100644 index 0000000000..b8941febcb --- /dev/null +++ b/docs/manual-tag-fix.md @@ -0,0 +1,40 @@ +# Manual Tag Fix for Version 0.155.0 + +Since the tag wasn't pushed, you need to manually create and push it to trigger the release. + +## Steps: + +1. **Pull the latest changes:** + ```bash + git checkout master + git pull + ``` + +2. **Create the annotated tag:** + ```bash + git tag -a 0.155.0 -m "Release 0.155.0" + ``` + +3. **Push the tag:** + ```bash + git push origin 0.155.0 + ``` + +4. **Verify:** + - Check tags: https://github.com/robbrad/UKBinCollectionData/tags + - Watch the release workflow: https://github.com/robbrad/UKBinCollectionData/actions + +## What Was Fixed + +1. **bump.yml** - Changed from `--follow-tags` to separate push commands +2. **pyproject.toml** - Added `annotated_tag = true` to Commitizen config + +## Future Releases + +The next merge will automatically: +1. Create annotated tag +2. Push commit and tag separately +3. Trigger release workflow +4. Publish to PyPI + +No manual intervention needed! diff --git a/docs/release-quick-reference.md b/docs/release-quick-reference.md new file mode 100644 index 0000000000..f501e82826 --- /dev/null +++ b/docs/release-quick-reference.md @@ -0,0 +1,95 @@ +# Release Workflow Quick Reference + +## Commit Message Cheat Sheet + +| Type | Version Bump | Example | +|------|--------------|---------| +| `feat:` | Minor (0.152.0 → 0.153.0) | `feat(councils): add Leeds support` | +| `fix:` | Patch (0.152.0 → 0.152.1) | `fix(selenium): handle timeout` | +| `feat!:` or `BREAKING CHANGE:` | Major (0.152.0 → 1.0.0) | `feat!: change API format` | +| `docs:` | None | `docs: update README` | +| `style:` | None | `style: format code` | +| `refactor:` | None | `refactor: simplify parser` | +| `test:` | None | `test: add unit tests` | +| `chore:` | None | `chore: update dependencies` | + +## Workflow Stages + +``` +PR → Tests → Merge → Bump (auto) → Tag (auto) → Release (auto) → PyPI (auto) +``` + +Everything after merge is fully automated! + +## How It Works + +1. **Developer**: Create PR with conventional commits +2. **CI**: Validates commits and runs tests +3. **Merge**: PR merged to master +4. **Commitizen**: Analyzes commits, bumps version, updates CHANGELOG +5. **Git**: Creates tag and pushes +6. **Release**: Publishes to PyPI and GitHub + +## Common Commands + +```bash +# Check current version +poetry version -s + +# Validate before PR +make pre-build + +# Run tests locally +make unit-tests +make integration-tests + +# Check commit messages +git log --oneline + +# Manual bump (if needed) +cz bump --yes --changelog +git push origin master --follow-tags +``` + +## Troubleshooting + +| Problem | Solution | +|---------|----------| +| Version bump didn't happen | Check commit message format (must use `feat:` or `fix:`) | +| Release didn't trigger | Check if tag was created in bump workflow logs | +| PyPI publish failed | Verify `PYPI_API_KEY` secret is set | +| Permission error | Verify `APP_ID` and `APP_PRIVATE_KEY` secrets are set | +| Version files out of sync | Commitizen handles this automatically | + +## Required Secrets + +- `APP_ID` - GitHub App ID (for protected branches) +- `APP_PRIVATE_KEY` - GitHub App private key +- `PYPI_API_KEY` - For PyPI publishing +- `CODECOV_TOKEN` - For test coverage (optional) + +**Note:** Uses GitHub App for secure, non-expiring authentication + +## Workflow Files + +- `behave_pull_request.yml` - PR tests +- `lint.yml` - Commit message validation +- `validate-release-ready.yml` - Pre-merge checks +- `bump.yml` - Automated version bumping and tagging +- `release.yml` - Publishing to PyPI and GitHub + +## Version Files (Auto-Synced) + +Commitizen automatically updates: +- `pyproject.toml` +- `custom_components/uk_bin_collection/manifest.json` +- `custom_components/uk_bin_collection/const.py` +- `CHANGELOG.md` + +## Quick Links + +- [Full Documentation](./release-workflow.md) +- [Setup Checklist](./release-workflow-setup-checklist.md) +- [Conventional Commits](https://www.conventionalcommits.org/) +- [Semantic Versioning](https://semver.org/) +- [Commitizen](https://commitizen-tools.github.io/commitizen/) diff --git a/docs/release-workflow-branch-protection.md b/docs/release-workflow-branch-protection.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/release-workflow-diagram.md b/docs/release-workflow-diagram.md new file mode 100644 index 0000000000..29f5dfa3fc --- /dev/null +++ b/docs/release-workflow-diagram.md @@ -0,0 +1,181 @@ +# Release Workflow Diagram + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ PULL REQUEST STAGE │ +└─────────────────────────────────────────────────────────────────────────────┘ + + Developer creates PR → master + ↓ + ┌───────────────────────────────────────────────────────────┐ + │ Automated Checks Run in Parallel: │ + │ │ + │ ✓ behave_pull_request.yml │ + │ - Unit tests (Python 3.12) │ + │ - Integration tests (changed councils only) │ + │ - Parity check (councils/input.json/features) │ + │ │ + │ ✓ lint.yml │ + │ - Validate conventional commit messages │ + │ │ + │ ✓ validate-release-ready.yml │ + │ - Check version file consistency │ + │ - Validate pyproject.toml │ + │ - Verify commitizen config │ + │ │ + │ ✓ hacs_validation.yml │ + │ - Validate Home Assistant integration │ + │ │ + │ ✓ codeql-analysis.yml │ + │ - Security scanning │ + └───────────────────────────────────────────────────────────┘ + ↓ + All checks pass? → YES → Ready to merge + ↓ NO + Fix issues and push updates + + +┌─────────────────────────────────────────────────────────────────────────────┐ +│ MERGE TO MASTER STAGE │ +└─────────────────────────────────────────────────────────────────────────────┘ + + PR merged to master + ↓ + ┌───────────────────────────────────────────────────────────┐ + │ bump.yml workflow triggers │ + │ │ + │ 1. Commitizen analyzes commits since last tag │ + │ - feat: → minor bump (0.152.0 → 0.153.0) │ + │ - fix: → patch bump (0.152.0 → 0.152.1) │ + │ - BREAKING CHANGE → major bump (0.152.0 → 1.0.0) │ + │ │ + │ 2. Updates version in: │ + │ - pyproject.toml │ + │ - manifest.json │ + │ - const.py │ + │ │ + │ 3. Creates commit: "bump: version X.Y.Z" │ + │ │ + │ 4. Creates git tag: X.Y.Z │ + │ │ + │ 5. Pushes commit and tag to master │ + └───────────────────────────────────────────────────────────┘ + ↓ + Tag pushed → Triggers release workflow + + +┌─────────────────────────────────────────────────────────────────────────────┐ +│ RELEASE STAGE │ +└─────────────────────────────────────────────────────────────────────────────┘ + + Tag X.Y.Z pushed + ↓ + ┌───────────────────────────────────────────────────────────┐ + │ release.yml workflow triggers │ + │ │ + │ 1. Checkout tagged commit │ + │ │ + │ 2. Install Poetry and dependencies │ + │ │ + │ 3. Verify version matches tag │ + │ (Poetry version == Git tag) │ + │ │ + │ 4. Build Python package │ + │ poetry build → creates dist/*.whl and dist/*.tar.gz │ + │ │ + │ 5. Create GitHub Release │ + │ - Auto-generated release notes │ + │ - Attach build artifacts │ + │ │ + │ 6. Publish to PyPI │ + │ poetry publish → uploads to pypi.org │ + └───────────────────────────────────────────────────────────┘ + ↓ + ┌───────────────────────────────────────────────────────────┐ + │ Release Complete! ✓ │ + │ │ + │ - GitHub Release: github.com/robbrad/.../releases │ + │ - PyPI Package: pypi.org/project/uk-bin-collection │ + │ - HACS Update: Available to Home Assistant users │ + └───────────────────────────────────────────────────────────┘ + + +┌─────────────────────────────────────────────────────────────────────────────┐ +│ COMMIT MESSAGE EXAMPLES │ +└─────────────────────────────────────────────────────────────────────────────┘ + +feat(councils): add Birmingham City Council support +→ Minor version bump (0.152.0 → 0.153.0) + +fix(selenium): handle connection timeout errors +→ Patch version bump (0.152.0 → 0.152.1) + +feat(api)!: change date format to ISO 8601 + +BREAKING CHANGE: API responses now use ISO 8601 dates +→ Major version bump (0.152.0 → 1.0.0) + +docs: update README with new council instructions +→ No version bump (documentation only) + + +┌─────────────────────────────────────────────────────────────────────────────┐ +│ TROUBLESHOOTING FLOW │ +└─────────────────────────────────────────────────────────────────────────────┘ + +Issue: Version bump didn't happen + ↓ +Check: Are commits using conventional format? + ↓ NO → Fix commit messages and force push + ↓ YES +Check: Is PERSONAL_ACCESS_TOKEN set? + ↓ NO → Add secret in repo settings + ↓ YES +Check: Bump workflow logs for errors + ↓ +Manual fix: Run commitizen locally + +───────────────────────────────────────────── + +Issue: Release didn't publish + ↓ +Check: Was tag created by bump workflow? + ↓ NO → Check bump workflow logs + ↓ YES +Check: Is PYPI_API_KEY valid? + ↓ NO → Update secret in repo settings + ↓ YES +Check: Release workflow logs for errors + ↓ +Manual fix: Run poetry publish locally + + +┌─────────────────────────────────────────────────────────────────────────────┐ +│ WORKFLOW DEPENDENCIES │ +└─────────────────────────────────────────────────────────────────────────────┘ + +Secrets Required: +├── PERSONAL_ACCESS_TOKEN (for bump workflow) +│ └── Needs: repo write access +│ +├── PYPI_API_KEY (for release workflow) +│ └── Needs: PyPI project upload permissions +│ +└── CODECOV_TOKEN (for test coverage) + └── Needs: Codecov project access + +Environments: +├── bump (requires approval) +└── release (requires approval) + +Configuration Files: +├── pyproject.toml +│ ├── [tool.poetry] version +│ └── [tool.commitizen] config +│ +├── custom_components/uk_bin_collection/manifest.json +│ └── version field +│ +└── custom_components/uk_bin_collection/const.py + └── INPUT_JSON_URL (includes version) +``` diff --git a/docs/release-workflow-fixes.md b/docs/release-workflow-fixes.md new file mode 100644 index 0000000000..238ed485c7 --- /dev/null +++ b/docs/release-workflow-fixes.md @@ -0,0 +1,201 @@ +# Release Workflow Fixes Applied + +## Summary +Fixed the release workflow to ensure proper version bumping and release publishing from PR merge to master. + +## Issues Identified + +1. **Bump workflow wasn't explicitly pushing tags** + - Commitizen action needed `push: true` parameter + - No confirmation output of version bump + +2. **Release workflow lacked validation** + - No verification that Poetry version matches git tag + - Could publish mismatched versions + +3. **Missing pre-merge validation** + - No check that version files are in sync before merge + - Could lead to failed releases + +4. **Inconsistent Poetry installation** + - Some workflows used `abatilo/actions-poetry` + - Others used `pipx install poetry` + +## Changes Made + +### 1. Updated `.github/workflows/bump.yml` +**Changes:** +- Added explicit Poetry installation for consistency +- Added `push: true` to commitizen action to ensure tags are pushed +- Added version output for debugging + +**Why:** Ensures tags are created and pushed, triggering the release workflow + +### 2. Updated `.github/workflows/release.yml` +**Changes:** +- Added `fetch-depth: 0` to checkout for full git history +- Renamed "Run image" step to "Install Poetry" for clarity +- Added version verification step to ensure Poetry version matches tag +- Split build and publish into separate steps +- Added build artifacts to GitHub release + +**Why:** Prevents publishing mismatched versions and improves reliability + +### 3. Created `.github/workflows/validate-release-ready.yml` +**New workflow that:** +- Validates `pyproject.toml` syntax +- Checks version consistency across files +- Validates commitizen configuration +- Runs on every PR + +**Why:** Catches version sync issues before merge, preventing failed releases + +### 4. Created `docs/release-workflow.md` +**Comprehensive documentation covering:** +- Complete workflow stages (PR → Merge → Release) +- Commit message format and examples +- Version numbering strategy +- Troubleshooting guide +- Manual release procedure +- Required secrets and environments + +**Why:** Provides clear guidance for contributors and maintainers + +### 5. Created `docs/release-workflow-diagram.md` +**Visual documentation showing:** +- ASCII flow diagrams for each stage +- Parallel workflow execution +- Decision points and error handling +- Commit message examples with version impacts +- Troubleshooting flows +- Dependency tree + +**Why:** Makes the workflow easy to understand at a glance + +## Complete Workflow Flow + +``` +1. Developer creates PR + ↓ +2. Automated tests run (behave, lint, validate) + ↓ +3. PR approved and merged to master + ↓ +4. bump.yml triggers: + - Analyzes commits + - Bumps version in all files + - Creates commit "bump: version X.Y.Z" + - Creates and pushes tag X.Y.Z + ↓ +5. release.yml triggers (on tag push): + - Verifies version matches tag + - Builds package + - Creates GitHub release + - Publishes to PyPI + ↓ +6. Release complete! +``` + +## Testing the Workflow + +### Test 1: Version Validation +```bash +# Should pass if versions are in sync +poetry check +jq -r '.version' custom_components/uk_bin_collection/manifest.json +poetry version -s +``` + +### Test 2: Commit Message Format +```bash +# Valid examples: +git commit -m "feat(councils): add new council" +git commit -m "fix(selenium): handle timeout" +git commit -m "docs: update README" + +# Invalid examples (will fail lint): +git commit -m "added new feature" +git commit -m "Fixed bug" +``` + +### Test 3: Manual Version Bump (if needed) +```bash +# Bump version +poetry version patch # or minor/major + +# Update manifest +# Edit custom_components/uk_bin_collection/manifest.json + +# Commit and tag +git add . +git commit -m "bump: version X.Y.Z" +git tag X.Y.Z +git push origin master --tags +``` + +## Required Secrets + +Ensure these are set in GitHub repository settings: + +1. **PERSONAL_ACCESS_TOKEN** + - Settings → Secrets → Actions + - Needs: `repo` scope + - Used by: bump.yml + +2. **PYPI_API_KEY** + - Settings → Secrets → Actions + - Get from: pypi.org account settings + - Used by: release.yml + +3. **CODECOV_TOKEN** + - Settings → Secrets → Actions + - Get from: codecov.io project settings + - Used by: test workflows + +## Verification Checklist + +After merging these changes: + +- [ ] Verify `PERSONAL_ACCESS_TOKEN` secret is set +- [ ] Verify `PYPI_API_KEY` secret is set +- [ ] Verify `CODECOV_TOKEN` secret is set +- [ ] Check bump and release environments exist +- [ ] Test with a small PR using conventional commits +- [ ] Monitor bump workflow creates tag +- [ ] Monitor release workflow publishes to PyPI +- [ ] Verify GitHub release is created +- [ ] Check PyPI package is available + +## Rollback Plan + +If issues occur: + +1. **Disable automatic bumping:** + - Add `[skip ci]` to commit messages + - Or temporarily disable bump.yml workflow + +2. **Manual release:** + - Follow manual release procedure in docs/release-workflow.md + - Use `poetry version` and `poetry publish` directly + +3. **Revert workflow changes:** + - Git revert the workflow file changes + - Return to previous manual process + +## Next Steps + +1. Merge these workflow fixes to master +2. Test with a small feature PR +3. Monitor the complete flow +4. Update team documentation if needed +5. Consider adding release notifications (Slack, Discord, etc.) + +## Additional Improvements (Future) + +Consider adding: +- Slack/Discord notifications on release +- Automated changelog generation +- Release candidate (RC) workflow for testing +- Automated rollback on failed releases +- Release metrics and monitoring +- Pre-release testing environment diff --git a/docs/release-workflow-migration.md b/docs/release-workflow-migration.md new file mode 100644 index 0000000000..1929c79b60 --- /dev/null +++ b/docs/release-workflow-migration.md @@ -0,0 +1,177 @@ +# Release Workflow Migration Guide + +## Overview + +The release workflow has been simplified to use Commitizen and GITHUB_TOKEN, eliminating complexity and manual steps. + +## What Changed + +### Before (Complex) +- Bump workflow created PRs for version bumps +- Required PERSONAL_ACCESS_TOKEN secret +- Manual PR review and merge for version bumps +- Separate environments with approvals +- Manual version file syncing +- Multiple validation steps + +### After (Simplified) +- Bump workflow directly commits and tags on master +- Uses built-in GITHUB_TOKEN +- Fully automated after PR merge +- No environment approvals needed +- Commitizen auto-syncs all version files +- Streamlined validation + +## Key Improvements + +### 1. Removed PERSONAL_ACCESS_TOKEN Requirement +- **Before**: Required creating and managing a personal access token +- **After**: Uses built-in `GITHUB_TOKEN` with proper permissions +- **Benefit**: One less secret to manage and rotate + +### 2. Eliminated Bump PRs +- **Before**: Bump workflow created a PR that needed manual merge +- **After**: Bump workflow directly commits to master after PR merge +- **Benefit**: Faster releases, no manual intervention + +### 3. Automatic Version Syncing +- **Before**: Manual checks to ensure version files stayed in sync +- **After**: Commitizen automatically updates all configured files +- **Benefit**: No version mismatch errors + +### 4. Simplified Configuration +- **Before**: Complex environment setup with approvals +- **After**: Simple workflow permissions +- **Benefit**: Easier to set up and maintain + +### 5. Better CHANGELOG Management +- **Before**: Manual or semi-automated changelog updates +- **After**: Commitizen automatically generates changelog from commits +- **Benefit**: Consistent, automated changelog + +## Migration Steps + +### 1. Set Up GitHub App +Follow the [GitHub App Setup Guide](./github-app-setup.md) to: +- Create a GitHub App +- Install it on your repository +- Generate a private key +- Add `APP_ID` and `APP_PRIVATE_KEY` secrets + +### 2. Update Secrets +```bash +# Add (new requirements) ++ APP_ID ++ APP_PRIVATE_KEY + +# Keep (still required) +- PYPI_API_KEY +- CODECOV_TOKEN (optional) + +# Remove (no longer needed) +- PERSONAL_ACCESS_TOKEN (if you had one) +``` + +### 3. Update Workflow Permissions +Ensure Settings → Actions → General → Workflow permissions: +- Select "Read and write permissions" +- Enable "Allow GitHub Actions to create and approve pull requests" + +### 4. Remove Environments (Optional) +The `bump` and `release` environments are no longer required, but can be kept if you want manual approval gates. + +### 5. Update pyproject.toml +Ensure Commitizen configuration includes: +```toml +[tool.commitizen] +name = "cz_conventional_commits" +version_provider = "poetry" +version_scheme = "semver" +major_version_zero = true +tag_format = "$version" +update_changelog_on_bump = true +version_files = [ + "custom_components/uk_bin_collection/manifest.json:version", + "custom_components/uk_bin_collection/manifest.json:requirements", + "custom_components/uk_bin_collection/const.py:INPUT_JSON_URL" +] +``` + +### 6. Test the New Workflow +1. Create a test branch with a conventional commit +2. Open and merge a PR +3. Watch the automated bump and release workflows +4. Verify the release appears on PyPI and GitHub + +## Workflow Comparison + +### Old Workflow +``` +PR → Tests → Merge → Bump Workflow → Bump PR → Manual Review → Merge Bump PR → Tag → Release +``` + +### New Workflow +``` +PR → Tests → Merge → Bump (auto) → Tag (auto) → Release (auto) +``` + +## File Changes + +### Modified Files +- `.github/workflows/bump.yml` - Simplified to direct commit/tag +- `.github/workflows/release.yml` - Cleaned up and uses GITHUB_TOKEN +- `.github/workflows/validate-release-ready.yml` - Removed version sync checks +- `pyproject.toml` - Enhanced Commitizen configuration +- `docs/release-workflow.md` - Updated documentation +- `docs/release-workflow-setup-checklist.md` - Simplified checklist +- `docs/release-quick-reference.md` - Updated quick reference + +### New Files +- `docs/release-workflow-migration.md` - This file + +## Rollback Plan + +If you need to rollback to the old workflow: + +1. Revert the workflow files: + ```bash + git revert + ``` + +2. Re-add PERSONAL_ACCESS_TOKEN secret + +3. Recreate bump and release environments + +## Benefits Summary + +✅ Fewer secrets to manage +✅ Faster release process +✅ No manual intervention needed +✅ Automatic version syncing +✅ Better changelog generation +✅ Simpler configuration +✅ Easier to understand and maintain + +## Support + +If you encounter issues: +1. Check workflow logs in GitHub Actions +2. Review `docs/release-workflow.md` +3. Verify GITHUB_TOKEN permissions +4. Ensure conventional commits are used +5. Check Commitizen configuration + +## Next Steps + +1. Review the updated documentation +2. Test the new workflow with a small change +3. Monitor the first few automated releases +4. Update team documentation and training +5. Remove old PERSONAL_ACCESS_TOKEN secret + +## Questions? + +See the full documentation: +- [Release Workflow](./release-workflow.md) +- [Setup Checklist](./release-workflow-setup-checklist.md) +- [Quick Reference](./release-quick-reference.md) diff --git a/docs/release-workflow-setup-checklist.md b/docs/release-workflow-setup-checklist.md new file mode 100644 index 0000000000..51dd7bee7b --- /dev/null +++ b/docs/release-workflow-setup-checklist.md @@ -0,0 +1,247 @@ +# Release Workflow Setup Checklist + +Use this checklist to verify your simplified release workflow is properly configured. + +## GitHub Repository Settings + +### GitHub App Setup (for protected branches) +- [ ] Create a GitHub App + - Go to: https://github.com/settings/apps/new + - Name: `UKBinCollection Release Bot` (must be unique) + - Homepage URL: `https://github.com/robbrad/UKBinCollectionData` + - Uncheck "Active" under Webhook + - Repository permissions: + - **Contents**: Read and write + - **Metadata**: Read-only (auto-selected) + - Where can this be installed: "Only on this account" + - Click "Create GitHub App" + +- [ ] Install the app on your repository + - Click "Install App" in left sidebar + - Click "Install" next to your account + - Select "Only select repositories" + - Choose `UKBinCollectionData` + - Click "Install" + +- [ ] Generate and save credentials + - In app settings, scroll to "Private keys" + - Click "Generate a private key" + - Save the downloaded `.pem` file securely + - Note your **App ID** (shown at top of settings page) + +### Secrets Configuration +- [ ] `APP_ID` is set + - Path: Settings → Secrets and variables → Actions → Repository secrets + - Value: Your GitHub App ID (e.g., `123456`) + +- [ ] `APP_PRIVATE_KEY` is set + - Path: Settings → Secrets and variables → Actions → Repository secrets + - Value: Entire contents of the `.pem` file + - Include the `-----BEGIN RSA PRIVATE KEY-----` and `-----END RSA PRIVATE KEY-----` lines + +- [ ] `PYPI_API_KEY` is set + - Path: Settings → Secrets and variables → Actions → Repository secrets + - Get from: https://pypi.org/manage/account/token/ + - Scope: Project-specific or account-wide + - Test: Should allow publishing packages + +- [ ] `CODECOV_TOKEN` is set (optional) + - Path: Settings → Secrets and variables → Actions → Repository secrets + - Get from: https://codecov.io/gh/robbrad/UKBinCollectionData/settings + - Test: Should allow uploading coverage reports + +### Branch Protection Rules +- [ ] `master` branch is protected + - Path: Settings → Branches → Add rule + - Branch name pattern: `master` + - Recommended settings: + - [x] Require a pull request before merging + - [x] Require status checks to pass before merging + - [x] Require branches to be up to date before merging + +### Actions Permissions +- [ ] Workflows have write permissions + - Path: Settings → Actions → General → Workflow permissions + - Select: "Read and write permissions" + - [x] Allow GitHub Actions to create and approve pull requests + +## Local Configuration + +### pyproject.toml +- [ ] Version is set correctly + ```toml + [tool.poetry] + version = "X.Y.Z" + ``` + +- [ ] Commitizen is configured + ```toml + [tool.commitizen] + name = "cz_conventional_commits" + version_provider = "poetry" + version_scheme = "semver" + major_version_zero = true + tag_format = "$version" + update_changelog_on_bump = true + version_files = [ + "custom_components/uk_bin_collection/manifest.json:version", + "custom_components/uk_bin_collection/manifest.json:requirements", + "custom_components/uk_bin_collection/const.py:INPUT_JSON_URL" + ] + ``` + +## Workflow Files + +### Required Workflows +- [ ] `.github/workflows/behave_pull_request.yml` exists +- [ ] `.github/workflows/lint.yml` exists +- [ ] `.github/workflows/validate-release-ready.yml` exists +- [ ] `.github/workflows/bump.yml` exists (simplified) +- [ ] `.github/workflows/release.yml` exists +- [ ] `.github/workflows/hacs_validation.yml` exists + +### Workflow Configuration +- [ ] `bump.yml` uses `GITHUB_TOKEN` + ```yaml + token: ${{ secrets.GITHUB_TOKEN }} + ``` + +- [ ] `bump.yml` runs `cz bump --yes --changelog` + ```yaml + - name: Bump version and create tag + run: cz bump --yes --changelog + ``` + +- [ ] `release.yml` uses `PYPI_API_KEY` + ```yaml + poetry config pypi-token.pypi "${{ secrets.PYPI_API_KEY }}" + ``` + +## Testing + +### Pre-Merge Testing +- [ ] Create a test branch + ```bash + git checkout -b test/release-workflow + ``` + +- [ ] Make a small change with conventional commit + ```bash + echo "# Test" >> README.md + git add README.md + git commit -m "fix: test release workflow" + ``` + +- [ ] Push and create PR + ```bash + git push origin test/release-workflow + # Create PR on GitHub + ``` + +- [ ] Verify workflows run: + - [ ] `behave_pull_request.yml` runs + - [ ] `lint.yml` runs + - [ ] `validate-release-ready.yml` runs + - [ ] All checks pass + +### Post-Merge Testing +- [ ] Merge the test PR +- [ ] Verify `bump.yml` runs automatically +- [ ] Check workflow logs: + - [ ] Commitizen analyzed commits + - [ ] Version was bumped in all files + - [ ] CHANGELOG.md was updated + - [ ] Commit was created with message `bump: version X.Y.Z` + - [ ] Tag was created and pushed + - [ ] No errors in logs + +- [ ] Verify `release.yml` runs automatically +- [ ] Check workflow logs: + - [ ] Version verification passed + - [ ] Package was built + - [ ] GitHub release was created + - [ ] PyPI publish succeeded + +### Verification +- [ ] Check GitHub releases page + - URL: https://github.com/robbrad/UKBinCollectionData/releases + - Latest release should be visible + - Release notes should be auto-generated + - Build artifacts should be attached + +- [ ] Check PyPI package page + - URL: https://pypi.org/project/uk-bin-collection/ + - Latest version should be available + - Package should be installable: + ```bash + pip install uk-bin-collection==X.Y.Z + ``` + +- [ ] Check version files are synced + ```bash + # All should show the same version + poetry version -s + jq -r '.version' custom_components/uk_bin_collection/manifest.json + grep INPUT_JSON_URL custom_components/uk_bin_collection/const.py + ``` + +## Rollback Plan + +If something goes wrong: + +### Delete Bad Release +```bash +# Delete tag locally +git tag -d X.Y.Z + +# Delete tag remotely +git push origin :refs/tags/X.Y.Z + +# Delete GitHub release manually on GitHub +``` + +### Manual Release +```bash +# Bump version with Commitizen +cz bump --yes --changelog + +# Push changes and tags +git push origin master --follow-tags + +# Or manually build and publish +poetry build +poetry publish +``` + +## Maintenance + +### Regular Checks +- [ ] Quarterly: Verify PYPI_API_KEY hasn't expired +- [ ] Quarterly: Update workflow actions to latest versions +- [ ] Quarterly: Review and update documentation + +### Action Updates +Check for updates to GitHub Actions: +- `actions/checkout@v5` → Check for newer version +- `actions/setup-python@v6` → Check for newer version +- `abatilo/actions-poetry@v4.0.0` → Check for newer version +- `ncipollo/release-action@v1` → Check for newer version + +## Support + +If you encounter issues: + +1. Check workflow logs in GitHub Actions tab +2. Review documentation in `docs/release-workflow.md` +3. Verify GITHUB_TOKEN has write permissions +4. Ensure conventional commits are used +5. Check Commitizen configuration in pyproject.toml + +## Sign-Off + +- [ ] All checklist items completed +- [ ] Test release successful +- [ ] Documentation reviewed + +**Completed by:** _______________ +**Date:** _______________ diff --git a/docs/release-workflow.md b/docs/release-workflow.md new file mode 100644 index 0000000000..728008dccf --- /dev/null +++ b/docs/release-workflow.md @@ -0,0 +1,202 @@ +# Release Workflow Documentation + +## Overview +This document describes the complete release workflow from pull request to published release. + +## Workflow Stages + +### 1. Pull Request Stage +**Triggers:** When a PR is opened/updated targeting `master` branch + +**Workflows that run:** +- `behave_pull_request.yml` - Runs tests on changed councils +- `lint.yml` - Validates commit messages follow conventional commits +- `validate-release-ready.yml` - Validates pyproject.toml and commit messages +- `hacs_validation.yml` - Validates Home Assistant integration + +**What happens:** +- Unit tests run on Python 3.12 +- Integration tests run only for changed council files +- Parity check ensures councils, input.json, and feature files are in sync +- Commit messages are validated against conventional commits format +- pyproject.toml is validated + +**Requirements to merge:** +- All tests must pass +- Commit messages must follow conventional commits format +- Code must pass linting + +### 2. Merge to Master Stage +**Triggers:** When PR is merged to `master` branch + +**Workflow that runs:** +- `bump.yml` - Automatically bumps version and creates release + +**What happens:** +1. Commitizen analyzes commit messages since last tag +2. Determines version bump type (major/minor/patch) based on conventional commits: + - `feat:` → minor version bump + - `fix:` → patch version bump + - `BREAKING CHANGE:` → major version bump +3. Updates version in all configured files: + - `pyproject.toml` + - `custom_components/uk_bin_collection/manifest.json` + - `custom_components/uk_bin_collection/const.py` +4. Updates CHANGELOG.md +5. Creates a commit with message `bump: version X.Y.Z` +6. Creates and pushes a git tag `X.Y.Z` +7. Pushes the commit and tag to master + +**Note:** The bump workflow is skipped if the commit message starts with `bump:` to prevent infinite loops. + +### 3. Release Stage +**Triggers:** When a tag is pushed (automatically by bump workflow) + +**Workflow that runs:** +- `release.yml` - Publishes the release + +**What happens:** +1. Checks out the tagged commit +2. Verifies the Poetry version matches the git tag +3. Builds the Python package with Poetry +4. Creates a GitHub release with auto-generated release notes +5. Publishes the package to PyPI +6. Attaches build artifacts to the GitHub release + +## Commit Message Format + +Follow [Conventional Commits](https://www.conventionalcommits.org/) format: + +``` +(): + + + +