Pythonとrepl.itを使って無料でDiscord Botを運用してみた  ~💩をつけまくってみる~

# 経緯
最近[TRPG](https://ja.wikipedia.org/wiki/%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%83%88%E3%83%BC%E3%82%AFRPG)をDiscord上でやっているからか、かなりの頻度でDiscordでチャットをするようになりました。
そこで今回は、チャット内で多発する「うんこ」に自動的にうんこの絵文字を付けるようなBOTを開発しました。
今回のようにあまり難しいBotでなければ小一時間（経験なし）で実際に運用できることが分かりました。
是非みなさんも僕のように生活を豊かにする（意味のない）BOT開発を楽しみましょう！

**こちらの記事では、Pythonの基礎知識がある方を対象に書いています。**
**また、「うんこ」という言葉が数え切れないほど出てきますが、ご容赦ください。**

### 実際に開発したもの
[UnkoMan](https://discord.com/oauth2/authorize?client_id=725701481172303973&permissions=2112&scope=bot)
<img width="152" alt="スクリーンショット 2020-06-25 23.12.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581320/b1c2e397-22f7-5d52-f433-01c2bd59c2dd.png">
<img width="451" alt="スクリーンショット 2020-06-25 23.13.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581320/ccaf0187-a54e-19ad-3144-ac67dcbfca8b.png">

# 開発環境
- [Repl.it](https://repl.it/)
    - Python 3.8
    - discord.py 1.3.3
    - Flask 1.1.2
- [Uptime Robot](https://uptimerobot.com/)

### Repl.it について
- オンラインコーディングプラットフォームです
- ブラウザに表示されたIDEからコードを実行できます
- ウェブ上でアプリやウェブサイトの開発ができます

### Uptime Robot について
- ネットワークを（50まで）無料で監視できるサービス


# Botアカウントの作成
1. [Discord Developer Portal](https://discord.com/developers/applications/)へアクセスしてDiscordのアカウントを使ってログインします
2. `New Application`ボタンを押して、名前を付けてください
    - 私の場合は`Unko`としました
<img width="1422" alt="スクリーンショット 2020-06-25 22.27.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581320/916c56a5-ae79-df4f-5c1c-62124b2acfd9.png">
3. 作成したApplicationの中に入り、`Bot`のタブに入ります。
4. `Add Bot`ボタンを押します
    - Botが作成された後にBotの`Username`を変更することができます
    - 私はここで**`UnkoMan`**という名前を授けました。 (`Save Changes`を押すのを忘れないように)
    - ここでプロファイル写真の変更も可能です
    - `PUBLIC BOT`がオンになっているのを確認します
    - `TOKEN`をコピーしておきます
<img width="1396" alt="スクリーンショット 2020-06-25 22.31.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581320/fccad285-452c-b2a9-5c80-8d3e7c6392dd.png">

5. `Guild`（discord APIではGuild == Serverらしい）を作成します。Discord内（アプリの方）で"+"ボタンを押してサーバーを作成します
<img width="533" alt="スクリーンショット 2020-06-25 22.33.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581320/28f5e655-24bb-acc1-b692-9b535191c4a0.png">

6. Botをサーバーへ登録します
    - `OAuth2`のタブに入り`SCOPES`内の`bot`をチェックします
    - <img width="1362" alt="スクリーンショット 2020-06-25 22.37.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581320/6920750e-b028-3a74-66eb-2603ee79348a.png">
    - 下にある`BOT PERMISSIONS`からBOTの必要な権限もチェックします
        - 今回はテキストに対する権限だけが必要なので`Send Messages`と`Add Reactions`のみをチェックしました
<img width="1037" alt="スクリーンショット 2020-06-25 22.37.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581320/81dec9c3-debe-c0ba-9934-5b9050d603b2.png">
    - すると認証URLが生成されるので、コピーしてブラウザで開きます
        - ちなみに他の人が保有しているサーバーにBotを追加したい場合もこの認証URLを使います
    - 先ほど作成したサーバーを選択して認証ボタンを押します
<img width="378" alt="スクリーンショット 2020-06-25 22.39.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581320/810435d3-fc16-7fb3-d584-cf4e838b121b.png">

7. Botが自分のサーバーにいることを確認！
<img width="208" alt="スクリーンショット 2020-06-25 22.41.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581320/58117c2c-4380-0539-7b47-bedd3e19c721.png">

# ウェブサーバー
1. Repl.itに行き、Python replを作成します
2. `Package`タブを開きdependencyをインストールしていきます。下記を検索してインストールしてください
    - discord.py
    - Flask
 
<img width="264" alt="スクリーンショット 2020-06-25 22.42.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581320/17e1c566-742d-4e0f-1e8b-395290dda334.png">

3. `server.py`という新規ファイルを作成します

<img width="258" alt="スクリーンショット 2020-06-25 22.43.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581320/435d90b5-7b3a-f4ff-fda0-42574fbab1b4.png">

- FlaskとThreadを使ってサーバーを作ります
- 後でサーバーをUptime robotにpingしてもらいます
 
```python:server.py
from flask import Flask
from threading import Thread

app = Flask("")

@app.route("/")
def main():
  return "UnkoMan is alive!"

def run():
  app.run("0.0.0.0", port=8080)

def keep_alive():
  t = Thread(target=run)
  t.start()

```


# Bot
1. `.env`ファイルを作成します

```dot
TOKEN=先ほどコピーしておいたTOKEN
```

2. `main.py`にどのようなBotにしたいかを書きます。以下コードにコメントを付けて説明します

####仕様
- `UNNKO_LIST`に入っている言葉にうんこの絵文字を付ける
- メンション（または、”お腹すいた”と言われたら）されたら、何かしらの返事をする
- 新しい人が入ってきたときに自己紹介をする

```python:main.py
import os
import discord
from server import keep_alive


# うんこに関する言葉のリスト
UNKO_LIST = ["うん", "うんこ", "poop", "うんち", "unnko", "unko", "くそ", "クソ", "糞", "大便", "排泄物", "ばば", "糞便", "crap", "feces", "manure", "shit"]


# .envからTOKENを取ってくる
TOKEN = os.getenv("TOKEN")
# 接続するためのオブジェクト
client = discord.Client()


# 起動時にログする
# @デコレーターを使ってイベントをハンドルします
@client.event
async def on_ready():
    print('UnkoMan is here')


@client.event
async def on_message(message):
    message.content = message.content.lower()

    # メッセージの送信者がBot自身だった場合は無視
    if message.author == client.user:
        return

    # メンションされたら返信
    if client.user in message.mentions:
        await reply_unko(message)

    # メッセージ自体または、メッセージの中にうんこ（うん以外）が入ってたら、うんこの絵文字
    if message.content in UNKO_LIST or any(s in message.content for s in UNKO_LIST[1:]):
        await react_unko(message)

    # 隠しコマンド：お腹すいたら、特別メッセージを送るよ
    if "お腹すいた" in message.content:
        await reply_unko(message, "お腹すいた")


async def reply_unko(message, special=""):
    if special == "お腹すいた":
        reply = "とりあえずうんこでも食っておけ（辛辣）"
    else:
        reply = f'{message.author.mention} 呼んだ？え？うんこ？あーそれは一理あるね'
    
    # メッセージの送信と送信したメッセージにうんこを
    sent_msg = await message.channel.send(reply)
    await react_unko(message)
    await react_unko(sent_msg)


# うんこの絵文字をメッセージに付ける
async def react_unko(message):
    await message.add_reaction("💩")


# 新しい人が入ってきたとき
@client.event
async def on_member_join(member):
    # guildはdiscord内のserverのこと
    for channel in member.guild.channels:
        # generalチャンネルでの自己紹介
        if str(channel) == "general":
            await channel.send_message(f"""初めまして {member.mention}さん　うんこマンです！""")

# ウェブサーバーを起動する
keep_alive()

# Discordへ接続
client.run(TOKEN)
```

**Repl.it内の`run`ボタンを押して実行！**

これでDiscord上のサーバーであなたのBotが動いているはずです！

気をつけなければいけないのは、repl.itではアクティビティがない状況が１時間続くとreplがオフラインになるとこです。
対策としてUptime Robotを使いBotを**365日24時間**稼働させる方法を説明します

# Uptime Robot
1. Uptime Robotにてアカウントを作成します
2. `+ Add New Monitor`ボタンを押します
<img width="956" alt="スクリーンショット 2020-06-25 22.47.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581320/a8f41f0a-149e-089b-6de0-fef863f3c65d.png">

3. `Monitor Type`を`HTTP(S)`、`Friendly Name`に好きな名前、`URL`をrepl.itで表示されていたものに、`Monitoring Interval`を`every 30 minutes`に設定してreplがオフラインにならないように設定します
    - エラーが起きたときように`Alert Contacts To Notify`をチェックしておきます
4. `Create Monitor`ボタンを押して**完成**です！
<img width="850" alt="スクリーンショット 2020-06-25 22.50.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581320/85b82800-ab87-b5f1-ee17-7c8b4b42f772.png">

###　追記
- "Repl it being just another host, won't be able to host it 24/7 so it will have a downtime of a few minutes within every 24 hours"　なので365日24時間は稼働できないようです。24時間中数分はダウンタイムがあるようですが、この際目を瞑りましょう...

# 終わりに
いかがでしたでしょうか？納得のいくBotが作れましたか？
もし、より機能を充実させてたいのであれば、[ドキュメント](https://discordpy.readthedocs.io/en/latest/)を読むことをお勧めします。

関連記事にQiitaの記事を載せているのですが、どうやら[Heroku](heroku.com)でもBotなら無料でホスティングができるようです。Herokuは、ウェブアプリでの開発でしか使ったことがなかったので、勘違いをしていましたが、どうやら["Heroku の無料プランでは30分動作しないWebアプリケーションはスリープしますが、 紹介した手順ではwebプロセスを使用していないため、問題なく常時稼働します。"](https://qiita.com/1ntegrale9/items/9d570ef8175cf178468f#dynos%E3%82%92on%E3%81%AB%E3%81%99%E3%82%8B)とのことです。
まあ、Procfileやらなんやら他にもファイルを作らなければならないので、repl.itで実装した方が面倒くさくないなぁ...笑

今回作成したBotですが、どなたか実用されたい方がいればどうぞ **[UnkoMan](https://discord.com/api/oauth2/authorize?client_id=725701481172303973&permissions=2112&scope=bot)** を是非よろしくお願いします。

また、`UnkoMan最高`など、何かコメント・質問があれば是非お知らせください！

# 関連記事・参考にしたもの
- [Hosting discord.py bots on repl.it](https://repl.it/talk/learn/Hosting-discordpy-bots-with-replit/11008)
- [Pythonで実用Discord Bot(discordpy解説)](https://qiita.com/1ntegrale9/items/9d570ef8175cf178468f)
- [Creating a Bot Account](https://discordpy.readthedocs.io/en/latest/discord.html)