Categoría: Bots de Discord

Crear un bot de Discord#7Pide texto al usuario con un modal (Modal) (ejemplo en Python)

Esta entrada es una traducción del original en coreano.

Índice8 secciones

📌 Episodio anterior de la serie: Crear un bot de Discord #6 - Implementa menús desplegables (Select Menu)

¡Hola! Soy Mojangcam 😎

En el episodio anterior aprendimos a ofrecer varias opciones de forma ordenada con un menú desplegable (Select Menu), ¿verdad? Pero tanto los botones como los menús desplegables te hacen elegir entre opciones definidas de antemano. ¿Qué haces cuando el usuario tiene que escribir y enviar texto libremente?

En este episodio vamos a crear justo lo que cumple esa función: ¡un modal (Modal)! Un modal es una ventana emergente de entrada que aparece en el centro de la pantalla cuando el usuario ejecuta un comando. Se usa en casi cualquier sitio donde "el usuario tiene que escribir texto": recepción de consultas, buzones de sugerencias, registro de apodos, formularios de reporte... Es mucho más limpio que hacerle escribir un texto largo en el chat, ¡y además puedes recibir varios campos de una sola vez!

📌 Lo que necesitas

  • Python 3.8 o superior
  • discord.py la última versión 2.x (los modales están disponibles desde la 2.0)
  • Token del bot + el bot ya invitado a tu servidor (mira el episodio 1 de la serie)
cmake
pip install -U discord.py

Para comprobar la versión, haz esto:

CSS
pip show discord.py

Si ves algo como Version: 2.x, es decir, 2.x o superior, ¡todo OK! El modal (Modal) es una función disponible desde discord.py 2.0, así que no funciona en la versión 1.x.

📌 Paso 1 - Crear la clase Modal

La clave del modal es la clase discord.ui.Modal. Heredas de esta clase, defines los campos de entrada (TextInput) y escribes lo que ocurre cuando el usuario envía el formulario (on_submit).

Código
import discord
from discord.ext import commands

class SuggestModal(discord.ui.Modal, title="Buzón de sugerencias 📮"):
    # Campo corto de una sola línea
    subject = discord.ui.TextInput(
        label="Título",
        placeholder="Escribe el título de tu sugerencia",
        max_length=50,
    )

    # Campo largo de varias líneas
    content = discord.ui.TextInput(
        label="Contenido",
        style=discord.TextStyle.long,
        placeholder="¡Escribe los detalles con total libertad!",
        required=True,
        max_length=500,
    )

    async def on_submit(self, interaction: discord.Interaction):
        await interaction.response.send_message(
            f"✅ ¡Hemos recibido tu sugerencia!\n**Título:** {self.subject.value}\n**Contenido:** {self.content.value}",
            ephemeral=True,
        )

Vamos a verlo parte por parte 💡

  • title : el título grande que aparece arriba del todo en la ventana emergente del modal. Se pone al definir la clase, así: discord.ui.Modal, title="...".
  • TextInput : un campo de entrada. Si los declaras como variables de clase, aparecen en pantalla en orden, de arriba abajo. (Puedes poner hasta 5 en un solo modal.)
  • label : la etiqueta que se ve encima del campo.
  • style : puedes elegir discord.TextStyle.short (por defecto, una línea) o discord.TextStyle.long (varias líneas).
  • placeholder : el texto de ayuda en gris que se muestra cuando el campo está vacío.
  • required / max_length : si el campo es obligatorio y el número máximo de caracteres. Con required=False se puede enviar aunque esté vacío.
  • on_submit : la función que se ejecuta cuando el usuario pulsa el botón "Enviar". Los valores introducidos se sacan con self.입력칸이름.value (cambia la parte del medio por el nombre de tu campo).

💡 Aquí, ephemeral=True es la opción para responder con un "mensaje que solo ves tú". ¡Es perfecta para respuestas que los demás no deben ver, como sugerencias o reportes!

📌 Paso 2 - Abrir el modal con un Slash Command

Los botones y los menús desplegables los enviábamos dentro de una View, pero ¡el modal no necesita View! En su lugar, lo abres directamente con interaction.response.send_modal().

Python
intents = discord.Intents.default()
bot = commands.Bot(command_prefix="!", intents=intents)

@bot.event
async def on_ready():
    await bot.tree.sync()
    print(f"✅ ¡{bot.user} ha iniciado sesión!")

@bot.tree.command(name="건의하기", description="¡Deja tu sugerencia para el servidor!")
async def suggest(interaction: discord.Interaction):
    await interaction.response.send_modal(SuggestModal())

La clave es que send_modal() tiene que ser la primera respuesta. Si ya respondiste con interaction.response.send_message() u otro método, ya no puedes abrir el modal. Por eso, lo normal es abrir el modal en cuanto el usuario ejecuta el slash command.

📌 Paso 3 - Añadir manejo de errores (opcional)

Si añades on_error por si algo falla al enviar el modal, el bot será más seguro.

Python
async def on_error(self, interaction: discord.Interaction, error: Exception):
    await interaction.response.send_message(
        "⚠️ Ocurrió un error al procesarlo. Inténtalo de nuevo en un momento.", ephemeral=True
    )
    print(error)

Esta función va dentro de la clase SuggestModal de arriba, debajo de on_submit y con la misma sangría.

📌 Código completo (puedes copiar y pegar)

Python
# discord.py 2.x / Python 3.8+
import discord
from discord.ext import commands

class SuggestModal(discord.ui.Modal, title="Buzón de sugerencias 📮"):
    subject = discord.ui.TextInput(
        label="Título",
        placeholder="Escribe el título de tu sugerencia",
        max_length=50,
    )
    content = discord.ui.TextInput(
        label="Contenido",
        style=discord.TextStyle.long,
        placeholder="¡Escribe los detalles con total libertad!",
        required=True,
        max_length=500,
    )

    async def on_submit(self, interaction: discord.Interaction):
        await interaction.response.send_message(
            f"✅ ¡Hemos recibido tu sugerencia!\n**Título:** {self.subject.value}\n**Contenido:** {self.content.value}",
            ephemeral=True,
        )

    async def on_error(self, interaction: discord.Interaction, error: Exception):
        await interaction.response.send_message(
            "⚠️ Ocurrió un error al procesarlo. Inténtalo de nuevo en un momento.", ephemeral=True
        )
        print(error)

intents = discord.Intents.default()
bot = commands.Bot(command_prefix="!", intents=intents)

@bot.event
async def on_ready():
    await bot.tree.sync()
    print(f"✅ ¡{bot.user} ha iniciado sesión!")

@bot.tree.command(name="건의하기", description="¡Deja tu sugerencia para el servidor!")
async def suggest(interaction: discord.Interaction):
    await interaction.response.send_modal(SuggestModal())

bot.run("TU_TOKEN_DEL_BOT")

📌 Resultado

Al escribir /건의하기 (sugerir), aparece en el centro de la pantalla la ventana emergente "Buzón de sugerencias" (건의함) con dos campos: Título (제목) y Contenido (내용). Al rellenarlos y pulsar Enviar, recibes un mensaje que solo ves tú: "¡Hemos recibido tu sugerencia!".

💡 Extra: si quieres enviar lo que recibes a un canal de administradores, busca ese canal dentro de on_submit y envíalo como embed. ¡Así tendrás un auténtico "bot de buzón de sugerencias"!

Código
async def on_submit(self, interaction: discord.Interaction):
    log_channel = interaction.guild.get_channel(관리자채널_ID)
    embed = discord.Embed(title=f"📮 Nueva sugerencia: {self.subject.value}", description=self.content.value)
    embed.set_footer(text=f"Enviado por: {interaction.user}")
    await log_channel.send(embed=embed)
    await interaction.response.send_message("✅ ¡Hemos recibido tu sugerencia!", ephemeral=True)

📌 Errores frecuentes

1. InteractionResponded: This interaction has already been responded to

Este error aparece si vuelves a llamar a send_modal() en una interacción a la que ya respondiste. El modal siempre tiene que abrirse como primera respuesta. Si ya diste otra respuesta, por ejemplo en el callback de un botón, ya no puedes abrir el modal.

2. No aparece porque tiene 6 campos o más

En un solo modal caben hasta 5 TextInput como máximo. Si necesitas recoger más datos, divide el contenido y diséñalo en varios pasos.

3. AttributeError: 'str' object has no attribute 'value'

Al sacar el valor introducido, no uses self.subject: tienes que añadir .value, como en self.subject.value, para obtener el texto real. Si olvidas el .value, obtienes el propio objeto TextInput.

📌 Para terminar

¡Hoy vimos cómo pedir al usuario que escriba texto directamente con un modal (Modal)! Pasando por botones → menús desplegables → modales, ya hemos visto casi toda la UI de interacción de Discord 🙌 En el próximo episodio veremos cómo decorar los embeds (Embed) con imágenes y campos para que queden bonitos. ¡No te lo pierdas!

📚 Serie Crear un bot de Discord #4 Envía embeds con Slash Commands #5 Implementa interacciones con botones (Button) #6 Implementa menús desplegables (Select Menu) #7 Pide texto al usuario con un modal (Modal) ← ¡estás leyendo este!

Serie · Parte 7 de 32

Crear un bot de Discord

  1. #1Crear un bot de Discord - ¡Domina por completo los Slash Commands!
  2. #2¡Añade opciones (argumentos) a tus Slash Commands!
  3. #3¡Añade subcomandos (Subcommand) a tus Slash Commands!
  4. #4¡Envía embeds con Slash Commands!
  5. #5¡Implementa interacciones con botones (Button)!
  6. #6¡Implementa menús desplegables (Select Menu)!
  7. #7Pide texto al usuario con un modal (Modal) (ejemplo en Python)Estás aquí
  8. #8Decora embeds (Embed) con imágenes y campos (ejemplo en Python)
  9. #9¡Integra la IA (API de Claude y ChatGPT)! (ejemplo en Python)
  10. #10¡Añade soporte multilingüe (i18n)! (ejemplo en Python)
  11. #11¡Crea un panel web con Flask! (ejemplo en Python)
  12. #12¡Gestiona los cooldowns y permisos de tus comandos! (ejemplo en Python)
  13. #13¡Administra tu servidor automatizando roles y canales! (ejemplo en Python)
  14. #14¡Guarda tus datos! JSON vs SQLite (ejemplo en Python)
  15. #15슬래시 명령어 그룹(Group)으로 명령어 정리하기! (파이썬 예제) (en coreano)
  16. #16리마인더·알림 기능 만들기! 봇이 먼저 말 걸게 하기 (파이썬 예제) (en coreano)
  17. #17RAG로 문서 기반 답변봇 만들기! 우리 서버 전용 FAQ 봇 (파이썬 예제) (en coreano)
  18. #18투표(Poll) 기능 만들기! 다수결로 정하는 서버 규칙 (파이썬 예제) (en coreano)
  19. #19Cog로 코드 쪼개기! 1000줄 봇도 안 더러워지는 구조 (파이썬 예제) (en coreano)
  20. #20자동 환영 메시지·온보딩 만들기! 규칙 동의 버튼으로 자동 역할 부여 (파이썬 예제) (en coreano)
  21. #21키워드 자동응답·금지어 필터 만들기! 경고 누적으로 자동 제재까지 (파이썬 예제) (en coreano)
  22. #22슬래시 명령어 자동완성(Autocomplete) 만들기! 금지어를 골라서 추가·삭제 (파이썬 예제) (en coreano)
  23. #23aiosqlite로 비동기 DB 제대로 설계하기! JSON 탈출 (파이썬 예제) (en coreano)
  24. #24채널 난독화(Channel Obfuscation) 대응하기! 11/16부터 안 보이는 채널이 생겨요 (파이썬 예제) (en coreano)
  25. #25봇이 죽지 않게! 에러 핸들링·로깅 체계 잡기 (파이썬 예제) (en coreano)
  26. #26티켓(Ticket) 문의 시스템 만들기! 버튼 하나로 1:1 상담 채널 자동 생성 (파이썬 예제) (en coreano)
  27. #27티켓 담당자 배정·우선순위 만들기! 스태프가 여러 명이어도 안 꼬이게 (파이썬 예제) (en coreano)
  28. #28레벨·경험치(XP) 시스템 만들기! 채팅할수록 레벨업, 랭킹판까지 (파이썬 예제) (en coreano)
  29. #29화폐·상점(Economy) 시스템 만들기! 코인 벌어서 아이템 사기 (파이썬 예제) (en coreano)
  30. #30¡Crea un bot de encuestas y votaciones (Poll)! Encuestas nativas + botones con recuento en tiempo real (ejemplo en Python) 🗳️
  31. #32¡Crea un canal de registros y auditoría (Audit Log) del servidor! Registra automáticamente mensajes borrados y editados, entradas, salidas y expulsiones (ejemplo en Python) 🕵️
  32. #33임시 음성방 확장, 파티방 만들기! 방장 위임·강퇴·초대 전용(비공개) 방 + 전용 채팅방 자동 생성 (파이썬 예제) 🎉 (en coreano)
Ver las 32Contraer

Autor

Mojangcam

Un registro práctico de desarrollo e IT: código que escribo, servidores que mantengo y los errores que resuelvo por el camino.