Hoy vamos a jugar un poco con los bots de Discord, accediendo a la API desde Python.
El punto de partida es este artículo de Real Python, que tiene algunos problemas debido a su antigüedad. El código que sale en ese tutorial no funciona, así que os he colgado aquí código que si funciona -al menos a día de hoy, diciembre 2020-
https://realpython.com/how-to-make-a-discord-bot-python/
La primera cosa que debo deciros es que en Windows para instalar el paquete de funciones de discord.py nos pide el compilador de Visual C++. Eso son como 13Gb en el disco duro, así que no lo recomiendo. Demasiado equipaje para tan poco viaje.
Lo he probado en Ubuntu server 20.04 y la verdad es que va como la seda con algunos paquetes que instales desde un simple apt install.
En concreto necesitarás los paquetes: python3-pip y python3-dotenv
alumno quejoso
Lo malo del tutorial, es que resulta que han cambiado la forma de acceder a la API de Discord por temas de privacidad y para ahorrar recursos del servidor. Por ejemplo, ahora pasar lista por Discord es un poco complicado, no consigo recibir el estado de los miembros del grupo de forma actualizada. Pero a parte de eso, si usáis el código del tutorial no funcionará porque le falta actualizarse.
Para ahorrar frustraciones os dejo aquí 2 bloques de código que si que funcionan:
El primer código es mibot.py y pone en marcha un bot que responde a comandos con el prefijo !, como todos los bots de Discord normales. Le he puesto algunas funciones a modo de ejemplo para que sepáis un poco como se hace y hagáis las vuestras propias con un poco de copiar & pegar.
# mibot.py
import os
import random
import discord
from discord.ext import commands
from discord import Member
from discord import Status
from dotenv import load_dotenv
load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')
#new from new discord API
intents = discord.Intents.default()
intents.members = True # Subscribe to the privileged members intent.
bot = commands.Bot(command_prefix='!', intents=intents)
@bot.event
async def on_member_join(member):
#await member.send("WElcome tio")
await member.create_dm()
await member.dm_channel.send(
f'Hola {member.name}, eres un vago y lo sabes.'
)
@bot.command(name='quehago', help='Responde con tus tareas para hoy. De forma aleatoria.')
async def nine_nine(ctx):
tareas_pendientes = [
'¿Has intentado hacer una web con WordPress?',
'¿Sabes crear otro WordPress para otro dominio en el mismo servidor?',
'¿Sabes usar tu propio servidor de DNS para dar DNS a tu equipo?',
(
'¿Serías capaz de repetir todas las prácticas que hemos hecho hasta ahora sin la ayuda del profe?, '
'No hay narices a hacerlo.'
),
]
response = random.choice(tareas_pendientes)
await ctx.send(response)
@bot.command(name='dados', help='Simula una tirada de datos de D&D. Dile el numero de dados y las caras.')
async def tirar(ctx, num_dados: int, num_caras: int):
dado = [
str(random.choice(range(1, num_caras + 1)))
for _ in range(num_dados)
]
await ctx.send(', '.join(dado))
@bot.command(name='musica', help='Busca una cancion. Es solo un ejemplo.')
async def buscar(ctx, cancion: str):
busca = 'https://www.youtube.com/results?search_query='+cancion
await ctx.send(busca)
@bot.command(name='crear_canal')
@commands.has_role('profesor')
async def create_channel(ctx, nombre_canal='canal_del_profe'):
guild = ctx.guild
existing_channel = discord.utils.get(guild.channels, name=nombre_canal)
if not existing_channel:
print(f'Creando un nuevo canal: {nombre_canal}')
await guild.create_text_channel(nombre_canal)
@bot.event
async def on_command_error(ctx, error):
if isinstance(error, commands.errors.CheckFailure):
await ctx.send('No tienes el rol apropiado para usar esta función.')
bot.run(TOKEN)
El segundo código micliente.py es diferente y está poco recomendado porque abusa de los recursos del sistema, pero para hacer 4 bromas en un server privado de 10 personas no pasa nada.
# micliente.py
import os
import random
import discord
from discord.ext import commands
from discord import Member
from discord import Status
from dotenv import load_dotenv
load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')
GUILD = os.getenv('DISCORD_GUILD')
client = discord.Client()
#new from new discord API
intents = discord.Intents.default()
intents.members = True # Subscribe to the privileged members intent.
client = discord.Client(intents=intents)
@client.event
async def on_ready():
for guild in client.guilds:
if guild.name == GUILD:
break
print(
f'{client.user} está conectado al siguiente server/guild:\n'
f'{guild.name}(id: {guild.id})\n'
)
@client.event
async def on_message(message):
#esto es para que el bot no entre en bucle hablando consigo mismo
if message.author == client.user:
return
tareas_pendientes = [
'¿Has intentado hacer una web con WordPress?',
'¿Sabes crear otro WordPress para otro dominio en el mismo servidor?',
'¿Sabes usar tu propio servidor de DNS para dar DNS a tu equipo?',
(
'¿Serías capaz de repetir todas las prácticas que hemos hecho hasta ahora sin la ayuda del profe?, '
'No hay narices a hacerlo.'
),
]
#Esto es una verguenza de codigo, pero bueno, para ser la clase de programación CERO no os lio mas.
#Aqui el mensaje debe ser exactamente "que hago"
if message.content == 'que hago':
response = random.choice(tareas_pendientes)
await message.channel.send(response)
if message.content == 'toful':
response = 'https://www.youtube.com/channel/UCDqo6EnTY9JTyJja-wxopxg/videos'
await message.channel.send(response)
if 'happy birthday' in message.content.lower():
await message.channel.send('Happy Birthday! 🎈🎉')
if 'dani' in message.content.lower():
await message.channel.send('Me voy a chivar')
#Aqui el mensaje debe CONTENER que hago en algun punto
if 'que hago' in message.content.lower():
mensaje = random.choice(tareas_pendientes)
await message.channel.send(mensaje)
if 'kevin' in message.content.lower():
await message.channel.send('Comentario ingenioso preferiblemente hiriente aquí')
if 'toful' in message.content.lower():
respuesta = 'https://www.youtube.com/channel/UCDqo6EnTY9JTyJja-wxopxg/videos'
await message.channel.send(respuesta)
@client.event
async def on_error(event, *args, **kwargs):
with open('err.log', 'a') as f:
if event == 'on_message':
f.write(f'Mensaje que ha fallado: {args[0]}\n')
else:
raise
client.run(TOKEN)
De nuevo es un código vergonzoso, pero como es para gente que no ha visto código en su vida y solo quieren echarse unas risas no pasa nada.
La diferencia entre un código y otro es que el bot solo se activa cuando lo invocas con el prefijo !, pero sin embargo el cliente se lee todos los mensajes que se envían a todos los canales analizando línea a línea si se dice alguna palabra que esté en su código. Además para no liaros, hago un montón de if en lugar de usar alguna solución que nos ahorre consultas innecesarias, …
Si os apetece haceros un bot vosotros mismos, podéis seguir el tutorial en inglés para la parte de Discord, luego instalar el python3 y el python3-pip en un servidor Ubuntu y si cambiáis el TOKEN = …. por el código de vuestro bot debería funcionaros sin problema. GUILD=es el nombre de vuestro servidor, tal y como indicaba el tutorial en inglés.
Todo eso de la variable de entorno ENV es por si subes el código al Git o cosas así. En nuestro caso, pégalo ahí mismo como todo niño de 8 años haría. Esto es muy script kiddie, pero si tengo que enseñaros TODO en 1h se mata la diversión 😉
En clase ya os daré más detalles, el artículo este es por si queréis probarlo en casa o por si alguien busca código funcional para empezar en esto de los bots de Discord.