Pincer Commands Module#

Commands#

command#

@command(func=None, *, name=None, description='Description not set', enable_default=True, guild=None, cooldown=0, cooldown_scale=60.0, cooldown_scope=ThrottleScope.USER, parent=None)#

A decorator to create a slash command to register and respond to with the discord API from a function.

str - String int - Integer bool - Boolean float - Number pincer.objects.User - User pincer.objects.Channel - Channel pincer.objects.Role - Role pincer.objects.Mentionable - Mentionable

class Bot(Client):
    @command(
        name="test",
        description="placeholder"
    )
    async def test_command(
        self,
        ctx: MessageContext,
        amount: int,
        name: CommandArg[
            str,
            Description("Do something cool"),
            Choices(Choice("first value", 1), 5)
        ],
        optional_int: CommandArg[
            int,
            MinValue(10),
            MaxValue(100),
        ] = 50
    ):
        return Message(
            f"You chose {amount}, {name}, {letter}",
            flags=InteractionFlags.EPHEMERAL
        )
References from above:

Client, Message, MessageContext, InteractionFlags, Choices, Choice, typing_extensions.Annotated (Python 3.8), typing.Annotated (Python 3.9+), CommandArg, Description, MinValue, MaxValue

Parameters
  • name (Optional[str]) – The name of the command

    Default: None

  • description (Optional[str]) – The description of the command

    Default: Description not set

  • enable_default (Optional[bool]) – Whether the command is enabled by default

    Default: True

  • guild (Optional[Union[Snowflake, int, str]]) – What guild to add it to (don’t specify for global)

    Default: None

  • cooldown (Optional[int]) – The amount of times in the cooldown_scale the command can be invoked

    Default: 0

  • cooldown_scale (Optional[float]) – The ‘checking time’ of the cooldown

    Default: 60

  • cooldown_scope (ThrottleScope) – What type of cooldown strategy to use

    Default: ThrottleScope.USER

Raises
  • CommandIsNotCoroutine – If the command function is not a coro

  • InvalidCommandName – If the command name does not follow the regex ^[\w-]{1,32}$

  • InvalidCommandGuild – If the guild id is invalid

  • CommandDescriptionTooLong – Descriptions max 100 characters If the annotation on an argument is too long (also max 100)

  • CommandAlreadyRegistered – If the command already exists

  • TooManyArguments – Max 25 arguments to pass for commands

  • InvalidArgumentAnnotation – Annotation amount is max 25, Not a valid argument type, Annotations must consist of name and value

@message_command(func=None, *, name=None, enable_default=True, guild=None, cooldown=0, cooldown_scale=60, cooldown_scope=ThrottleScope.USER)#

A decorator to create a user command to register and respond to the Discord API from a function.

class Bot(Client):
    @user_command
    async def test_message_command(
        self,
        ctx: MessageContext,
        message: UserMessage,
    ):
        return message.content
References from above:

Client, MessageContext, UserMessage, User, GuildMember,

Parameters
  • name (Optional[str]) – The name of the command

    Default: None

  • enable_default (Optional[bool]) – Whether the command is enabled by default

    Default: True

  • guild (Optional[Union[Snowflake, int, str]]) – What guild to add it to (don’t specify for global)

    Default: None

  • cooldown (Optional[int]) – The amount of times in the cooldown_scale the command can be invoked

    Default: 0

  • cooldown_scale (Optional[float]) – The ‘checking time’ of the cooldown

    Default: 60

  • cooldown_scope (ThrottleScope) – What type of cooldown strategy to use

    Default: ThrottleScope.USER

Raises
  • CommandIsNotCoroutine – If the command function is not a coro

  • InvalidCommandName – If the command name does not follow the regex ^[\w-]{1,32}$

  • InvalidCommandGuild – If the guild id is invalid

  • CommandDescriptionTooLong – Descriptions max 100 characters If the annotation on an argument is too long (also max 100)

  • CommandAlreadyRegistered – If the command already exists

  • InvalidArgumentAnnotation – Annotation amount is max 25, Not a valid argument type, Annotations must consist of name and value

@user_command(func=None, *, name=None, enable_default=True, guild=None, cooldown=0, cooldown_scale=60, cooldown_scope=ThrottleScope.USER)#

A decorator to create a user command registering and responding to the Discord API from a function.

class Bot(Client):
    @user_command
    async def test_user_command(
        self,
        ctx: MessageContext,
        user: User,
        member: GuildMember
    ):
        if not member:
            # member is missing if this is a DM
            # This bot doesn't like being DMed, so it won't respond
            return

        return f"Hello {user.name}, this is a Guild."
References from above:

Client, MessageContext, User, GuildMember,

Parameters
  • name (Optional[str]) – The name of the command

    Default: None

  • enable_default (Optional[bool]) – Whether the command is enabled by default

    Default: True

  • guild (Optional[Union[Snowflake, int, str]]) – What guild to add it to (don’t specify for global)

    Default: None

  • cooldown (Optional[int]) – The amount of times in the cooldown_scale the command can be invoked

    Default: 0

  • cooldown_scale (Optional[float]) – The ‘checking time’ of the cooldown

    Default: 60

  • cooldown_scope (ThrottleScope) – What type of cooldown strategy to use

    Default: ThrottleScope.USER

Raises
  • CommandIsNotCoroutine – If the command function is not a coro

  • InvalidCommandName – If the command name does not follow the regex ^[\w-]{1,32}$

  • InvalidCommandGuild – If the guild id is invalid

  • CommandDescriptionTooLong – Descriptions max 100 characters If the annotation on an argument is too long (also max 100)

  • CommandAlreadyRegistered – If the command already exists

  • InvalidArgumentAnnotation – Annotation amount is max 25, Not a valid argument type, Annotations must consist of name and value

Command Types#

class Modifier#

Bases: object

Modifies a CommandArg by being added to CommandArg’s args.

Modifiers go inside an typing.Annotated type hint.

Annotated[
    # This is the type of command.
    # Supported types are str, int, bool, float, User, Channel, and Role
    int,
    # The modifiers to the command go here
    Description("Pick a number 1-10"),
    MinValue(1),
    MaxValue(10)
]
class Description#

Bases: Modifier

Represents the description of an application command option

# Creates an int argument with the description "example description"
Annotated[
    int,
    Description("example description")
]
Parameters

desc (str) – The description for the command.

class Choices#

Bases: Modifier

Represents a group of application command choices that a user can pick from

Annotated[
    int,
    Choices(
        Choice("First Number", 10),
        20,
        50
    )
]
Parameters

*choices (Union[Choice, str, int, float]) – A choice. If the type is not Choice, the same value will be used for the choice name and value.

class Choice#

Bases: Modifier

Represents a choice that the user can pick from

Choices(
    Choice("First Number", 10),
    Choice("Second Number", 20)
)
Parameters
  • name (str) – The name of the choice

  • value (Union[int, str, float]) – The value of the choice

class MaxValue#

Bases: Modifier

Represents the max value for a number

Annotated[
    int,
    # The user can't pick a number above 10
    MaxValue(10)
]
Parameters

max_value (Union[float, int]) – The max value a user can choose.

class MinValue#

Bases: Modifier

Represents the minimum value for a number

Annotated[
    int,
    # The user can't pick a number below 10
    MinValue(10)
]
Parameters

min_value (Union[float, int]) – The minimum value a user can choose.

class ChannelTypes#

Bases: Modifier

Represents a group of channel types that a user can pick from

Annotated[
    Channel,
    # The user will only be able to choice between GUILD_TEXT and
    GUILD_TEXT channels.
    ChannelTypes(
        ChannelType.GUILD_TEXT,
        ChannelType.GUILD_VOICE
    )
]
Parameters

*types (ChannelType) – A list of channel types that the user can pick from.

class CommandArg#

Bases: object

Holds the parameters of an application command option

Note

Deprecated. typing.Annotated or typing_extensions.Annotated should be used instead. See https://docs.pincer.dev/en/stable/interactions.html#arguments for more information.

Annotated[
    # This is the type of command.
    # Supported types are str, int, bool, float, User, Channel, and Role
    int,
    # The modifiers to the command go here
    Description("Pick a number 1-10"),
    MinValue(1),
    MaxValue(10)
]
Parameters
  • command_type (T) – The type of the command

  • *args (Modifier) –

ChatCommandHandler#

class ChatCommandHandler#

Bases: object

Singleton containing methods used to handle various commands

The register and built_register#

I found the way Discord expects commands to be registered to be very different than how you want to think about command registration. i.e. Discord wants nesting but we don’t want any nesting. Nesting makes it hard to think about commands and also will increase lookup time. The way this problem is avoided is by storing a version of the commands that we can deal with as library developers and a version of the command that Discord thinks we should provide. That is where the register and the built_register help simplify the design of the library. The register is simply where the “Pincer version” of commands gets saved to memory. The built_register is where the version of commands that Discord requires is saved. The register allows for O(1) lookups by storing commands in a Python dictionary. It does cost some memory to save two copies in the current iteration of the system but we should be able to drop the built_register in runtime if we want to. I don’t feel that lost maintainability from this is optimal. We can index by in O(1) by checking the register but can still use the built_register if we need to do a nested lookup.

client#

The client object

Type

Client

managers#

Dictionary of managers

Type

Dict[str, Any]

register#

Dictionary of InteractableStructure

Type

Dict[str, AppCommand]]

built_register#

Dictionary of InteractableStructure where the commands are converted to the format that Discord expects for sub commands and sub command groups.

Type

Dict[str, AppCommand]]

await add_command(cmd)#

This function is a coroutine.

Add an app command

Parameters

cmd (AppCommand) – Command to add

await add_commands(commands)#

This function is a coroutine.

Add a list of app commands

Parameters

commands (List[AppCommand]) – List of command objects to add

await get_commands()#

This function is a coroutine.

Get a list of app commands from Discord

Returns

List of commands.

Return type

List[AppCommand]

await initialize()#

This function is a coroutine.

Call methods of this class to refresh all app commands

await remove_command(cmd)#

This function is a coroutine.

Remove a specific command

Parameters

cmd (AppCommand) – What command to delete

Message Components#

class ActionRow#

Bases: APIObject

Represents an Action Row

Parameters

*components (MessageComponent) – MessageComponent, Button, or SelectMenu

to_dict()#

Transform the current object to a dictionary representation. Parameters that start with an underscore are not serialized.

class Button#

Bases: _Component

Represents a Discord Button object. Buttons are interactive components that render on messages.

They can be clicked by users, and send an interaction to your app when clicked.

style#

One of button styles. Use LinkButton if you need the LINK style.

Type

ButtonStyle

label#

text that appears on the button, max 80 characters

Type

APINullable[str]

emoji#

name, id, and animated

Type

APINullable[Emoji]

custom_id#

A developer-defined identifier for the button, max 100 characters

Type

APINullable[str]

disabled#

Whether the button is disabled

Default: False

Type

APINullable[bool]

classmethod bind_client(client)#

Links the object to the client.

Parameters

client (Client) – The client to link to.

classmethod from_dict(data)#

Parse an API object from a dictionary.

to_dict()#

Transform the current object to a dictionary representation. Parameters that start with an underscore are not serialized.

with_attrs(**kwargs)#

Modifies attributes are returns an object with these attributes.

some_button.with_attrs(label="A new label")
**kwargs

Attributes to modify and their values.

class LinkButton#

Bases: _Component

Represents Button message component with a link.

label#

text that appears on the button, max 80 characters

Type

APINullable[str]

emoji#

name, id, and animated

Type

APINullable[Emoji]

custom_id#

A developer-defined identifier for the button, max 100 characters

Type

APINullable[str]

url#

A url for link-style buttons

Type

APINullable[str]

disabled#

Whether the button is disabled (default False)

Type

APINullable[bool]

class ButtonStyle#

Bases: IntEnum

Buttons come in a variety of styles to convey different types of actions. These styles also define what fields are valid for a button.

Primary#
  • color: blurple

  • required_field: custom_id

Secondary#
  • color: gray

  • required_field: custom_id

Success#
  • color: green

  • required_field: custom_id

Danger#
  • color: red

  • required_field: custom_id

  • color: gray, navigates to a URL

  • required_field: url

class SelectMenu#

Bases: _Component

Represents a Discord Select Menu

custom_id#

A developer-defined identifier for the button, max 100 characters

Type

str

options#

The choices in the select, max 25

Type

List[SelectOption]

placeholder#

Custom placeholder text if nothing is selected, max 100 characters

Type

APINullable[str]

min_values#

The minimum number of items that must be chosen; min 0, max 25

Default: 1

Type

APINullable[int]

max_values#

The maximum number of items that can be chosen; max 25

Default: 1

Type

APINullable[int]

disabled#

Disable the selects

Default: False

Type

APINullable[bool]

classmethod bind_client(client)#

Links the object to the client.

Parameters

client (Client) – The client to link to.

classmethod from_dict(data)#

Parse an API object from a dictionary.

to_dict()#

Transform the current object to a dictionary representation. Parameters that start with an underscore are not serialized.

with_appended_options(*options)#

Append *options to the options parameter and returns a new SelectMenu.

*optionsSelectOption

List of options to append

with_attrs(**kwargs)#

Modifies attributes are returns an object with these attributes.

some_button.with_attrs(label="A new label")
**kwargs

Attributes to modify and their values.

with_options(*options)#

Sets the options parameter to *options and returns a new SelectMenu.

*optionsSelectOption

List of options to set

class SelectOption#

Bases: APIObject

Represents a Discord Select Option

label#

The user-facing name of the option, max 100 characters

Type

str

value#

The def-defined value of the option, max 100 characters

Type

str

description#

An additional description of the option, max 100 characters

Type

APINullable[str]

emoji#

id, name, and animated

Type

APINullable[Emoji]

default#

Will render this option as selected by default

Type

APINullable[bool]

class _Component#

Bases: APIObject

Represents a message component with attributes that can be modified inline

with_attrs(**kwargs)#

Modifies attributes are returns an object with these attributes.

some_button.with_attrs(label="A new label")
**kwargs

Attributes to modify and their values.

@button(label, style, emoji=None, url=None, disabled=None, custom_id=None)#

Turn a function into handler for a Button. See Button for information on parameters.

The function will still be callable.

from pincer.commands import ActionRow, Button

class Bot(Client):

    @command
    async def send_a_button(self):
        return Message(
            content="Click a button",
            components=[
                ActionRow(
                    self.button_one
                )
            ]
        )

    @button(label="Click me!", style=ButtonStyle.PRIMARY)
    async def button_one():
        return "Button one pressed"
@select_menu(func=None, options=None, placeholder=None, min_values=None, max_values=None, disabled=None, custom_id=None)#

Turn a function into handler for a SelectMenu. See SelectMenu for information on parameters.

The function will still be callable.

from pincer.commands import button, ActionRow, ButtonStyle

class Bot(Client):

    @command
    async def send_a_select_menu(self):
        return Message(
            content="Choose an option",
            components=[
                ActionRow(
                    self.select_menu
                )
            ]
        )

    @select_menu(options=[
        SelectOption(label="Option 1"),
        SelectOption(label="Option 2", value="value different than label")
    ])
    async def select_menu(values: List[str]):
        return f"{values[0]} selected"
@component(custom_id)#

Generic handler for Message Components. Can be used with manually constructed Button and SelectMenu objects.

Parameters

custom_id (str) – The ID of the message component to handle.

Command Groups#

class Group#

Bases: object

The group object represents a group that commands can be in. This is always a top level command.

class Bot:

    group = Group("cool_commands")

    @command(parent=group)
    async def a_very_cool_command():
        pass

This code creates a command called cool_commands with the subcommand a_very_cool_command

Parameters
  • name (str) – The name of the command group.

  • description (Optional[str]) – The description of the command. This has to be sent to Discord, but it does nothing, so it is optional.

class Subgroup#

Bases: object

A subgroup of commands. This allows you to create subcommands inside a subcommand-group.

class Bot:

    group = Group("cool_commands")
    sub_group = Subgroup("group_of_cool_commands")

    @command(parent=sub_group)
    async def a_very_cool_command():
        pass

This code creates a command called cool_commands with the subcommand-group group_of_cool_commands that has the subcommand a_very_cool_command.

Parameters
  • name (str) – The name of the command sub-group.

  • parent (Group) – The parent group of this command.

  • description (Optional[str]) – The description of the command. This has to be sent to Discord, but it does nothing, so it is optional.

Interactable Objects#

Methods
class Interactable#

Bases: object

Class that can register PartialInteractable objects. Any class that subclasses this class can register Application Commands and Message Components. PartialInteractable objects are registered by running the register function and setting an attribute of the client to the result.

unassign()#

Removes this objects loaded commands from ChatCommandHandler and ComponentHandler and removes loaded events from the client.