pycord-v3 icon indicating copy to clipboard operation
pycord-v3 copied to clipboard

feat: Viewless components

Open Paillat-dev opened this issue 8 months ago • 3 comments

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.pages entirely 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

Paillat-dev avatar Aug 03 '25 12:08 Paillat-dev

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

Paillat-dev avatar Aug 17 '25 23:08 Paillat-dev

Marking as ready x review so the docs build but not ready yet

Paillat-dev avatar Aug 18 '25 10:08 Paillat-dev

Documentation build overview

📚 pycord-next | 🛠️ Build #30574587 | 📁 Comparing 4c98a787b4e91bef0b49bbe83cda036874096532 against latest (e4b48e6bb0c441f6eb8fbf64c62881a12afb0864)


🔍 Preview build

Show files changed (68 files in total): 📝 28 modified | ➕ 24 added | ➖ 16 deleted
File Status
genindex.html 📝 modified
index.html 📝 modified
migrating_to_v2.html 📝 modified
_modules/index.html 📝 modified
api/abcs.html 📝 modified
api/application_commands.html 📝 modified
api/clients.html 📝 modified
api/data_classes.html 📝 modified
api/enums.html 📝 modified
api/events.html 📝 modified
api/gears.html 📝 modified
api/models.html 📝 modified
api/ui_kit.html 📝 modified
api/webhooks.html 📝 modified
_modules/discord/abc.html 📝 modified
_modules/discord/bot.html 📝 modified
_modules/discord/client.html 📝 modified
_modules/discord/components.html ➖ deleted
_modules/discord/enums.html 📝 modified
_modules/discord/guild.html 📝 modified
_modules/discord/interactions.html 📝 modified
_modules/discord/message.html 📝 modified
ext/commands/api.html 📝 modified
ext/pages/index.html ➖ deleted
_modules/discord/components/action_row.html ➕ added
_modules/discord/components/button.html ➕ added
_modules/discord/components/channel_select_menu.html ➕ added
_modules/discord/components/component.html ➕ added
_modules/discord/components/components_holder.html ➕ added
_modules/discord/components/container.html ➕ added
_modules/discord/components/default_select_option.html ➕ added
_modules/discord/components/file_component.html ➕ added
_modules/discord/components/label.html ➕ added
_modules/discord/components/media_gallery.html ➕ added
_modules/discord/components/media_gallery_item.html ➕ added
_modules/discord/components/mentionable_select_menu.html ➕ added
_modules/discord/components/modal.html ➕ added
_modules/discord/components/partial_components.html ➕ added
_modules/discord/components/role_select_menu.html ➕ added
_modules/discord/components/section.html ➕ added
_modules/discord/components/separator.html ➕ added
_modules/discord/components/string_select_menu.html ➕ added
_modules/discord/components/text_display.html ➕ added
_modules/discord/components/text_input.html ➕ added
_modules/discord/components/thumbnail.html ➕ added
_modules/discord/components/unfurled_media_item.html ➕ added
_modules/discord/components/unknown_component.html ➕ added
_modules/discord/components/user_select_menu.html ➕ added
_modules/discord/events/channel.html 📝 modified
_modules/discord/events/gateway.html 📝 modified
_modules/discord/events/interaction.html 📝 modified
_modules/discord/gears/gear.html 📝 modified
_modules/discord/ui/button.html ➖ deleted
_modules/discord/ui/container.html ➖ deleted
_modules/discord/ui/file.html ➖ deleted
_modules/discord/ui/input_text.html ➖ deleted
_modules/discord/ui/item.html ➖ deleted
_modules/discord/ui/media_gallery.html ➖ deleted
_modules/discord/ui/modal.html ➖ deleted
_modules/discord/ui/section.html ➖ deleted
_modules/discord/ui/select.html ➖ deleted
_modules/discord/ui/separator.html ➖ deleted
_modules/discord/ui/text_display.html ➖ deleted
_modules/discord/ui/thumbnail.html ➖ deleted
_modules/discord/ui/view.html ➖ deleted
modules/discord/webhook/async.html 📝 modified
_modules/discord/ext/bridge/core.html 📝 modified
_modules/discord/ext/pages/pagination.html ➖ deleted