Bots de Telegram en python.

Goyo Regalado
11 min readJan 14, 2019

Hace un tiempo, en un grupo de Telegram surgió la necesidad de desarrollar un bot con una función muy concreta: Clasificar los enlaces que publicaban los usuarios para que no se perdieran.

Hacía mucho tiempo que quería hincarle el diente a esto de los bots de Telegram y ésta fue la excusa perfecta.

Este artículo pretende ser una pequeña guía sobre cómo desarrollar un bot que cumpla con esta función. Como me conozco, la idea será irlo desarrollando de manera paulatina. Así que, si todo va bien, puede que éste sea el primero de una serie de artículos sobre el tema.

¿Qué diablos es eso de un bot?

Primero lo primero, aclaremos conceptos. Un bot no es otra cosa que un programa que es capaz de “engancharse” a una conversación de un sistema de mensajería y atender a los mensajes de los usuarios e interactuar con estos de distintos modos.

Uno de estos modos consiste en tener una serie de comandos que desencadenan acciones preprogramadas, cuando el usuario lanza uno de estos comandos, el bot hace algo concreto. Los comandos, en el caso de los bots de Telegram, siempre empiezan por un carácter “/”

BotFather es un ejemplo claro del uso de comandos que comienzan con “/”

Por ejemplo, si mi nuevo bot entiende un comando /list_languages, cuando un usuario de una conversación en la que está presente escribe /list_languages, mi bot devuelve una lista con nombres de lenguajes de programación. Lo que ocurrirá es que tendremos un nuevo mensaje en la conversación, que habrá enviado nuestro pequeño engendro sintético y que será la respuesta a nuestro comando.

Otro modo de actuar totalmente distinto es el de los bot inline. Los bots inline esperan que el resto de miembros de la conversación empiecen un nuevo mensaje haciendo mención a su nombre precedido del carácter “@”. Se han desarrollado múltiples bots inline muy interesantes, por ejemplo, “@gif” que espera que le pases un texto y te devuelve gifs relacionados con el mismo.

Si escribimos @gif en cualquier conversación de Telegram podremos buscar gif’s animados de una manera muy simple. Es un ejemplo de bot inline.

En cualquier caso, mis favoritos son los que viven agazapados en la ventana de chat y actúan espontáneamente reaccionando a los propios mensajes. Así que éste es el tipo de bot sobre el que vamos a trabajar.

Generando un bot

Los desarrolladores de Telegram han elegido un método muy llamativo para solicitar las credenciales necesarias para comenzar a trabajar con su API de bots. Básicamente tienes que abrir una conversación con “@BotFather” desde el propio Telegram. “@BotFather” es un bot y reconoce una serie de comandos.

El que nos ocupa es /newbot, que nos pedirá que introduzcamos un nombre para nuestro nuevo esbirro digital así como un nombre de usuario.

Los dos datos pueden parecer lo mismo pero no lo son. El nombre aparecerá en la información de contacto del bot sin embargo, el nombre de usuario o username en inglés debe terminar siempre en bot y será el identificador que utilicemos cuando hagamos mención al bot en una ventana de chat, en nuestro caso sería “@AchicaynaBot”. De hecho, una manera de abrir una conversación con este bot es, simplemente, dirigirte a la url http://t.me/<UsernameDelBot>, en nuestro caso http://t.me/AchicaynaBot

Una vez ejecutado este paso nos encontraremos con la respuesta a nuestra solicitud que tendrá forma de mensaje y dentro de éste encontraremos un texto que pone algo así como:

Use this token to access the HTTP API: 110201543:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw

Ese chorizo de números y letras es el identificador de tu bot, todo el mundo se refiere a él como token y deberías cuidarlo. Cualquiera que utilice ese token será, a efectos de la API de Telegram, tu bot. No te preocupes, esto tendrá sentido en un momentito. Por cierto, ese token no es válido.

Lo siguiente que debes hacer es lanzar en la conversación con “@BotFather” el comando /setdescription con él podrás introducir un texto breve que sirva para presentar en sociedad a tu bot. Cada vez que alguien abra una conversación con él, verá esta descripción.

Por último, habrá que ponerle cara a nuestro esbirro digital, así que busca una imagen adecuada y, una vez más, dentro de la ventana de la conversación con “@BotFather” lanza el comando /setuserpic “@BotFather” te pedirá que subas una imagen de tu galería que pasará a ser la imagen del perfil de tu bot.

Todas las piezas están en su sitio, nuestro bot existe, tiene un nombre y hasta imagen de perfil, ahora toca darle un poco de vida.

Dándole vida al bot

Existen múltiples librerías para muchos lenguajes de programación distintos que permiten interactuar con la API de bots de Telegram.

Los que me conocen saben que me encanta Python, tengo auténtica debilidad por este lenguaje así que este ejemplo se desarrollará íntegramente utilizándolo.

Para Python también existen varias librerías con las que desarrollar bots de Telegram, unas más eficientes que otras, para este ejemplo nosotros nos vamos a centrar en python-telegram-bot. Puedes acceder al repositorio en la siguiente dirección: https://github.com/python-telegram-bot/python-telegram-bot

Para instalarla no tienes más que ejecutar:

pip install python-telegram-bot

Mi recomendación es que, cada vez que empieces con el desarrollo de cualquier aplicación te plantees usar un virtualenv separado. No voy a explicar en este artículo cómo hacerlo porque sino nos liamos, pero seguro que encuentras por ahí abundante información sobre el tema.

Del mismo modo, también he creado un repositorio en gitlab para este proyecto. Si te sientes perdido o no entiendes algo, ahí tendrás disponible todo el proceso de desarrollo. La dirección del repositorio es: https://gitlab.com/goyoregalado/achicaynabot

Cada paso de estos artículos formará parte de una nueva rama del repositorio así podrás hacer un seguimiento secuencial del mismo y volver atrás cuando sea necesario.

Como verás, la estructura del proyecto es muy simple:

Árbol de directorios del proyecto.

En este primer momento, todo el código que hará que nuestro bot cobre vida estará en el archivo bot.py. La mayoría de ejemplos que encontrarás por ahí siguen esta misma estructura, sin embargo, he decidido añadir un paquete de Python mínimo dentro del directorio config que nos permita aislar nuestro token de autenticación del repositorio. De esta manera, solamente tendrás que añadir un archivo denominado auth.py tal y como se muestra en la imagen e incluir dentro de él el token de tu bot como puedes ver a continuación.

Una vez descargado el repositorio solamente tendrás que crear un archivo denominado auth.py dentro del directorio config e incluir tu token como se muestra.

Como habrás supuesto, este valor debe ser el token que te ofreció “@BotFather” cuando solicitaste la creación del bot si suponemos que el Token que te devolvió fue este: 110201543:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw

Como última precaución, editaremos el fichero .gitignore para que no podamos almacenar por error nuestros datos de autenticación en el repositorio.

Ejemplo de archivo .gitignore que nos permite evitar que nuestros datos de autentificación suban al repositorio por error.

Vamos a empezar con un código mínimo que nos permita interactuar con el bot. Dentro del fichero bot.py encontrarás lo siguiente.

from telegram.ext import Updater
from config.auth import token
if __name__ == '__main__':
updater = Updater(token=token)
dispatcher = updater.dispatcher

Si ahora ejecutásemos este código utilizando el intérprete de Python veríamos que el resultado es bastante pobre, básicamente, tras poco más de un segundo el programa terminará sin errores.

No es un inicio muy impresionante, pero confía en mi, vamos por el buen camino.

El objeto Updater es el responsable de enviar las “reacciones” de nuestro bot de vuelta a las ventanas de chat. Por ejemplo, si un usuario enviase el comando /start a nuestro bot, Updater sería el que sabría cómo debe responder a ese comando.

Para inicializar nuestro bot, tendremos que pasarle el token que nos identifica unívocamente en los servidores de Telegram.

El propio objeto Updater nos devuelve la referencia a otro objeto importantísimo, el dispatcher. Si nuestro bot fuera un ser vivo, el dispatcher sería su oreja. La función del dispatcher es mantenerse a la escucha en todas las ventanas de chat en las que esté presente nuestro ente digital.

Un poco de feedback

Lo siguiente que vamos a hacer es que nuestro bot nos devuelva algo de información. Para lograrlo importaremos una nueva librería, logging.

La función de esta librería es registrar eventos, devolvernos información sobre el estado del bot. Es un software bastante versátil, podemos conectarlo directamente a los registros de nuestro sistema operativo. Nosotros solamente lo utilizaremos para que nos muestre por la pantalla la información que consideremos oportuna.

La manera de utilizar logging puede parecer un poco extraña, pero veremos que no es tan compleja como parece.

import logging

logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
logger = logging.getLogger('AchicaynaBot')

La primera línea importa la librería, ya lo hemos hecho en otros casos en nuestro código.

La segunda línea, sin embargo sí empieza a ser interesante. Lo que hacemos es configurar qué información se va a almacenar cada vez que se registre un evento.

El primer argumento es format y, en nuestro caso, hemos decidido configurar esas salidas de esta manera:

format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'

Esto no significa otra cosa que queremos que cada línea comience con la fecha y hora en un formato como el siguiente: 2019–01–13 22:19:36,146

A continuación, separado por un guión aparecerá el nombre de nuestro bot, separado por un nuevo guión el nivel del registro que estamos mostrando y por último el mensaje que nuestro bot quiere mostrar.

El segundo argumento es level que establece la gravedad mínima que tiene que tener un evento para que nuestro bot decida molestarnos.

Los mensajes de registro suelen tener una jerarquía dónde los mensajes menos prioritarios son aquellos que los programadores utilizamos para buscar errores en nuestro código y los más importantes son aquellos que, de producirse, podrían hacer que la aplicación no pudiera continuar funcionando. Por orden de menor a mayor prioridad, diríamos que los niveles de registro son: Debug, info, warning, error y critical. Si no se indica otra cosa, la librería logging está configurada para mostrar mensajes con categoría mínima de warning. Esta categoría de eventos representa un aviso de que ha ocurrido algo inesperado.

Si dejásemos esta configuración tal cual, la información que esperamos recibir no se mostraría, porque no es algo inesperado, así que establecemos nuestro nivel mínimo de prioridad en info. Esto lo hacemos pasando el siguiente valor como segundo argumento:

level=logging.INFO

Por último, almacenaremos en la variable logger una instancia del objeto logger que nos permita enviar un mensaje.

De manera que, si quisiéramos enviar un mensaje al usuario para informarle de que está ocurriendo algo haríamos uso de alguno de los siguientes métodos.

logger.debug(‘Este mensaje es sólo para frikis programadores como nosotros ;)’)logger.info(‘Este mensaje representa algo normal’)logger.warning(‘Esto ya no es tan normal’)logger.error(‘Deberías empezar a preocuparte’)logger.critical(‘El bot está así X(’)

Llegados a este punto vamos a hacer que Achicayna empiece a hacer algo productivo.

En primer lugar, vamos a hacer que pueda responder a su primer comando. Este comando será /start y como respuesta, nuestro maravilloso bot, enviará un mensaje al chat en el que diga: “Soy un Achicayna, que entre los primeros pobladores de Canarias era el equivalente a un plebeyo”. Esto es un pequeño guiño cultural a mi tierra.

Tendremos que hablar de nuevo con “@BotFather” e indicarle que queremos configurar a nuestro bot. Para ello le enviaremos el comando /mybots, que listará todos los bots que hemos creado hasta la fecha.

Con el comando /mybots siempre podremos consultar la lista de bots que hemos creado.

Una vez seleccionado nuestro bot, se nos dará una serie de opciones que permiten configurar distintos aspectos del mismo.

Menú de configuración del bot.

Seleccionaremos la opción “Edit Bot” y una vez allí “@BotFather” nos ofrecerá un nuevo menú.

En este menú podremos definir la lista de comandos del bot.

Para informar a Telegram de los comandos que entenderá nuestro bot bastará con pulsar sobre el botón “Edit Commands”. Una vez más, la manera de establecer esa lista de comandos es muy original. Tenemos que enviarle un mensaje a “@BotFather” con el siguiente formato:

comando1 - Texto explicativo sobre el comando1.
comando2 - Texto explicativo sobre el comando2.
Añadimos nuestro comando start

Una vez hecho esto, estamos listos para indicarle a nuestro bot cómo debe reaccionar cuando reciba el único comando que entenderá de momento.

En primer lugar programaremos la función que se hará cargo de esta respuesta. A estas funciones se les llama “handlers” y siempre tienen la misma estructura.

def start(bot, update):    logger.info('He recibido un comando start')
bot.send_message(
chat_id=update.message.chat_id,
text="Soy un Achicayna, que entre los primeros pobladores de
Canarias era el equivalente a un plebeyo."
)

Reciben como argumento dos parámetros: bot, que representa al propio bot y nos provee de métodos para enviar mensajes y update que representa el mensaje que ha desencadenado que se llame a esta función, en este caso, el comando “/start”

La primera línea añadirá una salida a nuestro registro que nos avise de que se ha recibido el comando.

La segunda línea inyectará en el chat en el que se ha lanzado el comando un mensaje de parte de Achicayna.

Ya estamos casi listos. Ahora hay que indicarle al dispatcher qué queremos hacer cuando recibamos cada comando. En esta caso aún sólo hemos definido uno, así que la cosa será fácil.

Tendremos que importar CommandHandler desde telegram.ext

from telegram.ext import CommandHandler

Este objeto recibe como parámetros tanto la cadena de texto del comando (sin la barra inicial), como el nombre de la función que deberá ejecutarse cuando se reciba, vamos, nuestro famoso “handler”.

Y todo esto lo daremos de alta en el dispatcher usando el siguiente código:

dispatcher.add_handler(CommandHandler('start', start))

Esto viene a significar: “Dispatcher, cuando recibas un comando ‘start’ ejecuta la función start”

Finalmente tenemos que decirle al objeto updater que esté atento a cualquier mensaje que pueda llegarle al bot y que mientras no reciba ninguno que se mantenga a la espera. Esto se hace con estas dos nuevas líneas de código:

updater.start_polling()
updater.idle()

El código final de nuestro bot quedaría de este modo:

from telegram.ext import Updater, CommandHandler
from config.auth import token

import logging

logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s -
%(message)s', level=logging.INFO)
logger = logging.getLogger('AchicaynaBot')


def start(bot, update):
logger.info('He recibido un comando start')
bot.send_message(
chat_id=update.message.chat_id,
text="Soy un Achicayna, que entre los primeros pobladores de
Canarias era el equivalente a un plebeyo."
)


if __name__ == '__main__':

updater = Updater(token=token)
dispatcher = updater.dispatcher

dispatcher.add_handler(CommandHandler('start', start))

updater.start_polling()
updater.idle()

Si ejecutásemos nuestro bot haciendo uso del comando python src/bot.py podríamos comenzar a conversar con Achicayna, aunque, por ahora, es un bot un poco soso.

Nuestra primera breve conversación con Achicayna

Bueno, no está mal, hemos hecho algunos progresos. Todo el código hasta este punto forma parte del branch 01-minimum-bot del repositorio.

En la próxima entrega vamos a hacer que Achicayna no sólo reaccione a comandos sino que esté pendiente de lo que se escribe en las ventanas de chat en las que participe.

Espero que el artículo les haya sido de utilidad.

--

--