Discord.py 初学者に向けて


## 1. はじめに

Discord.pyとは...?
- DiscordのAPIラッパーをPython向けに書いたモジュール
- 主に非同期処理で動いている。例えば関数は`async def hoge():`で動いてるし、処理は`await`を使う。

## 2. Pythonの基本構文
コレが分かっていないと作ってエラーが出ても特定できず解決が難しいです。
先にpythonを軽く理解してからコーディングに入ったほうがいいかも。

文字列(str)と数値(int)
- ""(ダブルクォーテーション)で囲うと文字列になる
- 数値の場合は""をつけない
```py
number = 3
string = "Hello Python!"
```

変数とprint()関数
```py
#変数名 = 代入する値(strまたはint)
i = "Hello World!"
print(i)
```

演算子
- 数値同士で演算子を使うとそのまま四則演算の結果になる
- 文字列同士だと連結される
```py
#結果:9
i = 3 + 6

#結果:7
i = 12 - 5

#結果:10
i = 50 / 5

#結果:1(10割る3の余り)
i = 10 % 3
```
```py
#結果:Hello World!
i = "Hello" + "World!"
```

## 3. 雛形(1) discord.Client()を使う方法
※Discord.py 1.7.3を使った場合です
```py:bot.py
import discord

intents = discord.Intents.all()

TOKEN = "ここにTOKENを張り付けてください"

bot = discord.Client(intents=intents)

@bot.event
async def on_ready():
    print(f"""
Bot logged!
bot user name:{bot.user.name}
bot user id:{bot.user.id}
    """)

@bot.event
async def on_message(message):
    if message.content == "!hoge":
        await message.channel.send(f"こんにちは！{message.author.mention}さん！")

if __name__ == "__main__":
    bot.run(TOKEN)
```
これで一通り動くと思います。
ただ[Discord Developers Portal](https://discord.com/devepolers)に行ってBotアカウントのIntentsをすべてONにしなきゃいけません。じゃないと動かないです

なお`discord.Client()`方式は今後の学習の妨げになるので、構文が簡単だからってすべてこれにしないほうがいいです。SlashCommand組むときに泣きます。

## 4.雛形(2) commands.Botを使う方法
```py:bot.py
import discord
from discord.ext import commands

TOKEN = "ここにTOKENを入れてください"

intents = discord.Intents.all()
bot = commands.Bot(command_prefix="!", intents=intents)
bot.remove_command("help") #最初から入っているhelpコマンドを消す。消したほうがいいかも??

@bot.event
async def on_ready():
    print(f"""
Bot logged!
bot user name:{bot.user.name}
bot user id:{bot.user.id}
    """)

@bot.command()
async def hoge(ctx):
    await ctx.send(f"こんにちは！{ctx.author.mention}さん！")

@bot.command()
async def return(ctx, arg):
    await ctx.send(f"あなたが入力した内容はこれですね？\n{arg.content}")

@bot.event
async def on_message(message):
    if message.content == "a":
        await message.channel.send("あ")
    await bot.process_commands(message) #これつけないとon_messageが着火しません

bot.run(TOKEN)
```
基本的にこのひな形であればだいたいなんでも作れると思います。pycordやnextcord使うときはちょっと書き換えしないといけませんが汗
なお、`commands.Bot`を継承したclassを使ってやるのもいいかもしれないです。
```py:bot.py
import discord
from discord.ext import commands

TOKEN = "ここにTOKENを入れてください"

class Hogebot(commands.Bot):
    def __init__(self, command_prefix, intents, description=None):
        super().__init__(
                        command_prefix=command_prefix,
                        intents=intents,
                        description=description
                        )

    async def on_ready(self):
        print(f"""
Bot logged!
bot user name:{self.user.name}
bot user id:{self.user.id}
        """)
        await self.change_presense(activity=discord.Game(name=f"{len(self.guilds)}servers"))

intents = discord.Intents.all()
bot = Hogebot(command_prefix="!", intents=intents)
bot.remove_command("help") #最初から入っているhelpコマンドを消す。消したほうがいいかも??

@bot.command()
async def hoge(ctx):
    await ctx.send(f"こんにちは！{ctx.author.mention}さん！")

@bot.command()
async def return(ctx, arg):
    await ctx.send(f"あなたが入力した内容はこれですね？\n{arg.content}")

@bot.event
async def on_message(message):
    if message.content == "a":
        await message.channel.send("あ")
    await bot.process_commands(message) #これつけないとon_messageが着火しません

if __name__ == "__main__":
    bot.run(TOKEN)
```

## 5.SlashCommandについて
**SlashCommandは便宜上Discord.py 2.0.0にて紹介させていただきます。**
**つきましてはDiscord.py2.0.0をダウンロードするため、以下のコマンドを実行するようにしてください**
```shell:Windowsの場合
$ python -m pip install discord.py==2.0.0

$ python -m pip install discord==2.0.0
```
Windows以外の方は
```shell:Windows以外の場合
$ python3 -m pip install discord==2.0.0

$ python3 -m pip install discord.py==2.0.0
```

SlashCommandとはまず何か。
下の画像を見てもらえばわかるんですが端的に言えば「コマンドをわかりやすくしたもの」です。
`/`と打つだけで一覧が出てきたり、コマンドが被ってもしっかり反応します。さらには`これらはあなただけに表示されています`などのものも作れます。
なお、d.py V2.0には標準搭載されています。
__ちなみに、[Discord Developers Portal](https://discord.com/devepolers)にて`bot`と`applications.commands`両方に権限を与え、新しい招待リンクで再度Botを認証する必要があります。__
[![Image from Gyazo](https://i.gyazo.com/ff999c2c7363d73e17f1668f4efa0ec6.png)](https://gyazo.com/ff999c2c7363d73e17f1668f4efa0ec6)


雛型です。
```py:bot.py
import discord
from discord import app_commands

TOKEN = "ここにTOKENを入れてください"
GUILD = discord.Object(id=int("ここに試験用のサーバーIDを入れてください"))

intents = discord.Intents.all() #本番環境では適宜変更してください
class Client(discord.Client):
    def __init__(self, *, intents: discord.Intents):
        super().__init__(intents=intents)
        self.tree = app_commands.CommandTree(self)
    
    async def setup_hook(self):
        self.tree.copy_global_to(guild=GUILD)
        await self.tree.sync(guild=GUILD)

client = Client(intents=intents)

@client.event
async def on_ready():
    print(f"BotUsername:{bot.user.name} logged.")
    await client.change_presence(activity=discord.Game(name=f"{str(len(bot.guilds))} servers"))

@client.tree.command(
name="hello",
description="hello world"
)
async def hello(interaction: discord.Interaction):
    await interaction.response.send_message("Hello World!")

client.run(TOKEN)

```
重要なのは7行目の`self.tree = app_commands.CommandTree(client)`です。これを使ってスラッシュコマンドを作れます。
各コマンドの定義は
```py:bot.py
@client.tree.command()
```
というデコレーターを使います。`()`内に`name=""`,`description=""`といった属性(それぞれコマンドの名前と説明文)を入れてやると晴れてコマンドができます。
ちなみに18行目からの
```py:bot.py
async def hello(interaction: discord.Interaction):
    await interaction.response.send_message("Hello World!")
```
が関数本体です。関数名はデコレーター内の`name=""`に合わせてください。また、受け取るメッセージは本来の`Context`とは違い、`discord.Interaction`を受け取ります。
詳細は[これ](https://discordpy.readthedocs.io/ja/latest/interactions/api.html#discord.Interaction)を見たほうが早いかも??

## 6.各種discord.uiについて
discord.pyにはモーダルウィンドウ(下記画像参照)やセレクトメニューを作るための`discord.ui`などが存在します。
[![Image from Gyazo](https://i.gyazo.com/735a2e20f00dabf6522ed59d517b0431.png)](https://gyazo.com/735a2e20f00dabf6522ed59d517b0431)

実装方法はこんな感じ。
```py:bot.py
class Modal(discord.ui.Modal):
    def __init__(self):
        super().__init__(
            title="なにか入力するところ",
            timeout=None
        )
        
        self.answer = discord.ui.TextInput(
            label="なんか入力してみてね！",
            style=discord.TextStyle.short,
            placeholder="例:HelloWorld!",
            required=True
        )
        self.add_item(self.answer)

    async def on_submit(self, interaction: discord.Interaction) -> None:
        return await interaction.response.send_message("あなたが入力したものはこれですね！\n{}".format(self.answer.value)")
```
これでスラッシュコマンドの実行関数に`await interaction.response.send_modal(Modal())`を渡してあげるとモーダルウィンドウが出現します！やったね！

ボタンの実装方法はこんな感じ
```py:bot.py
class ButtonView(discord.ui.View):
    def __init__(self):
        super().__init__(timeout=None)
    @discord.ui.button(label="ボタン～～", style=discord.ButtonStyle.primary, custom_id="persistent_view:btn")
    async def callback_btn(self, button: discord.ui.Button, interaction: discord.Interaction):
        await button.response.send_message("Hello World!")
```
:::note warn
ここで重要ポイント!!
`discord.ui.View`はBotが再起動すると`インタラクションに失敗しました。`と出るので
先ほどのスラッシュコマンドbotのひな型内にあった`async def setup_hook(self):`という関数内に`add_view(ButtonView())`で登録してあげる必要があります。
ただし、
> `discord.ui.View`の`__init__`内にある`super().__init__()`には`timeout=None`と入れてあげてください
> デコレーター`@discord.ui.button()`には`custom_id=""`を設定してあげてください
:::


残りは今度書く...(疲れた)