Building a Generative Image Bot with OpenAI, Telegram, FastAPI and Deta Space
Tags: ai, tutorial, webdev, python
Published: 2023-03-31 08:53:20




You may have seen some of the amazing images generated by new AI services, like [Midjourney](https://www.midjourney.com/) and Open AI's [DALL·E 2](https://openai.com/product/dall-e-2).
 
![Images](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/frh04enpvcvc6b0hy74y.png)


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](https://deta.space/developers), 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:

- A [Telegram](https://telegram.org/) Account
- An [OpenAI](https://openai.com/) Account
- A [Deta Space](https://deta.space/signup?dev_mode=true) Account (make sure to enable *Developer Mode* when signing up)

**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](https://openai.com/product/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](https://fastapi.tiangolo.com/) server on [Deta Space](https://deta.space/developers) to bridge Telegram and DALL·E 2. This server will save every image in a personal file store on [Deta Drive](https://deta.space/docs/en/reference/drive/about).


## 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](https://telegram.me/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](https://platform.openai.com/account/api-keys). 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](https://github.com/xeust/telemage), and the core logic lives in the [`main.py`](https://github.com/xeust/telemage/blob/main/main.py) file. The external Python libraries required are listed in the [`requirements.txt`](https://github.com/xeust/telemage/blob/main/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`](https://github.com/xeust/telemage/blob/main/main.py#L174) 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`](https://github.com/xeust/telemage/blob/main/main.py#L124) 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_id`s 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_id`s:

```python
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
```

One note: we assume that we've stored a list of authorized `chat_id`s in a [Deta Base](https://deta.space/docs/en/basics/data), 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. 

```python

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
```

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](https://deta.space/docs/en/reference/drive/about), 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`:

```python
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")
```

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_id`s. 

This is all done in a simple UI, declared in a single [`index.html` file](https://github.com/xeust/telemage/blob/main/index.html), with a bit of support from [JS and CSS](https://github.com/xeust/telemage/tree/chat_id/public). This HTML file is served by the server in `main.py` via the `/`, `/setup`, and `/authorize` routes as a [Jinja](https://jinja.palletsprojects.com/en/3.1.x/) 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](https://deta.space/docs/en/basics/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](https://deta.space/docs/en/basics/projects#creating-a-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`](https://deta.space/docs/en/reference/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](https://github.com/xeust/telemage/blob/main/Spacefile). But to make things easiest, it's recommended to clone the [entire GitHub repository to bootstrap all the files](https://github.com/xeust/telemage).


### 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gvj3dmqk2dwtl2e82qn3.jpg)

![Environment Vars](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ovo22on7160yhgu6flk0.jpg)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zcnocd5x47rn8uzkdzsz.jpg)

### Accessing Your Photos

All the images you generate will be stored on your personal cloud in a [Drive](https://deta.space/docs/en/reference/drive/about). You can view and download the photos, as well as program against them using the [Deta Drive SDK](https://deta.space/docs/en/reference/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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s37azgf7y23y9o9ljxxw.jpg)

![Photos](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/neekbxobjpqy2mn4d84z.png)


## Other Options for Running Your Bot

You can install the app built in this tutorial in your own personal cloud in [one click here](https://deta.space/discovery/@max/telespace). 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](https://ngrok.com/) 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](https://deta.space/docs/en/basics/micros#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.*