v1.0への移行

v1.0 は完全な書き直しによる、ライブラリの中で最大の破壊的な変更の1つです。

変更量が膨大かつ長く、どこからどう見ても完全に新しいライブラリです。

再設計の一部は、物事をより使いやすく、自然にすることです。 models では、 Client インスタンスを動作させる代わりに行われます。

Pythonのバージョンの変更

discord.py の開発をより簡単にし、またその依存関係にあるライブラリをアップグレードして Python 3.7 以上を使えるようにするために、 discord. py は Python 3.5.3 より古いバージョンに対するサポートを諦めざるを得ませんでした。これはつまり Python 3.4 に対するサポートは打ち切られた ということです。

主要なモデルの変更

以下は、v1.0における主なモデルの変更点です。

Snowflakeのint型への変更

v1.0以前は、全てのsnowflakes ( id 属性) が文字列でしたが、 int に変更されました。

簡単な例:

# before
ch = client.get_channel('84319995256905728')
if message.author.id == '80528701850124288':
    ...

# after
ch = client.get_channel(84319995256905728)
if message.author.id == 80528701850124288:
    ...

この変更により,公式クライアントの「 ID をコピー」機能を使用した際に間違いがより起こりにくくなりました。もはや取得した ID をクォーテーションマークで囲う必要はありませんし,内部で JSON の代わりに ETF を用いることで最適化の機会を得ることにもなります。

Server から Guild に変更。

公式APIドキュメントでは「Server」は「Guild」と呼ばれています。APIドキュメントとの一貫性を保つために、モデルの名称が Guild に変更され、それを参照するすべてのインスタンスも変更されました。

変更の一覧は以下の通りです。

変更前

変更後

Message.server

Message.guild

Channel.server

GuildChannel.guild

Client.servers

Client.guilds

Client.get_server

Client.get_guild()

Emoji.server

Emoji.guild

Role.server

Role.guild

Invite.server

Invite.guild

Member.server

Member.guild

Permissions.manage_server

Permissions.manage_guild

VoiceClient.server

VoiceClient.guild

Client.create_server

Client.create_guild()

モデルのステートフル化

前述したように、多くの機能が Client からそれぞれの model へと移されました。

これらの変更点の一覧は以下の通りです。

変更前

変更後

Client.add_reaction

Message.add_reaction()

Client.add_roles

Member.add_roles()

Client.ban

Member.ban() または Guild.ban()

Client.change_nickname

Member.edit()

Client.clear_reactions

Message.clear_reactions()

Client.create_channel

Guild.create_text_channel()Guild.create_voice_channel()

Client.create_custom_emoji

Guild.create_custom_emoji()

Client.create_invite

abc.GuildChannel.create_invite()

Client.create_role

Guild.create_role()

Client.delete_channel

abc.GuildChannel.delete()

Client.delete_channel_permissions

abc.GuildChannel.set_permissions()overwriteNone に設定しました。

Client.delete_custom_emoji

Emoji.delete()

Client.delete_invite

Invite.delete() または Client.delete_invite()

Client.delete_message

Message.delete()

Client.delete_messages

TextChannel.delete_messages()

Client.delete_role

Role.delete()

Client.delete_server

Guild.delete()

Client.edit_channel

TextChannel.edit() または VoiceChannel.edit()

Client.edit_channel_permissions

abc.GuildChannel.set_permissions()

Client.edit_custom_emoji

Emoji.edit()

Client.edit_message

Message.edit()

Client.edit_profile

ClientUser.edit() ( Client.user から取得可能)

Client.edit_role

Role.edit()

Client.edit_server

Guild.edit()

Client.estimate_pruned_members

Guild.estimate_pruned_members()

Client.get_all_emojis

Client.emojis

Client.get_bans

Guild.bans()

Client.get_invite

Client.fetch_invite()

Client.get_message

abc.Messageable.fetch_message()

Client.get_reaction_users

Reaction.users()

Client.get_user_info

Client.fetch_user()

Client.invites_from

abc.GuildChannel.invites() または Guild.invites()

Client.join_voice_channel

VoiceChannel.connect() ( ボイスの変更 を参照)

Client.kick

Guild.kick() または Member.kick()

Client.leave_server

Guild.leave()

Client.logs_from

abc.Messageable.history() ( 非同期のイテレータ を参照)

Client.move_channel

TextChannel.edit() または VoiceChannel.edit()

Client.move_member

Member.edit()

Client.move_role

Role.edit()

Client.pin_message

Message.pin()

Client.pins_from

abc.Messageable.pins()

Client.prune_members

Guild.prune_members()

Client.purge_from

TextChannel.purge()

Client.remove_reaction

Message.remove_reaction()

Client.remove_roles

Member.remove_roles()

Client.replace_roles

Member.edit()

Client.send_file

abc.Messageable.send() ( メッセージの送信 を参照)

Client.send_message

abc.Messageable.send() ( メッセージの送信 を参照)

Client.send_typing

abc.Messageable.trigger_typing() ( abc.Messageable.typing() を使用してください)

Client.server_voice_state

Member.edit()

Client.start_private_message

User.create_dm()

Client.unban

Guild.unban() または Member.unban()

Client.unpin_message

Message.unpin()

Client.wait_for_message

Client.wait_for() ( イベントの待機 を参照)

Client.wait_for_reaction

Client.wait_for() ( イベントの待機 を参照)

Client.wait_until_login

削除済

Client.wait_until_ready

変更なし

プロパティの変更

一貫性を持たせるために、いくつかのプロパティがメソッドに変更されました。

プロパティの代わりに追加されたメソッドは以下のとおりです。(使用の際にはカッコが必要です)

辞書の値の変更

v1.0以前では、複数のモデルを集約して取得するプロパティは「辞書ビュー」オブジェクトで結果を返していました。

これは、オブジェクトを用いて繰り返し処理を行っている間に、辞書サイズが変更されたとき、RuntimeErrorを発生させてタスクをクラッシュさせていました。これを軽減させるため「辞書ビュー」オブジェクトはリストに変更されました。

以下のビューがリストへ変更されています。

ボイスステートの変更

v0.11.0では、ボイスステートを参照するために VoiceState が追加され、このクラスを参照するために Member.voice が使われていました。

これはユーザーにとって透過的なものでしたが、ライブラリがメモリを節約できるようボイスステートの変化が可視的になりました。

ボイスの属性にアクセスするには Member.voice を用いる方法しか存在しません。メンバーがボイスステートを持たない場合は None が返ることに注意してください。

簡単な例:

# before
member.deaf
member.voice.voice_channel

# after
if member.voice: # can be None
    member.voice.deaf
    member.voice.channel

ユーザーとメンバーの分離

v1.0では、メモリの節約のため、 MemberUser のサブクラスではなくなりました。代わりに、 User に記述されたものと同等のプロパティを実装することで、この二つのクラスはフラット化されています。そのため、使用方法に機能的変更はありません。ただし、 isinstance() が使えなくなってしまったことは留意しておくべきです。

メモリの節約は、グローバルな User のキャッシュを持つことで実現しました。これによって Client.get_user() を使ってIDから簡単に User を取得できます。また、あなたのクライアントが見ることができる UserClient.users ですべて取得できるようにもなりました。

チャンネルタイプの分割

v1.0以前のバージョンでは、チャンネルは is_private で判別する ChannelPrivateChannel の二通りしかありませんでした。

メモリ使用量を削減するため、チャンネルを4つのタイプへ分割しました。

  • ギルドのテキストチャンネル用である TextChannel

  • ギルドのボイスチャンネル用である VoiceChannel

  • メンバーのDMチャンネル用である DMChannel

  • メンバーが参加するグループDMチャンネル用である GroupChannel

これらに分割されたことにより、 is_private は削除されました。v1.0では isinstance() を使うべきでしょう。

型は二通りの 抽象基底クラス に分けられます。

チャンネルがギルドチャンネルであるかをチェックしたい場合:

isinstance(channel, discord.abc.GuildChannel)

チャンネルがプライベートチャンネルであるかをチェックしたい場合:

isinstance(channel, discord.abc.PrivateChannel)

もちろん、特定のチャンネルタイプを探したい場合、そのチャンネルタイプを渡すことも可能です。

isinstance(channel, discord.TextChannel)

この型の分割により、イベントにも変更がありました。これについては、 イベントの変更点 で列挙されています。

その他のモデルの変更点

その他にも、モデルに追加または削除されたものが多くあります。

以下がその一覧です。

削除済

  • Client.login() はメールとパスワードによるログインを受け付けなくなりました。

    • トークンと bot=False を使用してください。

  • Client.get_all_emojis

  • Client.messages

  • Client.wait_for_messageClient.wait_for_reaction は削除されました。

  • Channel.voice_members

  • Channel.is_private

    • 代わりに 抽象基底クラス のいずれかを使って、 isinstance を使用してください。

    • 例: isinstance(channel, discord.abc.GuildChannel) はプライベートチャンネルでないことを確認します。

  • Client.accept_invite

    • これに代わるものはありません。この機能はAPI的に非推奨です。

  • Guild.default_channel / Server.default_channelChannel.is_default

    • デフォルトチャンネルの概念はDiscordから削除されました。 #329 を参照してください。

  • Message.edited_timestamp

  • Message.timestamp

  • Colour.to_tuple()

  • Permissions.view_audit_logs

  • Member.game

  • Guild.role_hierarchy / Server.role_hierarchy

    • 代わりに Guild.roles を使用してください。ソート順が以前の Guild.role_hierarchy とは逆になっていることに注意してください。

変更

  • Member.avatar_urlUser.avatar_url はアバターが設定されていなければ、デフォルトアバターが返るようになりました。

  • Message.embedsdict オブジェクトから Embed のリストに変更されました。

  • Message.attachmentsdict オブジェクトから Attachment のリストに変更されました。

  • Guild.roles はヒエラルキー順にソートされるようになりました。先頭には必ず @everyone が格納されます。

追加

メッセージの送信

変更点の一つは、以前の Client.send_messageClient.send_file の機能を単一のメソッド send() に統合したことです。

基本的には以下のとおりです:

# before
await client.send_message(channel, 'Hello')

# after
await channel.send('Hello')

これは埋め込みメッセージなどといった、従来の send_message が持っていた機能を全てサポートしています。

e = discord.Embed(title='foo')
await channel.send('Hello', embed=e)

これはファイルの送信に対応できるように拡張されましたが、複数のファイルを送信する際には、 File の擬似的な名前付きタプルでファイルを渡す必要があります。

# before
await client.send_file(channel, 'cool.png', filename='testing.png', content='Hello')

# after
await channel.send('Hello', file=discord.File('cool.png', 'testing.png'))

この変更は、複数の添付ファイルの送信を容易にするために行われました。

my_files = [
    discord.File('cool.png', 'testing.png'),
    discord.File(some_fp, 'cool_filename.png'),
]

await channel.send('Your images:', files=my_files)

非同期のイテレータ

v1.0以前のバージョンは、Python 3.4 または 3.5以上の環境において Client.logs_from のような特定の関数で、処理終了時に異なった型を返していました。

これはv1.0で元に戻り、 AsyncIterator という抽象概念を満たす特異な型を返します。

これは通常のイテレータと同様の処理が行なえます。

async for message in channel.history():
    print(message)

またはリストにできます。

messages = await channel.history().flatten()
for message in messages:
    print(message)

AsyncIterator を返すことで便利な点は AsyncIterator.map()AsyncIterator.filter() といった関数をチェーンできることです:

async for m_id in channel.history().filter(lambda m: m.author == client.user).map(lambda m: m.id):
    print(m_id)

AsyncIterator.map() または AsyncIterator.filter() に渡される関数はコルーチンか通常の関数です。

AsyncIterator.get() または AsyncIterator.find() を介して discord.utils.get() または discord.utils.find() で単一の要素を取得することもできます。

my_last_message = await channel.history().get(author=client.user)

AsyncIterator を返すのは以下のとおりです:

イベントの変更点

多くのイベントに変更がありました。

名前に server が含まれていたイベントのほとんどが、 guild を使った名前に変更されました。

変更前:

  • on_server_join

  • on_server_remove

  • on_server_update

  • on_server_role_create

  • on_server_role_delete

  • on_server_role_update

  • on_server_emojis_update

  • on_server_available

  • on_server_unavailable

変更後:

on_voice_state_update() イベントの引数が変更されました。

変更前:

async def on_voice_state_update(before, after)

変更後:

async def on_voice_state_update(member, before, after)

新しくなったイベントは、二つの Member の代わりに、 一つの Member と二つの VoiceState を受け取るようになりました。

on_guild_emojis_update() イベントの引数が変更されました。

変更前:

async def on_guild_emojis_update(before, after)

変更後:

async def on_guild_emojis_update(guild, before, after)

最初の引数は絵文字の更新が行われた Guild です。

on_member_ban() も引数が変更されました。

変更前:

async def on_member_ban(member)

変更後:

async def on_member_ban(guild, user)

変更の一つは、イベントが Member または User のどちらかを受け取れるようになったことです。 User で受け取る場合には第一引数として Guild が渡されます。

on_channel_ のようなイベントは、チャンネルタイプにより分割されました ( チャンネルタイプの分割 を参照)。

変更前:

  • on_channel_delete

  • on_channel_create

  • on_channel_update

変更後:

on_guild_channel_ イベントは更新される abc.GuildChannel (TextChannel および VoiceChannel)に対応しており、 on_private_channel_ イベントは更新される abc.PrivateChannel (DMChannel および GroupChannel)に対応しています。

ボイスの変更

ボイスの送信が完全に再構成されました。

主な変更点は以下のとおりです。

  • ボイスチャンネルへの接続は Client.join_voice_channel に代わって VoiceChannel.connect() になりました。

  • プレイヤーを作成せずに操作が可能になりました。(プレイヤーの保存の必要もありません)

  • 代わりに VoiceClientVoiceClient.play() を介して AudioSource の再生を要求します。

  • 様々な組み込み AudioSource があります。

  • create_ffmpeg_player/create_stream_player/create_ytdl_player はすべて削除されました。

    • 代わりに AudioSource を作成してください。

  • VoiceClient.play() を呼び出しても AudioPlayer は返ってきません。

    • 代わりに、UserMember のように、「フラット化」されています。

  • after パラメータの関数は、ひとつの引数 (エラー) を取ります。

基本的には以下のとおりです。

変更前:

vc = await client.join_voice_channel(channel)
player = vc.create_ffmpeg_player('testing.mp3', after=lambda: print('done'))
player.start()

player.is_playing()
player.pause()
player.resume()
player.stop()
# ...

変更後:

vc = await channel.connect()
vc.play(discord.FFmpegPCMAudio('testing.mp3'), after=lambda e: print('done', e))
vc.is_playing()
vc.pause()
vc.resume()
vc.stop()
# ...

AudioSource の再設計により、 VoiceClient.source を介して実行中の VoiceClient のソースを変更できるようになりました。

例えば、 PCMVolumeTransformer を追加すると、ボリュームの変更ができるようになります。

vc.source = discord.PCMVolumeTransformer(vc.source)
vc.source.volume = 0.6

再設計によるさらなる利点は、再接続において遥かに柔軟性を持ったことです。

  • 音声ウェブソケットは、切断された際に自動的に再接続し、ハンドシェイクを再実行します。

  • 初期接続のハンドシェイクは、最大5回までの再試行となったので、大量の asyncio.TimeoutError に悩まされることはなくなりました。

  • VCの切断を検知すると、オーディオは停止し、再開しようとします。

    • これはサーバーリージョンの変更も含まれます。

イベントの待機

v1.0以前のバージョンでは、イベントの発生を待つ方法として Client.wait_for_messageClient.wait_for_reaction の二つの関数が用意されていました。このアプローチの欠点はライブラリが提供するイベント以外の発生を待つことが出来ない点です。

v1.0では別のイベントを待つ概念が Client.wait_for() として一般化され、どのイベントでも動くようになりました。

例えば、メッセージを待つ処理は以下のようになります。

# before
msg = await client.wait_for_message(author=message.author, channel=message.channel)

# after
def pred(m):
    return m.author == message.author and m.channel == message.channel

msg = await client.wait_for('message', check=pred)

複数の返り値に対応するため、 Client.wait_for() は単一の引数、引数なし、あるいは引数のタプルを返すようになっています。

例えば、リアクションを待つ処理は以下のようになります。

reaction, user = await client.wait_for('reaction_add', check=lambda r, u: u.id == 176995180300206080)

# use user and reaction

この関数は複数の引数を返すため、 timeout に設定した時間経過すると、 None を返すのではなく、 asyncio.TimeoutError を発生させるようになりました。以下はその例になります。

def pred(m):
    return m.author == message.author and m.channel == message.channel

try:

    msg = await client.wait_for('message', check=pred, timeout=60.0)
except asyncio.TimeoutError:
    await channel.send('You took too long...')
else:
    await channel.send('You said {0.content}, {0.author}.'.format(msg))

依存関係のアップグレード

ライブラリのv1.0への更新に伴い、要件が aiohttp v2.0以上へと変更されました。

これは後方互換性のない変更となるため、 aiohttp の変更点の詳細について changesMigration to 2.x ページを参照すべきです。

ユーザーにとって最も重要な変更点は、以下のヘルパー関数の削除です。

  • aiohttp.get

  • aiohttp.post

  • aiohttp.delete

  • aiohttp.patch

  • aiohttp.head

  • aiohttp.put

  • aiohttp.request

代わりにセッションを作成することをお勧めします。

async with aiohttp.ClientSession() as sess:
    async with sess.get('url') as resp:
        # work with resp

リクエストごとにセッションを作成するのは良いとは言えないため、変数に格納しておき、破棄しなければならない時に session.close を呼び出すのが良いでしょう。

シャーディング

シャーディングの扱いに対して大きな変更があり、現在、シャーディングは第一級オブジェクトとして扱われています。

Botアカウントを使用していて、かつ一つのプロセスでBotをシャーディングしたい場合は、 AutoShardedClient を使用してください。

このクラスは複数のプロセスを起動したり、IPCを処理することなくシャーディングが行えます。

シャーディングしたクライアントはユーザーアカウントをサポートしないことを覚えておきましょう。これは、接続の形態と状態処理の変更によるものです。

使い方は簡単です。

client = discord.AutoShardedClient()

Client の代わりに上記のようにしてください。

これは /gateway/bot エンドポイントを使って、あなたのBotに必要な数のシャードを起動します。このエンドポイントはシャードごとに1000ギルドを割り当てます。

シャードをより詳細に制御したい場合は、 shard_countshard_ids を利用してください。

# launch 10 shards regardless
client = discord.AutoShardedClient(shard_count=10)

# launch specific shard IDs in this process
client = discord.AutoShardedClient(shard_count=10, shard_ids=(1, 2, 5, 6))

コマンド拡張を利用しているユーザーのために、同様に動作する AutoShardedBot が用意されています。

接続の改善

v1.0では、自動再接続機能が大幅に強化されました。

Client.connect() には新しいキーワード引数が追加されました。再接続機能の設定を行う reconnect はデフォルトで True に設定されています。有効にすると、クライアントは全インターネットのインスタンスがオフラインになった際や、Discordが指数関数的後退によってオフラインになった際に自動で再接続を試みます。

Client.run()Client.start() にも同様のキーワード引数が追加されていますが、このさい接続機能をオフにする場合以外は指定する必要はありません。

コマンド拡張の変更

モデルのステートフル化 により、拡張モジュールの設計にもいくつかの変更があります。

コンテキストの変更

v1.0において、 Context は取得と利用の面において、多くの変更があります。

最も大きな変更点は pass_context=True が廃止され、常に Context が渡されるようになったことです。そのため以下のようになります。

# before
@bot.command()
async def foo():
    await bot.say('Hello')

# after
@bot.command()
async def foo(ctx):
    await ctx.send('Hello')

その理由として、 Context が abc.Messageable の要件を満たしていることが挙げられます。 これは TextChannelDMChannel と同等の機能を持っており、 send() を用いることで、従来の bot.say のようにDMまたはテキストチャンネルにメッセージを送信することが出来ます。古いヘルパー関数は新しい abc.Messageable インタフェースの実装に伴い削除されました。詳細は ヘルパー関数の削除 を参照してください。

Context がデフォルトで渡されるので、ショートカットが追加されました:

新しいショートカット

  • ctx.authorctx.message.author のショートカットです。

  • ctx.guildctx.message.guild のショートカットです。

  • ctx.channelctx.message.channel のショートカットです。

  • ctx.mectx.message.guild.me のショートカットです。

  • ctx.voice_clientctx.message.guild.voice_client のショートカットです。

新しい機能

  • Context.reinvoke() はコマンドを再度呼び出します。

    • クールダウンの回避に利用できます。

  • Context.valid で、Bot.invoke() で呼びだせるか確認します。

  • Context.send_help() を使うと、新しい HelpCommand システムである項目のヘルプを出力できます。

    • コマンドの使用法を誤ったときにヘルプを表示させたい場合に便利です。

コンテキストのサブクラス

v1.0では、 Context を継承したサブクラスを作成し、デフォルトで実装されているものの代わりに使うことが出来ます。

例えば、コンテキストに機能の追加を行いたい場合は以下のように実装が出来ます。

class MyContext(commands.Context):
    @property
    def secret(self):
        return 'my secret here'

また、 on_message() 内で get_context()invoke() を組み合わせることであなたのカスタムコンテキストを使用できます。

class MyBot(commands.Bot):
    async def on_message(self, message):
        ctx = await self.get_context(message, cls=MyContext)
        await self.invoke(ctx)

これにより、コマンドからあなたのカスタムコンテキストにアクセスすることが可能です。

@bot.command()
async def secret(ctx):
    await ctx.send(ctx.secret)

ヘルパー関数の削除

新しい Context の変更によって、たくさんのメッセージ送信用のヘルパー関数が削除されました。

以下が削除された関数のリストです。

変更前

変更後

Bot.say

Context.send()

Bot.upload

Context.send()

Bot.whisper

ctx.author.send

Bot.type

Context.typing() または Context.trigger_typing()

Bot.reply

代替となるものはありません。

コマンドの変更

前述の通り、 pass_context=True は削除されたため、これをパラメータとして渡す必要はありません。

他に no_pm=True も削除されました。代わりに新しい組み込みチェックである guild_only() を使用してください。

BotGroupcommands 属性は辞書からエイリアスを持たないsetに変更されました。以前のような辞書を取得するには all_commands を使用してください。

コマンドインスタンスには新たな属性とプロパティが追加されました。

  1. コマンドのシグネチャを取得する signature

  2. usage はデフォルトのシグネチャをオーバーライドする属性です

  3. root_parent はサブコマンドのルートである、親グループを取得します

GroupBot は次のように変更されました。

  • commands は エイリアスなしの set に変更されました。

    • すべてのコマンドを従来の dict で取得するには all_commands を使用してください。

チェックの変更

v1.0以前のバージョンでは check() は同期関数でしたが、 v1.0のチェックはコルーチンにもなれます。

この変更に加え、新たなチェックが二つ追加されました。

  • guild_only() が以前の no_pm=True 機能を置き換えました。

  • is_owner()Client.application_info() のエンドポイントを使用してオーナーIDを取得します。

    • 実際には is_owner() という別の関数を使用して実行されます。

    • Bot.owner_id に値を指定することで自分でオーナーIDの設定ができます。

  • is_nsfw() はコマンドが実行されたチャンネルがNSFWチャンネルかどうかをチェックします。

    • これは新しく追加された TextChannel.is_nsfw() メソッドにより実行されています。

イベントの変更点

すべてのコマンド拡張のイベントが変更されました。

変更前:

on_command(command, ctx)
on_command_completion(command, ctx)
on_command_error(error, ctx)

変更後:

on_command(ctx)
on_command_completion(ctx)
on_command_error(ctx, error)

on_command()on_command_completion()command パラメータが削除されました。 Command インスタンスは更新されておらず、正しいものではありませんでした。最新の Command インスタンスを取得するには Context.command 属性を使用してください。

error()on_command_error() のようなエラーハンドラは他のイベント及びコマンドとの一貫性を保つため、最初のパラメータとして Context を使用するよう変更されました。

HelpFormatter および Help Command の変更

HelpFormatter クラスは削除され、 HelpCommand に置き換えられました。このクラスはヘルプコマンドのコマンドハンドリングや処理などといったすべてが格納されています。

ヘルプコマンドは属性である Bot.help_command に格納されています。追加の機能として、この属性に None を代入するか、 help_command=None のようにして __init__ にわたすことでヘルプコマンドを完全に無効化できます。

新しいインタフェースでは特殊なメソッドをオーバーライドすることでヘルプコマンドをカスタマイズすることができます。

特定のサブクラスはさらにカスタマイズ可能なメソッドを実装できます。

以前の HelpFormatter はその機能を全て実装した DefaultHelpCommand に置き換えられました。カスタマイズメソッドは添付のドキュメントで確認することができます。

このライブラリは多くのスペースをとらない、より小規模化した HelpCommand の実装である MinimalHelpCommand を提供します。カスタマイズ可能なメソッドは付随のドキュメントから確認することが可能です。

ヘルプコマンドをコグに関連付けることはできないのかという要望が多くありました。この新しい設計では HelpCommand.cog にバインドすることでコグを動的に変更することが可能です。この割当をおこなった後、ヘルプコマンドはコグの一部として、期待通りの動きをするでしょう。コグがアンロードされると、ヘルプコマンドはコグから「バインド解除」されます。

例えば、 HelpCommand をコグに実装するには、次のコードが役立つでしょう。

class MyHelpCommand(commands.MinimalHelpCommand):
    def get_command_signature(self, command):
        return '{0.clean_prefix}{1.qualified_name} {1.signature}'.format(self, command)

class MyCog(commands.Cog):
    def __init__(self, bot):
        self._original_help_command = bot.help_command
        bot.help_command = MyHelpCommand()
        bot.help_command.cog = self

    def cog_unload(self):
        self.bot.help_command = self._original_help_command

詳しくは、こちらの説明 をご覧ください。

コグの変更

コグは完全に刷新されました。これは コグ としてドキュメント化されています。

コグは将来的な校正のためのクラスである Cog を基底クラスとして持つ必要があります。このクラスには動作のカスタマイズのために、特別なメソッドが用意されています。

コグ内で on_message のようなリスナーを使用していた人は、 commands.Cog.listener() デコレータを用いて、リスナーを明示する必要があります。

それによって、コグはクラス定義の行で指定することによって、独自の名前を持てるようになりました。オプションはこれらを容易にするメタクラス、commands.CogMeta で見つかります。

すべての特別なメソッドを使用し、そして名前を指定したコグの例が以下のようになります:

class MyCog(commands.Cog, name='Example Cog'):
    def cog_unload(self):
        print('cleanup goes here')

    def bot_check(self, ctx):
        print('bot check')
        return True

    def bot_check_once(self, ctx):
        print('bot check once')
        return True

    async def cog_check(self, ctx):
        print('cog local check')
        return await ctx.bot.is_owner(ctx.author)

    async def cog_command_error(self, ctx, error):
        print('Error in {0.command.qualified_name}: {1}'.format(ctx, error))

    async def cog_before_invoke(self, ctx):
        print('cog local before: {0.command.qualified_name}'.format(ctx))

    async def cog_after_invoke(self, ctx):
        print('cog local after: {0.command.qualified_name}'.format(ctx))

    @commands.Cog.listener()
    async def on_message(self, message):
        pass

前後処理のフック

コマンドに、コマンドの実行前および実行後に処理が行えるようにするフックが新たに追加されました。

これは単一のパラメータとして Context を受け取り、かつコルーチンである必要があります。

また、このフックは全体、コグごと、あるいはコマンドごとに設定することが可能です。

基本的には以下のとおりです:

# global hooks:

@bot.before_invoke
async def before_any_command(ctx):
    # do something before a command is called
    pass

@bot.after_invoke
async def after_any_command(ctx):
    # do something after a command is called
    pass

後処理のフックは コマンドのエラー発生に関わらず 必ず呼び出されます。そのため、データベース接続のようなリソースのクリーンアップやエラー処理に最適です。

コマンドごとに設定する方法は以下のとおりです。

@bot.command()
async def foo(ctx):
    await ctx.send('foo')

@foo.before_invoke
async def before_foo_command(ctx):
    # do something before the foo command is called
    pass

@foo.after_invoke
async def after_foo_command(ctx):
    # do something after the foo command is called
    pass

これらのコグ用の特別なメソッドは Cog.cog_before_invoke()Cog.cog_after_invoke() です。例:

class MyCog(commands.Cog):
    async def cog_before_invoke(self, ctx):
        ctx.secret_cog_data = 'foo'

    async def cog_after_invoke(self, ctx):
        print('{0.command} is done...'.format(ctx))

    @commands.command()
    async def foo(self, ctx):
        await ctx.send(ctx.secret_cog_data)

Context.command_failed を使うことで、後処理でコマンドがエラーになったかを確認する事ができます。

呼び出される順序は以下のとおりです。

  1. コマンドごとの前処理。

  2. コグごとの前処理。

  3. 全体での前処理。

  4. 実行されたコマンド。

  5. コマンドごとの後処理。

  6. コグごとの後処理。

  7. 全体での後処理。

コンバーターの変更

v1.0以前では、コンバーターはユーザーにより渡された引数を、文字列の単独引数として呼び出す型ヒントでした。

このシステムは最終的に Context が組み込み可能な Converter システムをサポートするために拡張され、ビルトインの「discord」コンバータのようにより複雑な変換が行なえるようになりました。

v1.0ではこのコンバーターシステムは Converter 派生のクラスを使用できるよう変更されました。一貫性を保つため、 convert() は常にコルーチンとなるよう変更され、二つのパラメータを受け取ります。

更新前:

class MyConverter(commands.Converter):
    def convert(self):
        return self.ctx.message.server.me

変更後:

class MyConverter(commands.Converter):
    async def convert(self, ctx, argument):
        return ctx.me

コマンドフレームワークにも二つのコンバーターが追加されました。