Interactions#
Pincer makes it extremely easy to create an interaction. All you need to do is create a method with the @command decorator.
Note
Although purely functional bots are possible, it’s recommended to inherit from the Client class as seen in the example below.
from pincer import Client
from pincer.commands import command
class Bot(Client):
@command
async def some_command(self):
...
bot = Bot("TOKEN")
bot.run()
This registers a command called “some_command”. It’s pretty useless right now, so let’s take a closer look at what else you can do.
Note
If you’re not seeing your command register, it’s likely because it takes up to one hour for a global slash command to do so. Specify the guild in the command decorator to have application commands register instantly.
To register your command to a guild do:
@command(guild=MY_GUILD_ID)
async def some_command(self):
...
Sending messages to the user is extremely easy. What you want to return is inferred by the object’s return type. A str
can be returned to send a text message.
Here’s a simple ping command.
@command
async def ping(self):
return "pong"
If you need access to more information, you can pass in the ctx
object.
Note
ctx
and self
should be those exact names or the correct value will not be passed in.
from pincer.objects import MessageContext
...
@command
async def hello(self, ctx: MessageContext):
# Returns the name of the user that initiated the interaction
return f"hello {ctx.author}"
Application Command Types#
Pincer provides an API for all three interaction command types. The only thing that varies is the function signature.
from pincer.commands import command, user_command, message_command
from pincer.objects import MessageContext, UserMessage, User
...
@command
# Can have any amount of inputs
async def ping(self, ctx: MessageContext, arg1: str, arg2: str):
return "pong"
# Must have a parameter for users. User can be a GuildMember. All User
# methods are available to GuildMember because GuildMember inherits from
# User.
@user_command
async def user_ping(self, ctx: MessageContext, user: User):
return "pong"
# Must have a parameter for messages.
@message_command
async def message_ping(self, ctx: MessageContext, message: UserMessage):
return "pong"
Interaction Timeout#
Interactions time out after 3 seconds. To extend the timeout to 15 minutes you can run ack()
from
MessageContext
. InteractionFlags
is available in this method.
Arguments#
Every parameter besides ctx
and self
is inferred to be a slash command argument.
Notice how word is typehinted as string. Pincer uses type hints to infer the argument type that you want.
@command
async def say(self, word: str):
return word
The list of possible type hints is as follows:
Class |
Command Argument Type |
---|---|
String |
|
Integer |
|
Boolean |
|
Number |
|
User |
|
Channel |
|
Role |
|
Mentionable |
You might want to specify more information for your arguments. If you want a description for your command, you will have to use the
Description
type. Modifier types like this need to be inside of the Annotated
type.
from typing import Annotated # Python 3.9+
from typing_extensions import Annotated # Python 3.8
from pincer.commands import Description
from pincer.objects import MessageContext
@command
async def say(
self,
ctx: MessageContext,
word: Annotated[
str,
Description("A word that the bot will say.") # type: ignore # noqa: F722
]
):
# Returns the name of the user that initiated the interaction
return word
These are the available modifiers:
Modifier |
What it does |
Locked to types |
---|---|---|
|
Description of a command option. |
|
|
Application command choices. |
|
|
A group of channel types that a user can pick from. |
|
|
The maximum value for a number. |
|
|
The minimum value for a number. |
Parameters will be an optional slash command arguments if they have a default value in Python.
@command
async def say(
self,
word: str = "apple" # Word is optional
):
return word
Return Types#
str
isn’t the only thing you can return. For a more complex message, you can return a Message
object.
The message object allows you to return embeds and attachments. InteractionFlags
are only available in the response
if you return a Message
object.
from pincer import Client, command, Embed
from pincer.objects import Message, File
...
@command
async def a_complex_message(self):
return Message(
"This is the message's content",
embeds=[
Embed(
title="Pincer",
description=(
"🚀 An asynchronous python API wrapper meant to replace"
" discord.py\n> Snappy discord api wrapper written"
" with aiohttp & websockets"
).set_image(
url="attachments://some_image.png"
)
)
],
attachments=[
File.from_file("path/to/a/file.png", filename="new_name.png"),
"path/to/another/file.png" # A string is inferred to be a filepath here!
]
)
Attachments can also be Pillow images if Pillow is installed.
from PIL import Image
...
attachments=[
Image.new("RGBA", (500, 500), (255, 0, 0)), # Will automatically be named `image0.png`
Image.new("RGB", (500, 500)), # Will automatically be named `image1.png`
File.from_pillow_image(some_pillow_image, "this_is_the_image_name.png") # You can also do this to specify the name
]
Additionally, Pillow Images, Files, and Embeds can be returned directly without wrapping them in a Message
object.
...
@command
async def a_complex_message(self):
return Embed(
title="Pincer",
description=(
"🚀 An asynchronous python API wrapper meant to replace"
" discord.py\n> Snappy discord api wrapper written"
" with aiohttp & websockets"
)
)
Return Type |
Discord Message |
---|---|
text only message |
|
Discord embed |
|
file attachment |
|
single image attachment |
Sending Messages Without Return#
The MessageContext
object provides methods to send a response to an interaction.
from pincer.objects import MessageContext, Message
@command
async def some_command(self, ctx: MessageContext):
await ctx.send("Hello world!") # Sends hello world as the response to the interaction
return # No response will be sent now that the interaction has been completed
@command
async def some_other_command(self, ctx: MessageContext):
await ctx.channel.send("Hello world!") # Sends a message in the channel
return "Hello world 2" # This is sent because the interaction was not "used up"
Message Components#
Pincer supports buttons and select menus.
Subcommands and Subcommand Groups#
To nest commands, Pincer organizes them into Group
and
Subgroup
objects. Group and Subgroup names must only consist of
lowercase letters and underscores.
This chart shows the organization of nested commands:
If you use a group:
group name
command name
If you use a group and sub group:
group name
subgroup name
command name
Organizing commands like this is also valid:
group name
subgroup name
command name
command name
Group
and Subgroup
are set to the parent in a @command
decorator to nest a command inside of them. They are not available for User Commands and Message Commands.
from pincer.commands import Group, Subgroup
...
class Bot(Client):
command_group = Group("command_group")
command_sub_group = Subgroup("command_sub_group", parent=a_command_group)
@command(parent=command_group)
def command_group_command():
pass
@command(parent=command_sub_group)
def command_sub_group_command():
pass
# Creating these commands is valid because there is no top-level command or group
# with the same name.
@command
def command_group_command():
pass
@command
def command_sub_group():
pass
# This command is not valid because there is a group with the same name.
@command
def a_command_group():
pass