diff --git a/README.md b/README.md index 93b017473..9d011e539 100644 --- a/README.md +++ b/README.md @@ -1192,6 +1192,7 @@ If your service provider is not listed, feel free to open a [source request issu
Poland +- [App Moje Odpady](/doc/source/moje_odpady_pl.md) / moje-odpady.pl - [Bydgoszcz Pronatura](/doc/source/pronatura_bydgoszcz_pl.md) / pronatura.bydgoszcz.pl - [Ecoharmonogram](/doc/source/ecoharmonogram_pl.md) / ecoharmonogram.pl - [Gmina Miękinia](/doc/source/gmina_miekinia_pl.md) / api.skycms.com.pl diff --git a/custom_components/waste_collection_schedule/sources.json b/custom_components/waste_collection_schedule/sources.json index 2164b5d98..ae7487bd1 100644 --- a/custom_components/waste_collection_schedule/sources.json +++ b/custom_components/waste_collection_schedule/sources.json @@ -6359,6 +6359,11 @@ } ], "Poland": [ + { + "title": "App Moje Odpady", + "module": "moje_odpady_pl", + "default_params": {} + }, { "title": "Bydgoszcz Pronatura", "module": "pronatura_bydgoszcz_pl", diff --git a/custom_components/waste_collection_schedule/translations/en.json b/custom_components/waste_collection_schedule/translations/en.json index c38f16763..f209735cd 100644 --- a/custom_components/waste_collection_schedule/translations/en.json +++ b/custom_components/waste_collection_schedule/translations/en.json @@ -277,7 +277,9 @@ "app": "App", "service_provider": "Service Provider", "facility_id": "Facility Id", - "abfall": "Abfall" + "abfall": "Abfall", + "english": "English", + "voivodeship": "Voivodeship" }, "data_description": { "calendar_title": "A more readable, or user-friendly, name for the waste calendar. If nothing is provided, the name returned by the source will be used." @@ -526,7 +528,9 @@ "app": "App", "service_provider": "Service Provider", "facility_id": "Facility Id", - "abfall": "Abfall" + "abfall": "Abfall", + "english": "English", + "voivodeship": "Voivodeship" } } }, diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/source/moje_odpady_pl.py b/custom_components/waste_collection_schedule/waste_collection_schedule/source/moje_odpady_pl.py new file mode 100644 index 000000000..7a26b9bf2 --- /dev/null +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/moje_odpady_pl.py @@ -0,0 +1,211 @@ +from datetime import datetime + +import requests +from waste_collection_schedule import Collection # type: ignore[attr-defined] + +TITLE = "App Moje Odpady" +DESCRIPTION = "Source for App Moje Odpady." +URL = "https://moje-odpady.pl/" +TEST_CASES = { + "Aleksandr\u00f3w woj. \u015bl\u0105skie": { + "city": "Aleksandr\u00f3w", + "voivodeship": "woj. \u015bl\u0105skie", + }, + "with address and house_number": { + "city": "BASZKÓWKA", + "voivodeship": "woj. mazowieckie", + "address": "ANTONÓWKI", + "house_number": "Pozosta\u0142e", + }, + "english bin types": { + "city": "Marcin\u00f3w", + "voivodeship": "woj. \u0142\u00f3dzkie", + "english": True, + }, +} + + +ICON_MAP = { + "Trash": "mdi:trash-can", + "Glass": "mdi:bottle-soda", + "Bio": "mdi:leaf", + "Paper": "mdi:package-variant", + "Recycle": "mdi:recycle", +} + + +API_URL = "https://waste24.net/client/api/mywaste/v2/location_cities.php" + + +def make_comparable(text: str) -> str: + return text.lower().replace(" ", "").replace(".", "") + + +class Source: + def __init__( + self, + city: str, + voivodeship: str | None = None, + address: str | None = None, + house_number: str | None = None, + english: bool = False, + ): + self._city: str = make_comparable(city) + self._voivodeship: str | None = ( + make_comparable(voivodeship) if voivodeship else None + ) + self._address: str | None = make_comparable(address) if address else None + self._house_number: str | None = ( + make_comparable(house_number) if house_number else None + ) + self._english: bool = english + + self._org_number: int | None = None + self._real_city_name: str | None = None + self._real_address: str | None = None + self._real_house_number: str | None = None + + def _fetch_house_number(self): + payload = { + "org": self._org_number, + "city": self._real_city_name, + "address": self._real_address, + } + r = requests.post( + "https://waste24.net/client/api/mywaste/v2/location_addresses_nr.php", + json=payload, + ) + r.raise_for_status() + data = r.json() + if len(data) == 0: + return + if len(data) == 1: + self._real_house_number = data[0]["addressNr"] + return + + if self._house_number is None: + raise ValueError( + f"House number is required for this address, use one of {[a['nr'] for a in data]}" + ) + + self._real_house_number = None + for house_number in data: + if make_comparable(house_number["addressNr"]) == self._house_number: + self._real_house_number = house_number["addressNr"] + break + + if self._real_house_number is None: + raise ValueError( + f"House number {self._house_number} not found, use one of {[a['nr'] for a in data]}" + ) + + def _fetch_address(self): + payload = {"org": self._org_number, "city": self._real_city_name} + r = requests.post( + "https://waste24.net/client/api/mywaste/v2/location_addresses.php", + json=payload, + ) + r.raise_for_status() + + data = r.json() + if self._address is None: + raise ValueError( + f"Address is required for this city, use one of {[a['addressName'] for a in data]}" + ) + + countOfChilds = None + + for address in data: + if make_comparable(address["addressName"]) == self._address: + self._real_address = address["addressName"] + break + if self._real_address is None: + raise ValueError( + f"Address {self._address} not found, use one of {[a['addressName'] for a in data]}" + ) + + if countOfChilds: + self._fetch_house_number() + + def fetch_area(self): + r = requests.post( + "https://waste24.net/client/api/mywaste/v2/location_cities.php" + ) + r.raise_for_status() + + data = r.json() + city_matches = [] + + for city in data: + if make_comparable(city["cityName"]) == self._city: + city_matches.append(city) + + if not city_matches: + raise ValueError( + f"City {self._city} not found, use one of {[c['cityName'] for c in data]}" + ) + + self._org_number = None + self._real_city_name = None + countOfChilds = None + + if len(city_matches) == 1: + self._org_number = city_matches[0]["org"] + self._real_city_name = city_matches[0]["cityName"] + countOfChilds = city_matches[0]["countOfChilds"] + else: + if self._voivodeship is None: + raise ValueError( + f"Multiple cities found, specify voivodeship, use one of {[c['voivodeship'] for c in city_matches]}" + ) + for city in city_matches: + if make_comparable(city["voivodeship"]) == self._voivodeship: + self._org_number = city["org"] + self._real_city_name = city["cityName"] + countOfChilds = city["countOfChilds"] + break + if self._org_number is None: + raise ValueError( + f"City {self._city} in voivodeship {self._voivodeship} not found, use one of {[c['voivodeship'] for c in city_matches]}" + ) + + if countOfChilds: + self._fetch_address() + + def _get_scheduele(self) -> list[Collection]: + payload = { + "org": self._org_number, + "city": self._real_city_name, + "address": self._real_address or "", + "addressNr": self._real_house_number or "", + } + r = requests.post( + "https://waste24.net/client/api/mywaste/v2/schedule_list.php", json=payload + ) + r.raise_for_status() + data = r.json() + print(data) + entries = [] + for collection in data: + date_ = datetime.strptime(collection["data"], "%Y-%m-%d").date() + for containers in collection["containers"]: + bin_type = containers["containerName"] + if self._english: + bin_type = ( + containers.get("containerNameEn", bin_type).strip() or bin_type + ) + entries.append(Collection(date_, bin_type, ICON_MAP.get(bin_type))) + return entries + + def fetch(self) -> list[Collection]: + fresh_params = False + if self._org_number is None: + self.fetch_area() + try: + return self._get_scheduele() + except Exception: + if fresh_params: + raise + fresh_params = True + self.fetch_area() + return self._get_scheduele() diff --git a/doc/source/moje_odpady_pl.md b/doc/source/moje_odpady_pl.md new file mode 100644 index 000000000..bdd8ece17 --- /dev/null +++ b/doc/source/moje_odpady_pl.md @@ -0,0 +1,62 @@ +# App Moje Odpady + +Support for schedules provided by [App Moje Odpady](https://moje-odpady.pl/), serving multiple municipalities, Poland. + + + +## Configuration via configuration.yaml + +```yaml +waste_collection_schedule: + sources: + - name: moje_odpady_pl + args: + city: CITY + voivodeship: VPICODESHIP (województwo) + +``` + +### Configuration Variables + +**city** +*(String) (required)* + +**voivodeship** +*(String) (optional)* needed if there are multiple cities with the same name + +**address** +*(String) (optional)* only needed if the app asks for an address + +**house_number** +*(String) (optional)* rarely needed, only if the app asks for a house number + +**english** +*(String) (optional)* if set to "true" the app will return the schedule in English + +## Example + +```yaml +waste_collection_schedule: + sources: + - name: moje_odpady_pl + args: + city: Aleksandrów + voivodeship: woj. śląskie + english: False # optional parameter +``` + +```yaml +waste_collection_schedule: + sources: + - name: moje_odpady_pl + args: + city: BASZKÓWKA + voivodeship: woj. mazowieckie + address: ANTONÓWKI + house_number: Pozostałe + english: False # optional parameter +``` + +## How to get the source argument + +The parameters can be found by using the app ([GooglePlay](https://play.google.com/store/apps/details?id=com.mojeodpady&hl=pl), [AppStore](https://apps.apple.com/pl/app/waste-collection-schedule/id1248697353)) and checking the address input form. diff --git a/info.md b/info.md index ac473147f..8f5b5005a 100644 --- a/info.md +++ b/info.md @@ -32,7 +32,7 @@ Waste collection schedules from service provider web sites are updated daily, de | Netherlands | ACV Group, Alpen an den Rijn, Area Afval, Avalex, Avri, Bar Afvalbeheer, Circulus, Cyclus NV, Dar, Den Haag, GAD, Gemeente Almere, Gemeente Berkelland, Gemeente Cranendonck, Gemeente Hellendoorn, Gemeente Lingewaard, Gemeente Meppel, Gemeente Middelburg + Vlissingen, Gemeente Peel en Maas, Gemeente Schouwen-Duiveland, Gemeente Sudwest-Fryslan, Gemeente Venray, Gemeente Voorschoten, Gemeente Waalre, Gemeente Westland, HVC Groep, Meerlanden, Mijn Blink, PreZero, Purmerend, RAD BV, Reinis, Spaarnelanden, Twente Milieu, Waardlanden, Ximmio, ZRD, Ôffalkalinder van Noardeast-Fryslân & Dantumadiel | | New Zealand | Auckland Council, Christchurch City Council, Dunedin District Council, Gore, Invercargill & Southland, Hamilton City Council, Horowhenua District Council, Hutt City Council, Porirua City, Rotorua Lakes Council, Tauranga City Council, Waipa District Council, Wellington City Council | | Norway | BIR (Bergensområdets Interkommunale Renovasjonsselskap), Fosen Renovasjon, IRiS, Min Renovasjon, Movar IKS, Oslo Kommune, ReMidt Orkland muni, Sandnes Kommune, Stavanger Kommune, Trondheim | -| Poland | Bydgoszcz Pronatura, Ecoharmonogram, Gmina Miękinia, Koziegłowy/Objezierze/Oborniki, Poznań, Warsaw, Wrocław | +| Poland | App Moje Odpady, Bydgoszcz Pronatura, Ecoharmonogram, Gmina Miękinia, Koziegłowy/Objezierze/Oborniki, Poznań, Warsaw, Wrocław | | Slovenia | Moji odpadki, Ljubljana | | Sweden | Affärsverken, Boden, Borås Energi och Miljö, EDPEvent - Multi Source, Gästrike Återvinnare, Jönköping - June Avfall & Miljö, Landskrona - Svalövs Renhållning, Lerum Vatten och Avlopp, Linköping - Tekniska Verken, Lund Waste Collection, Mölndal, Norrtalje Vatten & Avfall, North / Middle Bohuslän - Rambo AB, Region Gotland, Ronneby Miljöteknik, Samverkan Återvinning Miljö (SÅM), Skellefteå, SRV Återvinning, SSAM (Deprecated), SSAM Södra Smalånds Avfall & Miljö, Sysav Sophämntning, Uppsala Vatten, Uppsala Vatten och Avfall AB (Deprecated), VA Syd Sophämntning, VIVAB Sophämtning | | Switzerland | A-Region, Alchenstorf, Andwil, Appenzell, Berg, Bühler, Canton of Zürich, Eggersriet, Gais, Gaiserwald, Goldach, Grosswangen, Grub, Heiden, Herisau, Horn, Hundwil, Häggenschwil, Lindau, Lutzenberg, Muolen, Mörschwil, Münchenstein, Münsingen BE, Switzerland, Real Luzern, Rehetobel, Rorschach, Rorschacherberg, Schwellbrunn, Schönengrund, Speicher, Stein, Steinach, Teufen, Thal, Trogen, Tübach, Untereggen, Urnäsch, Wald, Waldkirch, Waldstatt, Wittenbach, Wolfhalden |