From 2c4f14a8860351b482bbd2f40bffdd2a78f8cfd5 Mon Sep 17 00:00:00 2001 From: Manuel Vergara <manuel@vergaracarmona.es> Date: Fri, 21 Feb 2025 21:31:58 +0100 Subject: [PATCH] Update mareas_bot --- .../06_bots_telegram/10_mareas_bot/Dockerfile | 1 - .../06_bots_telegram/10_mareas_bot/bot/bot.py | 135 ++++++++++++------ .../10_mareas_bot/docker-compose.yaml | 16 +-- .../10_mareas_bot/requirements.txt | 2 +- 4 files changed, 103 insertions(+), 51 deletions(-) diff --git a/catch-all/06_bots_telegram/10_mareas_bot/Dockerfile b/catch-all/06_bots_telegram/10_mareas_bot/Dockerfile index 11f5f15..5e56b5f 100644 --- a/catch-all/06_bots_telegram/10_mareas_bot/Dockerfile +++ b/catch-all/06_bots_telegram/10_mareas_bot/Dockerfile @@ -11,7 +11,6 @@ COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Copiamos el resto de los archivos de la aplicación -COPY .env /app/. COPY ./bot /app # Comando para ejecutar el programa diff --git a/catch-all/06_bots_telegram/10_mareas_bot/bot/bot.py b/catch-all/06_bots_telegram/10_mareas_bot/bot/bot.py index bcd2929..7600dbb 100644 --- a/catch-all/06_bots_telegram/10_mareas_bot/bot/bot.py +++ b/catch-all/06_bots_telegram/10_mareas_bot/bot/bot.py @@ -6,7 +6,6 @@ Comandos: - /reset: Borrar el historial - /help: Mostrar este mensaje de ayuda """ - import aiohttp import json import logging @@ -14,14 +13,22 @@ import os import psycopg2 import redis +from aiohttp import ClientError, ClientResponseError, ClientConnectionError, ClientPayloadError from bs4 import BeautifulSoup from dotenv import load_dotenv from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update from telegram.ext import ApplicationBuilder, CommandHandler, CallbackQueryHandler +###################### +# Configuración # +###################### + # Cargar variables de entorno load_dotenv() +# Obtener token de Telegram +TELEGRAM_TOKEN = os.getenv('TELEGRAM_BOT_TOKEN') + # Configuración de logging: toma el nivel desde la variable de entorno LOG_LEVEL log_level = os.getenv('LOG_LEVEL', 'INFO').upper() logging.basicConfig( @@ -29,10 +36,12 @@ logging.basicConfig( level=getattr(logging, log_level, logging.INFO) ) -# Excluir mensajes de nivel INFO de httpx - class ExcludeInfoFilter(logging.Filter): + """ + Excluir mensajes de nivel INFO de httpx + """ + def filter(self, record): return record.levelno != logging.INFO @@ -40,19 +49,27 @@ class ExcludeInfoFilter(logging.Filter): httpx_logger = logging.getLogger("httpx") httpx_logger.addFilter(ExcludeInfoFilter()) -# Helper: Quitar dominio de la URL para que el callback_data sea corto +###################### +# Funciones Helper # +###################### def shorten_url(url): + """ + Helper: Quitar dominio de la URL para que el callback_data sea corto + """ domain = "https://tablademareas.com" + if url.startswith(domain): return url[len(domain):] return url -# Helper: Obtener el objeto chat, ya sea de update.message o de update.callback_query.message - def get_chat(update: Update): + """ + Helper: Obtener el objeto chat + ya sea de update.message o de update.callback_query.message + """ if update.message: return update.message.chat elif update.callback_query and update.callback_query.message: @@ -60,7 +77,10 @@ def get_chat(update: Update): return None -# Conexión a Redis +###################### +# Conexión a Redis # +###################### + try: REDIS_PASSWORD = os.getenv('REDIS_PASSWORD') r = redis.Redis( @@ -72,16 +92,20 @@ except Exception as e: logging.error(f"⚠️ Error conectando a Redis: {e}") r = None -# Funciones de caché - def cache_set(key, value, expire=3600): + """ + Guardar datos en caché con un tiempo de expiración + """ if r: r.set(key, json.dumps(value), ex=expire) logging.info(f"🗃️ Guardado en caché: {key}") def cache_get(key): + """ + Obtener datos de la caché + """ if r: data = r.get(key) if data: @@ -90,8 +114,11 @@ def cache_get(key): logging.info(f"🚫 Cache MISS para {key}") return None +######################### +# Conexión a PostgreSQL # +######################### + -# Conexión a PostgreSQL DATABASE_URL = os.getenv('DATABASE_URL') @@ -132,32 +159,49 @@ def init_db(): logging.info("✅ Base de datos inicializada correctamente.") -# Obtener token de Telegram -TELEGRAM_TOKEN = os.getenv('TELEGRAM_BOT_TOKEN') - -# Función para obtener datos de la web con caché - +###################### +# Funciones del Bot # +###################### async def fetch(url): + """ + Función para obtener datos de la web con caché + """ url = url.split("#")[0] # eliminar fragmentos cached_data = cache_get(url) if cached_data: return cached_data + async with aiohttp.ClientSession() as session: try: - async with session.get(url, headers={"User-Agent": "Mozilla/5.0"}) as response: + async with session.get( + url, + headers={"User-Agent": "Mozilla/5.0"} + ) as response: response.raise_for_status() text = await response.text() cache_set(url, text) return text + except ClientResponseError as e: + logging.error( + f"❌ Error HTTP {e.status} en fetch {url}: {e.message}" + ) + except ClientConnectionError: + logging.error(f"⚠️ Error de conexión al intentar acceder a {url}") + except ClientPayloadError: + logging.error(f"❗ Error en la carga de datos desde {url}") + except ClientError as e: + logging.error(f"🔴 Error en fetch {url}: {e}") except Exception as e: - logging.error(f"❌ Error en fetch {url}: {e}") - return None + logging.error(f"⚡ Error inesperado en fetch {url}: {e}") -# Función para registrar eventos + return None def log_user_event(telegram_id, event_type, event_data): + """ + Función para registrar eventos + """ conn = get_db_connection() if not conn: return @@ -169,10 +213,11 @@ def log_user_event(telegram_id, event_type, event_data): """, (telegram_id, event_type, json.dumps(event_data))) logging.info(f"📝 Evento registrado: {event_type} - {event_data}") -# Comando /start - async def start(update: Update, context): + """ + Comando /start + """ user = update.message.from_user conn = get_db_connection() if conn: @@ -185,13 +230,15 @@ async def start(update: Update, context): """, (user.id, user.username, user.first_name, user.last_name)) logging.info(f"👤 Nuevo usuario registrado: {user.username} ({user.id})") log_user_event(user.id, "start_command", {}) + await update.message.reply_text("¡Bienvenido! Soy un bot de tabla de mareas (https://tablademareas.com/).") await show_continents(update) -# Comando /reset - async def reset(update: Update, context): + """ + Comando /reset + """ user = update.message.from_user conn = get_db_connection() if conn: @@ -204,18 +251,20 @@ async def reset(update: Update, context): log_user_event(user.id, "reset_command", {}) await update.message.reply_text("Tu historial ha sido borrado.") -# Comando /help - async def help_command(update: Update, context): + """ + Comando /help + """ await update.message.reply_text( "Opciones:\n- /start: Iniciar el bot\n- /reset: Borrar el historial\n- /help: Mostrar este mensaje de ayuda" ) -# Función para obtener continentes (nivel 0) - async def show_continents(update: Update): + """ + Función para obtener continentes (nivel 0) + """ chat = get_chat(update) full_url = "https://tablademareas.com/" response_text = await fetch(full_url) @@ -237,10 +286,11 @@ async def show_continents(update: Update): else: await update.callback_query.edit_message_text('Selecciona un continente:', reply_markup=InlineKeyboardMarkup(keyboard)) -# Callback para continente: mostrar países - async def continent_callback(update: Update, context): + """ + Callback para continente: mostrar países + """ query = update.callback_query await query.answer() short_url = query.data.split(":", 1)[1] @@ -262,10 +312,11 @@ async def continent_callback(update: Update, context): log_user_event(query.from_user.id, "continent_selected", {"url": full_url}) await query.edit_message_text('Selecciona un país:', reply_markup=InlineKeyboardMarkup(keyboard)) -# Callback para país: mostrar provincias - async def country_callback(update: Update, context): + """ + Callback para país: mostrar provincias + """ query = update.callback_query await query.answer() short_url = query.data.split(":", 1)[1] @@ -287,10 +338,11 @@ async def country_callback(update: Update, context): log_user_event(query.from_user.id, "country_selected", {"url": full_url}) await query.edit_message_text('Selecciona una provincia:', reply_markup=InlineKeyboardMarkup(keyboard)) -# Callback para provincia: mostrar puertos - async def province_callback(update: Update, context): + """ + Callback para provincia: mostrar puertos + """ query = update.callback_query await query.answer() short_url = query.data.split(":", 1)[1] @@ -326,10 +378,11 @@ async def province_callback(update: Update, context): log_user_event(query.from_user.id, "province_selected", {"url": full_url}) await query.edit_message_text('Selecciona un puerto:', reply_markup=InlineKeyboardMarkup(keyboard)) -# Callback para puerto: acción final - async def port_callback(update: Update, context): + """ + Callback para puerto: acción final + """ query = update.callback_query await query.answer() short_url = query.data.split(":", 1)[1] @@ -339,16 +392,18 @@ async def port_callback(update: Update, context): log_user_event(query.from_user.id, "port_selected", {"url": full_url}) await query.edit_message_text(f"Enlace del puerto: {full_url}") -# Función principal para iniciar el bot - def main(): + """ + Función principal para iniciar el bot + """ app = ApplicationBuilder().token(TELEGRAM_TOKEN).build() app.add_handler(CommandHandler("start", start)) app.add_handler(CommandHandler("reset", reset)) app.add_handler(CommandHandler("help", help_command)) - app.add_handler(CallbackQueryHandler( - continent_callback, pattern='^continent:')) + app.add_handler( + CallbackQueryHandler(continent_callback, pattern='^continent:') + ) app.add_handler(CallbackQueryHandler( country_callback, pattern='^country:')) app.add_handler(CallbackQueryHandler( @@ -359,6 +414,6 @@ def main(): app.run_polling() -# Ejecutar la función principal si el script se ejecuta directamente + if __name__ == "__main__": main() diff --git a/catch-all/06_bots_telegram/10_mareas_bot/docker-compose.yaml b/catch-all/06_bots_telegram/10_mareas_bot/docker-compose.yaml index 66d6d56..2071787 100644 --- a/catch-all/06_bots_telegram/10_mareas_bot/docker-compose.yaml +++ b/catch-all/06_bots_telegram/10_mareas_bot/docker-compose.yaml @@ -7,8 +7,6 @@ services: condition: service_healthy tablamareas-redis: condition: service_healthy - env_file: - - .env networks: - tablamareas_network restart: unless-stopped @@ -16,31 +14,31 @@ services: test: ["CMD-SHELL", "pgrep -f bot.py || exit 1"] interval: 10s retries: 3 - start_period: 10s + start_period: 5s tablamareas-db: image: postgres:16-alpine container_name: tablamareas-db env_file: - - .env + - .db.env networks: - tablamareas_network volumes: - pgdata:/var/lib/postgresql/data restart: unless-stopped healthcheck: - test: ["CMD-SHELL", "pg_isready -U $POSTGRES_USER"] + test: ["CMD-SHELL", "pg_isready -U mareasuser -d tablamareasdb || exit 1"] interval: 10s - retries: 5 - start_period: 10s + retries: 3 + start_period: 5s tablamareas-redis: image: redis:alpine container_name: tablamareas-redis env_file: - - .env + - .redis.env networks: - tablamareas_network restart: unless-stopped @@ -48,7 +46,7 @@ services: healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s - retries: 5 + retries: 3 start_period: 5s diff --git a/catch-all/06_bots_telegram/10_mareas_bot/requirements.txt b/catch-all/06_bots_telegram/10_mareas_bot/requirements.txt index ffe4393..2ed27bc 100644 --- a/catch-all/06_bots_telegram/10_mareas_bot/requirements.txt +++ b/catch-all/06_bots_telegram/10_mareas_bot/requirements.txt @@ -4,4 +4,4 @@ psycopg2-binary==2.9.10 python-dotenv==1.0.1 python-telegram-bot>=21.10 redis==5.2.1 -requests==2.32.3 \ No newline at end of file +requests==2.32.3