From 51976fbe220fdb38e0109b8b9442dd57f2991a0e Mon Sep 17 00:00:00 2001 From: omar Date: Thu, 24 Sep 2026 18:50:59 +0200 Subject: [PATCH] fix: corregir bugs que costaban minutos y tapaban errores - google_calendar: RefreshError se referenciaba como google.oauth2.credentials.exceptions.RefreshError, ruta que no existe. Al fallar un refresh de token saltaba un AttributeError que ocultaba el error real y rompia el re-auth. Ahora se importa de google.auth.exceptions. - google_calendar: getEvent() comprobaba si habia un evento activo en ese instante. El daemon planifica de madrugada, asi que una ausencia de 09:00 a 18:00 no se detectaba y se fichaba igual. Ahora acepta un dia y comprueba solapamiento. Sin argumento mantiene el comportamiento anterior. - google_calendar: datetime.utcnow() esta deprecado en Python 3.12+. - timenet_manager: "args.basedtime and not args.type" ignoraba -t 0 (Entrada) al combinarlo con -bt, porque not 0 es True. Ahora es "args.type is None". - timenet_manager: updateVersion() asignaba el objeto Response al header Tn-V en vez de su texto. - timenet_manager: geo.accuracy se parseaba y se podia sobrescribir por CLI pero nunca se enviaba en el payload de cp/checks. Co-Authored-By: Claude Opus 5 (1M context) --- app/google_calendar.py | 44 +++++++++++++++++++++++++++++------------- app/timenet_manager.py | 9 +++++++-- 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/app/google_calendar.py b/app/google_calendar.py index 9bb8384..7997ce2 100644 --- a/app/google_calendar.py +++ b/app/google_calendar.py @@ -1,4 +1,4 @@ -import google.oauth2.credentials +from google.auth.exceptions import RefreshError import logging from google.auth.transport.requests import Request @@ -7,7 +7,7 @@ from google_auth_oauthlib.flow import InstalledAppFlow from googleapiclient.discovery import build from googleapiclient.errors import HttpError -from datetime import datetime +from datetime import datetime, timezone from dateutil.relativedelta import relativedelta import os @@ -84,7 +84,7 @@ class GoogleCalendar: logger.info("Refrescando credenciales...") self.creds.refresh(Request()) logger.info("Credenciales refrescadas correctamente") - except google.oauth2.credentials.exceptions.RefreshError as e: + except RefreshError as e: logger.warning(f"Error al refrescar credenciales: {str(e)}") raise except Exception as e: @@ -100,7 +100,7 @@ class GoogleCalendar: try: logger.info("Intentando refrescar credenciales expiradas...") self.refreshAuth() - except google.oauth2.credentials.exceptions.RefreshError as rerror: + except RefreshError as rerror: logger.warning(f"No se pudo refrescar credenciales: {str(rerror)}, solicitando nueva autenticación") self.serverAuth() except Exception as e: @@ -127,17 +127,36 @@ class GoogleCalendar: logger.error(f"Error al construir servicio de Google Calendar: {str(e)}", exc_info=True) raise - def getNow(self): - return datetime.utcnow().strftime('%Y-%m-%dT00:00:00Z') + def getNow(self, day=None): + d = day or datetime.now(timezone.utc) + return d.strftime('%Y-%m-%dT00:00:00Z') - def getNowAfterOneDay(self): - return (datetime.utcnow() + relativedelta(days=1)).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') - def getEvent(self): + @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() - now_after_one_day = self.getNowAfterOneDay() + now = self.getNow(day) + now_after_one_day = self.getNowAfterOneDay(day) logger.info(f"Buscando eventos entre {now} y {now_after_one_day}") try: @@ -185,8 +204,7 @@ class GoogleCalendar: end = event['end'].get('dateTime', event['end'].get('date')) logger.debug(f"Evaluando evento: {event.get('summary', 'Sin título')} - {start} a {end}") - if datetime.fromisoformat(start).timestamp() <= datetime.now().timestamp() <= datetime.fromisoformat( - end).timestamp(): + 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") diff --git a/app/timenet_manager.py b/app/timenet_manager.py index 2b201f6..a404aaf 100644 --- a/app/timenet_manager.py +++ b/app/timenet_manager.py @@ -180,7 +180,11 @@ class timenetManager: self.headers["dStr"] = "\"" + now.strftime("%d/%m/%Y %H:%M:%S") + "\"" def updateVersion(self): - self.headers[strings.tnv_string] = self.getVersion() + response = self.getVersion() + if response.status_code != 200: + logger.warning(f"No se pudo obtener la version: status_code={response.status_code}") + return + self.headers[strings.tnv_string] = response.text.replace("\"", "") def getVersion(self): self.updateTime() @@ -258,7 +262,7 @@ class timenetManager: # Determinar tipo de fichaje typ = 0 try: - if args.basedtime and not args.type: + if args.basedtime and args.type is None: hour = datetime.now().strftime("%H") logger.info(f"Hora actual: {hour}") @@ -312,6 +316,7 @@ class timenetManager: "date": self.headers[strings.tnd_string], "geoLatitude": config.get('geo', {}).get('latitude', ''), "geoLongitude": config.get('geo', {}).get('longitude', ''), + "geoAccuracy": config.get('geo', {}).get('accuracy', ''), "geoError": "", "dStr": self.headers["dStr"].replace("\"", "") }