Help us understand the problem. What is going on with this article?

Pythonで実用Discord Bot(discordpy解説)

はじめに

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

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

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

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

技術ドキュメントが読み慣れている方はこの記事は不要です。こちらをどうぞ。
- はじめに — discord.py 1.4.0a ドキュメント
- クイックスタート — discord.py 1.4.0a ドキュメント

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

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

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

Python未経験の方へ

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

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

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

動作環境

  • Python 3.8.2
  • pip 20.0.2
  • discord.py 1.3.3

初期設定

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

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

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

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

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

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

discord.pyのインストール(Windows以外の場合)
$ python3 -m pip install -U "discord.py[voice]"
discord.pyのインストール(Windowsの場合)
$ py -3 -m pip install -U discord.py[voice]

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

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

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

# 接続に必要なオブジェクトを生成
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 on_voice_state_update(member, before, after):
    ...

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

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

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

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

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

話しかけた人に返信する
# 返信する非同期関数を定義
async def reply(message):
    reply = f'{message.author.mention} 呼んだ?' # 返信メッセージの作成
    await message.channel.send(reply) # 返信メッセージを送信

# 発言時に実行されるイベントハンドラを定義
@client.event
async def on_message(message):
    if client.user in message.mentions: # 話しかけられたかの判定
        await reply(message) # 返信する非同期関数を実行

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

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

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

起動時に任意のチャンネルで挨拶する
CHANNEL_ID = 987654321987654321 # 任意のチャンネルID(int)

# 任意のチャンネルで挨拶する非同期関数を定義
async def greet():
    channel = client.get_channel(CHANNEL_ID)
    await channel.send('おはよう!')

# bot起動時に実行されるイベントハンドラを定義
@client.event
async def on_ready():
    await greet() # 挨拶する非同期関数を実行

この例では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 と表記することに注意。

発言者がいるサーバー内の様々なリストの取得と表示
# コマンドに対応するリストデータを取得する関数を定義
def get_data(message):
    command = message.content
    data_table = {
        '/members': message.guild.members, # メンバーのリスト
        '/roles': message.guild.roles, # 役職のリスト
        '/text_channels': message.guild.text_channels, # テキストチャンネルのリスト
        '/voice_channels': message.guild.voice_channels, # ボイスチャンネルのリスト
        '/category_channels': message.guild.categories, # カテゴリチャンネルのリスト
    }
    return data_table.get(command, '無効なコマンドです')

# 発言時に実行されるイベントハンドラを定義
@client.event
async def on_message(message):
    # コマンドに対応するデータを取得して表示
    print(get_data(message))

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

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

get_data 関数内での辞書の使い方についてはこちら。
Pythonの辞書のgetメソッドでキーから値を取得(存在しないキーでもOK) | note.nkmk.me

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

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

同一カテゴリ内にnewチャンネルを作成
# 発言したチャンネルのカテゴリ内にチャンネルを作成する非同期関数
async def create_channel(message, channel_name):
    category_id = message.channel.category_id
    category = message.guild.get_channel(category_id)
    new_channel = await category.create_text_channel(name=channel_name)
    return new_channel

# 発言時に実行されるイベントハンドラを定義
@client.event
async def on_message(message):
    if message.content.startswith('/mkch'):
        # チャンネルを作成する非同期関数を実行して Channel オブジェクトを取得
        new_channel = await create_channel(message, channel_name='new')

        # チャンネルのリンクと作成メッセージを送信
        text = f'{new_channel.mention} を作成しました'
        await message.channel.send(text)

この例では /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 を利用します。

役職の付与
ID_CHANNEL_WELCOME = 987654321987654321 # 入室用チャンネルのID(int)
ID_ROLE_WELCOME = 987654321987654321 # 付けたい役職のID(int)
EMOJI_WELCOME = '✅' # 対応する絵文字

# 役職を付与する非同期関数を定義
async def grant_role(payload):
    # 絵文字が異なる場合は処理を打ち切る
    if payload.emoji.name != EMOJI_WELCOME: 
        return

    # チャンネルが異なる場合は処理を打ち切る
    channel = client.get_channel(payload.channel_id)
    if channel.id != ID_CHANNEL_README:
        return

    # Member オブジェクトと Role オブジェクトを取得して役職を付与
    member = channel.guild.get_member(payload.user_id)
    role = guild.get_role(ID_ROLE_WELCOME)
    await member.add_roles(role)
    return member

# リアクション追加時に実行されるイベントハンドラを定義
@client.event
async def on_raw_reaction_add(payload):
    # 役職を付与する非同期関数を実行して Optional[Member] オブジェクトを取得
    member = await grant_role(payload)
    if member is not None: # 役職を付与したメンバーがいる時
        text = f'{member.mention} ようこそ!'
        await message.channel.send(text)

この例ではIDで指定したチャンネルで ✅ というリアクションを付けると、
そのメンバーにIDで指定した役職を付与します。
(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つのファイルを用意してください。
これらをbotのプログラムがあるディレクトリ直下に置いてください。

よく分からなければ、こちらのテンプレートリポジトリを参考にしてください。
https://github.com/DiscordBotPortalJP/discordpy-startup

runtime.txt

実行環境を記載します。

runtime.txt
python-3.8.2

Specifying a Python Runtime | Heroku Dev Center

requirements.txt

インストールする外部モジュールを記載します。

requirements.txt
discord.py

Python, pipでrequirements.txtを使ってパッケージ一括インストール | note.nkmk.me

Procfile

プロセスと実行するコマンドを プロセス名: コマンド の形で記載します。

Procfile
discordbot: python discordbot.py

プロセス名は discordbot 以外でも問題ありませんが、
webworker などの特殊な意味を持つプロセス名に注意してください。

procfile · herokaijp/devcenter Wiki

デプロイ方法1: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 の方は即時デプロイされます。

デプロイ方法2: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

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.py には記事にまとめきれない膨大な機能があります。
そのため 公式リファレンス が読めることが必須です。

しかし読み方を覚えるのも難しく、内容を全て網羅することも難しいので、
DiscordBotやその周辺技術に詳しい人が多く、質問もできるコミュニティを紹介します。

眺めているだけでも有益ですので、とりあえず入っておくのがオススメです。

関連記事

sencorp
幼稚園・保育園向けインターネット写真サービス「はいチーズ!」を提供しています。
https://sencorp.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした