From 44d233866395217a406a95f1cf919e91dec3f303 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Sat, 2 Aug 2025 02:22:53 +0200 Subject: [PATCH] get all primary calendar event by default --- .../connectors/google_calendar_connector.py | 261 ++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 surfsense_backend/app/connectors/google_calendar_connector.py diff --git a/surfsense_backend/app/connectors/google_calendar_connector.py b/surfsense_backend/app/connectors/google_calendar_connector.py new file mode 100644 index 0000000..d481491 --- /dev/null +++ b/surfsense_backend/app/connectors/google_calendar_connector.py @@ -0,0 +1,261 @@ +""" +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