Skip to content

Commit

Permalink
Merge pull request #10 from flynnoct/dev
Browse files Browse the repository at this point in the history
v1.1.0
  • Loading branch information
flynnoct authored Mar 4, 2023
2 parents 395f00e + f5a914f commit 9b3c898
Show file tree
Hide file tree
Showing 11 changed files with 329 additions and 52 deletions.
84 changes: 45 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,81 +1,87 @@
# chatgpt-telegram-bot
# ChatGPT Bot for Telegram

Telegram bot implemented by OpenAI ChatGPT API (gpt-3.5-turbo-0301) released on March 1, 2023.
![](/docs/dialog.png)

## News

- **DALL·E, the OpenAI Image Generation Model**, is now supported! Send a short prompt to the Bot and get your own painting!
- **Whisper, the OpenAI Intelligent Speech Recognizer**, is now supported! Now chat with the Bot with audio messages!

## Introduction

This is a Telegram bot implemented by OpenAI ChatGPT API (gpt-3.5-turbo-0301). It is based on [OpenAI ChatGPT API](https://platform.openai.com/docs/guides/chat) and [python-telegram-bot](https://python-telegram-bot.org)
ChatGPT Bot for Telegram is implemented with [OpenAI ChatGPT API](https://platform.openai.com/docs/guides/chat) released on March 1, 2023. The Telegram integration framework is based on [python-telegram-bot](https://python-telegram-bot.org).

ChatGPT Bot can act as your Telegram contact. You can chat with it either personally or in a group chat. Just like the popular AI on the OpenAI official site, the Bot shares knowledge and inspires exciting new ideas. Many interesting features, such as **DALL·E** and **Whisper** are integrated together to make our Bot smarter and more usable.

## Usage
We hope you enjoy it!

### Private Chat
## Features

You can just send a message to the bot in private chat. The bot will reply to you.
The Telegram Bot features the following functions:

### Group Chat
- An AI consultant, based on OpenAI ChatGPT, interacts in a conversational way.
- A flexible speech recognizer which supports audio interaction.
- A AI painter reponses to user's requirement prompt.

You can add the bot to a group chat. However it will only reply the messages with `@<bot_name>` mentioned.
Additonal functions are also implemented:

### Commands
- Set the daily limitation of requirements to **DALL·E**.
- Grant more resources to _Super Users_.

## Commands

- `/start`: Start the bot.
- `/clear`: Clear the conversation context.
- `/getid`: Get your Telegram user ID.
- `/dalle <prompt>`: Ask DALL·E for a painting based on your prompt.

## Sample Usage

## Installation
The Bot works in both personal and group chat of Telegram.
In a personal chat, simply send a message to the Bot and it will reply to you.
In a group chat, you need to tag the message with `@<bot_name>` to invoke the Bot.

### Prepare
### Preparation

1. Create a Telegram bot by [@BotFather](https://t.me/BotFather) and get the token.
2. Create an OpenAI account and get the API key.
3. A VM or a server with Python 3 is needed to run the bot.
3. A Linux VM or a server with Python 3 is needed to run the bot.
4. A practical Internet environment is required.
5. (Optional) [FFmpeg](https://ffmpeg.org) is required for the Bot to handle voice messages with Whisper. If you are not interested in using voice messages, you don't need to install it and **must set `enable_voice` in the config file to False**.

> **Note**: You should disable the privacy mode of the bot. Otherwise the bot will not receive the messages from the group chat. You can do this by sending `/setprivacy` to [@BotFather](https://t.me/BotFather).
### Deploy

Clone this repository.

```bash
git clone [email protected]:flynnoct/chatgpt-telegram-bot.git
```
### Deployment

Install the dependencies.
Download the latest release version and install the dependencies.

```bash
wget https://github.com/flynnoct/chatgpt-telegram-bot/releases/latest
pip install -r requirements.txt
```

Create config file.
Then, you need to create a config file to manage the Bot. The config file includes sensitive information, such as telegram_token and openai_api_key, and we only release the corresponding template `config.json.template`. Therefore, you need to create a new `config.json` file and replace the relative fields with your own.

```bash
cp config.json.template config.json
```

Modify config file. First,
Follow below procedures to fill you `config.json`:

```bash
vim config.json
```
1. Replace the `telegram_token` and `openai_api_key` with your own.
2. Add allowed users to the `allowed_users` list. You can get your user id by sending `/start` to [@userinfobot](https://t.me/userinfobot) or send `/getid` to the Bot (after you start it).

then, replace the `telegram_token` and `openai_api_key` with your own.
> Note: the user ID is a series of numbers, you should add it to the `allowed_users` list as a string (add quotation marks around it).
Add allowed users to the `allowed_users` list. You can get your user id by sending `/start` to [@userinfobot](https://t.me/userinfobot) or send `/getid` to this bot (after you start it).
Now, you can run the Bot with `start_bot.sh` and try talk to it. Also, you can invite it to group chats and share with your friends!

> Note: the user ID is a series of numbers, you should add it to the `allowed_users` list as a string (add quotation marks around it).
To clear ChatGPT conversation context and restart the Bot, run shell script `restart_bot.sh`. To shut down the Bot, run `stop_bot.sh`.

Run the bot.
## Release version and notes

```bash
nohup python3 telegram_message_parser.py &
```
or
```bash
pm2 start telegram_message_parser.py
```
> Note: A launch script will be added later.
The latest released version can be found [here](https://github.com/flynnoct/chatgpt-telegram-bot/releases/latest). More interesting new features are comming soon!

Now you can start a private chat to the bot or add the bot to your group chat. Enjoy.
The release notes are [here](/docs/release_notes.md).

## License

Expand All @@ -85,4 +91,4 @@ Now you can start a private chat to the bot or add the bot to your group chat. E

If you like this project, you can buy me a coffee ❤️ or give this repository a free star ⭐️.

Click [Alipay](donate_code/alipay.jpg) to open QR code.
Click [Alipay](donate_code/alipay.jpg) to open QR code.
19 changes: 13 additions & 6 deletions config.json.template
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
{
{
"openai_api_key": "<YOUR_OPENAI_API_KEY_HERE>",
"telegram_bot_token": "<YOUR_TELEGRAM_BOT_TOKEN_HERE>",
"allowed_users": [
"<USER_ID_1>",
"<USER_ID_2>"
],
"wait_time": 300
"enable_voice": true, // When enabled, Bot will accept audio messages with Whisper and reply.
"allowed_users": [
"<USER_ID_1>",
"<USER_ID_2>",
],
"wait_time": 600, // The time limit in seconds that the Bot will clear the conversation context.
"enable_dalle": true, // When enabled, Bot will involve DALL·E to handle requests for painting.
"super_users": [
"<SUPER_USER_ID_1>", // Super users are granted unlimited usage on DALL·E per day.
"<SUPER_USER_ID_2>"
],
"image_generation_limit_per_day": 5 // The upper limit that a normal user is allowed to invoke DALL·E per day.
}
Binary file added docs/dialog.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/logo.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file added docs/release_notes.md
Empty file.
99 changes: 98 additions & 1 deletion message_manager.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,39 @@
import time
import datetime
import os
import json
from user_context import UserContext
from openai_parser import OpenAIParser

class MessageManager:

userDict = {}
openai_parser = None
config_dict = {}
user_image_generation_usage_dict = {}
user_chat_usage_dict = {}
# Fixed by @Flynn
usage_dict = {}

def __init__(self):
self.openai_parser = OpenAIParser()
# load config
with open("config.json") as f:
self.config_dict = json.load(f)

(image_usage_file_name, now) = self.__get_usage_filename_and_key("image")
if not os.path.exists("./usage"):
os.makedirs("./usage")
if os.path.exists("./usage/" + image_usage_file_name):
with open("./usage/" + image_usage_file_name) as f:
self.user_image_generation_usage_dict = json.load(f)
else:
self.user_image_generation_usage_dict = {}

# Fixed by @Flynn
if now not in self.user_image_generation_usage_dict:
self.user_image_generation_usage_dict[now] = {}


def get_response(self, id, user, message):

Expand All @@ -31,7 +56,79 @@ def clear_context(self, id):
except Exception as e:
print(e)

def get_generated_image_url(self, user, prompt):

# Temporary fix by @Flynn, will be fixed in the next version
with open("config.json") as f:
super_users = json.load(f)["super_users"]
if user in super_users:
url = self.openai_parser.image_generation(user, prompt)
return (url, "Hey boss, it's on your account. 💰")
############################


used_num = self.__check_image_generation_limit(user)
if used_num >= self.config_dict["image_generation_limit_per_day"]:
return (None, "You have reached the limit.")
else:
self.__update_usage_info(user, used_num+1, "image")
url = self.openai_parser.image_generation(user, prompt)
# Temporary fix by @Flynn
return (url, "You have used " + str(used_num + 1) + " / " +
str(self.config_dict["image_generation_limit_per_day"]) +
" times.")

def get_transcript(self, user, audio_file):
try:
return self.openai_parser.speech_to_text(user, audio_file)
except Exception as e:
print(e)
return ""

def __get_usage_filename_and_key(self, chatORimage):
if chatORimage == "chat":
filename = "_char_usage.json"
elif chatORimage == "image":
filename = "_image_generation_usage.json"
return (datetime.datetime.now().strftime("%Y%m") + filename,
datetime.datetime.now().strftime("%Y-%m-%d"))

def __sendMessage(self, user, messageList):
ans = self.openai_parser.get_response(user, messageList)
return ans


def __check_image_generation_limit(self, user):
(_, now) = self.__get_usage_filename_and_key("image")
if now not in self.user_image_generation_usage_dict:
self.__update_dict("image")
if user not in self.user_image_generation_usage_dict[now]:
used_num = 0
else:
used_num = self.user_image_generation_usage_dict[now][user]
return used_num

def __update_dict(self, chatORimage):
(filename, now) = self.__get_usage_filename_and_key(chatORimage)
if not os.path.exists("./usage/" + filename):
if chatORimage == "image":
self.user_image_generation_usage_dict = {}
elif chatORimage == "chat":
self.user_chat_usage_dict = {}
return
if chatORimage == "image" and now not in self.user_image_generation_usage_dict:
self.user_image_generation_usage_dict[now] = {}
elif chatORimage == "chat" and now not in self.user_chat_usage_dict:
self.user_chat_usage_dict[now] = {}

def __update_usage_info(self, user, used_num, chatORimage):
(filename, now) = self.__get_usage_filename_and_key(chatORimage)
if now not in self.user_image_generation_usage_dict:
self.__update_dict(chatORimage)
if chatORimage == "image":
self.user_image_generation_usage_dict[now][user] = used_num
with open("./usage/" + filename, "w") as f:
json.dump(self.user_image_generation_usage_dict, f)
elif chatORimage == "chat":
self.user_chat_usage_dict[now][user] = used_num
# with open("./usage/" + filename, "w") as f:
# json.dump(self.user_chat_usage_dict, f)
37 changes: 37 additions & 0 deletions openai_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,43 @@ def get_response(self, userid, context_messages):
return response["choices"][0]["message"]["content"]
except Exception as e:
return str(e) + "\nSorry, I am not feeling well. Please try again."

def speech_to_text(self, userid, audio_file):
# transcript = openai.Audio.transcribe("whisper-1", audio_file, language="zh")
transcript = openai.Audio.transcribe("whisper-1", audio_file)
return transcript["text"]

def image_generation(self, userid, prompt):
response = openai.Image.create(prompt = prompt, n=1, size = "512x512", user = userid)
image_url = response["data"][0]["url"]
# self.update_image_generation_usage(userid)
# for debug use
# image_url = "https://catdoctorofmonroe.com/wp-content/uploads/2020/09/iconfinder_cat_tied_275717.png"
return image_url

def update_image_generation_usage(self, userid):
# get time
usage_file_name = datetime.datetime.now().strftime("%Y%m") + "_image_generation_usage.json"
now = datetime.datetime.now().strftime("%Y-%m-%d")
# create usage folder
if not os.path.exists("./usage"):
os.makedirs("./usage")
# load usage or create new
if os.path.exists("./usage/" + usage_file_name):
with open("./usage/" + usage_file_name) as f:
self.usage_dict = json.load(f)
else:
self.usage_dict = {}
# update usage
if now not in self.usage_dict:
self.usage_dict[now] = {}
if userid not in self.usage_dict[now]:
self.usage_dict[now][userid] = {"requests": 0}
self.usage_dict[now][userid]["requests"] += 1
# save usage
with open("./usage/" + usage_file_name, "w") as f:
json.dump(self.usage_dict, f)


def update_usage(self, total_tokens, userid):
# get time
Expand Down
17 changes: 17 additions & 0 deletions restart_bot.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash
# This script restarts a Python program named telegram_message_parser.py

echo "Restarting bot..."

pid=`ps -ef | grep telegram_message_parser.py | grep -v grep | awk '{print $2}'`

if [ -z $pid ]; then
echo "Bot is not running"
else
echo "Bot has been terminated"
kill -9 $pid
fi

echo "Starting bot..."
nohup python3 telegram_message_parser.py >/dev/null 2>&1 &
echo "Bot has been restarted successfully"
4 changes: 3 additions & 1 deletion start_bot.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#!/bin/bash
# This script start a Python program named telegram_message_parser.py and run it in the background

nohup python3 telegram_message_parser.py &
nohup python3 telegram_message_parser.py >/dev/null 2>&1 &
echo "Bot has been started successfully"
13 changes: 13 additions & 0 deletions stop_bot.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash
# This script stops a Python program named telegram_message_parser.py

# Find the PID of the process
pid=$(ps -ef | grep "telegram_message_parser.py" | grep -v grep | awk '{print $2}')

if [ -z "$pid" ]; then
echo "Bot is not running"
else
# Terminate the process
kill $pid
echo "Bot has been terminated"
fi
Loading

0 comments on commit 9b3c898

Please sign in to comment.