コマンド¶
コマンド拡張の最も魅力的な機能の一つは、簡単にコマンドが定義でき、かつそのコマンドを好きなようにネスト状にして、豊富なサブコマンドを用意することができる点です。
コマンドは、Pythonの関数と関連付けすることによって定義され、同様のシグネチャを使用してユーザーに呼び出されます。
警告
コマンド拡張機能が機能するには、 message_content
インテントが必要です。これは、デベロッパーポータルとコード内の両方で有効化しないといけません。
そうしない場合は、ボットはコマンドに応答しなくなります。
例えば、次のコマンド定義を使うと次のようになります。
@bot.command()
async def foo(ctx, arg):
await ctx.send(arg)
接頭辞を ($
) としたとすると、このコマンドは次のように実行できます。
$foo abc
コマンドには、少なくとも Context
を渡すための引数 ctx
が必要です。
コマンドを登録するには二通りの方法があります。一つ目は Bot.command()
を使用する方法で、二つ目が command()
デコレータを使用して Bot.add_command()
でインスタンスにコマンドを追加していく方法です。
本質的に、これら2つは同等になります:
import discord
from discord.ext import commands
intents = discord.Intents.default()
intents.message_content = True
bot = commands.Bot(command_prefix='$', intents=intents)
@bot.command()
async def test(ctx):
pass
# or:
@commands.command()
async def test(ctx):
pass
bot.add_command(test)
Bot.command()
が簡単かつ理解がしやすいので、ドキュメント上ではこちらを使っています。
Command
のコンストラクタの引数はデコレータに渡すことで利用できます。例えば、コマンドの名前を関数以外のものへと変更したい場合は以下のように簡単に設定することができます。
@bot.command(name='list')
async def _list(ctx, arg):
pass
パラメータ¶
Pythonの関数定義によって、同時にコマンドを定義するので、関数のパラメータを設定することにより、コマンドの引数受け渡し動作も定義することができます。
特定のパラメータタイプはユーザーサイドで異なる動作を行い、そしてほとんどの形式のパラメータタイプがサポートされています。
位置パラメータ¶
最も基本的な引数は位置パラメータです。与えられた値をそのまま渡します。
@bot.command()
async def test(ctx, arg):
await ctx.send(arg)
Botの使用者側は、通常の文字列を渡すだけで位置パラメータに値を渡すことができます。

間に空白を含む文字列を渡す場合は、文字列を引用符で囲む必要があります。

引用符を用いなかった場合、最初の文字列のみが渡されます。

位置パラメータは、Pythonの引数と同じものなので、好きなだけ設定することが可能です。
@bot.command()
async def test(ctx, arg1, arg2):
await ctx.send(f'You passed {arg1} and {arg2}')
可変長引数¶
場合によっては、可変長のパラメータを設定したい場合もあるでしょう。このライブラリはPythonの可変長パラメータと同様にこれをサポートしています。
@bot.command()
async def test(ctx, *args):
arguments = ', '.join(args)
await ctx.send(f'{len(args)} arguments: {arguments}')
これによって一つ、あるいは複数の引数を受け取ることができます。ただし、引数を渡す際の挙動は位置パラメータと同様のため、複数の単語を含む文字列は引用符で囲む必要があります。
例えば、ボット側ではこのように動きます。

複数単語の文字列を渡す際は、引用符で囲んでください。

Pythonの振る舞いと同様に、ユーザーは引数なしの状態を渡すことも一応できます。

args
変数は tuple
となるため、通常のタプルと同様の操作が行なえます。
キーワード引数¶
引数の構文解析を自分で行う場合や、複数単語の入力を引用符で囲む必要のないようにしたい場合は、渡された値を単一の引数として受け取るようにライブラリに求めることができます。以下のコードのようにキーワード引数のみを使用することでこれが可能になります。
@bot.command()
async def test(ctx, *, arg):
await ctx.send(arg)
警告
解析が曖昧になるため、キーワード引数は一つまでしか扱えません。
ボット側では、スペースを含む入力を引用符で囲む必要がありません:

引用符で囲んだ場合、消えずに残るので注意してください:

通常、キーワード引数は利便性のために空白文字で分割されます。この動作はデコレータの引数として Command.rest_is_raw
を使うことで切り替えることが可能です。
呼び出しコンテキスト¶
前述の通り、すべてのコマンドは必ず Context
と呼ばれるパラメータを受け取らなければいけません。
このパラメータにより、「呼び出しコンテキスト」というものにアクセスできます。言うなればコマンドがどのように実行されたのかを知るのに必要な基本的情報です。これにはたくさんの有用な情報が含まれています。
存在する場合、
Context.guild
は、コマンドのGuild
を返します。Context.message
は、コマンドのMessage
を返します。Context.author
は、 コマンドを呼び出したMember
またはUser
を返します。Context.send()
で、コマンドが実行されたチャンネルにメッセージを送信できます。
コンテキストは abc.Messageable
インタフェースを実装しているため、 abc.Messageable
上でできることは Context
上でも行うことが可能です。
コンバーター¶
ボットの引数を関数のパラメータとして設定するのは、ボットのコマンドインタフェースを定義する第一歩でしかありません。引数を実際に扱うには、大抵の場合、データを目的の型へとと変換する必要があります。私達はこれを コンバーター と呼んでいます。
コンバーターにはいくつかの種類があります:
引数を単独のパラメータとして受け取り、異なる型として返す、通常の呼び出し可能オブジェクト。
Converter
を継承したカスタムクラス。
基本的なコンバーター¶
基本的なコンバーターは、中核をなすものであり、受け取った引数を別のものへと変換します。
例えば、二つの値を加算したい場合、コンバーターを指定することにより、受け取った値を整数型へ変換するように要求できます。
@bot.command()
async def add(ctx, a: int, b: int):
await ctx.send(a + b)
コンバーターの指定には関数アノテーションというもの用います。これは PEP 3107 にて追加された Python 3 にのみ実装されている機能です。
これは、文字列をすべて大文字に変換する関数などといった、任意の呼び出し可能関数でも動作します。
def to_upper(argument):
return argument.upper()
@bot.command()
async def up(ctx, *, content: to_upper):
await ctx.send(content)
論理型¶
他の基本的なコンバーターとは違って、 bool
のコンバーターは若干扱いが異なります。 bool
型に直接キャストして、空でない引数を True
と判断するのではなく、与えられた値に基づいて True
か False
かを評価します。
if lowered in ('yes', 'y', 'true', 't', '1', 'enable', 'on'):
return True
elif lowered in ('no', 'n', 'false', 'f', '0', 'disable', 'off'):
return False
応用的なコンバーター¶
場合によっては、基本的なコンバーターを動かすのに必要な情報が不足していることがあります。例えば、実行されたコマンドの Message
から情報を取得したい場合や、非同期処理を行いたい場合です。
そういった用途のために、このライブラリは Converter
インタフェースを提供します。これによって Context
にアクセスが可能になり、また、呼び出し可能関数を非同期にもできるようになります。このインタフェースを使用して、カスタムコンバーターを定義したい場合は Converter.convert()
をオーバーライドしてください。
コンバーターの例
import random
class Slapper(commands.Converter):
async def convert(self, ctx, argument):
to_slap = random.choice(ctx.guild.members)
return f'{ctx.author} slapped {to_slap} because *{argument}*'
@bot.command()
async def slap(ctx, *, reason: Slapper):
await ctx.send(reason)
コンバーターはインスタンス化されていなくても構いません。以下の例の二つのは同じ処理になります。
@bot.command()
async def slap(ctx, *, reason: Slapper):
await ctx.send(reason)
# is the same as...
@bot.command()
async def slap(ctx, *, reason: Slapper()):
await ctx.send(reason)
コンバーターをインスタンス化する可能性がある場合、コンバーターの調整を行うために __init__
で何かしらの状態を設定することが出来ます。この例としてライブラリに実際に存在する clean_content
があります。
@bot.command()
async def clean(ctx, *, content: commands.clean_content):
await ctx.send(content)
# or for fine-tuning
@bot.command()
async def clean(ctx, *, content: commands.clean_content(use_nicknames=False)):
await ctx.send(content)
コンバーターが渡された引数を指定の型に変換できなかった場合は BadArgument
を送出しないといけません。
埋込み型の応用的なコンバーター¶
Converter
を継承したくない場合のために、応用的なコンバーターの高度な機能を備えたコンバーターを提供しています。これを使用することで2つのクラスを作成する必要がなくなります。
例えば、一般的な書き方だと、クラスとそのクラスへのコンバーターを定義します:
class JoinDistance:
def __init__(self, joined, created):
self.joined = joined
self.created = created
@property
def delta(self):
return self.joined - self.created
class JoinDistanceConverter(commands.MemberConverter):
async def convert(self, ctx, argument):
member = await super().convert(ctx, argument)
return JoinDistance(member.joined_at, member.created_at)
@bot.command()
async def delta(ctx, *, member: JoinDistanceConverter):
is_new = member.delta.days < 100
if is_new:
await ctx.send("Hey you're pretty new!")
else:
await ctx.send("Hm you're not so new.")
これでは面倒なので、 classmethod()
を使って組み込み型の応用的なコンバーターの実装が可能です。
class JoinDistance:
def __init__(self, joined, created):
self.joined = joined
self.created = created
@classmethod
async def convert(cls, ctx, argument):
member = await commands.MemberConverter().convert(ctx, argument)
return cls(member.joined_at, member.created_at)
@property
def delta(self):
return self.joined - self.created
@bot.command()
async def delta(ctx, *, member: JoinDistance):
is_new = member.delta.days < 100
if is_new:
await ctx.send("Hey you're pretty new!")
else:
await ctx.send("Hm you're not so new.")
Discord コンバーター¶
Discordモデル を使用して作業を行うのは、コマンドを定義する際には一般的なことです。そのため、このライブラリでは簡単に作業が行えるようになっています。
例えば、 Member
を受け取るには、これをコンバーターとして渡すだけです。
@bot.command()
async def joined(ctx, *, member: discord.Member):
await ctx.send(f'{member} joined on {member.joined_at}')
このコマンドが実行されると、与えられた文字列を Member
に変換して、それを関数のパラメーターとして渡します。これは文字列がメンション、ID、ニックネーム、ユーザー名 + Discordタグ、または普通のユーザー名かどうかをチェックすることで機能しています。デフォルトで定義されているコンバーターは、できるだけ簡単に使えるように作られています。
Discordモデルの多くがコンバーターとして動作します。
Object
(v2.0から)Message
(v1.1 から)PartialMessage
(v1.7 から)abc.GuildChannel
(v2.0から)StageChannel
(v1.7から)ForumChannel
(v2.0から)Guild
(v1.7 から)Thread
(v2.0から)GuildSticker
(v2.0から)ScheduledEvent
(v2.0から)
これらをコンバーターとして設定すると、引数を指定した型へとインテリジェントに変換します。
これらは 応用的なコンバーター インタフェースによって実装されています。コンバーターとクラスの関係は以下の通りです。
Discord クラス |
コンバーター |
コンバーターを継承することで、他のコンバーターの一部として使うことができます:
class MemberRoles(commands.MemberConverter):
async def convert(self, ctx, argument):
member = await super().convert(ctx, argument)
return [role.name for role in member.roles[1:]] # Remove everyone role!
@bot.command()
async def roles(ctx, *, member: MemberRoles):
"""Tells you a member's roles."""
await ctx.send('I see the following roles: ' + ', '.join(member))
特殊なコンバーター¶
コマンド拡張機能は一般的な線形解析を超える、より高度で複雑な使用法に対応するため、特殊なコンバーターをサポートしています。これらのコンバーターは、簡単な方法でコマンドに更に容易で動的な文法の導入を可能にします。
typing.Union¶
typing.Union
はコマンドが単数の型の代わりに、複数の特定の型を取り込める特殊な型ヒントです。例えば:
import typing
@bot.command()
async def union(ctx, what: typing.Union[discord.TextChannel, discord.Member]):
await ctx.send(what)
what
パラメータには discord.TextChannel
コンバーターか discord.Member
コンバーターのいずれかが用いられます。これは左から右の順で変換できるか試行することになります。最初に渡された値を discord.TextChannel
へ変換しようと試み、失敗した場合は discord.Member
に変換しようとします。すべてのコンバーターで失敗した場合は BadUnionArgument
というエラーが発生します。
以前に説明した有効なコンバーターは、すべて typing.Union
にわたすことが可能です。
typing.Optional¶
typing.Optional
は「後方参照」のような動作をする特殊な型ヒントです。コンバーターが指定された型へのパースに失敗した場合、パーサーは代わりに None
または指定されたデフォルト値をパラメータに渡したあと、そのパラメータをスキップします。次のパラメータまたはコンバータがあれば、そちらに進みます。
次の例をみてください:
import typing
@bot.command()
async def bottles(ctx, amount: typing.Optional[int] = 99, *, liquid="beer"):
await ctx.send(f'{amount} bottles of {liquid} on the wall!')

この例では引数を int
に変換することができなかったので、デフォルト値である 99
を代入し、パーサーは処理を続行しています。この場合、先程の変換に失敗した引数は liquid
パラメータに渡されます。
注釈
このコンバーターは位置パラメータでのみ動作し、可変長パラメータやキーワードパラメータでは機能しません。
typing.Literal¶
バージョン 2.0 で追加.
typing.Literal
は、渡されたパラメータが同じ型に変換された後にリストされた値のいずれかに等しいことを要求する特別な型ヒントです。 例えば、以下のように指定します。
from typing import Literal
@bot.command()
async def shop(ctx, buy_sell: Literal['buy', 'sell'], amount: Literal[1, 2], *, item: str):
await ctx.send(f'{buy_sell.capitalize()}ing {amount} {item}(s)!')
buy_sell
パラメータはリテラル文字列の "buy"
または "sell"
のどちらかで、 amount
は int
の 1
または 2
に変換されなければなりません。 buy_sell
または amount
がどの値にも一致しない場合は、特別なエラー BadLiteralArgument
が発生します。任意のリテラル値は、同じ typing.Literal
コンバーター内で混合してマッチさせることができます。
typing.Literal[True]
と typing.Literal[False]
は bool
コンバーターのルールに従っていることに注意してください。
typing.Annotated¶
バージョン 2.0 で追加.
typing.Annotated
は Python 3.9 で導入された特別な型です。 これを使用すると、型チェッカは1つのタイプを参照できますが、ライブラリは別のタイプを参照できます。 これは複雑なコンバータにて型チェッカを通すのに役立ちます。 Annotated
の2番目のパラメータはライブラリが使用するコンバータでなければなりません。
例えば、以下の例では:
from typing import Annotated
@bot.command()
async def fun(ctx, arg: Annotated[str, lambda s: s.upper()]):
await ctx.send(arg)
型チェッカは arg
を通常の str
として認識しますが、ライブラリは入力をすべて大文字に変更したいことを知っています。
注釈
Python 3.9未満では、 typing_extensions
ライブラリをインストールして Annotated
をそこからインポートすることを推奨します。
Greedy¶
Greedy
コンバーターは引数にリストが適用される以外は typing.Optional
を一般化したものです。簡単に言うと、与えられた引数を変換ができなくなるまで指定の型に変換しようと試みます。
次の例をみてください:
@bot.command()
async def slap(ctx, members: commands.Greedy[discord.Member], *, reason='no reason'):
slapped = ", ".join(x.name for x in members)
await ctx.send(f'{slapped} just got slapped for {reason}')
これが呼び出されると、任意の数のメンバーを渡すことができます:

このコンバータを利用した際に渡される型は、その対象となっているパラメータの種類によって異なります。
位置パラメータの場合、型はデフォルトのものか変換された値からなる
list
になります。可変長パラメータの場合、型は通常同様
tuple
になります。キーワードパラメータの場合、型は
Greedy
を使用していないときと同じになります。
Greedy
パラメータはデフォルト値を指定することでオプションにすることもできます。
typing.Optional
コンバータと併用することで、シンプルかつ表現に富む呼び出し構文を提供できます。
import typing
@bot.command()
async def ban(ctx, members: commands.Greedy[discord.Member],
delete_days: typing.Optional[int] = 0, *,
reason: str):
"""Mass bans members with an optional delete_days parameter"""
delete_seconds = delete_days * 86400 # one day
for member in members:
await member.ban(delete_message_seconds=delete_seconds, reason=reason)
このコマンドは以下のような方法で呼び出すことが可能です。
$ban @Member @Member2 spam bot
$ban @Member @Member2 7 spam bot
$ban @Member spam
警告
Greedy
と typing.Optional
の利用は強力かつ便利である反面、その代償として一部の人が驚いてしまうような曖昧な構文解析を許容することとなります。
例えば、 discord.Member
の typing.Optional
の後に int
が続くようなシグネチャでは MemberConverter
がメンバー取得のために様々な方法をとることが要因となり、名前が数字になっているメンバーを取得してしまう可能性があります。コードが意図しない曖昧な構文解析を引き起こさないよう注意してください。テクニックの一つとして、カスタムコンバーターを用いて予期される構文の許容を制限するか、このような衝突を最小限に抑えるために、パラメータを並び替えることなどが挙げられます。
曖昧な構文解析を防ぐため、 str
、 None
、 typing.Optional
、そして Greedy
を Greedy
コンバーターのパラメーターにするのは禁止されています。
discord.Attachment¶
バージョン 2.0 で追加.
discord.Attachment
コンバータはメッセージにアップロードされた添付ファイルから添付ファイルを一個取得する特別なコンバータです。このコンバータは、メッセージ内容は 確認せず アップロードされた添付ファイルのみ確認します。
次の例をみてください:
import discord
@bot.command()
async def upload(ctx, attachment: discord.Attachment):
await ctx.send(f'You have uploaded <{attachment.url}>')
コマンドの呼び出し時に、ユーザーはコマンドを実行するときにはファイルを直接アップロードしないといけません。これを typing.Optional
コンバータと組み合わせた場合、添付ファイルを提供する必要はありません。
import typing
import discord
@bot.command()
async def upload(ctx, attachment: typing.Optional[discord.Attachment]):
if attachment is None:
await ctx.send('You did not upload anything!')
else:
await ctx.send(f'You have uploaded <{attachment.url}>')
これは複数の添付ファイルでも動作します:
import typing
import discord
@bot.command()
async def upload_many(
ctx,
first: discord.Attachment,
second: typing.Optional[discord.Attachment],
):
if second is None:
files = [first.url]
else:
files = [first.url, second.url]
await ctx.send(f'You uploaded: {" ".join(files)}')
この例では、ユーザーは少なくとも1つのファイルを提供する必要がありますが、2つ目のファイルはオプションです。
特別なケースとして、 Greedy
を使用すると、存在する場合はメッセージ内の残りの添付ファイルが返されます。
import discord
from discord.ext import commands
@bot.command()
async def upload_many(
ctx,
first: discord.Attachment,
remaining: commands.Greedy[discord.Attachment],
):
files = [first.url]
files.extend(a.url for a in remaining)
await ctx.send(f'You uploaded: {" ".join(files)}')
なお、 discord.Attachment
の Greedy
の後に discord.Attachment
コンバータを使用するのは、Greedyが残りの添付ファイルをすでに使用しているため、常に失敗します。
添付ファイルが期待されているのに与えられていない場合、 MissingRequiredAttachment
がエラーハンドラに送出されます。
FlagConverter¶
バージョン 2.0 で追加.
FlagConverter
を使用すると、型アノテーション PEP 526 を使用してユーザーフレンドリーな「フラグ」を指定したり、 dataclasses
モジュールを彷彿とさせる構文を使用できます。
例えば、以下のコードです。
from discord.ext import commands
import discord
class BanFlags(commands.FlagConverter):
member: discord.Member
reason: str
days: int = 1
@commands.command()
async def ban(ctx, *, flags: BanFlags):
plural = f'{flags.days} days' if flags.days != 1 else f'{flags.days} day'
await ctx.send(f'Banned {flags.member} for {flags.reason!r} (deleted {plural} worth of messages)')
フラグに似たシンプルな構文を使用してコマンドを呼び出すことができます:

フラグは、フラグに値を渡す際に引用符を必要としない構文を使用しています。フラグ構文の目標は、できるだけユーザーフレンドリーにすることです。このため、複数のノブを使用する複雑なコマンドや、外部コマンド・インターフェースでキーワードのみのパラメータをシミュレートする場合、フラグを使用するのが適しています。 フラグ・コンバータでは、キーワード専用パラメータを使用することをお勧めします。 これにより、適切な解析と引用符での動作が保証されます。
内部的には、FlagConverter
クラスがクラスを調べてフラグを見つけます。フラグには、型アノテーションが付いたクラス変数と、 flag()
関数の結果が代入されたクラス変数があります。これらのフラグは、ユーザーが使用するインターフェースを定義するために使用されます。アノテーションは、フラグの引数が準拠しなければならないコンバーターに対応しています。
ほとんどの場合、フラグを定義するために余分な作業は必要ありません。 しかし、フラグ名やデフォルト値を制御するためにカスタマイズが必要な場合は、 flag()
関数が便利です:
from typing import List
class BanFlags(commands.FlagConverter):
members: List[discord.Member] = commands.flag(name='member', default=lambda ctx: [])
これは members
属性が member
というフラグにマップされ、デフォルト値が空のリストであることをパーサーに伝えます。 カスタマイズ性を向上させるために、デフォルトは値か呼び出し可能な値で、 Context
を唯一のパラメータとして取ります。この呼び出し可能な値は関数またはコルーチンのいずれかを使用できます。
フラグ構文をカスタマイズするために、クラスのパラメーターリストに渡せるオプションもいくつか用意されています。
# --hello world syntax
class PosixLikeFlags(commands.FlagConverter, delimiter=' ', prefix='--'):
hello: str
# /make food
class WindowsLikeFlags(commands.FlagConverter, prefix='/', delimiter=''):
make: str
# TOPIC: not allowed nsfw: yes Slowmode: 100
class Settings(commands.FlagConverter, case_insensitive=True):
topic: Optional[str]
nsfw: Optional[bool]
slowmode: Optional[int]
注釈
これらの例では引数のようにコマンドを実行するのと似ていますが、この構文と解析機はコマンドライン解析機ではありません。 この構文は主にDiscordの検索バー入力に触発されており、その結果、すべてのフラグに対応する値が必要になります。
フラグコンバータは FlagError
派生の例外のみ送出します。フラグ変換中にエラーが発生した場合、 BadFlagArgument
が代わりに送出され、元の例外は original
属性でアクセスできます。
フラグコンバーターは通常のコマンドと似ており、ほとんどのタイプのコンバータを型アノテーションとして使用できます(例外は Greedy
) 。以下で説明するように、特定のアノテーションに対する追加のサポートが追加されます。
typing.List¶
リストがフラグアノテーションとして与えられた場合、引数が何回も渡せることをパーサーに知らせます。
例えば、上記の例を拡張すると:
from discord.ext import commands
from typing import List
import discord
class BanFlags(commands.FlagConverter):
members: List[discord.Member] = commands.flag(name='member')
reason: str
days: int = 1
@commands.command()
async def ban(ctx, *, flags: BanFlags):
for member in flags.members:
await member.ban(reason=flags.reason, delete_message_days=flags.days)
members = ', '.join(str(member) for member in flags.members)
plural = f'{flags.days} days' if flags.days != 1 else f'{flags.days} day'
await ctx.send(f'Banned {members} for {flags.reason!r} (deleted {plural} worth of messages)')
これはフラグを繰り返し指定することで呼び出されます:

typing.Tuple¶
フラグを何度も指定する場合、上記の構文は少し繰り返しになるので、 tuple
型アノテーションを使用すると、可変タプルを使用した「欲張りな」セマンティクスを実現することができます。
from discord.ext import commands
from typing import Tuple
import discord
class BanFlags(commands.FlagConverter):
members: Tuple[discord.Member, ...]
reason: str
days: int = 1
これにより、以前の ban
コマンドを以下のように呼び出すことができます。

tuple
アノテーションはペアの解析を可能にします。例えば、以下のコードがあります。
# point: 10 11 point: 12 13
class Coordinates(commands.FlagConverter):
point: Tuple[int, int]
警告
解析が曖昧になってしまうため、パーサーはタプル引数がスペースを必要とする場合、引用符で囲むことを期待します。そのため、もし内部型のひとつが str
で、その引数がスペースを必要とする場合には、タプルの他の要素と区別するために引用符が使用されなければなりません。
typing.Dict¶
dict
アノテーションは、list
ではなく、dict
として与えられた戻り値の値を除いて、List[Tuple[K, V]]
と同等です
ハイブリッドコマンドでの動作¶
ハイブリッドコマンドとして使用された場合、パラメータはアプリケーションコマンドでは別々のものになります。例えば、次のコンバータは:
class BanFlags(commands.FlagConverter):
member: discord.Member
reason: str
days: int = 1
@commands.hybrid_command()
async def ban(ctx, *, flags: BanFlags):
...
以下のように定義されたアプリケーションコマンドと同等です:
@commands.hybrid_command()
async def ban(ctx, member: discord.Member, reason: str, days: int = 1):
...
これは、パラメータを名前で参照するデコレータはフラグ名を代わりに使用するということです。
class BanFlags(commands.FlagConverter):
member: discord.Member
reason: str
days: int = 1
@commands.hybrid_command()
@app_commands.describe(
member='The member to ban',
reason='The reason for the ban',
days='The number of days worth of messages to delete',
)
async def ban(ctx, *, flags: BanFlags):
...
使いやすいように、 flag()
関数は description
キーワード引数を受け取るので、説明をインラインで渡すことができます。
class BanFlags(commands.FlagConverter):
member: discord.Member = commands.flag(description='The member to ban')
reason: str = commands.flag(description='The reason for the ban')
days: int = commands.flag(default=1, description='The number of days worth of messages to delete')
@commands.hybrid_command()
async def ban(ctx, *, flags: BanFlags):
...
同様に、 name
キーワード引数を使用すると、 rename()
デコレータと同様に、パラメータの改名ができます。
ハイブリッドコマンド形式では、Discordの制限によりいくつかのアノテーションがサポートされていないことに注意してください。
typing.Tuple
typing.List
typing.Dict
注釈
ハイブリッドコマンド1個ごとにフラグコンバータ1個までサポートされています。 フラグコンバーターの動作の仕組みのため、あるシグネチャに2個フラグコンバータが存在するのはまれです。
パラメータのメタデータ¶
parameter()
はカスタムメタデータを Command
のパラメータに割り当てます。
これは以下の場合に便利です:
パラメータのアノテーションにカスタムコンバーターを付するカスタムコンバーターはランタイムで動作するため、型チェッカが理解できず、問題が発生します。
class SomeType: foo: int class MyVeryCoolConverter(commands.Converter[SomeType]): ... # implementation left as an exercise for the reader @bot.command() async def bar(ctx, cool_value: MyVeryCoolConverter): cool_value.foo # type checker warns MyVeryCoolConverter has no value foo (uh-oh)
しかし、
parameter()
を使用すれば、型チェッカが何が起こっているかを理解できるようになります。@bot.command() async def bar(ctx, cool_value: SomeType = commands.parameter(converter=MyVeryCoolConverter)): cool_value.foo # no error (hurray)
既定値の動的評価
@bot.command() async def wave(ctx, to: discord.User = commands.parameter(default=lambda ctx: ctx.author)): await ctx.send(f'Hello {to.mention} :wave:')
これが非常に一般的な使用法であることから、ライブラリは
Author
、CurrentChannel
、CurrentGuild
を提供します。これらを使用するとwave
は以下のように簡素化できます:@bot.command() async def wave(ctx, to: discord.User = commands.Author): await ctx.send(f'Hello {to.mention} :wave:')
Author
などは、表示される既定値が事前に指定されているなど、他の利点もあります。
エラーハンドリング¶
コマンドの解析に失敗すると、デフォルトではエラーの発生とそれが握り潰されたことを知らせるノイズのようなエラーがコンソールの stderr
に出力されます。
エラーを処理するには、エラーハンドラと呼ばれるものを利用する必要があります。
on_command_error()
グローバルエラーハンドラが存在し、これは イベントリファレンス のイベントのように動作します。
このハンドラはエラーが発生するたびに呼び出されます。
しかし、ほとんどの場合においては、コマンド自体に対応するローカルなエラー処理を行いたいと考えるでしょう。幸いなことに、コマンドにはローカルエラーハンドラが存在するため、これを利用して実現することができます。まず、エラーハンドラとして利用する関数を error()
でデコレートします。
@bot.command()
async def info(ctx, *, member: discord.Member):
"""Tells you some info about the member."""
msg = f'{member} joined on {member.joined_at} and has {len(member.roles)} roles.'
await ctx.send(msg)
@info.error
async def info_error(ctx, error):
if isinstance(error, commands.BadArgument):
await ctx.send('I could not find that member...')
ハンドラの最初の引数には Context
が渡され、2番目の引数には CommandError
が渡されます。 エラー一覧は 例外 から見ることができます。
チェック¶
コマンドをユーザーに使ってほしくない場合などがあります。例えば、使用者が権限を持っていない場合や、ボットによりブロックされている場合などです。コマンド拡張機能ではこのような機能を Checks と呼び、完全にサポートしています。
チェックは Context
を引数とする関数です。関数はこれらの選択ができます:
True
を返し、その人がコマンドを実行できることを示します。False
を返し、その人がコマンドを実行できないことを示します。CommandError
を継承する例外を発生させ、コマンドを実行できないことを示します。エラーハンドラ のように独自のエラーメッセージを使うことができます。
チェックを登録するには2つの方法があります:1つ目は check()
を使う方法です。
async def is_owner(ctx):
return ctx.author.id == 316026178463072268
@bot.command(name='eval')
@commands.check(is_owner)
async def _eval(ctx, *, code):
"""A bad example of an eval command"""
await ctx.send(eval(code))
例えば、この場合は is_owner
が True
だったときのみコマンドを実行します。しかし、チェックを使い回すために独自のデコレーターにしたくなることもあるでしょう。そうしたい場合は、
def is_owner():
async def predicate(ctx):
return ctx.author.id == 316026178463072268
return commands.check(predicate)
@bot.command(name='eval')
@is_owner()
async def _eval(ctx, *, code):
"""A bad example of an eval command"""
await ctx.send(eval(code))
このようにすると独自のデコレーターになります。
このチェックはとてもよく使われるため、ライブラリに標準で実装されています( is_owner()
)。
@bot.command(name='eval')
@commands.is_owner()
async def _eval(ctx, *, code):
"""A bad example of an eval command"""
await ctx.send(eval(code))
複数のチェックが渡されたときには、 すべて のチェックが True
になる必要があります。
def is_in_guild(guild_id):
async def predicate(ctx):
return ctx.guild and ctx.guild.id == guild_id
return commands.check(predicate)
@bot.command()
@commands.is_owner()
@is_in_guild(41771983423143937)
async def secretguilddata(ctx):
"""super secret stuff"""
await ctx.send('secret stuff')
もしチェックのうちどれかが失敗した場合、コマンドは実行されません。
もし例外が発生した場合、 エラーハンドラ によって例外が処理されます。もし CommandError
を継承しないエラーを発生させた場合、 CheckFailure
が発生します。
@bot.command()
@commands.is_owner()
@is_in_guild(41771983423143937)
async def secretguilddata(ctx):
"""super secret stuff"""
await ctx.send('secret stuff')
@secretguilddata.error
async def secretguilddata_error(ctx, error):
if isinstance(error, commands.CheckFailure):
await ctx.send('nothing to see here comrade.')
もし強化されたエラーシステムが必要な場合は、例外を継承し、False
を返す代わりに例外を発生させることができます。
class NoPrivateMessages(commands.CheckFailure):
pass
def guild_only():
async def predicate(ctx):
if ctx.guild is None:
raise NoPrivateMessages('Hey no DMs!')
return True
return commands.check(predicate)
@bot.command()
@guild_only()
async def test(ctx):
await ctx.send('Hey this is not a DM! Nice.')
@test.error
async def test_error(ctx, error):
if isinstance(error, NoPrivateMessages):
await ctx.send(error)
注釈
guild_only
デコレータはよく使われるため、標準で実装されています( guild_only()
)。
グローバルチェック¶
すべての コマンドにチェックをかけたいこともあるでしょう。そうしたい場合は、ライブラリのグローバルチェックを使うことができます。
グローバルチェックは、 Bot.check()
デコレータで登録されることを除き、通常のチェックと同様に動作します。
例えば、全DMをブロックするには、次の操作を行います。
@bot.check
async def globally_block_dms(ctx):
return ctx.guild is not None
警告
グローバルチェックを追加するときには注意して下さい。ボットを操作できなくなる可能性があります。
ハイブリッドコマンド¶
バージョン 2.0 で追加.
commands.HybridCommand
は、テキストコマンドとしても、スラッシュコマンドとしても呼び出せるコマンドです。これを使用すれば、別々のコードを書かずにコマンドをスラッシュコマンドとテキストコマンドの両方として定義できます。
ハイブリッドコマンドを定義するには、コマンドコールバックを Bot.hybrid_command()
デコレータで装飾しないといけません。
@bot.hybrid_command()
async def test(ctx):
await ctx.send("This is a hybrid command!")
上のコマンドはテキストコマンドとスラッシュコマンドの両方として実行できます。なお、スラッシュコマンドを表示するには、 sync
を呼び出して CommandTree
を手動で同期しないといけません。


Bot.hybrid_group()
デコレータを使用して、ハイブリッドコマンドグループとサブコマンドを作成できます。
@bot.hybrid_group(fallback="get")
async def tag(ctx, name):
await ctx.send(f"Showing tag: {name}")
@tag.command()
async def create(ctx, name):
await ctx.send(f"Created tag: {name}")
Discordの制限により、 スラッシュコマンドグループは直接呼び出すことができないため、 fallback
パラメータを使用して、親グループのコールバックを呼び出すサブコマンドを作成できます。


スラッシュコマンドには制限があるため、ハイブリッドコマンドではテキストコマンドの一部の機能がサポートされていません。スラッシュコマンドでサポートされている機能のみ使用している場合にハイブリッドコマンドを定義できます。
以下は現時点でハイブリッドコマンドではサポート されていません:
可変長引数。例:
*arg: int
深さが1より大きいグループコマンド。
- ほとんどの
typing.Union
型。 チャンネルの型のユニオン型は使用できます
ユーザーの型のユニオン型は使用できます
チャンネルの型とロールの型のユニオン型は使用できます
- ほとんどの
それ以外の、コンバーター、チェック、オートコンプリート、フラグ、その他はすべてハイブリッドコマンドで利用できます。なお、設計上の制限により、 discord.app_commands.autocomplete()
といったアプリケーションコマンド関連のデコレータは hybrid_command()
デコレータの下に配置しないといけません。
コードを簡単に書くために、 Context
クラスのメソッドや属性の動作が変化します:
Context.interaction
を用いてスラッシュコマンドのインタラクションを取得できます。インタラクションは一度しか応答できないため、
Context.send()
は、インタラクション応答とフォローアップ応答のどちらを送信するかを自動的に決定します。Context.defer()
はスラッシュコマンドではインタラクション応答を遅らせ、テキストコマンドでは入力インジケーターを表示します。