How to edit images in embeds (discord.py)

Question:

I have a command that generates a random color palette and it works pretty well. Then I wanted to add a button to it, a button that’d generate a new palette. I added the button, wrote a callback, but the callback just won’t work, because the interaction.response.edit_messsage() shows an error:

TypeError: edit_message() got an unexpected keyword argument 'file'

I know what that means, I cannot have a file=file line there, but if I don’t no image is sent in the embed… Embeds just need that to work properly (assuming that you’re generating an image from scratch, it’s simpler if you provide a link).

I have no clue what I could do to get the desired functionality now. Previously I’d create images, send them in a secret channel, get those images’ links and use them in the embed. It worked then, but it was painfully slow.

Here’s the code that I currently have:

@bot.tree.command(name="palette", description="Generates a random color palette")
async def palette(interaction: discord.Interaction):
    async def get_color_palette():
        try:
            response = requests.post("http://colormind.io/api/", json={"model": "default"}).json()
            colors = response["result"]
            colors = [tuple(x) for x in colors]
        except requests.exceptions.RequestException as e:
            return None, f"An error occurred while getting the color palette: {e}"

        return colors, None

    async def create_image(colors):
        # create an image with a black background
        wide = 300
        tall = int(wide / 5)
        image = Image.new("RGB", (wide, tall), (0, 0, 0))
        draw = ImageDraw.Draw(image)

        # draw squares with those colors
        x, y = 0, 0
        width, height = wide / 5, tall
        for color in colors:
            draw.rectangle((x, y, x + width, y + height), fill=color)
            x += width

        # save the image
        image_data = BytesIO()
        image.save(image_data, "PNG")
        image_data.seek(0)
        return image_data

    colors, error = await get_color_palette()

    if error:
        return await interaction.response.send_message(embed=discord.Embed(description=error))

    image = await create_image(colors)
    file = discord.File(image, "color_palette.png")
    embed = discord.Embed()
    embed.set_author(
        name="Here's your random color palette:",
        icon_url="https://media.discordapp.net/attachments/1060711805028155453/1061825040716402731/logo_beter.png")
    embed.set_image(
        url="attachment://color_palette.png")
    embed.set_footer(
        text="Generated with colormind.io")

    button = discord.ui.Button(label="Generate again", style=discord.ButtonStyle.gray)
    view = View()
    view.add_item(button)

    async def button_callback(interaction):
        colors, error = await get_color_palette()

        if error:
            return await interaction.response.send_message(embed=discord.Embed(description=error))

        image = await create_image(colors)
        file = discord.File(image, "color_palette.png")
        embed = discord.Embed()
        embed.set_author(
            name="Here's your random color palette:",
            icon_url="https://media.discordapp.net/attachments/1060711805028155453/1061825040716402731/logo_beter.png")
        embed.set_image(
            url="attachment://color_palette.png")
        embed.set_footer(
            text="Generated with colormind.io")

        await interaction.response.edit_message(file=file, embed=embed, view=view)

    button.callback = button_callback

    await interaction.response.send_message(file=file, embed=embed, view=view)

How can I achieve that? Any tips for the future?

Asked By: Imaginary

||

Answers:

That first comment is not true; you can attach the image you wanted to edit with the attachments argument of the edit_message method.

For example, this simple command sends an embed with an image named img1.png and has a button that, if you click it, will edit the embed and set the new image to img2.png.

@bot.command()
async def send(ctx: commands.Context):
    simple_view = ui.View()
    simple_button = ui.Button(label="Change Image")
    async def simple_callback(button_inter: Interaction):
        new_file = File("img2.png")
        new_embed = Embed(title="Button clicked")

        new_embed.set_image(url="attachment://img2.png") # set the embed's image to `img2.png`

        await button_inter.response.edit_message(embed=new_embed, attachments=[new_file]) # attach the new image file with the embed

    simple_button.callback = simple_callback
    simple_view.add_item(simple_button)

    file = File("img1.png")
    embed = Embed()

    embed.set_image(url="attachment://img1.png")

    await ctx.send(embed=embed, file=file, view=simple_view)

And here’s the example response:

Example

Answered By: Catgal