Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2edfc45226 |
18 changed files with 428 additions and 1136 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -1,5 +1,3 @@
|
|||
appData/*
|
||||
__pycache__
|
||||
|
||||
modulos/agenda/__pycache__
|
||||
modulos/todo/__pycache__
|
||||
todo/todoData*
|
||||
todo/__pycache__*
|
||||
|
|
|
|||
|
|
@ -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
8
README.md
Normal 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)
|
||||
118
README.rst
118
README.rst
|
|
@ -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
|
||||
203
base_de_datos.py
203
base_de_datos.py
|
|
@ -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()
|
||||
21
compose.yml
21
compose.yml
|
|
@ -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:
|
||||
|
|
@ -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
30
main.py
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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")
|
||||
|
|
@ -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
|
||||
|
||||
17
recordar.py
17
recordar.py
|
|
@ -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")
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
python-telegram-bot
|
||||
pytz
|
||||
10
todo/Containerfile
Normal file
10
todo/Containerfile
Normal 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
97
todo/README.md
Normal 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
96
todo/base_de_datos.py
Normal 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
1
todo/requirements.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
python-telegram-bot
|
||||
214
todo/todo_bot.py
Normal file
214
todo/todo_bot.py
Normal 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()
|
||||
Loading…
Reference in a new issue