1 Commits

Author SHA1 Message Date
Karim Iskakov 494ec7800a Change http version 2023-04-25 04:30:23 -05:00
20 changed files with 178 additions and 471 deletions
Vendored
BIN
View File
Binary file not shown.
-2
View File
@@ -1,2 +0,0 @@
mongodb
+13 -16
View File
@@ -1,23 +1,20 @@
FROM python:3.8-slim
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/*
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 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 apt-get update
RUN apt-get install -y python3 python3-pip python-dev build-essential python3-venv ffmpeg
COPY . /code
RUN mkdir -p /code
ADD . /code
WORKDIR /code
CMD ["bash"]
RUN pip3 install -r requirements.txt
CMD ["bash"]
+22 -28
View File
@@ -10,6 +10,9 @@
<a href="https://t.me/chatgpt_karfly_bot?start=source=github" alt="Run Telegram Bot shield"><img src="https://img.shields.io/badge/RUN-Telegram%20Bot-blue?logo=data:image/svg+xml;base64,PHN2ZyBpZD0iTGl2ZWxsb18xIiBkYXRhLW5hbWU9IkxpdmVsbG8gMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmlld0JveD0iMCAwIDI0MCAyNDAiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50IiB4MT0iMTIwIiB5MT0iMjQwIiB4Mj0iMTIwIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjMWQ5M2QyIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMzhiMGUzIi8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHRpdGxlPlRlbGVncmFtX2xvZ288L3RpdGxlPjxjaXJjbGUgY3g9IjEyMCIgY3k9IjEyMCIgcj0iMTIwIiBmaWxsPSJ1cmwoI2xpbmVhci1ncmFkaWVudCkiLz48cGF0aCBkPSJNODEuMjI5LDEyOC43NzJsMTQuMjM3LDM5LjQwNnMxLjc4LDMuNjg3LDMuNjg2LDMuNjg3LDMwLjI1NS0yOS40OTIsMzAuMjU1LTI5LjQ5MmwzMS41MjUtNjAuODlMODEuNzM3LDExOC42WiIgZmlsbD0iI2M4ZGFlYSIvPjxwYXRoIGQ9Ik0xMDAuMTA2LDEzOC44NzhsLTIuNzMzLDI5LjA0NnMtMS4xNDQsOC45LDcuNzU0LDAsMTcuNDE1LTE1Ljc2MywxNy40MTUtMTUuNzYzIiBmaWxsPSIjYTljNmQ4Ii8+PHBhdGggZD0iTTgxLjQ4NiwxMzAuMTc4LDUyLjIsMTIwLjYzNnMtMy41LTEuNDItMi4zNzMtNC42NGMuMjMyLS42NjQuNy0xLjIyOSwyLjEtMi4yLDYuNDg5LTQuNTIzLDEyMC4xMDYtNDUuMzYsMTIwLjEwNi00NS4zNnMzLjIwOC0xLjA4MSw1LjEtLjM2MmEyLjc2NiwyLjc2NiwwLDAsMSwxLjg4NSwyLjA1NSw5LjM1Nyw5LjM1NywwLDAsMSwuMjU0LDIuNTg1Yy0uMDA5Ljc1Mi0uMSwxLjQ0OS0uMTY5LDIuNTQyLS42OTIsMTEuMTY1LTIxLjQsOTQuNDkzLTIxLjQsOTQuNDkzcy0xLjIzOSw0Ljg3Ni01LjY3OCw1LjA0M0E4LjEzLDguMTMsMCwwLDEsMTQ2LjEsMTcyLjVjLTguNzExLTcuNDkzLTM4LjgxOS0yNy43MjctNDUuNDcyLTMyLjE3N2ExLjI3LDEuMjcsMCwwLDEtLjU0Ni0uOWMtLjA5My0uNDY5LjQxNy0xLjA1LjQxNy0xLjA1czUyLjQyNi00Ni42LDUzLjgyMS01MS40OTJjLjEwOC0uMzc5LS4zLS41NjYtLjg0OC0uNC0zLjQ4MiwxLjI4MS02My44NDQsMzkuNC03MC41MDYsNDMuNjA3QTMuMjEsMy4yMSwwLDAsMSw4MS40ODYsMTMwLjE3OFoiIGZpbGw9IiNmZmYiLz48L3N2Zz4=" width="230"/></a>
</p>
<p align="center">
<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" width="100"/></a>
</p>
We all love [chat.openai.com](https://chat.openai.com), but... It's TERRIBLY laggy, has daily limits, and is only accessible through an archaic web interface.
@@ -21,8 +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 and GPT-4 Turbo support
- GPT-4 Vision support
- GPT-4 support
- Group Chat support (/help_group_chat to get instructions)
- DALLE 2 (choose 👩‍🎨 Artist mode to generate images)
- Voice message recognition
@@ -38,6 +40,17 @@ 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
@@ -47,10 +60,7 @@ You can deploy your own bot, or use mine: [@chatgpt_karfly_bot](https://t.me/cha
- *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).
- *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)
- *2 Apt 2024*: Added [GPT-4 Vision](https://platform.openai.com/docs/guides/vision) support
- *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`).
## Bot commands
- `/retry` Regenerate last bot answer
@@ -77,37 +87,21 @@ You can deploy your own bot, or use mine: [@chatgpt_karfly_bot](https://t.me/cha
```
## ❤️ Top donations
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$**
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>
1. [Sem](https://t.me/sembrestels). Donation: **100$**
1. [Miksolo](https://t.me/Miksolo). Donation: **81$**
2. [Ryo](https://t.me/ryokihara). Donation: **80$**
*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$**
3. [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! 😊
1. [Sebastian](https://t.me/dell1503). Donation: **55$**
4. [Sebastian](https://t.me/dell1503). Donation: **55$**
1. [Alexander Zimin](https://t.me/azimin). Donation: **50$**
5. [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).
6. [Hans Blinken](https://t.me/hblink). Donation: **10$**
## References
1. [*Build ChatGPT from GPT-3*](https://learnprompting.org/docs/applied_prompting/build_chatgpt)
+33 -197
View File
@@ -1,9 +1,12 @@
import io
import os
import logging
import asyncio
import traceback
import html
import json
import tempfile
import pydub
from pathlib import Path
from datetime import datetime
import openai
@@ -31,7 +34,6 @@ import config
import database
import openai_utils
import base64
# setup
db = database.Database()
@@ -92,7 +94,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) or isinstance(n_used_tokens, float): # old format
if isinstance(n_used_tokens, int): # old format
new_n_used_tokens = {
"gpt-3.5-turbo": {
"n_input_tokens": 0,
@@ -178,161 +180,6 @@ 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 or ''
# send typing action
await update.message.chat.send_action(action="typing")
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)
@@ -360,8 +207,6 @@ 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:
@@ -372,6 +217,7 @@ 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
@@ -406,12 +252,11 @@ 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
@@ -425,12 +270,11 @@ 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": [{"type": "text", "text": _message}], "bot": answer, "date": datetime.now()}
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],
@@ -459,19 +303,7 @@ 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]:
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()
)
task = asyncio.create_task(message_handle_fn())
user_tasks[user_id] = task
try:
@@ -510,15 +342,25 @@ async def voice_message_handle(update: Update, context: CallbackContext):
db.set_user_attribute(user_id, "last_interaction", datetime.now())
voice = update.message.voice
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
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 = ""
transcribed_text = await openai_utils.transcribe_audio(buf)
text = f"🎤: <i>{transcribed_text}</i>"
await update.message.reply_text(text, parse_mode=ParseMode.HTML)
@@ -540,7 +382,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, size=config.image_size)
image_urls = await openai_utils.generate_images(message, n_images=config.return_n_generated_images)
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?"
@@ -563,7 +405,6 @@ 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 ✅")
@@ -834,19 +675,14 @@ 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)]
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)
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)
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))
+1 -2
View File
@@ -14,12 +14,11 @@ 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"]
openai_api_base = config_yaml.get("openai_api_base", None)
use_chatgpt_api = config_yaml.get("use_chatgpt_api", True)
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']}"
+17 -178
View File
@@ -1,17 +1,8 @@
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 = {
@@ -19,14 +10,13 @@ OPENAI_COMPLETION_OPTIONS = {
"max_tokens": 1000,
"top_p": 1,
"frequency_penalty": 0,
"presence_penalty": 0,
"request_timeout": 60.0,
"presence_penalty": 0
}
class ChatGPT:
def __init__(self, model="gpt-3.5-turbo"):
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}"
assert model in {"text-davinci-003", "gpt-3.5-turbo", "gpt-4"}, f"Unknown model: {model}"
self.model = model
async def send_message(self, message, dialog_messages=[], chat_mode="assistant"):
@@ -37,9 +27,8 @@ class ChatGPT:
answer = None
while answer is None:
try:
if self.model in {"gpt-3.5-turbo-16k", "gpt-3.5-turbo", "gpt-4", "gpt-4-1106-preview", "gpt-4-vision-preview"}:
if self.model in {"gpt-3.5-turbo", "gpt-4"}:
messages = self._generate_prompt_messages(message, dialog_messages, chat_mode)
r = await openai.ChatCompletion.acreate(
model=self.model,
messages=messages,
@@ -78,9 +67,8 @@ class ChatGPT:
answer = None
while answer is None:
try:
if self.model in {"gpt-3.5-turbo-16k", "gpt-3.5-turbo", "gpt-4", "gpt-4-1106-preview"}:
if self.model in {"gpt-3.5-turbo", "gpt-4"}:
messages = self._generate_prompt_messages(message, dialog_messages, chat_mode)
r_gen = await openai.ChatCompletion.acreate(
model=self.model,
messages=messages,
@@ -91,15 +79,11 @@ 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 = 0
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
elif self.model == "text-davinci-003":
prompt = self._generate_prompt(message, dialog_messages, chat_mode)
r_gen = await openai.Completion.acreate(
@@ -127,109 +111,6 @@ 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"
@@ -247,37 +128,14 @@ class ChatGPT:
return prompt
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):
def _generate_prompt_messages(self, message, dialog_messages, chat_mode):
prompt = config.chat_modes[chat_mode]["prompt_start"]
messages = [{"role": "system", "content": prompt}]
for dialog_message in dialog_messages:
messages.append({"role": "user", "content": dialog_message["user"]})
messages.append({"role": "assistant", "content": dialog_message["bot"]})
if image_buffer is not None:
messages.append(
{
"role": "user",
"content": [
{
"type": "text",
"text": message,
},
{
"type": "image",
"image": self._encode_image(image_buffer),
}
]
}
)
else:
messages.append({"role": "user", "content": message})
messages.append({"role": "user", "content": message})
return messages
@@ -288,21 +146,12 @@ 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-16k":
if model == "gpt-3.5-turbo":
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}")
@@ -310,20 +159,10 @@ class ChatGPT:
n_input_tokens = 0
for message in messages:
n_input_tokens += tokens_per_message
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
for key, value in message.items():
n_input_tokens += len(encoding.encode(value))
if key == "name":
n_input_tokens += tokens_per_name
n_input_tokens += 2
@@ -341,17 +180,17 @@ class ChatGPT:
return n_input_tokens, n_output_tokens
async def transcribe_audio(audio_file) -> str:
async def transcribe_audio(audio_file):
r = await openai.Audio.atranscribe("whisper-1", audio_file)
return r["text"] or ""
return r["text"]
async def generate_images(prompt, n_images=4, size="512x512"):
r = await openai.Image.acreate(prompt=prompt, n=n_images, size=size)
async def generate_images(prompt, n_images=4):
r = await openai.Image.acreate(prompt=prompt, n=n_images, size="512x512")
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())
+2 -2
View File
@@ -70,11 +70,11 @@ motivator:
money_maker:
name: 💰 Money Maker
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.
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.
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 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
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
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.
+3 -4
View File
@@ -1,14 +1,13 @@
telegram_token: ""
openai_api_key: ""
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
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
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
+2 -41
View File
@@ -1,4 +1,4 @@
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"]
available_text_models: ["gpt-3.5-turbo", "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.0015
price_per_1000_input_tokens: 0.002
price_per_1000_output_tokens: 0.002
scores:
@@ -14,19 +14,6 @@ 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
@@ -40,32 +27,6 @@ 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
+2 -1
View File
@@ -1,6 +1,7 @@
python-telegram-bot[rate-limiter]==20.1
openai==0.28.1
openai>=0.27.0
tiktoken>=0.3.0
PyYAML==6.0
pymongo==4.3.3
python-dotenv==0.21.0
pydub==0.25.1
+83
View File
@@ -0,0 +1,83 @@
# ❤️ 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.

After

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 B