Cómo construir un chatbot para Twitch

Cómo construir un chatbot para Twitch

Uno de los primeros proyectos que desarrollé fuera de la universidad fue un chatbot para la plataforma de transmisión en vivo de Twitch. Empecé por la influencia de la comunidad y, como principiante, desconocía los estándares de diseño, las definiciones de responsabilidades, entre otros aspectos.

En este artículo, te mostraré cómo di mis primeros pasos en este viaje. Una nota importante: este proyecto se desarrolló en el sistema operativo Windows, por lo que algunos comandos de terminal pueden tener una sintaxis diferente en otro sistema operativo.

Prerrequisitos

Para este proyecto, es necesario tener Python y PIP previamente instalados en tu computadora, así como sus variables de entorno ya configuradas. Antes, la creación del entorno virtual y la instalación de las dependencias del proyecto se hacían de forma manual.

Crear un ambiente virtual

Dentro de un directorio específico para el proyecto, abrimos la terminal y ejecutamos el primer comando “python -m venv venv” para crear el entorno virtual. Después de terminar la creación, activa el entorno con el comando “.\venv\Scripts\activate”. Todo el proyecto se ejecutará dentro de ese entorno.

Si la terminal CMD se ve así, sabemos que el entorno está activo:

(venv) D:\Projetos\bot>

Preparar el ambiente

Una dependencia que usaremos es "twitchio". Esta dependencia es lo que envuelve la API de Twitch y el IRC para realizar las comunicaciones. Para ello, lo instalaremos mediante el comando “pip install twitchio”.

Crear el archivo principal

Dentro de la carpeta “venv” creamos un archivo “index.py”. Aquí tendremos una versión minimalista del bot.

```

from twitchio.ext import commands

class Bot (commands.Bot):

def __init__(self):

super().__init__(token='oauth:...',

nick='bug_elseif', prefix='!',

initial_channels=['bug_elseif'])

async def event_ready(self):

print(f'Ready | {self.nick}')

@commands.command(name='teste')

async def teste(self, ctx):

await ctx.send('teste de comando')

bot = Bot()

bot.run()

```

Entendamos lo que sucede aquí

Primero, importamos en commands la biblioteca “twitchio.ext”.

Creamos una clase llamada Bot que recibe comandos y es la superclase de la biblioteca de twitchio.

Esta clase tiene un constructor que llama a la superclase heredada de la librería, la cual recibe la siguiente información:

  • token: es una clave generada por twitch exclusivamente para la cuenta propietaria del chatbot.
  • nick: nombre de la cuenta del propietario.
  • prefix: identificador de comando, cualquier mensaje escrito en el chat con el prefijo definido se identificará como un comando, generalmente se usa el carácter “!”, pero puede definir cualquier símbolo de tu elección.
  • initial_channels: A diferencia de los otros atributos que son del tipo String, este recibe una lista de canales donde el chatbot puede actuar. Los canales aquí descritos deben dar permiso al canal propietario a través del comando de moderación en la propia plataforma de Twitch.

Definiendo funciones async

Los eventos y comandos disparados por el canal de usuario funcionan de forma asíncrona, es decir, no es necesario esperar a que se ejecute un comando al principio del código para que se ejecute otro hacia el final.

La primera función asíncrona que tenemos es “event_ready”. Sirve para avisarnos que el chatbot está listo y activo en todos los canales.

La segunda función es un comando. Para ello, usamos un elemento llamado decorator.

El decorador es una forma de modificar el comportamiento de una función. En este caso, tenemos la función “comando”, que recibe un nombre (name='teste') como parámetro para diferenciarla de otras funciones command.

En el ejemplo, el “command” recibe el nombre test y luego definimos una función de modificación del comando.

El parámetro “ctx” se define como un contexto, es el canal que está usando este comando y al cual responderá.

“Await” es un comando que sirve para esperar a que se complete una rutina antes de finalizar la función. En el ejemplo, apuntamos a finalizar la función “ctx.send()” para seguir la función de prueba

La función “send()” envía el mensaje en el chat del canal establecido por “ctx”. El parámetro debe ser siempre una String.

Toda esta segunda función se puede utilizar como base para crear nuevos comandos.

Finalmente, tenemos una variable “bot” que recibe la clase Bot e inicia el ciclo de eventos asíncronos, conectándose con el IRC de Twitch.

Activar el chatbot

La activación del chatbot es manual en este punto. Se requieren los siguientes pasos:

  • Certificar que el ambiente virtual (venv) esté activo en la terminal. De lo contrario, basta activarlo con el comando “.\venv\Scripts\activate”.
  • Dentro del entorno virtual, navegue a la carpeta “venv” y ejecute el archivo principal a través del comando “python index.py”.

De esta forma, ya es posible utilizarlo en todos los canales descritos en “initial_channels”.

Complementando comandos

Hay varias formas de crear comandos a partir de la estructura del comando de prueba ya presentada, veamos algunas de ellas.

Usar la lógica dentro del propio comando

Durante los lives apliqué lalógica para aprender más sobre la sintaxis de Python. En aras de lograrlo, usé una lista de ejercicios que abordaban estructura secuencial, decisión, repetición, funciones, etc.

En esa lista vino un juego llamado dados (craps). Se presentaron algunas reglas y debía crear un código consistente con ellas. En el momento que leí las reglas del juego me di cuenta de que era posible colocarlas como un nuevo comando en el chatbot, por lo que llegamos al siguiente código (en portugués):

```

import random

@commands.command(name='craps')

async def craps(self, ctx):

nJogada = 1

ponto = 0

while True:

d1 = random.randint(1,7)

d2 = random.randint(1,7)

soma = d1+d2

if nJogada == 1 and (soma == 7 or soma == 11):

mensagem = "Parabéns!! | Pontos: 999  "

break

elif nJogada == 1 and (soma == 2 or soma == 3 or soma == 12):

mensagem = f"Craps - perdeu | Pontos = {ponto}"

break

elif nJogada == 1 and (4 <= soma <= 6 or 8 <= soma <= 10):

ponto += soma

nJogada += 1

continue

#-----------------------------------------------------

elif nJogada == 2:

if soma == 7:

mensagem = f"Craps - perdeu | Pontos = {ponto}"

break

else:

ponto += soma

nJogada += 1

continue

elif nJogada > 2:

if 4 <= soma <= 6 or 8 <= soma <= 10:

ponto += soma

nJogada += 1

continue

else:

mensagem = f"Craps - perdeu | Pontos = {ponto}"

break

await ctx.send(f'{ctx.author.name}: {mensagem}')

```

Esta fue la primera versión del juego, donde una persona de chat podía activar el comando “!craps”. El programa genera números aleatorios llegando a un resultado de victoria o derrota junto con la suma de los puntos y devolviendo la información a la persona. Para una mejor comprensión de la lógica del juego, las reglas se describen en el ejercicio número 10 de esta lista.

La estructura del comando sigue el mismo patrón que el comando test. Sin embargo, con la implementación de la lógica del juego dentro de la función (en este caso en “ctx.send()”), enviamos las variables con el nick de la persona que activó el comando junto con la variable “mensaje”, que se define según la cantidad de puntos que obtuvo la persona, ambos de tipo Strings.

Usar archivos Python externos

Uno de los comandos creados más tarde fue “¡música!”, que devuelve una pieza musical aleatoria a la persona que lo activó.

Creamos un archivo “musica.py” con una lista de cadenas con pequeños fragmentos de canciones.

´´´

musicas = [

""Donde quiera que vaya, te llevo en mis ojos",

"Fue poco tiempo. pero valió la pena. Viví cada segundo",

...

]

´´´

Este archivo se importa al "index.py" principal, donde se utilizará.

```

from musica import *

@commands.command(name='musica')

async def musica(self, ctx):

msg = random.choice(musicas)

await ctx.send(f'/me {ctx.author.name} - {msg}.’)

```

En este comando, usamos la lista "música" para devolver un elemento aleatorio con el comando "random.choice()" y lo asignamos a la variable "msg", la siguiente instrucción sigue el mismo patrón que la "prueba".

Usar archivos de texto externos

El comando “!first” fue desarrollado con el propósito de marcar a aquéllos que llegaron temprano al live. Requería un archivo “.txt” y algunos pasos para manipularlo.

Inicialmente en el constructor de la clase creamos un archivo que se lee de la siguiente manera:

```

with open ("first.txt", "r", encoding="utf-8") as file:

self.lista = file.readlines()

self.data = str(date.today())

if not len(self.lista) or self.lista[0] != self.data+"\n" :

with open ("first.txt", "w") as file:

file.write(self.data + "\n")

self.lista = [self.data]

```

El comando “with” garantiza que el recurso que estamos utilizando será terminado incluso si ocurre una excepción durante su ejecución. Esta función de abrir un archivo de texto con el comando "abrir", tiene tres parámetros:

  1. Primero está el nombre del archivo de texto que será manipulado.
  2. El segundo es el modo. En este caso, estamos usando el modo de lectura ®.
  3. Codificación en el padrón UTF-8.

Al final, nombramos el archivo como "fil" para facilitar su uso dentro del código más adelante. Almacenaremos en la variable lista el contenido leído del archivo de texto, donde cada línea es un índice de la lista. En la variable de fecha, vamos a almacenar una cadena con la fecha actual.

A continuación tenemos un condicional para verificar estas variables. Cumpliendo las condiciones, actualizamos los valores tanto de las variables como del archivo de texto, abriéndolo nuevamente, esta vez en modo escritura (w) para modificarlo. De lo contrario, pasaremos a la siguiente instrucción sin cambios.

Pasemos al comando:

```

@commands.command(name='first')

async def first(self, ctx):

nome = f'{ctx.author.name}\n'

if nome not in self.lista:

self.lista.append(nome)

with open ("first.txt", "a", encoding="utf-8") as file:

file.write(nome)

if len(self.lista)-1 == 1:

await ctx.send(f'{ctx.author.name} Parabéns, chegou cedo')

else:

await ctx.send(f'{ctx.author.name} Hoje não, você foi o {len(self.lista)-1}º')

return

await ctx.send(f'{ctx.author.name} Você ja está na lista ')

```

Este comando también usa la misma estructura básica, donde devuelve un mensaje al chat de acuerdo con las siguientes condiciones.

Si el nombre de la persona que activó el comando no está en la lista, lo agregaremos tanto en la variable como en el archivo de texto y luego haremos otra verificación para definir cuál de los mensajes se enviará.

En este comando, abriremos el archivo de texto en el modo agregar (a) que agrega un nuevo elemento dentro del archivo.

Consideraciones finales

Esta fue la primera versión del chatbot desarrollada e implementada de manera básica, pero muy importante al inicio de mis estudios. Al aprender más sobre los patrones de diseño, pasamos por una refactorización donde usamos la herramienta Poetry, que administra las dependencias, separó la lógica del archivo principal, organizando el código y haciéndolo más limpio, además de otras mejoras e implementaciones.


⚠️
Las opiniones y comentarios emitidos en este artículo son propiedad única de su autor y no necesariamente representan el punto de vista de Revelo.

Revelo Content Network da la bienvenida a todas las razas, etnias, nacionalidades, credos, géneros, orientaciones, puntos de vista e ideologías, siempre y cuando promuevan la diversidad, la equidad, la inclusión y el crecimiento profesional de los profesionales en tecnología.