Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 494ec7800a |
@@ -1,2 +0,0 @@
|
||||
mongodb
|
||||
|
||||
@@ -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"]
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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']}"
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||

|
||||
|
||||
# ETH
|
||||
|
||||
Address: `0xAca71cCdE4F4Ef8f82d437Be05A8EA705A66d5e8`
|
||||
|
||||
Scan it with your crypto wallet:
|
||||
|
||||

|
||||
|
||||
# BNB (BSC-20)
|
||||
|
||||
Address: `0xAca71cCdE4F4Ef8f82d437Be05A8EA705A66d5e8`
|
||||
|
||||
Scan it with your crypto wallet:
|
||||
|
||||

|
||||
|
||||
# TRON
|
||||
|
||||
Address: `TPUT2uL5fannBLcEq64pfvHftZB2a17Nze`
|
||||
|
||||
Scan it with your crypto wallet:
|
||||
|
||||

|
||||
|
||||
# USDT (ERC-20)
|
||||
|
||||
Address: `0xAca71cCdE4F4Ef8f82d437Be05A8EA705A66d5e8`
|
||||
|
||||
Scan it with your crypto wallet:
|
||||
|
||||

|
||||
|
||||
# USDT (TRC-20)
|
||||
|
||||
Address: `TPUT2uL5fannBLcEq64pfvHftZB2a17Nze`
|
||||
|
||||
Scan it with your crypto wallet:
|
||||
|
||||

|
||||
|
||||
# USDC (ERC-20)
|
||||
|
||||
Address: `0xAca71cCdE4F4Ef8f82d437Be05A8EA705A66d5e8`
|
||||
|
||||
Scan it with your crypto wallet:
|
||||
|
||||

|
||||
|
||||
# USDC (TRC-20)
|
||||
|
||||
Address: `TPUT2uL5fannBLcEq64pfvHftZB2a17Nze`
|
||||
|
||||
Scan it with your crypto wallet:
|
||||
|
||||

|
||||
|
||||
|
After Width: | Height: | Size: 449 B |
|
After Width: | Height: | Size: 442 B |
|
After Width: | Height: | Size: 449 B |
|
After Width: | Height: | Size: 449 B |
|
After Width: | Height: | Size: 449 B |
|
After Width: | Height: | Size: 449 B |
|
After Width: | Height: | Size: 449 B |
|
After Width: | Height: | Size: 449 B |