Complete recurrence editing and calendar preferences
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
import uuid
|
||||
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||
|
||||
|
||||
def graph_event_payload(item: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Translate one Microsoft Graph event into the calendar event contract."""
|
||||
|
||||
start = graph_datetime(item.get("start"))
|
||||
end = graph_datetime(item.get("end"))
|
||||
all_day = bool(item.get("isAllDay"))
|
||||
uid = str(
|
||||
item.get("iCalUId")
|
||||
or item.get("uid")
|
||||
or item.get("id")
|
||||
or uuid.uuid4()
|
||||
)
|
||||
return {
|
||||
"uid": uid,
|
||||
"recurrence_id": (
|
||||
str(item.get("id"))
|
||||
if item.get("type") == "occurrence"
|
||||
else None
|
||||
),
|
||||
"sequence": int(item.get("sequence") or 0),
|
||||
"summary": str(item.get("subject") or "(Untitled event)"),
|
||||
"description": graph_body_text(item),
|
||||
"location": graph_location_text(item.get("location")),
|
||||
"status": (
|
||||
"CANCELLED" if item.get("isCancelled") else "CONFIRMED"
|
||||
),
|
||||
"transparency": (
|
||||
"TRANSPARENT"
|
||||
if item.get("showAs") in {"free", "workingElsewhere"}
|
||||
else "OPAQUE"
|
||||
),
|
||||
"classification": (
|
||||
"PRIVATE"
|
||||
if item.get("sensitivity") == "private"
|
||||
else "PUBLIC"
|
||||
),
|
||||
"start_at": start,
|
||||
"end_at": end,
|
||||
"duration_seconds": int((end - start).total_seconds()),
|
||||
"all_day": all_day,
|
||||
"timezone": (
|
||||
(item.get("start") or {}).get("timeZone")
|
||||
if isinstance(item.get("start"), dict)
|
||||
else None
|
||||
)
|
||||
or "UTC",
|
||||
"organizer": graph_party(item.get("organizer")),
|
||||
"attendees": [
|
||||
graph_party(attendee)
|
||||
for attendee in item.get("attendees") or []
|
||||
],
|
||||
"categories": [
|
||||
str(category) for category in item.get("categories") or []
|
||||
],
|
||||
"rrule": (
|
||||
item.get("recurrence")
|
||||
if isinstance(item.get("recurrence"), dict)
|
||||
else None
|
||||
),
|
||||
"rdate": [],
|
||||
"exdate": [],
|
||||
"reminders": graph_reminders(item),
|
||||
"attachments": [],
|
||||
"related_to": [],
|
||||
"etag": item.get("@odata.etag"),
|
||||
"icalendar": {
|
||||
"component": "VEVENT",
|
||||
"schema_version": 1,
|
||||
"provider": "graph",
|
||||
"graph": item,
|
||||
},
|
||||
"metadata": {"graph": item},
|
||||
}
|
||||
|
||||
|
||||
def graph_datetime(value: Any) -> datetime:
|
||||
if not isinstance(value, dict) or not value.get("dateTime"):
|
||||
return datetime.now(timezone.utc)
|
||||
raw = str(value["dateTime"]).replace("Z", "+00:00")
|
||||
parsed = datetime.fromisoformat(raw)
|
||||
tz_name = str(value.get("timeZone") or "UTC")
|
||||
if parsed.tzinfo is None:
|
||||
try:
|
||||
parsed = parsed.replace(tzinfo=ZoneInfo(tz_name))
|
||||
except ZoneInfoNotFoundError:
|
||||
parsed = parsed.replace(tzinfo=timezone.utc)
|
||||
return normalize_datetime(parsed)
|
||||
|
||||
|
||||
def graph_body_text(item: dict[str, Any]) -> str | None:
|
||||
body = item.get("body")
|
||||
if isinstance(body, dict) and body.get("content"):
|
||||
return str(body["content"])
|
||||
return str(item.get("bodyPreview")) if item.get("bodyPreview") else None
|
||||
|
||||
|
||||
def graph_location_text(value: Any) -> str | None:
|
||||
if isinstance(value, dict) and value.get("displayName"):
|
||||
return str(value["displayName"])
|
||||
return None
|
||||
|
||||
|
||||
def graph_party(value: Any) -> dict[str, Any]:
|
||||
if not isinstance(value, dict):
|
||||
return {}
|
||||
email = (
|
||||
value.get("emailAddress")
|
||||
if isinstance(value.get("emailAddress"), dict)
|
||||
else value
|
||||
)
|
||||
result = {
|
||||
"name": (
|
||||
str(email.get("name") or "")
|
||||
if isinstance(email, dict)
|
||||
else ""
|
||||
),
|
||||
"email": (
|
||||
str(email.get("address") or "")
|
||||
if isinstance(email, dict)
|
||||
else ""
|
||||
),
|
||||
}
|
||||
if value.get("type"):
|
||||
result["role"] = value["type"]
|
||||
if value.get("status"):
|
||||
result["status"] = value["status"]
|
||||
return result
|
||||
|
||||
|
||||
def graph_reminders(item: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
if (
|
||||
item.get("isReminderOn")
|
||||
and item.get("reminderMinutesBeforeStart") is not None
|
||||
):
|
||||
return [
|
||||
{
|
||||
"action": "DISPLAY",
|
||||
"trigger_minutes_before": int(
|
||||
item["reminderMinutesBeforeStart"]
|
||||
),
|
||||
}
|
||||
]
|
||||
return []
|
||||
|
||||
|
||||
def normalize_datetime(value: datetime) -> datetime:
|
||||
if value.tzinfo is None:
|
||||
return value.replace(tzinfo=timezone.utc)
|
||||
return value.astimezone(timezone.utc)
|
||||
Reference in New Issue
Block a user