From fbecbbf0a15e6e6127880ed12fc08f3f82089f26 Mon Sep 17 00:00:00 2001 From: Manuel Vergara Date: Thu, 11 Jul 2024 02:54:47 +0200 Subject: [PATCH] Add Translator Bot for Telegram --- .../03_translator_bot/Dockerfile | 10 + .../03_translator_bot/config.py | 19 ++ .../03_translator_bot/logger.py | 63 ++++++ .../03_translator_bot/requirements.txt | 3 + .../03_translator_bot/translator.py | 211 ++++++++++++++++++ catch-all/06_bots_telegram/README.md | 28 +-- 6 files changed, 320 insertions(+), 14 deletions(-) create mode 100644 catch-all/06_bots_telegram/03_translator_bot/Dockerfile create mode 100644 catch-all/06_bots_telegram/03_translator_bot/config.py create mode 100644 catch-all/06_bots_telegram/03_translator_bot/logger.py create mode 100644 catch-all/06_bots_telegram/03_translator_bot/requirements.txt create mode 100644 catch-all/06_bots_telegram/03_translator_bot/translator.py diff --git a/catch-all/06_bots_telegram/03_translator_bot/Dockerfile b/catch-all/06_bots_telegram/03_translator_bot/Dockerfile new file mode 100644 index 0000000..b441f42 --- /dev/null +++ b/catch-all/06_bots_telegram/03_translator_bot/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.10-alpine + +WORKDIR /app + +COPY . /app/ + +RUN pip install -r requirements.txt + +CMD ["python", "translator.py"] + diff --git a/catch-all/06_bots_telegram/03_translator_bot/config.py b/catch-all/06_bots_telegram/03_translator_bot/config.py new file mode 100644 index 0000000..173fd3d --- /dev/null +++ b/catch-all/06_bots_telegram/03_translator_bot/config.py @@ -0,0 +1,19 @@ +# 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') +GROUP_CHAT_ID = os.getenv('GROUP_CHAT_ID') + +# Validar que las variables de entorno estén configuradas +if not BOT_TOKEN or not GROUP_CHAT_ID: + raise AssertionError("Por favor, configura las variables de entorno BOT_TOKEN y GROUP_CHAT_ID") + +# Convertir GROUP_CHAT_ID a entero +GROUP_CHAT_ID = int(GROUP_CHAT_ID) diff --git a/catch-all/06_bots_telegram/03_translator_bot/logger.py b/catch-all/06_bots_telegram/03_translator_bot/logger.py new file mode 100644 index 0000000..ae4aa28 --- /dev/null +++ b/catch-all/06_bots_telegram/03_translator_bot/logger.py @@ -0,0 +1,63 @@ +""" +# Ejemplo de uso del logger + +if __name__ == "__main__": + logger.info('Logger configurado correctamente.') + logger.debug('Este es un mensaje de depuración.') + logger.warning('Este es un mensaje de advertencia.') + logger.error('Este es un mensaje de error.') + logger.critical('Este es un mensaje crítico.') +""" + +# logger.py +# Este módulo configura el logging con rotación de archivos. + + +import logging +import os +from logging.handlers import RotatingFileHandler + + +def setup_logger(): + # Crear el directorio de logs si no existe + log_directory = 'logs' + if not os.path.exists(log_directory): + os.makedirs(log_directory) + + # Ruta del archivo de log + log_file = os.path.join(log_directory, 'bot.log') + + # Formato de los mensajes de log + log_formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s') + + # Configurar RotatingFileHandler + # maxBytes: tamaño máximo del archivo de log en bytes (5MB en este caso) + # backupCount: número máximo de archivos de respaldo + file_handler = RotatingFileHandler( + log_file, maxBytes=5*1024*1024, backupCount=5) + file_handler.setFormatter(log_formatter) + file_handler.setLevel(logging.INFO) + + # Configurar StreamHandler para la consola + console_handler = logging.StreamHandler() + console_handler.setFormatter(log_formatter) + + # Puedes cambiar este nivel según tus necesidades + console_handler.setLevel(logging.DEBUG) + + # Configurar el logger + logger = logging.getLogger('telegram_bot') + # Configurar el nivel del logger a DEBUG para capturar todos los mensajes + logger.setLevel(logging.DEBUG) + logger.addHandler(file_handler) + logger.addHandler(console_handler) + + # Evitar que los mensajes se dupliquen en el log + logger.propagate = False + + return logger + + +# Inicializar el logger +logger = setup_logger() diff --git a/catch-all/06_bots_telegram/03_translator_bot/requirements.txt b/catch-all/06_bots_telegram/03_translator_bot/requirements.txt new file mode 100644 index 0000000..f80b1f2 --- /dev/null +++ b/catch-all/06_bots_telegram/03_translator_bot/requirements.txt @@ -0,0 +1,3 @@ +python-telegram-bot==21.3 +translate==3.6.1 +python-dotenv==1.0.1 \ No newline at end of file diff --git a/catch-all/06_bots_telegram/03_translator_bot/translator.py b/catch-all/06_bots_telegram/03_translator_bot/translator.py new file mode 100644 index 0000000..c101c8a --- /dev/null +++ b/catch-all/06_bots_telegram/03_translator_bot/translator.py @@ -0,0 +1,211 @@ +""" +Este módulo contiene el código para un bot de Telegram que realiza tareas de traducción. + +El bot utiliza la API de Telegram Bot para interactuar con los usuarios y la API de Google Translate para la traducción. + +La función principal inicializa el bot y comienza a escuchar los mensajes entrantes. + +Para ejecutar el bot, asegúrese de tener las credenciales y la configuración de la API necesarias configuradas en el módulo `config`. + +Extraido del tutorial de youtube y luego actualizado: https://www.youtube.com/watch?v=8buZAq148gk&ab_channel=SBDeveloper + +Autor: manuelver +""" + +import signal + +from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup +from telegram.ext import ApplicationBuilder, MessageHandler, CommandHandler, CallbackContext, filters, CallbackQueryHandler + +from translate import Translator + +import config +from logger import logger + + +def def_handler(sig, frame): + """ + Función manejadora de señales para salir del programa de manera elegante. + """ + logger.info("Saliendo del programa...") + exit(1) + + +# Configurar el manejador de señal para SIGINT (Ctrl+C) +signal.signal(signal.SIGINT, def_handler) + + +async def select_origin_lang(update: Update, context: CallbackContext) -> None: + """ + Función para seleccionar el idioma de origen para la traducción. + """ + keyboard = [ + [ + InlineKeyboardButton("Español", callback_data='es'), + InlineKeyboardButton("Catalán", callback_data='ca'), + InlineKeyboardButton("English", callback_data='en'), + InlineKeyboardButton("Français", callback_data='fr') + ], + [ + InlineKeyboardButton("Deutsch", callback_data='de'), + InlineKeyboardButton("Italiano", callback_data='it'), + InlineKeyboardButton("Português", callback_data='pt') + ], + [ + InlineKeyboardButton("Русский (Ruso)", callback_data='ru'), + InlineKeyboardButton("日本語 (Japonés)", callback_data='ja'), + InlineKeyboardButton("中文 (Chino)", callback_data='zh') + ], + [ + InlineKeyboardButton("(Árabe) العربية", callback_data='ar'), + InlineKeyboardButton("हिन्दी (Hindi)", callback_data='hi'), + InlineKeyboardButton("עברית (Hebreo)", callback_data='he') + ] + ] + + reply_markup = InlineKeyboardMarkup(keyboard) + + await update.message.reply_text( + 'Por favor, selecciona el idioma de origen:', reply_markup=reply_markup) + + +async def select_dest_lang(update: Update, context: CallbackContext) -> None: + """ + Función para seleccionar el idioma de destino para la traducción. + """ + keyboard = [ + [ + InlineKeyboardButton("Español", callback_data='es'), + InlineKeyboardButton("Catalán", callback_data='ca'), + InlineKeyboardButton("English", callback_data='en'), + InlineKeyboardButton("Français", callback_data='fr') + ], + [ + InlineKeyboardButton("Deutsch", callback_data='de'), + InlineKeyboardButton("Italiano", callback_data='it'), + InlineKeyboardButton("Português", callback_data='pt') + ], + [ + InlineKeyboardButton("Русский (Ruso)", callback_data='ru'), + InlineKeyboardButton("日本語 (Japonés)", callback_data='ja'), + InlineKeyboardButton("中文 (Chino)", callback_data='zh') + ], + [ + InlineKeyboardButton("(Árabe) العربية", callback_data='ar'), + InlineKeyboardButton("हिन्दी (Hindi)", callback_data='hi'), + InlineKeyboardButton("עברית (Hebreo)", callback_data='he') + ] + ] + + reply_markup = InlineKeyboardMarkup(keyboard) + + await update.message.reply_text( + f'Por favor, selecciona el idioma de destino:', reply_markup=reply_markup) + + + +async def button(update: Update, context: CallbackContext) -> None: + """ + Función para manejar los botones de idioma seleccionados. + """ + query = update.callback_query + await query.answer() + + if 'origin_lang' not in context.user_data: + context.user_data['origin_lang'] = query.data + await query.edit_message_text(text=f"Idioma de origen seleccionado.\n\nAhora selecciona el idioma de destino con el comando /langTo") + await select_dest_lang(update, context) + else: + context.user_data['dest_lang'] = query.data + await query.edit_message_text(text=f"Idioma de destino seleccionado.\n\nEnvía tu texto para traducir.") + + logger.info(f"Idioma seleccionado: {query.data}") + + +async def lang_translator(user_input, from_lang, to_lang): + + try: + translator = Translator(from_lang=from_lang, to_lang=to_lang) + translation = translator.translate(user_input) + + return translation + + except TranslationError as e: + logger.error(f"Error en la traducción: {str(e)}") + return "Error en la traducción. Por favor, intenta de nuevo más tarde." + + except Exception as e: + logger.error(f"Error inesperado al traducir el texto: {str(e)}") + return "Error inesperado al traducir el texto." + + +async def reply(update: Update, context: CallbackContext): + + user_input = update.message.text + from_lang = context.user_data.get( + 'origin_lang', 'es') # Español por defecto + to_lang = context.user_data.get('dest_lang', 'en') # Inglés por defecto + + translation = await lang_translator(user_input, from_lang, to_lang) + await update.message.reply_text(translation) + + logger.info(f"Mensaje recibido: {user_input}") + logger.info(f"Texto traducido: {translation}") + + +async def start(update: Update, context: CallbackContext): + + await update.message.reply_text("¡Hola! Soy un bot de traducción.\n\nSi necesitas ayuda: /help.") + + logger.info("Comando /start recibido.") + + +async def help_command(update: Update, context: CallbackContext): + """ + Función para mostrar los comandos disponibles. + """ + commands = [ + "/start - Iniciar el bot", + "/langFrom - Seleccionar idioma de origen", + "/langTo - Seleccionar idioma de destino", + "/help - Mostrar este mensaje de ayuda" + ] + help_text = "\n".join(commands) + + await update.message.reply_text(f"El idioma por defecto origen es español y el de destino el Inglés.\nPuedes configurar otras opciones.\nUna vez lo tengas listo tan solo tienes que enviar el texto con el idioma origen.\n\nOpciones:\n{help_text}") + + logger.info("Comando /help recibido.") + + +def main(): + """ + Función principal para inicializar el bot y comenzar a escuchar los mensajes. + """ + api = config.BOT_TOKEN + + application = ApplicationBuilder().token(api).build() + + # Manejadores de comandos y mensajes + application.add_handler(CommandHandler('start', start)) + application.add_handler(CommandHandler('langFrom', select_origin_lang)) + application.add_handler(CommandHandler('langTo', select_dest_lang)) + application.add_handler(CommandHandler('help', help_command)) + application.add_handler(CommandHandler('command', help_command)) + application.add_handler(MessageHandler( + filters.TEXT & ~filters.COMMAND, reply)) + application.add_handler(CallbackQueryHandler(button)) + + # Iniciar el bot + logger.info("Bot iniciado.") + + # Iniciar el bucle de eventos + application.run_polling() + + +if __name__ == '__main__': + try: + main() + except Exception as e: + logger.error(f'Error en la ejecución del bot: {str(e)}') + except KeyboardInterrupt: + def_handler(None, None) diff --git a/catch-all/06_bots_telegram/README.md b/catch-all/06_bots_telegram/README.md index 313ddcd..5d5e3b8 100644 --- a/catch-all/06_bots_telegram/README.md +++ b/catch-all/06_bots_telegram/README.md @@ -1,16 +1,16 @@ # Bots de Telegram -| Nombre | Descripción | Nivel | -| ---------------------------------------------------- | ----------------------------------------- | ---------- | -| [Bot que devuelve id del bot](./01_id_bot/id_bot.py) | Bot que devuelve el id del bot | básico | -| [Bot pruebas](./02_pruebas_bot/bot.py) | Bot que devuelve mensajes básicos | básico | -| **Bot de traducción** (próximamente) | Bot que traduce mensajes a varios idiomas | intermedio | -| **Bot de clima** (próximamente) | Bot que devuelve el clima de una ciudad | intermedio | -| **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 | -| **Bot de películas** (próximamente) | Bot que devuelve información de películas | avanzado | -| **Bot de series** (próximamente) | Bot que devuelve información de series | avanzado | -| **Bot de libros** (próximamente) | Bot que devuelve información de libros | avanzado | -| **Bot de recetas** (próximamente) | Bot que devuelve recetas de cocina | avanzado | -| **Bot de deportes** (próximamente) | Bot que devuelve información de deportes | avanzado | +| Nombre | Descripción | Nivel | +| ----------------------------------------- | ----------------------------------------- | ---------- | +| [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 | avanzado | +| **Bot de clima** (próximamente) | Bot que devuelve el clima de una ciudad | intermedio | +| **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 | +| **Bot de películas** (próximamente) | Bot que devuelve información de películas | avanzado | +| **Bot de series** (próximamente) | Bot que devuelve información de series | avanzado | +| **Bot de libros** (próximamente) | Bot que devuelve información de libros | avanzado | +| **Bot de recetas** (próximamente) | Bot que devuelve recetas de cocina | avanzado | +| **Bot de deportes** (próximamente) | Bot que devuelve información de deportes | avanzado |