#!/usr/bin/env python3 """ fredagsbar_ics_generator_v3.py Genererer .ics invitationer baseret på JSON stories. Understøtter nu 'hints' i filnavne og headers. """ import json import argparse import sys from ics import Calendar, Event from datetime import datetime, date, time, timedelta from zoneinfo import ZoneInfo import uuid import os import re # ====== STANDARD INDSTILLINGER ====== START_DATE = date(2026, 1, 29) START_TIME = time(15, 00) DURATION_MINUTES = 60 TIMEZONE = ZoneInfo("Europe/Copenhagen") OUTPUT_BASE_DIR = "fredagsbar_output" # ==================================== def load_story(filepath): """Indlæser JSON fil og validerer strukturen.""" try: with open(filepath, 'r', encoding='utf-8') as f: data = json.load(f) return data except Exception as e: print(f"Fejl ved indlæsning af {filepath}: {e}") sys.exit(1) def next_or_same_friday(d: date) -> date: return d + timedelta(days=(4 - d.weekday()) % 7) def sanitize_filename(s: str) -> str: s = re.sub(r"[^\w\s-]", "", s, flags=re.UNICODE) s = re.sub(r"\s+", "_", s.strip()) return s[:120] def strip_html_tags(text: str) -> str: clean = re.compile('<.*?>') return re.sub(clean, '', text) def inject_outlook_lines(ical_text: str, html_description: str) -> str: """Indsætter Outlook-specifikke linjer og HTML beskrivelse.""" injection_status = "TRANSP:OPAQUE\r\nX-MICROSOFT-CDO-BUSYSTATUS:BUSY\r\n" # Outlook kræver often at HTML er på én linje eller foldet korrekt html_oneline = html_description.replace("\n", "") injection_html = f"X-ALT-DESC;FMTTYPE=text/html:{html_oneline}\r\n" def replacer(match): vevent = match.group(0) if "X-MICROSOFT-CDO-BUSYSTATUS" not in vevent: vevent = vevent.replace("\r\nEND:VEVENT", "\r\n" + injection_status + "END:VEVENT") # Overskriv eller indsæt X-ALT-DESC if "X-ALT-DESC" in vevent: # Simpel håndtering: Hvis vi allerede har det, ignorer (eller implementer regex replace af X-ALT-DESC) pass else: vevent = vevent.replace("\r\nEND:VEVENT", "\r\n" + injection_html + "END:VEVENT") return vevent text_crlf = ical_text.replace("\n", "\r\n") vevent_pattern = re.compile(r"BEGIN:VEVENT[\s\S]*?END:VEVENT", flags=re.IGNORECASE) return vevent_pattern.sub(replacer, text_crlf) def create_event_ics_file(event_data, meta, log_index, total_logs, start_dt, end_dt, out_path): title = event_data["title"] story_text = event_data["story"] hint = event_data.get("hint", "") # Hent hint hvis det findes cal = Calendar() event = Event() event.uid = str(uuid.uuid4()) # Hvis der er et hint, sæt det evt. ind i titlen eller behold titlen ren corporate event.name = title event.begin = start_dt event.end = end_dt event.status = "CONFIRMED" event.classification = "PUBLIC" # Meta styling font = meta.get("font", "Arial, sans-serif") bg_color = meta.get("bg_color", "#f0f0f0") text_color = meta.get("text_color", "#000000") theme_color = meta.get("theme_color", "#000000") log_prefix = meta.get("log_prefix", "LOG") # Byg Header strengen header_text = f"// {log_prefix} {log_index:02d}/{total_logs} // SUBJECT: {title.upper()}" if hint: header_text += f" // CODE: {hint}" # --- HTML Version (Outlook) --- html_desc = ( f"
" f"{header_text}

" f"
" f"{story_text}" # Her indsætter vi story direkte, da den nu indeholder HTML tags fra JSON f"

" "
" "OBS: Dette er en invitation til en fredagsbar.
" "Dette event er automatisk genereret til Fredagsbar, med en title der giver Nicolaj mulighed for at deltage.
" "Ingen forberedelse nødvendig.

" "" "Vibecoded sourcecode til generering kan findes her: gitea.weircon.dk/{{repo}}" "" "
" ) # --- Plain Text Version --- # Vi erstatter
med \n for læsbarhed i plain text plain_story = story_text.replace("
", "\n").replace("", "").replace("", "").replace("", "").replace("", "") plain_desc = ( f"{header_text}\n\n" f"{plain_story}\n\n" "--------------------------------------------------\n" "OBS: Dette er en invitation til en fredagsbar.\n" "Ingen forberedelse nødvendig.\n" "https://gitea.weircon.dk/{{repo}}" ) event.description = plain_desc cal.events.add(event) serialized = cal.serialize() processed = inject_outlook_lines(serialized, html_desc) with open(out_path, "w", encoding="utf-8", newline="\r\n") as f: f.write(processed) print(f"[{log_index}/{total_logs}] '{title}' -> {out_path}") def generate_series(json_path): data = load_story(json_path) meta = data["meta"] events = data["events"] story_slug = sanitize_filename(meta.get("name", "story")) output_dir = os.path.join(OUTPUT_BASE_DIR, story_slug) os.makedirs(output_dir, exist_ok=True) print(f"--- Starter generering af: {meta.get('name')} ---") first_friday = next_or_same_friday(START_DATE) total_logs = len(events) for idx, event_entry in enumerate(events): event_date = first_friday + timedelta(weeks=idx) start_dt = datetime.combine(event_date, START_TIME).replace(tzinfo=TIMEZONE) end_dt = start_dt + timedelta(minutes=DURATION_MINUTES) log_num = idx + 1 # Bruger hint i filnavnet hvis det findes hint_part = "" if "hint" in event_entry: hint_part = f"_{sanitize_filename(event_entry['hint'])}" fname = f"{event_date.strftime('%Y%m%d')}_Log{log_num}{hint_part}_{sanitize_filename(event_entry['title'])}.ics" out_path = os.path.join(output_dir, fname) create_event_ics_file(event_entry, meta, log_num, total_logs, start_dt, end_dt, out_path) if __name__ == "__main__": parser = argparse.ArgumentParser(description="Generer Fredagsbar ICS filer fra JSON historie.") parser.add_argument("story_file", help="Sti til JSON filen med historien (f.eks. stories/story_scp.json)") args = parser.parse_args() if not os.path.exists(args.story_file): print(f"Fejl: Filen '{args.story_file}' findes ikke.") sys.exit(1) generate_series(args.story_file) print("\nFærdig!")