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?
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:
Related
Hello I recently made a help command using discord_components buttons (I know they arent fully supported by Discord.py) but I still went ahead. The problem is that whenever I run the command and receive the Buttons to click on, they alway say "This Interaction Failed". I can't seem to find what's wrong. Please help.
Thanking You,
NightMX.
import discord
from discord.ext import commands
from discord_components.component import ButtonStyle
from discord_components import DiscordComponents, Button, Select, SelectOption
from discord_components.interaction import InteractionType
class BotCommands(commands.Cog):
def __init__(self, client):
self.client = client
#commands.command()
async def helpv2(self, ctx):
funbutton = Button(style=ButtonStyle.grey, label="Fun Commands", id="funcmds")
monkedevbutton = Button(style=ButtonStyle.grey, label="Attachment Commands", id="monkecmds")
# utilitybutton = Button(style = ButtonStyle.grey, label = "3", id = "embed3")
funembed = discord.Embed(title="Fun Commands", colour=discord.Colour.orange())
funembed.add_field(name="k.joke", value="Sends a Random joke from PyJokes")
monkedevembed = discord.Embed(title="Fun Commands", colour=discord.Colour.blurple())
monkedevembed.add_field(name="k.dog", value="Sends a Random Dog Fact")
monkedevembed.add_field(name="k.monkey", value="Sends a Monkey's Picture")
monkedevembed.add_field(name="k.bird", value="Sends a Bird's Picture")
await ctx.send(
"Kola's Beta Help Command!",
components=[[funbutton, monkedevbutton]]
)
buttons = {
"funcmds": funembed,
"monkedcmds": monkedevembed
}
while True:
event = await self.bot.wait_for('button_click')
if event.channel is not ctx.channel:
return
if event.channel == ctx.channel:
response = buttons.get(event.component.id)
if response is None:
await event.channel.send(
"Something went Wrong"
)
if event.channel == ctx.channel:
await event.respond(
type=InteractionType.ChannelMessageWithSource, embed=response
)
def setup(client):
client.add_cog(BotCommands(client))
should be content="something" instead of embed = response
I'm trying to make a little weather forecast program which gives you an image with an overview of the weather in python
To get the weather, I'm using openweathermap and it works ok for me, but for the image I'm using PIL to paste the weather icon, but for some reason there's a part of it that's not being pasted, here you can see what the icon should be: https://openweathermap.org/img/wn/04n#2x.png, and here's how it appeared in the image that came out of my script:
Here's the part of the code that generates the image:
def drawImage(d):
img=PIL.Image.open("base.png")
url=f"https://openweathermap.org/img/wn/{d['icon']}#2x.png"
weatherIcon=Image.open(requests.get(url, stream=True).raw)
print(url)
img.paste(weatherIcon, (00, 10))
now=datetime.now()
name="boards/"+now.strftime('%d_%m_%Y_%H_%M_%S')+".png"
img.save(name)
return name
Some notes on this code:
The base.png is just a 720x720 blank image
The d that gets passed in is a dictionary with all the information, but here it only needs the icon, so I'll give this example: {"icon": "04n"}
I got the URL for the image from the website of OpenWeatherMap, see documentation: https://openweathermap.org/weather-conditions
This is happening because the icon image you download has transparency (an alpha channel). To remove that, you can use this answer.
I've simplified it slightly, define the following function:
def remove_transparency(im, bg_colour=(255, 255, 255)):
if im.mode in ('RGBA', 'LA') or (im.mode == 'P' and 'transparency' in im.info):
alpha = im.getchannel('A')
bg = Image.new("RGBA", im.size, bg_colour + (255,))
bg.paste(im, mask=alpha)
return bg
else:
return im
and call it in your code:
weatherIcon=Image.open(requests.get(url, stream=True).raw)
print(url)
weatherIcon = remove_transparency(weatherIcon)
img.paste(weatherIcon, (00, 10))
You might want to adjust that bg_colour parameter.
This code works with users that have .png format in their profile pictures, however, when it comes to users that have .gif animated profile pictures, the code does not work. It gives this error OSError(f"cannot write mode {mode} as PNG") from e OSError: cannot write mode PA as PNG
I attempted to change all .png to .gif but I still had trouble.
ValueError: image has wrong mode
This is the aforementioned code that only works with .png format.
class avatar(commands.Cog):
def __init__(self, client):
self.client = client
#commands.Cog.listener()
async def on_member_join(self, member):
guild = self.client.get_guild(GUILD_ID)
general_channel = guild.get_channel(CHANNEL_ID)
url = requests.get(member.avatar_url)
avatar = Image.open(BytesIO(url.content))
avatar = avatar.resize((285,285));
bigsize = (avatar.size[0] * 3, avatar.size[1] * 3)
mask = Image.new('L', bigsize, 0)
draw = ImageDraw.Draw(mask)
draw.ellipse((0, 0) + bigsize, fill=255)
mask = mask.resize(avatar.size, Image.ANTIALIAS)
avatar.putalpha(mask)
output = ImageOps.fit(avatar, mask.size, centering=(1420, 298))
output.putalpha(mask)
output.save('avatar.png')
img = Image.open('welcomealpha.png')
img.paste(avatar,(1408,265), avatar)
img.save('wel.png')
file = discord.File('wel.png')
channel = self.client.get_channel(CHANNEL_ID)
await channel.send(file=file)
guild = self.client.get_guild(GUILD_ID)
channel = guild.get_channel(CHANNEL_ID)
Could it be that the bot doesn't know how to discern between .gif & .png ? If that's the case, what would be the most efficient way for the bot to recognize which profile picture format each new user has in order to manipulate image/gif accordingly to its format?
The error message is quite clear here: Your original Image object has mode P, i.e. it's a palettised image. When adding an alpha channel as you did, you get mode PA. As Pillow tells you, saving Image objects with mode PA as PNG is not supported. Since you only want to save to some static PNG without any animation, I assume it's save to convert the Image object to mode RGB right in the beginning, such that you get a RGBA mode Image object in the end, which can be saved as PNG without any problems.
I took the following excerpt from your code and added the conversion to mode RGB:
from PIL import Image, ImageDraw, ImageOps
avatar = Image.open('homer.gif').convert('RGB')
avatar = avatar.resize((285, 285))
bigsize = (avatar.size[0] * 3, avatar.size[1] * 3)
mask = Image.new('L', bigsize, 0)
draw = ImageDraw.Draw(mask)
draw.ellipse((0, 0) + bigsize, fill=255)
mask = mask.resize(avatar.size, Image.ANTIALIAS)
avatar.putalpha(mask)
output = ImageOps.fit(avatar, mask.size, centering=(1420, 298))
output.putalpha(mask)
output.save('avatar.png')
The GIF input is Homer; the corresponding Image object has mode P:
The exported PNG is the following; it seems to be the first frame of the GIF:
----------------------------------------
System information
----------------------------------------
Platform: Windows-10-10.0.16299-SP0
Python: 3.9.1
Pillow: 8.1.0
----------------------------------------
I have a music bot that streams radio and each stream is a command for ease of use. The problem is that there are 100+ stations now and it's getting increasingly more difficult to update the code for each one.
#bot.command(aliases=["VAR1", "VAR2"])
#commands.check(if_channel_private)
async def VAR(ctx):
current_time = datetime.now().strftime("%H:%M")
station = 'VAR3'
if len(embed_history.fields) <= 4:
embed_history.add_field(inline=False, name=f"[{current_time}] Played:", value=f'`{station}`')
elif len(embed_history.fields) > 4:
embed_history.remove_field(0)
embed_history.add_field(inline=False, name=f"[{current_time}] Played:", value=f'`{station}`')
stream = 'URL VAR4'
resume = stream
if len(pause_list) != 0 or len(radio_station) != 0:
pause_list.clear()
radio_station.clear()
voice = get(bot.voice_clients, guild=ctx.guild)
if voice.is_playing():
voice.stop()
try:
voice.play(FFmpegPCMAudio(stream))
print(f'Playing {station}')
except:
print(f"An error occurred while trying to play {station}")
await ctx.send(error_message)
radio_station.append(station)
pause_list.append(resume)
embed_st.clear_fields()
embed_st.add_field(inline=False, name=embed_name,
value="VAR5")
await ctx.send(embed=embed_st)
#VAR.error
async def _pr_error(ctx, error):
if isinstance(error, commands.CheckFailure):
if ctx.message.author.voice is None:
return
await ctx.send(f"Only users in __the same private voice channel__ can change the station!")
else:
await ctx.send(f"```css\n"
"[ERROR: Unexpected error has occured!]"
"```")
print(error)
This is how it looks like for each station. Is it possible to write this only once and then call it for every station? And only to have to change the variables (VAR)? Otherwise, it bloats the whole text file with repetitive code...
I don't check this code, but may be it's work. Create function, and send to it your variables station and stream:
#bot.command(aliases=["VAR", "VAR"])
#commands.check(if_channel_private)
async def VAR(ctx):
await some_function('VAR', 'URL_VAR')
# Your function for all stations
async def some_function(station, stream):
current_time = datetime.now().strftime("%H:%M")
if len(embed_history.fields) <= 4:
embed_history.add_field(inline=False, name=f"[{current_time}] Played:", value=f'`{station}`')
elif len(embed_history.fields) > 4:
embed_history.remove_field(0)
embed_history.add_field(inline=False, name=f"[{current_time}] Played:", value=f'`{station}`')
resume = stream
if len(pause_list) != 0 or len(radio_station) != 0:
pause_list.clear()
radio_station.clear()
voice = get(bot.voice_clients, guild=ctx.guild)
if voice.is_playing():
voice.stop()
try:
voice.play(FFmpegPCMAudio(stream))
print(f'Playing {station}')
except:
print(f"An error occurred while trying to play {station}")
await ctx.send(error_message)
radio_station.append(station)
pause_list.append(resume)
embed_st.clear_fields()
embed_st.add_field(inline=False, name=embed_name,
value="VAR")
await ctx.send(embed=embed_st)
Not sure if its the lack of coffee, but I'm having a slight issue with uploading random images.
#client.command(aliases=['cuddle'])
async def _cuddle(ctx, *, user):
image= [
'file_1.gif',
'file_2.gif',
'file_3.gif',
'file_4.gif',
'file_5.gif',
'file_6.gif',
'file_7.gif']
await ctx.send(f'You got a Cuddle from{ctx.message.author.mention}, {user}!\n {random.choice(file =discord.file(image))}')
its mostly the
{random.choice(file =discord.file(image))}
ive tryed
discord.file'file_1.gif', method but with no prevail.
I've solved it for anyone who wants to know
import os
#commands.command(aliases=['image'])
async def images(self, ctx):
image = os.listdir('./cogs/image/')
imgString = random.choice(image) # Selects a random element from the list
path = "./cogs/image/" + imgString
await ctx.send(file=discord.File(path))