はじめに
この記事は Python と discord.py を利用した Discord Bot 開発のチュートリアルです。
Pythonの基礎知識がある方を対象読者としています。
(関数の定義と呼び出しができるレベルを想定しています)
Python で Discord Bot を開発する場合、
Discord API ラッパー の discord.py を利用するとお手軽なのですが、
そのためにはこちらの 公式ドキュメント を根気よく読む必要があります。
この記事ではドキュメントの内容を簡単に噛み砕き、
Botを作成する手順とよく使う機能の実装方法を紹介します。
技術ドキュメントを読み慣れている方はこの記事は不要です。こちらをどうぞ。
プログラミング未経験の方へ(クリックで開く)
何もわからないけどとりあえずBotを動かしたい方はこちらがオススメ。
Discord Bot 最速チュートリアル【Python&Heroku&GitHub】
Python未経験の方へ(クリックで開く)
この記事の内容は、Pythonの基礎知識がないと躓いてしまう可能性があります。
Python入門サイトは数多くありますが、以下のサイトがオススメです。
動作環境
- 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 をインストールしましょう。
$ python3 -m pip install -U "discord.py[voice]"
$ py -3 -m pip install -U discord.py[voice]
そして以下のコードを 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 を起動します。
$ python3 discordbot.py
Bot が参加している Discord サーバーのテキストチャンネルで、
/neko
と発言すると Bot が にゃーん
と返してくれます。
機能の追加
基本的に、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.event
と async 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 を利用します。
# 発言したチャンネルのカテゴリ内にチャンネルを作成する非同期関数
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
# チャンネルが異なる場合は処理を打ち切る
if payload.channel_id != ID_CHANNEL_WELCOME:
return
# Member オブジェクトと Role オブジェクトを取得して役職を付与
member = payload.member
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 client.get_channel(ID_CHANNEL_WELCOME).send(text)
この例ではIDで指定したチャンネルで ✅ というリアクションを付けると、
そのメンバーにIDで指定した役職を付与します。
デフォルトで閲覧権限を無効にし、役職に閲覧権限を付けることで、
利用規約を読むまではチャンネルの閲覧制限を掛けておく、
というようなシステムも実現できます。
Botを24時間365日稼働させる
※コマンドラインやgitの基本的な使い方を習得している必要があります。
自前のPCで24時間365日運用し続けるのは無理があるので、
リモートサーバーを借りて Bot を運用しましょう。
無料で簡単に使える Heroku へのデプロイ方法を紹介します。
Herokuのセットアップ
まずはHerokuアカウントを作成しましょう。
Heroku | Sign up
次にアプリを作成しましょう。
Create New App | Heroku
App name
は discordbot-1ntegrale9
のような感じで適当に付けましょう。
名前を入力したら Create App
をクリックしましょう。
Choose a region
と Add to pipeline
はそのままで大丈夫です。
デプロイに必要なファイルの作成
追加で以下の3つのファイルを用意してください。
これらをbotのプログラムがあるディレクトリ直下に置いてください。
よく分からなければ、こちらのテンプレートリポジトリを参考にしてください。
https://github.com/DiscordBotPortalJP/discordpy-startup
runtime.txt
実行環境を記載します。
python-3.8.2
Specifying a Python Runtime | Heroku Dev Center
requirements.txt
インストールする外部モジュールを記載します。
discord.py
Python, pipでrequirements.txtを使ってパッケージ一括インストール | note.nkmk.me
Procfile
プロセスと実行するコマンドを プロセス名: コマンド
の形で記載します。
discordbot: python discordbot.py
プロセス名は discordbot
以外でも問題ありませんが、
web
や worker
などの特殊な意味を持つプロセス名に注意してください。
procfile · herokaijp/devcenter Wiki
デプロイ方法
以下の2通りの方法があります。お好きな方をお選びください。
- GitHubリポジトリと連携する場合(オススメ)
- Heroku CLIを利用する場合
GitHubリポジトリと連携する場合
※トークンを直接コードに書いている場合は、必ずプライベートリポジトリにしてください
まずGitHubアカウントを連携する必要があります。
Heroku の右上のメニューから Account settings
を選び、
Manage Account の Third-party Services から設定します。
Dashboard の Deploy を開き、Deployment method を GitHub にします。
下の Connect to GitHub で自分のアカウントを指定し、
リポジトリ名を Search して Connect します。
Automatic deploys
の Enable Automatic Deploys
をクリックすると、
GitHubリポジトリにpushする度にデプロイされます。
Manual deploy
の Deploy Branch
の方は即時デプロイされます。
Heroku CLIを利用する場合
Heroku CLIをインストール した後、
Deploy using Heroku Git
に従ってデプロイします。
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 が発生します
ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed
というエラーが実行時に発生する場合があります。その場合は、
$ /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)
特に以下は日本でよく利用されている印象です。
- JavaScript ( discord.js / Eris )
- Ruby ( discordrb )
- Java ( Discord4J )
終わりに
discord.py には記事にまとめきれない膨大な機能があります。
そのため 公式リファレンス が読めることが必須です。
しかし読み方を覚えるのも難しく、内容を全て網羅することも難しいので、
DiscordBotやその周辺技術に詳しい人が多く、質問もできるコミュニティを紹介します。
眺めているだけでも有益ですので、とりあえず入っておくのがオススメです。
-
Discord Bot Portal JP
- Discord Bot の利用者/開発者が集まるサーバー
- この記事で分からないことがあればこちらでご質問ください。
-
Python.jp
- Python.jp の公式サーバー
- Python の初心者から上級者まで集まる大手コミュニティです。
- 利用規約があるので必ずお読みください。
-
discord.py
- discord.py の公式サーバー(英語&日本語)
- Python初心者向けではないので注意。
- 高度な開発を行う場合には頼りになります。
-
インフラ勉強会
- 殆ど毎日インフラに関する勉強会が行われているサーバー
- インフラ勉強会のご紹介 - Qiita
- インフラ勉強会について – インフラ勉強会