From e3363b67b3c2fedc5a320d2128835b5c0043643b Mon Sep 17 00:00:00 2001 From: Finn Freitag Date: Sat, 26 Nov 2022 13:41:59 +0100 Subject: [PATCH 001/127] Initial commit for Nordwestmecklenburg source --- .../source/geoport_nwm_de.py | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 custom_components/waste_collection_schedule/waste_collection_schedule/source/geoport_nwm_de.py diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/geoport_nwm_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/geoport_nwm_de.py new file mode 100644 index 00000000..507eae5f --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/geoport_nwm_de.py @@ -0,0 +1,53 @@ +import datetime +import requests +import urllib +from waste_collection_schedule import Collection # type: ignore[attr-defined] +from waste_collection_schedule.service.ICS import ICS + +TITLE = "Landkreis Nordwestmecklenburg" +DESCRIPTION = "Source for Landkreis Nordwestmecklenburg" +URL = "https://www.geoport-nwm.de/de/abfuhrtermine-geoportal.html" +TEST_CASES = { + "Rüting": {"district": "Rüting"}, + # Ortsteil_Rueting + "Grevenstein u.": {"district": "Grevenstein u. Ausbau"}, + # Ortsteil_Grevenstein_u_Ausbau + "Seefeld": {"district": "Seefeld/ Testorf- Steinfort"}, + # Ortsteil_Seefeld_Testorf_Steinfort + "1100l": {"district": "Groß Stieten (1.100 l Behälter)"} + # Ortsteil_Gross_Stieten_1100_l +} + + +class Source: + def __init__(self, district): + self._district = district + self._ics = ICS() + + def fetch(self): + arg = convert_to_arg(self._district) + today = datetime.date.today() + year = today.year + r = requests.get( + f"https://www.geoport-nwm.de/nwm-download/Abfuhrtermine/ICS/{year}/{arg}.ics") + + dates = self._ics.convert(r.text) + + entries = [] + for d in dates: + entries.append(Collection(d[0], d[1])) + return entries + + +def convert_to_arg(district): + district = district.replace("(1.100 l Behälter)", "1100_l") + district = district.replace("ü", "ue") + district = district.replace("ö", "oe") + district = district.replace("ä", "ae") + district = district.replace("ß", "ss") + district = district.replace("/", "") + district = district.replace("-", "") + district = district.replace(".", "") + district = district.replace(" ", "_") + arg = urllib.parse.quote("Ortsteil_" + district) + return arg From 03dd77a99153d6764c02750d592dfb5c792ea48d Mon Sep 17 00:00:00 2001 From: dt215git Date: Wed, 21 Dec 2022 15:41:44 +0000 Subject: [PATCH 002/127] SSLError fix --- .../source/ccc_govt_nz.py | 50 +++++++++++++++++-- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ccc_govt_nz.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ccc_govt_nz.py index 0d098489..e8ae745c 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ccc_govt_nz.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ccc_govt_nz.py @@ -3,11 +3,37 @@ import datetime import requests from waste_collection_schedule import Collection +# Updated to work around SSL UNSAFE_LEGACY_RENEGOTIATION_DISABLED error using method discussed in +# https://stackoverflow.com/questions/71603314/ssl-error-unsafe-legacy-renegotiation-disabled +import ssl +import urllib3 + TITLE = "Christchurch City Council" DESCRIPTION = "Source for Christchurch City Council." URL = "https://ccc.govt.nz/services/rubbish-and-recycling/collections" TEST_CASES = {"53 Hereford Street": {"address": "53 Hereford Street"}} +# Additional code snippet to work around SSL issue +class CustomHttpAdapter (requests.adapters.HTTPAdapter): + # "Transport adapter" that allows us to use custom ssl_context. + + def __init__(self, ssl_context=None, **kwargs): + self.ssl_context = ssl_context + super().__init__(**kwargs) + + def init_poolmanager(self, connections, maxsize, block=False): + self.poolmanager = urllib3.poolmanager.PoolManager( + num_pools=connections, maxsize=maxsize, + block=block, ssl_context=self.ssl_context) + + +def get_legacy_session(): + ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) + ctx.options |= 0x4 # OP_LEGACY_SERVER_CONNECT + session = requests.session() + session.mount('https://', CustomHttpAdapter(ctx)) + return session +# End SSL issue code snippet class Source: def __init__(self, address): @@ -24,10 +50,18 @@ class Source: "crs": "epsg:4326", "limit": 1, } - r = requests.get( - "https://opendata.ccc.govt.nz/CCCSearch/rest/address/suggest", + + # Updated request using SSL code snippet + r = get_legacy_session().get("https://opendata.ccc.govt.nz/CCCSearch/rest/address/suggest", params=addressQuery, + # verify=False, ) + + # Original request code + # r = requests.get( + # "https://opendata.ccc.govt.nz/CCCSearch/rest/address/suggest", + # params=addressQuery, + # ) address = r.json() # Find the Bin service by Rating Unit ID @@ -35,10 +69,18 @@ class Source: "client_id": "69f433c880c74c349b0128e9fa1b6a93", "client_secret": "139F3D2A83E34AdF98c80566f2eb7212" } - r = requests.get( - "https://ccc-data-citizen-api-v1-prod.au-s1.cloudhub.io/api/v1/properties/" + str(address[0]["RatingUnitID"]), + + # Updated request using SSL code snippet + r = get_legacy_session().get("https://ccc-data-citizen-api-v1-prod.au-s1.cloudhub.io/api/v1/properties/" + str(address[0]["RatingUnitID"]), headers=binsHeaders + # verify=False, ) + + # Original request code + # r = requests.get( + # "https://ccc-data-citizen-api-v1-prod.au-s1.cloudhub.io/api/v1/properties/" + str(address[0]["RatingUnitID"]), + # headers=binsHeaders + # ) bins = r.json() # Deduplicate the Bins in case the Rating Unit has more than one of the same Bin type From 239e6c77b54eb7f0aa2e423fe83255c2e2ae6da2 Mon Sep 17 00:00:00 2001 From: Demel75 Date: Wed, 21 Dec 2022 18:06:47 +0100 Subject: [PATCH 003/127] add zva-wmk --- .../source/zva_wmk_de.py | 50 +++++++++++++++++++ doc/source/zva_wmk_de.md | 23 +++++++++ 2 files changed, 73 insertions(+) create mode 100644 custom_components/waste_collection_schedule/waste_collection_schedule/source/zva_wmk_de.py create mode 100644 doc/source/zva_wmk_de.md diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/zva_wmk_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/zva_wmk_de.py new file mode 100644 index 00000000..f4e6fa9c --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/zva_wmk_de.py @@ -0,0 +1,50 @@ +import requests +import datetime +from waste_collection_schedule import Collection # type: ignore[attr-defined] +from waste_collection_schedule.service.ICS import ICS + +import urllib + +TITLE = "Zweckverband Abfallwirtschaft Werra-Meißner-Kreis" +DESCRIPTION = "Source for Zweckverband Abfallwirtschaft Werra-Meißner-Kreis" +URL = "https://www.zva-wmk.de/" +TEST_CASES = { + "Frankenhain": {"city": "Berkatal - Frankenhain", "street": "Teichhof"}, + "Hebenshausen": {"city": "Neu-Eichenberg - Hebenshausen", "street": "Bachstraße"}, + "Vockerode": {"city": "Meißner - Vockerode", "street": "Feuerwehr"} +} + + +class Source: + def __init__(self, city, street): + self._city = city + self._street = street + self._ics = ICS(split_at=" / ") + + def fetch(self): + city = self._city.replace('ß', 'ẞ').upper() + city = city.replace(" - ", "_") + city = city.replace(" ", "+") + city = city.replace("ẞ", "ß") + street = self._street + street = street.replace(" ","+") + today = datetime.date.today() + year = today.year + year = 2023 + if year == 2022: + yearstr = "" + street = self._street.upper() + else: + yearstr = ("-" + str(year)) + payload = {"city": city, "street": street} + urlzva = "https://www.zva-wmk.de/termine/schnellsuche"+yearstr+"&type=all&link=ical×tart=6&fullday=1&timeend=17&reminder=1440&display=0" + + r = requests.get(urlzva, params=payload) + + r.encoding = r.apparent_encoding + dates = self._ics.convert(r.text) + + entries = [] + for d in dates: + entries.append(Collection(d[0], d[1])) + return entries diff --git a/doc/source/zva_wmk_de.md b/doc/source/zva_wmk_de.md new file mode 100644 index 00000000..afeed090 --- /dev/null +++ b/doc/source/zva_wmk_de.md @@ -0,0 +1,23 @@ +# Zweckverband Abfallwirtschaft Werra-Meißner-Kreis +Support für Werra-Meißner-Kreis located in Hesse, Germany + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: zva_wmk_de + args: + city: CITY + street: STREET +``` + +### Configuration Variables + +**city**
+*(string) (required)* + +**street**
+### How to get the source arguments + +Visit [zva-wmk.de](https://www.zva-wmk.de/termine/schnellsuche-2023) and search for your locality. Use the value from the "Ort" dropdown as `city` argument and the one from "Ortsteil/Straße" as `street` as shown. From 54b766f54edb48ddf5f78293b261284e2ccafa83 Mon Sep 17 00:00:00 2001 From: Demel75 Date: Wed, 21 Dec 2022 20:12:52 +0100 Subject: [PATCH 004/127] Update zva_wmk_de.py removed test line --- .../waste_collection_schedule/source/zva_wmk_de.py | 1 - 1 file changed, 1 deletion(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/zva_wmk_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/zva_wmk_de.py index f4e6fa9c..8d89191e 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/zva_wmk_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/zva_wmk_de.py @@ -30,7 +30,6 @@ class Source: street = street.replace(" ","+") today = datetime.date.today() year = today.year - year = 2023 if year == 2022: yearstr = "" street = self._street.upper() From 5e5963ea19a21814ea18c89712820bb410e68003 Mon Sep 17 00:00:00 2001 From: dt215git Date: Wed, 21 Dec 2022 20:36:29 +0000 Subject: [PATCH 005/127] SSLError workaround moved to service folder --- .../service/SSLError.py | 27 ++++++++++++ .../source/aucklandcouncil_govt_nz.py | 40 ++---------------- .../source/ccc_govt_nz.py | 41 ++----------------- 3 files changed, 33 insertions(+), 75 deletions(-) create mode 100644 custom_components/waste_collection_schedule/waste_collection_schedule/service/SSLError.py diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/service/SSLError.py b/custom_components/waste_collection_schedule/waste_collection_schedule/service/SSLError.py new file mode 100644 index 00000000..4239f2cd --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/service/SSLError.py @@ -0,0 +1,27 @@ +#Work around SSL UNSAFE_LEGACY_RENEGOTIATION_DISABLED errors using method discussed in +# https://stackoverflow.com/questions/71603314/ssl-error-unsafe-legacy-renegotiation-disabled + +import requests +import ssl +import urllib3 + +class CustomHttpAdapter (requests.adapters.HTTPAdapter): + # "Transport adapter" that allows us to use custom ssl_context. + + def __init__(self, ssl_context=None, **kwargs): + self.ssl_context = ssl_context + super().__init__(**kwargs) + + def init_poolmanager(self, connections, maxsize, block=False): + self.poolmanager = urllib3.poolmanager.PoolManager( + num_pools=connections, maxsize=maxsize, + block=block, ssl_context=self.ssl_context) + + +def get_legacy_session(): + ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) + ctx.options |= 0x4 # OP_LEGACY_SERVER_CONNECT + session = requests.session() + session.mount('https://', CustomHttpAdapter(ctx)) + return session + diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/aucklandcouncil_govt_nz.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/aucklandcouncil_govt_nz.py index bbc30841..d0e64710 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/aucklandcouncil_govt_nz.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/aucklandcouncil_govt_nz.py @@ -1,14 +1,11 @@ import datetime from html.parser import HTMLParser -import requests +# import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -# Updated to work around SSL UNSAFE_LEGACY_RENEGOTIATION_DISABLED error using method discussed in -# https://stackoverflow.com/questions/71603314/ssl-error-unsafe-legacy-renegotiation-disabled -import ssl -import urllib3 - +# Include work around for SSL UNSAFE_LEGACY_RENEGOTIATION_DISABLED error +from ..service.SSLError import get_legacy_session TITLE = "Auckland council" DESCRIPTION = "Source for Auckland council." @@ -34,30 +31,6 @@ MONTH = { } -# Additional code snippet to work around SSL issue -class CustomHttpAdapter (requests.adapters.HTTPAdapter): - # "Transport adapter" that allows us to use custom ssl_context. - - def __init__(self, ssl_context=None, **kwargs): - self.ssl_context = ssl_context - super().__init__(**kwargs) - - def init_poolmanager(self, connections, maxsize, block=False): - self.poolmanager = urllib3.poolmanager.PoolManager( - num_pools=connections, maxsize=maxsize, - block=block, ssl_context=self.ssl_context) - - -def get_legacy_session(): - ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) - ctx.options |= 0x4 # OP_LEGACY_SERVER_CONNECT - session = requests.session() - session.mount('https://', CustomHttpAdapter(ctx)) - return session -# End SSL issue code snippet - - - def toDate(formattedDate): items = formattedDate.split() return datetime.date(int(items[3]), MONTH[items[2]], int(items[1])) @@ -144,13 +117,6 @@ class Source: # verify=False, ) - # Original request code - # r = requests.get( - # "https://www.aucklandcouncil.govt.nz/rubbish-recycling/rubbish-recycling-collections/Pages/collection-day-detail.aspx", - # params=params, - # verify=False, - # ) - p = WasteSearchResultsParser() p.feed(r.text) return p.entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ccc_govt_nz.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ccc_govt_nz.py index e8ae745c..cc995ba2 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ccc_govt_nz.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ccc_govt_nz.py @@ -3,37 +3,15 @@ import datetime import requests from waste_collection_schedule import Collection -# Updated to work around SSL UNSAFE_LEGACY_RENEGOTIATION_DISABLED error using method discussed in -# https://stackoverflow.com/questions/71603314/ssl-error-unsafe-legacy-renegotiation-disabled -import ssl -import urllib3 +# Include work around for SSL UNSAFE_LEGACY_RENEGOTIATION_DISABLED error +from ..service.SSLError import get_legacy_session + TITLE = "Christchurch City Council" DESCRIPTION = "Source for Christchurch City Council." URL = "https://ccc.govt.nz/services/rubbish-and-recycling/collections" TEST_CASES = {"53 Hereford Street": {"address": "53 Hereford Street"}} -# Additional code snippet to work around SSL issue -class CustomHttpAdapter (requests.adapters.HTTPAdapter): - # "Transport adapter" that allows us to use custom ssl_context. - - def __init__(self, ssl_context=None, **kwargs): - self.ssl_context = ssl_context - super().__init__(**kwargs) - - def init_poolmanager(self, connections, maxsize, block=False): - self.poolmanager = urllib3.poolmanager.PoolManager( - num_pools=connections, maxsize=maxsize, - block=block, ssl_context=self.ssl_context) - - -def get_legacy_session(): - ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) - ctx.options |= 0x4 # OP_LEGACY_SERVER_CONNECT - session = requests.session() - session.mount('https://', CustomHttpAdapter(ctx)) - return session -# End SSL issue code snippet class Source: def __init__(self, address): @@ -51,17 +29,10 @@ class Source: "limit": 1, } - # Updated request using SSL code snippet r = get_legacy_session().get("https://opendata.ccc.govt.nz/CCCSearch/rest/address/suggest", params=addressQuery, # verify=False, ) - - # Original request code - # r = requests.get( - # "https://opendata.ccc.govt.nz/CCCSearch/rest/address/suggest", - # params=addressQuery, - # ) address = r.json() # Find the Bin service by Rating Unit ID @@ -75,12 +46,6 @@ class Source: headers=binsHeaders # verify=False, ) - - # Original request code - # r = requests.get( - # "https://ccc-data-citizen-api-v1-prod.au-s1.cloudhub.io/api/v1/properties/" + str(address[0]["RatingUnitID"]), - # headers=binsHeaders - # ) bins = r.json() # Deduplicate the Bins in case the Rating Unit has more than one of the same Bin type From 3c60777c5cd4042a5fdedcb32e5e428859ff3156 Mon Sep 17 00:00:00 2001 From: Demel75 Date: Thu, 22 Dec 2022 07:56:39 +0100 Subject: [PATCH 006/127] Update info.md added as wantedt for zva_wmk_de --- info.md | 1 + 1 file changed, 1 insertion(+) diff --git a/info.md b/info.md index 14bd1295..c6ac2750 100644 --- a/info.md +++ b/info.md @@ -124,6 +124,7 @@ Currently the following service providers are supported: - [Umweltbetrieb Stadt Bielefeld](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/bielefeld_de.md) - [WAS Wolfsburg](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/was_wolfsburg_de.md) - [Wermelskirchen](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/wermelskirchen_de.md) +- [Zweckverband Abfallwirtschaft Werra-Meißner-Kreis](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/zva_wmk_de.md) ### Lithuania From 1128cb831fc6cb7402e2d0982539b3e333373024 Mon Sep 17 00:00:00 2001 From: dt215git Date: Thu, 22 Dec 2022 08:14:01 +0000 Subject: [PATCH 007/127] single session implemented --- .../waste_collection_schedule/source/ccc_govt_nz.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ccc_govt_nz.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ccc_govt_nz.py index cc995ba2..eaa618c2 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ccc_govt_nz.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ccc_govt_nz.py @@ -18,6 +18,9 @@ class Source: self._address = address def fetch(self): + + s = get_legacy_session() + entries = [] # Find the Rating Unit ID by the physical address @@ -29,7 +32,7 @@ class Source: "limit": 1, } - r = get_legacy_session().get("https://opendata.ccc.govt.nz/CCCSearch/rest/address/suggest", + r = s.get("https://opendata.ccc.govt.nz/CCCSearch/rest/address/suggest", params=addressQuery, # verify=False, ) @@ -42,7 +45,7 @@ class Source: } # Updated request using SSL code snippet - r = get_legacy_session().get("https://ccc-data-citizen-api-v1-prod.au-s1.cloudhub.io/api/v1/properties/" + str(address[0]["RatingUnitID"]), + r = s.get("https://ccc-data-citizen-api-v1-prod.au-s1.cloudhub.io/api/v1/properties/" + str(address[0]["RatingUnitID"]), headers=binsHeaders # verify=False, ) From 0149c86b36ac3bb4eef08a82dbe2039677e4fbb6 Mon Sep 17 00:00:00 2001 From: dt215git Date: Thu, 22 Dec 2022 10:46:01 +0000 Subject: [PATCH 008/127] services import statements refactored --- .../waste_collection_schedule/source/aucklandcouncil_govt_nz.py | 2 +- .../waste_collection_schedule/source/ccc_govt_nz.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/aucklandcouncil_govt_nz.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/aucklandcouncil_govt_nz.py index d0e64710..682f5c17 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/aucklandcouncil_govt_nz.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/aucklandcouncil_govt_nz.py @@ -5,7 +5,7 @@ from html.parser import HTMLParser from waste_collection_schedule import Collection # type: ignore[attr-defined] # Include work around for SSL UNSAFE_LEGACY_RENEGOTIATION_DISABLED error -from ..service.SSLError import get_legacy_session +from waste_collection_schedule.service.SSLError import get_legacy_session TITLE = "Auckland council" DESCRIPTION = "Source for Auckland council." diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ccc_govt_nz.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ccc_govt_nz.py index eaa618c2..052b2c0d 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ccc_govt_nz.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ccc_govt_nz.py @@ -4,7 +4,7 @@ import requests from waste_collection_schedule import Collection # Include work around for SSL UNSAFE_LEGACY_RENEGOTIATION_DISABLED error -from ..service.SSLError import get_legacy_session +from waste_collection_schedule.service.SSLError import get_legacy_session TITLE = "Christchurch City Council" From c83f346c83d8b2c190f8467cbcf1e330080fb421 Mon Sep 17 00:00:00 2001 From: Demel75 Date: Thu, 22 Dec 2022 14:05:13 +0100 Subject: [PATCH 009/127] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index cfbb99c9..10ba8dcc 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,7 @@ Currently the following service providers are supported: - [Umweltbetrieb Stadt Bielefeld](./doc/source/bielefeld_de.md) - [WAS Wolfsburg](./doc/source/was_wolfsburg_de.md) - [Wermelskirchen](./doc/source/wermelskirchen_de.md) +- [Zweckverband Abfallwirtschaft Werra-Meißner-Kreis](./doc/source/zva_wmk_de.md) ### Lithuania From fb78d5c4044132cd537eb34d116636a29d47bed9 Mon Sep 17 00:00:00 2001 From: dt215git Date: Thu, 22 Dec 2022 20:08:03 +0000 Subject: [PATCH 010/127] bielefeld_de refactored --- .../source/bielefeld_de.py | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/bielefeld_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bielefeld_de.py index 3906e187..accb8e44 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/bielefeld_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bielefeld_de.py @@ -4,6 +4,7 @@ import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS +from waste_collection_schedule.service.SSLError import get_legacy_session TITLE = "Bielefeld" DESCRIPTION = "Source for Stadt Bielefeld." @@ -45,12 +46,17 @@ class Source: self._ics = ICS() def fetch(self): - session = requests.session() + s = get_legacy_session() + # session = requests.session() - r = session.get( + r = s.get( SERVLET, params={"SubmitAction": "wasteDisposalServices", "InFrameMode": "TRUE"}, ) + # r = session.get( + # SERVLET, + # params={"SubmitAction": "wasteDisposalServices", "InFrameMode": "TRUE"}, + # ) r.raise_for_status() r.encoding = "utf-8" @@ -68,17 +74,26 @@ class Source: args["ContainerGewaehlt_2"] = "on" args["ContainerGewaehlt_3"] = "on" args["ContainerGewaehlt_4"] = "on" - r = session.post( + + r = s.post( SERVLET, data=args, ) + # r = session.post( + # SERVLET, + # data=args, + # ) r.raise_for_status() args["SubmitAction"] = "forward" - r = session.post( + r = s.post( SERVLET, data=args, ) + # r = session.post( + # SERVLET, + # data=args, + # ) r.raise_for_status() reminder_day = "keine Erinnerung" # "keine Erinnerung", "am Vortag", "2 Tage vorher", "3 Tage vorher" @@ -88,10 +103,14 @@ class Source: args["SubmitAction"] = "filedownload_ICAL" args["ICalErinnerung"] = reminder_day args["ICalZeit"] = reminder_time - r = session.post( + r = s.post( SERVLET, data=args, ) + # r = session.post( + # SERVLET, + # data=args, + # ) r.raise_for_status() dates = self._ics.convert(r.text) From 65bd964884bcc0a80637027342d2eb998900e98c Mon Sep 17 00:00:00 2001 From: Mirko Lenz Date: Fri, 23 Dec 2022 01:11:40 +0100 Subject: [PATCH 011/127] Add support for c-trace Buergerportal --- .../source/c_trace_buergerportal.py | 233 ++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_buergerportal.py diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_buergerportal.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_buergerportal.py new file mode 100644 index 00000000..9e15d030 --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_buergerportal.py @@ -0,0 +1,233 @@ +import contextlib +import re +from base64 import standard_b64decode +from datetime import datetime +from typing import List, Optional, TypedDict, Union + +import requests +from waste_collection_schedule import Collection # type: ignore[attr-defined] + +TITLE = "C-Trace Bürgerportal" +URL = "https://www.c-trace.de" +DESCRIPTION = "Source for waste collection in multiple service areas." +TEST_CASES = { + # "Cochem-Zell": { + # "url": "https://buerger-portal-cochemzell.azurewebsites.net/", + # "district": "Bullay", + # "street": "Layenweg", + # "number": 3, + # }, + "Alb-Donau-Kreis": { + "url": "https://buerger-portal-albdonaukreisabfallwirtschaft.azurewebsites.net", + "district": "Blaubeuren", + "street": "Alberstraße", + "number": 3, + }, + "Biedenkopf MZV": { + "url": "https://biedenkopfmzv.buergerportal.digital/", + "district": "Biedenkopf", + "subdistrict": "Breidenstein", + "street": "Auf dem Hammer", + "number": 1, + }, +} +ICONS = { + "mobil": "mdi:truck", + "bio": "mdi:leaf", + "papier": "mdi:package-variant", + "verpackung": "mdi:recycle", + "gelb": "mdi:recycle", + "rest": "mdi:trash-can", + "gruen": "mdi:forest", + "grün": "mdi:forest", + "baum": "mdi:forest", + "schnitt": "mdi:forest", + "schad": "mdi:biohazard", +} +API_HEADERS = { + "Accept": "application/json, text/plain;q=0.5", + "Cache-Control": "no-cache", +} + + +def quote_none(value: Optional[str]) -> str: + if value is None: + return "null" + + return f"'{value}'" + + +class Source: + def __init__( + self, + url: str, + district: str, + street: str, + number: Union[int, str, None] = None, + subdistrict: Optional[str] = None, + ): + self.api_url = f"{url.removesuffix('/')}/api" + self.district = district + self.subdistrict = subdistrict + self.street = street + self.number = number + + def fetch(self): + session = requests.session() + session.headers.update(API_HEADERS) + + year = datetime.now().year + entries: list[Collection] = [] + + district_id = self.fetch_district_id(session) + street_id = self.fetch_street_id(session, district_id) + # Eventually verify house number in the future + + params = { + "$expand": "Abfuhrplan,Abfuhrplan/GefaesstarifArt/Abfallart,Abfuhrplan/GefaesstarifArt/Volumen", + "$orderby": "Abfuhrplan/GefaesstarifArt/Abfallart/Name,Abfuhrplan/GefaesstarifArt/Volumen/VolumenWert", + "orteId": district_id, + "strassenId": street_id, + "jahr": year, + } + + if self.number: + params["hausNr"] = (f"'{self.number}'",) + + res = session.get( + f"{self.api_url}/AbfuhrtermineAbJahr", + params=params, + ) + res.raise_for_status() + payload: CollectionsRes = res.json() + + date_regex = re.compile(r"\d+") + + for collection in payload["d"]: + if date_match := re.search(date_regex, collection["Termin"]): + timestamp = float(date_match.group()) + date = datetime.utcfromtimestamp(timestamp / 1000).date() + waste_type = collection["Abfuhrplan"]["GefaesstarifArt"]["Abfallart"][ + "Name" + ] + icon = None + # Maybe append collection["Abfuhrplan"]["GefaesstarifArt"]["Volumen"]["VolumenWert"] to waste type + + for icon_type, tested_icon in ICONS.items(): + if icon_type.lower() in waste_type.lower(): + icon = tested_icon + + entries.append(Collection(date, waste_type, icon or "mdi:trash-can")) + + if len(entries) == 0: + raise ValueError( + "No collections found! Please verify that your configuration is correct." + ) + + return entries + + def fetch_district_id(self, session: requests.Session) -> int: + res = session.get( + f"{self.api_url}/OrteMitOrtsteilen", + headers=API_HEADERS, + ) + res.raise_for_status() + payload: DistrictsRes = res.json() + + return next( + entry["OrteId"] + for entry in payload["d"] + if entry["Ortsname"] == self.district + and entry["Ortsteilname"] == self.subdistrict + ) + + def fetch_street_id(self, session: requests.Session, district_id: int): + res = session.get( + f"{self.api_url}/Strassen", + params={ + "$filter": f"Ort/OrteId eq {district_id} and OrtsteilName eq {quote_none(self.subdistrict)}", + "$orderby": "Name asc", + }, + headers=API_HEADERS, + ) + res.raise_for_status() + payload: StreetsRes = res.json() + + return next( + entry["StrassenId"] + for entry in payload["d"] + if entry["Name"] == self.street + ) + + +# Typed dictionaries for the API +# Automatically generated using https://pytyper.dev/ + + +class DistrictRes(TypedDict): + OrteId: int + Ortsname: str + Ortsteilname: Optional[str] + + +class DistrictsRes(TypedDict): + d: List[DistrictRes] + + +class StreetRes(TypedDict): + StrassenId: int + Name: str + Plz: str + + +class StreetsRes(TypedDict): + d: List[StreetRes] + + +class Capacity(TypedDict): + VolumenId: int + VolumenWert: str + + +class WasteType(TypedDict): + AbfallartenId: int + Code: str + Name: str + Farbe: str + IsBio: bool + IsPapier: bool + IsRest: bool + IsWertstoff: bool + Bemerkung: None + Aktiv: None + IsSchadstoff: None + + +class ContainerType(TypedDict): + GefaesstarifArtenId: int + BescheidText: None + BescheidTextLeerungsgebuehr: None + Bezeichnung: str + GefaesstarifArtVerwenden: bool + GefaesstarifArtVerwendenAbfallkalender: bool + Bemerkung: None + Volumen: Capacity + Abfallart: WasteType + # Abfuhrrhythmus: Abfuhrrhythmus + + +class CollectionPlan(TypedDict): + AbfuhrplaeneId: int + Jahr: int + GefaesstarifArt: ContainerType + # AbfallartenObj: Abfuhrrhythmus + + +class CollectionRes(TypedDict): + AbfuhrtermineId: int + Termin: str + Abfuhrplan: CollectionPlan + + +class CollectionsRes(TypedDict): + d: List[CollectionRes] From b61473518d1acf053fc8b49dc7c37894b37e6024 Mon Sep 17 00:00:00 2001 From: Mirko Lenz Date: Fri, 23 Dec 2022 07:41:58 +0100 Subject: [PATCH 012/127] art_trier_de: Optimize imports --- .../waste_collection_schedule/source/art_trier_de.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/art_trier_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/art_trier_de.py index 744e8f0d..04d98059 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/art_trier_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/art_trier_de.py @@ -1,7 +1,7 @@ import contextlib from datetime import datetime -from urllib.parse import quote from typing import Optional +from urllib.parse import quote import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] From e16b1ad1a3bc129e06875c0195277313cf34ede9 Mon Sep 17 00:00:00 2001 From: Mirko Lenz Date: Fri, 23 Dec 2022 07:42:39 +0100 Subject: [PATCH 013/127] art_trier_de: Simplify district parsing logic --- .../source/art_trier_de.py | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/art_trier_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/art_trier_de.py index 04d98059..ac26c9db 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/art_trier_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/art_trier_de.py @@ -43,26 +43,25 @@ ICON_MAP = { "Restmüll": "mdi:trash-can", "Gelber Sack": "mdi:recycle", } -SPECIAL_CHARS = { - ord("ä"): "ae", - ord("ü"): "ue", - ord("ö"): "oe", - ord("ß"): "ss", - ord("("): "", - ord(")"): "", - ord("."): "", -} +SPECIAL_CHARS = str.maketrans( + { + " ": "_", + "ä": "ae", + "ü": "ue", + "ö": "oe", + "ß": "ss", + "(": None, + ")": None, + ",": None, + ".": None, + } +) class Source: def __init__(self, district: str, zip_code: str): self._district = quote( - district.lower() - .removeprefix("stadt ") - .replace(" ", "_") - .replace(",", "") - .translate(SPECIAL_CHARS) - .strip() + district.lower().removeprefix("stadt ").translate(SPECIAL_CHARS).strip() ) self._zip_code = zip_code self._ics = ICS(regex=r"^A.R.T. Abfuhrtermin: (.*)", split_at=r" & ") From d63e1e06219eb7b1e2a4779a7875c699244ebe63 Mon Sep 17 00:00:00 2001 From: Mirko Lenz Date: Fri, 23 Dec 2022 07:43:10 +0100 Subject: [PATCH 014/127] art_trier_de: Raise eventual http errors --- .../waste_collection_schedule/source/art_trier_de.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/art_trier_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/art_trier_de.py index ac26c9db..3c196ff9 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/art_trier_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/art_trier_de.py @@ -69,8 +69,10 @@ class Source: def fetch(self): url = f"{API_URL}/{self._zip_code}_{self._district}_{REMINDER_DAY}-{REMINDER_TIME}.ics" - r = requests.get(url) - schedule = self._ics.convert(r.text) + res = requests.get(url) + res.raise_for_status() + + schedule = self._ics.convert(res.text) return [ Collection( From 0801850f6dac934dcc1bdcb7a55d9f3441fe6b6c Mon Sep 17 00:00:00 2001 From: mampfes Date: Fri, 23 Dec 2022 11:14:14 +0100 Subject: [PATCH 015/127] refactor zva_wmk_de - fetch this year and next year automatically - remove obsolete conversions - remove obsolete imports --- .../source/zva_wmk_de.py | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/zva_wmk_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/zva_wmk_de.py index 8d89191e..728eabb9 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/zva_wmk_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/zva_wmk_de.py @@ -1,49 +1,54 @@ -import requests import datetime + +import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -import urllib - TITLE = "Zweckverband Abfallwirtschaft Werra-Meißner-Kreis" DESCRIPTION = "Source for Zweckverband Abfallwirtschaft Werra-Meißner-Kreis" URL = "https://www.zva-wmk.de/" TEST_CASES = { "Frankenhain": {"city": "Berkatal - Frankenhain", "street": "Teichhof"}, "Hebenshausen": {"city": "Neu-Eichenberg - Hebenshausen", "street": "Bachstraße"}, - "Vockerode": {"city": "Meißner - Vockerode", "street": "Feuerwehr"} + "Vockerode": {"city": "Meißner - Vockerode", "street": "Feuerwehr"}, } class Source: def __init__(self, city, street): + city = city.replace("ß", "ẞ").upper().replace("ẞ", "ß") + city = city.replace(" - ", "_") self._city = city self._street = street self._ics = ICS(split_at=" / ") def fetch(self): - city = self._city.replace('ß', 'ẞ').upper() - city = city.replace(" - ", "_") - city = city.replace(" ", "+") - city = city.replace("ẞ", "ß") - street = self._street - street = street.replace(" ","+") today = datetime.date.today() - year = today.year - if year == 2022: - yearstr = "" - street = self._street.upper() - else: - yearstr = ("-" + str(year)) - payload = {"city": city, "street": street} - urlzva = "https://www.zva-wmk.de/termine/schnellsuche"+yearstr+"&type=all&link=ical×tart=6&fullday=1&timeend=17&reminder=1440&display=0" - r = requests.get(urlzva, params=payload) - - r.encoding = r.apparent_encoding + entries = self._fetch_year(today.year) + if today.month == 12: + entries.extend(self._fetch_year(today.year + 1)) + + return entries + + def _fetch_year(self, year): + if year == 2022: + yearstr = "" + street = self._street.upper() + else: + yearstr = f"-{year}" + street = self._street + + params = {"city": self._city, "street": street, "type": "all", "link": "ical"} + + r = requests.get( + f"https://www.zva-wmk.de/termine/schnellsuche{yearstr}", params=params + ) + r.raise_for_status() + dates = self._ics.convert(r.text) entries = [] for d in dates: - entries.append(Collection(d[0], d[1])) + entries.append(Collection(d[0], d[1])) return entries From fe1fb0be91802489d44c1aaa07665e3f502801f1 Mon Sep 17 00:00:00 2001 From: Mirko Lenz Date: Fri, 23 Dec 2022 13:33:38 +0100 Subject: [PATCH 016/127] buergerportal: use operator name instead of url --- .../source/c_trace_buergerportal.py | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_buergerportal.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_buergerportal.py index 9e15d030..e630f601 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_buergerportal.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_buergerportal.py @@ -17,14 +17,14 @@ TEST_CASES = { # "street": "Layenweg", # "number": 3, # }, - "Alb-Donau-Kreis": { - "url": "https://buerger-portal-albdonaukreisabfallwirtschaft.azurewebsites.net", + "Alb-Donau": { + "operator": "alb_donau", "district": "Blaubeuren", "street": "Alberstraße", "number": 3, }, - "Biedenkopf MZV": { - "url": "https://biedenkopfmzv.buergerportal.digital/", + "Biedenkopf": { + "operator": "biedenkopf", "district": "Biedenkopf", "subdistrict": "Breidenstein", "street": "Auf dem Hammer", @@ -48,6 +48,13 @@ API_HEADERS = { "Accept": "application/json, text/plain;q=0.5", "Cache-Control": "no-cache", } +Operator = Literal["cochem_zell", "alb_donau", "biedenkopf"] +# Important: Remove the trailing slash +OPERATOR_URLS: dict[Operator, str] = { + "cochem_zell": "https://buerger-portal-cochemzell.azurewebsites.net", + "alb_donau": "https://buerger-portal-albdonaukreisabfallwirtschaft.azurewebsites.net", + "biedenkopf": "https://biedenkopfmzv.buergerportal.digital", +} def quote_none(value: Optional[str]) -> str: @@ -60,13 +67,13 @@ def quote_none(value: Optional[str]) -> str: class Source: def __init__( self, - url: str, + operator: Operator, district: str, street: str, - number: Union[int, str, None] = None, subdistrict: Optional[str] = None, + number: Union[int, str, None] = None, ): - self.api_url = f"{url.removesuffix('/')}/api" + self.api_url = f"{OPERATOR_URLS[operator]}/api" self.district = district self.subdistrict = subdistrict self.street = street From 84d89e08a251051da0403125a9a2b39299711e0d Mon Sep 17 00:00:00 2001 From: Mirko Lenz Date: Fri, 23 Dec 2022 13:34:04 +0100 Subject: [PATCH 017/127] buergerportal: add cochem-zell test case --- .../source/c_trace_buergerportal.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_buergerportal.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_buergerportal.py index e630f601..5af6a389 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_buergerportal.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_buergerportal.py @@ -11,12 +11,13 @@ TITLE = "C-Trace Bürgerportal" URL = "https://www.c-trace.de" DESCRIPTION = "Source for waste collection in multiple service areas." TEST_CASES = { - # "Cochem-Zell": { - # "url": "https://buerger-portal-cochemzell.azurewebsites.net/", - # "district": "Bullay", - # "street": "Layenweg", - # "number": 3, - # }, + "Cochem-Zell": { + "operator": "cochem_zell", + "district": "Bullay", + "subdistrict": "Bullay", + "street": "Layenweg", + "number": 3, + }, "Alb-Donau": { "operator": "alb_donau", "district": "Blaubeuren", @@ -37,6 +38,7 @@ ICONS = { "papier": "mdi:package-variant", "verpackung": "mdi:recycle", "gelb": "mdi:recycle", + "lvp": "mdi:recycle", "rest": "mdi:trash-can", "gruen": "mdi:forest", "grün": "mdi:forest", From 8cfd22aac497fa4f0eb03268db9439b545493c41 Mon Sep 17 00:00:00 2001 From: Mirko Lenz Date: Fri, 23 Dec 2022 13:34:31 +0100 Subject: [PATCH 018/127] buergerportal: add missing literal import --- .../waste_collection_schedule/source/c_trace_buergerportal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_buergerportal.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_buergerportal.py index 5af6a389..0a214df6 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_buergerportal.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_buergerportal.py @@ -2,7 +2,7 @@ import contextlib import re from base64 import standard_b64decode from datetime import datetime -from typing import List, Optional, TypedDict, Union +from typing import List, Literal, Optional, TypedDict, Union import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] From 99e33a14cc6de3199c68df9c69b4b557bea08401 Mon Sep 17 00:00:00 2001 From: Mirko Lenz Date: Fri, 23 Dec 2022 13:34:48 +0100 Subject: [PATCH 019/127] buergerportal: provide better error messages --- .../source/c_trace_buergerportal.py | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_buergerportal.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_buergerportal.py index 0a214df6..1077bdfd 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_buergerportal.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_buergerportal.py @@ -143,12 +143,18 @@ class Source: res.raise_for_status() payload: DistrictsRes = res.json() - return next( - entry["OrteId"] - for entry in payload["d"] - if entry["Ortsname"] == self.district - and entry["Ortsteilname"] == self.subdistrict - ) + try: + return next( + entry["OrteId"] + for entry in payload["d"] + if entry["Ortsname"] == self.district + and entry["Ortsteilname"] == self.subdistrict + ) + except StopIteration: + raise ValueError( + "District id cannot be fetched. " + "Please make sure that you entered a subdistrict if there is a comma on the website." + ) def fetch_street_id(self, session: requests.Session, district_id: int): res = session.get( @@ -162,11 +168,16 @@ class Source: res.raise_for_status() payload: StreetsRes = res.json() - return next( - entry["StrassenId"] - for entry in payload["d"] - if entry["Name"] == self.street - ) + try: + return next( + entry["StrassenId"] + for entry in payload["d"] + if entry["Name"] == self.street + ) + except StopIteration: + raise ValueError( + "Street ID cannot be fetched. Please verify your configuration." + ) # Typed dictionaries for the API From c322c6a982c4ca20f1267e51d78265d05663de19 Mon Sep 17 00:00:00 2001 From: Mirko Lenz Date: Fri, 23 Dec 2022 13:35:19 +0100 Subject: [PATCH 020/127] burgerportal: rename source --- .../source/{c_trace_buergerportal.py => buergerportal_de.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename custom_components/waste_collection_schedule/waste_collection_schedule/source/{c_trace_buergerportal.py => buergerportal_de.py} (99%) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_buergerportal.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/buergerportal_de.py similarity index 99% rename from custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_buergerportal.py rename to custom_components/waste_collection_schedule/waste_collection_schedule/source/buergerportal_de.py index 1077bdfd..913abbe2 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_buergerportal.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/buergerportal_de.py @@ -7,7 +7,7 @@ from typing import List, Literal, Optional, TypedDict, Union import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "C-Trace Bürgerportal" +TITLE = "Bürgerportal" URL = "https://www.c-trace.de" DESCRIPTION = "Source for waste collection in multiple service areas." TEST_CASES = { From b1cb5c3bd650fca4120e28373a5a65fd5a82d299 Mon Sep 17 00:00:00 2001 From: Mirko Lenz Date: Fri, 23 Dec 2022 14:25:40 +0100 Subject: [PATCH 021/127] buergerportal: remove unneeded imports --- .../waste_collection_schedule/source/buergerportal_de.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/buergerportal_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/buergerportal_de.py index 913abbe2..888d4316 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/buergerportal_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/buergerportal_de.py @@ -1,6 +1,4 @@ -import contextlib import re -from base64 import standard_b64decode from datetime import datetime from typing import List, Literal, Optional, TypedDict, Union From 4b6b0ef10573a6d47c9572582abeebe633910b2c Mon Sep 17 00:00:00 2001 From: Chistian Arnold Date: Fri, 23 Dec 2022 15:47:48 +0100 Subject: [PATCH 022/127] awb_es_de: now using all available ics AWB-ES uses one ics file per year, now every linked ics for a given location gets pulled --- .../source/awb_es_de.py | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awb_es_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awb_es_de.py index 1ccc80b2..18a963e3 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awb_es_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awb_es_de.py @@ -37,24 +37,25 @@ class Source: soup = BeautifulSoup(r.text, features="html.parser") downloads = soup.find_all("a", href=True) - ics_url = None + ics_urls = list() for download in downloads: href = download.get("href") - if "t=ics" in href: - ics_url = href - break + if "t=ics" in href and href not in ics_urls: #The website lists the same url multiple times, we only want it once + ics_urls.append(href) - if ics_url is None: + if not ics_urls: raise Exception(f"ics url not found") - # get ics file - r = session.get(ics_url, headers=HEADERS) - r.raise_for_status() - - # parse ics file - dates = self._ics.convert(r.text) - entries = [] - for d in dates: - entries.append(Collection(d[0], d[1])) + for ics_url in ics_urls: + # get ics file + r = session.get(ics_url, headers=HEADERS) + r.raise_for_status() + + # parse ics file + dates = self._ics.convert(r.text) + + for d in dates: + entries.append(Collection(d[0], d[1])) + return entries From 890cb063d9f19b54b9a3f600c411676a32a9b3da Mon Sep 17 00:00:00 2001 From: Mirko Lenz Date: Fri, 23 Dec 2022 16:22:58 +0100 Subject: [PATCH 023/127] buergerportal: add docs --- README.md | 4 ++- doc/source/buergerportal_de.md | 63 ++++++++++++++++++++++++++++++++++ info.md | 4 ++- 3 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 doc/source/buergerportal_de.md diff --git a/README.md b/README.md index 10ba8dcc..cf4196bc 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,7 @@ Currently the following service providers are supported: - [Abfallwirtschaft Stuttgart](./doc/source/stuttgart_de.md) - [Abfallwirtschaft Südholstein](./doc/source/awsh_de.md) - [Abfallwirtschaft Zollernalbkreis](./doc/source/abfall_zollernalbkreis_de.md) +- [Alb-Donau-Kreis](./doc/source/buergerportal_de.md) - [ART Trier](./doc/source/art_trier_de.md) - [AVL Ludwigsburg](./doc/source/avl_ludwigsburg_de.md) - [AWB Bad Kreuznach](./doc/source/awb_bad_kreuznach_de.md) @@ -111,9 +112,10 @@ Currently the following service providers are supported: - [AWIDO-online.de](./doc/source/awido_de.md) - [Berlin-Recycling.de](./doc/source/berlin_recycling_de.md) - [Bogenschuetz-Entsorgung.de](./doc/source/infeo_at.md) +- [Biedenkopf MZF](./doc/source/buergerportal_de.md) - [BSR.de / Berliner Stadtreinigungsbetriebe](./doc/source/bsr_de.md) - [C-Trace.de](./doc/source/c_trace_de.md) -- [Cochem-Zell](./doc/source/cochem_zell_online_de.md) +- [Cochem-Zell](./doc/source/buergerportal_de.md) - [EGN-Abfallkalender.de](./doc/source/egn_abfallkalender_de.md) - [Erlangen-Höchstadt](./doc/source/erlangen_hoechstadt_de.md) - [Jumomind.de](./doc/source/jumomind_de.md) diff --git a/doc/source/buergerportal_de.md b/doc/source/buergerportal_de.md new file mode 100644 index 00000000..26034709 --- /dev/null +++ b/doc/source/buergerportal_de.md @@ -0,0 +1,63 @@ +# Bürgerportal + +Source for waste collection in multiple service areas. + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: buergerportal_de + args: + operator: OPERATOR + district: DISTRICT + subdistrict: SUBDISTRICT + street: STREET_NAME + number: HOUSE_NUMBER +``` + +## Supported Operators + +- `cochem_zell`: +- `alb_donau`: +- `biedenkopf`: + +### Configuration Variables + +**operator**\ +_(string) (required)_ + +**district**\ +_(string) (required)_ + +**street**\ +_(string) (required)_ + +**number**\ +_(string|int) (required)_ + +**subdistrict**\ +_(string) (optional) (default: null)_ + +## Example + +```yaml +waste_collection_schedule: + sources: + - name: buergerportal_de + args: + operator: cochem_zell + district: Bullay + subdistrict: Bullay + street: Layenweg + number: 3 +``` + +## How to get the source arguments + +1. Open the URL of your operator and click on the menu option `Abfuhrkalender` in the left sidebar. +2. Select your `district` (Ort). _Note_: If your district contains two values separated by a comma, you also have to specify the `subdistrict` (Ortsteil): Enter the first part into the field `district` and the second part into the field `subdistrict`. This is necessary even if your `district` and `subdistrict` have the same value (e.g., `Bullay, Bullay`). Subdistrict may only be left empty if there is no comma in the field value. +3. Select your `street` (Straße). +4. Select your `number` (Hausnummer). + +All parameters are _case-sensitive_. diff --git a/info.md b/info.md index c6ac2750..dc7939c7 100644 --- a/info.md +++ b/info.md @@ -86,6 +86,7 @@ Currently the following service providers are supported: - [Abfallwirtschaft Stuttgart](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/stuttgart_de.md) - [Abfallwirtschaft Südholstein](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/awsh_de.md) - [Abfallwirtschaft Zollernalbkreis](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/abfall_zollernalbkreis_de.md) +- [Alb-Donau-Kreis](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/buergerportal_de.md) - [ART Trier](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/art_trier_de.md) - [AVL Ludwigsburg](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/avl_ludwigsburg_de.md) - [AWB Bad Kreuznach](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/awb_bad_kreuznach_de.md) @@ -95,10 +96,11 @@ Currently the following service providers are supported: - [AWBKoeln.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/awbkoeln_de.md) - [AWIDO-online.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/awido_de.md) - [Berlin-Recycling.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/berlin_recycling_de.md) +- [Biedenkopf MZV](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/buergerportal_de.md) - [Bogenschuetz-Entsorgung.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/infeo_at.md) - [BSR.de / Berliner Stadtreinigungsbetriebe](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/bsr_de.md) - [C-Trace.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/c_trace_de.md) -- [Cochem-Zell](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/cochem_zell_online_de.md) +- [Cochem-Zell](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/buergerportal_de.md) - [EGN-Abfallkalender.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/egn_abfallkalender_de.md) - [Erlangen-Höchstadt](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/erlangen_hoechstadt_de.md) - [Jumomind.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/jumomind_de.md) From 18a89c37276c2bfc7c75bd0beb83e32985ed2138 Mon Sep 17 00:00:00 2001 From: Danny Keary <2210900+DanAE111@users.noreply.github.com> Date: Sat, 24 Dec 2022 15:18:49 +1100 Subject: [PATCH 024/127] Added source for Nillumbik VIC AU --- README.md | 1 + .../source/nillumbik_vic_gov_au.py | 80 +++++++++++++++++++ doc/source/nillumbik_vic_gov_au.md | 32 ++++++++ info.md | 1 + 4 files changed, 114 insertions(+) create mode 100755 custom_components/waste_collection_schedule/waste_collection_schedule/source/nillumbik_vic_gov_au.py create mode 100644 doc/source/nillumbik_vic_gov_au.md diff --git a/README.md b/README.md index 10ba8dcc..a72f02d7 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,7 @@ Currently the following service providers are supported: - [Macedon Ranges Shire Council, Melbourne](./doc/source/mrsc_vic_gov_au.md) - [Maroondah City Council](./doc/source/maroondah_vic_gov_au.md) - [Melton City Council, Melbourne](./doc/source/melton_vic_gov_au.md) +- [Nillumbik Shire Council](./doc/source/nillumbik_vic_gov_au.md) - [North Adelaide Waste Management Authority, South Australia](./doc/source/nawma_sa_gov_au.md) - [RecycleSmart](./doc/source/recyclesmart_com.md) - [Stonnington City Council, Melbourne](./doc/source/stonnington_vic_gov_au.md) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/nillumbik_vic_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/nillumbik_vic_gov_au.py new file mode 100755 index 00000000..05e057ee --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/nillumbik_vic_gov_au.py @@ -0,0 +1,80 @@ +import logging +import re +from datetime import datetime + +import requests +from bs4 import BeautifulSoup +from waste_collection_schedule import Collection # type: ignore[attr-defined] + +TITLE = "Nillumbik Shire Council" +DESCRIPTION = "Source for Nillumbik Shire Council rubbish collection." +URL = "https://www.nillumbik.vic.gov.au" +TEST_CASES = { + "Test": {"street_address": "11 Sunnyside Crescent, WATTLE GLEN, 3096"} +} + +_LOGGER = logging.getLogger(__name__) + +ICON_MAP = { + "Food and Green Waste": "mdi:leaf", + "Hard Waste": "mdi:sofa", + "Recycling": "mdi:recycle", +} + + +class Source: + def __init__(self, street_address): + self._street_address = street_address + + def fetch(self): + session = requests.Session() + + response = session.get("https://www.nillumbik.vic.gov.au/Residents/Waste-and-recycling/Bin-collection/Check-my-bin-day") + response.raise_for_status() + + response = session.get( + "https://www.nillumbik.vic.gov.au/api/v1/myarea/search", + params={"keywords": self._street_address}, + ) + response.raise_for_status() + addressSearchApiResults = response.json() + if ( + addressSearchApiResults["Items"] is None + or len(addressSearchApiResults["Items"]) < 1 + ): + _LOGGER.error( + f"Address search for '{self._street_address}' returned no results. Check your address on https://www.nillumbik.vic.gov.au/Residents/Waste-and-recycling/Bin-collection/Check-my-bin-day" + ) + return [] + + addressSearchTopHit = addressSearchApiResults["Items"][0] + _LOGGER.debug("Address search top hit: %s", addressSearchTopHit) + + geolocationid = addressSearchTopHit["Id"] + _LOGGER.debug("Geolocationid: %s", geolocationid) + + response = session.get( + "https://www.nillumbik.vic.gov.au/ocapi/Public/myarea/wasteservices?ocsvclang=en-AU", + params={"geolocationid": geolocationid}, + ) + response.raise_for_status() + + wasteApiResult = response.json() + _LOGGER.debug("Waste API result: %s", wasteApiResult) + + soup = BeautifulSoup(wasteApiResult["responseContent"], "html.parser") + + entries = [] + for article in soup.find_all("article"): + waste_type = article.h3.string + icon = ICON_MAP.get(waste_type, "mdi:trash-can") + next_pickup = article.find(class_="next-service").string.strip() + if re.match(r"[^\s]* \d{1,2}\/\d{1,2}\/\d{4}", next_pickup): + next_pickup_date = datetime.strptime( + next_pickup.split(sep=" ")[1], "%d/%m/%Y" + ).date() + entries.append( + Collection(date=next_pickup_date, t=waste_type, icon=icon) + ) + + return entries diff --git a/doc/source/nillumbik_vic_gov_au.md b/doc/source/nillumbik_vic_gov_au.md new file mode 100644 index 00000000..ca422857 --- /dev/null +++ b/doc/source/nillumbik_vic_gov_au.md @@ -0,0 +1,32 @@ +# Nillumbik Shire Council + +Support for schedules provided by [Nillumbik Shire Council](https://www.nillumbik.vic.gov.au). + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: nillumbik_vic_gov_au + args: + street_address: STREET_ADDRESS +``` + +### Configuration Variables + +**street_address**
+*(string) (required)* + +## Example + +```yaml +waste_collection_schedule: + sources: + - name: nillumbik_vic_gov_au + args: + street_address: 30 Crest Road, RESEARCH, 3095 +``` + +## How to get the source arguments + +Visit the [Nillumbik Shire Council waste and recycling](https://www.nillumbik.vic.gov.au/Residents/Waste-and-recycling/Bin-collection/Check-my-bin-day) page and search for your address. The arguments should exactly match the street address shown in the autocomplete result. \ No newline at end of file diff --git a/info.md b/info.md index c6ac2750..fffad445 100644 --- a/info.md +++ b/info.md @@ -51,6 +51,7 @@ Currently the following service providers are supported: - [Macedon Ranges Shire Council, Melbourne](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/mrsc_vic_gov_au.md) - [Maroondah City Council](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/maroondah_vic_gov_au.md) - [Melton City Council, Melbourne](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/melton_vic_gov_au.md) +- [Nillumbik Shire Council, Melbourne](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/nillumbik_vic_gov_au.md) - [North Adelaide Waste Management Authority, South Australia](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/nawma_sa_gov_au.md) - [RecycleSmart](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/recyclesmart.md) - [Stonnington City Council, Melbourne](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/stonnington_vic_gov_au.md) From 7070c6a139974cb329de21b817141a627b79cb07 Mon Sep 17 00:00:00 2001 From: mampfes Date: Sat, 24 Dec 2022 09:31:30 +0100 Subject: [PATCH 025/127] add missing ids to ximmio_nl --- .../waste_collection_schedule/source/ximmio_nl.py | 4 ++++ doc/source/ximmio_nl.md | 2 ++ 2 files changed, 6 insertions(+) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ximmio_nl.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ximmio_nl.py index b4c057b3..43ba8448 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ximmio_nl.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ximmio_nl.py @@ -14,14 +14,17 @@ TEST_CASES = { SERVICE_URLS = { + "avalex": "https://wasteprod2api.ximmio.com", "meerlanden": "https://wasteprod2api.ximmio.com", "rad": "https://wasteprod2api.ximmio.com", + "westland": "https://wasteprod2api.ximmio.com", } SERVICE_IDS = { "acv": "f8e2844a-095e-48f9-9f98-71fceb51d2c3", "almere": "53d8db94-7945-42fd-9742-9bbc71dbe4c1", "areareiniging": "adc418da-d19b-11e5-ab30-625662870761", + "avalex": "f7a74ad1-fdbf-4a43-9f91-44644f4d4222", "avri": "78cd4156-394b-413d-8936-d407e334559a", "bar": "bb58e633-de14-4b2a-9941-5bc419f1c4b0", "hellendoorn": "24434f5b-7244-412b-9306-3a2bd1e22bc1", @@ -31,6 +34,7 @@ SERVICE_IDS = { "reinis": "9dc25c8a-175a-4a41-b7a1-83f237a80b77", "twentemilieu": "8d97bb56-5afd-4cbc-a651-b4f7314264b4", "waardlanden": "942abcf6-3775-400d-ae5d-7380d728b23c", + "westland": "6fc75608-126a-4a50-9241-a002ce8c8a6c", "ximmio": "800bf8d7-6dd1-4490-ba9d-b419d6dc8a45", } diff --git a/doc/source/ximmio_nl.md b/doc/source/ximmio_nl.md index 4de6bf18..75e2ba0d 100644 --- a/doc/source/ximmio_nl.md +++ b/doc/source/ximmio_nl.md @@ -24,6 +24,7 @@ Use one of the following codes as company code: - acv - almere - areareiniging +- avalex - avri - bar - hellendoorn @@ -33,6 +34,7 @@ Use one of the following codes as company code: - reinis - twentemilieu - waardlanden +- westland - ximmio **post_code**
From d419c273d997f7872863ea06e75bf0ab08eb1e0b Mon Sep 17 00:00:00 2001 From: mampfes Date: Sat, 24 Dec 2022 09:32:13 +0100 Subject: [PATCH 026/127] improve hvcgroep source to support other dutch services --- README.md | 2 +- .../source/hvcgroep_nl.py | 67 ++++++++++++++----- doc/source/hvcgroep_nl.md | 49 +++++++++++++- info.md | 2 +- 4 files changed, 99 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index bed4763e..22ac4c54 100644 --- a/README.md +++ b/README.md @@ -146,8 +146,8 @@ Currently the following service providers are supported: ### Netherlands +- [HVCGroep and others](./doc/source/hvcgroep_nl.md) - [Ximmio](./doc/source/ximmio_nl.md) -- [HVCGroep](./doc/source/hvcgroep_nl.md) ### New Zealand diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/hvcgroep_nl.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/hvcgroep_nl.py index 4ca4d264..d1b3aaa0 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/hvcgroep_nl.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/hvcgroep_nl.py @@ -1,4 +1,3 @@ -import json import logging from datetime import datetime @@ -8,13 +7,52 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "HVCGroep" DESCRIPTION = "Source for the Dutch HVCGroep waste management." URL = "https://www.hvcgroep.nl/zelf-regelen/afvalkalender" -TEST_CASES = {"Tollebeek": {"postal_code": "8309AV", "house_number": "1"}} +TEST_CASES = { + "Tollebeek": {"postal_code": "8309AV", "house_number": "1"}, + "Hvgroep: Tollebeek": { + "postal_code": "8309AV", + "house_number": "1", + "service": "hvcgroep", + }, + "Cyclus": {"postal_code": "2841ML", "house_number": "1090", "service": "cyclusnv"}, + "Mjinblink": { + "postal_code": "5741BV", + "house_number": "76", + "service": "mjinblink", + }, +} _LOGGER = logging.getLogger(__name__) +SERVICE_MAP = { + "alphenaandenrijn": "https://afvalkalender.alphenaandenrijn.nl", + "cranendonck": "https://afvalkalender.cranendonck.nl", + "cyclusnv": "https://afvalkalender.cyclusnv.nl", + "dar": "https://afvalkalender.dar.nl", + "denhaag": "https://huisvuilkalender.denhaag.nl", + "gad": "https://inzamelkalender.gad.nl", + "gemeenteberkelland": "https://afvalkalender.gemeenteberkelland.nl", + "hvcgroep": "https://inzamelkalender.hvcgroep.nl", + "lingewaard": "https://afvalwijzer.lingewaard.nl", + "middelburgvlissingen": "https://afvalwijzer.middelburgvlissingen.nl", + "mijnblink": "https://mijnblink.nl", + "peelenmaas": "https://afvalkalender.peelenmaas.nl", + "prezero": "https://inzamelwijzer.prezero.nl", + "purmerend": "https://afvalkalender.purmerend.nl", + "rmn": "https://inzamelschema.rmn.nl", + "schouwen-duiveland": "https://afvalkalender.schouwen-duiveland.nl", + "spaarnelanden": "https://afvalwijzer.spaarnelanden.nl", + "stadswerk072": "https://www.stadswerk072.nl", + "sudwestfryslan": "https://afvalkalender.sudwestfryslan.nl", + "venray": "https://afvalkalender.venray.nl", + "voorschoten": "https://afvalkalender.voorschoten.nl", + "waalre": "https://afvalkalender.waalre.nl", + "zrd": "https://afvalkalender.zrd.nl", +} + class Source: - def __init__(self, postal_code, house_number): + def __init__(self, postal_code, house_number, service="hvcgroep"): self.postal_code = postal_code self.house_number = house_number self.icons = { @@ -23,15 +61,14 @@ class Source: "papier-en-karton": "mdi:archive", "restafval": "mdi:trash-can", } + self._url = SERVICE_MAP[service] def fetch(self): - bag_id = 0 # Retrieve bagid (unique waste management identifier) - r = requests.get( - f"https://inzamelkalender.hvcgroep.nl/adressen/{self.postal_code}:{self.house_number}" - ) - data = json.loads(r.text) + r = requests.get(f"{self._url}/adressen/{self.postal_code}:{self.house_number}") + r.raise_for_status() + data = r.json() # Something must be wrong, maybe the address isn't valid? No need to do the extra requests so just return here. if len(data) == 0: @@ -41,16 +78,14 @@ class Source: bag_id = data[0]["bagid"] # Retrieve the details about different waste management flows (for example, paper, plastic etc.) - r = requests.get( - f"https://inzamelkalender.hvcgroep.nl/rest/adressen/{bag_id}/afvalstromen" - ) - waste_flows = json.loads(r.text) + r = requests.get(f"{self._url}/rest/adressen/{bag_id}/afvalstromen") + r.raise_for_status() + waste_flows = r.json() # Retrieve the coming pickup dates for waste. - r = requests.get( - f"https://inzamelkalender.hvcgroep.nl/rest/adressen/{bag_id}/ophaaldata" - ) - data = json.loads(r.text) + r = requests.get(f"{self._url}/rest/adressen/{bag_id}/ophaaldata") + r.raise_for_status() + data = r.json() entries = [] diff --git a/doc/source/hvcgroep_nl.md b/doc/source/hvcgroep_nl.md index 4419e30b..5d55dae8 100644 --- a/doc/source/hvcgroep_nl.md +++ b/doc/source/hvcgroep_nl.md @@ -1,6 +1,6 @@ # HVCGroep -Support for schedules provided by [hvcgroep.nl](https://www.hvcgroep.nl/). +Support for schedules provided by [hvcgroep.nl](https://www.hvcgroep.nl/) and other Dutch municipalities. ## Configuration via configuration.yaml @@ -11,23 +11,66 @@ waste_collection_schedule: args: postal_code: POSTAL_CODE house_number: HOUSE_NUMBER + service: SERVICE ``` ### Configuration Variables -**postal_code**
+**postal_code** *(string) (required)* -**house_number**
+**house_number** *(string) (required)* +**service** +*(string) (optional, default="hvcgroep")* + +Use one of the following codes as service code: + +- alphenaandenrijn +- cranendonck +- cyclusnv +- dar +- denhaag +- gad +- gemeenteberkelland +- hvcgroep +- lingewaard +- middelburgvlissingen +- mijnblink +- peelenmaas +- prezero +- purmerend +- rmn +- schouwen-duiveland +- spaarnelanden +- stadswerk072 +- sudwestfryslan +- venray +- voorschoten +- waalre +- zrd + ## Example ```yaml +# hvgroep waste_collection_schedule: sources: - name: hvcgroep_nl args: postal_code: 1713VM house_number: 1 + service: hvxgroep +``` + +```yaml +# cyclusnv +waste_collection_schedule: + sources: + - name: hvcgroep_nl + args: + postal_code: 2841ML + house_number: 1090 + service: cyclusnv ``` diff --git a/info.md b/info.md index 0c67d925..99a14d01 100644 --- a/info.md +++ b/info.md @@ -131,8 +131,8 @@ Currently the following service providers are supported: ### Netherlands +- [HVCGroep and others](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/hvcgroep_nl.md) - [Ximmio](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/ximmio_nl.md) -- [HVCGroep](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/hvcgroep_nl.md) ### New Zealand From 5a736d037c2bfdc6e320cd9c8b1ca79f9fb3caf1 Mon Sep 17 00:00:00 2001 From: mampfes Date: Sat, 24 Dec 2022 10:43:40 +0100 Subject: [PATCH 027/127] add note to banyule_vic_gov_au that source is defect --- doc/source/banyule_vic_gov_au.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/source/banyule_vic_gov_au.md b/doc/source/banyule_vic_gov_au.md index 310ebdb7..7e995a74 100644 --- a/doc/source/banyule_vic_gov_au.md +++ b/doc/source/banyule_vic_gov_au.md @@ -1,5 +1,8 @@ # Banyule City Council +**NOTE: Source is not working any more due to anti-scraping features: +** + Support for schedules provided by [Banyule City Council](https://www.banyule.vic.gov.au/binday). This implementation is heavily based upon the Stonnington City Council parser, as both interfaces appear to use the same back-end. ## Configuration via configuration.yaml From 36224140df554e99f972605e08c810347be56cdf Mon Sep 17 00:00:00 2001 From: mampfes Date: Sat, 24 Dec 2022 11:01:29 +0100 Subject: [PATCH 028/127] remove debug output --- .../source/cheshire_east_gov_uk.py | 2 +- .../source/grafikai_svara_lt.py | 18 +-- .../source/kingston_gov_uk.py | 137 +++++++++++------- .../source/richmondshire_gov_uk.py | 11 +- .../source/ssam_se.py | 34 ++++- 5 files changed, 128 insertions(+), 74 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/cheshire_east_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/cheshire_east_gov_uk.py index b9abf0ba..603e26dc 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/cheshire_east_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/cheshire_east_gov_uk.py @@ -43,7 +43,7 @@ class Source: r.raise_for_status() soup = BeautifulSoup(r.text, features="html.parser") s = soup.find("a", attrs={"class": "get-job-details"}) - print(s) + if s is None: raise Exception("address not found") self._uprn = s["data-uprn"] diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/grafikai_svara_lt.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/grafikai_svara_lt.py index 6992518d..9c36cc39 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/grafikai_svara_lt.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/grafikai_svara_lt.py @@ -5,16 +5,15 @@ import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Grafikai.svara.lt" -DESCRIPTION = "Source for UAB \"Kauno švara\"." +DESCRIPTION = 'Source for UAB "Kauno švara".' URL = "http://grafikai.svara.lt" TEST_CASES = { "Demokratų g. 7, Kaunas": { "region": "Kauno m. sav.", "street": "Demokratų g.", "house_number": "7", - "waste_object_ids": [101358, 100858, 100860] + "waste_object_ids": [101358, 100858, 100860], }, - "Alytaus g. 2, Išlaužo k., Išlaužo sen. Prienų r. sav.": { "region": "Prienų r. sav.", "street": "Alytaus g.", @@ -34,7 +33,9 @@ ICONS = { class Source: API_URL = "http://grafikai.svara.lt/api/" - def __init__(self, region, street, house_number, district=None, waste_object_ids=None): + def __init__( + self, region, street, house_number, district=None, waste_object_ids=None + ): if waste_object_ids is None: waste_object_ids = [] self._region = region @@ -68,10 +69,8 @@ class Source: for collection in data["data"]: try: type = collection["descriptionPlural"].casefold() - if self.check_if_waste_object_defined(collection['wasteObjectId']): - waste_object_query = { - "wasteObjectId": collection['wasteObjectId'] - } + if self.check_if_waste_object_defined(collection["wasteObjectId"]): + waste_object_query = {"wasteObjectId": collection["wasteObjectId"]} rwo = requests.get( self.API_URL + "schedule", @@ -81,7 +80,6 @@ class Source: self.check_for_error_status(data_waste_object) for collection_waste_object in data_waste_object: - print(collection_waste_object["date"]) entries.append( Collection( date=datetime.strptime( @@ -107,6 +105,6 @@ class Source: if "status" in collection: raise Exception( "Error: failed to fetch get data, got status: {}".format( - collection['status'] + collection["status"] ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/kingston_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kingston_gov_uk.py index 347bbd55..484b3b40 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/kingston_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kingston_gov_uk.py @@ -1,90 +1,123 @@ import logging -import requests import time - from datetime import datetime + +import requests from waste_collection_schedule import Collection -TITLE = 'www.kingston.gov.uk' +TITLE = "www.kingston.gov.uk" DESCRIPTION = ( - 'Source for waste collection services for The Royal Borough of Kingston Council' + "Source for waste collection services for The Royal Borough of Kingston Council" ) -URL = 'https://kingston-self.achieveservice.com/service/in_my_area?displaymode=collections' +URL = "https://kingston-self.achieveservice.com/service/in_my_area?displaymode=collections" HEADERS = { "user-agent": "Mozilla/5.0", } -COOKIES = { - -} +COOKIES = {} TEST_CASES = { - "Blagdon Road - number" : {"uprn": 100021772910}, - "Blagdon Road - string" : {"uprn": "100021772910"}, + "Blagdon Road - number": {"uprn": 100021772910}, + "Blagdon Road - string": {"uprn": "100021772910"}, } API_URLS = { - 'session': 'https://kingston-self.achieveservice.com/service/In_my_Area_Results?uprn=100021772910&displaymode=collections&altVal=', - 'auth': 'https://kingston-self.achieveservice.com/authapi/isauthenticated?uri=https%253A%252F%252Fkingston-self.achieveservice.com%252Fservice%252FIn_my_Area_Results%253Fuprn%253D100021772910%2526displaymode%253Dcollections%2526altVal%253D&hostname=kingston-self.achieveservice.com&withCredentials=true', - 'schedule': 'https://kingston-self.achieveservice.com/apibroker/runLookup?id=601a61f9a3188&repeat_against=&noRetry=true&getOnlyTokens=undefined&log_id=&app_name=AF-Renderer::Self&' + "session": "https://kingston-self.achieveservice.com/service/In_my_Area_Results?uprn=100021772910&displaymode=collections&altVal=", + "auth": "https://kingston-self.achieveservice.com/authapi/isauthenticated?uri=https%253A%252F%252Fkingston-self.achieveservice.com%252Fservice%252FIn_my_Area_Results%253Fuprn%253D100021772910%2526displaymode%253Dcollections%2526altVal%253D&hostname=kingston-self.achieveservice.com&withCredentials=true", + "schedule": "https://kingston-self.achieveservice.com/apibroker/runLookup?id=601a61f9a3188&repeat_against=&noRetry=true&getOnlyTokens=undefined&log_id=&app_name=AF-Renderer::Self&", } _LOGGER = logging.getLogger(__name__) + class Source: def __init__(self, uprn: str): self._uprn = str(uprn) + def fetch(self): s = requests.Session() - #This request sets up the cookies - r0 = s.get(API_URLS['session'], headers=HEADERS) + # This request sets up the cookies + r0 = s.get(API_URLS["session"], headers=HEADERS) r0.raise_for_status() - #This request gets the session key from the PHPSESSID (in the cookies) - authRequest = s.get(API_URLS['auth'], headers=HEADERS) + # This request gets the session key from the PHPSESSID (in the cookies) + authRequest = s.get(API_URLS["auth"], headers=HEADERS) authData = authRequest.json() - sessionKey = authData['auth-session'] + sessionKey = authData["auth-session"] now = time.time_ns() // 1_000_000 - #now query using the uprn - payload = { "formValues": { "Section 1": { "UPRN_FromUrl": { "value": self._uprn }, "borough_code": { "value": "RBK" }, "show_wasteCollection": { "value": "1" }, "echo_borough": { "value": "RBK" }, "echo_uprn": { "value": self._uprn } } } } + # now query using the uprn + payload = { + "formValues": { + "Section 1": { + "UPRN_FromUrl": {"value": self._uprn}, + "borough_code": {"value": "RBK"}, + "show_wasteCollection": {"value": "1"}, + "echo_borough": {"value": "RBK"}, + "echo_uprn": {"value": self._uprn}, + } + } + } - scheduleRequest = s.post(API_URLS['schedule'] + '&_' + str(now) + '&sid=' + sessionKey , headers=HEADERS, json=payload) - data = scheduleRequest.json()['integration']['transformed']['rows_data']['0'] - print(data) + scheduleRequest = s.post( + API_URLS["schedule"] + "&_" + str(now) + "&sid=" + sessionKey, + headers=HEADERS, + json=payload, + ) + data = scheduleRequest.json()["integration"]["transformed"]["rows_data"]["0"] entries = [] - entries.append(Collection( - date = datetime.strptime(data['echo_refuse_next_date'], '%Y-%m-%d %H:%M:%S').date(), - t = 'refuse bin', - icon = 'mdi:trash-can' - )) + entries.append( + Collection( + date=datetime.strptime( + data["echo_refuse_next_date"], "%Y-%m-%d %H:%M:%S" + ).date(), + t="refuse bin", + icon="mdi:trash-can", + ) + ) + + entries.append( + Collection( + date=datetime.strptime( + data["echo_food_waste_next_date"], "%Y-%m-%d %H:%M:%S" + ).date(), + t="food waste bin", + icon="mdi:trash-can", + ) + ) + + entries.append( + Collection( + date=datetime.strptime( + data["echo_paper_and_card_next_date"], "%Y-%m-%d %H:%M:%S" + ).date(), + t="paper and card recycling bin", + icon="mdi:recycle", + ) + ) + + entries.append( + Collection( + date=datetime.strptime( + data["echo_mixed_recycling_next_date"], "%Y-%m-%d %H:%M:%S" + ).date(), + t="mixed recycling bin", + icon="mdi:recycle", + ) + ) + + entries.append( + Collection( + date=datetime.strptime( + data["echo_garden_waste_next_date"], "%Y-%m-%d %H:%M:%S" + ).date(), + t="garden waste bin", + icon="mdi:leaf", + ) + ) - entries.append(Collection( - date = datetime.strptime(data['echo_food_waste_next_date'], '%Y-%m-%d %H:%M:%S').date(), - t = 'food waste bin', - icon = 'mdi:trash-can' - )) - - entries.append(Collection( - date = datetime.strptime(data['echo_paper_and_card_next_date'], '%Y-%m-%d %H:%M:%S').date(), - t = 'paper and card recycling bin', - icon = 'mdi:recycle' - )) - - entries.append(Collection( - date = datetime.strptime(data['echo_mixed_recycling_next_date'], '%Y-%m-%d %H:%M:%S').date(), - t = 'mixed recycling bin', - icon = 'mdi:recycle' - )) - - entries.append(Collection( - date = datetime.strptime(data['echo_garden_waste_next_date'], '%Y-%m-%d %H:%M:%S').date(), - t = 'garden waste bin', - icon = 'mdi:leaf' - )) - return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/richmondshire_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/richmondshire_gov_uk.py index 86b27ad1..e6349f67 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/richmondshire_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/richmondshire_gov_uk.py @@ -1,12 +1,16 @@ from datetime import datetime + import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -import json TITLE = "Richmondshire (North Yorkshire)" DESCRIPTION = "To find your UPRN, visit the Richmondshire page and use the address search. Right-click your entry in the house dropdown, choose Inspect, and copy the UPRN from the value" URL = "https://www.richmondshire.gov.uk/collectionCalendar" -TEST_CASES = {"test 1": {"uprn": 200001767082}, "test 2": {"uprn": 200001767078}, "test3": {"uprn": 200001767079 }} +TEST_CASES = { + "test1": {"uprn": 200001767082}, + "test2": {"uprn": 200001767078}, + "test3": {"uprn": 200001767079}, +} ICONS = { "240L GREY RUBBISH BIN": "mdi:trash-can", @@ -14,6 +18,7 @@ ICONS = { "140L GARDEN BIN": "mdi:leaf", } + class Source: def __init__( self, uprn @@ -24,9 +29,7 @@ class Source: r = requests.get( f"https://www.richmondshire.gov.uk/Umbraco/Api/MyAreaApi/GetBinRoundData?uprn={self._uprn}" ) -# print(r.text) ids = r.json() -# print (len(ids)) entries = [] diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ssam_se.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ssam_se.py index ff16a326..6dbf5469 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ssam_se.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ssam_se.py @@ -19,7 +19,6 @@ class Source: def fetch(self): params = {"searchText": self._street_address} - print(self._street_address) response = requests.post( "https://edpfuture.ssam.se/FutureWeb/SimpleWastePickup/SearchAdress", params=params, @@ -44,27 +43,48 @@ class Source: entries = [] for item in data["RhServices"]: - waste_type ="" + waste_type = "" next_pickup = item["NextWastePickup"] next_pickup_date = datetime.fromisoformat(next_pickup).date() if item["WasteType"] == "FNI1": - waste_type = "Kärl 1, "+ item["BinType"]["ContainerType"]+" " +str(item["BinType"]["Size"])+item["BinType"]["Unit"] + waste_type = ( + "Kärl 1, " + + item["BinType"]["ContainerType"] + + " " + + str(item["BinType"]["Size"]) + + item["BinType"]["Unit"] + ) icon = "mdi:trash-can" elif item["WasteType"] == "FNI2": - waste_type = "Kärl 2, "+ item["BinType"]["ContainerType"]+" " +str(item["BinType"]["Size"])+item["BinType"]["Unit"] + waste_type = ( + "Kärl 2, " + + item["BinType"]["ContainerType"] + + " " + + str(item["BinType"]["Size"]) + + item["BinType"]["Unit"] + ) icon = "mdi:trash-can" elif item["BinType"]["Code"] == "KM140": waste_type = "Matpåsar" icon = "mdi:recycle" else: - waste_type = item["WasteType"] +" "+ item["BinType"]["ContainerType"]+" " +str(item["BinType"]["Size"])+item["BinType"]["Unit"] + waste_type = ( + item["WasteType"] + + " " + + item["BinType"]["ContainerType"] + + " " + + str(item["BinType"]["Size"]) + + item["BinType"]["Unit"] + ) icon = "mdi:trash-can" if item["WasteType"] == "Trädgårdsavfall": icon = "mdi:leaf" found = 0 for x in entries: - if (x.date==next_pickup_date and x.type==waste_type): + if x.date == next_pickup_date and x.type == waste_type: found = 1 if found == 0: - entries.append(Collection(date=next_pickup_date, t=waste_type, icon=icon)) + entries.append( + Collection(date=next_pickup_date, t=waste_type, icon=icon) + ) return entries From 8b620755107c46c18ef0bd8f51bda631e4c1baea Mon Sep 17 00:00:00 2001 From: mampfes Date: Sat, 24 Dec 2022 11:01:30 +0100 Subject: [PATCH 029/127] remove defect source AVL Ludwigsburg --- README.md | 1 - .../source/avl_ludwigsburg_de.py | 93 ------------------- doc/source/avl_ludwigsburg_de.md | 26 ------ info.md | 1 - 4 files changed, 121 deletions(-) delete mode 100644 custom_components/waste_collection_schedule/waste_collection_schedule/source/avl_ludwigsburg_de.py delete mode 100644 doc/source/avl_ludwigsburg_de.md diff --git a/README.md b/README.md index cc9fa70d..e7ad9258 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,6 @@ Currently the following service providers are supported: - [Abfallwirtschaft Südholstein](./doc/source/awsh_de.md) - [Abfallwirtschaft Zollernalbkreis](./doc/source/abfall_zollernalbkreis_de.md) - [ART Trier](./doc/source/art_trier_de.md) -- [AVL Ludwigsburg](./doc/source/avl_ludwigsburg_de.md) - [AWB Bad Kreuznach](./doc/source/awb_bad_kreuznach_de.md) - [AWB Esslingen](./doc/source/awb_es_de.md) - [AWB Landkreis Augsburg](./doc/source/c_trace_de.md) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/avl_ludwigsburg_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/avl_ludwigsburg_de.py deleted file mode 100644 index 374b7f6a..00000000 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/avl_ludwigsburg_de.py +++ /dev/null @@ -1,93 +0,0 @@ -import requests -from bs4 import BeautifulSoup -from waste_collection_schedule import Collection # type: ignore[attr-defined] -from waste_collection_schedule.service.ICS import ICS - -TITLE = "avl-ludwigsburg.de" -DESCRIPTION = "Abfallverwertungsgesellschaft des Landkreises Ludwigsburg mbH" -URL = "https://www.avl-ludwigsburg.de/privatkunden/termine/abfallkalender/suche/" - -TEST_CASES = { - "CityWithoutStreet": {"city": "Möglingen"}, - "CityWithStreet": {"city": "Ludwigsburg", "street": "Bahnhofstraße"}, -} - - -class Source: - def __init__(self, city, street=None): - self._city = city - self._street = street - self._ics = ICS() - - def fetch(self): - # Get the hidden parameters by loading the page - session = requests.Session() - r = session.get(URL) - r.raise_for_status() - - soup = BeautifulSoup(r.text, features="html.parser") - hidden_tags = soup.find_all("input", type="hidden") - - # Prepare data for the real web request - data = {} - for tag in hidden_tags: - data[tag.get("name")] = tag.get("value") - - # Find the cities which do need a street name - data_cities_with_streets = soup.find_all( - "input", type="text", placeholder="Ort eingeben" - ) - cities_with_streets = "" - for tag in data_cities_with_streets: - cities_with_streets += tag.get("data-cities-with-streets") - cities_with_streets = cities_with_streets.split(",") - - data["tx_avlcollections_pi5[wasteCalendarLocationItem]"] = self._city - data["tx_avlcollections_pi5[wasteCalendarStreetItem]"] = self._street - - # Remove some data which the webserver doesn't like - data.pop("id", None) - data.pop("tx_kesearch_pi1[page]", None) - data.pop("tx_kesearch_pi1[resetFilters]", None) - data.pop("tx_kesearch_pi1[sortByField]", None) - data.pop("tx_kesearch_pi1[sortByDir]", None) - - # Depending on the city remove the street from the data set - if self._city.lower() not in cities_with_streets: - data.pop("tx_avlcollections_pi5[wasteCalendarStreetItem]", None) - - # Get the final data - r = session.post(URL, data=data) - r.raise_for_status() - - if r.text.find("Ort konnte nicht gefunden werden.") != -1: - raise Exception("Error: Ort konnte nicht gefunden werden.") - - if r.text.find("Straße konnte nicht gefunden werden.") != -1: - raise Exception("Error: Ort konnte nicht gefunden werden.") - - if r.text.find(".ics") == -1: - raise Exception("Error: No ics link found.") - - soup = BeautifulSoup(r.text, features="html.parser") - downloads = soup.find_all("a", href=True) - ics_link = "" - for download in downloads: - link = download.get("href") - if ".ics" in link: - ics_link = link - full_url = "https://www.avl-ludwigsburg.de" + ics_link - return self.fetch_ics(full_url) - - def fetch_ics(self, url): - r = requests.get(url) - r.raise_for_status() - - # Parse ics file - r.encoding = "utf-8" - dates = self._ics.convert(r.text) - - entries = [] - for d in dates: - entries.append(Collection(d[0], d[1])) - return entries diff --git a/doc/source/avl_ludwigsburg_de.md b/doc/source/avl_ludwigsburg_de.md deleted file mode 100644 index bbd7a2fc..00000000 --- a/doc/source/avl_ludwigsburg_de.md +++ /dev/null @@ -1,26 +0,0 @@ -# AVL Ludwigsburg - -Support for schedules provided by [avl-ludwigsburg.de](https://www.avl-ludwigsburg.de) located in Baden Württemberg, Germany. - -## Configuration via configuration.yaml - -```yaml -waste_collection_schedule: - sources: - - name: avl_ludwigsburg_de - args: - city: Ludwigsburg - street: Bahnhofstraße -``` - -### Configuration Variables - -**city**
-*(string) (required)* - -**street**
-*(string) (optional - depending on city)* - -## How to get the source arguments - -Check [avl-ludwigsburg.de Abfallkalender](https://www.avl-ludwigsburg.de/privatkunden/termine/abfallkalender/) if you only need the city e.g. Möglingen or if you need an additional street name e.g. in Ludwigsburg. \ No newline at end of file diff --git a/info.md b/info.md index f850708d..a904563c 100644 --- a/info.md +++ b/info.md @@ -87,7 +87,6 @@ Currently the following service providers are supported: - [Abfallwirtschaft Südholstein](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/awsh_de.md) - [Abfallwirtschaft Zollernalbkreis](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/abfall_zollernalbkreis_de.md) - [ART Trier](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/art_trier_de.md) -- [AVL Ludwigsburg](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/avl_ludwigsburg_de.md) - [AWB Bad Kreuznach](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/awb_bad_kreuznach_de.md) - [AWB Esslingen](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/awb_es_de.md) - [AWB Limburg-Weilburg](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/awb_lm_de.md) From 141f18be857bc6472ed8678907292c905575ba6b Mon Sep 17 00:00:00 2001 From: mampfes Date: Sat, 24 Dec 2022 11:01:31 +0100 Subject: [PATCH 030/127] fix source elmbridge_gov_uk --- .../source/elmbridge_gov_uk.py | 88 +++++++++---------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/elmbridge_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/elmbridge_gov_uk.py index 209375ba..4fc98655 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/elmbridge_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/elmbridge_gov_uk.py @@ -1,15 +1,13 @@ import logging -import requests - -from bs4 import BeautifulSoup from datetime import datetime, timedelta + +import requests +from bs4 import BeautifulSoup from waste_collection_schedule import Collection -TITLE = 'elmbridge.gov.uk' -DESCRIPTION = ( - 'Source for waste collection services for Elmbridge Borough Council' -) -URL = 'https://www.elmbridge.gov.uk/waste-and-recycling/' +TITLE = "elmbridge.gov.uk" +DESCRIPTION = "Source for waste collection services for Elmbridge Borough Council" +URL = "https://www.elmbridge.gov.uk/waste-and-recycling/" HEADERS = { @@ -17,7 +15,7 @@ HEADERS = { } TEST_CASES = { - "Test_001" : {"uprn": 10013119164}, + "Test_001": {"uprn": 10013119164}, "Test_002": {"uprn": "100061309206"}, "Test_003": {"uprn": 100062119825}, "Test_004": {"uprn": "100061343923"}, @@ -25,19 +23,19 @@ TEST_CASES = { } API_URLS = { - 'session': 'https://emaps.elmbridge.gov.uk/myElmbridge.aspx', - 'search': 'https://emaps.elmbridge.gov.uk/myElmbridge.aspx?action=SetAddress&UniqueId={}', - 'schedule': 'https://emaps.elmbridge.gov.uk/myElmbridge.aspx?tab=0#Refuse_&_Recycling', + "session": "https://emaps.elmbridge.gov.uk/myElmbridge.aspx", + "search": "https://emaps.elmbridge.gov.uk/myElmbridge.aspx?action=SetAddress&UniqueId={}", + "schedule": "https://emaps.elmbridge.gov.uk/myElmbridge.aspx?tab=0#Refuse_&_Recycling", } OFFSETS = { - 'Monday': 0, - 'Tuesday': 1, - 'Wednesday': 2, - 'Thursday': 3, - 'Friday': 4, - 'Saturday': 5, - 'Sunday': 6, + "Monday": 0, + "Tuesday": 1, + "Wednesday": 2, + "Thursday": 3, + "Friday": 4, + "Saturday": 5, + "Sunday": 6, } ICONS = { @@ -61,63 +59,65 @@ class Source: # This script assumes the week-commencing dates are for the current year. # This'll cause problems in December as upcoming January collections will have been assigned dates in the past. # Some clunky logic can deal with this: - # If a date in less than 1 month in the past, it doesn't matter as the collection will have recently occured. + # If a date in less than 1 month in the past, it doesn't matter as the collection will have recently occurred. # If a date is more than 1 month in the past, assume it's an incorrectly assigned date and increment the year by 1. - # Once that's been done, offset the week-commencing dates to match day of the week each waste collection type is scheduled. + # Once that's been done, offset the week-commencing dates to match day of the week each waste collection type is scheduled. # If you have a better way of doing this, feel free to update via a Pull Request! # Get current date and year in format consistent with API result today = datetime.now() - today = today.replace(hour = 0, minute = 0, second = 0, microsecond = 0) + today = today.replace(hour=0, minute=0, second=0, microsecond=0) year = today.year s = requests.Session() - r0 = s.get(API_URLS['session'], headers=HEADERS) + r0 = s.get(API_URLS["session"], headers=HEADERS) r0.raise_for_status() - r1 = s.get(API_URLS['search'].format(self._uprn), headers=HEADERS) + r1 = s.get(API_URLS["search"].format(self._uprn), headers=HEADERS) r1.raise_for_status() - r2 = s.get(API_URLS['schedule'], headers=HEADERS) + r2 = s.get(API_URLS["schedule"], headers=HEADERS) r2.raise_for_status() - + responseContent = r2.content - soup = BeautifulSoup(responseContent, 'html.parser') + soup = BeautifulSoup(responseContent, "html.parser") entries = [] - notice = soup.find('div', {'class': 'atPanelContent atFirst atAlt0'}) - notices = notice.text.replace('\nRefuse and recycling collection days\n', '').split('.') - notices.pop(-1) # Remove superflous element - frame = soup.find('div', {'class': 'atPanelContent atAlt1 atLast'}) - table = frame.find('table') + notice = soup.find("div", {"class": "atPanelContent atFirst atAlt0"}) + notices = notice.text.replace( + "\nRefuse and recycling collection days\n", "" + ).split(".") + notices.pop(-1) # Remove superfluous element + frame = soup.find("div", {"class": "atPanelContent atAlt1 atLast"}) + table = frame.find("table") - for tr in table.find_all('tr'): + for tr in table.find_all("tr"): row = [] - for td in tr.find_all('td'): + for td in tr.find_all("td"): row.append(td.text.strip()) - row.pop(1) # removes superflous element - dt = row[0] + ' ' + str(year) - dt = datetime.strptime(dt, '%d %b %Y') + row.pop(1) # removes superfluous element + dt = row[0] + " " + str(year) + dt = datetime.strptime(dt, "%d %b %Y") # Amend year, if necessary - if (dt - today) < timedelta(days = -31): - dt += timedelta(year = 1) + if (dt - today) < timedelta(days=-31): + dt = dt.replace(year=dt.year + 1) row[0] = dt # Separate out same-day waste collections - wastetypes = row[1].split(' + ') + wastetypes = row[1].split(" + ") # Sort out date offsets for each collection type for waste in wastetypes: for day, offset in OFFSETS.items(): for sentence in notices: if (waste in sentence) and (day in sentence): - new_date = row[0] + timedelta(days = offset) + new_date = row[0] + timedelta(days=offset) entries.append( Collection( - date = new_date.date(), - t = waste + ' bin', - icon = ICONS.get(waste.upper()), + date=new_date.date(), + t=waste + " bin", + icon=ICONS.get(waste.upper()), ) ) From d80e74a2412145df5818dd0ea01605cb75f15479 Mon Sep 17 00:00:00 2001 From: mampfes Date: Sat, 24 Dec 2022 11:01:33 +0100 Subject: [PATCH 031/127] fix source erlangen_hoechstadt_de during year transition --- .../source/erlangen_hoechstadt_de.py | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/erlangen_hoechstadt_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/erlangen_hoechstadt_de.py index d366bdef..929f030d 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/erlangen_hoechstadt_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/erlangen_hoechstadt_de.py @@ -1,17 +1,16 @@ -import requests import datetime + +import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -import urllib - TITLE = "Landkreis Erlangen-Höchstadt" DESCRIPTION = "Source for Landkreis Erlangen-Höchstadt" URL = "https://www.erlangen-hoechstadt.de/" TEST_CASES = { "Höchstadt": {"city": "Höchstadt", "street": "Böhmerwaldstraße"}, "Brand": {"city": "Eckental", "street": "Eckenhaid, Amselweg"}, - "Ortsteile": {"city": "Wachenroth", "street": "Wachenroth Ort ink. aller Ortsteile"} + "Ortsteile": {"city": "Wachenroth", "street": "Ort inkl. aller Ortsteile"}, } @@ -22,20 +21,25 @@ class Source: self._ics = ICS(split_at=" / ") def fetch(self): - city = self._city.upper() - street = self._street today = datetime.date.today() - year = today.year - - payload = {"ort": city, "strasse": street, - "abfallart": "Alle", "jahr": year} - r = requests.get( - "https://www.erlangen-hoechstadt.de/komx/surface/dfxabfallics/GetAbfallIcs", params=payload - ) - r.encoding = r.apparent_encoding - dates = self._ics.convert(r.text) + dates = self.fetch_year(today.year) + if today.month == 12: + dates.extend(self.fetch_year(today.year + 1)) entries = [] for d in dates: entries.append(Collection(d[0], d[1])) return entries + + def fetch_year(self, year): + city = self._city.upper() + street = self._street + + payload = {"ort": city, "strasse": street, "abfallart": "Alle", "jahr": year} + r = requests.get( + "https://www.erlangen-hoechstadt.de/komx/surface/dfxabfallics/GetAbfallIcs", + params=payload, + ) + r.raise_for_status() + r.encoding = r.apparent_encoding + return self._ics.convert(r.text) From e17092168edaeceff8b54780266d5c32a9715a7e Mon Sep 17 00:00:00 2001 From: mampfes Date: Sat, 24 Dec 2022 11:01:34 +0100 Subject: [PATCH 032/127] log as info, not as warning: derby_gov_uk --- .../waste_collection_schedule/source/derby_gov_uk.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/derby_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/derby_gov_uk.py index 897ac858..e82f1b29 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/derby_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/derby_gov_uk.py @@ -1,11 +1,10 @@ +import logging from datetime import datetime +from urllib.parse import parse_qs, urlsplit import requests -from waste_collection_schedule import Collection # type: ignore[attr-defined] - from bs4 import BeautifulSoup -from urllib.parse import urlsplit, parse_qs -import logging +from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Derby.gov.uk" DESCRIPTION = "Source for Derby.gov.uk services for Derby City Council, UK." @@ -75,7 +74,7 @@ class Source: try: date = datetime.strptime(date.text, "%A, %d %B %Y:").date() except ValueError: - _LOGGER.error(f"Skipped {date} as it does not match time format") + _LOGGER.info(f"Skipped {date} as it does not match time format") continue img_tag = result.find("img") collection_type = img_tag["alt"] From f460d205a787538423771228f6c38dc4d75a07d3 Mon Sep 17 00:00:00 2001 From: mampfes Date: Sat, 24 Dec 2022 11:01:35 +0100 Subject: [PATCH 033/127] remove defect source muenchenstein_ch --- README.md | 1 - .../source/muenchenstein_ch.py | 76 ------------------- doc/source/muenchenstein_ch.md | 38 ---------- info.md | 1 - 4 files changed, 116 deletions(-) delete mode 100755 custom_components/waste_collection_schedule/waste_collection_schedule/source/muenchenstein_ch.py delete mode 100644 doc/source/muenchenstein_ch.md diff --git a/README.md b/README.md index e7ad9258..a4662526 100644 --- a/README.md +++ b/README.md @@ -180,7 +180,6 @@ Currently the following service providers are supported: - [A-Region.ch](./doc/source/a_region_ch.md) - [Lindau.ch](./doc/source/lindau_ch.md) -- [Münchenstein](./doc/source/muenchenstein_ch.md) ### United States of America diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/muenchenstein_ch.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/muenchenstein_ch.py deleted file mode 100755 index 3a5cb59f..00000000 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/muenchenstein_ch.py +++ /dev/null @@ -1,76 +0,0 @@ -import json -from datetime import datetime, timedelta - -import requests -from bs4 import BeautifulSoup -from waste_collection_schedule import Collection # type: ignore[attr-defined] - -TITLE = "Abfallsammlung Münchenstein" -DESCRIPTION = "Source for Muenchenstein waste collection." -URL = "https://www.muenchenstein.ch/abfallsammlung" -TEST_CASES = { - "Abfuhrkreis Ost": {"waste_district": "Abfuhrkreis Ost"}, - "Abfuhrkreis West": {"waste_district": "492"}, -} - - -IconMap = { - "kehricht": "mdi:trash-can", - "hackseldienst": "mdi:leaf", - "papierabfuhr": "mdi:newspaper-variant-multiple-outline", - "kartonabfuhr": "mdi:package-variant", - "altmetalle": "mdi:nail", -} - - -class Source: - def __init__(self, waste_district): - self._waste_district = waste_district - - def fetch(self): - response = requests.get(URL) - - html = BeautifulSoup(response.text, "html.parser") - - table = html.find("table", attrs={"id": "icmsTable-abfallsammlung"}) - data = json.loads(table.attrs["data-entities"]) - - entries = [] - for item in data["data"]: - if ( - self._waste_district in item["abfallkreisIds"] - or self._waste_district in item["abfallkreisNameList"] - ): - next_pickup = item["_anlassDate-sort"].split()[0] - next_pickup_date = datetime.fromisoformat(next_pickup).date() - - waste_type = BeautifulSoup(item["name"], "html.parser").text - waste_type_sorted = BeautifulSoup(item["name-sort"], "html.parser").text - - entries.append( - Collection( - date=next_pickup_date, - t=waste_type, - icon=IconMap.get(waste_type_sorted, "mdi:trash-can"), - ) - ) - - # Collection of "Kehricht und Kleinsperrgut brennbar" are not listed with dates as events on website. - # Instead it states the day of the week for each waste district: tuesday for east and friday for west - # So we're going to set those collections programmatically for the next 4 occurrences - weekday_collection = 2 if self._waste_district == 'Abfuhrkreis Ost' or self._waste_district == 491 else 4 - weekday_today = datetime.now().isoweekday() - for x in range(4): - days_to_pickup = (x * 7) + ((weekday_collection - weekday_today) % 7) - next_pickup_date = (datetime.now() + timedelta(days=days_to_pickup)).date() - waste_type = "Kehricht und Kleinsperrgut brennbar" - - entries.append( - Collection( - date=next_pickup_date, - t=waste_type, - icon=IconMap.get(waste_type_sorted, "mdi:trash-can"), - ) - ) - - return entries diff --git a/doc/source/muenchenstein_ch.md b/doc/source/muenchenstein_ch.md deleted file mode 100644 index 6ecc64f2..00000000 --- a/doc/source/muenchenstein_ch.md +++ /dev/null @@ -1,38 +0,0 @@ -# Abfallsammlung Münchenstein, BL, Switzerland - -Support for schedules provided by [https://www.muenchenstein.ch/abfallsammlung](https://www.muenchenstein.ch/abfallsammlung). - -This source is just a slight modification of [@atrox06](https://github.com/atrox06)'s work for lindau_ch. So kudos to him. - -## Configuration via configuration.yaml - -```yaml -waste_collection_schedule: - sources: - - name: muenchenstein_ch - args: - waste_district: DISTRICT - -``` - -### Configuration Variables - -**waste_district**
-*(string) (required)* - -Valid options for waste_district: -- Abfuhrkreis Ost -- Abfuhrkreis West - -or use one the following IDs: 491 for "Ost", 492 for "West" - -## Example - -```yaml -waste_collection_schedule: - sources: - - name: muenchenstein_ch - args: - waste_district: Abfuhrkreis West - -``` diff --git a/info.md b/info.md index a904563c..49e39956 100644 --- a/info.md +++ b/info.md @@ -165,7 +165,6 @@ Currently the following service providers are supported: - [A-Region.ch](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/a_region_ch.md) - [Lindau.ch](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/lindau_ch.md) -- [Münchenstein](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/muenchenstein_ch.md) ### United States of America From 293f2a1187094712fe63bb0e21f3c1edbdbe9224 Mon Sep 17 00:00:00 2001 From: mampfes Date: Sat, 24 Dec 2022 11:01:36 +0100 Subject: [PATCH 034/127] replace
with double-spaces in markdown files --- doc/source/a_region_ch.md | 4 +- doc/source/abfall_io.md | 12 +-- doc/source/abfall_zollernalbkreis_de.md | 6 +- doc/source/abfallnavi_de.md | 8 +- doc/source/abfalltermine_forchheim_de.md | 9 +- doc/source/alw_wf_de.md | 4 +- doc/source/aucklandcouncil_govt_nz.md | 2 +- doc/source/aw_harburg_de.md | 6 +- doc/source/awb_bad_kreuznach_de.md | 6 +- doc/source/awb_es_de.md | 4 +- doc/source/awb_oldenburg_de.md | 4 +- doc/source/awbkoeln_de.md | 4 +- doc/source/awido_de.md | 8 +- doc/source/awn_de.md | 8 +- doc/source/awr_de.md | 4 +- doc/source/awsh_de.md | 4 +- doc/source/banyule_vic_gov_au.md | 4 +- doc/source/belmont_wa_gov_au.md | 2 +- doc/source/berlin_recycling_de.md | 4 +- doc/source/bielefeld_de.md | 6 +- doc/source/bmv_at.md | 15 ++-- doc/source/bradford_gov_uk.md | 2 +- doc/source/brisbane_qld_gov_au.md | 6 +- doc/source/bsr_de.md | 4 +- doc/source/cambridge_gov_uk.md | 4 +- doc/source/campbelltown_nsw_gov_au.md | 8 +- doc/source/canadabay_nsw_gov_au.md | 6 +- doc/source/canterbury_gov_uk.md | 4 +- doc/source/ccc_govt_nz.md | 2 +- doc/source/cheshire_east_gov_uk.md | 6 +- doc/source/chesterfield_gov_uk.md | 2 +- doc/source/cochem_zell_online_de.md | 2 +- doc/source/colchester_gov_uk.md | 2 +- doc/source/cornwall_gov_uk.md | 6 +- doc/source/data_umweltprofis_at.md | 2 +- doc/source/derby_gov_uk.md | 6 +- doc/source/egn_abfallkalender_de.md | 8 +- doc/source/elmbridge_gov_uk.md | 4 +- doc/source/environmentfirst_co_uk.md | 8 +- doc/source/erlangen_hoechstadt_de.md | 4 +- doc/source/fccenvironment_co_uk.md | 2 +- doc/source/grafikai_svara_lt.md | 10 +-- doc/source/guildford_gov_uk.md | 2 +- doc/source/horowhenua_govt_nz.md | 8 +- doc/source/huntingdonshire_gov_uk.md | 2 +- doc/source/hygea_be.md | 4 +- doc/source/ics.md | 107 +++++++---------------- doc/source/infeo_at.md | 4 +- doc/source/innerwest_nsw_gov_au.md | 6 +- doc/source/ipswich_qld_gov_au.md | 4 +- doc/source/jumomind_de.md | 6 +- doc/source/kaev_niederlausitz_de.md | 2 +- doc/source/kingston_gov_uk.md | 2 +- doc/source/kuringgai_nsw_gov_au.md | 8 +- doc/source/kwu_de.md | 6 +- doc/source/landkreis_rhoen_grabfeld.md | 6 +- doc/source/landkreis_wittmund_de.md | 4 +- doc/source/lerum_se.md | 2 +- doc/source/lewisham_gov_uk.md | 8 +- doc/source/lindau_ch.md | 2 +- doc/source/lrasha_de.md | 2 +- doc/source/manchester_uk.md | 2 +- doc/source/maroondah_vic_gov_au.md | 2 +- doc/source/melton_vic_gov_au.md | 2 +- doc/source/middlesbrough_gov_uk.md | 2 +- doc/source/miljoteknik_se.md | 2 +- doc/source/mrsc_vic_gov_au.md | 2 +- doc/source/muellmax_de.md | 8 +- doc/source/nawma_sa_gov_au.md | 6 +- doc/source/newcastle_gov_uk.md | 2 +- doc/source/nottingham_city_gov_uk.md | 2 +- doc/source/nsomerset_gov_uk.md | 4 +- doc/source/peterborough_gov_uk.md | 8 +- doc/source/pgh_st.md | 6 +- doc/source/recycleapp_be.md | 8 +- doc/source/recyclesmart_com.md | 4 +- doc/source/regioentsorgung_de.md | 6 +- doc/source/republicservices_com.md | 2 +- doc/source/rh_entsorgung_de.md | 8 +- doc/source/richmondshire_gov_uk.md | 2 +- doc/source/rushmoor_gov_uk.md | 2 +- doc/source/sbazv_de.md | 6 +- doc/source/scambs_gov_uk.md | 4 +- doc/source/seattle_gov.md | 4 +- doc/source/sector27_de.md | 4 +- doc/source/sheffield_gov_uk.md | 2 +- doc/source/ssam_se.md | 2 +- doc/source/stadt_willich_de.md | 2 +- doc/source/stadtreinigung_dresden_de.md | 2 +- doc/source/stadtreinigung_hamburg.md | 2 +- doc/source/stadtreinigung_leipzig_de.md | 4 +- doc/source/staedteservice_de.md | 4 +- doc/source/static.md | 14 +-- doc/source/stevenage_gov_uk.md | 4 +- doc/source/stonnington_vic_gov_au.md | 2 +- doc/source/stuttgart_de.md | 4 +- doc/source/sysav_se.md | 2 +- doc/source/tewkesbury_gov_uk.md | 2 +- doc/source/thehills_nsw_gov_au.md | 6 +- doc/source/vasyd_se.md | 2 +- doc/source/waipa_nz.md | 2 +- doc/source/walsall_gov_uk.md | 2 +- doc/source/warszawa19115_pl.md | 4 +- doc/source/was_wolfsburg_de.md | 4 +- doc/source/wastenet_org_nz.md | 2 +- doc/source/wellington_govt_nz.md | 8 +- doc/source/wermelskirchen_de.md | 4 +- doc/source/westberks_gov_uk.md | 6 +- doc/source/wiltshire_gov_uk.md | 4 +- doc/source/wsz_moosburg_at.md | 8 +- doc/source/wuerzburg_de.md | 4 +- doc/source/wyndham_vic_gov_au.md | 2 +- doc/source/ximmio_nl.md | 6 +- doc/source/york_gov_uk.md | 2 +- 114 files changed, 286 insertions(+), 331 deletions(-) diff --git a/doc/source/a_region_ch.md b/doc/source/a_region_ch.md index 73d9d3fa..1f0ce6d5 100644 --- a/doc/source/a_region_ch.md +++ b/doc/source/a_region_ch.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**municipality**
+**municipality** *(string) (required)* -**district**
+**district** *(string) (optional)* Some municipalities (like Rorschach) are separated into districts for several waste types (e.g. `Hauskehricht` and `Papier, Karton`). diff --git a/doc/source/abfall_io.md b/doc/source/abfall_io.md index 019c647d..cb89caf2 100644 --- a/doc/source/abfall_io.md +++ b/doc/source/abfall_io.md @@ -22,22 +22,22 @@ waste_collection_schedule: ### Configuration Variables -**key**
+**key** *(hash) (required)* -**f_id_kommune**
+**f_id_kommune** *(integer) (required)* -**f_id_bezirk**
+**f_id_bezirk** *(integer) (optional)* -**f_id_strasse**
+**f_id_strasse** *(integer) (required)* -**f_id_strasse_hnr**
+**f_id_strasse_hnr** *(string) (optional)* -**f_abfallarten**
+**f_abfallarten** *(list of integer) (optional)* ## Example diff --git a/doc/source/abfall_zollernalbkreis_de.md b/doc/source/abfall_zollernalbkreis_de.md index c9536fff..25dda320 100644 --- a/doc/source/abfall_zollernalbkreis_de.md +++ b/doc/source/abfall_zollernalbkreis_de.md @@ -26,13 +26,13 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**street**
+**street** *(integer) (optional)* -**types**
+**types** *(list of string) (required)* ## Example diff --git a/doc/source/abfallnavi_de.md b/doc/source/abfallnavi_de.md index ddda77da..960cd109 100644 --- a/doc/source/abfallnavi_de.md +++ b/doc/source/abfallnavi_de.md @@ -17,16 +17,16 @@ waste_collection_schedule: ### Configuration Variables -**service**
+**service** *(string) (required)* -**ort**
+**ort** *(string) (required)* -**strasse**
+**strasse** *(string) (required)* -**hausnummer**
+**hausnummer** *(string) (optional)* ## Example diff --git a/doc/source/abfalltermine_forchheim_de.md b/doc/source/abfalltermine_forchheim_de.md index 71eb3e76..7bab897b 100644 --- a/doc/source/abfalltermine_forchheim_de.md +++ b/doc/source/abfalltermine_forchheim_de.md @@ -15,16 +15,15 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**area**
+**area** *(string) (required)* ### How to get the source arguments The arguments can be found on [abfalltermine-forchheim.de](https://www.abfalltermine-forchheim.de/). Search for your area. Use the part in front of the dash as `city` argument and the part behind it as `area` argument. Do not insert additional spaces. -
**Examples** Forchheim - Bamberger Straße (nördlich der Adenauerallee) @@ -33,10 +32,10 @@ Forchheim - Bamberger Straße (nördlich der Adenauerallee) city: Forchheim area: Bamberger Straße (nördlich der Adenauerallee) ``` -
+ Dormitz - Dormitz ```yaml city: Dormitz area: Dormitz -``` \ No newline at end of file +``` diff --git a/doc/source/alw_wf_de.md b/doc/source/alw_wf_de.md index ef71cb3d..4feefb04 100644 --- a/doc/source/alw_wf_de.md +++ b/doc/source/alw_wf_de.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**ort**
+**ort** *(string) (required)* -**strasse**
+**strasse** *(string) (required)* ## Example diff --git a/doc/source/aucklandcouncil_govt_nz.md b/doc/source/aucklandcouncil_govt_nz.md index b5836373..5501b2c1 100644 --- a/doc/source/aucklandcouncil_govt_nz.md +++ b/doc/source/aucklandcouncil_govt_nz.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**area_number**
+**area_number** *(string) (required)* ## Example diff --git a/doc/source/aw_harburg_de.md b/doc/source/aw_harburg_de.md index 42b4c654..d7c19626 100644 --- a/doc/source/aw_harburg_de.md +++ b/doc/source/aw_harburg_de.md @@ -16,13 +16,13 @@ waste_collection_schedule: ### Configuration Variables -**level_1**
+**level_1** *(string) (required)* -**level_2**
+**level_2** *(string) (required)* -**level_3**
+**level_3** *(string) (optional - depending on level_2)* ## Example diff --git a/doc/source/awb_bad_kreuznach_de.md b/doc/source/awb_bad_kreuznach_de.md index 71d6c44c..538ad3f9 100644 --- a/doc/source/awb_bad_kreuznach_de.md +++ b/doc/source/awb_bad_kreuznach_de.md @@ -16,13 +16,13 @@ waste_collection_schedule: ### Configuration Variables -**ort**
+**ort** *(string) (required)* -**strasse**
+**strasse** *(string) (required)* -**nummer**
+**nummer** *(integer) (required)* ## Example diff --git a/doc/source/awb_es_de.md b/doc/source/awb_es_de.md index cb8b7d70..91b067e2 100644 --- a/doc/source/awb_es_de.md +++ b/doc/source/awb_es_de.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**street**
+**street** *(string) (required)* ## How to get the source arguments diff --git a/doc/source/awb_oldenburg_de.md b/doc/source/awb_oldenburg_de.md index cd5f3bc9..34d3f046 100644 --- a/doc/source/awb_oldenburg_de.md +++ b/doc/source/awb_oldenburg_de.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**street**
+**street** *(string) (required)* -**house_number**
+**house_number** *(string) (required)* ## Example diff --git a/doc/source/awbkoeln_de.md b/doc/source/awbkoeln_de.md index 7c0ed621..e04a0caf 100644 --- a/doc/source/awbkoeln_de.md +++ b/doc/source/awbkoeln_de.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**street_code**
+**street_code** *(string) (required)* -**building_number**
+**building_number** *(string) (required)* ## Example diff --git a/doc/source/awido_de.md b/doc/source/awido_de.md index 5501c077..6028b8ab 100644 --- a/doc/source/awido_de.md +++ b/doc/source/awido_de.md @@ -17,16 +17,16 @@ waste_collection_schedule: ### Configuration Variables -**customer**
+**customer** *(string) (required)* -**city**
+**city** *(string) (required)* -**street**
+**street** *(integer) (optional, depends on customer)* -**housenumber**
+**housenumber** *(integer) (optional, depends on customer)* ## Example diff --git a/doc/source/awn_de.md b/doc/source/awn_de.md index c4b295a0..d4d9e06a 100644 --- a/doc/source/awn_de.md +++ b/doc/source/awn_de.md @@ -17,16 +17,16 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**street**
+**street** *(string) (required)* -**house_number**
+**house_number** *(integer) (required)* -**address_suffix**
+**address_suffix** *(string) (optional) (default: "")* ## Example diff --git a/doc/source/awr_de.md b/doc/source/awr_de.md index 4fbeaf19..397633a0 100644 --- a/doc/source/awr_de.md +++ b/doc/source/awr_de.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**street**
+**street** *(string) (required)* ## Example diff --git a/doc/source/awsh_de.md b/doc/source/awsh_de.md index 10ff6651..48ec014c 100644 --- a/doc/source/awsh_de.md +++ b/doc/source/awsh_de.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**street**
+**street** *(string) (required)* ## Example diff --git a/doc/source/banyule_vic_gov_au.md b/doc/source/banyule_vic_gov_au.md index 7e995a74..b3a39435 100644 --- a/doc/source/banyule_vic_gov_au.md +++ b/doc/source/banyule_vic_gov_au.md @@ -17,10 +17,10 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (optional)* -**geolocation_id**
+**geolocation_id** *(string) (optional)* At least one argument must be provided. diff --git a/doc/source/belmont_wa_gov_au.md b/doc/source/belmont_wa_gov_au.md index e3fa379e..30053364 100644 --- a/doc/source/belmont_wa_gov_au.md +++ b/doc/source/belmont_wa_gov_au.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**address**
+**address** *(string) (required)* ## Example diff --git a/doc/source/berlin_recycling_de.md b/doc/source/berlin_recycling_de.md index 9ab0ae29..66634479 100644 --- a/doc/source/berlin_recycling_de.md +++ b/doc/source/berlin_recycling_de.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**username**
+**username** *(string) (required)* -**password**
+**password** *(string) (required)* ## Example diff --git a/doc/source/bielefeld_de.md b/doc/source/bielefeld_de.md index 9c0b0ef0..eddb2121 100644 --- a/doc/source/bielefeld_de.md +++ b/doc/source/bielefeld_de.md @@ -16,13 +16,13 @@ waste_collection_schedule: ### Configuration Variables -**street**
+**street** *(string) (required)* -**house_number**
+**house_number** *(integer) (required)* -**address_suffix**
+**address_suffix** *(string) (optional) (default: "")* ## Example diff --git a/doc/source/bmv_at.md b/doc/source/bmv_at.md index 3e5343cb..025156fa 100644 --- a/doc/source/bmv_at.md +++ b/doc/source/bmv_at.md @@ -20,17 +20,14 @@ Copy the values for `Ort`, `Straße` and `Hausnummer` into the configuration. Do ### Configuration Variables -**ORT**
-*(string) (required)*
-City +**ORT** +*(string) (required)* -**STRASSE**
-*(string) (required)*
-Street +**STRASSE** +*(string) (required)* -**HAUSNUMMER**
-*(string) (required)*
-Housenumber +**HAUSNUMMER** +*(string) (required)* ## Examples diff --git a/doc/source/bradford_gov_uk.md b/doc/source/bradford_gov_uk.md index 10a2a003..a5a29a0c 100644 --- a/doc/source/bradford_gov_uk.md +++ b/doc/source/bradford_gov_uk.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* ## Example diff --git a/doc/source/brisbane_qld_gov_au.md b/doc/source/brisbane_qld_gov_au.md index 84b0f29c..a6958f2e 100644 --- a/doc/source/brisbane_qld_gov_au.md +++ b/doc/source/brisbane_qld_gov_au.md @@ -16,13 +16,13 @@ waste_collection_schedule: ### Configuration Variables -**suburb**
+**suburb** *(string) (required)* -**street_name**
+**street_name** *(string) (required)* -**street_number**
+**street_number** *(string) (required)* ## Example diff --git a/doc/source/bsr_de.md b/doc/source/bsr_de.md index 71ab7e97..9c8ac66c 100644 --- a/doc/source/bsr_de.md +++ b/doc/source/bsr_de.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**abf_strasse**
+**abf_strasse** *(string) (required)* -**abf_hausnr**
+**abf_hausnr** *(string) (required)* ## Example diff --git a/doc/source/cambridge_gov_uk.md b/doc/source/cambridge_gov_uk.md index 3fe67289..72265066 100644 --- a/doc/source/cambridge_gov_uk.md +++ b/doc/source/cambridge_gov_uk.md @@ -16,10 +16,10 @@ waste_collection_schedule: ### Configuration Variables -**POST_CODE**
+**POST_CODE** *(string) (required)* -**NUMBER**
+**NUMBER** *(string) (required)* diff --git a/doc/source/campbelltown_nsw_gov_au.md b/doc/source/campbelltown_nsw_gov_au.md index e9ce6941..c66d42ec 100644 --- a/doc/source/campbelltown_nsw_gov_au.md +++ b/doc/source/campbelltown_nsw_gov_au.md @@ -17,16 +17,16 @@ waste_collection_schedule: ### Configuration Variables -**post_code**
+**post_code** *(string) (required)* -**suburb**
+**suburb** *(string) (required)* -**street_name**
+**street_name** *(string) (required)* -**street_number**
+**street_number** *(string) (required)* ## Example diff --git a/doc/source/canadabay_nsw_gov_au.md b/doc/source/canadabay_nsw_gov_au.md index 036bb409..57db14e1 100644 --- a/doc/source/canadabay_nsw_gov_au.md +++ b/doc/source/canadabay_nsw_gov_au.md @@ -16,13 +16,13 @@ waste_collection_schedule: ### Configuration Variables -**suburb**
+**suburb** *(string) (required)* -**street_name**
+**street_name** *(string) (required)* -**street_number**
+**street_number** *(string) (required)* ## Example diff --git a/doc/source/canterbury_gov_uk.md b/doc/source/canterbury_gov_uk.md index 8bae0d02..f91de56f 100644 --- a/doc/source/canterbury_gov_uk.md +++ b/doc/source/canterbury_gov_uk.md @@ -16,10 +16,10 @@ waste_collection_schedule: ### Configuration Variables -**POST_CODE**
+**POST_CODE** *(string) (required)* -**NUMBER**
+**NUMBER** *(string) (required)* diff --git a/doc/source/ccc_govt_nz.md b/doc/source/ccc_govt_nz.md index 324e0f6d..1e53a9e0 100644 --- a/doc/source/ccc_govt_nz.md +++ b/doc/source/ccc_govt_nz.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**address**
+**address** *(string) (required)* ## Bin Names example - Garbage, Recycle & Organic diff --git a/doc/source/cheshire_east_gov_uk.md b/doc/source/cheshire_east_gov_uk.md index 3f2c899c..dbf80b65 100644 --- a/doc/source/cheshire_east_gov_uk.md +++ b/doc/source/cheshire_east_gov_uk.md @@ -23,15 +23,15 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* --- or --- -**postcode**
+**postcode** *(string) (required)* -**name_number**
+**name_number** *(string) (required)* ## Examples diff --git a/doc/source/chesterfield_gov_uk.md b/doc/source/chesterfield_gov_uk.md index eb5e4e30..b835f15e 100644 --- a/doc/source/chesterfield_gov_uk.md +++ b/doc/source/chesterfield_gov_uk.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* This is required to unambiguously identify the property. diff --git a/doc/source/cochem_zell_online_de.md b/doc/source/cochem_zell_online_de.md index 8b2ee087..494ea88d 100644 --- a/doc/source/cochem_zell_online_de.md +++ b/doc/source/cochem_zell_online_de.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**district**
+**district** _(string) (required)_ ## Example diff --git a/doc/source/colchester_gov_uk.md b/doc/source/colchester_gov_uk.md index 4e10beb7..b8d911c1 100644 --- a/doc/source/colchester_gov_uk.md +++ b/doc/source/colchester_gov_uk.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**llpgid**
+**llpgid** *(string) (required)* #### How to find your `llpgid` code diff --git a/doc/source/cornwall_gov_uk.md b/doc/source/cornwall_gov_uk.md index 9350dbb4..4fbb074e 100644 --- a/doc/source/cornwall_gov_uk.md +++ b/doc/source/cornwall_gov_uk.md @@ -17,13 +17,13 @@ waste_collection_schedule: ### Configuration Variables -**postcode**
+**postcode** *(string) (optional)* -**hournameornumber**
+**hournameornumber** *(string) (optional)* -**uprn**
+**uprn** *(string) (optional)* Either the postcode and housenameornumber or the UPRN should be supplied in the arguments diff --git a/doc/source/data_umweltprofis_at.md b/doc/source/data_umweltprofis_at.md index 15bd4143..b4beceeb 100644 --- a/doc/source/data_umweltprofis_at.md +++ b/doc/source/data_umweltprofis_at.md @@ -17,7 +17,7 @@ waste_collection_schedule: ### Configuration Variables -**xmlurl**
+**xmlurl** *(URL) (required)* ## Example diff --git a/doc/source/derby_gov_uk.md b/doc/source/derby_gov_uk.md index ed502ff2..0714d0d3 100644 --- a/doc/source/derby_gov_uk.md +++ b/doc/source/derby_gov_uk.md @@ -27,13 +27,13 @@ waste_collection_schedule: ### Configuration Variables -**premises_id**
+**premises_id** *(int) (required if post_code not provided)* -**post_code**
+**post_code** *(string) (required if premises_id not provided)* -**house_number**
+**house_number** *(int) (required if premises_id not provided)* ## Example diff --git a/doc/source/egn_abfallkalender_de.md b/doc/source/egn_abfallkalender_de.md index 304ed574..85c13ca8 100644 --- a/doc/source/egn_abfallkalender_de.md +++ b/doc/source/egn_abfallkalender_de.md @@ -19,19 +19,19 @@ The arguments can be found above the calendar after generating one [here](https: ### Configuration Variables -**city**
+**city** *(string) (required)* City, extracted from the displayed address. -**district**
+**district** *(string) (required)* District, extracted from the displayed address. -**street**
+**street** *(string) (required)* Street, extracted from the displayed address. -**housenumber**
+**housenumber** *(string) (required)* Housenumber, extracted from the displayed address. diff --git a/doc/source/elmbridge_gov_uk.md b/doc/source/elmbridge_gov_uk.md index d714e020..845cc547 100644 --- a/doc/source/elmbridge_gov_uk.md +++ b/doc/source/elmbridge_gov_uk.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* This is required to unambiguously identify the property. @@ -72,7 +72,7 @@ Trying to convert all this into a schedule of dates for each specific waste col * It assumes the week-commencing dates are for the current year. * This'll cause problems in December as upcoming January collections will have been assigned dates in the past. * Some clunky logic can deal with this: - * If a date in less than 1 month in the past, it doesn't matter as the collection will have recently occured. + * If a date in less than 1 month in the past, it doesn't matter as the collection will have recently occurred. * If a date is more than 1 month in the past, assume it's an incorrectly assigned date and increments the year by 1. * Once that's been done, offset the week-commencing dates to match day of the week indicated for each waste collection type. diff --git a/doc/source/environmentfirst_co_uk.md b/doc/source/environmentfirst_co_uk.md index b6ef2154..75c71d65 100644 --- a/doc/source/environmentfirst_co_uk.md +++ b/doc/source/environmentfirst_co_uk.md @@ -17,22 +17,22 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (optional) (preferred method)* This is required if you do not supply any other options. Using a UPRN removes the need to do an address look up using web requests. -**post_code**
+**post_code** *(string) (optional)* This is required if you do not supply a UPRN. Single space between 1st and 2nd part of postcode is optional. -**number**
+**number** *(string) (optional)* This is required if you supply a Postcode and have a house number. -**name**
+**name** *(string) (optional)* This is required if you supply a Postcode and you have a house name rather than a house number. diff --git a/doc/source/erlangen_hoechstadt_de.md b/doc/source/erlangen_hoechstadt_de.md index 49d21c04..fa4b493b 100644 --- a/doc/source/erlangen_hoechstadt_de.md +++ b/doc/source/erlangen_hoechstadt_de.md @@ -14,10 +14,10 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**street**
+**street** *(string) (required)* ### How to get the source arguments diff --git a/doc/source/fccenvironment_co_uk.md b/doc/source/fccenvironment_co_uk.md index 99aedccc..6114b587 100644 --- a/doc/source/fccenvironment_co_uk.md +++ b/doc/source/fccenvironment_co_uk.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* This is required to unambiguously identify the property. diff --git a/doc/source/grafikai_svara_lt.md b/doc/source/grafikai_svara_lt.md index 3b256b08..40973bae 100644 --- a/doc/source/grafikai_svara_lt.md +++ b/doc/source/grafikai_svara_lt.md @@ -19,19 +19,19 @@ waste_collection_schedule: ### Configuration Variables -**region**
+**region** *(string) (required)* -**street**
+**street** *(string) (required)* -**house_number**
+**house_number** *(string) (required)* -**district**
+**district** *(string) (optional)* -**waste_object_ids**
+**waste_object_ids** *(list) (optional)* ## Example diff --git a/doc/source/guildford_gov_uk.md b/doc/source/guildford_gov_uk.md index 74b4b39b..26744d23 100644 --- a/doc/source/guildford_gov_uk.md +++ b/doc/source/guildford_gov_uk.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* ## Example diff --git a/doc/source/horowhenua_govt_nz.md b/doc/source/horowhenua_govt_nz.md index 275a24d1..78af4cb4 100644 --- a/doc/source/horowhenua_govt_nz.md +++ b/doc/source/horowhenua_govt_nz.md @@ -17,16 +17,16 @@ waste_collection_schedule: ### Configuration Variables -**post_code**
+**post_code** *(string) (required)* -**town**
+**town** *(string) (required)* -**street_name**
+**street_name** *(string) (required)* -**street_number**
+**street_number** *(string) (required)* ## Example diff --git a/doc/source/huntingdonshire_gov_uk.md b/doc/source/huntingdonshire_gov_uk.md index 924520de..4dcfdc01 100644 --- a/doc/source/huntingdonshire_gov_uk.md +++ b/doc/source/huntingdonshire_gov_uk.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* ## Example diff --git a/doc/source/hygea_be.md b/doc/source/hygea_be.md index 13d89b3f..14e74cfb 100644 --- a/doc/source/hygea_be.md +++ b/doc/source/hygea_be.md @@ -17,11 +17,11 @@ The arguments can be found in the URL after visiting the [the calendar](https:// ### Configuration Variables -**streetIndex**
+**streetIndex** *(int)* Street index, extracted from URL. -**cp**
+**cp** *(int)* Postal code, extracted from URL. diff --git a/doc/source/ics.md b/doc/source/ics.md index 51c08d6c..fe527bd5 100644 --- a/doc/source/ics.md +++ b/doc/source/ics.md @@ -96,7 +96,7 @@ waste_collection_schedule: ### Configuration Variables -**url**
+**url** *(string) (optional)* URL to ICS / iCal file. File will be downloaded using a HTTP GET request. @@ -105,26 +105,26 @@ If the original url contains the current year (4 digits including century), this You have to specify either `url` or `file`! -**file**
+**file** *(string) (optional)* Local ICS / iCal file name. Can be used instead of `url` for local files. You have to specify either `url` or `file`! -**offset**
+**offset** *(int) (optional, default: `0`)* Offset in days which will be added to every start time. Can be used if the start time of the events in the ICS file are ahead of the actual date. -**method**
+**method** *(string) (optional, default: `GET`)* Method to send the URL `params`. Need to be `GET` or `POST`. -**params**
+**params** *(dict) (optional, default: None)* Dictionary, list of tuples or bytes to send in the query string for the HTTP request. @@ -136,24 +136,24 @@ This gets Only used if `url` is specified, not used for `file`. -**year_field**
+**year_field** *(string) (optional, default: None)* Field in params dictionary to be replaced with current year (4 digits including century). -**regex**
+**regex** *(string) (optional, default: None)* Regular expression used to remove needless text from collection types. See also example below. -**split_at**
+**split_at** *(string) (optional, default: None)* Delimiter to split event summary into individual collection types. If your service puts multiple collections types which occur at the same day into a single event, this option can be used to separate the collection types again. -**version**
+**version** *(integer) (optional, default: 2)* Selects the underlying ICS file parser: @@ -161,7 +161,7 @@ Selects the underlying ICS file parser: - version: 1 uses `recurring_ical_events` - version: 2 uses `icalevents` -**verify_ssl**
+**verify_ssl** *(boolean) (optional, default: True)* Allows do disable SSL certificate checks in case the HTTPS server of your service provider is misconfigured and therefore doesn't send intermediate certificates. Unlike browsers, python doesn't support automatic fetching of missing intermediates. @@ -622,14 +622,19 @@ waste_collection_schedule: ``` You can also compose the URL yourself. You need the following elements for this: -1. the nis-code of your municipality: query the api with the name of your municipality;
example: https://limburg.net/api-proxy/public/afval-kalender/gemeenten/search?query=Peer -```json -[{"nisCode":"72030","naam":"Peer"}] -``` -2. the number of your street: query the api with the nis-code of your municipality and the name of your street;
example: https://limburg.net/api-proxy/public/afval-kalender/gemeente/72030/straten/search?query=Zuidervest -```json -[{"nummer":"66536","naam":"Zuidervest"}] -``` +1. the nis-code of your municipality: query the api with the name of your municipality; example: https://limburg.net/api-proxy/public/afval-kalender/gemeenten/search?query=Peer + + ```json + [{"nisCode":"72030","naam":"Peer"}] + ``` + +2. the number of your street: query the api with the nis-code of your municipality and the name of your street +example: https://limburg.net/api-proxy/public/afval-kalender/gemeente/72030/straten/search?query=Zuidervest + + ```json + [{"nummer":"66536","naam":"Zuidervest"}] + ``` + 3. your housenumber ```yaml @@ -722,15 +727,16 @@ sensor: The Bromley council has a simple way to generate an iCal. All you need is the URL - * Go to [Bromley Bin Collection](https://recyclingservices.bromley.gov.uk/waste) - * Enter your post code, then select your address from the dropdown. The results page will show your collection schedule. - * Your unique code can be found in the URL, eg: *recyclingservices.bromley.gov.uk/waste/`6261994`* - * You can either use the following link and replace your ID, or copy the link address on the "Add to you calendar" link: *https://recyclingservices.bromley.gov.uk/waste/6261994/calendar.ics* +- Go to [Bromley Bin Collection](https://recyclingservices.bromley.gov.uk/waste) +- Enter your post code, then select your address from the dropdown. The results page will show your collection schedule. +- Your unique code can be found in the URL, eg: *recyclingservices.bromley.gov.uk/waste/`6261994`* +- You can either use the following link and replace your ID, or copy the link address on the "Add to you calendar" link: *https://recyclingservices.bromley.gov.uk/waste/6261994/calendar.ics* Note: - * This has been designed to break each bin collection into different sensors. - * This was created at a property that has a garden waste subscription. You may need to amit that from the code - * This display number of days until collection. Replace `value_template` with `date_template: '{{value.date.strftime("%A %d %B %Y")}}'` to display date of collection + +- This has been designed to break each bin collection into different sensors. +- This was created at a property that has a garden waste subscription. You may need to amit that from the code +- This display number of days until collection. Replace `value_template` with `date_template: '{{value.date.strftime("%A %d %B %Y")}}'` to display date of collection ```yaml #Waste Collection - London Borough of Bromley @@ -752,54 +758,7 @@ waste_collection_schedule: args: url: YOUR_URL version: 2 - -sensor: - #Food Waste - - platform: waste_collection_schedule - source_index: 0 - name: Bins - Food Waste Collection # Change this to whatever you want the UI to display, sensor name will be similar - types: - - Food Waste - details_format: appointment_types - value_template: "{% if value.daysTo == 0 %}Today{% elif value.daysTo == 1 %}Tomorrow{% else %}in {{value.daysTo}} days{% endif %}" - - #Garden Waste - - platform: waste_collection_schedule - source_index: 0 - name: Bins - Garden Waste Collection # Change this to whatever you want the UI to display, sensor name will be similar - types: - - Garden Waste - details_format: appointment_types - value_template: "{% if value.daysTo == 0 %}Today{% elif value.daysTo == 1 %}Tomorrow{% else %}in {{value.daysTo}} days{% endif %}" - - #Mixed Recycling - - platform: waste_collection_schedule - source_index: 0 - name: Bins - Mixed Recycling Collection # Change this to whatever you want the UI to display - types: - - Mixed Recycling - details_format: appointment_types - value_template: "{% if value.daysTo == 0 %}Today{% elif value.daysTo == 1 %}Tomorrow{% else %}in {{value.daysTo}} days{% endif %}" - - #General Waste - - platform: waste_collection_schedule - source_index: 0 - name: Bins - General Waste Collection # Change this to whatever you want the UI to display - types: - - General Waste - details_format: appointment_types - value_template: "{% if value.daysTo == 0 %}Today{% elif value.daysTo == 1 %}Tomorrow{% else %}in {{value.daysTo}} days{% endif %}" - - #Paper & Cardboard - - platform: waste_collection_schedule - source_index: 0 - name: Bins - Cardboard Collection # Change this to whatever you want the UI to display - types: - - Cardboard - details_format: appointment_types - value_template: "{% if value.daysTo == 0 %}Today{% elif value.daysTo == 1 %}Tomorrow{% else %}in {{value.daysTo}} days{% endif %}" - -``` + ``` *** @@ -830,4 +789,4 @@ waste_collection_schedule: regex: "(.*)\s+\|" ``` -*** \ No newline at end of file +*** diff --git a/doc/source/infeo_at.md b/doc/source/infeo_at.md index 5a169f2a..f7c8cda2 100644 --- a/doc/source/infeo_at.md +++ b/doc/source/infeo_at.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**customer**
+**customer** *(string) (required)* -**zone**
+**zone** *(string) (required)* ## Example diff --git a/doc/source/innerwest_nsw_gov_au.md b/doc/source/innerwest_nsw_gov_au.md index c44e13b4..6a5a7bac 100644 --- a/doc/source/innerwest_nsw_gov_au.md +++ b/doc/source/innerwest_nsw_gov_au.md @@ -16,13 +16,13 @@ waste_collection_schedule: ### Configuration Variables -**suburb**
+**suburb** *(string) (required)* -**street_name**
+**street_name** *(string) (required)* -**street_number**
+**street_number** *(string) (required)* ## Example diff --git a/doc/source/ipswich_qld_gov_au.md b/doc/source/ipswich_qld_gov_au.md index 198740bc..952ec443 100644 --- a/doc/source/ipswich_qld_gov_au.md +++ b/doc/source/ipswich_qld_gov_au.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**street**
+**street** *(string) (required)* -**suburb**
+**suburb** *(string) (required)* ## Example diff --git a/doc/source/jumomind_de.md b/doc/source/jumomind_de.md index bff40164..f21bd7ff 100644 --- a/doc/source/jumomind_de.md +++ b/doc/source/jumomind_de.md @@ -16,13 +16,13 @@ waste_collection_schedule: ### Configuration Variables -**service_id**
+**service_id** *(string) (required)* -**city_id**
+**city_id** *(string) (required)* -**area_id**
+**area_id** *(string) (required)* ## Example diff --git a/doc/source/kaev_niederlausitz_de.md b/doc/source/kaev_niederlausitz_de.md index e28604b3..9ccbdaef 100644 --- a/doc/source/kaev_niederlausitz_de.md +++ b/doc/source/kaev_niederlausitz_de.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**abf_suche**
+**abf_suche** *(string) (required)* diff --git a/doc/source/kingston_gov_uk.md b/doc/source/kingston_gov_uk.md index ca607f34..6a9b860a 100644 --- a/doc/source/kingston_gov_uk.md +++ b/doc/source/kingston_gov_uk.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* ## Example using UPRN diff --git a/doc/source/kuringgai_nsw_gov_au.md b/doc/source/kuringgai_nsw_gov_au.md index 86aec35e..199689ec 100644 --- a/doc/source/kuringgai_nsw_gov_au.md +++ b/doc/source/kuringgai_nsw_gov_au.md @@ -17,16 +17,16 @@ waste_collection_schedule: ### Configuration Variables -**post_code**
+**post_code** *(string) (required)* -**suburb**
+**suburb** *(string) (required)* -**street_name**
+**street_name** *(string) (required)* -**street_number**
+**street_number** *(string) (required)* ## Example diff --git a/doc/source/kwu_de.md b/doc/source/kwu_de.md index a736045a..77a29e2b 100644 --- a/doc/source/kwu_de.md +++ b/doc/source/kwu_de.md @@ -16,13 +16,13 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**street**
+**street** *(string) (required)* -**number**
+**number** *(string) (required)* ## How to get the source arguments diff --git a/doc/source/landkreis_rhoen_grabfeld.md b/doc/source/landkreis_rhoen_grabfeld.md index 90f7225b..d0ed4c80 100644 --- a/doc/source/landkreis_rhoen_grabfeld.md +++ b/doc/source/landkreis_rhoen_grabfeld.md @@ -22,12 +22,12 @@ waste_collection_schedule: ### Configuration Variables -**district** and **city** can be used independently, they can also be omitted to get the calender for the whole rural district. +**district** and **city** can be used independently, they can also be omitted to get the calendar for the whole rural district. -**district**
+**district** *(string)* -**street**
+**street** *(string)* ## Example diff --git a/doc/source/landkreis_wittmund_de.md b/doc/source/landkreis_wittmund_de.md index 22e1537e..766fa504 100644 --- a/doc/source/landkreis_wittmund_de.md +++ b/doc/source/landkreis_wittmund_de.md @@ -16,10 +16,10 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**street**
+**street** *(string) (optional)* ## Example diff --git a/doc/source/lerum_se.md b/doc/source/lerum_se.md index a75974bc..859e8e47 100644 --- a/doc/source/lerum_se.md +++ b/doc/source/lerum_se.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (required)* ## Example diff --git a/doc/source/lewisham_gov_uk.md b/doc/source/lewisham_gov_uk.md index 8f65871a..3d4fb8ef 100644 --- a/doc/source/lewisham_gov_uk.md +++ b/doc/source/lewisham_gov_uk.md @@ -17,22 +17,22 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (optional)* This is required if you do not supply any other options. (Using this removes the need to do an address look up web request) -**name**
+**name** *(string) (optional)* This is required if you supply a Postcode and do not have a house number. -**number**
+**number** *(string) (optional)* This is required if you supply a Postcode and have a house number. -**post_code**
+**post_code** *(string) (optional)* This is required if you do not supply a UPRN. Single space between 1st and 2nd part of postcode is optional. diff --git a/doc/source/lindau_ch.md b/doc/source/lindau_ch.md index c36f5b68..98a6d370 100644 --- a/doc/source/lindau_ch.md +++ b/doc/source/lindau_ch.md @@ -15,7 +15,7 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* Choose one of the following list: diff --git a/doc/source/lrasha_de.md b/doc/source/lrasha_de.md index fdd418de..feb62a77 100644 --- a/doc/source/lrasha_de.md +++ b/doc/source/lrasha_de.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**location**
+**location** *(string) (required)* ## How to get the source arguments diff --git a/doc/source/manchester_uk.md b/doc/source/manchester_uk.md index 0fdf99b6..759fdc6d 100644 --- a/doc/source/manchester_uk.md +++ b/doc/source/manchester_uk.md @@ -16,7 +16,7 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* ## Example diff --git a/doc/source/maroondah_vic_gov_au.md b/doc/source/maroondah_vic_gov_au.md index 2c5d8522..15f33795 100644 --- a/doc/source/maroondah_vic_gov_au.md +++ b/doc/source/maroondah_vic_gov_au.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**address**
+**address** *(string) (required)* ## Example diff --git a/doc/source/melton_vic_gov_au.md b/doc/source/melton_vic_gov_au.md index a7b22fca..8d259497 100644 --- a/doc/source/melton_vic_gov_au.md +++ b/doc/source/melton_vic_gov_au.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (required)* ## Example diff --git a/doc/source/middlesbrough_gov_uk.md b/doc/source/middlesbrough_gov_uk.md index bd7cd23b..75ba55a4 100644 --- a/doc/source/middlesbrough_gov_uk.md +++ b/doc/source/middlesbrough_gov_uk.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* ## Example using UPRN diff --git a/doc/source/miljoteknik_se.md b/doc/source/miljoteknik_se.md index 964d439e..bd3d1a37 100644 --- a/doc/source/miljoteknik_se.md +++ b/doc/source/miljoteknik_se.md @@ -16,7 +16,7 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (required)* ## Example diff --git a/doc/source/mrsc_vic_gov_au.md b/doc/source/mrsc_vic_gov_au.md index b01afde5..69bcbc35 100644 --- a/doc/source/mrsc_vic_gov_au.md +++ b/doc/source/mrsc_vic_gov_au.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (required)* ## Example diff --git a/doc/source/muellmax_de.md b/doc/source/muellmax_de.md index 5cfb9b8d..9622250e 100644 --- a/doc/source/muellmax_de.md +++ b/doc/source/muellmax_de.md @@ -17,16 +17,16 @@ waste_collection_schedule: ### Configuration Variables -**service**
+**service** *(string) (required)* -**mm_frm_ort_sel**
+**mm_frm_ort_sel** *(string) (optional)* -**mm_frm_str_sel**
+**mm_frm_str_sel** *(string) (optional)* -**mm_frm_hnr_sel**
+**mm_frm_hnr_sel** *(string) (optional)* ## Example diff --git a/doc/source/nawma_sa_gov_au.md b/doc/source/nawma_sa_gov_au.md index 3e97c82f..d684e119 100644 --- a/doc/source/nawma_sa_gov_au.md +++ b/doc/source/nawma_sa_gov_au.md @@ -17,13 +17,13 @@ waste_collection_schedule: ### Configuration Variables -**suburb**
+**suburb** *(string) (required)* -**street_name**
+**street_name** *(string) (required)* -**street_number**
+**street_number** *(string) (optional)* Only required if the street crosses multiple collection areas with different days. diff --git a/doc/source/newcastle_gov_uk.md b/doc/source/newcastle_gov_uk.md index 0e19477f..c31b59ff 100644 --- a/doc/source/newcastle_gov_uk.md +++ b/doc/source/newcastle_gov_uk.md @@ -16,7 +16,7 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* This is required to unambiguously identify the property. diff --git a/doc/source/nottingham_city_gov_uk.md b/doc/source/nottingham_city_gov_uk.md index ccfd814b..5e7a637a 100644 --- a/doc/source/nottingham_city_gov_uk.md +++ b/doc/source/nottingham_city_gov_uk.md @@ -16,7 +16,7 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* ## Example diff --git a/doc/source/nsomerset_gov_uk.md b/doc/source/nsomerset_gov_uk.md index 64d5a940..cb13df37 100644 --- a/doc/source/nsomerset_gov_uk.md +++ b/doc/source/nsomerset_gov_uk.md @@ -17,10 +17,10 @@ waste_collection_schedule: ### Configuration Variables -**postcode**
+**postcode** *(string) (required)* -**uprn**
+**uprn** *(string) (required)* ## Examples diff --git a/doc/source/peterborough_gov_uk.md b/doc/source/peterborough_gov_uk.md index 69c0fd06..ffc01c83 100644 --- a/doc/source/peterborough_gov_uk.md +++ b/doc/source/peterborough_gov_uk.md @@ -17,22 +17,22 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (optional)* This is required if you do not supply any other options. (Using this removes the need to do an address look up web request) -**name**
+**name** *(string) (optional)* This is required if you supply a Postcode and do not have a house number. -**number**
+**number** *(string) (optional)* This is required if you supply a Postcode and have a house number. -**post_code**
+**post_code** *(string) (optional)* This is required if you do not supply a UPRN. Single space between 1st and 2nd part of postcode is optional. diff --git a/doc/source/pgh_st.md b/doc/source/pgh_st.md index cd84b904..a78fb362 100644 --- a/doc/source/pgh_st.md +++ b/doc/source/pgh_st.md @@ -16,13 +16,13 @@ waste_collection_schedule: ### Configuration Variables -**house_number**
+**house_number** *(integer) (required)* -**street_name**
+**street_name** *(string) (required)* -**zipcode**
+**zipcode** *(integer) (required)* ## Example diff --git a/doc/source/recycleapp_be.md b/doc/source/recycleapp_be.md index 3d7155ba..419a649d 100644 --- a/doc/source/recycleapp_be.md +++ b/doc/source/recycleapp_be.md @@ -19,19 +19,19 @@ The source arguments are simply the values of the form elements on the homepage. ### Configuration Variables -**postcode**
+**postcode** *(int)* Postal Code. -**street**
+**street** *(string)* Street name. -**house_number**
+**house_number** *(int)* House number -**add_events**
+**add_events** *(boolean)* Add events (e.g. Repair Cafe) in addition to waste collections. diff --git a/doc/source/recyclesmart_com.md b/doc/source/recyclesmart_com.md index 5141928d..b5ec3ce9 100644 --- a/doc/source/recyclesmart_com.md +++ b/doc/source/recyclesmart_com.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**email**
+**email** *(string) (required)* -**password**
+**password** *(string) (required)* ## Example diff --git a/doc/source/regioentsorgung_de.md b/doc/source/regioentsorgung_de.md index 3feb0496..cfb5af59 100644 --- a/doc/source/regioentsorgung_de.md +++ b/doc/source/regioentsorgung_de.md @@ -16,13 +16,13 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**street**
+**street** *(string) (required)* -**house_number**
+**house_number** *(string | number) (required)* ## Example diff --git a/doc/source/republicservices_com.md b/doc/source/republicservices_com.md index 5feca1c2..4e820062 100644 --- a/doc/source/republicservices_com.md +++ b/doc/source/republicservices_com.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (required)* ## Example diff --git a/doc/source/rh_entsorgung_de.md b/doc/source/rh_entsorgung_de.md index 7242e9b0..f7433805 100644 --- a/doc/source/rh_entsorgung_de.md +++ b/doc/source/rh_entsorgung_de.md @@ -17,16 +17,16 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**street**
+**street** *(string) (required)* -**house_number**
+**house_number** *(integer) (required)* -**address_suffix**
+**address_suffix** *(string) (optional) (default: "")* ## Example diff --git a/doc/source/richmondshire_gov_uk.md b/doc/source/richmondshire_gov_uk.md index d1424739..af8c9492 100644 --- a/doc/source/richmondshire_gov_uk.md +++ b/doc/source/richmondshire_gov_uk.md @@ -15,7 +15,7 @@ waste_collection_schedule: ### Configuration Variables -**UPRN**
+**UPRN** *(integer) (required)* ## Example diff --git a/doc/source/rushmoor_gov_uk.md b/doc/source/rushmoor_gov_uk.md index b223aabe..17aa0637 100644 --- a/doc/source/rushmoor_gov_uk.md +++ b/doc/source/rushmoor_gov_uk.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* ## Example diff --git a/doc/source/sbazv_de.md b/doc/source/sbazv_de.md index d959be9d..0e73a456 100644 --- a/doc/source/sbazv_de.md +++ b/doc/source/sbazv_de.md @@ -16,13 +16,13 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**district**
+**district** *(string) (required)* -**street**
+**street** *(string) (required)* ## Example diff --git a/doc/source/scambs_gov_uk.md b/doc/source/scambs_gov_uk.md index 4c009038..7e991acc 100644 --- a/doc/source/scambs_gov_uk.md +++ b/doc/source/scambs_gov_uk.md @@ -17,10 +17,10 @@ waste_collection_schedule: ### Configuration Variables -**POST_CODE**
+**POST_CODE** *(string) (required)* -**NUMBER**
+**NUMBER** *(string) (required)* ## Example diff --git a/doc/source/seattle_gov.md b/doc/source/seattle_gov.md index 17169349..4ee26744 100644 --- a/doc/source/seattle_gov.md +++ b/doc/source/seattle_gov.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (required)* -**prem_code**
+**prem_code** *(string) (optional)* ## Example diff --git a/doc/source/sector27_de.md b/doc/source/sector27_de.md index e8831298..aeac17bf 100644 --- a/doc/source/sector27_de.md +++ b/doc/source/sector27_de.md @@ -19,10 +19,10 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**street**
+**street** *(string) (required)* ## Example diff --git a/doc/source/sheffield_gov_uk.md b/doc/source/sheffield_gov_uk.md index 82aa8800..5957d2f7 100644 --- a/doc/source/sheffield_gov_uk.md +++ b/doc/source/sheffield_gov_uk.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (optional) (preferred method)* This is required if you do not supply any other options. Using a UPRN removes the need to do an address look up using web requests. diff --git a/doc/source/ssam_se.md b/doc/source/ssam_se.md index a3f1e88f..448031fb 100644 --- a/doc/source/ssam_se.md +++ b/doc/source/ssam_se.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (required)* ## Example diff --git a/doc/source/stadt_willich_de.md b/doc/source/stadt_willich_de.md index ac2ff7c8..6aaf3e8e 100644 --- a/doc/source/stadt_willich_de.md +++ b/doc/source/stadt_willich_de.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**street**
+**street** *(string) (required)* ## How to get the source arguments diff --git a/doc/source/stadtreinigung_dresden_de.md b/doc/source/stadtreinigung_dresden_de.md index a0e8f7f7..70276151 100644 --- a/doc/source/stadtreinigung_dresden_de.md +++ b/doc/source/stadtreinigung_dresden_de.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**standort**
+**standort** *(string) (required)* ## Example diff --git a/doc/source/stadtreinigung_hamburg.md b/doc/source/stadtreinigung_hamburg.md index 2f1b83a3..7614fd5c 100644 --- a/doc/source/stadtreinigung_hamburg.md +++ b/doc/source/stadtreinigung_hamburg.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**hnId**
+**hnId** *(string) (required)* ## Example diff --git a/doc/source/stadtreinigung_leipzig_de.md b/doc/source/stadtreinigung_leipzig_de.md index d5bac15a..92b40c61 100644 --- a/doc/source/stadtreinigung_leipzig_de.md +++ b/doc/source/stadtreinigung_leipzig_de.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**street**
+**street** *(string) (required)* -**house_number**
+**house_number** *(string) (required)* ## Example diff --git a/doc/source/staedteservice_de.md b/doc/source/staedteservice_de.md index 33f00cd1..1f943bf3 100644 --- a/doc/source/staedteservice_de.md +++ b/doc/source/staedteservice_de.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**street_number**
+**street_number** *(string) (required)* ## Example diff --git a/doc/source/static.md b/doc/source/static.md index 61fc38f9..a69a73c2 100644 --- a/doc/source/static.md +++ b/doc/source/static.md @@ -20,40 +20,40 @@ waste_collection_schedule: ### Configuration Variables -**TYPE**
+**TYPE** *(string) (required)* The type of this source. -**DATES**
+**DATES** *(list) (optional)* A list of dates in format "YYYY-MM-DD" which should be added to the source. Dates defined in this list will be added in addition to calculated dates from the recurrence and will not be affected by the exclude-list. -**FREQUENCY**
+**FREQUENCY** *(string) (optional)* Defines the frequency of the recurrence. Must be one of "DAILY", "WEEKLY", "MONTHLY" or "YEARLY". -**INTERVAL**
+**INTERVAL** *(int) (optional, default: ```1```)* Defines the interval of the recurrence. -**START**
+**START** *(string) (optional)* Defines the start of the recurrence in the format "YYYY-MM-DD". Required if *FREQUENCY* is set. -**UNTIL**
+**UNTIL** *(string) (optional)* Defines the end of the recurrence in the format "YYYY-MM-DD". Required if *FREQUENCY* is set. -**EXCLUDES**
+**EXCLUDES** *(list) (optional)* A list of dates in format "YYYY-MM-DD" which should be excluded from the recurrence. diff --git a/doc/source/stevenage_gov_uk.md b/doc/source/stevenage_gov_uk.md index 2bfccac3..f03abbba 100644 --- a/doc/source/stevenage_gov_uk.md +++ b/doc/source/stevenage_gov_uk.md @@ -15,12 +15,12 @@ waste_collection_schedule: ### Configuration Variables -**postcode**
+**postcode** *(string) (required)* Postcode of property. This is required. Stevenage Borough Council API does not support UKPRN. Single space between 1st and 2nd part of postcode is optional. -**road**
+**road** *(string) (required)* Name of road property is in. This is required. diff --git a/doc/source/stonnington_vic_gov_au.md b/doc/source/stonnington_vic_gov_au.md index 8b80441d..1e9f1be5 100644 --- a/doc/source/stonnington_vic_gov_au.md +++ b/doc/source/stonnington_vic_gov_au.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (required)* ## Example diff --git a/doc/source/stuttgart_de.md b/doc/source/stuttgart_de.md index 5e55194a..ad34b8fc 100644 --- a/doc/source/stuttgart_de.md +++ b/doc/source/stuttgart_de.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**street**
+**street** *(string) (required)* -**streetnr**
+**streetnr** *(string) (required)* ## Example diff --git a/doc/source/sysav_se.md b/doc/source/sysav_se.md index 4a00ae8b..65258a27 100644 --- a/doc/source/sysav_se.md +++ b/doc/source/sysav_se.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (required)* ## Example diff --git a/doc/source/tewkesbury_gov_uk.md b/doc/source/tewkesbury_gov_uk.md index cccea756..ccc177e1 100644 --- a/doc/source/tewkesbury_gov_uk.md +++ b/doc/source/tewkesbury_gov_uk.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**POSTCODE**
+**POSTCODE** *(string) (required)* diff --git a/doc/source/thehills_nsw_gov_au.md b/doc/source/thehills_nsw_gov_au.md index 35e517fe..59252d09 100644 --- a/doc/source/thehills_nsw_gov_au.md +++ b/doc/source/thehills_nsw_gov_au.md @@ -16,13 +16,13 @@ waste_collection_schedule: ### Configuration Variables -**suburb**
+**suburb** *(string) (required)* -**street**
+**street** *(string) (required)* -**houseNo**
+**houseNo** *(string) (required)* ## Example diff --git a/doc/source/vasyd_se.md b/doc/source/vasyd_se.md index 751f44a9..b6bd3d45 100644 --- a/doc/source/vasyd_se.md +++ b/doc/source/vasyd_se.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (required)* ## Example diff --git a/doc/source/waipa_nz.md b/doc/source/waipa_nz.md index c8fa6583..487e65b6 100644 --- a/doc/source/waipa_nz.md +++ b/doc/source/waipa_nz.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**address**
+**address** *(string) (required)* ## Example diff --git a/doc/source/walsall_gov_uk.md b/doc/source/walsall_gov_uk.md index 698cf4fa..42058c18 100644 --- a/doc/source/walsall_gov_uk.md +++ b/doc/source/walsall_gov_uk.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (optional) (preferred method)* This is required if you do not supply any other options. Using a UPRN removes the need to do an address look up using web requests. diff --git a/doc/source/warszawa19115_pl.md b/doc/source/warszawa19115_pl.md index 94d5a435..8f0d079c 100644 --- a/doc/source/warszawa19115_pl.md +++ b/doc/source/warszawa19115_pl.md @@ -14,10 +14,10 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (optional)* -**geolocation_id**
+**geolocation_id** *(string) (optional)* At least one argument must be provided. diff --git a/doc/source/was_wolfsburg_de.md b/doc/source/was_wolfsburg_de.md index 6269f31d..38c7abf5 100644 --- a/doc/source/was_wolfsburg_de.md +++ b/doc/source/was_wolfsburg_de.md @@ -17,10 +17,10 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**street**
+**street** *(string) (required)* ## Example diff --git a/doc/source/wastenet_org_nz.md b/doc/source/wastenet_org_nz.md index 1cc01052..018c15b7 100644 --- a/doc/source/wastenet_org_nz.md +++ b/doc/source/wastenet_org_nz.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**address**
+**address** *(string) (required)* ## Example diff --git a/doc/source/wellington_govt_nz.md b/doc/source/wellington_govt_nz.md index b26ebe62..2d4b50d3 100644 --- a/doc/source/wellington_govt_nz.md +++ b/doc/source/wellington_govt_nz.md @@ -15,11 +15,11 @@ waste_collection_schedule: ### Configuration Variables -**streetName**
-*(string)*
+**streetName** +*(string)* -**streetId**
-*(string)*
+**streetId** +*(string)* *One of the above is required* diff --git a/doc/source/wermelskirchen_de.md b/doc/source/wermelskirchen_de.md index a6aab9f6..dd28229c 100644 --- a/doc/source/wermelskirchen_de.md +++ b/doc/source/wermelskirchen_de.md @@ -29,10 +29,10 @@ waste_collection_schedule: ### Configuration Variables -**street**
+**street** *(string) (required)* -**house_number**
+**house_number** *(string) (required)* ## How to get the source arguments diff --git a/doc/source/westberks_gov_uk.md b/doc/source/westberks_gov_uk.md index 3e39be4c..c38cac29 100644 --- a/doc/source/westberks_gov_uk.md +++ b/doc/source/westberks_gov_uk.md @@ -17,13 +17,13 @@ waste_collection_schedule: ### Configuration Variables -**postcode**
+**postcode** _(string) (optional)_ -**hournameornumber**
+**hournameornumber** _(string) (optional)_ -**uprn**
+**uprn** _(string) (optional)_ Either the postcode _and_ housenameornumber or the UPRN should be supplied in the arguments diff --git a/doc/source/wiltshire_gov_uk.md b/doc/source/wiltshire_gov_uk.md index c0fffdd8..317c8bc5 100644 --- a/doc/source/wiltshire_gov_uk.md +++ b/doc/source/wiltshire_gov_uk.md @@ -17,10 +17,10 @@ waste_collection_schedule: ### Configuration Variables -**postcode**
+**postcode** *(string) (required)* -**uprn**
+**uprn** *(string) (required)* Both the postcode and the UPRN should be supplied in the arguments. diff --git a/doc/source/wsz_moosburg_at.md b/doc/source/wsz_moosburg_at.md index a76e4302..23a5e476 100644 --- a/doc/source/wsz_moosburg_at.md +++ b/doc/source/wsz_moosburg_at.md @@ -18,7 +18,7 @@ waste_collection_schedule: #### Configuration Variables -**address_id**
+**address_id** *(integer) (required)* See the next section on how to obtain it. #### How to get the Address ID @@ -49,13 +49,13 @@ waste_collection_schedule: Please note that exact spelling and casing matters. -**municipal**
+**municipal** *(string) (required)* -**address**
+**address** *(string) (required)* -**street**
+**street** *(string) (required)* #### How to get the correct Address diff --git a/doc/source/wuerzburg_de.md b/doc/source/wuerzburg_de.md index 5a212007..d07e853d 100644 --- a/doc/source/wuerzburg_de.md +++ b/doc/source/wuerzburg_de.md @@ -17,10 +17,10 @@ waste_collection_schedule: **district** and **street** can be used independently, only **one** is required. If set, priority will be given to **street**. -**district**
+**district** *(string) (required)* - if *street* is empty -**street**
+**street** *(string) (required)* - if *district* is empty ## Example diff --git a/doc/source/wyndham_vic_gov_au.md b/doc/source/wyndham_vic_gov_au.md index 578f80ec..c3ac6b08 100644 --- a/doc/source/wyndham_vic_gov_au.md +++ b/doc/source/wyndham_vic_gov_au.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (required)* ## Example diff --git a/doc/source/ximmio_nl.md b/doc/source/ximmio_nl.md index 75e2ba0d..960068f8 100644 --- a/doc/source/ximmio_nl.md +++ b/doc/source/ximmio_nl.md @@ -16,7 +16,7 @@ waste_collection_schedule: ### Configuration Variables -**company**
+**company** *(string) (required)* Use one of the following codes as company code: @@ -37,10 +37,10 @@ Use one of the following codes as company code: - westland - ximmio -**post_code**
+**post_code** *(string) (required)* -**house_number**
+**house_number** *(integer) (required)* ## Example diff --git a/doc/source/york_gov_uk.md b/doc/source/york_gov_uk.md index 16042cd8..f644dcd0 100644 --- a/doc/source/york_gov_uk.md +++ b/doc/source/york_gov_uk.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**uprn**
+**uprn** *(string) (required)* ## Example From ed42612163e8dcf6fab779cdb2bda3aed307d0f4 Mon Sep 17 00:00:00 2001 From: mampfes Date: Sat, 24 Dec 2022 11:01:37 +0100 Subject: [PATCH 035/127] clean markdown warnings --- doc/source/abfall_zollernalbkreis_de.md | 2 +- doc/source/alw_wf_de.md | 2 +- doc/source/aucklandcouncil_govt_nz.md | 4 ++-- doc/source/aw_harburg_de.md | 1 - doc/source/awb_es_de.md | 2 +- doc/source/awido_de.md | 2 +- doc/source/awn_de.md | 2 +- doc/source/belmont_wa_gov_au.md | 2 +- doc/source/bielefeld_de.md | 4 ++-- doc/source/cambridge_gov_uk.md | 1 - doc/source/canterbury_gov_uk.md | 3 +-- doc/source/chesterfield_gov_uk.md | 3 ++- doc/source/colchester_gov_uk.md | 3 ++- doc/source/cornwall_gov_uk.md | 2 +- doc/source/data_umweltprofis_at.md | 5 ++--- doc/source/derby_gov_uk.md | 1 + doc/source/environmentfirst_co_uk.md | 7 +++++-- doc/source/erlangen_hoechstadt_de.md | 5 +++-- doc/source/fccenvironment_co_uk.md | 3 ++- doc/source/guildford_gov_uk.md | 3 ++- doc/source/horowhenua_govt_nz.md | 1 - doc/source/infeo_at.md | 12 +++++++++--- doc/source/kaev_niederlausitz_de.md | 2 -- doc/source/kingston_gov_uk.md | 4 ++-- doc/source/kuringgai_nsw_gov_au.md | 1 - doc/source/kwu_de.md | 2 +- doc/source/lewisham_gov_uk.md | 5 ++++- doc/source/manchester_uk.md | 4 +--- doc/source/melton_vic_gov_au.md | 2 +- doc/source/middlesbrough_gov_uk.md | 4 ++-- doc/source/miljoteknik_se.md | 1 - doc/source/newcastle_gov_uk.md | 3 ++- doc/source/peterborough_gov_uk.md | 5 ++++- doc/source/republicservices_com.md | 2 +- doc/source/richmondshire_gov_uk.md | 2 +- doc/source/rushmoor_gov_uk.md | 3 ++- doc/source/sheffield_gov_uk.md | 5 +++-- doc/source/static.md | 2 +- doc/source/stevenage_gov_uk.md | 3 ++- doc/source/tewkesbury_gov_uk.md | 1 - doc/source/walsall_gov_uk.md | 5 +++-- doc/source/wiltshire_gov_uk.md | 2 +- doc/source/wsz_moosburg_at.md | 14 +++++--------- doc/source/wyndham_vic_gov_au.md | 2 +- 44 files changed, 77 insertions(+), 67 deletions(-) diff --git a/doc/source/abfall_zollernalbkreis_de.md b/doc/source/abfall_zollernalbkreis_de.md index 25dda320..4b95eba7 100644 --- a/doc/source/abfall_zollernalbkreis_de.md +++ b/doc/source/abfall_zollernalbkreis_de.md @@ -67,6 +67,6 @@ Another way get the source arguments is to extract the arguments from the websit 2. Enter your data, but don't click on "ICS Download" so far! 3. Open the Developer Tools (Ctrl + Shift + I) and open the `Network` tab. 4. Now click the "ICS Download" button. -5. You should see (amongst other's) one POST entry to Host https://www.abfallkalender-zak.de/ labeled `/` in the network recording. +5. You should see (amongst other's) one POST entry to Host `https://www.abfallkalender-zak.de/` labeled `/` in the network recording. 6. Select `/` on the left hand side and click on Request on the right hand side. 7. At the `Form Data` you can find the values for `city` and `street` etc.. diff --git a/doc/source/alw_wf_de.md b/doc/source/alw_wf_de.md index 4feefb04..eea1ce21 100644 --- a/doc/source/alw_wf_de.md +++ b/doc/source/alw_wf_de.md @@ -34,7 +34,7 @@ waste_collection_schedule: ## How to get the source arguments -1. Go to the calendar at https://www.alw-wf.de/index.php/abfallkalender. +1. Go to the [calendar](https://www.alw-wf.de/index.php/abfallkalender). 2. Select your location in the drop down menus. - Notice: The page reloads after selecting `Ort`, so wait for that before selecting `Straße`. 3. Copy the **exact** values from the 2 drop down menus as `ort` and `strasse` in the source configuration. diff --git a/doc/source/aucklandcouncil_govt_nz.md b/doc/source/aucklandcouncil_govt_nz.md index 5501b2c1..a71cde8f 100644 --- a/doc/source/aucklandcouncil_govt_nz.md +++ b/doc/source/aucklandcouncil_govt_nz.md @@ -31,6 +31,6 @@ waste_collection_schedule: The source argument is the area number from Auckland Council site: -- Open your collection days page by entering your address [on the Auckland Council collection day finder](https://www.aucklandcouncil.govt.nz/rubbish-recycling/rubbish-recycling-collections/Pages/rubbish-recycling-collection-days.aspx) -- Look for 'an' parameter in your browser URL, e.g. https://www.aucklandcouncil.govt.nz/rubbish-recycling/rubbish-recycling-collections/Pages/collection-day-detail.aspx?an=12342306525 +- Open your collection days page by entering your address [on the Auckland Council collection day finder](https://www.aucklandcouncil.govt.nz/rubbish-recycling/rubbish-recycling-collections/Pages/rubbish-recycling-collection-days.aspx) +- Look for 'an' parameter in your browser URL, e.g. `https://www.aucklandcouncil.govt.nz/rubbish-recycling/rubbish-recycling-collections/Pages/collection-day-detail.aspx?an=12342306525` - In this example the area number is `12342306525`. diff --git a/doc/source/aw_harburg_de.md b/doc/source/aw_harburg_de.md index d7c19626..8f656507 100644 --- a/doc/source/aw_harburg_de.md +++ b/doc/source/aw_harburg_de.md @@ -36,7 +36,6 @@ waste_collection_schedule: level_2: "Evendorf" ``` - ```yaml waste_collection_schedule: sources: diff --git a/doc/source/awb_es_de.md b/doc/source/awb_es_de.md index 91b067e2..afd95786 100644 --- a/doc/source/awb_es_de.md +++ b/doc/source/awb_es_de.md @@ -23,4 +23,4 @@ waste_collection_schedule: ## How to get the source arguments -Visit (Abfuhrtermine)[`https://www.awb-es.de/abfuhr/abfuhrtermine/__Abfuhrtermine.html`] and search for your address. The `city` and `street` argument should exactly match the autocomplete result. +Visit [Abfuhrtermine](`https://www.awb-es.de/abfuhr/abfuhrtermine/__Abfuhrtermine.html`) and search for your address. The `city` and `street` argument should exactly match the autocomplete result. diff --git a/doc/source/awido_de.md b/doc/source/awido_de.md index 6028b8ab..24db4d3e 100644 --- a/doc/source/awido_de.md +++ b/doc/source/awido_de.md @@ -1,6 +1,6 @@ # AWIDO based services -Cubefour AWIDO is a platform for waste schedules, which has several German cities and districts as customers. The homepage of the company is https://www.awido-online.de/. +Cubefour AWIDO is a platform for waste schedules, which has several German cities and districts as customers. The homepage of the company is [https://www.awido-online.de/](https://www.awido-online.de/). ## Configuration via configuration.yaml diff --git a/doc/source/awn_de.md b/doc/source/awn_de.md index d4d9e06a..475c89ac 100644 --- a/doc/source/awn_de.md +++ b/doc/source/awn_de.md @@ -45,4 +45,4 @@ waste_collection_schedule: ## How to get the source arguments These values are the location you want to query for. Make sure, the writing is exactly as it is on [https://www.awn-online.de/abfuhrtermine](https://www.awn-online.de/abfuhrtermine). Typos will result in an parsing error which is printed in the log. As `house_number` expects a numeric input, address suffixes have to be provided via the `address_suffix` argument. -`address_suffix` could be for example a alpha-numeric character "A" or a additional house number like "/1". \ No newline at end of file +`address_suffix` could be for example a alpha-numeric character "A" or a additional house number like "/1". diff --git a/doc/source/belmont_wa_gov_au.md b/doc/source/belmont_wa_gov_au.md index 30053364..1f807c08 100644 --- a/doc/source/belmont_wa_gov_au.md +++ b/doc/source/belmont_wa_gov_au.md @@ -29,4 +29,4 @@ waste_collection_schedule: ## How to get the source arguments -Visit the [Belmont City Council Waste and Recycling](https://www.belmont.wa.gov.au/live/at-your-place/bins,-waste-and-recycling) page and search for your address. The arguments should exactly match the results of the property. \ No newline at end of file +Visit the [Belmont City Council Waste and Recycling](https://www.belmont.wa.gov.au/live/at-your-place/bins,-waste-and-recycling) page and search for your address. The arguments should exactly match the results of the property. diff --git a/doc/source/bielefeld_de.md b/doc/source/bielefeld_de.md index eddb2121..16854356 100644 --- a/doc/source/bielefeld_de.md +++ b/doc/source/bielefeld_de.md @@ -38,5 +38,5 @@ waste_collection_schedule: ## How to get the source arguments -These values are the location you want to query for. Make sure, the writing is exactly as it is on [https://anwendungen.bielefeld.de/WasteManagementBielefeld/WasteManagementServlet?SubmitAction=wasteDisposalServices](https://anwendungen.bielefeld.de/WasteManagementBielefeld/WasteManagementServlet?SubmitAction=wasteDisposalServices). Typos will result in an parsing error which is printed in the log. As `house_number` expects a numeric input, address suffixes have to be provided via the `address_suffix` argument. -`address_suffix` could be for example a alpha-numeric character "A" or a additional house number like "/1". \ No newline at end of file +These values are the location you want to query for. Make sure, the writing is exactly as it is on `https://anwendungen.bielefeld.de/WasteManagementBielefeld/WasteManagementServlet?SubmitAction=wasteDisposalServices]`. Typos will result in an parsing error which is printed in the log. As `house_number` expects a numeric input, address suffixes have to be provided via the `address_suffix` argument. +`address_suffix` could be for example a alpha-numeric character "A" or a additional house number like "/1". diff --git a/doc/source/cambridge_gov_uk.md b/doc/source/cambridge_gov_uk.md index 72265066..9c9fa50c 100644 --- a/doc/source/cambridge_gov_uk.md +++ b/doc/source/cambridge_gov_uk.md @@ -22,7 +22,6 @@ waste_collection_schedule: **NUMBER** *(string) (required)* - ## Example ```yaml diff --git a/doc/source/canterbury_gov_uk.md b/doc/source/canterbury_gov_uk.md index f91de56f..09e07912 100644 --- a/doc/source/canterbury_gov_uk.md +++ b/doc/source/canterbury_gov_uk.md @@ -22,7 +22,6 @@ waste_collection_schedule: **NUMBER** *(string) (required)* - ## Examples ```yaml @@ -33,7 +32,7 @@ waste_collection_schedule: post_code: "ct68ru" number: "63" ``` - + ```yaml waste_collection_schedule: sources: diff --git a/doc/source/chesterfield_gov_uk.md b/doc/source/chesterfield_gov_uk.md index b835f15e..687d0dff 100644 --- a/doc/source/chesterfield_gov_uk.md +++ b/doc/source/chesterfield_gov_uk.md @@ -20,6 +20,7 @@ waste_collection_schedule: This is required to unambiguously identify the property. ## Example using UPRN + ```yaml waste_collection_schedule: sources: @@ -30,4 +31,4 @@ waste_collection_schedule: ## How to find your `UPRN` -An easy way to find your Unique Property Reference Number (UPRN) is by going to https://www.findmyaddress.co.uk/ and entering in your address details. \ No newline at end of file +An easy way to find your Unique Property Reference Number (UPRN) is by going to [https://www.findmyaddress.co.uk/](https://www.findmyaddress.co.uk/) and entering in your address details. diff --git a/doc/source/colchester_gov_uk.md b/doc/source/colchester_gov_uk.md index b8d911c1..ca5682b2 100644 --- a/doc/source/colchester_gov_uk.md +++ b/doc/source/colchester_gov_uk.md @@ -18,6 +18,7 @@ waste_collection_schedule: *(string) (required)* #### How to find your `llpgid` code + The LLPDID code can be found in the URL after entering your postcode and selecting your address on the [Colchester Your recycling calendar page](https://www.colchester.gov.uk/your-recycling-calendar/). The URL in your browser URL bar should look like `https://www.colchester.gov.uk/your-recycling-calendar/?start=true&step=1&llpgid=1197e725-3c27-e711-80fa-5065f38b5681`. ## Example @@ -28,4 +29,4 @@ waste_collection_schedule: - name: colchester_gov_uk args: llpgid: "1197e725-3c27-e711-80fa-5065f38b5681" -``` \ No newline at end of file +``` diff --git a/doc/source/cornwall_gov_uk.md b/doc/source/cornwall_gov_uk.md index 4fbb074e..b1799e4f 100644 --- a/doc/source/cornwall_gov_uk.md +++ b/doc/source/cornwall_gov_uk.md @@ -42,4 +42,4 @@ waste_collection_schedule: ## How to find your UPRN -An easy way to discover your Unique Property Reference Number (UPRN) is by going to [Find My Address](https://www.findmyaddress.co.uk/) and providng your address details. +An easy way to discover your Unique Property Reference Number (UPRN) is by going to [Find My Address](https://www.findmyaddress.co.uk/) and providng your address details. diff --git a/doc/source/data_umweltprofis_at.md b/doc/source/data_umweltprofis_at.md index b4beceeb..3d7f78b3 100644 --- a/doc/source/data_umweltprofis_at.md +++ b/doc/source/data_umweltprofis_at.md @@ -4,8 +4,7 @@ Support for schedules provided by [Umweltprofis.at](https://www.umweltprofis.at) ## Configuration via configuration.yaml -You need to generate your personal XML link before you can start using this source. Go to [https://data.umweltprofis.at/opendata/AppointmentService/index.aspx](https://data.umweltprofis.at/opendata/AppointmentService/index.aspx) and fill out the form. At the end -at step 6 you get a link to a XML file. Copy this link and paste it to configuration.yaml as seen below. +You need to generate your personal XML link before you can start using this source. Go to [https://data.umweltprofis.at/opendata/AppointmentService/index.aspx](https://data.umweltprofis.at/opendata/AppointmentService/index.aspx) and fill out the form. At the end at step 6 you get a link to a XML file. Copy this link and paste it to configuration.yaml as seen below. ```yaml waste_collection_schedule: @@ -28,4 +27,4 @@ waste_collection_schedule: - name: data_umweltprofis_at args: xmlurl: https://data.umweltprofis.at/opendata/AppointmentService/AppointmentService.asmx/GetTermineForLocationSecured?Key=TEMPKeyabvvMKVCic0cMcmsTEMPKey&StreetNr=124972&HouseNr=Alle&intervall=Alle -``` \ No newline at end of file +``` diff --git a/doc/source/derby_gov_uk.md b/doc/source/derby_gov_uk.md index 0714d0d3..69cd8763 100644 --- a/doc/source/derby_gov_uk.md +++ b/doc/source/derby_gov_uk.md @@ -6,6 +6,7 @@ city of Derby, UK. ## Configuration via configuration.yaml (recommended) + ```yaml waste_collection_schedule: sources: diff --git a/doc/source/environmentfirst_co_uk.md b/doc/source/environmentfirst_co_uk.md index 75c71d65..9740c7f9 100644 --- a/doc/source/environmentfirst_co_uk.md +++ b/doc/source/environmentfirst_co_uk.md @@ -38,6 +38,7 @@ This is required if you supply a Postcode and have a house number. This is required if you supply a Postcode and you have a house name rather than a house number. ## Example using UPRN + ```yaml waste_collection_schedule: sources: @@ -47,6 +48,7 @@ waste_collection_schedule: ``` ## Example using Address lookup (Postcode and house number) + ```yaml waste_collection_schedule: sources: @@ -57,6 +59,7 @@ waste_collection_schedule: ``` ## Example using Address lookup (Postcode and house name) + ```yaml waste_collection_schedule: sources: @@ -68,6 +71,6 @@ waste_collection_schedule: ## How to find your `UPRN` -An easy way to find your Unique Property Reference Number (UPRN) is by going to https://www.findmyaddress.co.uk/ and entering in your address details. +An easy way to find your Unique Property Reference Number (UPRN) is by going to [https://www.findmyaddress.co.uk/](https://www.findmyaddress.co.uk/) and entering in your address details. -Otherwise you can inspect the web requests on the [Environment First](https://www.environmentfirst.co.uk/) having searched using your address details. Your UPRN is the collection of digits at the end of the URL, for example: *https://www.environmentfirst.co.uk/house.php?uprn=`100060091178`* +Otherwise you can inspect the web requests on the [Environment First](https://www.environmentfirst.co.uk/) having searched using your address details. Your UPRN is the collection of digits at the end of the URL, for example: `https://www.environmentfirst.co.uk/house.php?uprn=100060091178` diff --git a/doc/source/erlangen_hoechstadt_de.md b/doc/source/erlangen_hoechstadt_de.md index fa4b493b..909a20f9 100644 --- a/doc/source/erlangen_hoechstadt_de.md +++ b/doc/source/erlangen_hoechstadt_de.md @@ -1,5 +1,6 @@ # Erlangen-Höchstadt -Support for Landkreis [Erlangen-Höchstadt]() located in Bavaria, Germany. + +Support for Landkreis Erlangen-Höchstadt located in Bavaria, Germany. ## Configuration via configuration.yaml @@ -22,4 +23,4 @@ waste_collection_schedule: ### How to get the source arguments -Visit [erlangen-hoechstadt.de](https://www.erlangen-hoechstadt.de/aktuelles/abfallkalender/) and search for your area. Use the value from the "Ort" dropdown as `city` argument and the one from "Ortsteil/Straße" as `street`. `street` is case sensitive! \ No newline at end of file +Visit [erlangen-hoechstadt.de](https://www.erlangen-hoechstadt.de/aktuelles/abfallkalender/) and search for your area. Use the value from the "Ort" dropdown as `city` argument and the one from "Ortsteil/Straße" as `street`. `street` is case sensitive! diff --git a/doc/source/fccenvironment_co_uk.md b/doc/source/fccenvironment_co_uk.md index 6114b587..c4f4b1af 100644 --- a/doc/source/fccenvironment_co_uk.md +++ b/doc/source/fccenvironment_co_uk.md @@ -20,6 +20,7 @@ waste_collection_schedule: This is required to unambiguously identify the property. ## Example using UPRN + ```yaml waste_collection_schedule: sources: @@ -30,4 +31,4 @@ waste_collection_schedule: ## How to find your `UPRN` -An easy way to find your Unique Property Reference Number (UPRN) is by going to https://www.findmyaddress.co.uk/ and entering in your address details. \ No newline at end of file +An easy way to find your Unique Property Reference Number (UPRN) is by going to [https://www.findmyaddress.co.uk/](https://www.findmyaddress.co.uk/) and entering in your address details. diff --git a/doc/source/guildford_gov_uk.md b/doc/source/guildford_gov_uk.md index 26744d23..83cf428a 100644 --- a/doc/source/guildford_gov_uk.md +++ b/doc/source/guildford_gov_uk.md @@ -28,4 +28,5 @@ waste_collection_schedule: ``` ## How to get the source argument -# Find the UPRN of your address using https://www.findmyaddress.co.uk/search + +Find the UPRN of your address using [https://www.findmyaddress.co.uk/search](https://www.findmyaddress.co.uk/search). diff --git a/doc/source/horowhenua_govt_nz.md b/doc/source/horowhenua_govt_nz.md index 78af4cb4..db7baec8 100644 --- a/doc/source/horowhenua_govt_nz.md +++ b/doc/source/horowhenua_govt_nz.md @@ -31,7 +31,6 @@ waste_collection_schedule: ## Example - ```yaml waste_collection_schedule: sources: diff --git a/doc/source/infeo_at.md b/doc/source/infeo_at.md index f7c8cda2..b39f8577 100644 --- a/doc/source/infeo_at.md +++ b/doc/source/infeo_at.md @@ -1,6 +1,6 @@ -# INFEO based services +# INFEO based services -INFEO is a platform for waste schedules, which has several German, Austrian and Swiss cities and districts as customers. The homepage of the company is https://www.infeo.at/. +INFEO is a platform for waste schedules, which has several German, Austrian and Swiss cities and districts as customers. The homepage of the company is [https://www.infeo.at/](https://www.infeo.at/). ## Configuration via configuration.yaml @@ -45,33 +45,39 @@ If your provider is also using infeo.at you can just try to use the name of your ### zone #### Bogenschuetz-Entsorgung.de -- Go to your calendar at `https://www.bogenschuetz-entsorgung.de/images/wastecal/index-zone.html`. + +- Go to your [calendar](https://www.bogenschuetz-entsorgung.de/images/wastecal/index-zone.html). - Browse through all the available years and check the naming of your desired zone and try to find what makes it unique. - Put this unique string into `zone` of your configuration. - It will just be checked if the calendar contains an entry that contains your keyword `zone`. ##### Example 1: Dettenhausen + - For 2022 it is: `Dettenhausen, Tübingen (Bebenhausen; Lustnau)` - For 2023 it is: `Dettenhausen` - Use `Dettenhausen` as zone ##### Example 2: Ofterdingen + - For 2022 it is: `Dußlingen, Ofterdingen` - For 2023 it is: `Ofterdingen` - Use `Ofterdingen` as zone ##### Example 3: Felldorf + - For 2022 it is: `Rottenburg (Bad Niedernau; Bieringen; Eckenweiler; Frommenhausen; Obernau; Schwalldorf), Starzach (Bierlingen; Börstingen; Felldorf; Sulzau; Wachendorf)` - For 2023 it is: `Starzach (Bierlingen; Börstingen; Felldorf; Sulzau; Wachendorf)` - Use `Felldorf` as zone ##### Example 4: Tübingen Innenstadt + - For 2022 it is: `Tübingen (Bezirk 4 - Innenstadt)` - For 2023 it is: `Tübingen (Bezirk 4 - Innenstadt)` - Use `Innenstadt` as zone - Do NOT use `Tübingen` as it is used multiple times! ##### Example 5: Pfäffingen + - For 2022 it is: `Tübingen (Bühl; Hirschau; Kilchberg; Unterjesingen; Weilheim), Rottenburg (Kiebingen; Wurmlingen), Ammerbuch (Pfäffingen)` - For 2023 it is: `Ammerbuch (Pfäffingen)` - Use `Pfäffingen` as zone diff --git a/doc/source/kaev_niederlausitz_de.md b/doc/source/kaev_niederlausitz_de.md index 9ccbdaef..7c35ac3c 100644 --- a/doc/source/kaev_niederlausitz_de.md +++ b/doc/source/kaev_niederlausitz_de.md @@ -17,7 +17,6 @@ waste_collection_schedule: **abf_suche** *(string) (required)* - ## Example ```yaml @@ -44,7 +43,6 @@ waste_collection_schedule: abf_suche: "Staakow" ``` - ## How to get the source arguments 1. Go to your calendar at 1. Go to your calendar at [https://www.kaev.de/Info-und-Service/Tourenplan/Tourenplan-Abfalltermine.html](https://www.kaev.de/Info-und-Service/Tourenplan/Tourenplan-Abfalltermine.html). diff --git a/doc/source/kingston_gov_uk.md b/doc/source/kingston_gov_uk.md index 6a9b860a..7307e8b9 100644 --- a/doc/source/kingston_gov_uk.md +++ b/doc/source/kingston_gov_uk.md @@ -1,4 +1,4 @@ -# Thr Royal Borough of Kingston Council +# The Royal Borough of Kingston Council Support for schedules provided by [The Royal Borough of Kingston Council](https://kingston-self.achieveservice.com/service/in_my_area?displaymode=collections). @@ -29,4 +29,4 @@ waste_collection_schedule: ## How to get the source argument -An easy way to find your Unique Property Reference Number (UPRN) is by going to https://www.findmyaddress.co.uk/ and entering in your address details. \ No newline at end of file +An easy way to find your Unique Property Reference Number (UPRN) is by going to [https://www.findmyaddress.co.uk/](https://www.findmyaddress.co.uk/) and entering in your address details. diff --git a/doc/source/kuringgai_nsw_gov_au.md b/doc/source/kuringgai_nsw_gov_au.md index 199689ec..6416a108 100644 --- a/doc/source/kuringgai_nsw_gov_au.md +++ b/doc/source/kuringgai_nsw_gov_au.md @@ -31,7 +31,6 @@ waste_collection_schedule: ## Example - ```yaml waste_collection_schedule: sources: diff --git a/doc/source/kwu_de.md b/doc/source/kwu_de.md index 77a29e2b..3fe69599 100644 --- a/doc/source/kwu_de.md +++ b/doc/source/kwu_de.md @@ -27,4 +27,4 @@ waste_collection_schedule: ## How to get the source arguments -Visit (Entsorgungskalender)[`https://www.kwu-entsorgung.de/?page_id=337`] and search for your address. The `city`, `street` and `number` argument should exactly match the autocomplete result. +Visit [Entsorgungskalender](https://www.kwu-entsorgung.de/?page_id=337`) and search for your address. The `city`, `street` and `number` argument should exactly match the autocomplete result. diff --git a/doc/source/lewisham_gov_uk.md b/doc/source/lewisham_gov_uk.md index 3d4fb8ef..6d8bac8a 100644 --- a/doc/source/lewisham_gov_uk.md +++ b/doc/source/lewisham_gov_uk.md @@ -38,10 +38,12 @@ This is required if you supply a Postcode and have a house number. This is required if you do not supply a UPRN. Single space between 1st and 2nd part of postcode is optional. #### How to find your `UPRN` -An easy way to discover your Unique Property Reference Number (UPRN) is by going to https://www.findmyaddress.co.uk/ and entering in your address details. + +An easy way to discover your Unique Property Reference Number (UPRN) is by going to [https://www.findmyaddress.co.uk/](https://www.findmyaddress.co.uk/) and entering in your address details. Otherwise you can inspect the web requests the Peterborough Council website makes when entering in your postcode and then selecting your address. ## Example using UPRN + ```yaml waste_collection_schedule: sources: @@ -51,6 +53,7 @@ waste_collection_schedule: ``` ## Example using Address lookup + ```yaml waste_collection_schedule: sources: diff --git a/doc/source/manchester_uk.md b/doc/source/manchester_uk.md index 759fdc6d..840331ac 100644 --- a/doc/source/manchester_uk.md +++ b/doc/source/manchester_uk.md @@ -33,6 +33,4 @@ waste_collection_schedule: The UPRN code can be found in the page by entering your postcode on the [Manchester City Council Bin Collections page -](https://www.manchester.gov.uk/bincollections/). When on the address list, -View the source code for the page, and look for your address, the uprn will be -shown as the value. +](https://www.manchester.gov.uk/bincollections/). When on the address list, view the source code for the page, and look for your address, the uprn will be shown as the value. diff --git a/doc/source/melton_vic_gov_au.md b/doc/source/melton_vic_gov_au.md index 8d259497..bfa51fbd 100644 --- a/doc/source/melton_vic_gov_au.md +++ b/doc/source/melton_vic_gov_au.md @@ -29,4 +29,4 @@ waste_collection_schedule: ## How to get the source arguments -Visit the [Melton City Council waste and recycling](https://www.melton.vic.gov.au/My-Area) page and search for your address. The arguments should exactly match the street address shown in the autocomplete result. \ No newline at end of file +Visit the [Melton City Council waste and recycling](https://www.melton.vic.gov.au/My-Area) page and search for your address. The arguments should exactly match the street address shown in the autocomplete result. diff --git a/doc/source/middlesbrough_gov_uk.md b/doc/source/middlesbrough_gov_uk.md index 75ba55a4..46b42769 100644 --- a/doc/source/middlesbrough_gov_uk.md +++ b/doc/source/middlesbrough_gov_uk.md @@ -1,4 +1,4 @@ -# Middlesbrough Council +# Middlesbrough Council Support for schedules provided by [Middlesbrough Council](https://www.middlesbrough.gov.uk/bin-collection-dates). @@ -29,4 +29,4 @@ waste_collection_schedule: ## How to get the source argument -An easy way to find your Unique Property Reference Number (UPRN) is by going to https://www.findmyaddress.co.uk/ and entering in your address details. \ No newline at end of file +An easy way to find your Unique Property Reference Number (UPRN) is by going to [https://www.findmyaddress.co.uk/](https://www.findmyaddress.co.uk/) and entering in your address details. diff --git a/doc/source/miljoteknik_se.md b/doc/source/miljoteknik_se.md index bd3d1a37..60458945 100644 --- a/doc/source/miljoteknik_se.md +++ b/doc/source/miljoteknik_se.md @@ -40,4 +40,3 @@ The following waste types will be returned: * "Mat, Brännbart, färgat glas, tidningar." * "Plast, pappersförpackningar, ofärgat glas, metall." - diff --git a/doc/source/newcastle_gov_uk.md b/doc/source/newcastle_gov_uk.md index c31b59ff..42fb8b43 100644 --- a/doc/source/newcastle_gov_uk.md +++ b/doc/source/newcastle_gov_uk.md @@ -22,6 +22,7 @@ waste_collection_schedule: This is required to unambiguously identify the property. ## Example using UPRN + ```yaml waste_collection_schedule: sources: @@ -32,4 +33,4 @@ waste_collection_schedule: ## How to find your `UPRN` -An easy way to find your Unique Property Reference Number (UPRN) is by going to https://www.findmyaddress.co.uk/ and entering in your address details. \ No newline at end of file +An easy way to find your Unique Property Reference Number (UPRN) is by going to [https://www.findmyaddress.co.uk/](https://www.findmyaddress.co.uk/) and entering in your address details. diff --git a/doc/source/peterborough_gov_uk.md b/doc/source/peterborough_gov_uk.md index ffc01c83..93a8066a 100644 --- a/doc/source/peterborough_gov_uk.md +++ b/doc/source/peterborough_gov_uk.md @@ -38,10 +38,12 @@ This is required if you supply a Postcode and have a house number. This is required if you do not supply a UPRN. Single space between 1st and 2nd part of postcode is optional. #### How to find your `UPRN` -An easy way to discover your Unique Property Reference Number (UPRN) is by going to https://www.findmyaddress.co.uk/ and entering in your address details. + +An easy way to discover your Unique Property Reference Number (UPRN) is by going to [https://www.findmyaddress.co.uk/](https://www.findmyaddress.co.uk/) and entering in your address details. Otherwise you can inspect the web requests the Peterborough Council website makes when entering in your postcode and then selecting your address. ## Example using UPRN + ```yaml waste_collection_schedule: sources: @@ -51,6 +53,7 @@ waste_collection_schedule: ``` ## Example using Address lookup + ```yaml waste_collection_schedule: sources: diff --git a/doc/source/republicservices_com.md b/doc/source/republicservices_com.md index 4e820062..7653f06b 100644 --- a/doc/source/republicservices_com.md +++ b/doc/source/republicservices_com.md @@ -29,4 +29,4 @@ waste_collection_schedule: ## How to check the street address -The street address can be tested [here](https://republicservices.com). \ No newline at end of file +The street address can be tested [here](https://republicservices.com). diff --git a/doc/source/richmondshire_gov_uk.md b/doc/source/richmondshire_gov_uk.md index af8c9492..9d5c6223 100644 --- a/doc/source/richmondshire_gov_uk.md +++ b/doc/source/richmondshire_gov_uk.md @@ -30,4 +30,4 @@ waste_collection_schedule: ## How to find your `UPRN` -An easy way to find your Unique Property Reference Number (UPRN) is by going to https://www.findmyaddress.co.uk/ and entering in your address details. Or you can visit the Richmondshire page and use the address search. Right-click your entry in the house dropdown, choose Inspect, and copy the UPRN from the value \ No newline at end of file +An easy way to find your Unique Property Reference Number (UPRN) is by going to [https://www.findmyaddress.co.uk/](https://www.findmyaddress.co.uk/) and entering in your address details. Or you can visit the Richmondshire page and use the address search. Right-click your entry in the house dropdown, choose Inspect, and copy the UPRN from the value. diff --git a/doc/source/rushmoor_gov_uk.md b/doc/source/rushmoor_gov_uk.md index 17aa0637..95a8142d 100644 --- a/doc/source/rushmoor_gov_uk.md +++ b/doc/source/rushmoor_gov_uk.md @@ -28,4 +28,5 @@ waste_collection_schedule: ``` ## How to get the source argument -# Find the UPRN of your address using https://www.findmyaddress.co.uk/search + +Find the UPRN of your address using [https://www.findmyaddress.co.uk/search](https://www.findmyaddress.co.uk/search). diff --git a/doc/source/sheffield_gov_uk.md b/doc/source/sheffield_gov_uk.md index 5957d2f7..35184b4c 100644 --- a/doc/source/sheffield_gov_uk.md +++ b/doc/source/sheffield_gov_uk.md @@ -20,6 +20,7 @@ waste_collection_schedule: This is required if you do not supply any other options. Using a UPRN removes the need to do an address look up using web requests. ## Example using UPRN + ```yaml waste_collection_schedule: sources: @@ -30,6 +31,6 @@ waste_collection_schedule: ## How to find your `UPRN` -An easy way to find your Unique Property Reference Number (UPRN) is by going to https://www.findmyaddress.co.uk/ and entering in your address details. +An easy way to find your Unique Property Reference Number (UPRN) is by going to [https://www.findmyaddress.co.uk/](https://www.findmyaddress.co.uk/) and entering in your address details. -Otherwise you can inspect the URL on [Sheffield City Council's Waste Services](https://wasteservices.sheffield.gov.uk/) site having searched for and selected your address details. Your UPRN is the collection of digits at the end of the URL (before /calendar), for example: *https://wasteservices.sheffield.gov.uk/property/`100050938234`* or *https://wasteservices.sheffield.gov.uk/property/`100050938234`/calendar* \ No newline at end of file +Otherwise you can inspect the URL on [Sheffield City Council's Waste Services](https://wasteservices.sheffield.gov.uk/) site having searched for and selected your address details. Your UPRN is the collection of digits at the end of the URL (before /calendar), for example: `https://wasteservices.sheffield.gov.uk/property/100050938234` or `https://wasteservices.sheffield.gov.uk/property/100050938234/calendar` diff --git a/doc/source/static.md b/doc/source/static.md index a69a73c2..557f4bb2 100644 --- a/doc/source/static.md +++ b/doc/source/static.md @@ -80,4 +80,4 @@ waste_collection_schedule: dates: # Manually define dates that are not part of the recurrence - '2022-07-28' - '2022-09-22' -``` \ No newline at end of file +``` diff --git a/doc/source/stevenage_gov_uk.md b/doc/source/stevenage_gov_uk.md index f03abbba..106b94fa 100644 --- a/doc/source/stevenage_gov_uk.md +++ b/doc/source/stevenage_gov_uk.md @@ -1,4 +1,4 @@ -# Stevenage Borough Council +# Stevenage Borough Council Support for schedules provided by [Stevenage Borough Council](https://www.stevenage.gov.uk/waste-and-recycling/your-bin-collections). @@ -26,6 +26,7 @@ Postcode of property. This is required. Stevenage Borough Council API does not s Name of road property is in. This is required. ## Example + ```yaml waste_collection_schedule: sources: diff --git a/doc/source/tewkesbury_gov_uk.md b/doc/source/tewkesbury_gov_uk.md index ccc177e1..71ae38c1 100644 --- a/doc/source/tewkesbury_gov_uk.md +++ b/doc/source/tewkesbury_gov_uk.md @@ -17,7 +17,6 @@ waste_collection_schedule: **POSTCODE** *(string) (required)* - ## Example ```yaml diff --git a/doc/source/walsall_gov_uk.md b/doc/source/walsall_gov_uk.md index 42058c18..3e6cd1f9 100644 --- a/doc/source/walsall_gov_uk.md +++ b/doc/source/walsall_gov_uk.md @@ -20,6 +20,7 @@ waste_collection_schedule: This is required if you do not supply any other options. Using a UPRN removes the need to do an address look up using web requests. ## Example using UPRN + ```yaml waste_collection_schedule: sources: @@ -30,6 +31,6 @@ waste_collection_schedule: ## How to find your `UPRN` -An easy way to find your Unique Property Reference Number (UPRN) is by going to https://www.findmyaddress.co.uk/ and entering in your address details. +An easy way to find your Unique Property Reference Number (UPRN) is by going to [https://www.findmyaddress.co.uk/](https://www.findmyaddress.co.uk/) and entering in your address details. -Otherwise you can inspect the web requests on [Walsall Council](https://www.environmentfirst.co.uk/) having searched for and selected your address details. Your UPRN is the collection of digits at the end of the URL, for example: *https://cag.walsall.gov.uk/BinCollections/GetBins?uprn=`100071103746`* +Otherwise you can inspect the web requests on [Walsall Council](https://www.environmentfirst.co.uk/) having searched for and selected your address details. Your UPRN is the collection of digits at the end of the URL, for example: `https://cag.walsall.gov.uk/BinCollections/GetBins?uprn=100071103746` diff --git a/doc/source/wiltshire_gov_uk.md b/doc/source/wiltshire_gov_uk.md index 317c8bc5..7222fc9c 100644 --- a/doc/source/wiltshire_gov_uk.md +++ b/doc/source/wiltshire_gov_uk.md @@ -38,4 +38,4 @@ waste_collection_schedule: ## How to find your UPRN -An easy way to discover your Unique Property Reference Number (UPRN) is by going to [Find My Address](https://www.findmyaddress.co.uk/) and providng your address details. \ No newline at end of file +An easy way to discover your Unique Property Reference Number (UPRN) is by going to [Find My Address](https://www.findmyaddress.co.uk/) and providng your address details. diff --git a/doc/source/wsz_moosburg_at.md b/doc/source/wsz_moosburg_at.md index 23a5e476..da8200f8 100644 --- a/doc/source/wsz_moosburg_at.md +++ b/doc/source/wsz_moosburg_at.md @@ -2,11 +2,11 @@ Support for schedules provided by [wsz-moosburg.at](https://wsz-moosburg.at). -## Configuration via configuration.yaml +## Configuration Variables There are two options to configure this source. -### Using the Address ID +### 1. Using the Address ID ```yaml waste_collection_schedule: @@ -16,12 +16,10 @@ waste_collection_schedule: address_id: ID ``` -#### Configuration Variables - **address_id** *(integer) (required)* See the next section on how to obtain it. -#### How to get the Address ID +### How to get the Address ID For this you will have to use a (desktop) browser with developer tools, e.g. Google Chrome: @@ -33,7 +31,7 @@ For this you will have to use a (desktop) browser with developer tools, e.g. Goo 6. Select the last entry in the `Network` tab's list, it should be a number followed by `?include-public-holidays`, e.g. `69980?include-public-holidays`. 7. This number (e.g. `69980`) is what needs to be used as `address_id` in the configuration. -### Using the full Address +### 2. Using the full Address ```yaml waste_collection_schedule: @@ -45,8 +43,6 @@ waste_collection_schedule: street: Straße ``` -#### Configuration Variables - Please note that exact spelling and casing matters. **municipal** @@ -65,6 +61,6 @@ In any web browser: 1. Open [https://wsz-moosburg.at/calendar](https://wsz-moosburg.at/calendar). 2. Select your `Gemeinde` from the list. This is the value for `municipal`. 3. Select your `Addresse` from the list. This is the value for `address`. -4. There might be another step to select your `Straße`, but this depends on the address. +4. There might be another step to select your `Straße`, but this depends on the address. - If it's prompted to you, select that as well. This is the value for `street`. - If it is not prompted, use the same value for `address` also for `street`. diff --git a/doc/source/wyndham_vic_gov_au.md b/doc/source/wyndham_vic_gov_au.md index c3ac6b08..5f920229 100644 --- a/doc/source/wyndham_vic_gov_au.md +++ b/doc/source/wyndham_vic_gov_au.md @@ -29,4 +29,4 @@ waste_collection_schedule: ## How to get the source arguments -Visit the [Wyndham City Council waste and recycling](https://digital.wyndham.vic.gov.au/myWyndham/) page and search for your address. The arguments should exactly match the street address shown in the autocomplete result. \ No newline at end of file +Visit the [Wyndham City Council waste and recycling](https://digital.wyndham.vic.gov.au/myWyndham/) page and search for your address. The arguments should exactly match the street address shown in the autocomplete result. From 20e829e50f03f0aa715fa68ea5330d05092825f5 Mon Sep 17 00:00:00 2001 From: mampfes Date: Sat, 24 Dec 2022 11:01:38 +0100 Subject: [PATCH 036/127] beautify test case names for wsz_moosburg_at --- .../source/wsz_moosburg_at.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wsz_moosburg_at.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wsz_moosburg_at.py index 45a5c5c9..5cf42e7c 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wsz_moosburg_at.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wsz_moosburg_at.py @@ -10,28 +10,28 @@ DESCRIPTION = ( ) URL = "https://wsz-moosburg.at/calendar" TEST_CASES = { - "Address_Id_Moosburg_Obergöriach_Obergöriach": {"address_id": 70265}, - "Address_Id_Moosburg_Moosburg_Pestalozzistr": {"address_id": 70082}, - "Address_Id_Pörtschach_10OktoberStr_10OktoberStr": {"address_id": 69866}, - "Address_Id_Techelsberg_SüdlichDerBahn-BahnhofTöschlingBisSaagNr-19": { + "Id: Moosburg, Obergöriach": {"address_id": 70265}, + "Id: Moosburg, Pestalozzistr": {"address_id": 70082}, + "Id: Pörtschach, 10. OktoberStr": {"address_id": 69866}, + "Id: Techelsberg, Südlich der Bahn: Bahnhof Töschling bis Saag Nr. 19": { "address_id": 69980 }, - "Address_Data_Moosburg_Obergöriach_Obergöriach": { + "Full: Moosburg, Obergöriach": { "municipal": "Moosburg", "address": "Obergöriach", "street": "Obergöriach", }, - "Address_Data_Moosburg_Moosburg_Pestalozzistr": { + "Full: Moosburg, Pestalozzistr": { "municipal": "Moosburg", "address": "Moosburg", "street": "Pestalozzistraße", }, - "Address_Data_Pörtschach_10OktoberStr_10OktoberStr": { + "Full: Pörtschach, 10. OktoberStr": { "municipal": "Pörtschach", "address": "10.-Oktober-Straße", "street": "10.-Oktober-Straße", }, - "Address_Data_Techelsberg_SüdlichDerBahn-BahnhofTöschlingBisSaagNr-19": { + "Data: Techelsberg, Südlich der Bahn: Bahnhof Töschling bis Saag Nr. 19": { "municipal": "Techelsberg", "address": "Südlich der Bahn: Bahnhof Töschling bis Saag Nr. 19", "street": "Südlich der Bahn: Bahnhof Töschling bis Saag Nr. 19", From 845a1829c5bc5b561af11d0c4f7e7bf8e1e44a0f Mon Sep 17 00:00:00 2001 From: mampfes Date: Sat, 24 Dec 2022 11:01:40 +0100 Subject: [PATCH 037/127] replace with simple url notation --- doc/source/chesterfield_gov_uk.md | 2 +- doc/source/environmentfirst_co_uk.md | 2 +- doc/source/fccenvironment_co_uk.md | 2 +- doc/source/kingston_gov_uk.md | 2 +- doc/source/lewisham_gov_uk.md | 2 +- doc/source/middlesbrough_gov_uk.md | 2 +- doc/source/newcastle_gov_uk.md | 2 +- doc/source/peterborough_gov_uk.md | 2 +- doc/source/richmondshire_gov_uk.md | 2 +- doc/source/sheffield_gov_uk.md | 2 +- doc/source/walsall_gov_uk.md | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/doc/source/chesterfield_gov_uk.md b/doc/source/chesterfield_gov_uk.md index 687d0dff..2f62038e 100644 --- a/doc/source/chesterfield_gov_uk.md +++ b/doc/source/chesterfield_gov_uk.md @@ -31,4 +31,4 @@ waste_collection_schedule: ## How to find your `UPRN` -An easy way to find your Unique Property Reference Number (UPRN) is by going to [https://www.findmyaddress.co.uk/](https://www.findmyaddress.co.uk/) and entering in your address details. +An easy way to find your Unique Property Reference Number (UPRN) is by going to and entering in your address details. diff --git a/doc/source/environmentfirst_co_uk.md b/doc/source/environmentfirst_co_uk.md index 9740c7f9..35d32f5e 100644 --- a/doc/source/environmentfirst_co_uk.md +++ b/doc/source/environmentfirst_co_uk.md @@ -71,6 +71,6 @@ waste_collection_schedule: ## How to find your `UPRN` -An easy way to find your Unique Property Reference Number (UPRN) is by going to [https://www.findmyaddress.co.uk/](https://www.findmyaddress.co.uk/) and entering in your address details. +An easy way to find your Unique Property Reference Number (UPRN) is by going to and entering in your address details. Otherwise you can inspect the web requests on the [Environment First](https://www.environmentfirst.co.uk/) having searched using your address details. Your UPRN is the collection of digits at the end of the URL, for example: `https://www.environmentfirst.co.uk/house.php?uprn=100060091178` diff --git a/doc/source/fccenvironment_co_uk.md b/doc/source/fccenvironment_co_uk.md index c4f4b1af..c41ac3f6 100644 --- a/doc/source/fccenvironment_co_uk.md +++ b/doc/source/fccenvironment_co_uk.md @@ -31,4 +31,4 @@ waste_collection_schedule: ## How to find your `UPRN` -An easy way to find your Unique Property Reference Number (UPRN) is by going to [https://www.findmyaddress.co.uk/](https://www.findmyaddress.co.uk/) and entering in your address details. +An easy way to find your Unique Property Reference Number (UPRN) is by going to and entering in your address details. diff --git a/doc/source/kingston_gov_uk.md b/doc/source/kingston_gov_uk.md index 7307e8b9..c659c73a 100644 --- a/doc/source/kingston_gov_uk.md +++ b/doc/source/kingston_gov_uk.md @@ -29,4 +29,4 @@ waste_collection_schedule: ## How to get the source argument -An easy way to find your Unique Property Reference Number (UPRN) is by going to [https://www.findmyaddress.co.uk/](https://www.findmyaddress.co.uk/) and entering in your address details. +An easy way to find your Unique Property Reference Number (UPRN) is by going to and entering in your address details. diff --git a/doc/source/lewisham_gov_uk.md b/doc/source/lewisham_gov_uk.md index 6d8bac8a..a7a0b377 100644 --- a/doc/source/lewisham_gov_uk.md +++ b/doc/source/lewisham_gov_uk.md @@ -39,7 +39,7 @@ This is required if you do not supply a UPRN. Single space between 1st and 2nd p #### How to find your `UPRN` -An easy way to discover your Unique Property Reference Number (UPRN) is by going to [https://www.findmyaddress.co.uk/](https://www.findmyaddress.co.uk/) and entering in your address details. +An easy way to discover your Unique Property Reference Number (UPRN) is by going to and entering in your address details. Otherwise you can inspect the web requests the Peterborough Council website makes when entering in your postcode and then selecting your address. ## Example using UPRN diff --git a/doc/source/middlesbrough_gov_uk.md b/doc/source/middlesbrough_gov_uk.md index 46b42769..6298097a 100644 --- a/doc/source/middlesbrough_gov_uk.md +++ b/doc/source/middlesbrough_gov_uk.md @@ -29,4 +29,4 @@ waste_collection_schedule: ## How to get the source argument -An easy way to find your Unique Property Reference Number (UPRN) is by going to [https://www.findmyaddress.co.uk/](https://www.findmyaddress.co.uk/) and entering in your address details. +An easy way to find your Unique Property Reference Number (UPRN) is by going to and entering in your address details. diff --git a/doc/source/newcastle_gov_uk.md b/doc/source/newcastle_gov_uk.md index 42fb8b43..67eb2245 100644 --- a/doc/source/newcastle_gov_uk.md +++ b/doc/source/newcastle_gov_uk.md @@ -33,4 +33,4 @@ waste_collection_schedule: ## How to find your `UPRN` -An easy way to find your Unique Property Reference Number (UPRN) is by going to [https://www.findmyaddress.co.uk/](https://www.findmyaddress.co.uk/) and entering in your address details. +An easy way to find your Unique Property Reference Number (UPRN) is by going to and entering in your address details. diff --git a/doc/source/peterborough_gov_uk.md b/doc/source/peterborough_gov_uk.md index 93a8066a..a5827b18 100644 --- a/doc/source/peterborough_gov_uk.md +++ b/doc/source/peterborough_gov_uk.md @@ -39,7 +39,7 @@ This is required if you do not supply a UPRN. Single space between 1st and 2nd p #### How to find your `UPRN` -An easy way to discover your Unique Property Reference Number (UPRN) is by going to [https://www.findmyaddress.co.uk/](https://www.findmyaddress.co.uk/) and entering in your address details. +An easy way to discover your Unique Property Reference Number (UPRN) is by going to and entering in your address details. Otherwise you can inspect the web requests the Peterborough Council website makes when entering in your postcode and then selecting your address. ## Example using UPRN diff --git a/doc/source/richmondshire_gov_uk.md b/doc/source/richmondshire_gov_uk.md index 9d5c6223..f0425ad8 100644 --- a/doc/source/richmondshire_gov_uk.md +++ b/doc/source/richmondshire_gov_uk.md @@ -30,4 +30,4 @@ waste_collection_schedule: ## How to find your `UPRN` -An easy way to find your Unique Property Reference Number (UPRN) is by going to [https://www.findmyaddress.co.uk/](https://www.findmyaddress.co.uk/) and entering in your address details. Or you can visit the Richmondshire page and use the address search. Right-click your entry in the house dropdown, choose Inspect, and copy the UPRN from the value. +An easy way to find your Unique Property Reference Number (UPRN) is by going to and entering in your address details. Or you can visit the Richmondshire page and use the address search. Right-click your entry in the house dropdown, choose Inspect, and copy the UPRN from the value. diff --git a/doc/source/sheffield_gov_uk.md b/doc/source/sheffield_gov_uk.md index 35184b4c..9bf39af3 100644 --- a/doc/source/sheffield_gov_uk.md +++ b/doc/source/sheffield_gov_uk.md @@ -31,6 +31,6 @@ waste_collection_schedule: ## How to find your `UPRN` -An easy way to find your Unique Property Reference Number (UPRN) is by going to [https://www.findmyaddress.co.uk/](https://www.findmyaddress.co.uk/) and entering in your address details. +An easy way to find your Unique Property Reference Number (UPRN) is by going to and entering in your address details. Otherwise you can inspect the URL on [Sheffield City Council's Waste Services](https://wasteservices.sheffield.gov.uk/) site having searched for and selected your address details. Your UPRN is the collection of digits at the end of the URL (before /calendar), for example: `https://wasteservices.sheffield.gov.uk/property/100050938234` or `https://wasteservices.sheffield.gov.uk/property/100050938234/calendar` diff --git a/doc/source/walsall_gov_uk.md b/doc/source/walsall_gov_uk.md index 3e6cd1f9..64dfe128 100644 --- a/doc/source/walsall_gov_uk.md +++ b/doc/source/walsall_gov_uk.md @@ -31,6 +31,6 @@ waste_collection_schedule: ## How to find your `UPRN` -An easy way to find your Unique Property Reference Number (UPRN) is by going to [https://www.findmyaddress.co.uk/](https://www.findmyaddress.co.uk/) and entering in your address details. +An easy way to find your Unique Property Reference Number (UPRN) is by going to and entering in your address details. Otherwise you can inspect the web requests on [Walsall Council](https://www.environmentfirst.co.uk/) having searched for and selected your address details. Your UPRN is the collection of digits at the end of the URL, for example: `https://cag.walsall.gov.uk/BinCollections/GetBins?uprn=100071103746` From 31f15d7acd6a5f098f8ba49a0633ec10ceeb9952 Mon Sep 17 00:00:00 2001 From: mampfes Date: Sat, 24 Dec 2022 11:01:41 +0100 Subject: [PATCH 038/127] don't use apparent_encoding because it is slow and may produce wrong results. Better set the encoding directly. --- .../source/abfalltermine_forchheim_de.py | 15 +++++++++------ .../source/erlangen_hoechstadt_de.py | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfalltermine_forchheim_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfalltermine_forchheim_de.py index 244c036b..9fd18d81 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfalltermine_forchheim_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfalltermine_forchheim_de.py @@ -1,16 +1,19 @@ -import requests -from waste_collection_schedule import Collection # type: ignore[attr-defined] -from waste_collection_schedule.service.ICS import ICS - import urllib +import requests +from waste_collection_schedule import Collection # type: ignore[attr-defined] +from waste_collection_schedule.service.ICS import ICS + TITLE = "Landkreis Forchheim" DESCRIPTION = "Source for Landkreis Forchheim" URL = "https://www.abfalltermine-forchheim.de/" TEST_CASES = { "Dormitz": {"city": "Dormitz", "area": "Dormitz"}, "Rüsselbach": {"city": "Igensdorf", "area": "Oberrüsselbach"}, - "Kellerstraße": {"city": "Forchheim", "area": "Untere Kellerstraße (ab Adenauerallee bis Piastenbrücke)"} + "Kellerstraße": { + "city": "Forchheim", + "area": "Untere Kellerstraße (ab Adenauerallee bis Piastenbrücke)", + }, } @@ -25,7 +28,7 @@ class Source: r = requests.get( f"http://www.abfalltermine-forchheim.de/Forchheim/Landkreis/{place}/ics?RESTMUELL=true&RESTMUELL_SINGLE=true&BIO=true&YELLOW_SACK=true&PAPER=true" ) - r.encoding = r.apparent_encoding + r.encoding = "utf-8" dates = self._ics.convert(r.text) entries = [] diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/erlangen_hoechstadt_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/erlangen_hoechstadt_de.py index 929f030d..3d0ced12 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/erlangen_hoechstadt_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/erlangen_hoechstadt_de.py @@ -41,5 +41,5 @@ class Source: params=payload, ) r.raise_for_status() - r.encoding = r.apparent_encoding + r.encoding = "utf-8" return self._ics.convert(r.text) From 09417da59d6c7ab74b6f1d1d70b249d6dc694eb1 Mon Sep 17 00:00:00 2001 From: mampfes Date: Sat, 24 Dec 2022 11:01:42 +0100 Subject: [PATCH 039/127] add ALBA, Berlin example to abfall.io --- .../waste_collection_schedule/source/abfall_io.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_io.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_io.py index bbc9fda4..1e678411 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_io.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_io.py @@ -56,7 +56,13 @@ TEST_CASES = { "f_id_strasse": 621, "f_id_strasse_hnr": 872, "f_abfallarten": [27, 28, 17, 67], - } + }, + "ALBA Berlin": { + "key": "9583a2fa1df97ed95363382c73b41b1b", + "f_id_kommune": 3227, + "f_id_strasse": 3475, + "f_id_strasse_hnr": 185575, + }, } _LOGGER = logging.getLogger(__name__) @@ -148,10 +154,10 @@ class Source: # - AWB Limburg-Weilheim uses this list to select a "Sonderabfall " # waste type. The warning could be removed by adding the extra config # option "f_abfallarten" with the following values [27, 28, 17, 67] - html_warnings = re.findall("\ Date: Sat, 24 Dec 2022 13:13:20 +0100 Subject: [PATCH 040/127] cochem_zell_online_de: remove source starting with 2023, Cochem-Zell now uses the buergerportal_de source --- .../source/cochem_zell_online_de.py | 69 ------------------- doc/source/cochem_zell_online_de.md | 34 --------- 2 files changed, 103 deletions(-) delete mode 100644 custom_components/waste_collection_schedule/waste_collection_schedule/source/cochem_zell_online_de.py delete mode 100644 doc/source/cochem_zell_online_de.md diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/cochem_zell_online_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/cochem_zell_online_de.py deleted file mode 100644 index c2e43d95..00000000 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/cochem_zell_online_de.py +++ /dev/null @@ -1,69 +0,0 @@ -import contextlib -from datetime import datetime - -import requests -from waste_collection_schedule import Collection # type: ignore[attr-defined] -from waste_collection_schedule.service.ICS import ICS - -TITLE = "Abfall Cochem-Zell" -DESCRIPTION = "Source for waste collection in district Cochem-Zell." -URL = "https://www.cochem-zell-online.de/abfallkalender/" -TEST_CASES = { - "Alf": {"district": "Alf"}, - "Bullay": {"district": "Bullay"}, - "Zell-Stadt": {"district": "Zell-Stadt"}, - "Pünderich": {"district": "Pünderich"}, -} - -API_URL = "https://abfallkalender10.app.moba.de/Cochem_Zell/api" -REMINDER_DAY = 0 # The calendar event should be on the same day as the waste collection -REMINDER_HOUR = 6 # The calendar event should start on any hour of the correct day, so this does not matter much -FILENAME = "Abfallkalender.ics" -ICON_MAP = { - "Biotonne": "mdi:leaf", - "Gruengut": "mdi:forest", - "Papierabfall": "mdi:package-variant", - "Restmülltonne": "mdi:trash-can", - "Umweltmobil": "mdi:truck", - "Verpackungsabfall": "mdi:recycle", -} - - -class Source: - def __init__(self, district: str): - self._district = district - self._ics = ICS() - - def fetch(self): - now = datetime.now() - entries = self._fetch_year(now.year) - - if now.month == 12: - # also get data for next year if we are already in december - with contextlib.suppress(Exception): - entries.extend(self._fetch_year(now.year + 1)) - - return entries - - def _fetch_year(self, year: int): - url = "/".join( - str(param) - for param in ( - API_URL, - self._district, - year, - REMINDER_DAY, - REMINDER_HOUR, - FILENAME, - ) - ) - - r = requests.get(url) - schedule = self._ics.convert(r.text) - - return [ - Collection( - date=entry[0], t=entry[1], icon=ICON_MAP.get(entry[1], "mdi:trash-can") - ) - for entry in schedule - ] diff --git a/doc/source/cochem_zell_online_de.md b/doc/source/cochem_zell_online_de.md deleted file mode 100644 index 8b2ee087..00000000 --- a/doc/source/cochem_zell_online_de.md +++ /dev/null @@ -1,34 +0,0 @@ -# Cochem-Zell - -Support for schedules provided by . - -## Configuration via configuration.yaml - -```yaml -waste_collection_schedule: - sources: - - name: cochem_zell_online_de - args: - district: DISTRICT -``` - -### Configuration Variables - -**district**
-_(string) (required)_ - -## Example - -```yaml -waste_collection_schedule: - sources: - - name: cochem_zell_online_de - args: - district: "Zell-Stadt" -``` - -## How to get the source arguments - -1. Open . -2. Click on the selection field named `Ortsteil`. -3. Enter the name _with correct capitalization_ in the argument `district`. From 17eddb08fce4ccfc887f16780cc8db645052e636 Mon Sep 17 00:00:00 2001 From: bbr111 Date: Sat, 24 Dec 2022 17:28:46 +0100 Subject: [PATCH 041/127] Add Source SRV atervinning, SE Add Source SRV atervinning, SE --- README.md | 1 + .../source/srv_se.py | 39 +++++++++++++++++++ doc/source/srv_se.md | 35 +++++++++++++++++ info.md | 1 + 4 files changed, 76 insertions(+) create mode 100644 custom_components/waste_collection_schedule/waste_collection_schedule/source/srv_se.py create mode 100644 doc/source/srv_se.md diff --git a/README.md b/README.md index c691e850..905a40b6 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,7 @@ Currently the following service providers are supported: - [Lerum.se](./doc/source/lerum_se.md) - [Ronneby Miljöteknik](./doc/source/miljoteknik_se.md) - [SSAM.se](./doc/source/ssam_se.md) +- [srvatervinning.se](./doc/source/srv_se.md) - [Sysav.se](./doc/source/sysav_se.md) - [Vasyd.se](./doc/source/vasyd_se.md) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/srv_se.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/srv_se.py new file mode 100644 index 00000000..a6384bec --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/srv_se.py @@ -0,0 +1,39 @@ +import logging +import requests +from datetime import datetime +from waste_collection_schedule import Collection + +TITLE = "SRV, Sweden" +DESCRIPTION = "Source for SRV, Sweden" +URL = "https://www.srvatervinning.se/sophamtning/privat/hamtinformation-och-driftstorningar" +TEST_CASES = { + "Skansvägen" : {"address":"Skansvägen" }, + "TEST2" : {"address":"tun" } +} + +_LOGGER = logging.getLogger(__name__) + +class Source: + def __init__(self, address): + self._address = address + + def fetch(self): + + r = requests.get('https://www.srvatervinning.se/rest-api/srv-slamsok-rest-new/search?query='+self._address+'&city=') + + if r.status_code != 200: + _LOGGER.error("Error querying calender data") + return [] + + data = r.json() + + entries = [] + + for container in data["results"][0]["containers"]: + type=container["contentType"] + for calentry in container["calendars"]: + dates = {calentry["startDate"],type} + date_obj = datetime.strptime(calentry["startDate"], '%Y-%m-%d').date() + entries.append(Collection(date_obj,type)) + + return entries \ No newline at end of file diff --git a/doc/source/srv_se.md b/doc/source/srv_se.md new file mode 100644 index 00000000..b9b288ca --- /dev/null +++ b/doc/source/srv_se.md @@ -0,0 +1,35 @@ +# SRV återvinning AB + +Support for schedules provided by [SRV återvinning AB](https://www.srvatervinning.se/), Sweden. + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: srv_se + args: + address: address +``` + +### Configuration Variables + +**address**
+*(string) (required)* + +## Example + +```yaml +waste_collection_schedule: + sources: + - name: srv_se + args: + address: "Skansvägen" + +``` + +## How to get the source arguments + +1. Go to your calendar at [SRV återvinning AB](https://www.srvatervinning.se/sophamtning/privat/hamtinformation-och-driftstorningar) +2. Enter your address. +3. Copy the exact values from the textboxes in the source configuration. diff --git a/info.md b/info.md index f2cb4c2d..834a5d7f 100644 --- a/info.md +++ b/info.md @@ -158,6 +158,7 @@ Currently the following service providers are supported: - [Lerum.se](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/lerum_se.md) - [Ronneby Miljöteknik](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/miljoteknik_se.md) +- [srvatervinning.se](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/srv_se.md) - [SSAM.se](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/ssam_se.md) - [Sysav.se](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/sysav_se.md) - [Vasyd.se](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/vasyd_se.md) From a961b686f31a2e73916eb67d5c8cc11df8960ba9 Mon Sep 17 00:00:00 2001 From: bbr111 Date: Mon, 26 Dec 2022 14:35:20 +0100 Subject: [PATCH 042/127] =?UTF-8?q?Add=20SRV=20=C3=A5tervinning=20-=20chan?= =?UTF-8?q?ges?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit change filenames change sourcename in doc remove old files remove dates variable use params in requests --- README.md | 2 +- .../source/{srv_se.py => srvatervinning_se.py} | 12 ++++++++---- doc/source/{srv_se.md => srvatervinning_se.md} | 4 ++-- info.md | 2 +- 4 files changed, 12 insertions(+), 8 deletions(-) rename custom_components/waste_collection_schedule/waste_collection_schedule/source/{srv_se.py => srvatervinning_se.py} (75%) rename doc/source/{srv_se.md => srvatervinning_se.md} (92%) diff --git a/README.md b/README.md index 905a40b6..3e058d55 100644 --- a/README.md +++ b/README.md @@ -174,7 +174,7 @@ Currently the following service providers are supported: - [Lerum.se](./doc/source/lerum_se.md) - [Ronneby Miljöteknik](./doc/source/miljoteknik_se.md) - [SSAM.se](./doc/source/ssam_se.md) -- [srvatervinning.se](./doc/source/srv_se.md) +- [srvatervinning.se](./doc/source/srvatervinning_se.md) - [Sysav.se](./doc/source/sysav_se.md) - [Vasyd.se](./doc/source/vasyd_se.md) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/srv_se.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/srvatervinning_se.py similarity index 75% rename from custom_components/waste_collection_schedule/waste_collection_schedule/source/srv_se.py rename to custom_components/waste_collection_schedule/waste_collection_schedule/source/srvatervinning_se.py index a6384bec..f8231048 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/srv_se.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/srvatervinning_se.py @@ -3,8 +3,8 @@ import requests from datetime import datetime from waste_collection_schedule import Collection -TITLE = "SRV, Sweden" -DESCRIPTION = "Source for SRV, Sweden" +TITLE = "SRV återvinning AB Sweden" +DESCRIPTION = "Source for SRV återvinning AB, Sweden" URL = "https://www.srvatervinning.se/sophamtning/privat/hamtinformation-och-driftstorningar" TEST_CASES = { "Skansvägen" : {"address":"Skansvägen" }, @@ -19,7 +19,12 @@ class Source: def fetch(self): - r = requests.get('https://www.srvatervinning.se/rest-api/srv-slamsok-rest-new/search?query='+self._address+'&city=') + params = { + "query" : self._address, + "city" : "", + } + url = 'https://www.srvatervinning.se/rest-api/srv-slamsok-rest-new/search' + r = requests.get(url, params) if r.status_code != 200: _LOGGER.error("Error querying calender data") @@ -32,7 +37,6 @@ class Source: for container in data["results"][0]["containers"]: type=container["contentType"] for calentry in container["calendars"]: - dates = {calentry["startDate"],type} date_obj = datetime.strptime(calentry["startDate"], '%Y-%m-%d').date() entries.append(Collection(date_obj,type)) diff --git a/doc/source/srv_se.md b/doc/source/srvatervinning_se.md similarity index 92% rename from doc/source/srv_se.md rename to doc/source/srvatervinning_se.md index b9b288ca..b9e77edd 100644 --- a/doc/source/srv_se.md +++ b/doc/source/srvatervinning_se.md @@ -7,7 +7,7 @@ Support for schedules provided by [SRV återvinning AB](https://www.srvatervinni ```yaml waste_collection_schedule: sources: - - name: srv_se + - name: srvatervinning_se args: address: address ``` @@ -22,7 +22,7 @@ waste_collection_schedule: ```yaml waste_collection_schedule: sources: - - name: srv_se + - name: srvatervinning_se args: address: "Skansvägen" diff --git a/info.md b/info.md index 834a5d7f..343eb618 100644 --- a/info.md +++ b/info.md @@ -158,7 +158,7 @@ Currently the following service providers are supported: - [Lerum.se](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/lerum_se.md) - [Ronneby Miljöteknik](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/miljoteknik_se.md) -- [srvatervinning.se](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/srv_se.md) +- [srvatervinning.se](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/srvatervinning_se.md) - [SSAM.se](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/ssam_se.md) - [Sysav.se](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/sysav_se.md) - [Vasyd.se](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/vasyd_se.md) From f1ea1c8bb1c5a5b7f274cda35e0863d097386329 Mon Sep 17 00:00:00 2001 From: Finn Freitag Date: Mon, 26 Dec 2022 21:26:53 +0100 Subject: [PATCH 043/127] Finish Source Nordwestmecklenburg --- README.md | 1 + .../source/geoport_nwm_de.py | 14 +++++------- doc/source/geoport_nwm_de.md | 22 +++++++++++++++++++ info.md | 1 + 4 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 doc/source/geoport_nwm_de.md diff --git a/README.md b/README.md index 89854522..1a839ab0 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,7 @@ Currently the following service providers are supported: - [Cochem-Zell](./doc/source/buergerportal_de.md) - [EGN-Abfallkalender.de](./doc/source/egn_abfallkalender_de.md) - [Erlangen-Höchstadt](./doc/source/erlangen_hoechstadt_de.md) +- [Geodatenportal Nordwestmecklenburg](./doc/source/geoport_nwm_de.md) - [Jumomind.de](./doc/source/jumomind_de.md) - [KAEV Niederlausitz](./doc/source/kaev_niederlausitz_de.md) - [KWB-Goslar.de](./doc/source/kwb_goslar_de.md) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/geoport_nwm_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/geoport_nwm_de.py index 507eae5f..88fbb716 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/geoport_nwm_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/geoport_nwm_de.py @@ -9,13 +9,10 @@ DESCRIPTION = "Source for Landkreis Nordwestmecklenburg" URL = "https://www.geoport-nwm.de/de/abfuhrtermine-geoportal.html" TEST_CASES = { "Rüting": {"district": "Rüting"}, - # Ortsteil_Rueting - "Grevenstein u.": {"district": "Grevenstein u. Ausbau"}, - # Ortsteil_Grevenstein_u_Ausbau + "Grevenstein u. ...": {"district": "Grevenstein u. Ausbau"}, "Seefeld": {"district": "Seefeld/ Testorf- Steinfort"}, - # Ortsteil_Seefeld_Testorf_Steinfort - "1100l": {"district": "Groß Stieten (1.100 l Behälter)"} - # Ortsteil_Gross_Stieten_1100_l + "1100l": {"district": "Groß Stieten (1.100 l Behälter)"}, + "kl. Bünsdorf": {"district": "Klein Bünsdorf"} } @@ -27,10 +24,9 @@ class Source: def fetch(self): arg = convert_to_arg(self._district) today = datetime.date.today() - year = today.year + year = 2023 #today.year r = requests.get( f"https://www.geoport-nwm.de/nwm-download/Abfuhrtermine/ICS/{year}/{arg}.ics") - dates = self._ics.convert(r.text) entries = [] @@ -46,7 +42,7 @@ def convert_to_arg(district): district = district.replace("ä", "ae") district = district.replace("ß", "ss") district = district.replace("/", "") - district = district.replace("-", "") + district = district.replace("- ", "-") district = district.replace(".", "") district = district.replace(" ", "_") arg = urllib.parse.quote("Ortsteil_" + district) diff --git a/doc/source/geoport_nwm_de.md b/doc/source/geoport_nwm_de.md new file mode 100644 index 00000000..80d012e3 --- /dev/null +++ b/doc/source/geoport_nwm_de.md @@ -0,0 +1,22 @@ +# Landkreis Nordwestmecklenburg + +Support for Landkreis Nordwestmecklenburg in Mecklenburg-Vorpommern, Germany. + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: geoport_nwm_de + args: + district: DISTRICT +``` + +### Configuration Variables + +**district** +*(string) (required)* + +### How to get the source arguments + +Visit [geoport-nwm.de](https://www.geoport-nwm.de/de/abfuhrtermine-geoportal.html) and search for your area. Copy the value from the text input field and use it for the `district` argument. It is case sensitive. \ No newline at end of file diff --git a/info.md b/info.md index 0d305f22..a955c65c 100644 --- a/info.md +++ b/info.md @@ -103,6 +103,7 @@ Currently the following service providers are supported: - [Cochem-Zell](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/buergerportal_de.md) - [EGN-Abfallkalender.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/egn_abfallkalender_de.md) - [Erlangen-Höchstadt](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/erlangen_hoechstadt_de.md) +- [Geodatenportal Nordwestmecklenburg](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/geoport_nwm_de.md) - [Jumomind.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/jumomind_de.md) - [KWB-Goslar.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/kwb_goslar_de.md) - [KWU-Entsorgung.de](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/doc/source/kwu_de.md) From 9d7ab8a98d09473c92199fb958070dac270bab3b Mon Sep 17 00:00:00 2001 From: Finn Freitag Date: Mon, 26 Dec 2022 21:30:53 +0100 Subject: [PATCH 044/127] Fix request to use current year --- .../waste_collection_schedule/source/geoport_nwm_de.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/geoport_nwm_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/geoport_nwm_de.py index 88fbb716..6c29a02b 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/geoport_nwm_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/geoport_nwm_de.py @@ -24,7 +24,7 @@ class Source: def fetch(self): arg = convert_to_arg(self._district) today = datetime.date.today() - year = 2023 #today.year + year = today.year r = requests.get( f"https://www.geoport-nwm.de/nwm-download/Abfuhrtermine/ICS/{year}/{arg}.ics") dates = self._ics.convert(r.text) From 92e4c670726e64253fbcb7d0e7500d22939361a6 Mon Sep 17 00:00:00 2001 From: bbr111 Date: Mon, 26 Dec 2022 23:16:59 +0100 Subject: [PATCH 045/127] Add refresh_source Service Add an Service .refresh_source to manual update source --- custom_components/waste_collection_schedule/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/custom_components/waste_collection_schedule/__init__.py b/custom_components/waste_collection_schedule/__init__.py index 72f05442..95f9253f 100644 --- a/custom_components/waste_collection_schedule/__init__.py +++ b/custom_components/waste_collection_schedule/__init__.py @@ -123,6 +123,12 @@ async def async_setup(hass: HomeAssistant, config: dict): # initial fetch of all data hass.add_job(api._fetch) + + def refresh_source(): + hass.add_job(api._fetch) + + # Register new Service refresh_source + hass.services.async_register(DOMAIN, 'refresh_source', refresh_source) return True From c211fc19f7c384d993c840faa0aafb77f2ac99ca Mon Sep 17 00:00:00 2001 From: bbr111 Date: Mon, 26 Dec 2022 23:29:22 +0100 Subject: [PATCH 046/127] Create services.yaml --- custom_components/waste_collection_schedule/services.yaml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 custom_components/waste_collection_schedule/services.yaml diff --git a/custom_components/waste_collection_schedule/services.yaml b/custom_components/waste_collection_schedule/services.yaml new file mode 100644 index 00000000..4c05b93d --- /dev/null +++ b/custom_components/waste_collection_schedule/services.yaml @@ -0,0 +1,3 @@ +refresh_source: + name: refresh_source + description: refresh waste source \ No newline at end of file From 45a126a0ff24baa0c35521d7b8d108f2ffceea98 Mon Sep 17 00:00:00 2001 From: bbr111 Date: Mon, 26 Dec 2022 23:16:59 +0100 Subject: [PATCH 047/127] Add refresh_source Service Add an Service .refresh_source to manual update source Create services.yaml --- custom_components/waste_collection_schedule/__init__.py | 6 ++++++ custom_components/waste_collection_schedule/services.yaml | 3 +++ 2 files changed, 9 insertions(+) create mode 100644 custom_components/waste_collection_schedule/services.yaml diff --git a/custom_components/waste_collection_schedule/__init__.py b/custom_components/waste_collection_schedule/__init__.py index 72f05442..95f9253f 100644 --- a/custom_components/waste_collection_schedule/__init__.py +++ b/custom_components/waste_collection_schedule/__init__.py @@ -123,6 +123,12 @@ async def async_setup(hass: HomeAssistant, config: dict): # initial fetch of all data hass.add_job(api._fetch) + + def refresh_source(): + hass.add_job(api._fetch) + + # Register new Service refresh_source + hass.services.async_register(DOMAIN, 'refresh_source', refresh_source) return True diff --git a/custom_components/waste_collection_schedule/services.yaml b/custom_components/waste_collection_schedule/services.yaml new file mode 100644 index 00000000..4c05b93d --- /dev/null +++ b/custom_components/waste_collection_schedule/services.yaml @@ -0,0 +1,3 @@ +refresh_source: + name: refresh_source + description: refresh waste source \ No newline at end of file From 573e6067275b368f1880fb937eb46218b061b7fc Mon Sep 17 00:00:00 2001 From: dt215git Date: Tue, 27 Dec 2022 08:51:09 +0000 Subject: [PATCH 048/127] folders reorganised --- {doc => images}/button-cards.png | Bin {doc => images}/calendar.png | Bin {doc => images}/date-of-next-collections.png | Bin {doc => images}/days-to-next-collections.png | Bin {doc => images}/default-entity.png | Bin {doc => images}/more-info-appointment-types.png | Bin {doc => images}/more-info-generic.png | Bin {doc => images}/more-info-upcoming.png | Bin {doc => images}/next-collection-type.png | Bin {doc => images}/next-collections-date-and-days.png | Bin {doc => images}/upcoming_details.png | Bin LICENSE => md_archive/LICENSE | 0 README.md => md_archive/README.md | 0 info.md => md_archive/info.md | 0 14 files changed, 0 insertions(+), 0 deletions(-) rename {doc => images}/button-cards.png (100%) rename {doc => images}/calendar.png (100%) rename {doc => images}/date-of-next-collections.png (100%) rename {doc => images}/days-to-next-collections.png (100%) rename {doc => images}/default-entity.png (100%) rename {doc => images}/more-info-appointment-types.png (100%) rename {doc => images}/more-info-generic.png (100%) rename {doc => images}/more-info-upcoming.png (100%) rename {doc => images}/next-collection-type.png (100%) rename {doc => images}/next-collections-date-and-days.png (100%) rename {doc => images}/upcoming_details.png (100%) rename LICENSE => md_archive/LICENSE (100%) rename README.md => md_archive/README.md (100%) rename info.md => md_archive/info.md (100%) diff --git a/doc/button-cards.png b/images/button-cards.png similarity index 100% rename from doc/button-cards.png rename to images/button-cards.png diff --git a/doc/calendar.png b/images/calendar.png similarity index 100% rename from doc/calendar.png rename to images/calendar.png diff --git a/doc/date-of-next-collections.png b/images/date-of-next-collections.png similarity index 100% rename from doc/date-of-next-collections.png rename to images/date-of-next-collections.png diff --git a/doc/days-to-next-collections.png b/images/days-to-next-collections.png similarity index 100% rename from doc/days-to-next-collections.png rename to images/days-to-next-collections.png diff --git a/doc/default-entity.png b/images/default-entity.png similarity index 100% rename from doc/default-entity.png rename to images/default-entity.png diff --git a/doc/more-info-appointment-types.png b/images/more-info-appointment-types.png similarity index 100% rename from doc/more-info-appointment-types.png rename to images/more-info-appointment-types.png diff --git a/doc/more-info-generic.png b/images/more-info-generic.png similarity index 100% rename from doc/more-info-generic.png rename to images/more-info-generic.png diff --git a/doc/more-info-upcoming.png b/images/more-info-upcoming.png similarity index 100% rename from doc/more-info-upcoming.png rename to images/more-info-upcoming.png diff --git a/doc/next-collection-type.png b/images/next-collection-type.png similarity index 100% rename from doc/next-collection-type.png rename to images/next-collection-type.png diff --git a/doc/next-collections-date-and-days.png b/images/next-collections-date-and-days.png similarity index 100% rename from doc/next-collections-date-and-days.png rename to images/next-collections-date-and-days.png diff --git a/doc/upcoming_details.png b/images/upcoming_details.png similarity index 100% rename from doc/upcoming_details.png rename to images/upcoming_details.png diff --git a/LICENSE b/md_archive/LICENSE similarity index 100% rename from LICENSE rename to md_archive/LICENSE diff --git a/README.md b/md_archive/README.md similarity index 100% rename from README.md rename to md_archive/README.md diff --git a/info.md b/md_archive/info.md similarity index 100% rename from info.md rename to md_archive/info.md From d65dcb2383cb5a8fee128e665958c7cc51d0521c Mon Sep 17 00:00:00 2001 From: dt215git Date: Tue, 27 Dec 2022 08:59:49 +0000 Subject: [PATCH 049/127] new images added, info.md and readme.md updated --- README.md | 308 ++++++++++++++++++++++++++++++++++++++++ images/wcs_animated.gif | Bin 0 -> 87116 bytes images/wcs_code_btn.png | Bin 0 -> 1989 bytes images/wcs_fork_btn.png | Bin 0 -> 2349 bytes info.md | 35 +++++ 5 files changed, 343 insertions(+) create mode 100644 README.md create mode 100644 images/wcs_animated.gif create mode 100644 images/wcs_code_btn.png create mode 100644 images/wcs_fork_btn.png create mode 100644 info.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..b6f5e2d2 --- /dev/null +++ b/README.md @@ -0,0 +1,308 @@ + + +Waste Collection Schedule logo + +# Waste Collection Schedule + +**A custom component for Home Assistant that retrieves waste collection schedules from a wide range of service providers.** + +Waste Collection Schedule animation +Waste collection schedules from service provider web sites are updated daily, derived from local ICS/iCal files, or generated from user-specified dates or regularly repeating date patterns. The Home Assistant built-in Calendar is automatically populated with schedules, and there is a high degree of flexibility in how information can be format and displayed in entity cards or pop-ups. The framework can easily be extended to support additional waste collection service providers, or other services which provide schedules. + +# Supported Service Providers + +Waste collection schedules in the following formats and countries are supported. Click on the section heading to view details of individual service providers. + +
+ICS/iCal and User-Specified +

+ +- [Generic ICS / iCal File](/doc/source/ics.md) +- [User Specified](/doc/source/static.md) +

+
+ +
+Australia +

+ +- [Banyule City Council](/doc/source/banyule_vic_gov_au.md) +- [Belmont City Council](/doc/source/belmont_wa_gov_au.md) +- [Brisbane City Council](/doc/source/brisbane_qld_gov_au.md) +- [Campbelltown City Council](/doc/source/campbelltown_nsw_gov_au.md) +- [City of Canada Bay Council](/doc/source/canadabay_nsw_gov_au.md) +- [Inner West Council (NSW)](/doc/source/innerwest_nsw_gov_au.md) +- [Ku-ring-gai Council](/doc/source/kuringgai_nsw_gov_au.md) +- [Macedon Ranges Shire Council, Melbourne](/doc/source/mrsc_vic_gov_au.md) +- [Maroondah City Council](/doc/source/maroondah_vic_gov_au.md) +- [Melton City Council, Melbourne](/doc/source/melton_vic_gov_au.md) +- [Nillumbik Shire Council](/doc/source/nillumbik_vic_gov_au.md) +- [North Adelaide Waste Management Authority, South Australia](/doc/source/nawma_sa_gov_au.md) +- [RecycleSmart](/doc/source/recyclesmart_com.md) +- [Stonnington City Council, Melbourne](/doc/source/stonnington_vic_gov_au.md) +- [The Hills Council, Sydney](/doc/source/thehills_nsw_gov_au.md) +- [Wyndham City Council, Melbourne](/doc/source/wyndham_vic_gov_au.md) +

+
+ +
+Austria +

+ +- [BMV.at](/doc/source/bmv_at.md) +- [Data.Umweltprofis](/doc/source/data_umweltprofis_at.md) +- [Korneuburg Stadtservice](/doc/source/korneuburg_stadtservice_at.md) +- [WSZ-Moosburg.at](/doc/source/wsz_moosburg_at.md) +

+
+ +
+Belgium +

+ +- [Hygea.be](/doc/source/hygea_be.md) +- [Recycle! / RecycleApp.be](/doc/source/recycleapp_be.md) +

+
+ +
+Canada +

+ +- [City of Toronto](/doc/source/toronto_ca.md) +

+
+ +
+Germany +

+ +- [Abfall.IO / AbfallPlus.de](/doc/source/abfall_io.md) +- [AbfallNavi.de (RegioIT.de)](/doc/source/abfallnavi_de.md) +- [Abfallkalender Würzburg](/doc/source/wuerzburg_de.md) +- [Abfalltermine Forchheim](/doc/source/abfalltermine_forchheim_de.md) +- [Abfallwirtschaft Bremen](/doc/source/c_trace_de.md) +- [Abfallwirtschaft Landkreis Harburg](/doc/source/aw_harburg_de.md) +- [Abfallwirtschaft Landkreis Wolfenbüttel](/doc/source/alw_wf_de.md) +- [Abfallwirtschaft Neckar-Odenwald-Kreis](/doc/source/awn_de.md) +- [Abfallwirtschaft Rendsburg](/doc/source/awr_de.md) +- [Abfallwirtschaft Stuttgart](/doc/source/stuttgart_de.md) +- [Abfallwirtschaft Südholstein](/doc/source/awsh_de.md) +- [Abfallwirtschaft Zollernalbkreis](/doc/source/abfall_zollernalbkreis_de.md) +- [Alb-Donau-Kreis](/doc/source/buergerportal_de.md) +- [ART Trier](/doc/source/art_trier_de.md) +- [AWB Bad Kreuznach](/doc/source/awb_bad_kreuznach_de.md) +- [AWB Esslingen](/doc/source/awb_es_de.md) +- [AWB Landkreis Augsburg](/doc/source/c_trace_de.md) +- [AWB Limburg-Weilburg](/doc/source/awb_lm_de.md) +- [AWB Oldenburg](/doc/source/awb_oldenburg_de.md) +- [AWBKoeln.de](/doc/source/awbkoeln_de.md) +- [AWIDO-online.de](/doc/source/awido_de.md) +- [Berlin-Recycling.de](/doc/source/berlin_recycling_de.md) +- [Bogenschuetz-Entsorgung.de](/doc/source/infeo_at.md) +- [BSR.de / Berliner Stadtreinigungsbetriebe](/doc/source/bsr_de.md) +- [C-Trace.de](/doc/source/c_trace_de.md) +- [Cochem-Zell](/doc/source/cochem_zell_online_de.md) +- [EGN-Abfallkalender.de](/doc/source/egn_abfallkalender_de.md) +- [Erlangen-Höchstadt](/doc/source/erlangen_hoechstadt_de.md) +- [Jumomind.de](/doc/source/jumomind_de.md) +- [KAEV Niederlausitz](/doc/source/kaev_niederlausitz_de.md) +- [KWB-Goslar.de](/doc/source/kwb_goslar_de.md) +- [KWU-Entsorgung](/doc/source/kwu_de.md) +- [Landkreis-Wittmund.de](/doc/source/landkreis_wittmund_de.md) +- [Landkreis Rhön Grabfeld](/doc/source/landkreis_rhoen_grabfeld.md) +- [Landkreis Schwäbisch Hall](/doc/source/lrasha_de.md) +- [Muellmax.de](/doc/source/muellmax_de.md) +- [MyMuell App](/doc/source/jumomind_de.md) +- [RegioEntsorgung](/doc/source/regioentsorgung_de.md) +- [Rhein-Hunsrück Entsorgung (RHE)](/doc/source/rh_entsorgung_de.md) +- [Sector27.de](/doc/source/sector27_de.md) +- [Stadtreinigung Dresden](/doc/source/stadtreinigung_dresden_de.md) +- [Stadtreinigung.Hamburg](/doc/source/stadtreinigung_hamburg.md) +- [Stadtreinigung-Leipzig.de](/doc/source/stadtreinigung_leipzig_de.md) +- [Stadt-Willich.de](/doc/source/stadt_willich_de.md) +- [StadtService Brühl](/doc/source/stadtservice_bruehl_de.md) +- [Südbrandenburgischer Abfallzweckverband](/doc/source/sbazv_de.md) +- [Umweltbetrieb Stadt Bielefeld](/doc/source/bielefeld_de.md) +- [WAS Wolfsburg](/doc/source/was_wolfsburg_de.md) +- [Wermeldkirchen](/doc/source/wermelskirchen_de.md) +- [Zweckverband Abfallwirtschaft Werra-Meißner-Kreis](/doc/source/zva_wmk_de.md) + +

+
+ +
+Lithuania +

+ +- [Kauno švara](/doc/source/grafikai_svara_lt.md) +

+
+ +
+Netherlands +

+ +- [HVCGroep and others](/doc/source/hvcgroep_nl.md) +- [Ximmio](/doc/source/ximmio_nl.md) +

+
+ +
+New Zealand +

+ +- [Auckland](/doc/source/aucklandcouncil_govt_nz.md) +- [Christchurch](/doc/source/ccc_govt_nz.md) +- [Gore, Invercargill & Southland](/doc/source/wastenet_org_nz.md) +- [Horowhenua District](/doc/source/horowhenua_govt_nz.md) +- [Waipa District](/doc/source/waipa_nz.md) +- [Wellington](/doc/source/wellington_govt_nz.md) +

+
+ +
+Norway +

+ +- [Min Renovasjon](/doc/source/minrenovasjon_no.md) +- [Oslo Kommune](/doc/source/oslokommune_no.md) +

+
+ +
+Poland +

+ +- [Warsaw](/doc/source/warszawa19115_pl.md) +- [Multiple communities - ecoharmonogram](/doc/source/ecoharmonogram_pl.md) +

+
+ +
+Sweden +

+ +- [Lerum.se](/doc/source/lerum_se.md) +- [Ronneby Miljöteknik](/doc/source/miljoteknik_se.md) +- [SSAM.se](/doc/source/ssam_se.md) +- [Sysav.se](/doc/source/sysav_se.md) +- [Vasyd.se](/doc/source/vasyd_se.md) +

+
+ +
+Switzerland +

+ +- [A-Region.ch](/doc/source/a_region_ch.md) +- [Lindau.ch](/doc/source/lindau_ch.md) +

+
+ +
+United States of America +

+ +- [PGH.ST](/doc/source/pgh_st.md) +- [Republic Services](/doc/source/republicservices_com.md) +- [Seattle Public Utilities](/doc/source/seattle_gov.md) +

+
+ +
+United Kingdom +

+ +- [Bracknell Forest Council - bracknell-forest.gov.uk](/doc/source/bracknell_forest_gov_uk.md) +- [Bradford Metropolitan District Council - bradford.gov.uk](/doc/source/bradford_gov_uk.md) +- [Braintree District Council - bracknell-forest.gov.uk](/doc/source/bracknell_forest_gov_uk.md) +- [Cambridge City Council - cambridge.gov.uk](/doc/source/cambridge_gov_uk.md) +- [Canterbury City Council - canterbury.gov.uk](/doc/source/canterbury_gov_uk.md) +- [Cheshire East Council - cheshireeast.gov.uk](/doc/source/cheshire_east_gov_uk.md) +- [Chesterfield Borough Council - chesterfield.gov.uk](/doc/source/chesterfield_gov_uk.md) +- [Colchester Borough Council - colchester.gov.uk](/doc/source/colchester_gov_uk.md) +- [Cornwall Council - cornwall.gov.uk](/doc/source/cornwall_gov_uk.md) +- [Derby City Council - derby.gov.uk](/doc/source/derby_gov_uk.md) +- [Eastbourne Borough Council - lewes-eastbourne.gov.uk](/doc/source/environmentfirst_co_uk.md) +- [Elmbridge Borough Council - elmbridge.gov.uk](/doc/source/elmbridge_gov_uk.md) +- [Guildford Borough Council - guildford.gov.uk](/doc/source/guildford_gov_uk.md) +- [Harborough District Council - harborough.gov.uk](/doc/source/fccenvironment_co_uk.md) +- [Huntingdonshire District Council - huntingdonshire.gov.uk](/doc/source/huntingdonshire_gov_uk.md) +- [The Royal Borough of Kingston - kinston.gov.uk](/doc/source/kingston_gov_uk.md) +- [Lewes District Council - lewes-eastbourne.gov.uk](/doc/source/environmentfirst_co_uk.md) +- [London Borough of Lewisham - lewisham.gov.uk](.doc/source/lewisham_gov_uk.md) +- [Manchester City Council - manchester.gov.uk](/doc/source/manchester_uk.md) +- [Newcastle City Council - newcastle.gov.uk](/doc/source/newcastle_gov_uk.md) +- [North Somerset Council - n-somerset.gov.uk](/doc/source/nsomerset_gov_uk.md) +- [Nottingham City Council - nottinghamcity.gov.uk](/doc/source/nottingham_city_gov_uk.md) +- [Peterborough City Council - peterborough.gov.uk](/doc/source/peterborough_gov_uk.md) +- [Richmondshire District Council - richmondshire.gov.uk](/doc/source/richmondshire_gov_uk.md) +- [Rushmoor Borough Council - rushmoor.gov.uk](/doc/source/rushmoor_gov_uk.md) +- [Sheffield City Council - sheffield.gov.uk](/doc/source/sheffield_gov_uk.md) +- [South Cambridgeshire District Council - scambs.gov.uk](/doc/source/scambs_gov_uk.md) +- [South Norfolk and Broadland Council - southnorfolkandbroadland.gov.uk](/doc/source/south_norfolk_and_broadland_gov_uk.md) +- [Stevenage Borough Council - stevenage.gov.uk](/doc/source/stevenage_gov_uk.md) +- [Tewkwsbury Borough Council - tewkesbury_gov_uk](/doc/source/tewkesbury_gov_uk.md) +- [City of York Council - york.gov.uk](/doc/source/york_gov_uk.md) +- [Walsall Council - walsall.gov.uk](/doc/source/walsall_gov_uk.md) +- [West Berkshire Council - westberks.gov.uk](/doc/source/westberks_gov_uk.md) +- [Wiltshire Council - wiltshire.gov.uk](/doc/source/wiltshire_gov_uk.md) +

+
+ +# Installation and Configuration +![hacs badge](https://img.shields.io/badge/HACS-Default-orange) +![hacs installs](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Flauwbier.nl%2Fhacs%2Fwaste_collection_schedule) + +The Waste Collection Schedule can be installed via [HACS](https://hacs.xyz/), or by manually copying the [`waste_collection_schedule`](https://github.com/mampfes/hacs_waste_collection_schedule/tree/master/custom_components) directory to Home Assistant's `config/custom_components/` directory. For further details see the [installation and configuration](/doc/installation.md) page. + +# Contributing To The Project +![python badge](https://img.shields.io/badge/Made%20with-Python-orange) +![github contributors](https://img.shields.io/github/contributors/mampfes/hacs_waste_collection_schedule?color=orange) +![last commit](https://img.shields.io/github/last-commit/mampfes/hacs_waste_collection_schedule?color=orange) +![Community Discussion](https://img.shields.io/badge/Home%20Assistant%20Community-Discussion-orange) + +There are several ways of contributing to this project, they include: +- Adding new service providers +- Updating or improving the documentation +- Helping answer/fix any issues raised +- Join in with the Home Assistant Community discussion + +For further details see [contribution](/doc/contributing.md) guidelines, or take a look at our [online](/doc/online.md) mentions. + + + + + +# Known Issues +The following waste service providers return errors when running the test_source script +- [ ] berlin_recycling_de, JSONDecodeError +- [ ] abfall_io, Traunstein test case fails +- [ ] sector27_de, TimeoutError +- [ ] banyule_vic_gov_au, JSONDecodeError +- [ ] muenchenstein_ch, AttributeError +- [ ] grafikai_svara_lt, TimeoutError +- [ ] avl_ludwigsburg_de, HTTPError +- [ ] warszawal9115_pl, HTTPError + +If you can fix any of these, please raise a Pull Request with the updates. + +# Licence +![github licence](https://img.shields.io/badge/Licence-MIT-orange) + +This project uses the MIT Licence, for more details see the [licence](/doc/licence.md) document. + +# Showing Your Appreciation +If you like this project, please give it a star on [GitHub](https://github.com/mampfes/hacs_waste_collection_schedule) or consider becomming a [Sponsor](https://github.com/sponsors/mampfes). diff --git a/images/wcs_animated.gif b/images/wcs_animated.gif new file mode 100644 index 0000000000000000000000000000000000000000..21aa208095d149cc6a2c2fdb41a2b29ac7349113 GIT binary patch literal 87116 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 z zGt^;$z5jk|DijEC0>Oy@1*4fLa6kZn1gg4JH~=8xF^T{dL=B|jz_gNAK#DYnj!F!3 z7FKGgfCB884{O`5dx1a|PMyp!RM6mdOiyS4L?j(77=XCGRA^GQx^iGv42ZX^a}Xnj z0|wWK(S*^<<}Mg8DuC!P!2yH`3j~-r0E2H56cKubCPtbwWqD%Bkp)I>qv^InI#})% zV?&1s^C%ybR0*KeX-67g8>kL00jtu)L;jQ z*8hcKRsG_aRuO8^0RsS-V}b}TFrW%)=>>4111M;~K#Ln-;1y*Bm_QT)+8JR10~!Kg z0s`m=*Q1Olq#|BUW06EtI5M^fnMOx38IS`fm|(&Q5MbZ{05XAq0g*DUrVBVkb!nJP zV13{Kjw~IZ$^-=zP#T#hY52kk5mb=Cmq!6001WaNL6ZO{{Gtt76P)np3l<(w7B7V^ zrHeN8$S4245H)CP{6zoozR`4X94+_03Ai}sRa(0 zK%KfMMYGDM&Y%pd)2WSk2VloCG?69LU@Dd6Om;ga_%Bl201TCuiia8bm(^ay4 z>4>&>3$XW$)uVw86}r1kfT1euKy+qH-6(D8gd&etu}l^~tBaZ8gy|Cl{h-hk(%I

$m%p=0tQ&XC!hm?2ADMt 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 odkQ|s1-3>_T?wO literal 0 HcmV?d00001 diff --git a/images/wcs_code_btn.png b/images/wcs_code_btn.png new file mode 100644 index 0000000000000000000000000000000000000000..07afbddb541767012ed1477c917f9b64819f913b GIT binary patch literal 1989 zcmV;$2RitPP)EX>4Tx04R}tkv&MmKpe$iTcsiuk#;EJkf92K1yK=4twIqhgj%6h2a`*`ph-iL z;^HW{799LotU9+0Yt2!bCVj!sUBE>hzEl0u6Z503ls?%w0>9pG10C4=2nH^D?*4Oj8P0o%+%*ZF$K@@bq^n3@8Uem``n+SPsy7M@Cn4TOgAjz4dUrd zOXs{#9APC%AwDM_Gw6cEk6f2se&bwp*v~T~MkYN^93d8p9V~Y+D;X;B6md*ZHOlvA zT~;`6aaJoe*19KuVJNSyEOVXa5RzEL5=01)Q9~IOScucAkzyiE`w0*KpyLPFL=9Bb# zTZmKj!>Fn*_Gp+u90Ey^wuq01}tpET324YJ`L;(K){{a7>y{D4^000SaNLh0L z04^f{04^f|c%?sf000HlNklbhdlY%zI z7=n3bC1%!)S!AQ;G|i<=QK>_sre)eVqnY(bGMPylT4IxErBf*qiV7qjiV}Fo93sbY zAKM>wDncfD=~?@m*?)F--r0SA`#jJ4zB4Ou1yRO$IVeVlwxI8)yOzGXf7vU7Uc}~X!QpZ!0lUR$R2n3;AF|m^R-!3;LQ2X40U+Vk`(! z=n%C%C8fvs7!H>MZBGeNnU(n{qB3Fjbw3Keiu#UuQlUEu3A31ENMe{?ht_G~x6^z0 zuwXsg>utz4G^mcL>06mtHkGFi)!|m;+z0{OJ8mr-05T6+{-I@i#s)*Dr6d&!@IEMvkVqjHT{(( z$2nAW8vmJV$uJCJ$G(%e6gmGa=8@P`HvOE_?pl6n0aN1A06HN^#oa^46M61)W>+`0 zEnm)~e6o8vm1;XGz=ev?0nd;*ZW5#QfwWqWvGd?cmKK%K44?`pW$a7L9ui9*&T;s7 zE~P5rifHsBHEAxhqT?7KIjE}6W6if4_@<%t&X?m6^F?tUOZKd4cir^WB7jZ`>OoBJ zUeup0p|)LPaZ^*doz0$?37Ns#$wnFvzD@e^vjp~=!J_1M$hblK9{%7KI*Q$&TkZ+@fRT$d_66xIY&*t>8!d9W3WcF&+sW z(P=?KSAw+A*wXY5OmeYutU23#}07*A=0owLq6(`baL!RyR?49ku6<@%tr< zEfm+3qZ$!KL|_9^U8>o8#&Sure6g5c%r3?NVz&q)G~wKr{xwg?mrJTvtLCQwe+2Ap zJ`PXtuNLGjFwAx!!fhbSq#GuCU5wWjp{w_==ZoSzro^R@o%(vG zO5nA#2Ea*C$sTG#ALF^mz_tR^gGr5!z*6-SrS2vw%ociw4k6^yO@7=RHUPJ!lIq4P zPPO)BSeWk87=qtm67;G+-B`;ha}4C3KGNliDkbsSv`ilD{`X<>2=N@=L=~Pg=*?zF z^Gy$wUs^zT_dZOCOCu~Ols#qpZmKu6`T8%Mq%t54Q_^@MXr{#l0lE=9nY@5!^v77S z^J9uyT+}spW&D`wjFKxk(IgNV{Q_@|4C9A=?~-@^G#3Byym+qxx4E1;u|LKK=J9xN z0QHqy*mkA@b3iJlQNw68pQ2ufWK!}%X8IrGgP)IJzD6<~tv$if+7n(CKy_#VFRBfbKFGk(*H5S9l2ih${^GgdlFXn&9za~e)J93m=X&*+BqsUQEtWLZi}PueTvuODsrgKP4hQgrxSvT4F)0$So3Nvl~exDN*qS zF&YWk?4}~ONDu+C5>46a29a*sK&8ap#!}Wz8;H6=BrDOB4Yb|lRek}vea*k4UqJo< XEEZzG33o%r00000NkvXXu0mjf6IZi8 literal 0 HcmV?d00001 diff --git a/images/wcs_fork_btn.png b/images/wcs_fork_btn.png new file mode 100644 index 0000000000000000000000000000000000000000..dc10a2fdcb54e6b0eb221deee4d62059e79cd1d1 GIT binary patch literal 2349 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 + +# Waste Collection Schedule +![hacs_badge](https://img.shields.io/badge/HACS-Default-orange) +![hacs installs](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Flauwbier.nl%2Fhacs%2Fwaste_collection_schedule) +![Community Discussion](https://img.shields.io/badge/Home%20Assistant%20Community-Discussion-orange) + +**A custom component for Home Assistant that retrieves waste collection schedules from a wide range of service providers.** + +Waste Collection Schedule animation +Waste collection schedules from service provider web sites are updated daily, derived from local ICS/iCal files, or generated from user-specified dates or regularly repeating date patterns. The Home Assistant built-in Calendar is automatically populated with schedules, and there is a high degree of flexibility in how information can be format and displayed in entity cards or pop-ups. The framework can easily be extended to support additional waste collection service providers, or other services which provide schedules. + + +## Supported Service Providers + +| Country | Service Providers | +|--|--| +| Generic | ICS / iCal files | +| Static | User-defined dates or repeating date patterns | +| Australia | Banyule City Council, Belmont City Council, Brisbane City Council, Campbelltown City Council, City of Canada Bay Council, Inner West Council (NSW), Ku-ring-gai Council, Macedon Ranges Shire Council (Melbourne), Maroondah City Council, Melton City Council (Melbourne), Nillumbik Shire Council, North Adelaide Waste Management Authority (South Australia), RecycleSmart, Stonnington City Council (Melbourne), The Hills Council (Sydney), Wyndham City Council (Melbourne) | +| Austria | BMV, Data.Umweltprois, Korneuburg Stadtservice, WSZ-Moosburg | +| Belgium | Hyhea, Recycle! / RecycleApp | +| Canada | City of Toronto | +| Germany | Abfall.IO / AbfallPlus, AbfallNavi (RegioIT.de), Abfallkalender Würzburg, Abfalltermine Forchheim, Abfallwirtschaft Bremen, Abfallwirtschaft Landkreis Harburg, Abfallwirtschaft Landkreis Wolfenbüttel, Abfallwirtschaft Neckar-Odenwald-Kreis, Abfallwirtschaft Rendsburg, Abfallwirtschaft Stuttgart, Abfallwirtschaft Südholstein, Abfallwirtschaft Zollernalbkreis, Alb-Donau-Kreis, ART Trier, AWB Bad Kreuznach, AWB Esslingen, AWB Limburg-Weilburg, AWB Oldenburg, AWBKoeln, AWIDO-online, Berlin-Recycling, Bogenschuetz-Entsorgung, BSR.de / Berliner Stadtreinigungsbetriebe, C-Trace, Cochem-Zell, EGN-Abfallkalender, Erlangen-Höchstadt, Jumomind, KAEV Niederlausitz, KWB-Goslar, KWU-Entsorgung, Landkreis-Wittmund, Landkreis Rhön Grabfeld, Landkreis Schwäbisch Hall, Muellmax, MyMuell App, RegioEntsorgung, Rhein-Hunsrück Entsorgung (RHE), Sector27, Stadtreinigung Dresden, Stadtreinigung.Hamburg, Stadtreinigung-Leipzig, Stadt-Willich, StadtService Brühl, Südbrandenburgischer Abfallzweckverband, Umweltbetrieb Stadt Bielefeld, WAS Wolfsburg, Wermeldkirchen, Zweckverband Abfallwirtschaft Werra-Meißner-Kreis | +| Lituania | Kauno švara | +| Netherlands | HVCGroep, Ximmio | +| New Zealand | Auckland, Christchurch, Gore, Invercargill & Shouthand, Horowhenua District, Wapia District, Wellington | +| Norway | Min Renovasjon, Oslo Kommune | +| Poland | Warsaw, Multiple Communities (Echoharmonogram) | +| Sweden | Lerum, Ronneby Miljöteknik, SSAM, Sysav, Vasyd | +| Switzerland | A-region, Lindau | +| USA | PGT.ST, Republic Services, Seattle Public Utilities | +| UK | Bracknell Forest Council, Bradford Metropolitan District Council, Braintree District Council, Cambridge City Council, Canterbury City Council, Cheshire East Council, Chesterfield Borough Council, Colchester Borough Council, Cornwall Council, Derby City Council, Eastbourne Borough Council, Elmbridge Borough Council, Guildford Borough Council, Harborough District Council, Huntingdonshire District Council, The Royal Borough of Kingston, Lewes District Council, London Borough of Lewisham, Manchester City Council, Newcastle City Council, North Somerset Council, Nottingham City Council, Peterborough City Council, Richmondshire District Council, Rushmoor Borough Council, Sheffield City Council, South Cambridgeshire District Council, South Norfolk and Broadland Council, Stevenage Borough Council, Tewkesbury Borough Council, City of York Council, Walsall Council, West Berkshire Council, Wiltshire Council | + +For full details on supported service providers, and more project details, visit us on [GitHub](https://github.com/mampfes/hacs_waste_collection_schedule) From f8bd810ca336fa262a34e9b1299e7a66b30a40aa Mon Sep 17 00:00:00 2001 From: dt215git Date: Tue, 27 Dec 2022 09:06:04 +0000 Subject: [PATCH 050/127] new .md files added --- doc/contributing.md | 218 +++++++++++++++++++++++++++++++++ doc/faq.md | 281 +++++++++++++++++++++++++++++++++++++++++++ doc/installation.md | 141 ++++++++++++++++++++++ doc/licence.md | 23 ++++ doc/online.md | 24 ++++ doc/page_template.md | 24 ++++ 6 files changed, 711 insertions(+) create mode 100644 doc/contributing.md create mode 100644 doc/faq.md create mode 100644 doc/installation.md create mode 100644 doc/licence.md create mode 100644 doc/online.md create mode 100644 doc/page_template.md diff --git a/doc/contributing.md b/doc/contributing.md new file mode 100644 index 00000000..5161f268 --- /dev/null +++ b/doc/contributing.md @@ -0,0 +1,218 @@ +Waste Collection Schedule logo + +# Contributing To Waste Collection Schedule +![python badge](https://img.shields.io/badge/Made%20with-Python-orange) +![github contributors](https://img.shields.io/github/contributors/mampfes/hacs_waste_collection_schedule?color=orange) +![last commit](https://img.shields.io/github/last-commit/mampfes/hacs_waste_collection_schedule?color=orange) + +There are several ways of contributing to this project, including: +- Providing new service providers +- Updating or improving the documentation +- Helping answer/fix any issues raised +- Join in with the Home Assistant Community discussion + +## Adding New Service Providers + +### Fork And Clone The Repository, And Checkout A New Branch +In GitHub, navigate to the repository [homepage](https://github.com/mampfes/hacs_waste_collection_schedule). Click the `fork` button at the top-right side of the page to fork the repository. + +![fork](/images/wcs_fork_btn.png) + +Navigate to your fork's homepage, click the `code` button and copy the url. + +![code](/images/wcs_code_btn.png) + +On your local machine, open a terminal and navigate to the location where you want the cloned directory. Type `git clone` and paste in the url copied earlier. It should look something like this, but with your username replacing `YOUR-GITHUB-USERNAME`: +```bash +git clone https://github.com/YOUR-GITHUB-USERNAME/hacs_waste_collection_schedule +``` + +Before making any changes, create a new branch to work on. +```bash +git branch +``` + For example, if you were adding a new provider called abc.com, you could do + ```bash +git branch adding_abc_com +``` + +For more info on forking/cloning a repository, see GitHub's [fork-a-repo](https://docs.github.com/en/get-started/quickstart/fork-a-repo) document. + +### Files Required For A New Service Provider +The following files need to be provided to support a new service provider: +- A python `source script` that retrieves the collection schedule information, formats it appropriately, and has test cases that can be used to confirm functionality. +- A `source markdown (.md)` file that describes how to configure the new source and sensor, with examples. +- An updated `README.md` file containing details of the new service provider. +- An updated `info.md` file containing details of the new service provider. + +The framework contains a test script that can be used to confirm source scripts are retrieving and returning correctly formatted waste collection schedules. + +### Python Source Script + +Create a new file in the `custom_components/waste_collection_schedule/waste_collection_schedule/source` folder. The file name should be the url of your service provider in lower case, for example `abc_com.py` for `https://www.abc.com`. + +The script should have the following general structure + +```py +import datetime +from waste_collection_schedule import Collection + + +DESCRIPTION = "Source script for abc.com" # Describe your source +URL = "abc.com" # Insert url to service homepage +TEST_CASES = { # Insert arguments for test cases to be used by test_sources.py script + "TestName1": {"arg1": 100, "arg2": "street"} + "TestName2": {"arg1": 200, "arg2": "road"} + "TestName3": {"arg1": 300, "arg2": "lane"} +} +API_URLS = { # Dict of API end-points if required + "address_search": "https://abc.com/search/", + "collection": "https://abc.com/search/{}/", +} +ICONS = { # Dict of waste types and suitable mdi icons + "DOMESTIC": "mdi:trash-can", + "RECYCLE": "mdi:recycle", + "ORGANIC": "mdi:leaf", +} + + +class Source: + def __init__(self, arg1, arg2): # argX correspond to the args dict in the source configuration + self._arg1 = arg1 + self._arg2 = arg2 + + def fetch(self): + + # replace this comment with + # api calls or web scraping required + # to capture waste collection schedules + # and extract date and waste type details + + entries = [] # List that holds collection schedule + + entries.append( + Collection( + date = datetime.datetime(2020, 4, 11), # Collection date + t = "Waste Type", # Collection type + icon = ICONS.get("Waste Type"), # Collection icon + ) + ) + + return entries +``` +Filtering of data for waste types or time periods is a functionality of the framework and should not be done by the source script. Therefore: +- A source script should return all data for all available waste types. +- A source script should **not** provide options to limit the returned waste types. +- A source script should return all data for the entire time period available (including past dates if they are returned). +- A source script should **not** provide a configuration option to limit the requested time frame. + + +### Service Provider Markdown File +Create a new markdown file in the `custom_components/waste_collection_schedule/doc/source` folder. The file name should be the url of your service provider in lower case, for example `abc_com.md` for `https://www.abc.com`. + +The markdown file should have the following general structure: + +1. A description of how the source should be configured. +2. A description of the arguments required, the type, and whether they are optional/mandatory. +3. A working example (usually one of the test cases from the `.py` file) + +For example: + +**Configuration via configuration.yaml** +```yaml +waste_collection_schedule: + sources: + - name: abc_com + args: + uprn: UNIQUE_PROPERTY_REFERENCE_NUMBER +``` + +**Configuration Variables** + +**uprn** _(string) (required)_ : The unique 12-digit identifier for your property + +**Example** +```yaml +waste_collection_schedule: + sources: + - name: abc_com + args: + uprn: "e3850cac2d5b" +``` +Note: Your uprn can be found on invoices from your service provider + + +### Updated README File +The README.md file in the top level folder contains a list of supported service providers. Please add your new entry to the relevant country section, creating a new section if yours is the first provider for that country. The entry should contain the name of the service provider and a link to the service providers markdown file. For example: +```markdown +- [Abfall.IO / AbfallPlus.de](/doc/source/abfall_io.md) +``` + +### Updated info.md File +The `info.md` is rendered in the HACS user interface within Home Assistant and gives potential users a summary of what the component does, and the service providers supported. Please add your new service provider to the appropriate country listed in the table. If yours is the first service provider for a country, create a new table row. + +### Test The New Source File +Debugging a source script within Home Assistant is not recommended. Home Assistant's start-up process is too slow for fast debugging cycles. To help with debugging/troubleshooting, the Waste Collection Schedule framework contains a command line script that can be used to test source scripts. The script iterates through the `test cases` defined in the source script passing each set of arguments to the source script and prints the results. + +The script supports the following options: + +| Option | Argument | Description | +|--------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------| +| `-s` | SOURCE | [Source name](https://github.com/mampfes/hacs_waste_collection_schedule#source-configuration-variables) (source file name without ending `.py`) | +| `-l` | - | List all found dates | +| `-i` | - | Add icon name to output. Only effective together with `-l`. | + +For debugging purposes of a single source, it is recommended to use the `-s SOURCE` option. If used without any arguments provided, the script tests every script in the `custom_components/waste_collection_schedule/waste_collection_schedule/source` folder and prints the number of found entries for every test case. + +To use it: +1. Navigate to the `custom_components/waste_collection_schedule/waste_collection_schedule/test/` directory +2. Confirm the `test_sources.py` script is present +3. Execute the test script. For example, testing the abfall_io.py source script would be: +```bash +test_sources.py -s abfall_io +``` +4. Confirm the results returned match expectation. For example, testing abfall_io returns: +```text +Testing source abfall_io ... + found 285 entries for Waldenbuch + found 58 entries for Landshut + found 109 entries for Schoenmackers + found 3 entries for Freudenstadt + found 211 entries for Ludwigshafen am Rhein + found 119 entries for Traunstein + found 287 entries for Thalheim +``` +5. To view individual date entries and assigned icons, use the `-i -l` arguments, for example: +```bash +test_sources.py -s richmondshire_gov_uk -i -l +Testing source richmondshire_gov_uk ... + found 53 entries for test 1 + 2023-01-02: 240L GREY RUBBISH BIN [mdi:trash-can] + 2023-01-07: 55L RECYCLING BOX [mdi:recycle] + 2023-01-13: 240L GREY RUBBISH BIN [mdi:trash-can] + 2023-01-20: 55L RECYCLING BOX [mdi:recycle] + 2023-01-27: 240L GREY RUBBISH BIN [mdi:trash-can] + ... + 2023-12-01: 240L GREY RUBBISH BIN [mdi:trash-can] + 2023-12-08: 55L RECYCLING BOX [mdi:recycle] + 2023-12-15: 240L GREY RUBBISH BIN [mdi:trash-can] +``` + +### Sync Branch and Create A Pull Request +Having completed your changes, sync your local branch to your GitHub repo, and then create a pull request. When creating a pull request, please provide a meaningful description of what the pull request covers. Ideally it should cite the service provider, confirm the .py, .md, README and info.md files have all been updated, and the output of the test_sources.py script demonstrating functionality. Once submitted a number of automated tests are run against the updated files to confirm they can be merged into the master branch. Note: Pull requests from first time contributors also undergo a manual code review before a merge confirmation in indicated. + +Once a pull request has been merged into the master branch, you'll receive a confirmation message. At that point you can delete your branch if you wish. + +## Update Or Improve The Documentation +Non-code contributions are welcome. If you find typos, spelling mistakes, or think there are other ways to improve the documentation please submit a pull request with updated text. Sometimes a picture paints a thousand words, so if a screenshots would better explain something, those are also welcome. + +## Help Answer/Fix Issues Raised +![GitHub issues](https://img.shields.io/github/issues-raw/mampfes/hacs_waste_collection_schedule?color=orange) + +Open-source projects are always a work in progress, and [issues](https://github.com/mampfes/hacs_waste_collection_schedule/issues) arise from time-to-time. If you come across a new issue, please raise it. If you have a solution to an open issue, please raise a pull request with your solution. + +## Join The Home Assistant Community Discussion +![Community Discussion](https://img.shields.io/badge/Home%20Assistant%20Community-Discussion-orange) + + +The main discussion thread on Home Assistant's Community forum can be found [here](https://community.home-assistant.io/t/waste-collection-schedule-framework/186492). \ No newline at end of file diff --git a/doc/faq.md b/doc/faq.md new file mode 100644 index 00000000..43911c52 --- /dev/null +++ b/doc/faq.md @@ -0,0 +1,281 @@ + + + +Waste Collection Schedule logo + +# Frequently Asked Questions, or "How Do I ...?" + +

+How do I format dates? +

+ +Use [strftime](https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior) in `value_template` or `date_template`: + +```yaml +# returns "20.03.2020" +value_template: '{{value.date.strftime("%d.%m.%Y")}}' +date_template: '{{value.date.strftime("%d.%m.%Y")}}' + +# returns "03/20/2020" +value_template: '{{value.date.strftime("%m/%d/%Y")}}' +date_template: '{{value.date.strftime("%m/%d/%Y")}}' + +# returns "Fri, 03/20/2020" +value_template: '{{value.date.strftime("%a, %m/%d/%Y")}}' +date_template: '{{value.date.strftime("%a, %m/%d/%Y")}}' +``` +

+
+ +
+How do I show the number of days to the next collection? +

+ +Set `value_template` within the sensor configuration: + +```yaml +value_template: 'in {{value.daysTo}} days' +``` +

+
+ +
+How do I show Today / Tomorrow instead of in 0 / 1 days? +

+ +Set `value_template` within the sensor configuration: + +```yaml +# returns "Today" if value.daysTo == 0 +# returns "Tomorrow" if value.daysTo == 1 +# returns "in X days" if value.daysTo > 1 +value_template: '{% if value.daysTo == 0 %}Today{% elif value.daysTo == 1 %}Tomorrow{% else %}in {{value.daysTo}} days{% endif %}' +``` +

+
+ +
+How do I join waste types in a value_template? +

+ +Use the `join` filter: + +```yaml +# returns "Garbage, Recycle" +value_template: '{{value.types|join(", ")}}' + +# returns "Garbage+Recycle" +value_template: '{{value.types|join("+")}}' +``` + +Note: If you don't specify a `value_template`, waste types will be joined using the `separator` configuration variable. +

+
+ +
+How do I setup a sensor which shows only the days to the next collection? +

+ +Set `value_template` within the sensor configuration: + +```yaml +value_template: '{{value.daysTo}}' +``` +

+
+ +
+How do I setup a sensor which shows only the date of the next collection? +

+ +Set `value_template` within the sensor configuration: + +```yaml +value_template: '{{value.date.strftime("%m/%d/%Y")}}' +``` +

+
+ +
+How do I configure a sensor which shows only the waste type of the next collection? +

+ +Set `value_template` within the sensor configuration: + +```yaml +value_template: '{{value.types|join(", ")}}' +``` +

+
+ +
+How do I configure a sensor to show only collections of a specific waste type? + +

+ +Set `types` within the sensor configuration: + +```yaml +sensor: + - platform: waste_collection_schedule + name: next_garbage_collection + types: + - Garbage + + - platform: waste_collection_schedule + name: next_recycle_collection + types: + - Recycle +``` + +Note: If you have set an alias for a waste type, you must use the alias name. +

+
+ +
+How can I rename an waste type? +

+ +Set `alias` in the customize section of a source: + +```yaml +waste_collection_schedule: + sources: + - name: NAME + customize: + - type: Very long garbage name + alias: Garbage + - type: Very long recycle name + alias: Recycle +``` +

+
+ +
+How can I hide a waste type I don't want to see? +

+ +Set `show` configuration variable to *false* in the customize section of a source: + +```yaml +waste_collection_schedule: + sources: + - name: NAME + customize: + - type: Unwanted Waste Type + show: false +``` +

+
+ +
+How do I show a coloured Lovelace card depending on the due date? +

+ +You can use [Button Card](https://github.com/custom-cards/button-card) to create a coloured Lovelace cards: + +![Button Card](/images/button-cards.png) + +```yaml +# configuration.yaml +sensor: + - platform: waste_collection_schedule + name: MyButtonCardSensor + value_template: '{{value.types|join(", ")}}|{{value.daysTo}}|{{value.date.strftime("%d.%m.%Y")}}|{{value.date.strftime("%a")}}' +``` + +```yaml +# button-card configuration +type: 'custom:button-card' +entity: sensor.mybuttoncardsensor +layout: icon_name_state2nd +show_label: true +label: | + [[[ + var days_to = entity.state.split("|")[1] + if (days_to == 0) + { return "Today" } + else if (days_to == 1) + { return "Tomorrow" } + else + { return "in " + days_to + " days" } + ]]] +show_name: true +name: | + [[[ + return entity.state.split("|")[0] + ]]] +state: + - color: red + operator: template + value: '[[[ return entity.state.split("|")[1] == 0 ]]]' + - color: orange + operator: template + value: '[[[ return entity.state.split("|")[1] == 1 ]]]' + - value: default +``` +

+
+ +
+Can I also use the Garbage Collection Card instead? +

+ +Yes, the [Garbage Collection Card](https://github.com/amaximus/garbage-collection-card) can also be used with *Waste Collection Schedule*: + +```yaml +# configuration.yaml +sensor: + - platform: waste_collection_schedule + name: garbage_days + details_format: appointment_types + value_template: "{{ value.daysTo }}" + types: + - Garbage + + - platform: template + sensors: + garbage: + value_template: > + {% if states('sensor.garbage_days')|int > 2 %} + 2 + {% else %} + {{ states('sensor.garbage_days')|int }} + {% endif %} + attribute_templates: + next_date: "{{ state_attr('sensor.garbage_days', 'Garbage') }}" + days: "{{ states('sensor.garbage_days')|int }}" +``` + +```yaml +# garbage-collection-card configuration +entity: sensor.garbage +type: 'custom:garbage-collection-card' +``` +

+
+ +
+How can I sort waste type specific entities? +

+ +Prerequisites: You already have dedicated sensors per waste type and want to show the sensor with the next collection in a Lovelace card. + +Add `add_days_to: True` to the configuration of all sensors you want to sort. This will add the attribute `daysTo` which can be used by e.g. [auto-entities](https://github.com/thomasloven/lovelace-auto-entities) to sort entities by day of next collection. +

+
+ diff --git a/doc/installation.md b/doc/installation.md new file mode 100644 index 00000000..669957ba --- /dev/null +++ b/doc/installation.md @@ -0,0 +1,141 @@ +Waste Collection Schedule logo + +# Installation + +## Automated Installation Using HACS +![hacs_badge](https://img.shields.io/badge/HACS-Default-orange) +![hacs installs](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Flauwbier.nl%2Fhacs%2Fwaste_collection_schedule) + +The `Waste Collection Schedule` component can be installed via [HACS](https://hacs.xyz/). This allows you to be notified of any updates or new releases of the component. + +After installing HACS: +1. Visit the HACS `Integrations` panel in Home Assistant. +2. Click `Explore & Download Repositories`. +3. Search for `Waste Collection Schedule`. +4. Click on the `Waste Collection Schedule` entry. +5. Click on `Download` to copy the relevant files to your `config/custom_components/` directory. +6. [Configure your waste collection source(s)](#configuring-waste-collection-schedules). +7. [Configure your waste collection sensor(s)](#configuring-waste-collection-schedules). +8. Restart Home Assistant. + +## Manual Installation + +1. Navigate to the [waste_collection_schedule](https://github.com/mampfes/hacs_waste_collection_schedule/tree/master/custom_components) directory. +2. Copy the `waste_collection_schedule` folder (including all files and subdirectories) to your Home Assistant `config/custom_components/` directory. +3. [Configure your waste collection source(s)](#configuring-waste-collection-schedules). +4. [Configure your waste collection sensor(s)](#configuring-waste-collection-schedules). +5. Restart Home Assistant. + +# Configuring Waste Collection Schedules + +To use Waste Collection Schedules, additional entries need to be made in your `configuration.yaml` file. The required entries are: + +1. Configuring source(s) + + For each service provider, a source has to be added to the configuration. The source takes care of the arguments required to get the correct information from the service provider's web page, e.g. district, city, street, house number, etc. + + If you have to fetch data from multiple service providers, you have to add multiple sources. You can also add the same service provider multiple times. This only makes sense if you use it with different arguments, e.g. you are looking to display waste collection schedules for multiple districts served by the same provider. + +2. Configuring sensor(s) + + Sensors are used to visualize the retrieved information, e.g. waste type, next collection date, or number of days to next collection. The sensor state (which can be shown in a Lovelace/Mushroom cards) can be customized using templates. For example, you can display the collection type only, or the next collection date, or a combination of all available information. + + You can also add multiple sensors per source if you are going to display the information in separate entities. For example, if you want each waste type to have its own entity, you can add one sensor per collection type. + +## Configuring Source(s) + +```yaml +waste_collection_schedule: + sources: + - name: SOURCE + args: + arg1: ARG1 + arg2: ARG2 + arg3: ARG3 + customize: + - type: TYPE + alias: ALIAS + show: SHOW + icon: ICON + picture: PICTURE + use_dedicated_calendar: USE_DEDICATED_CALENDAR + dedicated_calendar_title: DEDICATED_CALENDAR_TITLE + calendar_title: CALENDAR_TITLE + fetch_time: FETCH_TIME + random_fetch_time_offset: RANDOM_FETCH_TIME_OFFSET + day_switch_time: DAY_SWITCH_TIME + separator: SEPARATOR +``` + + +| Parameter | Type | Requirement | Description | +|-----|-----|-----|-----| +| sources: | | required | Contains information for the service provider being used | +| name: | string | required | name of the service provider source to use. Should be the same as the source filename, but without the `.py` extension. See the [README](/README.md#supported-service-providers) for supported service providers | +| args: | various | required | source-specific arguments provided to service provider to unambiguously identify the collection schedule to return. Depending on the service provider, some arguments may be mandatory, and some may be optional. See individual sources for more details | +| customize: | | optional | Can be used to customise data retrieved from a source | +| type: | string | required | The identity of the waste type as returned from the source | +| alias: | string | optional | A more readable, or user-friendly, name for the type of waste being collected. Default is `None` | +| show: | boolean | optional | Show (`True`) or hide (`False`) collections of this specific waste type. Default is `True` | +| icon: | string | optional | Icon to use for this specific waste type. Icons from the Home Assistant mdi icon set can be used. Default is `None`. | +| picture: | string | optional | string representation of the path to a picture used to represent this specific waste type. Default is `None` | +| use_dedicated_calendar: | boolean | optional | Creates a calendar dedicated to this specific waste type. Default is `False` | +| dedicated_calendar_title: | string | optional | A more readable, or user-friendly, name for this specific waste calendar object. If nothing is provided, the name returned by the source will be used | +| fetch_time: | time | optional | representation of the time of day in "HH:MM" that Home Assistant polls service provider for latest collection schedule. If no time is provided, the default of "01:00" is used | +| random_fetch_time_offset: | int | optional | randomly offsets the `fetch_time` by up to _int_ minutes. Can be used to distribute Home Assistant fetch commands over a longer time frame to avoid peak loads at service providers | +| day_switch_time: | time | optional | time of the day in "HH:MM" that Home Assistant dismisses the current entry and moves to the next entry. If no time if provided, the default of "10:00" is used. | +| separator: | string | optional | Used to join entries if the multiple values for a single day are returned by the source. If no value is entered, the default of ", " is used | + +## Configuring source sensor(s) + +Add the following lines to your `configuration.yaml` file: + +```yaml +sensor: + - platform: waste_collection_schedule + source_index: SOURCE_INDEX + name: NAME + details_format: DETAILS_FORMAT + count: COUNT + leadtime: LEADTIME + value_template: VALUE_TEMPLATE + date_template: DATE_TEMPLATE + add_days_to: ADD_DAYS_TO + types: + - Waste Type 1 + - Waste Type 2 +``` +| Parameter | Type | Requirement | Description | +|--|--|--|--| +| platform | | required | waste_collection_schedule | +| source_index | int | optional | Used to assign a sensor to a specific source. Only needed if multiple sources are defined. The first source defined is source_index 0, the second source_index 1, etc. If no value is supplied, the default of 0 is used | +| name | string | required | The name Home Assistant used for this sensor | +| details_format | string | optional | Specifies the format used to display info in Home Assistant's _more info_ pop-up. Valid values are: `"upcoming"`, `"appointment_types"` and `"generic"`. If no value is supplied, the default of "upcoming" is used. See [options for details_format](#options-for-details_format-parameter) for more details | +| count | int | optional | Limits Home Assistant's _more info_ popup to displaying the next _int_ collections | +| leadtime | int | optional | Limits Home Assistant's _more info_ popup to only displaying collections happening within the next _leadtime_ days| +| value_template | string | optional | Uses Home Assistant templating to format the state information of an entity. See [template variables](#template-variables-for-value_template-and-date_template-parameters) for further details | +| date_template | string | optional | Uses Home Assistant templating to format the dates appearing within the _more info_ popup information of an entity. See [template variables](#template-variables-for-value_template-and-date_template-parameters) for further details | +| add_days_to | boolean | optional | Adds a `daysTo` attribute to the source entity state containing the number of days to the next collection | +| types | list of strings | optional | Used to filter waste types. The sensor will only display collections matching these waste types | + +## Options for _details_format_ parameter ## +Possible choices: +| upcoming | appointment_types | generic | +|--|--|--| +| shows a list of upcoming collections |shows a list of waste types and their next collection date | provides all attributes as generic Python data types. | +| ![Upcoming](/images/more-info-upcoming.png) | ![Waste Types](/images/more-info-appointment-types.png) | ![Generic](/images/more-info-generic.png) | + +## Template variables for _value_template_ and _date_template_ parameters + +The following variables can be used within `value_template` and `date_template`: + +| Variable | Description | Type |Comments | +|--|--|--|--| +| `value.date` | Collection date | [datetime.date](https://docs.python.org/3/library/datetime.html#datetime.date) | Use [strftime](https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior) to format the output | +| `value.daysTo` | Days to collection | int | 0 = today, 1 = tomorrow, etc | +| `value.types` | Waste types | list of strings | Use `join` filter to join types | + +## Further help ## +For a full example, see [custom_components/waste_collection_schedule/waste_collection_schedule/source/example.py](/custom_components/waste_collection_schedule/waste_collection_schedule/source/example.py). + +For other examples on how to configure source(s) and sensor(s), see the [FAQ](/doc/faq.md). diff --git a/doc/licence.md b/doc/licence.md new file mode 100644 index 00000000..5e5cb598 --- /dev/null +++ b/doc/licence.md @@ -0,0 +1,23 @@ +Waste Collection Schedule logo + +# MIT License + +Copyright (c) 2020 Steffen Zimmermann + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/doc/online.md b/doc/online.md new file mode 100644 index 00000000..0388c37a --- /dev/null +++ b/doc/online.md @@ -0,0 +1,24 @@ + + + +Waste Collection Schedule logo + + +# Waste Collection Schedule Online + + +## Community Forum +![Community Discussion](https://img.shields.io/badge/Home%20Assistant%20Community-Discussion-orange) + +The main discussion thread on Home Assistant's Community forum can be found [here](https://community.home-assistant.io/t/waste-collection-schedule-framework/186492). + + +## YouTube +There are some videos on YouTube: + +- [Bunte Mülltonnenerinnerung mit Home Assistant](https://youtu.be/MzQgARDvRww) (German language) +- [Abfall Kalender in Home Assistant mit Erinnerung in Home Assistant](https://youtu.be/aCKLKGYiA7w) (German language) + +Note: These videos were **not** created by the developer of this component and therefore may be outdated. If you have questions about this component, please create an issue here on GitHub. Do not ask your question in the YouTube comments because any answers may not be correct. diff --git a/doc/page_template.md b/doc/page_template.md new file mode 100644 index 00000000..7c79200b --- /dev/null +++ b/doc/page_template.md @@ -0,0 +1,24 @@ + + + +Waste Collection Schedule logo + + +# PAGE TITLE GOES HERE + + +Asperiores et ratione similique sapiente impedit fuga. Ullam nihil cupiditate ut aliquam non dolorem qui. Rerum ipsa esse amet ipsam omnis et aut laboriosam. Quo dolorem veniam eius doloribus sint aut. Minus placeat quis corporis. Ipsam soluta quidem reprehenderit aspernatur sit sed. + +## SOME SUB-TITLE +Repudiandae sequi sint tenetur quia perferendis quod amet nulla. Eum sed id quam nobis modi aut nulla libero. Veniam accusantium quia asperiores expedita. + +### A SUB-SUB-TITLE +Officiis iure et et et. Recusandae quia hic est in in exercitationem est praesentium. Alias dolores adipisci eum. + +### ANOTHER SUB-SUB-TITLE +Velit unde voluptatem quo nisi voluptatum doloribus beatae. Perferendis voluptatem eos ut et quidem culpa. Accusantium consequatur necessitatibus non. + +## AND ANOTHER SUB-TITLE +Vitae animi tempora voluptatem doloribus est voluptate qui. Nihil quia numquam voluptates. Aut saepe quia doloribus. Quidem ea non nihil sit cum. Voluptates aperiam quod molestiae non et velit dolorem eos. \ No newline at end of file From 58f85ab386f35bf484e6aaa9bc1c2f6bd6c690cf Mon Sep 17 00:00:00 2001 From: dt215git Date: Tue, 27 Dec 2022 09:17:26 +0000 Subject: [PATCH 051/127] readme updated with latest sources --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b6f5e2d2..b52d6069 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ Waste collection schedules in the following formats and countries are supported. - [Landkreis Schwäbisch Hall](/doc/source/lrasha_de.md) - [Muellmax.de](/doc/source/muellmax_de.md) - [MyMuell App](/doc/source/jumomind_de.md) +- [Neunkirchen Siegerland](/doc/source/abfall_neunkirchen_siegerland_de.md) - [RegioEntsorgung](/doc/source/regioentsorgung_de.md) - [Rhein-Hunsrück Entsorgung (RHE)](/doc/source/rh_entsorgung_de.md) - [Sector27.de](/doc/source/sector27_de.md) @@ -122,6 +123,7 @@ Waste collection schedules in the following formats and countries are supported. - [Stadtreinigung-Leipzig.de](/doc/source/stadtreinigung_leipzig_de.md) - [Stadt-Willich.de](/doc/source/stadt_willich_de.md) - [StadtService Brühl](/doc/source/stadtservice_bruehl_de.md) +- [Städteservice Raunheim Rüsselsheim](/doc/source/staedteservice_de.md) - [Südbrandenburgischer Abfallzweckverband](/doc/source/sbazv_de.md) - [Umweltbetrieb Stadt Bielefeld](/doc/source/bielefeld_de.md) - [WAS Wolfsburg](/doc/source/was_wolfsburg_de.md) @@ -186,6 +188,7 @@ Waste collection schedules in the following formats and countries are supported. - [Lerum.se](/doc/source/lerum_se.md) - [Ronneby Miljöteknik](/doc/source/miljoteknik_se.md) - [SSAM.se](/doc/source/ssam_se.md) +- [srvatervinning.se](/doc/source/srvatervinning_se.md) - [Sysav.se](/doc/source/sysav_se.md) - [Vasyd.se](/doc/source/vasyd_se.md)

@@ -243,7 +246,7 @@ Waste collection schedules in the following formats and countries are supported. - [South Cambridgeshire District Council - scambs.gov.uk](/doc/source/scambs_gov_uk.md) - [South Norfolk and Broadland Council - southnorfolkandbroadland.gov.uk](/doc/source/south_norfolk_and_broadland_gov_uk.md) - [Stevenage Borough Council - stevenage.gov.uk](/doc/source/stevenage_gov_uk.md) -- [Tewkwsbury Borough Council - tewkesbury_gov_uk](/doc/source/tewkesbury_gov_uk.md) +- [Tewkwsbury Borough Council - tewkesbury.gov.uk](/doc/source/tewkesbury_gov_uk.md) - [City of York Council - york.gov.uk](/doc/source/york_gov_uk.md) - [Walsall Council - walsall.gov.uk](/doc/source/walsall_gov_uk.md) - [West Berkshire Council - westberks.gov.uk](/doc/source/westberks_gov_uk.md) @@ -292,9 +295,7 @@ The following waste service providers return errors when running the test_source - [ ] abfall_io, Traunstein test case fails - [ ] sector27_de, TimeoutError - [ ] banyule_vic_gov_au, JSONDecodeError -- [ ] muenchenstein_ch, AttributeError - [ ] grafikai_svara_lt, TimeoutError -- [ ] avl_ludwigsburg_de, HTTPError - [ ] warszawal9115_pl, HTTPError If you can fix any of these, please raise a Pull Request with the updates. From 272973dcf9ce9979738525ac5e810f11a60a280a Mon Sep 17 00:00:00 2001 From: dt215git Date: Tue, 27 Dec 2022 09:25:14 +0000 Subject: [PATCH 052/127] info.md updated with latest changes --- info.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/info.md b/info.md index 1c342e43..ec381e4b 100644 --- a/info.md +++ b/info.md @@ -21,13 +21,13 @@ Waste collection schedules from service provider web sites are updated daily, de | Austria | BMV, Data.Umweltprois, Korneuburg Stadtservice, WSZ-Moosburg | | Belgium | Hyhea, Recycle! / RecycleApp | | Canada | City of Toronto | -| Germany | Abfall.IO / AbfallPlus, AbfallNavi (RegioIT.de), Abfallkalender Würzburg, Abfalltermine Forchheim, Abfallwirtschaft Bremen, Abfallwirtschaft Landkreis Harburg, Abfallwirtschaft Landkreis Wolfenbüttel, Abfallwirtschaft Neckar-Odenwald-Kreis, Abfallwirtschaft Rendsburg, Abfallwirtschaft Stuttgart, Abfallwirtschaft Südholstein, Abfallwirtschaft Zollernalbkreis, Alb-Donau-Kreis, ART Trier, AWB Bad Kreuznach, AWB Esslingen, AWB Limburg-Weilburg, AWB Oldenburg, AWBKoeln, AWIDO-online, Berlin-Recycling, Bogenschuetz-Entsorgung, BSR.de / Berliner Stadtreinigungsbetriebe, C-Trace, Cochem-Zell, EGN-Abfallkalender, Erlangen-Höchstadt, Jumomind, KAEV Niederlausitz, KWB-Goslar, KWU-Entsorgung, Landkreis-Wittmund, Landkreis Rhön Grabfeld, Landkreis Schwäbisch Hall, Muellmax, MyMuell App, RegioEntsorgung, Rhein-Hunsrück Entsorgung (RHE), Sector27, Stadtreinigung Dresden, Stadtreinigung.Hamburg, Stadtreinigung-Leipzig, Stadt-Willich, StadtService Brühl, Südbrandenburgischer Abfallzweckverband, Umweltbetrieb Stadt Bielefeld, WAS Wolfsburg, Wermeldkirchen, Zweckverband Abfallwirtschaft Werra-Meißner-Kreis | +| Germany | Abfall.IO / AbfallPlus, AbfallNavi (RegioIT.de), Abfallkalender Würzburg, Abfalltermine Forchheim, Abfallwirtschaft Bremen, Abfallwirtschaft Landkreis Harburg, Abfallwirtschaft Landkreis Wolfenbüttel, Abfallwirtschaft Neckar-Odenwald-Kreis, Abfallwirtschaft Rendsburg, Abfallwirtschaft Stuttgart, Abfallwirtschaft Südholstein, Abfallwirtschaft Zollernalbkreis, Alb-Donau-Kreis, ART Trier, AWB Bad Kreuznach, AWB Esslingen, AWB Landkreis Ausburg, AWB Limburg-Weilburg, AWB Oldenburg, AWBKoeln, AWIDO-online, Berlin-Recycling, Bogenschuetz-Entsorgung, Biedenkopf MZF, BSR.de / Berliner Stadtreinigungsbetriebe, C-Trace, Cochem-Zell, EGN-Abfallkalender, Erlangen-Höchstadt, Jumomind, KAEV Niederlausitz, KWB-Goslar, KWU-Entsorgung, Landkreis-Wittmund, Landkreis Rhön Grabfeld, Landkreis Schwäbisch Hall, Muellmax, MyMuell App, Neunkirchen Siegerland, RegioEntsorgung, Rhein-Hunsrück Entsorgung (RHE), Sector27, Stadtreinigung Dresden, Stadtreinigung.Hamburg, Stadtreinigung-Leipzig, Stadt-Willich, StadtService Brühl, Städteservice Raunheim Rüsselsheim, Südbrandenburgischer Abfallzweckverband, Umweltbetrieb Stadt Bielefeld, WAS Wolfsburg, Wermeldkirchen, Zweckverband Abfallwirtschaft Werra-Meißner-Kreis | | Lituania | Kauno švara | | Netherlands | HVCGroep, Ximmio | | New Zealand | Auckland, Christchurch, Gore, Invercargill & Shouthand, Horowhenua District, Wapia District, Wellington | | Norway | Min Renovasjon, Oslo Kommune | | Poland | Warsaw, Multiple Communities (Echoharmonogram) | -| Sweden | Lerum, Ronneby Miljöteknik, SSAM, Sysav, Vasyd | +| Sweden | Lerum, Ronneby Miljöteknik, SSAM, Srvatervinning, Sysav, Vasyd | | Switzerland | A-region, Lindau | | USA | PGT.ST, Republic Services, Seattle Public Utilities | | UK | Bracknell Forest Council, Bradford Metropolitan District Council, Braintree District Council, Cambridge City Council, Canterbury City Council, Cheshire East Council, Chesterfield Borough Council, Colchester Borough Council, Cornwall Council, Derby City Council, Eastbourne Borough Council, Elmbridge Borough Council, Guildford Borough Council, Harborough District Council, Huntingdonshire District Council, The Royal Borough of Kingston, Lewes District Council, London Borough of Lewisham, Manchester City Council, Newcastle City Council, North Somerset Council, Nottingham City Council, Peterborough City Council, Richmondshire District Council, Rushmoor Borough Council, Sheffield City Council, South Cambridgeshire District Council, South Norfolk and Broadland Council, Stevenage Borough Council, Tewkesbury Borough Council, City of York Council, Walsall Council, West Berkshire Council, Wiltshire Council | From 897c6e2fc6d8adc0ea4f597a8bc80b189923576e Mon Sep 17 00:00:00 2001 From: dt215git Date: Tue, 27 Dec 2022 09:32:27 +0000 Subject: [PATCH 053/127] faq link added to readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b52d6069..75a58399 100644 --- a/README.md +++ b/README.md @@ -258,7 +258,7 @@ Waste collection schedules in the following formats and countries are supported. ![hacs badge](https://img.shields.io/badge/HACS-Default-orange) ![hacs installs](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Flauwbier.nl%2Fhacs%2Fwaste_collection_schedule) -The Waste Collection Schedule can be installed via [HACS](https://hacs.xyz/), or by manually copying the [`waste_collection_schedule`](https://github.com/mampfes/hacs_waste_collection_schedule/tree/master/custom_components) directory to Home Assistant's `config/custom_components/` directory. For further details see the [installation and configuration](/doc/installation.md) page. +The Waste Collection Schedule can be installed via [HACS](https://hacs.xyz/), or by manually copying the [`waste_collection_schedule`](https://github.com/mampfes/hacs_waste_collection_schedule/tree/master/custom_components) directory to Home Assistant's `config/custom_components/` directory. For further details see the [installation and configuration](/doc/installation.md) page, or the [FAQ](/doc/faq.md). # Contributing To The Project ![python badge](https://img.shields.io/badge/Made%20with-Python-orange) From ff790d64172bf20e37940c22a8d391c5f2cd9925 Mon Sep 17 00:00:00 2001 From: dt215git Date: Tue, 27 Dec 2022 09:39:56 +0000 Subject: [PATCH 054/127] braintree/bracknell typos corrected --- README.md | 2 +- doc/source/braintree_gov_uk.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 75a58399..9b81fb81 100644 --- a/README.md +++ b/README.md @@ -219,7 +219,7 @@ Waste collection schedules in the following formats and countries are supported. - [Bracknell Forest Council - bracknell-forest.gov.uk](/doc/source/bracknell_forest_gov_uk.md) - [Bradford Metropolitan District Council - bradford.gov.uk](/doc/source/bradford_gov_uk.md) -- [Braintree District Council - bracknell-forest.gov.uk](/doc/source/bracknell_forest_gov_uk.md) +- [Braintree District Council - braintree.gov.uk](/doc/source/braintree_gov_uk.md) - [Cambridge City Council - cambridge.gov.uk](/doc/source/cambridge_gov_uk.md) - [Canterbury City Council - canterbury.gov.uk](/doc/source/canterbury_gov_uk.md) - [Cheshire East Council - cheshireeast.gov.uk](/doc/source/cheshire_east_gov_uk.md) diff --git a/doc/source/braintree_gov_uk.md b/doc/source/braintree_gov_uk.md index 7854e6de..8c9c8d23 100644 --- a/doc/source/braintree_gov_uk.md +++ b/doc/source/braintree_gov_uk.md @@ -1,4 +1,4 @@ -# Bracknell Forest Council +# Braintree District Council Support for schedules provided by [Braintree District Council](https://www.braintree.gov.uk/xfp/form/554), serving Braintree, UK. From 1db7cf068fe616b5186a9c262e27925da54aa3eb Mon Sep 17 00:00:00 2001 From: Finn Freitag Date: Tue, 27 Dec 2022 15:06:37 +0100 Subject: [PATCH 055/127] Add support for next year's collections in December --- .../source/geoport_nwm_de.py | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/geoport_nwm_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/geoport_nwm_de.py index 6c29a02b..77242fdf 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/geoport_nwm_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/geoport_nwm_de.py @@ -22,18 +22,33 @@ class Source: self._ics = ICS() def fetch(self): - arg = convert_to_arg(self._district) today = datetime.date.today() - year = today.year - r = requests.get( - f"https://www.geoport-nwm.de/nwm-download/Abfuhrtermine/ICS/{year}/{arg}.ics") - dates = self._ics.convert(r.text) + dates = [] + if today.month == 12: + # On Dec 27 2022, the 2022 schedule was no longer available for test case "Seefeld", all others worked + try: + dates = self.fetch_year(today.year) + except Exception: + pass + try: + dates.extend(self.fetch_year(today.year + 1)) + except Exception: + pass + else: + dates = self.fetch_year(today.year) entries = [] for d in dates: entries.append(Collection(d[0], d[1])) return entries + def fetch_year(self, year): + arg = convert_to_arg(self._district) + r = requests.get( + f"https://www.geoport-nwm.de/nwm-download/Abfuhrtermine/ICS/{year}/{arg}.ics") + r.raise_for_status() + return self._ics.convert(r.text) + def convert_to_arg(district): district = district.replace("(1.100 l Behälter)", "1100_l") From 9c350639a517e049c8a33aa887d6771ab6d49da6 Mon Sep 17 00:00:00 2001 From: Chris Pressland Date: Tue, 27 Dec 2022 17:13:42 +0000 Subject: [PATCH 056/127] Refactor fccenvironment_co_uk --- README.md | 2 + .../source/fccenvironment_co_uk.py | 155 ++++++++++++------ doc/source/fccenvironment_co_uk.md | 26 ++- info.md | 2 +- 4 files changed, 132 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 9b81fb81..749db9ee 100644 --- a/README.md +++ b/README.md @@ -244,12 +244,14 @@ Waste collection schedules in the following formats and countries are supported. - [Rushmoor Borough Council - rushmoor.gov.uk](/doc/source/rushmoor_gov_uk.md) - [Sheffield City Council - sheffield.gov.uk](/doc/source/sheffield_gov_uk.md) - [South Cambridgeshire District Council - scambs.gov.uk](/doc/source/scambs_gov_uk.md) +- [South Hams - southhams.gov.uk](https://southhams.gov.uk/) - [South Norfolk and Broadland Council - southnorfolkandbroadland.gov.uk](/doc/source/south_norfolk_and_broadland_gov_uk.md) - [Stevenage Borough Council - stevenage.gov.uk](/doc/source/stevenage_gov_uk.md) - [Tewkwsbury Borough Council - tewkesbury.gov.uk](/doc/source/tewkesbury_gov_uk.md) - [City of York Council - york.gov.uk](/doc/source/york_gov_uk.md) - [Walsall Council - walsall.gov.uk](/doc/source/walsall_gov_uk.md) - [West Berkshire Council - westberks.gov.uk](/doc/source/westberks_gov_uk.md) +- [West Devon - westdevon.gov.uk](https://www.westdevon.gov.uk/) - [Wiltshire Council - wiltshire.gov.uk](/doc/source/wiltshire_gov_uk.md)

diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/fccenvironment_co_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/fccenvironment_co_uk.py index 3558a20b..d475a956 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/fccenvironment_co_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/fccenvironment_co_uk.py @@ -1,74 +1,127 @@ -import logging -import requests +from urllib.parse import urlparse +import requests from bs4 import BeautifulSoup -from datetime import datetime +from dateutil import parser from waste_collection_schedule import Collection TITLE = "fccenvironment.co.uk" -DESCRIPTION = ( - """Consolidated source for waste collection services for ~60 local authorities. - Currently supports: - Market Harborough - """ -) +DESCRIPTION = """ + Consolidated source for waste collection services for ~60 local authorities. + Currently supports: + West Devon (Generic Provider) + South Hams (Generic Provider) + Market Harborough (Custom Provider) + """ URL = "https://fccenvironment.co.uk" TEST_CASES = { - "Test_001" : {"uprn": "100030491624"}, - "Test_002": {"uprn": "100030491614"}, - "Test_003": {"uprn": "100030493289"}, - "Test_004": {"uprn": "200001136341"} + "14_LE16_9QX": {"uprn": "100030491624"}, # region ommited to test default values + "4_LE16_9QX": {"uprn": "100030491614", "region": "harborough"}, + "16_LE16_7NA": {"uprn": "100030493289", "region": "harborough"}, + "10_LE16_8ER": {"uprn": "200001136341", "region": "harborough"}, + "9_PL20_7SH": {"uprn": "10001326315", "region": "westdevon"}, + "3_PL20_7RY": {"uprn": "10001326041", "region": "westdevon"}, + "2_PL21_9BN": {"uprn": "100040279446", "region": "southhams"}, + "4_SL21_0HZ": {"uprn": "100040281987", "region": "southhams"}, } - ICONS = { - "NON-RECYCLABLE WASTE BIN COLLECTION": "mdi:trash-can", - "RECYCLING COLLECTION": "mdi:recycle", - "GARDEN WASTE COLLECTION": "mdi:leaf", + "Refuse": "mdi:trash-can", + "Recycling": "mdi:recycle", + "Garden": "mdi:leaf", } -_LOGGER = logging.getLogger(__name__) - - class Source: - def __init__(self, uprn=None): - self._uprn = uprn + def __init__(self, uprn: str, region: str = "harborough") -> None: + self.uprn = uprn + self.region = region - def fetch(self): + def getcollectiondetails(self, endpoint: str) -> list[Collection]: + domain = urlparse(endpoint).netloc + session = requests.Session() + cookies = session.get(f"https://{domain}/") + response = session.post( + endpoint, + headers={ + "x-requested-with": "XMLHttpRequest", + }, + data={ + "fcc_session_token": cookies.cookies["fcc_session_cookie"], + "uprn": self.uprn, + }, + ) + results = {} + for item in response.json()["binCollections"]["tile"]: + try: + soup = BeautifulSoup(item[0], "html.parser") + date = parser.parse(soup.find_all("b")[2].text.split(",")[1].strip()).date() + service = soup.text.split("\n")[0] + except parser._parser.ParserError: + continue - s = requests.Session() - - if self._uprn: - # POST request returns schedule for matching uprn - payload = { - "Uprn": self._uprn - } - r = s.post("https://www.fccenvironment.co.uk/harborough/detail-address", data = payload) - responseContent = r.text + """ + Handle duplication before creating the list of Collections + """ + for type in ICONS: + if type in service: + if type in results.keys(): + if date < results[type]: + results[type] = date + else: + results[type] = date entries = [] - # Extract waste types and dates from responseContent - soup = BeautifulSoup(responseContent, "html.parser") - services = soup.find("div", attrs={"class": "blocks block-your-next-scheduled-bin-collection-days"}) - items = services.find_all("li") - for item in items: - date_text = item.find("span", attrs={"class": "pull-right"}).text.strip() - try: - date = datetime.strptime(date_text, "%d %B %Y").date() - except ValueError: - continue - else: - waste_type = item.text.split(' (')[0] - entries.append( - Collection( - date=date, - t=waste_type, - icon=ICONS.get(waste_type.upper()), - ) + for result in results: + entries.append( + Collection( + date=results[result], + t=result, + icon=ICONS[result], ) - + ) return entries + + def harborough(self) -> list[Collection]: + _icons = { + "NON-RECYCLABLE WASTE BIN COLLECTION": "mdi:trash-can", + "RECYCLING COLLECTION": "mdi:recycle", + "GARDEN WASTE COLLECTION": "mdi:leaf", + } # Custom icons to avoid a breaking change + r = requests.post("https://www.fccenvironment.co.uk/harborough/detail-address", data={"Uprn": self.uprn}) + soup = BeautifulSoup(r.text, "html.parser") + services = soup.find("div", attrs={"class": "blocks block-your-next-scheduled-bin-collection-days"}).find_all( + "li" + ) + entries = [] + for service in services: + for type in _icons: + if type.lower() in service.text.lower(): + try: + date = parser.parse(service.find("span", attrs={"class": "pull-right"}).text.strip()).date() + except parser._parser.ParserError: + continue + + entries.append( + Collection( + date=date, + t=type, + icon=_icons[type.upper()], + ) + ) + return entries + + def fetch(self) -> list[Collection]: + if self.region == "harborough": + return self.harborough() + elif self.region == "westdevon": + return self.getcollectiondetails( + endpoint="https://westdevon.fccenvironment.co.uk/ajaxprocessor/getcollectiondetails" + ) + elif self.region == "southhams": + return self.getcollectiondetails( + endpoint="https://waste.southhams.gov.uk/mycollections/getcollectiondetails" + ) diff --git a/doc/source/fccenvironment_co_uk.md b/doc/source/fccenvironment_co_uk.md index c41ac3f6..04d67c9c 100644 --- a/doc/source/fccenvironment_co_uk.md +++ b/doc/source/fccenvironment_co_uk.md @@ -1,6 +1,9 @@ # FCC Environment -Consolidated support for schedules provided by ~60 local authorities. Currently supports [Harborough District Council](www.harborough.gov.uk) +Consolidated support for schedules provided by ~60 local authorities. Currently supports: + - [Harborough District Council](https://www.harborough.gov.uk/) + - [South Hams](https://southhams.gov.uk/) + - [West Devon](https://www.westdevon.gov.uk/) ## Configuration via configuration.yaml @@ -10,6 +13,7 @@ waste_collection_schedule: - name: fccenvironment_co_uk args: uprn: UNIQUE_PROPERTY_REFERENCE_NUMBER + region: REGION_NAME ``` ### Configuration Variables @@ -19,6 +23,15 @@ waste_collection_schedule: This is required to unambiguously identify the property. +**region**
+*(string) (optional)* + +Defaults to `harborough`, should be one of: + - `harborough` + - `westdevon` + - `southhams` + + ## Example using UPRN ```yaml @@ -29,6 +42,17 @@ waste_collection_schedule: uprn: 100030493289 ``` +## Example using UPRN and Region + +```yaml +waste_collection_schedule: + sources: + - name: fccenvironment_co_uk + args: + uprn: 10001326041 + region: westdevon +``` + ## How to find your `UPRN` An easy way to find your Unique Property Reference Number (UPRN) is by going to and entering in your address details. diff --git a/info.md b/info.md index ec381e4b..14050e70 100644 --- a/info.md +++ b/info.md @@ -30,6 +30,6 @@ Waste collection schedules from service provider web sites are updated daily, de | Sweden | Lerum, Ronneby Miljöteknik, SSAM, Srvatervinning, Sysav, Vasyd | | Switzerland | A-region, Lindau | | USA | PGT.ST, Republic Services, Seattle Public Utilities | -| UK | Bracknell Forest Council, Bradford Metropolitan District Council, Braintree District Council, Cambridge City Council, Canterbury City Council, Cheshire East Council, Chesterfield Borough Council, Colchester Borough Council, Cornwall Council, Derby City Council, Eastbourne Borough Council, Elmbridge Borough Council, Guildford Borough Council, Harborough District Council, Huntingdonshire District Council, The Royal Borough of Kingston, Lewes District Council, London Borough of Lewisham, Manchester City Council, Newcastle City Council, North Somerset Council, Nottingham City Council, Peterborough City Council, Richmondshire District Council, Rushmoor Borough Council, Sheffield City Council, South Cambridgeshire District Council, South Norfolk and Broadland Council, Stevenage Borough Council, Tewkesbury Borough Council, City of York Council, Walsall Council, West Berkshire Council, Wiltshire Council | +| UK | Bracknell Forest Council, Bradford Metropolitan District Council, Braintree District Council, Cambridge City Council, Canterbury City Council, Cheshire East Council, Chesterfield Borough Council, Colchester Borough Council, Cornwall Council, Derby City Council, Eastbourne Borough Council, Elmbridge Borough Council, Guildford Borough Council, Harborough District Council, Huntingdonshire District Council, The Royal Borough of Kingston, Lewes District Council, London Borough of Lewisham, Manchester City Council, Newcastle City Council, North Somerset Council, Nottingham City Council, Peterborough City Council, Richmondshire District Council, Rushmoor Borough Council, Sheffield City Council, South Cambridgeshire District Council, South Hams, South Norfolk and Broadland Council, Stevenage Borough Council, Tewkesbury Borough Council, City of York Council, Walsall Council, West Berkshire Council, West Devon, Wiltshire Council | For full details on supported service providers, and more project details, visit us on [GitHub](https://github.com/mampfes/hacs_waste_collection_schedule) From 13e74b0182b3c6982441757449dcec1ac4f355a3 Mon Sep 17 00:00:00 2001 From: dt215git Date: Tue, 27 Dec 2022 20:54:20 +0000 Subject: [PATCH 057/127] image urls updated --- info.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/info.md b/info.md index 14050e70..f9e3fc8f 100644 --- a/info.md +++ b/info.md @@ -1,13 +1,11 @@ -Waste Collection Schedule logo +Waste Collection Schedule logo # Waste Collection Schedule -![hacs_badge](https://img.shields.io/badge/HACS-Default-orange) -![hacs installs](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Flauwbier.nl%2Fhacs%2Fwaste_collection_schedule) -![Community Discussion](https://img.shields.io/badge/Home%20Assistant%20Community-Discussion-orange) +![hacs_badge](https://img.shields.io/badge/HACS-Default-orange) ![hacs installs](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Flauwbier.nl%2Fhacs%2Fwaste_collection_schedule) [![Community Discussion](https://img.shields.io/badge/Home%20Assistant%20Community-Discussion-orange)](https://community.home-assistant.io/t/waste-collection-schedule-framework/186492). **A custom component for Home Assistant that retrieves waste collection schedules from a wide range of service providers.** -Waste Collection Schedule animation +Waste Collection Schedule animation Waste collection schedules from service provider web sites are updated daily, derived from local ICS/iCal files, or generated from user-specified dates or regularly repeating date patterns. The Home Assistant built-in Calendar is automatically populated with schedules, and there is a high degree of flexibility in how information can be format and displayed in entity cards or pop-ups. The framework can easily be extended to support additional waste collection service providers, or other services which provide schedules. From 0e469cd200507717cb1f638eec9cd36951e4bfb2 Mon Sep 17 00:00:00 2001 From: dt215git Date: Wed, 28 Dec 2022 10:21:30 +0000 Subject: [PATCH 058/127] image urls changed to raw.githubusercontent.com --- info.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/info.md b/info.md index f9e3fc8f..61e25dcf 100644 --- a/info.md +++ b/info.md @@ -1,11 +1,11 @@ -Waste Collection Schedule logo +Waste Collection Schedule logo # Waste Collection Schedule -![hacs_badge](https://img.shields.io/badge/HACS-Default-orange) ![hacs installs](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Flauwbier.nl%2Fhacs%2Fwaste_collection_schedule) [![Community Discussion](https://img.shields.io/badge/Home%20Assistant%20Community-Discussion-orange)](https://community.home-assistant.io/t/waste-collection-schedule-framework/186492). +![hacs_badge](https://img.shields.io/badge/HACS-Default-orange) ![hacs installs](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Flauwbier.nl%2Fhacs%2Fwaste_collection_schedule) [![Community Discussion](https://img.shields.io/badge/Home%20Assistant%20Community-Discussion-orange)](https://community.home-assistant.io/t/waste-collection-schedule-framework/186492) **A custom component for Home Assistant that retrieves waste collection schedules from a wide range of service providers.** -Waste Collection Schedule animation +Waste Collection Schedule animation Waste collection schedules from service provider web sites are updated daily, derived from local ICS/iCal files, or generated from user-specified dates or regularly repeating date patterns. The Home Assistant built-in Calendar is automatically populated with schedules, and there is a high degree of flexibility in how information can be format and displayed in entity cards or pop-ups. The framework can easily be extended to support additional waste collection service providers, or other services which provide schedules. @@ -30,4 +30,4 @@ Waste collection schedules from service provider web sites are updated daily, de | USA | PGT.ST, Republic Services, Seattle Public Utilities | | UK | Bracknell Forest Council, Bradford Metropolitan District Council, Braintree District Council, Cambridge City Council, Canterbury City Council, Cheshire East Council, Chesterfield Borough Council, Colchester Borough Council, Cornwall Council, Derby City Council, Eastbourne Borough Council, Elmbridge Borough Council, Guildford Borough Council, Harborough District Council, Huntingdonshire District Council, The Royal Borough of Kingston, Lewes District Council, London Borough of Lewisham, Manchester City Council, Newcastle City Council, North Somerset Council, Nottingham City Council, Peterborough City Council, Richmondshire District Council, Rushmoor Borough Council, Sheffield City Council, South Cambridgeshire District Council, South Hams, South Norfolk and Broadland Council, Stevenage Borough Council, Tewkesbury Borough Council, City of York Council, Walsall Council, West Berkshire Council, West Devon, Wiltshire Council | -For full details on supported service providers, and more project details, visit us on [GitHub](https://github.com/mampfes/hacs_waste_collection_schedule) +For full details on supported service providers, and more project details, visit us on [GitHub](https://github.com/mampfes/hacs_waste_collection_schedule). From c7db195fe76823ed2a2920fe4146b3534451c1b4 Mon Sep 17 00:00:00 2001 From: Benjamin <46243805+bbr111@users.noreply.github.com> Date: Wed, 28 Dec 2022 12:29:24 +0100 Subject: [PATCH 059/127] Add documantation for service --- doc/installation.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/installation.md b/doc/installation.md index 669957ba..e44134f5 100644 --- a/doc/installation.md +++ b/doc/installation.md @@ -135,6 +135,14 @@ The following variables can be used within `value_template` and `date_template`: | `value.daysTo` | Days to collection | int | 0 = today, 1 = tomorrow, etc | | `value.types` | Waste types | list of strings | Use `join` filter to join types | +## HomeAssistant Service to manually update the source + +If you want to manually update the source, you can call the service: + +`waste_collection_schedule.source_update` + +Normally the configuration parametet 'fetch_time' is used to do this periodically. + ## Further help ## For a full example, see [custom_components/waste_collection_schedule/waste_collection_schedule/source/example.py](/custom_components/waste_collection_schedule/waste_collection_schedule/source/example.py). From 390f506941a5cae763bc8ddc7d6414de689455a7 Mon Sep 17 00:00:00 2001 From: bbr111 Date: Wed, 28 Dec 2022 12:50:34 +0100 Subject: [PATCH 060/127] Rename Service to fetch_data --- custom_components/waste_collection_schedule/__init__.py | 6 +++--- custom_components/waste_collection_schedule/services.yaml | 6 +++--- doc/installation.md | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/custom_components/waste_collection_schedule/__init__.py b/custom_components/waste_collection_schedule/__init__.py index 95f9253f..42fc848a 100644 --- a/custom_components/waste_collection_schedule/__init__.py +++ b/custom_components/waste_collection_schedule/__init__.py @@ -124,11 +124,11 @@ async def async_setup(hass: HomeAssistant, config: dict): # initial fetch of all data hass.add_job(api._fetch) - def refresh_source(): + def fetch_data(): hass.add_job(api._fetch) - # Register new Service refresh_source - hass.services.async_register(DOMAIN, 'refresh_source', refresh_source) + # Register new Service fetch_data + hass.services.async_register(DOMAIN, 'fetch_data', fetch_data) return True diff --git a/custom_components/waste_collection_schedule/services.yaml b/custom_components/waste_collection_schedule/services.yaml index 4c05b93d..1a316482 100644 --- a/custom_components/waste_collection_schedule/services.yaml +++ b/custom_components/waste_collection_schedule/services.yaml @@ -1,3 +1,3 @@ -refresh_source: - name: refresh_source - description: refresh waste source \ No newline at end of file +fetch_data: + name: fetch_data + description: fetch current source data \ No newline at end of file diff --git a/doc/installation.md b/doc/installation.md index e44134f5..23e2edb9 100644 --- a/doc/installation.md +++ b/doc/installation.md @@ -139,7 +139,7 @@ The following variables can be used within `value_template` and `date_template`: If you want to manually update the source, you can call the service: -`waste_collection_schedule.source_update` +`waste_collection_schedule.fetch_data` Normally the configuration parametet 'fetch_time' is used to do this periodically. From 794e280de2e74d99e7545b4533d6f9cfd8415632 Mon Sep 17 00:00:00 2001 From: mampfes Date: Tue, 27 Dec 2022 17:07:01 +0100 Subject: [PATCH 061/127] prepare for automatic docu generation - check source titles - check source urls - add extra info for sources which serve multiple districts/municipalities --- .../source/abfall_io.py | 3 +- .../abfall_neunkirchen_siegerland_de.py | 4 +- .../source/abfall_zollernalbkreis_de.py | 23 +-- .../source/abfallnavi_de.py | 2 +- .../source/abfalltermine_forchheim_de.py | 2 +- .../source/alw_wf_de.py | 13 +- .../source/art_trier_de.py | 2 +- .../source/aucklandcouncil_govt_nz.py | 2 +- .../source/aw_harburg_de.py | 11 +- .../source/awb_oldenburg_de.py | 6 +- .../source/awido_de.py | 2 +- .../source/awn_de.py | 2 +- .../source/awr_de.py | 2 +- .../source/awsh_de.py | 2 +- .../source/banyule_vic_gov_au.py | 2 +- .../source/berlin_recycling_de.py | 2 +- .../source/bmv_at.py | 2 +- .../source/bracknell_forest_gov_uk.py | 9 +- .../source/bradford_gov_uk.py | 31 +-- .../source/braintree_gov_uk.py | 6 +- .../source/brisbane_qld_gov_au.py | 2 +- .../source/bsr_de.py | 2 +- .../source/buergerportal_de.py | 40 +++- .../source/c_trace_de.py | 2 +- .../source/cambridge_gov_uk.py | 8 +- .../source/canadabay_nsw_gov_au.py | 2 +- .../source/canterbury_gov_uk.py | 8 +- .../source/ccc_govt_nz.py | 2 +- .../source/cheshire_east_gov_uk.py | 6 +- .../source/chesterfield_gov_uk.py | 20 +- .../source/colchester_gov_uk.py | 8 +- .../source/cornwall_gov_uk.py | 5 +- .../source/data_umweltprofis_at.py | 2 +- .../source/derby_gov_uk.py | 8 +- .../source/ecoharmonogram_pl.py | 4 +- .../source/egn_abfallkalender_de.py | 11 +- .../source/elmbridge_gov_uk.py | 18 +- .../source/environmentfirst_co_uk.py | 23 ++- .../source/fccenvironment_co_uk.py | 24 ++- .../source/grafikai_svara_lt.py | 6 +- .../source/guildford_gov_uk.py | 7 +- .../source/huntingdonshire_gov_uk.py | 6 +- .../source/hvcgroep_nl.py | 134 +++++++++---- .../source/infeo_at.py | 9 +- .../source/innerwest_nsw_gov_au.py | 2 +- .../source/ipswich_qld_gov_au.py | 6 +- .../source/kaev_niederlausitz.py | 8 +- .../source/kingston_gov_uk.py | 4 +- .../source/korneuburg_stadtservice_at.py | 13 +- .../source/kwb_goslar_de.py | 4 +- .../source/kwu_de.py | 15 +- .../source/landkreis_rhoen_grabfeld.py | 29 +-- .../source/landkreis_wittmund_de.py | 20 +- .../source/lerum_se.py | 2 +- .../source/lewisham_gov_uk.py | 3 +- .../source/lindau_ch.py | 8 +- .../source/lrasha_de.py | 7 +- .../source/manchester_uk.py | 11 +- .../source/melton_vic_gov_au.py | 2 +- .../source/middlesbrough_gov_uk.py | 25 +-- .../source/miljoteknik_se.py | 8 +- .../source/minrenovasjon_no.py | 55 +++--- .../source/mrsc_vic_gov_au.py | 2 +- .../source/newcastle_gov_uk.py | 30 +-- .../source/nottingham_city_gov_uk.py | 2 +- .../source/nsomerset_gov_uk.py | 2 +- .../source/oslokommune_no.py | 18 +- .../source/peterborough_gov_uk.py | 8 +- .../source/pgh_st.py | 6 +- .../source/recyclesmart_com.py | 1 + .../source/regioentsorgung_de.py | 19 +- .../source/republicservices_com.py | 1 + .../source/rh_entsorgung_de.py | 2 +- .../source/richmondshire_gov_uk.py | 8 +- .../source/rushmoor_gov_uk.py | 3 +- .../source/sbazv_de.py | 22 ++- .../source/scambs_gov_uk.py | 9 +- .../source/seattle_gov.py | 3 +- .../source/sector27_de.py | 2 +- .../source/sheffield_gov_uk.py | 31 ++- .../south_norfolk_and_broadland_gov_uk.py | 7 +- .../source/srvatervinning_se.py | 4 +- .../source/ssam_se.py | 4 +- .../source/stadt_willich_de.py | 13 +- .../source/stadtreinigung_dresden_de.py | 2 +- .../source/stadtservice_bruehl_de.py | 2 +- .../source/staedteservice_de.py | 7 +- .../source/stevenage_gov_uk.py | 7 +- .../source/stonnington_vic_gov_au.py | 2 +- .../source/stuttgart_de.py | 2 +- .../source/sysav_se.py | 2 +- .../source/tewkesbury_gov_uk.py | 8 +- .../source/thehills_nsw_gov_au.py | 2 +- .../source/toronto_ca.py | 8 +- .../source/vasyd_se.py | 4 +- .../source/waipa_nz.py | 4 +- .../source/walsall_gov_uk.py | 25 +-- .../source/warszawa19115_pl.py | 4 +- .../source/wastenet_org_nz.py | 2 +- .../source/wermelskirchen_de.py | 9 +- .../source/westberks_gov_uk.py | 11 +- .../source/wiltshire_gov_uk.py | 5 +- .../source/wsz_moosburg_at.py | 2 +- .../source/wuerzburg_de.py | 14 +- .../source/wyndham_vic_gov_au.py | 7 +- .../source/york_gov_uk.py | 6 +- .../source/zva_wmk_de.py | 2 +- .../abfall_neunkirchen_siegerland_de.md | 2 +- doc/source/art_trier_de.md | 4 +- doc/source/bracknell_forest_gov_uk.md | 4 +- doc/source/braintree_gov_uk.md | 4 +- doc/source/korneuburg_stadtservice_at.md | 13 +- doc/source/nillumbik_vic_gov_au.md | 2 +- doc/source/srvatervinning_se.md | 4 +- doc/source/stadtservice_bruehl_de.md | 5 +- doc/source/toronto_ca.md | 2 +- doc/source/zva_wmk_de.md | 7 +- make_docu.py | 185 ++++++++++++++++++ 118 files changed, 784 insertions(+), 489 deletions(-) create mode 100755 make_docu.py diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_io.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_io.py index 1e678411..86988763 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_io.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_io.py @@ -7,11 +7,12 @@ import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "AbfallPlus" +TITLE = "Abfall.IO / AbfallPlus" DESCRIPTION = ( "Source for AbfallPlus.de waste collection. Service is hosted on abfall.io." ) URL = "https://www.abfallplus.de" +COUNTRY = "de" TEST_CASES = { "Waldenbuch": { "key": "8215c62763967916979e0e8566b6172e", diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_neunkirchen_siegerland_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_neunkirchen_siegerland_de.py index 40c0e9e0..2fe4deca 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_neunkirchen_siegerland_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_neunkirchen_siegerland_de.py @@ -3,11 +3,11 @@ import requests from waste_collection_schedule import Collection from waste_collection_schedule.service.ICS import ICS -TITLE = "Abfall Neunkirchen Siegerland" +TITLE = "Neunkirchen Siegerland" DESCRIPTION = " Source for 'Abfallkalender Neunkirchen Siegerland'." URL = "https://www.neunkirchen-siegerland.de" TEST_CASES = { - "Waldstraße":{ "street":"Waldstr"} + "Waldstraße":{ "strasse":"Waldstr"} } _LOGGER = logging.getLogger(__name__) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_zollernalbkreis_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_zollernalbkreis_de.py index 245a964c..a139e04a 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_zollernalbkreis_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_zollernalbkreis_de.py @@ -4,7 +4,7 @@ import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "Abfall Zollernalbkreis" +TITLE = "Abfallwirtschaft Zollernalbkreis" DESCRIPTION = "Source for Abfallwirtschaft Zollernalbkreis waste collection." URL = "https://www.abfallkalender-zak.de" TEST_CASES = { @@ -42,6 +42,16 @@ TEST_CASES = { }, } +ICON_MAP = { + "Restmüll": "mdi:trash-can", + "Grünabfall" : "mdi:leaf", + "Gelber Sack" : "mdi:sack", + "Papiertonne" : "mdi:package-variant", + "Bildschirm-/Kühlgeräte" : "mdi:television-classic", + "Schadstoffsammlung" : "mdi:biohazard", + "altmetalle" : "mdi:nail", +} + class Source: def __init__(self, city, types, street=None): @@ -49,15 +59,6 @@ class Source: self._street = street self._types = types self._ics = ICS() - self._iconMap = { - "Restmüll": "mdi:trash-can", - "Grünabfall" : "mdi:leaf", - "Gelber Sack" : "mdi:sack", - "Papiertonne" : "mdi:package-variant", - "Bildschirm-/Kühlgeräte" : "mdi:television-classic", - "Schadstoffsammlung" : "mdi:biohazard", - "altmetalle" : "mdi:nail", - } def fetch(self): now = datetime.now() @@ -95,6 +96,6 @@ class Source: waste_type = d[1] next_pickup_date = d[0] - entries.append(Collection(date=next_pickup_date, t=waste_type, icon=self._iconMap.get(waste_type,"mdi:trash-can"))) + entries.append(Collection(date=next_pickup_date, t=waste_type, icon=ICON_MAP.get(waste_type,"mdi:trash-can"))) return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfallnavi_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfallnavi_de.py index b6e14a02..bf1d552d 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfallnavi_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfallnavi_de.py @@ -1,7 +1,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.AbfallnaviDe import AbfallnaviDe -TITLE = "AbfallNavi" +TITLE = "AbfallNavi (RegioIT.de)" DESCRIPTION = ( "Source for AbfallNavi waste collection. AbfallNavi is a brand name of regioit.de." ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfalltermine_forchheim_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfalltermine_forchheim_de.py index 9fd18d81..537e59b1 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfalltermine_forchheim_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfalltermine_forchheim_de.py @@ -4,7 +4,7 @@ import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "Landkreis Forchheim" +TITLE = "Abfalltermine Forchheim" DESCRIPTION = "Source for Landkreis Forchheim" URL = "https://www.abfalltermine-forchheim.de/" TEST_CASES = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/alw_wf_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/alw_wf_de.py index 3df86d2e..cfb31904 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/alw_wf_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/alw_wf_de.py @@ -5,9 +5,9 @@ import pytz import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "ALW Wolfenbüttel" +TITLE = "Abfallwirtschaft Landkreis Wolfenbüttel" DESCRIPTION = "Source for ALW Wolfenbüttel." -URL = "https://abfallapp.alw-wf.de" +URL = "https://alw-wf.de" TEST_CASES = { "Linden alte Straße": {"ort": "Linden mit Okertalsiedlung", "strasse": "Siedlung"}, "Linden neuere Straße": { @@ -17,6 +17,7 @@ TEST_CASES = { "Dettum": {"ort": "Dettum", "strasse": "Egal!"}, } +API_URL = "https://abfallapp.alw-wf.de" AUTH_DATA = { "auth": { "Name": "ALW", @@ -41,7 +42,7 @@ class Source: auth_params = json.dumps(AUTH_DATA) # ALW WF uses a self-signed certificate so we need to disable certificate verification - r = requests.post(f"{URL}/GetOrte.php", data=auth_params, verify=False) + r = requests.post(f"{API_URL}/GetOrte.php", data=auth_params, verify=False) orte = r.json() if orte["result"][0]["StatusCode"] != 200: raise Exception(f"Error getting Orte: {orte['result'][0]['StatusMsg']}") @@ -53,7 +54,7 @@ class Source: if ort_id is None: raise Exception(f"Error finding Ort {self._ort}") - r = requests.post(f"{URL}/GetStrassen.php", data=auth_params, verify=False) + r = requests.post(f"{API_URL}/GetStrassen.php", data=auth_params, verify=False) strassen = r.json() if strassen["result"][0]["StatusCode"] != 200: raise Exception( @@ -73,7 +74,7 @@ class Source: if strasse_id is None: raise Exception(f"Error finding Straße {self._strasse}") - r = requests.post(f"{URL}/GetArten.php", data=auth_params, verify=False) + r = requests.post(f"{API_URL}/GetArten.php", data=auth_params, verify=False) arten = r.json() if arten["result"][0]["StatusCode"] != 200: raise Exception(f"Error getting Arten: {arten['result'][0]['StatusMsg']}") @@ -84,7 +85,7 @@ class Source: entries = [] r = requests.post( - f"{URL}/GetTermine.php/{strasse_id}", data=auth_params, verify=False + f"{API_URL}/GetTermine.php/{strasse_id}", data=auth_params, verify=False ) termine = r.json() if termine["result"][0]["StatusCode"] != 200: diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/art_trier_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/art_trier_de.py index 3c196ff9..932e108f 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/art_trier_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/art_trier_de.py @@ -7,7 +7,7 @@ import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "Abfall ART Trier" +TITLE = "ART Trier" DESCRIPTION = "Source for waste collection of ART Trier." URL = "https://www.art-trier.de" TEST_CASES = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/aucklandcouncil_govt_nz.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/aucklandcouncil_govt_nz.py index 682f5c17..f183ebc4 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/aucklandcouncil_govt_nz.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/aucklandcouncil_govt_nz.py @@ -7,7 +7,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] # Include work around for SSL UNSAFE_LEGACY_RENEGOTIATION_DISABLED error from waste_collection_schedule.service.SSLError import get_legacy_session -TITLE = "Auckland council" +TITLE = "Auckland Council" DESCRIPTION = "Source for Auckland council." URL = "https://aucklandcouncil.govt.nz" TEST_CASES = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/aw_harburg_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/aw_harburg_de.py index 52487925..32fcb03b 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/aw_harburg_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/aw_harburg_de.py @@ -3,9 +3,9 @@ from bs4 import BeautifulSoup from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "AW Harburg" +TITLE = "Abfallwirtschaft Landkreis Harburg" DESCRIPTION = "Abfallwirtschaft Landkreis Harburg" -URL = "https://www.landkreis-harburg.de/bauen-umwelt/abfallwirtschaft/abfallkalender/" +URL = "https://www.landkreis-harburg.de" TEST_CASES = { "CityWithTwoLevels": {"level_1": "Hanstedt", "level_2": "Evendorf"}, @@ -16,6 +16,7 @@ TEST_CASES = { }, } +API_URL = "https://www.landkreis-harburg.de/bauen-umwelt/abfallwirtschaft/abfallkalender/" HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)", } @@ -33,11 +34,11 @@ class Source: # Get the IDs of the districts on the first level # Double loading is on purpose because sometimes the webpage has an overlay # which is gone on the second try in a session - r = session.get(URL, headers=HEADERS) + r = session.get(API_URL, headers=HEADERS) if "Zur aufgerufenen Seite" in r.text: - r = session.get(URL, headers=HEADERS) + r = session.get(API_URL, headers=HEADERS) if r.status_code != 200: - raise Exception(f"Error: failed to fetch first url: {URL}") + raise Exception(f"Error: failed to fetch first url: {API_URL}") # Get the IDs of the districts on the first level id = self.parse_level(r.text, 1) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awb_oldenburg_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awb_oldenburg_de.py index 1f8a5766..054663af 100755 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awb_oldenburg_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awb_oldenburg_de.py @@ -6,11 +6,13 @@ from waste_collection_schedule.service.ICS import ICS TITLE = "AWB Oldenburg" DESCRIPTION = "Source for 'Abfallwirtschaftsbetrieb Stadt Oldenburg (Oldb)'." -URL = "https://services.oldenburg.de/index.php" +URL = "https://oldenburg.de" TEST_CASES = { "Polizeiinspektion Oldenburg": {"street": "Friedhofsweg", "house_number": 30} } +API_URL = "https://services.oldenburg.de/index.php" + class Source: def __init__(self, street, house_number): @@ -39,7 +41,7 @@ class Source: args = urllib.parse.urlencode(args, quote_via=urllib.parse.quote) # post request - r = requests.get(URL, params=args) + r = requests.get(API_URL, params=args) dates = self._ics.convert(r.text) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awido_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awido_de.py index a58abaf4..bfbd4699 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awido_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awido_de.py @@ -5,7 +5,7 @@ import logging import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "AWIDO" +TITLE = "AWIDO Online" DESCRIPTION = "Source for AWIDO waste collection." URL = "https://www.awido-online.de/" TEST_CASES = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awn_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awn_de.py index 685f6dce..058d38da 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awn_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awn_de.py @@ -5,7 +5,7 @@ import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "AWN" +TITLE = "Abfallwirtschaft Neckar-Odenwald-Kreis" DESCRIPTION = "Source for AWN (Abfallwirtschaft Neckar-Odenwald-Kreis)." URL = "https://www.awn-online.de" TEST_CASES = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awr_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awr_de.py index 8f6fef4b..e2e0b520 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awr_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awr_de.py @@ -5,7 +5,7 @@ import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "AWR" +TITLE = "Abfallwirtschaft Rendsburg" DESCRIPTION = "Source for Abfallwirtschaft Rendsburg" URL = "https://www.awr.de" TEST_CASES = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awsh_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awsh_de.py index ca9b61c3..8a8c371f 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awsh_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awsh_de.py @@ -5,7 +5,7 @@ import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "AWSH" +TITLE = "Abfallwirtschaft Südholstein" DESCRIPTION = "Source for Abfallwirtschaft Südholstein" URL = "https://www.awsh.de" TEST_CASES = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/banyule_vic_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/banyule_vic_gov_au.py index ca13e793..7cf8e163 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/banyule_vic_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/banyule_vic_gov_au.py @@ -10,7 +10,7 @@ from waste_collection_schedule import Collection TITLE = 'Banyule City Council' DESCRIPTION = 'Source for Banyule City Council rubbish collection.' -URL = 'https://www.banyule.vic.gov.au/binday' +URL = 'https://www.banyule.vic.gov.au' TEST_CASES = { 'Monday A': {'street_address': '6 Mandall Avenue, IVANHOE'}, 'Monday A Geolocation ID': {'geolocation_id': '4f7ebfca-1526-4363-8b87-df3103a10a87'}, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/berlin_recycling_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/berlin_recycling_de.py index 82537d83..f1f7e956 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/berlin_recycling_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/berlin_recycling_de.py @@ -5,7 +5,7 @@ from html.parser import HTMLParser import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "Berline Recycling" +TITLE = "Berlin Recycling" DESCRIPTION = "Source for Berlin Recycling waste collection." URL = "https://berlin-recycling.de" TEST_CASES = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/bmv_at.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bmv_at.py index 9ab5d21d..cd58d464 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/bmv_at.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bmv_at.py @@ -5,7 +5,7 @@ import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "BMV.at" +TITLE = "Burgenländischer Müllverband" DESCRIPTION = "Source for BMV, Austria" URL = "https://www.bmv.at" TEST_CASES = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/bracknell_forest_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bracknell_forest_gov_uk.py index 720bf7ac..f423633a 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/bracknell_forest_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bracknell_forest_gov_uk.py @@ -4,7 +4,7 @@ import requests from dateutil import parser from waste_collection_schedule import Collection -TITLE = "bracknell-forest.gov.uk" +TITLE = "Bracknell Forest Council" DESCRIPTION = "Bracknell Forest Council, UK - Waste Collection" URL = "https://selfservice.mybfc.bracknell-forest.gov.uk" TEST_CASES = { @@ -13,7 +13,8 @@ TEST_CASES = { "32 Ashbourne": {"house_number": "32", "post_code": "RG12 8SG"}, "1 Acacia Avenue": {"house_number": "1", "post_code": "GU47 0RU"}, } -ICONS = { + +ICON_MAP = { "General Waste": "mdi:trash-can", "Recycling": "mdi:recycle", "Garden": "mdi:leaf", @@ -68,7 +69,7 @@ class Source: collection_lookup.raise_for_status() collections = collection_lookup.json()["response"]["collections"] entries = [] - for waste_type in ICONS.keys(): + for waste_type in ICON_MAP.keys(): try: entries.append( Collection( @@ -78,7 +79,7 @@ class Source: ]["date"] ).date(), t=waste_type, - icon=ICONS[waste_type], + icon=ICON_MAP[waste_type], ) ) except (StopIteration, TypeError): diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/bradford_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bradford_gov_uk.py index 2e6bcd6a..869dc058 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/bradford_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bradford_gov_uk.py @@ -11,18 +11,19 @@ import http.client as http_client import ssl import urllib3 -TITLE = "Bradford.gov.uk" +TITLE = "Bradford Metropolitan District Council" DESCRIPTION = ( "Source for Bradford.gov.uk services for Bradford Metropolitan Council, UK." ) -URL = "https://onlineforms.bradford.gov.uk/ufs/" +URL = "https://bradford.gov.uk" TEST_CASES = { "Ilkley": {"uprn": "100051250665"}, "Bradford": {"uprn": "100051239296"}, "Baildon": {"uprn": "10002329242"}, } -ICONS = { +API_URL = "https://onlineforms.bradford.gov.uk/ufs/" +ICON_MAP = { "REFUSE": "mdi:trash-can", "RECYCLING": "mdi:recycle", "GARDEN": "mdi:leaf", @@ -30,16 +31,16 @@ ICONS = { from pprint import pprint -class CustomHttpAdapter (requests.adapters.HTTPAdapter): - '''Transport adapter" that allows us to use custom ssl_context.''' - - def __init__(self, ssl_context=None, **kwargs): - self.ssl_context = ssl_context - super().__init__(**kwargs) - - def init_poolmanager(self, connections, maxsize, block=False): - self.poolmanager = urllib3.poolmanager.PoolManager( - num_pools=connections, maxsize=maxsize, +class CustomHttpAdapter (requests.adapters.HTTPAdapter): + '''Transport adapter" that allows us to use custom ssl_context.''' + + def __init__(self, ssl_context=None, **kwargs): + self.ssl_context = ssl_context + super().__init__(**kwargs) + + def init_poolmanager(self, connections, maxsize, block=False): + self.poolmanager = urllib3.poolmanager.PoolManager( + num_pools=connections, maxsize=maxsize, block=block, ssl_context=self.ssl_context) class Source: @@ -59,7 +60,7 @@ class Source: s.cookies.set( "COLLECTIONDATES", self._uprn, domain="onlineforms.bradford.gov.uk" ) - r = s.get(f"{URL}/collectiondates.eb") + r = s.get(f"{API_URL}/collectiondates.eb") soup = BeautifulSoup(r.text, features="html.parser") div = soup.find_all("table", {"role": "region"}) @@ -87,7 +88,7 @@ class Source: entry.text.strip(), "%a %b %d %Y" ).date(), t=type, - icon=ICONS[type], + icon=ICON_MAP[type], ) ) except ValueError: diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/braintree_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/braintree_gov_uk.py index b76826c9..e3c9c19a 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/braintree_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/braintree_gov_uk.py @@ -3,7 +3,7 @@ from bs4 import BeautifulSoup from dateutil import parser from waste_collection_schedule import Collection -TITLE = "braintree.gov.uk" +TITLE = "Braintree District Council" DESCRIPTION = "Braintree District Council, UK - Waste Collection" URL = "https://www.braintree.gov.uk" TEST_CASES = { @@ -13,7 +13,7 @@ TEST_CASES = { "20 Peel Crescent": {"house_number": "20", "post_code": "CM7 2RS"}, } -ICONS = { +ICON_MAP = { "Grey Bin": "mdi:trash-can", "Clear Sack": "mdi:recycle", "Green Bin": "mdi:leaf", @@ -51,7 +51,7 @@ class Source: Collection( date=parser.parse(collection_date).date(), t=collection_type, - icon=ICONS[collection_type] + icon=ICON_MAP[collection_type] ) ) except (StopIteration, TypeError): diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/brisbane_qld_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/brisbane_qld_gov_au.py index 510b4761..5288aeeb 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/brisbane_qld_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/brisbane_qld_gov_au.py @@ -6,7 +6,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Brisbane City Council" DESCRIPTION = "Source for Brisbane City Council rubbish collection." -URL = "https://www.brisbane.qld.gov.au/clean-and-green/rubbish-tips-and-bins/rubbish-collections/bin-collection-calendar" +URL = "https://www.brisbane.qld.gov.au" TEST_CASES = { "Suburban Social": { "suburb": "Chapel Hill", diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/bsr_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bsr_de.py index fa420fee..f4a0675f 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/bsr_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bsr_de.py @@ -4,7 +4,7 @@ import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "BSR" +TITLE = "Berliner Stadtreinigungsbetriebe" DESCRIPTION = "Source for Berliner Stadtreinigungsbetriebe waste collection." URL = "https://bsr.de" TEST_CASES = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/buergerportal_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/buergerportal_de.py index 888d4316..fcdbd973 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/buergerportal_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/buergerportal_de.py @@ -8,6 +8,8 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Bürgerportal" URL = "https://www.c-trace.de" DESCRIPTION = "Source for waste collection in multiple service areas." +def EXTRA_INFO(): + return [ { "title": s["title"], "url": s["url"] } for s in SERVICE_MAP ] TEST_CASES = { "Cochem-Zell": { "operator": "cochem_zell", @@ -30,7 +32,8 @@ TEST_CASES = { "number": 1, }, } -ICONS = { + +ICON_MAP = { "mobil": "mdi:truck", "bio": "mdi:leaf", "papier": "mdi:package-variant", @@ -49,12 +52,27 @@ API_HEADERS = { "Cache-Control": "no-cache", } Operator = Literal["cochem_zell", "alb_donau", "biedenkopf"] -# Important: Remove the trailing slash -OPERATOR_URLS: dict[Operator, str] = { - "cochem_zell": "https://buerger-portal-cochemzell.azurewebsites.net", - "alb_donau": "https://buerger-portal-albdonaukreisabfallwirtschaft.azurewebsites.net", - "biedenkopf": "https://biedenkopfmzv.buergerportal.digital", -} + +SERVICE_MAP = [ + { + "title": "KV Cochem-Zell", + "url": "https://www.cochem-zell-online.de/", + "api_url": "https://buerger-portal-cochemzell.azurewebsites.net/api", + "operator": "cochem_zell" + }, + { + "title": "Abfallwirtschaft Alb-Donau-Kreis", + "url": "https://www.aw-adk.de/", + "api_url": "https://buerger-portal-albdonaukreisabfallwirtschaft.azurewebsites.net/api", + "operator": "alb_donau" + }, + { + "title": "MZV Bidenkopf", + "url": "https://mzv-biedenkopf.de/", + "api_url": "https://biedenkopfmzv.buergerportal.digital/api", + "operator": "biedenkopf" + }, +] def quote_none(value: Optional[str]) -> str: @@ -64,6 +82,10 @@ def quote_none(value: Optional[str]) -> str: return f"'{value}'" +def get_api_map(): + return { s["operator"]:s["api_url"] for s in SERVICE_MAP } + + class Source: def __init__( self, @@ -73,7 +95,7 @@ class Source: subdistrict: Optional[str] = None, number: Union[int, str, None] = None, ): - self.api_url = f"{OPERATOR_URLS[operator]}/api" + self.api_url = get_api_map()[operator] self.district = district self.subdistrict = subdistrict self.street = street @@ -120,7 +142,7 @@ class Source: icon = None # Maybe append collection["Abfuhrplan"]["GefaesstarifArt"]["Volumen"]["VolumenWert"] to waste type - for icon_type, tested_icon in ICONS.items(): + for icon_type, tested_icon in ICON_MAP.items(): if icon_type.lower() in waste_type.lower(): icon = tested_icon diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_de.py index 8a02c081..ee46980a 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_de.py @@ -2,7 +2,7 @@ import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "C-Trace.de" +TITLE = "C-Trace" DESCRIPTION = "Source for C-Trace.de." URL = "https://c-trace.de/" TEST_CASES = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/cambridge_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/cambridge_gov_uk.py index 1e041599..ced93d21 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/cambridge_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/cambridge_gov_uk.py @@ -4,11 +4,11 @@ from datetime import datetime import requests from waste_collection_schedule import Collection -TITLE = "Cambridge.gov.uk" +TITLE = "Cambridge City Council" DESCRIPTION = ( "Source for cambridge.gov.uk services for Cambridge and part of Cambridgeshire" ) -URL = "cambridge.gov.uk" +URL = "https://cambridge.gov.uk" TEST_CASES = { "houseNumber": {"post_code": "CB13JD", "number": 37}, "houseName": {"post_code": "cb215hd", "number": "ROSEMARY HOUSE"}, @@ -19,7 +19,7 @@ API_URLS = { "collection": "https://servicelayer3c.azure-api.net/wastecalendar/collection/search/{}/", } -ICONS = { +ICON_MAP = { "DOMESTIC": "mdi:trash-can", "RECYCLE": "mdi:recycle", "ORGANIC": "mdi:leaf", @@ -63,7 +63,7 @@ class Source: collection["date"], "%Y-%m-%dT%H:%M:%SZ" ).date(), t=round_type.title(), - icon=ICONS.get(round_type), + icon=ICON_MAP.get(round_type), ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/canadabay_nsw_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/canadabay_nsw_gov_au.py index b4ca87c2..c4db33bc 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/canadabay_nsw_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/canadabay_nsw_gov_au.py @@ -6,7 +6,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "City of Canada Bay Council" DESCRIPTION = "Source for City of Canada Bay Council rubbish collection." -URL = "https://www.canadabay.nsw.gov.au/residents/waste-and-recycling/my-bins/my-bin-collection" +URL = "https://www.canadabay.nsw.gov.au" TEST_CASES = { "Harry's Shed": { "suburb": "Concord", diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/canterbury_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/canterbury_gov_uk.py index 7bd4e472..e5b4ae9a 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/canterbury_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/canterbury_gov_uk.py @@ -5,11 +5,11 @@ import json import requests from waste_collection_schedule import Collection -TITLE = "canterbury.gov.uk" +TITLE = "Canterbury City Council" DESCRIPTION = ( "Source for canterbury.gov.uk services for canterbury" ) -URL = "canterbury.gov.uk" +URL = "https://canterbury.gov.uk" TEST_CASES = { "houseNumber": {"post_code": "ct68ru", "number": "63"}, "houseName": {"post_code": "ct68ru", "number": "KOWLOON"}, @@ -20,7 +20,7 @@ API_URLS = { "collection": "https://zbr7r13ke2.execute-api.eu-west-2.amazonaws.com/Beta/get-bin-dates", } -ICONS = { +ICON_MAP = { "General": "mdi:trash-can", "Recycling": "mdi:recycle", "Food": "mdi:food-apple", @@ -77,7 +77,7 @@ class Source: date, "%Y-%m-%dT%H:%M:%S" ).date(), t=collection, - icon=ICONS.get(collection), + icon=ICON_MAP.get(collection), ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ccc_govt_nz.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ccc_govt_nz.py index 052b2c0d..ca3c4a6c 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ccc_govt_nz.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ccc_govt_nz.py @@ -9,7 +9,7 @@ from waste_collection_schedule.service.SSLError import get_legacy_session TITLE = "Christchurch City Council" DESCRIPTION = "Source for Christchurch City Council." -URL = "https://ccc.govt.nz/services/rubbish-and-recycling/collections" +URL = "https://ccc.govt.nz" TEST_CASES = {"53 Hereford Street": {"address": "53 Hereford Street"}} diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/cheshire_east_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/cheshire_east_gov_uk.py index 603e26dc..bd63ebc3 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/cheshire_east_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/cheshire_east_gov_uk.py @@ -4,11 +4,9 @@ import requests from bs4 import BeautifulSoup from waste_collection_schedule import Collection -TITLE = "cheshireeast.gov.uk" +TITLE = "Cheshire East Council" DESCRIPTION = "Source for cheshireeast.gov.uk services for Cheshire East" -URL = "cheshireeast.gov.uk" - - +URL = "https://cheshireeast.gov.uk" TEST_CASES = { "houseUPRN": {"uprn": "100010132071"}, "houseAddress": {"postcode": "WA16 0AY", "name_number": "1"}, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/chesterfield_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/chesterfield_gov_uk.py index da6da807..929e1ab3 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/chesterfield_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/chesterfield_gov_uk.py @@ -14,12 +14,8 @@ import urllib3 urllib3.disable_warnings() -TITLE = "chesterfield.gov.uk" - -DESCRIPTION = ( - "Source for waste collection services for Chesterfield Borough Council" -) - +TITLE = "Chesterfield Borough Council" +DESCRIPTION = "Source for waste collection services for Chesterfield Borough Council" URL = "https://www.chesterfield.gov.uk/" HEADERS = { @@ -33,13 +29,13 @@ TEST_CASES = { "Test_004": {"uprn": "74020930"}, } -ICONS = { +ICON_MAP = { "DOMESTIC REFUSE": "mdi:trash-can", "DOMESTIC RECYCLING": "mdi:recycle", "DOMESTIC ORGANIC": "mdi:leaf", } -APIS = { +API_URLS = { "session": "https://www.chesterfield.gov.uk/bins-and-recycling/bin-collections/check-bin-collections.aspx", "fwuid": "https://myaccount.chesterfield.gov.uk/anonymous/c/cbc_VE_CollectionDaysLO.app?aura.format=JSON&aura.formatAdapter=LIGHTNING_OUT", "search": "https://myaccount.chesterfield.gov.uk/anonymous/aura?r=2&aura.ApexAction.execute=1", @@ -57,13 +53,13 @@ class Source: s = requests.Session() r = s.get( - APIS["session"], + API_URLS["session"], headers=HEADERS, ) # Capture fwuid value r = s.get( - APIS["fwuid"], + API_URLS["fwuid"], verify=False, headers=HEADERS, ) @@ -83,7 +79,7 @@ class Source: "aura.token": "null", } r = s.post( - APIS["search"], + API_URLS["search"], data=payload, verify=False, headers=HEADERS, @@ -108,7 +104,7 @@ class Source: Collection( date=dt_local.date(), t=waste_type, - icon=ICONS.get(waste_type.upper()), + icon=ICON_MAP.get(waste_type.upper()), ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/colchester_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/colchester_gov_uk.py index 42ac875b..ce0fea32 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/colchester_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/colchester_gov_uk.py @@ -4,7 +4,7 @@ from datetime import datetime, timedelta import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "Colchester.gov.uk" +TITLE = "Colchester Borough Council" DESCRIPTION = "Source for Colchester.gov.uk services for the borough of Colchester, UK." URL = "https://colchester.gov.uk" TEST_CASES = { @@ -13,7 +13,7 @@ TEST_CASES = { "The Lane, Colchester": {"llpgid": "7cd96a3d-6027-e711-80fa-5065f38b56d1"}, } -ICONS = { +ICON_MAP = { "Black bags": "mdi:trash-can", "Glass": "mdi:glass-fragile", "Cans": "mdi:trash-can", @@ -63,7 +63,7 @@ class Source: Collection( date=date.date(), t=day["Name"].title(), - icon=ICONS[day["Name"]], + icon=ICON_MAP[day["Name"]], ) ) # As Colchester.gov.uk only provides the current collection cycle, the next must be extrapolated @@ -73,7 +73,7 @@ class Source: Collection( date=date.date() + timedelta(days=14), t=day["Name"].title(), - icon=ICONS[day["Name"]], + icon=ICON_MAP[day["Name"]], ) ) except ValueError: diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/cornwall_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/cornwall_gov_uk.py index 11896234..6b57b8cc 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/cornwall_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/cornwall_gov_uk.py @@ -4,13 +4,14 @@ import requests from bs4 import BeautifulSoup from waste_collection_schedule import Collection -TITLE = "Cornwall Council, UK" +TITLE = "Cornwall Council" DESCRIPTION = "Source for cornwall.gov.uk services for Cornwall Council" -URL = "cornwall.gov.uk" +URL = "https://cornwall.gov.uk" TEST_CASES = { "known_uprn": {"uprn": "100040118005"}, "unknown_uprn": {"postcode": "TR261SP", "housenumberorname": "7"}, } + SEARCH_URLS = { "uprn_search": "https://www.cornwall.gov.uk/my-area/", "collection_search": "https://www.cornwall.gov.uk/umbraco/Surface/Waste/MyCollectionDays?subscribe=False", diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/data_umweltprofis_at.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/data_umweltprofis_at.py index 8ccd1004..a8692800 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/data_umweltprofis_at.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/data_umweltprofis_at.py @@ -5,7 +5,7 @@ from xml.dom.minidom import parseString from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "UMWELTPROFIS" +TITLE = "Umweltprofis" DESCRIPTION = "Source for Umweltprofis" URL = "https://www.umweltprofis.at" TEST_CASES = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/derby_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/derby_gov_uk.py index e82f1b29..c7ccaaa2 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/derby_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/derby_gov_uk.py @@ -6,9 +6,9 @@ import requests from bs4 import BeautifulSoup from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "Derby.gov.uk" +TITLE = "Derby City Council" DESCRIPTION = "Source for Derby.gov.uk services for Derby City Council, UK." -URL = "https://secure.derby.gov.uk/binday/" +URL = "https://derby.gov.uk" TEST_CASES = { # Derby City council wants specific addresses, hopefully these are generic enough. "Community Of The Holy Name, Morley Road, Derby, DE21 4TB": { @@ -20,7 +20,7 @@ TEST_CASES = { }, } -ICONS = { +ICON_MAP = { "Black bin": "mdi:trash-can", "Blue bin": "mdi:recycle", "Brown bin": "mdi:leaf", @@ -82,7 +82,7 @@ class Source: Collection( date=date, t=collection_type, - icon=ICONS[collection_type], + icon=ICON_MAP[collection_type], ) ) return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ecoharmonogram_pl.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ecoharmonogram_pl.py index 568fdeb0..679ccaa9 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ecoharmonogram_pl.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ecoharmonogram_pl.py @@ -3,8 +3,9 @@ from ..collection import Collection from ..service.EcoHarmonogramPL import Ecoharmonogram +TITLE = "Ecoharmonogram" DESCRIPTION = "Source for ecoharmonogram.pl" -URL = "ecoharmonogram.pl" +URL = "https://ecoharmonogram.pl" TEST_CASES = { "Simple test case": {"town": "Krzeszowice", "street": "Wyki", "house_number": ""}, "Sides multi test case": {"town": "Częstochowa", "street": "Boczna", "additional_sides_matcher": "wie"}, @@ -14,7 +15,6 @@ TEST_CASES = { "additional_sides_matcher": "Wielorodzinna - powyżej 7 lokali"}, "Simple test with community": {"town": "Gdańsk", "street": "Jabłoniowa", "house_number": "55", "additional_sides_matcher": "", "community": "108" }, } -TITLE = "ecoharmonogram.pl" class Source: diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/egn_abfallkalender_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/egn_abfallkalender_de.py index 5a3971b3..e60dd323 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/egn_abfallkalender_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/egn_abfallkalender_de.py @@ -7,7 +7,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "EGN Abfallkalender" DESCRIPTION = "Source for EGN Abfallkalender" -URL = "https://www.egn-abfallkalender.de/kalender" +URL = "https://www.egn-abfallkalender.de" TEST_CASES = { "Grevenbroich": { "city": "Grevenbroich", @@ -31,7 +31,8 @@ TEST_CASES = { _LOGGER = logging.getLogger(__name__) -IconMap = { +API_URL = "https://www.egn-abfallkalender.de/kalender" +ICON_MAP = { "Grau": "mdi:trash-can", "Gelb": "mdi:sack", "Blau": "mdi:package-variant", @@ -48,7 +49,7 @@ class Source: def fetch(self): s = requests.session() - r = s.get(URL) + r = s.get(API_URL) soup = BeautifulSoup(r.text, features="html.parser") tag = soup.find("meta", {"name": "csrf-token"}) @@ -62,7 +63,7 @@ class Source: "street": self._street, "street_number": self._housenumber, } - r = s.post(URL, data=post_data, headers=headers) + r = s.post(API_URL, data=post_data, headers=headers) data = r.json() @@ -85,7 +86,7 @@ class Source: .capitalize() ) entries.append( - Collection(date=date, t=color, icon=IconMap.get(color)) + Collection(date=date, t=color, icon=ICON_MAP.get(color)) ) return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/elmbridge_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/elmbridge_gov_uk.py index 4fc98655..0431d923 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/elmbridge_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/elmbridge_gov_uk.py @@ -5,15 +5,9 @@ import requests from bs4 import BeautifulSoup from waste_collection_schedule import Collection -TITLE = "elmbridge.gov.uk" +TITLE = "Elmbridge Borough Council" DESCRIPTION = "Source for waste collection services for Elmbridge Borough Council" -URL = "https://www.elmbridge.gov.uk/waste-and-recycling/" - - -HEADERS = { - "user-agent": "Mozilla/5.0", -} - +URL = "https://www.elmbridge.gov.uk" TEST_CASES = { "Test_001": {"uprn": 10013119164}, "Test_002": {"uprn": "100061309206"}, @@ -38,13 +32,17 @@ OFFSETS = { "Sunday": 6, } -ICONS = { +ICON_MAP = { "REFUSE": "mdi:trash-can", "RECYCLING": "mdi:recycle", "FOOD": "mdi:food", "GARDEN": "mdi:leaf", } +HEADERS = { + "user-agent": "Mozilla/5.0", +} + _LOGGER = logging.getLogger(__name__) @@ -117,7 +115,7 @@ class Source: Collection( date=new_date.date(), t=waste + " bin", - icon=ICONS.get(waste.upper()), + icon=ICON_MAP.get(waste.upper()), ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/environmentfirst_co_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/environmentfirst_co_uk.py index 1bdfc1b3..ed9a8a3d 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/environmentfirst_co_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/environmentfirst_co_uk.py @@ -5,24 +5,31 @@ from bs4 import BeautifulSoup from dateutil.parser import parse from waste_collection_schedule import Collection -TITLE = "environmentfirst.co.uk" - +TITLE = "Environment First" +URL = "https://environmentfirst.co.uk" +EXTRA_INFO = [ + { + "title": "Eastbourne Borough Council", + "url": "https://lewes-eastbourne.gov.uk" + }, + { + "title": "Lewes District Council", + "url": "https://lewes-eastbourne.gov.uk" + }, +] DESCRIPTION = ( """Consolidated source for waste collection services from: Eastbourne Borough Council Lewes District Council """ ) - -URL = "https://environmentfirst.co.uk" - TEST_CASES = { "houseUPRN" : {"uprn": "100060063421"}, "houseNumber": {"post_code": "BN228SG", "number": 3}, "houseName": {"post_code": "BN73LG", "number": "Garden Cottage"}, } -ICONS = { +ICON_MAP = { "RUBBISH": "mdi:trash-can", "RECYCLING": "mdi:recycle", "GARDEN WASTE": "mdi:leaf", @@ -92,13 +99,13 @@ class Source: x = soup.findAll("p") for i in x[1:-1]: # ignores elements containing address and marketing message if " day " in i.text: - for round_type in ICONS: + for round_type in ICON_MAP: if round_type in i.text.upper(): entries.append( Collection( date = parse(str.split(i.text, ":")[1]).date(), t = round_type, - icon = ICONS.get(round_type), + icon = ICON_MAP.get(round_type), ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/fccenvironment_co_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/fccenvironment_co_uk.py index d475a956..5312407e 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/fccenvironment_co_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/fccenvironment_co_uk.py @@ -5,8 +5,7 @@ from bs4 import BeautifulSoup from dateutil import parser from waste_collection_schedule import Collection -TITLE = "fccenvironment.co.uk" - +TITLE = "FCC Environment" DESCRIPTION = """ Consolidated source for waste collection services for ~60 local authorities. Currently supports: @@ -14,8 +13,21 @@ DESCRIPTION = """ South Hams (Generic Provider) Market Harborough (Custom Provider) """ - URL = "https://fccenvironment.co.uk" +EXTRA_INFO = [ + { + "title": "Harborough District Council", + "url": "https://harborough.gov.uk" + }, + { + "title": "South Hams District Council", + "url": "https://southhams.gov.uk/" + }, + { + "title": "West Devon Borough Council", + "url": "https://www.westdevon.gov.uk/" + }, +] TEST_CASES = { "14_LE16_9QX": {"uprn": "100030491624"}, # region ommited to test default values @@ -28,7 +40,7 @@ TEST_CASES = { "4_SL21_0HZ": {"uprn": "100040281987", "region": "southhams"}, } -ICONS = { +ICON_MAP = { "Refuse": "mdi:trash-can", "Recycling": "mdi:recycle", "Garden": "mdi:leaf", @@ -66,7 +78,7 @@ class Source: """ Handle duplication before creating the list of Collections """ - for type in ICONS: + for type in ICON_MAP: if type in service: if type in results.keys(): if date < results[type]: @@ -80,7 +92,7 @@ class Source: Collection( date=results[result], t=result, - icon=ICONS[result], + icon=ICON_MAP[result], ) ) return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/grafikai_svara_lt.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/grafikai_svara_lt.py index 9c36cc39..e15b77a4 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/grafikai_svara_lt.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/grafikai_svara_lt.py @@ -4,7 +4,7 @@ from datetime import datetime import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "Grafikai.svara.lt" +TITLE = "Kauno švara" DESCRIPTION = 'Source for UAB "Kauno švara".' URL = "http://grafikai.svara.lt" TEST_CASES = { @@ -22,7 +22,7 @@ TEST_CASES = { }, } -ICONS = { +ICON_MAP = { "mišrių atliekų": "mdi:trash-can", "antrinių žaliavų (popierius/plastikas)": "mdi:recycle", "antrinių žaliavų (stiklas)": "mdi:glass-fragile", @@ -86,7 +86,7 @@ class Source: collection_waste_object["date"], "%Y-%m-%dT%H:%M:%S" ).date(), t=collection["descriptionFmt"].title(), - icon=ICONS.get(type, "mdi:trash-can"), + icon=ICON_MAP.get(type, "mdi:trash-can"), ) ) except ValueError: diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/guildford_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/guildford_gov_uk.py index da5aa643..2dd6761d 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/guildford_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/guildford_gov_uk.py @@ -4,9 +4,8 @@ from datetime import datetime import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "guildford.gov.uk" +TITLE = "Guildford Borough Council" DESCRIPTION = "Source for guildford.gov.uk services for Guildford, UK." -# Find the UPRN of your address using https://www.findmyaddress.co.uk/search URL = "https://guildford.gov.uk" TEST_CASES = { "GU12": {"uprn": "10007060305"}, @@ -14,7 +13,7 @@ TEST_CASES = { "GU2": {"uprn": "100061391831"}, } -ICONS = { +ICON_MAP = { "Refuse": "mdi:trash-can", "Food": "mdi:food-apple", "Recycling": "mdi:recycle", @@ -74,7 +73,7 @@ class Source: collection["NextDate"], "%Y-%m-%dT%H:%M:%S.000Z" ).date(), t=collection["FeatureName"], - icon=ICONS[collection["FeatureName"]], + icon=ICON_MAP[collection["FeatureName"]], ) ) except ValueError: diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/huntingdonshire_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/huntingdonshire_gov_uk.py index 9d37fbc3..e209a01e 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/huntingdonshire_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/huntingdonshire_gov_uk.py @@ -4,7 +4,7 @@ from datetime import datetime import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "Huntingdonshire.gov.uk" +TITLE = "Huntingdonshire District Council" DESCRIPTION = "Source for Huntingdonshire.gov.uk services for Huntingdonshire District Council." URL = "https://www.huntingdonshire.gov.uk" TEST_CASES = { @@ -12,7 +12,7 @@ TEST_CASES = { "Inkerman Rise, St. Neots": {"uprn": "10000144271"}, } -ICONS = { +ICON_MAP = { "Refuse": "mdi:trash-can", "Recycling": "mdi:recycle", "Garden": "mdi:leaf", @@ -45,7 +45,7 @@ class Source: collection["date"], "%Y-%m-%dT%H:%M:%SZ" ).date(), t=round_type.title(), - icon=ICONS.get(round_type), + icon=ICON_MAP.get(round_type), ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/hvcgroep_nl.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/hvcgroep_nl.py index d1b3aaa0..5dd44dd0 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/hvcgroep_nl.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/hvcgroep_nl.py @@ -4,9 +4,11 @@ from datetime import datetime import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "HVCGroep" +TITLE = None DESCRIPTION = "Source for the Dutch HVCGroep waste management." -URL = "https://www.hvcgroep.nl/zelf-regelen/afvalkalender" +URL = "https://www.hvcgroep.nl" +def EXTRA_INFO(): + return [ { "title": s["title"], "url": get_main_url(s["api_url"])} for s in SERVICE_MAP ] TEST_CASES = { "Tollebeek": {"postal_code": "8309AV", "house_number": "1"}, "Hvgroep: Tollebeek": { @@ -15,39 +17,105 @@ TEST_CASES = { "service": "hvcgroep", }, "Cyclus": {"postal_code": "2841ML", "house_number": "1090", "service": "cyclusnv"}, - "Mjinblink": { + "Mijnblink": { "postal_code": "5741BV", "house_number": "76", - "service": "mjinblink", + "service": "mijnblink", }, } _LOGGER = logging.getLogger(__name__) -SERVICE_MAP = { - "alphenaandenrijn": "https://afvalkalender.alphenaandenrijn.nl", - "cranendonck": "https://afvalkalender.cranendonck.nl", - "cyclusnv": "https://afvalkalender.cyclusnv.nl", - "dar": "https://afvalkalender.dar.nl", - "denhaag": "https://huisvuilkalender.denhaag.nl", - "gad": "https://inzamelkalender.gad.nl", - "gemeenteberkelland": "https://afvalkalender.gemeenteberkelland.nl", - "hvcgroep": "https://inzamelkalender.hvcgroep.nl", - "lingewaard": "https://afvalwijzer.lingewaard.nl", - "middelburgvlissingen": "https://afvalwijzer.middelburgvlissingen.nl", - "mijnblink": "https://mijnblink.nl", - "peelenmaas": "https://afvalkalender.peelenmaas.nl", - "prezero": "https://inzamelwijzer.prezero.nl", - "purmerend": "https://afvalkalender.purmerend.nl", - "rmn": "https://inzamelschema.rmn.nl", - "schouwen-duiveland": "https://afvalkalender.schouwen-duiveland.nl", - "spaarnelanden": "https://afvalwijzer.spaarnelanden.nl", - "stadswerk072": "https://www.stadswerk072.nl", - "sudwestfryslan": "https://afvalkalender.sudwestfryslan.nl", - "venray": "https://afvalkalender.venray.nl", - "voorschoten": "https://afvalkalender.voorschoten.nl", - "waalre": "https://afvalkalender.waalre.nl", - "zrd": "https://afvalkalender.zrd.nl", +SERVICE_MAP = [ + { "title": "Alpen an den Rijn", + "api_url": "https://afvalkalender.alphenaandenrijn.nl", + }, + { "title": "Gemeente Cranendonck", + "api_url": "https://afvalkalender.cranendonck.nl", + }, + { "title": "Cyclus NV", + "api_url": "https://afvalkalender.cyclusnv.nl", + }, + { "title": "Dar", + "api_url": "https://afvalkalender.dar.nl", + }, + { "title": "Den Haag", + "api_url": "https://huisvuilkalender.denhaag.nl", + }, + { "title": "GAD", + "api_url": "https://inzamelkalender.gad.nl", + }, + { "title": "Gemeente Berkelland", + "api_url": "https://afvalkalender.gemeenteberkelland.nl", + }, + { "title": "HVC Groep", + "api_url": "https://inzamelkalender.hvcgroep.nl", + }, + { "title": "Gemeente Lingewaard", + "api_url": "https://afvalwijzer.lingewaard.nl", + }, + { "title": "Gemeente Middelburg + Vlissingen", + "api_url": "https://afvalwijzer.middelburgvlissingen.nl", + }, + { "title": "Mijn Blink", + "api_url": "https://mijnblink.nl", + }, + { "title": "Gemeente Peel en Maas", + "api_url": "https://afvalkalender.peelenmaas.nl", + }, + { "title": "PreZero", + "api_url": "https://inzamelwijzer.prezero.nl", + }, + { "title": "Purmerend", + "api_url": "https://afvalkalender.purmerend.nl", + }, + { "title": "Reinigingsbedrijf Midden Nederland", + "api_url": "https://inzamelschema.rmn.nl", + }, + { "title": "Gemeente Schouwen-Duiveland", + "api_url": "https://afvalkalender.schouwen-duiveland.nl", + }, + { "title": "Spaarne Landen", + "api_url": "https://afvalwijzer.spaarnelanden.nl", + }, + { "title": "Stadswerk 072", + "api_url": "https://www.stadswerk072.nl", + }, + { "title": "Gemeente Sudwest-Fryslan", + "api_url": "https://afvalkalender.sudwestfryslan.nl", + }, + { "title": "Gemeente Venray", + "api_url": "https://afvalkalender.venray.nl", + }, + { "title": "Gemeente Voorschoten", + "api_url": "https://afvalkalender.voorschoten.nl", + }, + { "title": "Gemeente Wallre", + "api_url": "https://afvalkalender.waalre.nl", + }, + { "title": "ZRD", + "api_url": "https://afvalkalender.zrd.nl", + }, +] + +def get_service_name_map(): + def extract_service_name(api_url): + name = api_url.split(".")[-2] + name = name.split("/")[-1] + return name + + return { extract_service_name(s["api_url"]):s["api_url"] for s in SERVICE_MAP } + +def get_main_url(url): + x = url.split(".")[-2:] + x[0] = x[0].removeprefix("https://") + return "https://" + ".".join(x) + +ICON_MAP = { + "plastic-blik-drinkpak": "mdi:recycle", + "gft": "mdi:leaf", + "papier-en-karton": "mdi:archive", + "restafval": "mdi:trash-can", } @@ -55,13 +123,7 @@ class Source: def __init__(self, postal_code, house_number, service="hvcgroep"): self.postal_code = postal_code self.house_number = house_number - self.icons = { - "plastic-blik-drinkpak": "mdi:recycle", - "gft": "mdi:leaf", - "papier-en-karton": "mdi:archive", - "restafval": "mdi:trash-can", - } - self._url = SERVICE_MAP[service] + self._url = get_service_name_map()[service] def fetch(self): @@ -97,7 +159,7 @@ class Source: Collection( date=datetime.strptime(item["ophaaldatum"], "%Y-%m-%d").date(), t=waste_details[0]["title"], - icon=self.icons.get(waste_details[0]["icon"], "mdi:trash-can"), + icon=ICON_MAP.get(waste_details[0]["icon"], "mdi:trash-can"), ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/infeo_at.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/infeo_at.py index b9201e8f..d69b27f6 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/infeo_at.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/infeo_at.py @@ -6,9 +6,16 @@ from waste_collection_schedule.service.ICS import ICS _LOGGER = logging.getLogger(__name__) -TITLE = "INFEO" +TITLE = "infeo" DESCRIPTION = "Source for INFEO waste collection." URL = "https://www.infeo.at/" +EXTRA_INFO = [ + { + "title": "Bogenschütz Entsorgung", + "url": "https://bogenschuetz-entsorgung.de", + "country": "de", + }, +] TEST_CASES = {"Bogenschütz": {"customer": "bogenschütz", "zone": "Dettenhausen"}} diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/innerwest_nsw_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/innerwest_nsw_gov_au.py index 46b99c11..cf3888ad 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/innerwest_nsw_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/innerwest_nsw_gov_au.py @@ -6,7 +6,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Inner West Council (NSW)" DESCRIPTION = "Source for Inner West Council (NSW) rubbish collection." -URL = "https://www.innerwest.nsw.gov.au/live/waste-and-recycling/bins-and-clean-ups/waste-calendar" +URL = "https://www.innerwest.nsw.gov.au" TEST_CASES = { "Random address": { "suburb": "Tempe", diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ipswich_qld_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ipswich_qld_gov_au.py index 591702fb..46c4ce48 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ipswich_qld_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ipswich_qld_gov_au.py @@ -7,14 +7,14 @@ from waste_collection_schedule import Collection TITLE = "Ipswich City Council" DESCRIPTION = "Source for Ipswich City Council rubbish collection." -URL = "https://www.ipswich.qld.gov.au/live/waste-and-recycling/bin-collection-calendar" +URL = "https://www.ipswich.qld.gov.au" TEST_CASES = { "Camira State School": {"street": "184-202 Old Logan Rd", "suburb": "Camira"}, "Random": {"street": "50 Brisbane Road", "suburb": "Redbank"}, } -ICONS = { +ICON_MAP = { "Waste Bin": "mdi:trash-can", "Recycle Bin": "mdi:recycle", "FOGO Bin": "mdi:leaf", @@ -91,7 +91,7 @@ class IpswichGovAuParser(HTMLParser): self._entries.append( Collection( - self._loaded_date, data, icon=ICONS.get(data, "mdi:trash-can") + self._loaded_date, data, icon=ICON_MAP.get(data, "mdi:trash-can") ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/kaev_niederlausitz.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kaev_niederlausitz.py index a9ac3429..88f1ef04 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/kaev_niederlausitz.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kaev_niederlausitz.py @@ -6,9 +6,9 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS TITLE = "KAEV Niederlausitz" -DESCRIPTION = "Source for Kommunaler Abfallverband niederlausitz waste collection." +DESCRIPTION = "Source for Kommunaler Abfallverband Niederlausitz waste collection." URL = "https://www.kaev.de/" -URL_ADDRESS = 'https://www.kaev.de/Templates/Content/DetailTourenplanWebsite/ajax.aspx/getAddress' +COUNTRY = "de" TEST_CASES = { "Luckau / OT Zieckau": { "abf_suche": "Luckau / OT Zieckau", @@ -21,11 +21,13 @@ TEST_CASES = { }, } +API_URL = 'https://www.kaev.de/Templates/Content/DetailTourenplanWebsite/ajax.aspx/getAddress' + def get_kalender_id(search): s=requests.Session() s.get('https://www.kaev.de/') payload={"query": search} - resp = s.post(URL_ADDRESS, json=payload).json() + resp = s.post(API_URL, json=payload).json() abf_cal = json.loads(resp["d"]) return abf_cal diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/kingston_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kingston_gov_uk.py index 484b3b40..e0aa859a 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/kingston_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kingston_gov_uk.py @@ -5,11 +5,11 @@ from datetime import datetime import requests from waste_collection_schedule import Collection -TITLE = "www.kingston.gov.uk" +TITLE = "The Royal Borough of Kingston Council" DESCRIPTION = ( "Source for waste collection services for The Royal Borough of Kingston Council" ) -URL = "https://kingston-self.achieveservice.com/service/in_my_area?displaymode=collections" +URL = "kingston.gov.uk" HEADERS = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/korneuburg_stadtservice_at.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/korneuburg_stadtservice_at.py index ac0c48c8..e53ef22d 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/korneuburg_stadtservice_at.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/korneuburg_stadtservice_at.py @@ -6,9 +6,14 @@ from bs4 import BeautifulSoup from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS # type: ignore[attr-defined] -TITLE = 'Müllabfuhr Korneuburg' +TITLE = 'Stadtservice Korneuburg' DESCRIPTION = 'Source for Stadtservice Korneuburg' URL = 'https://www.korneuburg.gv.at' +TEST_CASES = { + "Rathaus": {"street_name": "Hauptplatz", "street_number": 39}, # Teilgebiet 4 + "Rathaus using Teilgebiet": {"street_name": "SomeStreet", "street_number": "1A", "teilgebiet": "4"}, # Teilgebiet 4 + "Werft": {"street_name": "Am Hafen", "street_number": 6} # Teilgebiet 2 +} # Mapping of teilgebiete to calendar urls WASTE_TYPE_URLS = { @@ -18,12 +23,6 @@ WASTE_TYPE_URLS = { '4': ('Biomuell_2', 'Restmuell', 'Papier', 'Gelber_Sack_3') } -TEST_CASES = { - "Rathaus": {"street_name": "Hauptplatz", "street_number": 39}, # Teilgebiet 4 - "Rathaus using Teilgebiet": {"street_name": "SomeStreet", "street_number": "1A", "teilgebiet": "4"}, # Teilgebiet 4 - "Werft": {"street_name": "Am Hafen", "street_number": 6} # Teilgebiet 2 -} - class Source: def __init__(self, street_name, street_number, teilgebiet=-1): diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/kwb_goslar_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kwb_goslar_de.py index 94a755da..1f8f765a 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/kwb_goslar_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kwb_goslar_de.py @@ -2,9 +2,9 @@ import requests from waste_collection_schedule import Collection from waste_collection_schedule.service.ICS import ICS -TITLE = "KreisWirtschaftsBetriebe Goslar" +TITLE = "Kreiswirtschaftsbetriebe Goslar" DESCRIPTION = "Source for kwb-goslar.de waste collection." -URL = "https://www.kwb-goslar.de/Abfallwirtschaft/Abfuhr/" +URL = "https://www.kwb-goslar.de" TEST_CASES = { "Berliner Straße (Clausthal-Zellerfeld)": {"pois": "2523.602"}, "Braunschweiger Straße (Seesen)": {"pois": "2523.409"}, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/kwu_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kwu_de.py index cf5b0e3f..ad8ed4e1 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/kwu_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kwu_de.py @@ -7,13 +7,18 @@ from waste_collection_schedule.service.ICS import ICS TITLE = "KWU Entsorgung Landkreis Oder-Spree" DESCRIPTION = "Source for KWU Entsorgung, Germany" URL = "https://www.kwu-entsorgung.de/" - TEST_CASES = { "Erkner": {"city": "Erkner", "street": "Heinrich-Heine-Straße", "number": "11"}, "Bad Saarow": {"city": "Bad Saarow", "street": "Ahornallee", "number": "1"} } HEADERS = {"user-agent": "Mozilla/5.0 (xxxx Windows NT 10.0; Win64; x64)"} +ICON_MAP = { + "Restabfall": "mdi:trash-can-outline", + "Gelber Sack" : "mdi:recycle", + "Papiertonne" : "mdi:package-variant", + "Biotonne": "mdi:food-apple-outline", +} class Source: @@ -22,12 +27,6 @@ class Source: self._street = street self._number = number self._ics = ICS() - self._iconMap = { - "Restabfall": "mdi:trash-can-outline", - "Gelber Sack" : "mdi:recycle", - "Papiertonne" : "mdi:package-variant", - "Biotonne": "mdi:food-apple-outline", - } def fetch(self): session = requests.Session() @@ -94,7 +93,7 @@ class Source: waste_type = d[1].strip() next_pickup_date = d[0] - entries.append(Collection(date=next_pickup_date, t=waste_type, icon=self._iconMap.get(waste_type,"mdi:trash-can"))) + entries.append(Collection(date=next_pickup_date, t=waste_type, icon=ICON_MAP.get(waste_type,"mdi:trash-can"))) return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/landkreis_rhoen_grabfeld.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/landkreis_rhoen_grabfeld.py index c1f16c05..430afe52 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/landkreis_rhoen_grabfeld.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/landkreis_rhoen_grabfeld.py @@ -3,18 +3,10 @@ import datetime import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "Source for Rhön Grabfeld" +TITLE = "Landkreis Rhön Grabfeld" DESCRIPTION = "Source for Rhönn Grabfeld uses service by offizium." -URL = 'https://fs-api-rg.offizium.com/abfalltermine' -ICON_MAP = { - "Restmüll/Gelber Sack/Biotonne": "mdi:trash-can", - "Papiersammlung": "mdi:package-variant", - "Problemmüllsammlung": "mdi:biohazard" -} -EVENT_BLACKLIST = ['Wertstoffhof Mellrichstadt', - 'Wertstoffhof Bad Königshofen', 'Wertstoffzentrum Bad Neustadt', - 'Wertstoffsammelstelle Ostheim', - 'Wertstoffsammelstelle Bischofsheim'] +URL = "https://www.abfallinfo-rhoen-grabfeld.de/" +COUNTRY = "de" TEST_CASES = { "City only": {"city": "Ostheim"}, "City + District": {"city": "Ostheim", "district": "Oberwaldbehrungen"}, @@ -22,6 +14,19 @@ TEST_CASES = { "empty": {} } +API_URL = 'https://fs-api-rg.offizium.com/abfalltermine' + +EVENT_BLACKLIST = ['Wertstoffhof Mellrichstadt', + 'Wertstoffhof Bad Königshofen', 'Wertstoffzentrum Bad Neustadt', + 'Wertstoffsammelstelle Ostheim', + 'Wertstoffsammelstelle Bischofsheim'] + +ICON_MAP = { + "Restmüll/Gelber Sack/Biotonne": "mdi:trash-can", + "Papiersammlung": "mdi:package-variant", + "Problemmüllsammlung": "mdi:biohazard" +} + class Source: def __init__(self, city: str = None, district: str = None): @@ -31,7 +36,7 @@ class Source: def fetch(self): now = datetime.datetime.now().date() - r = requests.get(URL, params={ + r = requests.get(API_URL, params={ "stadt": self._city, "ortsteil": self._district }) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/landkreis_wittmund_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/landkreis_wittmund_de.py index df3a3f14..8ad0ab90 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/landkreis_wittmund_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/landkreis_wittmund_de.py @@ -4,12 +4,9 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS from bs4 import BeautifulSoup -TITLE = "Landkreis-Wittmund.de" +TITLE = "Landkreis Wittmund" DESCRIPTION = "Source for Landkreis Wittmund waste collection." -URL = "https://www.landkreis-wittmund.de/Leben-Wohnen/Wohnen/Abfall/Abfuhrkalender/" -AUTOCOMPLETE_URL = "https://www.landkreis-wittmund.de/output/autocomplete.php?out=json&type=abto&mode=&select=2&refid={}&term=" -DOWNLOAD_URL = "https://www.landkreis-wittmund.de/output/options.php?ModID=48&call=ical&ArtID%5B0%5D=3105.1&ArtID%5B1%5D=1.4&ArtID%5B2%5D=1.2&ArtID%5B3%5D=1.3&ArtID%5B4%5D=1.1&pois={}&alarm=0" - +URL = "https://www.landkreis-wittmund.de" TEST_CASES = { "CityWithoutStreet": { "city": "Werdum", @@ -20,6 +17,11 @@ TEST_CASES = { }, } +API_URL = "https://www.landkreis-wittmund.de/Leben-Wohnen/Wohnen/Abfall/Abfuhrkalender/" +AUTOCOMPLETE_URL = "https://www.landkreis-wittmund.de/output/autocomplete.php?out=json&type=abto&mode=&select=2&refid={}&term=" +DOWNLOAD_URL = "https://www.landkreis-wittmund.de/output/options.php?ModID=48&call=ical&ArtID%5B0%5D=3105.1&ArtID%5B1%5D=1.4&ArtID%5B2%5D=1.2&ArtID%5B3%5D=1.3&ArtID%5B4%5D=1.1&pois={}&alarm=0" + + class Source: def __init__(self, city, street=None): self._city = city @@ -36,11 +38,11 @@ class Source: return tag['value'] != "" and tag.string == self._city def fetch_city_id(self, cityName): - r = requests.get(URL) + r = requests.get(API_URL) if not r.ok: raise Exception( "Error: failed to fetch url: {}".format( - URL + API_URL ) ) @@ -64,7 +66,7 @@ class Source: def fetch_street_id(self, cityId, streetName): r = requests.get(AUTOCOMPLETE_URL.format(cityId, streetName), headers={ - "Referer": URL + "Referer": API_URL }) if not r.ok: @@ -100,7 +102,7 @@ class Source: def fetch_ics(self, url): r = requests.get(url, headers={ - "Referer": URL + "Referer": API_URL }) if not r.ok: diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/lerum_se.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/lerum_se.py index f761e770..6927a878 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/lerum_se.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/lerum_se.py @@ -8,7 +8,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Lerum Vatten och Avlopp" DESCRIPTION = "Source for Lerum Vatten och Avlopp waste collection." -URL = "https://vatjanst.lerum.se/FutureWeb/SimpleWastePickup/SimpleWastePickup" +URL = "https://vatjanst.lerum.se" TEST_CASES = { "PRO": {"street_address": "Floda stationsväg 5, Floda"}, "Polisen": {"street_address": "Göteborgsvägen 16, Lerum"}, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/lewisham_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/lewisham_gov_uk.py index 4272d6a4..b0e03193 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/lewisham_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/lewisham_gov_uk.py @@ -9,8 +9,7 @@ TITLE = "London Borough of Lewisham" DESCRIPTION = ( "Source for services from the London Borough of Lewisham" ) - -URL = "lewisham.gov.uk" +URL = "https://lewisham.gov.uk" TEST_CASES = { "houseNumber": {"post_code": "SE41LR", "number": 4}, "houseName": {"post_code": "SE233TE", "name": "The Haven"}, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/lindau_ch.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/lindau_ch.py index 8fd31cb5..1efb835a 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/lindau_ch.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/lindau_ch.py @@ -5,16 +5,16 @@ import requests from bs4 import BeautifulSoup from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "Abfall Lindau" +TITLE = "Lindau" DESCRIPTION = "Source for Lindau waste collection." -URL = "https://www.lindau.ch/abfalldaten" +URL = "https://www.lindau.ch" TEST_CASES = { "Tagelswangen": {"city": "Tagelswangen"}, "Grafstal": {"city": "190"}, } -IconMap = { +ICON_MAP = { "kehricht": "mdi:trash-can", "grungut": "mdi:leaf", "hackseldienst": "mdi:leaf", @@ -51,7 +51,7 @@ class Source: Collection( date=next_pickup_date, t=waste_type, - icon=IconMap.get(waste_type_sorted, "mdi:trash-can"), + icon=ICON_MAP.get(waste_type_sorted, "mdi:trash-can"), ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/lrasha_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/lrasha_de.py index fe32359a..f1d8f796 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/lrasha_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/lrasha_de.py @@ -6,13 +6,12 @@ from waste_collection_schedule.service.ICS import ICS TITLE = "Landkreis Schwäbisch Hall" DESCRIPTION = "Source for lrasha.de - Landkreis Schwäbisch Hall" -URL = "http://exchange.cmcitymedia.de/landkreis-schwaebisch-hallt3/wasteCalendarExport.php?location=" -# https://www.lrasha.de/de/buergerservice/abfallwirtschaft/abfallkalender - +URL = "https://www.lrasha.de" TEST_CASES = { "Ilshofen": {"location": "114"} } +API_URL = "http://exchange.cmcitymedia.de/landkreis-schwaebisch-hallt3/wasteCalendarExport.php?location=" HEADERS = {"user-agent": "Mozilla/5.0 (xxxx Windows NT 10.0; Win64; x64)"} @@ -23,7 +22,7 @@ class Source: def fetch(self): # get ics file - full_url = URL + str(self._location) + full_url = API_URL + str(self._location) r = requests.get(full_url, headers=HEADERS) r.raise_for_status() diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/manchester_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/manchester_uk.py index a6c935e6..61f35482 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/manchester_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/manchester_uk.py @@ -7,14 +7,15 @@ from bs4 import BeautifulSoup from urllib.parse import urlsplit, parse_qs import logging -TITLE = "manchester.gov.uk" +TITLE = "Manchester City Council" DESCRIPTION = "Source for bin collection services for Manchester City Council, UK." -URL = "https://www.manchester.gov.uk/bincollections/" +URL = "https://www.manchester.gov.uk" TEST_CASES = { "domestic": {'uprn': '000077065560'}, } -ICONS = { +API_URL = "https://www.manchester.gov.uk/bincollections/" +ICON_MAP = { "Black / Grey Bin": "mdi:trash-can", "Blue Bin": "mdi:recycle", "Brown Bin": "mdi:glass-fragile", @@ -39,7 +40,7 @@ class Source: entries = [] r = requests.post( - URL, + API_URL, data={ "mcc_bin_dates_uprn": self._uprn, "mcc_bin_dates_submit": "Go" @@ -64,7 +65,7 @@ class Source: Collection( date=date, t=collection_type, - icon=ICONS[collection_type], + icon=ICON_MAP[collection_type], ) ) except ValueError: diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/melton_vic_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/melton_vic_gov_au.py index f7ed62e7..8af37097 100755 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/melton_vic_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/melton_vic_gov_au.py @@ -8,7 +8,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Melton City Council" DESCRIPTION = "Source for Melton City Council rubbish collection." -URL = "https://www.melton.vic.gov.au/My-Area" +URL = "https://www.melton.vic.gov.au" TEST_CASES = { "Tuesday A": {"street_address": "23 PILBARA AVENUE BURNSIDE 3023"}, "Tuesday B": {"street_address": "29 COROWA CRESCENT BURNSIDE 3023"}, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/middlesbrough_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/middlesbrough_gov_uk.py index 85930e84..1e55946b 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/middlesbrough_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/middlesbrough_gov_uk.py @@ -5,21 +5,9 @@ import time from datetime import datetime from waste_collection_schedule import Collection -TITLE = 'middlesbrough.gov.uk' -DESCRIPTION = ( - 'Source for waste collection services for Middlesbrough Council' -) -URL = 'https://www.middlesbrough.gov.uk/bin-collection-dates' - - -HEADERS = { - "user-agent": "Mozilla/5.0", -} - -COOKIES = { - -} - +TITLE = 'Middlesbrough Council' +DESCRIPTION = 'Source for waste collection services for Middlesbrough Council' +URL = 'https://www.middlesbrough.gov.uk' TEST_CASES = { "Tollesby Road - number" : {"uprn": 100110140843}, "Tollesby Road - string" : {"uprn": "100110140843"}, @@ -33,6 +21,13 @@ API_URLS = { 'schedule': 'https://my.middlesbrough.gov.uk/apibroker/runLookup?id=5d78f40439054&repeat_against=&noRetry=true&getOnlyTokens=undefined&log_id=&app_name=AF-Renderer::Self&' } +HEADERS = { + "user-agent": "Mozilla/5.0", +} + +COOKIES = { +} + _LOGGER = logging.getLogger(__name__) class Source: diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/miljoteknik_se.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/miljoteknik_se.py index c1d6a039..840b9ce7 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/miljoteknik_se.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/miljoteknik_se.py @@ -33,15 +33,15 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] # those bins, only for the so called "Fyrfack" bins (meaning four slots). # -TITLE = "Ronneby Miljöteknik Sophämntning" +TITLE = "Ronneby Miljöteknik" DESCRIPTION = "Source for Ronneby Miljöteknik waste collection." -URL = ( - "http://www.fyrfackronneby.se/hamtningskalender/" -) +URL = "http://www.fyrfackronneby.se" TEST_CASES = { "Home": {"street_address": "Hjortsbergavägen 16, Johannishus"} } +API_URL = "http://www.fyrfackronneby.se/hamtningskalender/" + class Source: def __init__(self, street_address): diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/minrenovasjon_no.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/minrenovasjon_no.py index 9ddfbdef..a66b4a2b 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/minrenovasjon_no.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/minrenovasjon_no.py @@ -9,7 +9,7 @@ from pprint import pprint TITLE = "Min Renovasjon" DESCRIPTION = "Source for Norkart Komtek MinRenovasjon (Norway)." -URL = "https://www.norkart.no/komtek/renovasjon/" +URL = "https://www.norkart.no" # **street_code:** \ # **county_id:** \ @@ -29,8 +29,28 @@ TEST_CASES = { } } -BASE_URL = "https://komteksky.norkart.no/komtek.renovasjonwebapi/api/" +API_URL = "https://komteksky.norkart.no/komtek.renovasjonwebapi/api/" APP_KEY = "AE13DEEC-804F-4615-A74E-B4FAC11F0A30" +ICON_MAP = { + "": "mdi:trash-can", + "brush": "mdi:trash-can", + "elektriskogelektronisk": "mdi:chip", + "farligavfall": "mdi:trash-can", + "glassogmetallemballasje": "mdi:trash-can", + "hageavfall": "mdi:leaf", + "klaerogsko": "mdi:hanger", + "matavfall": "mdi:trash-can", + "matrestavfall": "mdi:trash-can", + "matrestavfallplast": "mdi:trash-can", + "metall": "mdi:trash-can", + "papir": "mdi:newspaper-variant-multiple", + "pappogkartong": "mdi:archive", + "plastemballasje": "mdi:trash-can", + "restavfall": "mdi:trash-can", + "drikkekartong": "mdi:newspaper-variant-multiple", + "papppapirdrikkekartong": "mdi:newspaper-variant-multiple", + "trevirke": "mdi:trash-can", + } class Source: def __init__(self, street_name, house_number, street_code, county_id): @@ -38,28 +58,6 @@ class Source: self._house_number = house_number self._street_code = street_code self._county_id = county_id - self._icon_map = { - "": "mdi:trash-can", - "brush": "mdi:trash-can", - "elektriskogelektronisk": "mdi:chip", - "farligavfall": "mdi:trash-can", - "glassogmetallemballasje": "mdi:trash-can", - "hageavfall": "mdi:leaf", - "klaerogsko": "mdi:hanger", - "matavfall": "mdi:trash-can", - "matrestavfall": "mdi:trash-can", - "matrestavfallplast": "mdi:trash-can", - "metall": "mdi:trash-can", - "papir": "mdi:newspaper-variant-multiple", - "pappogkartong": "mdi:archive", - "plastemballasje": "mdi:trash-can", - "restavfall": "mdi:trash-can", - "drikkekartong": "mdi:newspaper-variant-multiple", - "papppapirdrikkekartong": "mdi:newspaper-variant-multiple", - "trevirke": "mdi:trash-can" - - - } def fetch(self): headers = { @@ -69,15 +67,13 @@ class Source: } args = {} - r = requests.get(BASE_URL + 'fraksjoner', params = args, headers = headers) + r = requests.get(API_URL + 'fraksjoner', params = args, headers = headers) type = {} for f in json.loads(r.content): # pprint(f) - icon = "mdi:trash-can" icon_name = re.sub(r"^.*?/(\w+)\.\w{3,4}$", "\\1", f['Ikon']) - if icon_name in self._icon_map: - icon = self._icon_map[icon_name] + icon = ICON_MAP.get(icon_name, "mdi:trash-can") type[f['Id']] = { 'name': f['Navn'], 'image': f['Ikon'], @@ -88,10 +84,9 @@ class Source: 'gatenavn': self._street_name, 'husnr': self._house_number, 'gatekode': self._street_code, - } - r = requests.get(BASE_URL + 'tommekalender', params = args, headers = headers) + r = requests.get(API_URL + 'tommekalender', params = args, headers = headers) entries = [] for f in json.loads(r.content): diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/mrsc_vic_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/mrsc_vic_gov_au.py index f0dec057..f0d42cdf 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/mrsc_vic_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/mrsc_vic_gov_au.py @@ -8,7 +8,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Macedon Ranges Shire Council" DESCRIPTION = "Source for Macedon Ranges Shire Council rubbish collection." -URL = "https://www.mrsc.vic.gov.au/Live-Work/Bins-Rubbish-Recycling/Bins-and-collection-days/Bin-collection-days" +URL = "https://www.mrsc.vic.gov.au" TEST_CASES = { "Macedon IGA": {"street_address": "20 Victoria Street, Macedon"}, "ALDI Gisborne": {"street_address": "45 Aitken Street, Gisborne"}, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/newcastle_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/newcastle_gov_uk.py index 4e7a2b4e..0542396f 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/newcastle_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/newcastle_gov_uk.py @@ -14,26 +14,26 @@ urllib3.disable_warnings() _LOGGER = logging.getLogger(__name__) -TITLE = "newcastle.gov.uk" -DESCRIPTION = ( - """Source for waste collection services for Newcastle City Council""" -) -URL = "https://community.newcastle.gov.uk/my-neighbourhood/ajax/getBinsNew.php" -REGEX = "(Green|Blue|Brown) [bB]in \\((Domestic|Recycling|Garden)( Waste)?\\) details: <\\/strong>" \ - "collection day : [a-zA-Z]*day" \ - "Next collection : ([0-9]{2}-[A-Za-z]+-[0-9]{4})" -ICONS = { - "DOMESTIC": "mdi:trash-can", - "RECYCLING": "mdi:recycle", - "GARDEN": "mdi:leaf", -} - +TITLE = "Newcastle City Council" +DESCRIPTION = "Source for waste collection services for Newcastle City Council" +URL = "https://community.newcastle.gov.uk" TEST_CASES = { "Test_001": {"uprn": "004510053797"}, "Test_002": {"uprn": 4510053797} } +API_URL = "https://community.newcastle.gov.uk/my-neighbourhood/ajax/getBinsNew.php" +REGEX = "(Green|Blue|Brown) [bB]in \\((Domestic|Recycling|Garden)( Waste)?\\) details: <\\/strong>" \ + "collection day : [a-zA-Z]*day" \ + "Next collection : ([0-9]{2}-[A-Za-z]+-[0-9]{4})" +ICON_MAP = { + "DOMESTIC": "mdi:trash-can", + "RECYCLING": "mdi:recycle", + "GARDEN": "mdi:leaf", +} + + class Source: def __init__(self, uprn=None): self._uprn = str(uprn).zfill(12) @@ -56,7 +56,7 @@ class Source: Collection( date=datetime.strptime(collection_date, '%d-%b-%Y').date(), t=collection_type, - icon=ICONS.get(collection_type.upper()), + icon=ICON_MAP.get(collection_type.upper()), ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/nottingham_city_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/nottingham_city_gov_uk.py index 7c59774f..7f60304b 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/nottingham_city_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/nottingham_city_gov_uk.py @@ -5,7 +5,7 @@ import time import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "nottinghamcity.gov.uk" +TITLE = "Nottingham City Council" DESCRIPTION = "Source for nottinghamcity.gov.uk services for the city of Nottingham, UK." URL = "https://nottinghamcity.gov.uk" TEST_CASES = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/nsomerset_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/nsomerset_gov_uk.py index 74efd6a0..83b0b616 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/nsomerset_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/nsomerset_gov_uk.py @@ -5,7 +5,7 @@ import requests from bs4 import BeautifulSoup from waste_collection_schedule import Collection -TITLE = "North Somerset.gov.uk" +TITLE = "North Somerset Council" DESCRIPTION = "Source for n-somerset.gov.uk services for North Somerset, UK." URL = "n-somerset.gov.uk" diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/oslokommune_no.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/oslokommune_no.py index 87a7d594..064627e4 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/oslokommune_no.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/oslokommune_no.py @@ -9,7 +9,7 @@ from pprint import pprint TITLE = "Oslo Kommune" DESCRIPTION = "Oslo Kommune (Norway)." -URL = "https://www.oslo.kommune.no/avfall-og-gjenvinning/nar-hentes-avfallet/" +URL = "https://www.oslo.kommune.no" # **street_code:** \ # **county_id:** \ @@ -29,7 +29,12 @@ TEST_CASES = { } } -BASE_URL = "https://www.oslo.kommune.no/xmlhttprequest.php" +API_URL = "https://www.oslo.kommune.no/xmlhttprequest.php" +ICON_MAP = { + "": "mdi:trash-can", + "restavfall": "mdi:trash-can", + "papir": "mdi:newspaper-variant-multiple" +} class Source: def __init__(self, street_name, house_number, house_letter, street_id): @@ -37,11 +42,6 @@ class Source: self._house_number = house_number self._house_letter = house_letter self._street_id = street_id - self._icon_map = { - "": "mdi:trash-can", - "restavfall": "mdi:trash-can", - "papir": "mdi:newspaper-variant-multiple" - } def fetch(self): headers = { @@ -56,7 +56,7 @@ class Source: 'street_id': self._street_id, } - r = requests.get(BASE_URL, params = args, headers = headers) + r = requests.get(API_URL, params = args, headers = headers) entries = [] res = json.loads(r.content)['data']['result'][0]['HentePunkts'] @@ -70,7 +70,7 @@ class Source: tjeneste['TommeDato'], "%d.%m.%Y" ).date(), t = tekst, - icon = self._icon_map[tekst.lower()] or "mdi:trash-can" + icon = ICON_MAP.get(tekst.lower(), "mdi:trash-can") ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/peterborough_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/peterborough_gov_uk.py index afa6ca0f..f4859667 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/peterborough_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/peterborough_gov_uk.py @@ -4,11 +4,11 @@ import datetime import requests from waste_collection_schedule import Collection -TITLE = "Peterborough.gov.uk" +TITLE = "Peterborough City Council" DESCRIPTION = ( "Source for peterborough.gov.uk services for Peterborough" ) -URL = "peterborough.gov.uk" +URL = "https://peterborough.gov.uk" TEST_CASES = { "houseNumber": {"post_code": "PE57AX", "number": 1}, "houseName": {"post_code": "PE57AX", "name": "CASTOR HOUSE"}, @@ -20,7 +20,7 @@ API_URLS = { "collection": "https://www.peterborough.gov.uk/api/jobs/{start}/{end}/{uprn}", } -ICONS = { +ICON_MAP = { "Empty Bin 240L Black": "mdi:trash-can", "Empty Bin 240L Green": "mdi:recycle", "Empty Bin 240L Brown": "mdi:leaf", @@ -74,7 +74,7 @@ class Source: collection["nextDate"], "%Y-%m-%dT%H:%M:%S" ).date(), t=collection["jobDescription"], - icon=ICONS.get(collection["jobDescription"]), + icon=ICON_MAP.get(collection["jobDescription"]), ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/pgh_st.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/pgh_st.py index 678f132e..aebd8e74 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/pgh_st.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/pgh_st.py @@ -5,10 +5,10 @@ from urllib.parse import quote import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "PGH.ST" +TITLE = "City of Pittsburgh" DESCRIPTION = "Source for PGH.ST services for the city of Pittsburgh, PA, USA." -URL = "http://www.pgh.st" -TEST_CASES = {} +URL = "https://www.pgh.st" +COUNTRY = "us" TEST_CASES = { "Pittsburgh, Negley": { "house_number": 800, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/recyclesmart_com.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/recyclesmart_com.py index c9d28064..cbb3da6f 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/recyclesmart_com.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/recyclesmart_com.py @@ -7,6 +7,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "RecycleSmart" DESCRIPTION = "Source for RecycleSmart collection." URL = "https://www.recyclesmart.com/" +COUNTRY = "au" TEST_CASES = { "pickup": { "email": "!secret recyclesmart_email", diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/regioentsorgung_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/regioentsorgung_de.py index 83c9909f..7ae988bf 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/regioentsorgung_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/regioentsorgung_de.py @@ -2,15 +2,14 @@ import requests from waste_collection_schedule import Collection from waste_collection_schedule.service.ICS import ICS -TITLE = "RegioEntsorgung" +TITLE = "RegioEntsorgung Städteregion Aachen" DESCRIPTION = "RegioEntsorgung Städteregion Aachen" -URL = "https://regioentsorgung.de/service/abfallkalender/" - +URL = "https://regioentsorgung.de" TEST_CASES = { "Merzbrück": {"city": "Würselen", "street": "Merzbrück", "house_number": 200 }, } -BASE_URL = "https://tonnen.regioentsorgung.de/WasteManagementRegioentsorgung/WasteManagementServlet" +API_URL = "https://tonnen.regioentsorgung.de/WasteManagementRegioentsorgung/WasteManagementServlet" HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)", @@ -31,7 +30,7 @@ class Source: payload = { 'SubmitAction': 'wasteDisposalServices', } - r = session.get(BASE_URL, headers=HEADERS, params=payload) + r = session.get(API_URL, headers=HEADERS, params=payload) r.raise_for_status() payload = { @@ -41,7 +40,7 @@ class Source: 'Strasse': '', 'Hausnummer': '', } - r = session.post(BASE_URL, headers=HEADERS, data=payload) + r = session.post(API_URL, headers=HEADERS, data=payload) r.raise_for_status() payload = { @@ -51,7 +50,7 @@ class Source: 'Strasse': self.street, 'Hausnummer': '', } - r = session.post(BASE_URL, headers=HEADERS, data=payload) + r = session.post(API_URL, headers=HEADERS, data=payload) r.raise_for_status() payload = { @@ -61,14 +60,14 @@ class Source: 'Strasse': self.street, 'Hausnummer': self.house_number, } - r = session.post(BASE_URL, headers=HEADERS, data=payload) + r = session.post(API_URL, headers=HEADERS, data=payload) r.raise_for_status() payload = { 'ApplicationName': 'com.athos.kd.regioentsorgung.AbfuhrTerminModel', 'SubmitAction': 'forward', } - r = session.post(BASE_URL, headers=HEADERS, data=payload) + r = session.post(API_URL, headers=HEADERS, data=payload) r.raise_for_status() payload = { @@ -86,7 +85,7 @@ class Source: 'ICalZeit': '06:00 Uhr', 'SubmitAction': 'filedownload_ICAL', } - r = session.post(BASE_URL, headers=HEADERS, data=payload) + r = session.post(API_URL, headers=HEADERS, data=payload) r.raise_for_status() # Parse ics file diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/republicservices_com.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/republicservices_com.py index 4c4eddd4..d163c766 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/republicservices_com.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/republicservices_com.py @@ -7,6 +7,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Republic Services" DESCRIPTION = "Source for Republic Services Collection." URL = "https://www.republicservices.com" +COUNTRY = "us" TEST_CASES = { "Scott Country Clerk": {"street_address": "101 E Main St, Georgetown, KY 40324"}, "Branch County Clerk": {"street_address": "31 Division St. Coldwater, MI 49036"} diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/rh_entsorgung_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/rh_entsorgung_de.py index a2225889..d63d758e 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/rh_entsorgung_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/rh_entsorgung_de.py @@ -4,7 +4,7 @@ import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "RH Entsorgung" +TITLE = "Rhein-Hunsrück Entsorgung (RHE)" DESCRIPTION = "Source for RHE (Rhein Hunsrück Entsorgung)." URL = "https://www.rh-entsorgung.de" TEST_CASES = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/richmondshire_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/richmondshire_gov_uk.py index e6349f67..119e74f1 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/richmondshire_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/richmondshire_gov_uk.py @@ -3,16 +3,16 @@ from datetime import datetime import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "Richmondshire (North Yorkshire)" +TITLE = "Richmondshire District Council" DESCRIPTION = "To find your UPRN, visit the Richmondshire page and use the address search. Right-click your entry in the house dropdown, choose Inspect, and copy the UPRN from the value" -URL = "https://www.richmondshire.gov.uk/collectionCalendar" +URL = "https://www.richmondshire.gov.uk" TEST_CASES = { "test1": {"uprn": 200001767082}, "test2": {"uprn": 200001767078}, "test3": {"uprn": 200001767079}, } -ICONS = { +ICON_MAP = { "240L GREY RUBBISH BIN": "mdi:trash-can", "55L RECYCLING BOX": "mdi:recycle", "140L GARDEN BIN": "mdi:leaf", @@ -38,7 +38,7 @@ class Source: Collection( date=datetime.strptime(id["start"], "%Y-%m-%dT%H:%M:%S").date(), t=id["title"], - icon=ICONS.get(id["title"]), + icon=ICON_MAP.get(id["title"]), ) ) return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/rushmoor_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/rushmoor_gov_uk.py index 5ee7e3f5..af7461bf 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/rushmoor_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/rushmoor_gov_uk.py @@ -2,9 +2,8 @@ import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "rushmoor.gov.uk" +TITLE = "Rushmoor Borough Council" DESCRIPTION = "Source for rushmoor.gov.uk services for Rushmoor, UK." -# Find the UPRN of your address using https://www.findmyaddress.co.uk/search URL = "https://rushmoor.gov.uk" TEST_CASES = { "GU14": {"uprn": "100060551749"}, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/sbazv_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/sbazv_de.py index dad2e2fb..ee21b1d5 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/sbazv_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/sbazv_de.py @@ -5,9 +5,9 @@ import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "Abfall SBAZV" +TITLE = "Südbrandenburgischer Abfallzweckverband" DESCRIPTION = "SBAZV Brandenburg, Deutschland" -URL = "https://www.sbazv.de/entsorgungstermine/klein.ics" +URL = "https://www.sbazv.de" TEST_CASES = { "Wildau": { "city": "wildau", @@ -16,21 +16,23 @@ TEST_CASES = { } } +ICON_MAP = { + "Restmülltonnen": "mdi:trash-can", + "Laubsäcke" : "mdi:leaf", + "Gelbe Säcke" : "mdi:sack", + "Papiertonnen" : "mdi:package-variant", + "Weihnachtsbäume": "mdi:pine-tree", +} + # _LOGGER = logging.getLogger(__name__) + class Source: def __init__(self, city, district, street=None): self._city = city self._district = district self._street = street self._ics = ICS() - self._iconMap = { - "Restmülltonnen": "mdi:trash-can", - "Laubsäcke" : "mdi:leaf", - "Gelbe Säcke" : "mdi:sack", - "Papiertonnen" : "mdi:package-variant", - "Weihnachtsbäume": "mdi:pine-tree", - } def fetch(self): now = datetime.now() @@ -68,6 +70,6 @@ class Source: waste_type = d[1].strip() next_pickup_date = d[0] - entries.append(Collection(date=next_pickup_date, t=waste_type, icon=self._iconMap.get(waste_type,"mdi:trash-can"))) + entries.append(Collection(date=next_pickup_date, t=waste_type, icon=ICON_MAP.get(waste_type,"mdi:trash-can"))) return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/scambs_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/scambs_gov_uk.py index 177b5165..56427d99 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/scambs_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/scambs_gov_uk.py @@ -4,20 +4,21 @@ from datetime import datetime import requests from waste_collection_schedule import Collection -TITLE = "Scambs.gov.uk" +TITLE = "South Cambridgeshire District Council" DESCRIPTION = ( "Source for scambs.gov.uk services for South Cambridgeshire District Council" ) -URL = "scambs.gov.uk" +URL = "https://scambs.gov.uk" TEST_CASES = { "houseNumber": {"post_code": "CB236GZ", "number": 53}, "houseName": {"post_code": "CB225HT", "number": "Rectory Farm Cottage"}, } + API_URLS = { "address_search": "https://servicelayer3c.azure-api.net/wastecalendar/address/search/", "collection": "https://servicelayer3c.azure-api.net/wastecalendar/collection/search/{}/", } -ICONS = { +ICON_MAP = { "DOMESTIC": "mdi:trash-can", "RECYCLE": "mdi:recycle", "ORGANIC": "mdi:leaf", @@ -68,7 +69,7 @@ class Source: round_type, round_type.title() ), # returns concise values: Black Bin, Blue Bin, Green Bin # t = round_type.title(), # returns standard Scambs values: Black Bin Collection, Blue Bin Collection, Green Bin Collection - icon=ICONS.get(round_type), + icon=ICON_MAP.get(round_type), ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/seattle_gov.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/seattle_gov.py index a03ce1e5..c831b27f 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/seattle_gov.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/seattle_gov.py @@ -6,7 +6,8 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Seattle Public Utilities" DESCRIPTION = "Source for Seattle Public Utilities waste collection." -URL = "https://myutilities.seattle.gov/eportal/#/accountlookup/calendar" +URL = "https://myutilities.seattle.gov" +COUNTRY = "us" TEST_CASES = { "City Hall": {"street_address": "600 4th Ave"}, "Ballard Builders": {"street_address": "7022 12th Ave NW"}, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/sector27_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/sector27_de.py index 805ca2d1..24c4a2f6 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/sector27_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/sector27_de.py @@ -6,7 +6,7 @@ import re import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "Sector 27" +TITLE = "Sector 27 - Datteln, Marl, Oer-Erkenschwick" DESCRIPTION = "Source for Muellkalender in Kreis RE." URL = "https://muellkalender.sector27.de" TEST_CASES = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/sheffield_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/sheffield_gov_uk.py index bba1eb8d..2ff042c9 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/sheffield_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/sheffield_gov_uk.py @@ -4,20 +4,9 @@ from dateutil import parser import logging from waste_collection_schedule import Collection -TITLE = "Sheffield.gov.uk" - -DESCRIPTION = ( - "Source for waste collection services from Sheffield City Council (SCC)" -) - -# Base URL for waste collection services -URL = "https://wasteservices.sheffield.gov.uk/" - -# Headers to mimic the browser -HEADERS = { - "user-agent": "Mozilla/5.0", -} - +TITLE = "Sheffield City Council" +DESCRIPTION = "Source for waste collection services from Sheffield City Council (SCC)" +URL = "https://sheffield.gov.uk/" TEST_CASES = { # These are random addresses around Sheffield # If your property is listed here and you don't want it, please raise an issue and I'll amend @@ -26,8 +15,16 @@ TEST_CASES = { "test003" : {"uprn": "100050920796"}, } + +API_URL = "https://wasteservices.sheffield.gov.uk/" + +# Headers to mimic the browser +HEADERS = { + "user-agent": "Mozilla/5.0", +} + # Icons for the different bin types -ICONS = { +ICON_MAP = { "BLACK": "mdi:delete-empty", # General Waste "BROWN": "mdi:glass-fragile", # Glass, Tins, Cans & Plastics "BLUE": "mdi:newspaper", # Paper & Cardboard @@ -44,7 +41,7 @@ class Source: if self._uprn: # Get the page containing bin details # /calendar gives further future informaion over just the "Services" page - req = urllib.request.Request(f"{URL}/property/{self._uprn}/calendar",headers=HEADERS) + req = urllib.request.Request(f"{API_URL}/property/{self._uprn}/calendar",headers=HEADERS) with urllib.request.urlopen(req) as response: html_doc = response.read() @@ -65,7 +62,7 @@ class Source: Collection( date = collection_date, t = collection_type, - icon = ICONS.get(collection_type.replace(" Bin","").upper()), + icon = ICON_MAP.get(collection_type.replace(" Bin","").upper()), ) ) except ValueError: diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/south_norfolk_and_broadland_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/south_norfolk_and_broadland_gov_uk.py index 7fdf5b12..056cfa9d 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/south_norfolk_and_broadland_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/south_norfolk_and_broadland_gov_uk.py @@ -9,9 +9,8 @@ import requests from bs4 import BeautifulSoup as soup from waste_collection_schedule import Collection -TITLE = "South Norfolk and Broadland Council UK" +TITLE = "South Norfolk and Broadland Council" DESCRIPTION = "Source for southnorfolkandbroadland.gov.uk services for South Norfolk and Broadland, UK" - URL = "https://area.southnorfolkandbroadland.gov.uk/" TEST_CASES = { "Random address": { @@ -42,7 +41,7 @@ TEST_CASES = { } } -ICONS = { +ICON_MAP = { "Rubbish": "mdi:trash-can", "Recycling": "mdi:recycle", "Garden (if applicable)": "mdi:leaf" @@ -75,7 +74,7 @@ class Source: Collection( parse_date(tuple(bin_category.children)[3].strip()), tuple(bin_category.children)[1].text.strip(), - icon=ICONS.get(tuple(bin_category.children)[1].text.strip()) + icon=ICON_MAP.get(tuple(bin_category.children)[1].text.strip()) ) for bin_category in bin_categories diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/srvatervinning_se.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/srvatervinning_se.py index f8231048..2e8bc097 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/srvatervinning_se.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/srvatervinning_se.py @@ -3,9 +3,9 @@ import requests from datetime import datetime from waste_collection_schedule import Collection -TITLE = "SRV återvinning AB Sweden" +TITLE = "SRV Återvinning" DESCRIPTION = "Source for SRV återvinning AB, Sweden" -URL = "https://www.srvatervinning.se/sophamtning/privat/hamtinformation-och-driftstorningar" +URL = "https://www.srvatervinning.se" TEST_CASES = { "Skansvägen" : {"address":"Skansvägen" }, "TEST2" : {"address":"tun" } diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ssam_se.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ssam_se.py index 6dbf5469..5231537d 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ssam_se.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ssam_se.py @@ -4,9 +4,9 @@ from datetime import datetime import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "SSAM Sophämntning" +TITLE = "SSAM" DESCRIPTION = "Source for SSAM waste collection." -URL = "https://edpfuture.ssam.se/FutureWeb/SimpleWastePickup/GetWastePickupSchedule" +URL = "https://ssam.se" TEST_CASES = { "Home": {"street_address": "Asteroidvägen 1, Växjö"}, "Bostadsrätt": {"street_address": "Långa Gatan 29 -81, Växjö"}, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadt_willich_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadt_willich_de.py index afbc7fee..dfa06595 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadt_willich_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadt_willich_de.py @@ -6,17 +6,18 @@ from waste_collection_schedule.service.ICS import ICS TITLE = "Stadt Willich" DESCRIPTION = "Source for Stadt Willich waste collection." URL = "https://www.stadt-willich.de" -ICONS = { +TEST_CASES = { + "Altufer": {"street": "Altufer"}, + "Zum Schickerhof": {"street": "Zum Schickerhof"}, +} + +ICON_MAP = { "Graue Tonne": "mdi:trash-can", "Blaue Tonne": "mdi:newspaper-variant-multiple", "Gelbe Tonne": "mdi:recycle", "Bio Tonne": "mdi:bio", "Grünbündel": "mdi:tree", } -TEST_CASES = { - "Altufer": {"street": "Altufer"}, - "Zum Schickerhof": {"street": "Zum Schickerhof"}, -} class Source: @@ -59,7 +60,7 @@ class Source: entries = [] for d in dates: - icon = ICONS.get(d[1], "mdi:trash-can") + icon = ICON_MAP.get(d[1], "mdi:trash-can") entries.append(Collection( date=d[0], t=d[1], diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtreinigung_dresden_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtreinigung_dresden_de.py index f389a43d..78d71d55 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtreinigung_dresden_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtreinigung_dresden_de.py @@ -6,7 +6,7 @@ from waste_collection_schedule.service.ICS import ICS TITLE = "Stadtreinigung Dresden" DESCRIPTION = "Source for Stadtreinigung Dresden waste collection." -URL = "https://www.dresden.de/apps_ext/AbfallApp/wastebins?0" +URL = "https://www.dresden.de" TEST_CASES = { "Neumarkt 6": {"standort": 80542}, } diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtservice_bruehl_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtservice_bruehl_de.py index 407cf79b..3a334857 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtservice_bruehl_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtservice_bruehl_de.py @@ -7,7 +7,7 @@ from waste_collection_schedule.service.ICS import ICS TITLE = "StadtService Brühl" DESCRIPTION = "Source für Abfallkalender StadtService Brühl" -URL = "https://services.stadtservice-bruehl.de/abfallkalender/" +URL = "https://stadtservice-bruehl.de" TEST_CASES = { "TEST1" : {"strasse":"Badorfer Straße","hnr":"1" } } diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/staedteservice_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/staedteservice_de.py index 83d527d9..89b2c586 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/staedteservice_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/staedteservice_de.py @@ -4,10 +4,9 @@ import datetime from waste_collection_schedule import Collection from waste_collection_schedule.service.ICS import ICS -TITLE = "Städteservice" +TITLE = "Städteservice Raunheim Rüsselsheim" DESCRIPTION = "Städteservice Raunheim Rüsselsheim" URL = "https://www.staedteservice.de" - TEST_CASES = { "Rüsselsheim": { "city": "Rüsselsheim", @@ -19,7 +18,7 @@ TEST_CASES = { }, } -BASE_URL = "https://www.staedteservice.de/abfallkalender" +API_URL = "https://www.staedteservice.de/abfallkalender" CITY_CODE_MAP = { "Rüsselsheim": 1, @@ -64,7 +63,7 @@ class Source: def get_calendar_from_site(self, session: requests.Session, year: int) -> str: # example format: https://www.staedteservice.de/abfallkalender_1_477_2023.ics - URL = f"{BASE_URL}_{self.city_code}_{self.street_number}_{str(year)}.ics" + URL = f"{API_URL}_{self.city_code}_{self.street_number}_{str(year)}.ics" r = session.get(URL) r.raise_for_status() diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stevenage_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stevenage_gov_uk.py index ef2931ed..a0037690 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stevenage_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stevenage_gov_uk.py @@ -12,11 +12,12 @@ TEST_CASES = { "Wansbeck Close schedule": {"road": "Wansbeck Close", "postcode": "SG1 6AA"}, "Chepstow Close schedule": {"road": "Chepstow Close", "postcode": "SG1 5TT"}, } + SEARCH_URLS = { "round_search": "https://services.stevenage.gov.uk/~?a=find&v=1&p=P1&c=P1_C33_&act=P1_A43_", "collection_search": "https://services.stevenage.gov.uk/~?a=find&v=1&p=P1&c=P1_C37_&act=P1_A64_", } -ICONS = { +ICON_MAP = { "REFUSE": "mdi:trash-can", "RECYCLING": "mdi:recycle", } @@ -83,7 +84,7 @@ class Source: Collection( date=datetime.strptime(collection[1], "%d/%m/%Y").date(), t="Recycling", - icon=ICONS["RECYCLING"], + icon=ICON_MAP["RECYCLING"], ) ) elif collection[2] == "Refuse collection": @@ -91,7 +92,7 @@ class Source: Collection( date=datetime.strptime(collection[1], "%d/%m/%Y").date(), t="Refuse", - icon=ICONS["REFUSE"], + icon=ICON_MAP["REFUSE"], ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stonnington_vic_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stonnington_vic_gov_au.py index aa79389e..c7ba23a6 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stonnington_vic_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stonnington_vic_gov_au.py @@ -8,7 +8,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Stonnington City Council" DESCRIPTION = "Source for Stonnington City Council rubbish collection." -URL = "https://www.stonnington.vic.gov.au/Services/Waste-and-recycling" +URL = "https://www.stonnington.vic.gov.au" TEST_CASES = { "The Jam Factory": {"street_address": "500 Chapel Street, South Yarra"}, "Malvern Library": {"street_address": "1255 High Street, Malvern"}, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stuttgart_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stuttgart_de.py index 57ad6d10..4d6926ce 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stuttgart_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stuttgart_de.py @@ -6,7 +6,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Abfall Stuttgart" DESCRIPTION = "Source for waste collections for the city of Stuttgart, Germany." -URL = "https://service.stuttgart.de/lhs-services/aws/" +URL = "https://service.stuttgart.de" TEST_CASES = {"Im Steinengarten 7": {"street": "Im Steinengarten", "streetnr": 7}} diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/sysav_se.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/sysav_se.py index 9152c45f..ecb6f2e6 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/sysav_se.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/sysav_se.py @@ -6,7 +6,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Sysav Sophämntning" DESCRIPTION = "Source for Sysav waste collection." -URL = "https://www.sysav.se/Privat/min-sophamtning/" +URL = "https://www.sysav.se" TEST_CASES = { "Home": {"street_address": "Sommargatan 1, Svedala"}, "Polisen": {"street_address": "Stationsplan 1, Svedala"}, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/tewkesbury_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/tewkesbury_gov_uk.py index f6a11e10..a5ddc65e 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/tewkesbury_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/tewkesbury_gov_uk.py @@ -4,9 +4,9 @@ from urllib.parse import quote as urlquote import requests from waste_collection_schedule import Collection -TITLE = "Tewkesbury Borough Council Waste and Recycling" +TITLE = "Tewkesbury Borough Council" DESCRIPTION = "Home waste collection schedule for Tewkesbury Borough Council" -URL = "https://www.tewkesbury.gov.uk/waste-and-recycling" +URL = "https://www.tewkesbury.gov.uk" TEST_CASES = { "Council Office": {"postcode": "GL20 5TT"}, "Council Office No Spaces": {"postcode": "GL205TT"}, @@ -14,7 +14,7 @@ TEST_CASES = { API_URL = "https://api-2.tewkesbury.gov.uk/general/rounds/%s/nextCollection" -ICONS = { +ICON_MAP = { "Refuse": "mdi:trash-can", "Recycling": "mdi:recycle", "Garden": "mdi:leaf", @@ -47,7 +47,7 @@ class Source: date=datetime.strptime( schedule_entry["NextCollection"], "%Y-%m-%d").date(), t=schedule_entry["collectionType"], - icon=ICONS.get(schedule_entry["collectionType"]) + icon=ICON_MAP.get(schedule_entry["collectionType"]) ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/thehills_nsw_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/thehills_nsw_gov_au.py index 32468ab1..a9a94563 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/thehills_nsw_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/thehills_nsw_gov_au.py @@ -5,7 +5,7 @@ import logging import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "The Hills Shire Council" +TITLE = "The Hills Shire Council, Sydney" DESCRIPTION = "Source for Hills Shire Council, Sydney, Australia waste collection." URL = "https://www.thehills.nsw.gov.au/" TEST_CASES = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/toronto_ca.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/toronto_ca.py index 2f5b14c6..a38a984b 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/toronto_ca.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/toronto_ca.py @@ -6,17 +6,15 @@ from ..collection import Collection from datetime import datetime, timedelta -TITLE = 'Toronto' -DESCRIPTION = ( - 'Source for Toronto waste collection' -) +TITLE = 'City of Toronto' +DESCRIPTION = 'Source for Toronto waste collection' URL = 'https://www.toronto.ca' -CSV_URL = 'https://www.toronto.ca/ext/swms/collection_calendar.csv' TEST_CASES = { "224 Wallace Ave": {"street_address": "224 Wallace Ave"}, "324 Weston Rd": {"street_address": "324 Weston Rd"}, } +CSV_URL = 'https://www.toronto.ca/ext/swms/collection_calendar.csv' PROPERTY_LOOKUP_URL = 'https://map.toronto.ca/cotgeocoder/rest/geocoder/suggest' SCHEDULE_LOOKUP_URL = 'https://map.toronto.ca/cotgeocoder/rest/geocoder/findAddressCandidates' diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/vasyd_se.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/vasyd_se.py index 0111d2dd..d9e4b9f5 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/vasyd_se.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/vasyd_se.py @@ -6,9 +6,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "VA Syd Sophämntning" DESCRIPTION = "Source for VA Syd waste collection." -URL = ( - "https://www.vasyd.se/Artiklar/Avfall-och-soptomning-privat/sopt%C3%B6mning-schema/" -) +URL = "https://www.vasyd.se" TEST_CASES = { "Home": {"street_address": "Industrigatan 13, Malmö"}, "Polisen": {"street_address": "Drottninggatan 20, Malmö"}, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/waipa_nz.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/waipa_nz.py index 03c00010..9c9a6709 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/waipa_nz.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/waipa_nz.py @@ -9,8 +9,8 @@ TITLE = "Waipa District Council" DESCRIPTION = "Source for Waipa District Council. Finds both general and glass recycling dates." URL = "https://www.waipadc.govt.nz/" TEST_CASES = { - "10 Queen Street": {"address": "10 Queen Street"} #Monday - ,"1 Acacia Avenue": {"address": "1 Acacia Avenue"}#Wednesday + "10 Queen Street": {"address": "10 Queen Street"}, # Monday + "1 Acacia Avenue": {"address": "1 Acacia Avenue"}, # Wednesday } diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/walsall_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/walsall_gov_uk.py index d14ac65c..a501c681 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/walsall_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/walsall_gov_uk.py @@ -6,18 +6,9 @@ from datetime import datetime from waste_collection_schedule import Collection -TITLE = "walsall.gov.uk" - -DESCRIPTION = ( - "Source for waste collection services from Walsall Council" -) - -URL = "https://cag.walsall.gov.uk" - -HEADERS = { - "user-agent": "Mozilla/5.0", -} - +TITLE = "Walsall Council" +DESCRIPTION = "Source for waste collection services from Walsall Council" +URL = "https://www.walsall.gov.uk/" TEST_CASES = { "test001" : {"uprn": "100071103746"}, "test002" : {"uprn": 100071105627}, @@ -25,11 +16,15 @@ TEST_CASES = { "test004" : {"uprn": 100071048794}, } -ICONS = { +API_URL = "https://cag.walsall.gov.uk" +ICON_MAP = { "GREY": "mdi:trash-can", "GREEN": "mdi:recycle", "BROWN": "mdi:leaf", } +HEADERS = { + "user-agent": "Mozilla/5.0", +} _LOGGER = logging.getLogger(__name__) @@ -55,7 +50,7 @@ class Source: if "roundname" in item["href"]: #get bin colour bincolour = item["href"].split("=")[-1].split("%")[0].upper() - binURL = URL + item["href"] + binURL = API_URL + item["href"] r = s.get(binURL, headers=HEADERS) responseContent = r.text soup = BeautifulSoup(responseContent, "html.parser") @@ -67,7 +62,7 @@ class Source: Collection( date = collection_date.date(), t = bincolour, - icon = ICONS.get(bincolour), + icon = ICON_MAP.get(bincolour), ) ) except ValueError: diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/warszawa19115_pl.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/warszawa19115_pl.py index 51e2c0b5..593150a5 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/warszawa19115_pl.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/warszawa19115_pl.py @@ -4,9 +4,9 @@ from datetime import datetime import requests from waste_collection_schedule import Collection -TITLE = "Warszawa19115.pl" +TITLE = "Warsaw" DESCRIPTION = "Source for Warsaw city garbage collection" -URL = "https://warszawa19115.pl/harmonogramy-wywozu-odpadow" +URL = "https://warszawa19115.pl" TEST_CASES = { "Street Name": {"street_address": "MARSZAŁKOWSKA 84/92, 00-514 Śródmieście"}, "Geolocation ID": {"geolocation_id": "76802934"}, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wastenet_org_nz.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wastenet_org_nz.py index 425089fc..5cc5dae8 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wastenet_org_nz.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wastenet_org_nz.py @@ -5,7 +5,7 @@ from html.parser import HTMLParser import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "Wastenet" +TITLE = "Gore, Invercargill & Southland" DESCRIPTION = "Source for Wastenet.org.nz." URL = "http://www.wastenet.org.nz" TEST_CASES = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wermelskirchen_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wermelskirchen_de.py index 1f2b4b4b..0336d9fb 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wermelskirchen_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wermelskirchen_de.py @@ -4,17 +4,16 @@ import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "Abfallkalender Wermelskirchen" +TITLE = "Wermelskirchen" DESCRIPTION = "Source for Abfallabholung Wermelskirchen, Germany" -URL = "https://www.wermelskirchen.de/rathaus/buergerservice/formulare-a-z/abfallkalender-online/" - +URL = "https://www.wermelskirchen.de" TEST_CASES = { "Rathaus": {"street": "Telegrafenstraße", "house_number": "29"}, "Krankenhaus": {"street": "Königstraße", "house_number": "100"}, "Mehrzweckhalle": {"street": "An der Mehrzweckhalle", "house_number": "1"}, } -INFOS = { +ICON_MAP = { "Restabfall 2-woechentlich": { "icon": "mdi:trash-can", "image": "https://abfallkalender.citkomm.de/fileadmin/_processed_/1/b/csm_Restmuell_6b2b32c774.png", @@ -80,7 +79,7 @@ class Source: entries = [] for d in dates: - info = INFOS.get(d[1], {"icon": "mdi:trash-can", "image": ""}) + info = ICON_MAP.get(d[1], {"icon": "mdi:trash-can", "image": ""}) entries.append( Collection(d[0], d[1], picture=info["image"], icon=info["icon"]) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/westberks_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/westberks_gov_uk.py index 9a24b47d..df98987b 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/westberks_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/westberks_gov_uk.py @@ -6,10 +6,9 @@ import requests from waste_collection_schedule import Collection -TITLE = "West Berkshire Council, UK" +TITLE = "West Berkshire Council" DESCRIPTION = "Source for westberks.gov.uk services for West Berkshire Council" -URL = "westberks.gov.uk" - +URL = "https://westberks.gov.uk" TEST_CASES = { "known_uprn": {"uprn": "100080241094"}, "unknown_uprn_by_name": {"postcode": "RG7 6NZ", "housenumberorname": "PARROG HOUSE"}, @@ -17,7 +16,7 @@ TEST_CASES = { "unknown_uprn_business": {"postcode": "RG18 4GE", "housenumberorname": "3"} } -ICONS = { +ICON_MAP = { "RUBBISH": "mdi:trash-can", "RECYCLING": "mdi:recycle", } @@ -90,7 +89,7 @@ class Source: Collection( date=dt_local.date(), t=waste_type, - icon=ICONS.get(waste_type.upper()), + icon=ICON_MAP.get(waste_type.upper()), ) ) @@ -106,7 +105,7 @@ class Source: Collection( date=dt_local.date(), t=waste_type, - icon=ICONS.get(waste_type.upper()), + icon=ICON_MAP.get(waste_type.upper()), ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wiltshire_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wiltshire_gov_uk.py index b4a6bc0e..6c2d5ca1 100755 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wiltshire_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wiltshire_gov_uk.py @@ -4,12 +4,13 @@ import requests from bs4 import BeautifulSoup from waste_collection_schedule import Collection -TITLE = "Wiltshire Council, UK" +TITLE = "Wiltshire Council" DESCRIPTION = "Source for wiltshire.gov.uk services for Wiltshire Council" -URL = "wiltshire.gov.uk" +URL = "https://wiltshire.gov.uk" TEST_CASES = { "house_uprn": {"uprn": "100121085972", "postcode": "BA149QP"}, } + SEARCH_URLS = { "collection_search": "https://ilforms.wiltshire.gov.uk/wastecollectiondays/collectionlist" } diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wsz_moosburg_at.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wsz_moosburg_at.py index 5cf42e7c..0334b7fb 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wsz_moosburg_at.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wsz_moosburg_at.py @@ -8,7 +8,7 @@ TITLE = "WSZ Moosburg" DESCRIPTION = ( "Source for WSZ Moosburg/Kärnten, including Moosburg, Pörtschach, Techelsberg" ) -URL = "https://wsz-moosburg.at/calendar" +URL = "https://wsz-moosburg.at" TEST_CASES = { "Id: Moosburg, Obergöriach": {"address_id": 70265}, "Id: Moosburg, Pestalozzistr": {"address_id": 70082}, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wuerzburg_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wuerzburg_de.py index 9d16dd84..e06f31bc 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wuerzburg_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wuerzburg_de.py @@ -6,8 +6,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Abfallkalender Würzburg" DESCRIPTION = "Source for waste collection in the city of Würzburg, Germany." -URL = "https://www.wuerzburg.de/themen/umwelt-klima/vorsorge-entsorgung/abfallkalender/32208.Abfallkalender.html" -HEADERS = {"user-agent": "Mozilla/5.0 (xxxx Windows NT 10.0; Win64; x64)"} +URL = "https://www.wuerzburg.de" TEST_CASES = { "District only": {"district": "Altstadt"}, "Street only": {"street": "Juliuspromenade"}, @@ -15,6 +14,9 @@ TEST_CASES = { "District + Street diff": {"district": "Altstadt", "street": "Oberer Burgweg"}, } +API_URL = "https://www.wuerzburg.de/themen/umwelt-klima/vorsorge-entsorgung/abfallkalender/32208.Abfallkalender.html" +HEADERS = {"user-agent": "Mozilla/5.0 (xxxx Windows NT 10.0; Win64; x64)"} + class Source: def __init__(self, district: str = None, street: str = None): @@ -31,7 +33,7 @@ class Source: if not district and not street: raise ValueError("One of ['district', 'street'] is required.") - r = requests.get(URL, headers=HEADERS) + r = requests.get(API_URL, headers=HEADERS) r.raise_for_status() selects = BeautifulSoup(r.content, "html.parser").body.find_all("select") @@ -49,7 +51,7 @@ class Source: return strdict[street] except KeyError: raise KeyError( - f"Unable to find street '{street}'. Please compare exact typing with {URL}" + f"Unable to find street '{street}'. Please compare exact typing with {API_URL}" ) if district: @@ -64,7 +66,7 @@ class Source: return regdict[district] except KeyError: raise KeyError( - f"Unable to find district '{district}'. Please compare exact typing with {URL}" + f"Unable to find district '{district}'. Please compare exact typing with {API_URL}" ) def fetch(self): @@ -78,7 +80,7 @@ class Source: now = datetime.datetime.now().date() r = requests.get( - URL, + API_URL, headers=HEADERS, params={ "_func": "evList", diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wyndham_vic_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wyndham_vic_gov_au.py index 9c3d1c9c..930c2683 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wyndham_vic_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wyndham_vic_gov_au.py @@ -5,9 +5,9 @@ from bs4 import BeautifulSoup from waste_collection_schedule import Collection # type: ignore[attr-defined] from datetime import datetime -TITLE = "Wyndham City Council" +TITLE = "Wyndham City Council, Melbourne" DESCRIPTION = "Source for Wyndham City Council rubbish collection." -URL = "https://digital.wyndham.vic.gov.au/myWyndham/" +URL = "https://wyndham.vic.gov.au" TEST_CASES = { "Truganina South Primary School": {"street_address": "3-19 Parkvista Drive TRUGANINA 3029"}, "Westbourne Grammar School": {"street_address": "300 Sayers Road TRUGANINA 3029"}, @@ -15,6 +15,7 @@ TEST_CASES = { "Wyndham Park Primary School": {"street_address": "59-77 Kookaburra Avenue WERRIBEE 3030"}, } +API_URL = "https://digital.wyndham.vic.gov.au/myWyndham/" ICON_MAP = { "Green Waste": "mdi:leaf", "Garbage": "mdi:trash-can-outline", @@ -30,7 +31,7 @@ class Source: def fetch(self): session = requests.Session() - response = session.get(URL) + response = session.get(API_URL) response.raise_for_status() response = session.get("https://digital.wyndham.vic.gov.au/myWyndham/ajax/address-search-suggestions.asp?", params=dict(ASEARCH=self._street_address), diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/york_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/york_gov_uk.py index 77c09fa5..69d71654 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/york_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/york_gov_uk.py @@ -4,7 +4,7 @@ from datetime import datetime import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -TITLE = "York.gov.uk" +TITLE = "City of York Council" DESCRIPTION = "Source for York.gov.uk services for the city of York, UK." URL = "https://york.gov.uk" TEST_CASES = { @@ -12,7 +12,7 @@ TEST_CASES = { "Granary Walk, York": {"uprn": "010093236548"}, } -ICONS = { +ICON_MAP = { "REFUSE": "mdi:trash-can", "RECYCLING": "mdi:recycle", "GARDEN": "mdi:leaf", @@ -42,7 +42,7 @@ class Source: collection["date"], "%Y-%m-%dT%H:%M:%S" ).date(), t=collection["roundType"].title(), - icon=ICONS[collection["roundType"]], + icon=ICON_MAP[collection["roundType"]], ) ) except ValueError: diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/zva_wmk_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/zva_wmk_de.py index 728eabb9..5eeb113d 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/zva_wmk_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/zva_wmk_de.py @@ -4,7 +4,7 @@ import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "Zweckverband Abfallwirtschaft Werra-Meißner-Kreis" +TITLE = "Abfallwirtschaft Werra-Meißner-Kreis" DESCRIPTION = "Source for Zweckverband Abfallwirtschaft Werra-Meißner-Kreis" URL = "https://www.zva-wmk.de/" TEST_CASES = { diff --git a/doc/source/abfall_neunkirchen_siegerland_de.md b/doc/source/abfall_neunkirchen_siegerland_de.md index 9c60b2cb..aaca8eac 100644 --- a/doc/source/abfall_neunkirchen_siegerland_de.md +++ b/doc/source/abfall_neunkirchen_siegerland_de.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**strasse**
+**strasse** *(string) (required)* ## Example diff --git a/doc/source/art_trier_de.md b/doc/source/art_trier_de.md index e32706cf..abe70645 100644 --- a/doc/source/art_trier_de.md +++ b/doc/source/art_trier_de.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**district**
+**district** _(string) (required)_ -**zip_code**
+**zip_code** _(string) (required)_ ## Example diff --git a/doc/source/bracknell_forest_gov_uk.md b/doc/source/bracknell_forest_gov_uk.md index 49ab4142..aa41c9fe 100644 --- a/doc/source/bracknell_forest_gov_uk.md +++ b/doc/source/bracknell_forest_gov_uk.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**post_code**
+**post_code** *(string) (required)* -**house_number**
+**house_number** *(string) (required)* ## Example diff --git a/doc/source/braintree_gov_uk.md b/doc/source/braintree_gov_uk.md index 8c9c8d23..6560384e 100644 --- a/doc/source/braintree_gov_uk.md +++ b/doc/source/braintree_gov_uk.md @@ -15,10 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**post_code**
+**post_code** *(string) (required)* -**house_number**
+**house_number** *(string) (required)* ## Example diff --git a/doc/source/korneuburg_stadtservice_at.md b/doc/source/korneuburg_stadtservice_at.md index 7c550765..1f23d171 100644 --- a/doc/source/korneuburg_stadtservice_at.md +++ b/doc/source/korneuburg_stadtservice_at.md @@ -16,27 +16,28 @@ waste_collection_schedule: ### Configuration Variables -**street_name**
+**street_name** *(string) (required)* -**street_number**
+**street_number** *(string) (required)* -**teilgebiet**
+**teilgebiet** *(string) (optional)* ### How to get the source arguments + The arguments can be found on [Stadtservice Korneuburg](https://www.korneuburg.gv.at/Rathaus/Buergerservice/Muellabfuhr). Check if your address details are available on the official site. If not use something that is close by or the same region. You can enter your region number (`teilgebiet`) directly to skip the step that determines your region based on your address. Still some values need to be set for `street_name` and `street_number` which are then not used. -
## Example **First Entry** + ```yaml waste_collection_schedule: sources: @@ -45,7 +46,9 @@ waste_collection_schedule: street_name: "Albrecht Dürer-Gasse" street_number: 2 ``` + **Rathaus** + ```yaml waste_collection_schedule: sources: @@ -54,7 +57,9 @@ waste_collection_schedule: street_name: Hauptplatz street_number: 39 ``` + **Rathaus using Teilgebiet** + ```yaml waste_collection_schedule: sources: diff --git a/doc/source/nillumbik_vic_gov_au.md b/doc/source/nillumbik_vic_gov_au.md index ca422857..56876989 100644 --- a/doc/source/nillumbik_vic_gov_au.md +++ b/doc/source/nillumbik_vic_gov_au.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (required)* ## Example diff --git a/doc/source/srvatervinning_se.md b/doc/source/srvatervinning_se.md index b9e77edd..05c5c476 100644 --- a/doc/source/srvatervinning_se.md +++ b/doc/source/srvatervinning_se.md @@ -1,4 +1,4 @@ -# SRV återvinning AB +# SRV Återvinning Support for schedules provided by [SRV återvinning AB](https://www.srvatervinning.se/), Sweden. @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**address**
+**address** *(string) (required)* ## Example diff --git a/doc/source/stadtservice_bruehl_de.md b/doc/source/stadtservice_bruehl_de.md index c3abeece..a17c7b17 100644 --- a/doc/source/stadtservice_bruehl_de.md +++ b/doc/source/stadtservice_bruehl_de.md @@ -15,9 +15,10 @@ waste_collection_schedule: ### Configuration Variables -**strasse**
+**strasse** *(string) (required)* -**hnr**
+ +**hnr** *(string) (required)* ## Example diff --git a/doc/source/toronto_ca.md b/doc/source/toronto_ca.md index 97f57f4e..7e4321b5 100644 --- a/doc/source/toronto_ca.md +++ b/doc/source/toronto_ca.md @@ -14,7 +14,7 @@ waste_collection_schedule: ### Configuration Variables -**street_address**
+**street_address** *(string) (required)* ## Example diff --git a/doc/source/zva_wmk_de.md b/doc/source/zva_wmk_de.md index afeed090..1429c4e2 100644 --- a/doc/source/zva_wmk_de.md +++ b/doc/source/zva_wmk_de.md @@ -1,4 +1,5 @@ # Zweckverband Abfallwirtschaft Werra-Meißner-Kreis + Support für Werra-Meißner-Kreis located in Hesse, Germany ## Configuration via configuration.yaml @@ -14,10 +15,12 @@ waste_collection_schedule: ### Configuration Variables -**city**
+**city** *(string) (required)* -**street**
+**street** +*(street) (required)* + ### How to get the source arguments Visit [zva-wmk.de](https://www.zva-wmk.de/termine/schnellsuche-2023) and search for your locality. Use the value from the "Ort" dropdown as `city` argument and the one from "Ortsteil/Straße" as `street` as shown. diff --git a/make_docu.py b/make_docu.py new file mode 100755 index 00000000..0706194c --- /dev/null +++ b/make_docu.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 + +import argparse +import importlib +import re +import site +from pathlib import Path + +import yaml + +SECRET_FILENAME = "secrets.yaml" +SECRET_REGEX = re.compile(r"!secret\s(\w+)") + + +def main(): + parser = argparse.ArgumentParser(description="Test sources.") + args = parser.parse_args() + + # read secrets.yaml + secrets = {} + try: + with open(SECRET_FILENAME) as stream: + try: + secrets = yaml.safe_load(stream) + except yaml.YAMLError as exc: + print(exc) + except FileNotFoundError: + # ignore missing secrets.yaml + pass + + package_dir = Path(__file__).resolve().parents[0] / "custom_components" / "waste_collection_schedule" + source_dir = package_dir / "waste_collection_schedule" / "source" + print(source_dir) + + # add module directory to path + site.addsitedir(str(package_dir)) + + files = filter( + lambda x: x != "__init__", + map(lambda x: x.stem, source_dir.glob("*.py")), + ) + + sources = [] + + # retrieve all data from sources + for f in files: + # iterate through all *.py files in waste_collection_schedule/source + module = importlib.import_module(f"waste_collection_schedule.source.{f}") + + title = module.TITLE + url = module.URL + country = getattr(module, "COUNTRY", f.split("_")[-1]) + + if title is not None: + sources.append(SourceInfo(filename=f, title=title, url=url, country=country)) + + extra_info = getattr(module, "EXTRA_INFO", []) + if callable(extra_info): + extra_info = extra_info() + for e in extra_info: + sources.append(SourceInfo(filename=f, title=e.get("title", title), url=e.get("url", url), country=e.get("country", country))) + + # sort into countries + country_code_map = make_country_code_map() + countries = {} + zombies = [] + for s in sources: + # extract country code + code = s.country + if code in country_code_map: + countries.setdefault(country_code_map[code]["name"], []).append(s) + else: + zombies.append(s) + + for country in sorted(countries): + print(f"{country}") + for e in sorted(countries[country], key=lambda e: e.title): + print(f" {e.title} - {beautify_url(e.url)}") + + print("Zombies =========================") + for z in zombies: + print(z) + + +def beautify_url(url): + url = url.removesuffix("/") + url = url.removeprefix("https://") + url = url.removeprefix("www.") + return url + +class SourceInfo: + def __init__(self, filename, title, url, country): + self._filename = filename + self._title = title + self._url = url + self._country = country + + def __repr__(self): + return f"filename:{self._filename}, title:{self._title}, url:{self._url}, country:{self._country}" + + @property + def filename(self): + return self._filename + + @property + def title(self): + return self._title + + @property + def url(self): + return self._url + + @property + def country(self): + return self._country + + +def make_country_code_map(): + return { x["code"]:x for x in COUNTRYCODES } + +COUNTRYCODES = [ + { + "code": "au", + "name": "Australia", + }, + { + "code": "at", + "name": "Austria", + }, + { + "code": "be", + "name": "Belgium", + }, + { + "code": "ca", + "name": "Canada", + }, + { + "code": "de", + "name": "Germany", + }, + { + "code": "hamburg", + "name": "Germany", + }, + { + "code": "lt", + "name": "Lithuania", + }, + { + "code": "nl", + "name": "Netherlands", + }, + { + "code": "nz", + "name": "New Zealand", + }, + { + "code": "no", + "name": "Norway", + }, + { + "code": "pl", + "name": "Poland", + }, + { + "code": "se", + "name": "Sweden", + }, + { + "code": "ch", + "name": "Switzerland", + }, + { + "code": "us", + "name": "United States of America", + }, + { + "code": "uk", + "name": "United Kingdom", + }, +] + +if __name__ == "__main__": + main() \ No newline at end of file From 422aa63ede11c4d1bc60eca9ae48451753f2f0b2 Mon Sep 17 00:00:00 2001 From: mampfes Date: Tue, 27 Dec 2022 20:04:16 +0100 Subject: [PATCH 062/127] hide SSL warnings for alw_wf_de and blerin_recycling_de - remove debug output for stadtservice_bruehl_de - test sources in alphabetic order --- .../source/alw_wf_de.py | 8 +++ .../source/berlin_recycling_de.py | 8 +++ .../source/stadtservice_bruehl_de.py | 69 ++++++++++--------- .../test/test_sources.py | 2 +- 4 files changed, 54 insertions(+), 33 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/alw_wf_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/alw_wf_de.py index cfb31904..a6902173 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/alw_wf_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/alw_wf_de.py @@ -5,6 +5,14 @@ import pytz import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] +# With verify=True the POST fails due to a SSLCertVerificationError. +# Using verify=False works, but is not ideal. The following links may provide a better way of dealing with this: +# https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html#ssl-warnings +# https://urllib3.readthedocs.io/en/1.26.x/user-guide.html#ssl +# These two lines areused to suppress the InsecureRequestWarning when using verify=False +import urllib3 +urllib3.disable_warnings() + TITLE = "Abfallwirtschaft Landkreis Wolfenbüttel" DESCRIPTION = "Source for ALW Wolfenbüttel." URL = "https://alw-wf.de" diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/berlin_recycling_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/berlin_recycling_de.py index f1f7e956..cc91b474 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/berlin_recycling_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/berlin_recycling_de.py @@ -5,6 +5,14 @@ from html.parser import HTMLParser import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] +# With verify=True the POST fails due to a SSLCertVerificationError. +# Using verify=False works, but is not ideal. The following links may provide a better way of dealing with this: +# https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html#ssl-warnings +# https://urllib3.readthedocs.io/en/1.26.x/user-guide.html#ssl +# These two lines areused to suppress the InsecureRequestWarning when using verify=False +import urllib3 +urllib3.disable_warnings() + TITLE = "Berlin Recycling" DESCRIPTION = "Source for Berlin Recycling waste collection." URL = "https://berlin-recycling.de" diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtservice_bruehl_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtservice_bruehl_de.py index 3a334857..441a2246 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtservice_bruehl_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtservice_bruehl_de.py @@ -1,5 +1,6 @@ -import logging import datetime +import logging + import requests from bs4 import BeautifulSoup from waste_collection_schedule import Collection @@ -8,12 +9,11 @@ from waste_collection_schedule.service.ICS import ICS TITLE = "StadtService Brühl" DESCRIPTION = "Source für Abfallkalender StadtService Brühl" URL = "https://stadtservice-bruehl.de" -TEST_CASES = { - "TEST1" : {"strasse":"Badorfer Straße","hnr":"1" } -} +TEST_CASES = {"TEST1": {"strasse": "Badorfer Straße", "hnr": "1"}} _LOGGER = logging.getLogger(__name__) + class Source: def __init__(self, strasse, hnr): self._strasse = strasse @@ -26,58 +26,63 @@ class Source: year = today.year # Get District data = { - 'street': self._strasse, - 'street_number': self._hnr, - 'send_street_and_nummber_data': '' + "street": self._strasse, + "street_number": self._hnr, + "send_street_and_nummber_data": "", } - r = requests.post('https://services.stadtservice-bruehl.de/abfallkalender/', data=data) + r = requests.post( + "https://services.stadtservice-bruehl.de/abfallkalender/", data=data + ) if r.status_code != 200: _LOGGER.error("Error querying calender data") return [] - #print(r.text) + # print(r.text) soup = BeautifulSoup(r.text, "html.parser") for tag in soup.find_all("input", type="hidden"): - #print(tag["name"]) - #print(tag["value"]) - if (tag["name"] == "post_district"): post_district = tag["value"] + # print(tag["name"]) + # print(tag["value"]) + if tag["name"] == "post_district": + post_district = tag["value"] - if post_district == '': + if post_district == "": _LOGGER.error("Unable to get district") return [] - #print(post_district); - #Get ICAL + # print(post_district); + # Get ICAL data = { - 'post_year': year, - 'post_district': post_district, - 'post_street_name': self._strasse, - 'post_street_number': self._hnr, - 'checked_waste_type_hausmuell': 'on', - 'checked_waste_type_gelber_sack': 'on', - 'checked_waste_type_altpapier': 'on', - 'checked_waste_type_bio': 'on', - 'checked_waste_type_weihnachtsbaeume': 'on', - 'checked_waste_type_strassenlaub': 'on', - 'form_page_id': '9', - 'reminder_time': '8', - 'send_ics_download_configurator_data': '' + "post_year": year, + "post_district": post_district, + "post_street_name": self._strasse, + "post_street_number": self._hnr, + "checked_waste_type_hausmuell": "on", + "checked_waste_type_gelber_sack": "on", + "checked_waste_type_altpapier": "on", + "checked_waste_type_bio": "on", + "checked_waste_type_weihnachtsbaeume": "on", + "checked_waste_type_strassenlaub": "on", + "form_page_id": "9", + "reminder_time": "8", + "send_ics_download_configurator_data": "", } - r = requests.post('https://services.stadtservice-bruehl.de/abfallkalender/individuellen-abfuhrkalender-herunterladen/', data=data) + r = requests.post( + "https://services.stadtservice-bruehl.de/abfallkalender/individuellen-abfuhrkalender-herunterladen/", + data=data, + ) if r.status_code != 200: - _LOGGER.error("Error querying calender data") + _LOGGER.error("Error querying calendar data") return [] - print(r.text) dates = self._ics.convert(r.text) entries = [] for d in dates: - entries.append(Collection(d[0],d[1])) + entries.append(Collection(d[0], d[1])) return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/test/test_sources.py b/custom_components/waste_collection_schedule/waste_collection_schedule/test/test_sources.py index a00be9a0..2317b9a5 100755 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/test/test_sources.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/test/test_sources.py @@ -53,7 +53,7 @@ def main(): map(lambda x: x.stem, source_dir.glob("*.py")), ) - for f in files: + for f in sorted(files): # iterate through all *.py files in waste_collection_schedule/source print(f"Testing source {f} ...") module = importlib.import_module(f"waste_collection_schedule.source.{f}") From 2410917a99ba17bd19498698a398ea968b74391e Mon Sep 17 00:00:00 2001 From: mampfes Date: Wed, 28 Dec 2022 09:01:07 +0100 Subject: [PATCH 063/127] add blacklist to make_docu --- make_docu.py | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/make_docu.py b/make_docu.py index 0706194c..2ef09fe8 100755 --- a/make_docu.py +++ b/make_docu.py @@ -11,6 +11,8 @@ import yaml SECRET_FILENAME = "secrets.yaml" SECRET_REGEX = re.compile(r"!secret\s(\w+)") +BLACK_LIST = {"ics", "static", "example"} + def main(): parser = argparse.ArgumentParser(description="Test sources.") @@ -28,7 +30,11 @@ def main(): # ignore missing secrets.yaml pass - package_dir = Path(__file__).resolve().parents[0] / "custom_components" / "waste_collection_schedule" + package_dir = ( + Path(__file__).resolve().parents[0] + / "custom_components" + / "waste_collection_schedule" + ) source_dir = package_dir / "waste_collection_schedule" / "source" print(source_dir) @@ -52,19 +58,31 @@ def main(): country = getattr(module, "COUNTRY", f.split("_")[-1]) if title is not None: - sources.append(SourceInfo(filename=f, title=title, url=url, country=country)) + sources.append( + SourceInfo(filename=f, title=title, url=url, country=country) + ) extra_info = getattr(module, "EXTRA_INFO", []) if callable(extra_info): extra_info = extra_info() for e in extra_info: - sources.append(SourceInfo(filename=f, title=e.get("title", title), url=e.get("url", url), country=e.get("country", country))) + sources.append( + SourceInfo( + filename=f, + title=e.get("title", title), + url=e.get("url", url), + country=e.get("country", country), + ) + ) # sort into countries country_code_map = make_country_code_map() countries = {} zombies = [] for s in sources: + if s.filename in BLACK_LIST: + continue # skip + # extract country code code = s.country if code in country_code_map: @@ -80,7 +98,7 @@ def main(): print("Zombies =========================") for z in zombies: print(z) - + def beautify_url(url): url = url.removesuffix("/") @@ -88,6 +106,7 @@ def beautify_url(url): url = url.removeprefix("www.") return url + class SourceInfo: def __init__(self, filename, title, url, country): self._filename = filename @@ -116,7 +135,8 @@ class SourceInfo: def make_country_code_map(): - return { x["code"]:x for x in COUNTRYCODES } + return {x["code"]: x for x in COUNTRYCODES} + COUNTRYCODES = [ { @@ -182,4 +202,4 @@ COUNTRYCODES = [ ] if __name__ == "__main__": - main() \ No newline at end of file + main() From 9a2b7cb999dce2bffe65b1e00ff6ce9ef51a0d5e Mon Sep 17 00:00:00 2001 From: mampfes Date: Wed, 28 Dec 2022 09:01:36 +0100 Subject: [PATCH 064/127] add extra info to ximmio_nl --- .../source/ximmio_nl.py | 115 +++++++++++++++--- 1 file changed, 97 insertions(+), 18 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ximmio_nl.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ximmio_nl.py index 43ba8448..5a52dbbc 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ximmio_nl.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ximmio_nl.py @@ -6,6 +6,12 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Ximmio" DESCRIPTION = "Source for Ximmio B.V. waste collection." URL = "https://www.ximmio.nl" + + +def EXTRA_INFO(): + return [{"title": s["title"], "url": s["url"]} for s in SERVICE_MAP] + + TEST_CASES = { "ACV Group": {"company": "acv", "post_code": "6721MH", "house_number": 1}, "Meerlanden": {"company": "meerlanden", "post_code": "1435BX", "house_number": 650}, @@ -20,23 +26,96 @@ SERVICE_URLS = { "westland": "https://wasteprod2api.ximmio.com", } -SERVICE_IDS = { - "acv": "f8e2844a-095e-48f9-9f98-71fceb51d2c3", - "almere": "53d8db94-7945-42fd-9742-9bbc71dbe4c1", - "areareiniging": "adc418da-d19b-11e5-ab30-625662870761", - "avalex": "f7a74ad1-fdbf-4a43-9f91-44644f4d4222", - "avri": "78cd4156-394b-413d-8936-d407e334559a", - "bar": "bb58e633-de14-4b2a-9941-5bc419f1c4b0", - "hellendoorn": "24434f5b-7244-412b-9306-3a2bd1e22bc1", - "meerlanden": "800bf8d7-6dd1-4490-ba9d-b419d6dc8a45", - "meppel": "b7a594c7-2490-4413-88f9-94749a3ec62a", - "rad": "13a2cad9-36d0-4b01-b877-efcb421a864d", - "reinis": "9dc25c8a-175a-4a41-b7a1-83f237a80b77", - "twentemilieu": "8d97bb56-5afd-4cbc-a651-b4f7314264b4", - "waardlanden": "942abcf6-3775-400d-ae5d-7380d728b23c", - "westland": "6fc75608-126a-4a50-9241-a002ce8c8a6c", - "ximmio": "800bf8d7-6dd1-4490-ba9d-b419d6dc8a45", -} +SERVICE_MAP = [ + { + "title": "ACV Group", + "url": "https://www.acv-afvalkalender.nl/", + "uuid": "f8e2844a-095e-48f9-9f98-71fceb51d2c3", + "company": "acv", + }, + { + "title": "Gemeente Almere", + "url": "https://www.almere.nl/", + "uuid": "53d8db94-7945-42fd-9742-9bbc71dbe4c1", + "company": "almere", + }, + { + "title": "Area Afval", + "url": "https://www.area-afval.nl/", + "uuid": "adc418da-d19b-11e5-ab30-625662870761", + "company": "areareiniging", + }, + { + "title": "Avalex", + "url": "https://www.avalex.nl/", + "uuid": "f7a74ad1-fdbf-4a43-9f91-44644f4d4222", + "company": "avalex", + }, + { + "title": "Avri", + "url": "https://www.avri.nl/", + "uuid": "78cd4156-394b-413d-8936-d407e334559a", + "company": "avri", + }, + { + "title": "Bar Afvalbeheer", + "url": "https://www.bar-afvalbeheer.nl/", + "uuid": "bb58e633-de14-4b2a-9941-5bc419f1c4b0", + "company": "bar", + }, + { + "title": "Gemeente Hellendoorn", + "url": "https://www.hellendoorn.nl/", + "uuid": "24434f5b-7244-412b-9306-3a2bd1e22bc1", + "company": "hellendoorn", + }, + { + "title": "Meerlanden", + "url": "https://meerlanden.nl/", + "uuid": "800bf8d7-6dd1-4490-ba9d-b419d6dc8a45", + "company": "meerlanden", + }, + { + "title": "Gemeente Meppel", + "url": "https://www.meppel.nl/", + "uuid": "b7a594c7-2490-4413-88f9-94749a3ec62a", + "company": "meppel", + }, + { + "title": "RAD BV", + "url": "https://www.radbv.nl", + "uuid": "13a2cad9-36d0-4b01-b877-efcb421a864d", + "company": "rad", + }, + { + "title": "Reinis", + "url": "https://www.reinis.nl/", + "uuid": "9dc25c8a-175a-4a41-b7a1-83f237a80b77", + "company": "reinis", + }, + { + "title": "Twente Milieu", + "url": "https://www.twentemilieu.nl/", + "uuid": "8d97bb56-5afd-4cbc-a651-b4f7314264b4", + "company": "twentemilieu", + }, + { + "title": "Waardlanden", + "url": "https://www.waardlanden.nl/", + "uuid": "942abcf6-3775-400d-ae5d-7380d728b23c", + "company": "waardlanden", + }, + { + "title": "Gemeente Westland", + "url": "https://www.gemeentewestland.nl/", + "uuid": "6fc75608-126a-4a50-9241-a002ce8c8a6c", + "company": "westland", + }, +] + + +def get_service_name_map(): + return {s["company"]: s["uuid"] for s in SERVICE_MAP} class Source: @@ -44,7 +123,7 @@ class Source: self._post_code = post_code self._house_number = house_number self._url = SERVICE_URLS.get(company, "https://wasteapi.ximmio.com") - self._company_code = SERVICE_IDS[company] + self._company_code = get_service_name_map()[company] def fetch(self): data = { From 76a97312d8e2850376cd759e92de29735c7f2daa Mon Sep 17 00:00:00 2001 From: mampfes Date: Wed, 28 Dec 2022 09:14:42 +0100 Subject: [PATCH 065/127] sort sources case insensitive --- make_docu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/make_docu.py b/make_docu.py index 2ef09fe8..ff693bb5 100755 --- a/make_docu.py +++ b/make_docu.py @@ -92,7 +92,7 @@ def main(): for country in sorted(countries): print(f"{country}") - for e in sorted(countries[country], key=lambda e: e.title): + for e in sorted(countries[country], key=lambda e: e.title.lower()): print(f" {e.title} - {beautify_url(e.url)}") print("Zombies =========================") From 9e7e024a79eed3d5c3bcf39b6d11c612a88cf844 Mon Sep 17 00:00:00 2001 From: mampfes Date: Wed, 28 Dec 2022 12:31:48 +0100 Subject: [PATCH 066/127] automatically generate country section in README.md --- README.md | 272 +++++++++--------- .../source/nawma_sa_gov_au.py | 2 +- make_docu.py | 40 ++- 3 files changed, 178 insertions(+), 136 deletions(-) diff --git a/README.md b/README.md index 4a8711af..521527dc 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ **A custom component for Home Assistant that retrieves waste collection schedules from a wide range of service providers.** Waste Collection Schedule animation + Waste collection schedules from service provider web sites are updated daily, derived from local ICS/iCal files, or generated from user-specified dates or regularly repeating date patterns. The Home Assistant built-in Calendar is automatically populated with schedules, and there is a high degree of flexibility in how information can be format and displayed in entity cards or pop-ups. The framework can easily be extended to support additional waste collection service providers, or other services which provide schedules. # Supported Service Providers @@ -15,62 +16,50 @@ Waste collection schedules in the following formats and countries are supported.
ICS/iCal and User-Specified -

- - [Generic ICS / iCal File](/doc/source/ics.md) - [User Specified](/doc/source/static.md) -

+
Australia -

- -- [Banyule City Council](/doc/source/banyule_vic_gov_au.md) -- [Belmont City Council](/doc/source/belmont_wa_gov_au.md) -- [Brisbane City Council](/doc/source/brisbane_qld_gov_au.md) -- [Campbelltown City Council](/doc/source/campbelltown_nsw_gov_au.md) -- [City of Canada Bay Council](/doc/source/canadabay_nsw_gov_au.md) -- [Inner West Council (NSW)](/doc/source/innerwest_nsw_gov_au.md) -- [Ku-ring-gai Council](/doc/source/kuringgai_nsw_gov_au.md) -- [Macedon Ranges Shire Council, Melbourne](/doc/source/mrsc_vic_gov_au.md) -- [Maroondah City Council](/doc/source/maroondah_vic_gov_au.md) -- [Melton City Council, Melbourne](/doc/source/melton_vic_gov_au.md) -- [Nillumbik Shire Council](/doc/source/nillumbik_vic_gov_au.md) -- [North Adelaide Waste Management Authority, South Australia](/doc/source/nawma_sa_gov_au.md) -- [RecycleSmart](/doc/source/recyclesmart_com.md) -- [Stonnington City Council, Melbourne](/doc/source/stonnington_vic_gov_au.md) -- [The Hills Council, Sydney](/doc/source/thehills_nsw_gov_au.md) -- [Wyndham City Council, Melbourne](/doc/source/wyndham_vic_gov_au.md) -

+- [Banyule City Council](/doc/source/banyule_vic_gov_au.md) / banyule.vic.gov.au +- [Belmont City Council](/doc/source/belmont_wa_gov_au.md) / belmont.wa.gov.au +- [Brisbane City Council](/doc/source/brisbane_qld_gov_au.md) / brisbane.qld.gov.au +- [Campbelltown City Council](/doc/source/campbelltown_nsw_gov_au.md) / campbelltown.nsw.gov.au +- [City of Canada Bay Council](/doc/source/canadabay_nsw_gov_au.md) / canadabay.nsw.gov.au +- [Inner West Council (NSW)](/doc/source/innerwest_nsw_gov_au.md) / innerwest.nsw.gov.au +- [Ipswich City Council](/doc/source/ipswich_qld_gov_au.md) / ipswich.qld.gov.au +- [Ku-ring-gai Council](/doc/source/kuringgai_nsw_gov_au.md) / krg.nsw.gov.au +- [Macedon Ranges Shire Council](/doc/source/mrsc_vic_gov_au.md) / mrsc.vic.gov.au +- [Maroondah City Council](/doc/source/maroondah_vic_gov_au.md) / maroondah.vic.gov.au +- [Melton City Council](/doc/source/melton_vic_gov_au.md) / melton.vic.gov.au +- [Nillumbik Shire Council](/doc/source/nillumbik_vic_gov_au.md) / nillumbik.vic.gov.au +- [North Adelaide Waste Management Authority](/doc/source/nawma_sa_gov_au.md) / nawma.sa.gov.au +- [RecycleSmart](/doc/source/recyclesmart_com.md) / recyclesmart.com +- [Stonnington City Council](/doc/source/stonnington_vic_gov_au.md) / stonnington.vic.gov.au +- [The Hills Shire Council, Sydney](/doc/source/thehills_nsw_gov_au.md) / thehills.nsw.gov.au +- [Wyndham City Council, Melbourne](/doc/source/wyndham_vic_gov_au.md) / wyndham.vic.gov.au
Austria -

- -- [BMV.at](/doc/source/bmv_at.md) -- [Data.Umweltprofis](/doc/source/data_umweltprofis_at.md) -- [Korneuburg Stadtservice](/doc/source/korneuburg_stadtservice_at.md) -- [WSZ-Moosburg.at](/doc/source/wsz_moosburg_at.md) -

+- [Burgenländischer Müllverband](/doc/source/bmv_at.md) / bmv.at +- [infeo](/doc/source/infeo_at.md) / infeo.at +- [Stadtservice Korneuburg](/doc/source/korneuburg_stadtservice_at.md) / korneuburg.gv.at +- [Umweltprofis](/doc/source/data_umweltprofis_at.md) / umweltprofis.at +- [WSZ Moosburg](/doc/source/wsz_moosburg_at.md) / wsz-moosburg.at
Belgium -

- -- [Hygea.be](/doc/source/hygea_be.md) -- [Recycle! / RecycleApp.be](/doc/source/recycleapp_be.md) -

+- [Hygea](/doc/source/hygea_be.md) / hygea.be +- [Recycle!](/doc/source/recycleapp_be.md) / recycleapp.be
Canada -

- -- [City of Toronto](/doc/source/toronto_ca.md) -

+- [City of Toronto](/doc/source/toronto_ca.md) / toronto.ca
@@ -136,140 +125,157 @@ Waste collection schedules in the following formats and countries are supported.
Lithuania -

- -- [Kauno švara](/doc/source/grafikai_svara_lt.md) -

+- [Kauno švara](/doc/source/grafikai_svara_lt.md) / grafikai.svara.lt
Netherlands -

- -- [HVCGroep and others](/doc/source/hvcgroep_nl.md) -- [Ximmio](/doc/source/ximmio_nl.md) -

+- [ACV Group](/doc/source/ximmio_nl.md) / acv-afvalkalender.nl +- [Alpen an den Rijn](/doc/source/hvcgroep_nl.md) / alphenaandenrijn.nl +- [Area Afval](/doc/source/ximmio_nl.md) / area-afval.nl +- [Avalex](/doc/source/ximmio_nl.md) / avalex.nl +- [Avri](/doc/source/ximmio_nl.md) / avri.nl +- [Bar Afvalbeheer](/doc/source/ximmio_nl.md) / bar-afvalbeheer.nl +- [Cyclus NV](/doc/source/hvcgroep_nl.md) / cyclusnv.nl +- [Dar](/doc/source/hvcgroep_nl.md) / dar.nl +- [Den Haag](/doc/source/hvcgroep_nl.md) / denhaag.nl +- [GAD](/doc/source/hvcgroep_nl.md) / gad.nl +- [Gemeente Almere](/doc/source/ximmio_nl.md) / almere.nl +- [Gemeente Berkelland](/doc/source/hvcgroep_nl.md) / gemeenteberkelland.nl +- [Gemeente Cranendonck](/doc/source/hvcgroep_nl.md) / cranendonck.nl +- [Gemeente Hellendoorn](/doc/source/ximmio_nl.md) / hellendoorn.nl +- [Gemeente Lingewaard](/doc/source/hvcgroep_nl.md) / lingewaard.nl +- [Gemeente Meppel](/doc/source/ximmio_nl.md) / meppel.nl +- [Gemeente Middelburg + Vlissingen](/doc/source/hvcgroep_nl.md) / middelburgvlissingen.nl +- [Gemeente Peel en Maas](/doc/source/hvcgroep_nl.md) / peelenmaas.nl +- [Gemeente Schouwen-Duiveland](/doc/source/hvcgroep_nl.md) / schouwen-duiveland.nl +- [Gemeente Sudwest-Fryslan](/doc/source/hvcgroep_nl.md) / sudwestfryslan.nl +- [Gemeente Venray](/doc/source/hvcgroep_nl.md) / venray.nl +- [Gemeente Voorschoten](/doc/source/hvcgroep_nl.md) / voorschoten.nl +- [Gemeente Wallre](/doc/source/hvcgroep_nl.md) / waalre.nl +- [Gemeente Westland](/doc/source/ximmio_nl.md) / gemeentewestland.nl +- [HVC Groep](/doc/source/hvcgroep_nl.md) / hvcgroep.nl +- [Meerlanden](/doc/source/ximmio_nl.md) / meerlanden.nl +- [Mijn Blink](/doc/source/hvcgroep_nl.md) / mijnblink.nl +- [PreZero](/doc/source/hvcgroep_nl.md) / prezero.nl +- [Purmerend](/doc/source/hvcgroep_nl.md) / purmerend.nl +- [RAD BV](/doc/source/ximmio_nl.md) / radbv.nl +- [Reinigingsbedrijf Midden Nederland](/doc/source/hvcgroep_nl.md) / rmn.nl +- [Reinis](/doc/source/ximmio_nl.md) / reinis.nl +- [Spaarne Landen](/doc/source/hvcgroep_nl.md) / spaarnelanden.nl +- [Stadswerk 072](/doc/source/hvcgroep_nl.md) / stadswerk072.nl +- [Twente Milieu](/doc/source/ximmio_nl.md) / twentemilieu.nl +- [Waardlanden](/doc/source/ximmio_nl.md) / waardlanden.nl +- [Ximmio](/doc/source/ximmio_nl.md) / ximmio.nl +- [ZRD](/doc/source/hvcgroep_nl.md) / zrd.nl
New Zealand -

- -- [Auckland](/doc/source/aucklandcouncil_govt_nz.md) -- [Christchurch](/doc/source/ccc_govt_nz.md) -- [Gore, Invercargill & Southland](/doc/source/wastenet_org_nz.md) -- [Horowhenua District](/doc/source/horowhenua_govt_nz.md) -- [Waipa District](/doc/source/waipa_nz.md) -- [Wellington](/doc/source/wellington_govt_nz.md) -

+- [Auckland Council](/doc/source/aucklandcouncil_govt_nz.md) / aucklandcouncil.govt.nz +- [Christchurch City Council](/doc/source/ccc_govt_nz.md) / ccc.govt.nz +- [Gore, Invercargill & Southland](/doc/source/wastenet_org_nz.md) / wastenet.org.nz +- [Horowhenua District Council](/doc/source/horowhenua_govt_nz.md) / horowhenua.govt.nz +- [Waipa District Council](/doc/source/waipa_nz.md) / waipadc.govt.nz +- [Wellington City Council](/doc/source/wellington_govt_nz.md) / wellington.govt.nz
Norway -

- -- [Min Renovasjon](/doc/source/minrenovasjon_no.md) -- [Oslo Kommune](/doc/source/oslokommune_no.md) -

+- [Min Renovasjon](/doc/source/minrenovasjon_no.md) / norkart.no +- [Oslo Kommune](/doc/source/oslokommune_no.md) / oslo.kommune.no
Poland -

- -- [Warsaw](/doc/source/warszawa19115_pl.md) -- [Multiple communities - ecoharmonogram](/doc/source/ecoharmonogram_pl.md) -

+- [Ecoharmonogram](/doc/source/ecoharmonogram_pl.md) / ecoharmonogram.pl +- [Warsaw](/doc/source/warszawa19115_pl.md) / warszawa19115.pl
Sweden -

- -- [Lerum.se](/doc/source/lerum_se.md) -- [Ronneby Miljöteknik](/doc/source/miljoteknik_se.md) -- [SSAM.se](/doc/source/ssam_se.md) -- [srvatervinning.se](/doc/source/srvatervinning_se.md) -- [Sysav.se](/doc/source/sysav_se.md) -- [Vasyd.se](/doc/source/vasyd_se.md) -

+- [Lerum Vatten och Avlopp](/doc/source/lerum_se.md) / vatjanst.lerum.se +- [Ronneby Miljöteknik](/doc/source/miljoteknik_se.md) / fyrfackronneby.se +- [SRV Återvinning](/doc/source/srvatervinning_se.md) / srvatervinning.se +- [SSAM](/doc/source/ssam_se.md) / ssam.se +- [Sysav Sophämntning](/doc/source/sysav_se.md) / sysav.se +- [VA Syd Sophämntning](/doc/source/vasyd_se.md) / vasyd.se
Switzerland -

- -- [A-Region.ch](/doc/source/a_region_ch.md) -- [Lindau.ch](/doc/source/lindau_ch.md) -

-
- -
-United States of America -

- -- [PGH.ST](/doc/source/pgh_st.md) -- [Republic Services](/doc/source/republicservices_com.md) -- [Seattle Public Utilities](/doc/source/seattle_gov.md) -

+- [A-Region](/doc/source/a_region_ch.md) / a-region.ch +- [Lindau](/doc/source/lindau_ch.md) / lindau.ch
United Kingdom -

- -- [Bracknell Forest Council - bracknell-forest.gov.uk](/doc/source/bracknell_forest_gov_uk.md) -- [Bradford Metropolitan District Council - bradford.gov.uk](/doc/source/bradford_gov_uk.md) -- [Braintree District Council - braintree.gov.uk](/doc/source/braintree_gov_uk.md) -- [Cambridge City Council - cambridge.gov.uk](/doc/source/cambridge_gov_uk.md) -- [Canterbury City Council - canterbury.gov.uk](/doc/source/canterbury_gov_uk.md) -- [Cheshire East Council - cheshireeast.gov.uk](/doc/source/cheshire_east_gov_uk.md) -- [Chesterfield Borough Council - chesterfield.gov.uk](/doc/source/chesterfield_gov_uk.md) -- [Colchester Borough Council - colchester.gov.uk](/doc/source/colchester_gov_uk.md) -- [Cornwall Council - cornwall.gov.uk](/doc/source/cornwall_gov_uk.md) -- [Derby City Council - derby.gov.uk](/doc/source/derby_gov_uk.md) -- [Eastbourne Borough Council - lewes-eastbourne.gov.uk](/doc/source/environmentfirst_co_uk.md) -- [Elmbridge Borough Council - elmbridge.gov.uk](/doc/source/elmbridge_gov_uk.md) -- [Guildford Borough Council - guildford.gov.uk](/doc/source/guildford_gov_uk.md) -- [Harborough District Council - harborough.gov.uk](/doc/source/fccenvironment_co_uk.md) -- [Huntingdonshire District Council - huntingdonshire.gov.uk](/doc/source/huntingdonshire_gov_uk.md) -- [The Royal Borough of Kingston - kinston.gov.uk](/doc/source/kingston_gov_uk.md) -- [Lewes District Council - lewes-eastbourne.gov.uk](/doc/source/environmentfirst_co_uk.md) -- [London Borough of Lewisham - lewisham.gov.uk](.doc/source/lewisham_gov_uk.md) -- [Manchester City Council - manchester.gov.uk](/doc/source/manchester_uk.md) -- [Newcastle City Council - newcastle.gov.uk](/doc/source/newcastle_gov_uk.md) -- [North Somerset Council - n-somerset.gov.uk](/doc/source/nsomerset_gov_uk.md) -- [Nottingham City Council - nottinghamcity.gov.uk](/doc/source/nottingham_city_gov_uk.md) -- [Peterborough City Council - peterborough.gov.uk](/doc/source/peterborough_gov_uk.md) -- [Richmondshire District Council - richmondshire.gov.uk](/doc/source/richmondshire_gov_uk.md) -- [Rushmoor Borough Council - rushmoor.gov.uk](/doc/source/rushmoor_gov_uk.md) -- [Sheffield City Council - sheffield.gov.uk](/doc/source/sheffield_gov_uk.md) -- [South Cambridgeshire District Council - scambs.gov.uk](/doc/source/scambs_gov_uk.md) -- [South Hams - southhams.gov.uk](https://southhams.gov.uk/) -- [South Norfolk and Broadland Council - southnorfolkandbroadland.gov.uk](/doc/source/south_norfolk_and_broadland_gov_uk.md) -- [Stevenage Borough Council - stevenage.gov.uk](/doc/source/stevenage_gov_uk.md) -- [Tewkwsbury Borough Council - tewkesbury.gov.uk](/doc/source/tewkesbury_gov_uk.md) -- [City of York Council - york.gov.uk](/doc/source/york_gov_uk.md) -- [Walsall Council - walsall.gov.uk](/doc/source/walsall_gov_uk.md) -- [West Berkshire Council - westberks.gov.uk](/doc/source/westberks_gov_uk.md) -- [West Devon - westdevon.gov.uk](https://www.westdevon.gov.uk/) -- [Wiltshire Council - wiltshire.gov.uk](/doc/source/wiltshire_gov_uk.md) -

+- [Bracknell Forest Council](/doc/source/bracknell_forest_gov_uk.md) / selfservice.mybfc.bracknell-forest.gov.uk +- [Bradford Metropolitan District Council](/doc/source/bradford_gov_uk.md) / bradford.gov.uk +- [Braintree District Council](/doc/source/braintree_gov_uk.md) / braintree.gov.uk +- [Cambridge City Council](/doc/source/cambridge_gov_uk.md) / cambridge.gov.uk +- [Canterbury City Council](/doc/source/canterbury_gov_uk.md) / canterbury.gov.uk +- [Cheshire East Council](/doc/source/cheshire_east_gov_uk.md) / cheshireeast.gov.uk +- [Chesterfield Borough Council](/doc/source/chesterfield_gov_uk.md) / chesterfield.gov.uk +- [City of York Council](/doc/source/york_gov_uk.md) / york.gov.uk +- [Colchester Borough Council](/doc/source/colchester_gov_uk.md) / colchester.gov.uk +- [Cornwall Council](/doc/source/cornwall_gov_uk.md) / cornwall.gov.uk +- [Derby City Council](/doc/source/derby_gov_uk.md) / derby.gov.uk +- [Eastbourne Borough Council](/doc/source/environmentfirst_co_uk.md) / lewes-eastbourne.gov.uk +- [Elmbridge Borough Council](/doc/source/elmbridge_gov_uk.md) / elmbridge.gov.uk +- [Environment First](/doc/source/environmentfirst_co_uk.md) / environmentfirst.co.uk +- [FCC Environment](/doc/source/fccenvironment_co_uk.md) / fccenvironment.co.uk +- [Guildford Borough Council](/doc/source/guildford_gov_uk.md) / guildford.gov.uk +- [Harborough District Council](/doc/source/fccenvironment_co_uk.md) / harborough.gov.uk +- [Huntingdonshire District Council](/doc/source/huntingdonshire_gov_uk.md) / huntingdonshire.gov.uk +- [Lewes District Council](/doc/source/environmentfirst_co_uk.md) / lewes-eastbourne.gov.uk +- [London Borough of Lewisham](/doc/source/lewisham_gov_uk.md) / lewisham.gov.uk +- [Manchester City Council](/doc/source/manchester_uk.md) / manchester.gov.uk +- [Middlesbrough Council](/doc/source/middlesbrough_gov_uk.md) / middlesbrough.gov.uk +- [Newcastle City Council](/doc/source/newcastle_gov_uk.md) / community.newcastle.gov.uk +- [North Somerset Council](/doc/source/nsomerset_gov_uk.md) / n-somerset.gov.uk +- [Nottingham City Council](/doc/source/nottingham_city_gov_uk.md) / nottinghamcity.gov.uk +- [Peterborough City Council](/doc/source/peterborough_gov_uk.md) / peterborough.gov.uk +- [Richmondshire District Council](/doc/source/richmondshire_gov_uk.md) / richmondshire.gov.uk +- [Rushmoor Borough Council](/doc/source/rushmoor_gov_uk.md) / rushmoor.gov.uk +- [Sheffield City Council](/doc/source/sheffield_gov_uk.md) / sheffield.gov.uk +- [South Cambridgeshire District Council](/doc/source/scambs_gov_uk.md) / scambs.gov.uk +- [South Hams District Council](/doc/source/fccenvironment_co_uk.md) / southhams.gov.uk +- [South Norfolk and Broadland Council](/doc/source/south_norfolk_and_broadland_gov_uk.md) / area.southnorfolkandbroadland.gov.uk +- [Stevenage Borough Council](/doc/source/stevenage_gov_uk.md) / stevenage.gov.uk +- [Tewkesbury Borough Council](/doc/source/tewkesbury_gov_uk.md) / tewkesbury.gov.uk +- [The Royal Borough of Kingston Council](/doc/source/kingston_gov_uk.md) / kingston.gov.uk +- [Walsall Council](/doc/source/walsall_gov_uk.md) / walsall.gov.uk +- [West Berkshire Council](/doc/source/westberks_gov_uk.md) / westberks.gov.uk +- [West Devon Borough Council](/doc/source/fccenvironment_co_uk.md) / westdevon.gov.uk +- [Wiltshire Council](/doc/source/wiltshire_gov_uk.md) / wiltshire.gov.uk
+
+United States of America +- [City of Pittsburgh](/doc/source/pgh_st.md) / pgh.st +- [Republic Services](/doc/source/republicservices_com.md) / republicservices.com +- [Seattle Public Utilities](/doc/source/seattle_gov.md) / myutilities.seattle.gov +
+ + + # Installation and Configuration + ![hacs badge](https://img.shields.io/badge/HACS-Default-orange) ![hacs installs](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Flauwbier.nl%2Fhacs%2Fwaste_collection_schedule) The Waste Collection Schedule can be installed via [HACS](https://hacs.xyz/), or by manually copying the [`waste_collection_schedule`](https://github.com/mampfes/hacs_waste_collection_schedule/tree/master/custom_components) directory to Home Assistant's `config/custom_components/` directory. For further details see the [installation and configuration](/doc/installation.md) page, or the [FAQ](/doc/faq.md). # Contributing To The Project + ![python badge](https://img.shields.io/badge/Made%20with-Python-orange) ![github contributors](https://img.shields.io/github/contributors/mampfes/hacs_waste_collection_schedule?color=orange) ![last commit](https://img.shields.io/github/last-commit/mampfes/hacs_waste_collection_schedule?color=orange) ![Community Discussion](https://img.shields.io/badge/Home%20Assistant%20Community-Discussion-orange) There are several ways of contributing to this project, they include: + - Adding new service providers - Updating or improving the documentation - Helping answer/fix any issues raised @@ -293,7 +299,9 @@ If you'd like to help with any of these, please raise an [issue](https://github. --> # Known Issues -The following waste service providers return errors when running the test_source script + +The following waste service providers return errors when running the test_source script: + - [ ] berlin_recycling_de, JSONDecodeError - [ ] abfall_io, Traunstein test case fails - [ ] sector27_de, TimeoutError @@ -304,9 +312,11 @@ The following waste service providers return errors when running the test_source If you can fix any of these, please raise a Pull Request with the updates. # Licence + ![github licence](https://img.shields.io/badge/Licence-MIT-orange) This project uses the MIT Licence, for more details see the [licence](/doc/licence.md) document. # Showing Your Appreciation -If you like this project, please give it a star on [GitHub](https://github.com/mampfes/hacs_waste_collection_schedule) or consider becomming a [Sponsor](https://github.com/sponsors/mampfes). + +If you like this project, please give it a star on [GitHub](https://github.com/mampfes/hacs_waste_collection_schedule) or consider becoming a [Sponsor](https://github.com/sponsors/mampfes). diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/nawma_sa_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/nawma_sa_gov_au.py index 7eae80eb..c28544f7 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/nawma_sa_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/nawma_sa_gov_au.py @@ -8,7 +8,7 @@ TITLE = "North Adelaide Waste Management Authority" DESCRIPTION = ( "Source for nawma.sa.gov.au (Salisbury, Playford, and Gawler South Australia)." ) -URL = "http://www.nawma.sa.gov.au" +URL = "https://www.nawma.sa.gov.au" TEST_CASES = { "128 Bridge Road": { "street_number": "128", diff --git a/make_docu.py b/make_docu.py index ff693bb5..f54b376e 100755 --- a/make_docu.py +++ b/make_docu.py @@ -13,6 +13,9 @@ SECRET_REGEX = re.compile(r"!secret\s(\w+)") BLACK_LIST = {"ics", "static", "example"} +START_COUNTRY_SECTION = "" +END_COUNTRY_SECTION = "" + def main(): parser = argparse.ArgumentParser(description="Test sources.") @@ -90,10 +93,7 @@ def main(): else: zombies.append(s) - for country in sorted(countries): - print(f"{country}") - for e in sorted(countries[country], key=lambda e: e.title.lower()): - print(f" {e.title} - {beautify_url(e.url)}") + update_readme_md(countries) print("Zombies =========================") for z in zombies: @@ -102,11 +102,43 @@ def main(): def beautify_url(url): url = url.removesuffix("/") + url = url.removeprefix("http://") url = url.removeprefix("https://") url = url.removeprefix("www.") return url +def update_readme_md(countries): + # generate country list + str = "" + for country in sorted(countries): + str += "
\n" + str += f"{country}\n" + + for e in sorted(countries[country], key=lambda e: e.title.lower()): + # print(f" {e.title} - {beautify_url(e.url)}") + str += ( + f"- [{e.title}](/doc/source/{e.filename}.md) / {beautify_url(e.url)}\n" + ) + + str += "
\n" + str += "\n" + + # read entire file + with open("README.md") as f: + md = f.read() + + # find beginning and end of country section + start_pos = md.index(START_COUNTRY_SECTION) + len(START_COUNTRY_SECTION) + 1 + end_pos = md.index(END_COUNTRY_SECTION) + + md = md[:start_pos] + str + md[end_pos:] + + # write entire file + with open("README.md", "w") as f: + f.write(md) + + class SourceInfo: def __init__(self, filename, title, url, country): self._filename = filename From 16160d45093d7e7b48392559907f0c6e9fc841e5 Mon Sep 17 00:00:00 2001 From: mampfes Date: Wed, 28 Dec 2022 13:03:00 +0100 Subject: [PATCH 067/127] automatically update info.md --- info.md | 29 +++++++++++++++++++---------- make_docu.py | 26 ++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/info.md b/info.md index 94d0888e..24359f63 100644 --- a/info.md +++ b/info.md @@ -1,33 +1,42 @@ Waste Collection Schedule logo # Waste Collection Schedule + ![hacs_badge](https://img.shields.io/badge/HACS-Default-orange) ![hacs installs](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Flauwbier.nl%2Fhacs%2Fwaste_collection_schedule) [![Community Discussion](https://img.shields.io/badge/Home%20Assistant%20Community-Discussion-orange)](https://community.home-assistant.io/t/waste-collection-schedule-framework/186492) **A custom component for Home Assistant that retrieves waste collection schedules from a wide range of service providers.** Waste Collection Schedule animation -Waste collection schedules from service provider web sites are updated daily, derived from local ICS/iCal files, or generated from user-specified dates or regularly repeating date patterns. The Home Assistant built-in Calendar is automatically populated with schedules, and there is a high degree of flexibility in how information can be format and displayed in entity cards or pop-ups. The framework can easily be extended to support additional waste collection service providers, or other services which provide schedules. +Waste collection schedules from service provider web sites are updated daily, derived from local ICS/iCal files, or generated from user-specified dates or regularly repeating date patterns. The Home Assistant built-in Calendar is automatically populated with schedules, and there is a high degree of flexibility in how information can be format and displayed in entity cards or pop-ups. The framework can easily be extended to support additional waste collection service providers, or other services which provide schedules. ## Supported Service Providers | Country | Service Providers | |--|--| | Generic | ICS / iCal files | -| Static | User-defined dates or repeating date patterns | -| Australia | Banyule City Council, Belmont City Council, Brisbane City Council, Campbelltown City Council, City of Canada Bay Council, Inner West Council (NSW), Ku-ring-gai Council, Macedon Ranges Shire Council (Melbourne), Maroondah City Council, Melton City Council (Melbourne), Nillumbik Shire Council, North Adelaide Waste Management Authority (South Australia), RecycleSmart, Stonnington City Council (Melbourne), The Hills Council (Sydney), Wyndham City Council (Melbourne) | -| Austria | BMV, Data.Umweltprois, Korneuburg Stadtservice, WSZ-Moosburg | -| Belgium | Hyhea, Recycle! / RecycleApp | +| Static | User-defined dates or repeating date patterns | +| Australia | Banyule City Council, Belmont City Council, Brisbane City Council, Campbelltown City Council, City of Canada Bay Council, Inner West Council (NSW), Ipswich City Council, Ku-ring-gai Council, Macedon Ranges Shire Council, Maroondah City Council, Melton City Council, Nillumbik Shire Council, North Adelaide Waste Management Authority, RecycleSmart, Stonnington City Council, The Hills Shire Council, Sydney, Wyndham City Council, Melbourne | +| Austria | Burgenländischer Müllverband, infeo, Stadtservice Korneuburg, Umweltprofis, WSZ Moosburg | +| Belgium | Hygea, Recycle! | | Canada | City of Toronto | +<<<<<<< HEAD | Germany | Abfall.IO / AbfallPlus, AbfallNavi (RegioIT.de), Abfallkalender Würzburg, Abfalltermine Forchheim, Abfallwirtschaft Bremen, Abfallwirtschaft Landkreis Harburg, Abfallwirtschaft Landkreis Wolfenbüttel, Abfallwirtschaft Neckar-Odenwald-Kreis, Abfallwirtschaft Rendsburg, Abfallwirtschaft Stuttgart, Abfallwirtschaft Südholstein, Abfallwirtschaft Zollernalbkreis, Alb-Donau-Kreis, ART Trier, AWB Bad Kreuznach, AWB Esslingen, AWB Landkreis Ausburg, AWB Limburg-Weilburg, AWB Oldenburg, AWBKoeln, AWIDO-online, Berlin-Recycling, Bogenschuetz-Entsorgung, Biedenkopf MZF, BSR.de / Berliner Stadtreinigungsbetriebe, C-Trace, Cochem-Zell, EGN-Abfallkalender, Erlangen-Höchstadt, Landkreis Nordwestmecklenburg, Jumomind, KAEV Niederlausitz, KWB-Goslar, KWU-Entsorgung, Landkreis Wittmund, Landkreis Rhön Grabfeld, Landkreis Schwäbisch Hall, Muellmax, MyMuell App, Neunkirchen Siegerland, RegioEntsorgung, Rhein-Hunsrück Entsorgung (RHE), Sector27, Stadtreinigung Dresden, Stadtreinigung.Hamburg, Stadtreinigung-Leipzig, Stadt-Willich, StadtService Brühl, Städteservice Raunheim Rüsselsheim, Südbrandenburgischer Abfallzweckverband, Umweltbetrieb Stadt Bielefeld, WAS Wolfsburg, Wermeldkirchen, Zweckverband Abfallwirtschaft Werra-Meißner-Kreis | | Lituania | Kauno švara | | Netherlands | HVCGroep, Ximmio | | New Zealand | Auckland, Christchurch, Gore, Invercargill & Shouthand, Horowhenua District, Wapia District, Wellington | +======= +| Germany | Abfall Stuttgart, Abfall.IO / AbfallPlus, Abfallkalender Würzburg, AbfallNavi (RegioIT.de), Abfalltermine Forchheim, Abfallwirtschaft Alb-Donau-Kreis, Abfallwirtschaft Landkreis Harburg, Abfallwirtschaft Landkreis Wolfenbüttel, Abfallwirtschaft Neckar-Odenwald-Kreis, Abfallwirtschaft Rendsburg, Abfallwirtschaft Südholstein, Abfallwirtschaft Werra-Meißner-Kreis, Abfallwirtschaft Zollernalbkreis, Abfallwirtschaftsbetrieb Esslingen, ART Trier, AWB Bad Kreuznach, AWB Köln, AWB Oldenburg, AWIDO Online, Berlin Recycling, Berliner Stadtreinigungsbetriebe, Bielefeld, Bogenschütz Entsorgung, Bürgerportal, C-Trace, EGN Abfallkalender, Jumomind, KAEV Niederlausitz, Kreiswirtschaftsbetriebe Goslar, KV Cochem-Zell, KWU Entsorgung Landkreis Oder-Spree, Landkreis Erlangen-Höchstadt, Landkreis Rhön Grabfeld, Landkreis Schwäbisch Hall, Landkreis Wittmund, MZV Bidenkopf, Müllmax, Neunkirchen Siegerland, RegioEntsorgung Städteregion Aachen, Rhein-Hunsrück Entsorgung (RHE), Sector 27 - Datteln, Marl, Oer-Erkenschwick, Stadt Willich, Stadtreinigung Dresden, Stadtreinigung Hamburg, Stadtreinigung Leipzig, StadtService Brühl, Städteservice Raunheim Rüsselsheim, Südbrandenburgischer Abfallzweckverband, Wermelskirchen, Wolfsburger Abfallwirtschaft und Straßenreinigung | +| Lithuania | Kauno švara | +| Netherlands | ACV Group, Alpen an den Rijn, Area Afval, Avalex, Avri, Bar Afvalbeheer, Cyclus NV, Dar, Den Haag, GAD, Gemeente Almere, Gemeente Berkelland, Gemeente Cranendonck, Gemeente Hellendoorn, Gemeente Lingewaard, Gemeente Meppel, Gemeente Middelburg + Vlissingen, Gemeente Peel en Maas, Gemeente Schouwen-Duiveland, Gemeente Sudwest-Fryslan, Gemeente Venray, Gemeente Voorschoten, Gemeente Wallre, Gemeente Westland, HVC Groep, Meerlanden, Mijn Blink, PreZero, Purmerend, RAD BV, Reinigingsbedrijf Midden Nederland, Reinis, Spaarne Landen, Stadswerk 072, Twente Milieu, Waardlanden, Ximmio, ZRD | +| New Zealand | Auckland Council, Christchurch City Council, Gore, Invercargill & Southland, Horowhenua District Council, Waipa District Council, Wellington City Council | +>>>>>>> 5fcf5fa (automatically update info.md) | Norway | Min Renovasjon, Oslo Kommune | -| Poland | Warsaw, Multiple Communities (Echoharmonogram) | -| Sweden | Lerum, Ronneby Miljöteknik, SSAM, Srvatervinning, Sysav, Vasyd | -| Switzerland | A-region, Lindau | -| USA | PGT.ST, Republic Services, Seattle Public Utilities | -| UK | Bracknell Forest Council, Bradford Metropolitan District Council, Braintree District Council, Cambridge City Council, Canterbury City Council, Cheshire East Council, Chesterfield Borough Council, Colchester Borough Council, Cornwall Council, Derby City Council, Eastbourne Borough Council, Elmbridge Borough Council, Guildford Borough Council, Harborough District Council, Huntingdonshire District Council, The Royal Borough of Kingston, Lewes District Council, London Borough of Lewisham, Manchester City Council, Newcastle City Council, North Somerset Council, Nottingham City Council, Peterborough City Council, Richmondshire District Council, Rushmoor Borough Council, Sheffield City Council, South Cambridgeshire District Council, South Hams, South Norfolk and Broadland Council, Stevenage Borough Council, Tewkesbury Borough Council, City of York Council, Walsall Council, West Berkshire Council, West Devon, Wiltshire Council | +| Poland | Ecoharmonogram, Warsaw | +| Sweden | Lerum Vatten och Avlopp, Ronneby Miljöteknik, SRV Återvinning, SSAM, Sysav Sophämntning, VA Syd Sophämntning | +| Switzerland | A-Region, Lindau | +| United Kingdom | Bracknell Forest Council, Bradford Metropolitan District Council, Braintree District Council, Cambridge City Council, Canterbury City Council, Cheshire East Council, Chesterfield Borough Council, City of York Council, Colchester Borough Council, Cornwall Council, Derby City Council, Eastbourne Borough Council, Elmbridge Borough Council, Environment First, FCC Environment, Guildford Borough Council, Harborough District Council, Huntingdonshire District Council, Lewes District Council, London Borough of Lewisham, Manchester City Council, Middlesbrough Council, Newcastle City Council, North Somerset Council, Nottingham City Council, Peterborough City Council, Richmondshire District Council, Rushmoor Borough Council, Sheffield City Council, South Cambridgeshire District Council, South Hams District Council, South Norfolk and Broadland Council, Stevenage Borough Council, Tewkesbury Borough Council, The Royal Borough of Kingston Council, Walsall Council, West Berkshire Council, West Devon Borough Council, Wiltshire Council | +| United States of America | City of Pittsburgh, Republic Services, Seattle Public Utilities | + For full details on supported service providers, and more project details, visit us on [GitHub](https://github.com/mampfes/hacs_waste_collection_schedule). diff --git a/make_docu.py b/make_docu.py index f54b376e..a75b226e 100755 --- a/make_docu.py +++ b/make_docu.py @@ -94,6 +94,7 @@ def main(): zombies.append(s) update_readme_md(countries) + update_info_md(countries) print("Zombies =========================") for z in zombies: @@ -139,6 +140,31 @@ def update_readme_md(countries): f.write(md) +def update_info_md(countries): + # generate country list + str = "" + for country in sorted(countries): + str += f"| {country} | " + str += ", ".join( + [e.title for e in sorted(countries[country], key=lambda e: e.title.lower())] + ) + str += " |\n" + + # read entire file + with open("info.md") as f: + md = f.read() + + # find beginning and end of country section + start_pos = md.index(START_COUNTRY_SECTION) + len(START_COUNTRY_SECTION) + 1 + end_pos = md.index(END_COUNTRY_SECTION) + + md = md[:start_pos] + str + md[end_pos:] + + # write entire file + with open("info.md", "w") as f: + f.write(md) + + class SourceInfo: def __init__(self, filename, title, url, country): self._filename = filename From 2d14484b9ecd8170b9c3fe29a16509cce98be117 Mon Sep 17 00:00:00 2001 From: mampfes Date: Wed, 28 Dec 2022 13:06:07 +0100 Subject: [PATCH 068/127] update known issues --- README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 521527dc..f36c8bd3 100644 --- a/README.md +++ b/README.md @@ -260,6 +260,8 @@ Waste collection schedules in the following formats and countries are supported. +--- + # Installation and Configuration ![hacs badge](https://img.shields.io/badge/HACS-Default-orange) @@ -302,12 +304,7 @@ If you'd like to help with any of these, please raise an [issue](https://github. The following waste service providers return errors when running the test_source script: -- [ ] berlin_recycling_de, JSONDecodeError -- [ ] abfall_io, Traunstein test case fails -- [ ] sector27_de, TimeoutError -- [ ] banyule_vic_gov_au, JSONDecodeError -- [ ] grafikai_svara_lt, TimeoutError -- [ ] warszawal9115_pl, HTTPError +- `banyule_vic_gov_au`: JSONDecodeError, causes by Captcha If you can fix any of these, please raise a Pull Request with the updates. From 38d906bb82a3266300b320a3eda3b475d7366aab Mon Sep 17 00:00:00 2001 From: mampfes Date: Wed, 28 Dec 2022 13:17:08 +0100 Subject: [PATCH 069/127] rename update docu links script --- make_docu.py => update_docu_links.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename make_docu.py => update_docu_links.py (100%) diff --git a/make_docu.py b/update_docu_links.py similarity index 100% rename from make_docu.py rename to update_docu_links.py From 6cefdd7591eb181a5c958aa3c4d31cfa7e7e04da Mon Sep 17 00:00:00 2001 From: mampfes Date: Wed, 28 Dec 2022 13:20:46 +0100 Subject: [PATCH 070/127] fix docu after latest merge --- README.md | 108 +++++++++--------- .../source/geoport_nwm_de.py | 10 +- info.md | 9 +- 3 files changed, 58 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index f36c8bd3..238a0de1 100644 --- a/README.md +++ b/README.md @@ -64,63 +64,57 @@ Waste collection schedules in the following formats and countries are supported.
Germany -

- -- [Abfall.IO / AbfallPlus.de](/doc/source/abfall_io.md) -- [AbfallNavi.de (RegioIT.de)](/doc/source/abfallnavi_de.md) -- [Abfallkalender Würzburg](/doc/source/wuerzburg_de.md) -- [Abfalltermine Forchheim](/doc/source/abfalltermine_forchheim_de.md) -- [Abfallwirtschaft Bremen](/doc/source/c_trace_de.md) -- [Abfallwirtschaft Landkreis Harburg](/doc/source/aw_harburg_de.md) -- [Abfallwirtschaft Landkreis Wolfenbüttel](/doc/source/alw_wf_de.md) -- [Abfallwirtschaft Neckar-Odenwald-Kreis](/doc/source/awn_de.md) -- [Abfallwirtschaft Rendsburg](/doc/source/awr_de.md) -- [Abfallwirtschaft Stuttgart](/doc/source/stuttgart_de.md) -- [Abfallwirtschaft Südholstein](/doc/source/awsh_de.md) -- [Abfallwirtschaft Zollernalbkreis](/doc/source/abfall_zollernalbkreis_de.md) -- [Alb-Donau-Kreis](/doc/source/buergerportal_de.md) -- [ART Trier](/doc/source/art_trier_de.md) -- [AWB Bad Kreuznach](/doc/source/awb_bad_kreuznach_de.md) -- [AWB Esslingen](/doc/source/awb_es_de.md) -- [AWB Landkreis Augsburg](/doc/source/c_trace_de.md) -- [AWB Limburg-Weilburg](/doc/source/awb_lm_de.md) -- [AWB Oldenburg](/doc/source/awb_oldenburg_de.md) -- [AWBKoeln.de](/doc/source/awbkoeln_de.md) -- [AWIDO-online.de](/doc/source/awido_de.md) -- [Berlin-Recycling.de](/doc/source/berlin_recycling_de.md) -- [Bogenschuetz-Entsorgung.de](/doc/source/infeo_at.md) -- [BSR.de / Berliner Stadtreinigungsbetriebe](/doc/source/bsr_de.md) -- [C-Trace.de](/doc/source/c_trace_de.md) -- [Cochem-Zell](/doc/source/cochem_zell_online_de.md) -- [EGN-Abfallkalender.de](/doc/source/egn_abfallkalender_de.md) -- [Erlangen-Höchstadt](/doc/source/erlangen_hoechstadt_de.md) -- [Geodatenportal Nordwestmecklenburg](./doc/source/geoport_nwm_de.md) -- [Jumomind.de](/doc/source/jumomind_de.md) -- [KAEV Niederlausitz](/doc/source/kaev_niederlausitz_de.md) -- [KWB-Goslar.de](/doc/source/kwb_goslar_de.md) -- [KWU-Entsorgung](/doc/source/kwu_de.md) -- [Landkreis-Wittmund.de](/doc/source/landkreis_wittmund_de.md) -- [Landkreis Rhön Grabfeld](/doc/source/landkreis_rhoen_grabfeld.md) -- [Landkreis Schwäbisch Hall](/doc/source/lrasha_de.md) -- [Muellmax.de](/doc/source/muellmax_de.md) -- [MyMuell App](/doc/source/jumomind_de.md) -- [Neunkirchen Siegerland](/doc/source/abfall_neunkirchen_siegerland_de.md) -- [RegioEntsorgung](/doc/source/regioentsorgung_de.md) -- [Rhein-Hunsrück Entsorgung (RHE)](/doc/source/rh_entsorgung_de.md) -- [Sector27.de](/doc/source/sector27_de.md) -- [Stadtreinigung Dresden](/doc/source/stadtreinigung_dresden_de.md) -- [Stadtreinigung.Hamburg](/doc/source/stadtreinigung_hamburg.md) -- [Stadtreinigung-Leipzig.de](/doc/source/stadtreinigung_leipzig_de.md) -- [Stadt-Willich.de](/doc/source/stadt_willich_de.md) -- [StadtService Brühl](/doc/source/stadtservice_bruehl_de.md) -- [Städteservice Raunheim Rüsselsheim](/doc/source/staedteservice_de.md) -- [Südbrandenburgischer Abfallzweckverband](/doc/source/sbazv_de.md) -- [Umweltbetrieb Stadt Bielefeld](/doc/source/bielefeld_de.md) -- [WAS Wolfsburg](/doc/source/was_wolfsburg_de.md) -- [Wermeldkirchen](/doc/source/wermelskirchen_de.md) -- [Zweckverband Abfallwirtschaft Werra-Meißner-Kreis](/doc/source/zva_wmk_de.md) - -

+- [Abfall Stuttgart](/doc/source/stuttgart_de.md) / service.stuttgart.de +- [Abfall.IO / AbfallPlus](/doc/source/abfall_io.md) / abfallplus.de +- [Abfallkalender Würzburg](/doc/source/wuerzburg_de.md) / wuerzburg.de +- [AbfallNavi (RegioIT.de)](/doc/source/abfallnavi_de.md) / regioit.de +- [Abfalltermine Forchheim](/doc/source/abfalltermine_forchheim_de.md) / abfalltermine-forchheim.de +- [Abfallwirtschaft Alb-Donau-Kreis](/doc/source/buergerportal_de.md) / aw-adk.de +- [Abfallwirtschaft Landkreis Harburg](/doc/source/aw_harburg_de.md) / landkreis-harburg.de +- [Abfallwirtschaft Landkreis Wolfenbüttel](/doc/source/alw_wf_de.md) / alw-wf.de +- [Abfallwirtschaft Neckar-Odenwald-Kreis](/doc/source/awn_de.md) / awn-online.de +- [Abfallwirtschaft Rendsburg](/doc/source/awr_de.md) / awr.de +- [Abfallwirtschaft Südholstein](/doc/source/awsh_de.md) / awsh.de +- [Abfallwirtschaft Werra-Meißner-Kreis](/doc/source/zva_wmk_de.md) / zva-wmk.de +- [Abfallwirtschaft Zollernalbkreis](/doc/source/abfall_zollernalbkreis_de.md) / abfallkalender-zak.de +- [Abfallwirtschaftsbetrieb Esslingen](/doc/source/awb_es_de.md) / awb-es.de +- [ART Trier](/doc/source/art_trier_de.md) / art-trier.de +- [AWB Bad Kreuznach](/doc/source/awb_bad_kreuznach_de.md) / app.awb-bad-kreuznach.de +- [AWB Köln](/doc/source/awbkoeln_de.md) / awbkoeln.de +- [AWB Oldenburg](/doc/source/awb_oldenburg_de.md) / oldenburg.de +- [AWIDO Online](/doc/source/awido_de.md) / awido-online.de +- [Berlin Recycling](/doc/source/berlin_recycling_de.md) / berlin-recycling.de +- [Berliner Stadtreinigungsbetriebe](/doc/source/bsr_de.md) / bsr.de +- [Bielefeld](/doc/source/bielefeld_de.md) / bielefeld.de +- [Bogenschütz Entsorgung](/doc/source/infeo_at.md) / bogenschuetz-entsorgung.de +- [Bürgerportal](/doc/source/buergerportal_de.md) / c-trace.de +- [C-Trace](/doc/source/c_trace_de.md) / c-trace.de +- [EGN Abfallkalender](/doc/source/egn_abfallkalender_de.md) / egn-abfallkalender.de +- [Jumomind](/doc/source/jumomind_de.md) / jumomind.de +- [KAEV Niederlausitz](/doc/source/kaev_niederlausitz.md) / kaev.de +- [Kreiswirtschaftsbetriebe Goslar](/doc/source/kwb_goslar_de.md) / kwb-goslar.de +- [KV Cochem-Zell](/doc/source/buergerportal_de.md) / cochem-zell-online.de +- [KWU Entsorgung Landkreis Oder-Spree](/doc/source/kwu_de.md) / kwu-entsorgung.de +- [Landkreis Erlangen-Höchstadt](/doc/source/erlangen_hoechstadt_de.md) / erlangen-hoechstadt.de +- [Landkreis Nordwestmecklenburg](/doc/source/geoport_nwm_de.md) / geoport-nwm.de +- [Landkreis Rhön Grabfeld](/doc/source/landkreis_rhoen_grabfeld.md) / abfallinfo-rhoen-grabfeld.de +- [Landkreis Schwäbisch Hall](/doc/source/lrasha_de.md) / lrasha.de +- [Landkreis Wittmund](/doc/source/landkreis_wittmund_de.md) / landkreis-wittmund.de +- [MZV Bidenkopf](/doc/source/buergerportal_de.md) / mzv-biedenkopf.de +- [Müllmax](/doc/source/muellmax_de.md) / muellmax.de +- [Neunkirchen Siegerland](/doc/source/abfall_neunkirchen_siegerland_de.md) / neunkirchen-siegerland.de +- [RegioEntsorgung Städteregion Aachen](/doc/source/regioentsorgung_de.md) / regioentsorgung.de +- [Rhein-Hunsrück Entsorgung (RHE)](/doc/source/rh_entsorgung_de.md) / rh-entsorgung.de +- [Sector 27 - Datteln, Marl, Oer-Erkenschwick](/doc/source/sector27_de.md) / muellkalender.sector27.de +- [Stadt Willich](/doc/source/stadt_willich_de.md) / stadt-willich.de +- [Stadtreinigung Dresden](/doc/source/stadtreinigung_dresden_de.md) / dresden.de +- [Stadtreinigung Hamburg](/doc/source/stadtreinigung_hamburg.md) / stadtreinigung.hamburg +- [Stadtreinigung Leipzig](/doc/source/stadtreinigung_leipzig_de.md) / stadtreinigung-leipzig.de +- [StadtService Brühl](/doc/source/stadtservice_bruehl_de.md) / stadtservice-bruehl.de +- [Städteservice Raunheim Rüsselsheim](/doc/source/staedteservice_de.md) / staedteservice.de +- [Südbrandenburgischer Abfallzweckverband](/doc/source/sbazv_de.md) / sbazv.de +- [Wermelskirchen](/doc/source/wermelskirchen_de.md) / wermelskirchen.de +- [Wolfsburger Abfallwirtschaft und Straßenreinigung](/doc/source/was_wolfsburg_de.md) / was-wolfsburg.de
diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/geoport_nwm_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/geoport_nwm_de.py index 77242fdf..a7cff602 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/geoport_nwm_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/geoport_nwm_de.py @@ -1,18 +1,19 @@ import datetime -import requests import urllib + +import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS TITLE = "Landkreis Nordwestmecklenburg" DESCRIPTION = "Source for Landkreis Nordwestmecklenburg" -URL = "https://www.geoport-nwm.de/de/abfuhrtermine-geoportal.html" +URL = "https://www.geoport-nwm.de" TEST_CASES = { "Rüting": {"district": "Rüting"}, "Grevenstein u. ...": {"district": "Grevenstein u. Ausbau"}, "Seefeld": {"district": "Seefeld/ Testorf- Steinfort"}, "1100l": {"district": "Groß Stieten (1.100 l Behälter)"}, - "kl. Bünsdorf": {"district": "Klein Bünsdorf"} + "kl. Bünsdorf": {"district": "Klein Bünsdorf"}, } @@ -45,7 +46,8 @@ class Source: def fetch_year(self, year): arg = convert_to_arg(self._district) r = requests.get( - f"https://www.geoport-nwm.de/nwm-download/Abfuhrtermine/ICS/{year}/{arg}.ics") + f"https://www.geoport-nwm.de/nwm-download/Abfuhrtermine/ICS/{year}/{arg}.ics" + ) r.raise_for_status() return self._ics.convert(r.text) diff --git a/info.md b/info.md index 24359f63..c78133e0 100644 --- a/info.md +++ b/info.md @@ -20,17 +20,10 @@ Waste collection schedules from service provider web sites are updated daily, de | Austria | Burgenländischer Müllverband, infeo, Stadtservice Korneuburg, Umweltprofis, WSZ Moosburg | | Belgium | Hygea, Recycle! | | Canada | City of Toronto | -<<<<<<< HEAD -| Germany | Abfall.IO / AbfallPlus, AbfallNavi (RegioIT.de), Abfallkalender Würzburg, Abfalltermine Forchheim, Abfallwirtschaft Bremen, Abfallwirtschaft Landkreis Harburg, Abfallwirtschaft Landkreis Wolfenbüttel, Abfallwirtschaft Neckar-Odenwald-Kreis, Abfallwirtschaft Rendsburg, Abfallwirtschaft Stuttgart, Abfallwirtschaft Südholstein, Abfallwirtschaft Zollernalbkreis, Alb-Donau-Kreis, ART Trier, AWB Bad Kreuznach, AWB Esslingen, AWB Landkreis Ausburg, AWB Limburg-Weilburg, AWB Oldenburg, AWBKoeln, AWIDO-online, Berlin-Recycling, Bogenschuetz-Entsorgung, Biedenkopf MZF, BSR.de / Berliner Stadtreinigungsbetriebe, C-Trace, Cochem-Zell, EGN-Abfallkalender, Erlangen-Höchstadt, Landkreis Nordwestmecklenburg, Jumomind, KAEV Niederlausitz, KWB-Goslar, KWU-Entsorgung, Landkreis Wittmund, Landkreis Rhön Grabfeld, Landkreis Schwäbisch Hall, Muellmax, MyMuell App, Neunkirchen Siegerland, RegioEntsorgung, Rhein-Hunsrück Entsorgung (RHE), Sector27, Stadtreinigung Dresden, Stadtreinigung.Hamburg, Stadtreinigung-Leipzig, Stadt-Willich, StadtService Brühl, Städteservice Raunheim Rüsselsheim, Südbrandenburgischer Abfallzweckverband, Umweltbetrieb Stadt Bielefeld, WAS Wolfsburg, Wermeldkirchen, Zweckverband Abfallwirtschaft Werra-Meißner-Kreis | -| Lituania | Kauno švara | -| Netherlands | HVCGroep, Ximmio | -| New Zealand | Auckland, Christchurch, Gore, Invercargill & Shouthand, Horowhenua District, Wapia District, Wellington | -======= -| Germany | Abfall Stuttgart, Abfall.IO / AbfallPlus, Abfallkalender Würzburg, AbfallNavi (RegioIT.de), Abfalltermine Forchheim, Abfallwirtschaft Alb-Donau-Kreis, Abfallwirtschaft Landkreis Harburg, Abfallwirtschaft Landkreis Wolfenbüttel, Abfallwirtschaft Neckar-Odenwald-Kreis, Abfallwirtschaft Rendsburg, Abfallwirtschaft Südholstein, Abfallwirtschaft Werra-Meißner-Kreis, Abfallwirtschaft Zollernalbkreis, Abfallwirtschaftsbetrieb Esslingen, ART Trier, AWB Bad Kreuznach, AWB Köln, AWB Oldenburg, AWIDO Online, Berlin Recycling, Berliner Stadtreinigungsbetriebe, Bielefeld, Bogenschütz Entsorgung, Bürgerportal, C-Trace, EGN Abfallkalender, Jumomind, KAEV Niederlausitz, Kreiswirtschaftsbetriebe Goslar, KV Cochem-Zell, KWU Entsorgung Landkreis Oder-Spree, Landkreis Erlangen-Höchstadt, Landkreis Rhön Grabfeld, Landkreis Schwäbisch Hall, Landkreis Wittmund, MZV Bidenkopf, Müllmax, Neunkirchen Siegerland, RegioEntsorgung Städteregion Aachen, Rhein-Hunsrück Entsorgung (RHE), Sector 27 - Datteln, Marl, Oer-Erkenschwick, Stadt Willich, Stadtreinigung Dresden, Stadtreinigung Hamburg, Stadtreinigung Leipzig, StadtService Brühl, Städteservice Raunheim Rüsselsheim, Südbrandenburgischer Abfallzweckverband, Wermelskirchen, Wolfsburger Abfallwirtschaft und Straßenreinigung | +| Germany | Abfall Stuttgart, Abfall.IO / AbfallPlus, Abfallkalender Würzburg, AbfallNavi (RegioIT.de), Abfalltermine Forchheim, Abfallwirtschaft Alb-Donau-Kreis, Abfallwirtschaft Landkreis Harburg, Abfallwirtschaft Landkreis Wolfenbüttel, Abfallwirtschaft Neckar-Odenwald-Kreis, Abfallwirtschaft Rendsburg, Abfallwirtschaft Südholstein, Abfallwirtschaft Werra-Meißner-Kreis, Abfallwirtschaft Zollernalbkreis, Abfallwirtschaftsbetrieb Esslingen, ART Trier, AWB Bad Kreuznach, AWB Köln, AWB Oldenburg, AWIDO Online, Berlin Recycling, Berliner Stadtreinigungsbetriebe, Bielefeld, Bogenschütz Entsorgung, Bürgerportal, C-Trace, EGN Abfallkalender, Jumomind, KAEV Niederlausitz, Kreiswirtschaftsbetriebe Goslar, KV Cochem-Zell, KWU Entsorgung Landkreis Oder-Spree, Landkreis Erlangen-Höchstadt, Landkreis Nordwestmecklenburg, Landkreis Rhön Grabfeld, Landkreis Schwäbisch Hall, Landkreis Wittmund, MZV Bidenkopf, Müllmax, Neunkirchen Siegerland, RegioEntsorgung Städteregion Aachen, Rhein-Hunsrück Entsorgung (RHE), Sector 27 - Datteln, Marl, Oer-Erkenschwick, Stadt Willich, Stadtreinigung Dresden, Stadtreinigung Hamburg, Stadtreinigung Leipzig, StadtService Brühl, Städteservice Raunheim Rüsselsheim, Südbrandenburgischer Abfallzweckverband, Wermelskirchen, Wolfsburger Abfallwirtschaft und Straßenreinigung | | Lithuania | Kauno švara | | Netherlands | ACV Group, Alpen an den Rijn, Area Afval, Avalex, Avri, Bar Afvalbeheer, Cyclus NV, Dar, Den Haag, GAD, Gemeente Almere, Gemeente Berkelland, Gemeente Cranendonck, Gemeente Hellendoorn, Gemeente Lingewaard, Gemeente Meppel, Gemeente Middelburg + Vlissingen, Gemeente Peel en Maas, Gemeente Schouwen-Duiveland, Gemeente Sudwest-Fryslan, Gemeente Venray, Gemeente Voorschoten, Gemeente Wallre, Gemeente Westland, HVC Groep, Meerlanden, Mijn Blink, PreZero, Purmerend, RAD BV, Reinigingsbedrijf Midden Nederland, Reinis, Spaarne Landen, Stadswerk 072, Twente Milieu, Waardlanden, Ximmio, ZRD | | New Zealand | Auckland Council, Christchurch City Council, Gore, Invercargill & Southland, Horowhenua District Council, Waipa District Council, Wellington City Council | ->>>>>>> 5fcf5fa (automatically update info.md) | Norway | Min Renovasjon, Oslo Kommune | | Poland | Ecoharmonogram, Warsaw | | Sweden | Lerum Vatten och Avlopp, Ronneby Miljöteknik, SRV Återvinning, SSAM, Sysav Sophämntning, VA Syd Sophämntning | From 093c1c63ae17fc37e9648dc1db8d908d5046d5ed Mon Sep 17 00:00:00 2001 From: mampfes Date: Wed, 28 Dec 2022 17:29:57 +0100 Subject: [PATCH 071/127] add description for make_docu_links --- doc/contributing.md | 166 +++++++++++++++++++++++++++++++------------- 1 file changed, 116 insertions(+), 50 deletions(-) diff --git a/doc/contributing.md b/doc/contributing.md index 5161f268..416e0d53 100644 --- a/doc/contributing.md +++ b/doc/contributing.md @@ -1,11 +1,13 @@ Waste Collection Schedule logo # Contributing To Waste Collection Schedule + ![python badge](https://img.shields.io/badge/Made%20with-Python-orange) ![github contributors](https://img.shields.io/github/contributors/mampfes/hacs_waste_collection_schedule?color=orange) ![last commit](https://img.shields.io/github/last-commit/mampfes/hacs_waste_collection_schedule?color=orange) There are several ways of contributing to this project, including: + - Providing new service providers - Updating or improving the documentation - Helping answer/fix any issues raised @@ -14,6 +16,7 @@ There are several ways of contributing to this project, including: ## Adding New Service Providers ### Fork And Clone The Repository, And Checkout A New Branch + In GitHub, navigate to the repository [homepage](https://github.com/mampfes/hacs_waste_collection_schedule). Click the `fork` button at the top-right side of the page to fork the repository. ![fork](/images/wcs_fork_btn.png) @@ -23,29 +26,35 @@ Navigate to your fork's homepage, click the `code` button and copy the url. ![code](/images/wcs_code_btn.png) On your local machine, open a terminal and navigate to the location where you want the cloned directory. Type `git clone` and paste in the url copied earlier. It should look something like this, but with your username replacing `YOUR-GITHUB-USERNAME`: + ```bash git clone https://github.com/YOUR-GITHUB-USERNAME/hacs_waste_collection_schedule ``` Before making any changes, create a new branch to work on. + ```bash git branch ``` - For example, if you were adding a new provider called abc.com, you could do - ```bash + +For example, if you were adding a new provider called abc.com, you could do + +```bash git branch adding_abc_com ``` For more info on forking/cloning a repository, see GitHub's [fork-a-repo](https://docs.github.com/en/get-started/quickstart/fork-a-repo) document. ### Files Required For A New Service Provider + The following files need to be provided to support a new service provider: + - A python `source script` that retrieves the collection schedule information, formats it appropriately, and has test cases that can be used to confirm functionality. - A `source markdown (.md)` file that describes how to configure the new source and sensor, with examples. - An updated `README.md` file containing details of the new service provider. - An updated `info.md` file containing details of the new service provider. -The framework contains a test script that can be used to confirm source scripts are retrieving and returning correctly formatted waste collection schedules. +The framework contains a [test script](#test-the-new-source-file) that can be used to confirm source scripts are retrieving and returning correctly formatted waste collection schedules. ### Python Source Script @@ -57,19 +66,17 @@ The script should have the following general structure import datetime from waste_collection_schedule import Collection - +TITLE = "My Council" # Title will show up in README.md and info.md DESCRIPTION = "Source script for abc.com" # Describe your source -URL = "abc.com" # Insert url to service homepage +URL = "https://abc.com" # Insert url to service homepage. URL will show up in README.md and info.md TEST_CASES = { # Insert arguments for test cases to be used by test_sources.py script "TestName1": {"arg1": 100, "arg2": "street"} "TestName2": {"arg1": 200, "arg2": "road"} "TestName3": {"arg1": 300, "arg2": "lane"} } -API_URLS = { # Dict of API end-points if required - "address_search": "https://abc.com/search/", - "collection": "https://abc.com/search/{}/", -} -ICONS = { # Dict of waste types and suitable mdi icons + +API_URL = "https://abc.com/search/" +ICON_MAP = { # Optional: Dict of waste types and suitable mdi icons "DOMESTIC": "mdi:trash-can", "RECYCLE": "mdi:recycle", "ORGANIC": "mdi:leaf", @@ -94,20 +101,22 @@ class Source: Collection( date = datetime.datetime(2020, 4, 11), # Collection date t = "Waste Type", # Collection type - icon = ICONS.get("Waste Type"), # Collection icon + icon = ICON_MAP.get("Waste Type"), # Collection icon ) ) return entries ``` + Filtering of data for waste types or time periods is a functionality of the framework and should not be done by the source script. Therefore: + - A source script should return all data for all available waste types. - A source script should **not** provide options to limit the returned waste types. - A source script should return all data for the entire time period available (including past dates if they are returned). - A source script should **not** provide a configuration option to limit the requested time frame. - ### Service Provider Markdown File + Create a new markdown file in the `custom_components/waste_collection_schedule/doc/source` folder. The file name should be the url of your service provider in lower case, for example `abc_com.md` for `https://www.abc.com`. The markdown file should have the following general structure: @@ -119,6 +128,7 @@ The markdown file should have the following general structure: For example: **Configuration via configuration.yaml** + ```yaml waste_collection_schedule: sources: @@ -131,7 +141,8 @@ waste_collection_schedule: **uprn** _(string) (required)_ : The unique 12-digit identifier for your property -**Example** +Example: + ```yaml waste_collection_schedule: sources: @@ -139,19 +150,65 @@ waste_collection_schedule: args: uprn: "e3850cac2d5b" ``` + Note: Your uprn can be found on invoices from your service provider +### Update Links in README.md and info.md -### Updated README File -The README.md file in the top level folder contains a list of supported service providers. Please add your new entry to the relevant country section, creating a new section if yours is the first provider for that country. The entry should contain the name of the service provider and a link to the service providers markdown file. For example: -```markdown -- [Abfall.IO / AbfallPlus.de](/doc/source/abfall_io.md) +The `README.md` file in the top level folder contains a list of supported service providers. + +The `info.md` is rendered in the HACS user interface within Home Assistant and gives potential users a summary of what the component does, and the service providers supported. + +The links in both files can be updated automatically using the script `update_docu_links.py` in the top-level directory: + +```bash +./make_docu_links.py ``` -### Updated info.md File -The `info.md` is rendered in the HACS user interface within Home Assistant and gives potential users a summary of what the component does, and the service providers supported. Please add your new service provider to the appropriate country listed in the table. If yours is the first service provider for a country, create a new table row. +The script iterates through all source files and extracts some meta information like title and url. It is therefore important to set the attributes in the source file correctly. By default, the country classification is derived from the file name. If this doesn't match, the country code can be overwritten with the attribute `COUNTRY`. + +| Attribute | Type | Description | +|-|-|-| +| TITLE | String | Title of the source. Used as link title in README.md and info.md. | +| URL | String | Service provider homepage URL. The idea is to help users to identify their service provider if they search for an URL instead of a service provider name. The abbreviated domain name is therefore displayed next to the source title in README.md. | +| COUNTRY | String | [Optional] Overwrite default country code which is derived from source file name. | +| EXTRA_INFO | List of Dicts or Callable | [Optional] Used to add extra links in README.md and info.md if the source supports multiple service providers at the same time. The following keys can be added to the dict: `title`, `url`, `country`. In case a key is missing, the corresponding value from the attributes above will be used instead. | + +Examples: + +```python +# Standard case: Source supports one service provider +TITLE = "ART Trier" +URL = "https://www.art-trier.de" + +# Special case: Overwrite country code +TITLE = "RecycleSmart" +URL = "https://www.recyclesmart.com/" +COUNTRY = "au" + +# Special case: Source supports multiple service provider which should be added to README.md and info.md +TITLE = "FCC Environment" +URL = "https://fccenvironment.co.uk" +EXTRA_INFO = [ + { + "title": "Harborough District Council", + "url": "https://harborough.gov.uk" + }, + { + "title": "South Hams District Council", + "url": "https://southhams.gov.uk/" + }, +] + +# Special case: Same as before, but EXTRA_INFO created by a function from existing data +TITLE = "Bürgerportal" +URL = "https://www.c-trace.de" +def EXTRA_INFO(): + return [ { "title": s["title"], "url": s["url"] } for s in SERVICE_MAP ] +``` ### Test The New Source File + Debugging a source script within Home Assistant is not recommended. Home Assistant's start-up process is too slow for fast debugging cycles. To help with debugging/troubleshooting, the Waste Collection Schedule framework contains a command line script that can be used to test source scripts. The script iterates through the `test cases` defined in the source script passing each set of arguments to the source script and prints the results. The script supports the following options: @@ -159,60 +216,69 @@ The script supports the following options: | Option | Argument | Description | |--------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------| | `-s` | SOURCE | [Source name](https://github.com/mampfes/hacs_waste_collection_schedule#source-configuration-variables) (source file name without ending `.py`) | -| `-l` | - | List all found dates | -| `-i` | - | Add icon name to output. Only effective together with `-l`. | +| `-l` | - | List all found dates. | +| `-i` | - | Add icon name to output. Only effective together with `-l`. | For debugging purposes of a single source, it is recommended to use the `-s SOURCE` option. If used without any arguments provided, the script tests every script in the `custom_components/waste_collection_schedule/waste_collection_schedule/source` folder and prints the number of found entries for every test case. To use it: + 1. Navigate to the `custom_components/waste_collection_schedule/waste_collection_schedule/test/` directory 2. Confirm the `test_sources.py` script is present 3. Execute the test script. For example, testing the abfall_io.py source script would be: -```bash -test_sources.py -s abfall_io -``` + + ```bash + test_sources.py -s abfall_io + ``` + 4. Confirm the results returned match expectation. For example, testing abfall_io returns: -```text -Testing source abfall_io ... - found 285 entries for Waldenbuch - found 58 entries for Landshut - found 109 entries for Schoenmackers - found 3 entries for Freudenstadt - found 211 entries for Ludwigshafen am Rhein - found 119 entries for Traunstein - found 287 entries for Thalheim -``` + + ```text + Testing source abfall_io ... + found 285 entries for Waldenbuch + found 58 entries for Landshut + found 109 entries for Schoenmackers + found 3 entries for Freudenstadt + found 211 entries for Ludwigshafen am Rhein + found 119 entries for Traunstein + found 287 entries for Thalheim + ``` + 5. To view individual date entries and assigned icons, use the `-i -l` arguments, for example: -```bash -test_sources.py -s richmondshire_gov_uk -i -l -Testing source richmondshire_gov_uk ... - found 53 entries for test 1 - 2023-01-02: 240L GREY RUBBISH BIN [mdi:trash-can] - 2023-01-07: 55L RECYCLING BOX [mdi:recycle] - 2023-01-13: 240L GREY RUBBISH BIN [mdi:trash-can] - 2023-01-20: 55L RECYCLING BOX [mdi:recycle] - 2023-01-27: 240L GREY RUBBISH BIN [mdi:trash-can] - ... - 2023-12-01: 240L GREY RUBBISH BIN [mdi:trash-can] - 2023-12-08: 55L RECYCLING BOX [mdi:recycle] - 2023-12-15: 240L GREY RUBBISH BIN [mdi:trash-can] -``` + + ```bash + test_sources.py -s richmondshire_gov_uk -i -l + Testing source richmondshire_gov_uk ... + found 53 entries for test 1 + 2023-01-02: 240L GREY RUBBISH BIN [mdi:trash-can] + 2023-01-07: 55L RECYCLING BOX [mdi:recycle] + 2023-01-13: 240L GREY RUBBISH BIN [mdi:trash-can] + 2023-01-20: 55L RECYCLING BOX [mdi:recycle] + 2023-01-27: 240L GREY RUBBISH BIN [mdi:trash-can] + ... + 2023-12-01: 240L GREY RUBBISH BIN [mdi:trash-can] + 2023-12-08: 55L RECYCLING BOX [mdi:recycle] + 2023-12-15: 240L GREY RUBBISH BIN [mdi:trash-can] + ``` ### Sync Branch and Create A Pull Request + Having completed your changes, sync your local branch to your GitHub repo, and then create a pull request. When creating a pull request, please provide a meaningful description of what the pull request covers. Ideally it should cite the service provider, confirm the .py, .md, README and info.md files have all been updated, and the output of the test_sources.py script demonstrating functionality. Once submitted a number of automated tests are run against the updated files to confirm they can be merged into the master branch. Note: Pull requests from first time contributors also undergo a manual code review before a merge confirmation in indicated. Once a pull request has been merged into the master branch, you'll receive a confirmation message. At that point you can delete your branch if you wish. ## Update Or Improve The Documentation + Non-code contributions are welcome. If you find typos, spelling mistakes, or think there are other ways to improve the documentation please submit a pull request with updated text. Sometimes a picture paints a thousand words, so if a screenshots would better explain something, those are also welcome. ## Help Answer/Fix Issues Raised + ![GitHub issues](https://img.shields.io/github/issues-raw/mampfes/hacs_waste_collection_schedule?color=orange) Open-source projects are always a work in progress, and [issues](https://github.com/mampfes/hacs_waste_collection_schedule/issues) arise from time-to-time. If you come across a new issue, please raise it. If you have a solution to an open issue, please raise a pull request with your solution. ## Join The Home Assistant Community Discussion + ![Community Discussion](https://img.shields.io/badge/Home%20Assistant%20Community-Discussion-orange) - -The main discussion thread on Home Assistant's Community forum can be found [here](https://community.home-assistant.io/t/waste-collection-schedule-framework/186492). \ No newline at end of file +The main discussion thread on Home Assistant's Community forum can be found [here](https://community.home-assistant.io/t/waste-collection-schedule-framework/186492). From f321a47531759a4f7225149b9dd22950b478d4db Mon Sep 17 00:00:00 2001 From: mampfes Date: Wed, 28 Dec 2022 17:36:18 +0100 Subject: [PATCH 072/127] fix markdown warnings --- doc/faq.md | 14 ++++++++++---- doc/installation.md | 17 +++++++++++------ doc/online.md | 5 +++-- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/doc/faq.md b/doc/faq.md index 43911c52..134bcf6a 100644 --- a/doc/faq.md +++ b/doc/faq.md @@ -14,7 +14,6 @@ This is implemented using the following markup: The empty line after the

is intentional and is required for the expand/collapse to function correctly. --> - Waste Collection Schedule logo # Frequently Asked Questions, or "How Do I ...?" @@ -38,9 +37,9 @@ date_template: '{{value.date.strftime("%m/%d/%Y")}}' value_template: '{{value.date.strftime("%a, %m/%d/%Y")}}' date_template: '{{value.date.strftime("%a, %m/%d/%Y")}}' ``` +

-
How do I show the number of days to the next collection?

@@ -50,9 +49,9 @@ Set `value_template` within the sensor configuration: ```yaml value_template: 'in {{value.daysTo}} days' ``` +

-
How do I show Today / Tomorrow instead of in 0 / 1 days?

@@ -65,6 +64,7 @@ Set `value_template` within the sensor configuration: # returns "in X days" if value.daysTo > 1 value_template: '{% if value.daysTo == 0 %}Today{% elif value.daysTo == 1 %}Tomorrow{% else %}in {{value.daysTo}} days{% endif %}' ``` +

@@ -95,6 +95,7 @@ Set `value_template` within the sensor configuration: ```yaml value_template: '{{value.daysTo}}' ``` +

@@ -107,6 +108,7 @@ Set `value_template` within the sensor configuration: ```yaml value_template: '{{value.date.strftime("%m/%d/%Y")}}' ``` +

@@ -119,6 +121,7 @@ Set `value_template` within the sensor configuration: ```yaml value_template: '{{value.types|join(", ")}}' ``` +

@@ -162,6 +165,7 @@ waste_collection_schedule: - type: Very long recycle name alias: Recycle ``` +

@@ -179,6 +183,7 @@ waste_collection_schedule: - type: Unwanted Waste Type show: false ``` +

@@ -228,6 +233,7 @@ state: value: '[[[ return entity.state.split("|")[1] == 1 ]]]' - value: default ``` +

@@ -266,6 +272,7 @@ sensor: entity: sensor.garbage type: 'custom:garbage-collection-card' ``` +

@@ -278,4 +285,3 @@ Prerequisites: You already have dedicated sensors per waste type and want to sho Add `add_days_to: True` to the configuration of all sensors you want to sort. This will add the attribute `daysTo` which can be used by e.g. [auto-entities](https://github.com/thomasloven/lovelace-auto-entities) to sort entities by day of next collection.

- diff --git a/doc/installation.md b/doc/installation.md index 23e2edb9..0e35427e 100644 --- a/doc/installation.md +++ b/doc/installation.md @@ -3,12 +3,14 @@ # Installation ## Automated Installation Using HACS + ![hacs_badge](https://img.shields.io/badge/HACS-Default-orange) ![hacs installs](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Flauwbier.nl%2Fhacs%2Fwaste_collection_schedule) The `Waste Collection Schedule` component can be installed via [HACS](https://hacs.xyz/). This allows you to be notified of any updates or new releases of the component. After installing HACS: + 1. Visit the HACS `Integrations` panel in Home Assistant. 2. Click `Explore & Download Repositories`. 3. Search for `Waste Collection Schedule`. @@ -67,7 +69,6 @@ waste_collection_schedule: separator: SEPARATOR ``` - | Parameter | Type | Requirement | Description | |-----|-----|-----|-----| | sources: | | required | Contains information for the service provider being used | @@ -105,6 +106,7 @@ sensor: - Waste Type 1 - Waste Type 2 ``` + | Parameter | Type | Requirement | Description | |--|--|--|--| | platform | | required | waste_collection_schedule | @@ -118,8 +120,10 @@ sensor: | add_days_to | boolean | optional | Adds a `daysTo` attribute to the source entity state containing the number of days to the next collection | | types | list of strings | optional | Used to filter waste types. The sensor will only display collections matching these waste types | -## Options for _details_format_ parameter ## +## Options for _details_format_ parameter + Possible choices: + | upcoming | appointment_types | generic | |--|--|--| | shows a list of upcoming collections |shows a list of waste types and their next collection date | provides all attributes as generic Python data types. | @@ -135,15 +139,16 @@ The following variables can be used within `value_template` and `date_template`: | `value.daysTo` | Days to collection | int | 0 = today, 1 = tomorrow, etc | | `value.types` | Waste types | list of strings | Use `join` filter to join types | -## HomeAssistant Service to manually update the source +## HomeAssistant Service to manually trigger update -If you want to manually update the source, you can call the service: +If you want to trigger a manual update of the sources, you can call the service: `waste_collection_schedule.fetch_data` -Normally the configuration parametet 'fetch_time' is used to do this periodically. +Normally the configuration option 'fetch_time' is used to do this periodically. + +## Further Help -## Further help ## For a full example, see [custom_components/waste_collection_schedule/waste_collection_schedule/source/example.py](/custom_components/waste_collection_schedule/waste_collection_schedule/source/example.py). For other examples on how to configure source(s) and sensor(s), see the [FAQ](/doc/faq.md). diff --git a/doc/online.md b/doc/online.md index 0388c37a..beed334a 100644 --- a/doc/online.md +++ b/doc/online.md @@ -6,16 +6,17 @@ Github Markdown reference: https://docs.github.com/en/get-started/writing-on-git Waste Collection Schedule logo -# Waste Collection Schedule Online +# Waste Collection Schedule Online ## Community Forum + ![Community Discussion](https://img.shields.io/badge/Home%20Assistant%20Community-Discussion-orange) The main discussion thread on Home Assistant's Community forum can be found [here](https://community.home-assistant.io/t/waste-collection-schedule-framework/186492). +## YouTube -## YouTube There are some videos on YouTube: - [Bunte Mülltonnenerinnerung mit Home Assistant](https://youtu.be/MzQgARDvRww) (German language) From ffecfb91e960aa094be22222c896f8dc8fd1733b Mon Sep 17 00:00:00 2001 From: mampfes Date: Wed, 28 Dec 2022 17:39:09 +0100 Subject: [PATCH 073/127] add test cases to srvatervinning_se --- .../source/srvatervinning_se.py | 26 +++++++++++-------- doc/page_template.md | 6 ++++- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/srvatervinning_se.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/srvatervinning_se.py index 2e8bc097..eca71873 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/srvatervinning_se.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/srvatervinning_se.py @@ -1,18 +1,22 @@ import logging -import requests from datetime import datetime + +import requests from waste_collection_schedule import Collection TITLE = "SRV Återvinning" DESCRIPTION = "Source for SRV återvinning AB, Sweden" URL = "https://www.srvatervinning.se" TEST_CASES = { - "Skansvägen" : {"address":"Skansvägen" }, - "TEST2" : {"address":"tun" } + "Skansvägen": {"address": "Skansvägen"}, + "Test1": {"address": "tun"}, + "Tullinge 1": {"address": "Hanvedens allé 78"}, + "Tullinge 2": {"address": "Skogsmulles Väg 22"}, } _LOGGER = logging.getLogger(__name__) + class Source: def __init__(self, address): self._address = address @@ -20,14 +24,14 @@ class Source: def fetch(self): params = { - "query" : self._address, - "city" : "", + "query": self._address, + "city": "", } - url = 'https://www.srvatervinning.se/rest-api/srv-slamsok-rest-new/search' + url = "https://www.srvatervinning.se/rest-api/srv-slamsok-rest-new/search" r = requests.get(url, params) if r.status_code != 200: - _LOGGER.error("Error querying calender data") + _LOGGER.error("Error querying calendar data") return [] data = r.json() @@ -35,9 +39,9 @@ class Source: entries = [] for container in data["results"][0]["containers"]: - type=container["contentType"] + type = container["contentType"] for calentry in container["calendars"]: - date_obj = datetime.strptime(calentry["startDate"], '%Y-%m-%d').date() - entries.append(Collection(date_obj,type)) + date_obj = datetime.strptime(calentry["startDate"], "%Y-%m-%d").date() + entries.append(Collection(date_obj, type)) - return entries \ No newline at end of file + return entries diff --git a/doc/page_template.md b/doc/page_template.md index 7c79200b..2965be78 100644 --- a/doc/page_template.md +++ b/doc/page_template.md @@ -12,13 +12,17 @@ Github Markdown reference: https://docs.github.com/en/get-started/writing-on-git Asperiores et ratione similique sapiente impedit fuga. Ullam nihil cupiditate ut aliquam non dolorem qui. Rerum ipsa esse amet ipsam omnis et aut laboriosam. Quo dolorem veniam eius doloribus sint aut. Minus placeat quis corporis. Ipsam soluta quidem reprehenderit aspernatur sit sed. ## SOME SUB-TITLE + Repudiandae sequi sint tenetur quia perferendis quod amet nulla. Eum sed id quam nobis modi aut nulla libero. Veniam accusantium quia asperiores expedita. ### A SUB-SUB-TITLE + Officiis iure et et et. Recusandae quia hic est in in exercitationem est praesentium. Alias dolores adipisci eum. ### ANOTHER SUB-SUB-TITLE + Velit unde voluptatem quo nisi voluptatum doloribus beatae. Perferendis voluptatem eos ut et quidem culpa. Accusantium consequatur necessitatibus non. ## AND ANOTHER SUB-TITLE -Vitae animi tempora voluptatem doloribus est voluptate qui. Nihil quia numquam voluptates. Aut saepe quia doloribus. Quidem ea non nihil sit cum. Voluptates aperiam quod molestiae non et velit dolorem eos. \ No newline at end of file + +Vitae animi tempora voluptatem doloribus est voluptate qui. Nihil quia numquam voluptates. Aut saepe quia doloribus. Quidem ea non nihil sit cum. Voluptates aperiam quod molestiae non et velit dolorem eos. From f120f5167afa049cea9da3338c533f7b2a5d0020 Mon Sep 17 00:00:00 2001 From: mampfes Date: Wed, 28 Dec 2022 17:45:17 +0100 Subject: [PATCH 074/127] add extra info to a_region_ch --- README.md | 36 +++++++++++++++++++ .../source/a_region_ch.py | 6 ++++ info.md | 2 +- 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 238a0de1..e8e468f5 100644 --- a/README.md +++ b/README.md @@ -199,7 +199,43 @@ Waste collection schedules in the following formats and countries are supported.
Switzerland - [A-Region](/doc/source/a_region_ch.md) / a-region.ch +- [Andwil](/doc/source/a_region_ch.md) / a-region.ch +- [Appenzell](/doc/source/a_region_ch.md) / a-region.ch +- [Berg](/doc/source/a_region_ch.md) / a-region.ch +- [Bühler](/doc/source/a_region_ch.md) / a-region.ch +- [Eggersriet](/doc/source/a_region_ch.md) / a-region.ch +- [Gais](/doc/source/a_region_ch.md) / a-region.ch +- [Gaiserwald](/doc/source/a_region_ch.md) / a-region.ch +- [Goldach](/doc/source/a_region_ch.md) / a-region.ch +- [Grub](/doc/source/a_region_ch.md) / a-region.ch +- [Heiden](/doc/source/a_region_ch.md) / a-region.ch +- [Herisau](/doc/source/a_region_ch.md) / a-region.ch +- [Horn](/doc/source/a_region_ch.md) / a-region.ch +- [Hundwil](/doc/source/a_region_ch.md) / a-region.ch +- [Häggenschwil](/doc/source/a_region_ch.md) / a-region.ch - [Lindau](/doc/source/lindau_ch.md) / lindau.ch +- [Lutzenberg](/doc/source/a_region_ch.md) / a-region.ch +- [Muolen](/doc/source/a_region_ch.md) / a-region.ch +- [Mörschwil](/doc/source/a_region_ch.md) / a-region.ch +- [Rehetobel](/doc/source/a_region_ch.md) / a-region.ch +- [Rorschach](/doc/source/a_region_ch.md) / a-region.ch +- [Rorschacherberg](/doc/source/a_region_ch.md) / a-region.ch +- [Schwellbrunn](/doc/source/a_region_ch.md) / a-region.ch +- [Schönengrund](/doc/source/a_region_ch.md) / a-region.ch +- [Speicher](/doc/source/a_region_ch.md) / a-region.ch +- [Stein](/doc/source/a_region_ch.md) / a-region.ch +- [Steinach](/doc/source/a_region_ch.md) / a-region.ch +- [Teufen](/doc/source/a_region_ch.md) / a-region.ch +- [Thal](/doc/source/a_region_ch.md) / a-region.ch +- [Trogen](/doc/source/a_region_ch.md) / a-region.ch +- [Tübach](/doc/source/a_region_ch.md) / a-region.ch +- [Untereggen](/doc/source/a_region_ch.md) / a-region.ch +- [Urnäsch](/doc/source/a_region_ch.md) / a-region.ch +- [Wald](/doc/source/a_region_ch.md) / a-region.ch +- [Waldkirch](/doc/source/a_region_ch.md) / a-region.ch +- [Waldstatt](/doc/source/a_region_ch.md) / a-region.ch +- [Wittenbach](/doc/source/a_region_ch.md) / a-region.ch +- [Wolfhalden](/doc/source/a_region_ch.md) / a-region.ch
diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/a_region_ch.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/a_region_ch.py index 8fd8e945..ae38fd34 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/a_region_ch.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/a_region_ch.py @@ -8,6 +8,12 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "A-Region" DESCRIPTION = "Source for A-Region, Switzerland waste collection." URL = "https://www.a-region.ch" + + +def EXTRA_INFO(): + return [{"title": m} for m in MUNICIPALITIES] + + TEST_CASES = { "Andwil": {"municipality": "Andwil"}, "Rorschach": {"municipality": "Rorschach", "district": "Unteres Stadtgebiet"}, diff --git a/info.md b/info.md index c78133e0..16d00217 100644 --- a/info.md +++ b/info.md @@ -27,7 +27,7 @@ Waste collection schedules from service provider web sites are updated daily, de | Norway | Min Renovasjon, Oslo Kommune | | Poland | Ecoharmonogram, Warsaw | | Sweden | Lerum Vatten och Avlopp, Ronneby Miljöteknik, SRV Återvinning, SSAM, Sysav Sophämntning, VA Syd Sophämntning | -| Switzerland | A-Region, Lindau | +| Switzerland | A-Region, Andwil, Appenzell, Berg, Bühler, Eggersriet, Gais, Gaiserwald, Goldach, Grub, Heiden, Herisau, Horn, Hundwil, Häggenschwil, Lindau, Lutzenberg, Muolen, Mörschwil, Rehetobel, Rorschach, Rorschacherberg, Schwellbrunn, Schönengrund, Speicher, Stein, Steinach, Teufen, Thal, Trogen, Tübach, Untereggen, Urnäsch, Wald, Waldkirch, Waldstatt, Wittenbach, Wolfhalden | | United Kingdom | Bracknell Forest Council, Bradford Metropolitan District Council, Braintree District Council, Cambridge City Council, Canterbury City Council, Cheshire East Council, Chesterfield Borough Council, City of York Council, Colchester Borough Council, Cornwall Council, Derby City Council, Eastbourne Borough Council, Elmbridge Borough Council, Environment First, FCC Environment, Guildford Borough Council, Harborough District Council, Huntingdonshire District Council, Lewes District Council, London Borough of Lewisham, Manchester City Council, Middlesbrough Council, Newcastle City Council, North Somerset Council, Nottingham City Council, Peterborough City Council, Richmondshire District Council, Rushmoor Borough Council, Sheffield City Council, South Cambridgeshire District Council, South Hams District Council, South Norfolk and Broadland Council, Stevenage Borough Council, Tewkesbury Borough Council, The Royal Borough of Kingston Council, Walsall Council, West Berkshire Council, West Devon Borough Council, Wiltshire Council | | United States of America | City of Pittsburgh, Republic Services, Seattle Public Utilities | From 72121c057188cc3249fb8c2c871d69b72854faf8 Mon Sep 17 00:00:00 2001 From: dt215git Date: Wed, 28 Dec 2022 20:07:32 +0000 Subject: [PATCH 075/127] "\n" added to README ICS/iCal section --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e8e468f5..e3a0402d 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Waste collection schedules in the following formats and countries are supported.
ICS/iCal and User-Specified + - [Generic ICS / iCal File](/doc/source/ics.md) - [User Specified](/doc/source/static.md)
From f12d4a03b3e4334df8ceefd03817c44f096dced7 Mon Sep 17 00:00:00 2001 From: dt215git Date: Wed, 28 Dec 2022 20:08:46 +0000 Subject: [PATCH 076/127] "\n" added to update script for expand/collapse --- update_docu_links.py | 1 + 1 file changed, 1 insertion(+) diff --git a/update_docu_links.py b/update_docu_links.py index a75b226e..551083c4 100755 --- a/update_docu_links.py +++ b/update_docu_links.py @@ -115,6 +115,7 @@ def update_readme_md(countries): for country in sorted(countries): str += "
\n" str += f"{country}\n" + str += "\n" for e in sorted(countries[country], key=lambda e: e.title.lower()): # print(f" {e.title} - {beautify_url(e.url)}") From 6d86aa64bf7e34448abcdafdac618d4948af060d Mon Sep 17 00:00:00 2001 From: dt215git Date: Wed, 28 Dec 2022 20:09:58 +0000 Subject: [PATCH 077/127] updated README from docu script --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index e3a0402d..d6d39814 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Waste collection schedules in the following formats and countries are supported.
Australia + - [Banyule City Council](/doc/source/banyule_vic_gov_au.md) / banyule.vic.gov.au - [Belmont City Council](/doc/source/belmont_wa_gov_au.md) / belmont.wa.gov.au - [Brisbane City Council](/doc/source/brisbane_qld_gov_au.md) / brisbane.qld.gov.au @@ -45,6 +46,7 @@ Waste collection schedules in the following formats and countries are supported.
Austria + - [Burgenländischer Müllverband](/doc/source/bmv_at.md) / bmv.at - [infeo](/doc/source/infeo_at.md) / infeo.at - [Stadtservice Korneuburg](/doc/source/korneuburg_stadtservice_at.md) / korneuburg.gv.at @@ -54,17 +56,20 @@ Waste collection schedules in the following formats and countries are supported.
Belgium + - [Hygea](/doc/source/hygea_be.md) / hygea.be - [Recycle!](/doc/source/recycleapp_be.md) / recycleapp.be
Canada + - [City of Toronto](/doc/source/toronto_ca.md) / toronto.ca
Germany + - [Abfall Stuttgart](/doc/source/stuttgart_de.md) / service.stuttgart.de - [Abfall.IO / AbfallPlus](/doc/source/abfall_io.md) / abfallplus.de - [Abfallkalender Würzburg](/doc/source/wuerzburg_de.md) / wuerzburg.de @@ -120,11 +125,13 @@ Waste collection schedules in the following formats and countries are supported.
Lithuania + - [Kauno švara](/doc/source/grafikai_svara_lt.md) / grafikai.svara.lt
Netherlands + - [ACV Group](/doc/source/ximmio_nl.md) / acv-afvalkalender.nl - [Alpen an den Rijn](/doc/source/hvcgroep_nl.md) / alphenaandenrijn.nl - [Area Afval](/doc/source/ximmio_nl.md) / area-afval.nl @@ -167,6 +174,7 @@ Waste collection schedules in the following formats and countries are supported.
New Zealand + - [Auckland Council](/doc/source/aucklandcouncil_govt_nz.md) / aucklandcouncil.govt.nz - [Christchurch City Council](/doc/source/ccc_govt_nz.md) / ccc.govt.nz - [Gore, Invercargill & Southland](/doc/source/wastenet_org_nz.md) / wastenet.org.nz @@ -177,18 +185,21 @@ Waste collection schedules in the following formats and countries are supported.
Norway + - [Min Renovasjon](/doc/source/minrenovasjon_no.md) / norkart.no - [Oslo Kommune](/doc/source/oslokommune_no.md) / oslo.kommune.no
Poland + - [Ecoharmonogram](/doc/source/ecoharmonogram_pl.md) / ecoharmonogram.pl - [Warsaw](/doc/source/warszawa19115_pl.md) / warszawa19115.pl
Sweden + - [Lerum Vatten och Avlopp](/doc/source/lerum_se.md) / vatjanst.lerum.se - [Ronneby Miljöteknik](/doc/source/miljoteknik_se.md) / fyrfackronneby.se - [SRV Återvinning](/doc/source/srvatervinning_se.md) / srvatervinning.se @@ -199,6 +210,7 @@ Waste collection schedules in the following formats and countries are supported.
Switzerland + - [A-Region](/doc/source/a_region_ch.md) / a-region.ch - [Andwil](/doc/source/a_region_ch.md) / a-region.ch - [Appenzell](/doc/source/a_region_ch.md) / a-region.ch @@ -241,6 +253,7 @@ Waste collection schedules in the following formats and countries are supported.
United Kingdom + - [Bracknell Forest Council](/doc/source/bracknell_forest_gov_uk.md) / selfservice.mybfc.bracknell-forest.gov.uk - [Bradford Metropolitan District Council](/doc/source/bradford_gov_uk.md) / bradford.gov.uk - [Braintree District Council](/doc/source/braintree_gov_uk.md) / braintree.gov.uk @@ -284,6 +297,7 @@ Waste collection schedules in the following formats and countries are supported.
United States of America + - [City of Pittsburgh](/doc/source/pgh_st.md) / pgh.st - [Republic Services](/doc/source/republicservices_com.md) / republicservices.com - [Seattle Public Utilities](/doc/source/seattle_gov.md) / myutilities.seattle.gov From e774a351d689237eafbc0c8d6f72d9d6d5899f6b Mon Sep 17 00:00:00 2001 From: mampfes Date: Thu, 29 Dec 2022 15:12:05 +0100 Subject: [PATCH 078/127] improve logging in case of exception --- .../test/test_sources.py | 17 +++++++++++++++-- doc/contributing.md | 1 + 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/test/test_sources.py b/custom_components/waste_collection_schedule/waste_collection_schedule/test/test_sources.py index 2317b9a5..36bffb99 100755 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/test/test_sources.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/test/test_sources.py @@ -25,6 +25,12 @@ def main(): parser.add_argument( "-i", "--icon", action="store_true", help="Show waste type icon" ) + parser.add_argument( + "-t", + "--traceback", + action="store_true", + help="Print exception information and stack trace", + ) args = parser.parse_args() # read secrets.yaml @@ -97,8 +103,10 @@ def main(): print(f" {x.date.isoformat()}: {x.type}{icon_str}") except KeyboardInterrupt: exit() - except Exception: - print(traceback.format_exc()) + except Exception as exc: + print(f" {name} failed with exception: {exc}") + if args.traceback: + print(indent(traceback.format_exc(), 4)) def replace_secret(secrets, d): @@ -116,5 +124,10 @@ def replace_secret(secrets, d): print(f"identifier '{id}' not found in {SECRET_FILENAME}") +def indent(s, count): + indent = " " * count + return "\n".join([indent + line for line in s.split("\n")]) + + if __name__ == "__main__": main() diff --git a/doc/contributing.md b/doc/contributing.md index 416e0d53..8c762170 100644 --- a/doc/contributing.md +++ b/doc/contributing.md @@ -218,6 +218,7 @@ The script supports the following options: | `-s` | SOURCE | [Source name](https://github.com/mampfes/hacs_waste_collection_schedule#source-configuration-variables) (source file name without ending `.py`) | | `-l` | - | List all found dates. | | `-i` | - | Add icon name to output. Only effective together with `-l`. | +| `-t` | - | Show extended exception info and stack trace. | For debugging purposes of a single source, it is recommended to use the `-s SOURCE` option. If used without any arguments provided, the script tests every script in the `custom_components/waste_collection_schedule/waste_collection_schedule/source` folder and prints the number of found entries for every test case. From 3de31a30abbe55acb35a17fef946d62813d02170 Mon Sep 17 00:00:00 2001 From: mampfes Date: Thu, 29 Dec 2022 15:12:33 +0100 Subject: [PATCH 079/127] improve logging for ics.py --- .../waste_collection_schedule/source/ics.py | 13 +++---------- doc/source/ics.md | 5 ++++- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ics.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ics.py index 1ded17f2..761a524b 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ics.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ics.py @@ -94,7 +94,7 @@ TEST_CASES = { "year_field": "year", }, "EAW Rheingau Taunus": { - "url": "https://www.eaw-rheingau-taunus.de/abfallkalender/calendar.ics?streetid=1429", + "url": "https://www.eaw-rheingau-taunus.de/abfallsammlung/abfuhrtermine/feed.ics?tx_vierwdeaw_garbagecalendarics%5Baction%5D=ics&tx_vierwdeaw_garbagecalendarics%5Bcontroller%5D=GarbageCalendar&tx_vierwdeaw_garbagecalendarics%5Bstreet%5D=38", "split_at": ",", }, "Recollect, Ottawa": { @@ -196,16 +196,9 @@ class Source: raise RuntimeError( "Error: unknown method to fetch URL, use GET or POST; got {self._method}" ) + r.raise_for_status() + r.encoding = "utf-8" # requests doesn't guess the encoding correctly - - # check the return code - if not r.ok: - _LOGGER.error( - "Error: the response is not ok; need code 200, but got code %s" - % r.status_code - ) - return [] - return self._convert(r.text) def fetch_file(self, file): diff --git a/doc/source/ics.md b/doc/source/ics.md index fe527bd5..46b98813 100644 --- a/doc/source/ics.md +++ b/doc/source/ics.md @@ -421,12 +421,15 @@ waste_collection_schedule: ### EAW Rheingau Taunus +1. Find your ICS link via the web page +2. Remove the cHash attribute + ```yaml waste_collection_schedule: sources: - name: ics args: - url: "https://www.eaw-rheingau-taunus.de/abfallkalender/calendar.ics?streetid=1429" + url: "https://www.eaw-rheingau-taunus.de/abfallsammlung/abfuhrtermine/feed.ics?tx_vierwdeaw_garbagecalendarics%5Baction%5D=ics&tx_vierwdeaw_garbagecalendarics%5Bcontroller%5D=GarbageCalendar&tx_vierwdeaw_garbagecalendarics%5Bstreet%5D=38" split_at: "," ``` From b720145c31590b9abb76d00295aa819e94520e31 Mon Sep 17 00:00:00 2001 From: mampfes Date: Thu, 29 Dec 2022 15:47:58 +0100 Subject: [PATCH 080/127] fix error logging and a lot of minor refactorings --- .../service/ICS_v1.py | 7 +- .../abfall_neunkirchen_siegerland_de.py | 46 +++--- .../source/aw_harburg_de.py | 17 ++- .../source/awido_de.py | 25 ++-- .../source/awr_de.py | 17 +-- .../source/awsh_de.py | 17 ++- .../source/data_umweltprofis_at.py | 21 ++- .../source/derby_gov_uk.py | 2 +- .../source/egn_abfallkalender_de.py | 11 +- .../source/hvcgroep_nl.py | 137 +++++++++++------- .../source/hygea_be.py | 4 +- .../source/infeo_at.py | 53 ++++--- .../source/kwu_de.py | 88 ++++++----- .../source/manchester_uk.py | 41 ++---- .../source/melton_vic_gov_au.py | 3 +- .../source/mrsc_vic_gov_au.py | 11 +- .../source/newcastle_gov_uk.py | 32 ++-- .../source/nillumbik_vic_gov_au.py | 11 +- .../source/recycleapp_be.py | 13 +- .../source/sbazv_de.py | 30 ++-- .../source/sector27_de.py | 8 +- .../source/srvatervinning_se.py | 12 +- .../source/stadtreinigung_leipzig_de.py | 9 +- .../source/stadtservice_bruehl_de.py | 16 +- .../source/stonnington_vic_gov_au.py | 11 +- .../source/thehills_nsw_gov_au.py | 12 +- .../source/wyndham_vic_gov_au.py | 91 +++++++----- 27 files changed, 391 insertions(+), 354 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/service/ICS_v1.py b/custom_components/waste_collection_schedule/waste_collection_schedule/service/ICS_v1.py index 3009e9f7..765b6c7b 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/service/ICS_v1.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/service/ICS_v1.py @@ -18,12 +18,7 @@ class ICS_v1: def convert(self, ics_data): # parse ics file - try: - calendar = icalendar.Calendar.from_ical(ics_data) - except Exception as err: - _LOGGER.error(f"Parsing ics data failed:{str(err)}") - _LOGGER.debug(ics_data) - return [] + calendar = icalendar.Calendar.from_ical(ics_data) # calculate start- and end-date for recurring events start_date = datetime.datetime.now().replace( diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_neunkirchen_siegerland_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_neunkirchen_siegerland_de.py index 2fe4deca..b7920359 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_neunkirchen_siegerland_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_neunkirchen_siegerland_de.py @@ -1,17 +1,17 @@ import logging + import requests -from waste_collection_schedule import Collection +from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS TITLE = "Neunkirchen Siegerland" DESCRIPTION = " Source for 'Abfallkalender Neunkirchen Siegerland'." URL = "https://www.neunkirchen-siegerland.de" -TEST_CASES = { - "Waldstraße":{ "strasse":"Waldstr"} -} +TEST_CASES = {"Waldstraße": {"strasse": "Waldstr"}} _LOGGER = logging.getLogger(__name__) + class Source: def __init__(self, strasse): self._strasse = strasse @@ -19,13 +19,20 @@ class Source: def fetch(self): - args = {"out":"json", "type": "abto", "select":"2", "refid": "3362.1", "term": self._strasse } + args = { + "out": "json", + "type": "abto", + "select": "2", + "refid": "3362.1", + "term": self._strasse, + } header = {"referer": "https://www.neunkirchen-siegerland.de"} - r = requests.get("https://www.neunkirchen-siegerland.de/output/autocomplete.php", params=args,headers=header) - - if r.status_code != 200: - _LOGGER.error("Error querying calender data") - return [] + r = requests.get( + "https://www.neunkirchen-siegerland.de/output/autocomplete.php", + params=args, + headers=header, + ) + r.raise_for_status() ids = r.json() @@ -33,19 +40,22 @@ class Source: raise Exception("no address found") if len(ids) > 1: - raise Exception (" to many addresses found, specify more detailed street name") + raise Exception( + " to many addresses found, specify more detailed street name" + ) - args = {"ModID":48, "call": "ical", "pois": ids[0][0], "kat": 1, "alarm":0} - r = requests.get("https://www.neunkirchen-siegerland.de/output/options.php", params=args,headers=header) - - if r.status_code != 200: - _LOGGER.error("Error querying calender data") - return [] + args = {"ModID": 48, "call": "ical", "pois": ids[0][0], "kat": 1, "alarm": 0} + r = requests.get( + "https://www.neunkirchen-siegerland.de/output/options.php", + params=args, + headers=header, + ) + r.raise_for_status() dates = self._ics.convert(r.text) entries = [] for d in dates: - entries.append(Collection(d[0],d[1])) + entries.append(Collection(d[0], d[1])) return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/aw_harburg_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/aw_harburg_de.py index 32fcb03b..c21fee25 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/aw_harburg_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/aw_harburg_de.py @@ -16,7 +16,9 @@ TEST_CASES = { }, } -API_URL = "https://www.landkreis-harburg.de/bauen-umwelt/abfallwirtschaft/abfallkalender/" +API_URL = ( + "https://www.landkreis-harburg.de/bauen-umwelt/abfallwirtschaft/abfallkalender/" +) HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)", } @@ -35,10 +37,10 @@ class Source: # Double loading is on purpose because sometimes the webpage has an overlay # which is gone on the second try in a session r = session.get(API_URL, headers=HEADERS) + r.raise_for_status() if "Zur aufgerufenen Seite" in r.text: r = session.get(API_URL, headers=HEADERS) - if r.status_code != 200: - raise Exception(f"Error: failed to fetch first url: {API_URL}") + r.raise_for_status() # Get the IDs of the districts on the first level id = self.parse_level(r.text, 1) @@ -54,8 +56,7 @@ class Source: "selected_ebene": 0, } r = session.get(url, params=params, headers=HEADERS) - if r.status_code != 200: - raise Exception(f"Error: failed to fetch second url: {url}") + r.raise_for_status() # Get the IDs of the districts on the second level id = self.parse_level(r.text, 2) @@ -70,8 +71,7 @@ class Source: "selected_ebene": 0, } r = session.get(url, params=params, headers=HEADERS) - if r.status_code != 200: - raise Exception(f"Error: failed to fetch third url: {url}") + r.raise_for_status() # Get the IDs of the districts on the third level id = self.parse_level(r.text, 3) @@ -83,6 +83,7 @@ class Source: "owner": 20100, } r = session.get(url, params=params, headers=HEADERS) + r.raise_for_status() # Sometimes there is no garbage calendar available if "Es sind keine Abfuhrbezirke hinterlegt." in r.text: @@ -111,7 +112,7 @@ class Source: for d in dates: entries.append(Collection(d[0], d[1])) except ValueError: - pass # during year transition the ical for the next year may be empty + pass # during year transition the ical for the next year may be empty return entries def parse_level(self, response, level): diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awido_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awido_de.py index bfbd4699..3d330af5 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awido_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awido_de.py @@ -1,5 +1,4 @@ import datetime -import json import logging import requests @@ -44,14 +43,14 @@ class Source: r = requests.get( f"https://awido.cubefour.de/WebServices/Awido.Service.svc/secure/getPlaces/client={self._customer}" ) - places = json.loads(r.text) + r.raise_for_status() + places = r.json() # create city to key map from retrieved places city_to_oid = {place["value"].strip(): place["key"] for (place) in places} if self._city not in city_to_oid: - _LOGGER.error(f"city not found: {self._city}") - return [] + raise Exception(f"city not found: {self._city}") oid = city_to_oid[self._city] @@ -62,7 +61,8 @@ class Source: f"https://awido.cubefour.de/WebServices/Awido.Service.svc/secure/getGroupedStreets/{oid}", params={"client": self._customer}, ) - streets = json.loads(r.text) + r.raise_for_status() + streets = r.json() # create street to key map from retrieved places street_to_oid = { @@ -78,7 +78,8 @@ class Source: f"https://awido.cubefour.de/WebServices/Awido.Service.svc/secure/getGroupedStreets/{oid}", params={"client": self._customer}, ) - streets = json.loads(r.text) + r.raise_for_status() + streets = r.json() # create street to key map from retrieved places street_to_oid = { @@ -86,8 +87,7 @@ class Source: } if self._street not in street_to_oid: - _LOGGER.error(f"street not found: {self._street}") - return [] + raise Exception(f"street not found: {self._street}") oid = street_to_oid[self._street] @@ -96,7 +96,8 @@ class Source: f"https://awido.cubefour.de/WebServices/Awido.Service.svc/secure/getStreetAddons/{oid}", params={"client": self._customer}, ) - hsnbrs = json.loads(r.text) + r.raise_for_status() + hsnbrs = r.json() # create housenumber to key map from retrieved places hsnbr_to_oid = { @@ -104,8 +105,7 @@ class Source: } if self._housenumber not in hsnbr_to_oid: - _LOGGER.error(f"housenumber not found: {self._housenumber}") - return [] + raise Exception(f"housenumber not found: {self._housenumber}") oid = hsnbr_to_oid[self._housenumber] @@ -114,7 +114,8 @@ class Source: f"https://awido.cubefour.de/WebServices/Awido.Service.svc/secure/getData/{oid}", params={"fractions": "", "client": self._customer}, ) - cal_json = json.loads(r.text) + r.raise_for_status() + cal_json = r.json() # map fraction code to fraction name fractions = {fract["snm"]: fract["nm"] for (fract) in cal_json["fracts"]} diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awr_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awr_de.py index e2e0b520..363db3fb 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awr_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awr_de.py @@ -1,4 +1,3 @@ -import json import logging import requests @@ -24,7 +23,8 @@ class Source: def fetch(self): # retrieve list of cities r = requests.get("https://www.awr.de/api_v2/collection_dates/1/orte") - cities = json.loads(r.text) + r.raise_for_status() + cities = r.json() # create city to id map from retrieved cities city_to_id = { @@ -32,8 +32,7 @@ class Source: } if self._city not in city_to_id: - _LOGGER.error(f"city not found: {self._city}") - return [] + raise Exception(f"city not found: {self._city}") cityId = city_to_id[self._city] @@ -41,7 +40,8 @@ class Source: r = requests.get( f"https://www.awr.de/api_v2/collection_dates/1/ort/{cityId}/strassen" ) - streets = json.loads(r.text) + r.raise_for_status() + streets = r.json() # create street to id map from retrieved cities street_to_id = { @@ -50,8 +50,7 @@ class Source: } if self._street not in street_to_id: - _LOGGER.error(f"street not found: {self._street}") - return [] + raise Exception(f"street not found: {self._street}") streetId = street_to_id[self._street] @@ -59,7 +58,8 @@ class Source: r = requests.get( f"https://www.awr.de/api_v2/collection_dates/1/ort/{cityId}/abfallarten" ) - waste_types = json.loads(r.text) + r.raise_for_status() + waste_types = r.json() wt = "-".join([t["id"] for t in waste_types["abfallarten"]]) # get ics file @@ -73,4 +73,3 @@ class Source: for d in dates: entries.append(Collection(d[0], d[1])) return entries - diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awsh_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awsh_de.py index 8a8c371f..e82ec0e6 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/awsh_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/awsh_de.py @@ -1,4 +1,3 @@ -import json import logging import requests @@ -24,7 +23,8 @@ class Source: def fetch(self): # retrieve list of cities r = requests.get("https://www.awsh.de/api_v2/collection_dates/1/orte") - cities = json.loads(r.text) + r.raise_for_status() + cities = r.json() # create city to id map from retrieved cities city_to_id = { @@ -32,8 +32,7 @@ class Source: } if self._city not in city_to_id: - _LOGGER.error(f"city not found: {self._city}") - return [] + raise Exception(f"city not found: {self._city}") cityId = city_to_id[self._city] @@ -41,7 +40,8 @@ class Source: r = requests.get( f"https://www.awsh.de/api_v2/collection_dates/1/ort/{cityId}/strassen" ) - streets = json.loads(r.text) + r.raise_for_status() + streets = r.json() # create street to id map from retrieved cities street_to_id = { @@ -50,8 +50,7 @@ class Source: } if self._street not in street_to_id: - _LOGGER.error(f"street not found: {self._street}") - return [] + raise Exception(f"street not found: {self._street}") streetId = street_to_id[self._street] @@ -59,13 +58,15 @@ class Source: r = requests.get( f"https://www.awsh.de/api_v2/collection_dates/1/ort/{cityId}/abfallarten" ) - waste_types = json.loads(r.text) + r.raise_for_status() + waste_types = r.json() wt = "-".join([t["id"] for t in waste_types["abfallarten"]]) # get ics file r = requests.get( f"https://www.awsh.de/api_v2/collection_dates/1/ort/{cityId}/strasse/{streetId}/hausnummern/0/abfallarten/{wt}/kalender.ics" ) + r.raise_for_status() dates = self._ics.convert(r.text) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/data_umweltprofis_at.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/data_umweltprofis_at.py index a8692800..296244b4 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/data_umweltprofis_at.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/data_umweltprofis_at.py @@ -1,7 +1,8 @@ import logging -import requests from datetime import datetime from xml.dom.minidom import parseString + +import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS @@ -9,12 +10,17 @@ TITLE = "Umweltprofis" DESCRIPTION = "Source for Umweltprofis" URL = "https://www.umweltprofis.at" TEST_CASES = { - "Ebensee": {"url": "https://data.umweltprofis.at/OpenData/AppointmentService/AppointmentService.asmx/GetIcalWastePickupCalendar?key=KXX_K0bIXDdk0NrTkk3xWqLM9-bsNgIVBE6FMXDObTqxmp9S39nIqwhf9LTIAX9shrlpfCYU7TG_8pS9NjkAJnM_ruQ1SYm3V9YXVRfLRws1"}, - "Rohrbach": {"xmlurl": "https://data.umweltprofis.at/opendata/AppointmentService/AppointmentService.asmx/GetTermineForLocationSecured?Key=TEMPKeyabvvMKVCic0cMcmsTEMPKey&StreetNr=118213&HouseNr=Alle&intervall=Alle"}, + "Ebensee": { + "url": "https://data.umweltprofis.at/OpenData/AppointmentService/AppointmentService.asmx/GetIcalWastePickupCalendar?key=KXX_K0bIXDdk0NrTkk3xWqLM9-bsNgIVBE6FMXDObTqxmp9S39nIqwhf9LTIAX9shrlpfCYU7TG_8pS9NjkAJnM_ruQ1SYm3V9YXVRfLRws1" + }, + "Rohrbach": { + "xmlurl": "https://data.umweltprofis.at/opendata/AppointmentService/AppointmentService.asmx/GetTermineForLocationSecured?Key=TEMPKeyabvvMKVCic0cMcmsTEMPKey&StreetNr=118213&HouseNr=Alle&intervall=Alle" + }, } _LOGGER = logging.getLogger(__name__) + def getText(element): s = "" for e in element.childNodes: @@ -22,6 +28,7 @@ def getText(element): s += e.nodeValue return s + class Source: def __init__(self, url=None, xmlurl=None): self._url = url @@ -38,11 +45,11 @@ class Source: def fetch_ics(self): r = requests.get(self._url) - if r.status_code != 200: - _LOGGER.error("Error querying calendar data") - return [] + r.raise_for_status() - fixed_text = r.text.replace("REFRESH - INTERVAL; VALUE = ", "REFRESH-INTERVAL;VALUE=") + fixed_text = r.text.replace( + "REFRESH - INTERVAL; VALUE = ", "REFRESH-INTERVAL;VALUE=" + ) dates = self._ics.convert(fixed_text) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/derby_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/derby_gov_uk.py index c7ccaaa2..628a1e73 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/derby_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/derby_gov_uk.py @@ -37,7 +37,7 @@ class Source: self._post_code = post_code self._house_number = house_number if not any([self._premises_id, self._post_code and self._house_number]): - _LOGGER.error( + raise Exception( "premises_id or post_code and house number must be provided in config" ) self._session = requests.Session() diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/egn_abfallkalender_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/egn_abfallkalender_de.py index e60dd323..7a1561c9 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/egn_abfallkalender_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/egn_abfallkalender_de.py @@ -68,9 +68,14 @@ class Source: data = r.json() if data.get("error"): - for type, errormsg in data["errors"].items(): - _LOGGER.error(f"{type} - {errormsg}") - return [] + raise Exception( + "\n".join( + [ + f"{type} - {errormsg}" + for type, errormsg in data["errors"].items() + ] + ) + ) entries = [] for year, months in data["waste_discharge"].items(): diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/hvcgroep_nl.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/hvcgroep_nl.py index 5dd44dd0..635bc059 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/hvcgroep_nl.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/hvcgroep_nl.py @@ -7,8 +7,14 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = None DESCRIPTION = "Source for the Dutch HVCGroep waste management." URL = "https://www.hvcgroep.nl" + + def EXTRA_INFO(): - return [ { "title": s["title"], "url": get_main_url(s["api_url"])} for s in SERVICE_MAP ] + return [ + {"title": s["title"], "url": get_main_url(s["api_url"])} for s in SERVICE_MAP + ] + + TEST_CASES = { "Tollebeek": {"postal_code": "8309AV", "house_number": "1"}, "Hvgroep: Tollebeek": { @@ -27,89 +33,115 @@ TEST_CASES = { _LOGGER = logging.getLogger(__name__) SERVICE_MAP = [ - { "title": "Alpen an den Rijn", - "api_url": "https://afvalkalender.alphenaandenrijn.nl", + { + "title": "Alpen an den Rijn", + "api_url": "https://afvalkalender.alphenaandenrijn.nl", }, - { "title": "Gemeente Cranendonck", - "api_url": "https://afvalkalender.cranendonck.nl", + { + "title": "Gemeente Cranendonck", + "api_url": "https://afvalkalender.cranendonck.nl", }, - { "title": "Cyclus NV", - "api_url": "https://afvalkalender.cyclusnv.nl", + { + "title": "Cyclus NV", + "api_url": "https://afvalkalender.cyclusnv.nl", }, - { "title": "Dar", - "api_url": "https://afvalkalender.dar.nl", + { + "title": "Dar", + "api_url": "https://afvalkalender.dar.nl", }, - { "title": "Den Haag", - "api_url": "https://huisvuilkalender.denhaag.nl", + { + "title": "Den Haag", + "api_url": "https://huisvuilkalender.denhaag.nl", }, - { "title": "GAD", - "api_url": "https://inzamelkalender.gad.nl", + { + "title": "GAD", + "api_url": "https://inzamelkalender.gad.nl", }, - { "title": "Gemeente Berkelland", - "api_url": "https://afvalkalender.gemeenteberkelland.nl", + { + "title": "Gemeente Berkelland", + "api_url": "https://afvalkalender.gemeenteberkelland.nl", }, - { "title": "HVC Groep", - "api_url": "https://inzamelkalender.hvcgroep.nl", + { + "title": "HVC Groep", + "api_url": "https://inzamelkalender.hvcgroep.nl", }, - { "title": "Gemeente Lingewaard", - "api_url": "https://afvalwijzer.lingewaard.nl", + { + "title": "Gemeente Lingewaard", + "api_url": "https://afvalwijzer.lingewaard.nl", }, - { "title": "Gemeente Middelburg + Vlissingen", - "api_url": "https://afvalwijzer.middelburgvlissingen.nl", + { + "title": "Gemeente Middelburg + Vlissingen", + "api_url": "https://afvalwijzer.middelburgvlissingen.nl", }, - { "title": "Mijn Blink", - "api_url": "https://mijnblink.nl", + { + "title": "Mijn Blink", + "api_url": "https://mijnblink.nl", }, - { "title": "Gemeente Peel en Maas", - "api_url": "https://afvalkalender.peelenmaas.nl", + { + "title": "Gemeente Peel en Maas", + "api_url": "https://afvalkalender.peelenmaas.nl", }, - { "title": "PreZero", - "api_url": "https://inzamelwijzer.prezero.nl", + { + "title": "PreZero", + "api_url": "https://inzamelwijzer.prezero.nl", }, - { "title": "Purmerend", - "api_url": "https://afvalkalender.purmerend.nl", + { + "title": "Purmerend", + "api_url": "https://afvalkalender.purmerend.nl", }, - { "title": "Reinigingsbedrijf Midden Nederland", - "api_url": "https://inzamelschema.rmn.nl", + { + "title": "Reinigingsbedrijf Midden Nederland", + "api_url": "https://inzamelschema.rmn.nl", }, - { "title": "Gemeente Schouwen-Duiveland", - "api_url": "https://afvalkalender.schouwen-duiveland.nl", + { + "title": "Gemeente Schouwen-Duiveland", + "api_url": "https://afvalkalender.schouwen-duiveland.nl", }, - { "title": "Spaarne Landen", - "api_url": "https://afvalwijzer.spaarnelanden.nl", + { + "title": "Spaarne Landen", + "api_url": "https://afvalwijzer.spaarnelanden.nl", }, - { "title": "Stadswerk 072", - "api_url": "https://www.stadswerk072.nl", + { + "title": "Stadswerk 072", + "api_url": "https://www.stadswerk072.nl", }, - { "title": "Gemeente Sudwest-Fryslan", - "api_url": "https://afvalkalender.sudwestfryslan.nl", + { + "title": "Gemeente Sudwest-Fryslan", + "api_url": "https://afvalkalender.sudwestfryslan.nl", }, - { "title": "Gemeente Venray", - "api_url": "https://afvalkalender.venray.nl", + { + "title": "Gemeente Venray", + "api_url": "https://afvalkalender.venray.nl", }, - { "title": "Gemeente Voorschoten", - "api_url": "https://afvalkalender.voorschoten.nl", + { + "title": "Gemeente Voorschoten", + "api_url": "https://afvalkalender.voorschoten.nl", }, - { "title": "Gemeente Wallre", - "api_url": "https://afvalkalender.waalre.nl", + { + "title": "Gemeente Wallre", + "api_url": "https://afvalkalender.waalre.nl", }, - { "title": "ZRD", - "api_url": "https://afvalkalender.zrd.nl", + { + "title": "ZRD", + "api_url": "https://afvalkalender.zrd.nl", }, ] + def get_service_name_map(): def extract_service_name(api_url): name = api_url.split(".")[-2] name = name.split("/")[-1] return name - return { extract_service_name(s["api_url"]):s["api_url"] for s in SERVICE_MAP } + return {extract_service_name(s["api_url"]): s["api_url"] for s in SERVICE_MAP} + def get_main_url(url): - x = url.split(".")[-2:] - x[0] = x[0].removeprefix("https://") - return "https://" + ".".join(x) + x = url.split(".")[-2:] + x[0] = x[0].removeprefix("https://") + return "https://" + ".".join(x) + ICON_MAP = { "plastic-blik-drinkpak": "mdi:recycle", @@ -134,8 +166,7 @@ class Source: # Something must be wrong, maybe the address isn't valid? No need to do the extra requests so just return here. if len(data) == 0: - _LOGGER.error("no data found for this address") - return [] + raise Exception("no data found for this address") bag_id = data[0]["bagid"] diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/hygea_be.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/hygea_be.py index fa905c76..291d7c24 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/hygea_be.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/hygea_be.py @@ -39,9 +39,7 @@ class Source: response = requests.get( "https://www.hygea.be/displaycalws.html", params=params ) - - if not response.ok: - return [] + response.raise_for_status() data = json.loads(response.text) entries = [] diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/infeo_at.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/infeo_at.py index d69b27f6..94f5e325 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/infeo_at.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/infeo_at.py @@ -1,5 +1,5 @@ -import json import logging + import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS @@ -11,9 +11,9 @@ DESCRIPTION = "Source for INFEO waste collection." URL = "https://www.infeo.at/" EXTRA_INFO = [ { - "title": "Bogenschütz Entsorgung", - "url": "https://bogenschuetz-entsorgung.de", - "country": "de", + "title": "Bogenschütz Entsorgung", + "url": "https://bogenschuetz-entsorgung.de", + "country": "de", }, ] TEST_CASES = {"Bogenschütz": {"customer": "bogenschütz", "zone": "Dettenhausen"}} @@ -27,25 +27,25 @@ class Source: def fetch(self): baseUrl = f"https://services.infeo.at/awm/api/{self._customer}/wastecalendar" - issueUrl = "https://github.com/mampfes/hacs_waste_collection_schedule/issues/new" - + issueUrl = ( + "https://github.com/mampfes/hacs_waste_collection_schedule/issues/new" + ) + params = { "showUnpublishedCalendars": "false", } - + # get the available published calendar years url = f"{baseUrl}/calendars" response = requests.get(url, params=params) + response.raise_for_status() # data validation - if(response.status_code != 200): - _LOGGER.error(f"problems during api calendar year access, please file an issue at {issueUrl} and mention @dm82m and add this: {response.text}") - return [] - response = response.json() if len(response) <= 0: - _LOGGER.error(f"no calendars found, please file an issue at {issueUrl} and mention @dm82m") - return [] + raise Exception( + f"no calendars found, please file an issue at {issueUrl} and mention @dm82m" + ) entries = [] @@ -61,15 +61,14 @@ class Source: # get available zones for calendar year url = f"{baseUrl}/zones" response = requests.get(url, params=params) + response.raise_for_status() # data validation - if(response.status_code != 200): - _LOGGER.error(f"problems during api zones for calendar year access, please file an issue at {issueUrl} and mention @dm82m and add this: {response.text}") - return [] - response = response.json() if len(response) <= 0: - _LOGGER.warning(f"no zones found for calendar year {calendarYearName}, continuing with next calendar year ...") + _LOGGER.warning( + f"no zones found for calendar year {calendarYearName}, continuing with next calendar year ..." + ) continue zoneId = 0 @@ -80,7 +79,9 @@ class Source: zoneId = zone["id"] if zoneId == 0: - _LOGGER.warning(f"zone '{self._zone}' not found in calendar year {calendarYearName}, continuing with next calendar year ...") + _LOGGER.warning( + f"zone '{self._zone}' not found in calendar year {calendarYearName}, continuing with next calendar year ..." + ) continue params = { @@ -92,19 +93,17 @@ class Source: # get ical data for year and zone url = f"{baseUrl}/v2/export" response = requests.get(url, params=params) - - # data validation - if(response.status_code != 200): - _LOGGER.error(f"problems during api ical data for zone in calendar year, please file an issue at {issueUrl} and mention @dm82m and add this: {response.text}") - return [] + response.raise_for_status() dates = self._ics.convert(response.text) for d in dates: entries.append(Collection(d[0], d[1])) - + # validate that we processed some data and show an error if not if len(entries) <= 0: - _LOGGER.error(f"we were not able to get any waste entries for you! please file an issue at {issueUrl} and mention @dm82m and add this zone: '{self._zone}'") - + _LOGGER.warning( + f"we were not able to get any waste entries for you! please file an issue at {issueUrl} and mention @dm82m and add this zone: '{self._zone}'" + ) + return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/kwu_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kwu_de.py index ad8ed4e1..84c39b62 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/kwu_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kwu_de.py @@ -1,6 +1,7 @@ +from datetime import date + import requests from bs4 import BeautifulSoup -from datetime import date from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS @@ -9,16 +10,16 @@ DESCRIPTION = "Source for KWU Entsorgung, Germany" URL = "https://www.kwu-entsorgung.de/" TEST_CASES = { "Erkner": {"city": "Erkner", "street": "Heinrich-Heine-Straße", "number": "11"}, - "Bad Saarow": {"city": "Bad Saarow", "street": "Ahornallee", "number": "1"} + "Bad Saarow": {"city": "Bad Saarow", "street": "Ahornallee", "number": "1"}, } HEADERS = {"user-agent": "Mozilla/5.0 (xxxx Windows NT 10.0; Win64; x64)"} -ICON_MAP = { +ICON_MAP = { "Restabfall": "mdi:trash-can-outline", - "Gelber Sack" : "mdi:recycle", - "Papiertonne" : "mdi:package-variant", + "Gelber Sack": "mdi:recycle", + "Papiertonne": "mdi:package-variant", "Biotonne": "mdi:food-apple-outline", -} +} class Source: @@ -31,51 +32,64 @@ class Source: def fetch(self): session = requests.Session() - params = { - "city": self._city, - "street": self._street, - "number": self._number, - "direct": "true", - } - - r = requests.get("https://www.kwu-entsorgung.de/inc/wordpress/kal_objauswahl.php", headers=HEADERS) + r = requests.get( + "https://www.kwu-entsorgung.de/inc/wordpress/kal_objauswahl.php", + headers=HEADERS, + ) parsed_html = BeautifulSoup(r.text, "html.parser") - Orte = parsed_html.find_all('option') + Orte = parsed_html.find_all("option") for Ort in Orte: if self._city in Ort.text: - OrtValue = Ort['value'] + OrtValue = Ort["value"] break - r = requests.get("https://www.kwu-entsorgung.de/inc/wordpress/kal_str2ort.php", params={"ort": OrtValue}, headers=HEADERS) + r = requests.get( + "https://www.kwu-entsorgung.de/inc/wordpress/kal_str2ort.php", + params={"ort": OrtValue}, + headers=HEADERS, + ) parsed_html = BeautifulSoup(r.text, "html.parser") - Strassen = parsed_html.find_all('option') + Strassen = parsed_html.find_all("option") for Strasse in Strassen: if self._street in Strasse.text: - StrasseValue = Strasse['value'] + StrasseValue = Strasse["value"] break - r = requests.get("https://www.kwu-entsorgung.de/inc/wordpress/kal_str2ort.php", params={"ort": OrtValue, "strasse": StrasseValue}, headers=HEADERS) + r = requests.get( + "https://www.kwu-entsorgung.de/inc/wordpress/kal_str2ort.php", + params={"ort": OrtValue, "strasse": StrasseValue}, + headers=HEADERS, + ) parsed_html = BeautifulSoup(r.text, "html.parser") - Objekte = parsed_html.find_all('option') + objects = parsed_html.find_all("option") - for Objekt in Objekte: - if self._number in Objekt.text: - ObjektValue = Objekt['value'] + for obj in objects: + if self._number in obj.text: + ObjektValue = obj["value"] break - r = requests.post("https://www.kwu-entsorgung.de/inc/wordpress/kal_uebersicht-2020.php", data={"ort": OrtValue, "strasse": StrasseValue, "objekt": ObjektValue, "jahr": date.today().year}, headers=HEADERS) + r = requests.post( + "https://www.kwu-entsorgung.de/inc/wordpress/kal_uebersicht-2020.php", + data={ + "ort": OrtValue, + "strasse": StrasseValue, + "objekt": ObjektValue, + "jahr": date.today().year, + }, + headers=HEADERS, + ) parsed_html = BeautifulSoup(r.text, "html.parser") - Links = parsed_html.find_all('a') + Links = parsed_html.find_all("a") for Link in Links: - if 'ICal herunterladen' in Link.text: - ics_url = Link['href'] + if "ICal herunterladen" in Link.text: + ics_url = Link["href"] if ics_url is None: - raise Exception(f"ics url not found") + raise Exception("ics url not found") # get ics file r = session.get(ics_url, headers=HEADERS) @@ -85,15 +99,19 @@ class Source: dates = self._ics.convert(r.text) entries = [] - #for d in dates: + # for d in dates: # entries.append(Collection(d[0], d[1])) - #return entries + # return entries for d in dates: -# _LOGGER.error(d) waste_type = d[1].strip() next_pickup_date = d[0] - - entries.append(Collection(date=next_pickup_date, t=waste_type, icon=ICON_MAP.get(waste_type,"mdi:trash-can"))) + + entries.append( + Collection( + date=next_pickup_date, + t=waste_type, + icon=ICON_MAP.get(waste_type, "mdi:trash-can"), + ) + ) return entries - diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/manchester_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/manchester_uk.py index 61f35482..370a2c04 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/manchester_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/manchester_uk.py @@ -1,17 +1,15 @@ +import logging from datetime import datetime import requests -from waste_collection_schedule import Collection # type: ignore[attr-defined] - from bs4 import BeautifulSoup -from urllib.parse import urlsplit, parse_qs -import logging +from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Manchester City Council" DESCRIPTION = "Source for bin collection services for Manchester City Council, UK." URL = "https://www.manchester.gov.uk" TEST_CASES = { - "domestic": {'uprn': '000077065560'}, + "domestic": {"uprn": "000077065560"}, } API_URL = "https://www.manchester.gov.uk/bincollections/" @@ -26,25 +24,15 @@ _LOGGER = logging.getLogger(__name__) class Source: - def __init__( - self, uprn: int = None - ): + def __init__(self, uprn: int): self._uprn = uprn - if not self._uprn: - _LOGGER.error( - "uprn must be provided in config" - ) - self._session = requests.Session() def fetch(self): entries = [] r = requests.post( API_URL, - data={ - "mcc_bin_dates_uprn": self._uprn, - "mcc_bin_dates_submit": "Go" - }, + data={"mcc_bin_dates_uprn": self._uprn, "mcc_bin_dates_submit": "Go"}, ) soup = BeautifulSoup(r.text, features="html.parser") @@ -54,21 +42,18 @@ class Source: date = result.find("p", {"class": "caption"}) dates = [] dates.append(str(date.text).replace("Next collection ", "", 1)) - for date in result.find_all('li'): + for date in result.find_all("li"): dates.append(date.text) img_tag = result.find("img") collection_type = img_tag["alt"] for current_date in dates: - try: - date = datetime.strptime(current_date, "%A %d %b %Y").date() - entries.append( - Collection( - date=date, - t=collection_type, - icon=ICON_MAP[collection_type], - ) + date = datetime.strptime(current_date, "%A %d %b %Y").date() + entries.append( + Collection( + date=date, + t=collection_type, + icon=ICON_MAP[collection_type], ) - except ValueError: - _LOGGER.error(f"Skipped {current_date} as it does not match time format") + ) return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/melton_vic_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/melton_vic_gov_au.py index 8af37097..ce775880 100755 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/melton_vic_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/melton_vic_gov_au.py @@ -45,10 +45,9 @@ class Source: addressSearchApiResults["Items"] is None or len(addressSearchApiResults["Items"]) < 1 ): - _LOGGER.error( + raise Exception( f"Address search for '{self._street_address}' returned no results. Check your address on https://www.melton.vic.gov.au/My-Area" ) - return [] addressSearchTopHit = addressSearchApiResults["Items"][0] _LOGGER.debug("Address search top hit: %s", addressSearchTopHit) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/mrsc_vic_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/mrsc_vic_gov_au.py index f0d42cdf..ab6abc48 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/mrsc_vic_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/mrsc_vic_gov_au.py @@ -1,6 +1,6 @@ import logging -from datetime import datetime import re +from datetime import datetime import requests from bs4 import BeautifulSoup @@ -45,10 +45,9 @@ class Source: addressSearchApiResults["Items"] is None or len(addressSearchApiResults["Items"]) < 1 ): - _LOGGER.error( + raise Exception( f"Address search for '{self._street_address}' returned no results. Check your address on https://www.mrsc.vic.gov.au/Live-Work/Bins-Rubbish-Recycling/Bins-and-collection-days/Bin-collection-days" ) - return [] addressSearchTopHit = addressSearchApiResults["Items"][0] _LOGGER.debug("Address search top hit: %s", addressSearchTopHit) @@ -72,10 +71,12 @@ class Source: waste_type = article.h3.string icon = ICON_MAP.get(waste_type, "mdi:trash-can") next_pickup = article.find(class_="next-service").string.strip() - if re.match("[^\s]* \d{1,2}\/\d{1,2}\/\d{4}", next_pickup): + if re.match(r"[^\s]* \d{1,2}\/\d{1,2}\/\d{4}", next_pickup): next_pickup_date = datetime.strptime( next_pickup.split(sep=" ")[1], "%d/%m/%Y" ).date() - entries.append(Collection(date=next_pickup_date, t=waste_type, icon=icon)) + entries.append( + Collection(date=next_pickup_date, t=waste_type, icon=icon) + ) return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/newcastle_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/newcastle_gov_uk.py index 0542396f..9b597024 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/newcastle_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/newcastle_gov_uk.py @@ -1,32 +1,31 @@ import logging -from datetime import datetime import re +from datetime import datetime import requests +from waste_collection_schedule import Collection # type: ignore[attr-defined] + # These lines are needed to suppress the InsecureRequestWarning resulting from the POST verify=False option # With verify=True the POST fails due to a SSLCertVerificationError. -import urllib3 -from waste_collection_schedule import Collection - -urllib3.disable_warnings() # The following links may provide a better way of dealing with this, as using verify=False is not ideal: # https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html#ssl-warnings # https://urllib3.readthedocs.io/en/1.26.x/user-guide.html#ssl +import urllib3 +urllib3.disable_warnings() _LOGGER = logging.getLogger(__name__) TITLE = "Newcastle City Council" DESCRIPTION = "Source for waste collection services for Newcastle City Council" URL = "https://community.newcastle.gov.uk" -TEST_CASES = { - "Test_001": {"uprn": "004510053797"}, - "Test_002": {"uprn": 4510053797} -} +TEST_CASES = {"Test_001": {"uprn": "004510053797"}, "Test_002": {"uprn": 4510053797}} API_URL = "https://community.newcastle.gov.uk/my-neighbourhood/ajax/getBinsNew.php" -REGEX = "(Green|Blue|Brown) [bB]in \\((Domestic|Recycling|Garden)( Waste)?\\) details: <\\/strong>" \ - "collection day : [a-zA-Z]*day" \ - "Next collection : ([0-9]{2}-[A-Za-z]+-[0-9]{4})" +REGEX = ( + "(Green|Blue|Brown) [bB]in \\((Domestic|Recycling|Garden)( Waste)?\\) details: <\\/strong>" + "collection day : [a-zA-Z]*day" + "Next collection : ([0-9]{2}-[A-Za-z]+-[0-9]{4})" +) ICON_MAP = { "DOMESTIC": "mdi:trash-can", "RECYCLING": "mdi:recycle", @@ -35,13 +34,8 @@ ICON_MAP = { class Source: - def __init__(self, uprn=None): + def __init__(self, uprn): self._uprn = str(uprn).zfill(12) - if not self._uprn: - _LOGGER.error( - "uprn must be provided in config" - ) - self._uprn = self._uprn.zfill(12) self._session = requests.Session() def fetch(self): @@ -54,7 +48,7 @@ class Source: collection_date = collection[3] entries.append( Collection( - date=datetime.strptime(collection_date, '%d-%b-%Y').date(), + date=datetime.strptime(collection_date, "%d-%b-%Y").date(), t=collection_type, icon=ICON_MAP.get(collection_type.upper()), ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/nillumbik_vic_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/nillumbik_vic_gov_au.py index 05e057ee..9f9b06bb 100755 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/nillumbik_vic_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/nillumbik_vic_gov_au.py @@ -9,9 +9,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Nillumbik Shire Council" DESCRIPTION = "Source for Nillumbik Shire Council rubbish collection." URL = "https://www.nillumbik.vic.gov.au" -TEST_CASES = { - "Test": {"street_address": "11 Sunnyside Crescent, WATTLE GLEN, 3096"} -} +TEST_CASES = {"Test": {"street_address": "11 Sunnyside Crescent, WATTLE GLEN, 3096"}} _LOGGER = logging.getLogger(__name__) @@ -29,7 +27,9 @@ class Source: def fetch(self): session = requests.Session() - response = session.get("https://www.nillumbik.vic.gov.au/Residents/Waste-and-recycling/Bin-collection/Check-my-bin-day") + response = session.get( + "https://www.nillumbik.vic.gov.au/Residents/Waste-and-recycling/Bin-collection/Check-my-bin-day" + ) response.raise_for_status() response = session.get( @@ -42,10 +42,9 @@ class Source: addressSearchApiResults["Items"] is None or len(addressSearchApiResults["Items"]) < 1 ): - _LOGGER.error( + raise Exception( f"Address search for '{self._street_address}' returned no results. Check your address on https://www.nillumbik.vic.gov.au/Residents/Waste-and-recycling/Bin-collection/Check-my-bin-day" ) - return [] addressSearchTopHit = addressSearchApiResults["Items"][0] _LOGGER.debug("Address search top hit: %s", addressSearchTopHit) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/recycleapp_be.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/recycleapp_be.py index 652ef7b5..b7edde96 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/recycleapp_be.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/recycleapp_be.py @@ -51,20 +51,17 @@ class Source: "Authorization": "", } r = requests.get(f"{url}/access-token", headers=headers) + r.raise_for_status() headers["Authorization"] = r.json()["accessToken"] params = {"q": self._postcode} r = requests.get(f"{url}/zipcodes", params=params, headers=headers) - if r.status_code != 200: - _LOGGER.error("Get zip code failed") - return [] + r.raise_for_status() zipcodeId = r.json()["items"][0]["id"] params = {"q": self._street, "zipcodes": zipcodeId} r = requests.post(f"{url}/streets", params=params, headers=headers) - if r.status_code != 200: - _LOGGER.error("Get street id failed") - return [] + r.raise_for_status() streetId = None for item in r.json()["items"]: @@ -85,9 +82,7 @@ class Source: # "size":100, } r = requests.get(f"{url}/collections", params=params, headers=headers) - if r.status_code != 200: - _LOGGER.error("Get data failed") - return [] + r.raise_for_status() entries = [] for item in r.json()["items"]: diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/sbazv_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/sbazv_de.py index ee21b1d5..9c135114 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/sbazv_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/sbazv_de.py @@ -1,5 +1,4 @@ from datetime import datetime -import logging import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] @@ -9,20 +8,16 @@ TITLE = "Südbrandenburgischer Abfallzweckverband" DESCRIPTION = "SBAZV Brandenburg, Deutschland" URL = "https://www.sbazv.de" TEST_CASES = { - "Wildau": { - "city": "wildau", - "district": "Wildau", - "street": "Miersdorfer Str." - } + "Wildau": {"city": "wildau", "district": "Wildau", "street": "Miersdorfer Str."} } ICON_MAP = { "Restmülltonnen": "mdi:trash-can", - "Laubsäcke" : "mdi:leaf", - "Gelbe Säcke" : "mdi:sack", - "Papiertonnen" : "mdi:package-variant", + "Laubsäcke": "mdi:leaf", + "Gelbe Säcke": "mdi:sack", + "Papiertonnen": "mdi:package-variant", "Weihnachtsbäume": "mdi:pine-tree", -} +} # _LOGGER = logging.getLogger(__name__) @@ -59,17 +54,24 @@ class Source: # get ics file # https://www.sbazv.de/entsorgungstermine/klein.ics?city=Wildau&district=Wildau&street=Miersdorfer+Str. - r = requests.get("https://www.sbazv.de/entsorgungstermine/klein.ics", params=args) + r = requests.get( + "https://www.sbazv.de/entsorgungstermine/klein.ics", params=args + ) # parse ics file dates = self._ics.convert(r.text) entries = [] for d in dates: -# _LOGGER.error(d) waste_type = d[1].strip() next_pickup_date = d[0] - - entries.append(Collection(date=next_pickup_date, t=waste_type, icon=ICON_MAP.get(waste_type,"mdi:trash-can"))) + + entries.append( + Collection( + date=next_pickup_date, + t=waste_type, + icon=ICON_MAP.get(waste_type, "mdi:trash-can"), + ) + ) return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/sector27_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/sector27_de.py index 24c4a2f6..d3cccb6c 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/sector27_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/sector27_de.py @@ -53,8 +53,7 @@ class Source: def fetch(self): city = CITIES.get(self._city) if city is None: - _LOGGER.error(f"city not found {self._city}") - return [] + raise Exception(f"city not found {self._city}") args = city args["searchFor"] = self._street @@ -64,13 +63,13 @@ class Source: params=args, headers=HEADERS, ) + r.raise_for_status() streets = { e["name"].strip(): e["id"] for (e) in json.loads(extractJson(r.text)) } if self._street not in streets: - _LOGGER.error(f"street not found {self._street}") - return [] + raise Exception(f"street not found {self._street}") args = { "licenseKey": city["licenseKey"], @@ -89,6 +88,7 @@ class Source: params=args, headers=HEADERS, ) + r.raise_for_status() data = json.loads(extractJson(r.text)) for ts, pickups in data["pickups"].items(): diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/srvatervinning_se.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/srvatervinning_se.py index eca71873..ffcf929c 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/srvatervinning_se.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/srvatervinning_se.py @@ -2,7 +2,7 @@ import logging from datetime import datetime import requests -from waste_collection_schedule import Collection +from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "SRV Återvinning" DESCRIPTION = "Source for SRV återvinning AB, Sweden" @@ -27,12 +27,10 @@ class Source: "query": self._address, "city": "", } - url = "https://www.srvatervinning.se/rest-api/srv-slamsok-rest-new/search" - r = requests.get(url, params) - - if r.status_code != 200: - _LOGGER.error("Error querying calendar data") - return [] + r = requests.get( + "https://www.srvatervinning.se/rest-api/srv-slamsok-rest-new/search", params + ) + r.raise_for_status() data = r.json() diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtreinigung_leipzig_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtreinigung_leipzig_de.py index 960036ef..44d36468 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtreinigung_leipzig_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtreinigung_leipzig_de.py @@ -31,17 +31,14 @@ class Source: data = json.loads(r.text) if len(data["results"]) == 0: - _LOGGER.error(f"street not found: {self._street}") - return [] + raise Exception(f"street not found: {self._street}") street_entry = data["results"].get(self._street) if street_entry is None: - _LOGGER.error(f"street not found: {self._street}") - return [] + raise Exception(f"street not found: {self._street}") id = street_entry.get(str(self._house_number)) if id is None: - _LOGGER.error(f"house_number not found: {self._house_number}") - return [] + raise Exception(f"house_number not found: {self._house_number}") # get ics file params = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtservice_bruehl_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtservice_bruehl_de.py index 441a2246..c000778c 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtservice_bruehl_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtservice_bruehl_de.py @@ -3,7 +3,7 @@ import logging import requests from bs4 import BeautifulSoup -from waste_collection_schedule import Collection +from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS TITLE = "StadtService Brühl" @@ -34,12 +34,8 @@ class Source: r = requests.post( "https://services.stadtservice-bruehl.de/abfallkalender/", data=data ) + r.raise_for_status() - if r.status_code != 200: - _LOGGER.error("Error querying calender data") - return [] - - # print(r.text) soup = BeautifulSoup(r.text, "html.parser") for tag in soup.find_all("input", type="hidden"): @@ -49,8 +45,7 @@ class Source: post_district = tag["value"] if post_district == "": - _LOGGER.error("Unable to get district") - return [] + raise Exception("Unable to get district") # print(post_district); # Get ICAL @@ -74,10 +69,7 @@ class Source: "https://services.stadtservice-bruehl.de/abfallkalender/individuellen-abfuhrkalender-herunterladen/", data=data, ) - - if r.status_code != 200: - _LOGGER.error("Error querying calendar data") - return [] + r.raise_for_status() dates = self._ics.convert(r.text) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stonnington_vic_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stonnington_vic_gov_au.py index c7ba23a6..221923f8 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stonnington_vic_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stonnington_vic_gov_au.py @@ -1,6 +1,6 @@ import logging -from datetime import datetime import re +from datetime import datetime import requests from bs4 import BeautifulSoup @@ -45,10 +45,9 @@ class Source: addressSearchApiResults["Items"] is None or len(addressSearchApiResults["Items"]) < 1 ): - _LOGGER.error( + raise Exception( f"Address search for '{self._street_address}' returned no results. Check your address on https://www.stonnington.vic.gov.au/Services/Waste-and-recycling" ) - return [] addressSearchTopHit = addressSearchApiResults["Items"][0] _LOGGER.debug("Address search top hit: %s", addressSearchTopHit) @@ -72,10 +71,12 @@ class Source: waste_type = article.h3.string icon = ICON_MAP.get(waste_type, "mdi:trash-can") next_pickup = article.find(class_="next-service").string.strip() - if re.match("[^\s]* \d{1,2}\/\d{1,2}\/\d{4}", next_pickup): + if re.match(r"[^\s]* \d{1,2}\/\d{1,2}\/\d{4}", next_pickup): next_pickup_date = datetime.strptime( next_pickup.split(sep=" ")[1], "%d/%m/%Y" ).date() - entries.append(Collection(date=next_pickup_date, t=waste_type, icon=icon)) + entries.append( + Collection(date=next_pickup_date, t=waste_type, icon=icon) + ) return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/thehills_nsw_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/thehills_nsw_gov_au.py index a9a94563..53f613f1 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/thehills_nsw_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/thehills_nsw_gov_au.py @@ -37,8 +37,7 @@ class Source: # check if suburb exists if self._suburb not in suburbs: - _LOGGER.error(f"suburb not found: {self._suburb}") - return [] + raise Exception(f"suburb not found: {self._suburb}") suburbKey = suburbs[self._suburb] # get list of streets for selected suburb @@ -51,14 +50,14 @@ class Source: # check if street exists if self._street not in streets: - _LOGGER.error(f"street not found: {self._street}") - return [] + raise Exception(f"street not found: {self._street}") streetKey = streets[self._street] # get list of house numbers for selected street params = {"streetkey": streetKey, "suburbKey": suburbKey} r = requests.get( - f"{self._url}/properties/GetPropertiesByStreetAndSuburbKey", params=params, + f"{self._url}/properties/GetPropertiesByStreetAndSuburbKey", + params=params, ) data = json.loads(r.text) @@ -70,8 +69,7 @@ class Source: # check if house number exists if self._houseNo not in houseNos: - _LOGGER.error(f"house number not found: {self._houseNo}") - return [] + raise Exception(f"house number not found: {self._houseNo}") propertyKey = houseNos[self._houseNo] # get collection schedule diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wyndham_vic_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wyndham_vic_gov_au.py index 930c2683..116c7470 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/wyndham_vic_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/wyndham_vic_gov_au.py @@ -1,18 +1,24 @@ import logging +from datetime import datetime import requests from bs4 import BeautifulSoup from waste_collection_schedule import Collection # type: ignore[attr-defined] -from datetime import datetime TITLE = "Wyndham City Council, Melbourne" DESCRIPTION = "Source for Wyndham City Council rubbish collection." URL = "https://wyndham.vic.gov.au" TEST_CASES = { - "Truganina South Primary School": {"street_address": "3-19 Parkvista Drive TRUGANINA 3029"}, + "Truganina South Primary School": { + "street_address": "3-19 Parkvista Drive TRUGANINA 3029" + }, "Westbourne Grammar School": {"street_address": "300 Sayers Road TRUGANINA 3029"}, - "Werribee Mercy Hospital": {"street_address": "300-310 Princes Highway WERRIBEE 3030"}, - "Wyndham Park Primary School": {"street_address": "59-77 Kookaburra Avenue WERRIBEE 3030"}, + "Werribee Mercy Hospital": { + "street_address": "300-310 Princes Highway WERRIBEE 3030" + }, + "Wyndham Park Primary School": { + "street_address": "59-77 Kookaburra Avenue WERRIBEE 3030" + }, } API_URL = "https://digital.wyndham.vic.gov.au/myWyndham/" @@ -33,47 +39,52 @@ class Source: session = requests.Session() response = session.get(API_URL) response.raise_for_status() - response = session.get("https://digital.wyndham.vic.gov.au/myWyndham/ajax/address-search-suggestions.asp?", - params=dict(ASEARCH=self._street_address), - ) + response = session.get( + "https://digital.wyndham.vic.gov.au/myWyndham/ajax/address-search-suggestions.asp?", + params=dict(ASEARCH=self._street_address), + ) response.raise_for_status() html = response.content - property_address = BeautifulSoup(html, 'html.parser').find("li").get_text() + property_address = BeautifulSoup(html, "html.parser").find("li").get_text() _LOGGER.debug("Fetched Property Address: %s", property_address) - if property_address == 'No match found.': - _LOGGER.error( - f"Address search for '{self._street_address}' returned no results. Check your address on " - f"https://digital.wyndham.vic.gov.au/myWyndham/ " - ) - if property_address.upper() == self._street_address.upper(): - property_number = BeautifulSoup(html, 'html.parser').find('span').get_text() - _LOGGER.debug("Fetched Property Number: %s", property_number) - response = session.get( - "https://digital.wyndham.vic.gov.au/myWyndham/init-map-data.asp", - params=dict(propnum=property_number, radius="1000", mapfeatures="23,37,22,33,35"), - ) - response.raise_for_status() - wasteApiResult = response.content - soup = BeautifulSoup(wasteApiResult, 'html.parser') - entries = [] - - for article in soup.findAll("div", {"class": "waste"}): - if article.get_text().startswith('Next'): - waste_type = article.get_text().strip().split(':')[0][5:].replace(' Collection', '') - _LOGGER.debug("Waste Type: %s", waste_type) - icon = ICON_MAP.get(waste_type, 'mdi:trash-can') - _LOGGER.debug("Icon: %s", icon) - next_pickup_date = datetime.strptime(article.get_text().split(':')[1].strip(), "%A, %d %B %Y").date() - _LOGGER.debug("Next Pickup Date: %s", next_pickup_date) - entries.append( - Collection(date=next_pickup_date, t=waste_type, icon=icon) - ) - return entries - else: - _LOGGER.error( + if ( + property_address == "No match found." + or property_address.upper() != self._street_address.upper() + ): + raise Exception( f"Address search for '{self._street_address}' returned no results. Check your address on " f"https://digital.wyndham.vic.gov.au/myWyndham/ " ) + property_number = BeautifulSoup(html, "html.parser").find("span").get_text() + _LOGGER.debug("Fetched Property Number: %s", property_number) + response = session.get( + "https://digital.wyndham.vic.gov.au/myWyndham/init-map-data.asp", + params=dict( + propnum=property_number, radius="1000", mapfeatures="23,37,22,33,35" + ), + ) + response.raise_for_status() + wasteApiResult = response.content + soup = BeautifulSoup(wasteApiResult, "html.parser") + entries = [] - + for article in soup.findAll("div", {"class": "waste"}): + if article.get_text().startswith("Next"): + waste_type = ( + article.get_text() + .strip() + .split(":")[0][5:] + .replace(" Collection", "") + ) + _LOGGER.debug("Waste Type: %s", waste_type) + icon = ICON_MAP.get(waste_type, "mdi:trash-can") + _LOGGER.debug("Icon: %s", icon) + next_pickup_date = datetime.strptime( + article.get_text().split(":")[1].strip(), "%A, %d %B %Y" + ).date() + _LOGGER.debug("Next Pickup Date: %s", next_pickup_date) + entries.append( + Collection(date=next_pickup_date, t=waste_type, icon=icon) + ) + return entries From 754dd5fa570c66252e5f41bc86df4984498e522b Mon Sep 17 00:00:00 2001 From: mampfes Date: Thu, 29 Dec 2022 17:20:35 +0100 Subject: [PATCH 081/127] add color coding to test_sources.py output --- .../test/test_sources.py | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/test/test_sources.py b/custom_components/waste_collection_schedule/waste_collection_schedule/test/test_sources.py index 36bffb99..f48928aa 100755 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/test/test_sources.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/test/test_sources.py @@ -79,10 +79,18 @@ def main(): replace_secret(secrets, tc) # create source - source = module.Source(**tc) try: + source = module.Source(**tc) result = source.fetch() - print(f" found {len(result)} entries for {name}") + count = len(result) + if count > 0: + print( + f" found {bcolors.OKGREEN}{count}{bcolors.ENDC} entries for {name}" + ) + else: + print( + f" found {bcolors.WARNING}0{bcolors.ENDC} entries for {name}" + ) # test if source is returning the correct date format if ( @@ -94,7 +102,7 @@ def main(): > 0 ): print( - " ERROR: source returns invalid date format (datetime.datetime instead of datetime.date?)" + f"{bcolors.FAIL} ERROR: source returns invalid date format (datetime.datetime instead of datetime.date?){bcolors.ENDC}" ) if args.list: @@ -104,7 +112,7 @@ def main(): except KeyboardInterrupt: exit() except Exception as exc: - print(f" {name} failed with exception: {exc}") + print(f" {name} {bcolors.FAIL}failed{bcolors.ENDC}: {exc}") if args.traceback: print(indent(traceback.format_exc(), 4)) @@ -129,5 +137,17 @@ def indent(s, count): return "\n".join([indent + line for line in s.split("\n")]) +class bcolors: + HEADER = "\033[95m" + OKBLUE = "\033[94m" + OKCYAN = "\033[96m" + OKGREEN = "\033[92m" + WARNING = "\033[93m" + FAIL = "\033[91m" + ENDC = "\033[0m" + BOLD = "\033[1m" + UNDERLINE = "\033[4m" + + if __name__ == "__main__": main() From a5c399605c9b3379a502489a135d8baec1c95c3b Mon Sep 17 00:00:00 2001 From: mampfes Date: Thu, 29 Dec 2022 17:21:14 +0100 Subject: [PATCH 082/127] update known issues --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e8e468f5..75dece74 100644 --- a/README.md +++ b/README.md @@ -334,7 +334,10 @@ If you'd like to help with any of these, please raise an [issue](https://github. The following waste service providers return errors when running the test_source script: -- `banyule_vic_gov_au`: JSONDecodeError, causes by Captcha +- `banyule_vic_gov_au`: JSONDecodeError, caused by not supported Captcha wall +- `republicservices_com`: JSONDecoderError +- `newcastle_gov_uk`: all tests return 0 entries +- `awn_de`: all tests return 0 entries If you can fix any of these, please raise a Pull Request with the updates. From 785571656d302e311e46f8f7a9ee60a158df9ad4 Mon Sep 17 00:00:00 2001 From: mampfes Date: Thu, 29 Dec 2022 17:33:16 +0100 Subject: [PATCH 083/127] determine region in fetch, not in __init__ Otherwise it doesn't repair if is broken during instantiation --- .../source/korneuburg_stadtservice_at.py | 110 ++++++++++++------ 1 file changed, 75 insertions(+), 35 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/korneuburg_stadtservice_at.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/korneuburg_stadtservice_at.py index e53ef22d..db67794c 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/korneuburg_stadtservice_at.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/korneuburg_stadtservice_at.py @@ -4,23 +4,27 @@ from urllib.parse import urljoin import requests from bs4 import BeautifulSoup from waste_collection_schedule import Collection # type: ignore[attr-defined] -from waste_collection_schedule.service.ICS import ICS # type: ignore[attr-defined] +from waste_collection_schedule.service.ICS import ICS # type: ignore[attr-defined] -TITLE = 'Stadtservice Korneuburg' -DESCRIPTION = 'Source for Stadtservice Korneuburg' -URL = 'https://www.korneuburg.gv.at' +TITLE = "Stadtservice Korneuburg" +DESCRIPTION = "Source for Stadtservice Korneuburg" +URL = "https://www.korneuburg.gv.at" TEST_CASES = { - "Rathaus": {"street_name": "Hauptplatz", "street_number": 39}, # Teilgebiet 4 - "Rathaus using Teilgebiet": {"street_name": "SomeStreet", "street_number": "1A", "teilgebiet": "4"}, # Teilgebiet 4 - "Werft": {"street_name": "Am Hafen", "street_number": 6} # Teilgebiet 2 + "Rathaus": {"street_name": "Hauptplatz", "street_number": 39}, # Teilgebiet 4 + "Rathaus using Teilgebiet": { + "street_name": "SomeStreet", + "street_number": "1A", + "teilgebiet": "4", + }, # Teilgebiet 4 + "Werft": {"street_name": "Am Hafen", "street_number": 6}, # Teilgebiet 2 } # Mapping of teilgebiete to calendar urls WASTE_TYPE_URLS = { - '1': ('Biomuell_3', 'Restmuell_3', 'Papier_2', 'Gelber_Sack_4'), - '2': ('Biomuell_4', 'Restmuell_2', 'Papier_3', 'Gelber_Sack_1'), - '3': ('Biomuell_1', 'Restmuell_1', 'Papier_1', 'Gelber_Sack_2'), - '4': ('Biomuell_2', 'Restmuell', 'Papier', 'Gelber_Sack_3') + "1": ("Biomuell_3", "Restmuell_3", "Papier_2", "Gelber_Sack_4"), + "2": ("Biomuell_4", "Restmuell_2", "Papier_3", "Gelber_Sack_1"), + "3": ("Biomuell_1", "Restmuell_1", "Papier_1", "Gelber_Sack_2"), + "4": ("Biomuell_2", "Restmuell", "Papier", "Gelber_Sack_3"), } @@ -28,18 +32,15 @@ class Source: def __init__(self, street_name, street_number, teilgebiet=-1): self.street_name = street_name self.street_number = street_number + self.teilgebiet = teilgebiet + self._region = None self._street_name_id = -1 self._street_number_id = -1 - self._headers = {'User-Agent': 'Mozilla/5.0'} - self._cookies = {'ris_cookie_setting': 'g7750'} # Accept Cookie Consent + self._headers = {"User-Agent": "Mozilla/5.0"} + self._cookies = {"ris_cookie_setting": "g7750"} # Accept Cookie Consent self._ics = ICS() - if 0 < int(teilgebiet) <= 4: - self.region = str(teilgebiet) - else: - self.region = self.determine_region() - @staticmethod def extract_street_numbers(soup): @@ -52,19 +53,31 @@ class Source: street_number_idx += 1 possible_numbers = json.loads( - scripts[street_number_idx].string[19:].replace('\r\n', '').replace(', ]', ']').replace('\'', '"')) + scripts[street_number_idx] + .string[19:] + .replace("\r\n", "") + .replace(", ]", "]") + .replace("'", '"') + ) number_dict = dict() for idx, street_id in enumerate(possible_numbers): - number_dict[street_id[0]] = {e[1]: (e[0], e[2]) for _idx, e in enumerate(possible_numbers[idx][1])} + number_dict[street_id[0]] = { + e[1]: (e[0], e[2]) for _idx, e in enumerate(possible_numbers[idx][1]) + } return number_dict @staticmethod def extract_street_names(soup): - street_selector = soup.find("select", {"id": "225991280_boxmuellkalenderstrassedd"}).findAll("option") - available_streets = {street.string: int(street["value"]) for _idx, street in enumerate(street_selector)} + street_selector = soup.find( + "select", {"id": "225991280_boxmuellkalenderstrassedd"} + ).findAll("option") + available_streets = { + street.string: int(street["value"]) + for _idx, street in enumerate(street_selector) + } return available_streets @@ -73,8 +86,8 @@ class Source: region = -1 for span in soup.findAll("span"): - if span.parent.name == 'td' and "teilgebiet" in span.string.lower(): - region = span.string.split(' ')[1] + if span.parent.name == "td" and "teilgebiet" in span.string.lower(): + region = span.string.split(" ")[1] break return region @@ -82,6 +95,9 @@ class Source: def determine_region(self): """finds the target region for the street and street number""" + if 0 < int(self.teilgebiet) <= 4: + return str(self.teilgebiet) + # request address selection form url = urljoin(URL, "Rathaus/Buergerservice/Muellabfuhr") page = requests.get(url=url, headers=self._headers, cookies=self._cookies) @@ -94,44 +110,68 @@ class Source: street_found = self.street_name in available_streets.keys() if not street_found: - raise Exception(f"{self.street_name} not found. Please check back spelling with the official site: {url}") + raise Exception( + f"{self.street_name} not found. Please check back spelling with the official site: {url}" + ) self._street_name_id = available_streets.get(self.street_name) self._street_number_id, street_number_link = number_dict.get( - available_streets.get(self.street_name)).get(str(self.street_number), (-1, 'not found')) + available_streets.get(self.street_name) + ).get(str(self.street_number), (-1, "not found")) - if street_number_link == 'not found': - raise Exception(f"{self.street_number} not found. Available numbers for {self.street_name} are\ - {list(number_dict.get(available_streets['Am Hafen']).keys())}") + if street_number_link == "not found": + raise Exception( + f"{self.street_number} not found. Available numbers for {self.street_name} are\ + {list(number_dict.get(available_streets['Am Hafen']).keys())}" + ) # add selection cookie - self._cookies['riscms_muellkalender'] = str(f"{self._street_name_id}_{self._street_number_id}") + self._cookies["riscms_muellkalender"] = str( + f"{self._street_name_id}_{self._street_number_id}" + ) # request overview with address selection to get the region url = urljoin(URL, "system/web/kalender.aspx") - page = requests.get(url=url, headers=self._headers, cookies=self._cookies, - params={"sprache": "1", "menuonr": "225991280", "typids": street_number_link}) + page = requests.get( + url=url, + headers=self._headers, + cookies=self._cookies, + params={ + "sprache": "1", + "menuonr": "225991280", + "typids": street_number_link, + }, + ) soup = BeautifulSoup(page.content, "html.parser") region = self.extract_region(soup) if region == -1: - raise Exception(f"Region could not be found") + raise Exception("Region could not be found") return str(region) def get_region_links(self): """traverses the pages for different waste types and collects download links for the iCals""" + if self._region is None: + self._region = self.determine_region() + # create waste type urls ical_urls = [] - urls = [urljoin(URL, u) for u in WASTE_TYPE_URLS.get(self.region)] + urls = [urljoin(URL, u) for u in WASTE_TYPE_URLS.get(self._region)] for u in urls: r = requests.get(url=u, headers=self._headers, cookies=self._cookies) soup = BeautifulSoup(r.content, "html.parser") - download_link = soup.findAll("a", {"class": "piwik_download_tracker", "data-trackingtyp": "iCal/Kalender"}) + download_link = soup.findAll( + "a", + { + "class": "piwik_download_tracker", + "data-trackingtyp": "iCal/Kalender", + }, + ) if len(download_link): ical_urls.append(urljoin(URL, download_link[0].get("href"))) From d30b9148f8b1257325b1ef1488ff8442e8599e16 Mon Sep 17 00:00:00 2001 From: bbr111 Date: Thu, 29 Dec 2022 18:47:23 +0100 Subject: [PATCH 084/127] Add source for Breckland Council, UK Add source for Breckland Council, UK --- README.md | 1 + .../source/breckland_gov_uk.py | 77 +++++++++++++++++++ doc/source/breckland_gov_uk.md | 40 ++++++++++ info.md | 2 +- 4 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 custom_components/waste_collection_schedule/waste_collection_schedule/source/breckland_gov_uk.py create mode 100644 doc/source/breckland_gov_uk.md diff --git a/README.md b/README.md index aed427fe..7b4172d5 100644 --- a/README.md +++ b/README.md @@ -257,6 +257,7 @@ Waste collection schedules in the following formats and countries are supported. - [Bracknell Forest Council](/doc/source/bracknell_forest_gov_uk.md) / selfservice.mybfc.bracknell-forest.gov.uk - [Bradford Metropolitan District Council](/doc/source/bradford_gov_uk.md) / bradford.gov.uk - [Braintree District Council](/doc/source/braintree_gov_uk.md) / braintree.gov.uk +- [Breckland Council](/doc/source/breckland_gov_uk.md) / breckland.gov.uk - [Cambridge City Council](/doc/source/cambridge_gov_uk.md) / cambridge.gov.uk - [Canterbury City Council](/doc/source/canterbury_gov_uk.md) / canterbury.gov.uk - [Cheshire East Council](/doc/source/cheshire_east_gov_uk.md) / cheshireeast.gov.uk diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/breckland_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/breckland_gov_uk.py new file mode 100644 index 00000000..3968fd7a --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/breckland_gov_uk.py @@ -0,0 +1,77 @@ +import logging +import requests +from datetime import datetime +from waste_collection_schedule import Collection + +TITLE = "Breckland Council, UK" +DESCRIPTION = "Source for breckland.gov.uk" +URL = "https://www.breckland.gov.uk/mybreckland" +TEST_CASES = { + "test1" : {"postcode":"IP22 2LJ","address":"glen travis" }, +} + +_LOGGER = logging.getLogger(__name__) + +class Source: + def __init__(self,postcode,address): + self._postcode = postcode + self._address = address + + def fetch(self): + + json_data = { + "jsonrpc":"2.0", + "id":"", + "method":"Breckland.Whitespace.JointWasteAPI.GetSiteIDsByPostcode", + "params":{ + "postcode":self._postcode, + "environment":"live" + } + } + + headers = {'referer': 'https://www.breckland.gov.uk/mybreckland'} + url = 'https://www.breckland.gov.uk/apiserver/ajaxlibrary' + r = requests.post(url, json=json_data,headers=headers) + + if r.status_code != 200: + _LOGGER.error("Error querying calender data") + return [] + + json_response = r.json() + + uprn = "" + for key in json_response['result']: + if self._address.lower() in key['name'].lower(): + uprn = (key['uprn']) + + if uprn == "": + _LOGGER.error("Error querying calender data") + return [] + + json_data = { + "jsonrpc":"2.0", + "id":"", + "method":"Breckland.Whitespace.JointWasteAPI.GetBinCollectionsByUprn", + "params":{ + "uprn":uprn, + "environment":"live" + } + } + + r = requests.post(url, json=json_data,headers=headers) + + if r.status_code != 200: + _LOGGER.error("Error querying calender data") + return [] + + waste = r.json() + waste = waste['result'] + + entries = [] + + for data in waste: + print(data['collectiontype']) + print(data['nextcollection']) + entries.append(Collection(datetime.strptime(data['nextcollection'],'%d/%m/%Y %H:%M:%S').date(),data['collectiontype'])) + + return entries \ No newline at end of file diff --git a/doc/source/breckland_gov_uk.md b/doc/source/breckland_gov_uk.md new file mode 100644 index 00000000..ba13913e --- /dev/null +++ b/doc/source/breckland_gov_uk.md @@ -0,0 +1,40 @@ +# Breckland Council + +Support for schedules provided by [Breckland Council](https://www.breckland.gov.uk/mybreckland) + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: breckland_gov_uk + args: + postcode: POSTCODE + address: ADDRESS + +``` + +### Configuration Variables + +**POSTCODE** +*(string) (required)* + +**ADDRESS** +*(string) (required)* + +## Examples + +```yaml +waste_collection_schedule: + sources: + - name: breckland_gov_uk + args: + postcode: "IP22 2LJ" + address: "glen travis" +``` + +You can find all relevant informations at [Breckland Council](https://www.breckland.gov.uk/mybreckland) homepage +use the POSTCODE -> click find address. +Choose your address. Please only use the first part of your address. If you got an error, use less charecters from address. + + diff --git a/info.md b/info.md index 16d00217..81d438fa 100644 --- a/info.md +++ b/info.md @@ -28,7 +28,7 @@ Waste collection schedules from service provider web sites are updated daily, de | Poland | Ecoharmonogram, Warsaw | | Sweden | Lerum Vatten och Avlopp, Ronneby Miljöteknik, SRV Återvinning, SSAM, Sysav Sophämntning, VA Syd Sophämntning | | Switzerland | A-Region, Andwil, Appenzell, Berg, Bühler, Eggersriet, Gais, Gaiserwald, Goldach, Grub, Heiden, Herisau, Horn, Hundwil, Häggenschwil, Lindau, Lutzenberg, Muolen, Mörschwil, Rehetobel, Rorschach, Rorschacherberg, Schwellbrunn, Schönengrund, Speicher, Stein, Steinach, Teufen, Thal, Trogen, Tübach, Untereggen, Urnäsch, Wald, Waldkirch, Waldstatt, Wittenbach, Wolfhalden | -| United Kingdom | Bracknell Forest Council, Bradford Metropolitan District Council, Braintree District Council, Cambridge City Council, Canterbury City Council, Cheshire East Council, Chesterfield Borough Council, City of York Council, Colchester Borough Council, Cornwall Council, Derby City Council, Eastbourne Borough Council, Elmbridge Borough Council, Environment First, FCC Environment, Guildford Borough Council, Harborough District Council, Huntingdonshire District Council, Lewes District Council, London Borough of Lewisham, Manchester City Council, Middlesbrough Council, Newcastle City Council, North Somerset Council, Nottingham City Council, Peterborough City Council, Richmondshire District Council, Rushmoor Borough Council, Sheffield City Council, South Cambridgeshire District Council, South Hams District Council, South Norfolk and Broadland Council, Stevenage Borough Council, Tewkesbury Borough Council, The Royal Borough of Kingston Council, Walsall Council, West Berkshire Council, West Devon Borough Council, Wiltshire Council | +| United Kingdom | Bracknell Forest Council, Bradford Metropolitan District Council, Braintree District Council, Breckland Council, Cambridge City Council, Canterbury City Council, Cheshire East Council, Chesterfield Borough Council, City of York Council, Colchester Borough Council, Cornwall Council, Derby City Council, Eastbourne Borough Council, Elmbridge Borough Council, Environment First, FCC Environment, Guildford Borough Council, Harborough District Council, Huntingdonshire District Council, Lewes District Council, London Borough of Lewisham, Manchester City Council, Middlesbrough Council, Newcastle City Council, North Somerset Council, Nottingham City Council, Peterborough City Council, Richmondshire District Council, Rushmoor Borough Council, Sheffield City Council, South Cambridgeshire District Council, South Hams District Council, South Norfolk and Broadland Council, Stevenage Borough Council, Tewkesbury Borough Council, The Royal Borough of Kingston Council, Walsall Council, West Berkshire Council, West Devon Borough Council, Wiltshire Council | | United States of America | City of Pittsburgh, Republic Services, Seattle Public Utilities | From 903842e47b109a758b5b296fa737d5b283ffc2f7 Mon Sep 17 00:00:00 2001 From: dt215git Date: Thu, 29 Dec 2022 20:41:19 +0000 Subject: [PATCH 085/127] URL -> API_URL edit --- README.md | 1 - .../waste_collection_schedule/source/newcastle_gov_uk.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index aed427fe..1560ca4c 100644 --- a/README.md +++ b/README.md @@ -351,7 +351,6 @@ The following waste service providers return errors when running the test_source - `banyule_vic_gov_au`: JSONDecodeError, caused by not supported Captcha wall - `republicservices_com`: JSONDecoderError -- `newcastle_gov_uk`: all tests return 0 entries - `awn_de`: all tests return 0 entries If you can fix any of these, please raise a Pull Request with the updates. diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/newcastle_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/newcastle_gov_uk.py index 9b597024..ba53653f 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/newcastle_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/newcastle_gov_uk.py @@ -40,7 +40,7 @@ class Source: def fetch(self): entries = [] - res = requests.get(f"{URL}?uprn={self._uprn}") + res = requests.get(f"{API_URL}?uprn={self._uprn}") collections = re.findall(REGEX, res.text) for collection in collections: From cf582bdc0297e4acfa19dbebcacfdcdd7bc87888 Mon Sep 17 00:00:00 2001 From: dt215git Date: Thu, 29 Dec 2022 20:59:59 +0000 Subject: [PATCH 086/127] regex simplified, unnecessary urllib3 removed --- .../source/newcastle_gov_uk.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/newcastle_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/newcastle_gov_uk.py index ba53653f..9aedb156 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/newcastle_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/newcastle_gov_uk.py @@ -4,13 +4,6 @@ from datetime import datetime import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] -# These lines are needed to suppress the InsecureRequestWarning resulting from the POST verify=False option -# With verify=True the POST fails due to a SSLCertVerificationError. -# The following links may provide a better way of dealing with this, as using verify=False is not ideal: -# https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html#ssl-warnings -# https://urllib3.readthedocs.io/en/1.26.x/user-guide.html#ssl -import urllib3 -urllib3.disable_warnings() _LOGGER = logging.getLogger(__name__) @@ -22,10 +15,8 @@ TEST_CASES = {"Test_001": {"uprn": "004510053797"}, "Test_002": {"uprn": 4510053 API_URL = "https://community.newcastle.gov.uk/my-neighbourhood/ajax/getBinsNew.php" REGEX = ( - "(Green|Blue|Brown) [bB]in \\((Domestic|Recycling|Garden)( Waste)?\\) details: <\\/strong>" - "collection day : [a-zA-Z]*day" - "Next collection : ([0-9]{2}-[A-Za-z]+-[0-9]{4})" -) + "[Green|Blue|Brown] [Bb]in \(([A-Za-z]+)( Waste)?\) .*? ([0-9]{2}-[A-Za-z]+-[0-9]{4})" + ) ICON_MAP = { "DOMESTIC": "mdi:trash-can", "RECYCLING": "mdi:recycle", @@ -44,8 +35,8 @@ class Source: collections = re.findall(REGEX, res.text) for collection in collections: - collection_type = collection[1] - collection_date = collection[3] + collection_type = collection[0] + collection_date = collection[2] entries.append( Collection( date=datetime.strptime(collection_date, "%d-%b-%Y").date(), From 339cc68af80a385128a3dddaace2ecd837b5f290 Mon Sep 17 00:00:00 2001 From: Andre Basche Date: Fri, 30 Dec 2022 00:17:39 +0100 Subject: [PATCH 087/127] Parser for Kreis Ahrweiler --- .../source/meinawb_de.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 custom_components/waste_collection_schedule/waste_collection_schedule/source/meinawb_de.py diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/meinawb_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/meinawb_de.py new file mode 100644 index 00000000..72041839 --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/meinawb_de.py @@ -0,0 +1,64 @@ +import html +import random +import re +import string +from datetime import datetime + +import requests +from waste_collection_schedule import Collection + +TITLE = "meinawb.de" +DESCRIPTION = "Bin collection service from Kreis Ahrweiler/Germany" +URL = "https://extdienste01.koblenz.de/WasteManagementAhrweiler/WasteManagementServlet" + +city = "Bad Neuenahr-Ahrweiler" +street = "Hauptstrasse" +houseno = "91" +YEAR = "2023" + +ICONS = { + "RM": {"icon": "mdi:trash-can", "t": "Restabfall"}, + "RG2": {"icon": "mdi:trash-can", "t": "Restabfall Gewerbe / PLUS-Tonne"}, + "BM": {"icon": "mdi:leaf", "t": "Bioabfall"}, + "PA": {"icon": "mdi:package-variant", "t": "Altpapier"}, + "GT": {"icon": "mdi:recycle", "t": "Verpackungen"}, + "GS": {"icon": "mdi:forest", "t": "Grünabfall / Weihnachtsbäume"}, +} + + +class Source: + def __init__(self, city, street, house_number): + self._city = city + self._street = street + self._house_number = house_number + + @staticmethod + def parse_data(data, boundary): + result = "" + for key, value in data.items(): + result += f'------WebKitFormBoundary{boundary}\r\nContent-Disposition: form-data; name="{key}"\r\n\r\n{value}\r\n' + result += f"------WebKitFormBoundary{boundary}--\r\n" + return result.encode() + + @staticmethod + def parse_response_input(text): + parsed = re.findall("[A-Z][a-z]. ([0-9.]{10})

', response.text) + return [Collection(datetime.strptime(date, "%d.%m.%Y").date(), **ICONS[bin_type]) for bin_type, date in dates] From 53d28c35598503d53fe5957e4c19c3ac3fda1088 Mon Sep 17 00:00:00 2001 From: bbr111 Date: Fri, 30 Dec 2022 11:42:01 +0100 Subject: [PATCH 088/127] Add source for Breckland Council, UK - update Add ICON_Map Add support for uprn Update documentation --- .../source/breckland_gov_uk.py | 69 ++++++++++++------- doc/source/breckland_gov_uk.md | 20 +++++- 2 files changed, 61 insertions(+), 28 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/breckland_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/breckland_gov_uk.py index 3968fd7a..6ddc420a 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/breckland_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/breckland_gov_uk.py @@ -8,43 +8,56 @@ DESCRIPTION = "Source for breckland.gov.uk" URL = "https://www.breckland.gov.uk/mybreckland" TEST_CASES = { "test1" : {"postcode":"IP22 2LJ","address":"glen travis" }, + "test2" : {"uprn":"10011977093"}, +} + +API_URL = "https://www.breckland.gov.uk/apiserver/ajaxlibrary" +ICON_MAP = { +"Refuse Collection Service": "mdi:trash-can", +"Recycling Collection Service": "mdi:recycle", +"Garden Waste Service": "mdi:leaf", } _LOGGER = logging.getLogger(__name__) +headers = {'referer': URL} + class Source: - def __init__(self,postcode,address): + def __init__(self,postcode=None,address=None,uprn=None): self._postcode = postcode self._address = address + self._uprn = uprn def fetch(self): - json_data = { - "jsonrpc":"2.0", - "id":"", - "method":"Breckland.Whitespace.JointWasteAPI.GetSiteIDsByPostcode", - "params":{ - "postcode":self._postcode, - "environment":"live" - } - } - - headers = {'referer': 'https://www.breckland.gov.uk/mybreckland'} - url = 'https://www.breckland.gov.uk/apiserver/ajaxlibrary' - r = requests.post(url, json=json_data,headers=headers) - - if r.status_code != 200: - _LOGGER.error("Error querying calender data") + if self._postcode == None and self._address == None and self._uprn == None: + _LOGGER.error("no attributes - specify postcode and address or just uprn") return [] - json_response = r.json() + if self._uprn == None: + json_data = { + "jsonrpc":"2.0", + "id":"", + "method":"Breckland.Whitespace.JointWasteAPI.GetSiteIDsByPostcode", + "params":{ + "postcode":self._postcode, + "environment":"live" + } + } - uprn = "" - for key in json_response['result']: - if self._address.lower() in key['name'].lower(): - uprn = (key['uprn']) + r = requests.post(API_URL, json=json_data,headers=headers) - if uprn == "": + if r.status_code != 200: + _LOGGER.error("Error querying calender data") + return [] + + json_response = r.json() + + for key in json_response['result']: + if self._address.lower() in key['name'].lower(): + self._uprn = (key['uprn']) + + if self._uprn == None: _LOGGER.error("Error querying calender data") return [] @@ -53,12 +66,12 @@ class Source: "id":"", "method":"Breckland.Whitespace.JointWasteAPI.GetBinCollectionsByUprn", "params":{ - "uprn":uprn, + "uprn":self._uprn, "environment":"live" } } - r = requests.post(url, json=json_data,headers=headers) + r = requests.post(API_URL, json=json_data,headers=headers) if r.status_code != 200: _LOGGER.error("Error querying calender data") @@ -72,6 +85,10 @@ class Source: for data in waste: print(data['collectiontype']) print(data['nextcollection']) - entries.append(Collection(datetime.strptime(data['nextcollection'],'%d/%m/%Y %H:%M:%S').date(),data['collectiontype'])) + entries.append(Collection( + datetime.strptime(data['nextcollection'],'%d/%m/%Y %H:%M:%S').date(), + data['collectiontype'], + ICON_MAP.get(data['collectiontype']) + )) return entries \ No newline at end of file diff --git a/doc/source/breckland_gov_uk.md b/doc/source/breckland_gov_uk.md index ba13913e..ff3439c3 100644 --- a/doc/source/breckland_gov_uk.md +++ b/doc/source/breckland_gov_uk.md @@ -11,16 +11,20 @@ waste_collection_schedule: args: postcode: POSTCODE address: ADDRESS + uprn: UPRN ``` ### Configuration Variables **POSTCODE** -*(string) (required)* +*(string) (optional)* **ADDRESS** -*(string) (required)* +*(string) (optional)* + +**UPRN** +*(string) (optional)* ## Examples @@ -33,8 +37,20 @@ waste_collection_schedule: address: "glen travis" ``` +```yaml +waste_collection_schedule: + sources: + - name: breckland_gov_uk + args: + uprn: "10011977093" +``` + +If uprn is provided, only uprn is used. Otherwise postcode and address are required. + You can find all relevant informations at [Breckland Council](https://www.breckland.gov.uk/mybreckland) homepage use the POSTCODE -> click find address. Choose your address. Please only use the first part of your address. If you got an error, use less charecters from address. +## How to find your UPRN +An easy way to discover your Unique Property Reference Number (UPRN) is by going to [Find My Address](https://www.findmyaddress.co.uk/) and providng your address details. From d8d7ff416cf0cb7909c7606a0bfc97e701416bd9 Mon Sep 17 00:00:00 2001 From: dt215git Date: Fri, 30 Dec 2022 11:42:28 +0000 Subject: [PATCH 089/127] readme updated --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index aed427fe..e1775a32 100644 --- a/README.md +++ b/README.md @@ -319,7 +319,7 @@ The Waste Collection Schedule can be installed via [HACS](https://hacs.xyz/), or ![python badge](https://img.shields.io/badge/Made%20with-Python-orange) ![github contributors](https://img.shields.io/github/contributors/mampfes/hacs_waste_collection_schedule?color=orange) ![last commit](https://img.shields.io/github/last-commit/mampfes/hacs_waste_collection_schedule?color=orange) -![Community Discussion](https://img.shields.io/badge/Home%20Assistant%20Community-Discussion-orange) +[![Community Discussion](https://img.shields.io/badge/Home%20Assistant%20Community-Discussion-orange)](https://community.home-assistant.io/t/waste-collection-schedule-framework/186492) There are several ways of contributing to this project, they include: @@ -350,7 +350,6 @@ If you'd like to help with any of these, please raise an [issue](https://github. The following waste service providers return errors when running the test_source script: - `banyule_vic_gov_au`: JSONDecodeError, caused by not supported Captcha wall -- `republicservices_com`: JSONDecoderError - `newcastle_gov_uk`: all tests return 0 entries - `awn_de`: all tests return 0 entries From 77d6bccef1394434d2608db6f6d902f73b88e0eb Mon Sep 17 00:00:00 2001 From: mampfes Date: Fri, 30 Dec 2022 13:43:55 +0100 Subject: [PATCH 090/127] refactor breckland_gov_uk --- .../source/breckland_gov_uk.py | 100 ++++++++---------- doc/source/breckland_gov_uk.md | 8 +- 2 files changed, 47 insertions(+), 61 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/breckland_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/breckland_gov_uk.py index 6ddc420a..228179a2 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/breckland_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/breckland_gov_uk.py @@ -1,94 +1,82 @@ import logging -import requests from datetime import datetime + +import requests from waste_collection_schedule import Collection TITLE = "Breckland Council, UK" DESCRIPTION = "Source for breckland.gov.uk" URL = "https://www.breckland.gov.uk/mybreckland" TEST_CASES = { - "test1" : {"postcode":"IP22 2LJ","address":"glen travis" }, - "test2" : {"uprn":"10011977093"}, + "test1": {"postcode": "IP22 2LJ", "address": "glen travis"}, + "test2": {"uprn": "10011977093"}, } API_URL = "https://www.breckland.gov.uk/apiserver/ajaxlibrary" ICON_MAP = { -"Refuse Collection Service": "mdi:trash-can", -"Recycling Collection Service": "mdi:recycle", -"Garden Waste Service": "mdi:leaf", + "Refuse Collection Service": "mdi:trash-can", + "Recycling Collection Service": "mdi:recycle", + "Garden Waste Service": "mdi:leaf", } _LOGGER = logging.getLogger(__name__) -headers = {'referer': URL} +headers = {"referer": URL} + class Source: - def __init__(self,postcode=None,address=None,uprn=None): + def __init__(self, postcode=None, address=None, uprn=None): self._postcode = postcode self._address = address self._uprn = uprn + if postcode is None and address is None and uprn is None: + raise Exception("no attributes - specify postcode and address or just uprn") + def fetch(self): - - if self._postcode == None and self._address == None and self._uprn == None: - _LOGGER.error("no attributes - specify postcode and address or just uprn") - return [] - - if self._uprn == None: + if self._uprn is None: json_data = { - "jsonrpc":"2.0", - "id":"", - "method":"Breckland.Whitespace.JointWasteAPI.GetSiteIDsByPostcode", - "params":{ - "postcode":self._postcode, - "environment":"live" - } - } + "jsonrpc": "2.0", + "id": "", + "method": "Breckland.Whitespace.JointWasteAPI.GetSiteIDsByPostcode", + "params": {"postcode": self._postcode, "environment": "live"}, + } - r = requests.post(API_URL, json=json_data,headers=headers) - - if r.status_code != 200: - _LOGGER.error("Error querying calender data") - return [] + r = requests.post(API_URL, json=json_data, headers=headers) + r.raise_for_status() json_response = r.json() - for key in json_response['result']: - if self._address.lower() in key['name'].lower(): - self._uprn = (key['uprn']) + for key in json_response["result"]: + if self._address.lower() in key["name"].lower(): + self._uprn = key["uprn"] - if self._uprn == None: - _LOGGER.error("Error querying calender data") - return [] + if self._uprn is None: + raise Exception("Error querying calendar data") json_data = { - "jsonrpc":"2.0", - "id":"", - "method":"Breckland.Whitespace.JointWasteAPI.GetBinCollectionsByUprn", - "params":{ - "uprn":self._uprn, - "environment":"live" - } - } + "jsonrpc": "2.0", + "id": "", + "method": "Breckland.Whitespace.JointWasteAPI.GetBinCollectionsByUprn", + "params": {"uprn": self._uprn, "environment": "live"}, + } - r = requests.post(API_URL, json=json_data,headers=headers) + r = requests.post(API_URL, json=json_data, headers=headers) + r.raise_for_status() - if r.status_code != 200: - _LOGGER.error("Error querying calender data") - return [] - - waste = r.json() - waste = waste['result'] + waste = r.json()["result"] entries = [] for data in waste: - print(data['collectiontype']) - print(data['nextcollection']) - entries.append(Collection( - datetime.strptime(data['nextcollection'],'%d/%m/%Y %H:%M:%S').date(), - data['collectiontype'], - ICON_MAP.get(data['collectiontype']) - )) + entries.append( + Collection( + datetime.strptime( + data["nextcollection"], "%d/%m/%Y %H:%M:%S" + ).date(), + data["collectiontype"], + ICON_MAP.get(data["collectiontype"]), + ) + ) - return entries \ No newline at end of file + return entries diff --git a/doc/source/breckland_gov_uk.md b/doc/source/breckland_gov_uk.md index ff3439c3..95c8b2c5 100644 --- a/doc/source/breckland_gov_uk.md +++ b/doc/source/breckland_gov_uk.md @@ -11,8 +11,7 @@ waste_collection_schedule: args: postcode: POSTCODE address: ADDRESS - uprn: UPRN - + prn: UPRN ``` ### Configuration Variables @@ -47,9 +46,8 @@ waste_collection_schedule: If uprn is provided, only uprn is used. Otherwise postcode and address are required. -You can find all relevant informations at [Breckland Council](https://www.breckland.gov.uk/mybreckland) homepage -use the POSTCODE -> click find address. -Choose your address. Please only use the first part of your address. If you got an error, use less charecters from address. +You can find all relevant information at [Breckland Council](https://www.breckland.gov.uk/mybreckland) homepage. Use the POSTCODE -> click find address. +Choose your address. Please only use the first part of your address. If you got an error, use less characters from address. ## How to find your UPRN From b09945f3ba11460fdacb3e236f06a4eeaca259f8 Mon Sep 17 00:00:00 2001 From: mampfes Date: Fri, 30 Dec 2022 13:51:05 +0100 Subject: [PATCH 091/127] update manifest to 1.32.0 --- custom_components/waste_collection_schedule/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/waste_collection_schedule/manifest.json b/custom_components/waste_collection_schedule/manifest.json index 3e5a66bc..2275c81a 100644 --- a/custom_components/waste_collection_schedule/manifest.json +++ b/custom_components/waste_collection_schedule/manifest.json @@ -6,5 +6,5 @@ "dependencies": [], "codeowners": ["@mampfes"], "iot_class": "cloud_polling", - "version": "1.31.0" + "version": "1.32.0" } From 3b739e61a73424d3cb2109efbd0c3d0c02ea24b6 Mon Sep 17 00:00:00 2001 From: benzel Date: Fri, 30 Dec 2022 17:19:29 +0100 Subject: [PATCH 092/127] added service and more help added a (imo) better help, how to get the service-name and other parameters for c-trace --- doc/source/c_trace_de.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/doc/source/c_trace_de.md b/doc/source/c_trace_de.md index 39d23bc3..c0f93a39 100644 --- a/doc/source/c_trace_de.md +++ b/doc/source/c_trace_de.md @@ -50,4 +50,16 @@ This source requires the name of a `service` which is specific to your municipal |Municipality|service| |-|-| |Bremen|`bremenabfallkalender`| -|AWB Landkreis Augsburg|`augsburglandkreis`| \ No newline at end of file +|AWB Landkreis Augsburg|`augsburglandkreis`| +|WZV Kreis Segeberg|`segebergwzv-abfallkalender`| + +## Tip + +If your waste-service has an online-tool where you can get an ical or CSV-File, you can extract the needed `service` from the URL of the files. +![image](https://user-images.githubusercontent.com/2480235/210090615-29521bf0-eeaf-405d-8d02-56a505401f07.png) + +Link for above image: https://web.c-trace.de/segebergwzv-abfallkalender/(S(yoxdsu4g1uzrk3vt5gkztnae))/abfallkalender/cal/2023?Ort=Bad%20Segeberg&Strasse=Am%20Bienenhof&Hausnr=1%2B1a&abfall=0|1|2|3|4|5|6|7| + +From this Link you can extract the following parameters: + +web.c-trace.de/`service`/some-id/abfallkalender/cal/year?Ort=`ort`&Strasse=`strasse`&Hausnr=`hausnummer`... From 1378a7d83f3a387f19977d7b9b013a1efe5744e9 Mon Sep 17 00:00:00 2001 From: benzel Date: Fri, 30 Dec 2022 17:24:15 +0100 Subject: [PATCH 093/127] changed address to be the same of the service --- doc/source/c_trace_de.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/source/c_trace_de.md b/doc/source/c_trace_de.md index c0f93a39..20c66d44 100644 --- a/doc/source/c_trace_de.md +++ b/doc/source/c_trace_de.md @@ -56,9 +56,10 @@ This source requires the name of a `service` which is specific to your municipal ## Tip If your waste-service has an online-tool where you can get an ical or CSV-File, you can extract the needed `service` from the URL of the files. -![image](https://user-images.githubusercontent.com/2480235/210090615-29521bf0-eeaf-405d-8d02-56a505401f07.png) +![image](https://user-images.githubusercontent.com/2480235/210091450-663907b0-6a9c-45b4-b0ae-00110896bb08.png) -Link for above image: https://web.c-trace.de/segebergwzv-abfallkalender/(S(yoxdsu4g1uzrk3vt5gkztnae))/abfallkalender/cal/2023?Ort=Bad%20Segeberg&Strasse=Am%20Bienenhof&Hausnr=1%2B1a&abfall=0|1|2|3|4|5|6|7| + +Link for above image: https://web.c-trace.de/segebergwzv-abfallkalender/(S(ebi2zcbvfeqp0za3ofnepvct))/abfallkalender/cal/2023?Ort=Bad%20Segeberg&Strasse=Am%20Wasserwerk&Hausnr=2&abfall=0|1|2|3|4|5|6|7| From this Link you can extract the following parameters: From 7b76548a02c96b3916ea7e8c6cd2f53010c39319 Mon Sep 17 00:00:00 2001 From: Mirko Lenz Date: Fri, 30 Dec 2022 19:59:00 +0100 Subject: [PATCH 094/127] Add optional types to collection class --- .../waste_collection_schedule/collection.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/collection.py b/custom_components/waste_collection_schedule/waste_collection_schedule/collection.py index 551f9eea..16f2b910 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/collection.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/collection.py @@ -1,8 +1,14 @@ import datetime +from typing import Optional class CollectionBase(dict): # inherit from dict to enable JSON serialization - def __init__(self, date: datetime.date, icon: str = None, picture: str = None): + def __init__( + self, + date: datetime.date, + icon: Optional[str] = None, + picture: Optional[str] = None, + ): dict.__init__(self, date=date.isoformat(), icon=icon, picture=picture) self._date = date # store date also as python date object @@ -31,7 +37,11 @@ class CollectionBase(dict): # inherit from dict to enable JSON serialization class Collection(CollectionBase): def __init__( - self, date: datetime.date, t: str, icon: str = None, picture: str = None + self, + date: datetime.date, + t: str, + icon: Optional[str] = None, + picture: Optional[str] = None, ): CollectionBase.__init__(self, date=date, icon=icon, picture=picture) self["type"] = t From aee79cf611aa59c4efbb47f9ac898c13011b58d7 Mon Sep 17 00:00:00 2001 From: Mirko Lenz Date: Fri, 30 Dec 2022 19:59:45 +0100 Subject: [PATCH 095/127] art_trier_de: Remove default icon --- .../waste_collection_schedule/source/art_trier_de.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/art_trier_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/art_trier_de.py index 932e108f..16727adc 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/art_trier_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/art_trier_de.py @@ -75,8 +75,6 @@ class Source: schedule = self._ics.convert(res.text) return [ - Collection( - date=entry[0], t=entry[1], icon=ICON_MAP.get(entry[1], "mdi:trash-can") - ) + Collection(date=entry[0], t=entry[1], icon=ICON_MAP.get(entry[1])) for entry in schedule ] From 9157d2cdebee00b3347a7ceb41164fcf4a19b62a Mon Sep 17 00:00:00 2001 From: Mirko Lenz Date: Fri, 30 Dec 2022 20:01:56 +0100 Subject: [PATCH 096/127] burgerportal_de: Remove duplicate entries - Add option to show volume - Use set to remove duplicate collection entries --- .../source/buergerportal_de.py | 52 ++++++++++++++----- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/buergerportal_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/buergerportal_de.py index fcdbd973..bbe653dd 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/buergerportal_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/buergerportal_de.py @@ -1,5 +1,6 @@ +import datetime import re -from datetime import datetime +from dataclasses import dataclass from typing import List, Literal, Optional, TypedDict, Union import requests @@ -8,8 +9,12 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Bürgerportal" URL = "https://www.c-trace.de" DESCRIPTION = "Source for waste collection in multiple service areas." + + def EXTRA_INFO(): - return [ { "title": s["title"], "url": s["url"] } for s in SERVICE_MAP ] + return [{"title": s["title"], "url": s["url"]} for s in SERVICE_MAP] + + TEST_CASES = { "Cochem-Zell": { "operator": "cochem_zell", @@ -58,23 +63,35 @@ SERVICE_MAP = [ "title": "KV Cochem-Zell", "url": "https://www.cochem-zell-online.de/", "api_url": "https://buerger-portal-cochemzell.azurewebsites.net/api", - "operator": "cochem_zell" + "operator": "cochem_zell", }, { "title": "Abfallwirtschaft Alb-Donau-Kreis", "url": "https://www.aw-adk.de/", "api_url": "https://buerger-portal-albdonaukreisabfallwirtschaft.azurewebsites.net/api", - "operator": "alb_donau" + "operator": "alb_donau", }, { "title": "MZV Bidenkopf", "url": "https://mzv-biedenkopf.de/", "api_url": "https://biedenkopfmzv.buergerportal.digital/api", - "operator": "biedenkopf" + "operator": "biedenkopf", }, ] +# This datalcass is used for adding entries to a set and remove duplicate entries. +# The default `Collection` extends the standard dict and thus is not hashable. +@dataclass(frozen=True, eq=True) +class CollectionEntry: + date: datetime.date + waste_type: str + icon: Optional[str] + + def export(self) -> Collection: + return Collection(self.date, self.waste_type, self.icon) + + def quote_none(value: Optional[str]) -> str: if value is None: return "null" @@ -83,7 +100,7 @@ def quote_none(value: Optional[str]) -> str: def get_api_map(): - return { s["operator"]:s["api_url"] for s in SERVICE_MAP } + return {s["operator"]: s["api_url"] for s in SERVICE_MAP} class Source: @@ -94,19 +111,21 @@ class Source: street: str, subdistrict: Optional[str] = None, number: Union[int, str, None] = None, + show_volume: bool = False, ): self.api_url = get_api_map()[operator] self.district = district self.subdistrict = subdistrict self.street = street self.number = number + self.show_volume = show_volume - def fetch(self): + def fetch(self) -> list[Collection]: session = requests.session() session.headers.update(API_HEADERS) - year = datetime.now().year - entries: list[Collection] = [] + year = datetime.datetime.now().year + entries: set[CollectionEntry] = set() district_id = self.fetch_district_id(session) street_id = self.fetch_street_id(session, district_id) @@ -135,25 +154,32 @@ class Source: for collection in payload["d"]: if date_match := re.search(date_regex, collection["Termin"]): timestamp = float(date_match.group()) - date = datetime.utcfromtimestamp(timestamp / 1000).date() + date = datetime.datetime.utcfromtimestamp(timestamp / 1000).date() waste_type = collection["Abfuhrplan"]["GefaesstarifArt"]["Abfallart"][ "Name" ] icon = None - # Maybe append collection["Abfuhrplan"]["GefaesstarifArt"]["Volumen"]["VolumenWert"] to waste type for icon_type, tested_icon in ICON_MAP.items(): if icon_type.lower() in waste_type.lower(): icon = tested_icon - entries.append(Collection(date, waste_type, icon or "mdi:trash-can")) + if self.show_volume: + volume = int( + collection["Abfuhrplan"]["GefaesstarifArt"]["Volumen"][ + "VolumenWert" + ] + ) + waste_type = f"{waste_type} ({volume} l)" + + entries.add(CollectionEntry(date, waste_type, icon)) if len(entries) == 0: raise ValueError( "No collections found! Please verify that your configuration is correct." ) - return entries + return [entry.export() for entry in entries] def fetch_district_id(self, session: requests.Session) -> int: res = session.get( From c9e50180024ff25071b4a9b2265bc1d94d298388 Mon Sep 17 00:00:00 2001 From: Mirko Lenz Date: Fri, 30 Dec 2022 20:02:23 +0100 Subject: [PATCH 097/127] buergerportal_de: Add show_volume to documentation --- doc/source/buergerportal_de.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/doc/source/buergerportal_de.md b/doc/source/buergerportal_de.md index 26034709..340a0fd6 100644 --- a/doc/source/buergerportal_de.md +++ b/doc/source/buergerportal_de.md @@ -14,13 +14,14 @@ waste_collection_schedule: subdistrict: SUBDISTRICT street: STREET_NAME number: HOUSE_NUMBER + show_volume: SHOW_VOLUME ``` ## Supported Operators -- `cochem_zell`: - `alb_donau`: - `biedenkopf`: +- `cochem_zell`: ### Configuration Variables @@ -39,6 +40,9 @@ _(string|int) (required)_ **subdistrict**\ _(string) (optional) (default: null)_ +**show_volume**\ +_(boolean) (optional) (default: false)_ + ## Example ```yaml @@ -61,3 +65,11 @@ waste_collection_schedule: 4. Select your `number` (Hausnummer). All parameters are _case-sensitive_. + +## Notes on Container Volumes + +By default, this sources does not differentiate between different container sizes. +If your operator collects large containers (1000 l) on different dates than smaller ones (e.g., 120 l or 240 l), you may set `show_volume: true` in your configuration. +If you do, the volume will be added to the waste type. +For example, the collection `Bio` with a volume of 120 l would then be shown as `Bio (120 l)`. +With this additional information, you can adjust all waste collections to your needs by making use of a source's [`customize` option](../installation.md#configuring-sources). From 24220aff0587b980cec0cf7dd26c07bf5813695f Mon Sep 17 00:00:00 2001 From: Andre Basche Date: Fri, 30 Dec 2022 23:36:25 +0100 Subject: [PATCH 098/127] Improve meinawb_de, create docs --- README.md | 1 + .../source/meinawb_de.py | 92 ++++++++++++------- doc/source/meinawb_de.md | 46 ++++++++++ info.md | 2 +- 4 files changed, 107 insertions(+), 34 deletions(-) create mode 100644 doc/source/meinawb_de.md diff --git a/README.md b/README.md index ee15ca4f..9ae8eba6 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ Waste collection schedules in the following formats and countries are supported. - [Abfallwirtschaft Werra-Meißner-Kreis](/doc/source/zva_wmk_de.md) / zva-wmk.de - [Abfallwirtschaft Zollernalbkreis](/doc/source/abfall_zollernalbkreis_de.md) / abfallkalender-zak.de - [Abfallwirtschaftsbetrieb Esslingen](/doc/source/awb_es_de.md) / awb-es.de +- [Abfallwirtschaftsbetrieb Landkreis Ahrweiler](/doc/source/meinawb_de.md) / meinawb.de - [ART Trier](/doc/source/art_trier_de.md) / art-trier.de - [AWB Bad Kreuznach](/doc/source/awb_bad_kreuznach_de.md) / app.awb-bad-kreuznach.de - [AWB Köln](/doc/source/awbkoeln_de.md) / awbkoeln.de diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/meinawb_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/meinawb_de.py index 72041839..a35fdcb7 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/meinawb_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/meinawb_de.py @@ -1,4 +1,5 @@ import html +import logging import random import re import string @@ -7,58 +8,83 @@ from datetime import datetime import requests from waste_collection_schedule import Collection -TITLE = "meinawb.de" +_LOGGER = logging.getLogger(__name__) + +TITLE = "Abfallwirtschaftsbetrieb Landkreis Ahrweiler" +URL = "https://www.meinawb.de" DESCRIPTION = "Bin collection service from Kreis Ahrweiler/Germany" -URL = "https://extdienste01.koblenz.de/WasteManagementAhrweiler/WasteManagementServlet" +API_URL = "https://extdienste01.koblenz.de/WasteManagementAhrweiler/WasteManagementServlet" -city = "Bad Neuenahr-Ahrweiler" -street = "Hauptstrasse" -houseno = "91" -YEAR = "2023" +ICON_MAP = { + "Restabfall": "mdi:trash-can", + "Bioabfall": "mdi:leaf", + "Altpapier": "mdi:package-variant", + "Verpackungen": "mdi:recycle", + "Grünabfall / Weihnachtsbäume": "mdi:forest", +} +TYPES = { + "RM": "Restabfall", + "RG": "Restabfall", + "BM": "Bioabfall", + "PA": "Altpapier", + "GT": "Verpackungen", + "GS": "Grünabfall / Weihnachtsbäume", +} -ICONS = { - "RM": {"icon": "mdi:trash-can", "t": "Restabfall"}, - "RG2": {"icon": "mdi:trash-can", "t": "Restabfall Gewerbe / PLUS-Tonne"}, - "BM": {"icon": "mdi:leaf", "t": "Bioabfall"}, - "PA": {"icon": "mdi:package-variant", "t": "Altpapier"}, - "GT": {"icon": "mdi:recycle", "t": "Verpackungen"}, - "GS": {"icon": "mdi:forest", "t": "Grünabfall / Weihnachtsbäume"}, +TEST_CASES = { + "Oberzissen": {"city": "Oberzissen", "street": "Lindenstrasse", "house_number": "1"}, + "Niederzissen": {"city": "Niederzissen", "street": "Brohltalstrasse", "house_number": "189"}, + "Bad Neuenahr": {"city": "Bad Neuenahr-Ahrweiler", "street": "Hauptstrasse", "house_number": "91", + "address_suffix": "A"}, } class Source: - def __init__(self, city, street, house_number): + def __init__(self, city, street, house_number, address_suffix=""): self._city = city self._street = street self._house_number = house_number + self._address_suffix = address_suffix + + def __str__(self): + return f"{self._city} {self._street} {self._house_number} {self._address_suffix}" @staticmethod - def parse_data(data, boundary): + def _parse_data(data, boundary): result = "" for key, value in data.items(): - result += f'------WebKitFormBoundary{boundary}\r\nContent-Disposition: form-data; name="{key}"\r\n\r\n{value}\r\n' - result += f"------WebKitFormBoundary{boundary}--\r\n" + result += f'------{boundary}\r\nContent-Disposition: form-data; name="{key}"\r\n\r\n{value}\r\n' + result += f"------{boundary}--\r\n" return result.encode() @staticmethod - def parse_response_input(text): + def _parse_response_input(text): parsed = re.findall("[A-Z][a-z]. ([0-9.]{10})

', response.text) - return [Collection(datetime.strptime(date, "%d.%m.%Y").date(), **ICONS[bin_type]) for bin_type, date in dates] + "Hausnummerzusatz": self._address_suffix, "Zeitraum": html.unescape(calendar)}) + response = session.post(API_URL, headers=headers, data=self._parse_data(payload, boundary)) + if error := re.findall("informationItemsText_1\">([^<]+?)<", response.text): + _LOGGER.warning(f"{self} - {html.unescape(error[0])}") + return [] + return re.findall('

[A-Z][a-z]. ([0-9.]{10})

', response.text) + + def fetch(self): + session = requests.Session() + response = session.get(f"{API_URL}?SubmitAction=wasteDisposalServices&InFrameMode=true") + payload = self._parse_response_input(response.text) + calendars = re.findall('NAME="Zeitraum" VALUE=\"([^\"]+?)\"', response.text) + dates = [date for calendar in calendars for date in self._get_dates(session, payload, calendar)] + entries = [] + for bin_type, date in dates: + name = TYPES[next(x for x in list(TYPES) if x in bin_type)] + entries.append(Collection(datetime.strptime(date, "%d.%m.%Y").date(), name, ICON_MAP[name])) + return entries diff --git a/doc/source/meinawb_de.md b/doc/source/meinawb_de.md new file mode 100644 index 00000000..12501305 --- /dev/null +++ b/doc/source/meinawb_de.md @@ -0,0 +1,46 @@ +# Abfallwirtschaftsbetrieb Landkreis Ahrweiler (AWB) + +Support for schedules provided by [Abfallwirtschaftsbetrieb Landkreis Ahrweiler](https://www.meinawb.de/) located in Rhineland Palatinate, Germany. + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: meinawb_de + args: + city: CITY + street: STREET + house_number: HNR + address_suffix: HNR_SUFFIX +``` + +### Configuration Variables + +**city** +*(string) (required)* + +**street** +*(string) (required)* + +**house_number** +*(integer) (required)* + +**address_suffix** +*(string) (optional) (default: "")* + +## Example + +```yaml +waste_collection_schedule: + sources: + - name: meinawb_de + args: + city: Oberzissen + street: Ackerstrasse + address_suffix: 1 +``` + +## How to get the source arguments + +The arguments are your address. The input validation is a bit petty, so make sure you write it exactly like in the [web form](https://www.meinawb.de/abfuhrtermine). For troubleshooting, have a look in the home assistant logs. diff --git a/info.md b/info.md index 81d438fa..f756803d 100644 --- a/info.md +++ b/info.md @@ -20,7 +20,7 @@ Waste collection schedules from service provider web sites are updated daily, de | Austria | Burgenländischer Müllverband, infeo, Stadtservice Korneuburg, Umweltprofis, WSZ Moosburg | | Belgium | Hygea, Recycle! | | Canada | City of Toronto | -| Germany | Abfall Stuttgart, Abfall.IO / AbfallPlus, Abfallkalender Würzburg, AbfallNavi (RegioIT.de), Abfalltermine Forchheim, Abfallwirtschaft Alb-Donau-Kreis, Abfallwirtschaft Landkreis Harburg, Abfallwirtschaft Landkreis Wolfenbüttel, Abfallwirtschaft Neckar-Odenwald-Kreis, Abfallwirtschaft Rendsburg, Abfallwirtschaft Südholstein, Abfallwirtschaft Werra-Meißner-Kreis, Abfallwirtschaft Zollernalbkreis, Abfallwirtschaftsbetrieb Esslingen, ART Trier, AWB Bad Kreuznach, AWB Köln, AWB Oldenburg, AWIDO Online, Berlin Recycling, Berliner Stadtreinigungsbetriebe, Bielefeld, Bogenschütz Entsorgung, Bürgerportal, C-Trace, EGN Abfallkalender, Jumomind, KAEV Niederlausitz, Kreiswirtschaftsbetriebe Goslar, KV Cochem-Zell, KWU Entsorgung Landkreis Oder-Spree, Landkreis Erlangen-Höchstadt, Landkreis Nordwestmecklenburg, Landkreis Rhön Grabfeld, Landkreis Schwäbisch Hall, Landkreis Wittmund, MZV Bidenkopf, Müllmax, Neunkirchen Siegerland, RegioEntsorgung Städteregion Aachen, Rhein-Hunsrück Entsorgung (RHE), Sector 27 - Datteln, Marl, Oer-Erkenschwick, Stadt Willich, Stadtreinigung Dresden, Stadtreinigung Hamburg, Stadtreinigung Leipzig, StadtService Brühl, Städteservice Raunheim Rüsselsheim, Südbrandenburgischer Abfallzweckverband, Wermelskirchen, Wolfsburger Abfallwirtschaft und Straßenreinigung | +| Germany | Abfall Stuttgart, Abfall.IO / AbfallPlus, Abfallkalender Würzburg, AbfallNavi (RegioIT.de), Abfalltermine Forchheim, Abfallwirtschaft Alb-Donau-Kreis, Abfallwirtschaft Landkreis Harburg, Abfallwirtschaft Landkreis Wolfenbüttel, Abfallwirtschaft Neckar-Odenwald-Kreis, Abfallwirtschaft Rendsburg, Abfallwirtschaft Südholstein, Abfallwirtschaft Werra-Meißner-Kreis, Abfallwirtschaft Zollernalbkreis, Abfallwirtschaftsbetrieb Esslingen, Abfallwirtschaftsbetrieb Landkreis Ahrweiler, ART Trier, AWB Bad Kreuznach, AWB Köln, AWB Oldenburg, AWIDO Online, Berlin Recycling, Berliner Stadtreinigungsbetriebe, Bielefeld, Bogenschütz Entsorgung, Bürgerportal, C-Trace, EGN Abfallkalender, Jumomind, KAEV Niederlausitz, Kreiswirtschaftsbetriebe Goslar, KV Cochem-Zell, KWU Entsorgung Landkreis Oder-Spree, Landkreis Erlangen-Höchstadt, Landkreis Nordwestmecklenburg, Landkreis Rhön Grabfeld, Landkreis Schwäbisch Hall, Landkreis Wittmund, MZV Bidenkopf, Müllmax, Neunkirchen Siegerland, RegioEntsorgung Städteregion Aachen, Rhein-Hunsrück Entsorgung (RHE), Sector 27 - Datteln, Marl, Oer-Erkenschwick, Stadt Willich, Stadtreinigung Dresden, Stadtreinigung Hamburg, Stadtreinigung Leipzig, StadtService Brühl, Städteservice Raunheim Rüsselsheim, Südbrandenburgischer Abfallzweckverband, Wermelskirchen, Wolfsburger Abfallwirtschaft und Straßenreinigung | | Lithuania | Kauno švara | | Netherlands | ACV Group, Alpen an den Rijn, Area Afval, Avalex, Avri, Bar Afvalbeheer, Cyclus NV, Dar, Den Haag, GAD, Gemeente Almere, Gemeente Berkelland, Gemeente Cranendonck, Gemeente Hellendoorn, Gemeente Lingewaard, Gemeente Meppel, Gemeente Middelburg + Vlissingen, Gemeente Peel en Maas, Gemeente Schouwen-Duiveland, Gemeente Sudwest-Fryslan, Gemeente Venray, Gemeente Voorschoten, Gemeente Wallre, Gemeente Westland, HVC Groep, Meerlanden, Mijn Blink, PreZero, Purmerend, RAD BV, Reinigingsbedrijf Midden Nederland, Reinis, Spaarne Landen, Stadswerk 072, Twente Milieu, Waardlanden, Ximmio, ZRD | | New Zealand | Auckland Council, Christchurch City Council, Gore, Invercargill & Southland, Horowhenua District Council, Waipa District Council, Wellington City Council | From 6510d8587a06564b813d028bee5411a4059f6034 Mon Sep 17 00:00:00 2001 From: Andre Basche Date: Fri, 30 Dec 2022 23:38:38 +0100 Subject: [PATCH 099/127] Fix found typos --- doc/contributing.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/contributing.md b/doc/contributing.md index 8c762170..e7e1bed2 100644 --- a/doc/contributing.md +++ b/doc/contributing.md @@ -70,8 +70,8 @@ TITLE = "My Council" # Title will show up in README.md and info.md DESCRIPTION = "Source script for abc.com" # Describe your source URL = "https://abc.com" # Insert url to service homepage. URL will show up in README.md and info.md TEST_CASES = { # Insert arguments for test cases to be used by test_sources.py script - "TestName1": {"arg1": 100, "arg2": "street"} - "TestName2": {"arg1": 200, "arg2": "road"} + "TestName1": {"arg1": 100, "arg2": "street"}, + "TestName2": {"arg1": 200, "arg2": "road"}, "TestName3": {"arg1": 300, "arg2": "lane"} } @@ -162,7 +162,7 @@ The `info.md` is rendered in the HACS user interface within Home Assistant and g The links in both files can be updated automatically using the script `update_docu_links.py` in the top-level directory: ```bash -./make_docu_links.py +./update_docu_links.py ``` The script iterates through all source files and extracts some meta information like title and url. It is therefore important to set the attributes in the source file correctly. By default, the country classification is derived from the file name. If this doesn't match, the country code can be overwritten with the attribute `COUNTRY`. From a82c468a2e88f2c8346f82e5e27a81b248fce956 Mon Sep 17 00:00:00 2001 From: Andre Basche Date: Sat, 31 Dec 2022 00:18:34 +0100 Subject: [PATCH 100/127] Differentiate between normal and extra can --- .../waste_collection_schedule/source/meinawb_de.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/meinawb_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/meinawb_de.py index a35fdcb7..2e26179f 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/meinawb_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/meinawb_de.py @@ -17,6 +17,7 @@ API_URL = "https://extdienste01.koblenz.de/WasteManagementAhrweiler/WasteManagem ICON_MAP = { "Restabfall": "mdi:trash-can", + "Restabfall Plus": "mdi:trash-can", "Bioabfall": "mdi:leaf", "Altpapier": "mdi:package-variant", "Verpackungen": "mdi:recycle", @@ -24,7 +25,7 @@ ICON_MAP = { } TYPES = { "RM": "Restabfall", - "RG": "Restabfall", + "RG": "Restabfall Plus", "BM": "Bioabfall", "PA": "Altpapier", "GT": "Verpackungen", @@ -35,7 +36,7 @@ TEST_CASES = { "Oberzissen": {"city": "Oberzissen", "street": "Lindenstrasse", "house_number": "1"}, "Niederzissen": {"city": "Niederzissen", "street": "Brohltalstrasse", "house_number": "189"}, "Bad Neuenahr": {"city": "Bad Neuenahr-Ahrweiler", "street": "Hauptstrasse", "house_number": "91", - "address_suffix": "A"}, + "address_suffix": "A"}, } From f03266c3a6d17e2a3d0243ebc815e2764050ae71 Mon Sep 17 00:00:00 2001 From: TheBlackMini Date: Sun, 1 Jan 2023 22:57:27 +1000 Subject: [PATCH 101/127] New provider for Gold Coast QLD Australia --- .../source/goldcoast_qld_gov_au.py | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 custom_components/waste_collection_schedule/waste_collection_schedule/source/goldcoast_qld_gov_au.py diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/goldcoast_qld_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/goldcoast_qld_gov_au.py new file mode 100644 index 00000000..a64c151c --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/goldcoast_qld_gov_au.py @@ -0,0 +1,117 @@ +# import requests module +import requests + +# Import json module to parse json output +import json + +# Import Collection module to define for scheduler +from waste_collection_schedule import Collection # type: ignore[attr-defined] + +# Import datetime to manipulate the time output from the site +from datetime import date, datetime + +# Import BeautifulSoup to parse broken HTML from the site +from bs4 import BeautifulSoup + +from urllib.parse import quote_plus + +TITLE = "Gold Coast City Council" +DESCRIPTION = "Source for Gold Coast Council rubbish collection." +URL = "https://www.goldcoast.qld.gov.au" +TEST_CASES = { + "MovieWorx": { + "suburb": "Helensvale", + "street_name": "Millaroo Dr", + "street_number": "50", + }, + "The Henchman": { + "suburb": "Miami", + "street_name": "Henchman Ave", + "street_number": "6/8", + }, + "Pie Pie": { + "suburb": "Burleigh Heads", + "street_name": "Gold Coast Hwy", + "street_number": "1887", + }, +} + +HEADERS = {"user-agent": "Mozilla/5.0"} + +ICON_MAP = { # Dict of waste types and suitable mdi icons + "DOMESTIC": "mdi:trash-can", + "RECYCLE": "mdi:recycle", + "ORGANIC": "mdi:leaf", +} + +class Source: + def __init__(self, suburb, street_name, street_number): + self.suburb = suburb + self.street_name = street_name + self.street_number = street_number + + def fetch(self): + + today = date.today() + + # Construct the expected URL address + siteAddress = quote_plus(f"{self.street_number} {self.street_name} {self.suburb}") + # Making a get request + response = requests.get(f'https://www.goldcoast.qld.gov.au/api/v1/myarea/searchfuzzy?keywords={siteAddress}&maxresults=1') + data = json.loads(response.text) + + # Sort through the json to get the Geocoding GUID for the address + for item in data["Items"]: + siteId = item["Id"] + break + + # Query the API to get the next pick up dates + response = requests.get(f'Https://www.goldcoast.qld.gov.au/ocapi/Public/myarea/wasteservices?geolocationid={siteId}&ocsvclang=en-AU') + data = json.loads(response.text) + + # The above only gives us partial HTML code, this fixes that so we can easily search for the dates + html = BeautifulSoup(data["responseContent"], features="lxml") + + # Search through the returned HTML for the dates + waste = html.body.find('div', attrs={'class':'general-waste'}).find('div', attrs={'class':'next-service'}).text.strip() + recycling = html.body.find('div', attrs={'class':'recycling'}).find('div', attrs={'class':'next-service'}).text.strip() + green = html.body.find('div', attrs={'class':'green-waste'}).find('div', attrs={'class':'next-service'}).text.strip() + + # Convert the dates to what is expected + waste_collectiondate = date.fromisoformat(datetime.strptime(waste, '%a %d/%m/%Y').strftime('%Y-%m-%d')) + recycling_collectiondate = date.fromisoformat(datetime.strptime(recycling, '%a %d/%m/%Y').strftime('%Y-%m-%d')) + green_collectiondate = date.fromisoformat(datetime.strptime(green, '%a %d/%m/%Y').strftime('%Y-%m-%d')) + + entries = [] + + # Every collection day includes rubbish + entries.append( + Collection( + date = waste_collectiondate, # Collection date + t = "Rubbish", + icon=ICON_MAP.get("DOMESTIC") + ) + ) + + # Check to see if it's recycling week + if (recycling_collectiondate - today).days >= 0: + entries.append( + Collection( + date=recycling_collectiondate, + t="Recycling", + icon=ICON_MAP.get("RECYCLE") + ) + ) + + # Check to see if it's green waste week + if (green_collectiondate - today).days >= 0: + entries.append( + Collection( + date=green_collectiondate, + t="Garden", + icon=ICON_MAP.get("ORGANIC") + ) + ) + + # Return our result + return entries From c7802abefbb07fbff4323dc011b57cd89b5b6316 Mon Sep 17 00:00:00 2001 From: Adam Monahan Date: Sun, 1 Jan 2023 22:59:50 +1000 Subject: [PATCH 102/127] New provider for Gold Coast QLD Australia --- README.md | 1 + doc/source/goldcoast_qld_gov_au.md | 42 ++++++++++++++++++++++++++++++ info.md | 2 +- 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 doc/source/goldcoast_qld_gov_au.md diff --git a/README.md b/README.md index ee15ca4f..07e43fd4 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ Waste collection schedules in the following formats and countries are supported. - [Brisbane City Council](/doc/source/brisbane_qld_gov_au.md) / brisbane.qld.gov.au - [Campbelltown City Council](/doc/source/campbelltown_nsw_gov_au.md) / campbelltown.nsw.gov.au - [City of Canada Bay Council](/doc/source/canadabay_nsw_gov_au.md) / canadabay.nsw.gov.au +- [City of Gold Coast Council](/doc/source/goldcoast_qld_gov_au.md) / goldcoast.qld.gov.au - [Inner West Council (NSW)](/doc/source/innerwest_nsw_gov_au.md) / innerwest.nsw.gov.au - [Ipswich City Council](/doc/source/ipswich_qld_gov_au.md) / ipswich.qld.gov.au - [Ku-ring-gai Council](/doc/source/kuringgai_nsw_gov_au.md) / krg.nsw.gov.au diff --git a/doc/source/goldcoast_qld_gov_au.md b/doc/source/goldcoast_qld_gov_au.md new file mode 100644 index 00000000..cfc6f81d --- /dev/null +++ b/doc/source/goldcoast_qld_gov_au.md @@ -0,0 +1,42 @@ +# Gold Coast City Council + +Support for schedules provided by [Gold Coast City Council](https://www.goldcoast.qld.gov.au/Services/Waste-recycling/Find-my-bin-day). + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: goldcoast_qld_gov_au + args: + suburb: SUBURB + street_name: STREET_NAME + street_number: STREET_NUMBER +``` + +### Configuration Variables + +**suburb** +*(string) (required)* + +**street_name** +*(string) (required)* + +**street_number** +*(string) (required)* + +## Example + +```yaml +waste_collection_schedule: + sources: + - name: goldcoast_qld_gov_au + args: + suburb: Miami + street_name: Henchman Ave + street_number: 6/8 +``` + +## How to get the source arguments + +This is the address that the services are being picked up from (eg. your house!) diff --git a/info.md b/info.md index 81d438fa..bac37245 100644 --- a/info.md +++ b/info.md @@ -16,7 +16,7 @@ Waste collection schedules from service provider web sites are updated daily, de |--|--| | Generic | ICS / iCal files | | Static | User-defined dates or repeating date patterns | -| Australia | Banyule City Council, Belmont City Council, Brisbane City Council, Campbelltown City Council, City of Canada Bay Council, Inner West Council (NSW), Ipswich City Council, Ku-ring-gai Council, Macedon Ranges Shire Council, Maroondah City Council, Melton City Council, Nillumbik Shire Council, North Adelaide Waste Management Authority, RecycleSmart, Stonnington City Council, The Hills Shire Council, Sydney, Wyndham City Council, Melbourne | +| Australia | Banyule City Council, Belmont City Council, Brisbane City Council, Campbelltown City Council, City of Canada Bay Council, City of Gold Coast Council, Inner West Council (NSW), Ipswich City Council, Ku-ring-gai Council, Macedon Ranges Shire Council, Maroondah City Council, Melton City Council, Nillumbik Shire Council, North Adelaide Waste Management Authority, RecycleSmart, Stonnington City Council, The Hills Shire Council, Sydney, Wyndham City Council, Melbourne | | Austria | Burgenländischer Müllverband, infeo, Stadtservice Korneuburg, Umweltprofis, WSZ Moosburg | | Belgium | Hygea, Recycle! | | Canada | City of Toronto | From 679cda7b6cc081477cfc9b28eacb538d790f03b2 Mon Sep 17 00:00:00 2001 From: Garry Mitchell Date: Sun, 1 Jan 2023 20:05:03 +0000 Subject: [PATCH 103/127] Add source for Ashfield District Council, UK --- README.md | 1 + .../source/ashfield_gov_uk.py | 87 +++++++++++++++++++ doc/source/ashfield_gov_uk.md | 63 ++++++++++++++ info.md | 2 +- 4 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 custom_components/waste_collection_schedule/waste_collection_schedule/source/ashfield_gov_uk.py create mode 100644 doc/source/ashfield_gov_uk.md diff --git a/README.md b/README.md index ee15ca4f..0d2b49aa 100644 --- a/README.md +++ b/README.md @@ -254,6 +254,7 @@ Waste collection schedules in the following formats and countries are supported.
United Kingdom +- [Ashfield District Council](/doc/source/ashfield_gov_uk.md) / ashfield.gov.uk - [Bracknell Forest Council](/doc/source/bracknell_forest_gov_uk.md) / selfservice.mybfc.bracknell-forest.gov.uk - [Bradford Metropolitan District Council](/doc/source/bradford_gov_uk.md) / bradford.gov.uk - [Braintree District Council](/doc/source/braintree_gov_uk.md) / braintree.gov.uk diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ashfield_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ashfield_gov_uk.py new file mode 100644 index 00000000..399656ec --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ashfield_gov_uk.py @@ -0,0 +1,87 @@ +import json +import datetime + +import requests +from waste_collection_schedule import Collection + +TITLE = "Ashfield.gov.uk" +DESCRIPTION = "Source for ashfield.gov.uk, Ashfield District Council, UK" +URL = "https://www.ashfield.gov.uk" +TEST_CASES = { + "11 Maun View Gardens, Sutton-in-Ashfield": {"uprn": 10001336299}, + "4A Station Street, Kirkby-in-Ashfield": {"post_code": "NG177AR", "number": "4A"}, + "Ashfield District Council": {"post_code": "NG17 8DA", "name": "Ashfield District Council"} +} + +API_URLS = { + "address_search": "https://www.ashfield.gov.uk/api/powersuite/getaddresses/{postcode}", + "collection": "https://www.ashfield.gov.uk/api/powersuite/GetCollectionByUprnAndDate/{uprn}", +} + +ICON_MAP = { + "Residual Waste Collection Service": "mdi:trash-can", + "Domestic Recycling Collection Service": "mdi:recycle", + "Domestic Glass Collection Service": "mdi:glass-fragile", + "Garden Waste Collection Service": "mdi:leaf" +} + +NAMES = { + "Residual Waste Collection Service": "Red (rubbish)", + "Domestic Recycling Collection Service": "Green (recycling)", + "Domestic Glass Collection Service": "Blue (glass)", + "Garden Waste Collection Service": "Brown (garden)" +} + + +class Source: + def __init__(self, post_code=None, number=None, name=None, uprn=None): + self._post_code = post_code + self._number = number + self._name = name + self._uprn = uprn + + def fetch(self): + if not self._uprn: + # look up the UPRN for the address + q = str(API_URLS["address_search"]).format( + postcode=self._post_code) + r = requests.get(q) + r.raise_for_status() + addresses = r.json()["data"] + + if self._name: + self._uprn = [ + int(x["AccountSiteUprn"]) for x in addresses if x["SiteAddressName"].capitalize() == self._name.capitalize() + ][0] + elif self._number: + self._uprn = [ + int(x["AccountSiteUprn"]) for x in addresses if x["SiteAddressNumber"] == self._number + ][0] + + if not self._uprn: + raise Exception( + f"Could not find address {self._post_code} {self._number}{self._name}") + + q = str(API_URLS["collection"]).format( + uprn=self._uprn + ) + + r = requests.get(q) + r.raise_for_status() + + collections = r.json()["data"] + entries = [] + + if collections: + for collection in collections: + entries.append( + Collection( + date=datetime.datetime.strptime( + collection["Date"], "%d/%m/%Y %H:%M:%S" + ).date(), + t=NAMES.get(collection["Service"])['name'], + icon=ICON_MAP.get(collection["Service"])['icon'] + ) + ) + + return entries diff --git a/doc/source/ashfield_gov_uk.md b/doc/source/ashfield_gov_uk.md new file mode 100644 index 00000000..4852e217 --- /dev/null +++ b/doc/source/ashfield_gov_uk.md @@ -0,0 +1,63 @@ +# Ashfield District Council + +Support for schedules provided by [Ashfield District Council](https://www.ashfield.gov.uk/), serving Ashfield district in Nottinghshire, UK. + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: ashfield_gov_uk + args: + uprn: UNIQUE_PROPERTY_REFERENCE_NUMBER + post_code: POST_CODE + name: HOUSE_NAME + number: HOUSE_NUMBER +``` + +### Configuration Variables + +### Configuration Variables + +**uprn**
+*(string) (optional)* + +This is required if you do not supply any other options. (Using this removes the need to do an address look up web request) + +**name**
+*(string) (optional)* + +This is required if you supply a Postcode and do not have a house number. + +**number**
+*(string) (optional)* + +This is required if you supply a Postcode and have a house number. + +**post_code**
+*(string) (optional)* + +This is required if you do not supply a UPRN. Single space between 1st and 2nd part of postcode is optional. + +#### How to find your `UPRN` +An easy way to discover your Unique Property Reference Number (UPRN) is by going to https://www.findmyaddress.co.uk/ and entering in your address details. +Otherwise you can inspect the web requests the Ashfield District Council website makes when entering in your postcode and then selecting your address. + +## Example using UPRN +```yaml +waste_collection_schedule: + sources: + - name: ashfield_gov_uk + args: + uprn: 100032105121 +``` + +## Example using Address lookup +```yaml +waste_collection_schedule: + sources: + - name: ashfield_gov_uk + args: + post_code: "NG17 8DA" + name: "Ashfield District Council" +``` \ No newline at end of file diff --git a/info.md b/info.md index 81d438fa..a4a80aa4 100644 --- a/info.md +++ b/info.md @@ -28,7 +28,7 @@ Waste collection schedules from service provider web sites are updated daily, de | Poland | Ecoharmonogram, Warsaw | | Sweden | Lerum Vatten och Avlopp, Ronneby Miljöteknik, SRV Återvinning, SSAM, Sysav Sophämntning, VA Syd Sophämntning | | Switzerland | A-Region, Andwil, Appenzell, Berg, Bühler, Eggersriet, Gais, Gaiserwald, Goldach, Grub, Heiden, Herisau, Horn, Hundwil, Häggenschwil, Lindau, Lutzenberg, Muolen, Mörschwil, Rehetobel, Rorschach, Rorschacherberg, Schwellbrunn, Schönengrund, Speicher, Stein, Steinach, Teufen, Thal, Trogen, Tübach, Untereggen, Urnäsch, Wald, Waldkirch, Waldstatt, Wittenbach, Wolfhalden | -| United Kingdom | Bracknell Forest Council, Bradford Metropolitan District Council, Braintree District Council, Breckland Council, Cambridge City Council, Canterbury City Council, Cheshire East Council, Chesterfield Borough Council, City of York Council, Colchester Borough Council, Cornwall Council, Derby City Council, Eastbourne Borough Council, Elmbridge Borough Council, Environment First, FCC Environment, Guildford Borough Council, Harborough District Council, Huntingdonshire District Council, Lewes District Council, London Borough of Lewisham, Manchester City Council, Middlesbrough Council, Newcastle City Council, North Somerset Council, Nottingham City Council, Peterborough City Council, Richmondshire District Council, Rushmoor Borough Council, Sheffield City Council, South Cambridgeshire District Council, South Hams District Council, South Norfolk and Broadland Council, Stevenage Borough Council, Tewkesbury Borough Council, The Royal Borough of Kingston Council, Walsall Council, West Berkshire Council, West Devon Borough Council, Wiltshire Council | +| United Kingdom | Ashfield District Council, Bracknell Forest Council, Bradford Metropolitan District Council, Braintree District Council, Breckland Council, Cambridge City Council, Canterbury City Council, Cheshire East Council, Chesterfield Borough Council, City of York Council, Colchester Borough Council, Cornwall Council, Derby City Council, Eastbourne Borough Council, Elmbridge Borough Council, Environment First, FCC Environment, Guildford Borough Council, Harborough District Council, Huntingdonshire District Council, Lewes District Council, London Borough of Lewisham, Manchester City Council, Middlesbrough Council, Newcastle City Council, North Somerset Council, Nottingham City Council, Peterborough City Council, Richmondshire District Council, Rushmoor Borough Council, Sheffield City Council, South Cambridgeshire District Council, South Hams District Council, South Norfolk and Broadland Council, Stevenage Borough Council, Tewkesbury Borough Council, The Royal Borough of Kingston Council, Walsall Council, West Berkshire Council, West Devon Borough Council, Wiltshire Council | | United States of America | City of Pittsburgh, Republic Services, Seattle Public Utilities | From 80056de711c29d22b3c7ad9034de7f2769c31949 Mon Sep 17 00:00:00 2001 From: Garry Mitchell Date: Sun, 1 Jan 2023 21:02:36 +0000 Subject: [PATCH 104/127] Fix array lookup error --- .../waste_collection_schedule/source/ashfield_gov_uk.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ashfield_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ashfield_gov_uk.py index 399656ec..9fb5f9aa 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ashfield_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ashfield_gov_uk.py @@ -79,8 +79,8 @@ class Source: date=datetime.datetime.strptime( collection["Date"], "%d/%m/%Y %H:%M:%S" ).date(), - t=NAMES.get(collection["Service"])['name'], - icon=ICON_MAP.get(collection["Service"])['icon'] + t=NAMES.get(collection["Service"]), + icon=ICON_MAP.get(collection["Service"]) ) ) From 6a2169c9674a009f4f6a6529efbc931a7ee14bae Mon Sep 17 00:00:00 2001 From: Andre Basche Date: Mon, 2 Jan 2023 02:15:59 +0100 Subject: [PATCH 105/127] Fix new years bug --- .../source/meinawb_de.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/meinawb_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/meinawb_de.py index 2e26179f..b1f5d326 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/meinawb_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/meinawb_de.py @@ -63,15 +63,19 @@ class Source: parsed = re.findall("([^<]+?)<", response.text): _LOGGER.warning(f"{self} - {html.unescape(error[0])}") @@ -82,8 +86,10 @@ class Source: session = requests.Session() response = session.get(f"{API_URL}?SubmitAction=wasteDisposalServices&InFrameMode=true") payload = self._parse_response_input(response.text) - calendars = re.findall('NAME="Zeitraum" VALUE=\"([^\"]+?)\"', response.text) - dates = [date for calendar in calendars for date in self._get_dates(session, payload, calendar)] + if calendars := re.findall('NAME="Zeitraum" VALUE=\"([^\"]+?)\"', response.text): + dates = [date for calendar in calendars for date in self._get_dates(session, payload, calendar)] + else: + dates = self._get_dates(session, payload) entries = [] for bin_type, date in dates: name = TYPES[next(x for x in list(TYPES) if x in bin_type)] From caeafb135bb9126427a8a96abda92c294a5c5f4b Mon Sep 17 00:00:00 2001 From: Andre Basche Date: Mon, 2 Jan 2023 02:42:14 +0100 Subject: [PATCH 106/127] Refactor --- .../source/meinawb_de.py | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/meinawb_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/meinawb_de.py index b1f5d326..3353cc5e 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/meinawb_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/meinawb_de.py @@ -46,6 +46,7 @@ class Source: self._street = street self._house_number = house_number self._address_suffix = address_suffix + self._boundary = "WebKitFormBoundary" + "".join(random.sample(string.ascii_letters + string.digits, 16)) def __str__(self): return f"{self._city} {self._street} {self._house_number} {self._address_suffix}" @@ -63,33 +64,38 @@ class Source: parsed = re.findall("([^<]+?)<", response.text): + def _address(self): + return {"Ort": self._city, "Strasse": self._street, "Hausnummer": self._house_number, + "Hausnummerzusatz": self._address_suffix} + + def _headers(self): + return {'Content-Type': f'multipart/form-data; boundary=----{self._boundary}'} + + def _payload(self, last_request, action="", period="", **kwargs): + payload = self._parse_response_input(last_request) + payload.update({"SubmitAction": action, **kwargs}) + if period: + payload.update({"Zeitraum": html.unescape(period)}) + return self._parse_data(payload, self._boundary) + + def _get_dates(self, session, init_request, calendar=""): + kwargs = {"Ort": self._city, "Strasse": ""} + payload = self._payload(init_request, action="CITYCHANGED", period=calendar, **kwargs) + city_response = session.post(API_URL, headers=self._headers(), data=payload) + payload = self._payload(city_response.text, action="forward", period=calendar, **self._address()) + final_response = session.post(API_URL, headers=self._headers(), data=payload) + if error := re.findall("informationItemsText_1\">([^<]+?)<", final_response.text): _LOGGER.warning(f"{self} - {html.unescape(error[0])}") return [] - return re.findall('

[A-Z][a-z]. ([0-9.]{10})

', response.text) + return re.findall('

[A-Z][a-z]. ([0-9.]{10})

', final_response.text) def fetch(self): session = requests.Session() - response = session.get(f"{API_URL}?SubmitAction=wasteDisposalServices&InFrameMode=true") - payload = self._parse_response_input(response.text) - if calendars := re.findall('NAME="Zeitraum" VALUE=\"([^\"]+?)\"', response.text): - dates = [date for calendar in calendars for date in self._get_dates(session, payload, calendar)] + init_request = session.get(f"{API_URL}?SubmitAction=wasteDisposalServices&InFrameMode=true").text + if calendars := re.findall('NAME="Zeitraum" VALUE=\"([^\"]+?)\"', init_request): + dates = [date for calendar in calendars for date in self._get_dates(session, init_request, calendar)] else: - dates = self._get_dates(session, payload) + dates = self._get_dates(session, init_request) entries = [] for bin_type, date in dates: name = TYPES[next(x for x in list(TYPES) if x in bin_type)] From fdaf83e44b01d611e08c603012406957d7c5297e Mon Sep 17 00:00:00 2001 From: Adam Monahan Date: Mon, 2 Jan 2023 15:33:41 +1000 Subject: [PATCH 107/127] Updated to use melton_vic_gov_au as example code. As per PR #504 --- .../source/goldcoast_qld_gov_au.py | 137 +++++++----------- doc/source/goldcoast_qld_gov_au.md | 18 +-- 2 files changed, 53 insertions(+), 102 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/goldcoast_qld_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/goldcoast_qld_gov_au.py index a64c151c..c034877f 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/goldcoast_qld_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/goldcoast_qld_gov_au.py @@ -1,117 +1,78 @@ -# import requests module +import logging +import re +from datetime import datetime + import requests - -# Import json module to parse json output -import json - -# Import Collection module to define for scheduler -from waste_collection_schedule import Collection # type: ignore[attr-defined] - -# Import datetime to manipulate the time output from the site -from datetime import date, datetime - -# Import BeautifulSoup to parse broken HTML from the site from bs4 import BeautifulSoup - -from urllib.parse import quote_plus +from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Gold Coast City Council" DESCRIPTION = "Source for Gold Coast Council rubbish collection." URL = "https://www.goldcoast.qld.gov.au" TEST_CASES = { - "MovieWorx": { - "suburb": "Helensvale", - "street_name": "Millaroo Dr", - "street_number": "50", - }, - "The Henchman": { - "suburb": "Miami", - "street_name": "Henchman Ave", - "street_number": "6/8", - }, - "Pie Pie": { - "suburb": "Burleigh Heads", - "street_name": "Gold Coast Hwy", - "street_number": "1887", - }, + "MovieWorx": { "street_address": "50 Millaroo Dr Helensvale" }, + "The Henchman": { "street_address": "6/8 Henchman Ave Miami" }, + "Pie Pie": { "street_address": "1887 Gold Coast Hwy Burleigh Heads" } } -HEADERS = {"user-agent": "Mozilla/5.0"} +_LOGGER = logging.getLogger(__name__) ICON_MAP = { # Dict of waste types and suitable mdi icons - "DOMESTIC": "mdi:trash-can", - "RECYCLE": "mdi:recycle", - "ORGANIC": "mdi:leaf", + "General waste": "mdi:trash-can", + "Recycling": "mdi:recycle", + "Green organics": "mdi:leaf", } class Source: def __init__(self, suburb, street_name, street_number): - self.suburb = suburb - self.street_name = street_name - self.street_number = street_number + self._street_address = street_address def fetch(self): + session = requests.Session() - today = date.today() - - # Construct the expected URL address - siteAddress = quote_plus(f"{self.street_number} {self.street_name} {self.suburb}") # Making a get request - response = requests.get(f'https://www.goldcoast.qld.gov.au/api/v1/myarea/searchfuzzy?keywords={siteAddress}&maxresults=1') - data = json.loads(response.text) + response = session.get( + "https://www.goldcoast.qld.gov.au/api/v1/myarea/searchfuzzy?maxresults=1", + params={"keywords": self._street_address}, + ) + response.raise_for_status() + addressSearchApiResults = response.json() + if ( + addressSearchApiResults["Items"] is None + or len(addressSearchApiResults["Items"]) < 1 + ): + raise Exception( + f"Address search for '{self._street_address}' returned no results. Check your address on https://www.goldcoast.qld.gov.au/Services/Waste-recycling/Find-my-bin-day" + ) - # Sort through the json to get the Geocoding GUID for the address - for item in data["Items"]: - siteId = item["Id"] - break + addressSearchTopHit = addressSearchApiResults["Items"][0] + _LOGGER.debug("Address search top hit: %s", addressSearchTopHit) - # Query the API to get the next pick up dates - response = requests.get(f'Https://www.goldcoast.qld.gov.au/ocapi/Public/myarea/wasteservices?geolocationid={siteId}&ocsvclang=en-AU') - data = json.loads(response.text) + geolocationid = addressSearchTopHit["Id"] + _LOGGER.debug("Geolocationid: %s", geolocationid) - # The above only gives us partial HTML code, this fixes that so we can easily search for the dates - html = BeautifulSoup(data["responseContent"], features="lxml") + response = session.get( + "Https://www.goldcoast.qld.gov.au/ocapi/Public/myarea/wasteservices?ocsvclang=en-AU", + params={"geolocationid": geolocationid}, + ) + response.raise_for_status() - # Search through the returned HTML for the dates - waste = html.body.find('div', attrs={'class':'general-waste'}).find('div', attrs={'class':'next-service'}).text.strip() - recycling = html.body.find('div', attrs={'class':'recycling'}).find('div', attrs={'class':'next-service'}).text.strip() - green = html.body.find('div', attrs={'class':'green-waste'}).find('div', attrs={'class':'next-service'}).text.strip() + wasteApiResult = response.json() + _LOGGER.debug("Waste API result: %s", wasteApiResult) - # Convert the dates to what is expected - waste_collectiondate = date.fromisoformat(datetime.strptime(waste, '%a %d/%m/%Y').strftime('%Y-%m-%d')) - recycling_collectiondate = date.fromisoformat(datetime.strptime(recycling, '%a %d/%m/%Y').strftime('%Y-%m-%d')) - green_collectiondate = date.fromisoformat(datetime.strptime(green, '%a %d/%m/%Y').strftime('%Y-%m-%d')) + soup = BeautifulSoup(wasteApiResult["responseContent"], "html.parser") entries = [] - - # Every collection day includes rubbish - entries.append( - Collection( - date = waste_collectiondate, # Collection date - t = "Rubbish", - icon=ICON_MAP.get("DOMESTIC") - ) - ) - - # Check to see if it's recycling week - if (recycling_collectiondate - today).days >= 0: - entries.append( - Collection( - date=recycling_collectiondate, - t="Recycling", - icon=ICON_MAP.get("RECYCLE") + for article in soup.find_all("article"): + waste_type = article.h3.string + icon = ICON_MAP.get(waste_type, "mdi:trash-can") + next_pickup = article.find(class_="next-service").string.strip() + if re.match(r"[^\s]* \d{1,2}\/\d{1,2}\/\d{4}", next_pickup): + next_pickup_date = datetime.strptime( + next_pickup.split(sep=" ")[1], "%d/%m/%Y" + ).date() + entries.append( + Collection(date=next_pickup_date, t=waste_type, icon=icon) ) - ) - # Check to see if it's green waste week - if (green_collectiondate - today).days >= 0: - entries.append( - Collection( - date=green_collectiondate, - t="Garden", - icon=ICON_MAP.get("ORGANIC") - ) - ) - - # Return our result return entries diff --git a/doc/source/goldcoast_qld_gov_au.md b/doc/source/goldcoast_qld_gov_au.md index cfc6f81d..d4a8f24b 100644 --- a/doc/source/goldcoast_qld_gov_au.md +++ b/doc/source/goldcoast_qld_gov_au.md @@ -9,20 +9,12 @@ waste_collection_schedule: sources: - name: goldcoast_qld_gov_au args: - suburb: SUBURB - street_name: STREET_NAME - street_number: STREET_NUMBER + street_address: STREET_ADDRESS ``` ### Configuration Variables -**suburb** -*(string) (required)* - -**street_name** -*(string) (required)* - -**street_number** +**street_address** *(string) (required)* ## Example @@ -32,11 +24,9 @@ waste_collection_schedule: sources: - name: goldcoast_qld_gov_au args: - suburb: Miami - street_name: Henchman Ave - street_number: 6/8 + street_address: 6/8 Henchman Ave Miami ``` ## How to get the source arguments -This is the address that the services are being picked up from (eg. your house!) +The Gold Coast API allows for a fuzzy search, so no need to get overly complicated with the address. However, you can visit the [Gold Coast City Council](https://www.goldcoast.qld.gov.au/Services/Waste-recycling/Find-my-bin-day) page and search for your address. The arguments should exactly match the street address shown in the autocomplete result. From 31e14cbcc851a270a76c93a1631d400a894fdee9 Mon Sep 17 00:00:00 2001 From: mampfes Date: Mon, 2 Jan 2023 09:31:36 +0100 Subject: [PATCH 108/127] fixed docu link for ashfield_gov_uk and breckland_gov_uk --- README.md | 2 +- .../source/ashfield_gov_uk.py | 32 +++++++++++-------- .../source/breckland_gov_uk.py | 2 +- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 0d2b49aa..ae2793e0 100644 --- a/README.md +++ b/README.md @@ -258,7 +258,7 @@ Waste collection schedules in the following formats and countries are supported. - [Bracknell Forest Council](/doc/source/bracknell_forest_gov_uk.md) / selfservice.mybfc.bracknell-forest.gov.uk - [Bradford Metropolitan District Council](/doc/source/bradford_gov_uk.md) / bradford.gov.uk - [Braintree District Council](/doc/source/braintree_gov_uk.md) / braintree.gov.uk -- [Breckland Council](/doc/source/breckland_gov_uk.md) / breckland.gov.uk +- [Breckland Council](/doc/source/breckland_gov_uk.md) / breckland.gov.uk/mybreckland - [Cambridge City Council](/doc/source/cambridge_gov_uk.md) / cambridge.gov.uk - [Canterbury City Council](/doc/source/canterbury_gov_uk.md) / canterbury.gov.uk - [Cheshire East Council](/doc/source/cheshire_east_gov_uk.md) / cheshireeast.gov.uk diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ashfield_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ashfield_gov_uk.py index 9fb5f9aa..78a34f3f 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ashfield_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ashfield_gov_uk.py @@ -1,16 +1,18 @@ -import json import datetime import requests from waste_collection_schedule import Collection -TITLE = "Ashfield.gov.uk" +TITLE = "Ashfield District Council" DESCRIPTION = "Source for ashfield.gov.uk, Ashfield District Council, UK" URL = "https://www.ashfield.gov.uk" TEST_CASES = { "11 Maun View Gardens, Sutton-in-Ashfield": {"uprn": 10001336299}, "4A Station Street, Kirkby-in-Ashfield": {"post_code": "NG177AR", "number": "4A"}, - "Ashfield District Council": {"post_code": "NG17 8DA", "name": "Ashfield District Council"} + "Ashfield District Council": { + "post_code": "NG17 8DA", + "name": "Ashfield District Council", + }, } API_URLS = { @@ -22,14 +24,14 @@ ICON_MAP = { "Residual Waste Collection Service": "mdi:trash-can", "Domestic Recycling Collection Service": "mdi:recycle", "Domestic Glass Collection Service": "mdi:glass-fragile", - "Garden Waste Collection Service": "mdi:leaf" + "Garden Waste Collection Service": "mdi:leaf", } NAMES = { "Residual Waste Collection Service": "Red (rubbish)", "Domestic Recycling Collection Service": "Green (recycling)", "Domestic Glass Collection Service": "Blue (glass)", - "Garden Waste Collection Service": "Brown (garden)" + "Garden Waste Collection Service": "Brown (garden)", } @@ -43,28 +45,30 @@ class Source: def fetch(self): if not self._uprn: # look up the UPRN for the address - q = str(API_URLS["address_search"]).format( - postcode=self._post_code) + q = str(API_URLS["address_search"]).format(postcode=self._post_code) r = requests.get(q) r.raise_for_status() addresses = r.json()["data"] if self._name: self._uprn = [ - int(x["AccountSiteUprn"]) for x in addresses if x["SiteAddressName"].capitalize() == self._name.capitalize() + int(x["AccountSiteUprn"]) + for x in addresses + if x["SiteAddressName"].capitalize() == self._name.capitalize() ][0] elif self._number: self._uprn = [ - int(x["AccountSiteUprn"]) for x in addresses if x["SiteAddressNumber"] == self._number + int(x["AccountSiteUprn"]) + for x in addresses + if x["SiteAddressNumber"] == self._number ][0] if not self._uprn: raise Exception( - f"Could not find address {self._post_code} {self._number}{self._name}") + f"Could not find address {self._post_code} {self._number}{self._name}" + ) - q = str(API_URLS["collection"]).format( - uprn=self._uprn - ) + q = str(API_URLS["collection"]).format(uprn=self._uprn) r = requests.get(q) r.raise_for_status() @@ -80,7 +84,7 @@ class Source: collection["Date"], "%d/%m/%Y %H:%M:%S" ).date(), t=NAMES.get(collection["Service"]), - icon=ICON_MAP.get(collection["Service"]) + icon=ICON_MAP.get(collection["Service"]), ) ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/breckland_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/breckland_gov_uk.py index 228179a2..44753dc8 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/breckland_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/breckland_gov_uk.py @@ -4,7 +4,7 @@ from datetime import datetime import requests from waste_collection_schedule import Collection -TITLE = "Breckland Council, UK" +TITLE = "Breckland Council" DESCRIPTION = "Source for breckland.gov.uk" URL = "https://www.breckland.gov.uk/mybreckland" TEST_CASES = { From b87277cc08527034742e059de11f12388c283c10 Mon Sep 17 00:00:00 2001 From: mampfes Date: Mon, 2 Jan 2023 09:56:56 +0100 Subject: [PATCH 109/127] add c_trace_de extra_info --- README.md | 5 ++++- .../waste_collection_schedule/source/c_trace_de.py | 14 ++++++++++++++ info.md | 4 ++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c470df56..b1cd9e80 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Waste collection schedules in the following formats and countries are supported. - [Brisbane City Council](/doc/source/brisbane_qld_gov_au.md) / brisbane.qld.gov.au - [Campbelltown City Council](/doc/source/campbelltown_nsw_gov_au.md) / campbelltown.nsw.gov.au - [City of Canada Bay Council](/doc/source/canadabay_nsw_gov_au.md) / canadabay.nsw.gov.au -- [City of Gold Coast Council](/doc/source/goldcoast_qld_gov_au.md) / goldcoast.qld.gov.au +- [Gold Coast City Council](/doc/source/goldcoast_qld_gov_au.md) / goldcoast.qld.gov.au - [Inner West Council (NSW)](/doc/source/innerwest_nsw_gov_au.md) / innerwest.nsw.gov.au - [Ipswich City Council](/doc/source/ipswich_qld_gov_au.md) / ipswich.qld.gov.au - [Ku-ring-gai Council](/doc/source/kuringgai_nsw_gov_au.md) / krg.nsw.gov.au @@ -89,12 +89,14 @@ Waste collection schedules in the following formats and countries are supported. - [ART Trier](/doc/source/art_trier_de.md) / art-trier.de - [AWB Bad Kreuznach](/doc/source/awb_bad_kreuznach_de.md) / app.awb-bad-kreuznach.de - [AWB Köln](/doc/source/awbkoeln_de.md) / awbkoeln.de +- [AWB Landkreis Augsburg](/doc/source/c_trace_de.md) / awb-landkreis-augsburg.de - [AWB Oldenburg](/doc/source/awb_oldenburg_de.md) / oldenburg.de - [AWIDO Online](/doc/source/awido_de.md) / awido-online.de - [Berlin Recycling](/doc/source/berlin_recycling_de.md) / berlin-recycling.de - [Berliner Stadtreinigungsbetriebe](/doc/source/bsr_de.md) / bsr.de - [Bielefeld](/doc/source/bielefeld_de.md) / bielefeld.de - [Bogenschütz Entsorgung](/doc/source/infeo_at.md) / bogenschuetz-entsorgung.de +- [Bremener Stadreinigung](/doc/source/c_trace_de.md) / die-bremer-stadtreinigung.de - [Bürgerportal](/doc/source/buergerportal_de.md) / c-trace.de - [C-Trace](/doc/source/c_trace_de.md) / c-trace.de - [EGN Abfallkalender](/doc/source/egn_abfallkalender_de.md) / egn-abfallkalender.de @@ -123,6 +125,7 @@ Waste collection schedules in the following formats and countries are supported. - [Südbrandenburgischer Abfallzweckverband](/doc/source/sbazv_de.md) / sbazv.de - [Wermelskirchen](/doc/source/wermelskirchen_de.md) / wermelskirchen.de - [Wolfsburger Abfallwirtschaft und Straßenreinigung](/doc/source/was_wolfsburg_de.md) / was-wolfsburg.de +- [WZV Kreis Segeberg](/doc/source/c_trace_de.md) / wzv.de
diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_de.py index ee46980a..44d220e2 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/c_trace_de.py @@ -5,6 +5,20 @@ from waste_collection_schedule.service.ICS import ICS TITLE = "C-Trace" DESCRIPTION = "Source for C-Trace.de." URL = "https://c-trace.de/" +EXTRA_INFO = [ + { + "title": "Bremener Stadreinigung", + "url": "https://www.die-bremer-stadtreinigung.de/", + }, + { + "title": "AWB Landkreis Augsburg", + "url": "https://www.awb-landkreis-augsburg.de/", + }, + { + "title": "WZV Kreis Segeberg", + "url": "https://www.wzv.de/", + }, +] TEST_CASES = { "Bremen": {"ort": "Bremen", "strasse": "Abbentorstraße", "hausnummer": 5}, "AugsburgLand": { diff --git a/info.md b/info.md index 7af9e6b2..6d0445d3 100644 --- a/info.md +++ b/info.md @@ -16,11 +16,11 @@ Waste collection schedules from service provider web sites are updated daily, de |--|--| | Generic | ICS / iCal files | | Static | User-defined dates or repeating date patterns | -| Australia | Banyule City Council, Belmont City Council, Brisbane City Council, Campbelltown City Council, City of Canada Bay Council, City of Gold Coast Council, Inner West Council (NSW), Ipswich City Council, Ku-ring-gai Council, Macedon Ranges Shire Council, Maroondah City Council, Melton City Council, Nillumbik Shire Council, North Adelaide Waste Management Authority, RecycleSmart, Stonnington City Council, The Hills Shire Council, Sydney, Wyndham City Council, Melbourne | +| Australia | Banyule City Council, Belmont City Council, Brisbane City Council, Campbelltown City Council, City of Canada Bay Council, Gold Coast City Council, Inner West Council (NSW), Ipswich City Council, Ku-ring-gai Council, Macedon Ranges Shire Council, Maroondah City Council, Melton City Council, Nillumbik Shire Council, North Adelaide Waste Management Authority, RecycleSmart, Stonnington City Council, The Hills Shire Council, Sydney, Wyndham City Council, Melbourne | | Austria | Burgenländischer Müllverband, infeo, Stadtservice Korneuburg, Umweltprofis, WSZ Moosburg | | Belgium | Hygea, Recycle! | | Canada | City of Toronto | -| Germany | Abfall Stuttgart, Abfall.IO / AbfallPlus, Abfallkalender Würzburg, AbfallNavi (RegioIT.de), Abfalltermine Forchheim, Abfallwirtschaft Alb-Donau-Kreis, Abfallwirtschaft Landkreis Harburg, Abfallwirtschaft Landkreis Wolfenbüttel, Abfallwirtschaft Neckar-Odenwald-Kreis, Abfallwirtschaft Rendsburg, Abfallwirtschaft Südholstein, Abfallwirtschaft Werra-Meißner-Kreis, Abfallwirtschaft Zollernalbkreis, Abfallwirtschaftsbetrieb Esslingen, Abfallwirtschaftsbetrieb Landkreis Ahrweiler, ART Trier, AWB Bad Kreuznach, AWB Köln, AWB Oldenburg, AWIDO Online, Berlin Recycling, Berliner Stadtreinigungsbetriebe, Bielefeld, Bogenschütz Entsorgung, Bürgerportal, C-Trace, EGN Abfallkalender, Jumomind, KAEV Niederlausitz, Kreiswirtschaftsbetriebe Goslar, KV Cochem-Zell, KWU Entsorgung Landkreis Oder-Spree, Landkreis Erlangen-Höchstadt, Landkreis Nordwestmecklenburg, Landkreis Rhön Grabfeld, Landkreis Schwäbisch Hall, Landkreis Wittmund, MZV Bidenkopf, Müllmax, Neunkirchen Siegerland, RegioEntsorgung Städteregion Aachen, Rhein-Hunsrück Entsorgung (RHE), Sector 27 - Datteln, Marl, Oer-Erkenschwick, Stadt Willich, Stadtreinigung Dresden, Stadtreinigung Hamburg, Stadtreinigung Leipzig, StadtService Brühl, Städteservice Raunheim Rüsselsheim, Südbrandenburgischer Abfallzweckverband, Wermelskirchen, Wolfsburger Abfallwirtschaft und Straßenreinigung | +| Germany | Abfall Stuttgart, Abfall.IO / AbfallPlus, Abfallkalender Würzburg, AbfallNavi (RegioIT.de), Abfalltermine Forchheim, Abfallwirtschaft Alb-Donau-Kreis, Abfallwirtschaft Landkreis Harburg, Abfallwirtschaft Landkreis Wolfenbüttel, Abfallwirtschaft Neckar-Odenwald-Kreis, Abfallwirtschaft Rendsburg, Abfallwirtschaft Südholstein, Abfallwirtschaft Werra-Meißner-Kreis, Abfallwirtschaft Zollernalbkreis, Abfallwirtschaftsbetrieb Esslingen, Abfallwirtschaftsbetrieb Landkreis Ahrweiler, ART Trier, AWB Bad Kreuznach, AWB Köln, AWB Landkreis Augsburg, AWB Oldenburg, AWIDO Online, Berlin Recycling, Berliner Stadtreinigungsbetriebe, Bielefeld, Bogenschütz Entsorgung, Bremener Stadreinigung, Bürgerportal, C-Trace, EGN Abfallkalender, Jumomind, KAEV Niederlausitz, Kreiswirtschaftsbetriebe Goslar, KV Cochem-Zell, KWU Entsorgung Landkreis Oder-Spree, Landkreis Erlangen-Höchstadt, Landkreis Nordwestmecklenburg, Landkreis Rhön Grabfeld, Landkreis Schwäbisch Hall, Landkreis Wittmund, MZV Bidenkopf, Müllmax, Neunkirchen Siegerland, RegioEntsorgung Städteregion Aachen, Rhein-Hunsrück Entsorgung (RHE), Sector 27 - Datteln, Marl, Oer-Erkenschwick, Stadt Willich, Stadtreinigung Dresden, Stadtreinigung Hamburg, Stadtreinigung Leipzig, StadtService Brühl, Städteservice Raunheim Rüsselsheim, Südbrandenburgischer Abfallzweckverband, Wermelskirchen, Wolfsburger Abfallwirtschaft und Straßenreinigung, WZV Kreis Segeberg | | Lithuania | Kauno švara | | Netherlands | ACV Group, Alpen an den Rijn, Area Afval, Avalex, Avri, Bar Afvalbeheer, Cyclus NV, Dar, Den Haag, GAD, Gemeente Almere, Gemeente Berkelland, Gemeente Cranendonck, Gemeente Hellendoorn, Gemeente Lingewaard, Gemeente Meppel, Gemeente Middelburg + Vlissingen, Gemeente Peel en Maas, Gemeente Schouwen-Duiveland, Gemeente Sudwest-Fryslan, Gemeente Venray, Gemeente Voorschoten, Gemeente Wallre, Gemeente Westland, HVC Groep, Meerlanden, Mijn Blink, PreZero, Purmerend, RAD BV, Reinigingsbedrijf Midden Nederland, Reinis, Spaarne Landen, Stadswerk 072, Twente Milieu, Waardlanden, Ximmio, ZRD | | New Zealand | Auckland Council, Christchurch City Council, Gore, Invercargill & Southland, Horowhenua District Council, Waipa District Council, Wellington City Council | From 78b55f778c693ef71eb74081a0eac53e7a1e57c7 Mon Sep 17 00:00:00 2001 From: Patrick Gniza Date: Mon, 2 Jan 2023 10:56:05 +0100 Subject: [PATCH 110/127] changed URLs, after KWU changed calendardownload --- .../waste_collection_schedule/source/kwu_de.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/kwu_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kwu_de.py index cf5b0e3f..0e286519 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/kwu_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kwu_de.py @@ -39,7 +39,7 @@ class Source: "direct": "true", } - r = requests.get("https://www.kwu-entsorgung.de/inc/wordpress/kal_objauswahl.php", headers=HEADERS) + r = requests.get("https://kalender.kwu-entsorgung.de", headers=HEADERS, verify=False) parsed_html = BeautifulSoup(r.text, "html.parser") Orte = parsed_html.find_all('option') @@ -48,7 +48,7 @@ class Source: OrtValue = Ort['value'] break - r = requests.get("https://www.kwu-entsorgung.de/inc/wordpress/kal_str2ort.php", params={"ort": OrtValue}, headers=HEADERS) + r = requests.get("https://kalender.kwu-entsorgung.de/kal_str2ort.php", params={"ort": OrtValue}, headers=HEADERS, verify=False) parsed_html = BeautifulSoup(r.text, "html.parser") Strassen = parsed_html.find_all('option') @@ -57,7 +57,7 @@ class Source: StrasseValue = Strasse['value'] break - r = requests.get("https://www.kwu-entsorgung.de/inc/wordpress/kal_str2ort.php", params={"ort": OrtValue, "strasse": StrasseValue}, headers=HEADERS) + r = requests.get("https://kalender.kwu-entsorgung.de/kal_str2ort.php", params={"ort": OrtValue, "strasse": StrasseValue}, headers=HEADERS, verify=False) parsed_html = BeautifulSoup(r.text, "html.parser") Objekte = parsed_html.find_all('option') @@ -66,7 +66,7 @@ class Source: ObjektValue = Objekt['value'] break - r = requests.post("https://www.kwu-entsorgung.de/inc/wordpress/kal_uebersicht-2020.php", data={"ort": OrtValue, "strasse": StrasseValue, "objekt": ObjektValue, "jahr": date.today().year}, headers=HEADERS) + r = requests.post("https://kalender.kwu-entsorgung.de/kal_uebersicht-2023.php", data={"ort": OrtValue, "strasse": StrasseValue, "objekt": ObjektValue, "jahr": date.today().year}, headers=HEADERS, verify=False) parsed_html = BeautifulSoup(r.text, "html.parser") Links = parsed_html.find_all('a') @@ -79,7 +79,7 @@ class Source: raise Exception(f"ics url not found") # get ics file - r = session.get(ics_url, headers=HEADERS) + r = session.get(ics_url, headers=HEADERS, verify=False) r.raise_for_status() # parse ics file From 32017afcba8e72bcbacc739127a19f1ee28c039a Mon Sep 17 00:00:00 2001 From: mampfes Date: Tue, 3 Jan 2023 08:28:56 +0100 Subject: [PATCH 111/127] fix bashlash syntax for fes frankfurt and fix some markdown warnings --- doc/source/ics.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/doc/source/ics.md b/doc/source/ics.md index 46b98813..69f4bad3 100644 --- a/doc/source/ics.md +++ b/doc/source/ics.md @@ -6,7 +6,7 @@ This source has been successfully tested with the following service providers: ### Belgium -- [Limburg.net](https://www.limburg.net/afvalkalender) ([Example](#limburg-net)) +- [Limburg.net](https://www.limburg.net/afvalkalender) ([Example](#limburgnet)) ### Germany @@ -29,8 +29,8 @@ This source has been successfully tested with the following service providers: #### Brandenburg - - [Entsorgungsbetrieb Märkisch-Oderland](https://www.entsorgungsbetrieb-mol.de/de/tourenplaene.html) ([Example](#entsorgungsbetrieb-märkisch-oderland)) - +- [Entsorgungsbetrieb Märkisch-Oderland](https://www.entsorgungsbetrieb-mol.de/de/tourenplaene.html) ([Example](#entsorgungsbetrieb-märkisch-oderland)) + #### Hessen - [Erlensee](https://sperrmuell.erlensee.de/?type=reminder) ([Example](#erlensee)) @@ -47,7 +47,7 @@ This source has been successfully tested with the following service providers: #### Rheinland-Pfalz - [Zweckverband Abfallwirtschaft A.R.T. Trier](https://www.art-trier.de) - - Landkreis Vulkaneifel +- Landkreis Vulkaneifel #### Sachsen @@ -190,8 +190,7 @@ waste_collection_schedule: #### Landkreis Vulkaneifel -Go to the website: -[service provider website](https://www.art-trier.de/eo/cms?_bereich=artikel&_aktion=suche_rubrik&idrubrik=1003&_sortierung=info3_asc_info4_asc&info1=54578&info2=) +Go to the website: [art-trier.de](https://www.art-trier.de/eo/cms?_bereich=artikel&_aktion=suche_rubrik&idrubrik=1003&_sortierung=info3_asc_info4_asc&info1=54578&info2=) select your Postal code. @@ -625,14 +624,15 @@ waste_collection_schedule: ``` You can also compose the URL yourself. You need the following elements for this: -1. the nis-code of your municipality: query the api with the name of your municipality; example: https://limburg.net/api-proxy/public/afval-kalender/gemeenten/search?query=Peer + +1. the nis-code of your municipality: query the api with the name of your municipality; example: ```json [{"nisCode":"72030","naam":"Peer"}] ``` 2. the number of your street: query the api with the nis-code of your municipality and the name of your street -example: https://limburg.net/api-proxy/public/afval-kalender/gemeente/72030/straten/search?query=Zuidervest +example: ```json [{"nummer":"66536","naam":"Zuidervest"}] @@ -699,14 +699,14 @@ To use this you need to idenfify your Unique Property Reference Number (UPRN). T 1. The easiest way to discover your UPRN is by using https://www.findmyaddress.co.uk/ and entering in your address details. -Or + Or 2. By looking at the URLs generated by the South Cambs web site: - * 2.1. Go to [South Cambs Bin Collections](https://www.scambs.gov.uk/recycling-and-bins/find-your-household-bin-collection-day/) - * 2.2 Enter your post code, then select your address from the dropdown. The results page will show your collection schedule. - * 2.3. Your UPRN is the collection of digits at the end of the URL, for example: *scambs.gov.uk/recycling-and-bins/find-your-household-bin-collection-day/#id=`10008079869`* - * 2.4. The iCal collection schedule can then be obtained using: *refusecalendarapi.azurewebsites.net/calendar/ical/`10008079869`* + 1. Go to [South Cambs Bin Collections](https://www.scambs.gov.uk/recycling-and-bins/find-your-household-bin-collection-day/) + 2. Enter your post code, then select your address from the dropdown. The results page will show your collection schedule. + 3. Your UPRN is the collection of digits at the end of the URL, for example: *scambs.gov.uk/recycling-and-bins/find-your-household-bin-collection-day/#id=`10008079869`* + 4. The iCal collection schedule can then be obtained using: *refusecalendarapi.azurewebsites.net/calendar/ical/`10008079869`* ```yaml waste_collection_schedule: @@ -733,7 +733,7 @@ The Bromley council has a simple way to generate an iCal. All you need is the UR - Go to [Bromley Bin Collection](https://recyclingservices.bromley.gov.uk/waste) - Enter your post code, then select your address from the dropdown. The results page will show your collection schedule. - Your unique code can be found in the URL, eg: *recyclingservices.bromley.gov.uk/waste/`6261994`* -- You can either use the following link and replace your ID, or copy the link address on the "Add to you calendar" link: *https://recyclingservices.bromley.gov.uk/waste/6261994/calendar.ics* +- You can either use the following link and replace your ID, or copy the link address on the "Add to you calendar" link: Note: @@ -789,7 +789,7 @@ waste_collection_schedule: args: url: https://www.fes-frankfurt.de/abfallkalender/.ics split_at: " \/ " - regex: "(.*)\s+\|" + regex: "(.*)\\s+\\|" ``` *** From 390f50c1b008a43dbea4615aac91212ef3700eb3 Mon Sep 17 00:00:00 2001 From: Jan Date: Tue, 3 Jan 2023 10:01:43 +0100 Subject: [PATCH 112/127] Fix documentation for berlin_recycling_de as the example was wrong --- doc/source/berlin_recycling_de.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/berlin_recycling_de.md b/doc/source/berlin_recycling_de.md index 66634479..d785160b 100644 --- a/doc/source/berlin_recycling_de.md +++ b/doc/source/berlin_recycling_de.md @@ -26,7 +26,7 @@ waste_collection_schedule: ```yaml waste_collection_schedule: sources: - - name: bsr_de + - name: berlin_recycling_de args: username: My User Name password: My Password From 600c56f5a5b5b32db995a446ac10d5f0491ff4b6 Mon Sep 17 00:00:00 2001 From: Jan-Olaf Becker Date: Tue, 3 Jan 2023 13:47:54 +0100 Subject: [PATCH 113/127] abfuhrkalender_nuernberger_land_de: add new source --- README.md | 1 + .../abfuhrkalender_nuernberger_land_de.py | 48 +++++++++++++++++++ .../abfuhrkalender_nuernberger_land_de.md | 35 ++++++++++++++ info.md | 2 +- 4 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 custom_components/waste_collection_schedule/waste_collection_schedule/source/abfuhrkalender_nuernberger_land_de.py create mode 100644 doc/source/abfuhrkalender_nuernberger_land_de.md diff --git a/README.md b/README.md index b1cd9e80..24e2a773 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ Waste collection schedules in the following formats and countries are supported. - [Abfallwirtschaft Landkreis Harburg](/doc/source/aw_harburg_de.md) / landkreis-harburg.de - [Abfallwirtschaft Landkreis Wolfenbüttel](/doc/source/alw_wf_de.md) / alw-wf.de - [Abfallwirtschaft Neckar-Odenwald-Kreis](/doc/source/awn_de.md) / awn-online.de +- [Abfallwirtschaft Nürnberger Land](/doc/source/abfuhrkalender_nuernberger_land_de.md) / abfuhrkalender.nuernberger-land.de - [Abfallwirtschaft Rendsburg](/doc/source/awr_de.md) / awr.de - [Abfallwirtschaft Südholstein](/doc/source/awsh_de.md) / awsh.de - [Abfallwirtschaft Werra-Meißner-Kreis](/doc/source/zva_wmk_de.md) / zva-wmk.de diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfuhrkalender_nuernberger_land_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfuhrkalender_nuernberger_land_de.py new file mode 100644 index 00000000..0764611b --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfuhrkalender_nuernberger_land_de.py @@ -0,0 +1,48 @@ +import requests +from waste_collection_schedule import Collection +from waste_collection_schedule.service.ICS import ICS + +TITLE = "Abfallwirtschaft Nürnberger Land" +DESCRIPTION = "Source for Nürnberger Land" +URL = "https://abfuhrkalender.nuernberger-land.de" +TEST_CASES = { + "Schwarzenbruck, Mühlbergstraße": {"id": 16952001}, + "Burgthann, Brunhildstr": {"id": 14398001}, + "Kirchensittenbach, Erlenweg": {"id": 15192001}, +} + +API_URL = "https://abfuhrkalender.nuernberger-land.de/waste_calendar" + +ICON_MAP = { + "Restmüll/Biotonne": "mdi:trash-can", + "Biotonne" : "mdi:leaf", + "Papier/gelber Sack" : "mdi:package-variant", + "Giftmobil" : "mdi:biohazard", +} + + +class Source: + def __init__(self, id): + self._id = id + self._ics = ICS() + + def fetch(self): + + # fetch a list of all city ids + # fetch a list of all city districts + # fetch a list of all streets + # fetch a list of all street sections + + # fetch the ical + + r = requests.get(f"{API_URL}/ical?id={self._id}") + r.raise_for_status() + + dates = self._ics.convert(r.text) + + entries = [ + Collection(date=entry[0], t=entry[1], icon=next(v for k, v in ICON_MAP.items() if k in entry[1])) + for entry in dates + ] + + return entries \ No newline at end of file diff --git a/doc/source/abfuhrkalender_nuernberger_land_de.md b/doc/source/abfuhrkalender_nuernberger_land_de.md new file mode 100644 index 00000000..537f1992 --- /dev/null +++ b/doc/source/abfuhrkalender_nuernberger_land_de.md @@ -0,0 +1,35 @@ +# Abfuhrkalender Nürnberger Land + +Support for schedules provided by . + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: abfuhrkalender_nuernberger_land_de + args: + id: ID +``` + +### Configuration Variables + +**id** +_(integer) (required)_ : The unique 8-digit identifier of your street section + +## Example + +```yaml +waste_collection_schedule: + sources: + - name: abfuhrkalender_nuernberger_land_de + args: + id: 14579001 +``` + +## How to get the source arguments + +1. Open . +2. Fill out the filter fields on the page. +3. Right click the button "Termine in den Kalender importieren" and select "Copy link address". You should get something like this `https://abfuhrkalender.nuernberger-land.de/waste_calendar/ical?id=14579001` +4. Copy the id number at the end of the link to your configuration file. diff --git a/info.md b/info.md index 6d0445d3..4c68755c 100644 --- a/info.md +++ b/info.md @@ -20,7 +20,7 @@ Waste collection schedules from service provider web sites are updated daily, de | Austria | Burgenländischer Müllverband, infeo, Stadtservice Korneuburg, Umweltprofis, WSZ Moosburg | | Belgium | Hygea, Recycle! | | Canada | City of Toronto | -| Germany | Abfall Stuttgart, Abfall.IO / AbfallPlus, Abfallkalender Würzburg, AbfallNavi (RegioIT.de), Abfalltermine Forchheim, Abfallwirtschaft Alb-Donau-Kreis, Abfallwirtschaft Landkreis Harburg, Abfallwirtschaft Landkreis Wolfenbüttel, Abfallwirtschaft Neckar-Odenwald-Kreis, Abfallwirtschaft Rendsburg, Abfallwirtschaft Südholstein, Abfallwirtschaft Werra-Meißner-Kreis, Abfallwirtschaft Zollernalbkreis, Abfallwirtschaftsbetrieb Esslingen, Abfallwirtschaftsbetrieb Landkreis Ahrweiler, ART Trier, AWB Bad Kreuznach, AWB Köln, AWB Landkreis Augsburg, AWB Oldenburg, AWIDO Online, Berlin Recycling, Berliner Stadtreinigungsbetriebe, Bielefeld, Bogenschütz Entsorgung, Bremener Stadreinigung, Bürgerportal, C-Trace, EGN Abfallkalender, Jumomind, KAEV Niederlausitz, Kreiswirtschaftsbetriebe Goslar, KV Cochem-Zell, KWU Entsorgung Landkreis Oder-Spree, Landkreis Erlangen-Höchstadt, Landkreis Nordwestmecklenburg, Landkreis Rhön Grabfeld, Landkreis Schwäbisch Hall, Landkreis Wittmund, MZV Bidenkopf, Müllmax, Neunkirchen Siegerland, RegioEntsorgung Städteregion Aachen, Rhein-Hunsrück Entsorgung (RHE), Sector 27 - Datteln, Marl, Oer-Erkenschwick, Stadt Willich, Stadtreinigung Dresden, Stadtreinigung Hamburg, Stadtreinigung Leipzig, StadtService Brühl, Städteservice Raunheim Rüsselsheim, Südbrandenburgischer Abfallzweckverband, Wermelskirchen, Wolfsburger Abfallwirtschaft und Straßenreinigung, WZV Kreis Segeberg | +| Germany | Abfall Stuttgart, Abfall.IO / AbfallPlus, Abfallkalender Würzburg, AbfallNavi (RegioIT.de), Abfalltermine Forchheim, Abfallwirtschaft Alb-Donau-Kreis, Abfallwirtschaft Landkreis Harburg, Abfallwirtschaft Landkreis Wolfenbüttel, Abfallwirtschaft Neckar-Odenwald-Kreis, Abfallwirtschaft Nürnberger Land, Abfallwirtschaft Rendsburg, Abfallwirtschaft Südholstein, Abfallwirtschaft Werra-Meißner-Kreis, Abfallwirtschaft Zollernalbkreis, Abfallwirtschaftsbetrieb Esslingen, Abfallwirtschaftsbetrieb Landkreis Ahrweiler, ART Trier, AWB Bad Kreuznach, AWB Köln, AWB Landkreis Augsburg, AWB Oldenburg, AWIDO Online, Berlin Recycling, Berliner Stadtreinigungsbetriebe, Bielefeld, Bogenschütz Entsorgung, Bremener Stadreinigung, Bürgerportal, C-Trace, EGN Abfallkalender, Jumomind, KAEV Niederlausitz, Kreiswirtschaftsbetriebe Goslar, KV Cochem-Zell, KWU Entsorgung Landkreis Oder-Spree, Landkreis Erlangen-Höchstadt, Landkreis Nordwestmecklenburg, Landkreis Rhön Grabfeld, Landkreis Schwäbisch Hall, Landkreis Wittmund, MZV Bidenkopf, Müllmax, Neunkirchen Siegerland, RegioEntsorgung Städteregion Aachen, Rhein-Hunsrück Entsorgung (RHE), Sector 27 - Datteln, Marl, Oer-Erkenschwick, Stadt Willich, Stadtreinigung Dresden, Stadtreinigung Hamburg, Stadtreinigung Leipzig, StadtService Brühl, Städteservice Raunheim Rüsselsheim, Südbrandenburgischer Abfallzweckverband, Wermelskirchen, Wolfsburger Abfallwirtschaft und Straßenreinigung, WZV Kreis Segeberg | | Lithuania | Kauno švara | | Netherlands | ACV Group, Alpen an den Rijn, Area Afval, Avalex, Avri, Bar Afvalbeheer, Cyclus NV, Dar, Den Haag, GAD, Gemeente Almere, Gemeente Berkelland, Gemeente Cranendonck, Gemeente Hellendoorn, Gemeente Lingewaard, Gemeente Meppel, Gemeente Middelburg + Vlissingen, Gemeente Peel en Maas, Gemeente Schouwen-Duiveland, Gemeente Sudwest-Fryslan, Gemeente Venray, Gemeente Voorschoten, Gemeente Wallre, Gemeente Westland, HVC Groep, Meerlanden, Mijn Blink, PreZero, Purmerend, RAD BV, Reinigingsbedrijf Midden Nederland, Reinis, Spaarne Landen, Stadswerk 072, Twente Milieu, Waardlanden, Ximmio, ZRD | | New Zealand | Auckland Council, Christchurch City Council, Gore, Invercargill & Southland, Horowhenua District Council, Waipa District Council, Wellington City Council | From 2d4237ff07615a3fd96496a8e176660b84e6a96f Mon Sep 17 00:00:00 2001 From: mampfes Date: Tue, 3 Jan 2023 14:53:44 +0100 Subject: [PATCH 114/127] refactor nuernberger_land_de - rename source: only top-level domain is necessary - fix issue with non-ascii characters in ICS UID field - split waste types --- README.md | 2 +- .../abfuhrkalender_nuernberger_land_de.py | 48 ----------------- .../source/nuernberger_land_de.py | 52 +++++++++++++++++++ ...rger_land_de.md => nuernberger_land_de.md} | 4 +- 4 files changed, 55 insertions(+), 51 deletions(-) delete mode 100644 custom_components/waste_collection_schedule/waste_collection_schedule/source/abfuhrkalender_nuernberger_land_de.py create mode 100644 custom_components/waste_collection_schedule/waste_collection_schedule/source/nuernberger_land_de.py rename doc/source/{abfuhrkalender_nuernberger_land_de.md => nuernberger_land_de.md} (90%) diff --git a/README.md b/README.md index 24e2a773..b3749156 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ Waste collection schedules in the following formats and countries are supported. - [Abfallwirtschaft Landkreis Harburg](/doc/source/aw_harburg_de.md) / landkreis-harburg.de - [Abfallwirtschaft Landkreis Wolfenbüttel](/doc/source/alw_wf_de.md) / alw-wf.de - [Abfallwirtschaft Neckar-Odenwald-Kreis](/doc/source/awn_de.md) / awn-online.de -- [Abfallwirtschaft Nürnberger Land](/doc/source/abfuhrkalender_nuernberger_land_de.md) / abfuhrkalender.nuernberger-land.de +- [Abfallwirtschaft Nürnberger Land](/doc/source/nuernberger_land_de.md) / nuernberger-land.de - [Abfallwirtschaft Rendsburg](/doc/source/awr_de.md) / awr.de - [Abfallwirtschaft Südholstein](/doc/source/awsh_de.md) / awsh.de - [Abfallwirtschaft Werra-Meißner-Kreis](/doc/source/zva_wmk_de.md) / zva-wmk.de diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfuhrkalender_nuernberger_land_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfuhrkalender_nuernberger_land_de.py deleted file mode 100644 index 0764611b..00000000 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfuhrkalender_nuernberger_land_de.py +++ /dev/null @@ -1,48 +0,0 @@ -import requests -from waste_collection_schedule import Collection -from waste_collection_schedule.service.ICS import ICS - -TITLE = "Abfallwirtschaft Nürnberger Land" -DESCRIPTION = "Source for Nürnberger Land" -URL = "https://abfuhrkalender.nuernberger-land.de" -TEST_CASES = { - "Schwarzenbruck, Mühlbergstraße": {"id": 16952001}, - "Burgthann, Brunhildstr": {"id": 14398001}, - "Kirchensittenbach, Erlenweg": {"id": 15192001}, -} - -API_URL = "https://abfuhrkalender.nuernberger-land.de/waste_calendar" - -ICON_MAP = { - "Restmüll/Biotonne": "mdi:trash-can", - "Biotonne" : "mdi:leaf", - "Papier/gelber Sack" : "mdi:package-variant", - "Giftmobil" : "mdi:biohazard", -} - - -class Source: - def __init__(self, id): - self._id = id - self._ics = ICS() - - def fetch(self): - - # fetch a list of all city ids - # fetch a list of all city districts - # fetch a list of all streets - # fetch a list of all street sections - - # fetch the ical - - r = requests.get(f"{API_URL}/ical?id={self._id}") - r.raise_for_status() - - dates = self._ics.convert(r.text) - - entries = [ - Collection(date=entry[0], t=entry[1], icon=next(v for k, v in ICON_MAP.items() if k in entry[1])) - for entry in dates - ] - - return entries \ No newline at end of file diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/nuernberger_land_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/nuernberger_land_de.py new file mode 100644 index 00000000..1d7e2b5b --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/nuernberger_land_de.py @@ -0,0 +1,52 @@ +import requests +from waste_collection_schedule import Collection +from waste_collection_schedule.service.ICS import ICS + +TITLE = "Abfallwirtschaft Nürnberger Land" +DESCRIPTION = "Source for Nürnberger Land" +URL = "https://nuernberger-land.de" +TEST_CASES = { + "Schwarzenbruck, Mühlbergstraße": {"id": 16952001}, + "Burgthann, Brunhildstr": {"id": 14398001}, + "Kirchensittenbach, Erlenweg": {"id": 15192001}, +} + +API_URL = "https://abfuhrkalender.nuernberger-land.de/waste_calendar" + +ICON_MAP = { + "Restmüll": "mdi:trash-can", + "Biotonne": "mdi:leaf", + "Gelber Sack": "mdi:recycle", + "Papier": "mdi:package-variant", + "Giftmobil": "mdi:biohazard", +} + + +class Source: + def __init__(self, id): + self._id = id + self._ics = ICS(split_at="/") + + def fetch(self): + # fetch the ical + r = requests.get(f"{API_URL}/ical?id={self._id}") + r.raise_for_status() + + # replace non-ascii character in UID, otherwise ICS converter will fail + ics = "" + for line in r.text.splitlines(): + if line.startswith("UID"): + line = line.replace("ä", "ae") + line = line.replace("ö", "oe") + line = line.replace("ü", "ue") + ics += line + ics += "\n" + + dates = self._ics.convert(ics) + + entries = [] + + for d in dates: + entries.append(Collection(date=d[0], t=d[1], icon=ICON_MAP.get(d[1]))) + + return entries diff --git a/doc/source/abfuhrkalender_nuernberger_land_de.md b/doc/source/nuernberger_land_de.md similarity index 90% rename from doc/source/abfuhrkalender_nuernberger_land_de.md rename to doc/source/nuernberger_land_de.md index 537f1992..7179f30f 100644 --- a/doc/source/abfuhrkalender_nuernberger_land_de.md +++ b/doc/source/nuernberger_land_de.md @@ -7,7 +7,7 @@ Support for schedules provided by . ```yaml waste_collection_schedule: sources: - - name: abfuhrkalender_nuernberger_land_de + - name: nuernberger_land_de args: id: ID ``` @@ -22,7 +22,7 @@ _(integer) (required)_ : The unique 8-digit identifier of your street section ```yaml waste_collection_schedule: sources: - - name: abfuhrkalender_nuernberger_land_de + - name: nuernberger_land_de args: id: 14579001 ``` From 568d7c88894a0b847113bb2a19b39161dfc975d6 Mon Sep 17 00:00:00 2001 From: Jan-Olaf Becker Date: Tue, 3 Jan 2023 18:49:22 +0100 Subject: [PATCH 115/127] art_trier_de: fix copy paste error in readme --- doc/source/art_trier_de.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/art_trier_de.md b/doc/source/art_trier_de.md index abe70645..c31274c9 100644 --- a/doc/source/art_trier_de.md +++ b/doc/source/art_trier_de.md @@ -26,7 +26,7 @@ _(string) (required)_ ```yaml waste_collection_schedule: sources: - - name: cochem_zell_online_de + - name: art_trier_de args: district: "Wittlich, Marktplatz" zip_code: "54516" From cc72da3011e19dce7341ae6577c3b7cbb36466a3 Mon Sep 17 00:00:00 2001 From: mampfes Date: Tue, 3 Jan 2023 20:04:53 +0100 Subject: [PATCH 116/127] add HA hangs to known issues fix #548 --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index b3749156..32217678 100644 --- a/README.md +++ b/README.md @@ -362,6 +362,28 @@ The following waste service providers return errors when running the test_source If you can fix any of these, please raise a Pull Request with the updates. +--- + +## Home Assistant Hangs + +**Problem:** Home Assistant hangs during restart or configuration check. This occurs typically after Waste Collection Schedule has been added to the configuration. + +**Root Cause:** Home Assistant tries to install the required Python packages and fails somehow. This is not an issue of Waste Collection Schedule. + +**Solution:** Try to reinstall Waste Collection Schedule (if you are using HACS) or install the required Python packages manually. This list of required packages can be found in [manifest.json](/custom_components/waste_collection_schedule/manifest.json) after the keyword `requirements`. +https://github.com/mampfes/hacs_waste_collection_schedule/master/custom_components/manifest.json#L5 + +The actual procedure depends on your Home Assistant installation type. + +Example: + +```bash +sudo docker exec -it homeassistant /bin/bash +pip list +pip install recurring_ical_events # in case recurring_ical_events is missing +``` + + # Licence ![github licence](https://img.shields.io/badge/Licence-MIT-orange) From 4cad6a5780e256c8a37b685f5c1d6c1ed0fb7211 Mon Sep 17 00:00:00 2001 From: mampfes Date: Tue, 3 Jan 2023 20:58:36 +0100 Subject: [PATCH 117/127] split waste types into separate entities --- .../waste_collection_schedule/source/stadtservice_bruehl_de.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtservice_bruehl_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtservice_bruehl_de.py index c000778c..b9c2ec3c 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtservice_bruehl_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/stadtservice_bruehl_de.py @@ -18,7 +18,7 @@ class Source: def __init__(self, strasse, hnr): self._strasse = strasse self._hnr = hnr - self._ics = ICS() + self._ics = ICS(regex="(.*?) \\- ", split_at=", ") def fetch(self): From 97789f7c6680a8ccb7982dbd246bf95f317d3fc3 Mon Sep 17 00:00:00 2001 From: Phil Harrison Date: Tue, 3 Jan 2023 19:58:49 +0000 Subject: [PATCH 118/127] Adding Salford City Council to service providers --- README.md | 1 + .../source/salford_uk.py | 54 +++++++++++++++++++ doc/source/salford_uk.md | 36 +++++++++++++ info.md | 2 +- 4 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 custom_components/waste_collection_schedule/waste_collection_schedule/source/salford_uk.py create mode 100644 doc/source/salford_uk.md diff --git a/README.md b/README.md index b3749156..891cffcb 100644 --- a/README.md +++ b/README.md @@ -290,6 +290,7 @@ Waste collection schedules in the following formats and countries are supported. - [Peterborough City Council](/doc/source/peterborough_gov_uk.md) / peterborough.gov.uk - [Richmondshire District Council](/doc/source/richmondshire_gov_uk.md) / richmondshire.gov.uk - [Rushmoor Borough Council](/doc/source/rushmoor_gov_uk.md) / rushmoor.gov.uk +- [Manchester City Council](/doc/source/salford_uk.md) / salford.gov.uk - [Sheffield City Council](/doc/source/sheffield_gov_uk.md) / sheffield.gov.uk - [South Cambridgeshire District Council](/doc/source/scambs_gov_uk.md) / scambs.gov.uk - [South Hams District Council](/doc/source/fccenvironment_co_uk.md) / southhams.gov.uk diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/salford_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/salford_uk.py new file mode 100644 index 00000000..205c3db9 --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/salford_uk.py @@ -0,0 +1,54 @@ +import logging +from datetime import datetime + +import requests +from bs4 import BeautifulSoup +from waste_collection_schedule import Collection # type: ignore[attr-defined] + +TITLE = "Salford City Council" +DESCRIPTION = "Source for bin collection services for Salford City Council, UK." +URL = "https://www.Salford.gov.uk" +TEST_CASES = { + "domestic": {"uprn": "100011404886"}, +} + +ICON_MAP = { + "Domestic waste": "mdi:trash-can", + "Blue recycling (paper and card)": "mdi:recycle", + "Brown recycling (bottles and cans)": "mdi:glass-fragile", + "Food and garden waste": "mdi:leaf", +} + +_LOGGER = logging.getLogger(__name__) + + +class Source: + def __init__(self, uprn: int): + self._uprn = uprn + + def fetch(self): + entries = [] + + r = requests.get( + f"https://www.salford.gov.uk/bins-and-recycling/bin-collection-days/your-bin-collections/?UPRN={self._uprn}" + ) + + soup = BeautifulSoup(r.text, features="html.parser") + results = soup.find_all("div", {"class": "col-xs-12 col-md-6"}) + + for result in results: + dates = [] + for date in result.find_all("li"): + dates.append(date.text) + collection_type = (result.find("strong").text).replace(":", "") + for current_date in dates: + date = datetime.strptime(current_date, "%A %d %B %Y").date() + entries.append( + Collection( + date=date, + t=collection_type, + icon=ICON_MAP[collection_type], + ) + ) + + return entries \ No newline at end of file diff --git a/doc/source/salford_uk.md b/doc/source/salford_uk.md new file mode 100644 index 00000000..906eb1ee --- /dev/null +++ b/doc/source/salford_uk.md @@ -0,0 +1,36 @@ +# Salford City Council + +Support for schedules provided by [Salford City +Council](https://www.salford.gov.uk/bins-and-recycling/bin-collection-days/), serving the +city of Salford, UK. + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: salford_uk + args: + uprn: UPRN_CODE +``` + +### Configuration Variables + +**uprn** +*(string) (required)* + +## Example + +```yaml +waste_collection_schedule: + sources: + - name: salford_uk + args: + uprn: "100011404886" +``` + +## How to get the source argument + +The UPRN code can be found by entering your postcode on the +[Salford City Council bin collections days page +](https://www.salford.gov.uk/bins-and-recycling/bin-collection-days/). After selecting your address from the list and waiting for the new page to load, the uprn will be shown in the address bar. diff --git a/info.md b/info.md index 4c68755c..cebebd15 100644 --- a/info.md +++ b/info.md @@ -28,7 +28,7 @@ Waste collection schedules from service provider web sites are updated daily, de | Poland | Ecoharmonogram, Warsaw | | Sweden | Lerum Vatten och Avlopp, Ronneby Miljöteknik, SRV Återvinning, SSAM, Sysav Sophämntning, VA Syd Sophämntning | | Switzerland | A-Region, Andwil, Appenzell, Berg, Bühler, Eggersriet, Gais, Gaiserwald, Goldach, Grub, Heiden, Herisau, Horn, Hundwil, Häggenschwil, Lindau, Lutzenberg, Muolen, Mörschwil, Rehetobel, Rorschach, Rorschacherberg, Schwellbrunn, Schönengrund, Speicher, Stein, Steinach, Teufen, Thal, Trogen, Tübach, Untereggen, Urnäsch, Wald, Waldkirch, Waldstatt, Wittenbach, Wolfhalden | -| United Kingdom | Ashfield District Council, Bracknell Forest Council, Bradford Metropolitan District Council, Braintree District Council, Breckland Council, Cambridge City Council, Canterbury City Council, Cheshire East Council, Chesterfield Borough Council, City of York Council, Colchester Borough Council, Cornwall Council, Derby City Council, Eastbourne Borough Council, Elmbridge Borough Council, Environment First, FCC Environment, Guildford Borough Council, Harborough District Council, Huntingdonshire District Council, Lewes District Council, London Borough of Lewisham, Manchester City Council, Middlesbrough Council, Newcastle City Council, North Somerset Council, Nottingham City Council, Peterborough City Council, Richmondshire District Council, Rushmoor Borough Council, Sheffield City Council, South Cambridgeshire District Council, South Hams District Council, South Norfolk and Broadland Council, Stevenage Borough Council, Tewkesbury Borough Council, The Royal Borough of Kingston Council, Walsall Council, West Berkshire Council, West Devon Borough Council, Wiltshire Council | +| United Kingdom | Ashfield District Council, Bracknell Forest Council, Bradford Metropolitan District Council, Braintree District Council, Breckland Council, Cambridge City Council, Canterbury City Council, Cheshire East Council, Chesterfield Borough Council, City of York Council, Colchester Borough Council, Cornwall Council, Derby City Council, Eastbourne Borough Council, Elmbridge Borough Council, Environment First, FCC Environment, Guildford Borough Council, Harborough District Council, Huntingdonshire District Council, Lewes District Council, London Borough of Lewisham, Manchester City Council, Middlesbrough Council, Newcastle City Council, North Somerset Council, Nottingham City Council, Peterborough City Council, Richmondshire District Council, Rushmoor Borough Council, Salford City Council, Sheffield City Council, South Cambridgeshire District Council, South Hams District Council, South Norfolk and Broadland Council, Stevenage Borough Council, Tewkesbury Borough Council, The Royal Borough of Kingston Council, Walsall Council, West Berkshire Council, West Devon Borough Council, Wiltshire Council | | United States of America | City of Pittsburgh, Republic Services, Seattle Public Utilities | From 3a8a01ef876ea6ecf3df5765b4f9d368da0d39a5 Mon Sep 17 00:00:00 2001 From: Phil Harrison Date: Tue, 3 Jan 2023 20:01:26 +0000 Subject: [PATCH 119/127] Fixing README.md error --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4e92b304..2fec91c0 100644 --- a/README.md +++ b/README.md @@ -290,7 +290,7 @@ Waste collection schedules in the following formats and countries are supported. - [Peterborough City Council](/doc/source/peterborough_gov_uk.md) / peterborough.gov.uk - [Richmondshire District Council](/doc/source/richmondshire_gov_uk.md) / richmondshire.gov.uk - [Rushmoor Borough Council](/doc/source/rushmoor_gov_uk.md) / rushmoor.gov.uk -- [Manchester City Council](/doc/source/salford_uk.md) / salford.gov.uk +- [Salford City Council](/doc/source/salford_uk.md) / salford.gov.uk - [Sheffield City Council](/doc/source/sheffield_gov_uk.md) / sheffield.gov.uk - [South Cambridgeshire District Council](/doc/source/scambs_gov_uk.md) / scambs.gov.uk - [South Hams District Council](/doc/source/fccenvironment_co_uk.md) / southhams.gov.uk From 7551eba988fe55f9c40f68c8a5f0095aa0a64863 Mon Sep 17 00:00:00 2001 From: Phil Harrison Date: Tue, 3 Jan 2023 20:09:13 +0000 Subject: [PATCH 120/127] renamed source to salford_gov_uk --- doc/source/salford_gov_uk.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 doc/source/salford_gov_uk.md diff --git a/doc/source/salford_gov_uk.md b/doc/source/salford_gov_uk.md new file mode 100644 index 00000000..046bb370 --- /dev/null +++ b/doc/source/salford_gov_uk.md @@ -0,0 +1,36 @@ +# Salford City Council + +Support for schedules provided by [Salford City +Council](https://www.salford.gov.uk/bins-and-recycling/bin-collection-days/), serving the +city of Salford, UK. + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: salford_gov_uk + args: + uprn: UPRN_CODE +``` + +### Configuration Variables + +**uprn** +*(string) (required)* + +## Example + +```yaml +waste_collection_schedule: + sources: + - name: salford_gov_uk + args: + uprn: "100011404886" +``` + +## How to get the source argument + +The UPRN code can be found by entering your postcode on the +[Salford City Council bin collections days page +](https://www.salford.gov.uk/bins-and-recycling/bin-collection-days/). After selecting your address from the list and waiting for the new page to load, the uprn will be shown in the address bar. From 93b8a622dfe04fa99327371eb9ab0166695f6763 Mon Sep 17 00:00:00 2001 From: Phil Harrison Date: Tue, 3 Jan 2023 20:09:23 +0000 Subject: [PATCH 121/127] update --- README.md | 2 +- .../{salford_uk.py => salford_gov_uk.py} | 2 +- doc/source/salford_uk.md | 36 ------------------- 3 files changed, 2 insertions(+), 38 deletions(-) rename custom_components/waste_collection_schedule/waste_collection_schedule/source/{salford_uk.py => salford_gov_uk.py} (97%) delete mode 100644 doc/source/salford_uk.md diff --git a/README.md b/README.md index 2fec91c0..ba727c93 100644 --- a/README.md +++ b/README.md @@ -290,7 +290,7 @@ Waste collection schedules in the following formats and countries are supported. - [Peterborough City Council](/doc/source/peterborough_gov_uk.md) / peterborough.gov.uk - [Richmondshire District Council](/doc/source/richmondshire_gov_uk.md) / richmondshire.gov.uk - [Rushmoor Borough Council](/doc/source/rushmoor_gov_uk.md) / rushmoor.gov.uk -- [Salford City Council](/doc/source/salford_uk.md) / salford.gov.uk +- [Salford City Council](/doc/source/salford_gov_uk.md) / salford.gov.uk - [Sheffield City Council](/doc/source/sheffield_gov_uk.md) / sheffield.gov.uk - [South Cambridgeshire District Council](/doc/source/scambs_gov_uk.md) / scambs.gov.uk - [South Hams District Council](/doc/source/fccenvironment_co_uk.md) / southhams.gov.uk diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/salford_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/salford_gov_uk.py similarity index 97% rename from custom_components/waste_collection_schedule/waste_collection_schedule/source/salford_uk.py rename to custom_components/waste_collection_schedule/waste_collection_schedule/source/salford_gov_uk.py index 205c3db9..c5030460 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/salford_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/salford_gov_uk.py @@ -7,7 +7,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Salford City Council" DESCRIPTION = "Source for bin collection services for Salford City Council, UK." -URL = "https://www.Salford.gov.uk" +URL = "https://www.salford.gov.uk" TEST_CASES = { "domestic": {"uprn": "100011404886"}, } diff --git a/doc/source/salford_uk.md b/doc/source/salford_uk.md deleted file mode 100644 index 906eb1ee..00000000 --- a/doc/source/salford_uk.md +++ /dev/null @@ -1,36 +0,0 @@ -# Salford City Council - -Support for schedules provided by [Salford City -Council](https://www.salford.gov.uk/bins-and-recycling/bin-collection-days/), serving the -city of Salford, UK. - -## Configuration via configuration.yaml - -```yaml -waste_collection_schedule: - sources: - - name: salford_uk - args: - uprn: UPRN_CODE -``` - -### Configuration Variables - -**uprn** -*(string) (required)* - -## Example - -```yaml -waste_collection_schedule: - sources: - - name: salford_uk - args: - uprn: "100011404886" -``` - -## How to get the source argument - -The UPRN code can be found by entering your postcode on the -[Salford City Council bin collections days page -](https://www.salford.gov.uk/bins-and-recycling/bin-collection-days/). After selecting your address from the list and waiting for the new page to load, the uprn will be shown in the address bar. From c96bc6337b66849c42c59a987214004f39639cea Mon Sep 17 00:00:00 2001 From: Phil Harrison Date: Tue, 3 Jan 2023 20:10:58 +0000 Subject: [PATCH 122/127] adding newline to end of file --- .../waste_collection_schedule/source/salford_gov_uk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/salford_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/salford_gov_uk.py index c5030460..bc225407 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/salford_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/salford_gov_uk.py @@ -51,4 +51,4 @@ class Source: ) ) - return entries \ No newline at end of file + return entries From 08e713e95b3369cc6fff1ba62bba263f0792590d Mon Sep 17 00:00:00 2001 From: TheBlackMini Date: Wed, 4 Jan 2023 11:49:15 +1000 Subject: [PATCH 123/127] Bug: definition for street_address was incorrect --- .../waste_collection_schedule/source/goldcoast_qld_gov_au.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/goldcoast_qld_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/goldcoast_qld_gov_au.py index c034877f..3e049df6 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/goldcoast_qld_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/goldcoast_qld_gov_au.py @@ -24,7 +24,7 @@ ICON_MAP = { # Dict of waste types and suitable mdi icons } class Source: - def __init__(self, suburb, street_name, street_number): + def __init__(self, street_address): self._street_address = street_address def fetch(self): From 78dd1cbe32f52dda39cb774a9342f29a1b2ae3fe Mon Sep 17 00:00:00 2001 From: mampfes Date: Wed, 4 Jan 2023 07:36:28 +0100 Subject: [PATCH 124/127] refactor salford_gov_uk --- .../source/salford_gov_uk.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/salford_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/salford_gov_uk.py index bc225407..2ca44656 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/salford_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/salford_gov_uk.py @@ -27,15 +27,18 @@ class Source: self._uprn = uprn def fetch(self): - entries = [] - + params = {"UPRN": self._uprn} r = requests.get( - f"https://www.salford.gov.uk/bins-and-recycling/bin-collection-days/your-bin-collections/?UPRN={self._uprn}" + "https://www.salford.gov.uk/bins-and-recycling/bin-collection-days/your-bin-collections/", + params=params, ) + r.raise_for_status() soup = BeautifulSoup(r.text, features="html.parser") results = soup.find_all("div", {"class": "col-xs-12 col-md-6"}) + entries = [] + for result in results: dates = [] for date in result.find_all("li"): @@ -47,7 +50,7 @@ class Source: Collection( date=date, t=collection_type, - icon=ICON_MAP[collection_type], + icon=ICON_MAP.get(collection_type), ) ) From 9cc70af5064c50999738a723b93e398d07ce723e Mon Sep 17 00:00:00 2001 From: mampfes Date: Wed, 4 Jan 2023 08:58:02 +0100 Subject: [PATCH 125/127] add docu for ICS file fix #553 --- .../waste_collection_schedule/source/ics.py | 7 ++++++- doc/source/ics.md | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ics.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ics.py index 761a524b..afae579a 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ics.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ics.py @@ -1,5 +1,6 @@ import datetime import logging +from os import getcwd from pathlib import Path import requests @@ -202,7 +203,11 @@ class Source: return self._convert(r.text) def fetch_file(self, file): - f = open(file) + try: + f = open(file) + except FileNotFoundError as e: + _LOGGER.error(f"Working directory: '{getcwd()}'") + raise return self._convert(f.read()) def _convert(self, data): diff --git a/doc/source/ics.md b/doc/source/ics.md index 69f4bad3..a907e598 100644 --- a/doc/source/ics.md +++ b/doc/source/ics.md @@ -112,6 +112,22 @@ Local ICS / iCal file name. Can be used instead of `url` for local files. You have to specify either `url` or `file`! +Notes: + +- Some users have reported that on their installation, only local files below the folder `config/www` are accessible by the system. Therefore place the ics file their. +- If you are using relative paths (like in the example below), the path depends on which working directory your Home Assistant instance is running on. And this might depend on the installation method (core vs supervisor vs OS vs ...). Therefore check the log output, it tells you the current working directory. + + This example should work for HAOS based installations: + + ```yaml + # file location: config/www/calendar.ics + waste_collection_schedule: + sources: + - name: ics + args: + file: "www/calendar.ics" + ``` + **offset** *(int) (optional, default: `0`)* From 838152bf9d769401a863f379d7455664d6400d56 Mon Sep 17 00:00:00 2001 From: mampfes Date: Wed, 4 Jan 2023 09:01:46 +0100 Subject: [PATCH 126/127] fix link to manifest.json --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index ba727c93..c80b645a 100644 --- a/README.md +++ b/README.md @@ -371,8 +371,7 @@ If you can fix any of these, please raise a Pull Request with the updates. **Root Cause:** Home Assistant tries to install the required Python packages and fails somehow. This is not an issue of Waste Collection Schedule. -**Solution:** Try to reinstall Waste Collection Schedule (if you are using HACS) or install the required Python packages manually. This list of required packages can be found in [manifest.json](/custom_components/waste_collection_schedule/manifest.json) after the keyword `requirements`. -https://github.com/mampfes/hacs_waste_collection_schedule/master/custom_components/manifest.json#L5 +**Solution:** Try to reinstall Waste Collection Schedule (if you are using HACS) or install the required Python packages manually. This list of required packages can be found in [manifest.json](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/custom_components/waste_collection_schedule/manifest.json#L5). The actual procedure depends on your Home Assistant installation type. From 9abb60b512ae88cb0e769aedc6b5aaee4395466b Mon Sep 17 00:00:00 2001 From: mampfes Date: Wed, 4 Jan 2023 12:01:24 +0100 Subject: [PATCH 127/127] fix typo fix #553 --- README.md | 1 - doc/source/ics.md | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index c80b645a..b3ba8770 100644 --- a/README.md +++ b/README.md @@ -383,7 +383,6 @@ pip list pip install recurring_ical_events # in case recurring_ical_events is missing ``` - # Licence ![github licence](https://img.shields.io/badge/Licence-MIT-orange) diff --git a/doc/source/ics.md b/doc/source/ics.md index a907e598..987b3537 100644 --- a/doc/source/ics.md +++ b/doc/source/ics.md @@ -114,7 +114,7 @@ You have to specify either `url` or `file`! Notes: -- Some users have reported that on their installation, only local files below the folder `config/www` are accessible by the system. Therefore place the ics file their. +- Some users have reported that on their installation, only local files below the folder `config/www` are accessible by the system. Therefore place the ics file there. - If you are using relative paths (like in the example below), the path depends on which working directory your Home Assistant instance is running on. And this might depend on the installation method (core vs supervisor vs OS vs ...). Therefore check the log output, it tells you the current working directory. This example should work for HAOS based installations: