コマンド

コマンド拡張の最も魅力的な機能の一つは、簡単にコマンドが定義でき、かつそのコマンドを好きなようにネスト状にして、豊富なサブコマンドを用意することができる点です。

コマンドは、Pythonの関数と関連付けすることによって定義され、同様のシグネチャを使用してユーザーに呼び出されます。

例えば、指定されたコマンド定義を使うと次のようになります。

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

Prefixを ($) としたとすると、このコマンドは次の用に実行できます。

$foo abc

コマンドには、少なくとも Context を渡すための引数 ctx が必要です。

コマンドを登録するには二通りの方法があります。一つ目は Bot.command() を使用する方法で、二つ目が command() デコレータを使用して Bot.add_command() でインスタンスにコマンドを追加していく方法です。

本質的に、これら2つは同等になります:

from discord.ext import commands

bot = commands.Bot(command_prefix='$')

@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の使用者側は、通常の文字列を渡すだけで位置引数に値を渡すことができます。

../../_images/positional1.png

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

../../_images/positional2.png

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

../../_images/positional3.png

位置引数は、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}')

これによって一つ、あるいは複数の引数を受け取ることができます。ただし、引数を渡す際の挙動は位置引数と同様のため、複数の単語を含む文字列は引用符で囲む必要があります。

例えば、Bot側ではこのように動きます。

../../_images/variable1.png

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

../../_images/variable2.png

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

../../_images/variable3.png

args 変数は tuple となるため、通常タプルと同様の操作が行なえます。

キーワード引数

引数の構文解析を自分で行う場合や、複数単語の入力を引用符で囲む必要のないようにしたい場合は、渡された値を単一の引数として受け取るようにライブラリに求めることができます。以下のコードのようにキーワード引数のみを使用することでこれが可能になります。

@bot.command()
async def test(ctx, *, arg):
    await ctx.send(arg)

警告

解析が曖昧になるため、一つのキーワードのみの引数しか扱えません。

Bot側では、スペースを含む入力を引用符で囲む必要がありません

../../_images/keyword1.png

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

../../_images/keyword2.png

通常、キーワード引数は利便性のために空白文字で分割されます。この動作はデコレータの引数として Command.rest_is_raw を使うことで切り替えることが可能です。

呼び出しコンテクスト

前述の通り、すべてのコマンドは必ず Context と呼ばれるパラメータを受け取らなければいけません。

このパラメータにより、「呼び出しコンテクスト」というものにアクセスできます。言うなればコマンドがどのように実行されたのかを知るのに必要な基本的情報です。これにはたくさんの有用な情報が含まれています。

コンテクストは abc.Messageable インタフェースを実装しているため、 abc.Messageable 上でできることは Context 上でも行うことが可能です。

コンバータ

Botの引数を関数のパラメータとして設定するのは、Botのコマンドインタフェースを定義する第一歩です。引数を実際に扱うには、大抵の場合、データを目的の型へとと変換する必要があります。私達はこれを Converters と呼んでいます。

コンバータにはいくつかの種類があります:

  • 引数を一つのパラメータとして受け取り、異なる型として返す、通常の呼び出し可能オブジェクト。

    • これらにはあなたの作った関数、 boolint といったものまで含まれます。

  • 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 のコンバーターは若干異なる扱いになります。 bool 型に直接キャストする代わりに、与えられた値に基づいて TrueFalse を返します。

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モデルの多くがコンバーターとして動作します。

これらをコンバーターとして設定すると、引数を指定した型へとインテリジェントに変換します。

これらは 応用的なコンバータ インタフェースによって実装されています。コンバーターとクラスの関係は以下の通りです。

コンバーターを継承することで、他のコンバーターの一部として使うことができます:

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!')
../../_images/optional1.png

この例では引数を int に変換することができなかったので、デフォルト値である 99 を代入し、パーサーは処理を続行しています。この場合、先程の変換に失敗した引数は liquid パラメータに渡されます。

注釈

このコンバーターは位置パラメータでのみ動作し、可変長パラメータやキーワードパラメータでは機能しません。

typing.Literal

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は int1 または 2 に変換されなければなりません。buy_sell または amount がどの値にも一致しない場合は、特別なエラーが発生します BadLiteralArgument 。任意のリテラル値は、同じ typing.Literal コンバータ内で混合してマッチさせることができます。

typing.Literal[True]typing.Literal[False]bool コンバータのルールに従っていることに注意してください。

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

これが呼び出されると、任意の数のメンバーを渡すことができます:

../../_images/greedy1.png

このコンバータを利用した際に渡される型は、その対象となっているパラメータの種類によって異なります。

  • 位置パラメータの場合、型はデフォルトのものか変換された値からなる 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"""
    for member in members:
        await member.ban(delete_message_days=delete_days, reason=reason)

このコマンドは以下のような方法で呼び出すことが可能です。

$ban @Member @Member2 spam bot
$ban @Member @Member2 7 spam bot
$ban @Member spam

警告

Greedytyping.Optional の利用は強力かつ便利である反面、その代償として一部の人が驚いてしまうような曖昧な構文解析を許容することとなります。

例えば、 discord.Membertyping.Optional の後に int が続くようなシグネチャでは MemberConverter がメンバー取得のために様々な方法をとることが要因となり、名前が数字になっているメンバーを取得してしまう可能性があります。コードが意図しない曖昧な構文解析を引き起こさないよう注意してください。テクニックの一つとして、カスタムコンバータを用いて予期される構文の許容を制限するか、このような衝突を最小限に抑えるために、パラメータを並び替えることなどが挙げられます。

曖昧な構文解析を防ぐため、 strNonetyping.Optional 、そして GreedyGreedy コンバータのパラメータにするのは禁止されています。

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

フラグライクなシンプルな構文を使用してコマンドを呼び出すことができます:

../../_images/flags1.png

フラグはフラグに値を渡す際に引用符を必要としない構文を使用します。 フラグ構文の目的は、できるだけユーザーフレンドリーになることです。 これにより、外部コマンドインターフェースでキーワードのみのパラメータを回転またはシミュレートするための複数のノブを持つ複雑なコマンドにフラグが適しています。 フラグコンバーターでキーワードのみのパラメータを使用することをお勧めします。これにより、適切な解析と引用符での挙動を保証します。

内部的には、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 を唯一のパラメータとして使用します。この呼び出し可能な関数またはコルーチンのいずれかを使用できます。

Flag 構文をカスタマイズするために、class パラメータリストに渡すオプションもいくつかあります。

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

注釈

これらの例では引数のようにコマンドを実行するのと似ていますが、構文と parser はコマンドラインパーサではありません。 この構文は主にDiscordの検索バー入力に触発されており、その結果、すべてのフラグに対応する値が必要になります。

フラグコンバーターは通常のコマンドと似ており、ほとんどのタイプのコンバータを使用できます(例外は 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)')

これはフラグを繰り返し指定することで呼び出されます:

../../_images/flags2.png

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 コマンドを以下のように呼び出すことができます。

../../_images/flags3.png

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]] と同等です。

エラーハンドリング

コマンドの解析に失敗したとき、通常では煩わしいエラーはエラーの発生を伝えるためにコンソールの stderr で受け取られ、無視されていました。

エラーを処理するにはエラーハンドラと呼ばれるものを利用する必要があります。 on_command_error() というグローバルエラーハンドラが存在し、これは イベントリファレンス のイベントのように動作します。このグローバルエラーハンドラはエラーが発生するたびに呼び出されます。

しかし、ほとんどの場合においては、コマンド自体に対応するローカルなエラー処理を行いたいと考えるでしょう。幸いなことに、コマンドにはローカルエラーハンドラが存在するため、これを利用して実現することができます。まず、エラーハンドラとして利用する関数を Command.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 が渡されます。 エラー一覧は Exceptions から見ることができます。

チェック

コマンドをユーザーに使ってほしくない場合などがあります。例えば、使用者が権限を持っていない場合や、Botをブロックしている場合などです。コマンド拡張ではこのような機能を Checks と呼び、完全にサポートしています。

チェックは Context を引数とする関数です。関数はこれらの選択ができます:

  • True を返し、その人がコマンドを実行できることを示します。

  • False を返して、コマンドを実行できないことを知らせます。

  • CommandError を継承する例外を発生させ、コマンドを実行できないことを示する。

    • :ref:`エラーハンドラ <ext_commands_error_handler> ` のように独自のエラーメッセージを使うことができます。

チェックを登録するには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_ownerTrue だったときのみコマンドを実行します。しかし、チェックを使い回すために独自のデコレーターにしたくなることもあるでしょう。そうしたい場合は、

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)

@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() )。

グローバルチェック

特定のコマンドだけでなく、every コマンドにチェックを適用したい場合もあります。ライブラリはこれをサポートし、グローバルチェックコンセプトを使用します。

グローバルチェックは、 Bot.check() デコレータに登録されている場合を除き、通常のチェックと同様に動作します。

例えば、全DMをブロックするには、次の操作を行います。

@bot.check
async def globally_block_dms(ctx):
    return ctx.guild is not None

警告

グローバル小切手を作成する方法に注意してください。また、自分のボットからもロックされる可能性があります。