Skip to content

Converters

Basic⚓︎

Basic converter is a function or type that can be invoked with one argument to transform an input argument into a different form or representation.

Using function⚓︎

Example usage:

def swap_characters(argument: str):
    return argument.swapcase()

@bot.command()
async def swapcase(ctx: commands.Context, *, text: swap_characters):
    await ctx.send(text)

swapcase swapcase

Using built-in types⚓︎

@bot.command()
async def double(ctx, number: int):
    result = number * 2
    await ctx.send(f"The double of {number} is {result}")

@bot.command()
async def square(ctx, number: float):
    result = number ** 2
    await ctx.send(f"The square of {number} is {result}")

double square

Note

By default, when passing arguments to a command, they are treated as strings. Therefore, it is necessary to convert them to the appropriate data type, such as integers, in order to perform operations on them.

Warning

The bool type converter is handled in a slightly different way, and you can find more information about it here

Discord.py built-in⚓︎

Built-in converters in Discord.py are provided by the library and are readily available for use. They help in converting user input into specific data types such as discord.Member, discord.User, discord.TextChannel, discord.Role and many more full list can be found here.

Example usage:

@bot.command()
async def greet(ctx, member: discord.Member):
    await ctx.send(f"Hello, {member.mention}!")

greet

Advanced⚓︎

Advanced converters in Discord.py allow you to define your own conversion logic for specific types of inputs. You can create a custom converter by subclassing the commands.Converter class provided by Discord.py and implementing the convert method.

Example usage:

class DurationConverter(commands.Converter):
    async def convert(self, ctx: commands.Context, argument: str) -> datetime.timedelta:
        multipliers = {
            's': 1,  # seconds
            'm': 60,  # minutes
            'h': 3600,  # hours
            'd': 86400,  # days
            'w': 604800  # weeks
        }

        try:
            amount = int(argument[:-1])
            unit = argument[-1]
            seconds = amount * multipliers[unit]
            delta = datetime.timedelta(seconds=seconds)
            return delta
        except (ValueError, KeyError):
            raise commands.BadArgument("Invalid duration provided.")

@bot.command()
async def timeout(ctx: commands.Context, member: discord.Member, duration: DurationConverter):
    await member.timeout(duration)
    await ctx.send(f"Timed out {member.mention} for {duration}")

timeout timeout

Inline Advanced⚓︎

Even if we choose not to inherit from the Converter class, we can still offer a convert class method that incorporates the advanced functionalities typically associated with an advanced converter.

class Percentage:
    def __init__(self, value: float):
        self.value = value

    @classmethod
    async def convert(cls, ctx: commands.Context, argument: str):
        try:
            percentage = float(argument.strip("%")) / 100
            return cls(percentage)
        except ValueError:
            raise commands.BadArgument("Invalid percentage provided.")

@bot.command()
async def discount(ctx: commands.Context, original_price: float, percentage: Percentage):
    discounted_price = original_price * (1 - percentage.value)
    savings = original_price - discounted_price

    await ctx.send(f"Original Price: ${original_price:.2f}\n"
                   f"Discount: {percentage.value * 100}%\n"
                   f"Discounted Price: ${discounted_price:.2f}\n"
                   f"Savings: ${savings:.2f}")

inline inline

Special⚓︎

Warning

If you are using Python 3.9 or below when using Union, Optional, Literal or Annotated you have to import typing module

Union⚓︎

The Union converter allows a command to accept multiple specific types instead of just one type. It provides a way to introduce more relaxed and dynamic grammar to commands.

@bot.command()
async def union(ctx, channel: typing.Union[discord.TextChannel, discord.DMChannel], *, message: str):
    await channel.send(message)
@bot.command()
async def union(ctx, channel: discord.TextChannel | discord.DMChannel, *, message: str):
    await channel.send(message)

Optional⚓︎

The Optional converter allows for "back-referencing" behavior. If the converter fails to parse the input into the specified type, the parser will skip the parameter. Then, either None or the specified default value will be passed to the parameter. The parser will continue to the next parameters and converters, if any.

@bot.command()
async def greet(ctx: commands.Context, member: typing.Optional[discord.Member] = None):
    member = member or ctx.author
    await ctx.send(f"Hello, {member.mention}!")
@bot.command()
async def greet(ctx: commands.Context, member: discord.Member | None = None):
    member = member or ctx.author
    await ctx.send(f"Hello, {member.mention}!")

optional optional

Literal⚓︎

The Literal converter specifies that the passed parameter must be exactly one of the listed values.

@bot.command()
async def rps(ctx: commands.Context, move: typing.Literal["rock", "paper", "scissors"]):
    await ctx.send(f"You played {move}")

@rps.error
async def rps_error(ctx: commands.Context, error: commands.CommandInvokeError):
    if isinstance(error, commands.BadLiteralArgument):
        await ctx.send("Invalid choice. Valid choices are: rock, paper, scissors.")
        return
    print(error)

Warning

If none of the options from the Literal are passed then BadLiteralArgument is raised.

Annotated⚓︎

In Python 3.9, a new feature called typing.Annotated was introduced. It enables the type checker to view one type, while allowing the library to see a different type. This feature proves valuable when dealing with intricate converters, as it helps satisfy the type checker's requirements. The converter, which the library should utilize, must be specified as the second parameter of typing.Annotated.

We can update the definition of the previously defined command swapcase in a way that informs the type checker that the variable text will be of type str.

def swap_characters(argument: str):
    return argument.swapcase()

@bot.command()
async def swapcase(ctx: commands.Context, *, text: typing.Annotated[str, swap_characters]):
    await ctx.send(text)

swapcase swapcase

Note

If you're using a Python version lower than 3.9, you can import Annotated from the typing_extensions module, as it was introduced in Python 3.9.

Greedy⚓︎

The Greedy converter is for a list of arguments. It attempts to convert as much as possible until it can't convert anymore.

@bot.command()
async def add(ctx: commands.Context, numbers: commands.Greedy[int]):
    total = sum(numbers)
    await ctx.send(f"The sum of the numbers is: {total}")

greedy

Warning

The provided command definition async def command(ctx: commands.Context, argument1: commands.Greedy[int], argument2: int) won't work as expected because the Greedy type annotation consumes all available int values, leaving no values for the argument2. As a result, argument2 will be missing and MissingRequiredArgument will be raised. To ensure the availability of a value for argument2 annotate it with a different type, such as str or other.

Attachment⚓︎

Unlike the other built-in Discord.py converters, the Attachment converter does not handle text input. Instead, its purpose is to search for and retrieve uploaded files.

@bot.command()
async def upload(ctx: commands.Context, attachment: discord.Attachment):
    await ctx.send(f'You have uploaded <{attachment.url}>')

Flag Converters⚓︎

With the help of a FlagConverter, users can conveniently indicate user-friendly "flags" using PEP 526 type annotations.

Default syntax for flag converters:

!command argument1: value1 argument2: value2 ...

If you wish to learn more about this feature please refer to the documentation here

Parameter metadata⚓︎

The parameter feature enables the addition of metadata to command parameters, including details like converters, default values, descriptions, and displayed names. For a list of all available parameters, please refer to the documentation here.

We can update the definition of the previously defined command timeout, allowing us to type hint it as datetime.timedelta and utilize the converter argument of the parameter.

@bot.command()
async def timeout(ctx: commands.Context, member: discord.Member, duration: datetime.timedelta = commands.parameter(converter=DurationConverter)):
    await member.timeout(duration)
    await ctx.send(f"Timed out {member.mention} for {duration}")

timeout timeout

We can also update the definition of the previously defined command greet, incorporating the use of the parameter feature to define a default value. Instead of explicitly checking if the member was passed or using the command author, we can set a default value using parameter.

@bot.command()
async def greet(ctx: commands.Context, member: discord.Member = commands.parameter(default=lambda ctx: ctx.author)):
    await ctx.send(f"Hello, {member.mention}!")

We can further simplify the code by taking advantage of some common use-case defaults provided by the library. The Author, CurrentChannel, and CurrentGuild options can be used as default values, eliminating the need for explicit checks or manual setting.

@bot.command()
async def greet(ctx: commands.Context, member: discord.Member = commands.Author):
    await ctx.send(f"Hello, {member.mention}!")

optional optional

Comments