よくある質問

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

コルーチン

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

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

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

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

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

awaitasync def 関数の中でのみ使用できます。

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

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

もしロギングを有効にしている場合、ライブラリはブロッキングが起きていることを次のメッセージで警告しようと試みます: Heartbeat blocked for more than N seconds. ロギングを有効にするには、:ref:`logging_setup`をご覧ください。

長時間ブロックの原因として一般的なのは 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'])

一般

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

Where can I find usage examples?

Example code can be found in the examples folder in the repository.

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

The activity keyword argument may be passed in the Client constructor or Client.change_presence(), given an Activity object.

The constructor may be used for static activities, while Client.change_presence() may be used to update the activity at runtime.

警告

It is highly discouraged to use Client.change_presence() or API calls in on_ready() as this event may be called many times while running, not just once.

There is a high chance of disconnecting if presences are changed right after connecting.

The status type (playing, listening, streaming, watching) can be set using the ActivityType enum. For memory optimisation purposes, some activities are offered in slimmed-down versions:

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

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')

How do I send a DM?

Get the User or Member object and call abc.Messageable.send(). For example:

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

If you are responding to an event, such as on_message(), you already have the User object via Message.author:

await message.author.send('👋')

How do I get the ID of a sent message?

abc.Messageable.send() returns the Message that was sent. The ID of a message can be accessed via 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'))

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

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

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

  • '👍'

  • '\U0001F44D'

  • '\N{THUMBS UP SIGN}'

簡単な例。

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

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

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

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

簡単な例。

# 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() が役に立つでしょう。

簡単な例。

# 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リクエストはどうやって作ればよいですか。

To make a request, you should use a non-blocking library. This library already uses and requires a 3rd party library for making requests, aiohttp.

簡単な例。

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 は送信したい画像のファイル名にです。

簡単な例。

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側の制限により、ファイル名にアンダースコアが含まれていない場合があります。

Is there an event for audit log entries being created?

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)

Alternatively, you can place your on_message logic into a listener. In this setup, you should not manually call bot.process_commands(). This also allows you to do multiple things asynchronously in response to a message. Example:

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

コマンドの引数にクォーテーションが必要なのはなぜですか。

次の簡単なコマンドを見てみましょう。

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

このコマンドを ?echo a b c のように実行したとき、コマンドに渡されるのは最初の引数だけです。その後の引数はすべて無視されます。これを正常に動かすためには ?echo "a b c" のようにしてコマンドを実行するか、コマンドの引数を下記の例のようにしてみましょう

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

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

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

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

例:

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

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

Use the group() decorator. This will transform the callback into a Group which will allow you to add commands into the group operating as "subcommands". These groups can be arbitrarily nested as well.

例:

@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 のように使うことができます。