diff --git a/catch-all/06_bots_telegram/07_movie2_bot/Dockerfile b/catch-all/06_bots_telegram/07_movie2_bot/Dockerfile new file mode 100644 index 0000000..1715ff2 --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/Dockerfile @@ -0,0 +1,25 @@ +# Utiliza una imagen base de Python en Alpine +FROM python:3.12.4-alpine + +WORKDIR /app + +# Instala bash, herramientas de compilación y librerías necesarias +RUN apk update && \ + apk upgrade && \ + python -m venv /env && \ + /env/bin/pip install --upgrade pip + +# Añade el archivo de requisitos a la imagen +COPY requirements.txt /app/requirements.txt + +RUN /env/bin/pip install --no-cache-dir -r /app/requirements.txt + +# Añade el resto del código de la aplicación +COPY . /app + +# Configura el entorno virtual +ENV VIRTUAL_ENV=/env +ENV PATH=/env/bin:$PATH + +# Comando por defecto para ejecutar el bot +CMD ["python", "main.py"] diff --git a/catch-all/06_bots_telegram/07_movie2_bot/README.md b/catch-all/06_bots_telegram/07_movie2_bot/README.md new file mode 100644 index 0000000..99b133f --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/README.md @@ -0,0 +1,67 @@ +# QuizBot + +__Basado en el repositorio de [CineMonster](https://github.com/RogueFairyStudios/CineMonster)__ + +Bot de Telegram con un juego basado en preguntas sobre películas. Resumen de las funcionalidades: + + +## **Comandos Disponibles:** + +1. **`/start`**: + - **Descripción**: Inicia una nueva sesión de juego para el chat. + - **Acciones**: Crea una nueva instancia de `Session` y la almacena en `SESSIONS`. Si se encuentra una clase `Quiz` en el módulo `quiz`, la inicializa para la sesión. + +2. **`/roll`**: + - **Descripción**: Lanza una pregunta de trivia sobre películas. + - **Acciones**: Llama a `show` en el objeto `Quiz` de la sesión activa. Envía una imagen de una película al chat y establece el estado del juego en "running". + +3. **`/leaderboard`**: + - **Descripción**: Muestra la tabla de clasificación de los jugadores. + - **Acciones**: Envía la tabla de clasificación actual a través del `messenger`. La tabla muestra los jugadores y sus puntos. + +4. **`/repeat`**: + - **Descripción**: Repite la última pregunta de trivia sobre películas. + - **Acciones**: Envía de nuevo la imagen de la película al chat junto con la pregunta sobre el título de la película. + +5. **`/cut`**: + - **Descripción**: Permite que un jugador abandone el juego. + - **Acciones**: Elimina al jugador de la sesión actual y notifica al chat que el jugador ha abandonado el juego. + +6. **`/stop`**: + - **Descripción**: Finaliza la sesión de juego actual. + - **Acciones**: Elimina la sesión del chat actual de `SESSIONS` y notifica al chat que el juego ha terminado. + +7. **`/check_resps`**: + - **Descripción**: Verifica las respuestas enviadas por los jugadores. + - **Acciones**: Compara la respuesta del usuario con la respuesta correcta de la película y actualiza el puntaje si la respuesta es correcta. + + +## **Funcionalidades Adicionales:** + +- **Manejo de Temporizadores**: + - Utiliza `apscheduler` para ejecutar `update_all_timers` cada minuto, lo que actualiza los temporizadores de todas las sesiones y verifica la expiración del tiempo de juego. + +- **Mensajería**: + - Usa un objeto `messenger` para enviar mensajes y fotos a los usuarios en el chat, manejando la comunicación con Telegram. + +- **Gestión de Jugadores**: + - Permite agregar y quitar jugadores de la sesión. Actualiza el puntaje de los jugadores en función de sus respuestas correctas. + +- **Control de Estado del Juego**: + - Los estados del juego (`running`, `stopped`, `timed_out`) controlan el flujo del juego, incluyendo la verificación de respuestas y el manejo de tiempos de espera. + +- **Manejo de Errores**: + - Maneja errores durante el proceso de actualización y respuesta, notificando a los usuarios en caso de problemas con la pregunta de trivia o el estado de la sesión. + + +## **Estructura del Código:** + +1. **`Session`**: + - Maneja la lógica del juego, incluidos los jugadores, el estado de la sesión, y los temporizadores. + +2. **`Quiz`**: + - Se encarga de la lógica relacionada con las preguntas sobre películas, incluida la selección de una película al azar y la verificación de las respuestas. + +3. **`Server`**: + - Configura el bot de Telegram, maneja los comandos y los eventos, y gestiona las sesiones de juego. + diff --git a/catch-all/06_bots_telegram/07_movie2_bot/_config.yml b/catch-all/06_bots_telegram/07_movie2_bot/_config.yml new file mode 100644 index 0000000..c741881 --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-slate \ No newline at end of file diff --git a/catch-all/06_bots_telegram/07_movie2_bot/collection/Collection.py b/catch-all/06_bots_telegram/07_movie2_bot/collection/Collection.py new file mode 100644 index 0000000..49a660a --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/collection/Collection.py @@ -0,0 +1,40 @@ +from miners import Miner +from random import * + + +class Collection: + + movie_list = '' + + def __init__(self, miner, type): + self.miner = miner + self.type = type + + def top_250(self): + self.movie_list = self.miner.get_top(250) + + def general(self): + pass + + def get_rand_movie(self): + movie = None + + while movie is None: + if self.type is None: + number = str(randrange(1, 99999)) + if len(number) < 7: + number = '0' * (7 - len(number)) + number + movie_id = 'tt' + number + else: + self.top_250() + number = randrange(0, len(self.movie_list) - 1) + movie_id = self.movie_list[number]['tconst'] + + images, movie = self.miner.get_movie_by_id(movie_id) + print(movie['base']['title']) + if images is not None: + if images['totalImageCount'] < 1: + movie = None + + return movie, images + diff --git a/catch-all/06_bots_telegram/07_movie2_bot/collection/__init__.py b/catch-all/06_bots_telegram/07_movie2_bot/collection/__init__.py new file mode 100644 index 0000000..7e4899e --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/collection/__init__.py @@ -0,0 +1 @@ +from collection.Collection import Collection diff --git a/catch-all/06_bots_telegram/07_movie2_bot/conf/__init__.py b/catch-all/06_bots_telegram/07_movie2_bot/conf/__init__.py new file mode 100644 index 0000000..f86f597 --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/conf/__init__.py @@ -0,0 +1 @@ +from conf.config import Config, ProductionConfig, DevelopmentConfig, TestingConfig diff --git a/catch-all/06_bots_telegram/07_movie2_bot/conf/config.py b/catch-all/06_bots_telegram/07_movie2_bot/conf/config.py new file mode 100644 index 0000000..50721fc --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/conf/config.py @@ -0,0 +1,31 @@ +import os + +from dotenv import load_dotenv + + +load_dotenv('.env') + + +class Config(object): + + DEBUG = False + TESTING = False + DATABASE_URI = 'sqlite://:memory:' + DATABASE_URL = os.getenv('DATABASE_URL') + TELEGRAM_BOT_API = os.getenv('TELEGRAM_BOT_API') + LOG_FILE = 'logs/movie2_bot.log' + QUIZ_LANG = 'es' + + +class ProductionConfig(Config): + DATABASE_URI = 'mysql://user@localhost/foo' + SESSION_EXPIRATION_TIME = 30 + + +class DevelopmentConfig(Config): + DEBUG = True + SESSION_EXPIRATION_TIME = 10 + + +class TestingConfig(Config): + TESTING = True diff --git a/catch-all/06_bots_telegram/07_movie2_bot/docker-compose.yaml b/catch-all/06_bots_telegram/07_movie2_bot/docker-compose.yaml new file mode 100644 index 0000000..dbb74a7 --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/docker-compose.yaml @@ -0,0 +1,20 @@ +# version: '3.7' + +services: + movie2_bot: + env_file: + - .env + image: movie2_bot_python:latest + container_name: movie2_bot + environment: + - PUID=1000 + - PGID=1000 + - TZ=Europe/Madrid + volumes: + - ./movie2_bot_data:/app/db + - ./logs:/app/logs + restart: unless-stopped + +volumes: + movie2_bot_data: + logs: diff --git a/catch-all/06_bots_telegram/07_movie2_bot/interfaces/__init__.py b/catch-all/06_bots_telegram/07_movie2_bot/interfaces/__init__.py new file mode 100644 index 0000000..2d6b8a5 --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/interfaces/__init__.py @@ -0,0 +1 @@ +from interfaces.telegram.messenger import Messenger as TelegramMessenger diff --git a/catch-all/06_bots_telegram/07_movie2_bot/interfaces/telegram/messenger.py b/catch-all/06_bots_telegram/07_movie2_bot/interfaces/telegram/messenger.py new file mode 100644 index 0000000..bb3c4a6 --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/interfaces/telegram/messenger.py @@ -0,0 +1,45 @@ +from telegram.constants import ParseMode +import telegram.ext + +class Messenger: + formats = { + 'regular': "*%s*", + 'caption': "*%s*", + 'title': "=+= *%s* =+=", + 'highlight': "--+ %s +--", + 'bold': "*%s* %s" + } + + def __init__(self, bot, logger): + self.logger = logger + self.logger.debug("Started...") + self.bot = bot + + def send_msg(self, chat_id, msg, type_msg='regular'): + if type_msg not in self.formats: + self.logger.error(f"Invalid message type: {type_msg}") + return + + formatted_msg = self.format(type_msg, msg) + try: + self.bot.send_message( + chat_id=chat_id, + text=formatted_msg, + parse_mode=ParseMode.MARKDOWN_V2 + ) + except Exception as e: + self.logger.error(f"Error sending message: {e}") + + def format(self, type_msg, msg): + return self.formats[type_msg] % msg + + def send_photo(self, chat_id, photo, caption): + try: + self.bot.send_photo( + chat_id=chat_id, + photo=photo, + caption=caption, + parse_mode=ParseMode.MARKDOWN_V2 + ) + except Exception as e: + self.logger.error(f"Error sending photo: {e}") diff --git a/catch-all/06_bots_telegram/07_movie2_bot/main.py b/catch-all/06_bots_telegram/07_movie2_bot/main.py new file mode 100644 index 0000000..8d4c711 --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/main.py @@ -0,0 +1,11 @@ +from server import Server + + +def main(): + + Server() + + +if __name__ == '__main__': + + main() diff --git a/catch-all/06_bots_telegram/07_movie2_bot/miners/Miner.py b/catch-all/06_bots_telegram/07_movie2_bot/miners/Miner.py new file mode 100644 index 0000000..7613994 --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/miners/Miner.py @@ -0,0 +1,20 @@ +from abc import ABCMeta, abstractmethod + + +class Miner(object): + + __metaclass__ = ABCMeta + + handle = None + + @abstractmethod + def top_list(self, number): + pass + + @abstractmethod + def get_movie_id(self, index): + pass + + @abstractmethod + def get_movie_by_id(self, movie_id): + pass diff --git a/catch-all/06_bots_telegram/07_movie2_bot/miners/__init__.py b/catch-all/06_bots_telegram/07_movie2_bot/miners/__init__.py new file mode 100644 index 0000000..f3b545b --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/miners/__init__.py @@ -0,0 +1,2 @@ +from miners.Miner import Miner +from miners.imdb.ImdbMiner import IMDB diff --git a/catch-all/06_bots_telegram/07_movie2_bot/miners/imdb/ImdbMiner.py b/catch-all/06_bots_telegram/07_movie2_bot/miners/imdb/ImdbMiner.py new file mode 100644 index 0000000..8727efd --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/miners/imdb/ImdbMiner.py @@ -0,0 +1,20 @@ +from miners.Miner import Miner +from imdbpie import Imdb + + +class IMDB(Miner): + + def __init__(self): + + self.handle = Imdb() + super(IMDB, self).__init__() + + def top_list(self, number): + pop_movies = self.handle.top_250() + return pop_movies + + def get_movie_id(self, index): + return "tt" + index + + def get_movie_by_id(self, movie_id): + return self.handle.get_title_images(movie_id), self.handle.get_title(movie_id) diff --git a/catch-all/06_bots_telegram/07_movie2_bot/miners/imdb/__init__.py b/catch-all/06_bots_telegram/07_movie2_bot/miners/imdb/__init__.py new file mode 100644 index 0000000..103923f --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/miners/imdb/__init__.py @@ -0,0 +1 @@ +from miners.imdb.ImdbMiner import IMDB diff --git a/catch-all/06_bots_telegram/07_movie2_bot/models/Model.py b/catch-all/06_bots_telegram/07_movie2_bot/models/Model.py new file mode 100644 index 0000000..d3993e1 --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/models/Model.py @@ -0,0 +1,44 @@ +import os +import sys +import datetime + +from sqlalchemy import Column, ForeignKey, Integer, String, DateTime +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import relationship +from sqlalchemy import create_engine + +from conf import Config + + +Base = declarative_base() + + +class Player(Base): + __tablename__ = 'player' + id = Column(Integer, primary_key=True) + telegram_uid = Column(String(250), nullable=True) + email = Column(String(250), nullable=True) + phone = Column(String(30), nullable=True) + + +class Group(Base): + __tablename__ = 'group' + id = Column(Integer, primary_key=True) + name = Column(String(250), primary_key=True) + player_id = Column(Integer, ForeignKey('player.id')) + points = Column(Integer, nullable=True) + player = relationship(Player) + + +class Session(Base): + __tablename__ = 'session' + id = Column(Integer, primary_key=True) + group_id = Column(Integer, ForeignKey('group.id')) + group = relationship(Group) + started = Column(DateTime, default=datetime.datetime.utcnow) + ended = Column(DateTime) + + +engine = create_engine(Config.DATABASE_URL) + +Base.metadata.create_all(engine) diff --git a/catch-all/06_bots_telegram/07_movie2_bot/models/__init__.py b/catch-all/06_bots_telegram/07_movie2_bot/models/__init__.py new file mode 100644 index 0000000..37f7e62 --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/models/__init__.py @@ -0,0 +1 @@ +from models.Model import Player, Group, Session, engine diff --git a/catch-all/06_bots_telegram/07_movie2_bot/movie/Movie.py b/catch-all/06_bots_telegram/07_movie2_bot/movie/Movie.py new file mode 100644 index 0000000..161ba05 --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/movie/Movie.py @@ -0,0 +1,16 @@ +from abc import ABC, abstractmethod + + +class Movie(ABC): + + id = 0 + name = '' + type = '' + + @abstractmethod + def __init__(self, name): + self.name = name + self.type = None + + def get_name(self): + return self.name diff --git a/catch-all/06_bots_telegram/07_movie2_bot/movie/Pop.py b/catch-all/06_bots_telegram/07_movie2_bot/movie/Pop.py new file mode 100644 index 0000000..6de2ff3 --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/movie/Pop.py @@ -0,0 +1,8 @@ +from movie.Movie import Movie + + +class Pop(Movie): + + def __init__(self): + super(Pop, self).__init__(name='Pop Movie') + self.type = "pop" diff --git a/catch-all/06_bots_telegram/07_movie2_bot/movie/__init__.py b/catch-all/06_bots_telegram/07_movie2_bot/movie/__init__.py new file mode 100644 index 0000000..954f523 --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/movie/__init__.py @@ -0,0 +1,2 @@ +from movie.Movie import Movie +from movie.Pop import Pop diff --git a/catch-all/06_bots_telegram/07_movie2_bot/player/Player.py b/catch-all/06_bots_telegram/07_movie2_bot/player/Player.py new file mode 100644 index 0000000..da13b7b --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/player/Player.py @@ -0,0 +1,17 @@ +class Player: + + id = '' + points = 0 + name = '' + + def __init__(self, uid): + self.id = uid + + def get_points(self): + return self.points + + def set_name(self, name): + self.name = name + + def add_points(self, points): + self.points += points diff --git a/catch-all/06_bots_telegram/07_movie2_bot/player/__init__.py b/catch-all/06_bots_telegram/07_movie2_bot/player/__init__.py new file mode 100644 index 0000000..70d0e9b --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/player/__init__.py @@ -0,0 +1 @@ +from player.Player import Player diff --git a/catch-all/06_bots_telegram/07_movie2_bot/quiz/Quiz.py b/catch-all/06_bots_telegram/07_movie2_bot/quiz/Quiz.py new file mode 100644 index 0000000..846f259 --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/quiz/Quiz.py @@ -0,0 +1,98 @@ +from player.Player import Player +from random import choice +from collection.Collection import Collection +from miners.imdb.ImdbMiner import IMDB + + +class Quiz: + movies_type = '' + movie = None + images = None + + def __init__(self, session): + self.miner = IMDB() + self.session = session + + def set_level(self, level): + # Implementar el ajuste del nivel si es necesario + pass + + def rand_movie(self, rand_type=None): + collection = Collection(self.miner, rand_type) + self.movie, self.images = collection.get_rand_movie() + + def get_movie_photo(self): + if not self.images or 'images' not in self.images: + raise ValueError("No images available") + try: + return choice(self.images['images'])['url'] + except (IndexError, KeyError) as e: + raise ValueError("Error selecting image URL") from e + + def get_question(self, rand_type=None): + try: + self.rand_movie(rand_type) + return self.get_movie_photo() + except ValueError: + return _("not_possible_find_movie") + + def show(self, update, rand_type): + chat_id = update.message.chat_id + try: + movie_img = self.get_question(rand_type) + self.session.messenger.send_msg(chat_id, "movie_bot", "title") + self.session.messenger.send_photo( + chat_id, movie_img, caption=_("question_which_movie") + ) + self.session.updater_counter() + self.session.status = "running" + except ValueError as e: + self.session.messenger.send_msg( + chat_id, + msg=_("error_fetching_question"), + type_msg="bold" + ) + self.session.status = "stopped" + + def check_resps(self, update): + chat_id = update.message.chat_id + if not self.movie or 'base' not in self.movie or 'title' not in self.movie['base']: + self.session.messenger.send_msg( + chat_id, + msg=_("error_movie_data"), + type_msg="bold" + ) + return + + if str.lower(self.movie['base']['title']) == str.lower(update.message.text): + player = Player(update.message.from_user.id) + player.name = f"{update.message.from_user.first_name} {update.message.from_user.last_name}" + try: + self.session.player_add(player) + except ValueError: + pass + + self.session.players[update.message.from_user.id].add_points(1) + self.session.messenger.send_msg( + chat_id, + msg=(player.name, _("correct_answer")), + type_msg="bold" + ) + self.movie = None + self.session.status = "stopped" + + def check_expiration(self): + try: + self.session.update_timer() + except ValueError: + pass + + if self.session.status == "timed_out": + self.session.messenger.send_msg( + chat_id=self.session.chat_id, + msg=_("times_up", self.movie['base'] + ['title'] if self.movie else ""), + type_msg="bold" + ) + self.session.status = "stopped" + self.movie = None diff --git a/catch-all/06_bots_telegram/07_movie2_bot/quiz/__init__.py b/catch-all/06_bots_telegram/07_movie2_bot/quiz/__init__.py new file mode 100644 index 0000000..984bc13 --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/quiz/__init__.py @@ -0,0 +1 @@ +from quiz.Quiz import Quiz diff --git a/catch-all/06_bots_telegram/07_movie2_bot/quiz/leaderboard.py b/catch-all/06_bots_telegram/07_movie2_bot/quiz/leaderboard.py new file mode 100644 index 0000000..e69de29 diff --git a/catch-all/06_bots_telegram/07_movie2_bot/requirements.txt b/catch-all/06_bots_telegram/07_movie2_bot/requirements.txt new file mode 100644 index 0000000..2c318b6 --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/requirements.txt @@ -0,0 +1,6 @@ +Babel==2.15.0 +boto3==1.34.145 +imdbpie~=4.0 +python-dotenv==1.0.1 +python-telegram-bot[job-queue]==21.4 +six==1.16.0 diff --git a/catch-all/06_bots_telegram/07_movie2_bot/runtime.txt b/catch-all/06_bots_telegram/07_movie2_bot/runtime.txt new file mode 100644 index 0000000..7f3d94a --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/runtime.txt @@ -0,0 +1 @@ +python-3.12.4 \ No newline at end of file diff --git a/catch-all/06_bots_telegram/07_movie2_bot/server/Server.py b/catch-all/06_bots_telegram/07_movie2_bot/server/Server.py new file mode 100644 index 0000000..18317ab --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/server/Server.py @@ -0,0 +1,225 @@ +import logging +from argparse import ArgumentParser +from telegram import Update +from telegram.constants import ParseMode +from telegram.ext import Application, CommandHandler, MessageHandler, filters, CallbackContext + +import conf +import interfaces +import player +import quiz +import session +from translations.required import * + + +class Server: + logger = logging.getLogger(__name__) + SESSIONS = dict() + + def __init__(self): + self.config_instance = self.config_init() + + logging.basicConfig( + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + level=logging.DEBUG, + handlers=[ + logging.FileHandler(self.config_instance.LOG_FILE), + logging.StreamHandler() + ] + ) + + self.logger = logging.getLogger(__name__) + + self.application = Application.builder().token( + self.config_instance.TELEGRAM_BOT_API).build() + + # Add handlers + self.application.add_handler(MessageHandler( + filters.TEXT & ~filters.COMMAND, self.command_check_resps)) # Add handler for non-command messages + self.application.add_handler( + CommandHandler('start', self.command_start)) + self.application.add_handler(CommandHandler('roll', self.command_roll)) + self.application.add_handler(CommandHandler( + "leaderboard", self.command_leaderboard)) + self.application.add_handler( + CommandHandler("repeat", self.command_repeat)) + self.application.add_handler(CommandHandler("cut", self.command_cut)) + self.application.add_handler(CommandHandler("stop", self.command_stop)) + self.application.add_error_handler(self.error) + + # Set up job queue + self.application.job_queue.run_repeating( + self.update_all_timers, interval=60) + + self.logger.info("Iniciando...") + + # Start the Bot + self.application.run_polling() + + def config_init(self): + self.logger.info("Config init...") + + arg_parser = ArgumentParser(description="Movie2 Bot") + + arg_parser.add_argument( + "-e", "--env", metavar="env", type=str, default="prod", + help="Environment to run the bot: dev, test or prod" + ) + + arg_parser.add_argument( + "-v", "--verbose", metavar="verbose", type=bool, default=False, + help="Print information about running bot" + ) + + args = arg_parser.parse_args() + + if args.env == "prod": + return conf.ProductionConfig() + elif args.env == "dev": + return conf.DevelopmentConfig() + else: + return conf.TestingConfig() + + async def error(self, update: Update, context: CallbackContext): + self.logger.warning( + f'Update "{update}" caused error "{context.error}"') + + async def update_all_timers(self, context: CallbackContext): + self.logger.info("Updating all timers...") + for sess in self.SESSIONS.values(): + sess.update_timers() + if hasattr(sess, 'quiz'): + sess.quiz.check_expiration() + else: + self.logger.error("Quiz class not found in session") + + async def command_start(self, update: Update, context: CallbackContext): + self.logger.info("Command start...") + chat_id = update.message.chat_id + await update.message.reply_text('¡Hola! El comando /start ha sido recibido.') + + if chat_id not in self.SESSIONS: + self.messenger = interfaces.TelegramMessenger( + context.bot, self.logger + ) + self.SESSIONS[chat_id] = session.Session( + chat_id, self.config_instance, self.logger + ) + self.SESSIONS[chat_id].set_messenger(self.messenger) + + # Crear una instancia de la clase Quiz + try: + self.SESSIONS[chat_id].quiz = quiz.Quiz(self.SESSIONS[chat_id]) + except AttributeError as e: + self.logger.error(f"Error creating Quiz instance: {e}") + await self.messenger.send_msg( + chat_id, _("error_creating_quiz_instance") + ) + logging.error(f"Error creating Quiz instance: {e}") + + async def command_roll(self, update: Update, context: CallbackContext): + self.logger.info("Command roll...") + chat_id = update.message.chat_id + args = context.args + rand_type = args[0] if args else None + await self.SESSIONS[chat_id].messenger.send_msg( + chat_id, _("searching_movies")) + if hasattr(self.SESSIONS[chat_id], 'quiz'): + await self.SESSIONS[chat_id].quiz.show(update, rand_type) + else: + self.logger.error("Quiz instance not found in session") + + async def command_leaderboard(self, update: Update, context: CallbackContext): + self.logger.info("Command leaderboard...") + chat_id = update.message.chat_id + sess = self.SESSIONS.get(chat_id) + if sess: + try: + await sess.messenger.send_msg( + chat_id, _("leader_board_title"), 'highlights' + ) + ldb = sess.get_leaderboard() + await sess.messenger.send_msg(chat_id, ldb) + except ValueError as e: + await sess.messenger.send_msg( + chat_id, f'{update.message.from_user.first_name} {e.args[0]}' + ) + else: + await update.message.reply_text(_("session_not_found")) + + async def command_action(self, update: Update, context: CallbackContext): + self.logger.info("Command action...") + chat_id = update.message.chat_id + try: + player = player.Player(update.message.from_user.id) + player.name = f'{update.message.from_user.first_name} {update.message.from_user.last_name}' + self.SESSIONS[chat_id].player_add(player) + await self.SESSIONS[chat_id].messenger.send( + update, f'{player.name} {_("joined_the_game")}' + ) + except ValueError as e: + await self.SESSIONS[chat_id].messenger.send( + update, f'{update.message.from_user.first_name} {e.args[0]}' + ) + + async def command_repeat(self, update: Update, context: CallbackContext): + self.logger.info("Command repeat...") + chat_id = update.message.chat_id + if chat_id in self.SESSIONS: + movie_img = self.SESSIONS[chat_id].quiz.get_question() + await self.SESSIONS[chat_id].messenger.send_msg( + chat_id, _("repeting") + ) + await self.SESSIONS[chat_id].messenger.send_photo( + chat_id=chat_id, photo=movie_img, + caption=_("what_is_the_movie_series_name") + ) + await self.SESSIONS[chat_id].messenger.send_msg( + chat_id, "===========================" + ) + else: + await update.message.reply_text(_("session_not_found")) + + async def command_cut(self, update: Update, context: CallbackContext): + self.logger.info("Command cut...") + chat_id = update.message.chat_id + if chat_id in self.SESSIONS: + try: + player = player.Player(update.message.from_user.id) + self.SESSIONS[chat_id].player_quit(player) + await self.SESSIONS[chat_id].messenger.send( + update, f'{player.name} {_("quit_the_game")}' + ) + except ValueError as e: + await self.SESSIONS[chat_id].messenger.send( + update, f'{update.message.from_user.first_name} {e.args[0]}' + ) + else: + await update.message.reply_text(_("session_not_found")) + + async def command_stop(self, update: Update, context: CallbackContext): + self.logger.info("Command stop...") + chat_id = update.message.chat_id + if chat_id in self.SESSIONS: + self.SESSIONS[chat_id].stop() + await self.SESSIONS[chat_id].messenger.send( + update, _("game_stopped") + ) + else: + await update.message.reply_text(_("session_not_found")) + + # Implement the command_check_resps method + async def command_check_resps(self, update: Update, context: CallbackContext): + self.logger.info("Checking responses...") + chat_id = update.message.chat_id + if chat_id in self.SESSIONS: + user_response = update.message.text + sess = self.SESSIONS[chat_id] + # Add logic to handle user responses here + await sess.messenger.send_msg(chat_id, f"Received: {user_response}") + else: + await update.message.reply_text(_("session_not_found")) + + +if __name__ == "__main__": + Server() diff --git a/catch-all/06_bots_telegram/07_movie2_bot/server/__init__.py b/catch-all/06_bots_telegram/07_movie2_bot/server/__init__.py new file mode 100644 index 0000000..34b9cce --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/server/__init__.py @@ -0,0 +1 @@ +from server.Server import Server diff --git a/catch-all/06_bots_telegram/07_movie2_bot/session/Session.py b/catch-all/06_bots_telegram/07_movie2_bot/session/Session.py new file mode 100644 index 0000000..e116d33 --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/session/Session.py @@ -0,0 +1,55 @@ +import datetime + + +class Session: + + def __init__(self, chat_id, config, logger): + self.logger = logger + self.started = datetime.datetime.utcnow() + self.chat_id = chat_id + self.config = config + self.expiration = self.config.SESSION_EXPIRATION_TIME + self.players = {} + self.status = '' + self.messenger = None + self.counter = datetime.datetime.utcnow() + + def player_add(self, player): + if player.id not in self.players: + self.players[player.id] = player + else: + self.update_log() + raise ValueError('Ya está en la partida...') + + def player_quit(self, player): + if player.id in self.players: + del self.players[player.id] + else: + raise ValueError('no_está_en_la_partida') + + def end(self): + self.ended = datetime.datetime.utcnow() + + def get_leaderboard(self): + ldb = '' + for player in self.players.values(): + ldb += f'{player.name} : {player.get_points()}\n' + return ldb + + def set_messenger(self, messenger): + self.messenger = messenger + + def update_timers(self): + if self.status == 'running': + elapsed = self.update_log() + if elapsed.seconds > self.expiration: + self.status = 'timed_out' + + def update_counter(self): + self.counter = datetime.datetime.utcnow() + self.logger.debug(f'{self.chat_id} : updater_counter: {self.counter}') + + def update_log(self): + elapsed = datetime.datetime.utcnow() - self.counter + self.logger.debug(f'{self.chat_id} : updater_timer: {elapsed}') + return elapsed diff --git a/catch-all/06_bots_telegram/07_movie2_bot/session/__init__.py b/catch-all/06_bots_telegram/07_movie2_bot/session/__init__.py new file mode 100644 index 0000000..c2abb3e --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/session/__init__.py @@ -0,0 +1 @@ +from session.Session import Session diff --git a/catch-all/06_bots_telegram/07_movie2_bot/tests/.coveragerc b/catch-all/06_bots_telegram/07_movie2_bot/tests/.coveragerc new file mode 100644 index 0000000..f5a8f89 --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/tests/.coveragerc @@ -0,0 +1,2 @@ +[run] +omit = */.env/* \ No newline at end of file diff --git a/catch-all/06_bots_telegram/07_movie2_bot/translations/babel.cfg b/catch-all/06_bots_telegram/07_movie2_bot/translations/babel.cfg new file mode 100644 index 0000000..1d15bb3 --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/translations/babel.cfg @@ -0,0 +1 @@ +[python: **.py] \ No newline at end of file diff --git a/catch-all/06_bots_telegram/07_movie2_bot/translations/generate_files.sh b/catch-all/06_bots_telegram/07_movie2_bot/translations/generate_files.sh new file mode 100644 index 0000000..fb65708 --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/translations/generate_files.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# Script for Generate Translation files +# @author: manuelver +# requires: pybabel + +TRANSLATION_DIR=./ +LANGS=("pt_BR" "en" "es" "ca") # todo: smarter detection +GENERATED_DIR=./generated +options=("create" "update" "compile" "clean" "quit") +select opt in "${options[@]}"; do + case $opt in + "create") + echo "Creating files" + pybabel extract -F babel.cfg -o cinemonster.pot ../ + for lang in ${LANGS[@]}; do + pybabel init -i cinemonster.pot -d "${GENERATED_DIR}" -l ${lang} + done + ;; + "update") + echo "updating files" + pybabel update -i cinemonster.pot --previous -d "${GENERATED_DIR}" + ;; + "compile") + echo "compiling files" + pybabel compile -d "${GENERATED_DIR}" + ;; + "clean") + echo "cleaning files" + rm -rf "${GENERATED_DIR}" + rm cinemonster.pot + ;; + "quit") + break + ;; + *) echo invalid option ;; + esac +done diff --git a/catch-all/06_bots_telegram/07_movie2_bot/translations/generated/ca/LC_MESSAGES/messages.po b/catch-all/06_bots_telegram/07_movie2_bot/translations/generated/ca/LC_MESSAGES/messages.po new file mode 100644 index 0000000..ebed273 --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/translations/generated/ca/LC_MESSAGES/messages.po @@ -0,0 +1,68 @@ +# Catalan translations for PROJECT. +# Copyright (C) 2024 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2024-07-18 02:00+0200\n" +"PO-Revision-Date: 2024-07-18 02:00+0200\n" +"Last-Translator: FULL NAME \n" +"Language: ca\n" +"Language-Team: ca \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.12.1\n" + +#: ../quiz/Quiz.py:35 +msgid "not_possible_find_movie" +msgstr "" + +#: ../quiz/Quiz.py:42 +msgid "question_which_movie" +msgstr "" + +#: ../quiz/Quiz.py:61 +msgid "correct_answer" +msgstr "" + +#: ../quiz/Quiz.py:76 +msgid "times_up" +msgstr "" + +#: ../server/Server.py:89 +msgid "searching_movies" +msgstr "" + +#: ../server/Server.py:97 +msgid "leader_board_title" +msgstr "" + +#: ../server/Server.py:114 +msgid " joined_the_game" +msgstr "" + +#: ../server/Server.py:124 +msgid "repeting" +msgstr "" + +#: ../server/Server.py:128 +msgid "what_is_the_movie_series_name" +msgstr "" + +#: ../server/Server.py:142 +msgid " left_the_game" +msgstr "" + +#: ../server/Server.py:153 +msgid "ending_the_game" +msgstr "" + +#: ../server/Server.py:157 +msgid "game_was_not_finished" +msgstr "" + diff --git a/catch-all/06_bots_telegram/07_movie2_bot/translations/generated/en/LC_MESSAGES/messages.po b/catch-all/06_bots_telegram/07_movie2_bot/translations/generated/en/LC_MESSAGES/messages.po new file mode 100644 index 0000000..d2615c3 --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/translations/generated/en/LC_MESSAGES/messages.po @@ -0,0 +1,68 @@ +# English translations for PROJECT. +# Copyright (C) 2024 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2024-07-18 02:00+0200\n" +"PO-Revision-Date: 2024-07-18 02:00+0200\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.12.1\n" + +#: ../quiz/Quiz.py:35 +msgid "not_possible_find_movie" +msgstr "" + +#: ../quiz/Quiz.py:42 +msgid "question_which_movie" +msgstr "" + +#: ../quiz/Quiz.py:61 +msgid "correct_answer" +msgstr "" + +#: ../quiz/Quiz.py:76 +msgid "times_up" +msgstr "" + +#: ../server/Server.py:89 +msgid "searching_movies" +msgstr "" + +#: ../server/Server.py:97 +msgid "leader_board_title" +msgstr "" + +#: ../server/Server.py:114 +msgid " joined_the_game" +msgstr "" + +#: ../server/Server.py:124 +msgid "repeting" +msgstr "" + +#: ../server/Server.py:128 +msgid "what_is_the_movie_series_name" +msgstr "" + +#: ../server/Server.py:142 +msgid " left_the_game" +msgstr "" + +#: ../server/Server.py:153 +msgid "ending_the_game" +msgstr "" + +#: ../server/Server.py:157 +msgid "game_was_not_finished" +msgstr "" + diff --git a/catch-all/06_bots_telegram/07_movie2_bot/translations/generated/es/LC_MESSAGES/messages.po b/catch-all/06_bots_telegram/07_movie2_bot/translations/generated/es/LC_MESSAGES/messages.po new file mode 100644 index 0000000..86024d9 --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/translations/generated/es/LC_MESSAGES/messages.po @@ -0,0 +1,68 @@ +# Spanish translations for PROJECT. +# Copyright (C) 2024 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2024-07-18 02:00+0200\n" +"PO-Revision-Date: 2024-07-18 02:00+0200\n" +"Last-Translator: FULL NAME \n" +"Language: es\n" +"Language-Team: es \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.12.1\n" + +#: ../quiz/Quiz.py:35 +msgid "not_possible_find_movie" +msgstr "" + +#: ../quiz/Quiz.py:42 +msgid "question_which_movie" +msgstr "" + +#: ../quiz/Quiz.py:61 +msgid "correct_answer" +msgstr "" + +#: ../quiz/Quiz.py:76 +msgid "times_up" +msgstr "" + +#: ../server/Server.py:89 +msgid "searching_movies" +msgstr "" + +#: ../server/Server.py:97 +msgid "leader_board_title" +msgstr "" + +#: ../server/Server.py:114 +msgid " joined_the_game" +msgstr "" + +#: ../server/Server.py:124 +msgid "repeting" +msgstr "" + +#: ../server/Server.py:128 +msgid "what_is_the_movie_series_name" +msgstr "" + +#: ../server/Server.py:142 +msgid " left_the_game" +msgstr "" + +#: ../server/Server.py:153 +msgid "ending_the_game" +msgstr "" + +#: ../server/Server.py:157 +msgid "game_was_not_finished" +msgstr "" + diff --git a/catch-all/06_bots_telegram/07_movie2_bot/translations/generated/pt_BR/LC_MESSAGES/messages.po b/catch-all/06_bots_telegram/07_movie2_bot/translations/generated/pt_BR/LC_MESSAGES/messages.po new file mode 100644 index 0000000..fe48dcc --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/translations/generated/pt_BR/LC_MESSAGES/messages.po @@ -0,0 +1,68 @@ +# Portuguese (Brazil) translations for PROJECT. +# Copyright (C) 2024 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2024-07-18 02:00+0200\n" +"PO-Revision-Date: 2024-07-18 02:00+0200\n" +"Last-Translator: FULL NAME \n" +"Language: pt_BR\n" +"Language-Team: pt_BR \n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.12.1\n" + +#: ../quiz/Quiz.py:35 +msgid "not_possible_find_movie" +msgstr "" + +#: ../quiz/Quiz.py:42 +msgid "question_which_movie" +msgstr "" + +#: ../quiz/Quiz.py:61 +msgid "correct_answer" +msgstr "" + +#: ../quiz/Quiz.py:76 +msgid "times_up" +msgstr "" + +#: ../server/Server.py:89 +msgid "searching_movies" +msgstr "" + +#: ../server/Server.py:97 +msgid "leader_board_title" +msgstr "" + +#: ../server/Server.py:114 +msgid " joined_the_game" +msgstr "" + +#: ../server/Server.py:124 +msgid "repeting" +msgstr "" + +#: ../server/Server.py:128 +msgid "what_is_the_movie_series_name" +msgstr "" + +#: ../server/Server.py:142 +msgid " left_the_game" +msgstr "" + +#: ../server/Server.py:153 +msgid "ending_the_game" +msgstr "" + +#: ../server/Server.py:157 +msgid "game_was_not_finished" +msgstr "" + diff --git a/catch-all/06_bots_telegram/07_movie2_bot/translations/required.py b/catch-all/06_bots_telegram/07_movie2_bot/translations/required.py new file mode 100644 index 0000000..9244b5c --- /dev/null +++ b/catch-all/06_bots_telegram/07_movie2_bot/translations/required.py @@ -0,0 +1,20 @@ +import gettext + +""" localization support """ +en = gettext.translation( + 'messages', 'translations/generated', languages=['en'] +) + +pt_br = gettext.translation( + 'messages', 'translations/generated', languages=['pt_BR'] +) + +es = gettext.translation( + 'messages', 'translations/generated', languages=['es'] +) + +ca = gettext.translation( + 'messages', 'translations/generated', languages=['ca'] +) + +en.install() \ No newline at end of file diff --git a/catch-all/06_bots_telegram/README.md b/catch-all/06_bots_telegram/README.md index 0cd3428..6573041 100644 --- a/catch-all/06_bots_telegram/README.md +++ b/catch-all/06_bots_telegram/README.md @@ -6,16 +6,16 @@ -| 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 | básico | -| [Bot de traducción](./03_translator_bot/) | Bot que traduce mensajes a varios idiomas. Loggin | intermedio | -| [Bot de clima](./04_clima_bot/) | Bot que devuelve el clima de una ciudad | intermedio | -| [Bot de noticias](./05_rss_bot/) | Bot que devuelve noticias de última hora | intermedio | -| [Bot de películas I](./06_movie_bot/) | Bot que devuelve información de películas | intermedio | -| **Bot de películas II** (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 | -| **Bot de mareas** (próximamente) | Bot que devuelve información de mareas | 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 | básico | +| [Bot de traducción](./03_translator_bot/) | Bot que traduce mensajes a varios idiomas. Loggin | intermedio | +| [Bot de clima](./04_clima_bot/) | Bot que devuelve el clima de una ciudad | intermedio | +| [Bot de noticias](./05_rss_bot/) | Bot que devuelve noticias de última hora | intermedio | +| [Bot de películas](./06_movie_bot/) | Bot que devuelve información de películas | intermedio | +| [Bot trivial de películas](./07_movie2_bot/README.md) | 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 | +| **Bot de mareas** (próximamente) | Bot que devuelve información de mareas | avanzado |