DiscordのBOTを作ってDiscordをより便利にしちゃおう～！

# DiscordのBOT作成
## 経緯
python歴2年くらいの若輩プログラマーです。
趣味はゲーム！いやぁゲーム楽しい！

普段、インターネットの友達と通話やテキストチャットを送りあうのにDiscordを使用しています。
そんな中、こんなことが起こったのです。。。

Aくん「なにかイベントごとをやるときにイベント作成とそのイベント専用のテキストチャンネル作成を、同時にできたらすごい便利だよねぇ～…」

なるほど。確かに便利だ。

調べてみたらDIscordのAPIリファレンスがあるというじゃないか！
自問自答しました。

心の私「私ってプログラマーだよね？」
私「はい。」

心の私「しかもこれpythonだってよ？」
私「はい。」

心の私「じゃあ作ってみようぜ！！！！！！！！！」
私「はい！？！？！？！？！？！？！？」

はい、ってことで試しに作ってみました～

結構難しかったですが何とかなりました！

## 事前知識
こちらの記事は以下の方向けに作成しています。
- Discord知ってる人向けの記事になりますので、Discordって何！？気になる！！！って方はこちらをご覧ください。
[Discord](https://discord.com/ "Discord")

- ある程度pythonの開発知識、開発経験がある方

## 1. BOTのアカウント作成

まずはDiscordの[DEVELOPER PORTAL](https://discord.com/developers/applications "Applications")からBOTのアカウントを作成しましょう。

1. 右上の「New Application」押下
1. 好きなBOT名を入力し「Create」押下

はい！BOT用アカウントが簡単にできちゃいました！！
しかし、まだまだあるんですねぇ…


左側のBOTタブを押下しましょう。
以下の画像のようになっていると思いますので
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615863/731c2f25-74a6-c3e7-4fbb-32f265cdba0e.png)
「Add Bot」ボタンを押下しましょう。

そうすると
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615863/243eb577-7c49-a813-859f-3fafcc305df4.png)
なんか出ましたね。

そしたら、「Reset Token」ボタンを押下して
そこに出てきたTOKENをメモか何かに貼っておいてください。

:::note alert
ここで手に入れたTOKENは絶対に外部に漏らさないでください。
これが悪意ある人の手に渡るとBOTが乗っ取られてしまいます。。。
流出！ダメ！絶対！
:::

ではあとは自分のサーバーにこのBOTを招待すればこの画面でのやることは完了します！

あ！大事なこと忘れてました…
BOTの権限周りの設定をしてからサーバーに招待しましょう。

- MESSAGE CONTENT INTENT
これチェック入れておいてください。
開発時にこれにチェック入れていないとBOTからメッセージが送れませんでした。

ではBOTの招待URLを作成しましょう。
OAuth2→URL Generator からこんな感じに権限追加しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2615863/57bd41e0-77cd-16ef-12ac-65af37c9b403.png)
必要であれば他の権限も追加したり削除したりして大丈夫です。

権限追加した後、下に出てくるURLに飛ぶとサーバーを選択できるので、BOTを追加したいサーバーを選択しサーバーに入れてあげましょう。

サーバーにちゃんと参加されていればOKです！

ではこれから実際に開発していきましょう！

## 2. Pythonを使ったBOT開発！！

環境構築等は省略します。

今回の各バージョンは
- python-3.8.13
- py-cord 2.0.0b5 ← これがdiscord.pyです。
です。

:::note warn
discord.pyは開発終了してしまってます。
これの代替ライブラリとして今回はpy-cordを使用しました。
詳細は以下の記事が詳しく記載しているので省略いたします。
[【Python】discord.pyが開発終了ということなのでpycordに乗り換えよう](https://qiita.com/melonade/items/25c038e8e4e6800aa639)

2022/07/22追記 : discord.pyの開発がまた再開していました。。。しかし個人的には、APIリファレンスが見やすかったpy-cordがおススメです！
[discord.pyの開発が復活したと聞いて嬉しい(追記あり/discord.py v1.7.3はたぶん2022年中は使えます)](https://marukot-ch.hatenablog.com/entry/2022/03/06/105707)

:::

今回やりたい機能として
- テキストチャンネルの作成(公開/非公開)
- イベントの作成

です。

### 実装コード(全体)

```python:bot.py
# discordのbotに必要なライブラリをインポート
import discord
import datetime

# BOTのアクセストークン
TOKEN = "BOTのトークンはここ"

# 接続に必要なオブジェクトを生成
intents = discord.Intents.default()
intents.message_content = True
client = discord.Client(intents=intents)

# 発言したチャンネルのカテゴリ内にチャンネルを作成する非同期関数
async def create_channel(message, permission, channel_name):
    print("-----create_channel-----")
    category_id = message.channel.category_id
    category = message.guild.get_channel(category_id)

    # 第二引数が1の場合、プライベートチャンネルを作成
    try:
        if permission == "1":
            overwrites = {
                message.guild.default_role: discord.PermissionOverwrite(read_messages=False),
                message.guild.me: discord.PermissionOverwrite(read_messages=True)
            }
            new_channel = await category.create_text_channel(name=channel_name, overwrites=overwrites)
        elif permission =="0":
            new_channel = await category.create_text_channel(name=channel_name)
    except:
        import traceback
        traceback.print_exc()
        return
    return new_channel

async def create_scheduled_event(message, message_list):
    # スケジュールイベント作成
    name = message_list[4]
    tdatetime = datetime.datetime.strptime(message_list[3], '%Y%m%d-%H%M')
    start_time = tdatetime - datetime.timedelta(hours=9) 
    end_time = start_time + datetime.timedelta(days=1)
    # どのVCか判定
    location = message_list[2]

    privacy_level = discord.ScheduledEventPrivacyLevel.guild_only
    await discord.Guild.create_scheduled_event(self=message.guild , name=name, description="", start_time=start_time,end_time=end_time, location=location, privacy_level=privacy_level, reason=None)



@client.event
async def on_message(message):
    print("-----on_message-----")
    # "!作成 (0or1)　(イベントを実施するボイスチャンネル名) (日付:202204101900) 作成したいチャンネル名" になっていること
    message_list = list()
    message_list = message.content.split()
    error = ""
    # メッセージ送信者がBotだった場合は無視する
    if message.author == client.user:
        return
    if message_list[0].startswith('!ナマステ'):
        text = f'``` ナマステ～ ```'
        await message.channel.send(text)
        return
    if message_list[0].startswith('!help'):
        text = f'作成したいカテゴリー内のテキストチャンネルにて\n ``` !作成 (公開チャンネル: 0 or プライベートチャンネル: 1) (どのVCでやるか) (日付: <例>20220415-1900) 作成したいチャンネル名 \n !ナマステ : ナマステと返してくれる ```'
        await message.channel.send(text)
        return

    # !作成コマンド処理
    if message_list[0].startswith('!作成'):
        # コマンドのバリデーション判定
        if len(message_list) != 5:
            error = "!作成 (0or1) (どのVCでやるか:VC名) (日付:202204101900) 作成したいチャンネル名になっていない"    
        elif not message_list[1]=="0" and not message_list[1]=="1":
            error = "1つ目が0,1ではない"
        elif not message_list[2]=="ボイスチャンネル1":
            error = "正しいボイスチャンネルではない"

        if error:
            text = f'チャンネルの作成に失敗しました。エラー内容：{error}'
            await message.channel.send(text)
        try:
            # イベント作成
            await create_scheduled_event(message, message_list)

            # テキストチャンネル作成
            new_channel = await create_channel(message,message_list[1],message_list[4])

            # 全て作成完了後、メッセージを送る
            print("チャンネル作成完了")
            text = f'{new_channel.mention} を作成しました'
            await message.channel.send(text)
        except TypeError as e:
            text = f'作成失敗しました。エラー内容：{e}'
            await message.channel.send(text)
        except:
            import traceback
            traceback.print_exc()
            text = f'作成失敗しました。エラー内容：エラー'
            await message.channel.send(text)
            return
    return

# Botの起動とDiscordサーバーへの接続
client.run(TOKEN)

```

### コードの詳細

では、上から順に見ていきましょう！といってもほとんどコメントに記載してますけどね。

```python:bot.py
# 発言したチャンネルのカテゴリ内にチャンネルを作成する非同期関数
async def create_channel(message, permission, channel_name):

```
こちらはコマンドを入力したチャンネルと同じカテゴリにチャンネルを作成するメソッドです。

message:コマンドの中身やdiscordのアプリに関係があるメソッドなどがいろいろ入っている(細かくわからなくても大丈夫！)
permission:どの権限でチャンネルを作成したいか(今回は0:公開チャンネル、1:非公開チャンネルにしました。)
channel_name:どんな名前のチャンネルを作成したいか

を引数に入れています。

```python:bot.py
category_id = message.channel.category_id
category = message.guild.get_channel(category_id)

```

ユーザーが発言したチャンネルのカテゴリIDを取得してチャンネル情報を取得します。

```python:bot.py

if permission == "1":
            overwrites = {
                message.guild.default_role: discord.PermissionOverwrite(read_messages=False),
                message.guild.me: discord.PermissionOverwrite(read_messages=True)
            }
            new_channel = await category.create_text_channel(name=channel_name, overwrites=overwrites)
        elif permission =="0":
            new_channel = await category.create_text_channel(name=channel_name)

```

permittion=1の場合は非公開チャンネルにするためのデータを用意します。
overwritesの中身はそのままコピペでOK！
とりあえずこんな情報があるんだなって思っておけば大丈夫です。

そして、非公開チャンネルの場合は引数にoverwritesを、公開チャンネルの場合はoverwritesなしでdiscordのテキストチャンネル作成メソッドに渡してあげればチャンネル作成は終わりです！！(ヒュ～♪)

では次のメソッドへGO!!


```python:bot.py

async def create_scheduled_event(message, message_list):

```


こちらはDiscordの機能であるイベント作成機能を実行してくれるメソッドです。
中身を見てみましょう。


```python:bot.py

　　name = message_list[4]
    tdatetime = datetime.datetime.strptime(message_list[3], '%Y%m%d-%H%M')
    start_time = tdatetime - datetime.timedelta(hours=9) 
    end_time = start_time + datetime.timedelta(days=1)
    # どのVCか判定
    location = message_list[2]

　　privacy_level = discord.ScheduledEventPrivacyLevel.guild_only

```


以下の情報があればよさそうです。
 - イベント名
 - 開始日時
 - 終了日時
 - イベントをどこで行うか

privacy_levelはリファレンス見たらデフォルトしかなさそうなので放置！


```python:bot.py

await discord.Guild.create_scheduled_event(self=message.guild , name=name, description="", start_time=start_time,end_time=end_time, location=location, privacy_level=privacy_level, reason=None)

```

これらをAPIに渡してあげればOK！！



次のメソッド

```python:bot.py
async def on_message(message):


```


こちらはBOTがどんなメッセージを送信するか、メッセージの内容を決めて送ってあげる機能です。
引数のmessageには自分がコマンドとして送った内容が入っていますが、今回はこのような形にしました。

`!〇〇(用意したコマンド) (0or1)　(イベントを実施するボイスチャンネル名) (日付:202204101900) 作成したいチャンネル名`


```python:bot.py
    if message_list[0].startswith('!ナマステ'):
        text = f'``` ナマステ～ ```'
        await message.channel.send(text)
        return

```

送ったメッセージの内容の先頭(!〇〇)をみてそのコマンドに合った処理をしていきます。
上記の場合、`!ナマステ`って入れた場合、BOTが`ナマステ～`と返してくれるような仕組みです。


では次の処理がメインになります。


```python:bot.py
   if message_list[0].startswith('!作成'):
        # コマンドのバリデーション判定
        if len(message_list) != 5:
            error = "!作成 (0or1) (どのVCでやるか:VC名) (日付:202204101900) 作成したいチャンネル名になっていない"    
        elif not message_list[1]=="0" and not message_list[1]=="1":
            error = "1つ目が0,1ではない"
        elif not message_list[2]=="ボイスチャンネル1":
            error = "正しいボイスチャンネルではない"

        if error:
            text = f'チャンネルの作成に失敗しました。エラー内容：{error}'
            await message.channel.send(text)
        try:
            # イベント作成
            await create_scheduled_event(message, message_list)

            # テキストチャンネル作成
            new_channel = await create_channel(message,message_list[1],message_list[4])

            # 全て作成完了後、メッセージを送る
            print("チャンネル作成完了")
            text = f'{new_channel.mention} を作成しました'
            await message.channel.send(text)
        except TypeError as e:
            text = f'作成失敗しました。エラー内容：{e}'
            await message.channel.send(text)
        except:
            import traceback
            traceback.print_exc()
            text = f'作成失敗しました。エラー内容：エラー'
            await message.channel.send(text)
            return

```

こちらがイベントやテキストチャンネルを作成して、作成完了した旨をBOTがメッセージを送ってくれる機能です。

まず、コマンドを送ったメッセージをスペース区切りで5つになっていない場合はエラーを返すようにしています。
5つとか以下のことですね。

- !作成 
- (0or1 : 作成したいのは公開チャンネルか非公開チャンネルか)　
- (イベントを実施するボイスチャンネル名) 
- (日付:202204101900) 
- 作成したいチャンネル名

作成コマンドはこれらが情報としてほしかったのでこれ以外はエラーにしました。

他にも
2つ目が1 or 0以外の場合やボイスチャンネルが存在しないものである場合もエラーにしました。
今回は`ボイスチャンネル1`というボイスチャンネルが存在しているとします。


```python:bot.py
        if error:
            text = f'チャンネルの作成に失敗しました。エラー内容：{error}'
            await message.channel.send(text)

```

エラーがある場合、エラーがありますよっていう旨をBOTにメッセージとして送ってもらうようにしました。



```python:bot.py
        try:
            # イベント作成
            await create_scheduled_event(message, message_list)

            # テキストチャンネル作成
            new_channel = await create_channel(message,message_list[1],message_list[4])

            # 全て作成完了後、メッセージを送る
            print("チャンネル作成完了")
            text = f'{new_channel.mention} を作成しました'
            await message.channel.send(text)
        except TypeError as e:
            text = f'作成失敗しました。エラー内容：{e}'
            await message.channel.send(text)
        except:
            import traceback
            traceback.print_exc()
            text = f'作成失敗しました。エラー内容：エラー'
            await message.channel.send(text)
            return

```

特にエラーがなければ、各メッセージの内容を必要なメソッドに送り
最後は、チャンネル作成完了メッセージをBOTにメッセージ送信してあげれば完成です！！

また、作成の際になにかしらエラーが出た場合はエラー内容を送ってもらうようにします。

# おわりに

APIリファレンスを見ながらの格闘でしたが、日数にして3日、時間にして10時間くらいでできました。

皆さんも良きDiscordライフを～！
私も友達とDiscordでゲーム三昧してきます！！


最後に参考にしたサイトとAPIリファレンスを載せておきます。


- https://qiita.com/1ntegrale9/items/9d570ef8175cf178468f
- https://docs.pycord.dev/en/master/api.html