よくある質問

これは ndiscord.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'])

一般

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

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

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

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

Client 下にプレイ中状態の設定を行うためのメソッド Client.change_presence() が用意されています。 これの引数 activityActivity を渡します。

静的なアクティビティを設定する際はコンストラクタ、実行時にアクティビティを更新する際には 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('👋')

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

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

簡単な例。

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() という関数があります。これを用いることで、別スレッドからコルーチンを呼び出すことが可能です。

しかし、この関数は concurrent.Future を返すので、実際にはそこから結果を読み出す必要があります。これをすべてまとめると、次のことができます。

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

リクエストを送るには、ノンブロッキングのライブラリを使わなければなりません。このライブラリは、リクエストを作成するのにサードパーティー製の 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側の制限により、ファイル名にアンダースコアが含まれていない場合があります。

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

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

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

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

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

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

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

例:

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