Preguntas frecuentes

Esta es una lista de preguntas frecuentes sobre el uso de Pycord y sus módulos de extensión. Siéntete libre de sugerir una nueva pregunta o envía una a través de una pull request.

Corutinas

Las preguntas sobre corutinas y asyncio pertenecen aquí.

¿Qué es una corutina?

Una coroutine es una función que debe ser llamada con await o yield from. Cuando Python se encuentra con un await, se detiene la ejecución de la función en ese punto y se trabaja en otras cosas hasta que regresa a ese punto y termina su trabajo. Esto le permite a tu programa hacer múltiples cosas al mismo tiempo sin usar hilos o multiprocesamiento complicado.

Si te olvidas de utilizar await en una corrutina, entonces ésta no funcionará. Nunca olvides utilizar await en una corutina.

¿Dónde puedo usar await?

Solo puedes utilizar await dentro de funciones async def y en ningún otro lugar.

¿Qué significa «bloquear»?

En la programación asíncrona una llamada de bloqueo es esencialmente todas las partes de la función que no son await. Sin embargo, no desesperes, porque no todas las formas de bloqueo son malas! Usar llamadas de bloqueo es inevitable, pero debes trabajar en asegurarte de no bloquear excesivamente las funciones. Recuerda, si bloqueas durante demasiado tiempo, tu bot se congelará ya que no ha detenido la ejecución de la función en ese momento para hacer otras cosas.

Si activas el registro, esta biblioteca intentará advertirte de que un bloqueo está ocurriendo con el mensaje: Heartbeat blocked for more than N seconds. Puedes ver Setting Up Logging para detalles sobre como activar el registro.

Una fuente común de bloqueo durante demasiado tiempo es algo como time.sleep(). No hagas eso. Utiliza asyncio.sleep() en su lugar. Similar a este ejemplo:

# bad
time.sleep(10)

# good
await asyncio.sleep(10)

Otra fuente común de bloqueo durante demasiado tiempo es realizar peticiones HTTP con el famoso módulo requests. Mientras que requests es un asombroso módulo para la programación no asincrónica, no es una buena opción para asyncio porque ciertas peticiones pueden bloquear el loop de eventos por demasiado tiempo. En su lugar, usa el módulo aiohttp el cual viene instalado de lado con esta biblioteca.

Consideremos el siguiente ejemplo:

# bad
r = requests.get('http://aws.random.cat/meow')
if r.status_code == 200:
    js = r.json()
    await channel.send(js['file'])

# good
async with aiohttp.ClientSession() as session:
    async with session.get('http://aws.random.cat/meow') as r:
        if r.status == 200:
            js = await r.json()
            await channel.send(js['file'])

General

Las preguntas generales sobre la biblioteca están aquí.

¿Dónde puedo encontrar ejemplos de uso?

Puedes encontrar ejemplos de código en la carpeta de ejemplos en el repositorio.

¿Cómo establezco el estado «Jugando»?

El argumento de palabra clave activity puede ser pasado en el constructor Client o Client.change_presence(), dado un objeto Activity.

El constructor puede ser usado para actividades estáticas, mientras que Client.change_presence() puede ser usado para actualizar la actividad en tiempo de ejecución.

Advertencia

No es para nada recomendable usar Cliente.change_presence() o llamadas de API en on_ready() ya que este evento puede ser llamado muchas veces en tiempo de ejecución, no solo una vez.

Existe una gran probabilidad de desconexión si las presencias son cambiadas justo después de conectarse.

El tipo de estado (jugando, escuchando, streaming, viendo) puede ser establecido usando el enum ActivityType. Por motivos de optimización de memoria, algunas actividades son ofrecidas en versiones reducidas:

Al juntar estas dos piezas de información, obtenemos lo siguiente:

client = discord.Client(activity=discord.Game(name='my game'))

# or, for watching:
activity = discord.Activity(name='my activity', type=discord.ActivityType.watching)
client = discord.Client(activity=activity)

¿Cómo puedo enviar un mensaje a un canal en específico?

Debemos obtener el canal directamente y luego llamar al método apropiado. Ejemplo:

channel = client.get_channel(12324234183172)
await channel.send('hello')

¿Cómo envío un mensaje directo?

Obtén una instancia de User o Member y llama al método abc.Messageable.send(). Por ejemplo:

user = client.get_user(381870129706958858)
await user.send('👀')

Si estás respondiendo a un evento, como on_message(), ya posees el objeto User a través de Message.author:

await message.author.send('👋')

¿Cómo obtengo el ID de un mensaje enviado?

abc.Messageable.send() retorna el Message que fue enviado. Puedes acceder al ID del mensaje a través de Message.id:

message = await channel.send('hmm…')
message_id = message.id

¿Cómo puedo adjuntar una imagen?

Para adjuntar un archivo a Discord, tienes que utilizar una instancia de File.

La clase File acepta dos parámetros, un objeto tipo archivo (o la ruta de este) y el nombre del archivo a pasar a Discord al momento de subirlo.

Si quieres subir una imagen, es tan simple como:

await channel.send(file=discord.File('my_file.png'))

Si tienes un objeto tipo archivo, puedes hacer lo siguiente:

with open('my_file.png', 'rb') as fp:
    await channel.send(file=discord.File(fp, 'new_filename.png'))

Para subir varios archivos, puedes utilizar el argumento de palabra clave files en lugar de file:

my_files = [
    discord.File('result.zip'),
    discord.File('teaser_graph.png'),
]
await channel.send(files=my_files)

Si quiere subir algo desde una URL, tendrás que realizar una petición HTTP utilizando aiohttp y luego pasar una instancia de io.BytesIO a la clase File. Tal que así:

import io
import aiohttp

async with aiohttp.ClientSession() as session:
    async with session.get(my_url) as resp:
        if resp.status != 200:
            return await channel.send('Could not download file...')
        data = io.BytesIO(await resp.read())
        await channel.send(file=discord.File(data, 'cool_image.png'))

¿Cómo puedo añadir una reacción a un mensaje?

Utiliza el método Message.add_reaction().

Si quieres usar emojis unicode, debes pasar un unicode válido en una cadena. En tu código, puedes escribir esto de varias maneras diferentes:

  • '👍'

  • '\U0001F44D'

  • '\N{THUMBS UP SIGN}'

Ejemplo rápido:

emoji = '\N{THUMBS UP SIGN}'
# or '\U0001f44d' or '👍'
await message.add_reaction(emoji)

En caso de que quieras usar emojis que vienen de un mensaje, ya obtienes sus puntos de código en el contenido sin necesidad de hacer nada especial. No puedes enviar abreviaturas de estilo ':thumbsup:'`.

For custom emoji, you should pass an instance of GuildEmoji or AppEmoji. You can also pass a '<:name:id>' string, but if you can use said emoji, you should be able to use Client.get_emoji() to get an emoji via ID or use utils.find()/ utils.get() on Client.emojis or Guild.emojis collections.

El nombre y ID de un emoji personalizado puede ser encontrado en Discord añadiendo a :custom_emoji: una barra invertida. Por ejemplo, al enviar el mensaje \:python3: en Discord, el resultado será <:python3:232720527448342530>.

Ejemplo rápido:

# if you have the ID already
emoji = client.get_emoji(310177266011340803)
await message.add_reaction(emoji)

# no ID, do a lookup
emoji = discord.utils.get(guild.emojis, name='LUL')
if emoji:
    await message.add_reaction(emoji)

# if you have the name and ID of a custom emoji:
emoji = '<:python3:232720527448342530>'
await message.add_reaction(emoji)

¿Cómo puedo pasar una corrutina a la función «after» del reproductor?

El reproductor de música de la biblioteca se lanza en un hilo separado, por consecuencia, no se ejecuta dentro de una corrutina. Esto no significa que no es posible llamar una corrutina en el parámetro after. Para hacerlo debes pasar un callable que contenga un par de aspectos.

De lo primero que debes ser consciente es que llamar a una corrutina no es una operación segura en hilos. Puesto que estamos en otro hilo, debemos tomar precaución al llamar operaciones seguras en hilos así las cosas no fallan. Para nuestra suerte, asyncio viene con la función asyncio.run_coroutine_threadsafe() que nos permite llamar a una corrutina desde otro hilo.

Sin embargo, esta función devuelve un Future y para llamarlo tenemos que obtener su resultado. Poniendo todo esto junto podemos hacer lo siguiente:

def my_after(error):
    coro = some_channel.send('Song is done!')
    fut = asyncio.run_coroutine_threadsafe(coro, client.loop)
    try:
        fut.result()
    except:
        # an error happened sending the message
        pass

voice.play(discord.FFmpegPCMAudio(url), after=my_after)

¿Cómo puedo ejecutar algo en segundo plano?

Comprueba el ejemplo background_task.py.

¿Cómo obtengo un modelo en específico?

Hay varias maneras de hacer esto. Si tienes un ID de un modelo en específico entonces puedes usar una de las siguientes funciones:

Lo siguiente usa una solicitud HTTP:

Si las funciones anteriores no te ayudan, entonces usa utils.find() o utils.get() para encontrar modelos específicos.

Ejemplo rápido:

# find a guild by name
guild = discord.utils.get(client.guilds, name='My Server')

# make sure to check if it's found
if guild is not None:
    # find a channel by name
    channel = discord.utils.get(guild.text_channels, name='cool-channel')

¿Cómo hago una petición web?

Para hacer una solicitud, deberás usar una librería sin bloqueo. Esta biblioteca ya usa y requiere una librería de terceros para hacer peticiones, aiohttp.

Ejemplo rápido:

async with aiohttp.ClientSession() as session:
    async with session.get('http://aws.random.cat/meow') as r:
        if r.status == 200:
            js = await r.json()

See aiohttp’s full documentation for more information.

¿Cómo uso una imagen local para una imagen en una embed?

Los casos especiales de Discord cargan una imagen adjunta y la usan dentro de una embed para que no se muestre por separado, sino en la thumbnail (miniatura) de la embed, imagen, el pie o el icono de autor.

Para hacerlo, adjunta una imagen normalmente con abc.Messageable.send(), y establece la URL de la imagen a attachment://image.png, donde image.png es el nombre de la imagen que vas a enviar.

Ejemplo rápido:

file = discord.File("path/to/my/image.png", filename="image.png")
embed = discord.Embed()
embed.set_image(url="attachment://image.png")
await channel.send(file=file, embed=embed)

¿Hay un evento para la creación de registros de auditoría?

A partir de la versión 2.5, puedes recibir entradas del registro de auditoría con el evento on_audit_log_entry().

Extensiones de comandos

Las preguntas con respecto a discord.ext.commands pertenecen aquí.

¿Por qué on_message hace que mis comandos dejen de funcionar?

Sobrescribir el evento predeterminado on_message prohíbe a cualquier comando extra de ejecutarse. Para arreglar esto, añade bot.process_commands(message) al final de tu on_message. Por ejemplo:

@bot.event
async def on_message(message):
    # do some extra stuff here

    await bot.process_commands(message)

Como alternativa, puedes colocar la lógica de tu on_message en un listener. Con esta configuración, NO deberías llamar manualmente a bot.process_commands(). Esto también te permite realizar múltiples cosas asincrónicamente en respuesta a un mensaje. Ejemplo:

@bot.listen('on_message')
async def whatever_you_want_to_call_it(message):
    # do stuff here
    # do not process commands here

¿Por qué mis argumentos requieren comillas?

En un simple comando definido como:

@bot.command()
async def echo(ctx, message: str):
    await ctx.send(message)

Llamándolo a través de ?echo a b c solo obtendrá el primer argumento e ignorará el resto. Para arreglar esto deberías llamar al comando a través de ?echo "a b c" o cambiar la definición de tu comando para obtener un comportamiento «consumir al resto». Ejemplo:

@bot.command()
async def echo(ctx, *, message: str):
    await ctx.send(message)

Esto te permitirá usar ?echo a b c sin la necesidad de comillas.

¿Cómo obtengo el mensaje original?

El Context contiene el atributo message para obtener el mensaje original.

Ejemplo:

@bot.command()
async def length(ctx):
    await ctx.send(f'Your message is {len(ctx.message.content)} characters long.')

¿Cómo hago un subcomando?

Usa el decorador group(). Esto transformará la retrollamada en una Group que te permitirá añadir comandos al grupo operando como un «subcomando». Estos grupos también pueden ser anidados arbitrariamente.

Ejemplo:

@bot.group()
async def git(ctx):
    if ctx.invoked_subcommand is None:
        await ctx.send('Invalid git command passed...')

@git.command()
async def push(ctx, remote: str, branch: str):
    await ctx.send(f'Pushing to {remote} {branch}')

Esto podría ser usado como ?git push origin master.