よくある質問

これは discord.py 及び拡張モジュールの利用に際し、よくある質問をまとめたものです。気兼ねなく質問やプルリクエストを提出してください。

質問

コルーチン

コルーチンとasyncioに関する質問。

コルーチンとはなんですか。

coroutine とは await または yield from から呼び出さなければならない関数です。Pythonは await の付いた命令を処理する際、その時点で一度その関数の実行を停止し、他の作業を実行します。 これは await のついた命令の作業が終了し、再度停止したポイントに戻ってくるまで続きます。 これにより、スレッドや複雑なマルチプロセッシングを用いずに複数の処理を並列実行することができます。

コルーチンにawaitを記述し忘れた場合、コルーチンは実行されません。awaitの記述を忘れないように注意してください。

await はどこで使用することができますか。

awaitasync def で定義される関数の中でのみ使用できます。

「ブロッキング」とはなんですか。

非同期プログラミングにおけるブロッキングとは、関数内の await 修飾子がないコードすべてを指します。 しかし、全てのブロッキングが悪いというわけではありません。ブロッキングを使用することは避けられませんが、ブロックの発生は出来るだけ少なくする必要があります。長時間のブロックが発生すると、関数の実行が停止せず他の命令を処理することができないため、長時間Botがフリーズすることになることを覚えておきましょう。

ログの出力を有効にしている場合、ライブラリはブロッキングが起きていることを Heartbeat blocked for more than N seconds. というメッセージで警告します。ログの出力を有効にするには、 ログの設定 を参照してください。

長時間のブロッキングの原因として一般的なのは time.sleep() などです。 これは使用せず、下記の例のように asyncio.sleep() を使用してください。

# bad
time.sleep(10)

# good
await asyncio.sleep(10)

また、これだけでなく、有名なモジュール Requests: HTTP for Humans™ のHTTPリクエストも長時間ブロックの原因になります。 Requests: HTTP for Humans™ モジュールは同期プログラミングにおいては素晴らしいモジュールですが、特定のリクエストがイベントループを長時間ブロックする可能性があるため、 asyncio には適していません。 代わりにこのライブラリと一緒にインストールされた aiohttp を使用してください。

次の例を見てみましょう。

# bad
r = requests.get('http://aws.random.cat/meow')
if r.status_code == 200:
    js = r.json()
    await channel.send(js['file'])

# good
async with aiohttp.ClientSession() as session:
    async with session.get('http://aws.random.cat/meow') as r:
        if r.status == 200:
            js = await r.json()
            await channel.send(js['file'])

一般

ライブラリの使用に関する一般的な質問。

使用例はどこで確認できますか。

サンプルコードはレポジトリの examplesフォルダ で確認できます。

「プレイ中」状態の設定をするにはどうすればいいですか。

キーワード引数である activityClient のコンストラクタ、または Client.change_presence()Activity を渡すことで設定できます。

静的なアクティビティを設定する際はコンストラクタ、実行時にアクティビティを更新する際には Client.change_presence() が利用できます。

警告

on_ready()Client.change_presence() やその他APIの呼び出しを行うことは推奨できません。このイベントは実行中に一度ではなく複数回呼び出される可能性があるためです。

また、接続後すぐにプレゼンスを変更すると高確率で切断されます。

ステータスタイプ(プレイ中、再生中、配信中、視聴中)は列挙型の ActivityType を指定することで設定が可能です。メモリの最適化のため、一部のアクティビティはスリム化して提供しています。

これらの情報をまとめると以下のようになります。

client = discord.Client(activity=discord.Game(name='my game'))

# or, for watching:
activity = discord.Activity(name='my activity', type=discord.ActivityType.watching)
client = discord.Client(activity=activity)

特定のチャンネルにメッセージを送るにはどうすればいいですか。

チャンネルを直接取得してから、適切なメソッドの呼び出しを行う必要があります。以下がその例です。

channel = client.get_channel(12324234183172)
await channel.send('hello')

DMを送るにはどうすればいいですか。

UserMember を取得し、 abc.Messageable.send() を呼び出してください。以下は使用例です。

user = client.get_user(381870129706958858)
await user.send('👀')

on_message() などのイベントで応答を返したい場合は、 Message.author を介して User オブジェクトが取得できます。

await message.author.send('👋')

送信したメッセージのIDを取得するにはどうすればいいですか。

abc.Messageable.send() は送信したメッセージの Message を返します。メッセージのIDには Message.id でアクセスできます。

message = await channel.send('hmm…')
message_id = message.id

画像をアップロードするにはどうすればいいですか。

Discordに何かをアップロードする際には File オブジェクトを使用する必要があります。

File は二つのパラメータがあり、ファイルライクなオブジェクト(または、そのファイルパス)と、ファイル名を渡すことができます。

画像をアップロードするだけなら、以下のように簡単に行なえます。

await channel.send(file=discord.File('my_file.png'))

もし、ファイルライクなオブジェクトがあるなら、以下のような実装が可能です。

with open('my_file.png', 'rb') as fp:
    await channel.send(file=discord.File(fp, 'new_filename.png'))

複数のファイルをアップロードするには、 file の代わりに files を使用しましょう。

my_files = [
    discord.File('result.zip'),
    discord.File('teaser_graph.png'),
]
await channel.send(files=my_files)

URLから何かをアップロードする場合は、 aiohttp のHTTPリクエストを使用し、 io.BytesIO インスタンスを File に渡す必要があります。

import io
import aiohttp

async with aiohttp.ClientSession() as session:
    async with session.get(my_url) as resp:
        if resp.status != 200:
            return await channel.send('Could not download file...')
        data = io.BytesIO(await resp.read())
        await channel.send(file=discord.File(data, 'cool_image.png'))

メッセージにリアクションをつけるにはどうすればいいですか。

Message.add_reaction() を使用してください。

Unicodeの絵文字を使用する場合は、文字列内の有効なUnicodeのコードポイントを渡す必要があります。 例を挙げると、このようになります。

  • '👍'

  • '\U0001F44D'

  • '\N{THUMBS UP SIGN}'

Quick example:

emoji = '\N{THUMBS UP SIGN}'
# or '\U0001f44d' or '👍'
await message.add_reaction(emoji)

メッセージに含まれる絵文字を使用したい場合は、特になにをするでもなく、コンテンツ内のコードポイントを取得しています。また、 ':thumbsup:' のような簡略化したものを送信することは できません

カスタム絵文字については、 Emoji のインスタンスを渡してください。 '<:名前:ID>' 形式の文字列も渡せますが、その絵文字が使えるなら、 Client.get_emoji() でIDから絵文字を取得したり、 Client.emojisGuild.emojis に対して utils.find()/ utils.get() を利用することで取得ができるはずです。

カスタム絵文字の名前とIDをクライアント側で知るには、 :カスタム絵文字: の頭にバックスラッシュ(円記号)をつけます。たとえば、メッセージ \:python3: を送信すると、結果は <:python3:232720527448342530> になります。

Quick example:

# if you have the ID already
emoji = client.get_emoji(310177266011340803)
await message.add_reaction(emoji)

# no ID, do a lookup
emoji = discord.utils.get(guild.emojis, name='LUL')
if emoji:
    await message.add_reaction(emoji)

# if you have the name and ID of a custom emoji:
emoji = '<:python3:232720527448342530>'
await message.add_reaction(emoji)

音楽プレイヤーの「後処理」関数にコルーチンを渡すにはどうすればいいですか。

ライブラリの音楽プレーヤーは別のスレッドで起動するもので、コルーチン内で実行されるものではありません。しかし、 after にコルーチンが渡せないというわけではありません。コルーチンを渡すためには、いくつかの機能を包括した呼び出し可能コードで渡す必要があります。

コルーチンを呼び出すという動作はスレッドセーフなものではないということを最初に理解しておく必要があります。技術的に別スレッドなので、スレッドセーフに呼び出す際には注意が必要です。幸運にも、 asyncio には asyncio.run_coroutine_threadsafe() という関数があります。これを用いることで、別スレッドからコルーチンを呼び出すことが可能です。

However, this function returns a Future and to actually call it we have to fetch its result. Putting all of this together we can do the following:

def my_after(error):
    coro = some_channel.send('Song is done!')
    fut = asyncio.run_coroutine_threadsafe(coro, client.loop)
    try:
        fut.result()
    except:
        # an error happened sending the message
        pass

voice.play(discord.FFmpegPCMAudio(url), after=my_after)

バックグラウンドで何かを動かすにはどうすればいいですか。

background_task.pyの例を参照してください。

特定のモデルを取得するにはどうすればいいですか。

方法は複数ありますが、特定のモデルのIDがわかっていれば、以下の方法が使えます。

以下はHTTPリクエストを使用します。

上記の関数を使えない状況の場合、 utils.find() または utils.get() が役に立つでしょう。

Quick example:

# find a guild by name
guild = discord.utils.get(client.guilds, name='My Server')

# make sure to check if it's found
if guild is not None:
    # find a channel by name
    channel = discord.utils.get(guild.text_channels, name='cool-channel')

Webリクエストはどうやって作ればよいですか。

リクエストを送るには、ノンブロッキングのライブラリを使わなければなりません。このライブラリは、リクエストを作成するのにサードパーティー製の aiohttp を必要とします。

Quick example:

async with aiohttp.ClientSession() as session:
    async with session.get('http://aws.random.cat/meow') as r:
        if r.status == 200:
            js = await r.json()

詳細は aiohttpの完全なドキュメント を参照してください。

Embedの画像にローカルの画像を使用するにはどうすればいいですか。

特殊なケースとして、画像が別々に表示されないようDiscordにembedを用いてアップロードする際、画像は代わりにembedのサムネイルや画像、フッター、製作者アイコンに表示されます。

これを行うには、通常通り abc.Messageable.send() を用いて画像をアップロードし、Embedの画像URLに attachment://image.png を設定します。 image.png は送信する画像のファイル名になります。

Quick example:

file = discord.File("path/to/my/image.png", filename="image.png")
embed = discord.Embed()
embed.set_image(url="attachment://image.png")
await channel.send(file=file, embed=embed)

監査ログのエントリが作成されるイベントはありますか。

Discordはゲートウェイでこの情報をディスパッチしないため、ライブラリによってこの情報を提供することはできません。これはDiscord側の制限によるものです。

コマンド拡張

discord.ext.commands に関する質問。

on_message を使うとコマンドが動作しなくなります。どうしてですか。

デフォルトで提供されている on_message をオーバーライドすると、コマンドが実行されなくなります。これを修正するには on_message の最後に bot.process_commands(message) を追加してみてください。

@bot.event
async def on_message(message):
    # do some extra stuff here

    await bot.process_commands(message)

別の方法として、 on_message に書きたい処理を リスナー として登録することもできます。この方法では bot.process_commands() を呼び出す必要はありません。また、この方法ではメッセージの受信に対して複数の処理を非同期に実行することができます。使用例:

@bot.listen('on_message')
async def whatever_you_want_to_call_it(message):
    # do stuff here
    # do not process commands here

コマンドの引数に引用符が必要なのはなぜですか。

In a simple command defined as:

@bot.command()
async def echo(ctx, message: str):
    await ctx.send(message)

Calling it via ?echo a b c will only fetch the first argument and disregard the rest. To fix this you should either call it via ?echo "a b c" or change the signature to have "consume rest" behaviour. Example:

@bot.command()
async def echo(ctx, *, message: str):
    await ctx.send(message)

これにより、クォーテーションなしで ?echo a b c を使用することができます。

元の message を取得するにはどうすればよいですか。

Context は元のメッセージを取得するための属性である message を持っています。

Example:

@bot.command()
async def length(ctx):
    await ctx.send(f'Your message is {len(ctx.message.content)} characters long.')

サブコマンドを作るにはどうすればいいですか。

group() デコレータを使います。これにより、コールバックが Group に変換され、groupに「サブコマンド」として動作するコマンドを追加できます。これらのグループは、ネストすることもできます。

Example:

@bot.group()
async def git(ctx):
    if ctx.invoked_subcommand is None:
        await ctx.send('Invalid git command passed...')

@git.command()
async def push(ctx, remote: str, branch: str):
    await ctx.send(f'Pushing to {remote} {branch}')

これは ?git push origin master のように使うことができます。

Views and Modals

Questions regarding discord.ui.View, discord.ui.Modal, and their components such as buttons, select menus, etc.

How can I disable all items on timeout?

This requires three steps.

  1. Attach a message to the View using either the return type of send() or retrieving it via Interaction.original_message().

  2. Inside on_timeout(), loop over all items inside the view and mark them disabled.

  3. Edit the message we retrieved in step 1 with the newly modified view.

Putting it all together, we can do this in a text command:

class MyView(discord.ui.View):
    async def on_timeout(self) -> None:
        # Step 2
        for item in self.children:
            item.disabled = True

        # Step 3
        await self.message.edit(view=self)

    @discord.ui.button(label='Example')
    async def example_button(self, interaction: discord.Interaction, button: discord.ui.Button):
        await interaction.response.send_message('Hello!', ephemeral=True)

@bot.command()
async def timeout_example(ctx):
    """An example to showcase disabling buttons on timing out"""
    view = MyView()
    # Step 1
    view.message = await ctx.send('Press me!', view=view)

Application commands do not return a message when you respond with InteractionResponse.send_message(), therefore in order to reliably do this we should retrieve the message using Interaction.original_message().

Putting it all together, using the previous view definition:

@tree.command()
async def more_timeout_example(interaction):
    """Another example to showcase disabling buttons on timing out"""
    view = MyView()
    await interaction.response.send_message('Press me!', view=view)

    # Step 1
    view.message = await interaction.original_message()

Application Commands

Questions regarding Discord's new application commands, commonly known as "slash commands" or "context menu commands".

My bot's commands are not showing up!

  1. Did you sync() your command? Commands need to be synced before they will appear.

  2. Did you invite your bot with the correct permissions? Bots need to be invited with the applications.commands scope in addition to the bot scope. For example, invite the bot with the following URL: https://discord.com/oauth2/authorize?client_id=<client id>&scope=applications.commands+bot. Alternatively, if you use utils.oauth_url(), you can call the function as such: oauth_url(<other options>, scopes=("bot", "applications.commands")).