We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
ICS Scraper error
My Home-Assistant has the following error:

`2021-12-02 06:53:42 ERROR (SyncWorker_19) [waste_collection_schedule.scraper] fetch failed for source <waste_collection_schedule.source.ics.Source object at 0x7f3785601ca0>: 'NoneType' object does not support item assignment`

I have events for this year, but not for the next. I know, that the ICS scraper is trying to get events for the next year, when it's december. I don't know if this years events are cached or not. But I assume that the issue is because of the next year scraping. With this in mind I searched the source code.
My Python is quite bad, but I assume, that the problem is in line [162](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/custom_components/waste_collection_schedule/waste_collection_schedule/source/ics.py#L162). I think this line should look like the block [151-155](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/custom_components/waste_collection_schedule/waste_collection_schedule/source/ics.py#L151-L155).

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `custom_components/waste_collection_schedule/waste_collection_schedule/source/ics.py`
Content:
```
1 import datetime
2 import logging
3 from pathlib import Path
4 
5 import requests
6 from waste_collection_schedule import Collection  # type: ignore[attr-defined]
7 from waste_collection_schedule.service.ICS import ICS
8 
9 TITLE = "ICS"
10 DESCRIPTION = "Source for ICS based schedules."
11 URL = None
12 TEST_CASES = {
13     "Dortmund, Dudenstr. 5": {
14         "url": "https://www.edg.de/ical/kalender.ics?Strasse=Dudenstr.&Hausnummer=5&Erinnerung=-1&Abfallart=1,2,3,4"
15     },
16     "Leipzig, Sandgrubenweg 27": {
17         "url": "https://stadtreinigung-leipzig.de/wir-kommen-zu-ihnen/abfallkalender/ical.ics?position_nos=38296&name=Sandgrubenweg%2027"
18     },
19     "Ludwigsburg": {
20         "url": "https://www.avl-ludwigsburg.de/fileadmin/Files/Abfallkalender/ICS/Privat/Privat_{%Y}_Ossweil.ics"
21     },
22     "Esslingen, Bahnhof": {
23         "url": "https://api.abfall.io/?kh=DaA02103019b46345f1998698563DaAd&t=ics&s=1a862df26f6943997cef90233877a4fe"
24     },
25     "Test File": {
26         # Path is used here to allow to call the Source from any location.
27         # This is not required in a yaml configuration!
28         "file": str(Path(__file__).resolve().parents[1].joinpath("test/test.ics"))
29     },
30     "Test File (recurring)": {
31         # Path is used here to allow to call the Source from any location.
32         # This is not required in a yaml configuration!
33         "file": str(Path(__file__).resolve().parents[1].joinpath("test/recurring.ics"))
34     },
35     "München, Bahnstr. 11": {
36         "url": "https://www.awm-muenchen.de/entsorgen/abfuhrkalender?tx_awmabfuhrkalender_abfuhrkalender%5Bhausnummer%5D=11&tx_awmabfuhrkalender_abfuhrkalender%5Bleerungszyklus%5D%5BB%5D=1%2F2%3BU&tx_awmabfuhrkalender_abfuhrkalender%5Bleerungszyklus%5D%5BP%5D=1%2F2%3BG&tx_awmabfuhrkalender_abfuhrkalender%5Bleerungszyklus%5D%5BR%5D=001%3BU&tx_awmabfuhrkalender_abfuhrkalender%5Bsection%5D=ics&tx_awmabfuhrkalender_abfuhrkalender%5Bsinglestandplatz%5D=false&tx_awmabfuhrkalender_abfuhrkalender%5Bstandplatzwahl%5D=true&tx_awmabfuhrkalender_abfuhrkalender%5Bstellplatz%5D%5Bbio%5D=70024507&tx_awmabfuhrkalender_abfuhrkalender%5Bstellplatz%5D%5Bpapier%5D=70024507&tx_awmabfuhrkalender_abfuhrkalender%5Bstellplatz%5D%5Brestmuell%5D=70024507&tx_awmabfuhrkalender_abfuhrkalender%5Bstrasse%5D=bahnstr.&tx_awmabfuhrkalender_abfuhrkalender%5Byear%5D={%Y}"
37     },
38     "Buxtehude, Am Berg": {
39         "url": "https://abfall.landkreis-stade.de/api_v2/collection_dates/1/ort/10/strasse/90/hausnummern/1/abfallarten/R02-R04-B02-D04-D12-P04-R12-R14-W0-R22-R24-R31/kalender.ics"
40     },
41     #    "Hausmüllinfo: ASR Chemnitz": {
42     #        "url": "https://asc.hausmuell.info/ics/ics.php",
43     #        "method": "POST",
44     #        "params": {
45     #            "hidden_id_egebiet": 439087,
46     #            "input_ort": "Chemnitz",
47     #            "input_str": "Straße der Nationen",
48     #            "input_hnr": 2,
49     #            "hidden_send_btn": "ics",
50     #            # "hiddenYear": 2021,
51     #            "hidden_id_ort": 10,
52     #            "hidden_id_ortsteil": 0,
53     #            "hidden_id_str": 17814,
54     #            "hidden_id_hnr": 5538100,
55     #            "hidden_kalenderart": "privat",
56     #            "showBinsBio": "on",
57     #            "showBinsRest": "on",
58     #            "showBinsRest_rc": "on",
59     #            "showBinsPapier": "on",
60     #            "showBinsOrganic": "on",
61     #            "showBinsXmas": "on",
62     #            "showBinsDsd": "on",
63     #            "showBinsProb": "on",
64     #        },
65     #        "year_field": "hiddenYear",
66     #    },
67     "Abfall Zollernalbkreis, Ebingen": {
68         "url": "https://www.abfallkalender-zak.de",
69         "params": {
70             "city": "2,3,4",
71             "street": "3",
72             "types[]": [
73                 "restmuell",
74                 "gelbersack",
75                 "papiertonne",
76                 "biomuell",
77                 "gruenabfall",
78                 "schadstoffsammlung",
79                 "altpapiersammlung",
80                 "schrottsammlung",
81                 "weihnachtsbaeume",
82                 "elektrosammlung",
83             ],
84             "go_ics": "Download",
85         },
86         "year_field": "year",
87     },
88     "Detmold": {
89         "url": "https://abfuhrkalender.detmold.de/icsmaker.php",
90         "method": "GET",
91         "params": {"strid": 338},
92         "year_field": "year",
93     },
94     "EAW Rheingau Taunus": {
95         "url": "https://www.eaw-rheingau-taunus.de/abfallkalender/calendar.ics?streetid=1429",
96         "split_at": ",",
97     },
98     "Recollect, Ottawa": {
99         "url": "https://recollect.a.ssl.fastly.net/api/places/BCCDF30E-578B-11E4-AD38-5839C200407A/services/208/events.en.ics",
100         "split_at": "\\, [and ]*",
101     },
102     "Frankfurt am Main, Achenbachstrasse 3": {
103         "url": "https://www.fes-frankfurt.de/abfallkalender/QWNoZW5iYWNoc3RyLnwzfDYwNTk2.ics"
104     },
105     "Erlensee, Am Haspel": {
106         "url": "https://sperrmuell.erlensee.de/?type=reminder",
107         "method": "POST",
108         "params": {
109             "street": 8,
110             "eventType[]": [27, 23, 19, 20, 21, 24, 22, 25, 26],
111             "timeframe": 23,
112             "download": "ical",
113         },
114     },
115 }
116 
117 
118 HEADERS = {"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}
119 _LOGGER = logging.getLogger(__name__)
120 
121 
122 class Source:
123     def __init__(
124         self,
125         url=None,
126         file=None,
127         offset=None,
128         params=None,
129         year_field=None,
130         method="GET",
131         split_at=None,
132     ):
133         self._url = url
134         self._file = file
135         if bool(self._url is not None) == bool(self._file is not None):
136             raise RuntimeError("Specify either url or file")
137         self._ics = ICS(offset=offset, split_at=split_at)
138         self._params = params
139         self._year_field = year_field  # replace this field in params with current year
140         self._method = method  # The method to send the params
141 
142     def fetch(self):
143         if self._url is not None:
144             if "{%Y}" in self._url or self._year_field is not None:
145                 # url contains wildcard or params contains year field
146                 now = datetime.datetime.now()
147 
148                 # replace year in url
149                 url = self._url.replace("{%Y}", str(now.year))
150 
151                 # replace year in params
152                 if self._year_field is not None:
153                     if self._params is None:
154                         raise RuntimeError("year_field specified without params")
155                     self._params[self._year_field] = str(now.year)
156 
157                 entries = self.fetch_url(url, self._params)
158 
159                 if now.month == 12:
160                     # also get data for next year if we are already in december
161                     url = self._url.replace("{%Y}", str(now.year + 1))
162                     self._params[self._year_field] = str(now.year + 1)
163 
164                     try:
165                         entries.extend(self.fetch_url(url), self._params)
166                     except Exception:
167                         # ignore if fetch for next year fails
168                         pass
169                 return entries
170             else:
171                 return self.fetch_url(self._url, self._params)
172         elif self._file is not None:
173             return self.fetch_file(self._file)
174 
175     def fetch_url(self, url, params=None):
176         # get ics file
177         if self._method == "GET":
178             r = requests.get(url, params=params, headers=HEADERS)
179         elif self._method == "POST":
180             r = requests.post(url, data=params, headers=HEADERS)
181         else:
182             _LOGGER.error(
183                 "Error: unknown method to fetch URL, use GET or POST; got %s"
184                 % self._method
185             )
186             return "error"
187         r.encoding = "utf-8"  # requests doesn't guess the encoding correctly
188 
189         # check the return code
190         if not r.ok:
191             _LOGGER.error(
192                 "Error: the response is not ok; need code 200, but got code %s"
193                 % r.status_code
194             )
195             return "error"
196 
197         return self._convert(r.text)
198 
199     def fetch_file(self, file):
200         f = open(file)
201         return self._convert(f.read())
202 
203     def _convert(self, data):
204         dates = self._ics.convert(data)
205 
206         entries = []
207         for d in dates:
208             entries.append(Collection(d[0], d[1]))
209         return entries
210
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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
--- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ics.py
+++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ics.py
@@ -159,7 +159,8 @@
                 if now.month == 12:
                     # also get data for next year if we are already in december
                     url = self._url.replace("{%Y}", str(now.year + 1))
-                    self._params[self._year_field] = str(now.year + 1)
+                    if self._year_field is not None:
+                        self._params[self._year_field] = str(now.year + 1)
 
                     try:
                         entries.extend(self.fetch_url(url), self._params)

{"golden_diff": "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\n--- a/custom_components/waste_collection_schedule/waste_collection_schedule/source/ics.py\n+++ b/custom_components/waste_collection_schedule/waste_collection_schedule/source/ics.py\n@@ -159,7 +159,8 @@\n                 if now.month == 12:\n                     # also get data for next year if we are already in december\n                     url = self._url.replace(\"{%Y}\", str(now.year + 1))\n-                    self._params[self._year_field] = str(now.year + 1)\n+                    if self._year_field is not None:\n+                        self._params[self._year_field] = str(now.year + 1)\n \n                     try:\n                         entries.extend(self.fetch_url(url), self._params)\n", "issue": "ICS Scraper error\nMy Home-Assistant has the following error:\r\n\r\n`2021-12-02 06:53:42 ERROR (SyncWorker_19) [waste_collection_schedule.scraper] fetch failed for source <waste_collection_schedule.source.ics.Source object at 0x7f3785601ca0>: 'NoneType' object does not support item assignment`\r\n\r\nI have events for this year, but not for the next. I know, that the ICS scraper is trying to get events for the next year, when it's december. I don't know if this years events are cached or not. But I assume that the issue is because of the next year scraping. With this in mind I searched the source code.\r\nMy Python is quite bad, but I assume, that the problem is in line [162](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/custom_components/waste_collection_schedule/waste_collection_schedule/source/ics.py#L162). I think this line should look like the block [151-155](https://github.com/mampfes/hacs_waste_collection_schedule/blob/master/custom_components/waste_collection_schedule/waste_collection_schedule/source/ics.py#L151-L155).\n", "before_files": [{"content": "import datetime\nimport logging\nfrom pathlib import Path\n\nimport requests\nfrom waste_collection_schedule import Collection  # type: ignore[attr-defined]\nfrom waste_collection_schedule.service.ICS import ICS\n\nTITLE = \"ICS\"\nDESCRIPTION = \"Source for ICS based schedules.\"\nURL = None\nTEST_CASES = {\n    \"Dortmund, Dudenstr. 5\": {\n        \"url\": \"https://www.edg.de/ical/kalender.ics?Strasse=Dudenstr.&Hausnummer=5&Erinnerung=-1&Abfallart=1,2,3,4\"\n    },\n    \"Leipzig, Sandgrubenweg 27\": {\n        \"url\": \"https://stadtreinigung-leipzig.de/wir-kommen-zu-ihnen/abfallkalender/ical.ics?position_nos=38296&name=Sandgrubenweg%2027\"\n    },\n    \"Ludwigsburg\": {\n        \"url\": \"https://www.avl-ludwigsburg.de/fileadmin/Files/Abfallkalender/ICS/Privat/Privat_{%Y}_Ossweil.ics\"\n    },\n    \"Esslingen, Bahnhof\": {\n        \"url\": \"https://api.abfall.io/?kh=DaA02103019b46345f1998698563DaAd&t=ics&s=1a862df26f6943997cef90233877a4fe\"\n    },\n    \"Test File\": {\n        # Path is used here to allow to call the Source from any location.\n        # This is not required in a yaml configuration!\n        \"file\": str(Path(__file__).resolve().parents[1].joinpath(\"test/test.ics\"))\n    },\n    \"Test File (recurring)\": {\n        # Path is used here to allow to call the Source from any location.\n        # This is not required in a yaml configuration!\n        \"file\": str(Path(__file__).resolve().parents[1].joinpath(\"test/recurring.ics\"))\n    },\n    \"M\u00fcnchen, Bahnstr. 11\": {\n        \"url\": \"https://www.awm-muenchen.de/entsorgen/abfuhrkalender?tx_awmabfuhrkalender_abfuhrkalender%5Bhausnummer%5D=11&tx_awmabfuhrkalender_abfuhrkalender%5Bleerungszyklus%5D%5BB%5D=1%2F2%3BU&tx_awmabfuhrkalender_abfuhrkalender%5Bleerungszyklus%5D%5BP%5D=1%2F2%3BG&tx_awmabfuhrkalender_abfuhrkalender%5Bleerungszyklus%5D%5BR%5D=001%3BU&tx_awmabfuhrkalender_abfuhrkalender%5Bsection%5D=ics&tx_awmabfuhrkalender_abfuhrkalender%5Bsinglestandplatz%5D=false&tx_awmabfuhrkalender_abfuhrkalender%5Bstandplatzwahl%5D=true&tx_awmabfuhrkalender_abfuhrkalender%5Bstellplatz%5D%5Bbio%5D=70024507&tx_awmabfuhrkalender_abfuhrkalender%5Bstellplatz%5D%5Bpapier%5D=70024507&tx_awmabfuhrkalender_abfuhrkalender%5Bstellplatz%5D%5Brestmuell%5D=70024507&tx_awmabfuhrkalender_abfuhrkalender%5Bstrasse%5D=bahnstr.&tx_awmabfuhrkalender_abfuhrkalender%5Byear%5D={%Y}\"\n    },\n    \"Buxtehude, Am Berg\": {\n        \"url\": \"https://abfall.landkreis-stade.de/api_v2/collection_dates/1/ort/10/strasse/90/hausnummern/1/abfallarten/R02-R04-B02-D04-D12-P04-R12-R14-W0-R22-R24-R31/kalender.ics\"\n    },\n    #    \"Hausm\u00fcllinfo: ASR Chemnitz\": {\n    #        \"url\": \"https://asc.hausmuell.info/ics/ics.php\",\n    #        \"method\": \"POST\",\n    #        \"params\": {\n    #            \"hidden_id_egebiet\": 439087,\n    #            \"input_ort\": \"Chemnitz\",\n    #            \"input_str\": \"Stra\u00dfe der Nationen\",\n    #            \"input_hnr\": 2,\n    #            \"hidden_send_btn\": \"ics\",\n    #            # \"hiddenYear\": 2021,\n    #            \"hidden_id_ort\": 10,\n    #            \"hidden_id_ortsteil\": 0,\n    #            \"hidden_id_str\": 17814,\n    #            \"hidden_id_hnr\": 5538100,\n    #            \"hidden_kalenderart\": \"privat\",\n    #            \"showBinsBio\": \"on\",\n    #            \"showBinsRest\": \"on\",\n    #            \"showBinsRest_rc\": \"on\",\n    #            \"showBinsPapier\": \"on\",\n    #            \"showBinsOrganic\": \"on\",\n    #            \"showBinsXmas\": \"on\",\n    #            \"showBinsDsd\": \"on\",\n    #            \"showBinsProb\": \"on\",\n    #        },\n    #        \"year_field\": \"hiddenYear\",\n    #    },\n    \"Abfall Zollernalbkreis, Ebingen\": {\n        \"url\": \"https://www.abfallkalender-zak.de\",\n        \"params\": {\n            \"city\": \"2,3,4\",\n            \"street\": \"3\",\n            \"types[]\": [\n                \"restmuell\",\n                \"gelbersack\",\n                \"papiertonne\",\n                \"biomuell\",\n                \"gruenabfall\",\n                \"schadstoffsammlung\",\n                \"altpapiersammlung\",\n                \"schrottsammlung\",\n                \"weihnachtsbaeume\",\n                \"elektrosammlung\",\n            ],\n            \"go_ics\": \"Download\",\n        },\n        \"year_field\": \"year\",\n    },\n    \"Detmold\": {\n        \"url\": \"https://abfuhrkalender.detmold.de/icsmaker.php\",\n        \"method\": \"GET\",\n        \"params\": {\"strid\": 338},\n        \"year_field\": \"year\",\n    },\n    \"EAW Rheingau Taunus\": {\n        \"url\": \"https://www.eaw-rheingau-taunus.de/abfallkalender/calendar.ics?streetid=1429\",\n        \"split_at\": \",\",\n    },\n    \"Recollect, Ottawa\": {\n        \"url\": \"https://recollect.a.ssl.fastly.net/api/places/BCCDF30E-578B-11E4-AD38-5839C200407A/services/208/events.en.ics\",\n        \"split_at\": \"\\\\, [and ]*\",\n    },\n    \"Frankfurt am Main, Achenbachstrasse 3\": {\n        \"url\": \"https://www.fes-frankfurt.de/abfallkalender/QWNoZW5iYWNoc3RyLnwzfDYwNTk2.ics\"\n    },\n    \"Erlensee, Am Haspel\": {\n        \"url\": \"https://sperrmuell.erlensee.de/?type=reminder\",\n        \"method\": \"POST\",\n        \"params\": {\n            \"street\": 8,\n            \"eventType[]\": [27, 23, 19, 20, 21, 24, 22, 25, 26],\n            \"timeframe\": 23,\n            \"download\": \"ical\",\n        },\n    },\n}\n\n\nHEADERS = {\"user-agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64)\"}\n_LOGGER = logging.getLogger(__name__)\n\n\nclass Source:\n    def __init__(\n        self,\n        url=None,\n        file=None,\n        offset=None,\n        params=None,\n        year_field=None,\n        method=\"GET\",\n        split_at=None,\n    ):\n        self._url = url\n        self._file = file\n        if bool(self._url is not None) == bool(self._file is not None):\n            raise RuntimeError(\"Specify either url or file\")\n        self._ics = ICS(offset=offset, split_at=split_at)\n        self._params = params\n        self._year_field = year_field  # replace this field in params with current year\n        self._method = method  # The method to send the params\n\n    def fetch(self):\n        if self._url is not None:\n            if \"{%Y}\" in self._url or self._year_field is not None:\n                # url contains wildcard or params contains year field\n                now = datetime.datetime.now()\n\n                # replace year in url\n                url = self._url.replace(\"{%Y}\", str(now.year))\n\n                # replace year in params\n                if self._year_field is not None:\n                    if self._params is None:\n                        raise RuntimeError(\"year_field specified without params\")\n                    self._params[self._year_field] = str(now.year)\n\n                entries = self.fetch_url(url, self._params)\n\n                if now.month == 12:\n                    # also get data for next year if we are already in december\n                    url = self._url.replace(\"{%Y}\", str(now.year + 1))\n                    self._params[self._year_field] = str(now.year + 1)\n\n                    try:\n                        entries.extend(self.fetch_url(url), self._params)\n                    except Exception:\n                        # ignore if fetch for next year fails\n                        pass\n                return entries\n            else:\n                return self.fetch_url(self._url, self._params)\n        elif self._file is not None:\n            return self.fetch_file(self._file)\n\n    def fetch_url(self, url, params=None):\n        # get ics file\n        if self._method == \"GET\":\n            r = requests.get(url, params=params, headers=HEADERS)\n        elif self._method == \"POST\":\n            r = requests.post(url, data=params, headers=HEADERS)\n        else:\n            _LOGGER.error(\n                \"Error: unknown method to fetch URL, use GET or POST; got %s\"\n                % self._method\n            )\n            return \"error\"\n        r.encoding = \"utf-8\"  # requests doesn't guess the encoding correctly\n\n        # check the return code\n        if not r.ok:\n            _LOGGER.error(\n                \"Error: the response is not ok; need code 200, but got code %s\"\n                % r.status_code\n            )\n            return \"error\"\n\n        return self._convert(r.text)\n\n    def fetch_file(self, file):\n        f = open(file)\n        return self._convert(f.read())\n\n    def _convert(self, data):\n        dates = self._ics.convert(data)\n\n        entries = []\n        for d in dates:\n            entries.append(Collection(d[0], d[1]))\n        return entries\n", "path": "custom_components/waste_collection_schedule/waste_collection_schedule/source/ics.py"}], "after_files": [{"content": "import datetime\nimport logging\nfrom pathlib import Path\n\nimport requests\nfrom waste_collection_schedule import Collection  # type: ignore[attr-defined]\nfrom waste_collection_schedule.service.ICS import ICS\n\nTITLE = \"ICS\"\nDESCRIPTION = \"Source for ICS based schedules.\"\nURL = None\nTEST_CASES = {\n    \"Dortmund, Dudenstr. 5\": {\n        \"url\": \"https://www.edg.de/ical/kalender.ics?Strasse=Dudenstr.&Hausnummer=5&Erinnerung=-1&Abfallart=1,2,3,4\"\n    },\n    \"Leipzig, Sandgrubenweg 27\": {\n        \"url\": \"https://stadtreinigung-leipzig.de/wir-kommen-zu-ihnen/abfallkalender/ical.ics?position_nos=38296&name=Sandgrubenweg%2027\"\n    },\n    \"Ludwigsburg\": {\n        \"url\": \"https://www.avl-ludwigsburg.de/fileadmin/Files/Abfallkalender/ICS/Privat/Privat_{%Y}_Ossweil.ics\"\n    },\n    \"Esslingen, Bahnhof\": {\n        \"url\": \"https://api.abfall.io/?kh=DaA02103019b46345f1998698563DaAd&t=ics&s=1a862df26f6943997cef90233877a4fe\"\n    },\n    \"Test File\": {\n        # Path is used here to allow to call the Source from any location.\n        # This is not required in a yaml configuration!\n        \"file\": str(Path(__file__).resolve().parents[1].joinpath(\"test/test.ics\"))\n    },\n    \"Test File (recurring)\": {\n        # Path is used here to allow to call the Source from any location.\n        # This is not required in a yaml configuration!\n        \"file\": str(Path(__file__).resolve().parents[1].joinpath(\"test/recurring.ics\"))\n    },\n    \"M\u00fcnchen, Bahnstr. 11\": {\n        \"url\": \"https://www.awm-muenchen.de/entsorgen/abfuhrkalender?tx_awmabfuhrkalender_abfuhrkalender%5Bhausnummer%5D=11&tx_awmabfuhrkalender_abfuhrkalender%5Bleerungszyklus%5D%5BB%5D=1%2F2%3BU&tx_awmabfuhrkalender_abfuhrkalender%5Bleerungszyklus%5D%5BP%5D=1%2F2%3BG&tx_awmabfuhrkalender_abfuhrkalender%5Bleerungszyklus%5D%5BR%5D=001%3BU&tx_awmabfuhrkalender_abfuhrkalender%5Bsection%5D=ics&tx_awmabfuhrkalender_abfuhrkalender%5Bsinglestandplatz%5D=false&tx_awmabfuhrkalender_abfuhrkalender%5Bstandplatzwahl%5D=true&tx_awmabfuhrkalender_abfuhrkalender%5Bstellplatz%5D%5Bbio%5D=70024507&tx_awmabfuhrkalender_abfuhrkalender%5Bstellplatz%5D%5Bpapier%5D=70024507&tx_awmabfuhrkalender_abfuhrkalender%5Bstellplatz%5D%5Brestmuell%5D=70024507&tx_awmabfuhrkalender_abfuhrkalender%5Bstrasse%5D=bahnstr.&tx_awmabfuhrkalender_abfuhrkalender%5Byear%5D={%Y}\"\n    },\n    \"Buxtehude, Am Berg\": {\n        \"url\": \"https://abfall.landkreis-stade.de/api_v2/collection_dates/1/ort/10/strasse/90/hausnummern/1/abfallarten/R02-R04-B02-D04-D12-P04-R12-R14-W0-R22-R24-R31/kalender.ics\"\n    },\n    #    \"Hausm\u00fcllinfo: ASR Chemnitz\": {\n    #        \"url\": \"https://asc.hausmuell.info/ics/ics.php\",\n    #        \"method\": \"POST\",\n    #        \"params\": {\n    #            \"hidden_id_egebiet\": 439087,\n    #            \"input_ort\": \"Chemnitz\",\n    #            \"input_str\": \"Stra\u00dfe der Nationen\",\n    #            \"input_hnr\": 2,\n    #            \"hidden_send_btn\": \"ics\",\n    #            # \"hiddenYear\": 2021,\n    #            \"hidden_id_ort\": 10,\n    #            \"hidden_id_ortsteil\": 0,\n    #            \"hidden_id_str\": 17814,\n    #            \"hidden_id_hnr\": 5538100,\n    #            \"hidden_kalenderart\": \"privat\",\n    #            \"showBinsBio\": \"on\",\n    #            \"showBinsRest\": \"on\",\n    #            \"showBinsRest_rc\": \"on\",\n    #            \"showBinsPapier\": \"on\",\n    #            \"showBinsOrganic\": \"on\",\n    #            \"showBinsXmas\": \"on\",\n    #            \"showBinsDsd\": \"on\",\n    #            \"showBinsProb\": \"on\",\n    #        },\n    #        \"year_field\": \"hiddenYear\",\n    #    },\n    \"Abfall Zollernalbkreis, Ebingen\": {\n        \"url\": \"https://www.abfallkalender-zak.de\",\n        \"params\": {\n            \"city\": \"2,3,4\",\n            \"street\": \"3\",\n            \"types[]\": [\n                \"restmuell\",\n                \"gelbersack\",\n                \"papiertonne\",\n                \"biomuell\",\n                \"gruenabfall\",\n                \"schadstoffsammlung\",\n                \"altpapiersammlung\",\n                \"schrottsammlung\",\n                \"weihnachtsbaeume\",\n                \"elektrosammlung\",\n            ],\n            \"go_ics\": \"Download\",\n        },\n        \"year_field\": \"year\",\n    },\n    \"Detmold\": {\n        \"url\": \"https://abfuhrkalender.detmold.de/icsmaker.php\",\n        \"method\": \"GET\",\n        \"params\": {\"strid\": 338},\n        \"year_field\": \"year\",\n    },\n    \"EAW Rheingau Taunus\": {\n        \"url\": \"https://www.eaw-rheingau-taunus.de/abfallkalender/calendar.ics?streetid=1429\",\n        \"split_at\": \",\",\n    },\n    \"Recollect, Ottawa\": {\n        \"url\": \"https://recollect.a.ssl.fastly.net/api/places/BCCDF30E-578B-11E4-AD38-5839C200407A/services/208/events.en.ics\",\n        \"split_at\": \"\\\\, [and ]*\",\n    },\n    \"Frankfurt am Main, Achenbachstrasse 3\": {\n        \"url\": \"https://www.fes-frankfurt.de/abfallkalender/QWNoZW5iYWNoc3RyLnwzfDYwNTk2.ics\"\n    },\n    \"Erlensee, Am Haspel\": {\n        \"url\": \"https://sperrmuell.erlensee.de/?type=reminder\",\n        \"method\": \"POST\",\n        \"params\": {\n            \"street\": 8,\n            \"eventType[]\": [27, 23, 19, 20, 21, 24, 22, 25, 26],\n            \"timeframe\": 23,\n            \"download\": \"ical\",\n        },\n    },\n}\n\n\nHEADERS = {\"user-agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64)\"}\n_LOGGER = logging.getLogger(__name__)\n\n\nclass Source:\n    def __init__(\n        self,\n        url=None,\n        file=None,\n        offset=None,\n        params=None,\n        year_field=None,\n        method=\"GET\",\n        split_at=None,\n    ):\n        self._url = url\n        self._file = file\n        if bool(self._url is not None) == bool(self._file is not None):\n            raise RuntimeError(\"Specify either url or file\")\n        self._ics = ICS(offset=offset, split_at=split_at)\n        self._params = params\n        self._year_field = year_field  # replace this field in params with current year\n        self._method = method  # The method to send the params\n\n    def fetch(self):\n        if self._url is not None:\n            if \"{%Y}\" in self._url or self._year_field is not None:\n                # url contains wildcard or params contains year field\n                now = datetime.datetime.now()\n\n                # replace year in url\n                url = self._url.replace(\"{%Y}\", str(now.year))\n\n                # replace year in params\n                if self._year_field is not None:\n                    if self._params is None:\n                        raise RuntimeError(\"year_field specified without params\")\n                    self._params[self._year_field] = str(now.year)\n\n                entries = self.fetch_url(url, self._params)\n\n                if now.month == 12:\n                    # also get data for next year if we are already in december\n                    url = self._url.replace(\"{%Y}\", str(now.year + 1))\n                    if self._year_field is not None:\n                        self._params[self._year_field] = str(now.year + 1)\n\n                    try:\n                        entries.extend(self.fetch_url(url), self._params)\n                    except Exception:\n                        # ignore if fetch for next year fails\n                        pass\n                return entries\n            else:\n                return self.fetch_url(self._url, self._params)\n        elif self._file is not None:\n            return self.fetch_file(self._file)\n\n    def fetch_url(self, url, params=None):\n        # get ics file\n        if self._method == \"GET\":\n            r = requests.get(url, params=params, headers=HEADERS)\n        elif self._method == \"POST\":\n            r = requests.post(url, data=params, headers=HEADERS)\n        else:\n            _LOGGER.error(\n                \"Error: unknown method to fetch URL, use GET or POST; got %s\"\n                % self._method\n            )\n            return \"error\"\n        r.encoding = \"utf-8\"  # requests doesn't guess the encoding correctly\n\n        # check the return code\n        if not r.ok:\n            _LOGGER.error(\n                \"Error: the response is not ok; need code 200, but got code %s\"\n                % r.status_code\n            )\n            return \"error\"\n\n        return self._convert(r.text)\n\n    def fetch_file(self, file):\n        f = open(file)\n        return self._convert(f.read())\n\n    def _convert(self, data):\n        dates = self._ics.convert(data)\n\n        entries = []\n        for d in dates:\n            entries.append(Collection(d[0], d[1]))\n        return entries\n", "path": "custom_components/waste_collection_schedule/waste_collection_schedule/source/ics.py"}]}