diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/service/ICS.py b/custom_components/waste_collection_schedule/waste_collection_schedule/service/ICS.py index 6b73387a3..74bb1cb4a 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/service/ICS.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/service/ICS.py @@ -3,6 +3,7 @@ import re from typing import Any, List, Optional, Tuple +import jinja2 from icalevents import icalevents _LOGGER = logging.getLogger(__name__) @@ -14,6 +15,7 @@ def __init__( offset: Optional[int] = None, regex: Optional[str] = None, split_at: Optional[str] = None, + title_template: Optional[str] = "{{date.summary}}", ): self._offset = offset self._regex = None @@ -25,6 +27,8 @@ def __init__( if split_at is not None: self._split_at = re.compile(split_at) + self._title_template = title_template + def convert(self, ics_data: str) -> List[Tuple[datetime.date, str]]: # calculate start- and end-date for recurring events start_date = datetime.datetime.now().replace( @@ -55,17 +59,19 @@ def convert(self, ics_data: str) -> List[Tuple[datetime.date, str]]: if self._offset is not None: dtstart += datetime.timedelta(days=self._offset) - # calculate waste type - summary = str(e.summary) + environment = jinja2.Environment() + title_template = environment.from_string(self._title_template) + entry_title = title_template.render(date=e) if self._regex is not None: - if match := self._regex.match(summary): - summary = match.group(1) + match = self._regex.match(entry_title) + if match: + entry_title = match.group(1) if self._split_at is not None: - summary = re.split(self._split_at, summary) - entries.extend((dtstart, t.strip().title()) for t in summary) + entry_title = re.split(self._split_at, entry_title) + entries.extend((dtstart, t.strip().title()) for t in entry_title) else: - entries.append((dtstart, summary)) + entries.append((dtstart, entry_title)) return entries diff --git a/custom_components/waste_collection_schedule/waste_collection_schedule/service/ICS_v1.py b/custom_components/waste_collection_schedule/waste_collection_schedule/service/ICS_v1.py index 765b6c7ba..999005e9f 100644 --- a/custom_components/waste_collection_schedule/waste_collection_schedule/service/ICS_v1.py +++ b/custom_components/waste_collection_schedule/waste_collection_schedule/service/ICS_v1.py @@ -4,18 +4,28 @@ import icalendar import recurring_ical_events +import jinja2 + _LOGGER = logging.getLogger(__name__) class ICS_v1: - def __init__(self, offset=None, regex=None, split_at=None): + def __init__( + self, + offset=None, + regex=None, + split_at=None, + title_template="{{date.summary}}", + ): self._offset = offset self._regex = None if regex is not None: self._regex = re.compile(regex) self._split_at = split_at + self._title_template = title_template + def convert(self, ics_data): # parse ics file calendar = icalendar.Calendar.from_ical(ics_data) @@ -43,17 +53,20 @@ def convert(self, ics_data): dtstart += datetime.timedelta(days=self._offset) # calculate waste type - summary = str(e.get("summary")) + environment = jinja2.Environment() + title_template = environment.from_string(self._title_template) + entry_title = title_template.render(date=e) + if self._regex is not None: - match = self._regex.match(summary) + match = self._regex.match(entry_title) if match: - summary = match.group(1) + entry_title = match.group(1) if self._split_at is not None: - summary = re.split(self._split_at, summary) - for t in summary: + entry_title = re.split(self._split_at, entry_title) + for t in entry_title: entries.append((dtstart, t.strip().title())) else: - entries.append((dtstart, summary)) + entries.append((dtstart, entry_title)) return 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 ead7c89af..7a3db2b92 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 @@ -115,6 +115,7 @@ def __init__( year_field=None, method="GET", regex=None, + title_template="{{date.summary}}", split_at=None, version=2, verify_ssl=True, @@ -125,9 +126,19 @@ def __init__( if bool(self._url is not None) == bool(self._file is not None): raise RuntimeError("Specify either url or file") if version == 1: - self._ics = ICS_v1(offset=offset, split_at=split_at, regex=regex) + self._ics = ICS_v1( + offset=offset, + split_at=split_at, + regex=regex, + title_template=title_template, + ) else: - self._ics = ICS(offset=offset, split_at=split_at, regex=regex) + self._ics = ICS( + offset=offset, + split_at=split_at, + regex=regex, + title_template=title_template, + ) self._params = params self._year_field = year_field # replace this field in params with current year self._method = method # The method to send the params diff --git a/doc/source/ics.md b/doc/source/ics.md index f4c9fbfd1..62f1fa004 100644 --- a/doc/source/ics.md +++ b/doc/source/ics.md @@ -121,6 +121,7 @@ waste_collection_schedule: version: 2 verify_ssl: VERIFY_SSL headers: HEADERS + title_template: "{{date.summary}}" ``` ### Configuration Variables @@ -222,6 +223,11 @@ Add custom headers to HTTP request, e.g. `referer`. By default, the `user-agent` See also [example](#custom-headers) below. +**title_template** +*(str) (optional, default: `{{date.summary}}`)* + +template for the event title. `date` is the event object depending on the selected ICS file parser version. + ## Examples and Notes *** @@ -295,3 +301,72 @@ waste_collection_schedule: ``` Removes the needless prefix "Abfuhr: " from the waste collection type. + +*** + +### FES Frankfurt + +```yaml +waste_collection_schedule: + sources: + - name: ics + args: + url: https://www.fes-frankfurt.de/abfallkalender/.ics + split_at: " \/ " + regex: "(.*)\\s+\\|" +``` + +*** + +### Abfallwirtschaftsbetrieb Ilm-Kreis + +Go to the [service provider website](https://aik.ilm-kreis.de/Abfuhrtermine/) and select location and street. Selection of desired waste types is optional. Afterwards an iCal calendar export is provided. Download it and find the download URL. Some parameters of the URL can be omitted. (e.g. `kat`, `ArtID`, `alarm`) + +Important: The base url of the provider's website `https://aik.ilm-kreis.de` needs to be set as a [custom header](#custom-headers) `referer`. Otherwise you'll get an HTTP 403 error. + +```yaml +waste_collection_schedule: + sources: + - name: ics + args: + url: "https://aik.ilm-kreis.de/output/options.php?ModID=48&call=ical&=&ArtID[0]=1.1&ArtID[1]=1.4&ArtID[2]=1.2&pois=3053.562&kat=1,&alarm=0" + headers: + referer: "https://aik.ilm-kreis.de" + calendar_title: Abfuhrtermine Witzleben +``` + + +### Münsingen, Canton of Bern, Switzerland + +Go to [Abfallkalender](https://www.muensingen.ch/de/verwaltung/dienstleistungen/detail/detail.php?i=90) to get the url of the ICal file. + +```yaml +waste_collection_schedule: + sources: + - name: ics + args: + url: "https://www.muensingen.ch/de/verwaltung/dokumente/dokumente/Papier-und-Kartonabfuhr-{%Y}.ics" + version: 1 + title_template: "{{date.summary}} {{date.location}}" + calendar_title: "Papier-und-Kartonabfuhr" + customize: + - type: Papier und Karton Gebiet Ost + alias: Gebiet Ost + show: false + icon: mdi:recycle + - type: Papier und Karton Gebiet West + alias: Gebiet West + icon: mdi:recycle + - type: Papier und Karton Gebiet Ost und West + alias: Gebiet Ost und West + icon: mdi:recycle + - name: ics + args: + url: "https://www.muensingen.ch/de/verwaltung/dokumente/dokumente/Gartenabfaelle-{%Y}.ics" + version: 1 + calendar_title: "Gartenabfaelle" + customize: + - type: "Grüngut" + alias: "Grüngut" + icon: mdi:leaf-circle +``` diff --git a/requirements.txt b/requirements.txt index 3fbeabf0e..e1c8d4ff2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,3 +17,4 @@ soupsieve==2.4.1 urllib3==2.0.4 x-wr-timezone==0.0.5 zope.interface==6.0 +jinja2=3.1.2