From 819aaaa1f5e1d052d12cefd95b4cf14ae449e5f4 Mon Sep 17 00:00:00 2001 From: Manuel Vergara Date: Sun, 14 Jul 2024 19:20:40 +0200 Subject: [PATCH] Add Weather Bot for Telegram --- .../06_bots_telegram/04_clima_bot/Dockerfile | 10 ++ .../04_clima_bot/clima_bot.py | 126 ++++++++++++++++++ .../06_bots_telegram/04_clima_bot/config.py | 17 +++ .../04_clima_bot/requirements.txt | 13 ++ catch-all/06_bots_telegram/README.md | 2 +- 5 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 catch-all/06_bots_telegram/04_clima_bot/Dockerfile create mode 100644 catch-all/06_bots_telegram/04_clima_bot/clima_bot.py create mode 100644 catch-all/06_bots_telegram/04_clima_bot/config.py create mode 100644 catch-all/06_bots_telegram/04_clima_bot/requirements.txt diff --git a/catch-all/06_bots_telegram/04_clima_bot/Dockerfile b/catch-all/06_bots_telegram/04_clima_bot/Dockerfile new file mode 100644 index 0000000..30a01c9 --- /dev/null +++ b/catch-all/06_bots_telegram/04_clima_bot/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.10-alpine + +WORKDIR /app + +COPY . /app/ + +RUN pip install -r requirements.txt + +CMD ["python", "clima_bot.py"] + diff --git a/catch-all/06_bots_telegram/04_clima_bot/clima_bot.py b/catch-all/06_bots_telegram/04_clima_bot/clima_bot.py new file mode 100644 index 0000000..3f8c549 --- /dev/null +++ b/catch-all/06_bots_telegram/04_clima_bot/clima_bot.py @@ -0,0 +1,126 @@ +import asyncio +import logging +import requests + +from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup +from telegram.ext import ApplicationBuilder, CommandHandler, MessageHandler, CallbackQueryHandler, CallbackContext +from telegram.ext import filters + +import config + +# Logging setup +logging.basicConfig( + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO) +logger = logging.getLogger(__name__) + +TELEGRAM_API_TOKEN = config.BOT_TOKEN +WEATHER_API_KEY = config.WEATHER_API_KEY +BASE_WEATHER_URL = "http://api.openweathermap.org/data/2.5/weather?q={}&appid={}" +FORECAST_URL = "http://api.openweathermap.org/data/2.5/forecast?q={}&appid={}" + +city = None + + +def weather_emoji(description): + """Devuelve un emoji basado en la descripción del clima.""" + description = description.lower() + if "clear" in description: + return "☀️" + elif "cloud" in description: + return "☁️" + elif "rain" in description: + return "🌧️" + elif "thunder" in description: + return "⛈️" + elif "snow" in description: + return "❄️" + elif "mist" in description or "fog" in description: + return "🌫️" + else: + return "" + + +async def start(update: Update, context: CallbackContext) -> None: + """Manejador del comando /start. Solicita al usuario la ciudad en la que vive.""" + logger.debug(f"Comando /start recibido de {update.message.chat.username}") + await update.message.reply_text("¿En qué ciudad vives?") + + +async def menu(update: Update, context: CallbackContext) -> None: + """Manejador de mensajes. Muestra un menú con opciones al usuario después de recibir la ciudad.""" + global city + city = update.message.text + logger.debug(f"Ciudad recibida: {city}") + keyboard = [ + [InlineKeyboardButton("Clima Actual", callback_data='current_weather')], + [InlineKeyboardButton("Pronóstico del Tiempo", callback_data='forecast')], + ] + reply_markup = InlineKeyboardMarkup(keyboard) + await update.message.reply_text('Elige una opción:', reply_markup=reply_markup) + + +async def button(update: Update, context: CallbackContext) -> None: + """Manejador de botones. Procesa la opción seleccionada por el usuario.""" + query = update.callback_query + await query.answer() + logger.debug(f"Opción seleccionada: {query.data}") + + try: + if query.data == 'current_weather': + response = requests.get(BASE_WEATHER_URL.format(city, WEATHER_API_KEY)) + response.raise_for_status() + data = response.json() + main = data['main'] + weather_data = data['weather'][0] + celsius_temp = main['temp'] - 273.15 + emoji = weather_emoji(weather_data['description']) + message = f"Clima actual en {city} {emoji}:\n" + message += f"Temperatura: {celsius_temp:.2f}°C\n" + message += f"Descripción: {weather_data['description'].capitalize()}\n" + message += f"Humedad: {main['humidity']}%\n" + await query.edit_message_text(text=message) + elif query.data == 'forecast': + response = requests.get(FORECAST_URL.format(city, WEATHER_API_KEY)) + response.raise_for_status() + data = response.json() + message = f"Pronóstico del tiempo para {city}:\n" + for item in data['list'][:5]: + celsius_temp = item['main']['temp'] - 273.15 + emoji = weather_emoji(item['weather'][0]['description']) + message += f"\nFecha: {item['dt_txt']} {emoji}\n" + message += f"Temperatura: {celsius_temp:.2f}°C\n" + message += f"Descripción: {item['weather'][0]['description'].capitalize()}\n" + await query.edit_message_text(text=message) + except requests.RequestException as e: + logger.error(f"Error al obtener datos del clima: {e}") + await query.edit_message_text( + text="No se puede encontrar información meteorológica para esta ciudad. Inténtalo de nuevo.") + except Exception as e: + logger.error(f"Error inesperado: {e}") + await query.edit_message_text( + text="Ocurrió un error inesperado. Inténtalo de nuevo más tarde.") + + +def error(update: Update, context: CallbackContext): + """Registra errores causados por actualizaciones.""" + logger.warning('La actualización "%s" causó el error "%s"', update, context.error) + + +def main(): + """Función principal del bot. Configura y ejecuta el bot de Telegram.""" + logger.info("Iniciando el bot...") + application = ApplicationBuilder().token(TELEGRAM_API_TOKEN).build() + + application.add_handler(CommandHandler("start", start)) + application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, menu)) + application.add_handler(CallbackQueryHandler(button)) + + # Registra todos los errores + application.add_error_handler(error) + + logger.info("Bot iniciado y en espera de mensajes...") + application.run_polling() + + +if __name__ == '__main__': + main() diff --git a/catch-all/06_bots_telegram/04_clima_bot/config.py b/catch-all/06_bots_telegram/04_clima_bot/config.py new file mode 100644 index 0000000..a7b21f8 --- /dev/null +++ b/catch-all/06_bots_telegram/04_clima_bot/config.py @@ -0,0 +1,17 @@ +# config.py +# Este módulo gestiona la configuración y la carga de variables de entorno. + +import os +from dotenv import load_dotenv + +# Cargar las variables de entorno desde el archivo .env +load_dotenv('.env') + +# Obtener el token del bot y el ID del chat de grupo desde las variables de entorno +BOT_TOKEN = os.getenv('BOT_TOKEN') +WEATHER_API_KEY = os.getenv('WEATHER_API_KEY') + +# Validar que las variables de entorno estén configuradas +if not BOT_TOKEN or not WEATHER_API_KEY: + raise AssertionError("Por favor, configura las variables de entorno BOT_TOKEN y GROUP_CHAT_ID") + diff --git a/catch-all/06_bots_telegram/04_clima_bot/requirements.txt b/catch-all/06_bots_telegram/04_clima_bot/requirements.txt new file mode 100644 index 0000000..27fb977 --- /dev/null +++ b/catch-all/06_bots_telegram/04_clima_bot/requirements.txt @@ -0,0 +1,13 @@ +certifi==2024.7.4 +cffi==1.16.0 +charset-normalizer==3.3.2 +cryptography==42.0.8 +decorator==5.1.1 +idna==3.7 +pycparser==2.22 +python-decouple==3.8 +python-dotenv==1.0.1 +python-telegram-bot==21.3 +requests==2.32.3 +tornado==6.4.1 +urllib3~=1.26 diff --git a/catch-all/06_bots_telegram/README.md b/catch-all/06_bots_telegram/README.md index 0b04da7..4005ea3 100644 --- a/catch-all/06_bots_telegram/README.md +++ b/catch-all/06_bots_telegram/README.md @@ -5,7 +5,7 @@ | [Bot id del chat](./01_id_bot/) | Bot que devuelve el id del bot | básico | | [Bot mensajes](./02_pruebas_bot/) | Bot que devuelve mensajes básicos. Loggin | intermedio | | [Bot de traducción](./03_translator_bot/) | Bot que traduce mensajes a varios idiomas. Loggin | avanzado | -| **Bot de clima** (próximamente) | Bot que devuelve el clima de una ciudad | intermedio | +| [Bot de clima](./04_clima_bot/) | Bot que devuelve el clima de una ciudad | avanzado | | **Bot de noticias** (próximamente) | Bot que devuelve noticias de última hora | intermedio | | **Bot de mareas** (próximamente) | Bot que devuelve información de mareas | avanzado | | **Bot de juegos** (próximamente) | Bot con juegos de adivinanzas y preguntas | avanzado |