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) <noreply@anthropic.com>
This commit is contained in:
2026-09-24 18:50:59 +02:00
parent 88b56a78a0
commit 51976fbe22
2 changed files with 38 additions and 15 deletions

View File

@@ -1,4 +1,4 @@
import google.oauth2.credentials from google.auth.exceptions import RefreshError
import logging import logging
from google.auth.transport.requests import Request 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.discovery import build
from googleapiclient.errors import HttpError from googleapiclient.errors import HttpError
from datetime import datetime from datetime import datetime, timezone
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
import os import os
@@ -84,7 +84,7 @@ class GoogleCalendar:
logger.info("Refrescando credenciales...") logger.info("Refrescando credenciales...")
self.creds.refresh(Request()) self.creds.refresh(Request())
logger.info("Credenciales refrescadas correctamente") 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)}") logger.warning(f"Error al refrescar credenciales: {str(e)}")
raise raise
except Exception as e: except Exception as e:
@@ -100,7 +100,7 @@ class GoogleCalendar:
try: try:
logger.info("Intentando refrescar credenciales expiradas...") logger.info("Intentando refrescar credenciales expiradas...")
self.refreshAuth() 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") logger.warning(f"No se pudo refrescar credenciales: {str(rerror)}, solicitando nueva autenticación")
self.serverAuth() self.serverAuth()
except Exception as e: 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) logger.error(f"Error al construir servicio de Google Calendar: {str(e)}", exc_info=True)
raise raise
def getNow(self): def getNow(self, day=None):
return datetime.utcnow().strftime('%Y-%m-%dT00:00:00Z') d = day or datetime.now(timezone.utc)
return d.strftime('%Y-%m-%dT00:00:00Z')
def getNowAfterOneDay(self): def getNowAfterOneDay(self, day=None):
return (datetime.utcnow() + relativedelta(days=1)).strftime('%Y-%m-%dT00:00:00Z') 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: try:
logger.info("Obteniendo eventos de Google Calendar...") logger.info("Obteniendo eventos de Google Calendar...")
now = self.getNow() now = self.getNow(day)
now_after_one_day = self.getNowAfterOneDay() now_after_one_day = self.getNowAfterOneDay(day)
logger.info(f"Buscando eventos entre {now} y {now_after_one_day}") logger.info(f"Buscando eventos entre {now} y {now_after_one_day}")
try: try:
@@ -185,8 +204,7 @@ class GoogleCalendar:
end = event['end'].get('dateTime', event['end'].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}") logger.debug(f"Evaluando evento: {event.get('summary', 'Sin título')} - {start} a {end}")
if datetime.fromisoformat(start).timestamp() <= datetime.now().timestamp() <= datetime.fromisoformat( if self._affects(start, end, day):
end).timestamp():
logger.info(f"Evento activo encontrado: {event.get('summary', 'Sin título')}") logger.info(f"Evento activo encontrado: {event.get('summary', 'Sin título')}")
if event.get('organizer', {}).get('displayName') == 'autoficher': if event.get('organizer', {}).get('displayName') == 'autoficher':
result = 'Forzado desde autoficher - ' + event.get("summary", "Sin Título") result = 'Forzado desde autoficher - ' + event.get("summary", "Sin Título")

View File

@@ -180,7 +180,11 @@ class timenetManager:
self.headers["dStr"] = "\"" + now.strftime("%d/%m/%Y %H:%M:%S") + "\"" self.headers["dStr"] = "\"" + now.strftime("%d/%m/%Y %H:%M:%S") + "\""
def updateVersion(self): 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): def getVersion(self):
self.updateTime() self.updateTime()
@@ -258,7 +262,7 @@ class timenetManager:
# Determinar tipo de fichaje # Determinar tipo de fichaje
typ = 0 typ = 0
try: try:
if args.basedtime and not args.type: if args.basedtime and args.type is None:
hour = datetime.now().strftime("%H") hour = datetime.now().strftime("%H")
logger.info(f"Hora actual: {hour}") logger.info(f"Hora actual: {hour}")
@@ -312,6 +316,7 @@ class timenetManager:
"date": self.headers[strings.tnd_string], "date": self.headers[strings.tnd_string],
"geoLatitude": config.get('geo', {}).get('latitude', ''), "geoLatitude": config.get('geo', {}).get('latitude', ''),
"geoLongitude": config.get('geo', {}).get('longitude', ''), "geoLongitude": config.get('geo', {}).get('longitude', ''),
"geoAccuracy": config.get('geo', {}).get('accuracy', ''),
"geoError": "", "geoError": "",
"dStr": self.headers["dStr"].replace("\"", "") "dStr": self.headers["dStr"].replace("\"", "")
} }