discord.py を使用したdiscord botの作り方を基本から応用まで

# discord.py チュートリアル

:::note warn
注：以下コードは古い場合があります。
discord.py 2.1.0でも動作を確認していますが、基本的にはdiscord.py 1.6.0 ver 対応のコードです。
:::

他にも思い出したり面白い応用コードがあれば追記します。

## 基本操作

### ログイン
#### 1.6.0 ver
```python
import discord

# Discord botクライアント
client = discord.Client()

# botが起動したときの処理
@client.event
async def on_ready():
    print("Botが立ち上がったよ！")

# Discord APIトークン
DISCORD_API_TOKEN = "discord developerで取得したトークンをこちらに"

# botを起動
client.run(DISCORD_API_TOKEN)
```
#### 2.1.0 ver
```python
import discord

# Discord botクライアント
client = discord.Client(intents=discord.Intents.all())

# botが起動したときの処理
@client.event
async def on_ready():
    print("Botが立ち上がったよ！")

# Discord APIトークン
DISCORD_API_TOKEN = "discord developerで取得したトークンをこちらに"

# botを起動
client.run(DISCORD_API_TOKEN)
```

### タスク作成
```python
import discord
from discord.ext import commands, tasks

# botが起動したときの処理
@client.event
async def on_ready():
    print("Botが立ち上がったよ！")
    loops.start()

# 10秒おきに「やあ」というだけ
@tasks.loop(seconds=10)
async def loops():
    print("やあ")
```

### discord上で誰かがしゃべった時の処理
```python
import discord

# メッセージが送信されたときの処理
@client.event
async def on_message(message):
    # 「おはよう」に対して「うるせえ」と返すだけ
    if message.content.startswith("おはよう"):
        await message.channel.send("うるせえ")
```

### discord上で誰かがリアクションした時の処理
```python
import discord
from discord.utils import get

# リアクション追加時の処理
@client.event
async def on_raw_reaction_add(payload):
    #📝のリアクションが特定のメッセージで2回以上押された場合、「📝のリアクションが2回以上押されたよ！」と言う
    if payload.emoji.name == "📝" :
        channel = client.get_channel(payload.channel_id)
        message = await channel.fetch_message(payload.message_id)
        reaction = get(message.reactions, emoji=payload.emoji.name)
        if reaction and reaction.count > 2:
            print("📝のリアクションが2回以上押されたよ！")
```

### embed送信
```python
import discord
import datetime

embeds=discord.Embed(title="テスト", url="https://www.google.com/",description="これはテストです", color=0x000000)
embeds.add_field(name="ぶんぶんはろー", value="ゆうちゅーぶ",inline = True)
embeds.set_footer(text=str(datetime.datetime.now().strftime('%Y.%m.%d, %H:%M:%S')))
msg = await client.get_channel("INT形式のチャンネルID").send(embed=embeds)
```

結果
![キャプチャ.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3201059/5c726bc4-7c21-55e1-d2f4-43bffbf62ac9.jpeg)

### 名前やIDの取得
```python
import discord

# メッセージが送信されたときの処理
@client.event
async def on_message(message):
    print(message.author) #あああ#1111
    print(message.author.id) #12345678909887
    print(message.author.name) #あああ
```

## 実際に使用した応用編

### 期間中にたくさん喋った人を確認する
```python
import discord
import datetime
from discord.utils import get
import collections

channel = client.get_channel(int("チャンネルID"))
before = await channel.fetch_message(int("このメッセージIDと以前のメッセージを取得する"))
after = await channel.fetch_message(int("このメッセージIDと以降のメッセージを取得する"))
chat_list = []
async for i in channel.history(after=after.created_at+ datetime.timedelta(seconds=-1),before=before.created_at+ datetime.timedelta(seconds=1)):
    chat_list.append(i.author.id)
chat_ranking = collections.Counter(chat_list).most_common()
for count,userdata in enumerate(chat_ranking):
    user = await server.fetch_member(int(userdata[0]))
    if count == 0:
        print(f"{user.name}さんは{userdata[1]}回発言し全体の1位です！")
    else:
        print(f"{user.name}さんは{userdata[1]}回発言しました")
```

### 画像を保存してzipファイルにしてあげなおす
```python
import discord
import requests
import shutil
import os

def download(url, file_name):
    r = requests.get(url, stream=True)
    if r.status_code == 200:
        with open(file_name, 'wb') as f:
            f.write(r.content)

# メッセージが送信されたときの処理
@client.event
async def on_message(message):
    # !imageという呪文で開始
    if message.content.startswith('!image'):
        os.mkdir("pic")
        message = channel.fetch_message(int("メッセージID"))
        for i in message.attachments:
            # 拡張子
            ext = i.url.split(".")[-1]
            # discordimg.拡張子 というネームで画像を保存する
            download(i.url,f"./pic/discordimg.{ext}")
        
        #フォルダをフォルダをzipに変換し送信
        shutil.make_archive('./discordpic', 'zip', root_dir='./pic')
        await message.channel.send(file=discord.File("discordpic.zip")) 
        shutil.rmtree("pic")
```

### 会話を無限に継続する
```python
import discord
import asyncio

# メッセージが送信されたときの処理
@client.event
async def on_message(message):
    if message.content.startswith('!talk'):
        await message.channel.send("ずっとオウム返ししてやるよ！")
        while True:
            try:
                # 話してるのが該当ユーザーか確認し、30秒たつと自動的にエラーを返す
                msg = await client.wait_for("message",check=lambda m: m.author == message.author, timeout=30.0)
                # 会話を抜け出す
                if msg.content == "!end":
                    await message.channel.send("もうギブアップかい！")
                    break
                else:
                    await message.channel.send(msg.content)

            except asyncio.TimeoutError:
                await message.channel.send('時間だよ！')
                break
        
        return
```            

### 特別なロールの人からしか受け付けない
```python
import discord

admin = ["admin"]
# メッセージが送信されたときの処理
@client.event
async def on_message(message):
        if message.content.startswith('!check') and list(set([z.name for z in message.author.roles])&set(admin)):
            await message.channel.send("youは管理者だね")
```


### サーバーで登録した絵文字をリアクションとして使う
```python
import discord
from discord.utils import get

# メッセージが送信されたときの処理
@client.event
async def on_message(message):
    if message.content.startswith('!addreact'):
        #サーバーにkaomojiという名前で登録した絵文字を使う
        react = get(message.guild.emojis, name='kaomoji')
        await message.add_reaction(react)
```

### リアクションで反応するスライド式embedを作成

意外と説明書だったり使えるのがこれ

```python
import discord

# メッセージが送信されたときの処理
@client.event
async def on_message(message):
    if message.content.startswith('!read'):
        #embedを事前に作成
        embed_1 =discord.Embed(title="やあ", description="こんにちは", color=0x000000)
        embed_2 =discord.Embed(title="Hi", description="Hello", color=0x000000)
        embed_3 =discord.Embed(title="你好", description="你好", color=0x000000)

        #最初にembed_1を表示
        page = "🇯🇵"
        msg = await message.channel.send(embed=embed_1)
        
        #リアクションを追加
        await msg.add_reaction("🇯🇵")
        await msg.add_reaction("🇺🇸")
        await msg.add_reaction("🇨🇳")
        
        #リアクションやユーザーが正しいかを確認
        def check(reaction, user):
                return user ==  message.author and str(reaction.emoji) in ["🇯🇵", "🇺🇸", "🇨🇳"] 

        while True:
            try:
                reaction, user = await client.wait_for("reaction_add", timeout=60, check=check)
                #違うページから押された場合は該当のembedに更新。リアクションのカウントを戻す
                if str(reaction.emoji) == "🇯🇵" and page != "🇯🇵":
                    page = "🇯🇵"
                    await msg.edit(embed=embed_1)
                    await msg.remove_reaction(reaction, user)
                elif str(reaction.emoji) == "🇺🇸" and page != "🇺🇸":
                    page = "🇺🇸"
                    await msg.edit(embed=embed_2)
                    await msg.remove_reaction(reaction, user)
                elif str(reaction.emoji) == "🇨🇳" and page != "🇨🇳":
                    page = "🇨🇳⚾"
                    await msg.edit(embed=embed_3)
                    await msg.remove_reaction(reaction, user)

            except asyncio.TimeoutError:
                #タイムアウトするとメッセージを削除
                await msg.delete()
                break
```
リアクションを押すことによってメッセージが変化
![キャプチャ.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3201059/62706253-8673-6314-c912-bdae7a89c421.jpeg)
![キャプチャ1.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3201059/691b140f-dd5f-3e1a-adb3-8d9a2623d918.jpeg)