From b1e0997e6a514e749c9b9cc15f377ac779847a1d Mon Sep 17 00:00:00 2001 From: SBen-IV Date: Sun, 26 Nov 2023 20:26:19 -0300 Subject: [PATCH] feat: agrego comando para pasar url a noticia; refactor de la view, los mensaje internos los maneja CindyMoon --- README.md | 2 +- src/connectors/fiuba_web.py | 4 +++ src/connectors/silk.py | 17 +++++++-- src/controllers/jjjameson.py | 25 +++++++------- src/error_handler.py | 3 +- src/exceptions/url_incorrecta_exception.py | 4 +++ src/main.py | 5 +-- src/view/cindy_moon.py | 40 ++++++++++++++++++++++ src/view/contants.py | 4 +++ src/view/interno.py | 21 +++++++++++- src/view/threats_and_menaces.py | 6 +--- 11 files changed, 105 insertions(+), 26 deletions(-) create mode 100644 src/exceptions/url_incorrecta_exception.py create mode 100644 src/view/cindy_moon.py create mode 100644 src/view/contants.py diff --git a/README.md b/README.md index d1cbf1d..7e13f5a 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ docker compose up --build # hace el build si es necesario y run ## TODOs - [x] Ver cómo cambiar las noticias automáticas para hacer menos pegadas a la página (método que reciba una fecha y devuelva sólo noticias posteriores). -- [ ] Agregar método para convertir una url de una noticia. +- [x] Agregar método para convertir una url de una noticia. - [ ] Agregar archivo config para variables como delay, intervalo entre mensajes, etc. - [x] Agregar script para correr en "dev" y "prod". - [x] Agregar comando /status, /estado o /info que devuelva el estado del bot diff --git a/src/connectors/fiuba_web.py b/src/connectors/fiuba_web.py index 7810b67..e0987d0 100644 --- a/src/connectors/fiuba_web.py +++ b/src/connectors/fiuba_web.py @@ -12,3 +12,7 @@ def obtener_noticias(self, n: int = 1) -> list: @abstractclassmethod def obtener_noticias_nuevas(self, ultima_noticia: Noticia) -> list: pass + + @abstractclassmethod + def obtener_noticia(self, url: str) -> Noticia: + pass diff --git a/src/connectors/silk.py b/src/connectors/silk.py index 2ee4cc6..39113fe 100644 --- a/src/connectors/silk.py +++ b/src/connectors/silk.py @@ -6,6 +6,7 @@ from error_handler import logging from entities.noticia import Noticia from exceptions.cantidad_noticias_exception import CantidadNoticiasMaximaException, CantidadNoticiasNegativaException +from exceptions.url_incorrecta_exception import URLNoPerteneceADominioException DOMINIO = "https://fi.uba.ar" LINK_NOTICIAS = DOMINIO + "/noticias/pagina/1" @@ -28,11 +29,15 @@ def obtener_noticias(self, n_noticias: int = 1) -> list: noticias = [] for uri in uris_noticias[:n_noticias]: - noticias.append(self.obtener_noticia(uri)) + noticias.append(self.__obtener_noticia(uri)) return noticias - def obtener_noticia(self, uri: str) -> Noticia: + def obtener_noticia(self, url: str) -> Noticia: + uri = self.__obtener_uri(url) + return self.__obtener_noticia(uri) + + def __obtener_noticia(self, uri: str) -> Noticia: url = DOMINIO + uri self.logger.info("Obteniendo noticia de {url}".format(url=url)) pagina = requests.get(url) @@ -57,7 +62,7 @@ def obtener_noticias_nuevas(self, ultima_noticia: Noticia) -> list: noticias_nuevas = [] for uri in uris_noticias[:MAX_NOTICIAS]: - noticia = self.obtener_noticia(uri) + noticia = self.__obtener_noticia(uri) if noticia.fecha > ultima_noticia.fecha: noticias_nuevas.append(noticia) @@ -77,3 +82,9 @@ def __validar_cantidad(self, n_noticias: int) -> None: raise CantidadNoticiasNegativaException(n_noticias) elif n_noticias > MAX_NOTICIAS: raise CantidadNoticiasMaximaException(n_noticias) + + def __obtener_uri(self, url: str) -> str: + if url.startswith(DOMINIO) and len(url) > len(DOMINIO): + return url[len(DOMINIO):] + + raise URLNoPerteneceADominioException(DOMINIO, url) \ No newline at end of file diff --git a/src/controllers/jjjameson.py b/src/controllers/jjjameson.py index 35f7935..16fdcdc 100644 --- a/src/controllers/jjjameson.py +++ b/src/controllers/jjjameson.py @@ -4,6 +4,7 @@ from telegram.ext import CallbackContext from connectors.fiuba_web import FiubaWeb from view.imprenta import Imprenta +from view.interno import Interno from repositories.noticias_repository import NoticiasRepository from repositories.estado_repository import EstadoRepository from error_handler import logging @@ -15,11 +16,12 @@ class JJJameson: - def __init__(self, fiuba_web: FiubaWeb, noticias_repo: NoticiasRepository, estado_repo: EstadoRepository, imprenta: Imprenta, job_queue, bot): + def __init__(self, fiuba_web: FiubaWeb, noticias_repo: NoticiasRepository, estado_repo: EstadoRepository, imprenta: Imprenta, interno: Interno, job_queue, bot): self.fiuba_web = fiuba_web self.repo = noticias_repo self.estado_repo = estado_repo self.imprenta = imprenta + self.interno = interno self.noticias_automaticas = self.estado_repo.noticias_automaticas() if self.noticias_automaticas: @@ -48,7 +50,9 @@ def conseguir_noticias(self, update: Update, context: CallbackContext): raise CantidadNoticiasNoEsNumeroException(arg=context.args[0]) def convertir_noticia(self, update: Update, context: CallbackContext): - update.effective_chat.send_message("Trabajando en esta feature...") + if len(context.args) > 0: + noticia = self.fiuba_web.obtener_noticia(context.args[0]) + self.interno.enviar_noticia(update.effective_chat, noticia) def __activar_noticias_automaticas(self, job_queue, chat): self.job = job_queue.run_repeating(self.conseguir_noticias_automatico, INTERVALO_MENSAJES_AUTOMATICOS, context=chat) @@ -59,9 +63,9 @@ def activar_noticias_automaticas(self, update: Update, context: CallbackContext) self.noticias_automaticas = True self.estado_repo.guardar(self.noticias_automaticas) self.logger.info("Se activaron las noticias automáticas.") - update.effective_chat.send_message("Se activaron las noticias automáticas.") + self.interno.notificar_noticias_automaticas(update.effective_chat, self.noticias_automaticas) else: - update.effective_chat.send_message("Las noticias automáticas ya están activadas.") + self.interno.notificar_noticias_automaticas(update.effective_chat, self.noticias_automaticas, self.noticias_automaticas) def desactivar_noticias_automaticas(self, update: Update, _: CallbackContext): if self.noticias_automaticas: @@ -69,9 +73,9 @@ def desactivar_noticias_automaticas(self, update: Update, _: CallbackContext): self.noticias_automaticas = False self.estado_repo.guardar(self.noticias_automaticas) self.logger.info("Se desactivaron las noticias automáticas.") - update.effective_chat.send_message("Se desactivaron las noticias automáticas.") + self.interno.notificar_noticias_automaticas(update.effective_chat, self.noticias_automaticas, True) else: - update.effective_chat.send_message("Las noticias automáticas no están activadas.") + self.interno.notificar_noticias_automaticas(update.effective_chat) def conseguir_noticias_automatico(self, context: CallbackContext): ultima_noticia_guardada = self.repo.ultima_noticia() @@ -87,12 +91,7 @@ def conseguir_noticias_automatico(self, context: CallbackContext): self.logger.info("No hay noticias nuevas.") def estado(self, update: Update, _: CallbackContext): - update.effective_chat.send_message("Noticias automáticas: {noticias_automaticas}".format(noticias_automaticas=self.noticias_automaticas)) + self.interno.notificar_estado(update.effective_chat, self.noticias_automaticas) def ayuda(self, update: Update, context: CallbackContext): - update.effective_chat.send_message("/noticias - trae las últimas noticias\n" - + "/convertir_noticia - convierte la noticia\n" - + "/empezar - \n" - + "/terminar - \n" - + "/estado - \n" - + "/ayuda - imprime este mensaje\n") + self.interno.ayuda(update.effective_chat) diff --git a/src/error_handler.py b/src/error_handler.py index 139463e..b219f5e 100644 --- a/src/error_handler.py +++ b/src/error_handler.py @@ -6,6 +6,7 @@ from telegram import Update, ParseMode from telegram.ext import CallbackContext from exceptions.cantidad_noticias_exception import * +from exceptions.url_incorrecta_exception import * logging.basicConfig(format='[%(asctime)s] [%(levelname)s] [%(name)s] [%(funcName)s] %(message)s', level=logging.INFO, datefmt="%d/%m/%Y | %H:%M:%S") @@ -20,7 +21,7 @@ def error_handler(update: Update, context: CallbackContext) -> None: """ logger.error(msg="Exception while handling an update:", exc_info=context.error) - if isinstance(context.error, CantidadNoticiasException): + if isinstance(context.error, CantidadNoticiasException) or isinstance(context.error, URLNoPerteneceADominioException): update.effective_chat.send_message(context.error.message) else: diff --git a/src/exceptions/url_incorrecta_exception.py b/src/exceptions/url_incorrecta_exception.py new file mode 100644 index 0000000..23b9b51 --- /dev/null +++ b/src/exceptions/url_incorrecta_exception.py @@ -0,0 +1,4 @@ +class URLNoPerteneceADominioException(Exception): + def __init__(self, dominio, url) -> None: + self.message = "{url} no pertenece al dominio {dominio}".format(url=url, dominio=dominio) + super().__init__(self.message) \ No newline at end of file diff --git a/src/main.py b/src/main.py index e7f1816..79448ba 100644 --- a/src/main.py +++ b/src/main.py @@ -3,6 +3,7 @@ from controllers.jjjameson import JJJameson from connectors.silk import Silk from view.threats_and_menaces import ThreatsAndMenaces +from view.cindy_moon import CindyMoon from repositories.noticias_repository import NoticiasRepository from repositories.estado_repository import EstadoRepository from error_handler import error_handler, logging @@ -21,11 +22,11 @@ def main(): logger.info("Iniciando {full_name} (@{username}).".format(full_name=bot.full_name, username=bot.username)) - jameson = JJJameson(Silk(), NoticiasRepository(), EstadoRepository(), ThreatsAndMenaces(), updater.job_queue, updater.bot) + jameson = JJJameson(Silk(), NoticiasRepository(), EstadoRepository(), ThreatsAndMenaces(), CindyMoon(), updater.job_queue, updater.bot) filtro = Filters.chat(chat_id=int(os.getenv('ID_GRUPO_NOTICIAS'))) updater.dispatcher.add_handler(CommandHandler('noticias', jameson.conseguir_noticias, filtro)) - updater.dispatcher.add_handler(CommandHandler('convertir_noticia', jameson.convertir_noticia, filtro)) + updater.dispatcher.add_handler(CommandHandler('convertir', jameson.convertir_noticia, filtro)) updater.dispatcher.add_handler(CommandHandler('empezar', jameson.activar_noticias_automaticas, filtro)) updater.dispatcher.add_handler(CommandHandler('terminar', jameson.desactivar_noticias_automaticas, filtro)) updater.dispatcher.add_handler(CommandHandler('ayuda', jameson.ayuda, filtro)) diff --git a/src/view/cindy_moon.py b/src/view/cindy_moon.py new file mode 100644 index 0000000..24797d3 --- /dev/null +++ b/src/view/cindy_moon.py @@ -0,0 +1,40 @@ +from view.interno import Interno +from entities.noticia import Noticia +from telegram import ParseMode, Chat +from error_handler import logging +from view.contants import * + +class CindyMoon(Interno): + def __init__(self): + self.logger = logging.getLogger(__class__.__name__) + + def notificar_noticias_automaticas(self, chat: Chat, estado_actual: bool = False, estado_anterior: bool = False) -> None: + message = "Las noticias automáticas no están activadas." + + if estado_actual != estado_anterior: + if estado_actual: + message = "Se activaron las noticias automáticas." + else: + message = "Se desactivaron las noticias automáticas." + elif estado_actual: + message = "Las noticias automáticas ya están activadas." + + chat.send_message(message) + + def notificar_estado(self, chat: Chat, noticias_automaticas: bool) -> None: + chat.send_message("Noticias automáticas: {noticias_automaticas}".format(noticias_automaticas=noticias_automaticas)) + + def enviar_noticia(self, chat: Chat, noticia: Noticia) -> None: + chat.send_message(FORMATO_MENSAJE.format(titulo=noticia.titulo, + descripcion=noticia.descripcion, + url=noticia.url, + texto_url=MAS_INFORMACION), + parse_mode=ParseMode.HTML) + + def ayuda(self, chat: Chat) -> None: + chat.send_message("/noticias - trae las últimas 'n' noticias\n" + + "/convertir - convierte la noticia\n" + + "/empezar - activa las noticias automáticas en el canal de noticias\n" + + "/terminar - desactiva las noticias automáticas en el canal de noticias\n" + + "/estado - muestra el estado del bot\n" + + "/ayuda - imprime este mensaje\n") diff --git a/src/view/contants.py b/src/view/contants.py new file mode 100644 index 0000000..8152c48 --- /dev/null +++ b/src/view/contants.py @@ -0,0 +1,4 @@ +from emoji import emojize + +MAS_INFORMACION = emojize(":information: Más información") +FORMATO_MENSAJE = "{titulo}\n\n{descripcion}\n\n{texto_url}\n" \ No newline at end of file diff --git a/src/view/interno.py b/src/view/interno.py index 7ba6391..72ff62d 100644 --- a/src/view/interno.py +++ b/src/view/interno.py @@ -1 +1,20 @@ -# Cindy Moon \ No newline at end of file +from abc import ABC, abstractclassmethod +from telegram import Chat +from entities.noticia import Noticia + +class Interno(ABC): + @abstractclassmethod + def notificar_noticias_automaticas(self, chat: Chat, estado_actual: bool, estado_anterior: bool) -> None: + pass + + @abstractclassmethod + def notificar_estado(self, chat: Chat, noticias_automaticas: bool) -> None: + pass + + @abstractclassmethod + def enviar_noticia(self, chat: Chat, noticia: Noticia) -> None: + pass + + @abstractclassmethod + def ayuda(self, chat: Chat) -> None: + pass diff --git a/src/view/threats_and_menaces.py b/src/view/threats_and_menaces.py index 38c4e13..6e26fd6 100644 --- a/src/view/threats_and_menaces.py +++ b/src/view/threats_and_menaces.py @@ -2,12 +2,8 @@ from view.imprenta import Imprenta from telegram import ParseMode, Chat -from emoji import emojize from error_handler import logging - -MAS_INFORMACION = emojize(":information: Más información") -FORMATO_MENSAJE = "{titulo}\n\n{descripcion}\n\n{texto_url}\n" - +from view.contants import * class ThreatsAndMenaces(Imprenta): def __init__(self):