この記事はPlayGroundAdventCalenderの1日目です。担当はtakumahです。
初日の記事ではDiscordでメールアドレス認証もどきを作った記事を書きます。
PlayGroundではDiscord運用が試験的に動いていますが、その過程でメールアドレスの認証が必要となりました。何が嬉しいのかというと、加入すると発行されるメールアドレスを利用してDiscordサーバーに参加した人が本加入した際に様々なものを見たりイベントに参加できたりできるようになります。(もっといい運用法はもちろんあるとは思いますが)
これをDiscordのbotを使って実装していく様子を見ていきましょう。(discord appの設定などは各自調べてください)
用意するもの
- discord.py 2.3.2
- requests 2.27.1
- python-dotenv 1.0.0
- Token

テストでもない限りTokenを直書きする人はいないとは思いますが、念の為環境変数から持ってこれるようにします。
import os
load_dotenv(os.path.join(os.path.dirname(__file__), ".env"))
TOKEN = os.environ.get("TOKEN")いよいよbotの作成ですが、下準備としてこいつ↓を作ります。

ボタンも簡単に作れるのですが、一度起動した時は動いても再起動したらボタンを押してもタイムアウトするという現象が起きます。これを回避するために永続化を行います。ちょうど公式リポジトリにサンプルが上がってるのでありがたく拝借したのが以下になります。
class ModalView(discord.ui.View):
    def __init__(self, timeout=None):
        super().__init__(timeout=None)
    
    @discord.ui.button(label="認証", style=discord.ButtonStyle.primary, custom_id="pg:verify")
    async def ok(self, interaction: discord.Interaction, button: discord.ui.Button):
        await interaction.response.send_message("test", ephemeral=True)
class PersistentViewBot(commands.Bot):
    def __init__(self):
        intents = discord.Intents.default()
        intents.message_content = True
        super().__init__(command_prefix=commands.when_mentioned_or("?"), intents=intents)
    async def setup_hook(self):
        self.add_view(ModalView())
    async def on_ready(self):
        print(f"Logged in as {self.user} (ID: {self.user.id})")
bot = PersistentViewBot()次にこいつ↓を作ります。

運用の都合上認証にはzapierを用いているので、ぶっちゃけメールアドレスはバケツリレーになりますが、多少のバリデーションはかけておきます。
class VerificationModal(discord.ui.Modal, title="Verify your account"):
    email = discord.ui.TextInput(
        label="mail address",
        style=discord.TextStyle.short,
        placeholder="example@example.com",
        required=True,
    )
    async def on_submit(self, interaction: discord.Interaction):
        if not self.email.value.endswith("@example.com"):
            raise ValueError("有効なメールアドレスを入力してください。")
        
        url = "" # zapierのwebhook url
        response = requests.post(url, json={
            "data": {
                "address": self.email.value,
                "id": interaction.user.id
            }
        })
        if not response.ok:
            raise ValueError("認証でエラーが発生しました。")
        await interaction.response.send_message(f"メールアドレスを送信しました。", ephemeral=True)
    async def on_error(self, interaction: discord.Interaction, error: Exception):
        await interaction.response.send_message(error, ephemeral=True)
あとはレスポンスをsend_modal関数にして完成です。最終的なものは以下になります。
import os
import discord
from discord.ext import commands
from dotenv import load_dotenv
import requests
load_dotenv(os.path.join(os.path.dirname(__file__), ".env"))
class VerificationModal(discord.ui.Modal, title="Verify your account"):
    email = discord.ui.TextInput(
        label="mail address",
        style=discord.TextStyle.short,
        placeholder="example@example.com",
        required=True,
    )
    async def on_submit(self, interaction: discord.Interaction):
        if not self.email.value.endswith("@example.com"):
            raise ValueError("有効なメールアドレスを入力してください。")
        
        url = "" # zapierのwebhook url
        response = requests.post(url, json={
            "data": {
                "address": self.email.value,
                "id": interaction.user.id
            }
        })
        if not response.ok:
            raise ValueError("認証でエラーが発生しました。")
        await interaction.response.send_message(f"メールアドレスを送信しました。", ephemeral=True)
    async def on_error(self, interaction: discord.Interaction, error: Exception):
        await interaction.response.send_message(error, ephemeral=True)
class ModalView(discord.ui.View):
    def __init__(self, timeout=None):
        super().__init__(timeout=None)
    
    @discord.ui.button(label="認証", style=discord.ButtonStyle.primary, custom_id="pg:verify")
    async def ok(self, interaction: discord.Interaction, button: discord.ui.Button):
        await interaction.response.send_modal(VerificationModal())
class PersistentViewBot(commands.Bot):
    def __init__(self):
        intents = discord.Intents.default()
        intents.message_content = True
        super().__init__(command_prefix=commands.when_mentioned_or("?"), intents=intents)
    async def setup_hook(self):
        self.add_view(ModalView())
    async def on_ready(self):
        print(f"Logged in as {self.user} (ID: {self.user.id})")
bot = PersistentViewBot()
bot.run(os.environ.get("TOKEN"))次はamaさんによる記事です。
 
                     
                         
                         
                     
                     
                    