90 lines
3.2 KiB
Python
90 lines
3.2 KiB
Python
"""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")
|