Agregar logging estructurado, try-catches y envío de errores por Telegram

- Agregado logging estructurado en todos los módulos
- Implementados try-catches en todos los métodos críticos
- Errores ahora se envían automáticamente por Telegram
- Mejorado manejo de excepciones en requests HTTP
- Agregado try-catch global en main.py para errores no manejados
- Logging detallado en timenet_manager, telegram_bot, google_calendar y config_parser
This commit is contained in:
Omar Sánchez Pizarro
2026-01-13 01:39:16 +01:00
parent c052439de3
commit ce179ebff9
5 changed files with 712 additions and 219 deletions

View File

@@ -1,8 +1,11 @@
import json, os import json, os
import logging
from app import arg_parser from app import arg_parser
args = arg_parser.ArgParser().parse() args = arg_parser.ArgParser().parse()
logger = logging.getLogger(__name__)
class ConfigParser: class ConfigParser:
config = None config = None
@@ -11,22 +14,71 @@ class ConfigParser:
self.loadConfig() self.loadConfig()
def loadConfig(self): def loadConfig(self):
try:
ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
path = os.path.join(ROOT_DIR, '../config.json') path = os.path.join(ROOT_DIR, '../config.json')
logger.info(f"Ruta base del config: {path}")
if args.config: if args.config:
path = args.config path = args.config
logger.info(f"Usando ruta de config personalizada: {path}")
with open(path, 'r') as f: if not os.path.exists(path):
error_msg = f"Archivo de configuración no encontrado: {path}"
logger.error(error_msg)
raise FileNotFoundError(error_msg)
logger.info(f"Cargando configuración desde: {path}")
with open(path, 'r', encoding='utf-8') as f:
try:
self.config = json.load(f) self.config = json.load(f)
logger.info("Configuración cargada correctamente")
except json.JSONDecodeError as e:
error_msg = f"Error al parsear JSON del archivo de configuración: {str(e)}"
logger.error(error_msg, exc_info=True)
raise ValueError(error_msg)
# Validar estructura básica
if not isinstance(self.config, dict):
error_msg = "El archivo de configuración no contiene un objeto JSON válido"
logger.error(error_msg)
raise ValueError(error_msg)
# Aplicar overrides de argumentos
try:
if args.geoLatitude: if args.geoLatitude:
if 'geo' not in self.config:
self.config['geo'] = {}
self.config['geo']['latitude'] = args.geoLatitude self.config['geo']['latitude'] = args.geoLatitude
logger.info(f"Latitud sobrescrita por argumento: {args.geoLatitude}")
if args.geoLongitude: if args.geoLongitude:
if 'geo' not in self.config:
self.config['geo'] = {}
self.config['geo']['longitude'] = args.geoLongitude self.config['geo']['longitude'] = args.geoLongitude
logger.info(f"Longitud sobrescrita por argumento: {args.geoLongitude}")
if args.user: if args.user:
self.config['user'] = args.user self.config['user'] = args.user
if args.geoAccuracy: logger.info(f"Usuario sobrescrito por argumento: {args.user}")
self.config['geo']['accuracy'] = args.geoAccuracy
if args.geoAccuracy:
if 'geo' not in self.config:
self.config['geo'] = {}
self.config['geo']['accuracy'] = args.geoAccuracy
logger.info(f"Precisión geográfica sobrescrita por argumento: {args.geoAccuracy}")
except Exception as e:
logger.warning(f"Error al aplicar overrides de argumentos: {str(e)}")
logger.info("Configuración procesada correctamente")
return self.config return self.config
except FileNotFoundError as e:
logger.error(f"Archivo de configuración no encontrado: {str(e)}", exc_info=True)
raise
except json.JSONDecodeError as e:
logger.error(f"Error de sintaxis JSON en configuración: {str(e)}", exc_info=True)
raise
except Exception as e:
error_msg = f"Error inesperado al cargar configuración: {str(e)}"
logger.error(error_msg, exc_info=True)
raise

View File

@@ -1,4 +1,5 @@
import google.oauth2.credentials import google.oauth2.credentials
import logging
from google.auth.transport.requests import Request from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials from google.oauth2.credentials import Credentials
@@ -11,51 +12,120 @@ from dateutil.relativedelta import relativedelta
import os import os
logger = logging.getLogger(__name__)
class GoogleCalendar: class GoogleCalendar:
def __init__(self): def __init__(self):
try:
logger.info("Inicializando GoogleCalendar...")
self.creds = None self.creds = None
self.service = None self.service = None
self.SCOPES = ['https://www.googleapis.com/auth/calendar.readonly'] self.SCOPES = ['https://www.googleapis.com/auth/calendar.readonly']
self.authfile = os.path.join(os.path.dirname(__file__), '../googleoauth.json') self.authfile = os.path.join(os.path.dirname(__file__), '../googleoauth.json')
self.grantfile = os.path.join(os.path.dirname(__file__), '../googleoauth_grant.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): if os.path.exists(self.authfile):
try: try:
logger.info("Cargando credenciales desde archivo...")
self.creds = Credentials.from_authorized_user_file(self.authfile, self.SCOPES) self.creds = Credentials.from_authorized_user_file(self.authfile, self.SCOPES)
logger.info("Credenciales cargadas correctamente")
except ValueError as error: except ValueError as error:
print(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() self.checkAuth()
logger.info("Construyendo servicio...")
self.buildService() 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): 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) flow = InstalledAppFlow.from_client_secrets_file(self.grantfile, self.SCOPES)
self.creds = flow.run_local_server(port=0) 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): def saveAuth(self):
try:
logger.info("Guardando credenciales...")
with open(self.authfile, 'w') as token: with open(self.authfile, 'w') as token:
token.write(self.creds.to_json()) 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): def refreshAuth(self):
try:
logger.info("Refrescando credenciales...")
self.creds.refresh(Request()) self.creds.refresh(Request())
logger.info("Credenciales refrescadas correctamente")
except google.oauth2.credentials.exceptions.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): def checkAuth(self):
try:
logger.info("Verificando estado de autenticación...")
if not self.creds or not self.creds.valid: 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: if self.creds and self.creds.expired and self.creds.refresh_token:
try: try:
logger.info("Intentando refrescar credenciales expiradas...")
self.refreshAuth() self.refreshAuth()
except google.oauth2.credentials.exceptions.RefreshError as rerror: except google.oauth2.credentials.exceptions.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() self.serverAuth()
else: else:
logger.info("Solicitando nueva autenticación...")
self.serverAuth() self.serverAuth()
logger.info("Guardando credenciales después de autenticación...")
self.saveAuth() 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): def buildService(self):
try:
logger.info("Construyendo servicio de Google Calendar...")
self.service = build('calendar', 'v3', credentials=self.creds) self.service = build('calendar', 'v3', credentials=self.creds)
# self.service.calendars().get(calendarId='e51d3bfb6b352c48afb8bd7a5d494599cc3eaa686800a856796306f50c6d72fd@group.calendar.google.com') logger.info("Servicio de Google Calendar construido correctamente")
# self.service.create_notification_channel() except Exception as e:
logger.error(f"Error al construir servicio de Google Calendar: {str(e)}", exc_info=True)
raise
def getNow(self): def getNow(self):
return datetime.utcnow().strftime('%Y-%m-%dT00:00:00Z') return datetime.utcnow().strftime('%Y-%m-%dT00:00:00Z')
@@ -65,43 +135,88 @@ class GoogleCalendar:
def getEvent(self): def getEvent(self):
try: try:
logger.info("Obteniendo eventos de Google Calendar...")
now = self.getNow() now = self.getNow()
now_after_one_day = self.getNowAfterOneDay() now_after_one_day = self.getNowAfterOneDay()
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( events_result_autoficher = self.service.events().list(
calendarId='e51d3bfb6b352c48afb8bd7a5d494599cc3eaa686800a856796306f50c6d72fd@group.calendar.google.com', calendarId='e51d3bfb6b352c48afb8bd7a5d494599cc3eaa686800a856796306f50c6d72fd@group.calendar.google.com',
timeMin=now, timeMin=now,
timeMax=now_after_one_day, maxResults=10, singleEvents=True, timeMax=now_after_one_day, maxResults=10, singleEvents=True,
orderBy='startTime').execute() 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( events_result_holiday = self.service.events().list(
calendarId='es.spain#holiday@group.v.calendar.google.com', calendarId='es.spain#holiday@group.v.calendar.google.com',
timeMin=now, timeMin=now,
timeMax=now_after_one_day, timeMax=now_after_one_day,
maxResults=10, singleEvents=True, maxResults=10, singleEvents=True,
orderBy='startTime').execute() 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', []) events = events_result_autoficher.get('items', []) + events_result_holiday.get('items', [])
logger.info(f"Total de eventos encontrados: {len(events)}")
if not events: if not events:
logger.info("No se encontraron eventos")
return False return False
# Prints the start and name of the next 10 events # Prints the start and name of the next 10 events
for event in events: for event in events:
try:
start = event['start'].get('dateTime', event['start'].get('date')) start = event['start'].get('dateTime', event['start'].get('date'))
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}")
if datetime.fromisoformat(start).timestamp() <= datetime.now().timestamp() <= datetime.fromisoformat( if datetime.fromisoformat(start).timestamp() <= datetime.now().timestamp() <= datetime.fromisoformat(
end).timestamp(): end).timestamp():
if event['organizer']['displayName'] == 'autoficher': logger.info(f"Evento activo encontrado: {event.get('summary', 'Sin título')}")
return 'Forzado desde autoficher - ' + 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 #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[ #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[ # 'description'] or 'Celebración\n' in event['description']) and not 'Cambio de horario' in event[
# 'summary']: # 'summary']:
# return event['summary'] # return event['summary']
except HttpError as error: except KeyError as e:
print(f'An error occurred: {error}') logger.warning(f"Evento sin campo requerido: {str(e)}, evento: {event.get('summary', 'Sin título')}")
return 'Error de llama a Google Calendar' 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 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'

View File

@@ -2,32 +2,65 @@ from app import config_parser
import telegram import telegram
from importlib.metadata import version from importlib.metadata import version
import asyncio import asyncio
import logging
config = config_parser.ConfigParser().loadConfig() config = config_parser.ConfigParser().loadConfig()
logger = logging.getLogger(__name__)
class telegramBot: class telegramBot:
bot = None bot = None
async def sendMessage(self, message): async def sendMessage(self, message):
try:
logger.info(f"Preparando envío de mensaje por Telegram (longitud: {len(str(message))} caracteres)")
ver = version("python-telegram-bot") ver = version("python-telegram-bot")
major = int(ver.split(".")[0]) major = int(ver.split(".")[0])
logger.info(f"Versión de python-telegram-bot: {ver} (major: {major})")
token = config.get("telegram", {}).get("token")
chat_id = config.get("telegram", {}).get("chat_id")
token = config["telegram"]["token"] if not token:
chat_id = config["telegram"]["chat_id"] error_msg = "Token de Telegram no configurado"
logger.error(error_msg)
print(f"Error: {error_msg}")
return
if not chat_id:
error_msg = "Chat ID de Telegram no configurado"
logger.error(error_msg)
print(f"Error: {error_msg}")
return
# -------------------- # --------------------
# PTB v20+ (async) # PTB v20+ (async)
# -------------------- # --------------------
if major >= 20: if major >= 20:
logger.info("Usando API async de python-telegram-bot v20+")
try:
self.bot = telegram.Bot(token) self.bot = telegram.Bot(token)
await self.bot.send_message(chat_id=chat_id, text=str(message)) await self.bot.send_message(chat_id=chat_id, text=str(message))
logger.info("Mensaje enviado correctamente por Telegram (v20+)")
return return
except telegram.error.TelegramError as e:
error_msg = f"Error de Telegram API al enviar mensaje (v20+): {str(e)}"
logger.error(error_msg, exc_info=True)
print(f"Error: {error_msg}")
raise
except Exception as e:
error_msg = f"Error inesperado al enviar mensaje por Telegram (v20+): {str(e)}"
logger.error(error_msg, exc_info=True)
print(f"Error: {error_msg}")
raise
# -------------------- # --------------------
# PTB v12/v13 (sync) # PTB v12/v13 (sync)
# -------------------- # --------------------
else: else:
logger.info("Usando API sync de python-telegram-bot v12/v13")
try:
self.bot = telegram.Bot(token) self.bot = telegram.Bot(token)
# ejecuta el método sync en un hilo para no romper asyncio.run() # ejecuta el método sync en un hilo para no romper asyncio.run()
@@ -36,3 +69,19 @@ class telegramBot:
None, None,
lambda: self.bot.send_message(chat_id=chat_id, text=str(message)) lambda: self.bot.send_message(chat_id=chat_id, text=str(message))
) )
logger.info("Mensaje enviado correctamente por Telegram (v12/v13)")
except telegram.error.TelegramError as e:
error_msg = f"Error de Telegram API al enviar mensaje (v12/v13): {str(e)}"
logger.error(error_msg, exc_info=True)
print(f"Error: {error_msg}")
raise
except Exception as e:
error_msg = f"Error inesperado al enviar mensaje por Telegram (v12/v13): {str(e)}"
logger.error(error_msg, exc_info=True)
print(f"Error: {error_msg}")
raise
except Exception as e:
error_msg = f"Error crítico al enviar mensaje por Telegram: {str(e)}"
logger.error(error_msg, exc_info=True)
print(f"Error crítico: {error_msg}")
# No re-lanzamos la excepción para evitar que rompa el flujo principal

View File

@@ -1,4 +1,5 @@
import asyncio import asyncio
import logging
import requests, json, pytz import requests, json, pytz
from datetime import datetime from datetime import datetime
@@ -11,17 +12,30 @@ config = config_parser.ConfigParser().loadConfig()
from app import strings from app import strings
# Configurar logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger(__name__)
class timenetManager: class timenetManager:
telegram = None telegram = None
message_acumulator = "" message_acumulator = ""
headers = {} headers = {}
def __init__(self): def __init__(self):
try:
logger.info("Inicializando timenetManager...")
self.telegram = telegram_bot.telegramBot() self.telegram = telegram_bot.telegramBot()
logger.info("Telegram bot inicializado correctamente")
if args.pin is None: if args.pin is None:
# if pin is None, prompt for it # if pin is None, prompt for it
logger.info("PIN no proporcionado, solicitando al usuario...")
args.pin = int(input("Introduzca su pin: ")) args.pin = int(input("Introduzca su pin: "))
logger.info("PIN recibido")
self.headers = { self.headers = {
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8", "Content-type": "application/x-www-form-urlencoded; charset=UTF-8",
@@ -37,44 +51,127 @@ class timenetManager:
"User-Agent": strings.user_agent, "User-Agent": strings.user_agent,
"Tn-U": strings.tn_u "Tn-U": strings.tn_u
} }
logger.info("Headers configurados, iniciando login...")
self.login() self.login()
logger.info("timenetManager inicializado correctamente")
except Exception as e:
error_msg = f"Error al inicializar timenetManager: {str(e)}"
logger.error(error_msg, exc_info=True)
try:
asyncio.run(self.telegram.sendMessage(f"🟥🕐 {error_msg}"))
except:
pass
raise
def get(self, url, data={}, headers={}): def get(self, url, data={}, headers={}):
try:
furl = config['api_url'] + url furl = config['api_url'] + url
if url != "version": if url != "version":
furl = config['api_url'] + "v1/" + url furl = config['api_url'] + "v1/" + url
logger.info(f"[GET] {furl} con headers {headers} y data {data}")
print("[GET] %s con headers %s y data %s" % (furl, headers, data)) print("[GET] %s con headers %s y data %s" % (furl, headers, data))
return requests.get(furl, data=data, headers=headers)
response = requests.get(furl, data=data, headers=headers, timeout=30)
logger.info(f"[GET] Respuesta recibida: status_code={response.status_code}")
return response
except requests.exceptions.Timeout as e:
error_msg = f"Timeout al hacer GET a {url}: {str(e)}"
logger.error(error_msg, exc_info=True)
self.addMessage(f"🟥🕐 {error_msg}")
self.sendReport()
raise
except requests.exceptions.RequestException as e:
error_msg = f"Error de conexión al hacer GET a {url}: {str(e)}"
logger.error(error_msg, exc_info=True)
self.addMessage(f"🟥🕐 {error_msg}")
self.sendReport()
raise
except Exception as e:
error_msg = f"Error inesperado al hacer GET a {url}: {str(e)}"
logger.error(error_msg, exc_info=True)
self.addMessage(f"🟥🕐 {error_msg}")
self.sendReport()
raise
def post(self, url, data={}, headers={}): def post(self, url, data={}, headers={}):
try:
furl = config['api_url'] + url furl = config['api_url'] + url
if url != "version": if url != "version":
furl = config['api_url'] + "v1/" + url furl = config['api_url'] + "v1/" + url
logger.info(f"[POST] {furl} con headers {headers} y data {data}")
print("[POST] %s con headers %s y data %s" % (furl, headers, data)) print("[POST] %s con headers %s y data %s" % (furl, headers, data))
return requests.post(furl, data=data, headers=headers)
response = requests.post(furl, data=data, headers=headers, timeout=30)
logger.info(f"[POST] Respuesta recibida: status_code={response.status_code}, text={response.text[:200]}")
return response
except requests.exceptions.Timeout as e:
error_msg = f"Timeout al hacer POST a {url}: {str(e)}"
logger.error(error_msg, exc_info=True)
self.addMessage(f"🟥🕐 {error_msg}")
self.sendReport()
raise
except requests.exceptions.RequestException as e:
error_msg = f"Error de conexión al hacer POST a {url}: {str(e)}"
logger.error(error_msg, exc_info=True)
self.addMessage(f"🟥🕐 {error_msg}")
self.sendReport()
raise
except Exception as e:
error_msg = f"Error inesperado al hacer POST a {url}: {str(e)}"
logger.error(error_msg, exc_info=True)
self.addMessage(f"🟥🕐 {error_msg}")
self.sendReport()
raise
def login(self): def login(self):
try:
logger.info(f"Iniciando login para usuario: {config.get('user', 'N/A')}")
self.updateTime() self.updateTime()
response = self.post("cp/login/" + config['user'], { response = self.post("cp/login/" + config['user'], {
"p": args.pin, "p": args.pin,
}, self.headers) }, self.headers)
logger.info(f"Login response status: {response.status_code}")
if response.status_code != 200: if response.status_code != 200:
error_msg = f"Error al iniciar sesión: status_code={response.status_code}, response={response.text}"
logger.error(error_msg)
print("Error al iniciar sesión: %s" % response.text) print("Error al iniciar sesión: %s" % response.text)
self.addMessage(f"🟥🕐 Error al iniciar sesión: {response.text}")
self.sendReport()
exit(20) exit(20)
self.headers["Token"] = response.text.replace("\"", "") token = response.text.replace("\"", "")
self.headers["Token"] = token
logger.info("Login exitoso, token obtenido")
except Exception as e:
error_msg = f"Error durante el login: {str(e)}"
logger.error(error_msg, exc_info=True)
self.addMessage(f"🟥🕐 {error_msg}")
self.sendReport()
raise
def addMessage(self, message): def addMessage(self, message):
self.message_acumulator += message + "\n" self.message_acumulator += message + "\n"
def sendReport(self): def sendReport(self):
# self.telegram.sendMessage(self.message_acumulator) try:
if self.message_acumulator.strip():
logger.info(f"Enviando reporte por Telegram: {self.message_acumulator[:100]}...")
asyncio.run(self.telegram.sendMessage(self.message_acumulator)) asyncio.run(self.telegram.sendMessage(self.message_acumulator))
logger.info("Reporte enviado correctamente")
else:
logger.debug("No hay mensajes para enviar")
self.message_acumulator = ""
except Exception as e:
error_msg = f"Error al enviar reporte por Telegram: {str(e)}"
logger.error(error_msg, exc_info=True)
print(f"Error al enviar mensaje por Telegram: {error_msg}")
# No acumulamos este error para evitar loops infinitos
self.message_acumulator = "" self.message_acumulator = ""
def updateTime(self): def updateTime(self):
@@ -93,96 +190,208 @@ class timenetManager:
return self.get('check/info?guid=' + config['user'], {}, self.headers) return self.get('check/info?guid=' + config['user'], {}, self.headers)
def checkLastCheck(self, typ): def checkLastCheck(self, typ):
response = self.get('cp/checks?start=' + datetime.now().strftime("%d/%m/%Y") + '&end=' + datetime.now().strftime("%d/%m/%Y"), {}, self.headers) try:
logger.info(f"Verificando último check de tipo {typ}")
date_str = datetime.now().strftime("%d/%m/%Y")
url = f'cp/checks?start={date_str}&end={date_str}'
logger.info(f"Consultando checks para fecha: {date_str}")
response = self.get(url, {}, self.headers)
if response.status_code != 200: if response.status_code != 200:
self.addMessage("Error al obtener los checks: %s" % response.text) error_msg = f"Error al obtener los checks: status_code={response.status_code}, response={response.text}"
logger.error(error_msg)
self.addMessage(f"🟥🕐 Error al obtener los checks: {response.text}")
self.sendReport() self.sendReport()
return False return False
try:
today_checks = json.loads(response.text) today_checks = json.loads(response.text)
if today_checks['C'] and today_checks['C'][0] and today_checks['C'][0]['C'] and today_checks['C'][0]['C'][-1] and today_checks['C'][0]['C'][-1]['T'] == typ: logger.info(f"Checks obtenidos: {today_checks}")
self.addMessage("Ya se ha realizado este marcaje antes a las %s" % today_checks['C'][0]['C'][-1]['H']) except json.JSONDecodeError as e:
error_msg = f"Error al parsear JSON de checks: {str(e)}, response={response.text}"
logger.error(error_msg, exc_info=True)
self.addMessage(f"🟥🕐 Error al procesar los checks: {str(e)}")
self.sendReport()
return False
if today_checks.get('C') and today_checks['C'][0] and today_checks['C'][0].get('C') and today_checks['C'][0]['C'] and today_checks['C'][0]['C'][-1] and today_checks['C'][0]['C'][-1].get('T') == typ:
hora = today_checks['C'][0]['C'][-1].get('H', 'N/A')
logger.info(f"Ya se ha realizado este marcaje antes a las {hora}")
self.addMessage(f"🟥🕐 Ya se ha realizado este marcaje antes a las {hora}")
self.sendReport() self.sendReport()
return True return True
logger.info("No se encontró un check previo del mismo tipo")
return False
except Exception as e:
error_msg = f"Error inesperado al verificar último check: {str(e)}"
logger.error(error_msg, exc_info=True)
self.addMessage(f"🟥🕐 {error_msg}")
self.sendReport()
return False return False
def sendUpdate(self): def sendUpdate(self):
try:
logger.info("Iniciando sendUpdate...")
# Verificar calendario
try:
logger.info("Consultando Google Calendar...")
calendar = google_calendar.GoogleCalendar() calendar = google_calendar.GoogleCalendar()
calendar = calendar.getEvent() calendar_event = calendar.getEvent()
if calendar and not args.force: logger.info(f"Resultado de calendario: {calendar_event}")
self.addMessage("🟥🕐 Comprobación de calendario: " + calendar)
if calendar_event and not args.force:
logger.info(f"Evento de calendario encontrado: {calendar_event}, cancelando fichaje")
self.addMessage("🟥🕐 Comprobación de calendario: " + calendar_event)
self.sendReport() self.sendReport()
return return
except Exception as e:
error_msg = f"Error al consultar Google Calendar: {str(e)}"
logger.error(error_msg, exc_info=True)
self.addMessage(f"🟥🕐 {error_msg}")
self.sendReport()
# Continuamos con el fichaje aunque falle el calendario
# Determinar tipo de fichaje
typ = 0 typ = 0
try:
if args.basedtime and not args.type: if args.basedtime and not args.type:
hour = datetime.now().strftime("%H") hour = datetime.now().strftime("%H")
logger.info(f"Hora actual: {hour}")
hIN = config['in_hours'] hIN = config.get('in_hours', [])
hOUT = config.get('out_hours', [])
hOUT = config['out_hours'] logger.info(f"Horas de entrada: {hIN}, Horas de salida: {hOUT}")
if hour in hIN: if hour in hIN:
typ = 0 typ = 0
logger.info("Tipo determinado: Entrada (0) por horario")
elif hour in hOUT: elif hour in hOUT:
typ = 1 typ = 1
logger.info("Tipo determinado: Salida (1) por horario")
else: else:
self.addMessage("No se ha fichado ni desfichado ya que estamos en horario laboral, fuerze el estado con el parametro --type <0 = Entrada, 1 = Salida>") logger.warning(f"No se puede determinar el tipo de fichaje para la hora {hour}")
self.addMessage("🟥🕐 No se ha fichado ni desfichado ya que estamos en horario laboral, fuerze el estado con el parametro --type <0 = Entrada, 1 = Salida>")
self.sendReport()
return
else: else:
typ = args.type typ = args.type
logger.info(f"Tipo determinado por argumento: {typ}")
except Exception as e:
error_msg = f"Error al determinar tipo de fichaje: {str(e)}"
logger.error(error_msg, exc_info=True)
self.addMessage(f"🟥🕐 {error_msg}")
self.sendReport()
return
# Verificar último check
logger.info("Verificando último check...")
if self.checkLastCheck(typ): if self.checkLastCheck(typ):
return; logger.info("Check ya realizado previamente, cancelando")
return
# Realizar check
response = None response = None
if not args.debug: if not args.debug:
try:
logger.info("Iniciando proceso de fichaje...")
self.addMessage("####HACIENDO CHECK####") self.addMessage("####HACIENDO CHECK####")
rand = utils.numero_aleatorio_con_probabilidad(230, 0.5) rand = utils.numero_aleatorio_con_probabilidad(230, 0.5)
logger.info(f"Esperando {rand} segundos antes de fichar")
self.addMessage("Esperando %s segundos para %s en un minuto aleatorio" % (rand,strings.conditional_response[int(typ)]["text2"])) self.addMessage("Esperando %s segundos para %s en un minuto aleatorio" % (rand,strings.conditional_response[int(typ)]["text2"]))
self.sendReport() self.sendReport()
sleep(rand) sleep(rand)
self.updateTime() self.updateTime()
data = { data = {
"typ": typ, "typ": typ,
"date": self.headers[strings.tnd_string], "date": self.headers[strings.tnd_string],
"geoLatitude": config['geo']['latitude'], "geoLatitude": config.get('geo', {}).get('latitude', ''),
"geoLongitude": config['geo']['longitude'], "geoLongitude": config.get('geo', {}).get('longitude', ''),
"geoError": "", "geoError": "",
"dStr": self.headers["dStr"].replace("\"", "") "dStr": self.headers["dStr"].replace("\"", "")
} }
logger.info(f"Enviando check con datos: typ={typ}, date={data['date']}")
response = self.post("cp/checks", data, self.headers) response = self.post("cp/checks", data, self.headers)
logger.info(f"Respuesta del check recibida: status={response.status_code}, text={response.text[:200]}")
except Exception as e:
error_msg = f"Error durante el proceso de fichaje: {str(e)}"
logger.error(error_msg, exc_info=True)
self.addMessage(f"🟥🕐 {error_msg}")
self.sendReport()
raise
else: else:
logger.info("Modo debug activado, no se realizará fichaje")
self.addMessage('Corriendo en modo debug. No se realizará ninguna acción') self.addMessage('Corriendo en modo debug. No se realizará ninguna acción')
self.sendReport() self.sendReport()
exit(20) exit(20)
# Validar respuesta
if not response:
error_msg = "No se recibió respuesta del servidor"
logger.error(error_msg)
self.addMessage(f"🟥🕐 {error_msg}")
self.sendReport()
exit(20)
if response.text == "no valid worker": if response.text == "no valid worker":
self.addMessage("El trabajador no ha podido ser identificado") error_msg = "El trabajador no ha podido ser identificado"
logger.error(error_msg)
self.addMessage(f"🟥🕐 {error_msg}")
self.sendReport() self.sendReport()
exit(20) exit(20)
try: try:
rj = json.loads(response.text) rj = json.loads(response.text)
except: logger.info(f"JSON parseado correctamente: {rj}")
self.addMessage("La respuesta al hacer check no es correcta... algo ha pasado :/ - %s - %s" % (response.text, response.status_code)) except json.JSONDecodeError as e:
error_msg = f"La respuesta al hacer check no es correcta... algo ha pasado :/ - response={response.text[:200]} - status_code={response.status_code} - error={str(e)}"
logger.error(error_msg, exc_info=True)
self.addMessage(f"🟥🕐 {error_msg}")
self.sendReport()
exit(20)
except Exception as e:
error_msg = f"Error al procesar respuesta del check: {str(e)}"
logger.error(error_msg, exc_info=True)
self.addMessage(f"🟥🕐 {error_msg}")
self.sendReport() self.sendReport()
exit(20) exit(20)
# Procesar resultado
if response.status_code == 200: if response.status_code == 200:
if rj['Repeated']: if rj.get('Repeated'):
try:
time = datetime.strptime(rj['RepeatedTime'], "%Y-%m-%dT%H:%M:%SZ").replace( time = datetime.strptime(rj['RepeatedTime'], "%Y-%m-%dT%H:%M:%SZ").replace(
tzinfo=pytz.utc).astimezone(pytz.timezone("Europe/Madrid")).strftime("%H:%M") tzinfo=pytz.utc).astimezone(pytz.timezone("Europe/Madrid")).strftime("%H:%M")
logger.info(f"Check repetido a las {time}")
self.addMessage("🕐 Ya se ha realizado este marcaje antes a las %s" % time) self.addMessage("🕐 Ya se ha realizado este marcaje antes a las %s" % time)
except Exception as e:
error_msg = f"Error al procesar tiempo de check repetido: {str(e)}"
logger.error(error_msg, exc_info=True)
self.addMessage(f"🟥🕐 {error_msg}")
else: else:
try:
time = datetime.now().astimezone(pytz.timezone("Europe/Madrid")).strftime("%H:%M") time = datetime.now().astimezone(pytz.timezone("Europe/Madrid")).strftime("%H:%M")
logger.info(f"Check exitoso a las {time}")
self.addMessage('%s Has %s correctamente a las %s' % (strings.conditional_response[int(typ)]['emoji'], strings.conditional_response[int(typ)]['text'], time)) self.addMessage('%s Has %s correctamente a las %s' % (strings.conditional_response[int(typ)]['emoji'], strings.conditional_response[int(typ)]['text'], time))
except Exception as e:
error_msg = f"Error al formatear tiempo de check exitoso: {str(e)}"
logger.error(error_msg, exc_info=True)
self.addMessage(f"🟥🕐 {error_msg}")
else: else:
self.addMessage("🟥🕐 No se ha podido fichar, la web no ha devuelto 200 OK") error_msg = f"No se ha podido fichar, la web no ha devuelto 200 OK - status_code={response.status_code}, response={response.text[:200]}"
logger.error(error_msg)
self.addMessage(f"🟥🕐 {error_msg}")
self.sendReport() self.sendReport()
logger.info("sendUpdate completado")
except Exception as e:
error_msg = f"Error crítico en sendUpdate: {str(e)}"
logger.error(error_msg, exc_info=True)
self.addMessage(f"🟥🕐 {error_msg}")
self.sendReport()
raise

70
main.py
View File

@@ -12,11 +12,79 @@
# #
# Creado: Omar Sánchez 04-05-2019 # Creado: Omar Sánchez 04-05-2019
# Importamos App Principal # Importamos App Principal
from app import timenet_manager import sys
import traceback
import logging
import asyncio
from app import timenet_manager, telegram_bot
# Configurar logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger(__name__)
if __name__ == '__main__': if __name__ == '__main__':
telegram = None
try:
logger.info("=" * 50)
logger.info("Iniciando autoficher...")
logger.info("=" * 50)
af = timenet_manager.timenetManager() af = timenet_manager.timenetManager()
telegram = af.telegram
af.sendUpdate() af.sendUpdate()
logger.info("=" * 50)
logger.info("Autoficher completado exitosamente")
logger.info("=" * 50)
except KeyboardInterrupt:
logger.info("Interrupción por el usuario (Ctrl+C)")
sys.exit(0)
except SystemExit as e:
logger.info(f"Salida del sistema con código: {e.code}")
sys.exit(e.code if e.code else 0)
except Exception as e:
error_msg = f"Error crítico no manejado: {str(e)}"
error_traceback = traceback.format_exc()
logger.error("=" * 50)
logger.error("ERROR CRÍTICO NO MANEJADO")
logger.error("=" * 50)
logger.error(error_msg)
logger.error(error_traceback)
logger.error("=" * 50)
# Intentar enviar error por Telegram
try:
if telegram:
full_error = f"🟥🕐 ERROR CRÍTICO EN AUTOFICHER\n\n{error_msg}\n\n```\n{error_traceback}\n```"
asyncio.run(telegram.sendMessage(full_error))
logger.info("Error enviado por Telegram")
else:
# Intentar crear un bot temporal para enviar el error
try:
temp_telegram = telegram_bot.telegramBot()
full_error = f"🟥🕐 ERROR CRÍTICO EN AUTOFICHER\n\n{error_msg}\n\n```\n{error_traceback}\n```"
asyncio.run(temp_telegram.sendMessage(full_error))
logger.info("Error enviado por Telegram (bot temporal)")
except:
logger.error("No se pudo enviar el error por Telegram")
except Exception as telegram_error:
logger.error(f"Error al intentar enviar mensaje por Telegram: {str(telegram_error)}")
# Imprimir error en consola también
print(f"\n{'=' * 50}")
print("ERROR CRÍTICO")
print(f"{'=' * 50}")
print(error_msg)
print(f"\nTraceback completo:")
print(error_traceback)
print(f"{'=' * 50}\n")
sys.exit(1)