discordで簡単な受け応え(とGoogle検索)ができるbotを作ろう

#はじめに
この記事は[リンク情報システム](https://www.lis.co.jp/)の「[2019 Tech Connect Summer](https://qiita.com/dgkz/items/2862d5e6b77203a126b3)」のリレー記事です。
engineer.hanzomonのグループメンバーによってリレーされます。

(リンク情報システムのFacebookは[こちらです](https://ja-jp.facebook.com/lis.co.jp/))

7日目担当の@o-changです。
流行に乗ってchatbotに手を出してみました。

#そもそもなんでdiscord?
社会人になり、自由な時間が少なくなったというのに、仲間内でマインクラフトが流行ってしまいました(絶望)
~~エンドラに引き続きウィザーもようやく先日討伐完了しました！~~

・・・という話は置いておいて、プレイしながら皆でボイスチャットをするツールとしてdiscordを選択し、
今では日常の連絡ですら使っています。

そんなdiscordですが、botの開発環境がかなり整っているツールでもあります。
特にPythonを使用するなら、discord.pyを導入するだけで簡単に作成できます。
なら友達とみんなで遊べるbotを作りたいな！と思ったのがきっかけです。

#余談
discordの既存のbot、とても面白いものが多いです。
[Discord Bot List](https://discordbots.org/) 等で有志の方々が作ったbotが公開されており、サーバーに気軽に導入して遊ぶことができます。
ゲームのチャットツールとしての利用が多いからか、遊び目的のbotが多い印象です。
一方で海外の開発者の方が多いため、ほとんどのbotが英語のみ対応となってしまっています。
人気なbotは日本語で解説してあるサイトも多くあるので、是非遊んでみてください！
~~ちなみにおすすめは[Aki(アキネーター)](https://discordbots.org/bot/aki)と[Pokecord(ポケモン)](https://www.pokecord.com/)です~~


#実際に作ってみよう！
今回は身内サーバーで動かすつもりなので、身内ネタを詰め込んだ簡単な受け応えができるbot
・・・だけでは沢山の先輩方の記事がありますので、それに加えてゲーム中に気軽にGoogle検索をするためのAPIでも実装してみることにしました。
ちなみに、これに合わせてPythonの勉強を始めた、完全な初学者です。
ご了承ください。
#環境構築
まずはdiscord.pyを導入しましょう。
私が使用した環境は以下の通りです。

>・Ubuntu 18.10
・python 3.6.8
・discord.py 1.2.3
・google 2.0.2
・文字コード UTF-8

※Google検索APIを使用する都合上、文字コードはUTF-8で指定してください。

それぞれ使用するパッケージをインストールしましょう。

```
$ python3 -m pip install -U discord.py
$ python3 -m pip install -U google
```

#botの作成
コーディングの環境が整ったところで、botを作成してきましょう。
[Discord DEVELOPER PORTAL](https://discordapp.com/developers/applications/)にアクセスしてアカウントを作ります。

作成方法ですが、以下の記事が非常にわかりやすかったので、引用させていただきます。
[Discord Botアカウント初期設定ガイド for Developer](https://qiita.com/1ntegrale9/items/cb285053f2fa5d0cccdf)

今回作成するbotの場合は権限と役職の設定は必要ないので、その手前まで行ってください。
また、今回は身内ネタ強めなので「PUBLIC BOT」はOFFで作成しました。
勝手に公開されるわけではないですが、一応保険ということでOFFにした方がいい気がします。



#ソースコード

```discordbot.py
# exitを使うため
import sys
# discordのAPI
import discord
# Google検索
from googlesearch import search

# 接続に必要らしい(よくわかってない)
client = discord.Client()

# とりあえずフラグでモード管理しようかなと
ModeFlag = 0

# 起動時のメッセージ
@client.event
async def on_ready():
    # 起動時にメッセージの送信
    channel = client.get_channel(チャンネルID)
    await channel.send('監視してるよ＾＾')

# メッセージを受けた時の動作
@client.event
async def on_message(message):
    # イベント入るたびに初期化はまずいのでグローバル変数で
    global ModeFlag
    # botの発言は無視する(無限ループ回避)
    if message.author.bot:
        return
    # 一応終了するコマンドも用意しておく
    if message.content == '!exit':
        await message.channel.send('ﾉｼ')
        sys.exit()
    # google検索モード(次に何か入力されるとそれを検索)
    if ModeFlag == 1:
        kensaku = message.content
        ModeFlag = 0
        count = 0
        # 日本語で検索した上位5件を順番に表示
        for url in search(kensaku, lang="jp",num = 5):
            await message.channel.send(url)
            count += 1
            if(count == 5):
               break
    # google検索モードへの切り替え
    if message.content == '!google':
        ModeFlag = 1
        await message.channel.send('検索するワードをチャットで発言してね')
    # 単純な応答
    if message.content == 'bot君いる？':
        await message.channel.send('私bot君。あなたの後ろにいるよ。')
    # 特定の文字から始まる文章が発言されたとき
    if message.content.startswith('負け'):
        lose = message.author.name + "の負け！ｗ"
        await message.channel.send(lose)
    #リプライを受け取った時
    if client.user in message.mentions:
        reply = f'{message.author.mention} うるさいよ。'
        await message.channel.send(reply)
    # これについては触れないよ。
    if message.content.startswith("なんだかんだ"):
        kanda = "かんだ・・・神田ァ！？\n" + "https://www.youtube.com/watch?v=KUwpssJX37M"
        await message.channel.send(kanda)
# botの起動と接続
client.run('botのアクセストークン')
```
それではファイルがあるディレクトリ内にて実行してみましょう。

```
$ python3 discordbot.py
```
botが起動メッセージを発言したら無事に動いています！
![起動.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/453534/05bc7eaa-531b-6a02-be1a-7f6f9e6bdee5.jpeg)


###＜簡単な解説＞
####1. チャンネルのID
discordで
ユーザー設定　→　テーマ　→　開発者モード
をONにすると、チャンネルIDを取得できるようになります。
取得方法はチャンネルで右クリックして「IDをコピー」でOKです。
ここは文字列ではなく数字のままでOKです。

####2. botのアクセストークン
作成したbotのアクセストークンです。
コピペしたあと、クオーテーションで囲んで文字列にしてください。

####3. イベントの仕組み
さて、ここからがコードとdiscordbot.pyの説明です。

discordbotの挙動は、**「イベントハンドラ」** で管理されています。
イベントと、そのイベントが起こった時の動作をまとめてイベントハンドラと呼びます。

```discordbot.py
@client.event
async def on_message(message):
```
async def の後ろが**「メッセージを受け取った時」** というイベントです。
「メッセージを受け取った時」というのは、**「botがいるサーバー上のテキストチャンネルに何か書き込まれたとき」** です。
どこのチャンネルに書き込んでもメッセージとして受け取った扱いになります。
※botへのリプのことではないです。
[Event Reference](https://discordpy.readthedocs.io/en/stable/api.html#event-reference)を見ると、様々なイベントが載ってるのでお好みで探してみてください。

そして、**1つのソースコードに同じイベントを複数書けない** という注意点があります。
そのため、「!google」のif文中に次の文字を受け取る処理が書けず、フラグを用いてモード管理する形で作成しました。
※ちなみにコマンドは「!」から始めてるbotが多いのでそれに従ってみましたが、逆に他のbotと競合する危険性もありそうです。
####4. 動作の解説

```discordbot.py
# message.content：受け取ったメッセージ
if message.content == 'bot君いる？'
# message.channel.send：イベントキーとなった発言がされたチャンネルに返信する
    await message.channel.send('私bot君。あなたの後ろにいるよ。')
```
![応答.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/453534/3d4f32c6-48d0-42df-fbc3-7fde6c749f53.jpeg)

awaitの後ろがイベントが起こった時の動作本体です。
メッセージをifで判断して返信するだけならこれで作れます。

```discordbot.py
# google検索してくれる部分
for url in search(kensaku, lang="jp",num = 5):
    await message.channel.send(url)
```
![google.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/453534/2861a829-5951-98c3-3ffa-a9ac718d883e.jpeg)

langで指定した言語で、numの数だけkensakuを検索し、上からurlに入れてくれます。
これでfor文抜けると思ったんですが、無限ループになったのでもう一個for文追加して無理やり止めてます。
printでurlを出力する際は止まるのでよくわかってないです。完全に勉強不足による妥協箇所です。
記事タイトルにいれてるのに妥協してしまった・・・

```discordbot.py
if message.content.startswith('負け'):
    lose = message.author.name + "の負け！ｗ"
    await message.channel.send(lose)
```
![負け.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/453534/8a733633-6c7a-abe1-a5f8-9596cc5a688b.jpeg)

startswithを使って、「負け」から始まる文章が投稿されたときの動作です。
message.author.nameは、キーとなったメッセージを投稿したアカウントのHNのことです。

```discordbot.py
if client.user in message.mentions:
    reply = f'{message.author.mention} うるさいよ。'
    await message.channel.send(reply)
```
![メンション.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/453534/cbac97e2-45e5-4fb8-c2f0-62c39aee79b3.jpeg)

botにメンション飛ばされたときの動作です。文章内容に関わらずメンションが来ると反応します。
message.author.mentionで、キーとなったメッセージを投稿したアカウント宛てのメンションを作成します。

以上、簡単な解説でした。アレンジしたり組み合わせたりすると、これだけでも結構色々なやり取りができるので試してみて下さい！
#おわりに
今は要請があった身内ネタを手動で追加してますが、 ~~非常にめんどくさいので~~ いつかは追加したり削除したりする処理もdiscordからできるといいなと思っています。
ひとまずSQLiteあたりの勉強でもしようかな・・・

拙い文章でしたがここまでお読みいただきありがとうございました。
明日の記事も是非よろしくお願いします！

---
[リンク情報システム株式会社](https://www.lis.co.jp)では一緒に働く仲間を[随時募集](https://www.lis.co.jp/employ_top)しています！
また、お仕事のご依頼、ビジネスパートナー様も募集しております。[お気軽にご連絡](https://www.lis.co.jp/inquiry)ください。