카테고리: 디스코드 봇 만들기

디스코드 봇 만들기#7모달(Modal)로 입력 받기 구현하기 (파이썬 예제)

목차8개 항목

📌 시리즈 이전 편: 디스코드 봇 만들기 #6 - 드롭다운(Select Menu) 메뉴 구현하기

안녕하세요! Mojangcam입니다 😎

지난 편에서는 드롭다운(Select Menu)으로 여러 선택지를 깔끔하게 담는 방법을 배웠죠? 그런데 버튼도 드롭다운도 결국 미리 정해둔 선택지 안에서 고르는 방식이에요. 유저가 직접 자유롭게 텍스트를 써서 보내야 할 때는 어떻게 할까요?

이번 편에서는 바로 그 역할을 하는 모달(Modal)을 만들어볼 거예요! 모달은 유저가 명령어를 누르면 화면 가운데에 뜨는 팝업 입력창이에요. 문의 접수, 건의함, 닉네임 등록, 신고 폼처럼 "유저가 직접 글자를 입력해야 하는" 거의 모든 곳에 쓰입니다. 채팅창에 길게 타이핑하게 하는 것보다 훨씬 깔끔하고, 여러 칸을 한 번에 받을 수도 있어요!

📌 준비물

  • Python 3.8 이상
  • discord.py 최신 2.x 버전 (모달은 2.0부터 지원)
  • 봇 토큰 + 서버에 봇 초대 완료 (시리즈 1편 참고)
cmake
pip install -U discord.py

버전 확인은 이렇게 해요:

CSS
pip show discord.py

Version: 2.x처럼 2.x 이상이면 OK! 모달(Modal)은 discord.py 2.0부터 지원되는 기능이라 1.x 버전에서는 동작하지 않아요.

📌 1단계 - Modal 클래스 만들기

모달의 핵심은 discord.ui.Modal 클래스예요. 이 클래스를 상속받아서 입력칸(TextInput)들을 정의하고, 유저가 제출했을 때의 동작(on_submit)을 작성합니다.

코드
import discord
from discord.ext import commands

class SuggestModal(discord.ui.Modal, title="건의함 📮"):
    # 한 줄짜리 짧은 입력칸
    subject = discord.ui.TextInput(
        label="제목",
        placeholder="건의 제목을 입력해주세요",
        max_length=50,
    )

    # 여러 줄 긴 입력칸
    content = discord.ui.TextInput(
        label="내용",
        style=discord.TextStyle.long,
        placeholder="자세한 내용을 자유롭게 적어주세요!",
        required=True,
        max_length=500,
    )

    async def on_submit(self, interaction: discord.Interaction):
        await interaction.response.send_message(
            f"✅ 건의가 접수됐어요!\n**제목:** {self.subject.value}\n**내용:** {self.content.value}",
            ephemeral=True,
        )

하나씩 뜯어볼게요 💡

  • title : 모달 팝업 맨 위에 크게 표시되는 제목이에요. discord.ui.Modal, title="..."처럼 클래스 정의할 때 넣어줍니다.
  • TextInput : 실제 입력칸 하나예요. 클래스 변수로 선언하면 위에서부터 순서대로 화면에 나와요. (모달 하나에 최대 5개까지 넣을 수 있어요.)
  • label : 입력칸 위에 보이는 이름표예요.
  • style : discord.TextStyle.short(기본, 한 줄) 또는 discord.TextStyle.long(여러 줄)을 고를 수 있어요.
  • placeholder : 아무것도 안 썼을 때 회색으로 보이는 안내 문구예요.
  • required / max_length : 필수 여부와 최대 글자 수예요. required=False로 하면 비워도 제출돼요.
  • on_submit : 유저가 "제출" 버튼을 누르면 실행되는 함수예요. 입력값은 self.입력칸이름.value로 꺼내 씁니다.

💡 여기서 ephemeral=True는 "나만 보이는 메시지"로 답하는 옵션이에요. 건의·신고처럼 남에게 안 보여야 하는 응답에 딱 좋아요!

📌 2단계 - 슬래시 명령어로 모달 띄우기

버튼·드롭다운은 View에 담아서 보냈지만, 모달은 View가 필요 없어요! 대신 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} 로그인 완료!")

@bot.tree.command(name="건의하기", description="서버에 건의사항을 남겨보세요!")
async def suggest(interaction: discord.Interaction):
    await interaction.response.send_modal(SuggestModal())

포인트는 send_modal()은 첫 응답이어야 한다는 거예요. 이미 interaction.response.send_message() 등으로 응답한 뒤에는 모달을 띄울 수 없어요. 그래서 보통 슬래시 명령어를 누르자마자 바로 모달을 여는 식으로 씁니다.

📌 3단계 - 에러 처리 추가하기 (선택)

모달 제출 중 문제가 생겼을 때를 대비해 on_error를 넣어두면 더 안전해요.

Python
async def on_error(self, interaction: discord.Interaction, error: Exception):
    await interaction.response.send_message(
        "⚠️ 처리 중 오류가 났어요. 잠시 후 다시 시도해주세요.", ephemeral=True
    )
    print(error)

이 함수는 위 SuggestModal 클래스 안에 on_submit 아래에 같은 들여쓰기로 넣어주면 됩니다.

📌 전체 코드 (복붙 OK)

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

class SuggestModal(discord.ui.Modal, title="건의함 📮"):
    subject = discord.ui.TextInput(
        label="제목",
        placeholder="건의 제목을 입력해주세요",
        max_length=50,
    )
    content = discord.ui.TextInput(
        label="내용",
        style=discord.TextStyle.long,
        placeholder="자세한 내용을 자유롭게 적어주세요!",
        required=True,
        max_length=500,
    )

    async def on_submit(self, interaction: discord.Interaction):
        await interaction.response.send_message(
            f"✅ 건의가 접수됐어요!\n**제목:** {self.subject.value}\n**내용:** {self.content.value}",
            ephemeral=True,
        )

    async def on_error(self, interaction: discord.Interaction, error: Exception):
        await interaction.response.send_message(
            "⚠️ 처리 중 오류가 났어요. 잠시 후 다시 시도해주세요.", 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} 로그인 완료!")

@bot.tree.command(name="건의하기", description="서버에 건의사항을 남겨보세요!")
async def suggest(interaction: discord.Interaction):
    await interaction.response.send_modal(SuggestModal())

bot.run("봇_토큰_입력")

📌 실행 결과

/건의하기를 입력하면 화면 가운데에 "건의함" 팝업이 뜨고, 제목과 내용 두 칸이 나와요. 입력하고 제출을 누르면 "건의가 접수됐어요!"라는 나만 보이는 메시지가 옵니다.

💡 보너스: 입력받은 내용을 관리자 채널로 보내고 싶다면, on_submit 안에서 특정 채널을 찾아 임베드로 전송하면 진짜 "건의함 봇"이 완성돼요!

코드
async def on_submit(self, interaction: discord.Interaction):
    log_channel = interaction.guild.get_channel(관리자채널_ID)
    embed = discord.Embed(title=f"📮 새 건의: {self.subject.value}", description=self.content.value)
    embed.set_footer(text=f"보낸 사람: {interaction.user}")
    await log_channel.send(embed=embed)
    await interaction.response.send_message("✅ 건의가 접수됐어요!", ephemeral=True)

📌 자주 나는 에러

1. InteractionResponded: This interaction has already been responded to

이미 응답한 상호작용에 send_modal()을 또 호출하면 나는 에러예요. 모달은 반드시 첫 응답으로 띄워야 해요. 버튼 콜백 등에서 다른 응답을 한 뒤에는 모달을 열 수 없습니다.

2. 입력칸이 6개 이상이라 안 뜸

모달 한 개에는 TextInput을 최대 5개까지만 넣을 수 있어요. 더 많이 받아야 한다면 내용을 나눠 여러 단계로 설계하세요.

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

입력값을 꺼낼 때 self.subject가 아니라 반드시 self.subject.value처럼 .value를 붙여야 실제 텍스트가 나와요. .value를 빼먹으면 TextInput 객체 자체가 나옵니다.

📌 마무리

오늘은 모달(Modal)로 유저에게 직접 텍스트를 입력받는 방법을 알아봤어요! 버튼 → 드롭다운 → 모달까지 오면서 이제 디스코드의 상호작용 UI를 거의 다 다뤄봤네요 🙌 다음 편에서는 임베드(Embed)에 이미지와 필드를 넣어 예쁘게 꾸미는 방법을 다뤄볼게요. 기대해주세요!

📚 디스코드 봇 만들기 시리즈 #4 Slash Command로 임베드 전송하기 #5 버튼(Button) 상호작용 구현하기 #6 드롭다운(Select Menu) 메뉴 구현하기 #7 모달(Modal)로 입력 받기 ← 지금 이 글!

연재 · 32편 중 7번째

디스코드 봇 만들기

  1. #1디스코드 봇 만들기 - Slash Command 완전 정복!
  2. #2Slash Command에 옵션(Arguments) 추가하기!
  3. #3Slash Command에 서브 커맨드(Subcommand) 넣기!
  4. #4Slash Command로 임베드 전송하기!
  5. #5버튼(Button) 상호작용 구현하기!
  6. #6드롭다운(Select Menu) 메뉴 구현하기!
  7. #7모달(Modal)로 입력 받기 구현하기 (파이썬 예제)지금 읽는 글
  8. #8임베드(Embed) 이미지·필드로 꾸미기 (파이썬 예제)
  9. #9AI(Claude·ChatGPT API) 연동하기! (파이썬 예제)
  10. #10다국어(i18n) 지원하기! (파이썬 예제)
  11. #11플라스크(Flask)로 웹 대시보드 만들기! (파이썬 예제)
  12. #12명령어 쿨다운(Cooldown)·권한(Permission) 관리하기! (파이썬 예제)
  13. #13역할·채널 자동화로 서버 관리하기! (파이썬 예제)
  14. #14데이터 저장하기! JSON vs SQLite (파이썬 예제)
  15. #15슬래시 명령어 그룹(Group)으로 명령어 정리하기! (파이썬 예제)
  16. #16리마인더·알림 기능 만들기! 봇이 먼저 말 걸게 하기 (파이썬 예제)
  17. #17RAG로 문서 기반 답변봇 만들기! 우리 서버 전용 FAQ 봇 (파이썬 예제)
  18. #18투표(Poll) 기능 만들기! 다수결로 정하는 서버 규칙 (파이썬 예제)
  19. #19Cog로 코드 쪼개기! 1000줄 봇도 안 더러워지는 구조 (파이썬 예제)
  20. #20자동 환영 메시지·온보딩 만들기! 규칙 동의 버튼으로 자동 역할 부여 (파이썬 예제)
  21. #21키워드 자동응답·금지어 필터 만들기! 경고 누적으로 자동 제재까지 (파이썬 예제)
  22. #22슬래시 명령어 자동완성(Autocomplete) 만들기! 금지어를 골라서 추가·삭제 (파이썬 예제)
  23. #23aiosqlite로 비동기 DB 제대로 설계하기! JSON 탈출 (파이썬 예제)
  24. #24채널 난독화(Channel Obfuscation) 대응하기! 11/16부터 안 보이는 채널이 생겨요 (파이썬 예제)
  25. #25봇이 죽지 않게! 에러 핸들링·로깅 체계 잡기 (파이썬 예제)
  26. #26티켓(Ticket) 문의 시스템 만들기! 버튼 하나로 1:1 상담 채널 자동 생성 (파이썬 예제)
  27. #27티켓 담당자 배정·우선순위 만들기! 스태프가 여러 명이어도 안 꼬이게 (파이썬 예제)
  28. #28레벨·경험치(XP) 시스템 만들기! 채팅할수록 레벨업, 랭킹판까지 (파이썬 예제)
  29. #29화폐·상점(Economy) 시스템 만들기! 코인 벌어서 아이템 사기 (파이썬 예제)
  30. #30투표·설문(Poll) 봇 만들기! 네이티브 투표 + 버튼으로 실시간 집계까지 (파이썬 예제) 🗳️
  31. #32서버 로그·감사(Audit Log) 채널 만들기! 메시지 삭제·수정·입퇴장·추방까지 자동 기록 (파이썬 예제) 🕵️
  32. #33임시 음성방 확장, 파티방 만들기! 방장 위임·강퇴·초대 전용(비공개) 방 + 전용 채팅방 자동 생성 (파이썬 예제) 🎉
전체 32편 보기접기

글쓴이

Mojangcam

직접 코드를 짜고, 서버를 돌리고, 막힌 에러를 해결하며 배운 것들을 기록하는 개발·IT 실전 기록 블로그