Compare commits

..
Author SHA1 Message Date
2edfc45226 Se corrigen detalles en comentatios. 2025-06-06 18:32:06 -06:00
18 changed files with 428 additions and 1136 deletions

6
.gitignore vendored
View file

@ -1,5 +1,3 @@
appData/*
__pycache__
modulos/agenda/__pycache__
modulos/todo/__pycache__
todo/todoData*
todo/__pycache__*

View file

@ -1,19 +0,0 @@
FROM python:3.12-slim
# Instalar tzdata para configurar zona horaria
RUN apt-get update && apt-get install -y tzdata sqlite3 && rm -rf /var/lib/apt/lists/*
ENV TZ=America/Mexico_City
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN mkdir -p /app/appData
WORKDIR /app
COPY ./modulos ./modulos
COPY ./base_de_datos.py .
COPY ./main.py .
COPY ./recordar.py .
COPY ./requirements.txt .
RUN pip install -r requirements.txt

8
README.md Normal file
View file

@ -0,0 +1,8 @@
# Mejoras Botija
Este repositorio contiene desarrollos y mejoras para Botija, el bot de Telegram utilizado en el grupo de la comunidad mxOS. Nuestro objetivo es extender sus funcionalidades para facilitar la organización, automatización y participación en el grupo, adaptándolo a las necesidades específicas de los miembros y aprovechando al máximo las capacidades de Telegram.
Index:
- [ToDo](todo)

View file

@ -1,118 +0,0 @@
Mejoras BotBotija
=================
Este repositorio contiene desarrollos y mejoras para Botija, el bot de
Telegram utilizado en el grupo de la comunidad mxOS. Nuestro objetivo es
extender sus funcionalidades para facilitar la organización,
automatización y participación en el grupo, adaptándolo a las
necesidades específicas de los miembros y aprovechando al máximo las
capacidades de Telegram.
comandos:
- `comandos <docs/comandos.rst>`__
Prerequisitos
=============
Python
------
Declarar variable de entorno para TOKEN de telegram
.. code:: bash
export TELEGRAM_BOT_TOKEN=<TOKEN>
export REUNION_URL=<URL general de reuniones>
Instalar las dependencias
.. code:: bash
pip install -r requirements.txt
podman
------
Correr el contenedor con un usuario no root
Declarar variable de entorno para TOKEN de telegram
.. code:: bash
export TELEGRAM_BOT_TOKEN=<TOKEN>
export REUNION_URL=<URL general de reuniones>
Instalar podman-compose
.. code:: bash
pip install podman-compose
Habilitar permanencia de sesion tras deslogueo de terminal
.. code:: bash
loginctl enable-linger
Ejecución
=========
python
------
1. Comandos Botija
Ejecutar el script principal
.. code:: bash
python main.py
El bot estará en modo escucha hasta recibir un comando
.. code:: bash
[anduril@elendil todo]$ python main.py
2025-06-01 11:49:07,297 - telegram.ext.Application - INFO - Application started
1. Sistema para checar cuando inicia la reunión
.. code:: bash
python recordar.py.py
El bot estará en modo escucha hasta recibir un comando
.. code:: bash
[anduril@elendil todo]$ python recordar.py
2025-06-01 11:49:07,297 - telegram.ext.Application - INFO - Application started
Al iniciar la aplicación, se creará una base de datos en sqlite llamada
``data_bot.db`` en el directorio ``appData``, la cual llevará el
registro de todo el sistema CRUD de ToDo y Agenda.
Para detener el bot desde terminal presionar CTRL+C
Nota: se recomienda ejecutar el script en una sesion de tmux o nohub
para ejecucion del script en segundo plano
podman
------
Construir imagen de contenedor
.. code:: bash
podman build -t comandos-botija .
Ejecutar el compose
.. code:: bash
podman-compose up -d

View file

@ -1,203 +0,0 @@
#!/usr/bin/env python
"""
Este programa es software libre: usted puede redistribuirlo y/o modificarlo
bajo los términos de la Licencia Pública General de GNU publicada por la
Free Software Foundation, GPLv3.
Programa para hacer un todo list en topics de telegram, usando el framework de python-telegram-bot
Uso:
- Agenda reunión /agenda crear [Título de la reunión] [Fecha en dd-mm-aaaa] [HH:MM CST]
- Mostrar reunión vigente /agenda ls
- Modificar reunión por titulo /agenda modificar [id_agenda] -t [Título de la reunión]
- Modificar reunión por fecha /agenda modificar [id_agenda] -f [Fecha en dd-mm-aaaa]
- Modificar reunión por hora /agenda modificar [id_agenda] -h [HH:MM CST]
- Eliminar reunión /agenda [id agenda]
Precionar Ctrl-C para enviar señal de interrupcion del proceso en la linea de comandos
"""
import sqlite3
from typing import List, Tuple
class BaseDeDatos:
def __init__(self, ruta_db: str) -> None:
self.conn = sqlite3.connect(ruta_db, check_same_thread=False)
self._crear_tablas()
def cerrar(self) -> None:
self.conn.close()
def _crear_tablas(self) -> None:
with self.conn:
#se uso varchar porque telegram en topico GENERAL tienen valor NONE
self.conn.execute('''
CREATE TABLE IF NOT EXISTS topics (
id varchar(100) PRIMARY KEY,
chat_id INTEGER not null
)
''')
self.conn.execute('''
CREATE TABLE IF NOT EXISTS tareas (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tarea varchar(255) not null,
creacion DATETIME DEFAULT CURRENT_TIMESTAMP,
topic_id varchar(100) not null,
FOREIGN KEY (topic_id) REFERENCES topics(id) ON DELETE CASCADE
)
''')
self.conn.execute('''
CREATE TABLE IF NOT EXISTS reuniones (
id INTEGER PRIMARY KEY AUTOINCREMENT,
titulo varchar(255) not null,
fecha varchar(15) not null,
hora varchar(10) not null,
zona_horaria varchar(5) not null,
estatus BOOLEAN DEFAULT 1,
creacion DATETIME DEFAULT CURRENT_TIMESTAMP,
topic_id varchar(100) not null,
FOREIGN KEY (topic_id) REFERENCES topics(id) ON DELETE CASCADE
)
''')
class ToDoBaseDeDatos(BaseDeDatos):
def __init__ (self, nombre_db='appData/data_bot.db'):
super().__init__(nombre_db)
def insertar_topic(self, topic_id: str, chat_id: str, tarea: str) -> None:
with self.conn:
#insertar tipic id si no existe
cursor = self.conn.execute('INSERT OR IGNORE INTO topics (id, chat_id) VALUES (?, ?)', (topic_id,chat_id,))
#insertar mensaje
cursor = self.conn. execute(
'INSERT INTO tareas (topic_id, tarea) VALUES (?, ?)',
(topic_id, tarea))
return cursor.lastrowid
def mostrar_mensajes_topic(self, topic_id: str) -> List[Tuple[int, str]]:
if topic_id == 'None':
topic_id = 'General'
print("Motrar todos los mensajes en id:" + topic_id)
with self.conn:
cursor = self.conn.execute(
'select id,tarea FROM tareas WHERE topic_id = ?',
(topic_id,)
)
return cursor.fetchall()
def eliminar_tarea(self, tarea_id: int) -> bool:
with self.conn:
cursor = self.conn.execute(
'DELETE from tareas WHERE id = ?',
(tarea_id,)
)
return cursor.rowcount > 0
def modificar_tarea(self, tarea_id: int, tarea_modificada: str) -> bool:
with self.conn:
cursor = self.conn.execute(
'UPDATE tareas SET tarea = ? WHERE id = ?',
(tarea_modificada, tarea_id)
)
return cursor.rowcount > 0
class reunionesBaseDeDatos(BaseDeDatos):
def __init__ (self, nombre_db='appData/data_bot.db'):
super().__init__(nombre_db)
def guardar_reunion(self, topic_id: str, chat_id: str, titulo: str, fecha: str, hora: str, zona_horaria: str) -> None:
with self.conn:
#insertar tipic id si no existe
cursor = self.conn.execute('INSERT OR IGNORE INTO topics (id, chat_id) VALUES (?, ?)', (topic_id,chat_id,))
#insertar mensaje
cursor = self.conn. execute(
'INSERT INTO reuniones (topic_id, titulo, fecha, hora, zona_horaria ) VALUES (?, ?, ?, ?, ?)',
(topic_id, titulo, fecha, hora, zona_horaria))
return cursor.lastrowid
def mostrar_reuniones(self) -> List[Tuple[int, str]]:
with self.conn:
cursor = self.conn.execute(
'select id,titulo,fecha,hora,zona_horaria FROM reuniones'
)
return cursor.fetchall()
def traer_eventos(self, fecha: str) -> List[Tuple[int, str]]:
with self.conn:
cursor = self.conn.execute(
'select id,titulo,fecha,hora,zona_horaria from reuniones where fecha = ?',
(fecha,)
)
return cursor.fetchall()
def eliminar_evento(self, tarea_id: int) -> bool:
with self.conn:
cursor = self.conn.execute(
'DELETE from reuniones WHERE id = ?',
(tarea_id,)
)
return cursor.rowcount > 0
def modificar_titulo(self, reunion_id: int, nuevo_titulo: str) -> bool:
with self.conn:
cursor = self.conn.execute(
'UPDATE reuniones SET titulo = ? WHERE id = ?',
(nuevo_titulo, reunion_id)
)
return cursor.rowcount > 0
def modificar_fecha(self, reunion_id: int, nueva_fecha: str) -> bool:
with self.conn:
cursor = self.conn.execute(
'UPDATE reuniones SET fecha = ? WHERE id = ?',
(nueva_fecha, reunion_id)
)
return cursor.rowcount > 0
def modificar_hora(self, reunion_id: int, nueva_hora: str) -> bool:
with self.conn:
cursor = self.conn.execute(
'UPDATE reuniones SET hora = ? WHERE id = ?',
(nueva_hora, reunion_id)
)
return cursor.rowcount > 0
def obtener_chat_id(self) -> list:
with self.conn:
cursor = self.conn.execute(
'SELECT chat_id FROM topics LIMIT 1'
)
return cursor.fetchone()

View file

@ -1,21 +0,0 @@
version: "1.5"
services:
crud:
image: localhost/comandos-botija:latest
environment:
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN}
entrypoint: ["python", "main.py"]
volumes:
- botija_data:/app/appData
recordatorio:
image: localhost/comandos-botija:latest
environment:
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN}
REUNION_URL: ${REUNION_URL}
entrypoint: ["python", "recordar.py"]
volumes:
- botija_data:/app/appData
volumes:
botija_data:

View file

@ -1,48 +0,0 @@
Comandos
========
ToDo
----
Este comando genera un ToDo list con las siguientes funciones:
=============== =============================================
Acción Comando
=============== =============================================
Crear tarea /todo crear [tarea]
Mostrar tarea /todo ls
Modificar tares /todo modificar [id_tarea] [tarea modificada]
Eliminar tarea /todo [id tarea]
=============== =============================================
Agenda
------
Gestiona las reuniones programadas relacionadas con temas de MxOS:
permite crearlas, editarlas y eliminarlas. Además, notifica en el canal
general las próximas reuniones y envía recordatorios automáticos a dicho
canal con antelación de 60, 30, 15, 5 ,1 y 2 minutos antes del inicio de
cada reunión.
Funciones:
+------------------------------+--------------------------------------+
| Acción | Comando |
+==============================+======================================+
| Agenda reunión | /agenda crear [título de la reunión] |
| | [fecha en dd-mm-aaaa] [HH:MM CST] |
+------------------------------+--------------------------------------+
| Mostrar reunión vigente | /agenda ls |
+------------------------------+--------------------------------------+
| Modificar reunión por titulo | /agenda modificar [id_agenda] -t |
| | [título de la reunión] |
+------------------------------+--------------------------------------+
| Modificar reunión por fecha | /agenda modificar [id_agenda] -f |
| | [fecha en dd-mm-aaaa] |
+------------------------------+--------------------------------------+
| Modificar reunión por hora | /agenda modificar [id_agenda] -h |
| | [HH:MM] |
+------------------------------+--------------------------------------+
| Eliminar reunión | /agenda [id agenda] |
+------------------------------+--------------------------------------+

30
main.py
View file

@ -1,30 +0,0 @@
# main.py
import os
import logging
from telegram.ext import Application
from modulos.todo.crud_todo import TodoBot
from modulos.agenda.crud_agenda import AgendaBot
# from agenda.crud_agenda
def main():
logging.basicConfig(
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
)
token = os.getenv("TELEGRAM_BOT_TOKEN")
app = Application.builder().token(token).build()
# crear directorio para base de datos
if not os.path.exists('appData'):
print('Creando directorio para base de datos en ruta actual! \n')
os.mkdir('appData')
todo_bot = TodoBot(app)
agenda_bot = AgendaBot(app)
app.run_polling()
if __name__ == "__main__":
main()

View file

@ -1,311 +0,0 @@
#!/usr/bin/env python
# pylint: disable=unused-argument
"""
Este programa es software libre: usted puede redistribuirlo y/o modificarlo
bajo los términos de la Licencia Pública General de GNU publicada por la
Free Software Foundation, GPLv3.
Programa para guardar reuniones de MxOS y avisar en el grupo de telegram cada 60, 30, 15 min hasta que inicie la sesion, usando el framework de python-telegram-bot
Uso:
- Agenda reunión /agenda crear [Título de la reunión] [Fecha en dd-mm-aaaa] [HH:MM CST]
- Mostrar reunión vigente /agenda ls
- Modificar reunión por titulo /agenda modificar [id_agenda] -t [Título de la reunión]
- Modificar reunión por fecha /agenda modificar [id_agenda] -f [Fecha en dd-mm-aaaa]
- Modificar reunión por hora /agenda modificar [id_agenda] -h [HH:MM CST]
- Eliminar reunión /agenda [id agenda]
Precionar Ctrl-C para enviar señal de interrupcion del proceso en la linea de comandos
"""
import logging
import os, re, time
from datetime import datetime
from threading import Thread
from base_de_datos import reunionesBaseDeDatos
from telegram import ReplyKeyboardMarkup, ReplyKeyboardRemove, Update
from telegram.ext import (
Application,
CommandHandler,
ContextTypes,
ConversationHandler,
MessageHandler,
Updater,
)
# Habilitar logging
logging.basicConfig(
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
)
# set higher logging level for httpx to avoid all GET and POST requests being logged
logging.getLogger("httpx").setLevel(logging.WARNING)
# # crear directorio para base de datos
# if not os.path.exists('reunionesData'):
# print('Creando directorio para base de datos en ruta actual! \n')
# os.mkdir('reunionesData')
# inicializar base de datos
db = reunionesBaseDeDatos()
class AgendaBot:
def __init__(self, application):
self.logger = logging.getLogger(__name__)
application.add_handler(CommandHandler("agenda", self.agenda))
# funcion para comando /agenda
async def agenda(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
"""Iniciar conversacion y preguntar al usuario por el evento a crear."""
if not context.args:
await self.ayuda(update,context)
return
subcomando = context.args[0]
argumentos = context.args[1:]
if subcomando == "crear":
#Guardar evento
await self.crear(update,context, argumentos)
print()
elif subcomando == "ls":
await self.ls(update,context)
elif subcomando == "rm":
if re.search("[0-9]+", str(argumentos[0])):
await self.rm(update,context, int(argumentos[0]))
print()
else:
await update.message.reply_text("/todo rm [Número de tarea a eliminar] \n")
elif subcomando == "modificar":
await self.modificar(update,context,argumentos)
print()
elif subcomando == "ayuda":
await self.ayuda(update,context)
else:
await self.ayuda(update,context)
async def ayuda(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text("Opciones: \n" +
"/agenda crear [título de la reunión] [fecha en dd-mm-aaa] [HH:MM] CST \n" +
"/agenda modificar [id_eveto] -t [Nuevo titulo] \t Modifica evento por título\n" +
"/agenda modificar [id_eveto] -h [HH:MM] \t Modifica evento por hora \n" +
"/agenda modificar [id_eveto] -f [dd-mm-aaa] \t Modifica evento por fecha \n" +
"/agenda ls \t\t lista eventos\n" +
"/agenda rm [Número de evento a eliminar] \t\tElimina evento\n")
return ConversationHandler.END
def _formato_fecha(self, fecha: str) -> bool:
if re.search("^(0[1-9]|[12][0-9]|3[01])-(0[1-9]|1[0-2])-(\\d{4})$", fecha):
return True
else:
return False
def _verificar_fecha(self, fecha: str) -> bool:
if datetime.strptime(fecha, "%d-%m-%Y").date() >= datetime.now().date():
return True
else:
return False
def _formato_hora(self, hora: str) -> bool:
if re.search("^([01][0-9]|2[0-3]):[0-5][0-9]$", hora):
return True
else:
return False
def _formato_zona(self, zona_horaria: str) -> bool:
if re.search("UTC$|utc$|CST$|cst$", zona_horaria):
return True
else:
return False
async def crear(self, update: Update, context: ContextTypes.DEFAULT_TYPE, argumentos) -> int:
# Quien envia la solicitud
user = update.message.from_user
#Traer el topic id
topic_id = update.message.message_thread_id
# traer el chat_id
chat_id = update.message.chat.id
#enviar mensaje si no se pasa nada al comando
if len(argumentos) <= 0:
await update.message.reply_text("/agenda crear [título de la reunión] [fecha en dd-mm-aaa] [HH:MM] CST \n")
return
# Parsear los datos de entrada
zona_horaria = argumentos.pop()
hora = argumentos.pop()
fecha = argumentos.pop()
titulo = " ".join(argumentos)
self.logger.info("********Ingresando nuevo evento********")
self.logger.info("%s ingreso: %s", user.first_name, titulo)
self.logger.info("Topic id es: " + str(topic_id))
#Verificar si el formato de la fecha es valida
if self._formato_fecha(fecha):
if self._verificar_fecha(fecha): #verificar si la fecha no esta en el pasado
self.logger.info(f"Día del evento: {fecha}")
else:
await update.message.reply_text("Fecha no valida, el evento está en el pasado. \n\n")
self.logger.info(f"El usuario {user.first_name} introdujo {fecha} hoy es {datetime.now().date()}")
self.logger.info("Fecha no valida, el evento está en el pasado.")
return
else:
await update.message.reply_text("Fecha no válida, introduce dd-mm-aaaa \n\n")
return
#Verifiar si hora es valida
if self._formato_hora(str(hora)):
print("Hora a guardar: "+ hora)
else:
await update.message.reply_text("Formato de hora no válido, introduce HH:MM \n\n")
return
#Verificar si es zona horaria valida
if self._formato_zona(zona_horaria):
print("Zona horaria: "+ zona_horaria)
else:
await update.message.reply_text("Zona horaria no válida, introduce utc|UTC|cst|CST \n\n")
return
if str(topic_id) == "None":
topic_id = "General"
#Guardar evento
db.guardar_reunion(topic_id, chat_id, titulo, fecha, hora, zona_horaria)
await update.message.reply_text("¡Listo!, la reunion fue guardada.\n\n")
return ConversationHandler.END
async def ls(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
"""Listar las reuniones guardadas"""
#esto trae el usuario
user = update.message.from_user
#Traer todas las reuniones
topic_messages = db.mostrar_reuniones()
#Juntar las columnas reunion fecha hora y zona horaria
reuniones = []
for fila in topic_messages:
reuniones.append(("Reunion "+str(fila[1]) +" el día "+str(fila[2]) +" a las "+ str(fila[3]+" "+str(fila[4]))))
if len(reuniones) > 0:
mensaje_final = ''
for mensaje in range(0, len(reuniones)):
mensaje_final += "\n"+str(mensaje)+".- "+reuniones[mensaje]
self.logger.info("******** consulta de reuniones ********")
self.logger.info(user.first_name + " consulto reuniones, las cuales son:")
self.logger.info(mensaje_final + "\n")
await update.message.reply_text("Reuniones a recordar:" + mensaje_final)
else:
await update.message.reply_text("Todavia no hay reuniones, ¿quieres agendar una? \n\n /agenda crear [título de la reunión] [fecha en dd-mm-aaa] CST")
return ConversationHandler.END
async def rm(self, update: Update, context: ContextTypes.DEFAULT_TYPE, numero_de_mensaje) -> int:
#Elimina evento agendado derivado del response de /agenda ls
#esto trae el usuario
user = update.message.from_user
#obteniendo mensajes
topic_messages = db.mostrar_reuniones()
if (int(numero_de_mensaje) >= 0 and int(numero_de_mensaje) < len(topic_messages)):
self.logger.info("-------Eliminando Evento------")
self.logger.info(topic_messages[int(numero_de_mensaje)])
mensaje_a_borrar = topic_messages[int(numero_de_mensaje)]
id_mensaje_a_borrar = mensaje_a_borrar[0]
if db.eliminar_evento(id_mensaje_a_borrar):
self.logger.info(user.first_name + " eliminió reunion")
await update.message.reply_text("Recordatorio eliminado :) \n")
elif len(topic_messages) == 0:
await update.message.reply_text("Todavia no hay eventos, ¿quieres agendar uno? \n\n /agenda crear [título de la reunión] [fecha en dd-mm-aaa] [HH:MM] CST")
else:
await update.message.reply_text("Introduce un ID valido :) \n")
return ConversationHandler.END
async def modificar(self, update: Update, context: ContextTypes.DEFAULT_TYPE, argumentos) -> int:
"""Modificar reunion
Los argumentos que se traen son:
por título: [id, fecha]
por fecha: [id, dd-mm-aaaa]
por hora: [id, HH:MM]
"""
#id de mensaje
id_mensaje = argumentos.pop(0)
#argumento del comando
comando = argumentos.pop(0)
#obteniendo mensaje a modificar
accion = " ".join(argumentos)
# esto trae el user
user = update.message.from_user
# esto trae el topic id
topic_id = update.message.message_thread_id
#Traer todas las reuniones
topic_messages = db.mostrar_reuniones()
if re.search("[0-9]+", str(id_mensaje)):
if (int(id_mensaje) >= 0 and int(id_mensaje) < len(topic_messages)):
tupla_mensajes = topic_messages[int(id_mensaje)]
if comando == "-t":
self.logger.info(f"usuario {user.first_name} modificó título: '{tupla_mensajes[1]}' a {accion}")
db.modificar_titulo(tupla_mensajes[0],accion)
await update.message.reply_text("¡Título actualizado! \n\n")
elif comando == "-h":
if self._formato_hora(accion):
self.logger.info(f"usuario {user.first_name} modificó hora: '{tupla_mensajes[3]}' a {accion}")
db.modificar_hora(tupla_mensajes[0],accion)
await update.message.reply_text("¡Hora actualizada! \n\n")
else:
await update.message.reply_text("Formato de hora no válido, introduce HH:MM \n\n")
elif comando == "-f":
if self._formato_fecha(accion) or self._verificar_fecha(accion):
self.logger.info(f"usuario {user.first_name} modificó fecha: '{tupla_mensajes[2]}' a {accion}")
db.modificar_fecha(tupla_mensajes[0],accion)
await update.message.reply_text("¡Fecha actualizada! \n\n")
else:
await update.message.reply_text("Fecha no válida, introduce dd-mm-aaaa \n\n")
else:
await update.message.reply_text("Modificar reunión por titulo \n /agenda modificar [id_agenda] -t [Título de la reunión] \n" +
"Modificar reunión por fecha \n /agenda modificar [id_agenda] -f [Fecha en dd-mm-aaaa] \n" +
"Modificar reunión por hora \n /agenda modificar [id_agenda] -h [HH:MM CST] \n" )
else:
await update.message.reply_text("Introduce un ID valido")
else:
await update.message.reply_text("Modificar reunión por titulo \n /agenda modificar [id_agenda] -t [Título de la reunión] \n" +
"Modificar reunión por fecha \n /agenda modificar [id_agenda] -f [Fecha en dd-mm-aaaa] \n" +
"Modificar reunión por hora \n /agenda modificar [id_agenda] -h [HH:MM CST] \n" )
return ConversationHandler.END
async def cerrar(self):
db.cerrar()

View file

@ -1,162 +0,0 @@
#!/usr/bin/env python
# pylint: disable=unused-argument
"""
Este programa es software libre: usted puede redistribuirlo y/o modificarlo
bajo los términos de la Licencia Pública General de GNU publicada por la
Free Software Foundation, GPLv3.
Programa para guardar reuniones de MxOS y avisar en el grupo de telegram cada 60, 30, 15 min hasta que inicie la sesion, usando el framework de python-telegram-bot
"""
from telegram import Bot
from telegram.error import TelegramError
from datetime import datetime
from base_de_datos import reunionesBaseDeDatos
import logging
import os, time
import asyncio
import pytz
from typing import Dict, Set, Tuple
# Configuración básica de logging
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO
)
logger = logging.getLogger(__name__)
# Configuración
TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN")
URL = os.environ.get("REUNION_URL")
ZONAS_HORARIAS = {
"CST": "America/Mexico_City",
"EST": "America/Cancun",
"MST": "America/Hermosillo",
"PT": "America/Tijuana"
}
class EventNotifier:
def __init__(self):
self.db = reunionesBaseDeDatos()
self.bot = Bot(token=TOKEN)
# Diccionario para registrar qué notificaciones ya se enviaron
self.notificaciones_enviadas: Dict[Tuple[str, str], Set[int]] = {}
# Intervalos de notificación en minutos (ordenados de mayor a menor)
self.INTERVALOS_NOTIFICACION = [60, 30, 10, 5, 2, 0]
def obtener_hora_actual(self, zona: str) -> str:
"""Obtiene la hora actual en la zona horaria especificada."""
tz = pytz.timezone(ZONAS_HORARIAS.get(zona, "America/Mexico_City"))
return datetime.now(tz).strftime("%H:%M")
def horas_a_minutos(self, hora: str) -> int:
"""Convierte una hora en formato HH:MM a minutos totales."""
try:
horas, minutos = map(int, hora.split(':'))
return horas * 60 + minutos
except (ValueError, AttributeError):
return 0
def minutos_restantes(self, hora_evento: str, hora_actual: str) -> int:
"""Calcula los minutos que faltan para el evento."""
return self.horas_a_minutos(hora_evento) - self.horas_a_minutos(hora_actual)
async def enviar_notificacion(self, evento: Tuple, minutos_restantes: int) -> bool:
#Envía la notificación al chat de Telegram
id, nombre_evento, _, hora_evento, zona = evento
evento_key = (id, nombre_evento, hora_evento, zona)
# Verificar si ya se envió esta notificación
if evento_key not in self.notificaciones_enviadas:
self.notificaciones_enviadas[evento_key] = set()
if minutos_restantes in self.notificaciones_enviadas[evento_key]:
return False
try:
logger.info(f"Minutos restantes: {minutos_restantes} para {nombre_evento}")
if minutos_restantes == 0:
mensaje = f"⚠️ ¡¡CAELE a la reunión!!: {nombre_evento} \n\n ESTÁ POR INICIAR estamos en: {URL}"
await self.bot.send_message(chat_id=CHAT_ID, text=mensaje)
logger.info(f"Notificación enviada: {mensaje}")
logger.info(f"Eliminando reunion: {nombre_evento} \n")
self.db.eliminar_evento(id)
elif minutos_restantes == 60 or minutos_restantes == 30 or minutos_restantes == 15 or minutos_restantes == 10 or minutos_restantes == 5 or minutos_restantes == 2:
mensaje = f"🔔 Recordatorio: El evento {nombre_evento} inicia en {minutos_restantes} minutos"
await self.bot.send_message(chat_id=CHAT_ID, text=mensaje)
logger.info(f"Notificación enviada: {mensaje}\n")
elif minutos_restantes > 0 and minutos_restantes < 2:
mensaje = f"🔔 Recordatorio: El evento {nombre_evento} inicia en {minutos_restantes} minuto"
await self.bot.send_message(chat_id=CHAT_ID, text=mensaje)
logger.info(f"Notificación enviada: {mensaje}\n")
self.notificaciones_enviadas[evento_key].add(minutos_restantes)
return True
except TelegramError as e:
logger.error(f"Error al enviar mensaje: {e}")
return False
def necesita_notificacion(self, minutos_restantes: int) -> bool:
# Determina si se debe enviar notificación para los minutos restantes dados.
return any(
abs(minutos_restantes - intervalo) <= 1 # Margen de 1 minuto
for intervalo in self.INTERVALOS_NOTIFICACION
)
async def procesar_evento(self, evento: Tuple):
#Procesa un único evento y envía notificaciones según sea necesario
id, nombre, fecha, hora_evento, zona = evento
hora_actual = self.obtener_hora_actual(zona)
minutos = self.minutos_restantes(hora_evento, hora_actual)
if self.necesita_notificacion(minutos):
await self.enviar_notificacion(evento, minutos)
elif minutos < 0:
# Limpiar eventos pasados del registro
evento_key = (nombre, hora_evento, zona)
if evento_key in self.notificaciones_enviadas:
del self.notificaciones_enviadas[evento_key]
async def procesar_eventos(self):
#Procesa todos los eventos del día actual
fecha_actual = datetime.now().strftime("%d-%m-%Y")
eventos_hoy = self.db.traer_eventos(fecha_actual)
if not eventos_hoy:
logger.debug("No hay eventos programados para hoy")
return
chat_ID = self.db.obtener_chat_id()
global CHAT_ID
CHAT_ID = str(chat_ID[0])
# Procesar cada evento concurrentemente
await asyncio.gather(*[self.procesar_evento(evento) for evento in eventos_hoy])
async def run(self):
"""Ejecuta el bot en un bucle infinito."""
logger.info("Iniciando bot de notificaciones de eventos")
try:
while True:
await self.procesar_eventos()
except asyncio.CancelledError:
logger.info("Bot detenido por señal de interrupción")
except Exception as e:
logger.error(f"Error inesperado: {e}")
finally:
logger.info("Bot detenido")
# if __name__ == '__main__':
# notifier = EventNotifier()
# try:
# asyncio.run(notifier.run())
# except KeyboardInterrupt:
# notifier.db.cerrar()
# logger.info("Bot detenido por el usuario")

View file

@ -1,201 +0,0 @@
#!/usr/bin/env python
# pylint: disable=unused-argument
"""
Este programa es software libre: usted puede redistribuirlo y/o modificarlo
bajo los términos de la Licencia Pública General de GNU publicada por la
Free Software Foundation, GPLv3.
Programa para hacer un todo list en topics de telegram, usando el framework de python-telegram-bot
Uso:
- Crear tarea /todo crear [tarea]
- Mostrar tarea /todo ls
- Modificar tares /todo modificar [id_tarea] [tarea modificada]
- Eliminar tarea /todo [id tarea]
Precionar Ctrl-C para enviar señal de interrupcion del proceso en la linea de comandos
"""
import logging
import os, re
from base_de_datos import ToDoBaseDeDatos
from telegram import ReplyKeyboardMarkup, ReplyKeyboardRemove, Update
from telegram.ext import (
Application,
CommandHandler,
ContextTypes,
ConversationHandler,
MessageHandler,
)
# Habilitar logging
logging.basicConfig(
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
)
# set higher logging level for httpx to avoid all GET and POST requests being logged
logging.getLogger("httpx").setLevel(logging.WARNING)
# #inicializar base de datos
db = ToDoBaseDeDatos()
class TodoBot:
def __init__(self, application):
self.logger = logging.getLogger(__name__)
application.add_handler(CommandHandler("todo", self.todo))
async def todo(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
"""Iniciar conversacion y preguntar al usuario por la lista a hacer."""
if not context.args:
await self.ayuda(update,context)
return
subcomando = context.args[0]
argumentos = context.args[1:]
if subcomando == "crear":
tareas_a_guardar = " ".join(argumentos)
await self.crear(update,context, tareas_a_guardar)
print()
elif subcomando == "ls":
await self.ls(update,context)
print()
elif subcomando == "rm":
if re.search("[0-9]+", str(argumentos[0])):
await self.rm(update,context, int(argumentos[0]))
print()
else:
await update.message.reply_text("/todo rm [Número de tarea a eliminar] \n")
print()
elif subcomando == "modificar":
await self.modificar(update,context,argumentos)
print()
elif subcomando == "ayuda":
await self.ayuda(update,context)
else:
await self.ayuda(update,context)
async def ayuda(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text("Opciones: \n" +
"/todo crear [tarea] \t Crea una tarea en el topico donde estas \n" +
"/todo modificar [id_tarea] [Nueva Tarea] \t Modifica tarea por id \n" +
"/todo ls \t\t lista tareas\n" +
"/todo rm [Número de tarea a eliminar] \t\tElimina tarea\n")
return ConversationHandler.END
async def crear(self, update: Update, context: ContextTypes.DEFAULT_TYPE, argumento) -> int:
"""Se crea la todo list"""
user = update.message.from_user
#Traer el topic id
topic_id = update.message.message_thread_id
# traer el chat_id
chat_id = update.message.chat.id
self.logger.info("++++++++ Guardando ToDo list ++++++++")
self.logger.info("%s ingreso: %s", user.first_name, argumento)
self.logger.info("Topic id es:" + str(topic_id))
if str(topic_id) == "None":
topic_id = "General"
#Guardar mensajes
db.insertar_topic(topic_id, chat_id, argumento)
await update.message.reply_text("¡Listo!, tu tarea fue guardada.\n\n")
return ConversationHandler.END
async def ls(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
"""Listar todos los mensajes que tiene un topico"""
# esto trae el topic id
topic_id = update.message.message_thread_id
topic_messages = db.mostrar_mensajes_topic(str(topic_id))
#obtener solo tareas de la tupla
tareas = [fila[1] for fila in topic_messages]
if len(tareas) > 0:
mensaje_final = ''
for mensaje in range(0, len(tareas)):
mensaje_final += "\n"+str(mensaje)+".- "+tareas[mensaje]
self.logger.info(mensaje_final)
await update.message.reply_text("Tareas que hay en el topic:" + mensaje_final)
else:
await update.message.reply_text("Todavia no hay tareas en este topico, ¿quieres crear una? \n\n /todo crear [area a guardar]")
return ConversationHandler.END
async def rm(self, update: Update, context: ContextTypes.DEFAULT_TYPE, numero_de_mensaje) -> int:
"""Elimina tarea en el topico por numero en /todo ls"""
# esto trae el topic id
topic_id = update.message.message_thread_id
#obteniendo mensajes
topic_messages = db.mostrar_mensajes_topic(str(topic_id))
if (int(numero_de_mensaje) >= 0 and int(numero_de_mensaje) < len(topic_messages)):
self.logger.info("++++++++ Borrar ToDo List ++++++++")
self.logger.info(topic_messages[int(numero_de_mensaje)])
mensaje_a_borrar = topic_messages[int(numero_de_mensaje)]
id_mensaje_a_borrar = mensaje_a_borrar[0]
self.logger.info(id_mensaje_a_borrar)
if db.eliminar_tarea(id_mensaje_a_borrar):
await update.message.reply_text("Tarea eliminada :) \n")
elif len(topic_messages) == 0:
await update.message.reply_text("Todavia no hay tareas en este topico, ¿quieres crear una? \n\n /todo crear [tarea a guardar]")
else:
await update.message.reply_text("Introduce un ID valido :) \n")
return ConversationHandler.END
async def modificar(self, update: Update, context: ContextTypes.DEFAULT_TYPE, argumentos) -> int:
"""Modifica tarea en el topico
Los argumentos que se traen son: [id, mensaje a modificar]
"""
#id de mensaje
id_de_mensaje = argumentos.pop(0)
#obteniendo mensaje a modificar
mensaje_a_modificar = " ".join(argumentos)
# esto trae el topic id
topic_id = update.message.message_thread_id
if re.search("[0-9]+", str(id_de_mensaje)):
#obteniendo mensajes
topic_messages = db.mostrar_mensajes_topic(str(topic_id))
if (int(id_de_mensaje) >= 0 and int(id_de_mensaje) < len(topic_messages)):
self.logger.info("Listando mensajes: \n")
self.logger.info(topic_messages[int(id_de_mensaje)])
tupla_mensajes = topic_messages[int(id_de_mensaje)]
id_mensaje_a_modificar = tupla_mensajes[0]
self.logger.info("ID mensaje a modificar: "+ str(id_mensaje_a_modificar))
#Modificar tarea
if db.modificar_tarea(int(id_mensaje_a_modificar), str(mensaje_a_modificar)):
await update.message.reply_text("Tarea modificada :) \n")
else:
await update.message.reply_text("Error al modificar tarea :( \n")
else:
await update.message.reply_text("Introduce un ID valido : \n")
else:
await update.message.reply_text("/todo modificar [id_tarea] [Nueva Tarea]")
return ConversationHandler.END

View file

@ -1,17 +0,0 @@
import os
import logging
import asyncio
import logging
from telegram.ext import Application
from modulos.agenda.recordatorio import EventNotifier
# from agenda.crud_agenda
if __name__ == '__main__':
logger = logging.getLogger(__name__)
notifier = EventNotifier()
try:
asyncio.run(notifier.run())
except KeyboardInterrupt:
notifier.db.cerrar()
logger.info("Bot detenido por el usuario")

View file

@ -1,2 +0,0 @@
python-telegram-bot
pytz

10
todo/Containerfile Normal file
View file

@ -0,0 +1,10 @@
FROM registry.access.redhat.com/ubi9/python-312:9.6-1747333115
RUN mkdir /opt/app-root/src/todoData
COPY base_de_datos.py .
COPY todo_bot.py .
COPY requirements.txt .
RUN pip install -r requirements.txt
ENTRYPOINT ["python","todo_bot.py"]

97
todo/README.md Normal file
View file

@ -0,0 +1,97 @@
# ToDo Bot
Este Bot genera un todo list con las siguientes funciones:
|Acción |Comando |
|--- | --- |
|Crear tarea |/todo crear [tarea] |
|Mostrar tarea |/todo ls |
|Modificar tarea|/todo modificar [id_tarea] [tarea modificada]|
|Eliminar tarea |/todo [id tarea] |
## USO
### Python
Declarar variable de entorno para TOKEN de telegram
~~~bash
export TELEGRAM_BOT_TOKEN=<TOKEN>
~~~
Ejecutar:
~~~bash
python todo_bot.py
~~~
El bot estará en modo escucha hasta recibir un comando
~~~bash
[anduril@elendil todo]$ python todo_bot.py
2025-06-01 11:49:07,297 - telegram.ext.Application - INFO - Application started
~~~
Al iniciar la aplicación, se creará una base de datos en sqlite llamada `todo_bot.db` en el directorio `todoData`, la cual llevará el registro de todas las tareas a guardar
Para detener el bot desde terminal presionar CTRL+C
>Nota: se recomienda ejecutar el script en una sesión de tmux o nohup para ejecución del script en segundo plano
### Podman
Ejecutar los siguientes pasos con un usuario que no sea root
1. Clonar el repositorio
2. Ingresar al repositorio y construir la imagen de contenedor
~~~bash
$ podman build -t todo_telegram .
~~~
3. Crear almacenamiento persistente
~~~bash
podman create volume todo
~~~
>: nota: los archivos del volumen de podman pueden ser exportados:
>: `podman volume export <nombre_del_volumen> --output <ruta_del_archivo_tar>`
4. Ejecutar contenedor
~~~bash
$ podman run -it -d --name todo -v todo:/todoData:Z -e TELEGRAM_BOT_TOKEN=<token> <tag_imagen_contenedor_construida>
~~~
Ejemplo:
~~~bash
[devops@zoro todo]$ podman run -it -d --name todo -v todo:/opt/app-root/src/todoData:Z e TELEGRAM_BOT_TOKEN=<token> localhost/todo:latest
6cfa05fa1fd081888c23cfdd859bd6229433d2d4b05e9ac5536b96ec49cb4c25
~~~
>Nota: La imagen de python ubi RHEL usa el workdir en /opt/app-root/src/
5. Verificar el funcionamiento
Verificar el funcionamiento con `podman ps` no debe tener status de `Exited`
~~~bash
podman ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c9d054b52482 localhost/todo:latest 4 seconds ago Up 4 seconds 8080/tcp todo
~~~
- Verificar si está recibiendo llamadas
~~~bash
podman logs <id_del_contenedor>
~~~
Ejemplo:
~~~bash
[devops@zoro todo]$ podman logs `c9d054b52482`
2025-06-01 20:09:09,656 - telegram.ext.Application - INFO - Application started
~~~

96
todo/base_de_datos.py Normal file
View file

@ -0,0 +1,96 @@
#!/usr/bin/env python
"""
Este programa es software libre: usted puede redistribuirlo y/o modificarlo
bajo los términos de la Licencia Pública General de GNU publicada por la
Free Software Foundation, GPLv3.
Programa para hacer un todo list en topics de telegram, usando el framework de python-telegram-bot
Uso:
- Crear tarea /todo crear [tarea]
- Mostrar tarea /todo ls
- Modificar tarea /todo modificar [id_tarea] [tarea modificada]
- Eliminar tarea /todo [id tarea]
Presionar Ctrl-C para enviar señal de interrupción del proceso en la línea de comandos
"""
import sqlite3
from typing import List, Tuple
class ToDoBaseDeDatos:
def __init__(self, nombre_db='todoData/todo_bot.db') -> None:
self.conn =sqlite3.connect(nombre_db)
self._crear_tablas()
def cerrar(self) -> None:
self.conn.close()
def _crear_tablas(self) -> None:
with self.conn:
#se usó varchar porque telegram en tópico GENERAL tienen valor NONE
self.conn.execute('''
CREATE TABLE IF NOT EXISTS topics (
id varchar(100) PRIMARY KEY
)
''')
self.conn.execute('''
CREATE TABLE IF NOT EXISTS tareas (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tarea varchar(255) not null,
creacion DATETIME DEFAULT CURRENT_TIMESTAMP,
topic_id varchar(100) not null,
FOREIGN KEY (topic_id) REFERENCES topics(id) ON DELETE CASCADE
)
''')
def insertar_topic(self, topic_id: str, tarea: str) -> None:
with self.conn:
#insertar tipic id si no existe
cursor = self.conn.execute('INSERT OR IGNORE INTO topics (id) VALUES (?)', (topic_id,))
#insertar mensaje
cursor = self.conn. execute(
'INSERT INTO tareas (topic_id, tarea) VALUES (?, ?)',
(topic_id, tarea))
return cursor.lastrowid
def mostrar_mensajes_topic(self, topic_id: str) -> List[Tuple[int, str]]:
if topic_id == 'None':
topic_id = 'General'
print("Mostrar todos los mensajes en id:" + topic_id)
with self.conn:
cursor = self.conn.execute(
'select id,tarea FROM tareas WHERE topic_id = ?',
(topic_id,)
)
return cursor.fetchall()
def eliminar_tarea(self, tarea_id: int) -> bool:
with self.conn:
cursor = self.conn.execute(
'DELETE from tareas WHERE id = ?',
(tarea_id,)
)
return cursor.rowcount > 0
def modificar_tarea(self, tarea_id: int, tarea_modificada: str) -> bool:
with self.conn:
cursor = self.conn.execute(
'UPDATE tareas SET tarea = ? WHERE id = ?',
(tarea_modificada, tarea_id)
)
return cursor.rowcount > 0

1
todo/requirements.txt Normal file
View file

@ -0,0 +1 @@
python-telegram-bot

214
todo/todo_bot.py Normal file
View file

@ -0,0 +1,214 @@
#!/usr/bin/env python
# pylint: disable=unused-argument
"""
Este programa es software libre: usted puede redistribuirlo y/o modificarlo
bajo los términos de la Licencia Pública General de GNU publicada por la
Free Software Foundation, GPLv3.
Programa para hacer un todo list en tópicos de telegram, usando el framework de python-telegram-bot
Uso:
- Crear tarea /todo crear [tarea]
- Mostrar tarea /todo ls
- Modificar tarea /todo modificar [id_tarea] [tarea modificada]
- Eliminar tarea /todo [id tarea]
Presionar Ctrl-C para enviar señal de interrupción del proceso en la línea de comandos
"""
import logging
import os, re
from base_de_datos import ToDoBaseDeDatos
from telegram import ReplyKeyboardMarkup, ReplyKeyboardRemove, Update
from telegram.ext import (
Application,
CommandHandler,
ContextTypes,
ConversationHandler,
MessageHandler,
)
# Habilitar logging
logging.basicConfig(
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
)
# set higher logging level for httpx to avoid all GET and POST requests being logged
logging.getLogger("httpx").setLevel(logging.WARNING)
logger = logging.getLogger(__name__)
# crear directorio para base de datos
if not os.path.exists('todoData'):
print('Creando directorio para base de datos en ruta actual! \n')
os.mkdir('todoData')
#inicializar base de datos
db = ToDoBaseDeDatos()
async def todo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
"""Iniciar conversación y preguntar al usuario por la lista a hacer."""
if not context.args:
await ayuda(update,context)
return
subcomando = context.args[0]
argumentos = context.args[1:]
if subcomando == "crear":
tareas_a_guardar = " ".join(argumentos)
await crear(update,context, tareas_a_guardar)
elif subcomando == "ls":
await ls(update,context)
elif subcomando == "rm":
if re.search("[0-9]+", str(argumentos[0])):
await rm(update,context, int(argumentos[0]))
else:
await update.message.reply_text("/todo rm [Número de tarea a eliminar] \n")
elif subcomando == "modificar":
await modificar(update,context,argumentos)
elif subcomando == "ayuda":
await ayuda(update,context)
else:
await ayuda(update,context)
async def ayuda(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text("Opciones: \n" +
"/todo crear [tarea] \t Crea una tarea en el tópico donde estás \n" +
"/todo modificar [id_tarea] [Nueva Tarea] \t Modifica tarea por id \n" +
"/todo ls \t\t lista tareas\n" +
"/todo rm [Número de tarea a eliminar] \t\tElimina tarea\n")
return ConversationHandler.END
async def crear(update: Update, context: ContextTypes.DEFAULT_TYPE, argumento) -> int:
"""Se crea la todo list"""
user = update.message.from_user
#Traer el topic id
topic_id = update.message.message_thread_id
logger.info("%s ingreso: %s", user.first_name, argumento)
logger.info("Topic id es:" + str(topic_id))
if str(topic_id) == "None":
topic_id = "General"
#guardar mensajes
db.insertar_topic(topic_id, argumento)
await update.message.reply_text("¡Listo!, tu tarea fue guardada\n\n")
return ConversationHandler.END
async def ls(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
"""Listar todos los mensajes que tiene un topico"""
# esto trae el topic id
topic_id = update.message.message_thread_id
topic_messages = db.mostrar_mensajes_topic(str(topic_id))
#obtener solo tareas de la tupla
tareas = [fila[1] for fila in topic_messages]
if len(tareas) > 0:
mensaje_final = ''
for mensaje in range(0, len(tareas)):
mensaje_final += "\n"+str(mensaje)+".- "+tareas[mensaje]
logger.info(mensaje_final)
await update.message.reply_text("Tareas que hay en el tópico:" + mensaje_final)
else:
await update.message.reply_text("Todavía no hay tareas en este tópico, ¿quieres crear una? \n\n /todo crear [tarea a guardar]")
return ConversationHandler.END
async def rm(update: Update, context: ContextTypes.DEFAULT_TYPE, numero_de_mensaje) -> int:
"""Elimina tarea en el topico por numero en /todo ls"""
# esto trae el topic id
topic_id = update.message.message_thread_id
#obteniendo mensajes
topic_messages = db.mostrar_mensajes_topic(str(topic_id))
if (int(numero_de_mensaje) >= 0 and int(numero_de_mensaje) < len(topic_messages)):
logger.info(topic_messages[int(numero_de_mensaje)])
mensaje_a_borrar = topic_messages[int(numero_de_mensaje)]
id_mensaje_a_borrar = mensaje_a_borrar[0]
logger.info(id_mensaje_a_borrar)
if db.eliminar_tarea(id_mensaje_a_borrar):
await update.message.reply_text("Tarea eliminada :) \n")
elif len(topic_messages) == 0:
await update.message.reply_text("Todavía no hay tareas en este tópico, ¿quieres crear una? \n\n /todo crear [tarea a guardar]")
else:
await update.message.reply_text("Introduce un ID válido :) \n")
return ConversationHandler.END
async def modificar(update: Update, context: ContextTypes.DEFAULT_TYPE, argumentos) -> int:
"""Modifica tarea en el topico
Los argumentos que se traen son: [id, mensaje a modificar]
"""
#id de mensaje
id_de_mensaje = argumentos.pop(0)
#obteniendo mensaje a modificar
mensaje_a_modificar = " ".join(argumentos)
# esto trae el topic id
topic_id = update.message.message_thread_id
if re.search("[0-9]+", str(id_de_mensaje)):
#obteniendo mensajes
topic_messages = db.mostrar_mensajes_topic(str(topic_id))
if (int(id_de_mensaje) >= 0 and int(id_de_mensaje) < len(topic_messages)):
logger.info("Listando mensajes: \n")
logger.info(topic_messages[int(id_de_mensaje)])
tupla_mensajes = topic_messages[int(id_de_mensaje)]
id_mensaje_a_modificar = tupla_mensajes[0]
logger.info("ID mensaje a modificar: "+ str(id_mensaje_a_modificar))
#Modificar tarea
if db.modificar_tarea(int(id_mensaje_a_modificar), str(mensaje_a_modificar)):
await update.message.reply_text("Tarea modificada :) \n")
else:
await update.message.reply_text("Error al modificar tarea :( \n")
else:
await update.message.reply_text("Introduce un ID válido : \n")
else:
await update.message.reply_text("/todo modificar [id_tarea] [Nueva Tarea]")
return ConversationHandler.END
def main() -> None:
try:
# Crear aplicacion y pasar variable de entorno TELEGRAM_BOT_TOKEN
application = Application.builder().token(os.environ.get("TELEGRAM_BOT_TOKEN")).build()
# Ejecutando la funcion principal
application.add_handler(CommandHandler("todo", todo))
# Ejecuta el bot hasta que se presione Ctrl-C
application.run_polling(allowed_updates=Update.ALL_TYPES)
finally:
db.cerrar()
if __name__ == "__main__":
main()