2022/04/07 追記
Discord.pyですが、開発が再開されたとのことです。
開発を再開されたことを受け、必要に応じてこの記事の更新を再開する予定です。
Qiitaの編集リクエストをお送りいただき、ありがとうございます。この場を借りて御礼申し上げます。
まえがき
この記事はDiscord.pyを使用してBOTを作る際に役立つかもしれないサンプルソースコードを掲載しています。
いずれもベストプラクティスであるとは限りませんが、DiscordのBOT作りの参考になればいいなという思いで、記事にしました。
環境
- Python 3.7以降
- discord.py 1.5.1 以降
- サンプルコードはDiscord.pyのBotフレームワーク(
discord.ext.commands
)を使用しています。
- サンプルコードはDiscord.pyのBotフレームワーク(
1.5からの仕様変更
(2020/11/21 追記)
Discord APIの仕様変更により、Discord.py 1.5から一部機能を使うにはintentsが必要になりました。
特に、メンバーのステータスや情報を取得する場合は、Discordデベロッパーポータルで設定が必要になります。
この設定をしないとBOTが起動しなかったり、意図しない動作が発生してしまうことがあります。
規模の大きいBOTを運用する場合(参加サーバーが100を超えるBOT)は別途申請が必要になります。
intentsを設定するには
Botインスタンス作成時にintents
という引数にIntents
インスタンスを代入します。
import discord
from discord.ext import commands
# discord.Intents.all()を使用するとpresencesやmembersも有効な状態になります。
discord_intents = discord.Intents.all()
# 用途に応じて以下のように無効化することもできます。設定できる項目は、以下のURLを参照してみてください。
# https://discordpy.readthedocs.io/ja/latest/api.html#discord.Intents
# discord_intents.bans = False # on_member_banやon_member_unbanのイベントを無効化
bot = commands.Bot(
command_prefix="!",
intents=discord_intents
)
※intentsが指定されなかった場合、自動的にintentsが設定されます。
BOTが反応する頭文字を変えたい
Botインスタンス作成時にcommand_prefix
という引数に頭文字を指定します。
from discord.ext import commands
bot = commands.Bot(
command_prefix="!"
)
メンションにも反応させたい
commands.when_mentioned_or
を使用するとメンションでも、頭文字でも反応するようになります。
from discord.ext import commands
bot = commands.Bot(
command_prefix=commands.when_mentioned_or("!")
)
コマンドの大文字小文字を気にしないようにしたい
Botインスタンス作成時にcase_insensitive
をTrue
にすると大文字小文字の区別をせず、反応するようになります。
from discord.ext import commands
bot = commands.Bot(
command_prefix="!",
case_insensitive=True
)
標準搭載のヘルプ機能を無効にしたい
Botインスタンス作成時にhelp_command
という引数をNone
にします。
from discord.ext import commands
bot = commands.Bot(
command_prefix="!",
help_command=None
)
Botアカウントに「~をプレイ中」と表示したい
commands.Bot
インスタンスを作成時にactivity
引数にdiscord.Game
インスタンスを代入します。
import discord
from discord.ext import commands
bot = commands.Bot(
command_prefix="!",
activity=discord.Game("Qiita") # Qiitaをプレイ中
)
もしくは、discord.Game
インスタンスを作成し、Botの接続完了時にchange_presence
で変更します。
import discord
from discord.ext import commands
bot = commands.Bot(
command_prefix="!"
)
presence = discord.Game("Qiita") # Qiitaをプレイ中
@bot.event
async def on_ready():
await bot.change_presence(activity=presence)
特定のリアクションをしたらロールを付与、解除したら剥奪する
(2020/11/21 追記) discord.Intents.reactions
のintentを有効にする必要があります。(ドキュメント)
イベントハンドラのon_raw_reaction_add
とon_raw_reaction_remove
を使用してロールの付与・剥奪を処理します。
on_raw_reaction_remove
では、member
が代入されないので、
get_guild
を使ってギルド(サーバー)情報を取得してから、get_member
でユーザー情報を取得する必要があります。
from discord.ext import commands
class RoleCog(commands.Cog):
def __init__(self, bot):
self.bot = bot # commands.Botインスタンスを代入
@commands.Cog.listener()
async def on_raw_reaction_add(self, payload):
if payload.member.bot: # BOTアカウントは無視する
return
if payload.channel_id != 123456789123: # 特定のチャンネル以外でリアクションした場合は無視する
return
if payload.emoji.name == "👍": # 特定の絵文字
await payload.member.add_roles(
payload.member.guild.get_role(123456789123) # ロールID
)
@commands.Cog.listener()
async def on_raw_reaction_remove(self, payload):
guild = self.bot.get_guild(payload.guild_id)
member = guild.get_member(payload.user_id)
if guild is None or member is None: # サーバーやメンバー情報が読めなかったら無視
return
if member.bot: # BOTアカウントは無視する
return
if payload.channel_id != 123456789123: # 特定のチャンネル以外でリアクションを解除した場合は無視する
return
if payload.emoji.name == "👍": # 特定の絵文字
await payload.member.remove_roles(
payload.member.guild.get_role(123456789123) # ロールID
)
「~が入力中」と表示させる
async with ctx.typing():
(ctx
はテキストチャンネル)を使用することで入力中のサインを表示できます。
import asyncio
from discord.ext import commands
class TypingCog(commands.Cog):
@commands.command()
async def delay_send(self, ctx):
async with ctx.typing(): # 送られてきたチャンネルで入力中と表示させる
await asyncio.sleep(5) # 重い処理をwith内で書く。(一例)
await ctx.send("処理完了")
カテゴリを取得したい
テキストチャンネル等と同様に、get_channel
関数でカテゴリを取得することができます。
from discord.ext import commands
bot = commands.Bot()
@bot.event
async def on_ready():
category = bot.get_channel(123456789123) # カテゴリのID
コマンドにDiscordのメンバーが予め入ったものを使用したい
メンバーを入れる引数の後にdiscord.Member
を指定すると、Member
インスタンスとして代入されます。
なお、メンバーが見つからなかった場合は例外commands.BadArgument
が送出されます。
エラーハンドリングは後述にて説明します。
import discord
from discord.ext import commands
class CommandArgCog(commands.Cog):
@commands.command()
async def say_hello(self, ctx, target: discord.Member): # targetにメンバー指定する
await ctx.send(f"Hello, {target.mention}!")
各コマンドのエラーハンドリングを行いたい
@{コマンドの関数名}.error
というデコレータ付け、引数にctx
とerror
を定義します。
ちなみに、コマンド処理内でエラーが発生すると、commands.CommandInvokeError
が送出されます。
import discord
from discord.ext import commands
class ErrorHandlingCog(commands.Cog):
@commands.command()
async def say_hello(self, ctx, target: discord.Member):
await ctx.send(f"Hello, {target.mention}!")
@say_hello.error
async def say_hello_error(self, ctx, error):
if isinstance(error, commands.BadArgument): # errorはBadArgumentか
await ctx.send(f"{ctx.message.author.mention} ターゲットを指定して下さい。もしくはユーザーが見つかりませんでした。")
return
実行しているBotが所属しているどのギルド(サーバー)に入ってないユーザー情報を取得したい
fetch_user
を使うとBotが所属しているギルドに入ってないユーザー情報を取得することができます。
fetch_user
はユーザーが見つからなかった場合に例外が出されるため、tryで囲むべきです。
import discord
from discord.ext import commands
class FetchUser(commands.Cog):
def __init__(self, bot):
self.bot = bot # commands.Botインスタンスを代入
@commands.command()
async def fetch_user(self, target_id: int):
try:
target = await self.bot.fetch_user(target_id)
except discord.NotFound:
# ユーザーが見つからなかった場合の処理(以下は一例)
await ctx.send("ユーザーが見つかりませんでした。")
return
await ctx.send(str(target)) # Username#0000
あとがき
紹介したのは一例でしたが、BOT製作の役に立てたら幸いです。