Gestor completo de tareas (To-do List) en Telegram con el lenguaje de programación Python. Con este código Python podremos dotar a nuestro Bot de Telegram de un gestor de tareas: alta de tareas, consulta de tareas, resolver tareas, reabrir, eliminar tareas, etc.
- Requisitos para desarrollar un gestor de tareas To-do List en Bot de Telegram con Python.
- Desarrollo del programa en Python para gestionar las tareas To-do List en Telegram.
- Funcionamiento de la aplicación Python y el Bot para Gestor de Tareas To-Do List.
Requisitos para desarrollar un gestor de tareas To-do List en Bot de Telegram con Python
Necesitaremos disponer de un Bot de Telegram y su token de acceso. En el siguiente enlace explicamos cómo crear un Bot de Telegram y obtener el token necesario para trabajar con él:
Para el desarrollo del programa en Python sólo necesitaremos cualquier editor o algún IDE que soporte Python, como por ejemplo Visual Studio Code.
Para el desarrollo de la aplicación Python usaremos varias librerías que debemos tener instaladas, como por ejemplo: requests, sqlite3, json. Para instalarlas, ejecutaremos estos comandos:
1 2 3 |
pip3 install requests pip3 install sqlite3 pip3 install json |
Desarrollo del programa en Python para gestionar las tareas To-do List en Telegram
Para almacenar los datos, las tareas de cada usuario, de cada chat de Telegram, el propio programa, en el primer inicio, creará una base de datos SQLite con la tabla «tareas», y tendrá una clase para realizar todas las operaciones sobre la base de datos SQLite:
- Crear tabla tareas.
- Insertar una nueva tarea.
- Eliminar una tarea.
- Mostrar las tareas no resueltas.
- Mostrar las tareas resueltas y no resueltas.
- Resolver una tarea.
- Reabrir una tarea resuelta.
Para ello, crearemos un fichero Python con el nombre bd.py y el siguiente código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
import sqlite3 class BD: # Constructor de la clase donde le pasamos la ruta donde # se creará la base de datos SQLite def __init__(self, bd = "todo.sqlite"): self.bd = bd self.conBD = sqlite3.connect(bd) # Crea la bd sqlite con la tabla "tareas" si no existe def crearBD(self): # Creamos la tabla "tareas", con clave primaria "codigo" autoincremental y # el campo "autor" para mostrar a cada usuario sólo sus tareas sql = """CREATE TABLE IF NOT EXISTS tareas ( codigo integer primary key autoincrement, descripcion text, fecha date, autor, resuelta boolean)""" self.conBD.execute(sql) sql = "CREATE INDEX IF NOT EXISTS descripcionIndice ON tareas (descripcion ASC)" self.conBD.execute(sql) sql = "CREATE INDEX IF NOT EXISTS autorIndice ON tareas (autor ASC)" self.conBD.execute(sql) self.conBD.commit() # Añade una nueva tarea (siempre con el ID del chat del autor) def insertarTarea(self, tarea, fecha, autor, resuelta): sql = "INSERT INTO tareas (descripcion, fecha, autor, resuelta) VALUES (?, ? ,?, ?)" parametros = (tarea, fecha, autor, resuelta) self.conBD.execute(sql, parametros) self.conBD.commit() # Elimina la tarea indicada (filtrada por código y autor) def eliminarTarea(self, codigo, autor): sql = "DELETE FROM tareas WHERE codigo = (?) AND autor = (?)" parametros = (codigo, autor) self.conBD.execute(sql, parametros) self.conBD.commit() # Obtiene las tareas no resueltas (filtradas por autor) def obtenerTareas(self, autor): sql = """SELECT codigo, descripcion, fecha, resuelta FROM tareas WHERE autor = (?) and (resuelta = 0 or resuelta is null)""" parametros = (autor, ) return [x for x in self.conBD.execute(sql, parametros)] # Obtiene una tarea filtrada por código y autor def obtenerTareaCodigo(self, codigo, autor): sql = """SELECT codigo, descripcion, fecha, resuelta FROM tareas WHERE codigo = (?) AND autor = (?)""" parametros = (codigo, autor) resultado = self.conBD.execute(sql, parametros).fetchone() return resultado # Obtiene todas las tareas de un autor def obtenerTareasTodas(self, autor): sql = """SELECT codigo, descripcion, fecha, resuelta FROM tareas WHERE autor = (?)""" parametros = (autor, ) return [x for x in self.conBD.execute(sql, parametros)] # Resuelve una tarea (filtrada por código y autor) def resolverTarea(self, codigo, autor): sql = """UPDATE tareas SET resuelta = 1 WHERE codigo = (?) AND autor = (?)""" parametros = (codigo, autor) self.conBD.execute(sql, parametros) self.conBD.commit() # Reabrir una tarea (filtrada por código y autor) def reabrirTarea(self, codigo, autor): sql = """UPDATE tareas SET resuelta = 0 WHERE codigo = (?) AND autor = (?)""" parametros = (codigo, autor) self.conBD.execute(sql, parametros) self.conBD.commit() |
El fichero principal de la aplicación, gestor_tareas.py, encargado de realizar todas las operaciones (conexión con el Bot, lectura y envío de mensajes, ejecución de comandos), tendrá el siguiente código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 |
# ProyectoA (https://www.proyectoa.com) # Gestor de Tareas (To-Do List) para Telegram # Versión 1.0 import json import requests import time import urllib from bd import BD import datetime from datetime import date from datetime import timedelta # Bot real en Telegram creado para este tutorial por ProyectoA: GestorDeTareas_Bot # Sustituir este Token por el obtenido en tu Bot TOKEN_BOT_TODOLIST = "7654:AEP_gbj79dXxGmLhT8" # La URL generada tendrá el formato: # https://api.telegram.org/bot9132:AFCcVf7cxw5/getUpdates URL = "https://api.telegram.org/bot{}/".format(TOKEN_BOT_TODOLIST) # Establecemos los comandos que reconocerá el Bot de Telegram comando_eliminar = ("eliminar", "borrar", "suprimir") comando_inicio = ("inicio", "iniciar", "start") comando_ayuda = ("ayuda", "help", "info", "comandos") comando_nueva = ("añadir", "nueva", "insertar", "tarea", "task", "new") comando_mostrar_todas = ("todas", "todos", "resueltas", "resueltos", "mostrar_todas", "mostrar_todos", "mostrar-todas", "mostrar.todas", "listar.todas","lista.todas", "ver.todas", "ver-todas", "all") comando_mostrar_no_resueltas = ("lista", "listar", "mostrar", "tareas", "ver", "list") comando_resolver = ("resolver", "finalizar", "completar", "fin", "end", "solved") comando_reabrir = ("reabrir", "abrir", "open") comando_mostrar_dias = ("pendientes", "dias", "pendiente", "restantes", "restante") # Instanciamos la clase BD para acceso a SQLite db = BD() # Obtener el contenido HTML de una URL def obtenerContenidoURL(url): respuesta = requests.get(url) contenido = respuesta.content.decode("utf8") return contenido # Obtener el contenido en JSON de una URL def obtenerJSONURL(url): contenido = obtenerContenidoURL(url) contenidoJSON = json.loads(contenido) return contenidoJSON # Obtener los últimos mensajes no leídos en Bot de Telegram en formato JSON def obtenerUltimosMensajes(offset=None): url = URL + "getUpdates" if offset: url += "?offset={}".format(offset) contenidoJSON = obtenerJSONURL(url) return contenidoJSON # Obtener el ID del úlitmo update del getUpdates (obtenerUltimosMensajes) def obtenerUltimoIDUpdate(updates): UpdateID = [] for update in updates["result"]: UpdateID.append(int(update["update_id"])) return max(UpdateID) # Obtenemos los mensajes y ejecutar el comando correspondiente # eliminar, insertar, listar, ayuda, resolver, reabrir, inicio def obtenerMensajeEjecutarComando(updates): textoRecibo = "" for update in updates["result"]: try: textoRecibo = update["message"]["text"] # Mostramos en cosola para depuración print("Texto leído: " + textoRecibo) except Exception as e: print(e) pass try: chat = update["message"]["chat"]["id"] except Exception as e: print(e) pass # Si se ha introducido una barra / se quita para comparar los comandos if textoRecibo.startswith("/"): textoRecibo = textoRecibo[1:] # Eliminar tarea (eliminar codigo_tarea) if textoRecibo.lower().startswith(comando_eliminar): # Obtenemos el comando y el código de la tarea (separados por espacio) textoDividido = textoRecibo.split(" ") if len(textoDividido) < 2: enviarMensajeAChat(texto="Envía: eliminar codigo_tarea", idChat=chat) else: codigo = textoDividido[1] tarea = db.obtenerTareaCodigo(codigo=codigo, autor=chat) if tarea == None: enviarMensajeAChat(texto=f"No se ha encontrado la tarea con código {codigo}", idChat=chat) else: db.eliminarTarea(codigo=codigo, autor=chat) enviarMensajeAChat(texto="La tarea " + codigo + " ha sido eliminada correctamente", idChat=chat) # Resolver tarea (resolver codigo_tarea) if textoRecibo.lower().startswith(comando_resolver): # Obtenemos el comando y el código de la tarea (separados por espacio) textoDividido = textoRecibo.split(" ") if len(textoDividido) < 2: enviarMensajeAChat(texto="Envía: resolve codigo_tarea", idChat=chat) else: codigo = textoDividido[1] tarea = db.obtenerTareaCodigo(codigo=codigo, autor=chat) if tarea == None: enviarMensajeAChat(texto=f"No se ha encontrado la tarea con código {codigo}", idChat=chat) else: db.resolverTarea(codigo=codigo, autor=chat) enviarMensajeAChat(texto="La tarea " + codigo + " ha sido resuelta correctamente", idChat=chat) # Reabrir tarea resuelta (reabrir codigo_tarea) if textoRecibo.lower().startswith(comando_reabrir): # Obtenemos el comando y el código de la tarea (separados por espacio) textoDividido = textoRecibo.split(" ") if len(textoDividido) < 2: enviarMensajeAChat(texto="Envía: reabrir codigo_tarea", idChat=chat) else: codigo = textoDividido[1] tarea = db.obtenerTareaCodigo(codigo=codigo, autor=chat) if tarea == None: enviarMensajeAChat(texto=f"No se ha encontrado la tarea con código {codigo}", idChat=chat) else: db.reabrirTarea(codigo=codigo, autor=chat) enviarMensajeAChat(texto="La tarea " + codigo + " ha sido reabierta correctamente", idChat=chat) # Comando "iniciar" elif textoRecibo.lower().startswith(comando_inicio): enviarMensajeAChat(texto="Envía: 'ayuda' para mostrar los comandos que el bot reconoce.", idChat=chat) # Comando "ayuda", mostramos todos los comandos y su uso elif textoRecibo.lower().startswith(comando_ayuda): comando1 = "<b>añadir..tarea..fecha</b> (dd-mm-aaaa) → Añadir una tarea" comando2 = "\n\n<b>tareas</b> → Mostrar tareas sin resolver" comando3 = "\n\n<b>pendientes</b> → Mostrar sin resolver con días pendientes" comando4 = "\n\n<b>todas</b> → Mostrar todas las tareas" comando5 = "\n\n<b>eliminar codigo_tarea</b> → Eliminar una tarea" comando6 = "\n\n<b>resolver codigo_tarea</b> → Resolver una tarea" comando7 = "\n\n<b>reabrir codigo_tarea</b> → Reabrir tarea resuelta" comando8 = "\n\n<b>ayuda</b> → Muestra los comandos reconocidos" mensaje = comando1 + comando2 + comando3 + comando4 + comando5 + comando6 + comando7 + comando8 enviarMensajeAChat(texto=mensaje, idChat=chat) # Comando mostrar tareas no resueltas elif textoRecibo.lower().startswith(comando_mostrar_no_resueltas): tareas = db.obtenerTareas(chat) if tareas == []: enviarMensajeAChat(texto="No hay tareas sin resolver para mostrar.", idChat=chat) for i in range(len(tareas)): codigo = tareas[i][0] tarea = tareas[i][1] fecha = tareas[i][2] mensaje = f"{codigo} {tarea} {fecha}" enviarMensajeAChat(texto=mensaje, idChat=chat) # Comando mostrar todas las tareas elif textoRecibo.lower().startswith(comando_mostrar_todas): tareas = db.obtenerTareasTodas(chat) if tareas == []: enviarMensajeAChat("No hay tareas para mostrar.", chat) for i in range(len(tareas)): codigo = tareas[i][0] tarea = tareas[i][1] fecha = tareas[i][2] resuelta = tareas[i][3] if (resuelta == 1): mensaje = f"{codigo} <s>{tarea}</s> {fecha}" else: mensaje = f"{codigo} {tarea} {fecha}" enviarMensajeAChat(texto=mensaje, idChat=chat) # Comando pendiente elif textoRecibo.lower().startswith(comando_mostrar_dias): # Obtenemos las tareas sin resolver tareas = db.obtenerTareas(chat) if tareas == []: enviarMensajeAChat("No hay tareas pendientes de resolver para mostrar.", chat) else: # pendiente = [0 for _ in range(0, len(tareas))] fechaHoy = datetime.datetime.today() for i in range(0, len(tareas)): try: codigo = tareas[i][0] texto = tareas[i][1] fechaTarea = tareas[i][2] fechaTareaFormato = datetime.datetime.strptime(fechaTarea, "%d-%m-%Y") fechaHoyFormato = datetime.datetime(year=fechaHoy.year, month=fechaHoy.month, day=fechaHoy.day) diferencia = fechaTareaFormato - fechaHoyFormato dias = diferencia.days if dias > 1: mensajeDias = "quedan " + str(dias) + " días" elif dias == 1: mensajeDias = "queda 1 día" else: mensajeDias = "fecha anterior" mensaje = str(codigo) + " " + texto + " → " + mensajeDias enviarMensajeAChat(texto=mensaje, idChat=chat) except: mensaje = f"Error al obtener la fecha de la tarea con código {tareas[i][0]}" enviarMensajeAChat(texto=mensaje, idChat=chat) pass # Comando añadir nueva tarea (añadir..texto..fecha) elif textoRecibo.lower().startswith(comando_nueva): # Usamos el separador ".." para obtener el texto de la tarea y la fecha textoDividido = textoRecibo.split("..") if len(textoDividido) < 3: enviarMensajeAChat(texto="Envía: añadir..tarea..fecha (formato dd-mm-aaaa)", idChat=chat) elif len(textoDividido[1]) < 1 or len(textoDividido[2]) < 1: enviarMensajeAChat(texto="Envía: añadir..tarea..fecha (formato dd-mm-aaaa)", idChat=chat) else: texto = textoDividido[1] fecha = textoDividido[2] db.insertarTarea(tarea=texto, fecha=fecha, autor=chat, resuelta=0) enviarMensajeAChat(texto="Tarea <i>" + texto + "</i> insertada correctamente", idChat=chat) # Si no se reconoce el comando enviado else: # Si existe la variable "chat" if "chat" in locals(): mensaje = f"No reconozco el comando <i>{textoRecibo}</i>, Envía: 'ayuda' para mostrarte los comandos que reconozco" enviarMensajeAChat(texto=mensaje, idChat=chat) else: # Si ha habido algún error y no existe la variable "chat" print("Error al obtener el ID del chat") # Obtiene el ID del chat del que recibe el texto y también el propio texto enviado def obtenerChatIDyTexto(updates): numUpdates = len(updates["result"]) ultimoUpdate = numUpdates - 1 texto = updates["result"][ultimoUpdate]["message"]["text"] idChat = updates["result"][ultimoUpdate]["message"]["chat"]["id"] return texto, idChat # Envía un mensaje al chat indicado de Telegram def enviarMensajeAChat(texto, idChat, formato=None): # Limpiamos el texto de cualquier carácter no permitido texto = urllib.parse.quote_plus(texto) url = URL + "sendMessage?text={}&chat_id={}&parse_mode=HTML".format(texto, idChat) if formato: url += "&reply_markup={}".format(formato) obtenerContenidoURL(url) # Procedimiento principal de inicio de aplicación def main(): # Creamos la base de datos SQLite y la tabla "tareas" (si no existe) db.crearBD() idUltimoUpdate = None # Ejeuctamos un bucle infinito para manter la aplicación siempre abierta # Comprobamos si hay nuevos mensajes por leer en el Bot while True: updates = obtenerUltimosMensajes(idUltimoUpdate) if len(updates["result"]) > 0: idUltimoUpdate = obtenerUltimoIDUpdate(updates) + 1 obtenerMensajeEjecutarComando(updates) time.sleep(0.5) # Llamamos al procedimiento principal if __name__ == '__main__': main() |
Funcionamiento de la aplicación Python y el Bot para Gestor de Tareas To-Do List
Agregaremos el Bot a Telegram, al agregarlo, nos mostrará el mensaje del comando inicial start:
Desde el chat del Bot, introduciremos el comando ayuda. Nos devolverá todos los comandos que se pueden ejecutar en el Bot:
añadir..tarea..fecha (dd-mm-aaaa) → Añadir una tarea
tareas → Mostrar tareas sin resolver
pendientes → Mostrar sin resolver con días pendientes
todas → Mostrar todas las tareas
eliminar codigo_tarea → Eliminar una tarea
resolver codigo_tarea → Resolver una tarea
reabrir codigo_tarea → Reabrir tarea resuelta
ayuda → Muestra los comandos reconocidos
Para añadir una tarea usaremos el siguiente comando (a modo de ejemplo):
añadir..Revisión paciente Lucas 2 semana..21-11-2024
Donde:
- Revisión paciente Lucas 2 semana: lo reemplazaremos por el texto de nuestra tarea.
- 21-11-2024: lo reemplazaremos por la fecha máxima de vencimiento de la tarea, en formato dd-mm-aaaa.
Si se ha introducido el comando correctamente, nos devolverá un mensaje indicando que la tarea ha sido insertada correctamente.
Para mostrar todas las tareas pendientes de resolver, usaremos el comando tareas o lista:
Para resolver una tarea, usaremos el comando resolver codigo_tarea, por ejemplo, para resolver la tarea 12, usaremos:
resolver 12
Para mostrar todas las tareas (tanto las resueltas como las no resueltas), usaremos el comando todas. Las tareas resueltas aparecerán tachadas:
Para reabrir una tarea resuelta y volverla a marcar como no resuelta, usaremos el comando reabrir codigo_tarea, por ejemplo, para marcar como no resuelta la tarea 12, usaremos el comando:
reabrir 12
Si volvemos a mostrar las tareas pendientes de resolver con el comando tareas, nos volverá a aparecer la 12 pendiente de resolver:
Para eliminar una tarea, usaremos el comando eliminar codigo_tarea. Por ejemplo, para eliminar la tarea 13, usaremos el comando:
eliminar 13
Para mostrar las tareas pendientes de resolver y los días que faltan para su vencimiento, usaremos el comando días: