カテゴリー: Discordボット開発

Discordボット開発#7モーダル(Modal)で入力を受け取る方法 (Pythonサンプル付き)

この記事は韓国語の原文から翻訳したものです。

目次8項目

📌 シリーズ前回: Discordボット開発 #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="目安箱 📮"):
    # 1行だけの短い入力欄
    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,
        )

1つずつ見ていきましょう 💡

  • title : モーダルのポップアップの一番上に大きく表示されるタイトルです。discord.ui.Modal, title="..."のように、クラスを定義するときに指定します。
  • TextInput : 実際の入力欄1つ分です。クラス変数として宣言すると、上から順番に画面に表示されます。(1つのモーダルに最大5個まで入れられます。)
  • label : 入力欄の上に表示される名札です。
  • style : discord.TextStyle.short(デフォルト、1行)または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です。

📌 全体のコード (コピペ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("ボット_トークン_入力")

📌 実行結果

/건의하기と入力すると、画面の中央に「目安箱」のポップアップが表示され、件名と内容の2つの入力欄が出てきます。入力して送信を押すと、「提案を受け付けました!」という自分だけに見えるメッセージが届きます。

💡 ボーナス: 受け取った内容を管理者チャンネルに送りたい場合は、on_submitの中で特定のチャンネルを探して埋め込み(Embed)で送信すれば、本格的な「目安箱ボット」の完成です!

コード
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個以上あって表示されない

1つのモーダルにはTextInputを最大5個までしか入れられません。もっと多くの項目を受け取りたい場合は、内容を分けて複数のステップで設計しましょう。

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

入力値を取り出すときは、self.subjectではなく必ずself.subject.valueのように.valueを付けないと、実際のテキストは得られません。.valueを付け忘れると、TextInputオブジェクトそのものが返ってきます。

📌 まとめ

今回はモーダル(Modal)でユーザーから直接テキスト入力を受け取る方法を紹介しました!ボタン → ドロップダウン → モーダルと進んできて、これでDiscordのインタラクションUIはほぼ一通り扱ったことになりますね 🙌 次回は埋め込み(Embed)に画像やフィールドを入れてきれいに飾る方法を扱います。お楽しみに!

📚 Discordボット開発シリーズ #4 スラッシュコマンドで埋め込みを送信する #5 ボタン(Button)のインタラクションを実装する #6 ドロップダウン(Select Menu)メニューを実装する #7 モーダル(Modal)で入力を受け取る ← 今読んでいる記事!

連載 · 全32回中7回目

Discordボット開発

  1. #1Discordボット開発 - スラッシュコマンド(Slash Command)完全攻略!
  2. #2スラッシュコマンドにオプション(Arguments)を追加しよう!
  3. #3スラッシュコマンドにサブコマンド(Subcommand)を追加しよう!
  4. #4スラッシュコマンドで埋め込み(Embed)を送信しよう!
  5. #5ボタン(Button)のインタラクションを実装しよう!
  6. #6ドロップダウン(Select Menu)メニューを実装しよう!
  7. #7モーダル(Modal)で入力を受け取る方法 (Pythonサンプル付き)現在の記事
  8. #8埋め込み(Embed)を画像・フィールドで飾る (Pythonサンプル付き)
  9. #9AI(Claude・ChatGPT API)と連携する! (Pythonサンプル付き)
  10. #10多言語(i18n)に対応する! (Pythonサンプル付き)
  11. #11FlaskでWebダッシュボードを作る! (Pythonサンプル付き)
  12. #12コマンドのクールダウン(Cooldown)・権限(Permission)を管理しよう! (Pythonサンプル付き)
  13. #13ロール・チャンネルの自動化でサーバーを管理しよう! (Pythonサンプル付き)
  14. #14データを保存しよう!JSON vs SQLite (Pythonサンプル付き)
  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)ボットを作ろう!ネイティブ投票+ボタンでリアルタイム集計まで(Pythonサンプル) 🗳️
  31. #32サーバーログ・監査ログ(Audit Log)チャンネルを作ろう!メッセージの削除・編集・入退室・キックまで自動記録(Pythonサンプル) 🕵️
  32. #33임시 음성방 확장, 파티방 만들기! 방장 위임·강퇴·초대 전용(비공개) 방 + 전용 채팅방 자동 생성 (파이썬 예제) 🎉 (韓国語)
全32回を表示閉じる

著者

Mojangcam

自分でコードを書き、サーバーを動かし、詰まったエラーを解決しながら学んだことを記録する開発・IT実践ブログ