Pythonで実用Discord Bot(discordpy解説)

前書き

この記事は Python と discord.py を利用した Discord Bot 開発のチュートリアルです。

Pythonの基礎知識がある方を対象読者としています。
(関数の知識があるのが望ましい)

Python で Discord Bot を開発する場合、
Discord API ラッパー の discord.py を利用するとお手軽なのですが、
そのためにはこちらの 公式ドキュメント を根気よく読む必要があります。

この記事ではドキュメントの内容を簡単に噛み砕き、
Botを作成する手順とよく使う機能の実装方法を紹介します。

プログラミング未経験の方へ

この記事の内容は、プログラミング未経験の方が実践するのは難しいです。
挑戦する場合は必ずこちらの2つのチュートリアルを履修してください。

何もわからないけどとりあえずBotを動かしたい方はこちらがオススメ。
Discord Bot 最速チュートリアル【Python&Heroku&GitHub】

Python未経験の方へ

この記事の内容は、Pythonの基礎知識がないと躓いてしまう可能性があります。
Python入門サイトは数多くありますが、以下のサイトがオススメです。

Pythonを一通り軽く学びたい方はこちら。
Python-izm | Python の入門から応用までをサポートする学習サイト

Pythonを一通りしっかり学びたい方はこちら。
Python3入門編のレッスン一覧 | プログラミング学習サービス【paizaラーニング】

関連Discordサーバー

DiscordBotやその周辺技術に詳しい人が多く、質問もできるコミュニティです。
とりあえず入っておくのがオススメです。

動作環境

  • Python 3.7.5
  • pip 19.3.1
  • discord.py 1.2.5

初期設定

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

まずは Discord Developer Portal でBotのアカウントを作成し、
Discordサーバーに登録しましょう。
アクセストークンも必要なので取得してください。

詳細な手順はこちらの記事にて紹介しています。
Discord Botアカウント初期設定ガイド for Developer - Qiita

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

ここから Python によるコーディングが必要になります。

まずは discord.py をインストールしましょう。

discord.pyのインストール
$ python3 -m pip install -U discord.py[voice]

そして以下のコードを discordbot.py という名前で保存します。

discordbot.py
# インストールした discord.py を読み込む
import discord

# 自分のBotのアクセストークンに置き換えてください
TOKEN = 'THi5IsDuMMyaCCesSTOK3nQ4.Cl2FMQ.ThIsi5DUMMyAcc3s5ToKen7kKWs'

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

# 起動時に動作する処理
@client.event
async def on_ready():
    # 起動したらターミナルにログイン通知が表示される
    print('ログインしました')

# メッセージ受信時に動作する処理
@client.event
async def on_message(message):
    # メッセージ送信者がBotだった場合は無視する
    if message.author.bot:
        return
    # 「/neko」と発言したら「にゃーん」が返る処理
    if message.content == '/neko':
        await message.channel.send('にゃーん')

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

Bot を起動します。

Botの起動
$ python3 discordbot.py

Bot が参加している Discord サーバーのテキストチャンネルで、
/neko と発言すると Bot が にゃーん と返してくれます。

Screenshot 2018-07-25 16.33.15.png

機能の追加

基本的に、Botのコードには
「トリガーとなる操作(イベント)」と「イベント発生時の処理」
の2つを記述していくことになります。

両者をまとめたものをイベントハンドラと呼びます。

"""メッセージ受信時に実行されるイベントハンドラ"""
@client.event # イベントを受信するための構文(デコレータ)
async def on_message(message): # イベントに対応する関数と受け取る引数
    ... # 処理いろいろ
"""Bot起動時に実行されるイベントハンドラ"""
@client.event
async def on_ready():
    ...
"""リアクション追加時に実行されるイベントハンドラ"""
@client.event
async def on_reaction_add(reaction, user):
    ...
"""新規メンバー参加時に実行されるイベントハンドラ"""
@client.event
async def on_member_join(member):
    ...
"""メンバーのボイスチャンネル出入り時に実行されるイベントハンドラ"""
@client.event
async def discord.on_voice_state_update(member, before, after):
    ...

イベントハンドラには @client.eventasync def の記述が必須です。
それぞれ デコレータコルーチン関数 というものですが、
難しいのでとりあえず書いておけば大丈夫だと思ってください。

また、1つのイベントに対して複数のイベントハンドラを定義するとエラーになります。
on_message を2つ以上記述するとエラー)

イベントのリストおよび詳細はこちら。
event-reference (discord.py documentation)

因みに以下に示すサンプルコード群を全て上記のコードに組み込むと、

このようなコードになります(クリックで開く)
discordbot.py
# インストールした discord.py を読み込む
import discord

# 自分のBotのアクセストークンに置き換えてください
TOKEN = 'MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs'

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

# 起動時に動作する処理
@client.event
async def on_ready():
    # 起動したらターミナルにログイン通知が表示される
    print('ログインしました')
    # 起動時に任意のチャンネルで挨拶する
    CHANNEL_ID = # 任意のチャンネルID(int)
    channel = client.get_channel(CHANNEL_ID)
    await channel.send('おはよう!')

# メッセージ受信時に動作する処理
@client.event
async def on_message(message):
    # メッセージ送信者がBotだった場合は無視する
    if message.author.bot:
        return
    # 「/neko」と発言したら「にゃーん」が返る処理
    if message.content == '/neko':
        await message.channel.send('にゃーん')
    # 話しかけた人に返信する
    if client.user in message.mentions: # 話しかけられたかの判定
        reply = f'{message.author.mention} 呼んだ?' # 返信メッセージの作成
        await message.channel.send(reply) # 返信メッセージを送信
    # メンバーのリストを取得して表示
    if message.content == '/members':
        print(message.guild.members)
    # 役職のリストを取得して表示
    if message.content == '/roles':
        print(message.guild.roles)
    # テキストチャンネルのリストを取得して表示
    if message.content == '/text_channels':
        print(message.guild.text_channels)
    # ボイスチャンネルのリストを取得して表示
    if message.content == '/voice_channels':
        print(message.guild.voice_channels)
    # カテゴリチャンネルのリストを取得して表示
    if message.content == '/category_channels':
        print(message.guild.categories)
    # 同一カテゴリ内にnewチャンネルを作成
    if message.content.startswith('/mkch'):
        category_id = message.channel.category_id
        category = client.get_channel(category_id)
        new_channel = await category.create_text_channel(name='new')
        reply = f'{new_channel.mention} を作成しました'
        await message.channel.send(reply)
    # ログの全削除
    if message.content == '/cleanup':
        await message.channel.purge()
        await message.channel.send('塵一つ残らないね!')
    # 役職の付与
    if message.content.startswith('/join'):
        role = discord.utils.get(message.guild.roles, name='member')
        await message.author.add_roles(role)
        reply = f'{message.author.mention} ようこそ!'
        await message.channel.send(reply)

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

e.g. 話しかけた人に返信する

discord.Message.mentions
discord.Message.author
discord.Member.mention
を利用します。

話しかけた人に返信する
@client.event
async def on_message(message):
    if client.user in message.mentions: # 話しかけられたかの判定
        reply = f'{message.author.mention} 呼んだ?' # 返信メッセージの作成
        await message.channel.send(reply) # 返信メッセージを送信

この例ではBotにメンションを送ると、
Botからメンション付きメッセージが返ってきます。

e.g. 起動時に任意のチャンネルで発言する

discord.Client.get_channel を利用します。

起動時に任意のチャンネルで挨拶する
@client.event
async def on_ready():
    CHANNEL_ID = # 任意のチャンネルID(int)
    channel = client.get_channel(CHANNEL_ID)
    await channel.send('おはよう!')

この例ではBotの起動時に指定したIDのチャンネルに挨拶してくれます。

ここで必要になるチャンネルのIDは、
ユーザ設定->テーマ->詳細設定開発者モードをONにし、
任意のチャンネル名を右クリックして「IDをコピー」から取得できます。
詳細:ユーザー/サーバー/メッセージIDはどこで見つけられる?

e.g. サーバー内の様々なリストを取得

discord.Guild.members
discord.Guild.roles
discord.Guild.text_channels
discord.Guild.voice_channels
discord.Guild.categories
を利用します。

discord.py ではサーバーを Guild と表記することに注意。

発言者がいるサーバー内の様々なリストの取得と表示
@client.event
async def on_message(message):
    # メンバーのリストを取得して表示
    if message.content == '/members':
        print(message.guild.members)
    # 役職のリストを取得して表示
    if message.content == '/roles':
        print(message.guild.roles)
    # テキストチャンネルのリストを取得して表示
    if message.content == '/text_channels':
        print(message.guild.text_channels)
    # ボイスチャンネルのリストを取得して表示
    if message.content == '/voice_channels':
        print(message.guild.voice_channels)
    # カテゴリチャンネルのリストを取得して表示
    if message.content == '/category_channels':
        print(message.guild.categories)

この例では各コマンドに対応するリストを取得して表示するだけですが、
これらのリストを利用することで以下の操作などが可能になります。

  • メンバー全員に役職を付与する
  • 管理者権限のある役職を全て削除する
  • テキストチャンネルを任意のカテゴリに一括で移動する

e.g. カテゴリ内にチャンネルを作成する

discord.CategoryChannel.create_text_channel を利用します。

同一カテゴリ内にnewチャンネルを作成
@client.event
async def on_message(message):
    if message.content.startswith('/mkch'):
        category_id = message.channel.category_id
        category = message.guild.get_channel(category_id)
        new_channel = await category.create_text_channel(name='new')
        reply = f'{new_channel.mention} を作成しました'
        await message.channel.send(reply)

この例では /mkch と打つと、
そのテキストチャンネルが属しているカテゴリに
新たに new という名前のテキストチャンネルを作成します。

e.g. 管理者がテキストチャンネルのログを全削除する

discord.Permissions.administrator
discord.TextChannel.purge
を利用します。

ログの全削除
@client.event
async def on_message(message):
    if message.content == '/cleanup':
        if message.author.guild_permissions.administrator:
            await message.channel.purge()
            await message.channel.send('塵一つ残らないね!')
        else:
            await message.channel.send('何様のつもり?')

この例では /cleanup と打つと、
そのテキストチャンネル内のログが全て消えます。
管理者以外が打つと怒られます。

e.g. 役職を付与する

discord.Member.add_roles を利用します。

役職の付与
@client.event
async def on_message(message):
    if message.content.startswith('/join'):
        role = discord.utils.get(message.guild.roles, name='member')
        await message.author.add_roles(role)
        reply = f'{message.author.mention} ようこそ!'
        await message.channel.send(reply)

この例では /join と打つと member という名前の役職を付与されます。
(memberという名前の役職がないとエラーになります)

デフォルトで閲覧権限を無効にし、役職に閲覧権限を付けることで、
コマンドを打つまではチャンネルの閲覧制限を掛けておくというような、
利用規約を読まない人を弾くシステムも実現できます。

Botを24時間365日稼働させる

※コマンドラインやgitの基本的な使い方を習得している必要があります。

自前のPCで24時間365日運用し続けるのは無理があるので、
リモートサーバーを借りて Bot を運用しましょう。
無料で簡単に使える Heroku へのデプロイ方法を紹介します。

Herokuのセットアップ

まずはHerokuアカウントを作成しましょう。
Heroku | Sign up

次にアプリを作成しましょう。
Create New App | Heroku

App namediscordbot-1ntegrale9 のような感じで適当に付けましょう。
名前を入力したら Create App をクリックしましょう。
Choose a regionAdd to pipeline はそのままで大丈夫です。

デプロイに必要なファイルの作成

追加で以下の3つのファイルが必要になります。
(左上がファイル名、内容がファイルの中身)

runtime.txt
python-3.7.3
requirements.txt
discord.py
Procfile
discordbot: python discordbot.py

これらをbotのプログラムがあるディレクトリ直下に置いてください。

デプロイ方法1:Heroku CLIを利用する

Heroku CLIをインストール した後、
Deploy using Heroku Git に従ってデプロイします。

叩くコマンド(DashboardのDeployを参照)
heroku login
cd <botを作成したディレクトリ>
git init
heroku git:remote -a <herokuのアプリ名>
git add .
git commit -am "make it better"
git push heroku master

デプロイ方法2:GitHubリポジトリと連携する

※トークンを直接コードに書いている場合は、必ずプライベートリポジトリにしてください

まずGitHubアカウントを連携する必要があります。

Heroku の右上のメニューから Account settings を選び、
Manage Account の Third-party Services から設定します。

Dashboard の Deploy を開き、Deployment method を GitHub にします。
下の Connect to GitHub で自分のアカウントを指定し、
リポジトリ名を Search して Connect します。

Automatic deploysEnable Automatic Deploys をクリックすると、
GitHubリポジトリにpushする度にデプロイされます。

Manual deployDeploy Branch の方は即時デプロイされます。

DynosをONにする

デプロイ後、DynosというのをONにするとBotが稼働します。
デフォルトではOFFになっているので、Resources タブから設定します。

右の鉛筆マークをクリックして、
スライドバーをクリックして、
Confirm をクリックします。
(表示されている$0.00は稼働で発生する料金=無料です。)

Overview タブでも Dynos の ON/OFF が確認できます。

Herokuの仕様について

HerokuではBotがエラーで落ちた場合に自動で再起動します。
また、24時間稼働すると再起動します。

Heroku の無料プランでは30分動作しないWebアプリケーションはスリープしますが、
紹介した手順ではwebプロセスを使用していないため、問題なく常時稼働します。

参考:HerokuでWebアプリ開発を始めるなら知っておきたいこと(1) 無料のスペック - アインシュタインの電話番号

Heroku で DB を使う

公式で PostgreSQL と Redis のアドオンが提供されています。

Postgres - SQL データベース・サービス | Heroku
キーバリュー型データストア Redis をクラウドで | Heroku

こちらも参考にしてください。
Heroku×Redis×Python で始める NoSQL DB 入門 - Qiita

補足事項

FAQ: 実行時に SSLError が発生します

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

というエラーが実行時に発生する場合があります。その場合は、

console
$ /Applications/Python\ 3.7/Install\ Certificates.command

を実行することで解消されると思います。

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

FAQ: git push heroku master時にエラーになる

! [remote rejected] master -> master (pre-receive hook declined) 

というエラーであれば、こちらをお読みください。
【Python&Heroku】push時にエラーになる時の対処法 - GraffitiNote

FAQ: discord.py を使わないと Bot は作れませんか?

直接 Discord の API を利用する方法もあります。
しかし、以下に示すスキルセットが必要です。

  • 公式のAPIドキュメント が読める
  • REST API に関する知識がある
  • Python で HTTP通信 および 非同期処理 を行うコードが書ける

因みに、disco というライブラリもありますが、
こちらを採用するメリットはあまり見受けられません。

FAQ: Python 以外の言語で Bot は作れますか?

もちろん可能です。
Discord公式でも多くのライブラリが紹介されています。
Community Resources (Discord Developer Portal - Documentation)

特に以下は日本でよく利用されている印象です。

後書き

結構丁寧に色々と記載したつもりではありますが、
くどくなるために割愛した部分が多くあるので、
細かい部分はやはりドキュメントを読むかDiscordで質問するなりで解決しましょう。

関連記事

ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
コメント
この記事にコメントはありません。
あなたもコメントしてみませんか :)
すでにアカウントを持っている方は
ユーザーは見つかりませんでした