Stupid copy and paste of scripts to support french. Btw. docker image needs to be recreated

This commit is contained in:
Reto Bollinger 2025-03-19 16:28:37 +01:00
parent f058484743
commit 134f3dedb2
10 changed files with 463 additions and 6 deletions

View file

@ -5,6 +5,7 @@ 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,4 +8,10 @@ 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

@ -5,7 +5,7 @@ title: Bienvenue au PC Stammertal
html:
<h2>Notre prochain événement: </h2><br>
<div class="nextevent">Samstag <strong>22. März 9:00, Frühlingsschiessen</strong>, Bülach</div>
<div class="nextevent">samedi <strong>22. mars 9:00, Frühlingsschiessen</strong>, Bülach</div>
<div>
&nbsp;<br>
&nbsp;<br>

View file

@ -0,0 +1,251 @@
_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

@ -39,7 +39,7 @@
['/termine', 'Événements'],
['/vorstand', 'Direction'],
['/about', 'À propos'],
['/kontakt', 'Contactez nous']
['/kontakt', 'Contactez-nous']
]
} %}

View file

@ -0,0 +1,57 @@
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

@ -0,0 +1,142 @@
#!/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)
events = get_events(self.ics_url, 'de_DE.UTF-8')
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)
events = get_events(ics_url, 'de_DE.UTF-8')
for event in events:

View file

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