diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 189f06008..0f98fafe2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: hooks: - id: codespell args: - - --ignore-words-list=hass,alot,datas,dof,dur,farenheit,hist,iff,ines,ist,lightsensor,mut,nd,pres,referer,ser,serie,te,technik,ue,uint,visability,wan,wanna,withing,Adresse,termine,adresse,oder,alle,assistent,hart,marz,worthing,linz,celle,vor + - --ignore-words-list=hass,alot,datas,dof,dur,farenheit,hist,iff,ines,ist,lightsensor,mut,nd,pres,referer,ser,serie,te,technik,ue,uint,visability,wan,wanna,withing,Adresse,termine,adresse,oder,alle,assistent,hart,marz,worthing,linz,celle,vor,leibnitz - --skip="./.*,*.csv,*.json" - --quiet-level=2 exclude_types: [csv, json] diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..958b68fce --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,57 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Test All Sources", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/custom_components/waste_collection_schedule/waste_collection_schedule/test/test_sources.py", + "console": "integratedTerminal", + "args": [ + "-t" + ] + }, + { + "name": "Test Current Source (.py)", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/custom_components/waste_collection_schedule/waste_collection_schedule/test/test_sources.py", + "console": "integratedTerminal", + "args": [ + "-s", + "${fileBasenameNoExtension}", + "-l", + "-i", + "-t" + ] + }, + { + "name": "Test All ICS Sources", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/custom_components/waste_collection_schedule/waste_collection_schedule/test/test_sources.py", + "console": "integratedTerminal", + "args": [ + "-I", + "-t" + ] + }, + { + "name": "Test Current ICS Source (.yaml)", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/custom_components/waste_collection_schedule/waste_collection_schedule/test/test_sources.py", + "console": "integratedTerminal", + "args": [ + "-y", + "${fileBasenameNoExtension}", + "-l", + "-i", + "-t" + ] + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 50c36b84f..100a9ba5f 100644 --- a/README.md +++ b/README.md @@ -260,6 +260,7 @@ Waste collection schedules in the following formats and countries are supported. - [Lackendorf](/doc/source/citiesapps_com.md) / lackendorf.at - [Langau](/doc/source/citiesapps_com.md) / langau.at - [Langenrohr](/doc/source/citiesapps_com.md) / langenrohr.gv.at +- [Leibnitz](/doc/source/citiesapps_com.md) / leibnitz.at - [Leithaprodersdorf](/doc/source/citiesapps_com.md) / leithaprodersdorf.at - [Lendorf](/doc/ics/muellapp_com.md) / muellapp.com - [Leoben](/doc/ics/muellapp_com.md) / muellapp.com @@ -510,6 +511,12 @@ Waste collection schedules in the following formats and countries are supported. - [RenoWeb](/doc/source/renoweb_dk.md) / renoweb.dk +
+Finland + +- [Kiertokapula Finland](/doc/source/kiertokapula_fi.md) / kiertokapula.fi +
+
France @@ -708,6 +715,7 @@ Waste collection schedules in the following formats and countries are supported. - [Heidelberg](/doc/ics/gipsprojekt_de.md) / heidelberg.de - [Heilbronn Entsorgungsbetriebe](/doc/source/heilbronn_de.md) / heilbronn.de - [Heinz-Entsorgung (Landkreis Freising)](/doc/ics/heinz_entsorgung_de.md) / heinz-entsorgung.de +- [Herten (durth-roos.de)](/doc/ics/herten_de.md) / herten.de - [Hohenlohekreis](/doc/source/app_abfallplus_de.md) / Abfall+ App: hokwaste - [Holtgast (MyMuell App)](/doc/source/jumomind_de.md) / mymuell.de - [HubertSchmid Recycling und Umweltschutz GmbH](/doc/source/api_hubert_schmid_de.md) / hschmid24.de/BlaueTonne @@ -772,6 +780,7 @@ Waste collection schedules in the following formats and countries are supported. - [Kreis Vechta](/doc/source/app_abfallplus_de.md) / Abfall+ App: awvapp - [Kreis Viersen](/doc/source/abfallnavi_de.md) / kreis-viersen.de - [Kreis Vorpommern-Rügen](/doc/source/app_abfallplus_de.md) / Abfall+ App: abfallappvorue +- [Kreis Waldshut](/doc/source/app_abfallplus_de.md) / Abfall+ App: abfallwecker - [Kreis Weißenburg-Gunzenhausen](/doc/source/app_abfallplus_de.md) / Abfall+ App: abfallappwug - [Kreis Wesermarsch](/doc/source/app_abfallplus_de.md) / Abfall+ App: abfallappgib - [Kreis Würzburg](/doc/source/app_abfallplus_de.md) / Abfall+ App: teamorange @@ -854,6 +863,7 @@ Waste collection schedules in the following formats and countries are supported. - [Landratsamt Bodenseekreis](/doc/ics/bodenseekreis_de.md) / bodenseekreis.de - [Landratsamt Dachau](/doc/source/awido_de.md) / landratsamt-dachau.de - [Landratsamt Main-Tauber-Kreis](/doc/source/c_trace_de.md) / main-tauber-kreis.de +- [Landratsamt Regensburg](/doc/source/awido_de.md) / landkreis-regensburg.de - [Landratsamt Traunstein](/doc/source/abfall_io.md) / traunstein.com - [Landratsamt Unterallgäu](/doc/source/abfall_io.md) / landratsamt-unterallgaeu.de - [Landshut](/doc/source/app_abfallplus_de.md) / Abfall+ App: abfallappla @@ -972,11 +982,10 @@ Waste collection schedules in the following formats and countries are supported. - [VIVO Landkreis Miesbach](/doc/source/abfall_io.md) / vivowarngau.de - [Volkmarsen (MyMuell App)](/doc/source/jumomind_de.md) / mymuell.de - [Vöhringen (MyMuell App)](/doc/source/jumomind_de.md) / mymuell.de -- [Waldshut](/doc/source/app_abfallplus_de.md) / Abfall+ App: abfallwecker - [Waldshut](/doc/source/app_abfallplus_de.md) / Abfall+ App: unterallgaeu - [WBO Wirtschaftsbetriebe Oberhausen](/doc/source/abfallnavi_de.md) / wbo-online.de - [Wegberg (MyMuell App)](/doc/source/jumomind_de.md) / mymuell.de -- [Wermelskirchen](/doc/source/wermelskirchen_de.md) / wermelskirchen.de +- [Wermelskirchen (Service Down)](/doc/source/wermelskirchen_de.md) / wermelskirchen.de - [Westerholt (MyMuell App)](/doc/source/jumomind_de.md) / mymuell.de - [Westerwaldkreis](/doc/source/app_abfallplus_de.md) / Abfall+ App: wabapp - [WGV Recycling GmbH](/doc/source/awido_de.md) / wgv-quarzbichl.de @@ -1203,18 +1212,22 @@ Waste collection schedules in the following formats and countries are supported. - [BCP Council](/doc/source/bcp_gov_uk.md) / bcpcouncil.gov.uk - [Bedford Borough Council](/doc/source/bedford_gov_uk.md) / bedford.gov.uk - [Binzone](/doc/source/binzone_uk.md) / southoxon.gov.uk +- [Birmingham City Council](/doc/source/birmingham_gov_uk.md) / birmingham.gov.uk - [Blackburn with Darwen Borough Council](/doc/source/blackburn_gov_uk.md) / blackburn.gov.uk - [Blackpool Council](/doc/source/blackpool_gov_uk.md) / blackpool.gov.uk - [Borough Council of King's Lynn & West Norfolk](/doc/source/west_norfolk_gov_uk.md) / west-norfolk.gov.uk +- [Borough of Broxbourne Council](/doc/source/broxbourne_gov_uk.md) / broxbourne.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 - [Breckland Council](/doc/source/breckland_gov_uk.md) / breckland.gov.uk/mybreckland - [Bristol City Council](/doc/source/bristol_gov_uk.md) / bristol.gov.uk - [Broadland District Council](/doc/source/south_norfolk_and_broadland_gov_uk.md) / area.southnorfolkandbroadland.gov.uk +- [Bromsgrove City Council](/doc/source/bromsgrove_gov_uk.md) / bromsgrove.gov.uk - [Broxtowe Borough Council](/doc/source/broxtowe_gov_uk.md) / broxtowe.gov.uk - [Buckinghamshire Waste Collection - Former Chiltern, South Bucks or Wycombe areas](/doc/source/chiltern_gov_uk.md) / chiltern.gov.uk - [Burnley Council](/doc/source/burnley_gov_uk.md) / burnley.gov.uk +- [Bury Council](/doc/source/bury_gov_uk.md) / bury.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 - [Cardiff Council](/doc/source/cardiff_gov_uk.md) / cardiff.gov.uk @@ -1236,6 +1249,7 @@ Waste collection schedules in the following formats and countries are supported. - [Derby City Council](/doc/source/derby_gov_uk.md) / derby.gov.uk - [Dudley Metropolitan Borough Council](/doc/source/dudley_gov_uk.md) / dudley.gov.uk - [Durham County Council](/doc/source/durham_gov_uk.md) / durham.gov.uk +- [East Ayrshire Council](/doc/source/east_ayrshire_gov_uk.md) / east-ayrshire.gov.uk - [East Cambridgeshire District Council](/doc/source/eastcambs_gov_uk.md) / eastcambs.gov.uk - [East Devon District Council](/doc/source/eastdevon_gov_uk.md) / eastdevon.gov.uk - [East Herts Council](/doc/source/eastherts_gov_uk.md) / eastherts.gov.uk @@ -1255,6 +1269,7 @@ Waste collection schedules in the following formats and countries are supported. - [Flintshire](/doc/source/flintshire_gov_uk.md) / flintshire.gov.uk - [Fylde Council](/doc/source/fylde_gov_uk.md) / fylde.gov.uk - [Gateshead Council](/doc/source/gateshead_gov_uk.md) / gateshead.gov.uk +- [Gedling Borough Council (unofficial)](/doc/ics/gedling_gov_uk.md) / github.com/jamesmacwhite/gedling-borough-council-bin-calendars - [Glasgow City Council](/doc/source/glasgow_gov_uk.md) / glasgow.gov.uk - [Guildford Borough Council](/doc/source/guildford_gov_uk.md) / guildford.gov.uk - [Gwynedd](/doc/source/gwynedd_gov_uk.md) / gwynedd.gov.uk @@ -1296,6 +1311,7 @@ Waste collection schedules in the following formats and countries are supported. - [Newcastle City Council](/doc/source/newcastle_gov_uk.md) / community.newcastle.gov.uk - [Newcastle Under Lyme Borough Council](/doc/source/newcastle_staffs_gov_uk.md) / newcastle-staffs.gov.uk - [Newport City Council](/doc/source/newport_gov_uk.md) / newport.gov.uk +- [North Ayrshire Council](/doc/source/north_ayrshire_gov_uk.md) / north-ayrshire.gov.uk - [North Herts Council](/doc/source/northherts_gov_uk.md) / north-herts.gov.uk - [North Kesteven District Council](/doc/source/north_kesteven_org_uk.md) / n-kesteven.org.uk - [North Lincolnshire Council](/doc/source/northlincs_gov_uk.md) / northlincs.gov.uk @@ -1313,6 +1329,7 @@ Waste collection schedules in the following formats and countries are supported. - [Reading Council](/doc/source/reading_gov_uk.md) / reading.gov.uk - [Redbridge Council](/doc/source/redbridge_gov_uk.md) / redbridge.gov.uk - [Reigate & Banstead Borough Council](/doc/source/reigatebanstead_gov_uk.md) / reigate-banstead.gov.uk +- [Renfrewshire Council](/doc/source/renfrewshire_gov_uk.md) / renfrewshire.gov.uk - [Rhondda Cynon Taf County Borough Council](/doc/source/rctcbc_gov_uk.md) / rctcbc.gov.uk - [Richmondshire District Council](/doc/source/richmondshire_gov_uk.md) / richmondshire.gov.uk - [Rotherham Metropolitan Borough Council](/doc/source/rotherham_gov_uk.md) / rotherham.gov.uk @@ -1383,6 +1400,7 @@ Waste collection schedules in the following formats and countries are supported. United States of America - [Albuquerque, New Mexico, USA](/doc/source/recyclecoach_com.md) / recyclecoach.com/cities/usa-nm-city-of-albuquerque +- [City of Austin, TX](/doc/ics/recollect.md) / austintexas.gov - [City of Bloomington](/doc/ics/recollect.md) / bloomington.in.gov - [City of Cambridge](/doc/ics/recollect.md) / cambridgema.gov - [City of Gastonia, NC](/doc/ics/recollect.md) / gastonianc.gov diff --git a/custom_components/waste_collection_schedule/config_flow.py b/custom_components/waste_collection_schedule/config_flow.py index f5df3e7b2..35a370d13 100644 --- a/custom_components/waste_collection_schedule/config_flow.py +++ b/custom_components/waste_collection_schedule/config_flow.py @@ -304,7 +304,7 @@ async def async_step_user(self, info): return self.async_show_form(step_id="user", data_schema=SCHEMA) - # Step 2: User selects country + # Step 2: User selects source async def async_step_source(self, info=None): sources = self._sources[self._country] sources_options = [SelectOptionDict(value="", label="")] + [ diff --git a/custom_components/waste_collection_schedule/sensor.py b/custom_components/waste_collection_schedule/sensor.py index 0a406c823..6bad4163b 100644 --- a/custom_components/waste_collection_schedule/sensor.py +++ b/custom_components/waste_collection_schedule/sensor.py @@ -126,7 +126,17 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= source_index = config[CONF_SOURCE_INDEX] if not isinstance(source_index, list): source_index = [source_index] - aggregator = CollectionAggregator([api.get_shell(i) for i in source_index]) + + shells = [] + for i in source_index: + shell = api.get_shell(i) + if shell is None: + raise ValueError( + f"source_index {i} out of range (0-{len(api.shells) - 1}) please check your sensor configuration" + ) + shells.append(shell) + + aggregator = CollectionAggregator(shells) entities = [] 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 16f2b9105..548784af9 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/collection.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/collection.py @@ -34,6 +34,10 @@ def picture(self): def set_picture(self, picture: str): self["picture"] = picture + def set_date(self, date: datetime.date): + self._date = date + self["date"] = date.isoformat() + class Collection(CollectionBase): def __init__( diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/service/AppAbfallplusDe.py b/custom_components/waste_collection_schedule/waste_collection_schedule/service/AppAbfallplusDe.py index 881b46e62..f9d19bdcb 100755 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/service/AppAbfallplusDe.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/service/AppAbfallplusDe.py @@ -153,7 +153,7 @@ "de.abfallwecker": [ "Rottweil", "Tuttlingen", - "Waldshut", + "Kreis Waldshut", "Prignitz", "Nordsachsen", ], @@ -352,6 +352,7 @@ def random_hex(length: int = 1) -> str: API_BASE = "https://app.abfallplus.de/{}" API_ASSISTANT = API_BASE.format("assistent/{}") # ignore: E501 USER_AGENT = "{}/9.1.0.0 iOS/17.5 Device/iPhone Screen/1170x2532" +ABFALLARTEN_H2_SKIP = ["Sondermüll"] def extract_onclicks( @@ -435,17 +436,12 @@ def _request( method="post", headers=None, ): - if headers: - headers["User-Agent"] = USER_AGENT.format( - MAP_APP_USERAGENTS.get(self._app_id, "%") - ) + if headers is None: + headers = {} - else: - headers = { - "User-Agent": USER_AGENT.format( - MAP_APP_USERAGENTS.get(self._app_id, "%") - ) - } + headers["User-Agent"] = USER_AGENT.format( + MAP_APP_USERAGENTS.get(self._app_id, "%") + ) if method not in ("get", "post"): raise Exception(f"Method {method} not supported.") @@ -778,6 +774,25 @@ def select_all_waste_types(self): r.raise_for_status() soup = BeautifulSoup(r.text, features="html.parser") self._f_id_abfallart = [] + for to_skip in ABFALLARTEN_H2_SKIP: + to_skip_element = soup.find("h2", text=to_skip) + div_to_skip = ( + to_skip_element.find_parent("div") if to_skip_element else None + ) + if div_to_skip: + for input in to_skip_element.find_parent("div").find_all( + "input", {"name": "f_id_abfallart[]"} + ): + if compare(input.text, self._region_search, remove_space=True): + id = input.attrs["id"].split("_")[-1] + self._f_id_abfallart.append(input.attrs["value"]) + self._needs_subtitle.append(id) + if id.isdigit(): + self._needs_subtitle.append(str(int(id) - 1)) + break + # remove sondermuell h2 from soup + div_to_skip.decompose() + for input in soup.find_all("input", {"name": "f_id_abfallart[]"}): if input.attrs["value"] == "0": if "id" not in input.attrs: @@ -790,6 +805,7 @@ def select_all_waste_types(self): continue self._f_id_abfallart.append(input.attrs["value"]) + self._f_id_abfallart = list(set(self._f_id_abfallart)) self._needs_subtitle = list(set(self._needs_subtitle)) def validate(self): diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/service/CitiesAppsCom.py b/custom_components/waste_collection_schedule/waste_collection_schedule/service/CitiesAppsCom.py index 686071d28..5a1e01580 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/service/CitiesAppsCom.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/service/CitiesAppsCom.py @@ -236,6 +236,7 @@ {"title": "Lackendorf", "url": "https://www.lackendorf.at", "country": "at"}, {"title": "Langau", "url": "http://www.langau.at", "country": "at"}, {"title": "Langenrohr", "url": "https://www.langenrohr.gv.at", "country": "at"}, + {"title": "Leibnitz", "url": "https://www.leibnitz.at", "country": "at"}, { "title": "Leithaprodersdorf", "url": "http://www.leithaprodersdorf.at", diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/service/Samiljo_se_wastetype_searcher.py b/custom_components/waste_collection_schedule/waste_collection_schedule/service/Samiljo_se_wastetype_searcher.py index 8eea9deb1..76c907502 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/service/Samiljo_se_wastetype_searcher.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/service/Samiljo_se_wastetype_searcher.py @@ -3,8 +3,6 @@ import time import requests from bs4 import BeautifulSoup -#import threading -import sys from requests.exceptions import HTTPError from http import HTTPStatus import argparse diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/aberdeenshire_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/aberdeenshire_gov_uk.py index fae0512d3..2aa8f05cb 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/aberdeenshire_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/aberdeenshire_gov_uk.py @@ -1,5 +1,3 @@ -import requests - from bs4 import BeautifulSoup from datetime import datetime from waste_collection_schedule import Collection # type: ignore[attr-defined] diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfallwirtschaft_vechta_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfallwirtschaft_vechta_de.py index ad523f5ed..7f4114b68 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfallwirtschaft_vechta_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfallwirtschaft_vechta_de.py @@ -28,7 +28,6 @@ "Glass": "mdi:bottle-soda", "Bioabfall": "mdi:leaf", "Altpapier": "mdi:package-variant", - "Altpapier": "mdi:package-variant", "Altpapier Siemer": "mdi:package-variant", "Altpapier Pamo": "mdi:package-variant", "Gelbe Tonne": "mdi:recycle", 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 a6902173d..99b62ce83 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 @@ -1,5 +1,6 @@ import datetime import json +import urllib3 import pytz import requests @@ -9,9 +10,9 @@ # 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() +# This line suppresses the InsecureRequestWarning when using verify=False +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + TITLE = "Abfallwirtschaft Landkreis Wolfenbüttel" DESCRIPTION = "Source for ALW Wolfenbüttel." diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/app_abfallplus_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/app_abfallplus_de.py index 643002fd6..7b8d8b50d 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/app_abfallplus_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/app_abfallplus_de.py @@ -7,6 +7,11 @@ DESCRIPTION = "Source for Apps by Abfall+." URL = "https://www.abfallplus.de/" TEST_CASES = { + "de.k4systems.abfallappnf Ahrenviöl alle Straßen": { + "app_id": "de.k4systems.abfallappnf", + "city": "Ahrenviöl", + "strasse": "Alle Straßen", + }, "de.albagroup.app Braunschweig Hauptstraße 7A ": { "app_id": "de.albagroup.app", "city": "Braunschweig", diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ashford_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ashford_gov_uk.py index 7458e8f30..2418f7197 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ashford_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ashford_gov_uk.py @@ -108,11 +108,17 @@ def fetch(self): date_soup = bin_text.find( "span", id=re.compile(r"CollectionDayLookup2_Label_\w*_Date") ) - if not date_soup or " " not in date_soup.text.strip(): + if not date_soup or ( + " " not in date_soup.text.strip() + and date_soup.text.strip().lower() != "today" + ): continue date_str: str = date_soup.text.strip() try: - date = datetime.strptime(date_str.split(" ")[1], "%d/%m/%Y").date() + if date_soup.text.strip().lower() == "today": + date = datetime.now().date() + else: + date = datetime.strptime(date_str.split(" ")[1], "%d/%m/%Y").date() except ValueError: continue 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 18a963e31..3eea8de27 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 @@ -44,7 +44,7 @@ def fetch(self): ics_urls.append(href) if not ics_urls: - raise Exception(f"ics url not found") + raise Exception("ics url not found") entries = [] for ics_url in ics_urls: 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 945deadb5..ad39cb47f 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 @@ -222,6 +222,11 @@ def EXTRA_INFO(): "url": "https://www.landratsamt-roth.de/", "service_id": "roth", }, + { + "title": "Landratsamt Regensburg", + "url": "https://www.landkreis-regensburg.de/", + "service_id": "lra-regensburg", + }, ] 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 5f596d386..74ce0705c 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 @@ -1,6 +1,7 @@ +import requests +import urllib3 from html.parser import HTMLParser -import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS @@ -8,9 +9,8 @@ # 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() +# This line suppresses the InsecureRequestWarning when using verify=False +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) TITLE = "Abfallwirtschaft Neckar-Odenwald-Kreis" diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/basingstoke_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/basingstoke_gov_uk.py index 37101bcf1..09638e3f5 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/basingstoke_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/basingstoke_gov_uk.py @@ -10,8 +10,9 @@ # 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 -urllib3.disable_warnings() +# This line suppresses the InsecureRequestWarning when using verify=False +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + TITLE = "Basingstoke and Deane Borough Council" DESCRIPTION = "Source for basingstoke.gov.uk services for Basingstoke and Deane Borough Council, UK." diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/bathnes_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bathnes_gov_uk.py index 9d6f68aba..e7f75e98a 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/bathnes_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bathnes_gov_uk.py @@ -1,7 +1,6 @@ -from datetime import date, datetime +from datetime import datetime from typing import List -import requests from waste_collection_schedule import Collection # Include work around for SSL UNSAFE_LEGACY_RENEGOTIATION_DISABLED error diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/bexley_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bexley_gov_uk.py index ccd024894..987bd0a74 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/bexley_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bexley_gov_uk.py @@ -34,16 +34,8 @@ def __init__(self, uprn): self._uprn = str(uprn).zfill(12) def fetch(self): - s = requests.Session() - # Set up session - timestamp = time_ns() // 1_000_000 # epoch time in milliseconds - session_request = s.get( - f"https://mybexley.bexley.gov.uk/apibroker/domain/mybexley.bexley.gov.uk?_={timestamp}", - headers=HEADERS, - ) - # This request gets the session ID sid_request = s.get( "https://mybexley.bexley.gov.uk/authapi/isauthenticated?uri=https%3A%2F%2Fmybexley.bexley.gov.uk%2Fservice%2FWhen_is_my_collection_day&hostname=mybexley.bexley.gov.uk&withCredentials=true", @@ -53,9 +45,9 @@ def fetch(self): sid = sid_data['auth-session'] # This request retrieves the schedule - timestamp = time_ns() // 1_000_000 # epoch time in milliseconds + timestamp = time_ns() // 1_000_000 # epoch time in milliseconds payload = { - "formValues": { "What is your address?": {"txtUPRN": {"value": self._uprn}}} + "formValues": {"What is your address?": {"txtUPRN": {"value": self._uprn}}} } schedule_request = s.post( f"https://mybexley.bexley.gov.uk/apibroker/runLookup?id=61320b2acf8a3&repeat_against=&noRetry=false&getOnlyTokens=undefined&log_id=&app_name=AF-Renderer::Self&_={timestamp}&sid={sid}", 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 0fd385367..91a61dc11 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 @@ -1,6 +1,5 @@ from html.parser import HTMLParser -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 diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/birmingham_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/birmingham_gov_uk.py new file mode 100644 index 000000000..7fb0de49f --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/birmingham_gov_uk.py @@ -0,0 +1,97 @@ +import re +from datetime import datetime + +import requests +from bs4 import BeautifulSoup +from dateutil.parser import parse +from dateutil.relativedelta import relativedelta +from waste_collection_schedule import Collection # type: ignore[attr-defined] + +TITLE = "Birmingham City Council" +DESCRIPTION = "Source for birmingham.gov.uk services for Birmingham, UK." +URL = "https://birmingham.gov.uk" +TEST_CASES = { + "Cherry Tree Croft": {"uprn": 100070321799, "postcode": "B27 6TF"}, + "Ludgate Loft Apartments": {"uprn": 10033389698, "postcode": "B3 1DW"}, + "Windermere Road": {"uprn": "100070566109", "postcode": "B13 9JP"}, + "Park Hill": {"uprn": "100070475114", "postcode": "B13 8DS"}, +} + +HEADERS = { + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36", +} + +API_URLS = { + "get_session": "https://www.birmingham.gov.uk/xfp/form/619", + "collection": "https://www.birmingham.gov.uk/xfp/form/619", +} +ICON_MAP = { + "Household Collection": "mdi:trash-can", + "Recycling Collection": "mdi:recycle", + "Green Recycling Chargeable Collections": "mdi:leaf", +} + + +class Source: + def __init__(self, uprn: str, postcode: str): + self._uprn = uprn + self._postcode = postcode + + def fetch(self): + entries: list[Collection] = [] + + session = requests.Session() + session.headers.update(HEADERS) + + token_response = session.get(API_URLS["get_session"]) + soup = BeautifulSoup(token_response.text, "html.parser") + token = soup.find("input", {"name": "__token"}).attrs["value"] + if not token: + raise ValueError( + "Could not parse CSRF Token from initial response. Won't be able to proceed." + ) + + form_data = { + "__token": token, + "page": "491", + "locale": "en_GB", + "q1f8ccce1d1e2f58649b4069712be6879a839233f_0_0": self._postcode, + "q1f8ccce1d1e2f58649b4069712be6879a839233f_1_0": self._uprn, + "next": "Next", + } + + collection_response = session.post(API_URLS["collection"], data=form_data) + + collection_soup = BeautifulSoup(collection_response.text, "html.parser") + + for table_row in collection_soup.find( + "table", class_="data-table" + ).tbody.find_all("tr"): + collection_type = table_row.contents[0].text + collection_next = table_row.contents[1].text + collection_date = re.findall(r"\(.*?\)", collection_next) + + if len(collection_date) != 1: + continue + + collection_date_obj = parse(re.sub("[()]", "", collection_date[0])).date() + + # since we only have the next collection day, if the parsed date is in the past, + # assume the day is instead next month + if collection_date_obj < datetime.now().date(): + collection_date_obj += relativedelta(months=1) + + entries.append( + Collection( + date=collection_date_obj, + t=collection_type, + icon=ICON_MAP.get(collection_type, "mdi:help"), + ) + ) + + if not entries: + raise ValueError( + "Could not get collections for the given combination of UPRN and Postcode." + ) + + return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/blackburn_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/blackburn_gov_uk.py index 49a94bea1..518462b71 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/blackburn_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/blackburn_gov_uk.py @@ -1,4 +1,5 @@ from datetime import datetime +import urllib3 from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.SSLError import get_legacy_session @@ -26,10 +27,8 @@ # 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() +# This line suppresses the InsecureRequestWarning when using verify=False +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) class Source: diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/bromsgrove_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bromsgrove_gov_uk.py new file mode 100644 index 000000000..47efc3641 --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bromsgrove_gov_uk.py @@ -0,0 +1,94 @@ +from datetime import datetime + +import requests +from bs4 import BeautifulSoup +from waste_collection_schedule import Collection # type: ignore[attr-defined] + +TITLE = "Bromsgrove City Council" +DESCRIPTION = "Source for bromsgrove.gov.uk services for Bromsgrove, UK." +URL = "https://bromsgrove.gov.uk" +TEST_CASES = { + "Shakespeare House": {"uprn": "10094552413", "postcode": "B61 8DA"}, + "The Lodge": {"uprn": 10000218025, "postcode": "B60 2AA"}, + "Ceader Lodge": {"uprn": 100120576392, "postcode": "B60 2JS"}, + "Finstall Road": {"uprn": 100120571971, "postcode": "B60 3DE"}, +} + +HEADERS = { + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36", +} + +API_URLS = { + "collection": "https://bincollections.bromsgrove.gov.uk/BinCollections/Details/", +} +ICON_MAP = { + "Grey": "mdi:trash-can", + "Green": "mdi:recycle", + "Brown": "mdi:leaf", +} + + +class Source: + def __init__(self, uprn: str, postcode: str): + self._uprn = uprn + self._postcode = "".join(postcode.split()).upper() + + def fetch(self): + entries: list[Collection] = [] + + session = requests.Session() + session.headers.update(HEADERS) + + form_data = {"UPRN": self._uprn} + + collection_response = session.post(API_URLS["collection"], data=form_data) + + # Parse HTML + soup = BeautifulSoup(collection_response.text, "html.parser") + + # Find postcode + postcode = "".join(soup.find("h3").text.split()[-2:]).upper() + + # Find bins and their collection details + bins = soup.find_all(class_="collection-container") + + # Initialize lists to store extracted information + bin_info = [] + + # Extract information for each bin + for bin in bins: + bin_name = bin.find(class_="heading").text.strip() + bin_color = bin.find("img")["alt"] + collection_dates = [] + collection_details = bin.find_all(class_="caption") + for detail in collection_details: + date_string = detail.text.split()[-3:] + collection_date = " ".join(date_string) + collection_dates.append( + datetime.strptime(collection_date, "%d %B %Y").date() + ) + bin_info.append( + { + "Bin Name": bin_name, + "Bin Color": bin_color, + "Collection Dates": collection_dates, + } + ) + + # Check if the postcode matches the one provided, otherwise don't fill in the output + if postcode == self._postcode: + for info in bin_info: + entries.append( + Collection( + date=info["Collection Dates"][0], + t=info["Bin Name"], + icon=ICON_MAP.get(info["Bin Color"], "mdi:help"), + ) + ) + + if not entries: + raise ValueError( + "Could not get collections for the given combination of UPRN and Postcode." + ) + + return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/broxbourne_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/broxbourne_gov_uk.py new file mode 100644 index 000000000..208047d9a --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/broxbourne_gov_uk.py @@ -0,0 +1,107 @@ +import datetime +import logging + +import requests +from bs4 import BeautifulSoup +from waste_collection_schedule import Collection # type: ignore[attr-defined] + +TITLE = "Borough of Broxbourne Council" +DESCRIPTION = "Source for broxbourne.gov.uk services for Broxbourne, UK." +URL = "https://www.broxbourne.gov.uk" +TEST_CASES = { + "Old School Cottage (Domestic Waste Only)": { + "uprn": "148040092", + "postcode": "EN10 7PX", + }, + "11 Park Road (All Services)": {"uprn": "148028240", "postcode": "EN11 8PU"}, + "11 Pulham Avenue (All Services)": {"uprn": 148024643, "postcode": "EN10 7TA"}, +} + +API_URLS = { + "get_session": "https://www.broxbourne.gov.uk/bin-collection-date", + "collection": "https://www.broxbourne.gov.uk/xfp/form/205", +} + +LOGGER = logging.getLogger(__name__) + +ICON_MAP = { + "Domestic": "mdi:trash-can", + "Recycling": "mdi:recycle", + "Green Waste": "mdi:leaf", + "Food": "mdi:food-apple", +} + + +class Source: + def __init__(self, uprn: str, postcode: str): + self._uprn = uprn + self._postcode = postcode + + def fetch(self): + entries: list[Collection] = [] + session = requests.Session() + + token_response = session.get(API_URLS["get_session"]) + soup = BeautifulSoup(token_response.text, "html.parser") + token = soup.find("input", {"name": "__token"}).attrs["value"] + if not token: + raise ValueError( + "Could not parse CSRF Token from initial response. Won't be able to proceed." + ) + + form_data = { + "__token": token, + "page": "490", + "locale": "en_GB", + "qacf7e570cf99fae4cb3a2e14d5a75fd0d6561058_0_0": self._postcode, + "qacf7e570cf99fae4cb3a2e14d5a75fd0d6561058_1_0": self._uprn, + "next": "Next", + } + + collection_response = session.post(API_URLS["collection"], data=form_data) + + collection_soup = BeautifulSoup(collection_response.text, "html.parser") + tr = collection_soup.findAll("tr") + + # The council API returns no year for the collections + # and so it needs to be calculated to format the date correctly + + today = datetime.date.today() + year = today.year + + for item in tr[1:]: # Ignore table header row + td = item.findAll("td") + waste_type = td[1].text.rstrip() + + # We need to replace characters due to encoding in form + collection_date_text = ( + td[0].text.split(" ")[0].replace("\xa0", " ") + " " + str(year) + ) + + try: + # Broxbourne give an empty date field where there is no collection + collection_date = datetime.datetime.strptime( + collection_date_text, "%a %d %B %Y" + ).date() + + except ValueError as e: + LOGGER.warning( + f"No date found for wastetype: {waste_type}. The date field in the table is empty or corrupted. Failed with error: {e}" + ) + continue + + # Calculate the year. As we only get collections a week in advance we can assume the current + # year unless the month is January in December where it will be next year + + if (collection_date.month == 1) and (today.month == 12): + collection_date = collection_date.replace(year=year + 1) + + entries.append( + Collection( + date=collection_date, + t=waste_type, + icon=ICON_MAP.get(waste_type), + ) + ) + + return entries 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 8df98ba39..28a250c3f 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 @@ -85,7 +85,7 @@ class Source: def __init__(self, abf_strasse, abf_hausnr): self._abf_strasse = abf_strasse self._abf_hausnr = abf_hausnr - self._ics = ICS(offset=1) + self._ics = ICS() def fetch(self): dates = [] diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/bury_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bury_gov_uk.py new file mode 100644 index 000000000..e761e8b97 --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/bury_gov_uk.py @@ -0,0 +1,111 @@ +import re +from datetime import datetime + +import requests +from waste_collection_schedule import Collection # type: ignore[attr-defined] + +TITLE = "Bury Council" +DESCRIPTION = "Source for bury.gov.uk services for Bury Council, UK." +URL = "https://bury.gov.uk" + +TEST_CASES = { + "Test_Address_001": {"postcode": "bl81dd", "address": "2 Oakwood Close"}, + "Test_Address_002": {"postcode": "bl8 2sg", "address": "9, BIRKDALE DRIVE"}, + "Test_Address_003": {"postcode": "BL8 3DG", "address": "18, slaidburn drive"}, + "Test_ID_001": {"id": 649158}, + "Test_ID_002": {"id": "593456"}, +} +ICON_MAP = { + "brown": "mdi:leaf", + "grey": "mdi:trash-can", + "green": "mdi:package-variant", + "blue": "mdi:bottle-soda-classic", +} +NAME_MAP = { + "brown": "Garden", + "grey": "General", + "green": "Paper/Cardboard", + "blue": "Plastic/Cans/Glass", +} +HEADERS = { + "Accept": "*/*", + "Accept-Language": "en-GB,en;q=0.9", + "Connection": "keep-alive", + "Ocp-Apim-Trace": "true", + "Origin": "https://bury.gov.uk", + "Referer": "https://bury.gov.uk", + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "cross-site", + "Sec-GPC": "1", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36", +} + + +class Source: + def __init__(self, postcode=None, address=None, id=None): + if id is None and (postcode is None or address is None): + raise ValueError("Postcode and address must be provided") + + self._id = str(id).zfill(6) if id is not None else None + self._postcode = postcode + self._address = address + + def compare_address(self, address) -> bool: + return ( + self._address.replace(",", "").replace(" ", "").upper() + == address.replace(",", "").replace(" ", "").upper() + ) + + def get_id(self, s): + url = "https://www.bury.gov.uk/app-services/getProperties" + params = {"postcode": self._postcode} + + r = s.get(url, headers=HEADERS, params=params) + r.raise_for_status() + data = r.json() + if data["error"] is True: + raise ValueError("Invalid postcode") + for item in data["response"]: + if self.compare_address(item["addressLine1"]): + self._id = item["id"] + break + if self._id is None: + raise ValueError("Invalid address") + + def fetch(self): + s = requests.Session() + if self._id is None: + self.get_id(s) + + # Retrieve the schedule + params = {"id": self._id} + response = s.get( + "https://www.bury.gov.uk/app-services/getPropertyById", + headers=HEADERS, + params=params, + ) + data = response.json() + + # Define a regular expression pattern to match ordinal suffixes + ordinal_suffix_pattern = r"(?<=\d)(?:st|nd|rd|th)" + + entries = [] + for bin_name, bin_info in data["response"]["bins"].items(): + # Remove the ordinal suffix from the date string + date_str_without_suffix = re.sub( + ordinal_suffix_pattern, "", bin_info["nextCollection"] + ) + + entries.append( + Collection( + date=datetime.strptime( + date_str_without_suffix, + "%A %d %B %Y", + ).date(), + t=NAME_MAP[bin_name], + icon=ICON_MAP.get(bin_name), + ) + ) + + return entries 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 929e1ab3e..f07affed3 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 @@ -1,6 +1,7 @@ import json import logging import requests +import urllib3 from datetime import datetime from waste_collection_schedule import Collection @@ -9,9 +10,8 @@ # 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() +# This line suppresses the InsecureRequestWarning when using verify=False +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) TITLE = "Chesterfield Borough Council" diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/cmcitymedia_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/cmcitymedia_de.py index b8bb412e5..a2b7163e7 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/cmcitymedia_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/cmcitymedia_de.py @@ -1,5 +1,4 @@ import datetime -import ssl import requests from waste_collection_schedule import Collection diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/denbighshire_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/denbighshire_gov_uk.py index 99ae0f171..0845340d1 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/denbighshire_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/denbighshire_gov_uk.py @@ -35,7 +35,7 @@ def fetch(self): message = json.loads(r.json()["message"]) entries = [] - print(message) + for type in ["Household", "Recycling", "Food"]: date_str = message[f"{type}Date"] date = datetime.strptime(date_str, "%A %d/%m/%Y").date() diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/dudley_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/dudley_gov_uk.py index 923135f89..8ca13e3a3 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/dudley_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/dudley_gov_uk.py @@ -75,8 +75,7 @@ def get_xmas_map(self, footer_panel) -> dict[date, date]: moved = self.check_date(moved.text, today, yr) moved_to = self.check_date(moved_to.text, today, yr) xmas_map[moved] = moved_to - except Exception as e: - print(e) + except Exception: continue return xmas_map diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/east_ayrshire_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/east_ayrshire_gov_uk.py new file mode 100644 index 000000000..d6bf7f398 --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/east_ayrshire_gov_uk.py @@ -0,0 +1,48 @@ +import requests +from bs4 import BeautifulSoup +from dateutil import parser +from waste_collection_schedule import Collection # type: ignore[attr-defined] + +TITLE = "East Ayrshire Council" +DESCRIPTION = "Source for east-ayrshire.gov.uk services for East Ayrshire" +URL = "https://www.east-ayrshire.gov.uk/" +API_URL = "https://www.east-ayrshire.gov.uk/Housing/RubbishAndRecycling/Collection-days/ViewYourRecyclingCalendar.aspx?r=" + +TEST_CASES = { + "Test_001": {"uprn": "127071649"}, + "Test_002": {"uprn": 127072649}, + "Test_003": {"uprn": 127072016}, +} + +ICON_MAP = { + "General waste bin": "mdi:trash-can", + "Garden waste bin": "mdi:leaf", + "Recycling trolley": "mdi:recycle", +} + + +class Source: + def __init__(self, uprn): + self._uprn = str(uprn) + + def fetch(self): + session = requests.Session() + return self.__get_bin_collection_info_page(session, self._uprn) + + def __get_bin_collection_info_page(self, session, uprn): + r = session.get(API_URL + uprn) + r.raise_for_status() + soup = BeautifulSoup(r.text, "html.parser") + bin_list = soup.find_all("time") + entries = [] + for bins in bin_list: + entries.append( + Collection( + date=parser.parse(bins["datetime"]).date(), + t=bins.select_one("span.ScheduleItem").get_text().strip(), + icon=ICON_MAP.get( + bins.select_one("span.ScheduleItem").get_text().strip() + ), + ) + ) + return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/east_northamptonshire_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/east_northamptonshire_gov_uk.py index f17bcb5d9..72892b9fb 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/east_northamptonshire_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/east_northamptonshire_gov_uk.py @@ -35,7 +35,6 @@ class Source: def __init__(self, uprn: str): self._uprn: str = uprn - print(self._uprn) def fetch(self): r = requests.get(API_URL.format(uprn=self._uprn)) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ekosystem_wroc_pl.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ekosystem_wroc_pl.py index ce99b51be..388362f22 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ekosystem_wroc_pl.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ekosystem_wroc_pl.py @@ -45,7 +45,7 @@ def fetch(self): entries = [] if PARAMS_NUMBER_PARAM_NAME not in calendar_data: - raise Exception(f"Error: parameter number not present in the url!") + raise Exception("Error: parameter number not present in the url!") for i in range(1, int(calendar_data[PARAMS_NUMBER_PARAM_NAME]) + 1): date_str = calendar_data[DATE_PARAM_FORMAT.format(i)] 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 744dee302..125711ac5 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 @@ -6,8 +6,14 @@ from dateutil import parser from waste_collection_schedule import Collection +# 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 +# This line suppresses the InsecureRequestWarning when using verify=False urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + TITLE = "FCC Environment" DESCRIPTION = """ Consolidated source for waste collection services for ~60 local authorities. diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/flintshire_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/flintshire_gov_uk.py index 6c8f6605a..fc6891572 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/flintshire_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/flintshire_gov_uk.py @@ -45,8 +45,8 @@ def fetch(self): cols = row.find_all("div") cols = list(map(lambda x: x.text.strip(), cols)) if len(cols) == 0 or not re.match(r"\d{2}/\d{2}/\d{4}", cols[0]): - print("Skipping row", row.find("div")) continue + date_str = cols[0] date = datetime.strptime(date_str, "%d/%m/%Y").date() diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/glasgow_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/glasgow_gov_uk.py index 1d2a0754d..8cfefa8c6 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/glasgow_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/glasgow_gov_uk.py @@ -13,7 +13,7 @@ "test 2 - flat": {"uprn": "906700335412"}, } -API_URL = "https://www.glasgow.gov.uk/forms/refuseandrecyclingcalendar/CollectionsCalendar.aspx?UPRN=" +API_URL = "https://onlineservices.glasgow.gov.uk/forms/RefuseAndRecyclingWebApplication/CollectionsCalendar.aspx?UPRN=" ICON_MAP = { "purple bins": "mdi:glass-fragile", "brown bins": "mdi:apple", diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/hawkesbury_nsw_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/hawkesbury_nsw_gov_au.py index e526c9136..ca49e4819 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/hawkesbury_nsw_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/hawkesbury_nsw_gov_au.py @@ -77,7 +77,6 @@ def get_data(self, bin_prefix: str, fields) -> list[Collection]: entries.append(Collection(dateStr.date(), name, ICON_MAP.get(name.upper()))) return entries - def fetch(self): # check address values are not abbreviated address = self._street diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/horowhenua_govt_nz.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/horowhenua_govt_nz.py index 23af20a63..7b1ccfdce 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/horowhenua_govt_nz.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/horowhenua_govt_nz.py @@ -64,7 +64,7 @@ def fetch(self): # 'collection' api call seems to require an ASP.Net_sessionID, so obtain the relevant cookie s = requests.Session() q = requote_uri(str(API_URLS["session"])) - r0 = s.get(q, headers = HEADERS) + s.get(q, headers = HEADERS) # Do initial address search address = "{} {} {} {}".format(self.street_number, self.street_name, self.town, self.post_code) 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 e209a01e2..442acad59 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 @@ -30,10 +30,6 @@ def fetch(self): ) # extract data from json - data = json.loads(r.text) - - entries = [] - collections = r.json()["collections"] entries = [] 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 df400af04..b0b207282 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 @@ -181,7 +181,6 @@ def fetch(self): return self.fetch_file(self._file) def fetch_url(self, url, params=None): - print(url) # get ics file if self._method == "GET": r = requests.get( @@ -195,7 +194,7 @@ def fetch_url(self, url, params=None): raise RuntimeError( "Error: unknown method to fetch URL, use GET or POST; got {self._method}" ) - print(r.text) + r.raise_for_status() if r.apparent_encoding == "UTF-8-SIG": diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/insert_it_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/insert_it_de.py index a367c2f60..53992afd8 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/insert_it_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/insert_it_de.py @@ -1,6 +1,5 @@ import json import requests -import urllib from datetime import datetime diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/karlsruhe_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/karlsruhe_de.py index ed3c21322..700b63ebd 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/karlsruhe_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/karlsruhe_de.py @@ -9,8 +9,9 @@ # 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 -urllib3.disable_warnings() +# This line suppresses the InsecureRequestWarning when using verify=False +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + TITLE = "City of Karlsruhe" DESCRIPTION = "Source for City of Karlsruhe." diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/kiertokapula_fi.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kiertokapula_fi.py new file mode 100644 index 000000000..a2ee0e126 --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kiertokapula_fi.py @@ -0,0 +1,110 @@ +import logging +from datetime import datetime + +import requests +from waste_collection_schedule import Collection # type: ignore[attr-defined] + +TITLE = "Kiertokapula Finland" +DESCRIPTION = "Schedule for kiertokapula FI" +URL = "https://www.kiertokapula.fi" +TEST_CASES = { + "Test1": { + "bill_number": "!secret kiertonkapula_fi_bill_number", + "password": "!secret kiertonkapula_fi_bill_password", + } +} +ICON_MAP = { + "SEK": "mdi:trash-can", + "MUO": "mdi:delete-variant", + "KAR": "mdi:package-variant", + "LAS": "mdi:glass-wine", + "MET": "mdi:tools", + "BIO": "mdi:leaf", +} +NAME_DEF = { + "SEK": "Sekajäte", + "MUO": "Muovi", + "KAR": "Kartonki", + "LAS": "Lasi", + "MET": "Metalli", + "BIO": "Bio", +} +API_URL = "https://asiakasnetti.kiertokapula.fi/kiertokapula" + +_LOGGER = logging.getLogger(__name__) + + +class Source: + def __init__( + self, + bill_number, + password, + ): + self._bill_number = bill_number + self._password = password + + def fetch(self): + session = requests.Session() + session.headers.update({"X-Requested-With": "XMLHttpRequest"}) + session.get(API_URL) + + # sign in + r = session.post( + API_URL + "/j_acegi_security_check?target=2", + data={ + "j_username": self._bill_number, + "j_password": self._password, + "remember-me": "false", + }, + ) + r.raise_for_status() + + # get customer info + + r = session.get(API_URL + "/secure/get_customer_datas.do") + r.raise_for_status() + data = r.json() + + entries = [] + + for estate in data.values(): + for customer in estate: + r = session.get( + API_URL + "/secure/get_services_by_customer_numbers.do", + params={"customerNumbers[]": customer["asiakasnro"]}, + ) + r.raise_for_status() + data = r.json() + for service in data: + if service["tariff"].get("productgroup", "PER") == "PER": + continue + next_date_str = None + if ( + "ASTSeurTyhj" in service + and service["ASTSeurTyhj"] is not None + and len(service["ASTSeurTyhj"]) > 0 + ): + next_date_str = service["ASTSeurTyhj"] + elif ( + "ASTNextDate" in service + and service["ASTNextDate"] is not None + and len(service["ASTNextDate"]) > 0 + ): + next_date_str = service["ASTNextDate"] + + if next_date_str is None: + continue + + next_date = datetime.strptime(next_date_str, "%Y-%m-%d").date() + entries.append( + Collection( + date=next_date, + t=service.get( + "ASTNimi", + NAME_DEF.get(service["tariff"]["productgroup"], "N/A"), + ), + icon=ICON_MAP.get(service["tariff"]["productgroup"]), + ) + ) + + return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/kingston_vic_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kingston_vic_gov_au.py index 58f4eb872..f8440efa5 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/kingston_vic_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kingston_vic_gov_au.py @@ -66,7 +66,7 @@ def fetch(self): # 'collection' api call seems to require an ASP.Net_sessionID, so obtain the relevant cookie s = requests.Session() q = requote_uri(str(API_URLS["session"])) - r0 = s.get(q, headers = HEADERS) + s.get(q, headers = HEADERS) # Do initial address search address = "{} {} {} {}".format(self.street_number, self.street_name, self.suburb, self.post_code) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/kuringgai_nsw_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kuringgai_nsw_gov_au.py index 301572e77..189a0ce00 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/kuringgai_nsw_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/kuringgai_nsw_gov_au.py @@ -84,7 +84,7 @@ def fetch(self): # 'collection' api call seems to require an ASP.Net_sessionID, so obtain the relevant cookie s = requests.Session() q = requote_uri(str(API_URLS["session"])) - r0 = s.get(q, headers = HEADERS) + s.get(q, headers = HEADERS) # Do initial address search address = "{} {}, {} NSW {}".format(self.street_number, self.street_name, self.suburb, self.post_code) 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 430afe528..ddcff38e6 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 @@ -34,8 +34,6 @@ def __init__(self, city: str = None, district: str = None): self._district = district def fetch(self): - now = datetime.datetime.now().date() - r = requests.get(API_URL, params={ "stadt": self._city, "ortsteil": self._district diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/lbbd_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/lbbd_gov_uk.py index 5e4f2fce6..434b746f5 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/lbbd_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/lbbd_gov_uk.py @@ -1,4 +1,3 @@ -import datetime import json import requests 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 b0e031936..33d2e79d7 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 @@ -1,5 +1,4 @@ import datetime -import logging import re import requests @@ -21,31 +20,30 @@ "collection": "https://lewisham.gov.uk/api/roundsinformation", } -URPN_DATA_ITEM = '{79b58e9a-0997-4f18-bb97-637fac570dd1}' +UPRN_DATA_ITEM = '{79b58e9a-0997-4f18-bb97-637fac570dd1}' REGEX = "(?PFood and garden waste|Recycling|Refuse).*?.*?>(?P.*?)<.*?\\\\t(?P[A-Za-z]*day).*?(?:

|[A-Za-z\\\\]*?(?P\d{2}/\d{2}/\d{4}))" -DAYS = ["MONDAY","TUESDAY","WEDNESDAY","THURSDAY","FRIDAY","SATURDAY","SUNDAY"] +DAYS = ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"] BINS = { "Refuse": { "icon": "mdi:trash-can", "alias": "Black Refuse" - }, + }, "Recycling": { "icon": "mdi:recycle", "alias": "Green Recycling" - }, + }, "Food": { "icon": "mdi:food-apple", "alias": "Grey Food" - }, + }, "Garden": { "icon": "mdi:leaf", "alias": "Brown Garden" - } + } } -#_LOGGER = logging.getLogger(__name__) class Source: def __init__(self, post_code=None, number=None, name=None, uprn=None): @@ -55,9 +53,8 @@ def __init__(self, post_code=None, number=None, name=None, uprn=None): self._uprn = uprn def fetch(self): - now = datetime.date.today() if not self._uprn: - + # look up the UPRN for the address p = {'postcodeOrStreet': self._post_code} r = requests.post(API_URLS["address_search"], params=p) @@ -77,7 +74,7 @@ def fetch(self): raise Exception(f"Could not find address {self._post_code} {self._number}{self._name}") p = { - 'item': URPN_DATA_ITEM, + 'item': UPRN_DATA_ITEM, 'uprn': self._uprn } r = requests.post(API_URLS["collection"], params=p) @@ -91,18 +88,18 @@ def fetch(self): for collection in collections: if collection[0].__contains__(' and '): - collections.append([collection[0].split(" and ",2)[0].title().replace(" Waste",""), collection[1], collection[2], collection[3]]) - collections.append([collection[0].split(" and ",2)[1].title().replace(" Waste",""), collection[1], collection[2], collection[3]]) + collections.append([collection[0].split(" and ", 2)[0].title().replace(" Waste", ""), collection[1], collection[2], collection[3]]) + collections.append([collection[0].split(" and ", 2)[1].title().replace(" Waste", ""), collection[1], collection[2], collection[3]]) else: if collection[3] != "": - nextDate = datetime.datetime.strptime(collection[3], "%d/%m/%Y").date() + next_date = datetime.datetime.strptime(collection[3], "%d/%m/%Y").date() elif collection[1] == "WEEKLY": - d = datetime.date.today(); - nextDate = d + datetime.timedelta((DAYS.index(collection[2].upper())+1 - d.isoweekday()) % 7) + d = datetime.date.today() + next_date = d + datetime.timedelta((DAYS.index(collection[2].upper())+1 - d.isoweekday()) % 7) entries.append( Collection( - date=nextDate, + date=next_date, t=BINS.get(collection[0])['alias'], icon=BINS.get(collection[0])['icon'] ) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/liverpool_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/liverpool_gov_uk.py index 0ee2fecae..f00a465d5 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/liverpool_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/liverpool_gov_uk.py @@ -1,5 +1,3 @@ -from datetime import datetime - import re import requests from bs4 import BeautifulSoup 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 32a915519..2a151438c 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 @@ -1,4 +1,3 @@ -import datetime import requests from waste_collection_schedule import Collection from waste_collection_schedule.service.ICS import ICS diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/lsr_nu.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/lsr_nu.py index 9831f68b8..2fbceee85 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/lsr_nu.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/lsr_nu.py @@ -1,4 +1,3 @@ -import json import logging from datetime import datetime diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/maidstone_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/maidstone_gov_uk.py index 703edf0dc..70fe80831 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/maidstone_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/maidstone_gov_uk.py @@ -1,8 +1,8 @@ import json -import requests - from datetime import datetime from time import time_ns + +import requests from waste_collection_schedule import Collection # type: ignore[attr-defined] # Many thanks to dt215git for their work on the Bexley version of this provider which helped me write this. @@ -11,83 +11,108 @@ DESCRIPTION = "Source for maidstone.gov.uk services for Maidstone Borough Council." URL = "https://maidstone.gov.uk" TEST_CASES = { - "Test_001": {"uprn": "10022892379"}, # has mutliple collections on same week per bin type - "Test_002": {"uprn": 10014307164}, # has duplicates of the same collection (two bins for this block of flats?) - "Test_003": {"uprn": "200003674881"} # has garden waste collection, at time of coding + "Test_001": { + "uprn": "10022892379" + }, # has multiple collections on same week per bin type + "Test_002": { + "uprn": 10014307164 + }, # has duplicates of the same collection (two bins for this block of flats?) + "Test_003": { + "uprn": "200003674881" + }, # has garden waste collection, at time of coding } HEADERS = { "user-agent": "Mozilla/5.0", } # map names and icons, maidstone group food recycling for both -BIN_MAP = { - "REFUSE": {"icon":"mdi:trash-can", "name": "Black bin and food"}, - "RECYCLING": {"icon":"mdi:recycle", "name": "Recycling bin and food"}, - "GARDEN": {"icon":"mdi:leaf", "name": "Garden bin"} +ICON_MAP = { + "clinical": "mdi:medical-bag", + "bulky": "mdi:sofa", + "residual": "mdi:trash-can", + "recycling": "mdi:recycle", + "garden": "mdi:leaf", + "food": "mdi:food", } class Source: def __init__(self, uprn): - #self._uprn = str(uprn).zfill(12) - self._uprn = str(uprn) + # self._uprn = str(uprn).zfill(12) + self._uprn = str(uprn).strip() def fetch(self): - s = requests.Session() # Set up session timestamp = time_ns() // 1_000_000 # epoch time in milliseconds - session_request = s.get( - f"https://self.maidstone.gov.uk/apibroker/domain/self.maidstone.gov.uk?_={timestamp}", + s.get( + f"https://my.maidstone.gov.uk/apibroker/domain/my.maidstone.gov.uk?_={timestamp}&sid=979631f89458fc974cc2aa69ebbd7996", headers=HEADERS, ) + timestamp = time_ns() // 1_000_000 # epoch time in milliseconds # This request gets the session ID sid_request = s.get( - "https://self.maidstone.gov.uk/authapi/isauthenticated?uri=https%3A%2F%2Fself.maidstone.gov.uk%2Fservice%2Fcheck_your_bin_day&hostname=self.maidstone.gov.uk&withCredentials=true", - headers=HEADERS + "https://my.maidstone.gov.uk/authapi/isauthenticated?uri=https%3A%2F%2Fmy.maidstone.gov.uk%2Fservice%2FFind-your-bin-day&hostname=my.maidstone.gov.uk&withCredentials=true", + headers=HEADERS, ) sid_data = sid_request.json() - sid = sid_data['auth-session'] + sid = sid_data["auth-session"] # This request retrieves the schedule - timestamp = time_ns() // 1_000_000 # epoch time in milliseconds + timestamp = time_ns() // 1_000_000 # epoch time in milliseconds payload = { - "formValues": { "Your collections": {"address": {"value" : self._uprn}, "uprn": {"value": self._uprn}}} + "formValues": { + "Lookup": { + "AddressData": {"value": self._uprn}, + "AddressUPRN": {"value": self._uprn}, + } + } } entries = [] + schedule_request = s.post( + f"https://my.maidstone.gov.uk/apibroker/runLookup?id=654b7b6478deb&repeat_against=&noRetry=true&getOnlyTokens=undefined&log_id=&app_name=AF-Renderer::Self&_={timestamp}&sid={sid}", + headers=HEADERS, + json=payload, + ) + rowdata = json.loads(schedule_request.content)["integration"]["transformed"][ + "rows_data" + ][self._uprn] + collections: dict[str, dict[str, list[datetime.date] | str]] = {} - # Extract bin types and next collection dates, for some reason unlike all others that use this service, you need to submit a bin type to get useful dates. - for bin in BIN_MAP.keys(): - # set seen dates - seen = [] + for key, value in rowdata.items(): + if ( + "NextCollectionDateMM" in key or "LastCollectionOriginalDateMM" in key + ) and value != "": + collection_key = key.split("_")[0] + if collection_key not in collections: + collections[collection_key] = {"dates": []} + collections[collection_key]["dates"].append( + datetime.strptime(value, "%d/%m/%Y").date() + ) + if "_Description" in key: + collection_key = key.split("_")[0] + if collection_key not in collections: + collections[collection_key] = {"dates": []} + collections[collection_key]["description"] = value - # create payload for bin type - payload = { - "formValues": { "Your collections": {"bin": {"value": bin}, "address": {"value" : self._uprn}, "uprn": {"value": self._uprn}}} - } - schedule_request = s.post( - f"https://self.maidstone.gov.uk/apibroker/runLookup?id=5c18dbdcb12cf&repeat_against=&noRetry=false&getOnlyTokens=undefined&log_id=&app_name=AF-Renderer::Self&_={timestamp}&sid={sid}", - headers=HEADERS, - json=payload + for key, collection in collections.items(): + bin = collection.get("description") or key + icon = ICON_MAP.get( + bin.lower() + .replace("domestic ", "") + .replace("communal ", "") + .replace("waste", "") + .strip() ) - rowdata = json.loads(schedule_request.content)['integration']['transformed']['rows_data'] - for item in rowdata: - collectionDate = rowdata[item]["Date"] - # need to dedupe as MBC seem to list the same collection twice for some places - if collectionDate not in seen: - entries.append( - Collection( - t=BIN_MAP[bin]['name'], - date=datetime.strptime( - collectionDate, "%d/%m/%Y" - ).date(), - icon=BIN_MAP.get(bin).get('icon'), - ) + for collectionDate in set(collection["dates"]): + entries.append( + Collection( + t=bin, + date=collectionDate, + icon=icon, ) - # add this date to seen so we don't use it again - seen.append(collectionDate) - - return entries \ No newline at end of file + ) + return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/maldon_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/maldon_gov_uk.py index 8f4b2216b..c98d3d5a1 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/maldon_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/maldon_gov_uk.py @@ -3,17 +3,17 @@ import requests from bs4 import BeautifulSoup -from waste_collection_schedule import Collection +from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Maldon District Council" -DESCRIPTION = ("Source for www.maldon.gov.uk services for Maldon, UK") +DESCRIPTION = "Source for www.maldon.gov.uk services for Maldon, UK" URL = "https://www.maldon.gov.uk/" TEST_CASES = { "test 1": {"uprn": "200000917928"}, - "test 2": {"uprn": "100091258454"}, + "test 2": {"uprn": 100091258454}, } API_URL = "https://maldon.suez.co.uk/maldon/ServiceSummary?uprn=" @@ -25,15 +25,15 @@ "Food": "mdi:food-apple", } + class Source: def __init__(self, uprn: str): self._uprn = uprn - def _extract_future_date(self, text): + def _extract_dates(self, text): # parse both dates and return the future one - dates = re.findall(r'\d{2}/\d{2}/\d{4}', text) - dates = [datetime.strptime(date, '%d/%m/%Y').date() for date in dates] - return max(dates) + dates = re.findall(r"\d{2}/\d{2}/\d{4}", text) + return [datetime.strptime(date, "%d/%m/%Y").date() for date in dates] def fetch(self): entries = [] @@ -51,15 +51,19 @@ def fetch(self): # check is a collection row title = collection.find("h2", {"class": "panel-title"}).text.strip() - if title == "Other Services" or "You are not currently subscribed" in collection.text: + if ( + title == "Other Services" + or "You are not currently subscribed" in collection.text + ): continue - entries.append( - Collection( - date=self._extract_future_date(collection.text), - t=title, - icon=ICON_MAP.get(title), + for date in self._extract_dates(collection.text): + entries.append( + Collection( + date=date, + t=title, + icon=ICON_MAP.get(title), + ) ) - ) return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/mansfield_vic_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/mansfield_vic_gov_au.py index 1b2d9fba7..30b35f80a 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/mansfield_vic_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/mansfield_vic_gov_au.py @@ -69,10 +69,13 @@ def fetch(self): for article in soup.find_all("article"): waste_type = article.h3.string icon = ICON_MAP.get(waste_type) - 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 = article.find(class_="next-service").string + if next_pickup is None: + continue + date_match = re.search(r"\d{1,2}\/\d{1,2}\/\d{4}", next_pickup) + if date_match: next_pickup_date = datetime.strptime( - next_pickup.split(sep=" ")[1], "%d/%m/%Y" + date_match.group(0), "%d/%m/%Y" ).date() entries.append( Collection(date=next_pickup_date, t=waste_type, icon=icon) diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/mojiodpadki_si.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/mojiodpadki_si.py index 5514d0ad4..fd14a7619 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/mojiodpadki_si.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/mojiodpadki_si.py @@ -61,7 +61,7 @@ def fetch(self): # response is in HTML - parse it soup = BeautifulSoup(body, "html.parser") - _LOGGER.debug(f"Parsed mojiodpadki.si response") + _LOGGER.debug("Parsed mojiodpadki.si response") # find years, months, dates and waste tags in all document tables year = datetime.date.today().year diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/movar_no.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/movar_no.py index acde67104..300ae6e78 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/movar_no.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/movar_no.py @@ -1,4 +1,3 @@ -import json from datetime import datetime import requests diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/north_ayrshire_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/north_ayrshire_gov_uk.py new file mode 100644 index 000000000..5682ff6e5 --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/north_ayrshire_gov_uk.py @@ -0,0 +1,85 @@ +import requests +from dateutil import parser +from waste_collection_schedule import Collection # type: ignore[attr-defined] + +TITLE = "North Ayrshire Council" +DESCRIPTION = "Source for north-ayrshire.gov.uk services for North Ayrshire" +URL = "https://www.north-ayrshire.gov.uk/" +API_URL = "https://www.maps.north-ayrshire.gov.uk/arcgis/rest/services/AGOL/YourLocationLive/MapServer/8/query?f=json&outFields=*&returnDistinctValues=true&returnGeometry=false&spatialRel=esriSpatialRelIntersects&where=UPRN%20%3D%20%27{0}%27" + +TEST_CASES = { + "Test_001": {"uprn": "126043248"}, + "Test_002": {"uprn": 126021147}, + "Test_003": {"uprn": 126091148}, +} + +ICON_MAP = { + "Grey": "mdi:trash-can", + "Brown": "mdi:leaf", + "Purple": "mdi:glass-fragile", + "Blue": "mdi:recycle", +} + + +class Source: + def __init__(self, uprn): + self._uprn = str(uprn) + + def fetch(self): + return self.__get_bin_collection_info_json(self._uprn) + + def __get_bin_collection_info_json(self, uprn): + r = requests.get(API_URL.format(uprn)) + bin_json = r.json()["features"] + bin_list = [] + if "BLUE_DATE_TEXT" in bin_json[0]["attributes"]: + bin_list.append( + [ + "Blue", + "/".join( + reversed(bin_json[0]["attributes"]["BLUE_DATE_TEXT"].split("/")) + ), + ] + ) + if "GREY_DATE_TEXT" in bin_json[0]["attributes"]: + bin_list.append( + [ + "Grey", + "/".join( + reversed(bin_json[0]["attributes"]["GREY_DATE_TEXT"].split("/")) + ), + ] + ) + if "PURPLE_DATE_TEXT" in bin_json[0]["attributes"]: + bin_list.append( + [ + "Purple", + "/".join( + reversed( + bin_json[0]["attributes"]["PURPLE_DATE_TEXT"].split("/") + ) + ), + ] + ) + if "BROWN_DATE_TEXT" in bin_json[0]["attributes"]: + bin_list.append( + [ + "Brown", + "/".join( + reversed( + bin_json[0]["attributes"]["BROWN_DATE_TEXT"].split("/") + ) + ), + ] + ) + + entries = [] + for bins in bin_list: + entries.append( + Collection( + date=parser.parse(bins[1]).date(), + t=bins[0], + icon=ICON_MAP.get(bins[0]), + ) + ) + return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/north_kesteven_org_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/north_kesteven_org_uk.py index 7f70a2def..9023af9ed 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/north_kesteven_org_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/north_kesteven_org_uk.py @@ -56,17 +56,16 @@ def fetch(self): date = date_li.text try: date = datetime.strptime(date.split(",")[1].strip(), "%d %B %Y").date() - except: - print("No date") + except Exception: continue entries.append( - Collection( - date=date, - t=bin_name, - icon=icon, - ) - ) + Collection( + date=date, + t=bin_name, + icon=icon, + ) + ) return entries \ No newline at end of file diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/northnorthants_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/northnorthants_gov_uk.py index 241973dd8..352a46982 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/northnorthants_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/northnorthants_gov_uk.py @@ -52,7 +52,6 @@ def fetch(self): headers = { "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64)", } - requests.packages.urllib3.disable_warnings() # Get variables for workings response = requests.get( 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 52002afb3..3e516f7b2 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 @@ -1,11 +1,8 @@ import requests -import urllib.parse import json import datetime -import re from waste_collection_schedule import Collection # type: ignore[attr-defined] -from pprint import pprint TITLE = "Oslo Kommune" DESCRIPTION = "Oslo Kommune (Norway)." 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 f48596679..dd1c33bce 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 @@ -1,4 +1,3 @@ -import logging import datetime import requests diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/portenf_sa_gov_au.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/portenf_sa_gov_au.py index 8740b5637..403a7f4a1 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/portenf_sa_gov_au.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/portenf_sa_gov_au.py @@ -11,8 +11,9 @@ # 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 -urllib3.disable_warnings() +# This line suppresses the InsecureRequestWarning when using verify=False +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + TITLE = "Port Adelaide Enfield, South Australia" DESCRIPTION = "Source for City of Port Adelaide Enfield, South Australia." diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/potsdam_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/potsdam_de.py index d5e8c8702..61f150e07 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/potsdam_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/potsdam_de.py @@ -2,7 +2,6 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] from datetime import datetime, timedelta -from dateutil import rrule import json TITLE = "Potsdam" diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/reading_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/reading_gov_uk.py index def4d61c3..cef048cf7 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/reading_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/reading_gov_uk.py @@ -1,4 +1,4 @@ -from datetime import date, datetime +from datetime import datetime from typing import List import requests diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/reigatebanstead_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/reigatebanstead_gov_uk.py index 1cf1c1545..e4eb290d2 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/reigatebanstead_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/reigatebanstead_gov_uk.py @@ -13,8 +13,8 @@ TEST_CASES = { "Test_001": {"uprn": 68110755}, "Test_002": {"uprn": "000068110755"}, - "Test_003": {"uprn": "68101147"}, #commercial refuse collection - "Test_004": {"uprn": "000068101147"}, #commercial refuse collection + "Test_003": {"uprn": "68101147"}, # commercial refuse collection + "Test_004": {"uprn": "000068101147"}, # commercial refuse collection } HEADERS = { "user-agent": "Mozilla/5.0", @@ -22,16 +22,17 @@ ICON_MAP = { "FOOD WASTE": "mdi:food", "MIXED RECYCLING": "mdi:recycle", - "GLASS": "mdi:recycle", #commercial - "MIXED CANS": "mdi:recycle", #commercial - "PLASTIC": "mdi:recycle", #commercial + "GLASS": "mdi:recycle", # commercial + "MIXED CANS": "mdi:recycle", # commercial + "PLASTIC": "mdi:recycle", # commercial "PAPER AND CARDBOARD": "mdi:newspaper", - "TRADE - PAPER AND CARDBOARD": "mdi:newspaper", #commercial + "TRADE - PAPER AND CARDBOARD": "mdi:newspaper", # commercial "REFUSE": "mdi:trash-can", - "TRADE - REFUSE": "mdi:trash-can", #commercial + "TRADE - REFUSE": "mdi:trash-can", # commercial "GARDEN WASTE": "mdi:leaf", } + class Source: def __init__(self, uprn): self._uprn = str(uprn) @@ -40,13 +41,6 @@ def fetch(self): s = requests.Session() - # Set up session - timestamp = time_ns() // 1_000_000 # epoch time in milliseconds - session_request = s.get( - f"https://my.reigate-banstead.gov.uk/apibroker/domain/my.reigate-banstead.gov.uk?_={timestamp}", - headers=HEADERS, - ) - # This request gets the session ID sid_request = s.get( "https://my.reigate-banstead.gov.uk/authapi/isauthenticated?uri=https%3A%2F%2Fmy.reigate-banstead.gov.uk%2Fservice%2FBins_and_recycling___collections_calendar&hostname=my.reigate-banstead.gov.uk&withCredentials=true", @@ -67,18 +61,28 @@ def fetch(self): # This request retrieves the schedule timestamp = time_ns() // 1_000_000 # epoch time in milliseconds - min_date = datetime.today().strftime("%Y-%m-%d") #today - max_date = datetime.today() + timedelta(days=28) # max of 28 days ahead + min_date = datetime.today().strftime("%Y-%m-%d") # today + max_date = datetime.today() + timedelta(days=28) # max of 28 days ahead max_date = max_date.strftime("%Y-%m-%d") payload = { - "formValues": { "Section 1": {"uprnPWB": {"value": self._uprn}, - "minDate": {"value": min_date}, - "maxDate": {"value": max_date}, - "tokenString": {"value": token_string}, - } - } - } + "formValues": { + "Section 1": { + "uprnPWB": { + "value": self._uprn + }, + "minDate": { + "value": min_date + }, + "maxDate": { + "value": max_date + }, + "tokenString": { + "value": token_string + }, + } + } + } schedule_request = s.post( f"https://my.reigate-banstead.gov.uk/apibroker/runLookup?id=609d41ca89251&repeat_against=&noRetry=true&getOnlyTokens=undefined&log_id=&app_name=AF-Renderer::Self&_={timestamp}&sid={sid}", @@ -94,20 +98,20 @@ def fetch(self): bindata = rowdata.findAll("ul") # Extract bin types and next collection dates - x=0 + x = 0 entries = [] for item in bindata: bin_date = datedata[x].text.strip() - x=x+1 + x = x+1 bins = item.findAll('span') - for bin in bins: - bin_type=bin.text.strip() + for bin_name in bins: + bin_type = bin_name.text.strip() entries.append( Collection( t=bin_type, date=datetime.strptime(bin_date, "%A %d %B %Y").date(), - icon=ICON_MAP.get(bin.text.upper()) + icon=ICON_MAP.get(bin_name.text.upper()) ) ) - return entries \ No newline at end of file + return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/renfrewshire_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/renfrewshire_gov_uk.py new file mode 100644 index 000000000..a7ab70bf1 --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/renfrewshire_gov_uk.py @@ -0,0 +1,98 @@ +from urllib.parse import parse_qs, urlparse + +import requests +from bs4 import BeautifulSoup +from dateutil import parser +from waste_collection_schedule import Collection # type: ignore[attr-defined] + +TITLE = "Renfrewshire Council" +DESCRIPTION = "Source for renfrewshire.gov.uk services for Renfrewshire" +URL = "https://renfrewshire.gov.uk/" +API_URL = "https://www.renfrewshire.gov.uk/article/2320/Check-your-bin-collection-day" + +TEST_CASES = { + "Test_001": {"postcode": "PA12 4JU", "uprn": 123033059}, + "Test_002": {"postcode": "PA12 4AJ", "uprn": "123034174"}, + "Test_003": {"postcode": "PA12 4EW", "uprn": "123033042"}, +} + +ICON_MAP = { + "Grey": "mdi:trash-can", + "Brown": "mdi:leaf", + "Green": "mdi:glass-fragile", + "Blue": "mdi:note", +} + + +class Source: + def __init__(self, postcode, uprn): + self._postcode = postcode + self._uprn = str(uprn) + + def fetch(self): + session = requests.Session() + bin_collection_info_page = self.__get_bin_collection_info_page( + session, self._uprn, self._postcode + ) + return self.__get_bin_collection_info(bin_collection_info_page) + + def __get_goss_form_ids(self, url): + parsed_form_url = urlparse(url) + form_url_values = parse_qs(parsed_form_url.query) + return { + "page_session_id": form_url_values["pageSessionId"][0], + "session_id": form_url_values["fsid"][0], + "nonce": form_url_values["fsn"][0], + } + + def __get_bin_collection_info_page(self, session, uprn, postcode): + r = session.get(API_URL) + r.raise_for_status() + soup = BeautifulSoup(r.text, "html.parser") + form = soup.find(id="RENFREWSHIREBINCOLLECTIONS_FORM") + goss_ids = self.__get_goss_form_ids(form["action"]) + r = session.post( + form["action"], + data={ + "RENFREWSHIREBINCOLLECTIONS_PAGESESSIONID": goss_ids["page_session_id"], + "RENFREWSHIREBINCOLLECTIONS_SESSIONID": goss_ids["session_id"], + "RENFREWSHIREBINCOLLECTIONS_NONCE": goss_ids["nonce"], + "RENFREWSHIREBINCOLLECTIONS_VARIABLES": "", + "RENFREWSHIREBINCOLLECTIONS_PAGENAME": "PAGE1", + "RENFREWSHIREBINCOLLECTIONS_PAGEINSTANCE": "0", + "RENFREWSHIREBINCOLLECTIONS_PAGE1_ADDRESSSTRING": "", + "RENFREWSHIREBINCOLLECTIONS_PAGE1_UPRN": uprn, + "RENFREWSHIREBINCOLLECTIONS_PAGE1_ADDRESSLOOKUPPOSTCODE": postcode, + "RENFREWSHIREBINCOLLECTIONS_PAGE1_NAVBUTTONS_NEXT": "Load Address", + }, + ) + r.raise_for_status() + return r.text + + def __get_bin_collection_info(self, binformation): + soup = BeautifulSoup(binformation, "html.parser") + all_collections = soup.select( + "#RENFREWSHIREBINCOLLECTIONS_PAGE1_COLLECTIONDETAILS" + ) + for collection in all_collections: + dates = collection.select("p.collection__date") + date_list = [] + bin_list = [] + for individualdate in dates: + date_list.append(parser.parse(individualdate.get_text()).date()) + bins = collection.select("p.bins__name") + for individualbin in bins: + bin_list.append(individualbin.get_text().strip()) + + schedule = list(zip(date_list, bin_list)) + + entries = [] + for sched_entry in schedule: + entries.append( + Collection( + date=sched_entry[0], + t=sched_entry[1], + icon=ICON_MAP.get(sched_entry[1]), + ) + ) + return entries 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 aed778fbb..eff1a1c25 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 @@ -1,13 +1,17 @@ import json -from datetime import datetime import requests - -# Suppress error messages relating to SSLCertVerificationError import urllib3 -urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) +from datetime import datetime 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 +# This line suppresses the InsecureRequestWarning when using verify=False +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + TITLE = "Stevenage Borough Council" DESCRIPTION = "Source for stevenage.gov.uk services for Stevenage, UK." 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 92e39285f..bb170b53e 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 @@ -14,7 +14,7 @@ "Deprecated postcode No Spaces": {"postcode": "GL205TT"}, } -DEPRICATED_API_URL = "https://api-2.tewkesbury.gov.uk/general/rounds/%s/nextCollection" +DEPRECATED_API_URL = "https://api-2.tewkesbury.gov.uk/general/rounds/%s/nextCollection" API_URL = "https://api-2.tewkesbury.gov.uk/incab/rounds/%s/next-collection" ICON_MAP = { @@ -29,23 +29,23 @@ class Source: def __init__(self, postcode: str | None = None, uprn: str | None = None): - self.urpn = str(uprn) if uprn is not None else None + self.uprn = str(uprn) if uprn is not None else None self.postcode = str(postcode) if postcode is not None else None def fetch(self): - if self.urpn is None: + if self.uprn is None: LOGGER.warning( "Using deprecated API might not work in the future. Please provide a UPRN." ) - return self.get_data(self.postcode, DEPRICATED_API_URL) - return self.get_data(self.urpn) + return self.get_data(self.postcode, DEPRECATED_API_URL) + return self.get_data(self.uprn) def get_data(self, uprn, api_url=API_URL): if uprn is None: raise Exception("UPRN not set") - encoded_urpn = urlquote(uprn) - request_url = api_url % encoded_urpn + encoded_uprn = urlquote(uprn) + request_url = api_url % encoded_uprn response = requests.get(request_url) response.raise_for_status() 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 2d35a2c82..9397d837b 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 @@ -14,7 +14,7 @@ "street": "Amanda Place", "houseNo": 10, }, - "Annangrove, Amanda Place 10": { + "Annangrove, Amanda Place 10 (2)": { "suburb": "ANn ANgROvE", "street": "amanda PlaC e", "houseNo": " 10 ", @@ -41,13 +41,13 @@ def fetch(self): suburbs[entry["Suburb"].strip().upper().replace(" ", "")] = entry["SuburbKey"] # check if suburb exists - suburb_searh = self._suburb.strip().upper().replace(" ", "") - if suburb_searh not in suburbs: + suburb_search = self._suburb.strip().upper().replace(" ", "") + if suburb_search not in suburbs: raise Exception(f"suburb not found: {self._suburb}") - suburbKey = suburbs[suburb_searh] + suburb_key = suburbs[suburb_search] # get list of streets for selected suburb - r = requests.get(f"{self._url}/streets/{suburbKey}") + r = requests.get(f"{self._url}/streets/{suburb_key}") data = json.loads(r.text) streets = {} @@ -58,30 +58,30 @@ def fetch(self): street_search = self._street.strip().upper().replace(" ", "") if street_search not in streets: raise Exception(f"street not found: {self._street}") - streetKey = streets[street_search] + street_key = streets[street_search] # get list of house numbers for selected street - params = {"streetkey": streetKey, "suburbKey": suburbKey} + params = {"streetkey": street_key, "suburbKey": suburb_key} r = requests.get( f"{self._url}/properties/GetPropertiesByStreetAndSuburbKey", params=params, ) data = json.loads(r.text) - houseNos = {} + house_numbers = {} for entry in data: - houseNos[ + house_numbers[ (str(int(entry["HouseNo"])) + entry.get("HouseSuffix", "").strip()).strip().upper().replace(" ", "") ] = entry["PropertyKey"] # check if house number exists houseNo_search = self._houseNo.strip().upper().replace(" ", "") - if houseNo_search not in houseNos: + if houseNo_search not in house_numbers: raise Exception(f"house number not found: {self._houseNo}") - propertyKey = houseNos[houseNo_search] + property_key = house_numbers[houseNo_search] # get collection schedule - r = requests.get(f"{self._url}/services/{propertyKey}") + r = requests.get(f"{self._url}/services/{property_key}") data = json.loads(r.text) entries = [] 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 64c12b5e9..62bb25ad5 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,7 +4,7 @@ from waste_collection_schedule import Collection # type: ignore[attr-defined] from waste_collection_schedule.service.ICS import ICS -TITLE = "Wermelskirchen" +TITLE = "Wermelskirchen (Service Down)" DESCRIPTION = "Source for Abfallabholung Wermelskirchen, Germany" URL = "https://www.wermelskirchen.de" TEST_CASES = { diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/west_norfolk_gov_uk.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/west_norfolk_gov_uk.py index 90c1a9410..86ec53664 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/west_norfolk_gov_uk.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/west_norfolk_gov_uk.py @@ -3,7 +3,7 @@ from bs4 import BeautifulSoup from datetime import datetime -from waste_collection_schedule import Collection +from waste_collection_schedule import Collection # type: ignore[attr-defined] TITLE = "Borough Council of King's Lynn & West Norfolk" @@ -24,6 +24,7 @@ "GARDEN": "mdi:leaf" } + class Source: def __init__(self, uprn): self._uprn = str(uprn).zfill(12) @@ -32,7 +33,7 @@ def fetch(self): # Get session and amend cookies s = requests.Session() - r0 = s.get( + s.get( "https://www.west-norfolk.gov.uk/info/20174/bins_and_recycling_collection_dates", headers=HEADERS ) @@ -44,7 +45,7 @@ def fetch(self): ) # Get initial collection dates using updated cookies - r1= s.get( + s.get( "https://www.west-norfolk.gov.uk/info/20174/bins_and_recycling_collection_dates", headers=HEADERS, cookies=s.cookies @@ -70,10 +71,10 @@ def fetch(self): dt = d.text + " " + month.text entries.append( Collection( - date = datetime.strptime(dt, "%d %B %Y").date(), - t = a, - icon = ICON_MAP.get(a.upper()) + date=datetime.strptime(dt, "%d %B %Y").date(), + t=a, + icon=ICON_MAP.get(a.upper()) ) ) - return entries \ No newline at end of file + return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/zva_sek_de.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/zva_sek_de.py index a6b6323d8..491c6a222 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/zva_sek_de.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/zva_sek_de.py @@ -74,7 +74,7 @@ def fetch(self): break if not bezirk_id: - raise Exception(f"bezirk not found") + raise Exception("bezirk not found") # get ortsteil id r = session.get(API_URL.format( @@ -91,7 +91,7 @@ def fetch(self): last_orts_id = part.split(" = ")[1][1:-1] if not ortsteil_id: - raise Exception(f"ortsteil not found") + raise Exception("ortsteil not found") street_id = None @@ -111,13 +111,13 @@ def fetch(self): last_street_id = part.split(" = ")[1][1:-1] if not street_id: - raise Exception(f"street not found") + raise Exception("street not found") entries = self.get_calendar_data(year, bezirk_id, ortsteil_id, street_id, session) if datetime.now().month >= 11: try: entries += self.get_calendar_data(year+1, bezirk_id, ortsteil_id, street_id, session) - except Exception as e: + except Exception: pass return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source_shell.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source_shell.py index 6f53c5d1a..dcb3f537d 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/source_shell.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source_shell.py @@ -82,6 +82,11 @@ def customize_function(entry: Collection, customize: Dict[str, Customize]): return entry +def apply_day_offset(entry: Collection, day_offset: int): + entry.set_date(entry.date + datetime.timedelta(days=day_offset)) + return entry + + class SourceShell: def __init__( self, @@ -92,6 +97,7 @@ def __init__( url: Optional[str], calendar_title: Optional[str], unique_id: str, + day_offset: int, ): self._source = source self._customize = customize @@ -102,6 +108,7 @@ def __init__( self._unique_id = unique_id self._refreshtime = None self._entries: List[Collection] = [] + self._day_offset = day_offset @property def refreshtime(self): @@ -127,6 +134,10 @@ def calendar_title(self): def unique_id(self): return self._unique_id + @property + def day_offset(self): + return self._day_offset + def fetch(self): """Fetch data from source.""" try: @@ -149,6 +160,10 @@ def fetch(self): # customize fetched entries entries = map(lambda x: customize_function(x, self._customize), entries) + # apply day offset + if self._day_offset != 0: + entries = map(lambda x: apply_day_offset(x, self._day_offset), entries) + self._entries = list(entries) def get_dedicated_calendar_types(self): @@ -182,6 +197,7 @@ def create( customize: Dict[str, Customize], source_args, calendar_title: Optional[str] = None, + day_offset: int = 0, ): # load source module try: @@ -204,6 +220,7 @@ def create( url=source_module.URL, # type: ignore[attr-defined] calendar_title=calendar_title, unique_id=calc_unique_source_id(source_name, source_args), + day_offset=day_offset, ) return g diff --git a/doc/ics/gedling_gov_uk.md b/doc/ics/gedling_gov_uk.md new file mode 100644 index 000000000..c4646c02d --- /dev/null +++ b/doc/ics/gedling_gov_uk.md @@ -0,0 +1,68 @@ +# Gedling Borough Council (unofficial) + +Gedling Borough Council (unofficial) is supported by the generic [ICS](/doc/source/ics.md) source. For all available configuration options, please refer to the source description. + + +## How to get the configuration arguments + +- Gedling Borough Council does not provide bin collections in the iCal calendar format directly. +- The iCal calendar files have been generated from the official printed calendars and hosted on GitHub for use. +- Go to the Gedling Borough Council [Refuse Collection Days](https://apps.gedling.gov.uk/refuse/search.aspx) site and enter your street name to find your bin day/garden waste collection schedule. e.g. "Wednesday G2". +- Find the [required collection schedule](https://jamesmacwhite.github.io/gedling-borough-council-bin-calendars/) and use the "Copy to clipboard" button for the URL of the .ics file. + +## Examples + +### Monday G1 (General bin collection) + +```yaml +waste_collection_schedule: + sources: + - name: ics + args: + url: https://raw.githubusercontent.com/jamesmacwhite/gedling-borough-council-bin-calendars/main/ical/gedling_borough_council_monday_g1_bin_schedule.ics +``` +### Wednesday G2 (General bin collection) + +```yaml +waste_collection_schedule: + sources: + - name: ics + args: + url: https://raw.githubusercontent.com/jamesmacwhite/gedling-borough-council-bin-calendars/main/ical/gedling_borough_council_wednesday_g2_bin_schedule.ics +``` +### Friday G3 (General bin collection) + +```yaml +waste_collection_schedule: + sources: + - name: ics + args: + url: https://raw.githubusercontent.com/jamesmacwhite/gedling-borough-council-bin-calendars/main/ical/gedling_borough_council_friday_g3_bin_schedule.ics +``` +### Monday A (Garden waste collection) + +```yaml +waste_collection_schedule: + sources: + - name: ics + args: + url: https://raw.githubusercontent.com/jamesmacwhite/gedling-borough-council-bin-calendars/main/ical/gedling_borough_council_monday_a_garden_bin_schedule.ics +``` +### Wednesday C (Garden waste collection) + +```yaml +waste_collection_schedule: + sources: + - name: ics + args: + url: https://raw.githubusercontent.com/jamesmacwhite/gedling-borough-council-bin-calendars/main/ical/gedling_borough_council_wednesday_c_garden_bin_schedule.ics +``` +### Friday E (Garden waste collection) + +```yaml +waste_collection_schedule: + sources: + - name: ics + args: + url: https://raw.githubusercontent.com/jamesmacwhite/gedling-borough-council-bin-calendars/main/ical/gedling_borough_council_friday_e_garden_bin_schedule.ics +``` diff --git a/doc/ics/herten_de.md b/doc/ics/herten_de.md new file mode 100644 index 000000000..c12c9deb4 --- /dev/null +++ b/doc/ics/herten_de.md @@ -0,0 +1,22 @@ +# Herten (durth-roos.de) + +Herten (durth-roos.de) is supported by the generic [ICS](/doc/source/ics.md) source. For all available configuration options, please refer to the source description. + + +## How to get the configuration arguments + +- Goto and select your location. +- Right click copy-url of the `iCalendar` button to get a webcal link. (You can ignore the note below as this source automatically refetches the ics file) +- Replace the `url` in the example configuration with this link. + +## Examples + +### Ackerstraße 1 + +```yaml +waste_collection_schedule: + sources: + - name: ics + args: + url: https://abfallkalender.durth-roos.de/herten/icalendar/Ackerstrasse_1.ics +``` diff --git a/doc/ics/recollect.md b/doc/ics/recollect.md index 8245a188c..bb57a3d0a 100644 --- a/doc/ics/recollect.md +++ b/doc/ics/recollect.md @@ -23,6 +23,7 @@ known to work with: |City of Georgetown, TX|USA|[texasdisposal.com](https://www.texasdisposal.com/waste-wizard/)| |City of Vancouver|Canada|[vancouver.ca](https://vancouver.ca/home-property-development/garbage-and-recycling-collection-schedules.aspx)| |City of Nanaimo|Canada|[nanaimo.ca](https://www.nanaimo.ca/city-services/garbage-recycling/collectionschedule)| +|City of Austin|USA|[austintexas.gov](https://www.austintexas.gov/myschedule)| and probably a lot more. @@ -96,3 +97,13 @@ waste_collection_schedule: args: url: webcal://recollect.a.ssl.fastly.net/api/places/3734BF46-A9A1-11E2-8B00-43B94144C028/services/193/events.en.ics?client_id=8844492C-9457-11EE-90E3-08A383E66757 ``` +### Cathedral of Junk, Austin, TX + +```yaml +waste_collection_schedule: + sources: + - name: ics + args: + split_at: '\, (?:and )?|(?: and )' + url: https://recollect.a.ssl.fastly.net/api/places/2587D9F6-DF59-11E8-96F5-0E2C682931C6/services/323/events.en-US.ics +``` diff --git a/doc/ics/yaml/gedling_gov_uk.yaml b/doc/ics/yaml/gedling_gov_uk.yaml new file mode 100644 index 000000000..62655fbd6 --- /dev/null +++ b/doc/ics/yaml/gedling_gov_uk.yaml @@ -0,0 +1,20 @@ +title: Gedling Borough Council (unofficial) +url: https://github.com/jamesmacwhite/gedling-borough-council-bin-calendars +howto: | + - Gedling Borough Council does not provide bin collections in the iCal calendar format directly. + - The iCal calendar files have been generated from the official printed calendars and hosted on GitHub for use. + - Go to the Gedling Borough Council [Refuse Collection Days](https://apps.gedling.gov.uk/refuse/search.aspx) site and enter your street name to find your bin day/garden waste collection schedule. e.g. "Wednesday G2". + - Find the [required collection schedule](https://jamesmacwhite.github.io/gedling-borough-council-bin-calendars/) and use the "Copy to clipboard" button for the URL of the .ics file. +test_cases: + Monday G1 (General bin collection): + url: "https://raw.githubusercontent.com/jamesmacwhite/gedling-borough-council-bin-calendars/main/ical/gedling_borough_council_monday_g1_bin_schedule.ics" + Wednesday G2 (General bin collection): + url: "https://raw.githubusercontent.com/jamesmacwhite/gedling-borough-council-bin-calendars/main/ical/gedling_borough_council_wednesday_g2_bin_schedule.ics" + Friday G3 (General bin collection): + url: "https://raw.githubusercontent.com/jamesmacwhite/gedling-borough-council-bin-calendars/main/ical/gedling_borough_council_friday_g3_bin_schedule.ics" + Monday A (Garden waste collection): + url: "https://raw.githubusercontent.com/jamesmacwhite/gedling-borough-council-bin-calendars/main/ical/gedling_borough_council_monday_a_garden_bin_schedule.ics" + Wednesday C (Garden waste collection): + url: "https://raw.githubusercontent.com/jamesmacwhite/gedling-borough-council-bin-calendars/main/ical/gedling_borough_council_wednesday_c_garden_bin_schedule.ics" + Friday E (Garden waste collection): + url: "https://raw.githubusercontent.com/jamesmacwhite/gedling-borough-council-bin-calendars/main/ical/gedling_borough_council_friday_e_garden_bin_schedule.ics" diff --git a/doc/ics/yaml/herten_de.yaml b/doc/ics/yaml/herten_de.yaml new file mode 100644 index 000000000..068e24720 --- /dev/null +++ b/doc/ics/yaml/herten_de.yaml @@ -0,0 +1,9 @@ +title: Herten (durth-roos.de) +url: https://herten.de +howto: | + - Goto and select your location. + - Right click copy-url of the `iCalendar` button to get a webcal link. (You can ignore the note below as this source automatically refetches the ics file) + - Replace the `url` in the example configuration with this link. +test_cases: + Ackerstraße 1: + url: "https://abfallkalender.durth-roos.de/herten/icalendar/Ackerstrasse_1.ics" diff --git a/doc/ics/yaml/recollect.yaml b/doc/ics/yaml/recollect.yaml index a8ed65231..d64f5fe5e 100644 --- a/doc/ics/yaml/recollect.yaml +++ b/doc/ics/yaml/recollect.yaml @@ -44,6 +44,9 @@ extra_info: - title: City of Nanaimo url: https://www.nanaimo.ca country: ca + - title: City of Austin, TX + url: https://austintexas.gov + country: us howto: | - To get the URL, search your address in the recollect form of your home town. - Click "Get a calendar", then "Add to Google Calendar". @@ -63,6 +66,7 @@ howto: | |City of Georgetown, TX|USA|[texasdisposal.com](https://www.texasdisposal.com/waste-wizard/)| |City of Vancouver|Canada|[vancouver.ca](https://vancouver.ca/home-property-development/garbage-and-recycling-collection-schedules.aspx)| |City of Nanaimo|Canada|[nanaimo.ca](https://www.nanaimo.ca/city-services/garbage-recycling/collectionschedule)| + |City of Austin|USA|[austintexas.gov](https://www.austintexas.gov/myschedule)| and probably a lot more. test_cases: @@ -85,3 +89,6 @@ test_cases: split_at: "\\, (?:and )?|(?: and )" 166 W 47th Ave, Vancouver: url: "webcal://recollect.a.ssl.fastly.net/api/places/3734BF46-A9A1-11E2-8B00-43B94144C028/services/193/events.en.ics?client_id=8844492C-9457-11EE-90E3-08A383E66757" + Cathedral of Junk, Austin, TX: + url: https://recollect.a.ssl.fastly.net/api/places/2587D9F6-DF59-11E8-96F5-0E2C682931C6/services/323/events.en-US.ics + split_at: "\\, (?:and )?|(?: and )" diff --git a/doc/installation.md b/doc/installation.md index 696da134f..7be5c758b 100644 --- a/doc/installation.md +++ b/doc/installation.md @@ -64,6 +64,7 @@ waste_collection_schedule: picture: PICTURE use_dedicated_calendar: USE_DEDICATED_CALENDAR dedicated_calendar_title: DEDICATED_CALENDAR_TITLE + day_offset: DAY_OFFSET calendar_title: CALENDAR_TITLE fetch_time: FETCH_TIME random_fetch_time_offset: RANDOM_FETCH_TIME_OFFSET @@ -78,6 +79,7 @@ waste_collection_schedule: | 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 | +| day_offset | int | optional | Offset in days to add to the collection date (can be negative). If no value is entered, the default of 0 is used | ## Attributes for _sources_ diff --git a/doc/source/app_abfallplus_de.md b/doc/source/app_abfallplus_de.md index e43802224..2e83cf5ec 100644 --- a/doc/source/app_abfallplus_de.md +++ b/doc/source/app_abfallplus_de.md @@ -6,7 +6,7 @@ Support for schedules provided by [Apps by Abfall+](https://www.abfallplus.de/), ```yaml waste_collection_schedule: - sources: + sources: - name: app_abfallplus_de args: app_id: APP ID @@ -49,7 +49,7 @@ If you need to select a first letter of you street name, you can use the city ar ```yaml waste_collection_schedule: - sources: + sources: - name: app_abfallplus_de args: app_id: de.albagroup.app @@ -60,7 +60,7 @@ waste_collection_schedule: ```yaml waste_collection_schedule: - sources: + sources: - name: app_abfallplus_de args: app_id: de.k4systems.bonnorange @@ -71,7 +71,7 @@ waste_collection_schedule: ```yaml waste_collection_schedule: - sources: + sources: - name: app_abfallplus_de args: app_id: de.k4systems.abfallappwug @@ -81,7 +81,7 @@ waste_collection_schedule: ```yaml waste_collection_schedule: - sources: + sources: - name: app_abfallplus_de args: app_id: de.k4systems.awbgp @@ -92,7 +92,7 @@ waste_collection_schedule: ```yaml waste_collection_schedule: - sources: + sources: - name: app_abfallplus_de args: app_id: de.k4systems.leipziglk @@ -100,6 +100,19 @@ waste_collection_schedule: bezirk: Brandis ``` +```yaml +waste_collection_schedule: + sources: + - name: app_abfallplus_de + args: + app_id: de.abfallwecker + city: Lauchringen + strasse: Bundesstr. + hnr: 20 + bundesland: Baden-Württemberg + landkreis: Kreis Waldshut +``` + ## How to get the source argument Use the app of your local provider and select your address. Provide all arguments that are requested by the app. @@ -144,7 +157,7 @@ The app_id can be found from the url of the play store entry: https://play.googl | de.k4systems.leipziglk | Landkreis Leipzig | | de.k4systems.abfallappbk | Bad Kissingen | | de.cmcitymedia.hokwaste | Hohenlohekreis | -| de.abfallwecker | Rottweil, Tuttlingen, Waldshut, Prignitz, Nordsachsen | +| de.abfallwecker | Rottweil, Tuttlingen, Kreis Waldshut, Prignitz, Nordsachsen | | de.k4systems.abfallappka | Kreis Karlsruhe | | de.k4systems.lkgoettingen | Abfallwirtschaft Altkreis Göttingen, Abfallwirtschaft Altkreis Osterode am Harz | | de.k4systems.abfallappcux | Kreis Cuxhaven | diff --git a/doc/source/awido_de.md b/doc/source/awido_de.md index 73e9ca20b..c6566be18 100644 --- a/doc/source/awido_de.md +++ b/doc/source/awido_de.md @@ -75,6 +75,7 @@ List of customers (2021-07-09): - `lra-ab`: Landkreis Aschaffenburg - `lra-dah`: Landratsamt Dachau - `lra-mue`: Landkreis Mühldorf a. Inn +- `lra-regensburg`: Landratsamt Regensburg - `lra-schweinfurt`: Landkreis Schweinfurt - `memmingen`: Stadt Memmingen - `neustadt`: Neustadt a.d. Waldnaab diff --git a/doc/source/birmingham_gov_uk.md b/doc/source/birmingham_gov_uk.md new file mode 100644 index 000000000..016b91221 --- /dev/null +++ b/doc/source/birmingham_gov_uk.md @@ -0,0 +1,51 @@ +# Birmingham City Council + +Support for schedules provided by [Birmingham City Council](https://www.birmingham.gov.uk/), in the UK. + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: birmingham_gov_uk + args: + uprn: UNIQUE_PROPERTY_REFERENCE_NUMBER + postcode: POSTCODE +``` + +### Configuration Variables + +**uprn**
+*(string)* + +The "Unique Property Reference Number" for your address. You can find it by searching for your address at https://www.findmyaddress.co.uk/. + +**postcode**
+*(string)* + +The Post Code for your address. This needs to match the postcode corresponding to your UPRN. + +## Example +```yaml +waste_collection_schedule: + sources: + - name: birmingham_gov_uk + args: + uprn: 100070321799 + postcode: B27 6TF +``` + +## Returned Collections +This source will return the next collection date for each container type. + +## Returned collection types + +### Household Collection +Grey lid rubbish bin is for general waste. + +### Recycling Collection +Green lid recycling bin is for dry recycling (metals, glass and plastics). +Blue lid recycling bin is for paper and card. + +### Green Recycling Chargeable Collections +Green Recycling (Chargeable Collections). diff --git a/doc/source/bromsgrove_gov_uk.md b/doc/source/bromsgrove_gov_uk.md new file mode 100644 index 000000000..d00280e65 --- /dev/null +++ b/doc/source/bromsgrove_gov_uk.md @@ -0,0 +1,55 @@ +# Bromsgrove District Council + +Support for schedules provided by [Bromsgrove District Council](https://www.bromsgrove.gov.uk/), in the UK. + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: bromsgrove_gov_uk + args: + uprn: UNIQUE_PROPERTY_REFERENCE_NUMBER + postcode: POSTCODE +``` + +### Configuration Variables + +**uprn** +*(string)* + +The "Unique Property Reference Number" for your address. You can find it by searching for your address at . + +**postcode** +*(string)* + +The Post Code for your address. This needs to match the postcode corresponding to your UPRN. + +## Example + +```yaml +waste_collection_schedule: + sources: + - name: bromsgrove_gov_uk + args: + uprn: 10094552413 + postcode: B61 8DA +``` + +## Returned Collections + +This source will return the next collection date for each container type. + +## Returned collection types + +### Household Collection + +Grey bin is for general waste. + +### Recycling Collection + +Green bin is for dry recycling (metals, glass, plastics, paper and card). + +### Garden waste Chargeable Collections + +Brown bin if for gareden waste. This is a annual chargable service. diff --git a/doc/source/broxbourne_gov_uk.md b/doc/source/broxbourne_gov_uk.md new file mode 100644 index 000000000..5fc683d19 --- /dev/null +++ b/doc/source/broxbourne_gov_uk.md @@ -0,0 +1,61 @@ +# Borough of Broxbourne Council + +Support for schedules provided by [Borough of Broxbourne Council](https://www.broxbourne.gov.uk/), in the UK. + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: broxbourne_gov_uk + args: + uprn: UNIQUE_PROPERTY_REFERENCE_NUMBER + postcode: POSTCODE +``` + +### Configuration Variables + +**uprn** +*(string)* + +The "Unique Property Reference Number" for your address. You can find it by searching for your address at . + +**postcode** +*(string)* + +The Post Code for your address. This needs to match the postcode corresponding to your UPRN. + +## Example + +```yaml +waste_collection_schedule: + sources: + - name: broxbourne_gov_uk + args: + uprn: 148028240 + postcode: EN11 8PU +``` + +## Returned Collections + +This source will return the next collection date for each container type serviced at your address. +If you don't subscribe to a garden waste bin, we don't return data for it. + +## Returned collection types + +### Domestic + +Black bin for general waste + +### Recycling + +Black recycling box for mixed recycling + +### Green Waste + +Green Bin for garden waste. +If you don't pay for a garden waste bin, it won't be included. + +### Food + +Green or Brown Caddy for food waste. diff --git a/doc/source/bury_gov_uk.md b/doc/source/bury_gov_uk.md new file mode 100644 index 000000000..caa8a9169 --- /dev/null +++ b/doc/source/bury_gov_uk.md @@ -0,0 +1,56 @@ +# Bury Council + +Support for schedules provided by [Bury Council](https://www.bury.gov.uk/), serving Bury, UK. + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: bury_gov_uk + args: + id: PROPERTY_ID + postcode: POSTCODE + address: ADDRESS +``` + +### Configuration Variables + +**id**
+*(string) (optional)* + +**postcode**
+*(string) (optional)* + +**address**
+*(string) (optional)* + + +## Example using UPRN + +```yaml +waste_collection_schedule: + sources: + - name: bury_gov_uk + args: + id: "647186" +``` + +## Example using Address and Postcode + +```yaml +waste_collection_schedule: + sources: + - name: bury_gov_uk + args: + address: "1 Oakwood Close" + postcode: "BL8 1DD" +``` + +## How to find your `PROPERTY_ID` + +Your PROPERTY_ID is the collection of numbers at the end of the url when viewing your collection schedule in Developer Tools on the Bury Council web site. + +For example: https://www.bury.gov.uk/app-services/getPropertyById?id=647186 + +You have to navigate to https://www.bury.gov.uk/waste-and-recycling/bin-collection-days-and-alerts, open Dev Tools, Select Network and then input your Postcode and select your Address. The URL should appear as network traffic. diff --git a/doc/source/citiesapps_com.md b/doc/source/citiesapps_com.md index 87cb9fefd..eef042d61 100644 --- a/doc/source/citiesapps_com.md +++ b/doc/source/citiesapps_com.md @@ -106,6 +106,7 @@ Support for schedules provided by [App CITIES](https://citiesapps.com), serving | Lackendorf | [lackendorf.at](https://www.lackendorf.at) | | Langau | [langau.at](http://www.langau.at) | | Langenrohr | [langenrohr.gv.at](https://www.langenrohr.gv.at) | +| Leibnitz | [leibnitz.at](https://www.leibnitz.at) | | Leithaprodersdorf | [leithaprodersdorf.at](http://www.leithaprodersdorf.at) | | Leutschach an der Weinstraße | [leutschach-weinstrasse.gv.at](https://www.leutschach-weinstrasse.gv.at) | | Lieboch | [lieboch.gv.at](https://www.lieboch.gv.at) | diff --git a/doc/source/east_ayrshire_gov_uk.md b/doc/source/east_ayrshire_gov_uk.md new file mode 100644 index 000000000..c06fe9753 --- /dev/null +++ b/doc/source/east_ayrshire_gov_uk.md @@ -0,0 +1,32 @@ +# East Ayrshire Council + +Support for schedules provided by [East Ayrshire Council](https://www.east-ayrshire.gov.uk/Housing/RubbishAndRecycling/Collection-days/ViewYourRecyclingCalendar.aspx). + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: east_ayrshire_gov_uk + args: + uprn: UNIQUE_PROPERTY_REFERENCE_NUMBER +``` + +### Configuration Variables + +**uprn** +*(string) (required)* + +## Example + +```yaml +waste_collection_schedule: + sources: + - name: east_ayrshire_gov_uk + args: + uprn: "127072649" +``` + +## How to find your `UPRN` + +An easy way to find your Unique Property Reference Number (UPRN) is by going to and entering your address details. \ No newline at end of file diff --git a/doc/source/glasgow_gov_uk.md b/doc/source/glasgow_gov_uk.md index 0b04ecbdc..e75aba270 100644 --- a/doc/source/glasgow_gov_uk.md +++ b/doc/source/glasgow_gov_uk.md @@ -1,6 +1,6 @@ # Glasgow City Council -Support for schedules provided by [Glasgow City Council](https://www.glasgow.gov.uk/forms/refuseandrecyclingcalendar/AddressSearch.aspx), serving the +Support for schedules provided by [Glasgow City Council](https://onlineservices.glasgow.gov.uk/forms/RefuseAndRecyclingWebApplication/AddressSearch.aspx), serving the city of Glasgow, UK. ## Configuration via configuration.yaml @@ -32,4 +32,4 @@ waste_collection_schedule: The UPRN code can be found by entering your postcode or address on the [Glasgow City Council Bin Collections page -](https://www.glasgow.gov.uk/forms/refuseandrecyclingcalendar/AddressSearch.aspx). When on the address list click the 'select' link for your address then on the calendar page look in the browser address bar for your UPRN code e.g. https://www.glasgow.gov.uk/forms/refuseandrecyclingcalendar/CollectionsCalendar.aspx?UPRN=YOURUPRNSHOWNHERE. +](https://onlineservices.glasgow.gov.uk/forms/RefuseAndRecyclingWebApplication/AddressSearch.aspx). When on the address list click the 'select' link for your address then on the calendar page look in the browser address bar for your UPRN code e.g. https://onlineservices.glasgow.gov.uk/forms/RefuseAndRecyclingWebApplication/CollectionsCalendar.aspx?UPRN=YOURUPRNSHOWNHERE. diff --git a/doc/source/ics.md b/doc/source/ics.md index e33ca689b..79177fc97 100644 --- a/doc/source/ics.md +++ b/doc/source/ics.md @@ -192,6 +192,7 @@ This source has been successfully tested with the following service providers: - [Hallesche Wasser und Stadtwirtschaft GmbH](/doc/ics/hws_halle_de.md) / hws-halle.de - [Heidelberg](/doc/ics/gipsprojekt_de.md) / heidelberg.de - [Heinz-Entsorgung (Landkreis Freising)](/doc/ics/heinz_entsorgung_de.md) / heinz-entsorgung.de +- [Herten (durth-roos.de)](/doc/ics/herten_de.md) / herten.de - [Kreisstadt Groß-Gerau](/doc/ics/gross_gerau_de.md) / gross-gerau.de - [Landkreis Anhalt-Bitterfeld](/doc/ics/abikw_de.md) / abikw.de - [Landkreis Böblingen](/doc/ics/abfall_app_net.md) / lrabb.de @@ -246,11 +247,13 @@ This source has been successfully tested with the following service providers: - [Anglesey](/doc/ics/anglesey_gov_wales.md) / anglesey.gov.wales - [Falkirk](/doc/ics/falkirk_gov_uk.md) / falkirk.gov.uk +- [Gedling Borough Council (unofficial)](/doc/ics/gedling_gov_uk.md) / github.com/jamesmacwhite/gedling-borough-council-bin-calendars - [Westmorland & Furness Council, Barrow area](/doc/ics/barrowbc_gov_uk.md) / barrowbc.gov.uk - [Westmorland & Furness Council, South Lakeland area](/doc/ics/southlakeland_gov_uk.md) / southlakeland.gov.uk ### United States of America +- [City of Austin, TX](/doc/ics/recollect.md) / austintexas.gov - [City of Bloomington](/doc/ics/recollect.md) / bloomington.in.gov - [City of Cambridge](/doc/ics/recollect.md) / cambridgema.gov - [City of Gastonia, NC](/doc/ics/recollect.md) / gastonianc.gov diff --git a/doc/source/kiertokapula_fi.md b/doc/source/kiertokapula_fi.md new file mode 100644 index 000000000..c73624536 --- /dev/null +++ b/doc/source/kiertokapula_fi.md @@ -0,0 +1,28 @@ +# Kiertokapula Finland + +Support for upcoming pick ups provided by [Kiertokapula self-service portal](https://asiakasnetti.kiertokapula.fi/). + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: kiertokapula_fi + args: + bill_number: "YOUR_BILL_NUMBER" + password: "YOUR_PASSWORD" +``` + +### Configuration Variables + +**bill_number** +*(string) (required)* + +**password** +*(string) (required)* + +## How to get the source argument + +**You need to have a registered account in Kiertokapula's self-service portal!** +To register one, you need to get your customer number from your bills, and password is by default your home address. +System will prompt you a password change, after you've done it, you have now registered your user and it's ready to be configured here. diff --git a/doc/source/north_ayrshire_gov_uk.md b/doc/source/north_ayrshire_gov_uk.md new file mode 100644 index 000000000..0dac323e9 --- /dev/null +++ b/doc/source/north_ayrshire_gov_uk.md @@ -0,0 +1,32 @@ +# North Ayrshire Council + +Support for schedules provided by [North Ayrshire Council](https://www.north-ayrshire.gov.uk/). + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: north_ayrshire_gov_uk + args: + uprn: UNIQUE_PROPERTY_REFERENCE_NUMBER +``` + +### Configuration Variables + +**uprn** +*(string) (required)* + +## Example + +```yaml +waste_collection_schedule: + sources: + - name: north_ayrshire_gov_uk + args: + uprn: "126043248" +``` + +## How to find your `UPRN` + +An easy way to find your Unique Property Reference Number (UPRN) is by going to and entering your address details. \ No newline at end of file diff --git a/doc/source/renfrewshire_gov_uk.md b/doc/source/renfrewshire_gov_uk.md new file mode 100644 index 000000000..137b0f83d --- /dev/null +++ b/doc/source/renfrewshire_gov_uk.md @@ -0,0 +1,37 @@ +# Renfrewshire Council + +Support for schedules provided by [Renfrewshire Council](https://www.renfrewshire.gov.uk/article/2320/Check-your-bin-collection-day). + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: renfrewshire_gov_uk + args: + postcode: POSTCODE + uprn: UNIQUE_PROPERTY_REFERENCE_NUMBER +``` + +### Configuration Variables + +**postcode** +*(string) (required)* + +**uprn** +*(string) (required)* + +## Example + +```yaml +waste_collection_schedule: + sources: + - name: renfrewshire_gov_uk + args: + postcode: "PA12 4AJ" + uprn: "123034174" +``` + +## How to find your `UPRN` + +An easy way to find your Unique Property Reference Number (UPRN) is by going to and entering your address details. diff --git a/doc/source/wermelskirchen_de.md b/doc/source/wermelskirchen_de.md index dd28229c9..893a2f6e2 100644 --- a/doc/source/wermelskirchen_de.md +++ b/doc/source/wermelskirchen_de.md @@ -1,5 +1,7 @@ # Wermelskirchen Abfallkalender +!!!! The IT service provider was hit by a disastrous cyber attack in October of 2023. Since then this API does not work anymore and might never in the future (at least in the same form). !!!! + Support for schedules provided by [Abfallkalender Wermelskirchen](https://www.wermelskirchen.de/rathaus/buergerservice/formulare-a-z/abfallkalender-online/) located in NRW, Germany. ## Limitations diff --git a/info.md b/info.md index 530a027ce..6143e961c 100644 --- a/info.md +++ b/info.md @@ -17,13 +17,14 @@ 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 | Armadale (Western Australia), Australian Capital Territory (ACT), Banyule City Council, Belmont City Council, Blacktown City Council (NSW), Brisbane City Council, Campbelltown City Council (NSW), Cardinia Shire Council, City of Ballarat, City of Canada Bay Council, City of Greater Geelong, City of Kingston, City of Onkaparinga Council, Cumberland Council (NSW), Gold Coast City Council, Hobsons Bay City Council, Hornsby Shire Council, Hume City Council, Inner West Council (NSW), Ipswich City Council, Knox City Council, Ku-ring-gai Council, Lake Macquarie City Council, Logan City Council, Macedon Ranges Shire Council, Mansfield Shire Council, Maribyrnong Council, Maroondah City Council, Melton City Council, Merri-bek City Council, Moreton Bay, Mosman Council, Nillumbik Shire Council, North Adelaide Waste Management Authority, Port Adelaide Enfield, South Australia, Port Stephens Council, RecycleSmart, Redland City Council (QLD), Shellharbour City Council, Stonnington City Council, The Hawkesbury City Council, Sydney, The Hills Shire Council, Sydney, Unley City Council (SA), Whittlesea City Council, Wollondilly Shire Council, Wollongong City Council, Wyndham City Council, Melbourne, Yarra Ranges Council | -| Austria | Abfallverband Hollabrunn, Abfallverband Korneuburg, Abfallverband Schwechat, Abfallwirtschaft der Stadt St. Pölten, Abfallwirtschaft Stadt Krems, Abfallwirtschaft Stadt St Pölten, Afritz am See, Alpbach, Altenmarkt an der Triesting, Althofen, Andau, Angath, Apetlon, App CITIES, Arnoldstein, Aschau im Zillertal, AWV Neunkirchen, AWV Wr. Neustadt, Bad Blumau, Bad Gleichenberg, Bad Häring, Bad Kleinkirchheim, Bad Loipersdorf, Bad Radkersburg, Bad Tatzmannsdorf, Bad Waltersdorf, Baldramsdorf, Berg im Drautal, Berndorf bei Salzburg, Bernstein, Bildein, Brandenberg, Breitenbach am Inn, Breitenbrunn am Neusiedler See, Breitenstein, Bromberg, Bruckneudorf, Buch - St. Magdalena, Burgau, Burgauberg-Neudauberg, Burgenländischer Müllverband, Dechantskirchen, Dellach, Dellach im Drautal, Deutsch Goritz, Deutsch Jahrndorf, Deutsch Kaltenbrunn, Deutschkreutz, Die NÖ Umweltverbände, Dobl-Zwaring, Drasenhofen, Draßmarkt, Ebenthal in Kärnten, Eberau, Eberndorf, Ebersdorf, Eberstein, Edelsbach bei Feldbach, Eggenburg, Eggersdorf bei Graz, Eichgraben, Eisenstadt, Eugendorf, Fehring, Feistritz im Rosental, Feistritz ob Bleiburg, Feldbach, Feldkirchen in Kärnten, Feldkirchen in Kärnten, Ferlach, Ferndorf, Ferndorf, Finkenstein am Faaker See, Frankenau-Unterpullendorf, Frauenkirchen, Frauenstein, Freistadt, Fresach, Friedberg, Frohnleiten, Fürstenfeld, Gabersdorf, GABL, Gattendorf, GAUL Laa an der Thaya, GAUM Mistelbach, GDA Amstetten, Gemeindeverband Horn, Gitschtal, Gitschtal, Globasnitz, Gmünd in Kärnten, Gols, Grafendorf bei Hartberg, Grafenschachen, Grafenstein, Grafenstein, Gratkorn, Gratwein-Straßengel, Greifenburg, Großkirchheim, Großsteinbach, Großwarasdorf, Großwilfersdorf, Gutenberg, Guttaring, GV Gmünd, GV Krems, GV Zwettl, GVA Baden, GVA Baden, GVA Lilienfeld, GVA Mödling, GVA Tulln, GVA Waidhofen/Thaya, GVU Bezirk Gänserndorf, GVU Melk, GVU Scheibbs, GVU Scheibbs, GVU St. Pölten, Güssing, Hagenberg im Mühlkreis, Hannersdorf, Hartberg, Heiligenblut am Großglockner, Heiligenkreuz, Heiligenkreuz am Waasen, Heimschuh, Henndorf am Wallersee, Henndorf am Wallersee, Hermagor-Pressegger See, Hirm, Hofstätten an der Raab, Hopfgarten im Brixental, Horitschon, Horn, Hornstein, Hüttenberg, Ilz, infeo, Innsbrucker Kommunalbetriebe, Inzenhof, Irschen, Jabing, Jagerberg, Kaindorf, Kaisersdorf, Kalsdorf bei Graz, Kapfenstein, Kemeten, Keutschach am See, Kirchbach, Kirchbach-Zerlach, Kirchberg an der Raab, Kirchbichl, Kirchdorf in Tirol, Kittsee, Klagenfurt am Wörthersee, Kleblach-Lind, Kleinmürbisch, Klingenbach, Klosterneuburg, Klöch, Kobersdorf, Kohfidisch, Korneuburg, Krems in Kärnten, Krensdorf, Krumpendorf am Wörthersee, Kuchl, Kundl, Kössen, Köstendorf, Kötschach-Mauthen, Köttmannsdorf, Laa an der Thaya, Lackenbach, Lackendorf, Langau, Langenrohr, Leithaprodersdorf, Lendorf, Leoben, Lesachtal, Leutschach an der Weinstraße, Lieboch, Linz AG, Litzelsdorf, Lockenhaus, Loipersbach im Burgenland, Ludmannsdorf, Lurnfeld, Magdalensberg, Mallnitz, Malta, Maria Rain, Maria Saal, Maria Wörth, Mariasdorf, Markt Hartmannsdorf, Markt Neuhodis, Marktgemeinde Edlitz, Marz, Mattersburg, Mattsee, Meiseldorf, Melk, Mettersdorf am Saßbach, Miesenbach, Millstatt, Mischendorf, Mistelbach, Mitterdorf an der Raab, Moosburg, Mureck, Mönchhof, Mörbisch am See, Mörtschach, Mühldorf, Müll App, Münster, Neudorf bei Parndorf, Neudörfl, Neufeld an der Leitha, Neumarkt am Wallersee, Neusiedl am See, Neustift bei Güssing, Nickelsdorf, Oberdrauburg, Oberndorf in Tirol, Oberpullendorf, Oberschützen, Obertrum am See, Oberwart, Oslip, Ottendorf an der Rittschein, Ottobrunn, Paldau, Pama, Pamhagen, Parndorf, Paternion, Payerbach, Peggau, Pernegg an der Mur, Pernegg im Waldviertel, Pfarrwerfen, Pilgersdorf, Pinggau, Pinkafeld, Podersdorf am See, Poggersdorf, Poggersdorf, Potzneusiedl, Poysdorf, Pöchlarn, Pörtschach am Wörther See, Raach am Hochgebirge, Raasdorf, Radenthein, Radfeld, Radmer, Ragnitz, Raiding, Ramsau im Zillertal, Rangersdorf, Reichenau, Reichenfels, Reith im Alpbachtal, Reißeck, Rennweg am Katschberg, Rohr bei Hartberg, Rohr im Burgenland, Rudersdorf, Rust, Saalfelden am Steinernen Meer, Sachsenburg, Sankt Georgen an der Stiefing, Sankt Gilgen, Sankt Oswald bei Plankenwarth, Schiefling am Wörthersee, Schleedorf, Schrattenberg, Schwadorf, Schwaz, Schwoich, Schäffern, Schützen am Gebirge, Seeboden, Seeham, Seekirchen am Wallersee, Seiersberg-Pirka, Siegendorf, Sigleß, Sigmundsherberg, Sinabelkirchen, Spittal an der Drau, St. Andrä, St. Andrä, St. Andrä am Zicksee, St. Anna am Aigen, St. Egyden am Steinfeld, St. Georgen an der Leys, St. Jakob im Rosental, St. Jakob im Rosental, St. Johann in der Haide, St. Johann in Tirol, St. Konrad, St. Lorenzen am Wechsel, St. Margareten im Rosental, St. Margarethen an der Raab, St. Margarethen im Burgenland, St. Peter - Freienstein, St. Peter am Ottersbach, St. Ruprecht an der Raab, St. Symvaro, St. Veit in der Südsteiermark, Stadt Salzburg, Stadtgemeinde Traiskirchen, Stadtservice Korneuburg, Stall, Stegersbach, Steinbrunn, Steinfeld, Steuerberg, Stinatz, Stiwoll, Stockenboi, Stockerau, Strass im Zillertal, Straß in Steiermark, Straßwalchen, Söchau, Söll, Tadten, Tattendorf, Techelsberg am Wörther See, Thal, Tieschen, Tobaj, Trebesing, Treffen am Ossiacher See, Tulln an der Donau, Umweltprofis, Unterfrauenhaid, Unterkohlstätten, Unterlamm, Unterwart, Vasoldsberg, Velden am Wörther See, Villach, Vordernberg, Völkermarkt, Völkermarkt, Walpersbach, Wattens, Weiden am See, Weitersfeld, Weiz, Weißensee, Weppersdorf, Werfenweng, Wies, Wiesen, Wiesfleck, Wiesmath, Wimpassing an der Leitha, Winden am See, Winklern, Wolfau, Wolfsberg, Wolfsberg, Wolkersdorf im Weinviertel, WSZ Moosburg, Wulkaprodersdorf, Wörterberg, Zagersdorf, Zelking-Matzleinsdorf, Zell, Zell am Ziller, Zellberg, Zillingtal, Zurndorf, Übelbach | +| Austria | Abfallverband Hollabrunn, Abfallverband Korneuburg, Abfallverband Schwechat, Abfallwirtschaft der Stadt St. Pölten, Abfallwirtschaft Stadt Krems, Abfallwirtschaft Stadt St Pölten, Afritz am See, Alpbach, Altenmarkt an der Triesting, Althofen, Andau, Angath, Apetlon, App CITIES, Arnoldstein, Aschau im Zillertal, AWV Neunkirchen, AWV Wr. Neustadt, Bad Blumau, Bad Gleichenberg, Bad Häring, Bad Kleinkirchheim, Bad Loipersdorf, Bad Radkersburg, Bad Tatzmannsdorf, Bad Waltersdorf, Baldramsdorf, Berg im Drautal, Berndorf bei Salzburg, Bernstein, Bildein, Brandenberg, Breitenbach am Inn, Breitenbrunn am Neusiedler See, Breitenstein, Bromberg, Bruckneudorf, Buch - St. Magdalena, Burgau, Burgauberg-Neudauberg, Burgenländischer Müllverband, Dechantskirchen, Dellach, Dellach im Drautal, Deutsch Goritz, Deutsch Jahrndorf, Deutsch Kaltenbrunn, Deutschkreutz, Die NÖ Umweltverbände, Dobl-Zwaring, Drasenhofen, Draßmarkt, Ebenthal in Kärnten, Eberau, Eberndorf, Ebersdorf, Eberstein, Edelsbach bei Feldbach, Eggenburg, Eggersdorf bei Graz, Eichgraben, Eisenstadt, Eugendorf, Fehring, Feistritz im Rosental, Feistritz ob Bleiburg, Feldbach, Feldkirchen in Kärnten, Feldkirchen in Kärnten, Ferlach, Ferndorf, Ferndorf, Finkenstein am Faaker See, Frankenau-Unterpullendorf, Frauenkirchen, Frauenstein, Freistadt, Fresach, Friedberg, Frohnleiten, Fürstenfeld, Gabersdorf, GABL, Gattendorf, GAUL Laa an der Thaya, GAUM Mistelbach, GDA Amstetten, Gemeindeverband Horn, Gitschtal, Gitschtal, Globasnitz, Gmünd in Kärnten, Gols, Grafendorf bei Hartberg, Grafenschachen, Grafenstein, Grafenstein, Gratkorn, Gratwein-Straßengel, Greifenburg, Großkirchheim, Großsteinbach, Großwarasdorf, Großwilfersdorf, Gutenberg, Guttaring, GV Gmünd, GV Krems, GV Zwettl, GVA Baden, GVA Baden, GVA Lilienfeld, GVA Mödling, GVA Tulln, GVA Waidhofen/Thaya, GVU Bezirk Gänserndorf, GVU Melk, GVU Scheibbs, GVU Scheibbs, GVU St. Pölten, Güssing, Hagenberg im Mühlkreis, Hannersdorf, Hartberg, Heiligenblut am Großglockner, Heiligenkreuz, Heiligenkreuz am Waasen, Heimschuh, Henndorf am Wallersee, Henndorf am Wallersee, Hermagor-Pressegger See, Hirm, Hofstätten an der Raab, Hopfgarten im Brixental, Horitschon, Horn, Hornstein, Hüttenberg, Ilz, infeo, Innsbrucker Kommunalbetriebe, Inzenhof, Irschen, Jabing, Jagerberg, Kaindorf, Kaisersdorf, Kalsdorf bei Graz, Kapfenstein, Kemeten, Keutschach am See, Kirchbach, Kirchbach-Zerlach, Kirchberg an der Raab, Kirchbichl, Kirchdorf in Tirol, Kittsee, Klagenfurt am Wörthersee, Kleblach-Lind, Kleinmürbisch, Klingenbach, Klosterneuburg, Klöch, Kobersdorf, Kohfidisch, Korneuburg, Krems in Kärnten, Krensdorf, Krumpendorf am Wörthersee, Kuchl, Kundl, Kössen, Köstendorf, Kötschach-Mauthen, Köttmannsdorf, Laa an der Thaya, Lackenbach, Lackendorf, Langau, Langenrohr, Leibnitz, Leithaprodersdorf, Lendorf, Leoben, Lesachtal, Leutschach an der Weinstraße, Lieboch, Linz AG, Litzelsdorf, Lockenhaus, Loipersbach im Burgenland, Ludmannsdorf, Lurnfeld, Magdalensberg, Mallnitz, Malta, Maria Rain, Maria Saal, Maria Wörth, Mariasdorf, Markt Hartmannsdorf, Markt Neuhodis, Marktgemeinde Edlitz, Marz, Mattersburg, Mattsee, Meiseldorf, Melk, Mettersdorf am Saßbach, Miesenbach, Millstatt, Mischendorf, Mistelbach, Mitterdorf an der Raab, Moosburg, Mureck, Mönchhof, Mörbisch am See, Mörtschach, Mühldorf, Müll App, Münster, Neudorf bei Parndorf, Neudörfl, Neufeld an der Leitha, Neumarkt am Wallersee, Neusiedl am See, Neustift bei Güssing, Nickelsdorf, Oberdrauburg, Oberndorf in Tirol, Oberpullendorf, Oberschützen, Obertrum am See, Oberwart, Oslip, Ottendorf an der Rittschein, Ottobrunn, Paldau, Pama, Pamhagen, Parndorf, Paternion, Payerbach, Peggau, Pernegg an der Mur, Pernegg im Waldviertel, Pfarrwerfen, Pilgersdorf, Pinggau, Pinkafeld, Podersdorf am See, Poggersdorf, Poggersdorf, Potzneusiedl, Poysdorf, Pöchlarn, Pörtschach am Wörther See, Raach am Hochgebirge, Raasdorf, Radenthein, Radfeld, Radmer, Ragnitz, Raiding, Ramsau im Zillertal, Rangersdorf, Reichenau, Reichenfels, Reith im Alpbachtal, Reißeck, Rennweg am Katschberg, Rohr bei Hartberg, Rohr im Burgenland, Rudersdorf, Rust, Saalfelden am Steinernen Meer, Sachsenburg, Sankt Georgen an der Stiefing, Sankt Gilgen, Sankt Oswald bei Plankenwarth, Schiefling am Wörthersee, Schleedorf, Schrattenberg, Schwadorf, Schwaz, Schwoich, Schäffern, Schützen am Gebirge, Seeboden, Seeham, Seekirchen am Wallersee, Seiersberg-Pirka, Siegendorf, Sigleß, Sigmundsherberg, Sinabelkirchen, Spittal an der Drau, St. Andrä, St. Andrä, St. Andrä am Zicksee, St. Anna am Aigen, St. Egyden am Steinfeld, St. Georgen an der Leys, St. Jakob im Rosental, St. Jakob im Rosental, St. Johann in der Haide, St. Johann in Tirol, St. Konrad, St. Lorenzen am Wechsel, St. Margareten im Rosental, St. Margarethen an der Raab, St. Margarethen im Burgenland, St. Peter - Freienstein, St. Peter am Ottersbach, St. Ruprecht an der Raab, St. Symvaro, St. Veit in der Südsteiermark, Stadt Salzburg, Stadtgemeinde Traiskirchen, Stadtservice Korneuburg, Stall, Stegersbach, Steinbrunn, Steinfeld, Steuerberg, Stinatz, Stiwoll, Stockenboi, Stockerau, Strass im Zillertal, Straß in Steiermark, Straßwalchen, Söchau, Söll, Tadten, Tattendorf, Techelsberg am Wörther See, Thal, Tieschen, Tobaj, Trebesing, Treffen am Ossiacher See, Tulln an der Donau, Umweltprofis, Unterfrauenhaid, Unterkohlstätten, Unterlamm, Unterwart, Vasoldsberg, Velden am Wörther See, Villach, Vordernberg, Völkermarkt, Völkermarkt, Walpersbach, Wattens, Weiden am See, Weitersfeld, Weiz, Weißensee, Weppersdorf, Werfenweng, Wies, Wiesen, Wiesfleck, Wiesmath, Wimpassing an der Leitha, Winden am See, Winklern, Wolfau, Wolfsberg, Wolfsberg, Wolkersdorf im Weinviertel, WSZ Moosburg, Wulkaprodersdorf, Wörterberg, Zagersdorf, Zelking-Matzleinsdorf, Zell, Zell am Ziller, Zellberg, Zillingtal, Zurndorf, Übelbach | | Belgium | Hygea, Limburg.net, Recycle! | | Canada | Aurora (ON), Calgary (AB), Calgary, AB, City of Edmonton, AB, City of Greater Sudbury, ON, City of Nanaimo, City of Peterborough, ON, City of Vancouver, London (ON), Montreal (QC), Ottawa, Canada, RM of Morris, MB, Strathcona County, ON, Toronto (ON), Vaughan (ON), Waste Wise APPS, Winnipeg (MB) | | Czech Republic | Praha, Rudna u Prahy | | Denmark | Renosyd, RenoWeb | +| Finland | Kiertokapula Finland | | France | Mairie de Mamirolle | -| Germany | Aballwirtschaft Ludwigslust-Parchim AöR, Abfall App, Abfall IO ICS Version, Abfall Stuttgart, Abfall-Wirtschafts-Verband Nordschwaben, Abfall.IO / AbfallPlus, Abfallbehandlungsgesellschaft Havelland mbH (abh), Abfallbewirtschaftung Ostalbkreis, Abfallentsorgung Kreis Kassel, Abfallkalender Hattingen, Abfallkalender Herne, Abfallkalender Kassel, Abfallkalender Luebeck, Abfallkalender Mannheim, Abfallkalender Offenbach, Abfallkalender Offenbach am Main (deprecated), Abfallkalender Würzburg, AbfallNavi (RegioIT.de), Abfalltermine Forchheim, Abfallwirtschaft Alb-Donau-Kreis, Abfallwirtschaft Altkreis Göttingen, Abfallwirtschaft Altkreis Osterode am Harz, Abfallwirtschaft Enzkreis, Abfallwirtschaft Freiburg, Abfallwirtschaft Germersheim, Abfallwirtschaft Isar-Inn, Abfallwirtschaft Kreis Plön, Abfallwirtschaft Lahn-Dill-Kreises, Abfallwirtschaft Landkreis Böblingen, Abfallwirtschaft Landkreis Freudenstadt, Abfallwirtschaft Landkreis Göppingen, Abfallwirtschaft Landkreis Harburg, Abfallwirtschaft Landkreis Haßberge, Abfallwirtschaft Landkreis Kitzingen, Abfallwirtschaft Landkreis Landsberg am Lech, Abfallwirtschaft Landkreis Wolfenbüttel, Abfallwirtschaft Neckar-Odenwald-Kreis, Abfallwirtschaft Nürnberger Land, Abfallwirtschaft Ortenaukreis, Abfallwirtschaft Pforzheim, Abfallwirtschaft Potsdam-Mittelmark (APM), Abfallwirtschaft Rems-Murr, Abfallwirtschaft Rendsburg, Abfallwirtschaft Rheingau-Taunus-Kreis, Abfallwirtschaft Sonneberg, Abfallwirtschaft Stadt Fürth, Abfallwirtschaft Stadt Nürnberg, Abfallwirtschaft Stadt Schweinfurt, Abfallwirtschaft Südholstein, Abfallwirtschaft Werra-Meißner-Kreis, Abfallwirtschafts-Zweckverband des Landkreises Hersfeld-Rotenburg, Abfallwirtschaftsbetrieb Bergisch Gladbach, Abfallwirtschaftsbetrieb des Landkreises Pfaffenhofen a.d.Ilm (AWP), Abfallwirtschaftsbetrieb Emsland, Abfallwirtschaftsbetrieb Esslingen, Abfallwirtschaftsbetrieb Ilm-Kreis, Abfallwirtschaftsbetrieb Kiel (ABK), Abfallwirtschaftsbetrieb Landkreis Ahrweiler, Abfallwirtschaftsbetrieb Landkreis Altenkirchen, Abfallwirtschaftsbetrieb Landkreis Augsburg, Abfallwirtschaftsbetrieb Landkreis Aurich, Abfallwirtschaftsbetrieb Landkreis Karlsruhe, Abfallwirtschaftsbetrieb LK Mainz-Bingen, Abfallwirtschaftsbetrieb Unstrut-Hainich-Kreis, Abfallwirtschaftsbetriebe Münster, Abfallwirtschaftsgesellschaft Landkreis Schaumburg, Abfallwirtschaftsverband Kreis Groß-Gerau, Abfallwirtschaftsverbandes Lippe, Abfallwirtschaftszweckverband Wartburgkreis (AZV), Abfallzweckverband Rhein-Mosel-Eifel (Landkreis Mayen-Koblenz), AHE Ennepe-Ruhr-Kreis, ALBA Berlin, ALBA Braunschweig, ALF Lahn-Fulda, Altmarkkreis Salzwedel, Altötting (LK), Apps by Abfall+, ART Trier, ART Trier (Depreciated), Aschaffenburg (MyMuell App), ASG Wesel, ASO Abfall-Service Osterholz, ASR Stadt Chemnitz, ATHOS GmbH, Augsburg, Aurich (MKW), AVL - Abfallverwertungsgesellschaft des Landkreises Ludwigsburg mbH, AWA Entsorgungs GmbH, AWB Abfallwirtschaft Vechta, AWB Bad Kreuznach, AWB Köln, AWB Landkreis Bad Dürkheim, AWB Landkreis Fürstenfeldbruck, AWB Oldenburg, AWB Westerwaldkreis, AWG Donau-Wald, AWG Kreis Warendorf, AWIDO Online, AWIGO Abfallwirtschaft Landkreis Osnabrück GmbH, AWISTA Düsseldorf, Awista Starnberg, AWL Neuss, AWM München, AZV Stadt und Landkreis Hof, Bad Arolsen (MyMuell App), Bad Homburg vdH, Bad Kissingen, Bamberg (Stadt und Landkreis), Barnim, Bau & Service Oberursel, Bau- und Entsorgungsbetrieb Emden, Bergischer Abfallwirtschaftverbund, Berlin, Berlin Recycling, Berliner Stadtreinigungsbetriebe, Beverungen (MyMuell App), Bielefeld, Blaue Tonne - Schlaue Tonne, Bogenschütz Entsorgung, Bonn, Braunschweig, Bremer Stadtreinigung, Burgenland (Landkreis), Bürgerportal, Bürgerportal Bedburg, C-Trace, Cederbaum Braunschweig, Celle, Cham Landkreis, Chemnitz (ASR), Chiemgau Recycling - Landkreis Rosenheim, City of Karlsruhe, CM City Media - Müllkalender, Darmstadt (MyMuell App), Darmstadt-Dieburg (ZAW), Dillingen Saar, Dinslaken, Drekopf, Duisburg, EAD Darmstadt, ebwo - Entsorgungs- und Baubetrieb Anstalt des öffentlichen Rechts der Stadt Worms, EDG Entsorgung Dortmund, EGN Abfallkalender, EGST Steinfurt, EGW Westmünsterland, Eichsfeldwerke GmbH, Eigenbetrieb Abfallwirtschaft Landkreis Spree-Neiße, Eigenbetrieb Kommunalwirtschaftliche Dienstleistungen Suhl, EKM Mittelsachsen GmbH, Entsorgungs- und Wirtschaftsbetrieb Landau in der Pfalz, Entsorgungsbetrieb Märkisch-Oderland, Entsorgungsbetrieb Stadt Mainz, Entsorgungsbetriebe Essen, Entsorgungsgesellschaft Görlitz-Löbau-Zittau, Erfstadt (inoffical), Esens (MyMuell App), ESG Soest - Entsorgungswirtschaft Soest GmbH, Essen, EVA Abfallentsorgung, EVS Entsorgungsverband Saar, FES Frankfurter Entsorgungs- und Service GmbH, Flensburg (MyMuell App), Frankfurt (Oder), Freiburg im Breisgau, Gelber Sack Stuttgart, Gelsendienste Gelsenkirchen, Gemeinde Blankenheim, Gemeinde Deggenhausertal, Gemeinde Kalletal, Gemeinde Lindlar, Gemeinde Roetgen, Gemeinde Schutterwald, Gemeinde Unterhaching, Gipsprojekt, Großkrotzenburg (MyMuell App), GSAK APP / Krefeld, GWA - Kreis Unna mbH, Göttinger Entsorgungsbetriebe, Gütersloh, Hagen, Hainburg (MyMuell App), Hallesche Wasser und Stadtwirtschaft GmbH, Halver, Hattersheim am Main, hausmüll.info, Havelland, Heidelberg, Heilbronn Entsorgungsbetriebe, Heinz-Entsorgung (Landkreis Freising), Hohenlohekreis, Holtgast (MyMuell App), HubertSchmid Recycling und Umweltschutz GmbH, Höxter, Ilm-Kreis, Ingolstadt, Insert IT Apps, Jumomind, KAEV Niederlausitz, Kamp-Lintfort (MyMuell App), Kirchdorf (MyMuell App), Kommunalservice Landkreis Börde AöR, Kreis Augsburg, Kreis Bad Kissingen, Kreis Bautzen, Kreis Bayreuth, Kreis Bergstraße, Kreis Breisgau-Hochschwarzwald, Kreis Calw, Kreis Cloppenburg, Kreis Coesfeld, Kreis Cuxhaven, Kreis Diepholz, Kreis Emmendingen, Kreis Emsland, Kreis Freudenstadt, Kreis Fürth, Kreis Garmisch-Partenkirchen, Kreis Göppingen, Kreis Heilbronn, Kreis Heinsberg, Kreis Karlsruhe, Kreis Kitzingen, Kreis Landsberg am Lech, Kreis Landshut, Kreis Limburg-Weilburg, Kreis Ludwigsburg, Kreis Lörrach, Kreis Mayen-Koblenz, Kreis Miesbach, Kreis Miltenberg, Kreis Märkisch-Oderland, Kreis Neustadt/Aisch-Bad Windsheim, Kreis Neuwied, Kreis Nienburg / Weser, Kreis Nordfriesland, Kreis Ostallgäu, Kreis Osterholz, Kreis Pinneberg, Kreis Rastatt, Kreis Ravensburg, Kreis Reutlingen, Kreis Rotenburg (Wümme), Kreis Schaumburg, Kreis Sigmaringen, Kreis Starnberg, Kreis Steinfurt, Kreis Südwestpfalz, Kreis Traunstein, Kreis Trier-Saarburg, Kreis Uelzen, Kreis Vechta, Kreis Viersen, Kreis Vorpommern-Rügen, Kreis Weißenburg-Gunzenhausen, Kreis Wesermarsch, Kreis Würzburg, Kreisstadt Dietzenbach, Kreisstadt Friedberg, Kreisstadt Groß-Gerau, Kreisstadt St. Wendel, Kreiswerke Schmalkalden-Meiningen GmbH, Kreiswirtschaftsbetriebe Goslar, Kronberg im Taunus, KV Cochem-Zell, KWU Entsorgung Landkreis Oder-Spree, Landkreis Anhalt-Bitterfeld, Landkreis Ansbach, Landkreis Aschaffenburg, Landkreis Aschaffenburg (MyMuell App), Landkreis Bayreuth, Landkreis Berchtesgadener Land, Landkreis Biberach (MyMuell App), Landkreis Böblingen, Landkreis Böblingen, Landkreis Börde AöR (KsB), Landkreis Calw, Landkreis Coburg, Landkreis Eichstätt (MyMuell App), Landkreis Erding, Landkreis Erlangen-Höchstadt, Landkreis Esslingen, Landkreis Friesland (MyMuell App), Landkreis Fulda, Landkreis Gießen, Landkreis Gotha, Landkreis Grafschaft, Landkreis Görlitz, Landkreis Günzburg, Landkreis Hameln-Pyrmont, Landkreis Harz, Landkreis Heidenheim, Landkreis Heilbronn, Landkreis Kelheim, Landkreis Kronach, Landkreis Kulmbach, Landkreis Kusel, Landkreis Leer (MyMuell App), Landkreis Leipzig, Landkreis Limburg-Weilburg, Landkreis Lüchow-Dannenberg, Landkreis Main-Spessart, Landkreis Mettmann (MyMuell App), Landkreis Mühldorf a. Inn, Landkreis Nordwestmecklenburg, Landkreis Northeim (unofficial), Landkreis Ostallgäu, Landkreis Paderborn (MyMuell App), Landkreis Peine, Landkreis Ravensburg, Landkreis Reutlingen, Landkreis Rhön Grabfeld, Landkreis Rosenheim, Landkreis Rotenburg (Wümme), Landkreis Roth, Landkreis Roth, Landkreis Schweinfurt, Landkreis Schwäbisch Hall, Landkreis Sigmaringen, Landkreis soest, Landkreis Stade, Landkreis Stendal, Landkreis Südliche Weinstraße, Landkreis Tirschenreuth, Landkreis Tübingen, Landkreis Vogtland, Landkreis Weißenburg-Gunzenhausen, Landkreis Wittmund, Landkreis Wittmund (MyMuell App), Landkreis Wittmund (MyMuell App), Landkreis Wunsiedel im Fichtelgebirge, Landkreisbetriebe Neuburg-Schrobenhausen, Landratsamt Aichach-Friedberg, Landratsamt Bodenseekreis, Landratsamt Dachau, Landratsamt Main-Tauber-Kreis, Landratsamt Traunstein, Landratsamt Unterallgäu, Landshut, Langen, Lebacher Abfallzweckverband (LAZ), Leverkusen, LK Schwandorf, Ludwigshafen, Ludwigshafen am Rhein, Lübbecke (Jumomind), Lübeck Entsorgungsbetriebe, mags Mönchengladbacher Abfall-, Grün- und Straßenbetriebe AöR, Main-Kinzig-Kreis, Main-Kinzig-Kreis (MyMuell App), Mechernich und Kommunen, Mein-Abfallkalender.de, Metzingen, Minden, MZV Biedenkopf, Mühlheim am Main (MyMuell App), Müllabfuhr Deutschland, MüllALARM / Schönmackers, Müllmax, München Landkreis, Neckar-Odenwald-Kreis, Nenndorf (MyMuell App), Neu Ulm, Neumünster (MyMuell App), Neunkirchen Siegerland, Neustadt a.d. Waldnaab, Neustadt an der Weinstraße, Nordsachsen, Oberhavel, Oberhavel AWU, Oldenburg, Ortenaukreis, Ostholstein, Ostprignitz-Ruppin, Potsdam, Prignitz, Prignitz, Pullach im Isartal, Recklinghausen, RegioEntsorgung AöR, RegioEntsorgung Städteregion Aachen, Rhein-Hunsrück (Jumomind), Rhein-Hunsrück Entsorgung (RHE), Rhein-Neckar-Kreis, Rhein-Neckar-Kreis, Rhein-Pfalz-Kreis, Rosbach Vor Der Höhe, Rottweil, Rottweil, RSAG Rhein-Sieg-Kreis, Salzgitter (MyMuell App), Salzlandkreis, Schmitten im Taunus (MyMuell App), Schwarze Elster, Schwarzwald-Baar-Kreis, Schöneck (MyMuell App), Schönmackers, Sector 27 - Datteln, Marl, Oer-Erkenschwick, Seligenstadt (MyMuell App), Stadt Aachen, Stadt Arnsberg, Stadt Bayreuth, Stadt Cottbus, Stadt Darmstadt, Stadt Detmold, Stadt Dorsten, Stadt Emmendingen, Stadt Fulda, Stadt Haltern am See, Stadt Hamm, Stadt Hanau, Stadt Kaufbeuren, Stadt Koblenz, Stadt Landshut, Stadt Mainhausen, Stadt Maintal, Stadt Memmingen, Stadt Messstetten, Stadt Norderstedt, Stadt Osnabrück, Stadt Overath, Stadt Regensburg, Stadt Solingen, Stadt Unterschleißheim, Stadtbetrieb Frechen, Stadtbildpflege Kaiserslautern, Stadtentsorgung Rostock, Stadtreinigung Dresden, Stadtreinigung Hamburg, Stadtreinigung Leipzig, Stadtreinigung Leipzig, StadtService Brühl, Stadtwerke Erfurt, SWE, Stadtwerke Hürth, STL Lüdenscheid, Städteservice Raunheim Rüsselsheim, SWK Herford, Südbrandenburgischer Abfallzweckverband, TBR Remscheid, TBV Velbert, Team Orange (Landkreis Würzburg), Technischer Betriebsdienst Reutlingen, tonnenleerung.de LK Aichach-Friedberg + Neuburg-Schrobenhausen, Tuttlingen, Tuttlingen, Tübingen, Uckermark, ULM (EBU), Ulm (MyMuell App), USB Bochum, Usingen (MyMuell App), VIVO Landkreis Miesbach, Volkmarsen (MyMuell App), Vöhringen (MyMuell App), Waldshut, Waldshut, WBO Wirtschaftsbetriebe Oberhausen, Wegberg (MyMuell App), Wermelskirchen, Westerholt (MyMuell App), Westerwaldkreis, WGV Recycling GmbH, Wilhelmshaven (MyMuell App), Wolfsburger Abfallwirtschaft und Straßenreinigung, WZV Kreis Segeberg, Würzburg, ZAH Hildesheim, ZAK Kempten, ZAW-SR Straubing, ZEW Zweckverband Entsorgungsregion West, ZfA Iserlohn, Zollernalbkreis, Zollernalbkreis, Zweckverband Abfallwirtschaft Kreis Bergstraße, Zweckverband Abfallwirtschaft Oberes Elbtal, Zweckverband Abfallwirtschaft Region Hannover, Zweckverband Abfallwirtschaft Saale-Orla, Zweckverband Abfallwirtschaft Schwalm-Eder-Kreis, Zweckverband Abfallwirtschaft Südwestsachsen (ZAS), Zweckverband München-Südost | +| Germany | Aballwirtschaft Ludwigslust-Parchim AöR, Abfall App, Abfall IO ICS Version, Abfall Stuttgart, Abfall-Wirtschafts-Verband Nordschwaben, Abfall.IO / AbfallPlus, Abfallbehandlungsgesellschaft Havelland mbH (abh), Abfallbewirtschaftung Ostalbkreis, Abfallentsorgung Kreis Kassel, Abfallkalender Hattingen, Abfallkalender Herne, Abfallkalender Kassel, Abfallkalender Luebeck, Abfallkalender Mannheim, Abfallkalender Offenbach, Abfallkalender Offenbach am Main (deprecated), Abfallkalender Würzburg, AbfallNavi (RegioIT.de), Abfalltermine Forchheim, Abfallwirtschaft Alb-Donau-Kreis, Abfallwirtschaft Altkreis Göttingen, Abfallwirtschaft Altkreis Osterode am Harz, Abfallwirtschaft Enzkreis, Abfallwirtschaft Freiburg, Abfallwirtschaft Germersheim, Abfallwirtschaft Isar-Inn, Abfallwirtschaft Kreis Plön, Abfallwirtschaft Lahn-Dill-Kreises, Abfallwirtschaft Landkreis Böblingen, Abfallwirtschaft Landkreis Freudenstadt, Abfallwirtschaft Landkreis Göppingen, Abfallwirtschaft Landkreis Harburg, Abfallwirtschaft Landkreis Haßberge, Abfallwirtschaft Landkreis Kitzingen, Abfallwirtschaft Landkreis Landsberg am Lech, Abfallwirtschaft Landkreis Wolfenbüttel, Abfallwirtschaft Neckar-Odenwald-Kreis, Abfallwirtschaft Nürnberger Land, Abfallwirtschaft Ortenaukreis, Abfallwirtschaft Pforzheim, Abfallwirtschaft Potsdam-Mittelmark (APM), Abfallwirtschaft Rems-Murr, Abfallwirtschaft Rendsburg, Abfallwirtschaft Rheingau-Taunus-Kreis, Abfallwirtschaft Sonneberg, Abfallwirtschaft Stadt Fürth, Abfallwirtschaft Stadt Nürnberg, Abfallwirtschaft Stadt Schweinfurt, Abfallwirtschaft Südholstein, Abfallwirtschaft Werra-Meißner-Kreis, Abfallwirtschafts-Zweckverband des Landkreises Hersfeld-Rotenburg, Abfallwirtschaftsbetrieb Bergisch Gladbach, Abfallwirtschaftsbetrieb des Landkreises Pfaffenhofen a.d.Ilm (AWP), Abfallwirtschaftsbetrieb Emsland, Abfallwirtschaftsbetrieb Esslingen, Abfallwirtschaftsbetrieb Ilm-Kreis, Abfallwirtschaftsbetrieb Kiel (ABK), Abfallwirtschaftsbetrieb Landkreis Ahrweiler, Abfallwirtschaftsbetrieb Landkreis Altenkirchen, Abfallwirtschaftsbetrieb Landkreis Augsburg, Abfallwirtschaftsbetrieb Landkreis Aurich, Abfallwirtschaftsbetrieb Landkreis Karlsruhe, Abfallwirtschaftsbetrieb LK Mainz-Bingen, Abfallwirtschaftsbetrieb Unstrut-Hainich-Kreis, Abfallwirtschaftsbetriebe Münster, Abfallwirtschaftsgesellschaft Landkreis Schaumburg, Abfallwirtschaftsverband Kreis Groß-Gerau, Abfallwirtschaftsverbandes Lippe, Abfallwirtschaftszweckverband Wartburgkreis (AZV), Abfallzweckverband Rhein-Mosel-Eifel (Landkreis Mayen-Koblenz), AHE Ennepe-Ruhr-Kreis, ALBA Berlin, ALBA Braunschweig, ALF Lahn-Fulda, Altmarkkreis Salzwedel, Altötting (LK), Apps by Abfall+, ART Trier, ART Trier (Depreciated), Aschaffenburg (MyMuell App), ASG Wesel, ASO Abfall-Service Osterholz, ASR Stadt Chemnitz, ATHOS GmbH, Augsburg, Aurich (MKW), AVL - Abfallverwertungsgesellschaft des Landkreises Ludwigsburg mbH, AWA Entsorgungs GmbH, AWB Abfallwirtschaft Vechta, AWB Bad Kreuznach, AWB Köln, AWB Landkreis Bad Dürkheim, AWB Landkreis Fürstenfeldbruck, AWB Oldenburg, AWB Westerwaldkreis, AWG Donau-Wald, AWG Kreis Warendorf, AWIDO Online, AWIGO Abfallwirtschaft Landkreis Osnabrück GmbH, AWISTA Düsseldorf, Awista Starnberg, AWL Neuss, AWM München, AZV Stadt und Landkreis Hof, Bad Arolsen (MyMuell App), Bad Homburg vdH, Bad Kissingen, Bamberg (Stadt und Landkreis), Barnim, Bau & Service Oberursel, Bau- und Entsorgungsbetrieb Emden, Bergischer Abfallwirtschaftverbund, Berlin, Berlin Recycling, Berliner Stadtreinigungsbetriebe, Beverungen (MyMuell App), Bielefeld, Blaue Tonne - Schlaue Tonne, Bogenschütz Entsorgung, Bonn, Braunschweig, Bremer Stadtreinigung, Burgenland (Landkreis), Bürgerportal, Bürgerportal Bedburg, C-Trace, Cederbaum Braunschweig, Celle, Cham Landkreis, Chemnitz (ASR), Chiemgau Recycling - Landkreis Rosenheim, City of Karlsruhe, CM City Media - Müllkalender, Darmstadt (MyMuell App), Darmstadt-Dieburg (ZAW), Dillingen Saar, Dinslaken, Drekopf, Duisburg, EAD Darmstadt, ebwo - Entsorgungs- und Baubetrieb Anstalt des öffentlichen Rechts der Stadt Worms, EDG Entsorgung Dortmund, EGN Abfallkalender, EGST Steinfurt, EGW Westmünsterland, Eichsfeldwerke GmbH, Eigenbetrieb Abfallwirtschaft Landkreis Spree-Neiße, Eigenbetrieb Kommunalwirtschaftliche Dienstleistungen Suhl, EKM Mittelsachsen GmbH, Entsorgungs- und Wirtschaftsbetrieb Landau in der Pfalz, Entsorgungsbetrieb Märkisch-Oderland, Entsorgungsbetrieb Stadt Mainz, Entsorgungsbetriebe Essen, Entsorgungsgesellschaft Görlitz-Löbau-Zittau, Erfstadt (inoffical), Esens (MyMuell App), ESG Soest - Entsorgungswirtschaft Soest GmbH, Essen, EVA Abfallentsorgung, EVS Entsorgungsverband Saar, FES Frankfurter Entsorgungs- und Service GmbH, Flensburg (MyMuell App), Frankfurt (Oder), Freiburg im Breisgau, Gelber Sack Stuttgart, Gelsendienste Gelsenkirchen, Gemeinde Blankenheim, Gemeinde Deggenhausertal, Gemeinde Kalletal, Gemeinde Lindlar, Gemeinde Roetgen, Gemeinde Schutterwald, Gemeinde Unterhaching, Gipsprojekt, Großkrotzenburg (MyMuell App), GSAK APP / Krefeld, GWA - Kreis Unna mbH, Göttinger Entsorgungsbetriebe, Gütersloh, Hagen, Hainburg (MyMuell App), Hallesche Wasser und Stadtwirtschaft GmbH, Halver, Hattersheim am Main, hausmüll.info, Havelland, Heidelberg, Heilbronn Entsorgungsbetriebe, Heinz-Entsorgung (Landkreis Freising), Herten (durth-roos.de), Hohenlohekreis, Holtgast (MyMuell App), HubertSchmid Recycling und Umweltschutz GmbH, Höxter, Ilm-Kreis, Ingolstadt, Insert IT Apps, Jumomind, KAEV Niederlausitz, Kamp-Lintfort (MyMuell App), Kirchdorf (MyMuell App), Kommunalservice Landkreis Börde AöR, Kreis Augsburg, Kreis Bad Kissingen, Kreis Bautzen, Kreis Bayreuth, Kreis Bergstraße, Kreis Breisgau-Hochschwarzwald, Kreis Calw, Kreis Cloppenburg, Kreis Coesfeld, Kreis Cuxhaven, Kreis Diepholz, Kreis Emmendingen, Kreis Emsland, Kreis Freudenstadt, Kreis Fürth, Kreis Garmisch-Partenkirchen, Kreis Göppingen, Kreis Heilbronn, Kreis Heinsberg, Kreis Karlsruhe, Kreis Kitzingen, Kreis Landsberg am Lech, Kreis Landshut, Kreis Limburg-Weilburg, Kreis Ludwigsburg, Kreis Lörrach, Kreis Mayen-Koblenz, Kreis Miesbach, Kreis Miltenberg, Kreis Märkisch-Oderland, Kreis Neustadt/Aisch-Bad Windsheim, Kreis Neuwied, Kreis Nienburg / Weser, Kreis Nordfriesland, Kreis Ostallgäu, Kreis Osterholz, Kreis Pinneberg, Kreis Rastatt, Kreis Ravensburg, Kreis Reutlingen, Kreis Rotenburg (Wümme), Kreis Schaumburg, Kreis Sigmaringen, Kreis Starnberg, Kreis Steinfurt, Kreis Südwestpfalz, Kreis Traunstein, Kreis Trier-Saarburg, Kreis Uelzen, Kreis Vechta, Kreis Viersen, Kreis Vorpommern-Rügen, Kreis Waldshut, Kreis Weißenburg-Gunzenhausen, Kreis Wesermarsch, Kreis Würzburg, Kreisstadt Dietzenbach, Kreisstadt Friedberg, Kreisstadt Groß-Gerau, Kreisstadt St. Wendel, Kreiswerke Schmalkalden-Meiningen GmbH, Kreiswirtschaftsbetriebe Goslar, Kronberg im Taunus, KV Cochem-Zell, KWU Entsorgung Landkreis Oder-Spree, Landkreis Anhalt-Bitterfeld, Landkreis Ansbach, Landkreis Aschaffenburg, Landkreis Aschaffenburg (MyMuell App), Landkreis Bayreuth, Landkreis Berchtesgadener Land, Landkreis Biberach (MyMuell App), Landkreis Böblingen, Landkreis Böblingen, Landkreis Börde AöR (KsB), Landkreis Calw, Landkreis Coburg, Landkreis Eichstätt (MyMuell App), Landkreis Erding, Landkreis Erlangen-Höchstadt, Landkreis Esslingen, Landkreis Friesland (MyMuell App), Landkreis Fulda, Landkreis Gießen, Landkreis Gotha, Landkreis Grafschaft, Landkreis Görlitz, Landkreis Günzburg, Landkreis Hameln-Pyrmont, Landkreis Harz, Landkreis Heidenheim, Landkreis Heilbronn, Landkreis Kelheim, Landkreis Kronach, Landkreis Kulmbach, Landkreis Kusel, Landkreis Leer (MyMuell App), Landkreis Leipzig, Landkreis Limburg-Weilburg, Landkreis Lüchow-Dannenberg, Landkreis Main-Spessart, Landkreis Mettmann (MyMuell App), Landkreis Mühldorf a. Inn, Landkreis Nordwestmecklenburg, Landkreis Northeim (unofficial), Landkreis Ostallgäu, Landkreis Paderborn (MyMuell App), Landkreis Peine, Landkreis Ravensburg, Landkreis Reutlingen, Landkreis Rhön Grabfeld, Landkreis Rosenheim, Landkreis Rotenburg (Wümme), Landkreis Roth, Landkreis Roth, Landkreis Schweinfurt, Landkreis Schwäbisch Hall, Landkreis Sigmaringen, Landkreis soest, Landkreis Stade, Landkreis Stendal, Landkreis Südliche Weinstraße, Landkreis Tirschenreuth, Landkreis Tübingen, Landkreis Vogtland, Landkreis Weißenburg-Gunzenhausen, Landkreis Wittmund, Landkreis Wittmund (MyMuell App), Landkreis Wittmund (MyMuell App), Landkreis Wunsiedel im Fichtelgebirge, Landkreisbetriebe Neuburg-Schrobenhausen, Landratsamt Aichach-Friedberg, Landratsamt Bodenseekreis, Landratsamt Dachau, Landratsamt Main-Tauber-Kreis, Landratsamt Regensburg, Landratsamt Traunstein, Landratsamt Unterallgäu, Landshut, Langen, Lebacher Abfallzweckverband (LAZ), Leverkusen, LK Schwandorf, Ludwigshafen, Ludwigshafen am Rhein, Lübbecke (Jumomind), Lübeck Entsorgungsbetriebe, mags Mönchengladbacher Abfall-, Grün- und Straßenbetriebe AöR, Main-Kinzig-Kreis, Main-Kinzig-Kreis (MyMuell App), Mechernich und Kommunen, Mein-Abfallkalender.de, Metzingen, Minden, MZV Biedenkopf, Mühlheim am Main (MyMuell App), Müllabfuhr Deutschland, MüllALARM / Schönmackers, Müllmax, München Landkreis, Neckar-Odenwald-Kreis, Nenndorf (MyMuell App), Neu Ulm, Neumünster (MyMuell App), Neunkirchen Siegerland, Neustadt a.d. Waldnaab, Neustadt an der Weinstraße, Nordsachsen, Oberhavel, Oberhavel AWU, Oldenburg, Ortenaukreis, Ostholstein, Ostprignitz-Ruppin, Potsdam, Prignitz, Prignitz, Pullach im Isartal, Recklinghausen, RegioEntsorgung AöR, RegioEntsorgung Städteregion Aachen, Rhein-Hunsrück (Jumomind), Rhein-Hunsrück Entsorgung (RHE), Rhein-Neckar-Kreis, Rhein-Neckar-Kreis, Rhein-Pfalz-Kreis, Rosbach Vor Der Höhe, Rottweil, Rottweil, RSAG Rhein-Sieg-Kreis, Salzgitter (MyMuell App), Salzlandkreis, Schmitten im Taunus (MyMuell App), Schwarze Elster, Schwarzwald-Baar-Kreis, Schöneck (MyMuell App), Schönmackers, Sector 27 - Datteln, Marl, Oer-Erkenschwick, Seligenstadt (MyMuell App), Stadt Aachen, Stadt Arnsberg, Stadt Bayreuth, Stadt Cottbus, Stadt Darmstadt, Stadt Detmold, Stadt Dorsten, Stadt Emmendingen, Stadt Fulda, Stadt Haltern am See, Stadt Hamm, Stadt Hanau, Stadt Kaufbeuren, Stadt Koblenz, Stadt Landshut, Stadt Mainhausen, Stadt Maintal, Stadt Memmingen, Stadt Messstetten, Stadt Norderstedt, Stadt Osnabrück, Stadt Overath, Stadt Regensburg, Stadt Solingen, Stadt Unterschleißheim, Stadtbetrieb Frechen, Stadtbildpflege Kaiserslautern, Stadtentsorgung Rostock, Stadtreinigung Dresden, Stadtreinigung Hamburg, Stadtreinigung Leipzig, Stadtreinigung Leipzig, StadtService Brühl, Stadtwerke Erfurt, SWE, Stadtwerke Hürth, STL Lüdenscheid, Städteservice Raunheim Rüsselsheim, SWK Herford, Südbrandenburgischer Abfallzweckverband, TBR Remscheid, TBV Velbert, Team Orange (Landkreis Würzburg), Technischer Betriebsdienst Reutlingen, tonnenleerung.de LK Aichach-Friedberg + Neuburg-Schrobenhausen, Tuttlingen, Tuttlingen, Tübingen, Uckermark, ULM (EBU), Ulm (MyMuell App), USB Bochum, Usingen (MyMuell App), VIVO Landkreis Miesbach, Volkmarsen (MyMuell App), Vöhringen (MyMuell App), Waldshut, WBO Wirtschaftsbetriebe Oberhausen, Wegberg (MyMuell App), Wermelskirchen (Service Down), Westerholt (MyMuell App), Westerwaldkreis, WGV Recycling GmbH, Wilhelmshaven (MyMuell App), Wolfsburger Abfallwirtschaft und Straßenreinigung, WZV Kreis Segeberg, Würzburg, ZAH Hildesheim, ZAK Kempten, ZAW-SR Straubing, ZEW Zweckverband Entsorgungsregion West, ZfA Iserlohn, Zollernalbkreis, Zollernalbkreis, Zweckverband Abfallwirtschaft Kreis Bergstraße, Zweckverband Abfallwirtschaft Oberes Elbtal, Zweckverband Abfallwirtschaft Region Hannover, Zweckverband Abfallwirtschaft Saale-Orla, Zweckverband Abfallwirtschaft Schwalm-Eder-Kreis, Zweckverband Abfallwirtschaft Südwestsachsen (ZAS), Zweckverband München-Südost | | Hungary | FKF Budapest, FKF Budaörs, ÉTH (Érd, Diósd, Nagytarcsa, Sóskút, Tárnok) | | Italy | Contarina S.p.A | | Lithuania | Kauno švara, Telšių keliai | diff --git a/update_docu_links.py b/update_docu_links.py index 9a58fbb40..548cc1883 100755 --- a/update_docu_links.py +++ b/update_docu_links.py @@ -125,9 +125,6 @@ def split_camel_and_snake_case(s): def main() -> None: - parser = argparse.ArgumentParser(description="Update docu links.") - # args = parser.parse_args() - sources: list[SourceInfo] = [] sources += browse_sources() @@ -657,6 +654,10 @@ def make_country_code_map(): "code": "fr", "name": "France", }, + { + "code": "fi", + "name": "Finland", + }, ]