from google.auth.exceptions import RefreshError import logging from google.auth.transport.requests import Request from google.oauth2.credentials import Credentials from google_auth_oauthlib.flow import InstalledAppFlow from googleapiclient.discovery import build from googleapiclient.errors import HttpError from datetime import datetime, timezone from dateutil.relativedelta import relativedelta import os logger = logging.getLogger(__name__) class GoogleCalendar: def __init__(self): try: logger.info("Inicializando GoogleCalendar...") self.creds = None self.service = None self.SCOPES = ['https://www.googleapis.com/auth/calendar.readonly'] self.authfile = os.path.join(os.path.dirname(__file__), '../googleoauth.json') self.grantfile = os.path.join(os.path.dirname(__file__), '../googleoauth_grant.json') logger.info(f"Auth file: {self.authfile}, Grant file: {self.grantfile}") if os.path.exists(self.authfile): try: logger.info("Cargando credenciales desde archivo...") self.creds = Credentials.from_authorized_user_file(self.authfile, self.SCOPES) logger.info("Credenciales cargadas correctamente") except ValueError as error: logger.error(f"Error al cargar credenciales: {error}", exc_info=True) print(f"Error al cargar credenciales: {error}") except Exception as e: logger.error(f"Error inesperado al cargar credenciales: {str(e)}", exc_info=True) print(f"Error inesperado al cargar credenciales: {str(e)}") else: logger.info("No se encontró archivo de credenciales, se solicitará autenticación") logger.info("Verificando autenticación...") self.checkAuth() logger.info("Construyendo servicio...") self.buildService() logger.info("GoogleCalendar inicializado correctamente") except Exception as e: logger.error(f"Error al inicializar GoogleCalendar: {str(e)}", exc_info=True) print(f"Error al inicializar GoogleCalendar: {str(e)}") raise def serverAuth(self): try: logger.info("Iniciando autenticación del servidor...") if not os.path.exists(self.grantfile): error_msg = f"Archivo de grant no encontrado: {self.grantfile}" logger.error(error_msg) raise FileNotFoundError(error_msg) flow = InstalledAppFlow.from_client_secrets_file(self.grantfile, self.SCOPES) self.creds = flow.run_local_server(port=0) logger.info("Autenticación del servidor completada") except FileNotFoundError as e: logger.error(f"Archivo de grant no encontrado: {str(e)}", exc_info=True) raise except Exception as e: logger.error(f"Error durante autenticación del servidor: {str(e)}", exc_info=True) raise def saveAuth(self): try: logger.info("Guardando credenciales...") with open(self.authfile, 'w') as token: token.write(self.creds.to_json()) logger.info("Credenciales guardadas correctamente") except Exception as e: logger.error(f"Error al guardar credenciales: {str(e)}", exc_info=True) raise def refreshAuth(self): try: logger.info("Refrescando credenciales...") self.creds.refresh(Request()) logger.info("Credenciales refrescadas correctamente") except RefreshError as e: logger.warning(f"Error al refrescar credenciales: {str(e)}") raise except Exception as e: logger.error(f"Error inesperado al refrescar credenciales: {str(e)}", exc_info=True) raise def checkAuth(self): try: logger.info("Verificando estado de autenticación...") if not self.creds or not self.creds.valid: logger.info("Credenciales no válidas o no existentes") if self.creds and self.creds.expired and self.creds.refresh_token: try: logger.info("Intentando refrescar credenciales expiradas...") self.refreshAuth() except RefreshError as rerror: logger.warning(f"No se pudo refrescar credenciales: {str(rerror)}, solicitando nueva autenticación") self.serverAuth() except Exception as e: logger.error(f"Error inesperado al refrescar: {str(e)}", exc_info=True) self.serverAuth() else: logger.info("Solicitando nueva autenticación...") self.serverAuth() logger.info("Guardando credenciales después de autenticación...") self.saveAuth() else: logger.info("Credenciales válidas") except Exception as e: logger.error(f"Error durante checkAuth: {str(e)}", exc_info=True) raise def buildService(self): try: logger.info("Construyendo servicio de Google Calendar...") self.service = build('calendar', 'v3', credentials=self.creds) logger.info("Servicio de Google Calendar construido correctamente") except Exception as e: logger.error(f"Error al construir servicio de Google Calendar: {str(e)}", exc_info=True) raise def getNow(self, day=None): d = day or datetime.now(timezone.utc) return d.strftime('%Y-%m-%dT00:00:00Z') def getNowAfterOneDay(self, day=None): d = day or datetime.now(timezone.utc) return (d + relativedelta(days=1)).strftime('%Y-%m-%dT00:00:00Z') @staticmethod def _affects(start, end, day): """Con `day`: el evento solapa ese dia. Sin `day`: esta activo ahora mismo.""" s0 = datetime.fromisoformat(start) e0 = datetime.fromisoformat(end) if day is None: return s0.timestamp() <= datetime.now().timestamp() <= e0.timestamp() d0 = datetime(day.year, day.month, day.day, tzinfo=s0.tzinfo) d1 = d0 + relativedelta(days=1) return s0 < d1 and e0 > d0 def getEvent(self, day=None): """Ausencia personal que afecta a `day` (por defecto, ahora mismo). Con `day` se pregunta si el evento CUBRE ese dia, no si esta activo en este instante: el daemon planifica de madrugada y una ausencia de 09:00 a 18:00 tiene que detectarse igual. """ try: logger.info("Obteniendo eventos de Google Calendar...") now = self.getNow(day) now_after_one_day = self.getNowAfterOneDay(day) logger.info(f"Buscando eventos entre {now} y {now_after_one_day}") try: logger.info("Consultando calendario autoficher...") events_result_autoficher = self.service.events().list( calendarId='e51d3bfb6b352c48afb8bd7a5d494599cc3eaa686800a856796306f50c6d72fd@group.calendar.google.com', timeMin=now, timeMax=now_after_one_day, maxResults=10, singleEvents=True, orderBy='startTime').execute() logger.info(f"Eventos de autoficher obtenidos: {len(events_result_autoficher.get('items', []))}") except HttpError as e: logger.error(f"Error HTTP al consultar calendario autoficher: {str(e)}", exc_info=True) events_result_autoficher = {'items': []} except Exception as e: logger.error(f"Error inesperado al consultar calendario autoficher: {str(e)}", exc_info=True) events_result_autoficher = {'items': []} try: logger.info("Consultando calendario de festivos...") events_result_holiday = self.service.events().list( calendarId='es.spain#holiday@group.v.calendar.google.com', timeMin=now, timeMax=now_after_one_day, maxResults=10, singleEvents=True, orderBy='startTime').execute() logger.info(f"Eventos de festivos obtenidos: {len(events_result_holiday.get('items', []))}") except HttpError as e: logger.warning(f"Error HTTP al consultar calendario de festivos (puede ser normal): {str(e)}") events_result_holiday = {'items': []} except Exception as e: logger.warning(f"Error inesperado al consultar calendario de festivos: {str(e)}") events_result_holiday = {'items': []} events = events_result_autoficher.get('items', []) + events_result_holiday.get('items', []) logger.info(f"Total de eventos encontrados: {len(events)}") if not events: logger.info("No se encontraron eventos") return False # Prints the start and name of the next 10 events for event in events: try: start = event['start'].get('dateTime', event['start'].get('date')) end = event['end'].get('dateTime', event['end'].get('date')) logger.debug(f"Evaluando evento: {event.get('summary', 'Sin título')} - {start} a {end}") if self._affects(start, end, day): logger.info(f"Evento activo encontrado: {event.get('summary', 'Sin título')}") if event.get('organizer', {}).get('displayName') == 'autoficher': result = 'Forzado desde autoficher - ' + event.get("summary", "Sin Título") logger.info(f"Evento de autoficher activo: {result}") return result #Desactivado por que no funcionaba muy bien ese calendario... se han puesto en el calendario autoficher los dias festivos correctos #if 'description' in event and ("Cataluña" in event['description'] or 'Día festivo' in event[ # 'description'] or 'Celebración\n' in event['description']) and not 'Cambio de horario' in event[ # 'summary']: # return event['summary'] except KeyError as e: logger.warning(f"Evento sin campo requerido: {str(e)}, evento: {event.get('summary', 'Sin título')}") continue except ValueError as e: logger.warning(f"Error al parsear fecha del evento: {str(e)}, evento: {event.get('summary', 'Sin título')}") continue except Exception as e: logger.warning(f"Error al procesar evento: {str(e)}, evento: {event.get('summary', 'Sin título')}") continue logger.info("No se encontraron eventos activos") return False except HttpError as error: error_msg = f'Error HTTP de Google Calendar: {error}' logger.error(error_msg, exc_info=True) print(f'An error occurred: {error}') return 'Error de llamada a Google Calendar' except Exception as e: error_msg = f'Error inesperado en getEvent: {str(e)}' logger.error(error_msg, exc_info=True) print(f'Error inesperado: {error_msg}') return 'Error de llamada a Google Calendar'