SurfSense/surfsense_backend/app/connectors/google_calendar_connector.py
2025-08-03 12:16:40 +02:00

261 lines
8.6 KiB
Python

"""
Google Calendar Connector Module | Google OAuth Credentials | Google Calendar API
A module for retrieving calendar events from Google Calendar using Google OAuth credentials.
Allows fetching events from specified calendars within date ranges using Google OAuth credentials.
"""
from datetime import datetime
from typing import Any
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
class GoogleCalendarConnector:
"""Class for retrieving data from Google Calendar using Google OAuth credentials."""
def __init__(
self,
credentials: Credentials,
):
"""
Initialize the GoogleCalendarConnector class.
Args:
credentials: Google OAuth Credentials object
"""
self._credentials = credentials
self.service = None
def _get_credentials(self) -> Credentials:
"""
Get valid Google OAuth credentials.
Returns:
Google OAuth credentials
Raises:
ValueError: If credentials have not been set
Exception: If credential refresh fails
"""
if not all(
[
self._credentials.client_id,
self._credentials.client_secret,
self._credentials.refresh_token,
]
):
raise ValueError(
"Google OAuth credentials (client_id, client_secret, refresh_token) must be set"
)
if self._credentials and not self._credentials.expired:
return self._credentials
# Create credentials from refresh token
self._credentials = Credentials(
token=self._credentials.token,
refresh_token=self._credentials.refresh_token,
token_uri=self._credentials.token_uri,
client_id=self._credentials.client_id,
client_secret=self._credentials.client_secret,
scopes=self._credentials.scopes,
)
# Refresh the token if needed
if self._credentials.expired or not self._credentials.valid:
try:
self._credentials.refresh(Request())
except Exception as e:
raise Exception(
f"Failed to refresh Google OAuth credentials: {e!s}"
) from e
return self._credentials
def _get_service(self):
"""
Get the Google Calendar service instance using Google OAuth credentials.
Returns:
Google Calendar service instance
Raises:
ValueError: If credentials have not been set
Exception: If service creation fails
"""
if self.service:
return self.service
try:
credentials = self._get_credentials()
self.service = build("calendar", "v3", credentials=credentials)
return self.service
except Exception as e:
raise Exception(f"Failed to create Google Calendar service: {e!s}") from e
def get_calendars(self) -> tuple[list[dict[str, Any]], str | None]:
"""
Fetch list of user's calendars using Google OAuth credentials.
Returns:
Tuple containing (calendars list, error message or None)
"""
try:
service = self._get_service()
calendars_result = service.calendarList().list().execute()
calendars = calendars_result.get("items", [])
# Format calendar data
formatted_calendars = []
for calendar in calendars:
formatted_calendars.append(
{
"id": calendar.get("id"),
"summary": calendar.get("summary"),
"description": calendar.get("description", ""),
"primary": calendar.get("primary", False),
"accessRole": calendar.get("accessRole"),
"timeZone": calendar.get("timeZone"),
}
)
return formatted_calendars, None
except Exception as e:
return [], f"Error fetching calendars: {e!s}"
def get_all_primary_calendar_events(
self,
max_results: int = 2500,
) -> tuple[list[dict[str, Any]], str | None]:
"""
Fetch events from the primary calendar using Google OAuth credentials.
Args:
max_results: Maximum number of events to fetch (default: 2500)
Returns:
Tuple containing (events list, error message or None)
"""
try:
service = self._get_service()
# Fetch events
events_result = (
service.events()
.list(
calendarId="primary",
maxResults=max_results,
singleEvents=True,
orderBy="startTime",
)
.execute()
)
events = events_result.get("items", [])
if not events:
return [], "No events found in the specified date range."
return events, None
except Exception as e:
return [], f"Error fetching events: {e!s}"
def format_event_to_markdown(self, event: dict[str, Any]) -> str:
"""
Format a Google Calendar event to markdown.
Args:
event: Event object from Google Calendar API
Returns:
Formatted markdown string
"""
# Extract basic event information
summary = event.get("summary", "No Title")
description = event.get("description", "")
location = event.get("location", "")
calendar_id = event.get("calendarId", "")
# Extract start and end times
start = event.get("start", {})
end = event.get("end", {})
start_time = start.get("dateTime") or start.get("date", "")
end_time = end.get("dateTime") or end.get("date", "")
# Format times for display
if start_time:
try:
if "T" in start_time: # DateTime format
start_dt = datetime.fromisoformat(start_time.replace("Z", "+00:00"))
start_formatted = start_dt.strftime("%Y-%m-%d %H:%M")
else: # Date format (all-day event)
start_formatted = start_time
except Exception:
start_formatted = start_time
else:
start_formatted = "Unknown"
if end_time:
try:
if "T" in end_time: # DateTime format
end_dt = datetime.fromisoformat(end_time.replace("Z", "+00:00"))
end_formatted = end_dt.strftime("%Y-%m-%d %H:%M")
else: # Date format (all-day event)
end_formatted = end_time
except Exception:
end_formatted = end_time
else:
end_formatted = "Unknown"
# Extract attendees
attendees = event.get("attendees", [])
attendee_list = []
for attendee in attendees:
email = attendee.get("email", "")
display_name = attendee.get("displayName", email)
response_status = attendee.get("responseStatus", "")
attendee_list.append(f"- {display_name} ({response_status})")
# Build markdown content
markdown_content = f"# {summary}\n\n"
# Add event details
markdown_content += f"**Start:** {start_formatted}\n"
markdown_content += f"**End:** {end_formatted}\n"
if location:
markdown_content += f"**Location:** {location}\n"
if calendar_id:
markdown_content += f"**Calendar:** {calendar_id}\n"
markdown_content += "\n"
# Add description if available
if description:
markdown_content += f"## Description\n\n{description}\n\n"
# Add attendees if available
if attendee_list:
markdown_content += "## Attendees\n\n"
markdown_content += "\n".join(attendee_list)
markdown_content += "\n\n"
# Add event metadata
markdown_content += "## Event Details\n\n"
markdown_content += f"- **Event ID:** {event.get('id', 'Unknown')}\n"
markdown_content += f"- **Created:** {event.get('created', 'Unknown')}\n"
markdown_content += f"- **Updated:** {event.get('updated', 'Unknown')}\n"
if event.get("recurringEventId"):
markdown_content += (
f"- **Recurring Event ID:** {event.get('recurringEventId')}\n"
)
return markdown_content