DEV Community

Cover image for Building a Generative Image Bot with OpenAI, Telegram, FastAPI and Deta Space
Max for Deta

Posted on • Updated on

Building a Generative Image Bot with OpenAI, Telegram, FastAPI and Deta Space

You may have seen some of the amazing images generated by new AI services, like Midjourney and Open AI's DALL·E 2.

Images

Want to get in on the action? In this step-by-step guide, we'll build a personal Telegram bot that will let you use DALL·E 2 from your phone.

When we're done, you'll have a fully functional bot capable of creating unique images from your own input. Your server will run and your images will be stored in your own personal cloud on Deta Space, where you can easily access and use them in other projects.

Let's get going.

Prerequisites

Before starting, out you will need the following, at a minimum:

Optional:

  • Additionally, you’ll want Python on your local machine, if you want to develop locally

Architecture

Architecturally, the bot is pretty simple:

  • Interface: A personal Telegram bot will serve as your primary user interface, where you’ll send prompts and receive images.
  • Generative Image Service: DALL·E 2 (OpenAI’s service for turning language into images) will turn the prompts into images.
  • Server + State: In between, you’ll run a FastAPI server on Deta Space to bridge Telegram and DALL·E 2. This server will save every image in a personal file store on Deta Drive.

Step 1: Creating Your Bot and Getting the Keys

The first step is to create the bot in Telegram and get the required keys from both Telegram and Open AI.

Telegram Bot Token

Search for BotFather in Telegram and start a conversation with it, by clicking the START button.

Type /newbot, and follow the prompts to set up a new bot with a name and username for your bot. BotFather will give you the bot token that you will use to authenticate your bot. Save this token somewhere safe; you will need it later.

⚠️ Keep your token secure, it can be used anyone to control your bot.

Open AI API Key

Next, create an OpenAI account and get an OpenAI API key. This key will let your assistant generate an image with DALL·E 2.

Step 2: Writing Your Bot’s Code

The server we'll write is fairly simple and will do two core things:

  • Use incoming messages from Telegram to generate images with DALL·E 2.
  • Save the generated images to your personal cloud and send them back to Telegram.

The complete code is in this repository, and the core logic lives in the main.py file. The external Python libraries required are listed in the requirements.txt file.

Setup a Webhook with Telegram

Before Telegram can send our server a webhook on every message, it needs to know about our server. The /set_webhook route, when triggered, will configure the connection with Telegram. Once configured, Telegram will send events to our server's /open route.

The code assumes we’ve set the Bot Key as an environment variable in Deta Space (TELEGRAM). It also assumes it's running on Deta Space, as it grabs the server url from the Space environment. But we’ll deploy the code to Deta Space and set the environment variables later.

Let’s move onto the main logic of our bot.

Process Messages from Telegram

The first step for the core logic of the bot is to process an incoming message from Telegram as a prompt for the DALL·E 2 API. The /open route in our server will handle this.

One thing to be aware of is that anyone can message our bot. But we don’t want them to be able to generate and receive images. So we’ll create a simple system to control who has access by checking the chat_id Telegram sends. Later on, we'll create an admin page that will let us add chat_ids to an authorized list.

Here's the relevant code that parses the incoming request and checks the chat_id against a list of authorized chat_ids:

from deta import Base, Drive
# ... other code
CONFIG = Base("config")
# ... other code

@app.post("/open")
async def http_handler(request: Request):
    incoming_data = await request.json()

    if "message" not in incoming_data:
        print(incoming_data)
        return send_error(None, "Unknown error, lol, handling coming soon")

    prompt = incoming_data["message"]["text"]
    chat_id = incoming_data["message"]["chat"]["id"]

    # ... other functionality
    authorized_chat_ids = CONFIG.get("chat_ids")

    # ... other functionality
    if authorized_chat_ids is None or chat_id not in authorized_chat_ids.get("value"): 
        payload = {"text": "You're not authorized!", "chat_id": chat_id}
        message_url = f"{BOT_URL}/sendMessage"
        requests.post(message_url, json=payload).json()
        return
Enter fullscreen mode Exit fullscreen mode

One note: we assume that we've stored a list of authorized chat_ids in a Deta Base, Deta's database service. After we validate the chat_id, we can grab the message and use it as a prompt for DALL·E 2 using a function get_image_from_prompt.

Generate Images with DALL-E 2

get_image_from_prompt is the function that sends the prompt to DALL·E 2, via the openai Python library. It'll get an image as a response.


OPEN_AI_KEY = os.getenv("OPEN_AI")

openai.api_key = OPEN_AI_KEY

def get_image_from_prompt(prompt):
    try:
        response = openai.Image.create(
            prompt=prompt,
            n=1,
            size="512x512",
            response_format="b64_json",
        )

        if "error" not in response:
            return {
                "b64img": response["data"][0]["b64_json"],  # type: ignore
                "created": response["created"],  # type: ignore
            }
        return {"error": response["error"]["message"]}  # type: ignore
    except Exception as e:
        return {"error": str(e)}  # type: ignore
Enter fullscreen mode Exit fullscreen mode

Using the defaults we’ve set, we’ll get a 512x512 image response as base-64 encoded JSON. This code block assumes our server has the Open AI key from earlier, stored in its environment as OPEN_AI_KEY, but we’ll set that later.

Now that we've got an image, we can save it and send it back to Telegram!

Save the images and send them back to Telegram

To save our image, we'll use Deta Drive, which will let us create a file store in our personal cloud. We can then send it the image back to our Telegram chat.

Both are done in the rest of the /open handler function from earlier, using a helper function save_and_send_img:

from deta import Base, Drive
# ... other code

PHOTOS = Drive("generations") # instantiate the Deta Drive
# ... other code

def save_and_send_img(b64img, chat_id, prompt, timestamp):
    image_data = base64.b64decode(b64img)
    filename = f"{timestamp} - {prompt}.png"
    PHOTOS.put(filename, image_data)
    photo_payload = {"photo": image_data}
    message_url = f"{BOT_URL}/sendPhoto?chat_id={chat_id}&caption={prompt}"
    requests.post(message_url, files=photo_payload).json()
    return {"chat_id": chat_id, "caption": prompt}

# ... other code

@app.post("/open")
def http_handler(request: Request):
    # ... other code
    open_ai_resp = get_image_from_prompt(prompt)

    if "b64img" in open_ai_resp:
        return save_and_send_img(
            open_ai_resp["b64img"], chat_id, prompt, open_ai_resp["created"]
        )

    if "error" in open_ai_resp:
        return send_error(chat_id, open_ai_resp["error"])

    return send_error(chat_id, "Unknown error, lol, handling coming soon")
Enter fullscreen mode Exit fullscreen mode

This function takes the base-64 encoded image and decodes it. Then it stores it to your Drive and sends it back to your Telegram bot.

Lastly, we'll talk about the management UI for your bot.

Creating a Management UI for your Bot

As the bot's owner, you'll want to know the bot's state, trigger the setup webhook with Telegram, and manage authorized chat_ids.

This is all done in a simple UI, declared in a single index.html file, with a bit of support from JS and CSS. This HTML file is served by the server in main.py via the /, /setup, and /authorize routes as a Jinja template.

Step 3: Running the Bot

Once you have written the code for your bot, you can deploy and run it out of your personal cloud in Deta Space.

Creating a Project in Deta Space

Before doing so, you’ll need to need to install and login to the Deta Space CLI. Then, ensure your local code has the server in your main.py file and dependencies in your requirements.txt file.

After you have the code, you can create your first Space Project. The CLI can bootstrap your project for Deta Space, if you haven't cloned the repository from GitHub.

In either case, you'll notice there is a Spacefile, which will tell Deta Space how to run your app. We'll want to ensure a few things about this Spacefile:

  • First, that the public_routes parameter is present, telling Deta Space to leave the /open route open to the public (so Telegram can send it events).
  • Second, to set two environment variables under the env parameter: TELEGRAM and OPEN_AI. We'll use these in our app to store the Telegram and Open AI keys, respectively.

The Spacefile also gives the app a name and sets an icon, among other things.

An example of a working Spacefile for our bot is here. But to make things easiest, it's recommended to clone the entire GitHub repository to bootstrap all the files.

Pushing Your Code and Setting Your Environment Variables

Once you’ve got your code ready and project created, you can push your local code to Deta Space with the Space CLI command space push. This will bundle your local code and deploy it as a usable app on Deta Space.

Once pushed, you’ll see a new icon in your Deta Space Canvas (the dashboard), which you'll see when you log in to Space. If you click the ..., then Settings, and then the Configuration tab, you’ll be able to set the environment variables.

Settings

Environment Vars

Enter the Open AI API key and Telegram Bot Key from earlier and click Save.

Setting the Webhook & Chat ID

Now that you’ve added the environment variables, you can configure your assistant with the bot we’ve created in Telegram. Click your app’s icon from the Deta Space Canvas (dashboard) to visit the bot’s admin panel. (On Deta Space, you are authenticated, so this view is only accessible to you).

If you’ve saved the Configuration Variables from the last step, it should show you the button to setup your webhook. Click the Setup Webhook button to do so.

One final step we’ll have to do is add your own chat_id to the list of authorized users:

  • Add your bot in Telegram as a contact via its username.
  • Then talk to it with the command “/chatid” to get the ID of your chat from your bot.
  • Third, Visit the the admin UI again, click the authorize button and then the add new chat id button.
  • Finally, enter the ID into the input field and click submit.

You are now ready to generate images!

Running Your Assistant

Send your bot a message on Telegram, containing any prompt. Your bot should respond with the resulting image!

Example Prompt

Accessing Your Photos

All the images you generate will be stored on your personal cloud in a Drive. You can view and download the photos, as well as program against them using the Deta Drive SDK. Click on the View Data option from the ... menu of your app, and then click the Drive tab to see everything you've got.

Viewing Data

Photos

Other Options for Running Your Bot

You can install the app built in this tutorial in your own personal cloud in one click here. No coding is necessary, you’ll just need to:

  • create a Telegram bot.
  • set the Open AI and Telegram bot keys.
  • visit the “/” route to configure everything before using the bot.

You can also run your assistant anywhere a uvicorn server will run, including locally for development. In the local case, you’ll need to use something like ngrok to tunnel your local host to an https url (and modify the DETA_SPACE_HOSTNAME variable in the code to grab the nrgok hostname).

Final Words

Congratulations — you’ve got an AI assistant running in that can be accessed from your phone. It'll operate and store everything in your own cloud.

One idea for you to play with is to expose the image-generating function as an API, which you can use from a number of other apps (check out the API Keys on Deta Space).

But have fun generating all sorts of images. In the next part of our series, we’ll be adding the ability to chat with our assistant, similar to ChatGPT — stay tuned!

Special thanks to Bighneswar Parida for assistance with code + words.

Top comments (6)

Collapse
 
chuanfengzhang profile image
Chuck

cool

Collapse
 
indika_wimalasuriya profile image
Indika_Wimalasuriya

Supper cool. Will give a try..

Collapse
 
bulingfeng profile image
Lingfeng Bu

cool!

Collapse
 
vargonis profile image
vargonis

I was able to make this work, but then I started anew with a similar project and run into a problem setting the webhook. Instead of having a UI which calls our /set_webhook endpoint, I directly made the /setWebHook Telegram API call in the main.py script. That seems to have broken things. I'm trying to understand what went wrong. A call to the Telegram API endpoint /getWebhookInfo tells me that the last error is this: "SSL error {error:0A000086:SSL routines::certificate verify failed}". Would somehow having the UI call the /set_webhook endpoint magically make this error disappear? Or could I have made another mistake somewhere else?

Collapse
 
xeust profile image
Max

Hi there,

Not sure why this would be an issue, it should work!

Any chance you have a link to a source code repo?

Collapse
 
vargonis profile image
vargonis

It should, but it didn't. I was able to solve the problem by searching over at discord. In fact, the problem was with the _ (underscore) in the project name! See discord.com/channels/8275465552004...