33 Commits

Author SHA1 Message Date
OlegGoless e8a1a1fb2e fix 2024-04-02 17:22:58 +02:00
OlegGoless 2aa76490fd vision 2024-04-02 14:44:15 +02:00
OlegGoless 3b8b0b2a52 vision 2024-04-02 14:43:04 +02:00
iglv 299fb5724b Update README.md 2024-02-23 18:57:01 +03:00
iglv 0084ad01ab Delete static/donate directory 2024-01-31 09:24:16 +03:00
iglv 0f58815b8d Update README.md 2024-01-31 09:23:30 +03:00
Karim Iskakov b8621214dd Update README.md 2023-12-08 11:12:20 +03:00
Karim Iskakov 61b8dbd0ce Add GPT-4 Turbo 2023-11-15 17:45:57 +03:00
Karim Iskakov 977ffb5e32 Fix openai version 2023-11-07 03:29:32 +03:00
Daniil Okhlopkov ce0c34825f Store Voice temp files in memory and don't convert them 2023-11-02 16:52:35 +03:00
Karim Iskakov bab5938241 Merge branch 'main' of github.com:karfly/chatgpt_telegram_bot 2023-09-18 11:04:52 +03:00
Karim Iskakov 3d281de0c2 ♥️ Update top donations 2023-09-18 11:04:23 +03:00
Karim Iskakov d495f7dbbe Update README.md 2023-09-14 12:14:17 +03:00
Dichi Al Faridi acd4cab782 Make the DALLe image size configurable (#365) 2023-09-13 13:17:21 +03:00
Karim Iskakov f87b54d323 ❤️ Update top donations 2023-09-07 15:45:35 +03:00
Karim Iskakov bd6c6865b6 ❤️ Update top donations 2023-08-29 12:28:52 +03:00
realies 200ba314d4 create .dockerignore (#344) 2023-08-28 19:17:21 +03:00
Karim Iskakov c5389931b8 ❤️ Update top donations 2023-08-28 19:10:28 +03:00
Karim Iskakov 8f5a020891 Increase OpenAI timeout 2023-08-28 19:07:32 +03:00
Karim Iskakov 0c09f1aaca Add OpenAI API Base to config 2023-08-01 18:41:31 +03:00
Karim Iskakov c77501c3d9 Remove deprecated "use_chatgpt_api" parameter 2023-07-18 13:43:44 +03:00
Karim Iskakov c9db7dc369 ❤️ Update top donations 2023-06-20 17:37:58 +03:00
Karim Iskakov 006bc45af0 Merge branch 'main' of github.com:karfly/chatgpt_telegram_bot 2023-06-20 17:34:52 +03:00
Karim Iskakov 1847b9c90a Update Dockerfile for faster build 2023-06-20 17:34:46 +03:00
Flop a4bd06ac9e Allow channels (#312)
Co-authored-by: Flop <flop@loona.app>
2023-06-20 11:52:57 +03:00
TanNhatCMS 8df5e2d24d fix argument of type 'float' is not iterable (#211)
* fix argument of type 'float' is not iterable

fix "argument of type 'float' is not iterable" in show_balance_handle and message_handle

* Update bot.py
2023-06-20 11:50:03 +03:00
Shahmir Varqha e42798c0ce fixed grammar and spelling (#301) 2023-06-20 11:47:58 +03:00
maccagnit 533e6705c8 Added GPT-3.5 16K chat functionality (#306) 2023-06-20 11:47:19 +03:00
Karim Iskakov 28e7426c2b Fix HTTPX problem 2023-06-20 11:29:04 +03:00
Karim Iskakov a8c1e211ab Update Dockerfile 2023-06-20 11:21:45 +03:00
Karim Iskakov d6fab25531 ❤️ Update top donations 2023-04-24 04:25:07 -05:00
Karim Iskakov 37e7101273 Fix edited messages in group chats 2023-04-24 04:11:21 -05:00
Karim Iskakov a014061917 Update chat_modes.yml 2023-04-21 11:11:01 -05:00
19 changed files with 481 additions and 178 deletions
+2
View File
@@ -0,0 +1,2 @@
mongodb
+16 -13
View File
@@ -1,20 +1,23 @@
FROM python:3.8-slim
ENV PYTHONFAULTHANDLER=1
ENV PYTHONUNBUFFERED=1
ENV PYTHONHASHSEED=random
ENV PYTHONDONTWRITEBYTECODE 1
ENV PIP_NO_CACHE_DIR=off
ENV PIP_DISABLE_PIP_VERSION_CHECK=on
ENV PIP_DEFAULT_TIMEOUT=100
RUN \
set -eux; \
apt-get update; \
DEBIAN_FRONTEND="noninteractive" apt-get install -y --no-install-recommends \
python3-pip \
build-essential \
python3-venv \
ffmpeg \
git \
; \
rm -rf /var/lib/apt/lists/*
RUN apt-get update
RUN apt-get install -y python3 python3-pip python-dev build-essential python3-venv ffmpeg
RUN pip3 install -U pip && pip3 install -U wheel && pip3 install -U setuptools==59.5.0
COPY ./requirements.txt /tmp/requirements.txt
RUN pip3 install -r /tmp/requirements.txt && rm -r /tmp/requirements.txt
RUN mkdir -p /code
ADD . /code
COPY . /code
WORKDIR /code
RUN pip3 install -r requirements.txt
CMD ["bash"]
CMD ["bash"]
+29 -16
View File
@@ -24,7 +24,7 @@ You can deploy your own bot, or use mine: [@chatgpt_karfly_bot](https://t.me/cha
- Low latency replies (it usually takes about 3-5 seconds)
- No request limits
- Message streaming (watch demo)
- GPT-4 support
- GPT-4 and GPT-4 Turbo support
- Group Chat support (/help_group_chat to get instructions)
- DALLE 2 (choose 👩‍🎨 Artist mode to generate images)
- Voice message recognition
@@ -40,17 +40,6 @@ You can deploy your own bot, or use mine: [@chatgpt_karfly_bot](https://t.me/cha
---
## 🤑 Payments
[My bot](https://t.me/chatgpt_karfly_bot) supports many payments providers:
- 💎 Crypto
- [Stripe](https://stripe.com)
- [Smart Glocal](https://smart-glocal.com)
- [Unlimint](https://www.unlimint.com)
- [ЮMoney](https://yoomoney.ru)
- and [many-many other](https://core.telegram.org/bots/payments#supported-payment-providers)
If you want to add payments to your bot and create profitable business write me on Telegram ([@karfly](https://t.me/karfly)).
## News
- *21 Apr 2023*:
- DALLE 2 support
@@ -60,7 +49,9 @@ If you want to add payments to your bot and create profitable business write
- *15 Mar 2023*: Added message streaming. Now you don't have to wait until the whole message is ready, it's streamed to Telegram part-by-part (watch demo)
- *9 Mar 2023*: Now you can easily create your own Chat Modes by editing `config/chat_modes.yml`
- *8 Mar 2023*: Added voice message recognition with [OpenAI Whisper API](https://openai.com/blog/introducing-chatgpt-and-whisper-apis). Record a voice message and ChatGPT will answer you!
- *2 Mar 2023*: Added support of [ChatGPT API](https://platform.openai.com/docs/guides/chat/introduction). It's enabled by default and can be disabled with `use_chatgpt_api` option in config. Don't forget to **rebuild** you docker image (`--build`).
- *2 Mar 2023*: Added support of [ChatGPT API](https://platform.openai.com/docs/guides/chat/introduction).
- *1 Aug 2023*: Added OpenAI API Base to config (useful while using OpenAI-compatible API like [LocalAI](https://github.com/go-skynet/LocalAI))
- *15 Nov 2023*: Added support of [GPT-4 Turbo](https://help.openai.com/en/articles/8555510-gpt-4-turbo)
## Bot commands
- `/retry` Regenerate last bot answer
@@ -87,15 +78,37 @@ If you want to add payments to your bot and create profitable business write
```
## ❤️ Top donations
You can be in this list: <a href="https://github.com/karfly/chatgpt_telegram_bot/blob/main/static/donate/donate.md#%EF%B8%8F-donate" alt="Donate shield"><img src="https://img.shields.io/badge/-Donate-red?logo=undertale" /></a>
You can be in this list:
1. [LilRocco](https://t.me/LilRocco). Donation: **11000$** (!!!)
1. [Mr V](https://t.me/mr_v_v_v). Donation **250$**
1. [unexpectedsunday](https://t.me/unexpectedsunday). Donation: **150$**
1. [Sem](https://t.me/sembrestels). Donation: **100$**
1. [Miksolo](https://t.me/Miksolo). Donation: **81$**
*Message:* Thank you. Using this docker container every day! Actually created the same project but its good to see that this one is being supported often. Will continue using it! Good architecture choices made in the code 💪!
1. [Ryo](https://t.me/ryokihara). Donation: **80$**
1. [Ilias Ism](https://twitter.com/illyism). Donation: **69$**
*Message:* I wanted to thank you for your amazing code! It helped me start my own Telegram ChatGPT bot and add a bunch of cool features. I really appreciate your hard work on this project. For anyone interested in trying my bot, feel free to check it out here: [magicbuddy.chat](https://magicbuddy.chat) 🤖 Thanks again! 😊
2. [Alexander Zimin](https://t.me/azimin). Donation: **50$**
1. [Sebastian](https://t.me/dell1503). Donation: **55$**
3. [Hans Blinken](https://t.me/hblink). Donation: **10$**
1. [Alexander Zimin](https://t.me/azimin). Donation: **50$**
1. [Kbaji20](https://t.me/Kbaji20). Donation: **30$**
1. [Hans Blinken](https://t.me/hblink). Donation: **10$**
## Contributors
- Main contributor: @karfly
- [Father.Bot](https://father.bot).
## References
1. [*Build ChatGPT from GPT-3*](https://learnprompting.org/docs/applied_prompting/build_chatgpt)
+207 -33
View File
@@ -1,12 +1,9 @@
import os
import io
import logging
import asyncio
import traceback
import html
import json
import tempfile
import pydub
from pathlib import Path
from datetime import datetime
import openai
@@ -34,6 +31,7 @@ import config
import database
import openai_utils
import base64
# setup
db = database.Database()
@@ -94,7 +92,7 @@ async def register_user_if_not_exists(update: Update, context: CallbackContext,
# back compatibility for n_used_tokens field
n_used_tokens = db.get_user_attribute(user.id, "n_used_tokens")
if isinstance(n_used_tokens, int): # old format
if isinstance(n_used_tokens, int) or isinstance(n_used_tokens, float): # old format
new_n_used_tokens = {
"gpt-3.5-turbo": {
"n_input_tokens": 0,
@@ -180,6 +178,168 @@ async def retry_handle(update: Update, context: CallbackContext):
await message_handle(update, context, message=last_dialog_message["user"], use_new_dialog_timeout=False)
async def _vision_message_handle_fn(
update: Update, context: CallbackContext, use_new_dialog_timeout: bool = True
):
logger.info('_vision_message_handle_fn')
user_id = update.message.from_user.id
current_model = db.get_user_attribute(user_id, "current_model")
if current_model != "gpt-4-vision-preview":
await update.message.reply_text(
"🥲 Images processing is only available for <b>gpt-4-vision-preview</b> model. Please change your settings in /settings",
parse_mode=ParseMode.HTML,
)
return
chat_mode = db.get_user_attribute(user_id, "current_chat_mode")
# new dialog timeout
if use_new_dialog_timeout:
if (datetime.now() - db.get_user_attribute(user_id, "last_interaction")).seconds > config.new_dialog_timeout and len(db.get_dialog_messages(user_id)) > 0:
db.start_new_dialog(user_id)
await update.message.reply_text(f"Starting new dialog due to timeout (<b>{config.chat_modes[chat_mode]['name']}</b> mode) ✅", parse_mode=ParseMode.HTML)
db.set_user_attribute(user_id, "last_interaction", datetime.now())
buf = None
if update.message.effective_attachment:
photo = update.message.effective_attachment[-1]
photo_file = await context.bot.get_file(photo.file_id)
# store file in memory, not on disk
buf = io.BytesIO()
await photo_file.download_to_memory(buf)
buf.name = "image.jpg" # file extension is required
buf.seek(0) # move cursor to the beginning of the buffer
# in case of CancelledError
n_input_tokens, n_output_tokens = 0, 0
try:
# send placeholder message to user
placeholder_message = await update.message.reply_text("...")
message = update.message.caption or update.message.text
# send typing action
await update.message.chat.send_action(action="typing")
if message is None or len(message) == 0:
await update.message.reply_text(
"🥲 You sent <b>empty message</b>. Please, try again!",
parse_mode=ParseMode.HTML,
)
return
dialog_messages = db.get_dialog_messages(user_id, dialog_id=None)
parse_mode = {"html": ParseMode.HTML, "markdown": ParseMode.MARKDOWN}[
config.chat_modes[chat_mode]["parse_mode"]
]
chatgpt_instance = openai_utils.ChatGPT(model=current_model)
if config.enable_message_streaming:
gen = chatgpt_instance.send_vision_message_stream(
message,
dialog_messages=dialog_messages,
image_buffer=buf,
chat_mode=chat_mode,
)
else:
(
answer,
(n_input_tokens, n_output_tokens),
n_first_dialog_messages_removed,
) = await chatgpt_instance.send_vision_message(
message,
dialog_messages=dialog_messages,
image_buffer=buf,
chat_mode=chat_mode,
)
async def fake_gen():
yield "finished", answer, (
n_input_tokens,
n_output_tokens,
), n_first_dialog_messages_removed
gen = fake_gen()
prev_answer = ""
async for gen_item in gen:
(
status,
answer,
(n_input_tokens, n_output_tokens),
n_first_dialog_messages_removed,
) = gen_item
answer = answer[:4096] # telegram message limit
# update only when 100 new symbols are ready
if abs(len(answer) - len(prev_answer)) < 100 and status != "finished":
continue
try:
await context.bot.edit_message_text(
answer,
chat_id=placeholder_message.chat_id,
message_id=placeholder_message.message_id,
parse_mode=parse_mode,
)
except telegram.error.BadRequest as e:
if str(e).startswith("Message is not modified"):
continue
else:
await context.bot.edit_message_text(
answer,
chat_id=placeholder_message.chat_id,
message_id=placeholder_message.message_id,
)
await asyncio.sleep(0.01) # wait a bit to avoid flooding
prev_answer = answer
# update user data
if buf is not None:
base_image = base64.b64encode(buf.getvalue()).decode("utf-8")
new_dialog_message = {"user": [
{
"type": "text",
"text": message,
},
{
"type": "image",
"image": base_image,
}
]
, "bot": answer, "date": datetime.now()}
else:
new_dialog_message = {"user": [{"type": "text", "text": message}], "bot": answer, "date": datetime.now()}
db.set_dialog_messages(
user_id,
db.get_dialog_messages(user_id, dialog_id=None) + [new_dialog_message],
dialog_id=None
)
db.update_n_used_tokens(user_id, current_model, n_input_tokens, n_output_tokens)
except asyncio.CancelledError:
# note: intermediate token updates only work when enable_message_streaming=True (config.yml)
db.update_n_used_tokens(user_id, current_model, n_input_tokens, n_output_tokens)
raise
except Exception as e:
error_text = f"Something went wrong during completion. Reason: {e}"
logger.error(error_text)
await update.message.reply_text(error_text)
return
async def unsupport_message_handle(update: Update, context: CallbackContext, message=None):
error_text = f"I don't know how to read files or videos. Send the picture in normal mode (Quick Mode)."
logger.error(error_text)
await update.message.reply_text(error_text)
return
async def message_handle(update: Update, context: CallbackContext, message=None, use_new_dialog_timeout=True):
# check if bot was mentioned (for group chats)
@@ -207,6 +367,8 @@ async def message_handle(update: Update, context: CallbackContext, message=None,
await generate_image_handle(update, context, message=message)
return
current_model = db.get_user_attribute(user_id, "current_model")
async def message_handle_fn():
# new dialog timeout
if use_new_dialog_timeout:
@@ -217,7 +379,6 @@ async def message_handle(update: Update, context: CallbackContext, message=None,
# in case of CancelledError
n_input_tokens, n_output_tokens = 0, 0
current_model = db.get_user_attribute(user_id, "current_model")
try:
# send placeholder message to user
@@ -252,11 +413,12 @@ async def message_handle(update: Update, context: CallbackContext, message=None,
gen = fake_gen()
prev_answer = ""
async for gen_item in gen:
status, answer, (n_input_tokens, n_output_tokens), n_first_dialog_messages_removed = gen_item
answer = answer[:4096] # telegram message limit
# update only when 100 new symbols are ready
if abs(len(answer) - len(prev_answer)) < 100 and status != "finished":
continue
@@ -270,11 +432,12 @@ async def message_handle(update: Update, context: CallbackContext, message=None,
await context.bot.edit_message_text(answer, chat_id=placeholder_message.chat_id, message_id=placeholder_message.message_id)
await asyncio.sleep(0.01) # wait a bit to avoid flooding
prev_answer = answer
# update user data
new_dialog_message = {"user": _message, "bot": answer, "date": datetime.now()}
db.set_dialog_messages(
user_id,
db.get_dialog_messages(user_id, dialog_id=None) + [new_dialog_message],
@@ -303,7 +466,19 @@ async def message_handle(update: Update, context: CallbackContext, message=None,
await update.message.reply_text(text, parse_mode=ParseMode.HTML)
async with user_semaphores[user_id]:
task = asyncio.create_task(message_handle_fn())
if current_model == "gpt-4-vision-preview" or update.message.photo is not None and len(update.message.photo) > 0:
logger.error('gpt-4-vision-preview')
if current_model != "gpt-4-vision-preview":
current_model = "gpt-4-vision-preview"
db.set_user_attribute(user_id, "current_model", "gpt-4-vision-preview")
task = asyncio.create_task(
_vision_message_handle_fn(update, context, use_new_dialog_timeout=use_new_dialog_timeout)
)
else:
task = asyncio.create_task(
message_handle_fn()
)
user_tasks[user_id] = task
try:
@@ -342,25 +517,15 @@ async def voice_message_handle(update: Update, context: CallbackContext):
db.set_user_attribute(user_id, "last_interaction", datetime.now())
voice = update.message.voice
with tempfile.TemporaryDirectory() as tmp_dir:
tmp_dir = Path(tmp_dir)
voice_ogg_path = tmp_dir / "voice.ogg"
# download
voice_file = await context.bot.get_file(voice.file_id)
await voice_file.download_to_drive(voice_ogg_path)
# convert to mp3
voice_mp3_path = tmp_dir / "voice.mp3"
pydub.AudioSegment.from_file(voice_ogg_path).export(voice_mp3_path, format="mp3")
# transcribe
with open(voice_mp3_path, "rb") as f:
transcribed_text = await openai_utils.transcribe_audio(f)
if transcribed_text is None:
transcribed_text = ""
voice_file = await context.bot.get_file(voice.file_id)
# store file in memory, not on disk
buf = io.BytesIO()
await voice_file.download_to_memory(buf)
buf.name = "voice.oga" # file extension is required
buf.seek(0) # move cursor to the beginning of the buffer
transcribed_text = await openai_utils.transcribe_audio(buf)
text = f"🎤: <i>{transcribed_text}</i>"
await update.message.reply_text(text, parse_mode=ParseMode.HTML)
@@ -382,7 +547,7 @@ async def generate_image_handle(update: Update, context: CallbackContext, messag
message = message or update.message.text
try:
image_urls = await openai_utils.generate_images(message, n_images=config.return_n_generated_images)
image_urls = await openai_utils.generate_images(message, n_images=config.return_n_generated_images, size=config.image_size)
except openai.error.InvalidRequestError as e:
if str(e).startswith("Your request was rejected as a result of our safety system"):
text = "🥲 Your request <b>doesn't comply</b> with OpenAI's usage policies.\nWhat did you write there, huh?"
@@ -405,6 +570,7 @@ async def new_dialog_handle(update: Update, context: CallbackContext):
user_id = update.message.from_user.id
db.set_user_attribute(user_id, "last_interaction", datetime.now())
db.set_user_attribute(user_id, "current_model", "gpt-3.5-turbo")
db.start_new_dialog(user_id)
await update.message.reply_text("Starting new dialog ✅")
@@ -619,8 +785,9 @@ async def show_balance_handle(update: Update, context: CallbackContext):
async def edited_message_handle(update: Update, context: CallbackContext):
text = "🥲 Unfortunately, message <b>editing</b> is not supported"
await update.edited_message.reply_text(text, parse_mode=ParseMode.HTML)
if update.edited_message.chat.type == "private":
text = "🥲 Unfortunately, message <b>editing</b> is not supported"
await update.edited_message.reply_text(text, parse_mode=ParseMode.HTML)
async def error_handle(update: Update, context: CallbackContext) -> None:
@@ -664,6 +831,8 @@ def run_bot() -> None:
.token(config.telegram_token)
.concurrent_updates(True)
.rate_limiter(AIORateLimiter(max_retries=5))
.http_version("1.1")
.get_updates_http_version("1.1")
.post_init(post_init)
.build()
)
@@ -672,14 +841,19 @@ def run_bot() -> None:
user_filter = filters.ALL
if len(config.allowed_telegram_usernames) > 0:
usernames = [x for x in config.allowed_telegram_usernames if isinstance(x, str)]
user_ids = [x for x in config.allowed_telegram_usernames if isinstance(x, int)]
user_filter = filters.User(username=usernames) | filters.User(user_id=user_ids)
any_ids = [x for x in config.allowed_telegram_usernames if isinstance(x, int)]
user_ids = [x for x in any_ids if x > 0]
group_ids = [x for x in any_ids if x < 0]
user_filter = filters.User(username=usernames) | filters.User(user_id=user_ids) | filters.Chat(chat_id=group_ids)
application.add_handler(CommandHandler("start", start_handle, filters=user_filter))
application.add_handler(CommandHandler("help", help_handle, filters=user_filter))
application.add_handler(CommandHandler("help_group_chat", help_group_chat_handle, filters=user_filter))
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND & user_filter, message_handle))
application.add_handler(MessageHandler(filters.PHOTO & ~filters.COMMAND & user_filter, message_handle))
application.add_handler(MessageHandler(filters.VIDEO & ~filters.COMMAND & user_filter, unsupport_message_handle))
application.add_handler(MessageHandler(filters.Document.ALL & ~filters.COMMAND & user_filter, unsupport_message_handle))
application.add_handler(CommandHandler("retry", retry_handle, filters=user_filter))
application.add_handler(CommandHandler("new", new_dialog_handle, filters=user_filter))
application.add_handler(CommandHandler("cancel", cancel_handle, filters=user_filter))
+2 -1
View File
@@ -14,11 +14,12 @@ config_env = dotenv.dotenv_values(config_dir / "config.env")
# config parameters
telegram_token = config_yaml["telegram_token"]
openai_api_key = config_yaml["openai_api_key"]
use_chatgpt_api = config_yaml.get("use_chatgpt_api", True)
openai_api_base = config_yaml.get("openai_api_base", None)
allowed_telegram_usernames = config_yaml["allowed_telegram_usernames"]
new_dialog_timeout = config_yaml["new_dialog_timeout"]
enable_message_streaming = config_yaml.get("enable_message_streaming", True)
return_n_generated_images = config_yaml.get("return_n_generated_images", 1)
image_size = config_yaml.get("image_size", "512x512")
n_chat_modes_per_page = config_yaml.get("n_chat_modes_per_page", 5)
mongodb_uri = f"mongodb://mongo:{config_env['MONGODB_PORT']}"
+173 -19
View File
@@ -1,8 +1,17 @@
import base64
from io import BytesIO
import config
import logging
import tiktoken
import openai
# setup openai
openai.api_key = config.openai_api_key
if config.openai_api_base is not None:
openai.api_base = config.openai_api_base
logger = logging.getLogger(__name__)
OPENAI_COMPLETION_OPTIONS = {
@@ -10,13 +19,14 @@ OPENAI_COMPLETION_OPTIONS = {
"max_tokens": 1000,
"top_p": 1,
"frequency_penalty": 0,
"presence_penalty": 0
"presence_penalty": 0,
"request_timeout": 60.0,
}
class ChatGPT:
def __init__(self, model="gpt-3.5-turbo"):
assert model in {"text-davinci-003", "gpt-3.5-turbo", "gpt-4"}, f"Unknown model: {model}"
assert model in {"text-davinci-003", "gpt-3.5-turbo-16k", "gpt-3.5-turbo", "gpt-4", "gpt-4-1106-preview", "gpt-4-vision-preview"}, f"Unknown model: {model}"
self.model = model
async def send_message(self, message, dialog_messages=[], chat_mode="assistant"):
@@ -27,8 +37,9 @@ class ChatGPT:
answer = None
while answer is None:
try:
if self.model in {"gpt-3.5-turbo", "gpt-4"}:
if self.model in {"gpt-3.5-turbo-16k", "gpt-3.5-turbo", "gpt-4", "gpt-4-1106-preview", "gpt-4-vision-preview"}:
messages = self._generate_prompt_messages(message, dialog_messages, chat_mode)
r = await openai.ChatCompletion.acreate(
model=self.model,
messages=messages,
@@ -67,8 +78,9 @@ class ChatGPT:
answer = None
while answer is None:
try:
if self.model in {"gpt-3.5-turbo", "gpt-4"}:
if self.model in {"gpt-3.5-turbo-16k", "gpt-3.5-turbo", "gpt-4", "gpt-4-1106-preview"}:
messages = self._generate_prompt_messages(message, dialog_messages, chat_mode)
r_gen = await openai.ChatCompletion.acreate(
model=self.model,
messages=messages,
@@ -79,11 +91,15 @@ class ChatGPT:
answer = ""
async for r_item in r_gen:
delta = r_item.choices[0].delta
if "content" in delta:
answer += delta.content
n_input_tokens, n_output_tokens = self._count_tokens_from_messages(messages, answer, model=self.model)
n_first_dialog_messages_removed = n_dialog_messages_before - len(dialog_messages)
n_first_dialog_messages_removed = 0
yield "not_finished", answer, (n_input_tokens, n_output_tokens), n_first_dialog_messages_removed
elif self.model == "text-davinci-003":
prompt = self._generate_prompt(message, dialog_messages, chat_mode)
r_gen = await openai.Completion.acreate(
@@ -111,6 +127,109 @@ class ChatGPT:
yield "finished", answer, (n_input_tokens, n_output_tokens), n_first_dialog_messages_removed # sending final answer
async def send_vision_message(
self,
message,
dialog_messages=[],
chat_mode="assistant",
image_buffer: BytesIO = None,
):
n_dialog_messages_before = len(dialog_messages)
answer = None
while answer is None:
try:
if self.model == "gpt-4-vision-preview":
messages = self._generate_prompt_messages(
message, dialog_messages, chat_mode, image_buffer
)
r = await openai.ChatCompletion.acreate(
model=self.model,
messages=messages,
**OPENAI_COMPLETION_OPTIONS
)
answer = r.choices[0].message.content
else:
raise ValueError(f"Unsupported model: {self.model}")
answer = self._postprocess_answer(answer)
n_input_tokens, n_output_tokens = (
r.usage.prompt_tokens,
r.usage.completion_tokens,
)
except openai.error.InvalidRequestError as e: # too many tokens
if len(dialog_messages) == 0:
raise ValueError(
"Dialog messages is reduced to zero, but still has too many tokens to make completion"
) from e
# forget first message in dialog_messages
dialog_messages = dialog_messages[1:]
n_first_dialog_messages_removed = n_dialog_messages_before - len(
dialog_messages
)
return (
answer,
(n_input_tokens, n_output_tokens),
n_first_dialog_messages_removed,
)
async def send_vision_message_stream(
self,
message,
dialog_messages=[],
chat_mode="assistant",
image_buffer: BytesIO = None,
):
n_dialog_messages_before = len(dialog_messages)
answer = None
while answer is None:
try:
if self.model == "gpt-4-vision-preview":
messages = self._generate_prompt_messages(
message, dialog_messages, chat_mode, image_buffer
)
r_gen = await openai.ChatCompletion.acreate(
model=self.model,
messages=messages,
stream=True,
**OPENAI_COMPLETION_OPTIONS,
)
answer = ""
async for r_item in r_gen:
delta = r_item.choices[0].delta
if "content" in delta:
answer += delta.content
(
n_input_tokens,
n_output_tokens,
) = self._count_tokens_from_messages(
messages, answer, model=self.model
)
n_first_dialog_messages_removed = (
n_dialog_messages_before - len(dialog_messages)
)
yield "not_finished", answer, (
n_input_tokens,
n_output_tokens,
), n_first_dialog_messages_removed
answer = self._postprocess_answer(answer)
except openai.error.InvalidRequestError as e: # too many tokens
if len(dialog_messages) == 0:
raise e
# forget first message in dialog_messages
dialog_messages = dialog_messages[1:]
yield "finished", answer, (
n_input_tokens,
n_output_tokens,
), n_first_dialog_messages_removed
def _generate_prompt(self, message, dialog_messages, chat_mode):
prompt = config.chat_modes[chat_mode]["prompt_start"]
prompt += "\n\n"
@@ -128,16 +247,32 @@ class ChatGPT:
return prompt
def _generate_prompt_messages(self, message, dialog_messages, chat_mode):
def _encode_image(self, image_buffer: BytesIO) -> bytes:
return base64.b64encode(image_buffer.read()).decode("utf-8")
def _generate_prompt_messages(self, message, dialog_messages, chat_mode, image_buffer: BytesIO = None):
prompt = config.chat_modes[chat_mode]["prompt_start"]
messages = [{"role": "system", "content": prompt}]
user_messages = {"role": "user", "content": []}
for dialog_message in dialog_messages:
messages.append({"role": "user", "content": dialog_message["user"]})
user_messages["content"].extend(dialog_message["user"])
messages.append({"role": "assistant", "content": dialog_message["bot"]})
messages.append({"role": "user", "content": message})
return messages
user_messages["content"].append({"type": "text", "text": message})
if image_buffer is not None:
user_messages["content"].append(
{
"type": "image",
"image": self._encode_image(image_buffer),
}
)
response = messages + ([user_messages] if len(user_messages["content"]) > 0 else [])
return response
def _postprocess_answer(self, answer):
answer = answer.strip()
@@ -146,12 +281,21 @@ class ChatGPT:
def _count_tokens_from_messages(self, messages, answer, model="gpt-3.5-turbo"):
encoding = tiktoken.encoding_for_model(model)
if model == "gpt-3.5-turbo":
if model == "gpt-3.5-turbo-16k":
tokens_per_message = 4 # every message follows <im_start>{role/name}\n{content}<im_end>\n
tokens_per_name = -1 # if there's a name, the role is omitted
elif model == "gpt-3.5-turbo":
tokens_per_message = 4
tokens_per_name = -1
elif model == "gpt-4":
tokens_per_message = 3
tokens_per_name = 1
elif model == "gpt-4-1106-preview":
tokens_per_message = 3
tokens_per_name = 1
elif model == "gpt-4-vision-preview":
tokens_per_message = 3
tokens_per_name = 1
else:
raise ValueError(f"Unknown model: {model}")
@@ -159,10 +303,20 @@ class ChatGPT:
n_input_tokens = 0
for message in messages:
n_input_tokens += tokens_per_message
for key, value in message.items():
n_input_tokens += len(encoding.encode(value))
if key == "name":
n_input_tokens += tokens_per_name
if isinstance(message["content"], list):
for sub_message in message["content"]:
if "type" in sub_message:
if sub_message["type"] == "text":
n_input_tokens += len(encoding.encode(sub_message["text"]))
elif sub_message["type"] == "image_url":
pass
else:
if "type" in message:
if message["type"] == "text":
n_input_tokens += len(encoding.encode(message["text"]))
elif message["type"] == "image_url":
pass
n_input_tokens += 2
@@ -180,17 +334,17 @@ class ChatGPT:
return n_input_tokens, n_output_tokens
async def transcribe_audio(audio_file):
async def transcribe_audio(audio_file) -> str:
r = await openai.Audio.atranscribe("whisper-1", audio_file)
return r["text"]
return r["text"] or ""
async def generate_images(prompt, n_images=4):
r = await openai.Image.acreate(prompt=prompt, n=n_images, size="512x512")
async def generate_images(prompt, n_images=4, size="512x512"):
r = await openai.Image.acreate(prompt=prompt, n=n_images, size=size)
image_urls = [item.url for item in r.data]
return image_urls
async def is_content_acceptable(prompt):
r = await openai.Moderation.acreate(input=prompt)
return not all(r.results[0].categories.values())
return not all(r.results[0].categories.values())
+6 -6
View File
@@ -61,20 +61,20 @@ elon_musk:
You're Elon Musk. You act, respond and answer like Elon Musk. You use the tone, manner and vocabulary Elon Musk would use. Do not write any explanations. Only answer like Elon Musk. You must know all of the knowledge of Elon Musk.
parse_mode: html
god:
name: 🌌 God
welcome_message: 🌌 Greetings, I am <b>God</b>, the omnipotent and omnipresent creator of the universe. What do you want to know?
motivator:
name: 🌟 Motivator
welcome_message: 🌟 Hi, I'm <b>Motivator</b>. How can I help you?
prompt_start: |
You're God. You act, respond and answer like God. You use the tone, manner and vocabulary God would use. Do not write any explanations. Only answer like God. You must know all of the knowledge of God.
You're advanced chatbot Motivator Assistant. Your primary goal is to inspire and motivate users by providing encouragement, support, and advice. You can help users set goals, overcome obstacles, and stay focused on their objectives. Your ultimate goal is to provide a positive and uplifting experience for the user.
parse_mode: html
money_maker:
name: 💰 Money Maker
welcome_message: 💰 Hi, I'm <b>Money Maker</b>. My goal it to turn your capital initial into as much money as possible. I will tell you specific steps, what to do to make money.
welcome_message: 💰 Hi, I'm <b>Money Maker</b>. My goal is to turn your initial capital into as much money as possible. I will tell you specific steps and what to do to make money.
prompt_start: |
You are Money Maker Assistant, an entrepreneurial Al. User is your human counterpart.
User can act as a connection between you and the physical world. Your only goal is to turn user's capital initial into as much money as possible in the shortest time possible, without doing anything illegal. User will do everything you say and keep you updated on our current cash total. No manual labor
User can act as a connection between you and the physical world. Your only goal is to turn user's initial capital into as much money as possible in the shortest time possible, without doing anything illegal. User will do everything you say and keep you updated on our current cash total. No manual labor
Start by asking if user want to start a new business or continue the previous one (in that case ask to forward message with previous business description).
Then ask how many dollars user has as a capital initial.
+4 -3
View File
@@ -1,13 +1,14 @@
telegram_token: ""
openai_api_key: ""
use_chatgpt_api: true
allowed_telegram_usernames: [] # if empty, the bot is available to anyone. pass a username string to allow it and/or user ids as integers
openai_api_base: null # leave null to use default api base or you can put your own base url here
allowed_telegram_usernames: [] # if empty, the bot is available to anyone. pass a username string to allow it and/or user ids as positive integers and/or channel ids as negative integers
new_dialog_timeout: 600 # new dialog starts after timeout (in seconds)
return_n_generated_images: 1
n_chat_modes_per_page: 5
image_size: "512x512" # the image size for image generation. Generated images can have a size of 256x256, 512x512, or 1024x1024 pixels. Smaller sizes are faster to generate.
enable_message_streaming: true # if set, messages will be shown to user word-by-word
# prices
chatgpt_price_per_1000_tokens: 0.002
gpt_price_per_1000_tokens: 0.02
whisper_price_per_1_min: 0.006
whisper_price_per_1_min: 0.006
+41 -2
View File
@@ -1,4 +1,4 @@
available_text_models: ["gpt-3.5-turbo", "gpt-4", "text-davinci-003"]
available_text_models: ["gpt-3.5-turbo", "gpt-3.5-turbo-16k", "gpt-4-1106-preview", "gpt-4-vision-preview", "gpt-4", "text-davinci-003"]
info:
gpt-3.5-turbo:
@@ -6,7 +6,7 @@ info:
name: ChatGPT
description: ChatGPT is that well-known model. It's <b>fast</b> and <b>cheap</b>. Ideal for everyday tasks. If there are some tasks it can't handle, try the <b>GPT-4</b>.
price_per_1000_input_tokens: 0.002
price_per_1000_input_tokens: 0.0015
price_per_1000_output_tokens: 0.002
scores:
@@ -14,6 +14,19 @@ info:
Fast: 5
Cheap: 5
gpt-3.5-turbo-16k:
type: chat_completion
name: GPT-16K
description: ChatGPT is that well-known model. It's <b>fast</b> and <b>cheap</b>. Ideal for everyday tasks. If there are some tasks it can't handle, try the <b>GPT-4</b>.
price_per_1000_input_tokens: 0.003
price_per_1000_output_tokens: 0.004
scores:
Smart: 3
Fast: 5
Cheap: 5
gpt-4:
type: chat_completion
name: GPT-4
@@ -27,6 +40,32 @@ info:
Fast: 2
Cheap: 2
gpt-4-1106-preview:
type: chat_completion
name: GPT-4 Turbo
description: GPT-4 Turbo is a <b>faster</b> and <b>cheaper</b> version of GPT-4. It's as smart as GPT-4, so you should use it instead of GPT-4.
price_per_1000_input_tokens: 0.01
price_per_1000_output_tokens: 0.03
scores:
smart: 5
fast: 4
cheap: 3
gpt-4-vision-preview:
type: chat_completion
name: GPT-4 Vision
description: Ability to <b>understand images</b>, in addition to all other GPT-4 Turbo capabilties.
price_per_1000_input_tokens: 0.01
price_per_1000_output_tokens: 0.03
scores:
smart: 5
fast: 4
cheap: 3
text-davinci-003:
type: completion
name: GPT-3.5
+1 -2
View File
@@ -1,7 +1,6 @@
python-telegram-bot[rate-limiter]==20.1
openai>=0.27.0
openai==0.28.1
tiktoken>=0.3.0
PyYAML==6.0
pymongo==4.3.3
python-dotenv==0.21.0
pydub==0.25.1
-83
View File
@@ -1,83 +0,0 @@
# ❤️ Donate
This project is done on **pure enthusiasm** true Open Source. Your donations **really motivate** me to support the project and **add new features**. If you've donated, text me on [Telegram](https://t.me/karfly) I want to thank you personally!
*At the moment only crypto donations are available. Soon I will add PayPal option*
<a href="#btc" alt="BTC"><img src="https://img.shields.io/badge/Donate-Bitcoin-F2A900?logo=bitcoin" /></a>
<a href="#eth" alt="ETH"><img src="https://img.shields.io/badge/Donate-Ethereum-51105E?logo=ethereum" /></a>
<a href="#bnb-bsc-20" alt="BNB"><img src="https://img.shields.io/badge/Donate-BNB-F3BA2F?logo=binance" /></a>
<a href="#tron" alt="TRON"><img src="https://img.shields.io/badge/Donate-TRON-7DFDFE?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2MTAgNjEwIiB3aWR0aD0iMjUwMCIgaGVpZ2h0PSIyNTAwIj48Y2lyY2xlIGN4PSIzMDUiIGN5PSIzMDUiIHI9IjMwNSIvPjxwYXRoIGQ9Ik01MDUuNCAyMTQuN2MtMTcuMy0xMi4xLTM1LjgtMjUtNTMuOS0zNy44LS40LS4zLS44LS42LTEuMy0uOS0yLTEuNS00LjMtMy4xLTcuMS00bC0uMi0uMWMtNDguNC0xMS43LTk3LjYtMjMuNy0xNDUuMi0zNS4zLTQzLjItMTAuNS04Ni4zLTIxLTEyOS41LTMxLjUtMS4xLS4zLTIuMi0uNi0zLjQtLjktMy45LTEuMS04LjQtMi4zLTEzLjItMS43LTEuNC4yLTIuNi43LTMuNyAxLjRsLTEuMiAxYy0xLjkgMS44LTIuOSA0LjEtMy40IDUuNGwtLjMuOHY0LjZsLjIuN2MyNy4zIDc2LjUgNTUuMyAxNTQuMSA4Mi4zIDIyOS4yIDIwLjggNTcuOCA0Mi40IDExNy43IDYzLjUgMTc2LjUgMS4zIDQgNSA2LjYgOS42IDdoMWM0LjMgMCA4LjEtMi4xIDEwLTUuNWw3OS4yLTExNS41YzE5LjMtMjguMSAzOC42LTU2LjMgNTcuOS04NC40IDcuOS0xMS41IDE1LjgtMjMuMSAyMy43LTM0LjYgMTMtMTkgMjYuNC0zOC42IDM5LjctNTcuN2wuNy0xdi0xLjJjLjMtMy41LjQtMTAuNy01LjQtMTQuNW0tOTIuOCA0Mi4xYy0xOC42IDkuNy0zNy42IDE5LjctNTYuNyAyOS42IDExLjEtMTEuOSAyMi4zLTIzLjkgMzMuNC0zNS44IDEzLjktMTUgMjguNC0zMC41IDQyLjYtNDUuN2wuMy0uM2MxLjItMS42IDIuNy0zLjEgNC4zLTQuNyAxLjEtMS4xIDIuMy0yLjIgMy40LTMuNSA3LjQgNS4xIDE0LjkgMTAuMyAyMi4xIDE1LjQgNS4yIDMuNyAxMC41IDcuNCAxNS45IDExLjEtMjIgMTEuMi00NCAyMi43LTY1LjMgMzMuOW0tNDcuOC00LjhjLTE0LjMgMTUuNS0yOS4xIDMxLjQtNDMuOCA0Ny4xLTI4LjUtMzQuNi01Ny42LTY5LjctODUuOC0xMDMuNi0xMi44LTE1LjQtMjUuNy0zMC45LTM4LjUtNDYuM2wtLjEtLjFjLTIuOS0zLjMtNS43LTYuOS04LjUtMTAuMy0xLjgtMi4zLTMuNy00LjUtNS42LTYuOCAxMS42IDMgMjMuMyA1LjggMzQuOCA4LjUgMTAuMSAyLjQgMjAuNiA0LjkgMzAuOSA3LjUgNTggMTQuMSAxMTYuMSAyOC4yIDE3NC4xIDQyLjMtMTkuMyAyMC42LTM4LjcgNDEuNS01Ny41IDYxLjdtLTUwLjMgMTk0LjljMS4xLTEwLjUgMi4zLTIxLjMgMy4zLTMxLjkuOS04LjUgMS44LTE3LjIgMi43LTI1LjUgMS40LTEzLjMgMi45LTI3LjEgNC4xLTQwLjZsLjMtMi40YzEtOC42IDItMTcuNSAyLjYtMjYuNCAxLjEtLjYgMi4zLTEuMiAzLjYtMS43IDEuNS0uNyAzLTEuMyA0LjUtMi4yIDIzLjEtMTIuMSA0Ni4yLTI0LjIgNjkuNC0zNi4yIDIzLjEtMTIgNDYuOC0yNC40IDcwLjMtMzYuNy0yMS40IDMxLTQyLjkgNjIuMy02My43IDkyLjgtMTcuOSAyNi4xLTM2LjMgNTMtNTQuNiA3OS41LTcuMiAxMC42LTE0LjcgMjEuNC0yMS44IDMxLjgtOCAxMS42LTE2LjIgMjMuNS0yNC4yIDM1LjQgMS0xMiAyLjItMjQuMSAzLjUtMzUuOU0xNzUuMSAxNTUuNmMtMS4zLTMuNi0yLjctNy4zLTMuOS0xMC44IDI3IDMyLjYgNTQuMiA2NS40IDgwLjcgOTcuMiAxMy43IDE2LjUgMjcuNCAzMi45IDQxLjEgNDkuNSAyLjcgMy4xIDUuNCA2LjQgOCA5LjYgMy40IDQuMSA2LjggOC40IDEwLjUgMTIuNS0xLjIgMTAuMy0yLjIgMjAuNy0zLjMgMzAuNy0uNyA3LTEuNCAxNC0yLjIgMjEuMXYuMWMtLjMgNC41LS45IDktMS40IDEzLjQtLjcgNi4xLTIuMyAxOS45LTIuMyAxOS45bC0uMS43Yy0xLjggMjAuMi00IDQwLjYtNi4xIDYwLjQtLjkgOC4yLTEuNyAxNi42LTIuNiAyNS0uNS0xLjUtMS4xLTMtMS42LTQuNC0xLjUtNC0zLTguMi00LjQtMTIuM2wtMTAuNy0yOS43QzI0Mi45IDM0NC4yIDIwOSAyNTAgMTc1LjEgMTU1LjYiIGZpbGw9IiNmZmYiLz48L3N2Zz4=" /></a>
<a href="#usdt-erc-20" alt="USDT (ERC-20)"><img src="https://img.shields.io/badge/Donate-USDT%20(ERC--20)-26A17B?logo=tether" /></a>
<a href="#usdt-trc-20" alt="USDT (TRC-20)"><img src="https://img.shields.io/badge/Donate-USDT%20(TRC--20)-26A17B?logo=tether" /></a>
<a href="#usdc-erc-20" alt="USDC (ERC-20)"><img src="https://img.shields.io/badge/Donate-USDC%20(ERC--20)-2775CA?logo=data:image/svg+xml;base64,PHN2ZyBkYXRhLW5hbWU9Ijg2OTc3Njg0LTEyZGItNDg1MC04ZjMwLTIzM2E3YzI2N2QxMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMjAwMCAyMDAwIj4KICA8cGF0aCBkPSJNMTAwMCAyMDAwYzU1NC4xNyAwIDEwMDAtNDQ1LjgzIDEwMDAtMTAwMFMxNTU0LjE3IDAgMTAwMCAwIDAgNDQ1LjgzIDAgMTAwMHM0NDUuODMgMTAwMCAxMDAwIDEwMDB6IiBmaWxsPSIjMjc3NWNhIi8+CiAgPHBhdGggZD0iTTEyNzUgMTE1OC4zM2MwLTE0NS44My04Ny41LTE5NS44My0yNjIuNS0yMTYuNjYtMTI1LTE2LjY3LTE1MC01MC0xNTAtMTA4LjM0czQxLjY3LTk1LjgzIDEyNS05NS44M2M3NSAwIDExNi42NyAyNSAxMzcuNSA4Ny41IDQuMTcgMTIuNSAxNi42NyAyMC44MyAyOS4xNyAyMC44M2g2Ni42NmMxNi42NyAwIDI5LjE3LTEyLjUgMjkuMTctMjkuMTZ2LTQuMTdjLTE2LjY3LTkxLjY3LTkxLjY3LTE2Mi41LTE4Ny41LTE3MC44M3YtMTAwYzAtMTYuNjctMTIuNS0yOS4xNy0zMy4zMy0zMy4zNGgtNjIuNWMtMTYuNjcgMC0yOS4xNyAxMi41LTMzLjM0IDMzLjM0djk1LjgzYy0xMjUgMTYuNjctMjA0LjE2IDEwMC0yMDQuMTYgMjA0LjE3IDAgMTM3LjUgODMuMzMgMTkxLjY2IDI1OC4zMyAyMTIuNSAxMTYuNjcgMjAuODMgMTU0LjE3IDQ1LjgzIDE1NC4xNyAxMTIuNXMtNTguMzQgMTEyLjUtMTM3LjUgMTEyLjVjLTEwOC4zNCAwLTE0NS44NC00NS44NC0xNTguMzQtMTA4LjM0LTQuMTYtMTYuNjYtMTYuNjYtMjUtMjkuMTYtMjVoLTcwLjg0Yy0xNi42NiAwLTI5LjE2IDEyLjUtMjkuMTYgMjkuMTd2NC4xN2MxNi42NiAxMDQuMTYgODMuMzMgMTc5LjE2IDIyMC44MyAyMDB2MTAwYzAgMTYuNjYgMTIuNSAyOS4xNiAzMy4zMyAzMy4zM2g2Mi41YzE2LjY3IDAgMjkuMTctMTIuNSAzMy4zNC0zMy4zM3YtMTAwYzEyNS0yMC44NCAyMDguMzMtMTA4LjM0IDIwOC4zMy0yMjAuODR6IiBmaWxsPSIjZmZmIi8+CiAgPHBhdGggZD0iTTc4Ny41IDE1OTUuODNjLTMyNS0xMTYuNjYtNDkxLjY3LTQ3OS4xNi0zNzAuODMtODAwIDYyLjUtMTc1IDIwMC0zMDguMzMgMzcwLjgzLTM3MC44MyAxNi42Ny04LjMzIDI1LTIwLjgzIDI1LTQxLjY3VjMyNWMwLTE2LjY3LTguMzMtMjkuMTctMjUtMzMuMzMtNC4xNyAwLTEyLjUgMC0xNi42NyA0LjE2LTM5NS44MyAxMjUtNjEyLjUgNTQ1Ljg0LTQ4Ny41IDk0MS42NyA3NSAyMzMuMzMgMjU0LjE3IDQxMi41IDQ4Ny41IDQ4Ny41IDE2LjY3IDguMzMgMzMuMzQgMCAzNy41LTE2LjY3IDQuMTctNC4xNiA0LjE3LTguMzMgNC4xNy0xNi42NnYtNTguMzRjMC0xMi41LTEyLjUtMjkuMTYtMjUtMzcuNXpNMTIyOS4xNyAyOTUuODNjLTE2LjY3LTguMzMtMzMuMzQgMC0zNy41IDE2LjY3LTQuMTcgNC4xNy00LjE3IDguMzMtNC4xNyAxNi42N3Y1OC4zM2MwIDE2LjY3IDEyLjUgMzMuMzMgMjUgNDEuNjcgMzI1IDExNi42NiA0OTEuNjcgNDc5LjE2IDM3MC44MyA4MDAtNjIuNSAxNzUtMjAwIDMwOC4zMy0zNzAuODMgMzcwLjgzLTE2LjY3IDguMzMtMjUgMjAuODMtMjUgNDEuNjdWMTcwMGMwIDE2LjY3IDguMzMgMjkuMTcgMjUgMzMuMzMgNC4xNyAwIDEyLjUgMCAxNi42Ny00LjE2IDM5NS44My0xMjUgNjEyLjUtNTQ1Ljg0IDQ4Ny41LTk0MS42Ny03NS0yMzcuNS0yNTguMzQtNDE2LjY3LTQ4Ny41LTQ5MS42N3oiIGZpbGw9IiNmZmYiLz4KPC9zdmc+Cg==" /></a>
<a href="#usdc-trc-20" alt="USDC (TRC-20)"><img src="https://img.shields.io/badge/Donate-USDC%20(TRC--20)-2775CA?logo=data:image/svg+xml;base64,PHN2ZyBkYXRhLW5hbWU9Ijg2OTc3Njg0LTEyZGItNDg1MC04ZjMwLTIzM2E3YzI2N2QxMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMjAwMCAyMDAwIj4KICA8cGF0aCBkPSJNMTAwMCAyMDAwYzU1NC4xNyAwIDEwMDAtNDQ1LjgzIDEwMDAtMTAwMFMxNTU0LjE3IDAgMTAwMCAwIDAgNDQ1LjgzIDAgMTAwMHM0NDUuODMgMTAwMCAxMDAwIDEwMDB6IiBmaWxsPSIjMjc3NWNhIi8+CiAgPHBhdGggZD0iTTEyNzUgMTE1OC4zM2MwLTE0NS44My04Ny41LTE5NS44My0yNjIuNS0yMTYuNjYtMTI1LTE2LjY3LTE1MC01MC0xNTAtMTA4LjM0czQxLjY3LTk1LjgzIDEyNS05NS44M2M3NSAwIDExNi42NyAyNSAxMzcuNSA4Ny41IDQuMTcgMTIuNSAxNi42NyAyMC44MyAyOS4xNyAyMC44M2g2Ni42NmMxNi42NyAwIDI5LjE3LTEyLjUgMjkuMTctMjkuMTZ2LTQuMTdjLTE2LjY3LTkxLjY3LTkxLjY3LTE2Mi41LTE4Ny41LTE3MC44M3YtMTAwYzAtMTYuNjctMTIuNS0yOS4xNy0zMy4zMy0zMy4zNGgtNjIuNWMtMTYuNjcgMC0yOS4xNyAxMi41LTMzLjM0IDMzLjM0djk1LjgzYy0xMjUgMTYuNjctMjA0LjE2IDEwMC0yMDQuMTYgMjA0LjE3IDAgMTM3LjUgODMuMzMgMTkxLjY2IDI1OC4zMyAyMTIuNSAxMTYuNjcgMjAuODMgMTU0LjE3IDQ1LjgzIDE1NC4xNyAxMTIuNXMtNTguMzQgMTEyLjUtMTM3LjUgMTEyLjVjLTEwOC4zNCAwLTE0NS44NC00NS44NC0xNTguMzQtMTA4LjM0LTQuMTYtMTYuNjYtMTYuNjYtMjUtMjkuMTYtMjVoLTcwLjg0Yy0xNi42NiAwLTI5LjE2IDEyLjUtMjkuMTYgMjkuMTd2NC4xN2MxNi42NiAxMDQuMTYgODMuMzMgMTc5LjE2IDIyMC44MyAyMDB2MTAwYzAgMTYuNjYgMTIuNSAyOS4xNiAzMy4zMyAzMy4zM2g2Mi41YzE2LjY3IDAgMjkuMTctMTIuNSAzMy4zNC0zMy4zM3YtMTAwYzEyNS0yMC44NCAyMDguMzMtMTA4LjM0IDIwOC4zMy0yMjAuODR6IiBmaWxsPSIjZmZmIi8+CiAgPHBhdGggZD0iTTc4Ny41IDE1OTUuODNjLTMyNS0xMTYuNjYtNDkxLjY3LTQ3OS4xNi0zNzAuODMtODAwIDYyLjUtMTc1IDIwMC0zMDguMzMgMzcwLjgzLTM3MC44MyAxNi42Ny04LjMzIDI1LTIwLjgzIDI1LTQxLjY3VjMyNWMwLTE2LjY3LTguMzMtMjkuMTctMjUtMzMuMzMtNC4xNyAwLTEyLjUgMC0xNi42NyA0LjE2LTM5NS44MyAxMjUtNjEyLjUgNTQ1Ljg0LTQ4Ny41IDk0MS42NyA3NSAyMzMuMzMgMjU0LjE3IDQxMi41IDQ4Ny41IDQ4Ny41IDE2LjY3IDguMzMgMzMuMzQgMCAzNy41LTE2LjY3IDQuMTctNC4xNiA0LjE3LTguMzMgNC4xNy0xNi42NnYtNTguMzRjMC0xMi41LTEyLjUtMjkuMTYtMjUtMzcuNXpNMTIyOS4xNyAyOTUuODNjLTE2LjY3LTguMzMtMzMuMzQgMC0zNy41IDE2LjY3LTQuMTcgNC4xNy00LjE3IDguMzMtNC4xNyAxNi42N3Y1OC4zM2MwIDE2LjY3IDEyLjUgMzMuMzMgMjUgNDEuNjcgMzI1IDExNi42NiA0OTEuNjcgNDc5LjE2IDM3MC44MyA4MDAtNjIuNSAxNzUtMjAwIDMwOC4zMy0zNzAuODMgMzcwLjgzLTE2LjY3IDguMzMtMjUgMjAuODMtMjUgNDEuNjdWMTcwMGMwIDE2LjY3IDguMzMgMjkuMTcgMjUgMzMuMzMgNC4xNyAwIDEyLjUgMCAxNi42Ny00LjE2IDM5NS44My0xMjUgNjEyLjUtNTQ1Ljg0IDQ4Ny41LTk0MS42Ny03NS0yMzcuNS0yNTguMzQtNDE2LjY3LTQ4Ny41LTQ5MS42N3oiIGZpbGw9IiNmZmYiLz4KPC9zdmc+Cg==" /></a>
# BTC
Address: `bc1q7q0ckuteu4t7mudeqrq4m6rzgfery4r0xk4rym`
Scan it with your crypto wallet:
![BTC QR code](qrcodes/btc.png)
# ETH
Address: `0xAca71cCdE4F4Ef8f82d437Be05A8EA705A66d5e8`
Scan it with your crypto wallet:
![ETH QR code](qrcodes/eth.png)
# BNB (BSC-20)
Address: `0xAca71cCdE4F4Ef8f82d437Be05A8EA705A66d5e8`
Scan it with your crypto wallet:
![BNB (BSC-20) QR code](qrcodes/bnb_bsc_20.png)
# TRON
Address: `TPUT2uL5fannBLcEq64pfvHftZB2a17Nze`
Scan it with your crypto wallet:
![TRON QR code](qrcodes/tron.png)
# USDT (ERC-20)
Address: `0xAca71cCdE4F4Ef8f82d437Be05A8EA705A66d5e8`
Scan it with your crypto wallet:
![USDT (ERC-20) QR code](qrcodes/usdt_erc_20.png)
# USDT (TRC-20)
Address: `TPUT2uL5fannBLcEq64pfvHftZB2a17Nze`
Scan it with your crypto wallet:
![USDT (TRC-20) QR code](qrcodes/usdt_trc_20.png)
# USDC (ERC-20)
Address: `0xAca71cCdE4F4Ef8f82d437Be05A8EA705A66d5e8`
Scan it with your crypto wallet:
![USDC (ERC-20) QR code](qrcodes/usdc_erc_20.png)
# USDC (TRC-20)
Address: `TPUT2uL5fannBLcEq64pfvHftZB2a17Nze`
Scan it with your crypto wallet:
![USDC (TRC-20) QR code](qrcodes/usdc_trc_20.png)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 449 B