We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
provide custom HEADERS in ical source
Hello, 

I'm trying to download an ics schedule from my providers homepage. It only works when a "Referer":"<url>" Header is set. Otherwise a 403 Error occure. I manually changed the fixed Header (currently the user-agent) in the ics.py, but that will be overridden in future updates, i guess.

My question (or feature request, i suppose) is, is there a way to provide such a custom Header in the source configuration for ics?

--- 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 os import getcwd
4 from pathlib import Path
5 
6 import requests
7 from waste_collection_schedule import Collection  # type: ignore[attr-defined]
8 from waste_collection_schedule.service.ICS import ICS
9 from waste_collection_schedule.service.ICS_v1 import ICS_v1
10 
11 TITLE = "ICS"
12 DESCRIPTION = "Source for ICS based schedules."
13 URL = None
14 TEST_CASES = {
15     "Dortmund, Dudenstr. 5": {
16         "url": "https://www.edg.de/ical/kalender.ics?Strasse=Dudenstr.&Hausnummer=5&Erinnerung=-1&Abfallart=1,2,3,4"
17     },
18     "Leipzig, Sandgrubenweg 27": {
19         "url": "https://stadtreinigung-leipzig.de/wir-kommen-zu-ihnen/abfallkalender/ical.ics?position_nos=38296&name=Sandgrubenweg%2027"
20     },
21     "Ludwigsburg": {
22         "url": "https://www.avl-ludwigsburg.de/fileadmin/Files/Abfallkalender/ICS/Privat/Privat_{%Y}_Ossweil.ics"
23     },
24     "Esslingen, Bahnhof": {
25         "url": "https://api.abfall.io/?kh=DaA02103019b46345f1998698563DaAd&t=ics&s=1a862df26f6943997cef90233877a4fe"
26     },
27     "Test File": {
28         # Path is used here to allow to call the Source from any location.
29         # This is not required in a yaml configuration!
30         "file": str(Path(__file__).resolve().parents[1].joinpath("test/test.ics"))
31     },
32     "Test File (recurring)": {
33         # Path is used here to allow to call the Source from any location.
34         # This is not required in a yaml configuration!
35         "file": str(Path(__file__).resolve().parents[1].joinpath("test/recurring.ics"))
36     },
37     "München, Bahnstr. 11": {
38         "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}",
39         "version": 1,
40     },
41     "Buxtehude, Am Berg": {
42         "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"
43     },
44     #    "Hausmüllinfo: ASR Chemnitz": {
45     #        "url": "https://asc.hausmuell.info/ics/ics.php",
46     #        "method": "POST",
47     #        "params": {
48     #            "hidden_id_egebiet": 439087,
49     #            "input_ort": "Chemnitz",
50     #            "input_str": "Straße der Nationen",
51     #            "input_hnr": 2,
52     #            "hidden_send_btn": "ics",
53     #            # "hiddenYear": 2021,
54     #            "hidden_id_ort": 10,
55     #            "hidden_id_ortsteil": 0,
56     #            "hidden_id_str": 17814,
57     #            "hidden_id_hnr": 5538100,
58     #            "hidden_kalenderart": "privat",
59     #            "showBinsBio": "on",
60     #            "showBinsRest": "on",
61     #            "showBinsRest_rc": "on",
62     #            "showBinsPapier": "on",
63     #            "showBinsOrganic": "on",
64     #            "showBinsXmas": "on",
65     #            "showBinsDsd": "on",
66     #            "showBinsProb": "on",
67     #        },
68     #        "year_field": "hiddenYear",
69     #    },
70     "Abfall Zollernalbkreis, Ebingen": {
71         "url": "https://www.abfallkalender-zak.de",
72         "params": {
73             "city": "2,3,4",
74             "street": "3",
75             "types[]": [
76                 "restmuell",
77                 "gelbersack",
78                 "papiertonne",
79                 "biomuell",
80                 "gruenabfall",
81                 "schadstoffsammlung",
82                 "altpapiersammlung",
83                 "schrottsammlung",
84                 "weihnachtsbaeume",
85                 "elektrosammlung",
86             ],
87             "go_ics": "Download",
88         },
89         "year_field": "year",
90     },
91     "Detmold": {
92         "url": "https://abfuhrkalender.detmold.de/icsmaker.php",
93         "method": "GET",
94         "params": {"strid": 338},
95         "year_field": "year",
96     },
97     "EAW Rheingau Taunus": {
98         "url": "https://www.eaw-rheingau-taunus.de/abfallsammlung/abfuhrtermine/feed.ics?tx_vierwdeaw_garbagecalendarics%5Baction%5D=ics&tx_vierwdeaw_garbagecalendarics%5Bcontroller%5D=GarbageCalendar&tx_vierwdeaw_garbagecalendarics%5Bstreet%5D=38",
99         "split_at": ",",
100     },
101     "Recollect, Ottawa": {
102         "url": "https://recollect.a.ssl.fastly.net/api/places/BCCDF30E-578B-11E4-AD38-5839C200407A/services/208/events.en.ics",
103         "split_at": "\\, [and ]*",
104     },
105     "Frankfurt am Main, Achenbachstrasse 3": {
106         "url": "https://www.fes-frankfurt.de/abfallkalender/QWNoZW5iYWNoc3RyLnwzfDYwNTk2.ics"
107     },
108     "Erlensee, Am Haspel": {
109         "url": "https://sperrmuell.erlensee.de/?type=reminder",
110         "method": "POST",
111         "params": {
112             "street": 8,
113             "eventType[]": [27, 23, 19, 20, 21, 24, 22, 25, 26],
114             "timeframe": 23,
115             "download": "ical",
116         },
117     },
118 }
119 
120 
121 HEADERS = {"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}
122 _LOGGER = logging.getLogger(__name__)
123 
124 
125 class Source:
126     def __init__(
127         self,
128         url=None,
129         file=None,
130         offset=None,
131         params=None,
132         year_field=None,
133         method="GET",
134         regex=None,
135         split_at=None,
136         version=2,
137         verify_ssl=True,
138     ):
139         self._url = url
140         self._file = file
141         if bool(self._url is not None) == bool(self._file is not None):
142             raise RuntimeError("Specify either url or file")
143         if version == 1:
144             self._ics = ICS_v1(offset=offset, split_at=split_at, regex=regex)
145         else:
146             self._ics = ICS(offset=offset, split_at=split_at, regex=regex)
147         self._params = params
148         self._year_field = year_field  # replace this field in params with current year
149         self._method = method  # The method to send the params
150         self._verify_ssl = verify_ssl
151 
152     def fetch(self):
153         if self._url is not None:
154             if "{%Y}" in self._url or self._year_field is not None:
155                 # url contains wildcard or params contains year field
156                 now = datetime.datetime.now()
157 
158                 # replace year in url
159                 url = self._url.replace("{%Y}", str(now.year))
160 
161                 # replace year in params
162                 if self._year_field is not None:
163                     if self._params is None:
164                         raise RuntimeError("year_field specified without params")
165                     self._params[self._year_field] = str(now.year)
166 
167                 entries = self.fetch_url(url, self._params)
168 
169                 if now.month == 12:
170                     # also get data for next year if we are already in december
171                     url = self._url.replace("{%Y}", str(now.year + 1))
172                     if self._year_field is not None:
173                         self._params[self._year_field] = str(now.year + 1)
174 
175                     try:
176                         entries.extend(self.fetch_url(url, self._params))
177                     except Exception:
178                         # ignore if fetch for next year fails
179                         pass
180                 return entries
181             else:
182                 return self.fetch_url(self._url, self._params)
183         elif self._file is not None:
184             return self.fetch_file(self._file)
185 
186     def fetch_url(self, url, params=None):
187         # get ics file
188         if self._method == "GET":
189             r = requests.get(
190                 url, params=params, headers=HEADERS, verify=self._verify_ssl
191             )
192         elif self._method == "POST":
193             r = requests.post(
194                 url, data=params, headers=HEADERS, verify=self._verify_ssl
195             )
196         else:
197             raise RuntimeError(
198                 "Error: unknown method to fetch URL, use GET or POST; got {self._method}"
199             )
200         r.raise_for_status()
201 
202         r.encoding = "utf-8"  # requests doesn't guess the encoding correctly
203         return self._convert(r.text)
204 
205     def fetch_file(self, file):
206         try:
207             f = open(file)
208         except FileNotFoundError as e:
209             _LOGGER.error(f"Working directory: '{getcwd()}'")
210             raise
211         return self._convert(f.read())
212 
213     def _convert(self, data):
214         dates = self._ics.convert(data)
215 
216         entries = []
217         for d in dates:
218             entries.append(Collection(d[0], d[1]))
219         return entries
220
```
--- 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
@@ -19,7 +19,7 @@
         "url": "https://stadtreinigung-leipzig.de/wir-kommen-zu-ihnen/abfallkalender/ical.ics?position_nos=38296&name=Sandgrubenweg%2027"
     },
     "Ludwigsburg": {
-        "url": "https://www.avl-ludwigsburg.de/fileadmin/Files/Abfallkalender/ICS/Privat/Privat_{%Y}_Ossweil.ics"
+        "url": "https://kundenportal.avl-lb.de/WasteManagementLudwigsburg/WasteManagementServiceServlet?ApplicationName=Calendar&SubmitAction=sync&StandortID=950230001&AboID=8188&Fra=BT;RT;PT;LT;GT"
     },
     "Esslingen, Bahnhof": {
         "url": "https://api.abfall.io/?kh=DaA02103019b46345f1998698563DaAd&t=ics&s=1a862df26f6943997cef90233877a4fe"
@@ -135,6 +135,7 @@
         split_at=None,
         version=2,
         verify_ssl=True,
+        headers={},
     ):
         self._url = url
         self._file = file
@@ -148,6 +149,8 @@
         self._year_field = year_field  # replace this field in params with current year
         self._method = method  # The method to send the params
         self._verify_ssl = verify_ssl
+        self._headers = HEADERS
+        self._headers.update(headers)
 
     def fetch(self):
         if self._url is not None:
@@ -187,11 +190,11 @@
         # get ics file
         if self._method == "GET":
             r = requests.get(
-                url, params=params, headers=HEADERS, verify=self._verify_ssl
+                url, params=params, headers=self._headers, verify=self._verify_ssl
             )
         elif self._method == "POST":
             r = requests.post(
-                url, data=params, headers=HEADERS, verify=self._verify_ssl
+                url, data=params, headers=self._headers, verify=self._verify_ssl
             )
         else:
             raise RuntimeError(

{"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@@ -19,7 +19,7 @@\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+        \"url\": \"https://kundenportal.avl-lb.de/WasteManagementLudwigsburg/WasteManagementServiceServlet?ApplicationName=Calendar&SubmitAction=sync&StandortID=950230001&AboID=8188&Fra=BT;RT;PT;LT;GT\"\n     },\n     \"Esslingen, Bahnhof\": {\n         \"url\": \"https://api.abfall.io/?kh=DaA02103019b46345f1998698563DaAd&t=ics&s=1a862df26f6943997cef90233877a4fe\"\n@@ -135,6 +135,7 @@\n         split_at=None,\n         version=2,\n         verify_ssl=True,\n+        headers={},\n     ):\n         self._url = url\n         self._file = file\n@@ -148,6 +149,8 @@\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         self._verify_ssl = verify_ssl\n+        self._headers = HEADERS\n+        self._headers.update(headers)\n \n     def fetch(self):\n         if self._url is not None:\n@@ -187,11 +190,11 @@\n         # get ics file\n         if self._method == \"GET\":\n             r = requests.get(\n-                url, params=params, headers=HEADERS, verify=self._verify_ssl\n+                url, params=params, headers=self._headers, verify=self._verify_ssl\n             )\n         elif self._method == \"POST\":\n             r = requests.post(\n-                url, data=params, headers=HEADERS, verify=self._verify_ssl\n+                url, data=params, headers=self._headers, verify=self._verify_ssl\n             )\n         else:\n             raise RuntimeError(\n", "issue": "provide custom HEADERS in ical source\nHello, \r\n\r\nI'm trying to download an ics schedule from my providers homepage. It only works when a \"Referer\":\"<url>\" Header is set. Otherwise a 403 Error occure. I manually changed the fixed Header (currently the user-agent) in the ics.py, but that will be overridden in future updates, i guess.\r\n\r\nMy question (or feature request, i suppose) is, is there a way to provide such a custom Header in the source configuration for ics?\n", "before_files": [{"content": "import datetime\nimport logging\nfrom os import getcwd\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\nfrom waste_collection_schedule.service.ICS_v1 import ICS_v1\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        \"version\": 1,\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/abfallsammlung/abfuhrtermine/feed.ics?tx_vierwdeaw_garbagecalendarics%5Baction%5D=ics&tx_vierwdeaw_garbagecalendarics%5Bcontroller%5D=GarbageCalendar&tx_vierwdeaw_garbagecalendarics%5Bstreet%5D=38\",\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        regex=None,\n        split_at=None,\n        version=2,\n        verify_ssl=True,\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        if version == 1:\n            self._ics = ICS_v1(offset=offset, split_at=split_at, regex=regex)\n        else:\n            self._ics = ICS(offset=offset, split_at=split_at, regex=regex)\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        self._verify_ssl = verify_ssl\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(\n                url, params=params, headers=HEADERS, verify=self._verify_ssl\n            )\n        elif self._method == \"POST\":\n            r = requests.post(\n                url, data=params, headers=HEADERS, verify=self._verify_ssl\n            )\n        else:\n            raise RuntimeError(\n                \"Error: unknown method to fetch URL, use GET or POST; got {self._method}\"\n            )\n        r.raise_for_status()\n\n        r.encoding = \"utf-8\"  # requests doesn't guess the encoding correctly\n        return self._convert(r.text)\n\n    def fetch_file(self, file):\n        try:\n            f = open(file)\n        except FileNotFoundError as e:\n            _LOGGER.error(f\"Working directory: '{getcwd()}'\")\n            raise\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 os import getcwd\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\nfrom waste_collection_schedule.service.ICS_v1 import ICS_v1\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://kundenportal.avl-lb.de/WasteManagementLudwigsburg/WasteManagementServiceServlet?ApplicationName=Calendar&SubmitAction=sync&StandortID=950230001&AboID=8188&Fra=BT;RT;PT;LT;GT\"\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        \"version\": 1,\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/abfallsammlung/abfuhrtermine/feed.ics?tx_vierwdeaw_garbagecalendarics%5Baction%5D=ics&tx_vierwdeaw_garbagecalendarics%5Bcontroller%5D=GarbageCalendar&tx_vierwdeaw_garbagecalendarics%5Bstreet%5D=38\",\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        regex=None,\n        split_at=None,\n        version=2,\n        verify_ssl=True,\n        headers={},\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        if version == 1:\n            self._ics = ICS_v1(offset=offset, split_at=split_at, regex=regex)\n        else:\n            self._ics = ICS(offset=offset, split_at=split_at, regex=regex)\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        self._verify_ssl = verify_ssl\n        self._headers = HEADERS\n        self._headers.update(headers)\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(\n                url, params=params, headers=self._headers, verify=self._verify_ssl\n            )\n        elif self._method == \"POST\":\n            r = requests.post(\n                url, data=params, headers=self._headers, verify=self._verify_ssl\n            )\n        else:\n            raise RuntimeError(\n                \"Error: unknown method to fetch URL, use GET or POST; got {self._method}\"\n            )\n        r.raise_for_status()\n\n        r.encoding = \"utf-8\"  # requests doesn't guess the encoding correctly\n        return self._convert(r.text)\n\n    def fetch_file(self, file):\n        try:\n            f = open(file)\n        except FileNotFoundError as e:\n            _LOGGER.error(f\"Working directory: '{getcwd()}'\")\n            raise\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"}]}