mampfes / hacs_waste_collection_schedule

Home Assistant integration framework for (garbage collection) schedules
MIT License
1.08k stars 658 forks source link

Adding Bonn Orange #1309

Closed morzan1001 closed 1 year ago

morzan1001 commented 1 year ago

Hi,

today I played around with this HomeAssistant integration and tried to add the appointments from Bonn Orange (https://www.bonnorange.de/service/privatpersonen/abfuhrtermine/termine). Bonn Orange offers an ICS download on their website and an app powered by "AbfallPlus".

I decided to try to pull data from the app, as that seemed easier than parsing the ICS correctly. I was able to use a proxy to pull the necessary information about waste types, street and municipality from the app, but no API key. The app uses a cookie for authentication. However, the cookie contained an MD5 hash (with a few other things) and I just tried to use this hash as a key.

Unfortunately, I now get the following errors in the Homeassistant:

Traceback (most recent call last):
  File "/config/custom_components/waste_collection_schedule/waste_collection_schedule/source_shell.py", line 134, in fetch
    entries = self._source.fetch()
              ^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/waste_collection_schedule/waste_collection_schedule/source/abfall_io.py", line 170, in fetch
    dates = self._ics.convert(ics_file)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/config/custom_components/waste_collection_schedule/waste_collection_schedule/service/ICS.py", line 38, in convert
    events: List[Any] = icalevents.events(
                        ^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/icalevents/icalevents.py", line 57, in events
    found_events += parse_events(
                    ^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/icalevents/icalparser.py", line 286, in parse_events
    raise ValueError("Content is invalid!")
ValueError: Content is invalid!

I assume that this is because my key is not correct and the API does not respond with an ICS file or that something else is wrong with the response. Unfortunately I have not found a way to debug this a bit more precisely. Do any of you have a tip for me on how to check the API's response or if my way to get the data from the app (cookie vs. key) works at all?

Thanks a lot :)

5ila5 commented 1 year ago

The website is Athos based and therefore pretty similar to other source we already implemented (abfallwirtschaft_pforzheim_de, awb_emsland_de, bielefed_de, bmv_at, meinawb_de, regioentsorgung_de, rh_etsorgung_de, rv_de, vielefeld_de). The apps are using a different API than the abfallplus source. I started writing an integration for all the abfallplus apps (https://github.com/5ila5/hacs_waste_collection_schedule/tree/add_service_appabfallplusde) but it's not yet completely finished and tested (did not worked on this for some weeks)

morzan1001 commented 1 year ago

Hey, you were absolutely right, I just had to adjust a few little things and it works. Thanks for your tip 👍

I have copied my bonn_orange.py here in case someone else can use it.

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

# Source code based on rh_entsorgung_de.md
TITLE = "Bonn Orange"
DESCRIPTION = "Source for Bonn Orange."
URL = "https://www.bonnorange.de"
TEST_CASES = {
    "Abenteuerweg": {
        "street": "Abenteuerweg",
        "house_number": 1,
        "address_suffix": "",
    },
    "Oppelner Str.": {
        "street": "Oppelner Str.",
        "house_number": 65,
    }
}

ICON_MAP = {
    "Restmuell": "mdi:trash-can",
    "Biobehaelter": "mdi:leaf",
    "Papierbehaelter": "mdi:package-variant",
    "Gelbe": "mdi:recycle",
    "Grossmuellbehaelter": "mdi:delete-circle",
}

API_URL = "https://www5.bonn.de/WasteManagementBonnOrange/WasteManagementServlet"

# Parser for HTML input (hidden) text

class HiddenInputParser(HTMLParser):
    def __init__(self):
        super().__init__()
        self._args = {}

    @property
    def args(self):
        return self._args

    def handle_starttag(self, tag, attrs):
        if tag == "input":
            d = dict(attrs)
            if str(d["type"]).lower() == "hidden":
                self._args[d["name"]] = d["value"] if "value" in d else ""

class Source:
    def __init__(self, street: str, house_number: int, address_suffix: str = ""):
        self._street = street
        self._hnr = house_number
        self._suffix = address_suffix
        self._ics = ICS()

    def fetch(self):
        session = requests.session()

        r = session.get(
            API_URL,
            params={"SubmitAction": "wasteDisposalServices",
                    "InFrameMode": "TRUE"},
        )
        r.raise_for_status()
        r.encoding = "utf-8"

        parser = HiddenInputParser()
        parser.feed(r.text)

        args = parser.args
        args["Ort"] = self._street[0].upper()
        args["Strasse"] = self._street
        args["Hausnummer"] = str(self._hnr)
        args["Hausnummerzusatz"] = self._suffix
        args["SubmitAction"] = "CITYCHANGED"
        r = session.post(
            API_URL,
            data=args,
        )
        r.raise_for_status()

        args["SubmitAction"] = "forward"
        args["ContainerGewaehltRM"] = "on"
        args["ContainerGewaehltBM"] = "on"
        args["ContainerGewaehltLVP"] = "on"
        args["ContainerGewaehltPA"] = "on"
        args["ContainerGewaehltPrMuell"] = "on"
        r = session.post(
            API_URL,
            data=args,
        )
        r.raise_for_status()

        args["ApplicationName"] = "com.athos.kd.bonn.AbfuhrTerminModel"
        args["SubmitAction"] = "filedownload_ICAL"

        r = session.post(
            API_URL,
            data=args,
        )
        r.raise_for_status()

        dates = self._ics.convert(r.text)

        entries = []
        for d in dates:
            entries.append(
                Collection(
                    d[0], d[1], ICON_MAP.get(d[1].split(" ")[0])
                )
            )
        return entries