diff --git a/devenv.nix b/devenv.nix
index c46ffe5..b96c8d3 100644
--- a/devenv.nix
+++ b/devenv.nix
@@ -18,9 +18,13 @@
sync.allExtras = true;
};
};
+ typst = {
+ enable = true;
+ };
};
packages = [
+ pkgs.hanken-grotesk
pkgs.uv
];
diff --git a/src/kronos/main.py b/src/kronos/main.py
index 25e9219..0909e09 100755
--- a/src/kronos/main.py
+++ b/src/kronos/main.py
@@ -1,4 +1,5 @@
import datetime
+import json
import os
import sys
from zoneinfo import ZoneInfo
@@ -17,8 +18,9 @@ MOBILIZON_API_URL = "https://events.hs3.pl/api"
QUERY_LIMIT = 100
TIME_WINDOW_DAYS = 365
TRUNCATE_AFTER_CHARACTERS = 300
-TEMPLATE_FILENAME = "templates/newsletter_template.html"
-OUTPUT_FILENAME = "newsletter_events.html"
+TEMPLATES = {
+ "templates/newsletter_template.html": "newsletter_events.html",
+}
def sanitize_html(raw_html: str) -> str:
@@ -50,6 +52,7 @@ def build_graphql_query():
title
description
beginsOn
+ endsOn
picture {
url
}
@@ -91,7 +94,7 @@ def fetch_events(begins_on: str, ends_on: str, limit: int = QUERY_LIMIT):
def prepare_events_for_template(raw_events: list) -> list:
- jours_semaine = [
+ days_week = [
"poniedziałek",
"wtorek",
"środa",
@@ -100,7 +103,7 @@ def prepare_events_for_template(raw_events: list) -> list:
"sobota",
"niedziela",
]
- mois_annee = [
+ month_year = [
"",
"stycznia",
"lutego",
@@ -132,6 +135,7 @@ def prepare_events_for_template(raw_events: list) -> list:
begins_iso = ev.get("beginsOn")
picture = ev.get("picture")
picture_url = picture.get("url") if picture else None
+ picture_filename = ev.get("picture_filename")
link = ev.get("url") or ""
if picture_url and "?" in picture_url:
@@ -146,15 +150,15 @@ def prepare_events_for_template(raw_events: list) -> list:
location = ""
dt_utc = datetime.datetime.fromisoformat(begins_iso.replace("Z", "+00:00"))
- dt_paris = dt_utc.astimezone(ZoneInfo("Europe/Paris"))
- jour_nom = jours_semaine[dt_paris.weekday()]
- jour_num = dt_paris.day
- mois_nom = mois_annee[dt_paris.month]
- annee = dt_paris.year
- heure = dt_paris.hour
- minute = dt_paris.minute
+ dt_warsaw = dt_utc.astimezone(ZoneInfo("Europe/Warsaw"))
+ day_name = days_week[dt_warsaw.weekday()]
+ day_num = dt_warsaw.day
+ month_name = month_year[dt_warsaw.month]
+ year = dt_warsaw.year
+ hour = dt_warsaw.hour
+ minute = dt_warsaw.minute
minute_str = f"{minute:02d}"
- full_date = f"{jour_nom}, {jour_num} {mois_nom} {annee} @ {heure}:{minute_str}"
+ full_date = f"{day_name}, {day_num} {month_name} {year} @ {hour}:{minute_str}"
prepared.append(
{
@@ -162,6 +166,7 @@ def prepare_events_for_template(raw_events: list) -> list:
"description": truncated,
"full_date": full_date,
"picture_url": picture_url,
+ "picture_filename": picture_filename,
"location": location,
"link": link,
"start_time": dt_utc,
@@ -191,6 +196,37 @@ def inline_css(input_html_path, output_html_path):
f.write(inlined_html)
+def download_picture():
+ pass
+
+
+def save_json_content(events):
+ json_dirname = "src/typst/content"
+ os.makedirs(json_dirname, exist_ok=True)
+ with open(os.path.join(json_dirname, "events.json"), "w") as f:
+ json.dump(sorted(events, key=lambda event: event["beginsOn"]), f)
+
+
+def extract_picture(event):
+ picture = event.get("picture")
+ picture_url = picture.get("url") if picture else None
+ picture_filename = None
+
+ if picture_url:
+ if "?" in picture_url:
+ picture_url = picture_url.split("?", 1)[0]
+
+ picture_dirname = "src/typst/assets/tmp"
+ picture_filename = os.path.join(picture_dirname, picture_url.split("/")[-1])
+ event["picture_filename"] = picture_filename
+
+ response = requests.get(picture_url)
+
+ os.makedirs(picture_dirname, exist_ok=True)
+ with open(picture_filename, "wb") as picture_file:
+ picture_file.write(response.content)
+
+
def _main():
load_dotenv()
@@ -216,28 +252,44 @@ def _main():
sys.exit(0)
for ev in raw_events:
- print(f"{ev.get('title', 'Untitled')} — {ev.get('beginsOn', 'Unknown date')}")
+ print(f"{ev.get('title', 'Untitled')} — {ev.get('beginsOn', 'Unknown date')}")
+
+ ev["txt_description"] = BeautifulSoup(ev["description"], "html.parser").get_text()
+
+ begins = ev["beginsOn"].split("T")
+ ends = ev["endsOn"].split("T")
+ txt_date = f"{begins[0]} {begins[1][:-4]}-"
+ if begins[0] != ends[0]:
+ txt_date += f"{ends[0]}"
+ txt_date += f"{ends[1][:-4]}"
+ ev["txt_date"] = txt_date
+
+ extract_picture(ev)
+
+ save_json_content(raw_events)
raw_events.sort(key=lambda ev: ev.get("beginsOn", ""))
events = prepare_events_for_template(raw_events)
- os.makedirs("dist", exist_ok=True)
- output_path = os.path.join(os.getcwd(), "dist", OUTPUT_FILENAME)
- html_output = render_newsletter(
- events=events, template_dir=os.getcwd(), template_name=TEMPLATE_FILENAME
- )
+ for template_filename, output_filename in TEMPLATES.items():
+ output = render_newsletter(
+ events=events, template_dir=os.getcwd(), template_name=template_filename
+ )
- with open(output_path, "w", encoding="utf-8") as f:
- f.write(html_output)
- print(f"File '{output_path}' generated successfully.", file=sys.stderr)
+ os.makedirs("dist", exist_ok=True)
+ output_path = os.path.join(os.getcwd(), "dist", output_filename)
+ with open(output_path, "w", encoding="utf-8") as f:
+ f.write(output)
+ print(f"File '{output_path}' generated successfully.", file=sys.stderr)
- inlined_output_path = os.path.join(os.getcwd(), "dist", "newsletter_events_inlined.html")
- inline_css(output_path, inlined_output_path)
- print(
- f"File '{inlined_output_path}' (inline CSS) generated.",
- file=sys.stderr,
- )
+ if output_filename.endswith(".html"):
+ inlined_output_path = os.path.join(os.getcwd(), "dist", f"inlined_{output_filename}")
+ inline_css(output_path, inlined_output_path)
+ print(
+ f"File '{inlined_output_path}' (inline CSS) generated.",
+ file=sys.stderr,
+ )
discord_token = os.getenv("DISCORD_TOKEN")
client = KronosBot(intents=intents)
diff --git a/src/typst/.gitignore b/src/typst/.gitignore
new file mode 100644
index 0000000..20634ad
--- /dev/null
+++ b/src/typst/.gitignore
@@ -0,0 +1,2 @@
+assets/tmp/*
+*.pdf
diff --git a/src/typst/assets/calendar.svg b/src/typst/assets/calendar.svg
new file mode 100644
index 0000000..7c6b5d6
--- /dev/null
+++ b/src/typst/assets/calendar.svg
@@ -0,0 +1,25941 @@
+
+
+
+
diff --git a/src/typst/assets/event.svg b/src/typst/assets/event.svg
new file mode 100644
index 0000000..517ccb8
--- /dev/null
+++ b/src/typst/assets/event.svg
@@ -0,0 +1,1244 @@
+
+
+
+
diff --git a/src/typst/assets/logo.png b/src/typst/assets/logo.png
new file mode 100644
index 0000000..dd8e793
Binary files /dev/null and b/src/typst/assets/logo.png differ
diff --git a/src/typst/assets/photo.jpg b/src/typst/assets/photo.jpg
new file mode 100644
index 0000000..718b180
Binary files /dev/null and b/src/typst/assets/photo.jpg differ
diff --git a/src/typst/calendar.typ b/src/typst/calendar.typ
new file mode 100644
index 0000000..e4d21ba
--- /dev/null
+++ b/src/typst/calendar.typ
@@ -0,0 +1,69 @@
+#set page(
+ paper: "a4",
+ background: context {
+ place(center + horizon, {
+ image("assets/calendar.svg")
+ })
+ },
+ margin: (top: .5cm, bottom: .5cm, x: 0cm),
+)
+
+#set text(fill: white)
+
+#let pat = tiling(size: (30pt, 30pt))[
+ #place(line(start: (0%, 0%), end: (100%, 100%), stroke: white))
+ #place(line(start: (0%, 100%), end: (100%, 0%), stroke: white))
+]
+
+#let add_event(number, title, description, date, address) = {
+ let ex = 85mm + calc.rem-euclid(number, 2) * 67mm
+ let ey = 46mm + calc.div-euclid(number, 2) * 76mm
+
+ place(
+ top+left,
+ dx: ex,
+ dy: ey,
+ block(
+ width: 32mm,
+ height: 48mm,
+
+ [
+ #block(
+ height: 14mm,
+ clip: true,
+ text(
+ font: ("Hanken Grotesk"),
+ title
+ )
+ )
+ #move(dy: 4mm,
+ block(
+ height: 3mm,
+ clip: true,
+ text(weight: "bold", size: 9pt, date)
+ )
+ )
+ #move(dy: 11mm,
+ block(
+ height: 12mm,
+ clip: true,
+ text(size: 9pt, address)
+ )
+ )
+ ],
+ )
+ )
+}
+
+#let events = json("content/events.json")
+#let index = 0
+
+#for event in events {
+ add_event(index, event.title, event.txt_description, event.txt_date, "Hackerspace Trójmiasto, al. Wojska Polskiego 41, Gdańsk")
+
+ index = index + 1
+
+ if (index >= 6) {
+ break
+ }
+}
diff --git a/src/typst/content/.gitkeep b/src/typst/content/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/src/typst/event.typ b/src/typst/event.typ
new file mode 100644
index 0000000..ae81c34
--- /dev/null
+++ b/src/typst/event.typ
@@ -0,0 +1,75 @@
+#import "@preview/bulb:0.1.0": dither
+
+#set page(
+ width: 297mm,
+ height: 169mm,
+ background: context {
+ place(center + horizon, {
+ image("assets/event.svg")
+ })
+ },
+ margin: (top: 0cm, bottom: .5cm, x: .5cm),
+)
+
+#let make_event_card(title, description, date, address, picture, name) = {
+ move(
+ dx: 148mm,
+ dy: 23mm,
+ image(
+ dither(
+ read("assets/photo.jpg", encoding: none),
+ mode: "bw",
+ method: "bayer8x8",
+ size: 360,
+ ),
+ ),
+ )
+ place(
+ top+left,
+ dx: 94mm,
+ dy: -4mm,
+ image("assets/hs3_logo_hires.png", height: 38mm, width: 38mm),
+ )
+
+ set text(font: ("Hanken Grotesk"), size: 14pt)
+
+ place(
+ top+left,
+ dx: 12mm,
+ dy: 30mm,
+ block(
+ width: 118mm,
+ height: 130mm,
+ grid(
+ rows: (2fr, 6fr, 0.75fr, 0.5fr),
+ text(size: 28pt, weight: "bold", title),
+ text(size: 10pt, description),
+ text(
+ size: 24pt,
+ weight: "bold",
+ grid(
+ columns: (1fr, 1fr),
+ date.split(" ").at(0),
+ align(right)[#date.split(" ").at(1)]
+ ),
+ ),
+ address,
+ )
+ )
+ )
+
+ align(right+bottom)[#text(size: 14pt, weight: "bold", name)]
+}
+
+#let events = json("content/events.json")
+#let index = 0
+
+#for event in events {
+ make_event_card(event.title, event.txt_description, event.txt_date, "Gdańsk, al. Wojska Polskiego 41", "", "Joanna Roscoe")
+
+ index = index + 1
+
+ if index < events.len() {
+ pagebreak()
+ }
+}