-1

What I'm trying to do: I have an on_message event for a 'global chat' command that I have created. This would send messages to any server that was in the json, currently supporting images, multiple lines of text, and custom emojis (as long as the bot shared a server with said emoji).

The on_message event

My problem: As you can see in the image above, one person, 'S o u p', has their profile picture replaced with a duck emoji. For context's sake, their avatar is not that of a duck emoji. However, if you were to look at the most recent message, the profile picture is shown. I believe that the problem lies in the size of the message.author's avatar. The PIL library may be a good solution, but I do not want to save the image.

Code I have tried:

avatar_bytes = await message.author.avatar_url.read()
guildem = client.get_guild(738285544911405107)
    try:
        em = await guildem.create_custom_emoji(name=f'{message.author.name}', image=avatar_bytes)
    except:
        em = ''

The code above is the one I had used in the image example I had provided.

avatar = message.author.avatar_url_as(size=128) # resize avatar
avatar_bytes = await avatar.read() # then read as bytes?

Above code gives exact same results. Avatars are turned into duck emojis.

Others I have looked at:

  1. How to crop an image to a shape for eg. circle using Pillow in Discord.py?: Messages did not send when using the code here. I assumed that using the PIL library would help reduce the file size significantly one way or the other without saving the image.
  2. How to reduce the image file size using PIL: All the answers required saving the image, which I do not want to do. I tried tweaking the code a little bit, but caused all avatars to be turned into duck emojis.

If necessary, I will include the entire on_message event. However, this does not feel relevant nor necessary to include since the emojis are only a small portion of said event.

Edit: Here is the code to the entire on_message event.

@client.event
async def on_message(message):
    if message.author == client.user: # stop bot from replying to self
        return
    else:
        if not message.guild: # prevent errors when messaging in bot dm's
            return
        f = open("global text.json") # existing json
"""
{
    "747061937673732097": 765059798131539988,
    "724783642546536458": 818707151122989119,
    "761524419003809832": 813963773295591485,
    "755309786232258630": 760381389685784587,
    "738285544911405107": 738285544911405110
}
"""
        data = json.load(f)
        for i in data:
            if str(i) == str(message.guild.id):
                if data[str(message.guild.id)] == message.channel.id:
                    avatar_bytes = await message.author.avatar_url.read()

                    guildem = client.get_guild(738285544911405107) # guild to upload emojis to
                    try:
                        em = await guildem.create_custom_emoji(name=f'{message.author.name}', image=avatar_bytes) # create emoji using avatar_bytes in guildem server
                    except:
                        em = ''

                    ### functions ###
                    def censor(text): # censor words for safety
                        banned = ["test"] # bad words were here, removed for stackoverflow reasons

                        text = text.split()

                        nearly = []
                        for word in text:
                            if any(item in word.lower() for item in banned):
                                word = word.lower()
                                replaced = word.replace(word, ('#'*(len(word))))
                                nearly.append(replaced)
                            else:
                                nearly.append(word)

                        message = ' '.join(nearly)

                        return message

                    def parag(string): # check for \n
                        length = [string]
                        if string in ["", " ", "  ", "   "]:
                            return "<:transparent:774136812813811713>"
                        final_list = []
                        if '\n' in string:
                            length = string.split('\n')
                        for item in length:
                            thing = censor(item)
                            final_list.append(thing)
                        return paragger(final_list)

                    def paragger(list: list): # returns original message with \n
                        message = ""
                        for item in list:
                            message += f"> {item}\n"
                        return message

                    msg = parag(message.content)

                    ### attachment handling ###
                    embed_list = []
                    if message.attachments:
                        if len(message.attachments) == 1:
                            embed = discord.Embed(title=f"{message.author}'s file", color=0xc39ce6)
                            for file in message.attachments:
                                embed.set_image(url=file.url)
                            embed_list.append(embed)
                        else:
                            count = 1
                            for file in message.attachments:
                                embed = discord.Embed(title=f"{message.author}'s file {count}")
                                embed.set_image(url=file.url)
                                embed_list.append(embed)
                    
                    for i in data:
                        if str(i) != str(message.guild.id):
                            channel = client.get_channel(data[str(i)])
                            try:
                                await channel.send(f"{em} {message.author} \n{msg}", allowed_mentions=allowed_mentions)
                                if len(embed_list) > 0:
                                    for embed in embed_list:
                                        await channel.send(embed=embed)
                            except:
                                await channel.send(f"{em} {message.author} \n> {em}", allowed_mentions=allowed_mentions) # only happens if message is larger than 2000 char
                    if em != '':
                        await em.delete() # delete the custom emoji to make space for more emojis later on
                    break
    
    await client.process_commands(message) # process, prevent commands breaking
Bagle
  • 2,326
  • 3
  • 12
  • 36
  • Can you share your code? – Łukasz Kwieciński Apr 04 '21 at 10:09
  • The `on_message` event code has been edited in, including comments for reading sake – Bagle Apr 04 '21 at 10:37
  • You want to create a cutsom emoji from a profile picture, is that right? Do you know that you wan create a webhook, and then send a message with the user name and the user profile picture like [this](https://i.imgur.com/xPQhooP.png)? – Baptiste Apr 06 '21 at 13:41
  • I know I can do this, but I would still want to use the emoji way. I think my question is mostly 'how do I make an image small enough to be an emoji without saving it' now that I think about it. – Bagle Apr 07 '21 at 00:42
  • Perhaps try `avatar_bytes = io.BytesIO(await avatar.read())` instead of the line you have. (be sure to `import io`) – Discrete Games Apr 07 '21 at 18:12
  • This brings up an attribute error, `AttributeError: 'str' object has no attribute 'read'`, the line of error being what you wrote (except `avatar` was replaced with `message.author.avatar` – Bagle Apr 08 '21 at 00:11

2 Answers2

2

Your issue is here: em = await guildem.create_custom_emoji(name=f'{message.author.name}', image=avatar_bytes)

You see, discord.py is looking for an emoji name with alphanumeric characters or underscores. S o a p does not fit this requirement, hence the error you have gotten. The following fixes the issue by replacing said characters with an underscore.

avatar_bytes = await message.author.avatar_url_as(size=128).read() # size=128 makes the size smaller for emojis
name = message.author.name.replace('-', '_').replace(' ', '_') # Replace the erroring characters
try:
    em = await guildem.create_custom_emoji(name=f'{name}', image=avatar_bytes)
except:
    em = ''

EDIT: After taking another look, the problem wasn't what I thought after all, the issue with the user named S o a p was that it had non alphanumeric characters, I've updated my answer to fix this.

zurgeg
  • 510
  • 6
  • 18
  • After implementing your code, there are still some avatars that will end up being returned as ''. I used the bot Carl-bot to test this since this bot's avatar wasn't able to show up in the first place. – Bagle Apr 09 '21 at 04:12
  • @Bagle Try doing `except Exception as e` and then doing `print('Error: {e}'.format(e=e))` which should show what exception is given. Additionally, you might need to compress the image if the users' PFP is too large. – zurgeg Apr 09 '21 at 14:41
  • I've improved my answer with an edit by using the url_as function, since this will size it down for emoji purposes. – zurgeg Apr 09 '21 at 14:50
  • Using your new code, `url_as` function, the previously named bot Carl-bot's avatar is still not shown. The Exception is as follows: `400 Bad Request (error code: 50035): Invalid Form Body In name: String value did not match validation regex.` || Tried again, replacing `image=avatar_bytes` with `avatar`, error: `'_io.BytesIO' object has no attribute 'startswith'` – Bagle Apr 10 '21 at 00:19
  • 1
    From the upload emoji page: “Must be alphanumeric or contains underscores”. Carl-bot’s name violates this rule. Try doing “name = message.author.name.replace(“-“,”_”)” then use name instead of message.author.name – zurgeg Apr 10 '21 at 00:25
  • @Bagle I've updated my answer to fix the original problem. The BytesIO stuff isn't needed anymore. – zurgeg Apr 10 '21 at 02:10
-1

You don't need to save image. This is how you send images to discord without saving it:

import io
import PIL

image = PIL.Image.new("RGB", (200, 200), (255, 255, 255))
bytes_io = io.BytesIO()
image.save(bytes_io, format="png")
bytes_io.seek(0)
file = discord.File(bytes_io, "white.png")
#now you can send this file to discord
await ctx.send(file=file)
Ali Hakan Kurt
  • 1,039
  • 5
  • 13
  • But isn't this saving the image? Anyway, I'm converting the message author's profile picture into an emoji, so it would be originally in bytes rather than png as far as I can tell. – Bagle Apr 09 '21 at 04:19