"""Generate iCalendar feeds for conferences.""" import hashlib from datetime import datetime, timedelta, timezone import agenda import agenda.utils from agenda import ical from agenda.types import StrDict def _conference_uid(conf: StrDict) -> str: """Generate deterministic UID for conference events.""" start = agenda.utils.as_date(conf["start"]) raw = f"conference|{start.isoformat()}|{conf.get('name','unknown')}" digest = hashlib.sha1(raw.encode("utf-8")).hexdigest() return f"conference-{digest}@agenda-codex" def _conference_location(conf: StrDict) -> str | None: """Build conference location string.""" parts: list[str] = [] venue = conf.get("venue") location = conf.get("location") if isinstance(venue, str) and venue.strip(): parts.append(venue.strip()) if isinstance(location, str) and location.strip(): parts.append(location.strip()) if country_code := conf.get("country"): country = agenda.get_country(country_code) if country: parts.append(country.name) return ", ".join(parts) if parts else None def _conference_description(conf: StrDict) -> str: """Build textual description for conferences.""" lines: list[str] = [] if topic := conf.get("topic"): lines.append(f"Topic: {topic}") if venue := conf.get("venue"): lines.append(f"Venue: {venue}") if address := conf.get("address"): lines.append(f"Address: {address}") if url := conf.get("url"): lines.append(f"URL: {url}") status_bits: list[str] = [] if conf.get("going"): status_bits.append("attending") if conf.get("speaking"): status_bits.append("speaking") if status_bits: lines.append(f"Status: {', '.join(status_bits)}") return "\n".join(lines) if lines else "Conference" def build_conference_ical(items: list[StrDict]) -> bytes: """Build iCalendar feed for all conferences.""" lines = [ "BEGIN:VCALENDAR", "VERSION:2.0", "PRODID:-//Agenda Codex//Conferences//EN", "CALSCALE:GREGORIAN", "METHOD:PUBLISH", "X-WR-CALNAME:Conferences", ] generated = datetime.now(tz=timezone.utc) for conf in items: if not conf["has_exact_dates"]: continue start_date = agenda.utils.as_date(conf["start"]) end_date = agenda.utils.as_date(conf["end"]) end_exclusive = end_date + timedelta(days=1) lines.append("BEGIN:VEVENT") ical.append_property(lines, "UID", _conference_uid(conf)) ical.append_property(lines, "DTSTAMP", ical.format_datetime_utc(generated)) ical.append_property(lines, "DTSTART;VALUE=DATE", ical.format_date(start_date)) ical.append_property(lines, "DTEND;VALUE=DATE", ical.format_date(end_exclusive)) ical.append_property(lines, "SUMMARY", ical.escape_text(conf["name"])) description = ical.escape_text(_conference_description(conf)) ical.append_property(lines, "DESCRIPTION", description) if location := _conference_location(conf): ical.append_property(lines, "LOCATION", ical.escape_text(location)) lines.append("END:VEVENT") lines.append("END:VCALENDAR") ical_text = "\r\n".join(lines) + "\r\n" return ical_text.encode("utf-8")