discord.py icon indicating copy to clipboard operation
discord.py copied to clipboard

Components V2

Open DA-344 opened this issue 1 year ago • 21 comments

Summary

Adds support for discord's components v2.

Testing and bug reporting is very much appreciated!

DDevs PR: https://github.com/discord/discord-api-docs/pull/7487

  1. Adds the following Components -> UI Version:
  • SectionComponent -> ui.Section
  • TextDisplay -> ui.TextDisplay
  • ThumbnailComponent -> ui.Thumbnail
  • MediaGalleryComponent -> ui.MediaGallery
  • FileComponent -> ui.File
  • SeparatorComponent -> ui.Separator
  • Container -> ui.Container
  • ActionRow -> ui.ActionRow
  1. Adds a new ui view like class, ui.LayoutView, that allows for these components to exist.
  2. Adds the id attribute to all Components and UI Components. -> This also adds the get_item method to ui.View, ui.LayoutView, ui.Container, and ui.Section.

You can install this branch for testing using py -m pip install -U git+https://github.com/DA-344/d.py@feat/components-v2

These changes have been tested with the following snippets **(outdated)**:

All of these use the jishaku package

Sending Components v2

Code:

class TestButton(discord.ui.Button):
    async def callback(self, interaction):
        await interaction.response.send_message('t')

class TestContainer(discord.ui.Container):
    text1 = discord.ui.TextDisplay("Hello world")
    text2 = discord.ui.TextDisplay("Row 3", row=2)

    section = discord.ui.Section(
        accessory=TestButton(
            label="Section Button",
        )
    ).add_item(discord.ui.TextDisplay("Text in a section"))


class TestView(discord.ui.LayoutView):
    container = TestContainer(id=1)

await _ctx.send(view=TestView())

Result (as expected): cv2 ex

Sending Components v2 items on webhooks

1st snippet: sends interactible items on app owned and not app owned webhooks

wh = await _channel.create_webhook(name='sumth')  # app owned

class TestButton(discord.ui.Button):
    async def callback(self, interaction):
        await interaction.response.send_message('t')

class TestContainer(discord.ui.Container):
    text1 = discord.ui.TextDisplay("Hello world")
    text2 = discord.ui.TextDisplay("Row 3", row=2)

    section = discord.ui.Section(
        accessory=TestButton(
            label="Section Button",
        )
    ).add_item(discord.ui.TextDisplay("Text in a section"))


class TestView(discord.ui.LayoutView):
    container = TestContainer(id=1)

v = TestView()

await wh.send(view=v)

wh2 = discord.Webhook.from_url('https://discord.com/api/webhooks/1362896342581641286/TOKEN', client=_bot)  # not app owned

await wh2.send(view=TestView())

Result (as expected): owner wh cv2

2nd snippet: sends non-interactible items on not app owned webhook

class TestButton(discord.ui.Button):
    async def callback(self, interaction):
        await interaction.response.send_message('t')

class TestContainer(discord.ui.Container):
    text1 = discord.ui.TextDisplay("Hello world")
    text2 = discord.ui.TextDisplay("Row 3", row=2)

    section = discord.ui.Section(
        accessory=TestButton(
            url="https://google.com",
            label='gugol',
        )
    ).add_item(discord.ui.TextDisplay("Text in a section"))


class TestView(discord.ui.LayoutView):
    container = TestContainer(id=1)

wh2 = discord.Webhook.from_url('https://discord.com/api/webhooks/1362896342581641286/TOKEN', client=_bot)

await wh2.send(view=TestView())

Result (as expected): not owner wh cv2

Receiving other message's components v2

Code:

yield _message.reference.resolved.components

Result (as expected): receiving cv2

Checklist

  • [x] If code changes were made then they have been tested.
    • [x] I have updated the documentation to reflect the changes.
  • [ ] This PR fixes an issue.
  • [x] This PR adds something new (e.g. new method or parameters).
  • [ ] This PR is a breaking change (e.g. methods or parameters removed/renamed)
  • [ ] This PR is not a code change (e.g. documentation, README, ...)

DA-344 avatar Apr 18 '25 22:04 DA-344

If an action row is added via add_item, view doesn't get set on buttons and they won't work:

class TestButton(discord.ui.Button):
    async def callback(self, interaction: Interaction):
        await interaction.response.send_message("Test button clicked!")


class TestActionRow(discord.ui.ActionRow):
    def __init__(self):
        super().__init__()
        self.add_item(TestButton(label="test2"))

    @discord.ui.button(label="test1")
    async def test_button(self, interaction: Interaction, button: discord.ui.Button):
        await interaction.response.send_message("Test button clicked!")


class TestLayout(discord.ui.LayoutView):
    def __init__(self):
        super().__init__()
        # test1 and test2 don't work
        self.add_item(TestActionRow())
        # test3 works
        self.add_item(discord.ui.Section(accessory=TestButton(label="test3")).add_item("text"))

qwewqa avatar Apr 19 '25 18:04 qwewqa

If an action row is added via add_item, view doesn't get set on buttons and they won't work

Thanks for reporting it, this should be fixed in the latest commit.

DA-344 avatar Apr 19 '25 20:04 DA-344

With these changes is there a straightforward way to send the simplest form of a components v2 message, aka just plaintext content?

The reason for wanting this is because the character limit would be 4000 instead of the old 2000.

It would be nice if the character limit of the existing "content" field was increased to 4000; and just make it use components v2 under the hood if it has to.

jakobdylanc avatar Apr 25 '25 14:04 jakobdylanc

You can make your own LayoutView subclass and automatically assign it a content with a TextDisplay, as making content be a shortcut for TextDisplay would make it not be consistent with the API.

DA-344 avatar Apr 25 '25 14:04 DA-344

I have a problem : I cannot get all components from a message image image I want to get container and text display components

theProgramer-J avatar Apr 25 '25 16:04 theProgramer-J

I have a problem : I cannot get all components from a message image image I want to get container and text display components

This PR hasn't even been merged yet; how did you already get the docs lol? Anyway, with this PR, Message.components does return all the new components too. Have you tried it?

Soheab avatar Apr 25 '25 16:04 Soheab

Yes I tried and I get [ ]

theProgramer-J avatar Apr 25 '25 16:04 theProgramer-J

Yes I tried and I get [ ]

Code?

Also, consider joining the support server and reporting this in the dedicated discussion thread.

Soheab avatar Apr 25 '25 18:04 Soheab

I had forgotten to py -m pip install -U git+https://github.com/DA-344/d.py@feat/components-v2 lol

theProgramer-J avatar Apr 26 '25 06:04 theProgramer-J

I don't know what version of the PR you are using, but Containers do not longer inherit from BaseView, and are just normal Items. I've pushed some fixes to is_persistent on v2 items.

DA-344 avatar May 08 '25 17:05 DA-344

Hey, i noticed an error, when you are sending a view with webhook (with client)

and then, editing it, no edit are pushed, and no error are raised

 webhook = Webhook.from_url(WEBHOOK, client=Bot)  


 message = await webhook.send(view=fview,wait=True)


 await asyncio.sleep(5)


 new_view = NewsView()

 await webhook.edit_message(message_id=message.id,view=new_view)

Atlas-timeless avatar May 12 '25 00:05 Atlas-timeless

Hey, i noticed an error, when you are sending a view with webhook (with client)

and then, editing it, no edit are pushed, and no error are raised

 webhook = Webhook.from_url(WEBHOOK, client=Bot)  


 message = await webhook.send(view=fview,wait=True)


 await asyncio.sleep(5)


 new_view = NewsView()

 await webhook.edit_message(message_id=message.id,view=new_view)

Would be useful to show what the view looks like.

martinbndr avatar May 12 '25 13:05 martinbndr

Hey, i noticed an error, when you are sending a view with webhook (with client) and then, editing it, no edit are pushed, and no error are raised

 webhook = Webhook.from_url(WEBHOOK, client=Bot)  


 message = await webhook.send(view=fview,wait=True)


 await asyncio.sleep(5)


 new_view = NewsView()

 await webhook.edit_message(message_id=message.id,view=new_view)

Would be useful to show what the view looks like.

Any view, a simple view with a MediaGallery arent editable

class first(Container): def init(self, id): super().init(id=id) self.add_item(MediaGallery(row=2).add_item(item=MediaGalleryItem(media=MEDIA_URL),))

    self.add_item(TextDisplay("We're bringing new components to messages that you can use in your apps."))
    
    

Atlas-timeless avatar May 12 '25 13:05 Atlas-timeless

If no errors are logged and the request was sent successfully, this means it is a discord side issue. Try enabling d.py debug mode, and show us the whole view you have, both the NewsView class and the fview one. If you try sending a v2 view and then editing it with a non-v2, it is not possible.

DA-344 avatar May 12 '25 13:05 DA-344

If no errors are logged and the request was sent successfully, this means it is a discord side issue. Try enabling d.py debug mode, and show us the whole view you have, both the NewsView class and the fview one. If you try sending a v2 view and then editing it with a non-v2, it is not possible.

Here is an example that not working,

from discord.ui import LayoutView, ActionRow, Separator, Section, Container, TextDisplay, Thumbnail, MediaGallery, Button, Item, File, Select,Modal
from discord.components import MediaGalleryItem 
from discord import ui


class first(Container):
    def __init__(self, id):
        super().__init__(id=id)
        self.add_item(TextDisplay("FIRST LAYOUT"))

class FIRST_LAYOUT(LayoutView):
    def __init__(self):
        super().__init__()
        self.add_item(first(id=1))

class second(Container):
    def __init__(self, id):
        super().__init__(id=id)
        self.add_item(TextDisplay("SECOND LAYOUT"))
        
class SECOND_LAYOUT(LayoutView):
    def __init__(self):
        super().__init__()
        self.add_item(second(id=2))


@app_commands.command(name="post_components")
@app_commands.checks.has_permissions(administrator=True)
async def post_components(self,interaction: discord.Interaction):
    await interaction.response.defer(ephemeral=True)
    session = aiohttp.ClientSession()
    try:

        first_view = FIRST_LAYOUT()
        webhook = Webhook.from_url(COMPONENT_WEBHOOK, client=Bot)  
        print(1)
        message = await webhook.send(view=first_view,wait=True)

        second_view = SECOND_LAYOUT()
        await asyncio.sleep(2)
        await message.edit(view=second_view)
        print(2)

        if session:
            await session.close()

    except Exception as e:
        Log.print(e,"ERROR")
        if session:
            await session.close()

Atlas-timeless avatar May 12 '25 13:05 Atlas-timeless

keep on getting Invalid Form Body In components.0.components.1.accessory: Value of field "type" must be one of (2, 11). when i try to send a image

Seamorethemaster avatar May 12 '25 21:05 Seamorethemaster

keep on getting Invalid Form Body In components.0.components.1.accessory: Value of field "type" must be one of (2, 11). when i try to send a image

A section's accessory can only be an instance of discord.ui.Thumbnail() or discord.ui.Button().

Soheab avatar May 12 '25 21:05 Soheab

I noticed that your PR claims to include support for the new component v2 system.

However, I couldn’t find any implementation or example in the code that demonstrates usage of the channel select UI element.

Could you confirm whether your PR currently includes support for this, and if so, where it’s implemented? If not, do you plan to add it?

acqxi avatar May 19 '25 16:05 acqxi

channel select is not a new thing, they exist pre components v2

thqnhz avatar May 19 '25 16:05 thqnhz

If no errors are logged and the request was sent successfully, this means it is a discord side issue. Try enabling d.py debug mode, and show us the whole view you have, both the NewsView class and the fview one. If you try sending a v2 view and then editing it with a non-v2, it is not possible.

Here is an example that not working,

from discord.ui import LayoutView, ActionRow, Separator, Section, Container, TextDisplay, Thumbnail, MediaGallery, Button, Item, File, Select,Modal
from discord.components import MediaGalleryItem 
from discord import ui


class first(Container):
    def __init__(self, id):
        super().__init__(id=id)
        self.add_item(TextDisplay("FIRST LAYOUT"))

class FIRST_LAYOUT(LayoutView):
    def __init__(self):
        super().__init__()
        self.add_item(first(id=1))

class second(Container):
    def __init__(self, id):
        super().__init__(id=id)
        self.add_item(TextDisplay("SECOND LAYOUT"))
        
class SECOND_LAYOUT(LayoutView):
    def __init__(self):
        super().__init__()
        self.add_item(second(id=2))


@app_commands.command(name="post_components")
@app_commands.checks.has_permissions(administrator=True)
async def post_components(self,interaction: discord.Interaction):
    await interaction.response.defer(ephemeral=True)
    session = aiohttp.ClientSession()
    try:

        first_view = FIRST_LAYOUT()
        webhook = Webhook.from_url(COMPONENT_WEBHOOK, client=Bot)  
        print(1)
        message = await webhook.send(view=first_view,wait=True)

        second_view = SECOND_LAYOUT()
        await asyncio.sleep(2)
        await message.edit(view=second_view)
        print(2)

        if session:
            await session.close()

    except Exception as e:
        Log.print(e,"ERROR")
        if session:
            await session.close()

Can't edit a component V2 with Webhook where it should be able to ? any news about this issue ?

Atlas-timeless avatar May 22 '25 09:05 Atlas-timeless

If discord.py sends the request successfully, may be a Discord side issue, as I mentioned. And, also as I mentioned, enable the debug logs to see the whether the request went through, with what payload, and the response to that request.

DA-344 avatar May 26 '25 06:05 DA-344

when's this shi getting pushed 😭💔 we need cv2

playfairs avatar Jul 26 '25 10:07 playfairs

omg no conflicts?? does this mean we get cv2 :o

playfairs avatar Jul 29 '25 03:07 playfairs

I've started testing, just to grasp the fundamentals. How would you go about embeding the LayoutView in a discord.embeds.Embed() or has this not yet been implemented? I know it can be done in discord.js I just don't see how it would be done in discord.py?

I was thinking maybe using the example below, and will test with this but am unsure.

class foo(Embed, LayoutView):

ConnerAdamsMaine avatar Aug 16 '25 01:08 ConnerAdamsMaine

@ConnerAdamsMaine Embeds and container v2 are two different things. The closest you have to an "embed" is Container which lets you set an accent colour. There is no way that discord.js supports whatever you're suggesting because they are fundamentally different things at an API layer.

Rapptz avatar Aug 16 '25 05:08 Rapptz

@Rapptz it's not an embed per-say, it was just the best way I could think of it. I realize now that it was an idiotic idea, I do apologize on that part. It's hard thinking with 48 hours of sleep deprivation. I have checked and it is the Container class that I was looking for!

ConnerAdamsMaine avatar Aug 16 '25 07:08 ConnerAdamsMaine