feat: Viewless components
ok now that this is starting to become more concrete, here is some info:
This pull request ~~refactors~~ rewrites py-cord's Components and Modals interface from the ground up. discord.ui has been removed entirely and replaced by top-level classes available in discord directly.
Since View has been removed, purposefully, py-cord becomes entirely stateless, which essentially means that all "views" are now persistent.
[!CAUTION] This removes
ext.pagesentirely since it cannot fundamentally be updated to work with the new design.
The API works as follows:
Components Example (click to open)
def build_container(secret_message: str) -> Container:
return Container(
ActionRow(
Button(
style=ButtonStyle.primary,
label="Click me! I'll tell you a secret",
custom_id=f"v1:hello_button_{secret_message}",
emoji=PartialEmoji(name="🤫"),
)
),
id=3,
)
@bot.command()
async def secret(ctx: ApplicationContext, secret_message: str) -> None:
await ctx.respond(
components=[build_container(secret_message)],
)
def hello_button_predicate(custom_id: str) -> bool:
return custom_id.startswith("v1:hello_button_")
@bot.component_listener(hello_button_predicate)
async def hello_button(interaction: ComponentInteraction[Button]) -> None:
secret_message = interaction.custom_id.split("hello_button_")[1]
await interaction.respond(
f"Hello {interaction.user.name}! The secret message is: ||{secret_message}||", ephemeral=True
)
If you want to understand the api in more detail, you can open the latest readthedocs build and take a look at:
-
discord.Component -
discord.ComponentsSequence -
discord.Bot.component_listener -
discord.Bot.add_component_listener -
discord.Bot.remove_component_listener
You can also take a look at the diff of the CHANGELOG-V3.md file
Additionally, this implements stuff of the new discord Modals API like shown below:
Modals Example (click to open)
from discord import components
SECRET_MODAL = components.Modal(
components.Label(
label="Secret Message",
component=components.TextInput(
style=discord.TextInputStyle.long,
custom_id="v1:secret_input",
placeholder="Enter your secret message here...",
required=True,
),
),
Label(
label="Favourite Color",
component=components.StringSelectMenu(
options=[
components.SelectOption(label="Red", value="red"),
components.SelectOption(label="Green", value="green"),
components.SelectOption(label="Blue", value="blue"),
],
required=False,
custom_id="v1:color_select",
min_values=1,
max_values=1
)
),
components.Label(
label="Your favourite user",
description="Please select your favourite user in this server",
component=components.UserSelectMenu(
custom_id="v1:user_select",
min_values=1,
max_values=4,
required=True,
default_values=[components.DefaultSelectOption(856780995629154305, "user"), components.DefaultSelectOption(707196665668436019, "user")]
)
),
components.TextDisplay(
content="By submitting this form, you acknowledge that the information provided is accurate and truthful to the best of your knowledge."
),
title="Secret Message Input",
custom_id=f"v1:secret_modal",
)
@bot.command(integration_types={components.IntegrationType.guild_install, components.IntegrationType.user_install}, context={components.InteractionContextType.guild, components.InteractionContextType.bot_dm, components.InteractionContextType.private_channel})
async def secret(ctx: discord.ApplicationContext) -> None:
await ctx.send_modal(SECRET_MODAL)
@bot.modal_listener("v1:secret_modal")
async def hello_button(
interaction: discord.ModalInteraction[
components.PartialLabel[components.PartialTextInput], components.PartialLabel[components.PartialStringSelect], components.PartialLabel[components.PartialUserSelect], components.PartialTextDisplay
],
) -> None:
_ = await interaction.respond(
components=[
components.Container(
components.TextDisplay("## Your secret message has been received!"),
components.TextDisplay(f"**Secret Message:** {interaction.components[0].component.value}"),
components.TextDisplay(
f"**Favourite Color:** {interaction.components[1].component.values[0]}"
),
components.TextDisplay(
f"**Favourite Users:** {', '.join([f'<@{option}>' for option in interaction.components[2].component.values])}"
),
)
],
allowed_mentions=AllowedMentions.none(),
)
This also removes multiple long deprecated methods / properties from interactions.py
depends on #44 depends on #62
fixes: #11
This finally works ish:
import discord
import logging
import os
from dotenv import load_dotenv
from discord import Button, ButtonStyle, ActionRow, Container
load_dotenv()
logging.basicConfig(level=logging.DEBUG)
bot = discord.Bot(intents=discord.Intents.default())
def build_container(secret_message: str) -> Container:
return Container(
ActionRow(
Button(
style=ButtonStyle.primary,
label="Click me! I'll tell you a secret",
custom_id=f"v1:hello_button_{secret_message}",
emoji=discord.PartialEmoji(name="🤫"),
)
),
id=3,
)
@bot.command()
async def secret(ctx: discord.ApplicationContext, secret_message: str) -> None:
await ctx.respond(
components=[build_container(secret_message)],
)
@bot.component(lambda i: i.startswith("v1:hello_button_"))
async def hello_button(interaction: discord.Interaction) -> None:
secret_message = interaction.custom_id.split("hello_button_")[1]
await interaction.respond(
f"Hello {interaction.user.name}! The secret message is: ||{secret_message}||", ephemeral=True
)
message = await interaction.channel.fetch_message(interaction.message.id)
print(message.components.get_by_id(3))
bot.run(os.getenv("TOKEN_3"))
Marking as ready x review so the docs build but not ready yet
Documentation build overview
📚 pycord-next | 🛠️ Build #30574587 | 📁 Comparing 4c98a787b4e91bef0b49bbe83cda036874096532 against latest (e4b48e6bb0c441f6eb8fbf64c62881a12afb0864)