Discord Bot Photo Frame

Aug 21, 2025 Update: Aug 21, 2025 983 Words

Alright, so. I found this nifty E Ink display - wow, they come in color now! I want one. So I bought one!

But what to do with it? Naturally, the vast majority of projects for these lil guys mostly center around some kinda of topical display, like a calendar or weather display. The sample project for this board is a comic book panel display that pulls from some online repository - cool, but not particularly interactive.

I want something a little more dynamic. Something with a little give and take.

My wife and I share a private Discord server - it is a wonderful place for us to place reminders, message each other when we are apart, post funny links- OK mostly just post pictures of our dog. Either way, we’ve grown to rather enjoy using Discord for communication and often have quick access to the service, knowing full well that the good times may be limited.

Anyways, I thought Discord might be a fun way to actually interface with the E Ink display. What if you could post an image onto Discord and have it show up on the Display? Would that be possible? What would I need to learn to accomplish such a feat? As it turns out, not too much.

The Pimoroni screen attaches to a Raspberry Pi via the “hat” and is not a true display in the traditional sense. The documentation indicate that the intended way to refresh the screen is through some Python commands… how convenient - the Discord API also has full Python support through Discord.py, and I have awesome 14 year old CS101 Python skills to dust off. Let’s do it.

I followed this tutorial to get started with my “development environment”. Essentially I knew I would be running the Pi Zero 2 w headless, so I would want some sort of SSH + FTP solution, and this tut sets up a super hacker man SSH and Notepad++ deal with NppFTP - a built in FTP client. I opted to just use Powershell for SSH because cmon, you don’t need RealVNC to do this kind of work.

Note: I ended up sticking with this development environment for my next project but likely I should figure out something better. VSCode has been excellent, and probably has FTP + SSH stuff built in, or at least as plugins. Something like IDLE would be useful too.

OK, so time for the MVP on this guy.

  • I want to be able to somehow tag an image and have it display on the E Ink screen.

Welp, that is pretty much it. Looking through the discord.py docs I slowly built up an understanding of the capabilities of the API. Essentially, you create a “bot” that gets invited to a server. The bot listens to whatever it has been given permission to listen to, and can optionally post and do other human stuff if told to. Any events that occur on the server (someone posting, login in ETC) can be sent to our discord.py app, which is running async (a new one for me).

You first need some boilerplate:

import discord
import asyncio

class MyClient(discord.Client):
    async def on_ready(self):
        print(f'Logged in as {self.user} (ID: {self.user.id})')
        print('------')
    #functions go here

intents = discord.Intents.default()
intents.message_content = True
client = MyClient(intents=intents)
client.run('your api key') #actually start the bot

You can make functions that listen for events in the server once joined. This looks something like this:

class MyClient(discord.Client):
    async def on_ready(self):
        print(f'Logged in as {self.user} (ID: {self.user.id})')
    async def on_reaction_add(self, reaction, user):
        message = reaction.message # our embed
        print("Reaction detected")

In this case, whenever someone “reacts” to something (IE posting an emoji under a post) our PY program is alerted. We can do things here!

        if str(reaction.emoji) == "⭐": #checking the emoji - unicode emojis work in Python!
            print("Found Star Reaction")
            for attachment in message.attachments:
                if any(attachment.filename.lower().endswith(image) for image in image_types): #image_types defined earlier, just a list
                    await attachment.save(f'attachments/{attachment.filename}') # 'attachments/{{attachment.filename}' is the PATH to where the attachments/images will be saved. Example: home/you/Desktop/attachments/{{attachment.filename}
                    print(f'Attachment {attachment.filename} has been saved to directory/folder > attachments.')
                    await updateInky(f'attachments/{attachment.filename}') #you have to run everything async with the "await" command to not block the thread

So this listens for the star emoji, then checks the message for attachments. If there are any images, save them to disk, then attempt to update the E Ink display.

But wait! WTF is “await”?

Yeah, since discord.py is async we have to make sure we have NO blocking functions - you know, like the normal way you do anything in Python. As far as I know there are numerous ways to do this but I went ahead and learned as little as I had to to get this to work. Discord.py uses this “asyncio” nonsense but there is also some built in stuff? There is also this “tothread” stuff, but honestly I kinda just kept trying things until it stopped complaining. Ughhhh just work please.

The code for submitting an image to the screen is easy enough, but of course varies from panel to panel. I had to install some basic software to get the appropriate py stuff. The image has to be formatted to the correct resolution and be saved to disk, so I wrote a little function to do all that. This of course needs a few imports (just look at the final source code)

def to_thread(func: typing.Callable) -> typing.Coroutine:
    @functools.wraps(func)
    async def wrapper(*args, **kwargs):
        return await asyncio.to_thread(func, *args, **kwargs)
    return wrapper

@to_thread #threading nonsense
def updateInky(file):
    print("Submitting image to inky")
    gpio.set_value(led, Value.ACTIVE)#LED on when receiving  
    openedImage = Image.open(file)
    im = openedImage.convert('RGB') #sanitizes the image (removes gif frames)
    resizedimage = im.resize(inky.resolution)
    resizedimage = ImageOps.pad(im, inky.resolution, color="#fff")
    try:
        inky.set_image(resizedimage)
    except TypeError:
        inky.set_image(resizedimage)
    inky.show()
    gpio.set_value(led, Value.INACTIVE)#LED off when done

That is… pretty much it! The bot listens for the star emoji, saves the attached image, formats it, then submits it to the screen. Done.