よくある質問

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

コルーチン

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

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

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

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

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

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

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

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

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

# bad
time.sleep(10)

# good
await asyncio.sleep(10)

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

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

# bad
r = requests.get('http://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://random.cat/meow') as r:
        if r.status == 200:
            js = await r.json()
            await channel.send(js['file'])

一般

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

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

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

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

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

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

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

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

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

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

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

簡単な例。

await message.add_reaction('\N{THUMBS UP SIGN}')

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

カスタム絵文字の場合、 discord.Emoji のインスタンスを渡す必要があります。'name:id' の文字列を渡すこともできます。その場合はあなたが絵文字を使用可能であれば、 Client.get_all_emojis() を使用してID経由で取得するか、あるいは Client.emojisGuild.emojis コレクションから utils.find()/ utils.get() を使用して取得が可能です。

簡単な例。

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

どうやってコルーチンをプレイヤーの後処理に渡すのですか。

ライブラリの音楽プレーヤーは別のスレッドで起動するもので、コルーチン内で実行されるものではありません。しかし、 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)

特定のユーザー、役割、チャンネル、サーバを取得するにはどうすればいいですか。

方法は複数ありますが、特定のモデルの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')

コマンド拡張

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)

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

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

@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('Your message is {} characters long.'.format(len(ctx.message.content)))

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

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('Pushing to {} {}'.format(remote, branch))

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