Compare commits

..

No commits in common. "aae5b14574e261a18e3998287c5019765a5a3f17" and "cd9d90b8e53fb157e7892f281d6c5d4529c763be" have entirely different histories.

14 changed files with 59 additions and 573 deletions

View file

@ -5,7 +5,6 @@ RUN apt install -y python3-pip python3-venv pipx curl locales
RUN pipx install lektor
RUN mkdir -p /opt/lektor/project && mkdir -p /opt/lektor/output
RUN sed -i '/de_DE.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
RUN sed -i '/fr_FR.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
RUN python3 -m venv /opt/venv

View file

@ -8,10 +8,4 @@ tempfile=$(mktemp)
sh -c ". /opt/venv/bin/activate && exec python /opt/lektor/scripts/calendar-fetcher-main.py ${CALENDAR_URL} > $tempfile"
mv $tempfile /opt/lektor/project/content/contents.lr
sh -c ". /opt/venv/bin/activate && exec python /opt/lektor/scripts/calendar-fetcher+fr.py ${CALENDAR_URL} > /opt/lektor/project/content/termine/contents+fr.lr"
# TODO As the file reads from the same file as it's output is afterwards piped into this leads to synchronization/buffering issues, we therefore write to a temporaray file and move it to the right place in a subsequent step
tempfile=$(mktemp)
sh -c ". /opt/venv/bin/activate && exec python /opt/lektor/scripts/calendar-fetcher-main+fr.py ${CALENDAR_URL} > $tempfile"
mv $tempfile /opt/lektor/project/content/contents+fr.lr
exec "$@"

View file

@ -1,17 +1,2 @@
[project]
name = PCS
[alternatives.xy]
name = German
primary = yes
locale = de_CH
[alternatives.de]
name = German
url_prefix = /de/
locale = de_CH
[alternatives.fr]
name = French
url_prefix = /fr/
locale = fr_CH

View file

@ -1,31 +0,0 @@
_model: htmlpage
---
title: Bienvenue au PC Stammertal
---
html:
<h2>Notre prochain événement: </h2><br>
<div class="nextevent">samedi <strong>22. mars 9:00, Frühlingsschiessen</strong>, Bülach</div>
<div>
&nbsp;<br>
&nbsp;<br>
&nbsp;<br>
</div>
<h3>...et ne manquez pas non plus :</h3>
<a href="https://www.wyland25.ch/" target="_blank"><img src="/images/zhksf.png" alt="27. Zürcher Kantonalschützenfest 2025" class="stamp"></a>
<div class="threecolumn">
<div>
<a href="termine/"><img src=" /images/termine_square.jpg" alt="Calendrier"> Tous nos événements</a>
</div>
<div>
<a href="about/"><img src="/images/about_square.jpg" alt="Livre"> À propos de nous</a>
</div>
<div>
<a href="kontakt/"><img src="/images/kontakt_square.jpg" alt="Lettres"> Contactez-nous</a>
</div>
</div>
---
_template:
page.html

View file

@ -5,7 +5,7 @@ title: Willkommen beim PC Stammertal
html:
<h2>Unser nächster Anlass: </h2><br>
<div class="nextevent">Samstag <strong>22. März 9:00, Frühlingsschiessen</strong>, Bülach</div>
<div class="nextevent">Leider unbekannt, aber <strong>frag mal den Vorstand</strong> der müsste es wissen</div>
<div>
&nbsp;<br>
&nbsp;<br>
@ -29,3 +29,4 @@ _template:
page.html

View file

@ -1,251 +0,0 @@
_model: page
---
title: Événements
---
body:
* <div>Frühlingsschiessen</div>&nbsp;
* <div>sam. 22. mars 2025</div>&nbsp;
* <div>9:00</div>&nbsp;
* <div>Bülach</div>&nbsp;
* <div>Standreinigung</div>&nbsp;
* <div>sam. 22. mars 2025</div>&nbsp;
* <div>9:00</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>freies Training</div>&nbsp;
* <div>lun. 31. mars 2025</div>&nbsp;
* <div>18:00</div>&nbsp;
* <div>Stammheim</div>&nbsp;
* <div>Eulachschiessen</div>&nbsp;
* <div>ven. 4. avril 2025</div>&nbsp;
* <div>16:00</div>&nbsp;
* <div>Winterthur</div>&nbsp;
* <div>Eulachschiessen</div>&nbsp;
* <div>sam. 5. avril 2025</div>&nbsp;
* <div>8:30</div>&nbsp;
* <div>Winterthur</div>&nbsp;
* <div>Eulachschiessen</div>&nbsp;
* <div>ven. 11. avril 2025</div>&nbsp;
* <div>16:00</div>&nbsp;
* <div>Winterthur</div>&nbsp;
* <div>Eulachschiessen</div>&nbsp;
* <div>sam. 12. avril 2025</div>&nbsp;
* <div>8:30</div>&nbsp;
* <div>Winterthur</div>&nbsp;
* <div>Kreis Winterschiessen</div>&nbsp;
* <div>lun. 14. avril 2025</div>&nbsp;
* <div>18:00</div>&nbsp;
* <div>Stammheim</div>&nbsp;
* <div>Obligatorisches Programm und Kreis Winterschiessen</div>&nbsp;
* <div>lun. 28. avril 2025</div>&nbsp;
* <div>18:00</div>&nbsp;
* <div>Stammheim</div>&nbsp;
* <div>freies Training</div>&nbsp;
* <div>lun. 5. mai 2025</div>&nbsp;
* <div>18:30</div>&nbsp;
* <div>Stammheim</div>&nbsp;
* <div>freies Training</div>&nbsp;
* <div>lun. 12. mai 2025</div>&nbsp;
* <div>18:30</div>&nbsp;
* <div>Stammheim</div>&nbsp;
* <div>Schlossschiessen</div>&nbsp;
* <div>jeu. 15. mai 2025</div>&nbsp;
* <div>17:30</div>&nbsp;
* <div>Wülflingen</div>&nbsp;
* <div>Schlossschiessen</div>&nbsp;
* <div>ven. 16. mai 2025</div>&nbsp;
* <div>17:30</div>&nbsp;
* <div>Wülflingen</div>&nbsp;
* <div>Schlossschiessen</div>&nbsp;
* <div>sam. 17. mai 2025</div>&nbsp;
* <div>9:30</div>&nbsp;
* <div>Wülflingen</div>&nbsp;
* <div>Obligatorisches Programm und freies Training</div>&nbsp;
* <div>lun. 19. mai 2025</div>&nbsp;
* <div>18:30</div>&nbsp;
* <div>Stammheim</div>&nbsp;
* <div>Schlossschiessen</div>&nbsp;
* <div>jeu. 22. mai 2025</div>&nbsp;
* <div>17:30</div>&nbsp;
* <div>Wülflingen</div>&nbsp;
* <div>Schlossschiessen</div>&nbsp;
* <div>ven. 23. mai 2025</div>&nbsp;
* <div>17:30</div>&nbsp;
* <div>Wülflingen</div>&nbsp;
* <div>freies Training</div>&nbsp;
* <div>lun. 26. mai 2025</div>&nbsp;
* <div>18:30</div>&nbsp;
* <div>Stammheim</div>&nbsp;
* <div>Kreis Sommerschiessen</div>&nbsp;
* <div>lun. 2. juin 2025</div>&nbsp;
* <div>18:30</div>&nbsp;
* <div>Stammheim</div>&nbsp;
* <div>Sommer-Schüsse</div>&nbsp;
* <div>ven. 6. juin 2025</div>&nbsp;
* <div>17:00</div>&nbsp;
* <div>Rafz</div>&nbsp;
* <div>Sommer-Schüsse</div>&nbsp;
* <div>sam. 14. juin 2025</div>&nbsp;
* <div>9:00</div>&nbsp;
* <div>Rafz</div>&nbsp;
* <div>Obligatorisches Programm und freies Training</div>&nbsp;
* <div>lun. 16. juin 2025</div>&nbsp;
* <div>18:30</div>&nbsp;
* <div>Stammheim</div>&nbsp;
* <div>freies Training</div>&nbsp;
* <div>lun. 23. juin 2025</div>&nbsp;
* <div>18:30</div>&nbsp;
* <div>Stammheim</div>&nbsp;
* <div>freies Training</div>&nbsp;
* <div>lun. 30. juin 2025</div>&nbsp;
* <div>18:30</div>&nbsp;
* <div>Stammheim</div>&nbsp;
* <div>Bezirks Sommerschiessen</div>&nbsp;
* <div>sam. 5. juillet 2025</div>&nbsp;
* <div>17:00</div>&nbsp;
* <div>Flurlingen</div>&nbsp;
* <div>freies Training</div>&nbsp;
* <div>lun. 7. juillet 2025</div>&nbsp;
* <div>18:30</div>&nbsp;
* <div>Stammheim</div>&nbsp;
* <div>Terassenfest</div>&nbsp;
* <div>ven. 11. juillet 2025</div>&nbsp;
* <div>19:00</div>&nbsp;
* <div>Stammheim</div>&nbsp;
* <div>Bezirks Sommerschiessen</div>&nbsp;
* <div>sam. 12. juillet 2025</div>&nbsp;
* <div>14:00</div>&nbsp;
* <div>Flurlingen</div>&nbsp;
* <div>freies Training</div>&nbsp;
* <div>lun. 14. juillet 2025</div>&nbsp;
* <div>18:30</div>&nbsp;
* <div>Stammheim</div>&nbsp;
* <div>Obligatorisches Programm und freies Training</div>&nbsp;
* <div>lun. 11. août 2025</div>&nbsp;
* <div>18:30</div>&nbsp;
* <div>Stammheim</div>&nbsp;
* <div>ZHKSF Ausbildung Pistole</div>&nbsp;
* <div>jeu. 14. août 2025</div>&nbsp;
* <div>18:30</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>Zürcher Kantonalschützenfest</div>&nbsp;
* <div>ven. 15. août 2025</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>Zürcher Kantonalschützenfest</div>&nbsp;
* <div>sam. 16. août 2025</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>Zürcher Kantonalschützenfest</div>&nbsp;
* <div>dim. 17. août 2025</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>KEIN Training</div>&nbsp;
* <div>lun. 18. août 2025</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>ZHKSF intern</div>&nbsp;
* <div>jeu. 21. août 2025</div>&nbsp;
* <div>18:30</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>Zürcher Kantonalschützenfest</div>&nbsp;
* <div>ven. 22. août 2025</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>Zürcher Kantonalschützenfest</div>&nbsp;
* <div>sam. 23. août 2025</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>Zürcher Kantonalschützenfest</div>&nbsp;
* <div>dim. 24. août 2025</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>Zürcher Kantonalschützenfest</div>&nbsp;
* <div>lun. 25. août 2025</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>Zürcher Kantonalschützenfest</div>&nbsp;
* <div>ven. 29. août 2025</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>Zürcher Kantonalschützenfest</div>&nbsp;
* <div>sam. 30. août 2025</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>Zürcher Kantonalschützenfest</div>&nbsp;
* <div>dim. 31. août 2025</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>freies Training</div>&nbsp;
* <div>lun. 1. septembre 2025</div>&nbsp;
* <div>18:30</div>&nbsp;
* <div>Stammheim</div>&nbsp;
* <div>Kreismatch</div>&nbsp;
* <div>lun. 8. septembre 2025</div>&nbsp;
* <div>18:00</div>&nbsp;
* <div>Stammheim</div>&nbsp;
* <div>Orientierungslauf Stammerberg (kein Schiessbetrieb!)</div>&nbsp;
* <div>sam. 13. septembre 2025</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>Unterstammheim</div>&nbsp;
* <div>Kreismatch</div>&nbsp;
* <div>lun. 15. septembre 2025</div>&nbsp;
* <div>18:00</div>&nbsp;
* <div>Stammheim</div>&nbsp;
* <div>Schwaderlohschiessen</div>&nbsp;
* <div>sam. 20. septembre 2025</div>&nbsp;
* <div>13:30</div>&nbsp;
* <div>Alterswilen</div>&nbsp;
* <div>freies Training</div>&nbsp;
* <div>lun. 22. septembre 2025</div>&nbsp;
* <div>18:00</div>&nbsp;
* <div>Stammheim</div>&nbsp;
* <div>Schwaderlohschiessen (unser Schiesstag)</div>&nbsp;
* <div>mar. 23. septembre 2025</div>&nbsp;
* <div>17:30</div>&nbsp;
* <div>Alterswilen</div>&nbsp;
* <div>Schwaderlohschiessen</div>&nbsp;
* <div>sam. 27. septembre 2025</div>&nbsp;
* <div>8:00</div>&nbsp;
* <div>Alterswilen</div>&nbsp;
* <div>Schwaderlohschiessen</div>&nbsp;
* <div>dim. 28. septembre 2025</div>&nbsp;
* <div>10:00</div>&nbsp;
* <div>Alterswilen</div>&nbsp;
* <div>Endschiessen</div>&nbsp;
* <div>lun. 29. septembre 2025</div>&nbsp;
* <div>17:00</div>&nbsp;
* <div>Stammheim</div>&nbsp;
* <div>Endschiessen</div>&nbsp;
* <div>lun. 6. octobre 2025</div>&nbsp;
* <div>17:00</div>&nbsp;
* <div>Stammheim</div>&nbsp;
* <div>Niklausschiessen</div>&nbsp;
* <div>sam. 25. octobre 2025</div>&nbsp;
* <div>9:00</div>&nbsp;
* <div>Diessenhofen</div>&nbsp;
* <div>Niklausschiessen</div>&nbsp;
* <div>dim. 26. octobre 2025</div>&nbsp;
* <div>9:00</div>&nbsp;
* <div>Diessenhofen</div>&nbsp;
* <div>Absenden</div>&nbsp;
* <div>ven. 31. octobre 2025</div>&nbsp;
* <div>19:00</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>Niklausschiessen</div>&nbsp;
* <div>sam. 1. novembre 2025</div>&nbsp;
* <div>9:00</div>&nbsp;
* <div>Diessenhofen</div>&nbsp;
* <div>Appenzeller Lupi Meisterschaft</div>&nbsp;
* <div>dim. 11. janvier 2026</div>&nbsp;
* <div>9:00</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>Generalversammlung</div>&nbsp;
* <div>jeu. 5. février 2026</div>&nbsp;
* <div>19:00</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
---
_template: page.html

View file

@ -4,14 +4,38 @@ title: Termine
---
body:
* <div>Generalversammlung</div>&nbsp;
* <div>Do. 6. Februar 2025</div>&nbsp;
* <div>19:00</div>&nbsp;
* <div>Restaurant Hirschen</div>&nbsp;
* <div>Bezirksmatch 10m</div>&nbsp;
* <div>Do. 13. Februar 2025</div>&nbsp;
* <div>18:00</div>&nbsp;
* <div>Stammheim</div>&nbsp;
* <div>Bezirksmatch 10m</div>&nbsp;
* <div>Do. 20. Februar 2025</div>&nbsp;
* <div>18:00</div>&nbsp;
* <div>Stammheim</div>&nbsp;
* <div>Bezirkswinterschiessen</div>&nbsp;
* <div>Do. 6. März 2025</div>&nbsp;
* <div>17:30</div>&nbsp;
* <div>Flurlingen</div>&nbsp;
* <div>Bezirkswinterschiessen</div>&nbsp;
* <div>Sa. 8. März 2025</div>&nbsp;
* <div>14:00</div>&nbsp;
* <div>Flurlingen</div>&nbsp;
* <div>Frühlingsschiessen</div>&nbsp;
* <div>Sa. 15. März 2025</div>&nbsp;
* <div>9:00</div>&nbsp;
* <div>Bülach</div>&nbsp;
* <div>Frühlingsschiessen</div>&nbsp;
* <div>Sa. 15. März 2025</div>&nbsp;
* <div>13:30</div>&nbsp;
* <div>Bülach</div>&nbsp;
* <div>Frühlingsschiessen</div>&nbsp;
* <div>Sa. 22. März 2025</div>&nbsp;
* <div>9:00</div>&nbsp;
* <div>Bülach</div>&nbsp;
* <div>Standreinigung</div>&nbsp;
* <div>Sa. 22. März 2025</div>&nbsp;
* <div>9:00</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>freies Training</div>&nbsp;
* <div>Mo. 31. März 2025</div>&nbsp;
* <div>18:00</div>&nbsp;
@ -36,6 +60,10 @@ body:
* <div>Mo. 14. April 2025</div>&nbsp;
* <div>18:00</div>&nbsp;
* <div>Stammheim</div>&nbsp;
* <div>freies Training</div>&nbsp;
* <div>Mo. 21. April 2025</div>&nbsp;
* <div>18:00</div>&nbsp;
* <div>Stammheim</div>&nbsp;
* <div>Obligatorisches Programm und Kreis Winterschiessen</div>&nbsp;
* <div>Mo. 28. April 2025</div>&nbsp;
* <div>18:00</div>&nbsp;
@ -244,6 +272,14 @@ body:
* <div>Do. 5. Februar 2026</div>&nbsp;
* <div>19:00</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>Bezirkssommerschiessen</div>&nbsp;
* <div>Do. 23. August 2029</div>&nbsp;
* <div>18:00</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
* <div>Bezirkssommerschiessen</div>&nbsp;
* <div>Do. 30. August 2029</div>&nbsp;
* <div>18:00</div>&nbsp;
* <div>&nbsp;</div>&nbsp;
---
_template: page.html

View file

@ -1,26 +0,0 @@
_model: page
---
title: Direction
---
body:
* <div>Ulrich Roland</div>&nbsp;
* <div>Président</div>&nbsp;
* <div><a href="mailto:praesidium@pc-stammertal.ch">praesidium@pc-stammertal.ch</a></div>&nbsp;
* <div>Ita Regula</div>&nbsp;
* <div>Trésorière</div>&nbsp;
* <div><a href="mailto:kasse@pc-stammertal.ch">kasse@pc-stammertal.ch</a></div>&nbsp;
* <div>Arnold Sonja</div>&nbsp;
* <div>Secrétaire</div>&nbsp;
* <div><a href="mailto:aktuariat@pc-stammertal.ch">aktuariat@pc-stammertal.ch</a></div>&nbsp;
* <div>Müller Konrad</div>&nbsp;
* <div>1. Maître de tir</div>&nbsp;
* <div><a href="mailto:schiesswesen@pc-stammertal.ch">schiesswesen@pc-stammertal.ch</a></div>&nbsp;
* <div>Bollinger Reto</div>&nbsp;
* <div>Maître de tir, Assesseur</div>&nbsp;
* <div><a href="mailto:beisitz@pc-stammertal.ch">beisitz@pc-stammertal.ch</a></div>&nbsp;
* <div>Horvath Richard</div>&nbsp;
* <div>Maître de tir, Porte-drapeau, Responsable du champ de tir</div>&nbsp;
* <div><a href="mailto:infrastruktur@pc-stammertal.ch">infrastruktur@pc-stammertal.ch</a></div>&nbsp;
---
_template: page.html

View file

@ -26,41 +26,19 @@
&#x2630;
</label>
<input id='menu' type='checkbox'>
<ul class="nav navbar-nav">
{% set navigation = {
'xy': [
{% for href, title in [
['/termine', 'Termine'],
['/vorstand', 'Vorstand'],
['/about', 'Über uns'],
['/kontakt', 'Kontakt aufnehmen'],
['.'|url(alt='fr'), '|FR|']
],
'de': [
['/termine', 'Termine'],
['/vorstand', 'Vorstand'],
['/about', 'Über uns'],
['/kontakt', 'Kontakt aufnehmen'],
['.'|url(alt='fr'), '|FR|']
],
'fr': [
['/termine', 'Événements'],
['/vorstand', 'Direction'],
['/about', 'À propos'],
['/kontakt', 'Contactez-nous'],
['.'|url(alt='de'), '|DE|']
]
} %}
{% for href, title in navigation[alt or 'de'] %}
<li class="{% if this.is_child_of(href) %}active{% endif %}
{% if href == '/kontakt' %} button{% endif %}">
<a href="{{ (href|url(alt=alt)) }}">{{ title }}</a>
</li>
['/kontakt', 'Kontakt aufnehmen']
] %}
<li class="{% if this.is_child_of(href) %}active{% endif
%}
{% if title == 'Kontakt aufnehmen' %} button{% endif
%}"><a href="{{ href|url }}">{{ title }}</a></li>
{% endfor %}
</ul>
</div>
</nav>
</div>

View file

@ -1,57 +0,0 @@
from datetime import datetime, date, timezone
import locale
import sys
from calendarstuff import get_events
def fetch_upcoming_events(ics_url):
events = get_events(ics_url, 'fr_FR.UTF-8')
for event in events:
start = event.get('dtstart').dt
out_summary = event.get('summary')
location = event.get('location', 'No location specified')
out_startdate = start.strftime("%a %-d. %B %Y")
# Format output based on whether it's an all-day event
if isinstance(start, date) and not isinstance(start, datetime):
out_starttime = "&nbsp;"
else:
out_starttime = start.strftime('%-H:%M')
if location != 'No location specified':
out_location = location
else:
out_location = "&nbsp;"
print(f"* <div>{out_summary}</div>&nbsp;")
print(f" * <div>{out_startdate}</div>&nbsp;")
print(f" * <div>{out_starttime}</div>&nbsp;")
print(f" * <div>{out_location}</div>&nbsp;")
if __name__ == "__main__":
ics_url = sys.argv[1]
# ics_url = "https://backoffice.pc-stammertal.ch/remote.php/dav/public-calendars/RqLX5wj25aY6cpnP?export"
print("_model: page")
print("---")
print("title: Événements")
print("---")
print("body:")
print("")
# print("<center><a href=\"https://backoffice.pc-stammertal.ch/remote.php/dav/public-calendars/RqLX5wj25aY6cpnP?export\">Kalender Abonnieren</a></center>")
# print("")
# print("<center><a href=\"https://backoffice.pc-stammertal.ch/remote.php/dav/public-calendars/RqLX5wj25aY6cpnP?export\"><img src=\"/images/calendar.png\" alt=\"Link als QR code\"></a></center>")
# print("")
fetch_upcoming_events(ics_url)
print("")
print("---")
print("_template: page.html")
print("")
print("")

View file

@ -1,142 +0,0 @@
#!/usr/bin/env python3
import sys
from pathlib import Path
from datetime import datetime, date, timezone
from typing import Optional, NamedTuple
from dataclasses import dataclass
from bs4 import BeautifulSoup
import locale
from calendarstuff import get_events
@dataclass
class EventDetails:
weekday: str
date: str
time: str
summary: str
location: str
def to_html(self) -> str:
return (
f'<div class="nextevent">{self.weekday} '
f'<strong>{self.date}{self.time}, {self.summary}</strong>'
f'{self.location}</div>'
)
class EventProcessor:
def __init__(self, ics_url: str, content_file: str):
self.ics_url = ics_url
self.content_file = Path(content_file)
self.fallback_html = (
'<div class="nextevent">Malheureusement inconnu, mais '
'<strong>demande à la direction</strong> qui devrait le savoir</div>'
)
@property
def fallback_content(self) -> str:
return f"""_model: htmlpage
---
title: Bienvenue au PC Stammertal
---
html:
<h2>Notre prochain événement: </h2><br>
{self.fallback_html}
<div>
&nbsp;<br>
&nbsp;<br>
&nbsp;<br>
</div>
<h3>...et ne manquez pas non plus :</h3>
<a href="https://www.wyland25.ch/" target="_blank"><img src="/images/zhksf.png" alt="27. Zürcher Kantonalschützenfest 2025" class="stamp"></a>
<div class="threecolumn">
<div>
<a href="termine/"><img src=" /images/termine_square.jpg" alt="Calendrier"> Tous nos événements</a>
</div>
<div>
<a href="about/"><img src="/images/about_square.jpg" alt="Livre"> À propos de nous</a>
</div>
<div>
<a href="kontakt/"><img src="/images/kontakt_square.jpg" alt="Lettres"> Contactez-nous</a>
</div>
</div>
---
_template:
page.html
"""
def setup_locale(self) -> None:
try:
locale.setlocale(locale.LC_TIME, 'de_DE.UTF-8')
except locale.Error as e:
print(f"Warning: Failed to set locale: {e}", file=sys.stderr)
def format_event_time(self, start: datetime | date) -> str:
return "" if isinstance(start, date) and not isinstance(start, datetime) else start.strftime(" %-H:%M")
def get_next_event(self) -> Optional[EventDetails]:
try:
events = get_events(self.ics_url, 'fr_FR.UTF-8')
event = events[0]
start = event.get('dtstart').dt
return EventDetails(
weekday=start.strftime("%A"),
date=start.strftime("%-d. %B"),
time=self.format_event_time(start),
summary=event.get('summary', ''),
location=f", {event.get('location')}" if event.get('location') else ""
)
except (StopIteration, AttributeError) as e:
print(f"No upcoming events found: {e}", file=sys.stderr)
return None
def read_current_content(self) -> tuple[str, str]:
try:
content = self.content_file.read_text()
soup = BeautifulSoup(content, 'html.parser')
events = soup.find_all('div', {'class': 'nextevent'})
return content, str(events[0]) if len(events) == 1 else ""
except (IOError, IndexError) as e:
print(f"Error reading content file: {e}", file=sys.stderr)
return "", ""
def process(self) -> str:
self.setup_locale()
content, source_str = self.read_current_content()
if not content:
return self.fallback_content
if len(source_str) > 0 and source_str in content:
event = self.get_next_event()
if not event:
return content.replace(source_str, self.fallback_html).rstrip()
return content.replace(source_str, event.to_html()).rstrip()
return self.fallback_content
def main() -> None:
if len(sys.argv) != 2:
print(f"Usage: {sys.argv[0]} <ICS_URL>", file=sys.stderr)
sys.exit(1)
processor = EventProcessor(
ics_url=sys.argv[1],
content_file="/opt/lektor/project/content/contents+fr.lr"
)
print(processor.process(), end='')
print("")
print("")
if __name__ == "__main__":
main()

View file

@ -80,7 +80,7 @@ page.html
def get_next_event(self) -> Optional[EventDetails]:
try:
events = get_events(self.ics_url, 'de_DE.UTF-8')
events = get_events(self.ics_url)
event = events[0]
start = event.get('dtstart').dt

View file

@ -6,7 +6,7 @@ from calendarstuff import get_events
def fetch_upcoming_events(ics_url):
events = get_events(ics_url, 'de_DE.UTF-8')
events = get_events(ics_url)
for event in events:

View file

@ -56,9 +56,9 @@ def split_multiday_events(events):
return split_events
def get_events(ics_url, tgt_locale):
def get_events(ics_url):
# Set German locale
locale.setlocale(locale.LC_TIME, tgt_locale)
locale.setlocale(locale.LC_TIME, 'de_DE.UTF-8')
response = requests.get(ics_url)
calendar = Calendar.from_ical(response.content)