GraffitiNote

PythonistaなWeb Engineerの知見

PythonでDiscord botを作る 【discord.py解説】

この記事はTUT Advent Calendar 2017の12日目(?)の記事です。

前書き

技科大生はSlackユーザーが多いと思いますが、
僕はSlackよりもDiscordが好きです。
アカウントを複数作らなくていいし、通話もできるし、
フレンド機能もあるし、役職設定で楽にグループ管理ができるので。

7500人を超える React のコミュニティが
Slack から Discord に移ったというニュースもあります。

Reactiflux is moving to Discord - React Blog

ということで、DiscordでもSlackと同じように
botを運用できるんですよって話を書こうと思います。

botアカウントの作成と登録

PythonでDiscord botを作る記事、あるにはあるんですが、
日本語だと参考になるのは以下の2つぐらいしかないです。

Pythonで簡単なDiscord Botの作り方 - Qiita

discordで使える白猫テニス用レート計算botを作ったよ! - Qiita

botを運用する前にアカウントを作成してサーバーに登録する必要がありますが、
そこまでは上記の記事等に従えばすんなりいけると思うので割愛します。

botプログラムの作成と起動

GitHub - Rapptz/discord.py: An API wrapper for Discord written in Python.

まずはこちらの discord.py を入れます。

$ pip install discord

公式のドキュメントに従って python プログラムを作成します。

import discord
import asyncio

client = discord.Client()

@client.event
async def on_ready():
    print('Logged in as')
    print(client.user.name)
    print(client.user.id)
    print('------')

@client.event
async def on_message(message):
    if message.content.startswith('!test'):
        counter = 0
        tmp = await client.send_message(message.channel, 'Calculating messages...')
        async for log in client.logs_from(message.channel, limit=100):
            if log.author == message.author:
                counter += 1

        await client.edit_message(tmp, 'You have {} messages.'.format(counter))
    elif message.content.startswith('!sleep'):
        await asyncio.sleep(5)
        await client.send_message(message.channel, 'Done sleeping')

client.run('token')

token には bot 管理画面から取得できるアクセストークンを設定してください。

作成したプログラムを起動して、
discord上で !test なり !sleep なり発言するとbotが反応します。

ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed

というエラーが起動時に発生する場合がありますが、
こちらの記事を参考に

macOS用公式インストーラーのPython 3.6でCERTIFICATE_VERIFY_FAILEDとなる問題 - Qiita

$ /Applications/Python\ 3.6/Install\ Certificates.command

を実行することで解決すると思います。

discord.py を使いこなす

基本的には

@client.event
async def on_message(message):

以下に条件なりを記述して

await client.send_message(message.channel, 'message')

で発言するという形でbotを作っていきますが、
発言に反応するだけのbotでは面白くないですよね?

ということで拡張していくのですが、
ドキュメントが充実していない上に参考になる記事も殆どありません。

英語の解説動画や記事、
公式の discord.py サーバーや Discord API サーバーもありますが、 英語が読めないと話にならないので、
素直にソースコードを読むのが良いと思います。

discord.py/discord/ 配下の
client.py channel.py member.py server.py user.py
あたりを読めば大抵のことは事足りると思います。 特に client.py は接続の起点なので一番大事。

任意のチャンネルで発言させる

例えば "bot_test" というチャンネルで発言させたい場合、

channel_bot_test = [channel for channel in client.get_all_channels() if channel.name == 'bot_test'][0]
await client.send_message(channel_bot_test, '勝手に喋るよ')

というように "bot_test"チャンネルのオブジェクトを取り出して指定します。 但し、この場合チャンネル名が変わった時点でエラーになるので、
"channel.id" で チャンネルID を調べて

[channel for channel in client.get_all_channels() if channel.id == 'XXXXXXXXXXXXXXXXXX'][0]

のように ID で識別するようにしましょう。
正直あまりスマートな方法ではないように思えますが、
これ以外の方法には辿り着けませんでした。

話しかけられたら返事をする

discord での menthon は
@USERNAME という形式で指定して行いますが、
内部的には(message.contentを見るとわかりますが) <@USERID>
という文字列になっています。

なので、client.user.idbot のユーザIDを取得し、
if client.user.id in message.content という条件の時に
message.author.mention を含めて発言をすることで
返信をすることができます。

ユーザー名のリストを取得する

client.get_all_members() で取得できるので、例えば

[member.display_name for member in client.get_all_members()]

で全てのユーザのニックネームのリストを取得できます。
ユーザ名やIDを取得することもできますし、
ボイスチャットに参加しているユーザのみを取得するメソッドもあります。

おわりに

大体以上のことを把握していれば、大抵のことは実現できると思います。

一番言いたいことは、
Slack で一々アカウントを作るのがめんどくさいですし、
15ワークスペースに参加していると5端末で75回も登録作業が必要で、
とてもじゃないですがやってられないし、
Slack でできることは、Discord でできるので、
みなさんも Discord 使いましょう、ってことですね。

大遅刻して申し訳ありませんでした。