<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>@codeonplate</title><author><name>@codeonplate</name></author><id>https://teletype.in/atom/codeonplate</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/codeonplate?offset=0"></link><link rel="alternate" type="text/html" href="https://teletype.in/@codeonplate?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=codeonplate"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/codeonplate?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-04-05T04:08:41.899Z</updated><entry><id>codeonplate:_ZyiqBKzo41</id><link rel="alternate" type="text/html" href="https://teletype.in/@codeonplate/_ZyiqBKzo41?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=codeonplate"></link><title>Отправитель Телеграмм</title><published>2025-10-20T08:56:13.252Z</published><updated>2025-10-20T08:58:27.620Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img4.teletype.in/files/f4/c4/f4c4aa24-843c-4650-813f-cf69a6e88050.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/8c/f7/8cf78966-a90b-4a95-bf99-1fbebc7b916d.webp&quot;&gt;Простой Telegram-бот на Python3, который автоматически пересылает сообщения из одного чата в другой.</summary><content type="html">
  &lt;p id=&quot;NwS7&quot;&gt;Простой Telegram-бот на Python3, который автоматически пересылает сообщения из одного чата в другой.&lt;/p&gt;
  &lt;h2 id=&quot;7aO7&quot;&gt;Миграция с версии V1&lt;/h2&gt;
  &lt;p id=&quot;Ubq2&quot;&gt;v2 использует другой формат файла конфигурации. Для получения дополнительной информации обратитесь к разделу &lt;a href=&quot;https://github.com/MrMissx/Telegram_Forwarder#configuration&quot; target=&quot;_blank&quot;&gt;Конфигурация&lt;/a&gt;. Бот не запустится, если файл конфигурации имеет неправильный формат.&lt;/p&gt;
  &lt;h2 id=&quot;teqy&quot;&gt;Запуск бота&lt;/h2&gt;
  &lt;p id=&quot;zVdt&quot;&gt;После того как вы настроите конфигурацию (см. ниже), просто запустите:&lt;/p&gt;
  &lt;pre id=&quot;08Ia&quot;&gt;форвардер python -m&lt;/pre&gt;
  &lt;p id=&quot;VAIf&quot;&gt;или с помощью поэзии (рекомендуется)&lt;/p&gt;
  &lt;pre id=&quot;DSr4&quot;&gt;экспедитор поэтического пробега&lt;/pre&gt;
  &lt;h2 id=&quot;p9SF&quot;&gt;Настройка бота (перед запуском бота ознакомьтесь с приведенными ниже инструкциями!):&lt;/h2&gt;
  &lt;p id=&quot;snoo&quot;&gt;Telegram Forwarder поддерживает только Python 3.9 и более поздние версии.&lt;/p&gt;
  &lt;h3 id=&quot;jR9Y&quot;&gt;Конфигурация&lt;/h3&gt;
  &lt;p id=&quot;XWDr&quot;&gt;Для работы бота необходимы два файла &lt;code&gt;.env&lt;/code&gt; и &lt;code&gt;chat_list.json&lt;/code&gt;.&lt;/p&gt;
  &lt;h4 id=&quot;Dozi&quot;&gt;&lt;code&gt;.env&lt;/code&gt;&lt;/h4&gt;
  &lt;p id=&quot;bvBc&quot;&gt;Шаблон окружения можно найти в &lt;code&gt;sample.env&lt;/code&gt;. Переименуйте его в &lt;code&gt;.env&lt;/code&gt; и заполните значениями:&lt;/p&gt;
  &lt;ul id=&quot;x0Ig&quot;&gt;
    &lt;li id=&quot;TRph&quot;&gt;&lt;code&gt;BOT_TOKEN&lt;/code&gt; — Токен Telegram-бота. Вы можете получить его у &lt;a href=&quot;https://t.me/BotFather&quot; target=&quot;_blank&quot;&gt;@BotFather&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;eVEC&quot;&gt;&lt;code&gt;OWNER_ID&lt;/code&gt; — Целое число, состоящее из идентификатора вашего владельца.&lt;/li&gt;
    &lt;li id=&quot;NrUF&quot;&gt;&lt;code&gt;REMOVE_TAG&lt;/code&gt; — установите значение &lt;code&gt;True&lt;/code&gt; , если хотите удалить тег (&amp;quot;Переслано с xxxxx&amp;quot;) из пересланного сообщения.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h4 id=&quot;FvEE&quot;&gt;&lt;code&gt;chat_list.json&lt;/code&gt;&lt;/h4&gt;
  &lt;p id=&quot;tl8q&quot;&gt;Шаблон chat_list можно найти в &lt;code&gt;chat_list.sample.json&lt;/code&gt;. Переименуйте его в &lt;code&gt;chat_list.json&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;ivNw&quot;&gt;Этот файл содержит список чатов, в которые нужно пересылать сообщения и из которых нужно пересылать сообщения. Бот ожидает, что это будет массив объектов со следующей структурой:&lt;/p&gt;
  &lt;pre id=&quot;pvYv&quot;&gt;[
 {
 &amp;quot;источник&amp;quot;: -10012345678,
 &amp;quot;получатель&amp;quot;: [-10011111111, &amp;quot;-10022222222#123456&amp;quot;]
 }, 
{ 
 &amp;quot;источник&amp;quot;: &amp;quot;-10087654321#000000&amp;quot;, // Тема/Группа форума
    &amp;quot;пункт назначения&amp;quot;: [&amp;quot;-10033333333#654321&amp;quot;], 
 &amp;quot;фильтры&amp;quot;: [&amp;quot;word1&amp;quot;, &amp;quot;word2&amp;quot;] // сообщение, содержащее это слово, будет переадресовано
 },
 {
 &amp;quot;источник&amp;quot;: -10087654321,
 &amp;quot;получатель&amp;quot;: [-10033333333],
 &amp;quot;черный список&amp;quot;: [&amp;quot;слово3&amp;quot;, &amp;quot;слово4&amp;quot;] // сообщения, содержащие это слово, не будут пересылаться
 },
 {
 &amp;quot;источник&amp;quot;: -10087654321,
 &amp;quot;получатель&amp;quot;: [-10033333333],
 &amp;quot;фильтры&amp;quot;: [&amp;quot;word5&amp;quot;],
 &amp;quot;чёрный список&amp;quot;: [&amp;quot;word6&amp;quot;]
 // для пересылки сообщение должно содержать слово5 и не должно содержать слово6
 }
]&lt;/pre&gt;
  &lt;ul id=&quot;6Pk6&quot;&gt;
    &lt;li id=&quot;Jyya&quot;&gt;&lt;code&gt;source&lt;/code&gt; — Идентификатор чата, из которого нужно пересылать сообщения. Это может быть группа или канал.Если исходный чат является тематической группой, вы &lt;strong&gt;ДОЛЖНЫ&lt;/strong&gt; явно указать идентификатор темы. Если идентификатор темы не указан, бот будет игнорировать входящие сообщения из тематической группы.&lt;/li&gt;
    &lt;li id=&quot;s90w&quot;&gt;&lt;code&gt;destination&lt;/code&gt; — Массив идентификаторов чатов для пересылки сообщений. Это может быть группа или канал.Destenation поддерживает чат Topics. Вы можете использовать строку &lt;code&gt;#topicID&lt;/code&gt; для переадресации на определённую тему. Пример: &lt;code&gt;[-10011111111, &amp;quot;-10022222222#123456&amp;quot;]&lt;/code&gt;. С этой настройкой переадресация будет осуществляться в чат &lt;code&gt;-10022222222&lt;/code&gt; с темой &lt;code&gt;123456&lt;/code&gt; и в чат &lt;code&gt;-10011111111&lt;/code&gt; .&lt;/li&gt;
    &lt;li id=&quot;Ryv2&quot;&gt;&lt;code&gt;filters&lt;/code&gt; (Необязательно) — массив строк для фильтрации слов. Если сообщение содержит какую-либо из строк в массиве, оно &lt;strong&gt;БУДЕТ&lt;/strong&gt; переслано.&lt;/li&gt;
    &lt;li id=&quot;SPqr&quot;&gt;&lt;code&gt;blacklist&lt;/code&gt; (Необязательно) — массив строк для внесения слов в чёрный список. Если сообщение содержит какую-либо строку из массива, оно &lt;strong&gt;НЕ БУДЕТ&lt;/strong&gt; переслано.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;vDAX&quot;&gt;Вы можете добавить столько объектов, сколько захотите. Бот будет пересылать сообщения из всех чатов в поле &lt;code&gt;source&lt;/code&gt; во все чаты в поле &lt;code&gt;destination&lt;/code&gt; . Дубликаты допускаются, так как бот уже обрабатывает их.&lt;/p&gt;
  &lt;h3 id=&quot;grtf&quot;&gt;Зависимости от Python&lt;/h3&gt;
  &lt;p id=&quot;MMuk&quot;&gt;Установите необходимые зависимости Python, перейдя в каталог проекта и выполнив команду:&lt;/p&gt;
  &lt;pre id=&quot;Yb7i&quot;&gt;поэтическая установка - только основная&lt;/pre&gt;
  &lt;p id=&quot;Qcu1&quot;&gt;или с помощью pip&lt;/p&gt;
  &lt;pre id=&quot;oSPn&quot;&gt;pip3 install -r requirements.txt&lt;/pre&gt;
  &lt;p id=&quot;Y7uO&quot;&gt;Это позволит установить все необходимые пакеты Python.&lt;/p&gt;
  &lt;h3 id=&quot;PXln&quot;&gt;Запуск в контейнере Docker&lt;/h3&gt;
  &lt;h4 id=&quot;XfV0&quot;&gt;Требования&lt;/h4&gt;
  &lt;ul id=&quot;uRTK&quot;&gt;
    &lt;li id=&quot;gsCq&quot;&gt;Докер&lt;/li&gt;
    &lt;li id=&quot;CaC7&quot;&gt;создание докера&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;Hd8x&quot;&gt;Перед запуском убедитесь, что все настройки выполнены (&lt;code&gt;.env&lt;/code&gt; и &lt;code&gt;chat_list.json&lt;/code&gt;)!&lt;/p&gt;
  &lt;p id=&quot;Yh7X&quot;&gt;Затем просто введите команду:&lt;/p&gt;
  &lt;pre id=&quot;ajbQ&quot;&gt;настройка docker compose up -d&lt;/pre&gt;
  &lt;p id=&quot;oaS1&quot;&gt;Вы можете просмотреть журналы с помощью команды:&lt;/p&gt;
  &lt;pre id=&quot;QRZ6&quot;&gt;docker создает журналы -f&lt;/pre&gt;
  &lt;p id=&quot;qt0c&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;YY4P&quot;&gt;Сам код:&lt;/h2&gt;
  &lt;h2 id=&quot;kAZ7&quot;&gt;✉forwarder&lt;/h2&gt;
  &lt;h3 id=&quot;Dxnb&quot;&gt;                    ✉modules&lt;/h3&gt;
  &lt;h3 id=&quot;Ii6E&quot;&gt;                           ↪️__init__.py&lt;/h3&gt;
  &lt;pre id=&quot;tiI9&quot;&gt;from forwarder import LOGGER&lt;/pre&gt;
  &lt;pre id=&quot;pu13&quot;&gt;
def __list_all_modules():
    import glob
    from os.path import basename, dirname, isfile&lt;/pre&gt;
  &lt;pre id=&quot;8FC1&quot;&gt;    # This generates a list of modules in this folder for the * in __main__ to work.
    mod_paths = glob.glob(dirname(__file__) + &amp;quot;/*.py&amp;quot;)
    all_modules = [
        basename(f)[:-3]
        for f in mod_paths
        if isfile(f) and f.endswith(&amp;quot;.py&amp;quot;) and not f.endswith(&amp;quot;__init__.py&amp;quot;)
    ]&lt;/pre&gt;
  &lt;pre id=&quot;UGjN&quot;&gt;    return all_modules&lt;/pre&gt;
  &lt;pre id=&quot;njnr&quot;&gt;
ALL_MODULES = sorted(__list_all_modules())
LOGGER.info(&amp;quot;Modules to load: &amp;quot; + str(ALL_MODULES))&lt;/pre&gt;
  &lt;p id=&quot;iyTC&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;jAkm&quot;&gt; ↪️default.py&lt;/h3&gt;
  &lt;pre id=&quot;cEG0&quot;&gt;from telegram import Update
from telegram.ext import ContextTypes, CommandHandler, filters
from telegram.constants import ParseMode&lt;/pre&gt;
  &lt;pre id=&quot;GTNp&quot;&gt;from forwarder import bot, OWNER_ID&lt;/pre&gt;
  &lt;pre id=&quot;ot8f&quot;&gt;PM_START_TEXT = &amp;quot;&amp;quot;&amp;quot;
Hey {}, I&amp;#x27;m {}!
I&amp;#x27;m a bot used to forward messages from one chat to another.&lt;/pre&gt;
  &lt;pre id=&quot;mTrm&quot;&gt;To obtain a list of commands, use /help.
&amp;quot;&amp;quot;&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;qfqk&quot;&gt;PM_HELP_TEXT = &amp;quot;&amp;quot;&amp;quot;
Here is a list of usable commands:
 - /start : Starts the bot.
 - /help : Sends you this help message.&lt;/pre&gt;
  &lt;pre id=&quot;dm54&quot;&gt;just send /id in private chat/group/channel and i will reply it&amp;#x27;s id.
&amp;quot;&amp;quot;&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;erix&quot;&gt;
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
    chat = update.effective_chat
    message = update.effective_message
    user = update.effective_user
    if not (chat and message and user):
        return&lt;/pre&gt;
  &lt;pre id=&quot;wC3B&quot;&gt;    if chat.type == &amp;quot;private&amp;quot;:
        await message.reply_text(
            PM_START_TEXT.format(user.first_name, context.bot.first_name),
            parse_mode=ParseMode.HTML,
        )
    else:
        await message.reply_text(&amp;quot;I&amp;#x27;m up and running!&amp;quot;)&lt;/pre&gt;
  &lt;pre id=&quot;jWFS&quot;&gt;
async def help(update: Update, _):
    chat = update.effective_chat
    message = update.effective_message
    if not (chat and message):
        return&lt;/pre&gt;
  &lt;pre id=&quot;4F9b&quot;&gt;    if not chat.type == &amp;quot;private&amp;quot;:
        await message.reply_text(&amp;quot;Contact me via PM to get a list of usable commands.&amp;quot;)
    else:
        await message.reply_text(PM_HELP_TEXT)&lt;/pre&gt;
  &lt;pre id=&quot;n0ew&quot;&gt;
bot.add_handler(CommandHandler(&amp;quot;start&amp;quot;, start, filters=filters.User(OWNER_ID)))
bot.add_handler(CommandHandler(&amp;quot;help&amp;quot;, help, filters=filters.User(OWNER_ID)))&lt;/pre&gt;
  &lt;p id=&quot;EHQL&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;Ye09&quot;&gt;↪️forward.py&lt;/h3&gt;
  &lt;pre id=&quot;O6ji&quot;&gt;import asyncio
from typing import Union, Optional&lt;/pre&gt;
  &lt;pre id=&quot;3KMx&quot;&gt;from telegram import Update, Message, MessageId
from telegram.error import ChatMigrated, RetryAfter
from telegram.ext import MessageHandler, filters, ContextTypes&lt;/pre&gt;
  &lt;pre id=&quot;6Hrv&quot;&gt;from forwarder import bot, REMOVE_TAG, LOGGER
from forwarder.utils import get_destination, get_config, predicate_text&lt;/pre&gt;
  &lt;pre id=&quot;gSJA&quot;&gt;
async def send_message(
    message: Message, chat_id: int, thread_id: Optional[int] = None
) -&amp;gt; Union[MessageId, Message]:
    if REMOVE_TAG:
        return await message.copy(chat_id, message_thread_id=thread_id)  # type: ignore
    return await message.forward(chat_id, message_thread_id=thread_id)  # type: ignore&lt;/pre&gt;
  &lt;pre id=&quot;q6Ai&quot;&gt;
async def forwarder(update: Update, _: ContextTypes.DEFAULT_TYPE) -&amp;gt; None:
    message = update.effective_message
    source = update.effective_chat&lt;/pre&gt;
  &lt;pre id=&quot;8iDy&quot;&gt;    if not message or not source:
        return&lt;/pre&gt;
  &lt;pre id=&quot;Oc8v&quot;&gt;    dest = get_destination(source.id, message.message_thread_id)&lt;/pre&gt;
  &lt;pre id=&quot;CwKE&quot;&gt;    for config in dest:&lt;/pre&gt;
  &lt;pre id=&quot;wW0f&quot;&gt;        if config.filters:
            if not predicate_text(config.filters, message.text or &amp;quot;&amp;quot;):
                return
        if config.blacklist:
            if predicate_text(config.blacklist, message.text or &amp;quot;&amp;quot;):
                return&lt;/pre&gt;
  &lt;pre id=&quot;WquC&quot;&gt;        for chat in config.destination:
            LOGGER.debug(f&amp;quot;Forwarding message {source.id} to {chat}&amp;quot;)
            try:
                await send_message(message, chat.get_id(), chat.get_topic())
            except RetryAfter as err:
                LOGGER.warning(f&amp;quot;Rate limited, retrying in {err.retry_after} seconds&amp;quot;)
                await asyncio.sleep(err.retry_after + 0.2)
                await send_message(message, chat.get_id(), thread_id=chat.get_topic())
            except ChatMigrated as err:
                await send_message(message, err.new_chat_id)
                LOGGER.warning(
                    f&amp;quot;Chat {chat} has been migrated to {err.new_chat_id}!! Edit the config file!!&amp;quot;
                )
            except Exception as err:
                LOGGER.error(f&amp;quot;Failed to forward message from {source.id} to {chat} due to {err}&amp;quot;)&lt;/pre&gt;
  &lt;pre id=&quot;KuMR&quot;&gt;
FORWARD_HANDLER = MessageHandler(
    filters.Chat([config.source.get_id() for config in get_config()])
    &amp;amp; ~filters.COMMAND
    &amp;amp; ~filters.StatusUpdate.ALL,
    forwarder,
)
bot.add_handler(FORWARD_HANDLER)&lt;/pre&gt;
  &lt;p id=&quot;GpJq&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;fsQN&quot;&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;6e8d&quot;&gt;↪️misc.py&lt;/h3&gt;
  &lt;pre id=&quot;MSNq&quot;&gt;from telegram import Update
from telegram.constants import ParseMode
from telegram.ext import filters, MessageHandler&lt;/pre&gt;
  &lt;pre id=&quot;DHrd&quot;&gt;from forwarder import OWNER_ID, bot&lt;/pre&gt;
  &lt;pre id=&quot;MvGP&quot;&gt;
async def get_id(update: Update, _):
    message = update.effective_message
    chat = update.effective_chat
    if not message or not chat:
        return&lt;/pre&gt;
  &lt;pre id=&quot;BHWW&quot;&gt;    if chat.type == &amp;quot;private&amp;quot;:  # Private chat with the bot
        return await message.reply_text(f&amp;quot;🙋‍♂️ Your ID is &amp;#x60;{chat.id}&amp;#x60;&amp;quot;)&lt;/pre&gt;
  &lt;pre id=&quot;lt7o&quot;&gt;    result = f&amp;quot;👥 Chat ID: &amp;#x60;{chat.id}&amp;#x60;&amp;quot;
    if chat.is_forum:
        result += f&amp;quot;\n💬 Forum/Topic ID: &amp;#x60;{message.message_thread_id}&amp;#x60;&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;oVDj&quot;&gt;    if message.reply_to_message:
        forwarder = message.reply_to_message.from_user
        if message.reply_to_message.forward_from:  # Forwarded user
            sender = message.reply_to_message.forward_from
            result += f&amp;quot;🙋‍♂️ The original sender ({sender.first_name}), ID is: &amp;#x60;{sender.id}&amp;#x60;\n&amp;quot;
            result += f&amp;quot;⏩ The forwarder ({forwarder.first_name if forwarder else &amp;#x27;Unknown&amp;#x27;}) ID: &amp;#x60;{forwarder.id if forwarder else &amp;#x27;Unknown&amp;#x27;}&amp;#x60;&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;e1eo&quot;&gt;        if message.reply_to_message.forward_from_chat:  # Forwarded channel
            channel = message.reply_to_message.forward_from_chat
            result += f&amp;quot;💬 The channel {channel.title} ID: &amp;#x60;{channel.id}&amp;#x60;\n&amp;quot;
            result += f&amp;quot;⏩ The forwarder ({forwarder.first_name if forwarder else &amp;#x27;Unknown&amp;#x27;}) ID: &amp;#x60;{forwarder.id if forwarder else &amp;#x27;Unknown&amp;#x27;}&amp;#x60;&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;OrSq&quot;&gt;    return await message.reply_text(
        result,
        parse_mode=ParseMode.MARKDOWN,
    )&lt;/pre&gt;
  &lt;pre id=&quot;Qs9h&quot;&gt;
GET_ID_HANDLER = MessageHandler(
    filters.COMMAND &amp;amp; filters.Regex(r&amp;quot;^/id&amp;quot;) &amp;amp; (filters.User(OWNER_ID) | filters.ChatType.CHANNEL),
    get_id,
)&lt;/pre&gt;
  &lt;pre id=&quot;stZs&quot;&gt;bot.add_handler(GET_ID_HANDLER)&lt;/pre&gt;
  &lt;p id=&quot;Y2k9&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;OpjD&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;tBKO&quot;&gt;✉utils&lt;/h2&gt;
  &lt;h3 id=&quot;3ehk&quot;&gt;       ↪️__init__.py&lt;/h3&gt;
  &lt;pre id=&quot;Ie54&quot;&gt;from .chat import *
from .message import *&lt;/pre&gt;
  &lt;p id=&quot;P5Ew&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;VBk1&quot;&gt;       ↪️chat.py&lt;/h3&gt;
  &lt;pre id=&quot;HBBJ&quot;&gt;from typing import List, List, Union, Optional&lt;/pre&gt;
  &lt;pre id=&quot;nZgq&quot;&gt;from forwarder import CONFIG&lt;/pre&gt;
  &lt;pre id=&quot;GOMn&quot;&gt;
PARSED_CONFIG = []&lt;/pre&gt;
  &lt;pre id=&quot;eR4B&quot;&gt;
class ChatConfig:
    __chat: Union[str, int]&lt;/pre&gt;
  &lt;pre id=&quot;YZXC&quot;&gt;    def __init__(self, chat_id: Union[str, int]):
        self.__chat = chat_id&lt;/pre&gt;
  &lt;pre id=&quot;FnwA&quot;&gt;    def __repr__(self) -&amp;gt; str:
        if self.is_topic:
            return f&amp;quot;{self.get_id()}#{self.get_topic()}&amp;quot;
        return str(self.get_id())&lt;/pre&gt;
  &lt;pre id=&quot;R3sI&quot;&gt;    @property
    def is_topic(self) -&amp;gt; bool:
        if isinstance(self.__chat, str) and len(self.__chat.split(&amp;quot;#&amp;quot;)) == 2:
            return True
        return False&lt;/pre&gt;
  &lt;pre id=&quot;ycq1&quot;&gt;    def get_topic(self) -&amp;gt; Optional[int]:
        if not self.is_topic:
            return None&lt;/pre&gt;
  &lt;pre id=&quot;Arxt&quot;&gt;        if isinstance(self.__chat, int):
            return None&lt;/pre&gt;
  &lt;pre id=&quot;FmPr&quot;&gt;        return int(self.__chat.split(&amp;quot;#&amp;quot;)[1])&lt;/pre&gt;
  &lt;pre id=&quot;NAlM&quot;&gt;    def get_id(self) -&amp;gt; int:
        if isinstance(self.__chat, int):
            return self.__chat
        return int(self.__chat.split(&amp;quot;#&amp;quot;)[0])&lt;/pre&gt;
  &lt;pre id=&quot;CVsq&quot;&gt;
class ForwardConfig:
    source: ChatConfig
    destination: List[ChatConfig]
    filters: Optional[List[str]]
    blacklist: Optional[List[str]]&lt;/pre&gt;
  &lt;pre id=&quot;cdup&quot;&gt;    def __init__(
        self,
        source: Union[str, int],
        destination: List[Union[str, int]],
        filters: Optional[List[str]] = None,
        blacklist: Optional[List[str]] = None,
    ):
        self.source = ChatConfig(source)
        self.destination = [ChatConfig(item) for item in destination]
        self.filters = filters
        self.blacklist = blacklist&lt;/pre&gt;
  &lt;pre id=&quot;HJeL&quot;&gt;
def get_config() -&amp;gt; List[ForwardConfig]:
    global PARSED_CONFIG
    if PARSED_CONFIG:
        return PARSED_CONFIG&lt;/pre&gt;
  &lt;pre id=&quot;Hml0&quot;&gt;    PARSED_CONFIG = [
        ForwardConfig(
            source=chat[&amp;quot;source&amp;quot;],
            destination=chat[&amp;quot;destination&amp;quot;],
            filters=chat.get(&amp;quot;filters&amp;quot;),
            blacklist=chat.get(&amp;quot;blacklist&amp;quot;),
        )
        for chat in CONFIG
    ]
    return PARSED_CONFIG&lt;/pre&gt;
  &lt;pre id=&quot;rjJw&quot;&gt;
def get_destination(chat_id: int, topic_id: Optional[int] = None) -&amp;gt; List[ForwardConfig]:
    &amp;quot;&amp;quot;&amp;quot;Get destination from a specific source chat&lt;/pre&gt;
  &lt;pre id=&quot;X1zh&quot;&gt;    Args:
        chat_id (&amp;#x60;int&amp;#x60;): source chat id
        topic_id (&amp;#x60;Optional[int]&amp;#x60;): source topic id. Defaults to None.
    &amp;quot;&amp;quot;&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;dBjg&quot;&gt;    dest: List[ForwardConfig] = []&lt;/pre&gt;
  &lt;pre id=&quot;UUAh&quot;&gt;    for chat in get_config():
        if chat.source.get_id() == chat_id and chat.source.get_topic() == topic_id:
            dest.append(chat)
    return dest&lt;/pre&gt;
  &lt;p id=&quot;HqJW&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;Wk0t&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;G2Ij&quot;&gt;    ↪️message.py&lt;/h3&gt;
  &lt;pre id=&quot;qlmo&quot;&gt;import re&lt;/pre&gt;
  &lt;pre id=&quot;Vba0&quot;&gt;from typing import List&lt;/pre&gt;
  &lt;pre id=&quot;8EZa&quot;&gt;
def predicate_text(filters: List[str], text: str) -&amp;gt; bool:
    &amp;quot;&amp;quot;&amp;quot;Check if the text contains any of the filters&amp;quot;&amp;quot;&amp;quot;
    for i in filters:
        pattern = r&amp;quot;( |^|[^\w])&amp;quot; + re.escape(i) + r&amp;quot;( |$|[^\w])&amp;quot;
        if re.search(pattern, text, flags=re.IGNORECASE):
            return True&lt;/pre&gt;
  &lt;pre id=&quot;J2Wo&quot;&gt;    return False&lt;/pre&gt;
  &lt;p id=&quot;ZOzk&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;cJOU&quot;&gt;↪️__init__.py&lt;/h3&gt;
  &lt;pre id=&quot;tdRl&quot;&gt;import logging
import json
from os import getenv, path&lt;/pre&gt;
  &lt;pre id=&quot;HHsJ&quot;&gt;from dotenv import load_dotenv
from telegram.ext import ApplicationBuilder&lt;/pre&gt;
  &lt;pre id=&quot;0CG9&quot;&gt;load_dotenv(&amp;quot;.env&amp;quot;)&lt;/pre&gt;
  &lt;pre id=&quot;5s0f&quot;&gt;
logging.basicConfig(
    format=&amp;quot;[ %(asctime)s: %(levelname)-8s ] %(name)-20s - %(message)s&amp;quot;,
    level=logging.INFO,
)&lt;/pre&gt;
  &lt;pre id=&quot;B0DY&quot;&gt;LOGGER = logging.getLogger(__name__)&lt;/pre&gt;
  &lt;pre id=&quot;SOtf&quot;&gt;httpx_logger = logging.getLogger(&amp;#x27;httpx&amp;#x27;)
httpx_logger.setLevel(logging.WARNING)&lt;/pre&gt;
  &lt;pre id=&quot;Oaet&quot;&gt;# load json file
config_name = &amp;quot;chat_list.json&amp;quot;
if not path.isfile(config_name):
    LOGGER.error(&amp;quot;No chat_list.json config file found! Exiting...&amp;quot;)
    exit(1)
with open(config_name, &amp;quot;r&amp;quot;) as data:
    CONFIG = json.load(data)&lt;/pre&gt;
  &lt;pre id=&quot;id43&quot;&gt;
BOT_TOKEN = getenv(&amp;quot;BOT_TOKEN&amp;quot;)
if not BOT_TOKEN:
    LOGGER.error(&amp;quot;No BOT_TOKEN token provided!&amp;quot;)
    exit(1)
OWNER_ID = int(getenv(&amp;quot;OWNER_ID&amp;quot;, &amp;quot;0&amp;quot;))
REMOVE_TAG = getenv(&amp;quot;REMOVE_TAG&amp;quot;, &amp;quot;False&amp;quot;) in {&amp;quot;true&amp;quot;, &amp;quot;True&amp;quot;, 1}&lt;/pre&gt;
  &lt;pre id=&quot;Tbh5&quot;&gt;bot = ApplicationBuilder().token(BOT_TOKEN).build()&lt;/pre&gt;
  &lt;p id=&quot;LKYN&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;QLcV&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;iO0j&quot;&gt; ↪️__main__.py&lt;/h3&gt;
  &lt;pre id=&quot;W2Pf&quot;&gt;from forwarder import main&lt;/pre&gt;
  &lt;pre id=&quot;N76Z&quot;&gt;if __name__ == &amp;quot;__main__&amp;quot;:
    main.run()&lt;/pre&gt;
  &lt;p id=&quot;zkXh&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;siq0&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;OJlR&quot;&gt;  ↪️main.py&lt;/h3&gt;
  &lt;pre id=&quot;PIYy&quot;&gt;import importlib&lt;/pre&gt;
  &lt;pre id=&quot;NS57&quot;&gt;from forwarder import LOGGER, bot
from forwarder.modules import ALL_MODULES&lt;/pre&gt;
  &lt;pre id=&quot;8cBK&quot;&gt;for module in ALL_MODULES:
    importlib.import_module(&amp;quot;forwarder.modules.&amp;quot; + module)&lt;/pre&gt;
  &lt;pre id=&quot;Hmay&quot;&gt;
def run():
    LOGGER.info(&amp;quot;Successfully loaded modules: &amp;quot; + str(ALL_MODULES))
    LOGGER.info(&amp;quot;Starting bot...&amp;quot;)
    bot.run_polling()&lt;/pre&gt;
  &lt;p id=&quot;2qRB&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;iLBT&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;qskI&quot;&gt;  ↪️.dockerignore&lt;/h3&gt;
  &lt;pre id=&quot;sF91&quot;&gt;.git
__pycache__
/chat_list.json
/.env&lt;/pre&gt;
  &lt;p id=&quot;bDx1&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;XfS7&quot;&gt; ↪️.gitignore&lt;/h3&gt;
  &lt;pre id=&quot;smdF&quot;&gt;# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class&lt;/pre&gt;
  &lt;pre id=&quot;Mja2&quot;&gt;# C extensions
*.so&lt;/pre&gt;
  &lt;pre id=&quot;tsHa&quot;&gt;# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg&lt;/pre&gt;
  &lt;pre id=&quot;5UIp&quot;&gt;# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec&lt;/pre&gt;
  &lt;pre id=&quot;r4pc&quot;&gt;# Installer logs
pip-log.txt
pip-delete-this-directory.txt&lt;/pre&gt;
  &lt;pre id=&quot;wuZb&quot;&gt;# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover&lt;/pre&gt;
  &lt;pre id=&quot;yxwl&quot;&gt;# Translations
*.mo
*.pot&lt;/pre&gt;
  &lt;pre id=&quot;W4OJ&quot;&gt;# Django stuff:
*.log&lt;/pre&gt;
  &lt;pre id=&quot;dQ4d&quot;&gt;# Sphinx documentation
docs/_build/&lt;/pre&gt;
  &lt;pre id=&quot;N2XO&quot;&gt;# PyBuilder
target/&lt;/pre&gt;
  &lt;pre id=&quot;zxa5&quot;&gt;# Visual Studio Code files
\.vscode/&lt;/pre&gt;
  &lt;pre id=&quot;FOng&quot;&gt;# Bot config file
# DO NOT REMOVE THIS FILE FROM .gitignore
.env
chat_list.json&lt;/pre&gt;
  &lt;p id=&quot;vjgK&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;ww7m&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;BRiL&quot;&gt; ↪️Dockerfile&lt;/h3&gt;
  &lt;pre id=&quot;sJZr&quot;&gt;FROM python:3.10-slim-bullseye&lt;/pre&gt;
  &lt;pre id=&quot;7RCO&quot;&gt;WORKDIR /app&lt;/pre&gt;
  &lt;pre id=&quot;fy5W&quot;&gt;RUN pip install poetry
RUN poetry config virtualenvs.create false&lt;/pre&gt;
  &lt;pre id=&quot;XFjR&quot;&gt;COPY pyproject.toml poetry.lock ./&lt;/pre&gt;
  &lt;pre id=&quot;IZHO&quot;&gt;RUN poetry install --no-root --only main&lt;/pre&gt;
  &lt;pre id=&quot;dQNs&quot;&gt;COPY . .&lt;/pre&gt;
  &lt;pre id=&quot;GWi3&quot;&gt;RUN poetry install --only main&lt;/pre&gt;
  &lt;pre id=&quot;twUh&quot;&gt;CMD [&amp;quot;poetry&amp;quot;, &amp;quot;run&amp;quot;, &amp;quot;forwarder&amp;quot;]&lt;/pre&gt;
  &lt;p id=&quot;IuqS&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;IWuA&quot;&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;jJ4u&quot;&gt; ↪️chat_list.sample.json&lt;/h3&gt;
  &lt;pre id=&quot;q1L2&quot;&gt;[
    {
        &amp;quot;source&amp;quot;: -10012345678,
        &amp;quot;destination&amp;quot;: [-10098765432, -10012345678]
    },
    {
        &amp;quot;source&amp;quot;: -10098765432,
        &amp;quot;destination&amp;quot;: [-10012345678, &amp;quot;-10012345678#98765&amp;quot;],
        &amp;quot;filters&amp;quot;: [&amp;quot;word1&amp;quot;, &amp;quot;word2&amp;quot;]
    },
    {
        &amp;quot;source&amp;quot;: -10087654321,
        &amp;quot;destination&amp;quot;: [-10033333333],
        &amp;quot;blacklist&amp;quot;: [&amp;quot;word3&amp;quot;, &amp;quot;word4&amp;quot;]
    },
    {
        &amp;quot;source&amp;quot;: -10087654321,
        &amp;quot;destination&amp;quot;: [-10033333333],
        &amp;quot;filters&amp;quot;: [&amp;quot;word5&amp;quot;],
        &amp;quot;blacklist&amp;quot;: [&amp;quot;word6&amp;quot;]
    }
]&lt;/pre&gt;
  &lt;p id=&quot;OF2n&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;UQLO&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;9PIX&quot;&gt;↪️docker-compose.yml&lt;/h3&gt;
  &lt;pre id=&quot;aZUg&quot;&gt;services:
  telegram-forwarder:
    container_name: telegram-forwarder
    image: telegram-forwarder-bot
    restart: unless-stopped
    build:
      context: .
      dockerfile: Dockerfile
    env_file:
      - .env
    volumes:
      - ./chat_list.json:/app/chat_list.json&lt;/pre&gt;
  &lt;p id=&quot;ihuU&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;a42K&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;ZoYb&quot;&gt;↪️poetry.lock&lt;/h3&gt;
  &lt;pre id=&quot;q3wv&quot;&gt;# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand.&lt;/pre&gt;
  &lt;pre id=&quot;YcFl&quot;&gt;[[package]]
name = &amp;quot;anyio&amp;quot;
version = &amp;quot;4.0.0&amp;quot;
description = &amp;quot;High level compatibility layer for multiple asynchronous event loop implementations&amp;quot;
category = &amp;quot;main&amp;quot;
optional = false
python-versions = &amp;quot;&amp;gt;=3.8&amp;quot;
files = [
    {file = &amp;quot;anyio-4.0.0-py3-none-any.whl&amp;quot;, hash = &amp;quot;sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f&amp;quot;},
    {file = &amp;quot;anyio-4.0.0.tar.gz&amp;quot;, hash = &amp;quot;sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a&amp;quot;},
]&lt;/pre&gt;
  &lt;pre id=&quot;5JsH&quot;&gt;[package.dependencies]
exceptiongroup = {version = &amp;quot;&amp;gt;=1.0.2&amp;quot;, markers = &amp;quot;python_version &amp;lt; \&amp;quot;3.11\&amp;quot;&amp;quot;}
idna = &amp;quot;&amp;gt;=2.8&amp;quot;
sniffio = &amp;quot;&amp;gt;=1.1&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;M1Pr&quot;&gt;[package.extras]
doc = [&amp;quot;Sphinx (&amp;gt;=7)&amp;quot;, &amp;quot;packaging&amp;quot;, &amp;quot;sphinx-autodoc-typehints (&amp;gt;=1.2.0)&amp;quot;]
test = [&amp;quot;anyio[trio]&amp;quot;, &amp;quot;coverage[toml] (&amp;gt;=7)&amp;quot;, &amp;quot;hypothesis (&amp;gt;=4.0)&amp;quot;, &amp;quot;psutil (&amp;gt;=5.9)&amp;quot;, &amp;quot;pytest (&amp;gt;=7.0)&amp;quot;, &amp;quot;pytest-mock (&amp;gt;=3.6.1)&amp;quot;, &amp;quot;trustme&amp;quot;, &amp;quot;uvloop (&amp;gt;=0.17)&amp;quot;]
trio = [&amp;quot;trio (&amp;gt;=0.22)&amp;quot;]&lt;/pre&gt;
  &lt;pre id=&quot;tkaI&quot;&gt;[[package]]
name = &amp;quot;black&amp;quot;
version = &amp;quot;23.9.0&amp;quot;
description = &amp;quot;The uncompromising code formatter.&amp;quot;
category = &amp;quot;dev&amp;quot;
optional = false
python-versions = &amp;quot;&amp;gt;=3.8&amp;quot;
files = [
    {file = &amp;quot;black-23.9.0-py3-none-any.whl&amp;quot;, hash = &amp;quot;sha256:9366c1f898981f09eb8da076716c02fd021f5a0e63581c66501d68a2e4eab844&amp;quot;},
    {file = &amp;quot;black-23.9.0.tar.gz&amp;quot;, hash = &amp;quot;sha256:3511c8a7e22ce653f89ae90dfddaf94f3bb7e2587a245246572d3b9c92adf066&amp;quot;},
]&lt;/pre&gt;
  &lt;pre id=&quot;SRwg&quot;&gt;[package.dependencies]
click = &amp;quot;&amp;gt;=8.0.0&amp;quot;
mypy-extensions = &amp;quot;&amp;gt;=0.4.3&amp;quot;
packaging = &amp;quot;&amp;gt;=22.0&amp;quot;
pathspec = &amp;quot;&amp;gt;=0.9.0&amp;quot;
platformdirs = &amp;quot;&amp;gt;=2&amp;quot;
tomli = {version = &amp;quot;&amp;gt;=1.1.0&amp;quot;, markers = &amp;quot;python_version &amp;lt; \&amp;quot;3.11\&amp;quot;&amp;quot;}
typing-extensions = {version = &amp;quot;&amp;gt;=4.0.1&amp;quot;, markers = &amp;quot;python_version &amp;lt; \&amp;quot;3.11\&amp;quot;&amp;quot;}&lt;/pre&gt;
  &lt;pre id=&quot;YvLI&quot;&gt;[package.extras]
colorama = [&amp;quot;colorama (&amp;gt;=0.4.3)&amp;quot;]
d = [&amp;quot;aiohttp (&amp;gt;=3.7.4)&amp;quot;]
jupyter = [&amp;quot;ipython (&amp;gt;=7.8.0)&amp;quot;, &amp;quot;tokenize-rt (&amp;gt;=3.2.0)&amp;quot;]
uvloop = [&amp;quot;uvloop (&amp;gt;=0.15.2)&amp;quot;]&lt;/pre&gt;
  &lt;pre id=&quot;n5xT&quot;&gt;[[package]]
name = &amp;quot;certifi&amp;quot;
version = &amp;quot;2023.7.22&amp;quot;
description = &amp;quot;Python package for providing Mozilla&amp;#x27;s CA Bundle.&amp;quot;
category = &amp;quot;main&amp;quot;
optional = false
python-versions = &amp;quot;&amp;gt;=3.6&amp;quot;
files = [
    {file = &amp;quot;certifi-2023.7.22-py3-none-any.whl&amp;quot;, hash = &amp;quot;sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9&amp;quot;},
    {file = &amp;quot;certifi-2023.7.22.tar.gz&amp;quot;, hash = &amp;quot;sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082&amp;quot;},
]&lt;/pre&gt;
  &lt;pre id=&quot;k9WF&quot;&gt;[[package]]
name = &amp;quot;click&amp;quot;
version = &amp;quot;8.1.7&amp;quot;
description = &amp;quot;Composable command line interface toolkit&amp;quot;
category = &amp;quot;dev&amp;quot;
optional = false
python-versions = &amp;quot;&amp;gt;=3.7&amp;quot;
files = [
    {file = &amp;quot;click-8.1.7-py3-none-any.whl&amp;quot;, hash = &amp;quot;sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28&amp;quot;},
    {file = &amp;quot;click-8.1.7.tar.gz&amp;quot;, hash = &amp;quot;sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de&amp;quot;},
]&lt;/pre&gt;
  &lt;pre id=&quot;xFbv&quot;&gt;[package.dependencies]
colorama = {version = &amp;quot;*&amp;quot;, markers = &amp;quot;platform_system == \&amp;quot;Windows\&amp;quot;&amp;quot;}&lt;/pre&gt;
  &lt;pre id=&quot;hMiQ&quot;&gt;[[package]]
name = &amp;quot;colorama&amp;quot;
version = &amp;quot;0.4.6&amp;quot;
description = &amp;quot;Cross-platform colored terminal text.&amp;quot;
category = &amp;quot;dev&amp;quot;
optional = false
python-versions = &amp;quot;!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,&amp;gt;=2.7&amp;quot;
files = [
    {file = &amp;quot;colorama-0.4.6-py2.py3-none-any.whl&amp;quot;, hash = &amp;quot;sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6&amp;quot;},
    {file = &amp;quot;colorama-0.4.6.tar.gz&amp;quot;, hash = &amp;quot;sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44&amp;quot;},
]&lt;/pre&gt;
  &lt;pre id=&quot;bkmO&quot;&gt;[[package]]
name = &amp;quot;exceptiongroup&amp;quot;
version = &amp;quot;1.1.3&amp;quot;
description = &amp;quot;Backport of PEP 654 (exception groups)&amp;quot;
category = &amp;quot;main&amp;quot;
optional = false
python-versions = &amp;quot;&amp;gt;=3.7&amp;quot;
files = [
    {file = &amp;quot;exceptiongroup-1.1.3-py3-none-any.whl&amp;quot;, hash = &amp;quot;sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3&amp;quot;},
    {file = &amp;quot;exceptiongroup-1.1.3.tar.gz&amp;quot;, hash = &amp;quot;sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9&amp;quot;},
]&lt;/pre&gt;
  &lt;pre id=&quot;K0Wr&quot;&gt;[package.extras]
test = [&amp;quot;pytest (&amp;gt;=6)&amp;quot;]&lt;/pre&gt;
  &lt;pre id=&quot;49ss&quot;&gt;[[package]]
name = &amp;quot;h11&amp;quot;
version = &amp;quot;0.14.0&amp;quot;
description = &amp;quot;A pure-Python, bring-your-own-I/O implementation of HTTP/1.1&amp;quot;
category = &amp;quot;main&amp;quot;
optional = false
python-versions = &amp;quot;&amp;gt;=3.7&amp;quot;
files = [
    {file = &amp;quot;h11-0.14.0-py3-none-any.whl&amp;quot;, hash = &amp;quot;sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761&amp;quot;},
    {file = &amp;quot;h11-0.14.0.tar.gz&amp;quot;, hash = &amp;quot;sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d&amp;quot;},
]&lt;/pre&gt;
  &lt;pre id=&quot;Ni45&quot;&gt;[[package]]
name = &amp;quot;httpcore&amp;quot;
version = &amp;quot;0.17.3&amp;quot;
description = &amp;quot;A minimal low-level HTTP client.&amp;quot;
category = &amp;quot;main&amp;quot;
optional = false
python-versions = &amp;quot;&amp;gt;=3.7&amp;quot;
files = [
    {file = &amp;quot;httpcore-0.17.3-py3-none-any.whl&amp;quot;, hash = &amp;quot;sha256:c2789b767ddddfa2a5782e3199b2b7f6894540b17b16ec26b2c4d8e103510b87&amp;quot;},
    {file = &amp;quot;httpcore-0.17.3.tar.gz&amp;quot;, hash = &amp;quot;sha256:a6f30213335e34c1ade7be6ec7c47f19f50c56db36abef1a9dfa3815b1cb3888&amp;quot;},
]&lt;/pre&gt;
  &lt;pre id=&quot;RVyN&quot;&gt;[package.dependencies]
anyio = &amp;quot;&amp;gt;=3.0,&amp;lt;5.0&amp;quot;
certifi = &amp;quot;*&amp;quot;
h11 = &amp;quot;&amp;gt;=0.13,&amp;lt;0.15&amp;quot;
sniffio = &amp;quot;&amp;gt;=1.0.0,&amp;lt;2.0.0&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;bNMW&quot;&gt;[package.extras]
http2 = [&amp;quot;h2 (&amp;gt;=3,&amp;lt;5)&amp;quot;]
socks = [&amp;quot;socksio (&amp;gt;=1.0.0,&amp;lt;2.0.0)&amp;quot;]&lt;/pre&gt;
  &lt;pre id=&quot;t60r&quot;&gt;[[package]]
name = &amp;quot;httpx&amp;quot;
version = &amp;quot;0.24.1&amp;quot;
description = &amp;quot;The next generation HTTP client.&amp;quot;
category = &amp;quot;main&amp;quot;
optional = false
python-versions = &amp;quot;&amp;gt;=3.7&amp;quot;
files = [
    {file = &amp;quot;httpx-0.24.1-py3-none-any.whl&amp;quot;, hash = &amp;quot;sha256:06781eb9ac53cde990577af654bd990a4949de37a28bdb4a230d434f3a30b9bd&amp;quot;},
    {file = &amp;quot;httpx-0.24.1.tar.gz&amp;quot;, hash = &amp;quot;sha256:5853a43053df830c20f8110c5e69fe44d035d850b2dfe795e196f00fdb774bdd&amp;quot;},
]&lt;/pre&gt;
  &lt;pre id=&quot;UZjj&quot;&gt;[package.dependencies]
certifi = &amp;quot;*&amp;quot;
httpcore = &amp;quot;&amp;gt;=0.15.0,&amp;lt;0.18.0&amp;quot;
idna = &amp;quot;*&amp;quot;
sniffio = &amp;quot;*&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;XM8L&quot;&gt;[package.extras]
brotli = [&amp;quot;brotli&amp;quot;, &amp;quot;brotlicffi&amp;quot;]
cli = [&amp;quot;click (&amp;gt;=8.0.0,&amp;lt;9.0.0)&amp;quot;, &amp;quot;pygments (&amp;gt;=2.0.0,&amp;lt;3.0.0)&amp;quot;, &amp;quot;rich (&amp;gt;=10,&amp;lt;14)&amp;quot;]
http2 = [&amp;quot;h2 (&amp;gt;=3,&amp;lt;5)&amp;quot;]
socks = [&amp;quot;socksio (&amp;gt;=1.0.0,&amp;lt;2.0.0)&amp;quot;]&lt;/pre&gt;
  &lt;pre id=&quot;E8x8&quot;&gt;[[package]]
name = &amp;quot;idna&amp;quot;
version = &amp;quot;3.4&amp;quot;
description = &amp;quot;Internationalized Domain Names in Applications (IDNA)&amp;quot;
category = &amp;quot;main&amp;quot;
optional = false
python-versions = &amp;quot;&amp;gt;=3.5&amp;quot;
files = [
    {file = &amp;quot;idna-3.4-py3-none-any.whl&amp;quot;, hash = &amp;quot;sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2&amp;quot;},
    {file = &amp;quot;idna-3.4.tar.gz&amp;quot;, hash = &amp;quot;sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4&amp;quot;},
]&lt;/pre&gt;
  &lt;pre id=&quot;UG4n&quot;&gt;[[package]]
name = &amp;quot;isort&amp;quot;
version = &amp;quot;5.12.0&amp;quot;
description = &amp;quot;A Python utility / library to sort Python imports.&amp;quot;
category = &amp;quot;dev&amp;quot;
optional = false
python-versions = &amp;quot;&amp;gt;=3.8.0&amp;quot;
files = [
    {file = &amp;quot;isort-5.12.0-py3-none-any.whl&amp;quot;, hash = &amp;quot;sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6&amp;quot;},
    {file = &amp;quot;isort-5.12.0.tar.gz&amp;quot;, hash = &amp;quot;sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504&amp;quot;},
]&lt;/pre&gt;
  &lt;pre id=&quot;0jpk&quot;&gt;[package.extras]
colors = [&amp;quot;colorama (&amp;gt;=0.4.3)&amp;quot;]
pipfile-deprecated-finder = [&amp;quot;pip-shims (&amp;gt;=0.5.2)&amp;quot;, &amp;quot;pipreqs&amp;quot;, &amp;quot;requirementslib&amp;quot;]
plugins = [&amp;quot;setuptools&amp;quot;]
requirements-deprecated-finder = [&amp;quot;pip-api&amp;quot;, &amp;quot;pipreqs&amp;quot;]&lt;/pre&gt;
  &lt;pre id=&quot;pLbV&quot;&gt;[[package]]
name = &amp;quot;mypy-extensions&amp;quot;
version = &amp;quot;1.0.0&amp;quot;
description = &amp;quot;Type system extensions for programs checked with the mypy type checker.&amp;quot;
category = &amp;quot;dev&amp;quot;
optional = false
python-versions = &amp;quot;&amp;gt;=3.5&amp;quot;
files = [
    {file = &amp;quot;mypy_extensions-1.0.0-py3-none-any.whl&amp;quot;, hash = &amp;quot;sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d&amp;quot;},
    {file = &amp;quot;mypy_extensions-1.0.0.tar.gz&amp;quot;, hash = &amp;quot;sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782&amp;quot;},
]&lt;/pre&gt;
  &lt;pre id=&quot;BiPf&quot;&gt;[[package]]
name = &amp;quot;packaging&amp;quot;
version = &amp;quot;23.1&amp;quot;
description = &amp;quot;Core utilities for Python packages&amp;quot;
category = &amp;quot;dev&amp;quot;
optional = false
python-versions = &amp;quot;&amp;gt;=3.7&amp;quot;
files = [
    {file = &amp;quot;packaging-23.1-py3-none-any.whl&amp;quot;, hash = &amp;quot;sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61&amp;quot;},
    {file = &amp;quot;packaging-23.1.tar.gz&amp;quot;, hash = &amp;quot;sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f&amp;quot;},
]&lt;/pre&gt;
  &lt;pre id=&quot;sipV&quot;&gt;[[package]]
name = &amp;quot;pathspec&amp;quot;
version = &amp;quot;0.11.2&amp;quot;
description = &amp;quot;Utility library for gitignore style pattern matching of file paths.&amp;quot;
category = &amp;quot;dev&amp;quot;
optional = false
python-versions = &amp;quot;&amp;gt;=3.7&amp;quot;
files = [
    {file = &amp;quot;pathspec-0.11.2-py3-none-any.whl&amp;quot;, hash = &amp;quot;sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20&amp;quot;},
    {file = &amp;quot;pathspec-0.11.2.tar.gz&amp;quot;, hash = &amp;quot;sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3&amp;quot;},
]&lt;/pre&gt;
  &lt;pre id=&quot;G2RQ&quot;&gt;[[package]]
name = &amp;quot;platformdirs&amp;quot;
version = &amp;quot;3.10.0&amp;quot;
description = &amp;quot;A small Python package for determining appropriate platform-specific dirs, e.g. a \&amp;quot;user data dir\&amp;quot;.&amp;quot;
category = &amp;quot;dev&amp;quot;
optional = false
python-versions = &amp;quot;&amp;gt;=3.7&amp;quot;
files = [
    {file = &amp;quot;platformdirs-3.10.0-py3-none-any.whl&amp;quot;, hash = &amp;quot;sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d&amp;quot;},
    {file = &amp;quot;platformdirs-3.10.0.tar.gz&amp;quot;, hash = &amp;quot;sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d&amp;quot;},
]&lt;/pre&gt;
  &lt;pre id=&quot;8UDB&quot;&gt;[package.extras]
docs = [&amp;quot;furo (&amp;gt;=2023.7.26)&amp;quot;, &amp;quot;proselint (&amp;gt;=0.13)&amp;quot;, &amp;quot;sphinx (&amp;gt;=7.1.1)&amp;quot;, &amp;quot;sphinx-autodoc-typehints (&amp;gt;=1.24)&amp;quot;]
test = [&amp;quot;appdirs (==1.4.4)&amp;quot;, &amp;quot;covdefaults (&amp;gt;=2.3)&amp;quot;, &amp;quot;pytest (&amp;gt;=7.4)&amp;quot;, &amp;quot;pytest-cov (&amp;gt;=4.1)&amp;quot;, &amp;quot;pytest-mock (&amp;gt;=3.11.1)&amp;quot;]&lt;/pre&gt;
  &lt;pre id=&quot;c7br&quot;&gt;[[package]]
name = &amp;quot;python-dotenv&amp;quot;
version = &amp;quot;1.0.0&amp;quot;
description = &amp;quot;Read key-value pairs from a .env file and set them as environment variables&amp;quot;
category = &amp;quot;main&amp;quot;
optional = false
python-versions = &amp;quot;&amp;gt;=3.8&amp;quot;
files = [
    {file = &amp;quot;python-dotenv-1.0.0.tar.gz&amp;quot;, hash = &amp;quot;sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba&amp;quot;},
    {file = &amp;quot;python_dotenv-1.0.0-py3-none-any.whl&amp;quot;, hash = &amp;quot;sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a&amp;quot;},
]&lt;/pre&gt;
  &lt;pre id=&quot;H5aK&quot;&gt;[package.extras]
cli = [&amp;quot;click (&amp;gt;=5.0)&amp;quot;]&lt;/pre&gt;
  &lt;pre id=&quot;mjy7&quot;&gt;[[package]]
name = &amp;quot;python-telegram-bot&amp;quot;
version = &amp;quot;20.5&amp;quot;
description = &amp;quot;We have made you a wrapper you can&amp;#x27;t refuse&amp;quot;
category = &amp;quot;main&amp;quot;
optional = false
python-versions = &amp;quot;&amp;gt;=3.8&amp;quot;
files = [
    {file = &amp;quot;python-telegram-bot-20.5.tar.gz&amp;quot;, hash = &amp;quot;sha256:2f45a94c861cbd40440ece2be176ef0fc69e10d84e6dfa17f9a456e32aeece13&amp;quot;},
    {file = &amp;quot;python_telegram_bot-20.5-py3-none-any.whl&amp;quot;, hash = &amp;quot;sha256:fc9605a855794231c802cc3948e6f7c319a817b5cd1827371f170bc7ca0ca279&amp;quot;},
]&lt;/pre&gt;
  &lt;pre id=&quot;CFAe&quot;&gt;[package.dependencies]
httpx = &amp;quot;&amp;gt;=0.24.1,&amp;lt;0.25.0&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;SnRn&quot;&gt;[package.extras]
all = [&amp;quot;APScheduler (&amp;gt;=3.10.4,&amp;lt;3.11.0)&amp;quot;, &amp;quot;aiolimiter (&amp;gt;=1.1.0,&amp;lt;1.2.0)&amp;quot;, &amp;quot;cachetools (&amp;gt;=5.3.1,&amp;lt;5.4.0)&amp;quot;, &amp;quot;cryptography (&amp;gt;=39.0.1)&amp;quot;, &amp;quot;httpx[http2]&amp;quot;, &amp;quot;httpx[socks]&amp;quot;, &amp;quot;pytz (&amp;gt;=2018.6)&amp;quot;, &amp;quot;tornado (&amp;gt;=6.2,&amp;lt;7.0)&amp;quot;]
callback-data = [&amp;quot;cachetools (&amp;gt;=5.3.1,&amp;lt;5.4.0)&amp;quot;]
ext = [&amp;quot;APScheduler (&amp;gt;=3.10.4,&amp;lt;3.11.0)&amp;quot;, &amp;quot;aiolimiter (&amp;gt;=1.1.0,&amp;lt;1.2.0)&amp;quot;, &amp;quot;cachetools (&amp;gt;=5.3.1,&amp;lt;5.4.0)&amp;quot;, &amp;quot;pytz (&amp;gt;=2018.6)&amp;quot;, &amp;quot;tornado (&amp;gt;=6.2,&amp;lt;7.0)&amp;quot;]
http2 = [&amp;quot;httpx[http2]&amp;quot;]
job-queue = [&amp;quot;APScheduler (&amp;gt;=3.10.4,&amp;lt;3.11.0)&amp;quot;, &amp;quot;pytz (&amp;gt;=2018.6)&amp;quot;]
passport = [&amp;quot;cryptography (&amp;gt;=39.0.1)&amp;quot;]
rate-limiter = [&amp;quot;aiolimiter (&amp;gt;=1.1.0,&amp;lt;1.2.0)&amp;quot;]
socks = [&amp;quot;httpx[socks]&amp;quot;]
webhooks = [&amp;quot;tornado (&amp;gt;=6.2,&amp;lt;7.0)&amp;quot;]&lt;/pre&gt;
  &lt;pre id=&quot;NikV&quot;&gt;[[package]]
name = &amp;quot;sniffio&amp;quot;
version = &amp;quot;1.3.0&amp;quot;
description = &amp;quot;Sniff out which async library your code is running under&amp;quot;
category = &amp;quot;main&amp;quot;
optional = false
python-versions = &amp;quot;&amp;gt;=3.7&amp;quot;
files = [
    {file = &amp;quot;sniffio-1.3.0-py3-none-any.whl&amp;quot;, hash = &amp;quot;sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384&amp;quot;},
    {file = &amp;quot;sniffio-1.3.0.tar.gz&amp;quot;, hash = &amp;quot;sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101&amp;quot;},
]&lt;/pre&gt;
  &lt;pre id=&quot;4JkC&quot;&gt;[[package]]
name = &amp;quot;tomli&amp;quot;
version = &amp;quot;2.0.1&amp;quot;
description = &amp;quot;A lil&amp;#x27; TOML parser&amp;quot;
category = &amp;quot;dev&amp;quot;
optional = false
python-versions = &amp;quot;&amp;gt;=3.7&amp;quot;
files = [
    {file = &amp;quot;tomli-2.0.1-py3-none-any.whl&amp;quot;, hash = &amp;quot;sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc&amp;quot;},
    {file = &amp;quot;tomli-2.0.1.tar.gz&amp;quot;, hash = &amp;quot;sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f&amp;quot;},
]&lt;/pre&gt;
  &lt;pre id=&quot;nct2&quot;&gt;[[package]]
name = &amp;quot;typing-extensions&amp;quot;
version = &amp;quot;4.7.1&amp;quot;
description = &amp;quot;Backported and Experimental Type Hints for Python 3.7+&amp;quot;
category = &amp;quot;dev&amp;quot;
optional = false
python-versions = &amp;quot;&amp;gt;=3.7&amp;quot;
files = [
    {file = &amp;quot;typing_extensions-4.7.1-py3-none-any.whl&amp;quot;, hash = &amp;quot;sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36&amp;quot;},
    {file = &amp;quot;typing_extensions-4.7.1.tar.gz&amp;quot;, hash = &amp;quot;sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2&amp;quot;},
]&lt;/pre&gt;
  &lt;pre id=&quot;M3CJ&quot;&gt;[metadata]
lock-version = &amp;quot;2.0&amp;quot;
python-versions = &amp;quot;^3.9&amp;quot;
content-hash = &amp;quot;75ecc460179a48fc1e03d65d5e35ddfceb5a814d89703bddfa3d447eefaf2ace&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;D8dq&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;8Rsx&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;Fvbq&quot;&gt; ↪️pyproject.toml&lt;/h3&gt;
  &lt;pre id=&quot;soYE&quot;&gt;[tool.poetry]
name = &amp;quot;telegram-forwarder&amp;quot;
version = &amp;quot;2.3.0&amp;quot;
description = &amp;quot;&amp;quot;
authors = [&amp;quot;mrmissx &amp;lt;hi@mrmiss.my.id&amp;gt;&amp;quot;]
license = &amp;quot;GNU General Public License v3.0&amp;quot;
readme = &amp;quot;README.md&amp;quot;
packages = [{ include = &amp;quot;forwarder&amp;quot; }]&lt;/pre&gt;
  &lt;pre id=&quot;NLC8&quot;&gt;[tool.poetry.scripts]
forwarder = &amp;quot;forwarder.main:run&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;4UDN&quot;&gt;[tool.poetry.dependencies]
python = &amp;quot;^3.9&amp;quot;
python-telegram-bot = &amp;quot;^20.5&amp;quot;
python-dotenv = &amp;quot;^1.0.0&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;5FTj&quot;&gt;
[tool.poetry.group.dev.dependencies]
black = &amp;quot;^23.3.0&amp;quot;
isort = &amp;quot;^5.12.0&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;dOCt&quot;&gt;
[tool.black]
line-length = 100
target-version = [&amp;quot;py38&amp;quot;]&lt;/pre&gt;
  &lt;pre id=&quot;pAbS&quot;&gt;#
# Isort Config
#
[tool.isort]
profile = &amp;quot;black&amp;quot;
known_third_party = [&amp;quot;telegram&amp;quot;, &amp;quot;dotenv&amp;quot;]&lt;/pre&gt;
  &lt;pre id=&quot;JLND&quot;&gt;
[build-system]
requires = [&amp;quot;poetry-core&amp;quot;]
build-backend = &amp;quot;poetry.core.masonry.api&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;t6Qc&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;7wDw&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;uVP9&quot;&gt;↪️requirements.txt&lt;/h3&gt;
  &lt;pre id=&quot;Y4WH&quot;&gt;anyio==3.6.2 ; python_version &amp;gt;= &amp;quot;3.9&amp;quot; and python_version &amp;lt; &amp;quot;4.0&amp;quot;
certifi==2022.12.7 ; python_version &amp;gt;= &amp;quot;3.9&amp;quot; and python_version &amp;lt; &amp;quot;4.0&amp;quot;
h11==0.14.0 ; python_version &amp;gt;= &amp;quot;3.9&amp;quot; and python_version &amp;lt; &amp;quot;4.0&amp;quot;
httpcore==0.16.3 ; python_version &amp;gt;= &amp;quot;3.9&amp;quot; and python_version &amp;lt; &amp;quot;4.0&amp;quot;
httpx==0.23.3 ; python_version &amp;gt;= &amp;quot;3.9&amp;quot; and python_version &amp;lt; &amp;quot;4.0&amp;quot;
idna==3.4 ; python_version &amp;gt;= &amp;quot;3.9&amp;quot; and python_version &amp;lt; &amp;quot;4.0&amp;quot;
python-dotenv==1.0.0 ; python_version &amp;gt;= &amp;quot;3.9&amp;quot; and python_version &amp;lt; &amp;quot;4.0&amp;quot;
python-telegram-bot==20.2 ; python_version &amp;gt;= &amp;quot;3.9&amp;quot; and python_version &amp;lt; &amp;quot;4.0&amp;quot;
rfc3986[idna2008]==1.5.0 ; python_version &amp;gt;= &amp;quot;3.9&amp;quot; and python_version &amp;lt; &amp;quot;4.0&amp;quot;
sniffio==1.3.0 ; python_version &amp;gt;= &amp;quot;3.9&amp;quot; and python_version &amp;lt; &amp;quot;4.0&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;oXjU&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;1N4W&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;KfmL&quot;&gt; ↪️sample.env&lt;/h3&gt;
  &lt;pre id=&quot;zhFf&quot;&gt;BOT_TOKEN=your_bot_token
OWNER_ID=your_telegram_id
REMOVE_TAG=True&lt;/pre&gt;

</content></entry><entry><id>codeonplate:50CIYv2M0qA</id><link rel="alternate" type="text/html" href="https://teletype.in/@codeonplate/50CIYv2M0qA?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=codeonplate"></link><title>Бот для трансляции файлов Telegram</title><published>2025-10-20T08:35:04.199Z</published><updated>2025-10-20T08:35:04.199Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/67/0e/670e883c-5f31-42d7-92b4-b1fbbe01931b.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://camo.githubusercontent.com/51dd7a80bb71edc298ed843b159567bf2e169fab91d4a554b5428ab49b2ee613/68747470733a2f2f7777772e6b6f7965622e636f6d2f7374617469632f696d616765732f6465706c6f792f627574746f6e2e737667&quot;&gt;Telegram-бот для создания прямой ссылки на ваши файлы в Telegram.</summary><content type="html">
  &lt;p id=&quot;oS8m&quot;&gt;Telegram-бот для &lt;strong&gt;создания прямой ссылки&lt;/strong&gt; на ваши файлы в Telegram.&lt;/p&gt;
  &lt;h2 id=&quot;k8oR&quot;&gt;Как сделать свой собственный&lt;/h2&gt;
  &lt;h3 id=&quot;hRF1&quot;&gt;Развертывание в Koyeb&lt;/h3&gt;
  &lt;p id=&quot;ADpn&quot;&gt;Важный&lt;/p&gt;
  &lt;p id=&quot;9M2t&quot;&gt;Вам нужно будет развернуть раздел «Переменные среды и файлы» и обновить переменные среды, прежде чем нажимать кнопку «Развернуть».&lt;/p&gt;
  &lt;p id=&quot;nu7m&quot;&gt;Примечание&lt;/p&gt;
  &lt;p id=&quot;wGM2&quot;&gt;При этом развертывается &lt;strong&gt;последняя версия Docker, а НЕ последняя фиксация&lt;/strong&gt;. Поскольку используется готовый Docker-контейнер, скорость развертывания будет значительно выше.&lt;/p&gt;
  &lt;figure id=&quot;yH3x&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://camo.githubusercontent.com/51dd7a80bb71edc298ed843b159567bf2e169fab91d4a554b5428ab49b2ee613/68747470733a2f2f7777772e6b6f7965622e636f6d2f7374617469632f696d616765732f6465706c6f792f627574746f6e2e737667&quot; width=&quot;178&quot; /&gt;
  &lt;/figure&gt;
  &lt;h3 id=&quot;cAC3&quot;&gt;Развертывание в Heroku&lt;/h3&gt;
  &lt;p id=&quot;CCFn&quot;&gt;Примечание&lt;/p&gt;
  &lt;p id=&quot;X9ri&quot;&gt;Чтобы развернуть приложение на Heroku, вам нужно &lt;a href=&quot;https://github.com/EverythingSuckz/TG-FileStreamBot/fork&quot; target=&quot;_blank&quot;&gt;форкнуть&lt;/a&gt; этот репозиторий.&lt;/p&gt;
  &lt;p id=&quot;83RH&quot;&gt;Нажмите кнопку ниже, чтобы быстро развернуть приложение на Heroku&lt;/p&gt;
  &lt;figure id=&quot;Dcm6&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://camo.githubusercontent.com/dc2056acd0e6ff421bfc2b129417f4f832d626c61d1c083221211d8503a429f7/68747470733a2f2f7777772e6865726f6b7563646e2e636f6d2f6465706c6f792f627574746f6e2e737667&quot; width=&quot;147&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;0q9p&quot;&gt;&lt;a href=&quot;https://devcenter.heroku.com/articles/config-vars#using-the-heroku-dashboard&quot; target=&quot;_blank&quot;&gt;Нажмите сюда&lt;/a&gt;, чтобы узнать, как добавить/изменить &lt;a href=&quot;https://github.com/EverythingSuckz/TG-FileStreamBot#required-vars&quot; target=&quot;_blank&quot;&gt;переменные среды&lt;/a&gt; в Heroku.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h3 id=&quot;EfLq&quot;&gt;Загрузка из выпусков&lt;/h3&gt;
  &lt;ul id=&quot;VfZ5&quot;&gt;
    &lt;li id=&quot;8lkE&quot;&gt;Перейдите на вкладку &lt;a href=&quot;https://github.com/EverythingSuckz/TG-FileStreamBot/releases&quot; target=&quot;_blank&quot;&gt;релизы&lt;/a&gt; и в разделе &lt;em&gt;предварительный релиз&lt;/em&gt; скачайте версию для вашей платформы и архитектуры.&lt;/li&gt;
    &lt;li id=&quot;PPMe&quot;&gt;Распакуйте zip-файл в папку.&lt;/li&gt;
    &lt;li id=&quot;IsM5&quot;&gt;Создайте файл с именем &lt;code&gt;fsb.env&lt;/code&gt; и добавьте в него все переменные (см. файл &lt;code&gt;fsb.sample.env&lt;/code&gt; для справки).&lt;/li&gt;
    &lt;li id=&quot;HPax&quot;&gt;Предоставьте исполняемому файлу разрешение на запуск с помощью команды &lt;code&gt;chmod +x fsb&lt;/code&gt; (для Windows не требуется).&lt;/li&gt;
    &lt;li id=&quot;P16K&quot;&gt;Запустите бота с помощью &lt;code&gt;./fsb run&lt;/code&gt; команды. (&lt;code&gt;./fsb.exe run&lt;/code&gt; для Windows)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;hr /&gt;
  &lt;h3 id=&quot;E2r1&quot;&gt;Запуск с помощью docker-compose&lt;/h3&gt;
  &lt;ul id=&quot;fqxU&quot;&gt;
    &lt;li id=&quot;BMQE&quot;&gt;Клонировать репозиторий&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;7dNA&quot;&gt;git clone https://github.com/EverythingSuckz/TG-FileStreamBot
cd TG-FileStreamBot&lt;/pre&gt;
  &lt;ul id=&quot;ZVg5&quot;&gt;
    &lt;li id=&quot;1ed1&quot;&gt;Создайте файл с именем &lt;code&gt;fsb.env&lt;/code&gt; и добавьте в него все переменные (см. файл &lt;code&gt;fsb.sample.env&lt;/code&gt; для справки).&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;ZpWW&quot;&gt;нано фсб.env&lt;/pre&gt;
  &lt;ul id=&quot;K1ds&quot;&gt;
    &lt;li id=&quot;h94b&quot;&gt;Соберите и запустите файл docker-compose&lt;/li&gt;
  &lt;/ul&gt;
  &lt;pre id=&quot;Ed57&quot;&gt;docker-compose up -d&lt;/pre&gt;
  &lt;p id=&quot;jggf&quot;&gt;или&lt;/p&gt;
  &lt;pre id=&quot;Ktf9&quot;&gt;настройка docker compose up -d&lt;/pre&gt;
  &lt;hr /&gt;
  &lt;h3 id=&quot;sNMh&quot;&gt;Запуск с помощью docker&lt;/h3&gt;
  &lt;pre id=&quot;GBVS&quot;&gt;docker run --env-file fsb.env ghcr.io/everythingsuckz/fsb:latest&lt;/pre&gt;
  &lt;p id=&quot;k7Nz&quot;&gt;Где &lt;code&gt;fsb.env&lt;/code&gt; — это файл среды, содержащий все переменные.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;h3 id=&quot;ewtf&quot;&gt;Сборка из исходного кода&lt;/h3&gt;
  &lt;h4 id=&quot;EHzc&quot;&gt;Убунту&lt;/h4&gt;
  &lt;p id=&quot;eCul&quot;&gt;Примечание&lt;/p&gt;
  &lt;p id=&quot;U8wm&quot;&gt;Обязательно установите Go 1.21 или более позднюю версию. См. &lt;a href=&quot;https://stackoverflow.com/a/17566846/15807350&quot; target=&quot;_blank&quot;&gt;https://stackoverflow.com/a/17566846/15807350&lt;/a&gt;&lt;/p&gt;
  &lt;pre id=&quot;Wb6t&quot;&gt;git clone https://github.com/EverythingSuckz/TG-FileStreamBot
cd TG-FileStreamBot
go build ./cmd/fsb/
chmod +x fsb
mv fsb.sample.env fsb.env
nano fsb.env
# (добавьте свои переменные среды, подробнее см. в следующем разделе)
./fsb run&lt;/pre&gt;
  &lt;p id=&quot;BM41&quot;&gt;а чтобы остановить программу, сделайте CTRL+C&lt;/p&gt;
  &lt;h4 id=&quot;QD3c&quot;&gt;Windows&lt;/h4&gt;
  &lt;p id=&quot;KKx6&quot;&gt;Примечание&lt;/p&gt;
  &lt;p id=&quot;LeBS&quot;&gt;Обязательно установите Go версии 1.21 или выше.&lt;/p&gt;
  &lt;pre id=&quot;sYmE&quot;&gt;git clone https://github.com/EverythingSuckz/TG-FileStreamBot
cd TG-FileStreamBot
go build ./cmd/fsb/
Rename-Item -LiteralPath &amp;quot;.\fsb.sample.env&amp;quot; -NewName &amp;quot;.\fsb.env&amp;quot;
notepad fsb.env
# (добавьте свои переменные среды, подробнее см. в следующем разделе)
.\ управление ФСБ&lt;/pre&gt;
  &lt;p id=&quot;K6xe&quot;&gt;а чтобы остановить программу, сделайте CTRL+C&lt;/p&gt;
  &lt;h2 id=&quot;I0XV&quot;&gt;Налаживание отношений&lt;/h2&gt;
  &lt;p id=&quot;EZRs&quot;&gt;Если вы используете локальный хостинг, создайте файл с именем &lt;code&gt;fsb.env&lt;/code&gt; в корневом каталоге и добавьте в него все переменные. Вы можете проверить &lt;code&gt;fsb.sample.env&lt;/code&gt;. Пример файла &lt;code&gt;fsb.env&lt;/code&gt;:&lt;/p&gt;
  &lt;pre id=&quot;zbzJ&quot;&gt;API_ID=452525
API_HASH=esx576f8738x883f3sfzx83
BOT_TOKEN=55838383:вашботтокенздесь
LOG_CHANNEL=-10045145224562
PORT=8080
HOST=http://вашсерверип
# (если вы хотите настроить несколько ботов)
MULTI_TOKEN1=55838373: ваш токен для работника
MULTI_TOKEN2=55838355: ваш токен для работника&lt;/pre&gt;
  &lt;h3 id=&quot;xTvy&quot;&gt;Требуемые переменные&lt;/h3&gt;
  &lt;p id=&quot;r2Z3&quot;&gt;Перед запуском бота необходимо настроить следующие обязательные переменные:&lt;/p&gt;
  &lt;ul id=&quot;PRKC&quot;&gt;
    &lt;li id=&quot;D1b5&quot;&gt;&lt;code&gt;API_ID&lt;/code&gt; Это идентификатор API для вашей учётной записи Telegram, который можно получить на сайте my.telegram.org.&lt;/li&gt;
    &lt;li id=&quot;QGYQ&quot;&gt;&lt;code&gt;API_HASH&lt;/code&gt; Это хэш API для вашей учётной записи в Telegram, который также можно получить на сайте my.telegram.org.&lt;/li&gt;
    &lt;li id=&quot;Nmlq&quot;&gt;&lt;code&gt;BOT_TOKEN&lt;/code&gt; : Это токен бота Telegram Media Streamer Bot, который можно получить у &lt;a href=&quot;https://telegram.dog/BotFather&quot; target=&quot;_blank&quot;&gt;@BotFather&lt;/a&gt;.&lt;/li&gt;
    &lt;li id=&quot;cRxy&quot;&gt;&lt;code&gt;LOG_CHANNEL&lt;/code&gt; : Это идентификатор канала для журнала, в который бот будет пересылать медиафайлы и сохранять их, чтобы сгенерированные прямые ссылки работали. Чтобы получить идентификатор канала, создайте новый канал в Telegram (открытый или закрытый), опубликуйте что-нибудь в канале, перешлите сообщение на &lt;a href=&quot;https://telegram.dog/MissRose_bot&quot; target=&quot;_blank&quot;&gt;@missrose_bot&lt;/a&gt; и &lt;strong&gt;ответьте на пересланное сообщение&lt;/strong&gt; командой /id. Скопируйте идентификатор пересланного канала и вставьте его в это поле.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;4sHD&quot;&gt;Необязательные переменные&lt;/h3&gt;
  &lt;p id=&quot;kznr&quot;&gt;Помимо обязательных переменных, вы также можете задать следующие необязательные переменные:&lt;/p&gt;
  &lt;ul id=&quot;bqZD&quot;&gt;
    &lt;li id=&quot;JG5L&quot;&gt;&lt;code&gt;PORT&lt;/code&gt; Здесь указывается порт, который будет прослушивать ваше веб-приложение. Значение по умолчанию — 8080.&lt;/li&gt;
    &lt;li id=&quot;kLBo&quot;&gt;&lt;code&gt;HOST&lt;/code&gt; Полное доменное имя, если оно есть, или IP-адрес вашего сервера. (Например, &lt;code&gt;https://example.com&lt;/code&gt; или &lt;code&gt;http://14.1.154.2:8080&lt;/code&gt;)&lt;/li&gt;
    &lt;li id=&quot;RBF5&quot;&gt;&lt;code&gt;HASH_LENGTH&lt;/code&gt; : Пользовательская длина хеша для сгенерированных URL-адресов. Длина хеша должна быть больше 5 и меньше или равна 32. Значение по умолчанию — 6.&lt;/li&gt;
    &lt;li id=&quot;BANy&quot;&gt;&lt;code&gt;USE_SESSION_FILE&lt;/code&gt; : Используйте файлы сеанса для рабочих клиентов. Это ускоряет запуск рабочего бота. (по умолчанию: &lt;code&gt;false&lt;/code&gt;)&lt;/li&gt;
    &lt;li id=&quot;AlCj&quot;&gt;&lt;code&gt;USER_SESSION&lt;/code&gt; : Строка сеанса Pyrogram для пользовательского бота. Используется для автоматического добавления ботов в &lt;code&gt;LOG_CHANNEL&lt;/code&gt;. (по умолчанию: &lt;code&gt;null&lt;/code&gt;)&lt;/li&gt;
    &lt;li id=&quot;xPLu&quot;&gt;&lt;code&gt;ALLOWED_USERS&lt;/code&gt; : Список идентификаторов пользователей, разделённых запятыми (&lt;code&gt;,&lt;/code&gt;). Если этот параметр задан, только пользователи из этого списка смогут использовать бота. (по умолчанию: &lt;code&gt;null&lt;/code&gt;)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;hr /&gt;
  &lt;h3 id=&quot;57lX&quot;&gt;Используйте несколько ботов, чтобы ускорить процесс&lt;/h3&gt;
  &lt;p id=&quot;hOXt&quot;&gt;Примечание&lt;/p&gt;
  &lt;p id=&quot;T2RZ&quot;&gt;&lt;strong&gt;Что такое многопользовательская функция и для чего она нужна?&lt;/strong&gt;&lt;br /&gt;Эта функция распределяет запросы Telegram API между рабочими ботами, чтобы ускорить загрузку, когда сервер используют многие пользователи, и избежать ограничений на количество запросов, установленных Telegram.&lt;/p&gt;
  &lt;p id=&quot;Zf1R&quot;&gt;Примечание&lt;/p&gt;
  &lt;p id=&quot;NK4s&quot;&gt;Вы можете добавить до 50 ботов, так как 50 — это максимальное количество администраторов ботов, которых можно назначить в Telegram-канале.&lt;/p&gt;
  &lt;p id=&quot;nj0m&quot;&gt;Чтобы включить многопользовательский режим, сгенерируйте новые токены бота и добавьте их в &lt;code&gt;fsb.env&lt;/code&gt; со следующими именами ключей.&lt;/p&gt;
  &lt;p id=&quot;BwZO&quot;&gt;&lt;code&gt;MULTI_TOKEN1&lt;/code&gt;: Добавьте сюда свой первый токен бота.&lt;/p&gt;
  &lt;p id=&quot;vogo&quot;&gt;&lt;code&gt;MULTI_TOKEN2&lt;/code&gt;: Добавьте сюда токен второго бота.&lt;/p&gt;
  &lt;p id=&quot;xmdE&quot;&gt;Вы также можете добавить столько ботов, сколько захотите. (максимальное количество — 50) &lt;code&gt;MULTI_TOKEN3&lt;/code&gt;, &lt;code&gt;MULTI_TOKEN4&lt;/code&gt;, и т. д.&lt;/p&gt;
  &lt;p id=&quot;wAz5&quot;&gt;Предупреждение&lt;/p&gt;
  &lt;p id=&quot;XqNu&quot;&gt;Не забудьте добавить всех этих рабочих ботов в &lt;code&gt;LOG_CHANNEL&lt;/code&gt; для корректной работы&lt;/p&gt;
  &lt;h3 id=&quot;y8qU&quot;&gt;Использование сеанса пользователя для автоматической регистрации ботов&lt;/h3&gt;
  &lt;p id=&quot;mcTv&quot;&gt;Предупреждение&lt;/p&gt;
  &lt;p id=&quot;YHAu&quot;&gt;Иногда это может привести к блокировке или удалению вашей учётной записи. &lt;strong&gt;Этому подвержены только недавно созданные учётные записи.&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;xewU&quot;&gt;Чтобы воспользоваться этой функцией, вам нужно сгенерировать строку сеанса pyrogram для учётной записи пользователя и добавить её в переменную &lt;code&gt;USER_SESSION&lt;/code&gt; в файле &lt;code&gt;fsb.env&lt;/code&gt; .&lt;/p&gt;
  &lt;h4 id=&quot;HHTB&quot;&gt;Что он делает?&lt;/h4&gt;
  &lt;p id=&quot;Bh6H&quot;&gt;Эта функция используется для автоматического добавления рабочих ботов в &lt;code&gt;LOG_CHANNEL&lt;/code&gt; при их запуске. Это удобно, если у вас много рабочих ботов и вы не хотите добавлять их в &lt;code&gt;LOG_CHANNEL&lt;/code&gt; вручную.&lt;/p&gt;
  &lt;h4 id=&quot;1glJ&quot;&gt;Как сгенерировать строку сеанса?&lt;/h4&gt;
  &lt;p id=&quot;BzmP&quot;&gt;Самый простой способ сгенерировать строку сеанса — запустить&lt;/p&gt;
  &lt;pre id=&quot;ZdCL&quot;&gt;./fsb session --api-id &amp;lt;ваш идентификатор API&amp;gt; --api-hash &amp;lt;ваш хэш API&amp;gt;&lt;/pre&gt;
  &lt;figure id=&quot;59fT&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/41/ff/41ff4566-821c-47f3-bcfb-2ccaf05f0a76.png&quot; width=&quot;903&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;nPQC&quot;&gt;Это позволит сгенерировать строку сеанса для вашей учётной записи с помощью аутентификации по QR-коду. Аутентификация по номеру телефона пока не поддерживается и будет добавлена в будущем.&lt;/p&gt;
  &lt;p id=&quot;NyYn&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;mX48&quot;&gt;Сам код:&lt;/h2&gt;
  &lt;h2 id=&quot;uoHz&quot;&gt;✉.vscode&lt;/h2&gt;
  &lt;h3 id=&quot;cTvx&quot;&gt;                   ↪️launch.json&lt;/h3&gt;
  &lt;pre id=&quot;WX4n&quot; data-lang=&quot;python&quot;&gt;{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    &amp;quot;version&amp;quot;: &amp;quot;0.2.0&amp;quot;,
    &amp;quot;configurations&amp;quot;: [
        {
            &amp;quot;name&amp;quot;: &amp;quot;Launch Package&amp;quot;,
            &amp;quot;type&amp;quot;: &amp;quot;go&amp;quot;,
            &amp;quot;request&amp;quot;: &amp;quot;launch&amp;quot;,
            &amp;quot;mode&amp;quot;: &amp;quot;auto&amp;quot;,
            &amp;quot;program&amp;quot;: &amp;quot;./cmd/fsb/&amp;quot;,
            &amp;quot;args&amp;quot;: [
                &amp;quot;run&amp;quot;
            ]
        },
        {
            &amp;quot;name&amp;quot;: &amp;quot;Generate Session&amp;quot;,
            &amp;quot;type&amp;quot;: &amp;quot;go&amp;quot;,
            &amp;quot;request&amp;quot;: &amp;quot;launch&amp;quot;,
            &amp;quot;mode&amp;quot;: &amp;quot;auto&amp;quot;,
            &amp;quot;program&amp;quot;: &amp;quot;./cmd/fsb/&amp;quot;,
            &amp;quot;args&amp;quot;: [
                &amp;quot;session&amp;quot;
            ],
            &amp;quot;console&amp;quot;: &amp;quot;integratedTerminal&amp;quot;
        }
    ]
}&lt;/pre&gt;
  &lt;p id=&quot;tXpm&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;ewNo&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;yyzk&quot;&gt;✉cmd/fsb&lt;/h2&gt;
  &lt;h3 id=&quot;AACN&quot;&gt;        ↪️main.go&lt;/h3&gt;
  &lt;pre id=&quot;oyAc&quot;&gt;package main&lt;/pre&gt;
  &lt;pre id=&quot;xyFR&quot;&gt;import (
	&amp;quot;EverythingSuckz/fsb/config&amp;quot;
	&amp;quot;fmt&amp;quot;
	&amp;quot;os&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;cWaA&quot;&gt;	&amp;quot;github.com/spf13/cobra&amp;quot;
)&lt;/pre&gt;
  &lt;pre id=&quot;Of64&quot;&gt;const versionString = &amp;quot;3.1.0&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;hw1E&quot;&gt;var rootCmd = &amp;amp;cobra.Command{
	Use:               &amp;quot;fsb [command]&amp;quot;,
	Short:             &amp;quot;Telegram File Stream Bot&amp;quot;,
	Long:              &amp;quot;Telegram Bot to generate direct streamable links for telegram media.&amp;quot;,
	Example:           &amp;quot;fsb run --port 8080&amp;quot;,
	Version:           versionString,
	CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true},
	Run: func(cmd *cobra.Command, args []string) {
		cmd.Help()
	},
}&lt;/pre&gt;
  &lt;pre id=&quot;kx4g&quot;&gt;func init() {
	config.SetFlagsFromConfig(runCmd)
	rootCmd.AddCommand(runCmd)
	rootCmd.AddCommand(sessionCmd)
	rootCmd.SetVersionTemplate(fmt.Sprintf(&amp;#x60;Telegram File Stream Bot version %s&amp;#x60;, versionString))
}&lt;/pre&gt;
  &lt;pre id=&quot;M3qX&quot;&gt;func main() {
	if err := rootCmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}&lt;/pre&gt;
  &lt;p id=&quot;Y24P&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;iUB8&quot;&gt;↪️run.go&lt;/h3&gt;
  &lt;pre id=&quot;RqNW&quot;&gt;package main&lt;/pre&gt;
  &lt;pre id=&quot;fSJk&quot;&gt;import (
	&amp;quot;EverythingSuckz/fsb/config&amp;quot;
	&amp;quot;EverythingSuckz/fsb/internal/bot&amp;quot;
	&amp;quot;EverythingSuckz/fsb/internal/cache&amp;quot;
	&amp;quot;EverythingSuckz/fsb/internal/routes&amp;quot;
	&amp;quot;EverythingSuckz/fsb/internal/types&amp;quot;
	&amp;quot;EverythingSuckz/fsb/internal/utils&amp;quot;
	&amp;quot;fmt&amp;quot;
	&amp;quot;net/http&amp;quot;
	&amp;quot;time&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;CkxT&quot;&gt;	&amp;quot;github.com/spf13/cobra&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;x29U&quot;&gt;	&amp;quot;github.com/gin-gonic/gin&amp;quot;
	&amp;quot;go.uber.org/zap&amp;quot;
)&lt;/pre&gt;
  &lt;pre id=&quot;nvWG&quot;&gt;var runCmd = &amp;amp;cobra.Command{
	Use:                &amp;quot;run&amp;quot;,
	Short:              &amp;quot;Run the bot with the given configuration.&amp;quot;,
	DisableSuggestions: false,
	Run:                runApp,
}&lt;/pre&gt;
  &lt;pre id=&quot;JwIc&quot;&gt;var startTime time.Time = time.Now()&lt;/pre&gt;
  &lt;pre id=&quot;mjNF&quot;&gt;func runApp(cmd *cobra.Command, args []string) {
	utils.InitLogger(config.ValueOf.Dev)
	log := utils.Logger
	mainLogger := log.Named(&amp;quot;Main&amp;quot;)
	mainLogger.Info(&amp;quot;Starting server&amp;quot;)
	config.Load(log, cmd)
	router := getRouter(log)&lt;/pre&gt;
  &lt;pre id=&quot;j8Rc&quot;&gt;	mainBot, err := bot.StartClient(log)
	if err != nil {
		log.Panic(&amp;quot;Failed to start main bot&amp;quot;, zap.Error(err))
	}
	cache.InitCache(log)
	workers, err := bot.StartWorkers(log)
	if err != nil {
		log.Panic(&amp;quot;Failed to start workers&amp;quot;, zap.Error(err))
		return
	}
	workers.AddDefaultClient(mainBot, mainBot.Self)
	bot.StartUserBot(log)
	mainLogger.Info(&amp;quot;Server started&amp;quot;, zap.Int(&amp;quot;port&amp;quot;, config.ValueOf.Port))
	mainLogger.Info(&amp;quot;File Stream Bot&amp;quot;, zap.String(&amp;quot;version&amp;quot;, versionString))
	mainLogger.Sugar().Infof(&amp;quot;Server is running at %s&amp;quot;, config.ValueOf.Host)
	err = router.Run(fmt.Sprintf(&amp;quot;:%d&amp;quot;, config.ValueOf.Port))
	if err != nil {
		mainLogger.Sugar().Fatalln(err)
	}
}&lt;/pre&gt;
  &lt;pre id=&quot;qDDC&quot;&gt;func getRouter(log *zap.Logger) *gin.Engine {
	if config.ValueOf.Dev {
		gin.SetMode(gin.DebugMode)
	} else {
		gin.SetMode(gin.ReleaseMode)
	}
	router := gin.Default()
	router.Use(gin.ErrorLogger())
	router.GET(&amp;quot;/&amp;quot;, func(ctx *gin.Context) {
		ctx.JSON(http.StatusOK, types.RootResponse{
			Message: &amp;quot;Server is running.&amp;quot;,
			Ok:      true,
			Uptime:  utils.TimeFormat(uint64(time.Since(startTime).Seconds())),
			Version: versionString,
		})
	})
	routes.Load(log, router)
	return router
}&lt;/pre&gt;
  &lt;p id=&quot;HqC0&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;bZ5Q&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;rxXN&quot;&gt;↪️session.go&lt;/h3&gt;
  &lt;pre id=&quot;MCoD&quot;&gt;package main&lt;/pre&gt;
  &lt;pre id=&quot;OjNa&quot;&gt;import (
	&amp;quot;fmt&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;z8GR&quot;&gt;	&amp;quot;EverythingSuckz/fsb/pkg/qrlogin&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;KtWG&quot;&gt;	&amp;quot;github.com/spf13/cobra&amp;quot;
)&lt;/pre&gt;
  &lt;pre id=&quot;BlYM&quot;&gt;var sessionCmd = &amp;amp;cobra.Command{
	Use:                &amp;quot;session&amp;quot;,
	Short:              &amp;quot;Generate a string session.&amp;quot;,
	DisableSuggestions: false,
	Run:                generateSession,
}&lt;/pre&gt;
  &lt;pre id=&quot;zZt0&quot;&gt;func init() {
	sessionCmd.Flags().StringP(&amp;quot;login-type&amp;quot;, &amp;quot;T&amp;quot;, &amp;quot;qr&amp;quot;, &amp;quot;The login type to use. Can be either &amp;#x27;qr&amp;#x27; or &amp;#x27;phone&amp;#x27;&amp;quot;)
	sessionCmd.Flags().Int32P(&amp;quot;api-id&amp;quot;, &amp;quot;I&amp;quot;, 0, &amp;quot;The API ID to use for the session (required).&amp;quot;)
	sessionCmd.Flags().StringP(&amp;quot;api-hash&amp;quot;, &amp;quot;H&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;The API hash to use for the session (required).&amp;quot;)
	sessionCmd.MarkFlagRequired(&amp;quot;api-id&amp;quot;)
	sessionCmd.MarkFlagRequired(&amp;quot;api-hash&amp;quot;)
}&lt;/pre&gt;
  &lt;pre id=&quot;Pzer&quot;&gt;func generateSession(cmd *cobra.Command, args []string) {
	loginType, _ := cmd.Flags().GetString(&amp;quot;login-type&amp;quot;)
	apiId, _ := cmd.Flags().GetInt32(&amp;quot;api-id&amp;quot;)
	apiHash, _ := cmd.Flags().GetString(&amp;quot;api-hash&amp;quot;)
	if loginType == &amp;quot;qr&amp;quot; {
		qrlogin.GenerateQRSession(int(apiId), apiHash)
	} else if loginType == &amp;quot;phone&amp;quot; {
		generatePhoneSession()
	} else {
		fmt.Println(&amp;quot;Invalid login type. Please use either &amp;#x27;qr&amp;#x27; or &amp;#x27;phone&amp;#x27;&amp;quot;)
	}
}&lt;/pre&gt;
  &lt;pre id=&quot;D60I&quot;&gt;func generatePhoneSession() {
	fmt.Println(&amp;quot;Phone session is not implemented yet.&amp;quot;)
}&lt;/pre&gt;
  &lt;p id=&quot;6QF2&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;r90J&quot;&gt;&lt;/h2&gt;
  &lt;h2 id=&quot;aKLy&quot;&gt;✉config&lt;/h2&gt;
  &lt;h3 id=&quot;ZHnC&quot;&gt;               ↪️config.go&lt;/h3&gt;
  &lt;pre id=&quot;2CV2&quot;&gt;package config&lt;/pre&gt;
  &lt;pre id=&quot;RUdj&quot;&gt;import (
	&amp;quot;errors&amp;quot;
	&amp;quot;io&amp;quot;
	&amp;quot;net&amp;quot;
	&amp;quot;net/http&amp;quot;
	&amp;quot;os&amp;quot;
	&amp;quot;path/filepath&amp;quot;
	&amp;quot;reflect&amp;quot;
	&amp;quot;regexp&amp;quot;
	&amp;quot;strconv&amp;quot;
	&amp;quot;strings&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;TJLs&quot;&gt;	&amp;quot;github.com/joho/godotenv&amp;quot;
	&amp;quot;github.com/kelseyhightower/envconfig&amp;quot;
	&amp;quot;github.com/spf13/cobra&amp;quot;
	&amp;quot;go.uber.org/zap&amp;quot;
)&lt;/pre&gt;
  &lt;pre id=&quot;lStj&quot;&gt;var ValueOf = &amp;amp;config{}&lt;/pre&gt;
  &lt;pre id=&quot;NeDw&quot;&gt;type allowedUsers []int64&lt;/pre&gt;
  &lt;pre id=&quot;4bNs&quot;&gt;func (au *allowedUsers) Decode(value string) error {
	if value == &amp;quot;&amp;quot; {
		return nil
	}
	ids := strings.Split(string(value), &amp;quot;,&amp;quot;)
	for _, id := range ids {
		idInt, err := strconv.ParseInt(id, 10, 64)
		if err != nil {
			return err
		}
		*au = append(*au, idInt)
	}
	return nil
}&lt;/pre&gt;
  &lt;pre id=&quot;cE2P&quot;&gt;type config struct {
	ApiID          int32        &amp;#x60;envconfig:&amp;quot;API_ID&amp;quot; required:&amp;quot;true&amp;quot;&amp;#x60;
	ApiHash        string       &amp;#x60;envconfig:&amp;quot;API_HASH&amp;quot; required:&amp;quot;true&amp;quot;&amp;#x60;
	BotToken       string       &amp;#x60;envconfig:&amp;quot;BOT_TOKEN&amp;quot; required:&amp;quot;true&amp;quot;&amp;#x60;
	LogChannelID   int64        &amp;#x60;envconfig:&amp;quot;LOG_CHANNEL&amp;quot; required:&amp;quot;true&amp;quot;&amp;#x60;
	Dev            bool         &amp;#x60;envconfig:&amp;quot;DEV&amp;quot; default:&amp;quot;false&amp;quot;&amp;#x60;
	Port           int          &amp;#x60;envconfig:&amp;quot;PORT&amp;quot; default:&amp;quot;8080&amp;quot;&amp;#x60;
	Host           string       &amp;#x60;envconfig:&amp;quot;HOST&amp;quot; default:&amp;quot;&amp;quot;&amp;#x60;
	HashLength     int          &amp;#x60;envconfig:&amp;quot;HASH_LENGTH&amp;quot; default:&amp;quot;6&amp;quot;&amp;#x60;
	UseSessionFile bool         &amp;#x60;envconfig:&amp;quot;USE_SESSION_FILE&amp;quot; default:&amp;quot;true&amp;quot;&amp;#x60;
	UserSession    string       &amp;#x60;envconfig:&amp;quot;USER_SESSION&amp;quot;&amp;#x60;
	UsePublicIP    bool         &amp;#x60;envconfig:&amp;quot;USE_PUBLIC_IP&amp;quot; default:&amp;quot;false&amp;quot;&amp;#x60;
	AllowedUsers   allowedUsers &amp;#x60;envconfig:&amp;quot;ALLOWED_USERS&amp;quot;&amp;#x60;
	MultiTokens    []string
}&lt;/pre&gt;
  &lt;pre id=&quot;qWqA&quot;&gt;var botTokenRegex = regexp.MustCompile(&amp;#x60;MULTI\_TOKEN\d+=(.*)&amp;#x60;)&lt;/pre&gt;
  &lt;pre id=&quot;Meef&quot;&gt;func (c *config) loadFromEnvFile(log *zap.Logger) {
	envPath := filepath.Clean(&amp;quot;fsb.env&amp;quot;)
	log.Sugar().Infof(&amp;quot;Trying to load ENV vars from %s&amp;quot;, envPath)
	err := godotenv.Load(envPath)
	if err != nil {
		if os.IsNotExist(err) {
			log.Sugar().Errorf(&amp;quot;ENV file not found: %s&amp;quot;, envPath)
			log.Sugar().Info(&amp;quot;Please create fsb.env file&amp;quot;)
			log.Sugar().Info(&amp;quot;For more info, refer: https://github.com/EverythingSuckz/TG-FileStreamBot/tree/golang#setting-up-things&amp;quot;)
			log.Sugar().Info(&amp;quot;Please ignore this message if you are hosting it in a service like Heroku or other alternatives.&amp;quot;)
		} else {
			log.Fatal(&amp;quot;Unknown error while parsing env file.&amp;quot;, zap.Error(err))
		}
	}
}&lt;/pre&gt;
  &lt;pre id=&quot;iz7p&quot;&gt;func SetFlagsFromConfig(cmd *cobra.Command) {
	cmd.Flags().Int32(&amp;quot;api-id&amp;quot;, ValueOf.ApiID, &amp;quot;Telegram API ID&amp;quot;)
	cmd.Flags().String(&amp;quot;api-hash&amp;quot;, ValueOf.ApiHash, &amp;quot;Telegram API Hash&amp;quot;)
	cmd.Flags().String(&amp;quot;bot-token&amp;quot;, ValueOf.BotToken, &amp;quot;Telegram Bot Token&amp;quot;)
	cmd.Flags().Int64(&amp;quot;log-channel&amp;quot;, ValueOf.LogChannelID, &amp;quot;Telegram Log Channel ID&amp;quot;)
	cmd.Flags().Bool(&amp;quot;dev&amp;quot;, ValueOf.Dev, &amp;quot;Enable development mode&amp;quot;)
	cmd.Flags().IntP(&amp;quot;port&amp;quot;, &amp;quot;p&amp;quot;, ValueOf.Port, &amp;quot;Server port&amp;quot;)
	cmd.Flags().String(&amp;quot;host&amp;quot;, ValueOf.Host, &amp;quot;Server host that will be included in links&amp;quot;)
	cmd.Flags().Int(&amp;quot;hash-length&amp;quot;, ValueOf.HashLength, &amp;quot;Hash length in links&amp;quot;)
	cmd.Flags().Bool(&amp;quot;use-session-file&amp;quot;, ValueOf.UseSessionFile, &amp;quot;Use session files&amp;quot;)
	cmd.Flags().String(&amp;quot;user-session&amp;quot;, ValueOf.UserSession, &amp;quot;Pyrogram user session&amp;quot;)
	cmd.Flags().Bool(&amp;quot;use-public-ip&amp;quot;, ValueOf.UsePublicIP, &amp;quot;Use public IP instead of local IP&amp;quot;)
	cmd.Flags().String(&amp;quot;multi-token-txt-file&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;Multi token txt file (Not implemented)&amp;quot;)
}&lt;/pre&gt;
  &lt;pre id=&quot;8frO&quot;&gt;func (c *config) loadConfigFromArgs(log *zap.Logger, cmd *cobra.Command) {
	apiID, _ := cmd.Flags().GetInt32(&amp;quot;api-id&amp;quot;)
	if apiID != 0 {
		os.Setenv(&amp;quot;API_ID&amp;quot;, strconv.Itoa(int(apiID)))
	}
	apiHash, _ := cmd.Flags().GetString(&amp;quot;api-hash&amp;quot;)
	if apiHash != &amp;quot;&amp;quot; {
		os.Setenv(&amp;quot;API_HASH&amp;quot;, apiHash)
	}
	botToken, _ := cmd.Flags().GetString(&amp;quot;bot-token&amp;quot;)
	if botToken != &amp;quot;&amp;quot; {
		os.Setenv(&amp;quot;BOT_TOKEN&amp;quot;, botToken)
	}
	logChannelID, _ := cmd.Flags().GetString(&amp;quot;log-channel&amp;quot;)
	if logChannelID != &amp;quot;&amp;quot; {
		os.Setenv(&amp;quot;LOG_CHANNEL&amp;quot;, logChannelID)
	}
	dev, _ := cmd.Flags().GetBool(&amp;quot;dev&amp;quot;)
	if dev {
		os.Setenv(&amp;quot;DEV&amp;quot;, strconv.FormatBool(dev))
	}
	port, _ := cmd.Flags().GetInt(&amp;quot;port&amp;quot;)
	if port != 0 {
		os.Setenv(&amp;quot;PORT&amp;quot;, strconv.Itoa(port))
	}
	host, _ := cmd.Flags().GetString(&amp;quot;host&amp;quot;)
	if host != &amp;quot;&amp;quot; {
		os.Setenv(&amp;quot;HOST&amp;quot;, host)
	}
	hashLength, _ := cmd.Flags().GetInt(&amp;quot;hash-length&amp;quot;)
	if hashLength != 0 {
		os.Setenv(&amp;quot;HASH_LENGTH&amp;quot;, strconv.Itoa(hashLength))
	}
	useSessionFile, _ := cmd.Flags().GetBool(&amp;quot;use-session-file&amp;quot;)
	if useSessionFile {
		os.Setenv(&amp;quot;USE_SESSION_FILE&amp;quot;, strconv.FormatBool(useSessionFile))
	}
	userSession, _ := cmd.Flags().GetString(&amp;quot;user-session&amp;quot;)
	if userSession != &amp;quot;&amp;quot; {
		os.Setenv(&amp;quot;USER_SESSION&amp;quot;, userSession)
	}
	usePublicIP, _ := cmd.Flags().GetBool(&amp;quot;use-public-ip&amp;quot;)
	if usePublicIP {
		os.Setenv(&amp;quot;USE_PUBLIC_IP&amp;quot;, strconv.FormatBool(usePublicIP))
	}
	multiTokens, _ := cmd.Flags().GetString(&amp;quot;multi-token-txt-file&amp;quot;)
	if multiTokens != &amp;quot;&amp;quot; {
		os.Setenv(&amp;quot;MULTI_TOKEN_TXT_FILE&amp;quot;, multiTokens)
		// TODO: Add support for importing tokens from a separate file
	}
}&lt;/pre&gt;
  &lt;pre id=&quot;72Kk&quot;&gt;func (c *config) setupEnvVars(log *zap.Logger, cmd *cobra.Command) {
	c.loadFromEnvFile(log)
	c.loadConfigFromArgs(log, cmd)
	err := envconfig.Process(&amp;quot;&amp;quot;, c)
	if err != nil {
		log.Fatal(&amp;quot;Error while parsing env variables&amp;quot;, zap.Error(err))
	}
	var ipBlocked bool
	ip, err := getIP(c.UsePublicIP)
	if err != nil {
		log.Error(&amp;quot;Error while getting IP&amp;quot;, zap.Error(err))
		ipBlocked = true
	}
	if c.Host == &amp;quot;&amp;quot; {
		c.Host = &amp;quot;http://&amp;quot; + ip + &amp;quot;:&amp;quot; + strconv.Itoa(c.Port)
		if c.UsePublicIP {
			if ipBlocked {
				log.Sugar().Warn(&amp;quot;Can&amp;#x27;t get public IP, using local IP&amp;quot;)
			} else {
				log.Sugar().Warn(&amp;quot;You are using a public IP, please be aware of the security risks while exposing your IP to the internet.&amp;quot;)
				log.Sugar().Warn(&amp;quot;Use &amp;#x27;HOST&amp;#x27; variable to set a domain name&amp;quot;)
			}
		}
		log.Sugar().Info(&amp;quot;HOST not set, automatically set to &amp;quot; + c.Host)
	}
	val := reflect.ValueOf(c).Elem()
	for _, env := range os.Environ() {
		if strings.HasPrefix(env, &amp;quot;MULTI_TOKEN&amp;quot;) {
			c.MultiTokens = append(c.MultiTokens, botTokenRegex.FindStringSubmatch(env)[1])
		}
	}
	val.FieldByName(&amp;quot;MultiTokens&amp;quot;).Set(reflect.ValueOf(c.MultiTokens))
}&lt;/pre&gt;
  &lt;pre id=&quot;iKeK&quot;&gt;func Load(log *zap.Logger, cmd *cobra.Command) {
	log = log.Named(&amp;quot;Config&amp;quot;)
	defer log.Info(&amp;quot;Loaded config&amp;quot;)
	ValueOf.setupEnvVars(log, cmd)
	ValueOf.LogChannelID = int64(stripInt(log, int(ValueOf.LogChannelID)))
	if ValueOf.HashLength == 0 {
		log.Sugar().Info(&amp;quot;HASH_LENGTH can&amp;#x27;t be 0, defaulting to 6&amp;quot;)
		ValueOf.HashLength = 6
	}
	if ValueOf.HashLength &amp;gt; 32 {
		log.Sugar().Info(&amp;quot;HASH_LENGTH can&amp;#x27;t be more than 32, changing to 32&amp;quot;)
		ValueOf.HashLength = 32
	}
	if ValueOf.HashLength &amp;lt; 5 {
		log.Sugar().Info(&amp;quot;HASH_LENGTH can&amp;#x27;t be less than 5, defaulting to 6&amp;quot;)
		ValueOf.HashLength = 6
	}
}&lt;/pre&gt;
  &lt;pre id=&quot;U8z9&quot;&gt;func getIP(public bool) (string, error) {
	var ip string
	var err error
	if public {
		ip, err = GetPublicIP()
	} else {
		ip, err = getInternalIP()
	}
	if ip == &amp;quot;&amp;quot; {
		ip = &amp;quot;localhost&amp;quot;
	}
	if err != nil {
		return &amp;quot;localhost&amp;quot;, err
	}
	return ip, nil
}&lt;/pre&gt;
  &lt;pre id=&quot;NZGF&quot;&gt;// https://stackoverflow.com/a/23558495/15807350
func getInternalIP() (string, error) {
	conn, err := net.Dial(&amp;quot;udp&amp;quot;, &amp;quot;8.8.8.8:80&amp;quot;)
	if err != nil {
		return &amp;quot;&amp;quot;, errors.New(&amp;quot;no internet connection&amp;quot;)
	}
	defer conn.Close()
	localAddr := conn.LocalAddr().(*net.UDPAddr)
	return localAddr.IP.String(), nil
}&lt;/pre&gt;
  &lt;pre id=&quot;8330&quot;&gt;func GetPublicIP() (string, error) {
	resp, err := http.Get(&amp;quot;https://api.ipify.org?format=text&amp;quot;)
	if err != nil {
		return &amp;quot;&amp;quot;, err
	}
	defer resp.Body.Close()
	ip, err := io.ReadAll(resp.Body)
	if err != nil {
		return &amp;quot;&amp;quot;, err
	}
	if !checkIfIpAccessible(string(ip)) {
		return string(ip), errors.New(&amp;quot;PORT is blocked by firewall&amp;quot;)
	}
	return string(ip), nil
}&lt;/pre&gt;
  &lt;pre id=&quot;rYPm&quot;&gt;func checkIfIpAccessible(ip string) bool {
	conn, err := net.Dial(&amp;quot;tcp&amp;quot;, ip+&amp;quot;:80&amp;quot;)
	if err != nil {
		return false
	}
	defer conn.Close()
	return true
}&lt;/pre&gt;
  &lt;pre id=&quot;Erla&quot;&gt;func stripInt(log *zap.Logger, a int) int {
	strA := strconv.Itoa(abs(a))
	lastDigits := strings.Replace(strA, &amp;quot;100&amp;quot;, &amp;quot;&amp;quot;, 1)
	result, err := strconv.Atoi(lastDigits)
	if err != nil {
		log.Sugar().Fatalln(err)
		return 0
	}
	return result
}&lt;/pre&gt;
  &lt;pre id=&quot;Rhau&quot;&gt;func abs(x int) int {
	if x &amp;lt; 0 {
		return -x
	}
	return x
}&lt;/pre&gt;
  &lt;p id=&quot;wycF&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;CYSS&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;PBN0&quot;&gt;✉internal&lt;/h2&gt;
  &lt;h3 id=&quot;JK06&quot;&gt;                ✉bot&lt;/h3&gt;
  &lt;h3 id=&quot;HhwM&quot;&gt;                         ↪️client.go&lt;/h3&gt;
  &lt;pre id=&quot;0cVV&quot;&gt;package bot&lt;/pre&gt;
  &lt;pre id=&quot;IxuA&quot;&gt;import (
	&amp;quot;EverythingSuckz/fsb/config&amp;quot;
	&amp;quot;EverythingSuckz/fsb/internal/commands&amp;quot;
	&amp;quot;context&amp;quot;
	&amp;quot;time&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;cEma&quot;&gt;	&amp;quot;go.uber.org/zap&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;EhrW&quot;&gt;	&amp;quot;github.com/celestix/gotgproto&amp;quot;
	&amp;quot;github.com/celestix/gotgproto/sessionMaker&amp;quot;
	&amp;quot;github.com/glebarez/sqlite&amp;quot;
)&lt;/pre&gt;
  &lt;pre id=&quot;ZWbu&quot;&gt;var Bot *gotgproto.Client&lt;/pre&gt;
  &lt;pre id=&quot;BexC&quot;&gt;func StartClient(log *zap.Logger) (*gotgproto.Client, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
	defer cancel()
	resultChan := make(chan struct {
		client *gotgproto.Client
		err    error
	})
	go func(ctx context.Context) {
		client, err := gotgproto.NewClient(
			int(config.ValueOf.ApiID),
			config.ValueOf.ApiHash,
			gotgproto.ClientTypeBot(config.ValueOf.BotToken),
			&amp;amp;gotgproto.ClientOpts{
				Session: sessionMaker.SqlSession(
					sqlite.Open(&amp;quot;fsb.session&amp;quot;),
				),
				DisableCopyright: true,
			},
		)
		resultChan &amp;lt;- struct {
			client *gotgproto.Client
			err    error
		}{client, err}
	}(ctx)&lt;/pre&gt;
  &lt;pre id=&quot;q0nO&quot;&gt;	select {
	case &amp;lt;-ctx.Done():
		return nil, ctx.Err()
	case result := &amp;lt;-resultChan:
		if result.err != nil {
			return nil, result.err
		}
		commands.Load(log, result.client.Dispatcher)
		log.Info(&amp;quot;Client started&amp;quot;, zap.String(&amp;quot;username&amp;quot;, result.client.Self.Username))
		Bot = result.client
		return result.client, nil
	}
}&lt;/pre&gt;
  &lt;p id=&quot;cN1Y&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;uFcY&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;B6Qm&quot;&gt; ↪️middleware.go&lt;/h3&gt;
  &lt;pre id=&quot;EE5f&quot;&gt;package bot&lt;/pre&gt;
  &lt;pre id=&quot;URmJ&quot;&gt;import (
	&amp;quot;time&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;csmd&quot;&gt;	&amp;quot;github.com/gotd/contrib/middleware/floodwait&amp;quot;
	&amp;quot;github.com/gotd/contrib/middleware/ratelimit&amp;quot;
	&amp;quot;github.com/gotd/td/telegram&amp;quot;
	&amp;quot;go.uber.org/zap&amp;quot;
	&amp;quot;golang.org/x/time/rate&amp;quot;
)&lt;/pre&gt;
  &lt;pre id=&quot;j3lm&quot;&gt;func GetFloodMiddleware(log *zap.Logger) []telegram.Middleware {
	waiter := floodwait.NewSimpleWaiter().WithMaxRetries(10)
	ratelimiter := ratelimit.New(rate.Every(time.Millisecond*100), 5)
	return []telegram.Middleware{
		waiter,
		ratelimiter,
	}
}&lt;/pre&gt;
  &lt;p id=&quot;Ex6e&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;daYE&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;EQfh&quot;&gt;↪️userbot.go&lt;/h3&gt;
  &lt;pre id=&quot;pewJ&quot;&gt;package bot&lt;/pre&gt;
  &lt;pre id=&quot;dY8E&quot;&gt;import (
	&amp;quot;EverythingSuckz/fsb/config&amp;quot;
	&amp;quot;errors&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;wKMy&quot;&gt;	&amp;quot;github.com/celestix/gotgproto&amp;quot;
	&amp;quot;github.com/celestix/gotgproto/sessionMaker&amp;quot;
	&amp;quot;github.com/gotd/td/tg&amp;quot;
	&amp;quot;go.uber.org/zap&amp;quot;
)&lt;/pre&gt;
  &lt;pre id=&quot;UJW1&quot;&gt;type UserBotStruct struct {
	log    *zap.Logger
	client *gotgproto.Client
}&lt;/pre&gt;
  &lt;pre id=&quot;094m&quot;&gt;var UserBot *UserBotStruct = &amp;amp;UserBotStruct{}&lt;/pre&gt;
  &lt;pre id=&quot;64Y8&quot;&gt;func StartUserBot(l *zap.Logger) {
	log := l.Named(&amp;quot;USERBOT&amp;quot;)
	if config.ValueOf.UserSession == &amp;quot;&amp;quot; {
		log.Warn(&amp;quot;User session is empty&amp;quot;)
		return
	}
	log.Sugar().Infoln(&amp;quot;Starting userbot&amp;quot;)
	client, err := gotgproto.NewClient(
		int(config.ValueOf.ApiID),
		config.ValueOf.ApiHash,
		gotgproto.ClientTypePhone(&amp;quot;&amp;quot;),
		&amp;amp;gotgproto.ClientOpts{
			Session:          sessionMaker.PyrogramSession(config.ValueOf.UserSession),
			DisableCopyright: true,
		},
	)
	if err != nil {
		log.Error(&amp;quot;Failed to start userbot&amp;quot;, zap.Error(err))
		return
	}
	UserBot.log = log
	UserBot.client = client
	log.Info(&amp;quot;Userbot started&amp;quot;, zap.String(&amp;quot;username&amp;quot;, client.Self.Username), zap.String(&amp;quot;FirstName&amp;quot;, client.Self.FirstName), zap.String(&amp;quot;LastName&amp;quot;, client.Self.LastName))
	if err := UserBot.AddBotsAsAdmins(); err != nil {
		log.Error(&amp;quot;Failed to add bots as admins&amp;quot;, zap.Error(err))
		return
	}
}&lt;/pre&gt;
  &lt;pre id=&quot;VDgk&quot;&gt;func (u *UserBotStruct) AddBotsAsAdmins() error {
	u.log.Info(&amp;quot;Preparing to add bots as admins&amp;quot;)
	ctx := u.client.CreateContext()
	channel := config.ValueOf.LogChannelID
	channelInfos, err := u.client.API().ChannelsGetChannels(
		ctx,
		[]tg.InputChannelClass{
			&amp;amp;tg.InputChannel{
				ChannelID: channel,
			},
		},
	)
	if err != nil {
		u.log.Error(&amp;quot;Failed to get channel info&amp;quot;, zap.Error(err))
		return errors.New(&amp;quot;failed to get channel info&amp;quot;)
	}
	if len(channelInfos.GetChats()) == 0 {
		return errors.New(&amp;quot;no channels found&amp;quot;)
	}
	inputChannel := channelInfos.GetChats()[0].(*tg.Channel).AsInput()
	currentAdmins := []int64{}
	admins, err := u.client.API().ChannelsGetParticipants(ctx, &amp;amp;tg.ChannelsGetParticipantsRequest{
		Channel: inputChannel,
		Filter:  &amp;amp;tg.ChannelParticipantsAdmins{},
		Offset:  0,
		Limit:   100,
	})
	if err != nil {
		u.log.Error(&amp;quot;Failed to get admins&amp;quot;, zap.Error(err))
		return err
	}
	for _, admin := range admins.(*tg.ChannelsChannelParticipants).Participants {
		if user, ok := admin.(*tg.ChannelParticipantAdmin); ok {
			currentAdmins = append(currentAdmins, user.UserID)
		}
	}
	for _, bot := range Workers.Bots {
		isAdmin := false
		for _, admin := range currentAdmins {
			if admin == bot.Self.ID {
				u.log.Sugar().Infof(&amp;quot;Bot @%s is already an admin&amp;quot;, bot.Self.Username)
				isAdmin = true
				continue
			}
		}
		if isAdmin {
			continue
		}
		botInfo, err := ctx.ResolveUsername(bot.Self.Username)
		if err != nil {
			u.log.Warn(err.Error())
		}
		_, err = u.client.API().ChannelsEditAdmin(
			u.client.CreateContext().Context,
			&amp;amp;tg.ChannelsEditAdminRequest{
				Channel: inputChannel,
				UserID:  botInfo.GetInputUser(),
				AdminRights: tg.ChatAdminRights{
					PostMessages: true,
				},
				Rank: &amp;quot;admin&amp;quot;,
			},
		)
		if err != nil {
			u.log.Sugar().Warnf(&amp;quot;Failed to add @%s as admin&amp;quot;, bot.Self.Username)
			u.log.Warn(err.Error())
		}
		u.log.Sugar().Infof(&amp;quot;Added @%s as admin&amp;quot;, bot.Self.Username)
	}
	return nil
}&lt;/pre&gt;
  &lt;p id=&quot;cPd5&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;Wpcz&quot;&gt;↪️workers.go&lt;/h3&gt;
  &lt;pre id=&quot;tvkg&quot;&gt;package bot&lt;/pre&gt;
  &lt;pre id=&quot;DVLJ&quot;&gt;import (
	&amp;quot;EverythingSuckz/fsb/config&amp;quot;
	&amp;quot;context&amp;quot;
	&amp;quot;fmt&amp;quot;
	&amp;quot;os&amp;quot;
	&amp;quot;path/filepath&amp;quot;
	&amp;quot;sync&amp;quot;
	&amp;quot;sync/atomic&amp;quot;
	&amp;quot;time&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;CtLM&quot;&gt;	&amp;quot;github.com/celestix/gotgproto&amp;quot;
	&amp;quot;github.com/celestix/gotgproto/sessionMaker&amp;quot;
	&amp;quot;github.com/glebarez/sqlite&amp;quot;
	&amp;quot;github.com/gotd/td/tg&amp;quot;
	&amp;quot;go.uber.org/zap&amp;quot;
)&lt;/pre&gt;
  &lt;pre id=&quot;HxO3&quot;&gt;type Worker struct {
	ID     int
	Client *gotgproto.Client
	Self   *tg.User
	log    *zap.Logger
}&lt;/pre&gt;
  &lt;pre id=&quot;SsFk&quot;&gt;func (w *Worker) String() string {
	return fmt.Sprintf(&amp;quot;{Worker (%d|@%s)}&amp;quot;, w.ID, w.Self.Username)
}&lt;/pre&gt;
  &lt;pre id=&quot;U8Sb&quot;&gt;type BotWorkers struct {
	Bots     []*Worker
	starting int
	index    int
	mut      sync.Mutex
	log      *zap.Logger
}&lt;/pre&gt;
  &lt;pre id=&quot;KpRK&quot;&gt;var Workers *BotWorkers = &amp;amp;BotWorkers{
	log:  nil,
	Bots: make([]*Worker, 0),
}&lt;/pre&gt;
  &lt;pre id=&quot;AtPw&quot;&gt;func (w *BotWorkers) Init(log *zap.Logger) {
	w.log = log.Named(&amp;quot;Workers&amp;quot;)
}&lt;/pre&gt;
  &lt;pre id=&quot;Qylp&quot;&gt;func (w *BotWorkers) AddDefaultClient(client *gotgproto.Client, self *tg.User) {
	if w.Bots == nil {
		w.Bots = make([]*Worker, 0)
	}
	w.incStarting()
	w.Bots = append(w.Bots, &amp;amp;Worker{
		Client: client,
		ID:     w.starting,
		Self:   self,
		log:    w.log,
	})
	w.log.Sugar().Info(&amp;quot;Default bot loaded&amp;quot;)
}&lt;/pre&gt;
  &lt;pre id=&quot;vjF9&quot;&gt;func (w *BotWorkers) incStarting() {
	w.mut.Lock()
	defer w.mut.Unlock()
	w.starting++
}&lt;/pre&gt;
  &lt;pre id=&quot;L3wY&quot;&gt;func (w *BotWorkers) Add(token string) (err error) {
	w.incStarting()
	var botID int = w.starting
	client, err := startWorker(w.log, token, botID)
	if err != nil {
		return err
	}
	w.log.Sugar().Infof(&amp;quot;Bot @%s loaded with ID %d&amp;quot;, client.Self.Username, botID)
	w.Bots = append(w.Bots, &amp;amp;Worker{
		Client: client,
		ID:     botID,
		Self:   client.Self,
		log:    w.log,
	})
	return nil
}&lt;/pre&gt;
  &lt;pre id=&quot;Fsbv&quot;&gt;func GetNextWorker() *Worker {
	Workers.mut.Lock()
	defer Workers.mut.Unlock()
	index := (Workers.index + 1) % len(Workers.Bots)
	Workers.index = index
	worker := Workers.Bots[index]
	Workers.log.Sugar().Debugf(&amp;quot;Using worker %d&amp;quot;, worker.ID)
	return worker
}&lt;/pre&gt;
  &lt;pre id=&quot;pnRC&quot;&gt;func StartWorkers(log *zap.Logger) (*BotWorkers, error) {
	Workers.Init(log)&lt;/pre&gt;
  &lt;pre id=&quot;RAjS&quot;&gt;	if len(config.ValueOf.MultiTokens) == 0 {
		Workers.log.Sugar().Info(&amp;quot;No worker bot tokens provided, skipping worker initialization&amp;quot;)
		return Workers, nil
	}
	Workers.log.Sugar().Info(&amp;quot;Starting&amp;quot;)
	if config.ValueOf.UseSessionFile {
		Workers.log.Sugar().Info(&amp;quot;Using session file for workers&amp;quot;)
		newpath := filepath.Join(&amp;quot;.&amp;quot;, &amp;quot;sessions&amp;quot;)
		if err := os.MkdirAll(newpath, os.ModePerm); err != nil {
			Workers.log.Error(&amp;quot;Failed to create sessions directory&amp;quot;, zap.Error(err))
			return nil, err
		}
	}&lt;/pre&gt;
  &lt;pre id=&quot;B0R3&quot;&gt;	var wg sync.WaitGroup
	var successfulStarts int32
	totalBots := len(config.ValueOf.MultiTokens)&lt;/pre&gt;
  &lt;pre id=&quot;dAjJ&quot;&gt;	for i := 0; i &amp;lt; totalBots; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()&lt;/pre&gt;
  &lt;pre id=&quot;tHL8&quot;&gt;			ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
			defer cancel()&lt;/pre&gt;
  &lt;pre id=&quot;OZK5&quot;&gt;			done := make(chan error, 1)
			go func() {
				err := Workers.Add(config.ValueOf.MultiTokens[i])
				done &amp;lt;- err
			}()&lt;/pre&gt;
  &lt;pre id=&quot;DL1o&quot;&gt;			select {
			case err := &amp;lt;-done:
				if err != nil {
					Workers.log.Error(&amp;quot;Failed to start worker&amp;quot;, zap.Int(&amp;quot;index&amp;quot;, i), zap.Error(err))
				} else {
					atomic.AddInt32(&amp;amp;successfulStarts, 1)
				}
			case &amp;lt;-ctx.Done():
				Workers.log.Error(&amp;quot;Timed out starting worker&amp;quot;, zap.Int(&amp;quot;index&amp;quot;, i))
			}
		}(i)
	}&lt;/pre&gt;
  &lt;pre id=&quot;AfC0&quot;&gt;	wg.Wait() // Wait for all goroutines to finish
	Workers.log.Sugar().Infof(&amp;quot;Successfully started %d/%d bots&amp;quot;, successfulStarts, totalBots)
	return Workers, nil
}&lt;/pre&gt;
  &lt;pre id=&quot;Z7oG&quot;&gt;func startWorker(l *zap.Logger, botToken string, index int) (*gotgproto.Client, error) {
	log := l.Named(&amp;quot;Worker&amp;quot;).Sugar()
	log.Infof(&amp;quot;Starting worker with index - %d&amp;quot;, index)
	var sessionType sessionMaker.SessionConstructor
	if config.ValueOf.UseSessionFile {
		sessionType = sessionMaker.SqlSession(sqlite.Open(fmt.Sprintf(&amp;quot;sessions/worker-%d.session&amp;quot;, index)))
	} else {
		sessionType = sessionMaker.SimpleSession()
	}
	client, err := gotgproto.NewClient(
		int(config.ValueOf.ApiID),
		config.ValueOf.ApiHash,
		gotgproto.ClientTypeBot(botToken),
		&amp;amp;gotgproto.ClientOpts{
			Session:          sessionType,
			DisableCopyright: true,
			Middlewares:      GetFloodMiddleware(log.Desugar()),
		},
	)
	if err != nil {
		return nil, err
	}
	return client, nil
}&lt;/pre&gt;
  &lt;p id=&quot;9Nix&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;hLzW&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;Y9rm&quot;&gt;✉cache&lt;/h2&gt;
  &lt;h3 id=&quot;QTYi&quot;&gt;              ↪️cache.go&lt;/h3&gt;
  &lt;pre id=&quot;Ta9U&quot;&gt;package cache&lt;/pre&gt;
  &lt;pre id=&quot;lGxO&quot;&gt;import (
	&amp;quot;EverythingSuckz/fsb/internal/types&amp;quot;
	&amp;quot;bytes&amp;quot;
	&amp;quot;encoding/gob&amp;quot;
	&amp;quot;sync&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;OFKL&quot;&gt;	&amp;quot;github.com/coocood/freecache&amp;quot;
	&amp;quot;github.com/gotd/td/tg&amp;quot;
	&amp;quot;go.uber.org/zap&amp;quot;
)&lt;/pre&gt;
  &lt;pre id=&quot;gRJi&quot;&gt;var cache *Cache&lt;/pre&gt;
  &lt;pre id=&quot;X1IH&quot;&gt;type Cache struct {
	cache *freecache.Cache
	mu    sync.RWMutex
	log   *zap.Logger
}&lt;/pre&gt;
  &lt;pre id=&quot;eFtg&quot;&gt;func InitCache(log *zap.Logger) {
	log = log.Named(&amp;quot;cache&amp;quot;)
	gob.Register(types.File{})
	gob.Register(tg.InputDocumentFileLocation{})
	gob.Register(tg.InputPhotoFileLocation{})
	defer log.Sugar().Info(&amp;quot;Initialized&amp;quot;)
	cache = &amp;amp;Cache{cache: freecache.NewCache(10 * 1024 * 1024), log: log}
}&lt;/pre&gt;
  &lt;pre id=&quot;3Z2v&quot;&gt;func GetCache() *Cache {
	return cache
}&lt;/pre&gt;
  &lt;pre id=&quot;0IRQ&quot;&gt;func (c *Cache) Get(key string, value *types.File) error {
	c.mu.RLock()
	defer c.mu.RUnlock()
	data, err := cache.cache.Get([]byte(key))
	if err != nil {
		return err
	}
	dec := gob.NewDecoder(bytes.NewReader(data))
	err = dec.Decode(&amp;amp;value)
	if err != nil {
		return err
	}
	return nil
}&lt;/pre&gt;
  &lt;pre id=&quot;7e8E&quot;&gt;func (c *Cache) Set(key string, value *types.File, expireSeconds int) error {
	c.mu.Lock()
	defer c.mu.Unlock()
	var buf bytes.Buffer
	enc := gob.NewEncoder(&amp;amp;buf)
	err := enc.Encode(value)
	if err != nil {
		return err
	}
	cache.cache.Set([]byte(key), buf.Bytes(), expireSeconds)
	return nil
}&lt;/pre&gt;
  &lt;pre id=&quot;EPMQ&quot;&gt;func (c *Cache) Delete(key string) error {
	c.mu.Lock()
	defer c.mu.Unlock()
	cache.cache.Del([]byte(key))
	return nil
}&lt;/pre&gt;
  &lt;p id=&quot;SHsX&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;EVCs&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;9Z1E&quot;&gt;  ✉commands&lt;/h2&gt;
  &lt;h3 id=&quot;t4IF&quot;&gt;                    ↪️commands.go&lt;/h3&gt;
  &lt;pre id=&quot;wiAA&quot;&gt;package commands&lt;/pre&gt;
  &lt;pre id=&quot;Ykuz&quot;&gt;import (
	&amp;quot;reflect&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;uIaA&quot;&gt;	&amp;quot;github.com/celestix/gotgproto/dispatcher&amp;quot;
	&amp;quot;go.uber.org/zap&amp;quot;
)&lt;/pre&gt;
  &lt;pre id=&quot;EEtz&quot;&gt;type command struct {
	log *zap.Logger
}&lt;/pre&gt;
  &lt;pre id=&quot;kQtd&quot;&gt;func Load(log *zap.Logger, dispatcher dispatcher.Dispatcher) {
	log = log.Named(&amp;quot;commands&amp;quot;)
	defer log.Info(&amp;quot;Initialized all command handlers&amp;quot;)
	Type := reflect.TypeOf(&amp;amp;command{log})
	Value := reflect.ValueOf(&amp;amp;command{log})
	for i := 0; i &amp;lt; Type.NumMethod(); i++ {
		Type.Method(i).Func.Call([]reflect.Value{Value, reflect.ValueOf(dispatcher)})
	}
}&lt;/pre&gt;
  &lt;p id=&quot;z4Zq&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;bvR3&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;Hiwc&quot;&gt;↪️start.go&lt;/h3&gt;
  &lt;pre id=&quot;Ca4s&quot;&gt;package commands&lt;/pre&gt;
  &lt;pre id=&quot;bm7G&quot;&gt;import (
	&amp;quot;EverythingSuckz/fsb/config&amp;quot;
	&amp;quot;EverythingSuckz/fsb/internal/utils&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;FKSA&quot;&gt;	&amp;quot;github.com/celestix/gotgproto/dispatcher&amp;quot;
	&amp;quot;github.com/celestix/gotgproto/dispatcher/handlers&amp;quot;
	&amp;quot;github.com/celestix/gotgproto/ext&amp;quot;
	&amp;quot;github.com/celestix/gotgproto/storage&amp;quot;
)&lt;/pre&gt;
  &lt;pre id=&quot;ayTP&quot;&gt;func (m *command) LoadStart(dispatcher dispatcher.Dispatcher) {
	log := m.log.Named(&amp;quot;start&amp;quot;)
	defer log.Sugar().Info(&amp;quot;Loaded&amp;quot;)
	dispatcher.AddHandler(handlers.NewCommand(&amp;quot;start&amp;quot;, start))
}&lt;/pre&gt;
  &lt;pre id=&quot;3OLw&quot;&gt;func start(ctx *ext.Context, u *ext.Update) error {
	chatId := u.EffectiveChat().GetID()
	peerChatId := ctx.PeerStorage.GetPeerById(chatId)
	if peerChatId.Type != int(storage.TypeUser) {
		return dispatcher.EndGroups
	}
	if len(config.ValueOf.AllowedUsers) != 0 &amp;amp;&amp;amp; !utils.Contains(config.ValueOf.AllowedUsers, chatId) {
		ctx.Reply(u, &amp;quot;You are not allowed to use this bot.&amp;quot;, nil)
		return dispatcher.EndGroups
	}
	ctx.Reply(u, &amp;quot;Hi, send me any file to get a direct streamble link to that file.&amp;quot;, nil)
	return dispatcher.EndGroups
}&lt;/pre&gt;
  &lt;p id=&quot;PJ8n&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;OGFf&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;PClW&quot;&gt;↪️stream.go&lt;/h3&gt;
  &lt;pre id=&quot;HTiJ&quot;&gt;package commands&lt;/pre&gt;
  &lt;pre id=&quot;mtW7&quot;&gt;import (
	&amp;quot;fmt&amp;quot;
	&amp;quot;strings&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;2FOK&quot;&gt;	&amp;quot;EverythingSuckz/fsb/config&amp;quot;
	&amp;quot;EverythingSuckz/fsb/internal/utils&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;zd9n&quot;&gt;	&amp;quot;github.com/celestix/gotgproto/dispatcher&amp;quot;
	&amp;quot;github.com/celestix/gotgproto/dispatcher/handlers&amp;quot;
	&amp;quot;github.com/celestix/gotgproto/ext&amp;quot;
	&amp;quot;github.com/celestix/gotgproto/storage&amp;quot;
	&amp;quot;github.com/celestix/gotgproto/types&amp;quot;
	&amp;quot;github.com/gotd/td/telegram/message/styling&amp;quot;
	&amp;quot;github.com/gotd/td/tg&amp;quot;
)&lt;/pre&gt;
  &lt;pre id=&quot;a93Z&quot;&gt;func (m *command) LoadStream(dispatcher dispatcher.Dispatcher) {
	log := m.log.Named(&amp;quot;start&amp;quot;)
	defer log.Sugar().Info(&amp;quot;Loaded&amp;quot;)
	dispatcher.AddHandler(
		handlers.NewMessage(nil, sendLink),
	)
}&lt;/pre&gt;
  &lt;pre id=&quot;MnvD&quot;&gt;func supportedMediaFilter(m *types.Message) (bool, error) {
	if not := m.Media == nil; not {
		return false, dispatcher.EndGroups
	}
	switch m.Media.(type) {
	case *tg.MessageMediaDocument:
		return true, nil
	case *tg.MessageMediaPhoto:
		return true, nil
	case tg.MessageMediaClass:
		return false, dispatcher.EndGroups
	default:
		return false, nil
	}
}&lt;/pre&gt;
  &lt;pre id=&quot;KL7g&quot;&gt;func sendLink(ctx *ext.Context, u *ext.Update) error {
	chatId := u.EffectiveChat().GetID()
	peerChatId := ctx.PeerStorage.GetPeerById(chatId)
	if peerChatId.Type != int(storage.TypeUser) {
		return dispatcher.EndGroups
	}
	if len(config.ValueOf.AllowedUsers) != 0 &amp;amp;&amp;amp; !utils.Contains(config.ValueOf.AllowedUsers, chatId) {
		ctx.Reply(u, &amp;quot;You are not allowed to use this bot.&amp;quot;, nil)
		return dispatcher.EndGroups
	}
	supported, err := supportedMediaFilter(u.EffectiveMessage)
	if err != nil {
		return err
	}
	if !supported {
		ctx.Reply(u, &amp;quot;Sorry, this message type is unsupported.&amp;quot;, nil)
		return dispatcher.EndGroups
	}
	update, err := utils.ForwardMessages(ctx, chatId, config.ValueOf.LogChannelID, u.EffectiveMessage.ID)
	if err != nil {
		utils.Logger.Sugar().Error(err)
		ctx.Reply(u, fmt.Sprintf(&amp;quot;Error - %s&amp;quot;, err.Error()), nil)
		return dispatcher.EndGroups
	}
	messageID := update.Updates[0].(*tg.UpdateMessageID).ID
	doc := update.Updates[1].(*tg.UpdateNewChannelMessage).Message.(*tg.Message).Media
	file, err := utils.FileFromMedia(doc)
	if err != nil {
		ctx.Reply(u, fmt.Sprintf(&amp;quot;Error - %s&amp;quot;, err.Error()), nil)
		return dispatcher.EndGroups
	}
	fullHash := utils.PackFile(
		file.FileName,
		file.FileSize,
		file.MimeType,
		file.ID,
	)
	hash := utils.GetShortHash(fullHash)
	link := fmt.Sprintf(&amp;quot;%s/stream/%d?hash=%s&amp;quot;, config.ValueOf.Host, messageID, hash)
	text := []styling.StyledTextOption{styling.Code(link)}
	row := tg.KeyboardButtonRow{
		Buttons: []tg.KeyboardButtonClass{
			&amp;amp;tg.KeyboardButtonURL{
				Text: &amp;quot;Download&amp;quot;,
				URL:  link + &amp;quot;&amp;amp;d=true&amp;quot;,
			},
		},
	}
	if strings.Contains(file.MimeType, &amp;quot;video&amp;quot;) || strings.Contains(file.MimeType, &amp;quot;audio&amp;quot;) || strings.Contains(file.MimeType, &amp;quot;pdf&amp;quot;) {
		row.Buttons = append(row.Buttons, &amp;amp;tg.KeyboardButtonURL{
			Text: &amp;quot;Stream&amp;quot;,
			URL:  link,
		})
	}
	markup := &amp;amp;tg.ReplyInlineMarkup{
		Rows: []tg.KeyboardButtonRow{row},
	}
	if strings.Contains(link, &amp;quot;http://localhost&amp;quot;) {
		_, err = ctx.Reply(u, text, &amp;amp;ext.ReplyOpts{
			NoWebpage:        false,
			ReplyToMessageId: u.EffectiveMessage.ID,
		})
	} else {
		_, err = ctx.Reply(u, text, &amp;amp;ext.ReplyOpts{
			Markup:           markup,
			NoWebpage:        false,
			ReplyToMessageId: u.EffectiveMessage.ID,
		})
	}
	if err != nil {
		utils.Logger.Sugar().Error(err)
		ctx.Reply(u, fmt.Sprintf(&amp;quot;Error - %s&amp;quot;, err.Error()), nil)
	}
	return dispatcher.EndGroups
}&lt;/pre&gt;
  &lt;p id=&quot;K2bS&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;9jn6&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;Czny&quot;&gt; ✉routes&lt;/h2&gt;
  &lt;h3 id=&quot;Hj2K&quot;&gt;               ↪️routes.go&lt;/h3&gt;
  &lt;pre id=&quot;9bhL&quot;&gt;package routes&lt;/pre&gt;
  &lt;pre id=&quot;WtuQ&quot;&gt;import (
	&amp;quot;reflect&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;1iTR&quot;&gt;	&amp;quot;github.com/gin-gonic/gin&amp;quot;
	&amp;quot;go.uber.org/zap&amp;quot;
)&lt;/pre&gt;
  &lt;pre id=&quot;n39L&quot;&gt;type Route struct {
	Name   string
	Engine *gin.Engine
}&lt;/pre&gt;
  &lt;pre id=&quot;fSlq&quot;&gt;func (r *Route) Init(engine *gin.Engine) {
	r.Engine = engine
}&lt;/pre&gt;
  &lt;pre id=&quot;YNYy&quot;&gt;type allRoutes struct {
	log *zap.Logger
}&lt;/pre&gt;
  &lt;pre id=&quot;ZU12&quot;&gt;func Load(log *zap.Logger, r *gin.Engine) {
	log = log.Named(&amp;quot;routes&amp;quot;)
	defer log.Sugar().Info(&amp;quot;Loaded all API Routes&amp;quot;)
	route := &amp;amp;Route{Name: &amp;quot;/&amp;quot;, Engine: r}
	route.Init(r)
	Type := reflect.TypeOf(&amp;amp;allRoutes{log})
	Value := reflect.ValueOf(&amp;amp;allRoutes{log})
	for i := 0; i &amp;lt; Type.NumMethod(); i++ {
		Type.Method(i).Func.Call([]reflect.Value{Value, reflect.ValueOf(route)})
	}
}&lt;/pre&gt;
  &lt;p id=&quot;EkK6&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;r2TE&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;yk4K&quot;&gt;↪️stream.go&lt;/h3&gt;
  &lt;pre id=&quot;m44X&quot;&gt;package routes&lt;/pre&gt;
  &lt;pre id=&quot;UbfP&quot;&gt;import (
	&amp;quot;EverythingSuckz/fsb/internal/bot&amp;quot;
	&amp;quot;EverythingSuckz/fsb/internal/utils&amp;quot;
	&amp;quot;fmt&amp;quot;
	&amp;quot;io&amp;quot;
	&amp;quot;net/http&amp;quot;
	&amp;quot;strconv&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;kn7z&quot;&gt;	&amp;quot;github.com/gotd/td/tg&amp;quot;
	range_parser &amp;quot;github.com/quantumsheep/range-parser&amp;quot;
	&amp;quot;go.uber.org/zap&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;jyOq&quot;&gt;	&amp;quot;github.com/gin-gonic/gin&amp;quot;
)&lt;/pre&gt;
  &lt;pre id=&quot;QUmL&quot;&gt;var log *zap.Logger&lt;/pre&gt;
  &lt;pre id=&quot;Oa9g&quot;&gt;func (e *allRoutes) LoadHome(r *Route) {
	log = e.log.Named(&amp;quot;Stream&amp;quot;)
	defer log.Info(&amp;quot;Loaded stream route&amp;quot;)
	r.Engine.GET(&amp;quot;/stream/:messageID&amp;quot;, getStreamRoute)
}&lt;/pre&gt;
  &lt;pre id=&quot;mCh4&quot;&gt;func getStreamRoute(ctx *gin.Context) {
	w := ctx.Writer
	r := ctx.Request&lt;/pre&gt;
  &lt;pre id=&quot;A1R2&quot;&gt;	messageIDParm := ctx.Param(&amp;quot;messageID&amp;quot;)
	messageID, err := strconv.Atoi(messageIDParm)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}&lt;/pre&gt;
  &lt;pre id=&quot;E9tj&quot;&gt;	authHash := ctx.Query(&amp;quot;hash&amp;quot;)
	if authHash == &amp;quot;&amp;quot; {
		http.Error(w, &amp;quot;missing hash param&amp;quot;, http.StatusBadRequest)
		return
	}&lt;/pre&gt;
  &lt;pre id=&quot;hVXx&quot;&gt;	worker := bot.GetNextWorker()&lt;/pre&gt;
  &lt;pre id=&quot;ChKq&quot;&gt;	file, err := utils.FileFromMessage(ctx, worker.Client, messageID)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}&lt;/pre&gt;
  &lt;pre id=&quot;n3ez&quot;&gt;	expectedHash := utils.PackFile(
		file.FileName,
		file.FileSize,
		file.MimeType,
		file.ID,
	)
	if !utils.CheckHash(authHash, expectedHash) {
		http.Error(w, &amp;quot;invalid hash&amp;quot;, http.StatusBadRequest)
		return
	}&lt;/pre&gt;
  &lt;pre id=&quot;VbCf&quot;&gt;	// for photo messages
	if file.FileSize == 0 {
		res, err := worker.Client.API().UploadGetFile(ctx, &amp;amp;tg.UploadGetFileRequest{
			Location: file.Location,
			Offset:   0,
			Limit:    1024 * 1024,
		})
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		result, ok := res.(*tg.UploadFile)
		if !ok {
			http.Error(w, &amp;quot;unexpected response&amp;quot;, http.StatusInternalServerError)
			return
		}
		fileBytes := result.GetBytes()
		ctx.Header(&amp;quot;Content-Disposition&amp;quot;, fmt.Sprintf(&amp;quot;inline; filename=\&amp;quot;%s\&amp;quot;&amp;quot;, file.FileName))
		if r.Method != &amp;quot;HEAD&amp;quot; {
			ctx.Data(http.StatusOK, file.MimeType, fileBytes)
		}
		return
	}&lt;/pre&gt;
  &lt;pre id=&quot;5TII&quot;&gt;	ctx.Header(&amp;quot;Accept-Ranges&amp;quot;, &amp;quot;bytes&amp;quot;)
	var start, end int64
	rangeHeader := r.Header.Get(&amp;quot;Range&amp;quot;)&lt;/pre&gt;
  &lt;pre id=&quot;yypm&quot;&gt;	if rangeHeader == &amp;quot;&amp;quot; {
		start = 0
		end = file.FileSize - 1
		w.WriteHeader(http.StatusOK)
	} else {
		ranges, err := range_parser.Parse(file.FileSize, r.Header.Get(&amp;quot;Range&amp;quot;))
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		start = ranges[0].Start
		end = ranges[0].End
		ctx.Header(&amp;quot;Content-Range&amp;quot;, fmt.Sprintf(&amp;quot;bytes %d-%d/%d&amp;quot;, start, end, file.FileSize))
		log.Info(&amp;quot;Content-Range&amp;quot;, zap.Int64(&amp;quot;start&amp;quot;, start), zap.Int64(&amp;quot;end&amp;quot;, end), zap.Int64(&amp;quot;fileSize&amp;quot;, file.FileSize))
		w.WriteHeader(http.StatusPartialContent)
	}&lt;/pre&gt;
  &lt;pre id=&quot;SVoJ&quot;&gt;	contentLength := end - start + 1
	mimeType := file.MimeType&lt;/pre&gt;
  &lt;pre id=&quot;yOQ8&quot;&gt;	if mimeType == &amp;quot;&amp;quot; {
		mimeType = &amp;quot;application/octet-stream&amp;quot;
	}&lt;/pre&gt;
  &lt;pre id=&quot;wpQg&quot;&gt;	ctx.Header(&amp;quot;Content-Type&amp;quot;, mimeType)
	ctx.Header(&amp;quot;Content-Length&amp;quot;, strconv.FormatInt(contentLength, 10))&lt;/pre&gt;
  &lt;pre id=&quot;7Gy4&quot;&gt;	disposition := &amp;quot;inline&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;OcHc&quot;&gt;	if ctx.Query(&amp;quot;d&amp;quot;) == &amp;quot;true&amp;quot; {
		disposition = &amp;quot;attachment&amp;quot;
	}&lt;/pre&gt;
  &lt;pre id=&quot;vekp&quot;&gt;	ctx.Header(&amp;quot;Content-Disposition&amp;quot;, fmt.Sprintf(&amp;quot;%s; filename=\&amp;quot;%s\&amp;quot;&amp;quot;, disposition, file.FileName))&lt;/pre&gt;
  &lt;pre id=&quot;WpDk&quot;&gt;	if r.Method != &amp;quot;HEAD&amp;quot; {
		lr, _ := utils.NewTelegramReader(ctx, worker.Client, file.Location, start, end, contentLength)
		if _, err := io.CopyN(w, lr, contentLength); err != nil {
			log.Error(&amp;quot;Error while copying stream&amp;quot;, zap.Error(err))
		}
	}
}&lt;/pre&gt;
  &lt;p id=&quot;ym1h&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;nHwG&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;HJ5a&quot;&gt;✉types&lt;/h2&gt;
  &lt;h3 id=&quot;yP3a&quot;&gt;         ↪️file.go&lt;/h3&gt;
  &lt;pre id=&quot;5Ybq&quot;&gt;package types&lt;/pre&gt;
  &lt;pre id=&quot;2BrE&quot;&gt;import (
	&amp;quot;crypto/md5&amp;quot;
	&amp;quot;encoding/hex&amp;quot;
	&amp;quot;reflect&amp;quot;
	&amp;quot;strconv&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;Mn6b&quot;&gt;	&amp;quot;github.com/gotd/td/tg&amp;quot;
)&lt;/pre&gt;
  &lt;pre id=&quot;QXOu&quot;&gt;type File struct {
	Location tg.InputFileLocationClass
	FileSize int64
	FileName string
	MimeType string
	ID       int64
}&lt;/pre&gt;
  &lt;pre id=&quot;KW5A&quot;&gt;type HashableFileStruct struct {
	FileName string
	FileSize int64
	MimeType string
	FileID   int64
}&lt;/pre&gt;
  &lt;pre id=&quot;LNIL&quot;&gt;func (f *HashableFileStruct) Pack() string {
	hasher := md5.New()
	val := reflect.ValueOf(*f)
	for i := 0; i &amp;lt; val.NumField(); i++ {
		field := val.Field(i)&lt;/pre&gt;
  &lt;pre id=&quot;8tjO&quot;&gt;		var fieldValue []byte
		switch field.Kind() {
		case reflect.String:
			fieldValue = []byte(field.String())
		case reflect.Int64:
			fieldValue = []byte(strconv.FormatInt(field.Int(), 10))
		}&lt;/pre&gt;
  &lt;pre id=&quot;oavr&quot;&gt;		hasher.Write(fieldValue)
	}
	return hex.EncodeToString(hasher.Sum(nil))
}&lt;/pre&gt;
  &lt;p id=&quot;Nd4U&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;afMH&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;75ZW&quot;&gt;↪️response.go&lt;/h3&gt;
  &lt;pre id=&quot;rHAF&quot;&gt;package types&lt;/pre&gt;
  &lt;pre id=&quot;GwfD&quot;&gt;type RootResponse struct {
	Message string &amp;#x60;json:&amp;quot;message&amp;quot;&amp;#x60;
	Ok      bool   &amp;#x60;json:&amp;quot;ok&amp;quot;&amp;#x60;
	Uptime  string &amp;#x60;json:&amp;quot;uptime&amp;quot;&amp;#x60;
	Version string &amp;#x60;json:&amp;quot;version&amp;quot;&amp;#x60;
}&lt;/pre&gt;
  &lt;p id=&quot;93oI&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;CXSM&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;tjmF&quot;&gt;✉utils&lt;/h2&gt;
  &lt;h3 id=&quot;JOrO&quot;&gt;             ↪️hashing.go&lt;/h3&gt;
  &lt;pre id=&quot;mjIP&quot;&gt;package utils&lt;/pre&gt;
  &lt;pre id=&quot;2HE3&quot;&gt;import (
	&amp;quot;EverythingSuckz/fsb/config&amp;quot;
	&amp;quot;EverythingSuckz/fsb/internal/types&amp;quot;
)&lt;/pre&gt;
  &lt;pre id=&quot;ZwDU&quot;&gt;func PackFile(fileName string, fileSize int64, mimeType string, fileID int64) string {
	return (&amp;amp;types.HashableFileStruct{FileName: fileName, FileSize: fileSize, MimeType: mimeType, FileID: fileID}).Pack()
}&lt;/pre&gt;
  &lt;pre id=&quot;X1T6&quot;&gt;func GetShortHash(fullHash string) string {
	return fullHash[:config.ValueOf.HashLength]
}&lt;/pre&gt;
  &lt;pre id=&quot;B9II&quot;&gt;func CheckHash(inputHash string, expectedHash string) bool {
	return inputHash == GetShortHash(expectedHash)
}&lt;/pre&gt;
  &lt;p id=&quot;aGOF&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;SJQU&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;otUo&quot;&gt;↪️helpers.go&lt;/h3&gt;
  &lt;pre id=&quot;F6za&quot;&gt;package utils&lt;/pre&gt;
  &lt;pre id=&quot;PN2D&quot;&gt;import (
	&amp;quot;EverythingSuckz/fsb/config&amp;quot;
	&amp;quot;EverythingSuckz/fsb/internal/cache&amp;quot;
	&amp;quot;EverythingSuckz/fsb/internal/types&amp;quot;
	&amp;quot;context&amp;quot;
	&amp;quot;errors&amp;quot;
	&amp;quot;fmt&amp;quot;
	&amp;quot;math/rand&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;Onml&quot;&gt;	&amp;quot;github.com/celestix/gotgproto&amp;quot;
	&amp;quot;github.com/celestix/gotgproto/ext&amp;quot;
	&amp;quot;github.com/celestix/gotgproto/storage&amp;quot;
	&amp;quot;github.com/gotd/td/tg&amp;quot;
	&amp;quot;go.uber.org/zap&amp;quot;
)&lt;/pre&gt;
  &lt;pre id=&quot;scs6&quot;&gt;// https://stackoverflow.com/a/70802740/15807350
func Contains[T comparable](s []T, e T) bool {
	for _, v := range s {
		if v == e {
			return true
		}
	}
	return false
}&lt;/pre&gt;
  &lt;pre id=&quot;QFfP&quot;&gt;func GetTGMessage(ctx context.Context, client *gotgproto.Client, messageID int) (*tg.Message, error) {
	inputMessageID := tg.InputMessageClass(&amp;amp;tg.InputMessageID{ID: messageID})
	channel, err := GetLogChannelPeer(ctx, client.API(), client.PeerStorage)
	if err != nil {
		return nil, err
	}
	messageRequest := tg.ChannelsGetMessagesRequest{Channel: channel, ID: []tg.InputMessageClass{inputMessageID}}
	res, err := client.API().ChannelsGetMessages(ctx, &amp;amp;messageRequest)
	if err != nil {
		return nil, err
	}
	messages := res.(*tg.MessagesChannelMessages)
	message := messages.Messages[0]
	if _, ok := message.(*tg.Message); ok {
		return message.(*tg.Message), nil
	} else {
		return nil, fmt.Errorf(&amp;quot;this file was deleted&amp;quot;)
	}
}&lt;/pre&gt;
  &lt;pre id=&quot;I9ev&quot;&gt;func FileFromMedia(media tg.MessageMediaClass) (*types.File, error) {
	switch media := media.(type) {
	case *tg.MessageMediaDocument:
		document, ok := media.Document.AsNotEmpty()
		if !ok {
			return nil, fmt.Errorf(&amp;quot;unexpected type %T&amp;quot;, media)
		}
		var fileName string
		for _, attribute := range document.Attributes {
			if name, ok := attribute.(*tg.DocumentAttributeFilename); ok {
				fileName = name.FileName
				break
			}
		}
		return &amp;amp;types.File{
			Location: document.AsInputDocumentFileLocation(),
			FileSize: document.Size,
			FileName: fileName,
			MimeType: document.MimeType,
			ID:       document.ID,
		}, nil
	case *tg.MessageMediaPhoto:
		photo, ok := media.Photo.AsNotEmpty()
		if !ok {
			return nil, fmt.Errorf(&amp;quot;unexpected type %T&amp;quot;, media)
		}
		sizes := photo.Sizes
		if len(sizes) == 0 {
			return nil, errors.New(&amp;quot;photo has no sizes&amp;quot;)
		}
		photoSize := sizes[len(sizes)-1]
		size, ok := photoSize.AsNotEmpty()
		if !ok {
			return nil, errors.New(&amp;quot;photo size is empty&amp;quot;)
		}
		location := new(tg.InputPhotoFileLocation)
		location.ID = photo.GetID()
		location.AccessHash = photo.GetAccessHash()
		location.FileReference = photo.GetFileReference()
		location.ThumbSize = size.GetType()
		return &amp;amp;types.File{
			Location: location,
			FileSize: 0, // caller should judge if this is a photo or not
			FileName: fmt.Sprintf(&amp;quot;photo_%d.jpg&amp;quot;, photo.GetID()),
			MimeType: &amp;quot;image/jpeg&amp;quot;,
			ID:       photo.GetID(),
		}, nil
	}
	return nil, fmt.Errorf(&amp;quot;unexpected type %T&amp;quot;, media)
}&lt;/pre&gt;
  &lt;pre id=&quot;x8ES&quot;&gt;func FileFromMessage(ctx context.Context, client *gotgproto.Client, messageID int) (*types.File, error) {
	key := fmt.Sprintf(&amp;quot;file:%d:%d&amp;quot;, messageID, client.Self.ID)
	log := Logger.Named(&amp;quot;GetMessageMedia&amp;quot;)
	var cachedMedia types.File
	err := cache.GetCache().Get(key, &amp;amp;cachedMedia)
	if err == nil {
		log.Debug(&amp;quot;Using cached media message properties&amp;quot;, zap.Int(&amp;quot;messageID&amp;quot;, messageID), zap.Int64(&amp;quot;clientID&amp;quot;, client.Self.ID))
		return &amp;amp;cachedMedia, nil
	}
	log.Debug(&amp;quot;Fetching file properties from message ID&amp;quot;, zap.Int(&amp;quot;messageID&amp;quot;, messageID), zap.Int64(&amp;quot;clientID&amp;quot;, client.Self.ID))
	message, err := GetTGMessage(ctx, client, messageID)
	if err != nil {
		return nil, err
	}
	file, err := FileFromMedia(message.Media)
	if err != nil {
		return nil, err
	}
	err = cache.GetCache().Set(
		key,
		file,
		3600,
	)
	if err != nil {
		return nil, err
	}
	return file, nil
}&lt;/pre&gt;
  &lt;pre id=&quot;9zv0&quot;&gt;func GetLogChannelPeer(ctx context.Context, api *tg.Client, peerStorage *storage.PeerStorage) (*tg.InputChannel, error) {
	cachedInputPeer := peerStorage.GetInputPeerById(config.ValueOf.LogChannelID)&lt;/pre&gt;
  &lt;pre id=&quot;bmJP&quot;&gt;	switch peer := cachedInputPeer.(type) {
	case *tg.InputPeerEmpty:
		break
	case *tg.InputPeerChannel:
		return &amp;amp;tg.InputChannel{
			ChannelID:  peer.ChannelID,
			AccessHash: peer.AccessHash,
		}, nil
	default:
		return nil, errors.New(&amp;quot;unexpected type of input peer&amp;quot;)
	}
	inputChannel := &amp;amp;tg.InputChannel{
		ChannelID: config.ValueOf.LogChannelID,
	}
	channels, err := api.ChannelsGetChannels(ctx, []tg.InputChannelClass{inputChannel})
	if err != nil {
		return nil, err
	}
	if len(channels.GetChats()) == 0 {
		return nil, errors.New(&amp;quot;no channels found&amp;quot;)
	}
	channel, ok := channels.GetChats()[0].(*tg.Channel)
	if !ok {
		return nil, errors.New(&amp;quot;type assertion to *tg.Channel failed&amp;quot;)
	}
	// Bruh, I literally have to call library internal functions at this point
	peerStorage.AddPeer(channel.GetID(), channel.AccessHash, storage.TypeChannel, &amp;quot;&amp;quot;)
	return channel.AsInput(), nil
}&lt;/pre&gt;
  &lt;pre id=&quot;fBr7&quot;&gt;func ForwardMessages(ctx *ext.Context, fromChatId, toChatId int64, messageID int) (*tg.Updates, error) {
	fromPeer := ctx.PeerStorage.GetInputPeerById(fromChatId)
	if fromPeer.Zero() {
		return nil, fmt.Errorf(&amp;quot;fromChatId: %d is not a valid peer&amp;quot;, fromChatId)
	}
	toPeer, err := GetLogChannelPeer(ctx, ctx.Raw, ctx.PeerStorage)
	if err != nil {
		return nil, err
	}
	update, err := ctx.Raw.MessagesForwardMessages(ctx, &amp;amp;tg.MessagesForwardMessagesRequest{
		RandomID: []int64{rand.Int63()},
		FromPeer: fromPeer,
		ID:       []int{messageID},
		ToPeer:   &amp;amp;tg.InputPeerChannel{ChannelID: toPeer.ChannelID, AccessHash: toPeer.AccessHash},
	})
	if err != nil {
		return nil, err
	}
	return update.(*tg.Updates), nil
}&lt;/pre&gt;
  &lt;p id=&quot;5Mug&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;zib4&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;kLfF&quot;&gt;↪️logger.go&lt;/h3&gt;
  &lt;pre id=&quot;jc2q&quot;&gt;package utils&lt;/pre&gt;
  &lt;pre id=&quot;4hLk&quot;&gt;import (
	&amp;quot;os&amp;quot;
	&amp;quot;time&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;5pZi&quot;&gt;	&amp;quot;go.uber.org/zap&amp;quot;
	&amp;quot;go.uber.org/zap/zapcore&amp;quot;
	&amp;quot;gopkg.in/natefinch/lumberjack.v2&amp;quot;
)&lt;/pre&gt;
  &lt;pre id=&quot;Z0ip&quot;&gt;var Logger *zap.Logger&lt;/pre&gt;
  &lt;pre id=&quot;o0xM&quot;&gt;func InitLogger(debugMode bool) {
	customTimeEncoder := func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
		enc.AppendString(t.Format(&amp;quot;02/01/2006 03:04 PM&amp;quot;))
	}
	consoleConfig := zap.NewDevelopmentEncoderConfig()
	consoleConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
	consoleConfig.EncodeTime = customTimeEncoder
	consoleEncoder := zapcore.NewConsoleEncoder(consoleConfig)&lt;/pre&gt;
  &lt;pre id=&quot;3HKo&quot;&gt;	fileEncoderConfig := zap.NewProductionEncoderConfig()
	fileEncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	fileEncoder := zapcore.NewJSONEncoder(fileEncoderConfig)&lt;/pre&gt;
  &lt;pre id=&quot;lp48&quot;&gt;	fileWriter := zapcore.AddSync(&amp;amp;lumberjack.Logger{
		Filename:   &amp;quot;logs/app.log&amp;quot;,
		MaxSize:    10,
		MaxBackups: 3,
		MaxAge:     7,
		Compress:   true,
	})&lt;/pre&gt;
  &lt;pre id=&quot;SLB2&quot;&gt;	var consoleLevel zapcore.Level
	if debugMode {
		consoleLevel = zapcore.DebugLevel
	} else {
		consoleLevel = zapcore.InfoLevel
	}&lt;/pre&gt;
  &lt;pre id=&quot;jhgJ&quot;&gt;	core := zapcore.NewTee(
		zapcore.NewCore(consoleEncoder, zapcore.AddSync(os.Stdout), consoleLevel),
		zapcore.NewCore(fileEncoder, fileWriter, zapcore.DebugLevel),
	)&lt;/pre&gt;
  &lt;pre id=&quot;BXoG&quot;&gt;	Logger = zap.New(core, zap.AddStacktrace(zapcore.FatalLevel))
}&lt;/pre&gt;
  &lt;p id=&quot;SRmN&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;2ySa&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;hknu&quot;&gt;↪️reader.go&lt;/h3&gt;
  &lt;pre id=&quot;wljk&quot;&gt;package utils&lt;/pre&gt;
  &lt;pre id=&quot;KHE1&quot;&gt;import (
	&amp;quot;context&amp;quot;
	&amp;quot;fmt&amp;quot;
	&amp;quot;io&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;Md9u&quot;&gt;	&amp;quot;github.com/celestix/gotgproto&amp;quot;
	&amp;quot;github.com/gotd/td/tg&amp;quot;
	&amp;quot;go.uber.org/zap&amp;quot;
)&lt;/pre&gt;
  &lt;pre id=&quot;hl4F&quot;&gt;type telegramReader struct {
	ctx           context.Context
	log           *zap.Logger
	client        *gotgproto.Client
	location      tg.InputFileLocationClass
	start         int64
	end           int64
	next          func() ([]byte, error)
	buffer        []byte
	bytesread     int64
	chunkSize     int64
	i             int64
	contentLength int64
}&lt;/pre&gt;
  &lt;pre id=&quot;WMk7&quot;&gt;func (*telegramReader) Close() error {
	return nil
}&lt;/pre&gt;
  &lt;pre id=&quot;pi10&quot;&gt;func NewTelegramReader(
	ctx context.Context,
	client *gotgproto.Client,
	location tg.InputFileLocationClass,
	start int64,
	end int64,
	contentLength int64,
) (io.ReadCloser, error) {&lt;/pre&gt;
  &lt;pre id=&quot;YKBY&quot;&gt;	r := &amp;amp;telegramReader{
		ctx:           ctx,
		log:           Logger.Named(&amp;quot;telegramReader&amp;quot;),
		location:      location,
		client:        client,
		start:         start,
		end:           end,
		chunkSize:     int64(1024 * 1024),
		contentLength: contentLength,
	}
	r.log.Sugar().Debug(&amp;quot;Start&amp;quot;)
	r.next = r.partStream()
	return r, nil
}&lt;/pre&gt;
  &lt;pre id=&quot;3hIW&quot;&gt;func (r *telegramReader) Read(p []byte) (n int, err error) {&lt;/pre&gt;
  &lt;pre id=&quot;xLZF&quot;&gt;	if r.bytesread == r.contentLength {
		r.log.Sugar().Debug(&amp;quot;EOF (bytesread == contentLength)&amp;quot;)
		return 0, io.EOF
	}&lt;/pre&gt;
  &lt;pre id=&quot;IsAQ&quot;&gt;	if r.i &amp;gt;= int64(len(r.buffer)) {
		r.buffer, err = r.next()
		r.log.Debug(&amp;quot;Next Buffer&amp;quot;, zap.Int64(&amp;quot;len&amp;quot;, int64(len(r.buffer))))
		if err != nil {
			return 0, err
		}
		if len(r.buffer) == 0 {
			r.next = r.partStream()
			r.buffer, err = r.next()
			if err != nil {
				return 0, err
			}&lt;/pre&gt;
  &lt;pre id=&quot;bc0G&quot;&gt;		}
		r.i = 0
	}
	n = copy(p, r.buffer[r.i:])
	r.i += int64(n)
	r.bytesread += int64(n)
	return n, nil
}&lt;/pre&gt;
  &lt;pre id=&quot;JfsF&quot;&gt;func (r *telegramReader) chunk(offset int64, limit int64) ([]byte, error) {&lt;/pre&gt;
  &lt;pre id=&quot;d8AC&quot;&gt;	req := &amp;amp;tg.UploadGetFileRequest{
		Offset:   offset,
		Limit:    int(limit),
		Location: r.location,
	}&lt;/pre&gt;
  &lt;pre id=&quot;ZfeX&quot;&gt;	res, err := r.client.API().UploadGetFile(r.ctx, req)&lt;/pre&gt;
  &lt;pre id=&quot;8vWG&quot;&gt;	if err != nil {
		return nil, err
	}&lt;/pre&gt;
  &lt;pre id=&quot;RI3q&quot;&gt;	switch result := res.(type) {
	case *tg.UploadFile:
		return result.Bytes, nil
	default:
		return nil, fmt.Errorf(&amp;quot;unexpected type %T&amp;quot;, r)
	}
}&lt;/pre&gt;
  &lt;pre id=&quot;PrMM&quot;&gt;func (r *telegramReader) partStream() func() ([]byte, error) {&lt;/pre&gt;
  &lt;pre id=&quot;ZQmz&quot;&gt;	start := r.start
	end := r.end
	offset := start - (start % r.chunkSize)&lt;/pre&gt;
  &lt;pre id=&quot;fQ0n&quot;&gt;	firstPartCut := start - offset
	lastPartCut := (end % r.chunkSize) + 1
	partCount := int((end - offset + r.chunkSize) / r.chunkSize)
	currentPart := 1&lt;/pre&gt;
  &lt;pre id=&quot;Koso&quot;&gt;	readData := func() ([]byte, error) {
		if currentPart &amp;gt; partCount {
			return make([]byte, 0), nil
		}
		res, err := r.chunk(offset, r.chunkSize)
		if err != nil {
			return nil, err
		}
		if len(res) == 0 {
			return res, nil
		} else if partCount == 1 {
			res = res[firstPartCut:lastPartCut]
		} else if currentPart == 1 {
			res = res[firstPartCut:]
		} else if currentPart == partCount {
			res = res[:lastPartCut]
		}&lt;/pre&gt;
  &lt;pre id=&quot;nnl7&quot;&gt;		currentPart++
		offset += r.chunkSize
		r.log.Sugar().Debugf(&amp;quot;Part %d/%d&amp;quot;, currentPart, partCount)
		return res, nil
	}
	return readData
}&lt;/pre&gt;
  &lt;p id=&quot;2dws&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;HJzx&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;KnN9&quot;&gt;↪️time_format.go&lt;/h3&gt;
  &lt;pre id=&quot;7U5s&quot;&gt;package utils&lt;/pre&gt;
  &lt;pre id=&quot;609f&quot;&gt;import (
	&amp;quot;fmt&amp;quot;
	&amp;quot;math/bits&amp;quot;
)&lt;/pre&gt;
  &lt;pre id=&quot;JbXJ&quot;&gt;func TimeFormat(seconds uint64) (timeStr string) {
	hours, remainder := bits.Div64(0, seconds, 3600)
	minutes, seconds := bits.Div64(0, remainder, 60)
	days, hours := bits.Div64(0, hours, 24)
	timeStr = &amp;quot;&amp;quot;
	if days &amp;gt; 0 {
		if days == 1 {
			timeStr += fmt.Sprintf(&amp;quot;%d day, &amp;quot;, days)
		} else {
			timeStr += fmt.Sprintf(&amp;quot;%d days, &amp;quot;, days)
		}
	}
	if hours &amp;gt; 0 {
		if hours == 1 {
			timeStr += fmt.Sprintf(&amp;quot;%d hour, &amp;quot;, hours)
		} else {
			timeStr += fmt.Sprintf(&amp;quot;%d hours, &amp;quot;, hours)
		}
	}
	if minutes &amp;gt; 0 {
		if minutes == 1 {
			timeStr += fmt.Sprintf(&amp;quot;%d minute, &amp;quot;, minutes)
		} else {
			timeStr += fmt.Sprintf(&amp;quot;%d minutes, &amp;quot;, minutes)
		}
	}
	if seconds &amp;gt; 0 {
		if seconds == 1 {
			timeStr += fmt.Sprintf(&amp;quot;%d second&amp;quot;, seconds)
		} else {
			timeStr += fmt.Sprintf(&amp;quot;%d seconds&amp;quot;, seconds)
		}
	}
	return timeStr
}&lt;/pre&gt;
  &lt;p id=&quot;eEqb&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;dWo8&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;N9o9&quot;&gt;✉pkg/qrlogin&lt;/h2&gt;
  &lt;h3 id=&quot;Td4u&quot;&gt;           ✉qrlogin&lt;/h3&gt;
  &lt;h3 id=&quot;OduR&quot;&gt;                      ↪️encoder.go&lt;/h3&gt;
  &lt;pre id=&quot;Ouv5&quot;&gt;// This file is a part of EverythingSuckz/TG-FileStreamBot
// And is licenced under the Affero General Public License.
// Any distributions of this code MUST be accompanied by a copy of the AGPL
// with proper attribution to the original author(s).&lt;/pre&gt;
  &lt;pre id=&quot;CR7l&quot;&gt;package qrlogin&lt;/pre&gt;
  &lt;pre id=&quot;gt41&quot;&gt;import (
	&amp;quot;bytes&amp;quot;
	&amp;quot;encoding/base64&amp;quot;
	&amp;quot;encoding/binary&amp;quot;
	&amp;quot;errors&amp;quot;
	&amp;quot;strings&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;HrNh&quot;&gt;	&amp;quot;github.com/gotd/td/session&amp;quot;
)&lt;/pre&gt;
  &lt;pre id=&quot;7nZe&quot;&gt;func EncodeToPyrogramSession(data *session.Data, appID int32) (string, error) {
	buf := new(bytes.Buffer)
	if err := buf.WriteByte(byte(data.DC)); err != nil {
		return &amp;quot;&amp;quot;, err
	}
	if err := binary.Write(buf, binary.BigEndian, appID); err != nil {
		return &amp;quot;&amp;quot;, err
	}
	var testMode byte
	if data.Config.TestMode {
		testMode = 1
	}
	if err := buf.WriteByte(testMode); err != nil {
		return &amp;quot;&amp;quot;, err
	}
	if len(data.AuthKey) != 256 {
		return &amp;quot;&amp;quot;, errors.New(&amp;quot;auth key must be 256 bytes long&amp;quot;)
	}
	if _, err := buf.Write(data.AuthKey); err != nil {
		return &amp;quot;&amp;quot;, err
	}
	if len(data.AuthKeyID) != 8 {
		return &amp;quot;&amp;quot;, errors.New(&amp;quot;auth key ID must be 8 bytes long&amp;quot;)
	}
	if _, err := buf.Write(data.AuthKeyID); err != nil {
		return &amp;quot;&amp;quot;, err
	}
	if err := buf.WriteByte(0); err != nil {
		return &amp;quot;&amp;quot;, err
	}
	// Convert the bytes buffer to a base64 string
	encodedString := base64.URLEncoding.EncodeToString(buf.Bytes())
	trimmedEncoded := strings.TrimRight(encodedString, &amp;quot;=&amp;quot;)
	return trimmedEncoded, nil
}&lt;/pre&gt;
  &lt;p id=&quot;9OCj&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;YshS&quot;&gt;↪️qrcode.go&lt;/h3&gt;
  &lt;pre id=&quot;lSU3&quot;&gt;// This file is a part of EverythingSuckz/TG-FileStreamBot
// And is licenced under the Affero General Public License.
// Any distributions of this code MUST be accompanied by a copy of the AGPL
// with proper attribution to the original author(s).&lt;/pre&gt;
  &lt;pre id=&quot;lLYH&quot;&gt;package qrlogin&lt;/pre&gt;
  &lt;pre id=&quot;zbAv&quot;&gt;import (
	&amp;quot;bufio&amp;quot;
	&amp;quot;context&amp;quot;
	&amp;quot;encoding/json&amp;quot;
	&amp;quot;errors&amp;quot;
	&amp;quot;fmt&amp;quot;
	&amp;quot;os&amp;quot;
	&amp;quot;runtime&amp;quot;
	&amp;quot;strings&amp;quot;
	&amp;quot;time&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;MFQ7&quot;&gt;	&amp;quot;github.com/gotd/td/session&amp;quot;
	&amp;quot;github.com/gotd/td/telegram&amp;quot;
	&amp;quot;github.com/gotd/td/telegram/auth/qrlogin&amp;quot;
	&amp;quot;github.com/gotd/td/tg&amp;quot;
	&amp;quot;github.com/gotd/td/tgerr&amp;quot;
	&amp;quot;github.com/mdp/qrterminal&amp;quot;
)&lt;/pre&gt;
  &lt;pre id=&quot;T8s8&quot;&gt;type CustomWriter struct {
	LineLength int
}&lt;/pre&gt;
  &lt;pre id=&quot;P29A&quot;&gt;func (w *CustomWriter) Write(p []byte) (n int, err error) {
	for _, c := range p {
		if c == &amp;#x27;\n&amp;#x27; {
			w.LineLength++
		}
	}
	return os.Stdout.Write(p)
}&lt;/pre&gt;
  &lt;pre id=&quot;gy4y&quot;&gt;func printQrCode(data string, writer *CustomWriter) {
	qrterminal.GenerateHalfBlock(data, qrterminal.L, writer)
}&lt;/pre&gt;
  &lt;pre id=&quot;UjhZ&quot;&gt;func clearQrCode(writer *CustomWriter) {
	for i := 0; i &amp;lt; writer.LineLength; i++ {
		fmt.Printf(&amp;quot;\033[F\033[K&amp;quot;)
	}
	writer.LineLength = 0
}&lt;/pre&gt;
  &lt;pre id=&quot;ZxL0&quot;&gt;func GenerateQRSession(apiId int, apiHash string) error {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	fmt.Println(&amp;quot;Generating QR session...&amp;quot;)
	reader := bufio.NewReader(os.Stdin)
	dispatcher := tg.NewUpdateDispatcher()
	loggedIn := qrlogin.OnLoginToken(dispatcher)
	sessionStorage := &amp;amp;session.StorageMemory{}
	client := telegram.NewClient(apiId, apiHash, telegram.Options{
		UpdateHandler:  dispatcher,
		SessionStorage: sessionStorage,
		Device: telegram.DeviceConfig{
			DeviceModel:   &amp;quot;Pyrogram&amp;quot;,
			SystemVersion: runtime.GOOS,
			AppVersion:    &amp;quot;2.0&amp;quot;,
		},
	})
	var stringSession string
	qrWriter := &amp;amp;CustomWriter{}
	tickerCtx, cancelTicker := context.WithCancel(context.Background())
	err := client.Run(ctx, func(ctx context.Context) error {
		authorization, err := client.QR().Auth(ctx, loggedIn, func(ctx context.Context, token qrlogin.Token) error {
			if qrWriter.LineLength == 0 {
				fmt.Printf(&amp;quot;\033[F\033[K&amp;quot;)
			}
			clearQrCode(qrWriter)
			printQrCode(token.URL(), qrWriter)
			qrWriter.Write([]byte(&amp;quot;\nTo log in, Open your Telegram app and go to Settings &amp;gt; Devices &amp;gt; Scan QR and scan the QR code.\n&amp;quot;))
			go func(ctx context.Context) {
				ticker := time.NewTicker(1 * time.Second)
				defer ticker.Stop()
				for {
					select {
					case &amp;lt;-ctx.Done():
						return
					case &amp;lt;-ticker.C:
						expiresIn := time.Until(token.Expires())
						if expiresIn &amp;lt;= 0 {
							return
						}
						fmt.Printf(&amp;quot;\rThis code expires in %s&amp;quot;, expiresIn.Truncate(time.Second))
					}
				}
			}(tickerCtx)
			return nil
		})
		if err != nil {
			if tgerr.Is(err, &amp;quot;SESSION_PASSWORD_NEEDED&amp;quot;) {
				cancelTicker()
				fmt.Println(&amp;quot;\n2FA password is required, enter it below: &amp;quot;)
				passkey, _ := reader.ReadString(&amp;#x27;\n&amp;#x27;)
				strippedPasskey := strings.TrimSpace(passkey)
				authorization, err = client.Auth().Password(ctx, strippedPasskey)
				if err != nil {
					if err.Error() == &amp;quot;invalid password&amp;quot; {
						fmt.Println(&amp;quot;Invalid password, please try again.&amp;quot;)
					}
					fmt.Println(&amp;quot;Error while logging in: &amp;quot;, err)
					return nil
				}
			}
		}
		if authorization == nil {
			cancel()
			return errors.New(&amp;quot;authorization is nil&amp;quot;)
		}
		user, err := client.Self(ctx)
		if err != nil {
			return err
		}
		if user.Username == &amp;quot;&amp;quot; {
			fmt.Println(&amp;quot;Logged in as &amp;quot;, user.FirstName, user.LastName)
		} else {
			fmt.Println(&amp;quot;Logged in as @&amp;quot;, user.Username)
		}
		res, _ := sessionStorage.LoadSession(ctx)
		type jsonDataStruct struct {
			Version int
			Data    session.Data
		}
		var jsonData jsonDataStruct
		json.Unmarshal(res, &amp;amp;jsonData)
		stringSession, err = EncodeToPyrogramSession(&amp;amp;jsonData.Data, int32(apiId))
		if err != nil {
			return err
		}
		fmt.Println(&amp;quot;Your pyrogram session string:&amp;quot;, stringSession)
		client.API().MessagesSendMessage(
			ctx,
			&amp;amp;tg.MessagesSendMessageRequest{
				NoWebpage: true,
				Peer:      &amp;amp;tg.InputPeerSelf{},
				Message:   &amp;quot;Your pyrogram session string: &amp;quot; + stringSession,
			},
		)
		return nil
	})
	if err != nil {
		return err
	}
	return nil
}&lt;/pre&gt;
  &lt;p id=&quot;zBZX&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;rLIo&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;1GZf&quot;&gt;↪️.gitattributes&lt;/h3&gt;
  &lt;pre id=&quot;CWm8&quot;&gt;# Auto detect text files and perform LF normalization
* text=auto&lt;/pre&gt;
  &lt;p id=&quot;W22W&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;ALcd&quot;&gt;↪️.gitignore&lt;/h3&gt;
  &lt;pre id=&quot;q0yY&quot;&gt;# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib&lt;/pre&gt;
  &lt;pre id=&quot;nicw&quot;&gt;# Test binary, built with &amp;#x60;go test -c&amp;#x60;
*.test&lt;/pre&gt;
  &lt;pre id=&quot;HSL5&quot;&gt;# Output of the go coverage tool, specifically when used with LiteIDE
*.out&lt;/pre&gt;
  &lt;pre id=&quot;Dll5&quot;&gt;# Dependency directories
vendor/&lt;/pre&gt;
  &lt;pre id=&quot;mzdL&quot;&gt;# Go workspace file
go.work&lt;/pre&gt;
  &lt;pre id=&quot;lFrI&quot;&gt;# Env files
fsb.env
.env&lt;/pre&gt;
  &lt;pre id=&quot;zrsd&quot;&gt;# Session files
*.session*
sessons/&lt;/pre&gt;
  &lt;pre id=&quot;wk3Q&quot;&gt;# build folder
dist/&lt;/pre&gt;
  &lt;pre id=&quot;KHsA&quot;&gt;# logs folder
logs/
*.log&lt;/pre&gt;
  &lt;p id=&quot;nbDV&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;i93A&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;RdzY&quot;&gt; ↪️.goreleaser.yaml&lt;/h3&gt;
  &lt;pre id=&quot;brk8&quot;&gt;version: 1
project_name: TG-FileStreamBot
env:
  - GO111MODULE=on
before:
  hooks:
    - go mod tidy
    - go generate ./...&lt;/pre&gt;
  &lt;pre id=&quot;EZzx&quot;&gt;builds:
  - main: ./cmd/fsb
    env:
      - CGO_ENABLED=0
    flags: -tags=musl
    ldflags: &amp;quot;-extldflags -static -s -w&amp;quot;
    binary: fsb
    goos:
      - linux
      - windows
      - darwin
    goarch:
      - amd64
      - arm64
    mod_timestamp: &amp;#x27;{{ .CommitTimestamp }}&amp;#x27;&lt;/pre&gt;
  &lt;pre id=&quot;uOrZ&quot;&gt;archives:
  - format: tar.gz
    name_template: &amp;quot;{{ .ProjectName }}-{{ .Tag }}-{{ .Os }}-{{ .Arch }}&amp;quot;
    format_overrides:
      - goos: windows
        format: zip&lt;/pre&gt;
  &lt;pre id=&quot;8zcU&quot;&gt;signs:
  - artifacts: checksum
    cmd: gpg2
    args:
      - &amp;quot;--batch&amp;quot;
      - &amp;quot;-u&amp;quot;
      - &amp;quot;{{ .Env.GPG_FINGERPRINT }}&amp;quot;
      - &amp;quot;--output&amp;quot;
      - &amp;quot;${signature}&amp;quot;
      - &amp;quot;--detach-sign&amp;quot;
      - &amp;quot;${artifact}&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;tCuQ&quot;&gt;checksum:
  name_template: &amp;quot;{{ .ProjectName }}-{{ .Tag }}-checksums.txt&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;Xs6V&quot;&gt;changelog:
  sort: asc
  filters:
    exclude:
      - &amp;quot;^docs:&amp;quot;
      - &amp;quot;^test:&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;zj2h&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;zlus&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;baFG&quot;&gt; ↪️Dockerfile&lt;/h3&gt;
  &lt;pre id=&quot;uLCo&quot;&gt;FROM golang:1.21-alpine3.18 as builder
RUN apk update &amp;amp;&amp;amp; apk upgrade --available &amp;amp;&amp;amp; sync
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o /app/fsb -ldflags=&amp;quot;-w -s&amp;quot; ./cmd/fsb&lt;/pre&gt;
  &lt;pre id=&quot;6NoA&quot;&gt;FROM scratch
COPY --from=builder /app/fsb /app/fsb
EXPOSE ${PORT}
ENTRYPOINT [&amp;quot;/app/fsb&amp;quot;, &amp;quot;run&amp;quot;]&lt;/pre&gt;
  &lt;p id=&quot;Cspy&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;Da7P&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;V33r&quot;&gt;↪️Procfile&lt;/h3&gt;
  &lt;pre id=&quot;M47E&quot; data-lang=&quot;python&quot;&gt;web: fsb run&lt;/pre&gt;
  &lt;pre id=&quot;Houi&quot;&gt;&lt;/pre&gt;
  &lt;pre id=&quot;asI5&quot;&gt; ↪️app.json&lt;/pre&gt;
  &lt;pre id=&quot;xids&quot;&gt;{
    &amp;quot;name&amp;quot;: &amp;quot;TG FileStreamBot&amp;quot;,
    &amp;quot;description&amp;quot;: &amp;quot;Stream Telegram files to web&amp;quot;,
    &amp;quot;keywords&amp;quot;: [
        &amp;quot;telegram&amp;quot;,
        &amp;quot;web&amp;quot;,
        &amp;quot;go&amp;quot;,
        &amp;quot;golang&amp;quot;,
        &amp;quot;file-streaming&amp;quot;,
        &amp;quot;file-to-link&amp;quot;,
        &amp;quot;TG-FileStreamBot&amp;quot;
    ],
    &amp;quot;repository&amp;quot;: &amp;quot;https://github.com/EverythingSuckz/TG-FileStreamBot&amp;quot;,
    &amp;quot;logo&amp;quot;: &amp;quot;https://telegra.ph/file/a8bb3f6b334ad1200ddb4.png&amp;quot;,
    &amp;quot;env&amp;quot;: {
        &amp;quot;API_ID&amp;quot;: {
            &amp;quot;description&amp;quot;: &amp;quot;Get this value from https://my.telegram.org&amp;quot;
        },
        &amp;quot;API_HASH&amp;quot;: {
            &amp;quot;description&amp;quot;: &amp;quot;Get this value from https://my.telegram.org&amp;quot;
        },
        &amp;quot;BOT_TOKEN&amp;quot;: {
            &amp;quot;description&amp;quot;: &amp;quot;Get this value from @BotFather&amp;quot;
        },
        &amp;quot;LOG_CHANNEL&amp;quot;: {
            &amp;quot;description&amp;quot;: &amp;quot;channel ID for the log channel where the bot will forward media messages and store these files&amp;quot;
        },
        &amp;quot;HOST&amp;quot;: {
            &amp;quot;description&amp;quot;: &amp;quot;A Fully Qualified Domain Name or Heroku App URL. (eg. https://example.herokuapp.com). Update it After Deploying the Bot&amp;quot;,
            &amp;quot;required&amp;quot;: false
        },
        &amp;quot;HASH_LENGTH&amp;quot;: {
            &amp;quot;description&amp;quot;: &amp;quot;Custom hash length for generated URLs. The hash length must be greater than 5 and less than or equal to 32. Default to 6&amp;quot;,
            &amp;quot;value&amp;quot;: &amp;quot;6&amp;quot;,
            &amp;quot;required&amp;quot;: false
        },
        &amp;quot;USE_SESSION_FILE&amp;quot;: {
            &amp;quot;description&amp;quot;: &amp;quot;Use session files for worker client(s). This speeds up the worker bot startups. default to false&amp;quot;,
            &amp;quot;required&amp;quot;: false
        },
        &amp;quot;USER_SESSION&amp;quot;: {
            &amp;quot;description&amp;quot;: &amp;quot;A pyrogram session string for a user bot. Used for auto adding the bots to LOG_CHANNEL. Default to null&amp;quot;,
            &amp;quot;required&amp;quot;: false
        }
    },
    &amp;quot;buildpacks&amp;quot;: [{
        &amp;quot;url&amp;quot;: &amp;quot;heroku/go&amp;quot;
      }],
    &amp;quot;formation&amp;quot;: {
        &amp;quot;web&amp;quot;: {
            &amp;quot;quantity&amp;quot;: 1,
            &amp;quot;size&amp;quot;: &amp;quot;Eco&amp;quot;
        }
    }
}&lt;/pre&gt;
  &lt;p id=&quot;tdh5&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;OjZE&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;sgVH&quot;&gt;↪️docker-compose.yaml&lt;/h3&gt;
  &lt;pre id=&quot;Bw3d&quot;&gt;name: TG File Stream Bot&lt;/pre&gt;
  &lt;pre id=&quot;fUaq&quot;&gt;services:
  fsb-run:
    image: ghcr.io/everythingsuckz/fsb
    container_name: fsb
    restart: always
    volumes:
      - ./logs:/app/logs
      - ./fsb.env:/app/fsb.env
    ports:
      - &amp;quot;${PORT:-8038}:${PORT:-8038}&amp;quot;
    env_file:
      - path: ./fsb.env
        required: true&lt;/pre&gt;
  &lt;p id=&quot;2MSU&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;Vpjj&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;nMI8&quot;&gt; ↪️fsb.sample.env&lt;/h3&gt;
  &lt;pre id=&quot;2Tye&quot;&gt;# Required Variables (DO NOT SKIP THESE)&lt;/pre&gt;
  &lt;pre id=&quot;JOWh&quot;&gt;API_ID=
API_HASH=
BOT_TOKEN=
LOG_CHANNEL=&lt;/pre&gt;
  &lt;pre id=&quot;cFVG&quot;&gt;# Optional Variables&lt;/pre&gt;
  &lt;pre id=&quot;L79U&quot;&gt;PORT=8080&lt;/pre&gt;
  &lt;pre id=&quot;b64o&quot;&gt;# The length of the hash in your URLs
# https://domain.tld/1254?hash=asd45a
#                              ^^^^^^
#                                /
#                         This is the hash&lt;/pre&gt;
  &lt;pre id=&quot;Hojl&quot;&gt;HASH_LENGTH=6&lt;/pre&gt;
  &lt;pre id=&quot;Ln8b&quot;&gt;# you can use IP address
# HOST=http://&amp;lt;ip address&amp;gt;:&amp;lt;PORT&amp;gt;
# Or you can also use a domain name
# HOST=https://example.com&lt;/pre&gt;
  &lt;pre id=&quot;5esm&quot;&gt;# For muti token support
# Refer https://github.com/EverythingSuckz/TG-FileStreamBot/tree/golang#use-multiple-bots-to-speed-up&lt;/pre&gt;
  &lt;pre id=&quot;VuM6&quot;&gt;# MULTI_TOKEN1=1857821156:AAEvrINCsduhjkjhahadvHRdk7oF46KZnc
# MULTI_TOKEN2=1355359001:AAF4dgddVVxDCt51FZqy1unh9h0SOTw0gU
# MULTI_TOKEN3=6941936497:AAGJzfoMHXshS8gVcsefUzpwyrbfU7gKRMM
# MULTI_TOKEN4=6546079247:AAF2k3uvO9Hqadfhjaskjds8jnzOAfQYUzTZ&lt;/pre&gt;
  &lt;p id=&quot;kDlL&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;zLYV&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;CQqf&quot;&gt; ↪️goreleaser.Dockerfile&lt;/h3&gt;
  &lt;pre id=&quot;9e9u&quot; data-lang=&quot;python&quot;&gt;FROM golang:1.21
CMD [&amp;quot;/app/fsb&amp;quot;]&lt;/pre&gt;

</content></entry><entry><id>codeonplate:QTexhRTGSsk</id><link rel="alternate" type="text/html" href="https://teletype.in/@codeonplate/QTexhRTGSsk?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=codeonplate"></link><title>Бот-Экспедитор Telegram</title><published>2025-10-18T12:48:49.598Z</published><updated>2025-10-18T12:48:49.598Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/8c/5b/8c5b41ab-b295-4076-820c-d1d1dc133b76.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/5c/8f/5c8f8399-721b-4729-a521-c02257812a33.jpeg&quot;&gt;Вам понадобятся два разных ключа API. Один для API телеграм-бота, а другой для аккаунта в Telegram, используемого в качестве агента.</summary><content type="html">
  &lt;ul id=&quot;MWqq&quot;&gt;
    &lt;li id=&quot;oKJf&quot;&gt;Автоматическая пересылка сообщений из каналов и групп (личных или общедоступных)&lt;/li&gt;
    &lt;li id=&quot;52QO&quot;&gt;Объедините каналы в тематические ленты&lt;/li&gt;
    &lt;li id=&quot;OGAH&quot;&gt;Клонируйте все сообщения из любого канала в свой собственный канал&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h1 id=&quot;eYwI&quot;&gt;Предварительная установка&lt;/h1&gt;
  &lt;p id=&quot;iZ3h&quot;&gt;Вам понадобятся два разных ключа API. Один для API телеграм-бота, а другой для аккаунта в Telegram, используемого в качестве агента.&lt;/p&gt;
  &lt;h2 id=&quot;Yw2q&quot;&gt;A. API бота&lt;/h2&gt;
  &lt;p id=&quot;dTFN&quot;&gt;elegram предлагает удобный способ создания API-ключей для телеграм-ботов. Вам нужно отправить сообщение &lt;a href=&quot;https://t.me/botfather&quot; target=&quot;_blank&quot;&gt;@botfather&lt;/a&gt; — телеграм-боту, который поможет вам создать новых ботов! Бот проведет вас через процесс создания новых ботов и управления ими, а также через процесс создания API-ключей.&lt;/p&gt;
  &lt;figure id=&quot;kx4n&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/34/aa/34aadc4d-30a4-400a-957f-bb889de11383.png&quot; width=&quot;975&quot; /&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;8ffM&quot;&gt;B. Telegram API&lt;/h2&gt;
  &lt;p id=&quot;oXBf&quot;&gt;Чтобы получить идентификатор API и хэш-идентификатор для аккаунта в Telegram, перейдите по ссылке &lt;a href=&quot;https://my.telegram.org/auth?to=apps&quot; target=&quot;_blank&quot;&gt;https://my.telegram.org/auth?to=apps&lt;/a&gt;&lt;/p&gt;
  &lt;figure id=&quot;X5Uy&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img4.teletype.in/files/3b/41/3b416a99-437d-4ce9-8495-e12af5400dd4.png&quot; width=&quot;855&quot; /&gt;
  &lt;/figure&gt;
  &lt;h1 id=&quot;VWNz&quot;&gt;Установка&lt;/h1&gt;
  &lt;p id=&quot;XvPg&quot;&gt;Клонировать репозиторий&lt;/p&gt;
  &lt;pre id=&quot;nTgs&quot;&gt;git clone https://github.com/adityathebe/telegramForwarder.git&lt;/pre&gt;
  &lt;h2 id=&quot;mvz3&quot;&gt;1. Docker (рекомендуется)&lt;/h2&gt;
  &lt;p id=&quot;bfrf&quot;&gt;Для файла &lt;code&gt;docker-compose&lt;/code&gt; требуется несколько переменных среды. Создайте файл &lt;code&gt;.env&lt;/code&gt; в корневом каталоге. Docker Compose автоматически извлечёт необходимые переменные среды из этого файла. Вот пример моего файла &lt;code&gt;.env&lt;/code&gt;&lt;/p&gt;
  &lt;pre id=&quot;6Ftp&quot;&gt;TG_API_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
TG_API_ID=XXXX
TG_HASH_ID=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
TG_BOT_USERNAME=@XXXXXXXXXXXXXXXXXbot
&lt;/pre&gt;
  &lt;p id=&quot;cJWw&quot;&gt;With that set up you&amp;#x27;re ready to go !&lt;/p&gt;
  &lt;pre id=&quot;cqyr&quot;&gt;докер-создание&lt;/pre&gt;
  &lt;blockquote id=&quot;wKIe&quot;&gt;При первом запуске этой команды могут возникнуть проблемы. Если это произойдёт, остановите процесс и запустите команду повторно&lt;/blockquote&gt;
  &lt;p id=&quot;BuHW&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;2Bvl&quot;&gt;2. Руководство пользователя&lt;/h2&gt;
  &lt;h3 id=&quot;sOmt&quot;&gt;1. Установка пакетов Python&lt;/h3&gt;
  &lt;pre id=&quot;i5mS&quot;&gt;cd агент
pip install -r requirements.txt&lt;/pre&gt;
  &lt;h3 id=&quot;3HQu&quot;&gt;2. Установите пакеты Nodejs&lt;/h3&gt;
  &lt;pre id=&quot;WDtN&quot;&gt;cd бот
npm установить&lt;/pre&gt;
  &lt;h3 id=&quot;kKfT&quot;&gt;3. Установите и настройте Postgress&lt;/h3&gt;
  &lt;p id=&quot;4UGr&quot;&gt;Установите сервер Postgres и создайте новую базу данных под названием &lt;code&gt;telegram&lt;/code&gt;.&lt;/p&gt;
  &lt;h3 id=&quot;xrKT&quot;&gt;4. Запускайте все подряд&lt;/h3&gt;
  &lt;p id=&quot;pjkT&quot;&gt;&lt;/p&gt;
  &lt;h1 id=&quot;qxiD&quot;&gt;Последующая установка&lt;/h1&gt;
  &lt;p id=&quot;mG54&quot;&gt;После того как вы настроите базу данных, агент Python и сервер Node, вам нужно будет войти в Telegram-аккаунт, который вы хотите использовать в качестве агента пересылки.&lt;/p&gt;
  &lt;p id=&quot;AqPN&quot;&gt;Чтобы войти в систему, перейдите по адресу &lt;a href=&quot;http://localhost:3000/login&quot; target=&quot;_blank&quot;&gt;http://localhost:3000/login&lt;/a&gt;&lt;/p&gt;
  &lt;h1 id=&quot;CZr9&quot;&gt;Что нужно иметь в виду&lt;/h1&gt;
  &lt;ol id=&quot;qkIG&quot;&gt;
    &lt;li id=&quot;eQTD&quot;&gt;Если вы запускаете код с помощью Docker и Docker Compose, вам нужно будет пересобрать образы Docker после установки новых обновлений.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;pre id=&quot;Mcix&quot;&gt;docker-compose up --build
&lt;/pre&gt;
  &lt;ol id=&quot;M8Qy&quot;&gt;
    &lt;li id=&quot;SGt6&quot;&gt;Docker сохранит базу данных postgres даже после остановки контейнеров Docker. Если по какой-то причине вы хотите начать с чистого листа и очистить базу данных, вам нужно очистить том Docker.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;pre id=&quot;YPO9&quot;&gt;docker-compose down -v
&lt;/pre&gt;
  &lt;h1 id=&quot;Va4H&quot;&gt;Доступ к базе данных из Docker-контейнера&lt;/h1&gt;
  &lt;p id=&quot;KE8A&quot;&gt;Если вы уже установили &lt;code&gt;psql&lt;/code&gt; или любой другой графический интерфейс для Postgres, то можете просто подключиться к базе данных, так как порт &lt;code&gt;5432&lt;/code&gt; открыт и сопоставлен с портом &lt;code&gt;5432&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;fQDU&quot;&gt;Однако, если у вас нет ни одного из этих инструментов и вы не хотите заморачиваться с их установкой, вы можете просто запустить интерактивную оболочку в Docker-контейнере и получить доступ к базе данных оттуда. Если вы выполните приведённую ниже команду, вы получите оболочку в Docker-контейнере postgres&lt;/p&gt;
  &lt;pre id=&quot;dpby&quot;&gt;Docker-контейнер exec -it telegramforwarder_postgres-db_1 psql -U postgres&lt;/pre&gt;
  &lt;blockquote id=&quot;bNS9&quot;&gt;Примечание 1. Образ PostgreSQL настраивает локальную аутентификацию по доверию, поэтому при подключении с локального хоста (внутри того же контейнера) пароль может не требоваться. Однако при подключении с другого хоста/контейнера потребуется пароль.&lt;/blockquote&gt;
  &lt;pre id=&quot;Y5qt&quot;&gt;ИЗ*
выбрать --  \c telegram пользователей;&lt;/pre&gt;
  &lt;h3 id=&quot;sKka&quot;&gt;Вопросы и ответы:&lt;/h3&gt;
  &lt;p id=&quot;gITE&quot;&gt;— В: Как начать пользоваться ботом?&lt;br /&gt;О: Отправьте боту команду /start и следуйте инструкциям.&lt;/p&gt;
  &lt;p id=&quot;3qjM&quot;&gt;Вопрос: нужны ли боту права администратора в канале/группе, из которых он осуществляет пересылку?&lt;br /&gt;Ответ: нет.&lt;/p&gt;
  &lt;p id=&quot;3oWo&quot;&gt;Вопрос: нужны ли боту права администратора в канале/группе, на которые он перенаправляет сообщения?&lt;br /&gt;Ответ: да.&lt;/p&gt;
  &lt;p id=&quot;ydIY&quot;&gt;Вопрос: Можно ли отфильтровать рекламу или медиаконтент (видео, стикеры и т. д.)?&lt;br /&gt;Ответ: Да.&lt;/p&gt;
  &lt;p id=&quot;TL9R&quot;&gt;Вопрос: Можно ли настроить автоматическую переадресацию с другого бота?&lt;br /&gt;Ответ: Да.&lt;/p&gt;
  &lt;p id=&quot;BV8y&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;C7Oe&quot;&gt;Сам код:&lt;/h2&gt;
  &lt;h2 id=&quot;wDvv&quot;&gt;✉Агент&lt;/h2&gt;
  &lt;h3 id=&quot;trsg&quot;&gt;      ✉  конфигурация&lt;/h3&gt;
  &lt;h3 id=&quot;6lp7&quot;&gt;              ↪️__init__.py&lt;/h3&gt;
  &lt;pre id=&quot;Jljx&quot; data-lang=&quot;python&quot;&gt;import os&lt;/pre&gt;
  &lt;pre id=&quot;8jJ2&quot;&gt;API_PORT = os.environ[&amp;#x27;API_PORT&amp;#x27;]&lt;/pre&gt;
  &lt;pre id=&quot;1LT7&quot;&gt;# Telegram configs
api_id = os.environ[&amp;#x27;TG_API_ID&amp;#x27;]
api_hash = os.environ[&amp;#x27;TG_HASH_ID&amp;#x27;]&lt;/pre&gt;
  &lt;pre id=&quot;kQvz&quot;&gt;# Database configs
DB_HOST = os.environ.get(&amp;#x27;DB_HOST&amp;#x27;) or &amp;#x27;localhost&amp;#x27;
DB_SESSION_DBNAME = &amp;quot;telethonsession&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;O21l&quot;&gt;# Telethon Session settings
TELETHON_SESSION_ID = &amp;#x27;synapticSupport&amp;#x27;
session_name_forwarder = &amp;#x27;synapticSupportForwarder&amp;#x27;&lt;/pre&gt;
  &lt;h3 id=&quot;gWdJ&quot;&gt;   &lt;/h3&gt;
  &lt;h3 id=&quot;TiKM&quot;&gt;✉дб&lt;/h3&gt;
  &lt;h3 id=&quot;ZjX7&quot;&gt;   ↪️database.py&lt;/h3&gt;
  &lt;pre id=&quot;RBiv&quot;&gt;port psycopg2
from config import DB_HOST&lt;/pre&gt;
  &lt;pre id=&quot;SOC1&quot;&gt;
class Database:
    def __init__(self):
        self.db = psycopg2.connect(
            host=DB_HOST,
            database=&amp;quot;telegram&amp;quot;,
            user=&amp;quot;postgres&amp;quot;,
            password=&amp;quot;mysecretpassword&amp;quot;)&lt;/pre&gt;
  &lt;pre id=&quot;PuqR&quot;&gt;    def get_user(self, user_id):
        cursor = self.db.cursor()
        sql = f&amp;quot;SELECT * FROM users WHERE chat_id = &amp;#x27;{user_id}&amp;#x27;&amp;quot;
        cursor.execute(sql)
        result = cursor.fetchone()
        cursor.close()
        return result&lt;/pre&gt;
  &lt;pre id=&quot;hAln&quot;&gt;    def get_active_redirections_of_source(self, source):
        cursor = self.db.cursor()
        sql = f&amp;quot;SELECT * FROM redirections WHERE source = &amp;#x27;{source}&amp;#x27; AND active = TRUE&amp;quot;
        cursor.execute(sql)
        result = cursor.fetchall()
        cursor.close()
        return result&lt;/pre&gt;
  &lt;pre id=&quot;Y13X&quot;&gt;    def get_redirection(self, redirection_id):
        &amp;quot;&amp;quot;&amp;quot;&amp;quot;
        Get redirection of given redirection id
        &amp;quot;&amp;quot;&amp;quot;
        cursor = self.db.cursor()
        sql = f&amp;quot;SELECT * FROM redirections WHERE id = &amp;#x27;{redirection_id}&amp;#x27; AND active = 1&amp;quot;
        cursor.execute(sql)
        result = cursor.fetchall()
        cursor.close()
        return result&lt;/pre&gt;
  &lt;pre id=&quot;cDLl&quot;&gt;    def get_all_redirections(self):
        cursor = self.db.cursor()
        sql = &amp;quot;SELECT * FROM redirections;&amp;quot;
        cursor.execute(sql)
        result = cursor.fetchall()
        cursor.close()
        return result&lt;/pre&gt;
  &lt;pre id=&quot;dY71&quot;&gt;    def get_filter(self, filter_id):
        cursor = self.db.cursor()
        sql = f&amp;quot;SELECT * FROM filters WHERE id = {filter_id}&amp;quot;
        cursor.execute(sql)
        result = cursor.fetchone()
        cursor.close()
        return result&lt;/pre&gt;
  &lt;pre id=&quot;m2Aa&quot;&gt;    def get_transformations_of_redirection(self, redirection_id):
        cursor = self.db.cursor()
        sql = f&amp;quot;SELECT * FROM transformations WHERE redirection_id = {redirection_id} ORDER BY rank&amp;quot;
        cursor.execute(sql)
        result = cursor.fetchall()
        cursor.close()
        return result&lt;/pre&gt;
  &lt;p id=&quot;F8J3&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;QXNJ&quot;&gt;✉Утилиты&lt;/h3&gt;
  &lt;h3 id=&quot;KdSG&quot;&gt;                       ↪️filter.py&lt;/h3&gt;
  &lt;pre id=&quot;Oujk&quot;&gt;import logging
from db.database import Database&lt;/pre&gt;
  &lt;pre id=&quot;ilzD&quot;&gt;logger = logging.getLogger(&amp;#x27;Filter&amp;#x27;)
database = Database()&lt;/pre&gt;
  &lt;pre id=&quot;392T&quot;&gt;
class MessageFilter:&lt;/pre&gt;
  &lt;pre id=&quot;jM7t&quot;&gt;    @staticmethod
    def get_filter(filter_id):
        filter = database.get_filter(filter_id)
        if filter is None:
            return False&lt;/pre&gt;
  &lt;pre id=&quot;52Ph&quot;&gt;        filter_dict = {
            &amp;#x27;id&amp;#x27;: filter[0],
            &amp;#x27;audio&amp;#x27;: filter[1],
            &amp;#x27;video&amp;#x27;: filter[2],
            &amp;#x27;photo&amp;#x27;: filter[3],
            &amp;#x27;sticker&amp;#x27;: filter[4],
            &amp;#x27;document&amp;#x27;: filter[5],
            &amp;#x27;hashtag&amp;#x27;: filter[6],
            &amp;#x27;link&amp;#x27;: filter[7],
            &amp;#x27;contain&amp;#x27;: filter[8],
            &amp;#x27;notcontain&amp;#x27;: filter[9]
        }
        return filter_dict&lt;/pre&gt;
  &lt;pre id=&quot;kvXf&quot;&gt;    @staticmethod
    def get_active_filters(filter_dict):
        &amp;quot;&amp;quot;&amp;quot;
        Takes in a filter dictionary
        Returns a list of active filter names
        Example: (&amp;#x27;photo&amp;#x27;, &amp;#x27;audio&amp;#x27;)
        &amp;quot;&amp;quot;&amp;quot;
        if not isinstance(filter_dict, dict):
            raise ValueError(&amp;#x27;Provide a dictionary&amp;#x27;)&lt;/pre&gt;
  &lt;pre id=&quot;GzEl&quot;&gt;        active_filter_list = []
        for key, value in filter_dict.items():
            if value != 0 and value is not None:
                if key != &amp;#x27;id&amp;#x27;:
                    active_filter_list.append(key)&lt;/pre&gt;
  &lt;pre id=&quot;7OTg&quot;&gt;        return active_filter_list&lt;/pre&gt;
  &lt;pre id=&quot;RJNy&quot;&gt;    @staticmethod
    def get_message_type(event):&lt;/pre&gt;
  &lt;pre id=&quot;gLyp&quot;&gt;        # Photos Messages
        if hasattr(event.media, &amp;#x27;photo&amp;#x27;):
            return &amp;#x27;photo&amp;#x27;&lt;/pre&gt;
  &lt;pre id=&quot;je2q&quot;&gt;        # Look for links and hashtags in event.entities
        if event.entities is not None:
            for entity in event.entities:
                entity_name = type(entity).__name__&lt;/pre&gt;
  &lt;pre id=&quot;oG1a&quot;&gt;                if entity_name == &amp;#x27;MessageEntityHashtag&amp;#x27;:
                    return &amp;#x27;hashtag&amp;#x27;&lt;/pre&gt;
  &lt;pre id=&quot;wduF&quot;&gt;                if entity_name == &amp;#x27;MessageEntityUrl&amp;#x27;:
                    return &amp;#x27;link&amp;#x27;&lt;/pre&gt;
  &lt;pre id=&quot;Gizf&quot;&gt;        # Text Messages
        if event.media == None:
            return &amp;#x27;text&amp;#x27;&lt;/pre&gt;
  &lt;pre id=&quot;UQ0j&quot;&gt;        # Documents (audio, video, sticker, files)
        mime_type = event.media.document.mime_type
        if &amp;#x27;audio&amp;#x27; in mime_type:
            return &amp;#x27;audio&amp;#x27;
        if &amp;#x27;video&amp;#x27; in mime_type:
            return &amp;#x27;video&amp;#x27;
        if &amp;#x27;image/webp&amp;#x27; in mime_type:
            return &amp;#x27;sticker&amp;#x27;&lt;/pre&gt;
  &lt;pre id=&quot;HTZS&quot;&gt;        # Anything else is a file
        return &amp;#x27;document&amp;#x27;&lt;/pre&gt;
  &lt;pre id=&quot;EASw&quot;&gt;    @staticmethod
    def filter_msg(filter_id, message_event):
        &amp;quot;&amp;quot;&amp;quot;
        Function that decides if a message should be forwarded or not
        Returns Boolean
        &amp;quot;&amp;quot;&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;mysM&quot;&gt;        # Get Filter dictionary from database
        filter_dict = MessageFilter.get_filter(filter_id)
        if filter_dict == False:
            logger.info(&amp;#x27;No filters for id : {}&amp;#x27;.format(filter_id))
            return False&lt;/pre&gt;
  &lt;pre id=&quot;iEM5&quot;&gt;        # Get Active Filter list
        filter_list = MessageFilter.get_active_filters(filter_dict)&lt;/pre&gt;
  &lt;pre id=&quot;KZHr&quot;&gt;        # Get Message Types
        msg_type = MessageFilter.get_message_type(message_event)
        msg_text = message_event.message.text.lower()&lt;/pre&gt;
  &lt;pre id=&quot;Cxgh&quot;&gt;        if msg_type in filter_list:
            logger.info(f&amp;#x27;Filter caught :: {msg_type}&amp;#x27;)
            return True&lt;/pre&gt;
  &lt;pre id=&quot;56xz&quot;&gt;        if &amp;#x27;contain&amp;#x27; not in filter_list and &amp;#x27;notcontain&amp;#x27; not in filter_list:
            logger.info(&amp;#x27;All filters passed.&amp;#x27;)
            return False&lt;/pre&gt;
  &lt;pre id=&quot;NgY7&quot;&gt;        # Assume message does not contain the required word
        contains_required_word = False
        if &amp;#x27;contain&amp;#x27; in filter_list:
            # Look for text messages only
            if message_event.media is not None:
                return False
            keywords = filter_dict[&amp;#x27;contain&amp;#x27;].split(&amp;#x27;&amp;lt;stop_word&amp;gt;&amp;#x27;)
            for keyword in keywords:
                if keyword in msg_text:
                    contains_required_word = True
                    break&lt;/pre&gt;
  &lt;pre id=&quot;kmKF&quot;&gt;        # Assume message does contain the blacklist word
        contains_blacklist_word = False
        if &amp;#x27;notcontain&amp;#x27; in filter_list:
            # Look for text messages only
            if message_event.media is not None:
                return False
            keywords = filter_dict[&amp;#x27;notcontain&amp;#x27;].split(&amp;#x27;&amp;lt;stop_word&amp;gt;&amp;#x27;)
            for keyword in keywords:
                if keyword in msg_text:
                    contains_blacklist_word = True
                    break&lt;/pre&gt;
  &lt;pre id=&quot;d82m&quot;&gt;        logger.info(&amp;#x27;Contains word :: {} &amp;amp;&amp;amp; Contains Blacklist :: {}&amp;#x27;.format(
            contains_required_word, contains_blacklist_word))
        return (not contains_required_word) or contains_blacklist_word&lt;/pre&gt;
  &lt;p id=&quot;KXA3&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;AZT9&quot;&gt;↪️transformation.py&lt;/h3&gt;
  &lt;pre id=&quot;rQ1y&quot;&gt;from db.database import Database&lt;/pre&gt;
  &lt;pre id=&quot;oJ0D&quot;&gt;# Connect to database
database = Database()&lt;/pre&gt;
  &lt;pre id=&quot;Z7OB&quot;&gt;
class MessageTransformation:&lt;/pre&gt;
  &lt;pre id=&quot;kHPg&quot;&gt;    @staticmethod
    def get_transformed_msg(message_event, redirection_id):
        transformations = database.get_transformations_of_redirection(
            redirection_id)&lt;/pre&gt;
  &lt;pre id=&quot;IHWu&quot;&gt;        # Apply Transformation
        msg_entities = message_event.message.entities
        msg_txt = message_event.message.message&lt;/pre&gt;
  &lt;pre id=&quot;KmO1&quot;&gt;        for transformation in transformations:
            old_phrase = transformation[2]
            new_phrase = transformation[3]
            msg_txt = msg_txt.replace(old_phrase, new_phrase)&lt;/pre&gt;
  &lt;pre id=&quot;C802&quot;&gt;        return msg_txt&lt;/pre&gt;
  &lt;pre id=&quot;1NC0&quot;&gt;
if __name__ == &amp;quot;__main__&amp;quot;:
    msg = &amp;#x27;hi there. I love violin&amp;#x27;
    resp = MessageTransformation.get_transformed_msg(msg, 41)
    print(resp)&lt;/pre&gt;
  &lt;p id=&quot;dT8R&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;xrdR&quot;&gt;↪️Файл Dockerfile&lt;/h3&gt;
  &lt;pre id=&quot;SLsm&quot;&gt;FROM python:3.8-rc-buster&lt;/pre&gt;
  &lt;pre id=&quot;T3jb&quot;&gt;RUN apt-get clean &amp;amp;&amp;amp; \
  rm -rf /var/lib/apt/lists/* &amp;amp;&amp;amp; \
  apt-get clean &amp;amp;&amp;amp; \
  apt-get update&lt;/pre&gt;
  &lt;pre id=&quot;Y4Y3&quot;&gt;RUN apt-get install python-dev default-libmysqlclient-dev -y&lt;/pre&gt;
  &lt;pre id=&quot;uF1s&quot;&gt;WORKDIR /telegram-python-agent&lt;/pre&gt;
  &lt;pre id=&quot;qVD3&quot;&gt;COPY requirements.txt .&lt;/pre&gt;
  &lt;pre id=&quot;5HcQ&quot;&gt;RUN pip install -r requirements.txt&lt;/pre&gt;
  &lt;pre id=&quot;VaKp&quot;&gt;COPY . .&lt;/pre&gt;
  &lt;pre id=&quot;PQVb&quot;&gt;CMD [&amp;quot;python&amp;quot;, &amp;quot;server.py&amp;quot;]&lt;/pre&gt;
  &lt;p id=&quot;VFRR&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;e8Iu&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;zREd&quot;&gt;↪️client.py&lt;/h3&gt;
  &lt;pre id=&quot;OIgr&quot;&gt;from telethon import TelegramClient, events, sync
from config import api_id, api_hash&lt;/pre&gt;
  &lt;pre id=&quot;qln2&quot;&gt;telegram_client = TelegramClient(&amp;#x27;session/telethon&amp;#x27;, api_id, api_hash&lt;/pre&gt;
  &lt;p id=&quot;oYmH&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;UMmb&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;IyXc&quot;&gt;↪️forwarder.py&lt;/h3&gt;
  &lt;pre id=&quot;MwR8&quot;&gt;import json
import logging
from client import telegram_client
from db.database import Database
from utils.filter import MessageFilter
from utils.transformation import MessageTransformation&lt;/pre&gt;
  &lt;pre id=&quot;gGXu&quot;&gt;
logger = logging.getLogger(&amp;#x27;Forwarder&amp;#x27;)
database = Database()&lt;/pre&gt;
  &lt;pre id=&quot;nTw9&quot;&gt;
async def forwarder_event_handler(event):
    # Ignore Outgoing Message Updates
    if (event.out):
        return&lt;/pre&gt;
  &lt;pre id=&quot;4XSh&quot;&gt;    is_group = event.is_group
    is_channel = event.is_channel
    is_private = event.is_private
    message = event.message.message
    has_media = event.media
    sender_id = None&lt;/pre&gt;
  &lt;pre id=&quot;GCGm&quot;&gt;    if is_channel:
        logger.info(&amp;#x27;Channel Message Received&amp;#x27;)
        sender_id = event.message.to_id.channel_id&lt;/pre&gt;
  &lt;pre id=&quot;5oOl&quot;&gt;    elif is_group:
        logger.info(&amp;#x27;Group Message Received&amp;#x27;)
        sender_id = event.message.to_id.chat_id&lt;/pre&gt;
  &lt;pre id=&quot;Aph0&quot;&gt;    elif is_private:
        logger.info(&amp;#x27;Private Message Received&amp;#x27;)
        sender_id = event.from_id if has_media else event.original_update.user_id&lt;/pre&gt;
  &lt;pre id=&quot;N16C&quot;&gt;    else:
        logger.info(&amp;#x27;Invalid Sender Type&amp;#x27;, event)&lt;/pre&gt;
  &lt;pre id=&quot;SkzE&quot;&gt;    logger.info(f&amp;#x27;Message : {message}&amp;#x27;)
    logger.info(f&amp;#x27;Sender : {sender_id}&amp;#x27;)&lt;/pre&gt;
  &lt;pre id=&quot;NJ8K&quot;&gt;    # Mark as read (Optional)
    user_entity = await telegram_client.get_input_entity(sender_id)
    await telegram_client.send_read_acknowledge(user_entity, max_id=event.original_update.pts)&lt;/pre&gt;
  &lt;pre id=&quot;GdmQ&quot;&gt;    # Get all Receivers of given userid
    try:
        redirections = database.get_active_redirections_of_source(sender_id)
        logger.info(f&amp;quot;Active Redirections: {json.dumps(redirections)}&amp;quot;)
        for redirection in redirections:
            redirection_id = redirection[0]
            author_id = int(redirection[1])
            source = int(redirection[2])
            destination = int(redirection[3])&lt;/pre&gt;
  &lt;pre id=&quot;4YOA&quot;&gt;            if source != sender_id:
                logger.info(&amp;quot;Source != Sender&amp;quot;)
                continue&lt;/pre&gt;
  &lt;pre id=&quot;H2sv&quot;&gt;            # Get User from database
            user = database.get_user(author_id)
            is_user_premium = user[4]&lt;/pre&gt;
  &lt;pre id=&quot;s69r&quot;&gt;            # Allow premium users only
            if is_user_premium == 1:
                should_filter = MessageFilter.filter_msg(redirection_id, event)
                if should_filter:
                    return&lt;/pre&gt;
  &lt;pre id=&quot;b2FD&quot;&gt;                if has_media:
                    await telegram_client.send_file(destination, event.media, caption=message)
                else:
                    transformed_message = MessageTransformation.get_transformed_msg(
                        event, redirection_id)
                    await telegram_client.send_message(destination, transformed_message)&lt;/pre&gt;
  &lt;pre id=&quot;Blq4&quot;&gt;            else:
                await telegram_client.forward_messages(destination, event.message.id, sender_id)
            logger.info(f&amp;#x27;Message sent to {destination}&amp;#x27;)&lt;/pre&gt;
  &lt;pre id=&quot;TKZw&quot;&gt;    except Exception as err:
        logger.error(err)&lt;/pre&gt;
  &lt;p id=&quot;HDAb&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;JZbU&quot;&gt;↪️requirements.txt&lt;/h3&gt;
  &lt;pre id=&quot;sCPV&quot; data-lang=&quot;python&quot;&gt;Quart==0.13.0
Telethon==1.16.2
psycopg2==2.8.5&lt;/pre&gt;
  &lt;p id=&quot;Qeme&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;iggW&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;FgL0&quot;&gt;↪️server.py&lt;/h3&gt;
  &lt;pre id=&quot;kyot&quot;&gt;mport logging
import telethon
import hypercorn.asyncio
from quart import Quart, request, render_template_string
from telethon import TelegramClient, events
from telethon.tl.functions.messages import ImportChatInviteRequest
from telethon.tl.functions.channels import JoinChannelRequest
from client import telegram_client
from config import API_PORT
from forwarder import forwarder_event_handler&lt;/pre&gt;
  &lt;pre id=&quot;VDqZ&quot;&gt;format_str = &amp;#x27;[%(levelname)s] %(filename)s:%(lineno)s -- %(message)s&amp;#x27;
logging.basicConfig(level=logging.INFO, format=format_str)&lt;/pre&gt;
  &lt;pre id=&quot;1tjt&quot;&gt;
# Create Flask application instance
app = Quart(__name__)&lt;/pre&gt;
  &lt;pre id=&quot;bcnp&quot;&gt;
@app.route(&amp;#x27;/&amp;#x27;)
def index():
    return &amp;quot;Telethon API is up and running&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;fnxl&quot;&gt;
@app.route(&amp;#x27;/joinPublicUserEntity&amp;#x27;)
async def joinPublicUserEntity():
    &amp;quot;&amp;quot;&amp;quot;
    Join Private Users
    &amp;quot;&amp;quot;&amp;quot;
    entity = request.args.get(&amp;#x27;entity&amp;#x27;)&lt;/pre&gt;
  &lt;pre id=&quot;Tdwl&quot;&gt;    app.logger.info(&amp;#x27;[/joinPublicUserEntity] :: Entity Name {}&amp;#x27;.format(entity))
    try:
        msg = &amp;#x27;/start&amp;#x27;
        result = await telegram_client.get_entity(entity)
        return result.to_dict()
    except Exception as exception:
        return {&amp;#x27;error&amp;#x27;: str(exception)}&lt;/pre&gt;
  &lt;pre id=&quot;i56N&quot;&gt;
@app.route(&amp;#x27;/joinPublicEntity&amp;#x27;)
async def joinPublicEntity():
    &amp;quot;&amp;quot;&amp;quot;
    Join Public Groups &amp;amp; Channels
    &amp;quot;&amp;quot;&amp;quot;
    entity = request.args.get(&amp;#x27;entity&amp;#x27;)
    app.logger.info(&amp;#x27;[/joinPublicEntity] :: Entity Name {}&amp;#x27;.format(entity))
    try:
        result = await telegram_client(JoinChannelRequest(entity))
        return result.chats[0].to_dict()
    except telethon.errors.rpcerrorlist.UserAlreadyParticipantError:
        return {&amp;#x27;success&amp;#x27;: &amp;#x27;ok&amp;#x27;}
    except Exception as exception:
        return {&amp;#x27;error&amp;#x27;: str(exception)}&lt;/pre&gt;
  &lt;pre id=&quot;i1v6&quot;&gt;
@app.route(&amp;#x27;/joinPrivateEntity&amp;#x27;)
async def joinPrivateEntity():
    &amp;quot;&amp;quot;&amp;quot;
    Join Invation Link
    &amp;quot;&amp;quot;&amp;quot;
    hash = request.args.get(&amp;#x27;hash&amp;#x27;)
    app.logger.info(&amp;#x27;[/joinPrivateEntity] :: Hash {}&amp;#x27;.format(hash))
    try:
        result = await telegram_client(ImportChatInviteRequest(hash))
        return result.chats[0].to_dict()
    except telethon.errors.rpcerrorlist.UserAlreadyParticipantError:
        return {&amp;#x27;succes&amp;#x27;: &amp;#x27;ok&amp;#x27;}
    except Exception as exception:
        app.logger.error(exception)
        return {&amp;#x27;error&amp;#x27;: str(exception)}&lt;/pre&gt;
  &lt;pre id=&quot;2TMK&quot;&gt;
@app.route(&amp;#x27;/getentity&amp;#x27;, methods=[&amp;#x27;GET&amp;#x27;])
async def getEntity():
    entity = request.args.get(&amp;#x27;entity&amp;#x27;)
    is_entity_id = request.args.get(&amp;#x27;is_id&amp;#x27;) or &amp;#x27;0&amp;#x27;&lt;/pre&gt;
  &lt;pre id=&quot;pjeS&quot;&gt;    if (is_entity_id == &amp;#x27;1&amp;#x27;):
        entity = int(entity)&lt;/pre&gt;
  &lt;pre id=&quot;9WK0&quot;&gt;    app.logger.info(&amp;#x27;[/getentity] :: Entity Name {}&amp;#x27;.format(entity))
    try:
        result = await telegram_client.get_entity(entity)
        return result.to_dict()
    except Exception as exception:
        app.logger.error(exception)
        return {&amp;#x27;error&amp;#x27;: str(exception)}&lt;/pre&gt;
  &lt;pre id=&quot;S3QT&quot;&gt;
@app.before_serving
async def startup():
    await telegram_client.connect()
    if await telegram_client.is_user_authorized():
        user = await telegram_client.get_me()
        app.logger.info(f&amp;#x27;Logged in as @{user.username}&amp;#x27;)&lt;/pre&gt;
  &lt;pre id=&quot;H7z5&quot;&gt;
@app.after_serving
async def cleanup():
    await telegram_client.disconnect()&lt;/pre&gt;
  &lt;pre id=&quot;JHUD&quot;&gt;BASE_TEMPLATE = &amp;#x27;&amp;#x27;&amp;#x27;
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;meta charset=&amp;#x27;UTF-8&amp;#x27;&amp;gt;
        &amp;lt;title&amp;gt;Telethon + Quart&amp;lt;/title&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;{{ content | safe }}&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&amp;#x27;&amp;#x27;&amp;#x27;&lt;/pre&gt;
  &lt;pre id=&quot;In69&quot;&gt;PHONE_FORM = &amp;#x27;&amp;#x27;&amp;#x27;
&amp;lt;form action=&amp;#x27;/login&amp;#x27; method=&amp;#x27;post&amp;#x27;&amp;gt;
    Phone (international format): &amp;lt;input name=&amp;#x27;phone&amp;#x27; type=&amp;#x27;text&amp;#x27; placeholder=&amp;#x27;+34600000000&amp;#x27;&amp;gt;
    &amp;lt;input type=&amp;#x27;submit&amp;#x27;&amp;gt;
&amp;lt;/form&amp;gt;
&amp;#x27;&amp;#x27;&amp;#x27;&lt;/pre&gt;
  &lt;pre id=&quot;ACbF&quot;&gt;CODE_FORM = &amp;#x27;&amp;#x27;&amp;#x27;
&amp;lt;form action=&amp;#x27;/login&amp;#x27; method=&amp;#x27;post&amp;#x27;&amp;gt;
    Telegram code: &amp;lt;input name=&amp;#x27;code&amp;#x27; type=&amp;#x27;text&amp;#x27; placeholder=&amp;#x27;70707&amp;#x27;&amp;gt;
    &amp;lt;input type=&amp;#x27;submit&amp;#x27;&amp;gt;
&amp;lt;/form&amp;gt;
&amp;#x27;&amp;#x27;&amp;#x27;&lt;/pre&gt;
  &lt;pre id=&quot;R0FO&quot;&gt;phone = None
@app.route(&amp;#x27;/login&amp;#x27;, methods=[&amp;#x27;GET&amp;#x27;, &amp;#x27;POST&amp;#x27;])
async def root():
    # We want to update the global phone variable to remember it
    global phone&lt;/pre&gt;
  &lt;pre id=&quot;mUvi&quot;&gt;    # Check form parameters (phone/code)
    form = await request.form
    if &amp;#x27;phone&amp;#x27; in form:
        phone = form[&amp;#x27;phone&amp;#x27;]
        await telegram_client.send_code_request(phone)&lt;/pre&gt;
  &lt;pre id=&quot;evnM&quot;&gt;    if &amp;#x27;code&amp;#x27; in form:
        await telegram_client.sign_in(code=form[&amp;#x27;code&amp;#x27;])&lt;/pre&gt;
  &lt;pre id=&quot;ic9s&quot;&gt;    # If we&amp;#x27;re logged in, show them some messages from their first dialog
    if await telegram_client.is_user_authorized():
        user = await telegram_client.get_me()
        return f&amp;#x27;Logged in as @{user.username}&amp;#x27;&lt;/pre&gt;
  &lt;pre id=&quot;T5Fr&quot;&gt;    # Ask for the phone if we don&amp;#x27;t know it yet
    if phone is None:
        return await render_template_string(BASE_TEMPLATE, content=PHONE_FORM)&lt;/pre&gt;
  &lt;pre id=&quot;GGJ9&quot;&gt;    # We have the phone, but we&amp;#x27;re not logged in, so ask for the code
    return await render_template_string(BASE_TEMPLATE, content=CODE_FORM)&lt;/pre&gt;
  &lt;pre id=&quot;Iq03&quot;&gt;
async def main():
    hypercorn_config = hypercorn.Config()
    hypercorn_config.bind = [f&amp;quot;0.0.0.0:{API_PORT}&amp;quot;]
    await hypercorn.asyncio.serve(app, hypercorn_config)&lt;/pre&gt;
  &lt;pre id=&quot;3A2t&quot;&gt;
if __name__ == &amp;quot;__main__&amp;quot;:
    telegram_client.on(events.NewMessage)(forwarder_event_handler)
    telegram_client.loop.run_until_complete(main())&lt;/pre&gt;
  &lt;p id=&quot;ORT7&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;7M7X&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;OUw8&quot;&gt;✉Бот&lt;/h2&gt;
  &lt;h3 id=&quot;WDgv&quot;&gt;      ✉Конфигурация&lt;/h3&gt;
  &lt;h3 id=&quot;mXTb&quot;&gt;            ↪️index.js&lt;/h3&gt;
  &lt;pre id=&quot;tx93&quot;&gt;const config = {
  PORT: process.env.PORT || 3000,
  DB_HOST: process.env.DB_HOST || &amp;#x27;localhost&amp;#x27;,
  AGENT_HOSTNAME: process.env.AGENT_HOSTNAME || &amp;#x27;localhost&amp;#x27;,
  AGENT_PORT: process.env.AGENT_PORT || 3000,
  TG: {
    TG_API_KEY: process.env.TG_API_KEY,
    TG_BOT_USERNAME: process.env.TG_BOT_USERNAME,
  },
  APP: {
    FREE_USER: {
      QUOTA_LIMIT: 10,
    },
    PREMIUM_USER: {
      QUOTA_LIMIT: 0,
    },
  },
};
&lt;/pre&gt;
  &lt;h3 id=&quot;TRhx&quot;&gt;&lt;/h3&gt;
  &lt;p id=&quot;8v1V&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;vXlC&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;qcTQ&quot;&gt;✉контроллеры&lt;/h3&gt;
  &lt;h3 id=&quot;ikvA&quot;&gt;         ✉Сообщение&lt;/h3&gt;
  &lt;h3 id=&quot;CH2c&quot;&gt;                   ↪️parser.js&lt;/h3&gt;
  &lt;pre id=&quot;F4fm&quot;&gt;const validCommands = [
  &amp;#x27;/add&amp;#x27;,
  &amp;#x27;/remove&amp;#x27;,
  &amp;#x27;/list&amp;#x27;,
  &amp;#x27;/activate&amp;#x27;,
  &amp;#x27;/deactivate&amp;#x27;,
  &amp;#x27;/help&amp;#x27;,
  &amp;#x27;/ref&amp;#x27;,
  &amp;#x27;/filters&amp;#x27;,
  &amp;#x27;/filter&amp;#x27;,
  &amp;#x27;/transform&amp;#x27;,
  &amp;#x27;/start&amp;#x27;,
  &amp;#x27;/transforms&amp;#x27;,
  &amp;#x27;/transformrank&amp;#x27;,
  &amp;#x27;/transformremove&amp;#x27;,
];&lt;/pre&gt;
  &lt;pre id=&quot;X6Ge&quot;&gt;// Command Error Constructor Function
const commandError = (command, errorMsg) =&amp;gt; {
  return {
    command,
    error: errorMsg,
  };
};&lt;/pre&gt;
  &lt;pre id=&quot;6sYQ&quot;&gt;const addMessageEntities = messageEvent =&amp;gt; {
  const entities = messageEvent.entities;
  let msgText = messageEvent.text;
  let addedOffset = 0;&lt;/pre&gt;
  &lt;pre id=&quot;VfQu&quot;&gt;  entities.forEach(entity =&amp;gt; {
    const entityType = entity.type;
    let offset = entity.offset + addedOffset;
    let length = offset + entity.length;&lt;/pre&gt;
  &lt;pre id=&quot;jw9R&quot;&gt;    if (entityType === &amp;#x27;pre&amp;#x27;) {
      msgText = msgText.slice(0, offset) + &amp;#x27;&amp;#x60;&amp;#x60;&amp;#x60;&amp;#x27; + msgText.slice(offset);
      length += 3;
      msgText = msgText.slice(0, length) + &amp;#x27;&amp;#x60;&amp;#x60;&amp;#x60;&amp;#x27; + msgText.slice(length);
      addedOffset += 6;
    } else if (entityType === &amp;#x27;bold&amp;#x27;) {
      msgText = msgText.slice(0, offset) + &amp;#x27;**&amp;#x27; + msgText.slice(offset);
      length += 2;
      msgText = msgText.slice(0, length) + &amp;#x27;**&amp;#x27; + msgText.slice(length);
      addedOffset += 4;
    } else if (entityType === &amp;#x27;italic&amp;#x27;) {
      msgText = msgText.slice(0, offset) + &amp;#x27;__&amp;#x27; + msgText.slice(offset);
      length += 2;
      msgText = msgText.slice(0, length) + &amp;#x27;__&amp;#x27; + msgText.slice(length);
      addedOffset += 4;
    } else if (entityType === &amp;#x27;code&amp;#x27;) {
      msgText = msgText.slice(0, offset) + &amp;#x27;&amp;#x60;&amp;#x27; + msgText.slice(offset);
      length += 1;
      msgText = msgText.slice(0, length) + &amp;#x27;&amp;#x60;&amp;#x27; + msgText.slice(length);
      addedOffset += 2;
    }
  });&lt;/pre&gt;
  &lt;pre id=&quot;zFKN&quot;&gt;  return msgText;
};&lt;/pre&gt;
  &lt;pre id=&quot;34aL&quot;&gt;/////////////////////////////
// Transformations Command //
/////////////////////////////&lt;/pre&gt;
  &lt;pre id=&quot;9AwG&quot;&gt;// Remove Transformation
const parseCommandTransformRemove = message =&amp;gt; {
  const msgArr = message.trim().split(&amp;#x27; &amp;#x27;);
  if (msgArr.length !== 2) {
    let errMsg = &amp;#x27;Should contain 1 parameter.\n\n&amp;#x27;;
    errMsg += &amp;#x27;&amp;#x60;/transformremove &amp;lt;redirection id&amp;gt;\n&amp;#x60;&amp;#x27;;
    return commandError(msgArr[0], errMsg);
  }
  return {
    transformationId: msgArr[1],
  };
};&lt;/pre&gt;
  &lt;pre id=&quot;oqL1&quot;&gt;// List Transformations
const parseCommandTransforms = message =&amp;gt; {
  const msgArr = message.trim().split(&amp;#x27; &amp;#x27;);
  if (msgArr.length !== 2) {
    let errMsg = &amp;#x27;Should contain 1 parameter.\n\n&amp;#x27;;
    errMsg += &amp;#x27;&amp;#x60;/transforms &amp;lt;redirection id&amp;gt;\n&amp;#x60;&amp;#x27;;
    return commandError(msgArr[0], errMsg);
  }
  return {
    redirectionId: msgArr[1],
  };
};&lt;/pre&gt;
  &lt;pre id=&quot;MHzQ&quot;&gt;// Add Transformation
const parseCommandTransform = (message, messageEvent) =&amp;gt; {
  message = addMessageEntities(messageEvent);
  const msgArrOfLines = message.trim().split(&amp;#x27;\n&amp;#x27;);
  const msgArr = msgArrOfLines[0].trim().replace(/\n/g, &amp;#x27;&amp;#x27;).split(&amp;#x27; &amp;#x27;);
  if (msgArrOfLines.length !== 3 || msgArr.length !== 2) {
    let errMsg = &amp;#x27;Should contain 3 parameters.\n\n&amp;#x27;;
    errMsg += &amp;#x27;&amp;#x60;/transform &amp;lt;redirection id&amp;gt;\n&amp;#x60;&amp;#x27;;
    errMsg += &amp;#x27;&amp;#x60;&amp;lt;phrase to transform&amp;gt;\n&amp;lt;phrase to transform with&amp;gt;&amp;#x60;\n\n&amp;#x27;;
    errMsg += &amp;#x27;Example: \n\n&amp;#x27;;
    errMsg += &amp;#x27;&amp;#x60;/transform 1\njustin bieber\nFreddie Mercury&amp;#x60;&amp;#x27;;
    return commandError(msgArr[0], errMsg);
  }
  return {
    redirectionId: msgArr[1],
    oldPhrase: msgArrOfLines[1],
    newPhrase: msgArrOfLines[2],
  };
};&lt;/pre&gt;
  &lt;pre id=&quot;wYCw&quot;&gt;// Swap rank of transformations
const parseCommandTransformRank = message =&amp;gt; {
  const msgArr = message.trim().split(&amp;#x27; &amp;#x27;);
  if (msgArr.length !== 4) {
    let errMsg = &amp;#x27;Should contain 3 parameters.\n\n&amp;#x27;;
    errMsg += &amp;#x27;&amp;#x60;/transformrank &amp;lt;redirection id&amp;gt; &amp;lt;rank1&amp;gt; &amp;lt;rank2&amp;gt;\n\n&amp;#x60;&amp;#x27;;
    errMsg += &amp;#x27;Example: \n\n&amp;#x27;;
    errMsg += &amp;#x27;&amp;#x60;/transform 100 2 3&amp;#x60;&amp;#x27;;
    return commandError(msgArr[0], errMsg);
  }&lt;/pre&gt;
  &lt;pre id=&quot;fKmO&quot;&gt;  return {
    redirectionId: msgArr[1],
    rank1: msgArr[2],
    rank2: msgArr[3],
  };
};&lt;/pre&gt;
  &lt;pre id=&quot;urVZ&quot;&gt;//////////////////////////
// Redirection commands //
//////////////////////////&lt;/pre&gt;
  &lt;pre id=&quot;W4Sy&quot;&gt;// Add Redirection
const parseCommandAdd = message =&amp;gt; {
  const msgArr = message.trim().split(&amp;#x27; &amp;#x27;);
  let errMsg = &amp;#x27;Should contain 2 parameters.\n\n&amp;#x27;;
  errMsg += &amp;#x27;&amp;#x60;/add @youtube @google\n/add https://t.me/joinchat @facebook&amp;#x60;&amp;#x27;;
  if (msgArr.length !== 3) return commandError(msgArr[0], errMsg);
  return {
    source: msgArr[1],
    destination: msgArr[2],
  };
};&lt;/pre&gt;
  &lt;pre id=&quot;HGaU&quot;&gt;// Remove Redirection
const parseCommandRemove = message =&amp;gt; {
  const msgArr = message.trim().split(&amp;#x27; &amp;#x27;);
  let errMsg = &amp;#x27;Should contain 1 parameter.\n\n&amp;#x27;;
  errMsg += &amp;#x27;&amp;#x60;/remove &amp;lt;redirection id&amp;gt;&amp;#x60;&amp;#x27;;
  if (msgArr.length !== 2) return commandError(msgArr[0], errMsg);
  return { redirectionId: msgArr[1] };
};&lt;/pre&gt;
  &lt;pre id=&quot;NMEs&quot;&gt;// List Redirections
const parseCommandList = message =&amp;gt; {
  const msgArr = message.trim().split(&amp;#x27; &amp;#x27;);
  if (msgArr.length !== 1) return commandError(msgArr[0], &amp;#x27;Should not have any parameter&amp;#x27;);
  return true;
};&lt;/pre&gt;
  &lt;pre id=&quot;xVVR&quot;&gt;// Activate Redirection
const parseCommandActivate = message =&amp;gt; {
  const msgArr = message.trim().split(&amp;#x27; &amp;#x27;);
  if (msgArr.length !== 2) return commandError(msgArr[0], &amp;#x27;Should contain 1 parameter\n\n&amp;#x60;/activate &amp;lt;redirection id&amp;gt;&amp;#x60;&amp;#x27;);&lt;/pre&gt;
  &lt;pre id=&quot;RcWW&quot;&gt;  const redirectionId = msgArr[1];
  if (isNaN(Number(redirectionId))) {
    return commandError(msgArr[0], &amp;#x60;redirection id should be a number&amp;#x60;);
  }&lt;/pre&gt;
  &lt;pre id=&quot;bzzW&quot;&gt;  return { redirectionId };
};&lt;/pre&gt;
  &lt;pre id=&quot;P5Az&quot;&gt;// Deactivate Redirection
const parseCommandDeactivate = message =&amp;gt; {
  const msgArr = message.trim().split(&amp;#x27; &amp;#x27;);
  if (msgArr.length !== 2) {
    return commandError(msgArr[0], &amp;#x27;Should contain 1 parameter\n\n&amp;#x60;/deactivate &amp;lt;redirection id&amp;gt;&amp;#x60;&amp;#x27;);
  }&lt;/pre&gt;
  &lt;pre id=&quot;lbXg&quot;&gt;  const redirectionId = msgArr[1];
  if (isNaN(Number(redirectionId))) {
    return commandError(msgArr[0], &amp;#x60;redirection id should be a number&amp;#x60;);
  }&lt;/pre&gt;
  &lt;pre id=&quot;Tnhq&quot;&gt;  return { redirectionId: msgArr[1] };
};&lt;/pre&gt;
  &lt;pre id=&quot;LJhS&quot;&gt;/////////////////////
// Filter Commands //
/////////////////////&lt;/pre&gt;
  &lt;pre id=&quot;lGgu&quot;&gt;// Add Filter
const parseCommandFilter = message =&amp;gt; {
  let parsedResponse = {};
  const msgArr = message.trim().split(&amp;#x27; &amp;#x27;);
  if (msgArr.length &amp;lt; 4) {
    let reply = &amp;#x27;Should contain at least 3 parameters.\n\n&amp;#x27;;
    reply += &amp;#x27;&amp;#x60;/filter &amp;lt;filter name&amp;gt; &amp;lt;redirection id&amp;gt; &amp;lt;filter state&amp;gt;&amp;#x60;\n\n&amp;#x27;;
    reply += &amp;#x27;Example : \n\n&amp;#x27;;
    reply += &amp;#x27;&amp;#x60;/filter photo 152 on&amp;#x60;&amp;#x27;;
    return commandError(msgArr[0], reply);
  }&lt;/pre&gt;
  &lt;pre id=&quot;xq4h&quot;&gt;  const validFilters = [&amp;#x27;photo&amp;#x27;, &amp;#x27;video&amp;#x27;, &amp;#x27;audio&amp;#x27;, &amp;#x27;sticker&amp;#x27;, &amp;#x27;document&amp;#x27;, &amp;#x27;hashtag&amp;#x27;, &amp;#x27;link&amp;#x27;, &amp;#x27;contain&amp;#x27;, &amp;#x27;notcontain&amp;#x27;];
  const validStates = [&amp;#x27;on&amp;#x27;, &amp;#x27;off&amp;#x27;];&lt;/pre&gt;
  &lt;pre id=&quot;mNQV&quot;&gt;  const filterName = msgArr[1];
  const redirectionId = msgArr[2];
  const filterState = msgArr[3].split(/\n/g)[0];&lt;/pre&gt;
  &lt;pre id=&quot;VG49&quot;&gt;  ////////////////////
  // Valid filter ? //
  ////////////////////
  if (validFilters.indexOf(filterName) &amp;lt; 0) {
    const errMsg = &amp;#x60;Invalid filter name. Available filters :\n\n- ${validFilters.join(&amp;#x27;\n- &amp;#x27;)}&amp;#x60;;
    return commandError(msgArr[0], errMsg);
  }&lt;/pre&gt;
  &lt;pre id=&quot;YYUE&quot;&gt;  ///////////////////
  // Valid state ? //
  ///////////////////
  if (validStates.indexOf(filterState.toLowerCase()) &amp;lt; 0) {
    const errMsg = &amp;#x60;Invalid State. Available states :\n\n- ${validStates.join(&amp;#x27;\n- &amp;#x27;)}&amp;#x60;;
    return commandError(msgArr[0], errMsg);
  }&lt;/pre&gt;
  &lt;pre id=&quot;3bnI&quot;&gt;  /////////////////////////////////////////////////////////////////
  // If filter name is contain or notcontain gather the keywords //
  // Keywords are only required if filterstate = &amp;#x27;on&amp;#x27;            //
  /////////////////////////////////////////////////////////////////
  if (filterState === &amp;#x27;on&amp;#x27;) {
    if (filterName === &amp;#x27;contain&amp;#x27; || filterName === &amp;#x27;notcontain&amp;#x27;) {
      const msgArrLine = message.trim().split(&amp;#x27;\n&amp;#x27;);
      const filterKeywords = msgArrLine.splice(1, msgArrLine.length).filter(x =&amp;gt; x !== &amp;#x27;&amp;#x27;);
      parsedResponse.keywords = filterKeywords;&lt;/pre&gt;
  &lt;pre id=&quot;c68k&quot;&gt;      if (filterKeywords.length === 0) {
        let errMsg = &amp;#x27;Please provide keywords.\n\n Example: \n&amp;#x27;;
        errMsg += &amp;#x27;&amp;#x60;/filter contain 1 on\nqueen\nBohemian Rhapsody&amp;#x60;&amp;#x27;;
        return commandError(msgArr[0], errMsg);
      }
    }
  }&lt;/pre&gt;
  &lt;pre id=&quot;im5m&quot;&gt;  parsedResponse.name = filterName;
  parsedResponse.state = filterState;
  parsedResponse.redirectionId = redirectionId;
  return parsedResponse;
};&lt;/pre&gt;
  &lt;pre id=&quot;GsZa&quot;&gt;// List Filters
const parseCommandFilters = message =&amp;gt; {
  const msgArr = message.trim().split(&amp;#x27; &amp;#x27;);
  let errMsg = &amp;#x27;Should contain 1 parameter\n\n&amp;#x27;;
  errMsg += &amp;#x27;&amp;#x60;/filters &amp;lt;filter id&amp;gt;&amp;#x60;&amp;#x27;;
  if (msgArr.length !== 2) return commandError(msgArr[0], errMsg);
  return { filterId: msgArr[1] };
};&lt;/pre&gt;
  &lt;pre id=&quot;L5DT&quot;&gt;class MessageParser {
  /**
   * Takes in message and checks if there is a valid command
   * @param {String} message Telegram Message
   * @returns {Boolean}
   */
  static isValidCommand(message) {
    const firstWord = message.trim().split(&amp;#x27; &amp;#x27;)[0];
    return validCommands.indexOf(firstWord) &amp;gt;= 0;
  }&lt;/pre&gt;
  &lt;pre id=&quot;VOg0&quot;&gt;  static hashMap() {
    return {
      &amp;#x27;/add&amp;#x27;: parseCommandAdd,
      &amp;#x27;/remove&amp;#x27;: parseCommandRemove,
      &amp;#x27;/list&amp;#x27;: parseCommandList,
      &amp;#x27;/activate&amp;#x27;: parseCommandActivate,
      &amp;#x27;/deactivate&amp;#x27;: parseCommandDeactivate,
      &amp;#x27;/filter&amp;#x27;: parseCommandFilter,
      &amp;#x27;/filters&amp;#x27;: parseCommandFilters,
      &amp;#x27;/transform&amp;#x27;: parseCommandTransform,
      &amp;#x27;/transforms&amp;#x27;: parseCommandTransforms,
      &amp;#x27;/transformrank&amp;#x27;: parseCommandTransformRank,
      &amp;#x27;/transformremove&amp;#x27;: parseCommandTransformRemove,
    };
  }&lt;/pre&gt;
  &lt;pre id=&quot;1xQV&quot;&gt;  /**
   * Takes in message and returns command.
   * Should only be called after making sure there is a valid command using isValidCommand()
   * @param {String} message Telegram Message
   * @returns {String} returns command name
   */
  static getCommand(message) {
    return message.trim().split(&amp;#x27; &amp;#x27;)[0];
  }
}&lt;/pre&gt;
  &lt;pre id=&quot;1rM3&quot;&gt;module.exports = MessageParser;&lt;/pre&gt;
  &lt;pre id=&quot;sPj0&quot;&gt;if (require.main === module) {
  let message = &amp;#x27;/list &amp;#x27;;
  const isValid = MessageParser.isValidCommand(message);
  if (isValid) {
    const command = MessageParser.getCommand(message);
    const parser = hashMap[command];
    const response = parser(message);&lt;/pre&gt;
  &lt;pre id=&quot;uCkR&quot;&gt;    if (response.error) {
      return console.log(response);
    }&lt;/pre&gt;
  &lt;pre id=&quot;rnyn&quot;&gt;    if (command === &amp;#x27;/add&amp;#x27;) {
      console.log(&amp;#x60;Source: ${response.source} &amp;amp;&amp;amp; Destination: ${response.destination}&amp;#x60;);
    } else if (command === &amp;#x27;/remove&amp;#x27;) {
      console.log(&amp;#x60;Redirection ID: ${response.redirectionId}&amp;#x60;);
    }
  }
}&lt;/pre&gt;
  &lt;p id=&quot;3EUx&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;JuUX&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;vV6H&quot;&gt;↪️activateRedirection.js&lt;/h3&gt;
  &lt;pre id=&quot;retG&quot;&gt;const database = require(&amp;#x27;../db/database&amp;#x27;);
const ForwardAgent = require(&amp;#x27;../services/agent&amp;#x27;);
const { TG_BOT_USERNAME } = require(&amp;#x27;../config&amp;#x27;).TG;&lt;/pre&gt;
  &lt;pre id=&quot;9XQA&quot;&gt;/**
 * Activates a redirection
 * @param {String} sender Chat id of the owner
 * @param {String} redirectionId Redirection Id
 */
const activateRedirection = async (sender, redirectionId) =&amp;gt; {
  /////////////////////////////////////////////
  // Sender must be the owner of redirection //
  /////////////////////////////////////////////
  const redirections = await database.getRedirections(sender);
  let redirectionOfInterest = redirections.filter(redirection =&amp;gt; redirection.id == redirectionId);
  if (redirectionOfInterest.length === 0) {
    throw new Error(&amp;#x27;Redirection does not exist&amp;#x27;);
  }&lt;/pre&gt;
  &lt;pre id=&quot;tkFc&quot;&gt;  /////////////////////////////////////////////
  // If destination is Channel or Supergroup //
  // Should have admin rights                //
  /////////////////////////////////////////////
  const destId = redirectionOfInterest[0].destination;
  const entity = await ForwardAgent.getEntity(destId, { is_id: true });
  if (entity.entity.type === &amp;#x27;channel&amp;#x27; &amp;amp;&amp;amp; entity.entity.megagroup === false) {
    if (!entity.entity.adminRights) {
      const errMsg = &amp;#x60;Please provide admin rights to ${TG_BOT_USERNAME} on ${redirectionOfInterest[0].destination_title}&amp;#x60;;
      throw new Error(errMsg);
    }
  }&lt;/pre&gt;
  &lt;pre id=&quot;cZiz&quot;&gt;  ////////////////////////
  // Update to database //
  ////////////////////////
  await database.activateRedirection(redirectionId);
};&lt;/pre&gt;
  &lt;pre id=&quot;dTRN&quot;&gt;module.exports = activateRedirection;&lt;/pre&gt;
  &lt;pre id=&quot;0TZh&quot;&gt;&lt;/pre&gt;
  &lt;p id=&quot;zQEn&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;QZS1&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;kkfb&quot;&gt;↪️addFilter.js&lt;/h3&gt;
  &lt;pre id=&quot;s9HX&quot;&gt;const database = require(&amp;#x27;../db/database&amp;#x27;);&lt;/pre&gt;
  &lt;pre id=&quot;94uL&quot;&gt;/**
 * Activates a redirection
 * @param {String} sender Chat id of the owner
 * @param {String} filterData.redirectionId Redirection Id
 * @param {String} filterData.name Filter name
 * @param {String} filterData.state Filter state
 * @param {Array} filterData.keywords Keywords if filter is contain or notcontain
 * @typedef {Object} AddFilterResponse
 * @property {string} error - Error Message
 * @property {Object} filterData - Filter Object
 * @returns {AddFilterResponse}
 */
const addFilter = async (sender, filterData) =&amp;gt; {
  /////////////////////////////////////////////
  // Sender must be the owner of redirection //
  /////////////////////////////////////////////
  const redirections = await database.getRedirections(sender);
  let redirectionOfInterest = redirections.filter(redirection =&amp;gt; redirection.id == filterData.redirectionId);
  if (redirectionOfInterest.length === 0) return { error: &amp;#x27;⚠ Redirection doesnot exist&amp;#x27; };&lt;/pre&gt;
  &lt;pre id=&quot;de2A&quot;&gt;  ////////////////////////////////////////////
  // If Filtername is contain or notcontain //
  ////////////////////////////////////////////
  if (filterData.name === &amp;#x27;contain&amp;#x27; || filterData.name === &amp;#x27;notcontain&amp;#x27;) {
    const keywords = filterData.keywords ? filterData.keywords.join(&amp;#x27;&amp;lt;stop_word&amp;gt;&amp;#x27;) : null;
    await database.saveFilter(filterData.redirectionId, filterData.name, keywords);
    return { filterData };
  } else {
    const filterState = filterData.state === &amp;#x27;on&amp;#x27; ? 1 : 0;
    await database.saveFilter(filterData.redirectionId, filterData.name, filterState);
    return { filterData };
  }
};&lt;/pre&gt;
  &lt;pre id=&quot;ahwK&quot;&gt;module.exports = addFilter;&lt;/pre&gt;
  &lt;pre id=&quot;pkdH&quot;&gt;if (require.main === module) {
  addFilter(&amp;#x27;451722605&amp;#x27;, {
    redirectionId: 41,
    state: &amp;#x27;on&amp;#x27;,
    keywords: [&amp;#x27;hi&amp;#x27;, &amp;#x27;there&amp;#x27;, &amp;#x27;how are you ?&amp;#x27;],
    name: &amp;#x27;contain&amp;#x27;,
  })
    .then(x =&amp;gt; console.log(x))
    .catch(x =&amp;gt; console.log(x));
}&lt;/pre&gt;
  &lt;h3 id=&quot;9jFm&quot;&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;f84z&quot;&gt;↪️addRedirection.js&lt;/h3&gt;
  &lt;pre id=&quot;lTep&quot;&gt;const database = require(&amp;#x27;../db/database&amp;#x27;);
const ForwardAgent = require(&amp;#x27;../services/agent&amp;#x27;);
const QUOTA_LIMIT = require(&amp;#x27;../config/&amp;#x27;).APP.FREE_USER.QUOTA_LIMIT;&lt;/pre&gt;
  &lt;pre id=&quot;tPVH&quot;&gt;/**
 * Verifies that the source/destination is in one of the two valid formats
 * -- If it&amp;#x27;s a public entity (username), it should start with &amp;quot;@&amp;quot;
 * -- If it&amp;#x27;s a private entity (invitation link), it should start with t.me/joinchat/&amp;lt;HASH&amp;gt;
 * @param {string} entity username or invitation link
 * @returns {Object}
 */
const checkSourcePattern = entity =&amp;gt; {
  if (entity.indexOf(&amp;#x27;@&amp;#x27;) === 0) return { username: entity };
  if (entity.indexOf(&amp;#x27;t.me/joinchat/&amp;#x27;) === 0) return { hash: entity.replace(&amp;#x27;t.me/joinchat/&amp;#x27;, &amp;#x27;&amp;#x27;) };
  if (entity.indexOf(&amp;#x27;https://t.me/joinchat/&amp;#x27;) === 0) return { hash: entity.replace(&amp;#x27;https://t.me/joinchat/&amp;#x27;, &amp;#x27;&amp;#x27;) };
  throw new Error(&amp;#x27;Invalid format&amp;#x27;);
};&lt;/pre&gt;
  &lt;pre id=&quot;qJ7S&quot;&gt;/**
 * Adds a redirection
 * @param {String} sender Chat id of the owner
 * @param {String} source Username / Link of Source
 * @param {String} destination Username / Link of Destination
 */
const addRedirection = async (sender, source, destination) =&amp;gt; {
  /////////////////////////////////////////////////
  // Validate and get Source &amp;amp;&amp;amp; Destination type //
  /////////////////////////////////////////////////
  let sourceType, destinationType;
  try {
    sourceType = checkSourcePattern(source);
    destinationType = checkSourcePattern(destination);
  } catch (error) {
    console.log(error);
    if (error) return { error: error.message };
  }&lt;/pre&gt;
  &lt;pre id=&quot;gBDx&quot;&gt;  /////////////////
  // Quota Check //
  /////////////////
  const userRecord = await database.getUser(sender);
  if (!userRecord) {
    return {
      error: &amp;#x27;You are not registered. Please send /start to register&amp;#x27;,
    };
  }
  const userIsPremium = userRecord.premium == &amp;#x27;1&amp;#x27; ? true : false;
  if (!userIsPremium) {
    if (userRecord.quota &amp;gt;= QUOTA_LIMIT) {
      return {
        error: &amp;#x27;You have reached your quota limit. Please upgrade your account.&amp;#x27;,
      };
    }
  }&lt;/pre&gt;
  &lt;pre id=&quot;dsRw&quot;&gt;  ///////////////////////////////////////
  // Get Entities                      //
  // If not joinable, will throw Error //
  ///////////////////////////////////////
  let sourceEntity = await ForwardAgent.getEntity(source);
  let destinationEntity = await ForwardAgent.getEntity(destination);&lt;/pre&gt;
  &lt;pre id=&quot;sVI4&quot;&gt;  //////////////////////////
  // Join agent to source //
  //////////////////////////
  let joinSrcRequestResponse = null;
  if (sourceType.username) {
    if (sourceEntity.entity.type === &amp;#x27;user&amp;#x27;) {
      joinSrcRequestResponse = await ForwardAgent.joinPublicUserEntity(sourceType.username);
    } else {
      joinSrcRequestResponse = await ForwardAgent.joinPublicEntity(sourceType.username);
    }
  } else if (sourceType.hash) {
    joinSrcRequestResponse = await ForwardAgent.joinPrivateEntity(sourceType.hash);
  }
  if (joinSrcRequestResponse.error) return { error: joinSrcRequestResponse.error };&lt;/pre&gt;
  &lt;pre id=&quot;1Vhq&quot;&gt;  ///////////////////////////////
  // Join agent to destination //
  ///////////////////////////////
  let joinDestRequestResponse = null;
  if (destinationType.username) {
    if (destinationEntity.entity.type === &amp;#x27;user&amp;#x27;) {
      joinDestRequestResponse = await ForwardAgent.joinPublicUserEntity(destinationType.username);
    } else {
      joinDestRequestResponse = await ForwardAgent.joinPublicEntity(destinationType.username);
    }
  } else if (destinationType.hash) {
    joinDestRequestResponse = await ForwardAgent.joinPrivateEntity(destinationType.hash);
  }
  if (joinDestRequestResponse.error) return { error: joinDestRequestResponse.error };&lt;/pre&gt;
  &lt;pre id=&quot;7bal&quot;&gt;  /////////////////////////////////////////////////////////////////
  // We cannot get entity from an invitation link before joining //
  // Now that we have joined, we can get the entity              //
  /////////////////////////////////////////////////////////////////
  if (sourceEntity.entity === null) {
    sourceEntity = await ForwardAgent.getEntity(source);
  }
  if (destinationEntity.entity === null) {
    destinationEntity = await ForwardAgent.getEntity(destination);
  }
  //////////////////////////
  // No Duplicate Entries //
  // No Circular Entries  //
  //////////////////////////
  const allRedirections = await database.getRedirections(sender);
  for (const redirection of allRedirections) {
    const source = redirection.source;
    const destination = redirection.destination;&lt;/pre&gt;
  &lt;pre id=&quot;FN54&quot;&gt;    if (source == sourceEntity.entity.chatId &amp;amp;&amp;amp; destination == destinationEntity.entity.chatId) {
      return { error: &amp;#x60;Redirection already exists with id &amp;lt;code&amp;gt;[${redirection.id}]&amp;lt;/code&amp;gt; &amp;#x60; };
    }&lt;/pre&gt;
  &lt;pre id=&quot;3pbx&quot;&gt;    if (source == destinationEntity.entity.chatId &amp;amp;&amp;amp; destination == sourceEntity.entity.chatId) {
      return { error: &amp;#x60;Circular redirection is not allowed &amp;lt;code&amp;gt;[${redirection.id}]&amp;lt;/code&amp;gt;&amp;#x60; };
    }
  }&lt;/pre&gt;
  &lt;pre id=&quot;3jn4&quot;&gt;  ///////////////////////
  // Store to database //
  // Increase Quota    //
  ///////////////////////
  const srcId = sourceEntity.entity.chatId;
  const destId = destinationEntity.entity.chatId;
  const srcTitle = sourceEntity.entity.title;
  const destTitle = destinationEntity.entity.title;
  await database.saveRedirection(sender, srcId, destId, srcTitle, destTitle);
  await database.changeUserQuota(sender);
  return { success: true };
};&lt;/pre&gt;
  &lt;pre id=&quot;i8GD&quot;&gt;module.exports = addRedirection;&lt;/pre&gt;
  &lt;p id=&quot;w1YP&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;87sR&quot;&gt;↪️addTransformation.js&lt;/h3&gt;
  &lt;pre id=&quot;XQhq&quot;&gt;const pino = require(&amp;#x27;pino&amp;#x27;);&lt;/pre&gt;
  &lt;pre id=&quot;aCJp&quot;&gt;const database = require(&amp;#x27;../db/database&amp;#x27;);
const logger = pino({ level: process.env.LOG_LEVEL || &amp;#x27;info&amp;#x27; });&lt;/pre&gt;
  &lt;pre id=&quot;TERB&quot;&gt;const addTransformation = async (sender, redirectionId, oldPhrase, newPhrase) =&amp;gt; {
  /////////////////////////////////////////////
  // Sender must be the owner of redirection //
  /////////////////////////////////////////////
  const redirections = await database.getRedirections(sender);
  let redirectionOfInterest = redirections.filter(redirection =&amp;gt; redirection.id == redirectionId);
  if (redirectionOfInterest.length === 0) throw Error(&amp;#x27;Redirection does not exist&amp;#x27;);&lt;/pre&gt;
  &lt;pre id=&quot;BBYD&quot;&gt;  /////////////////////////////////////////////////
  // Fetch Transformations for given redirection //
  // To determine the rank of transformation     //
  /////////////////////////////////////////////////
  const transformations = await database.getTransformationsOfRedirection(redirectionId);
  const currentHighestRank = transformations.reduce((prevVal, curVal, curIndex) =&amp;gt; {
    return curVal.rank &amp;gt; prevVal ? curVal.rank : prevVal;
  }, -1);
  const rankOfNewTransformation = currentHighestRank + 1;&lt;/pre&gt;
  &lt;pre id=&quot;ZcNK&quot;&gt;  /////////////////////
  // Update database //
  /////////////////////
  console.log({ redirectionId, oldPhrase, newPhrase, rankOfNewTransformation, sender });
  const dbResponse = await database.saveTransformation(redirectionId, oldPhrase, newPhrase, rankOfNewTransformation);
  return { redirectionId, transformationId: dbResponse.rows[0].id };
};&lt;/pre&gt;
  &lt;pre id=&quot;DbSg&quot;&gt;module.exports = addTransformation;&lt;/pre&gt;
  &lt;p id=&quot;ex3Y&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;fCU8&quot;&gt;↪️отключитеRedirection.js&lt;/h3&gt;
  &lt;pre id=&quot;XWXX&quot;&gt;const database = require(&amp;#x27;../db/database&amp;#x27;);&lt;/pre&gt;
  &lt;pre id=&quot;G7KO&quot;&gt;/**
 * Activates a redirection
 * @param {String} sender Chat id of the owner
 * @param {String} redirectionId Redirection Id
 */
const deactivateRedirection = (sender, redirectionId) =&amp;gt; {
  return new Promise(async (resolve, reject) =&amp;gt; {
    try {
      /////////////////////////////////////////////
      // Sender must be the owner of redirection //
      /////////////////////////////////////////////
      const redirections = await database.getRedirections(sender);
      let redirectionOfInterest = redirections.filter((redirection) =&amp;gt; redirection.id == redirectionId)
      if (redirectionOfInterest.length === 0) throw Error(&amp;#x27;Redirection doesnot exist&amp;#x27;);&lt;/pre&gt;
  &lt;pre id=&quot;wHeF&quot;&gt;      ////////////////////////
      // Update to database //
      ////////////////////////
      const dbResponse = await database.deactivateRedirection(redirectionId);
      return resolve({ redirection: redirectionOfInterest[0], dbResponse });
    } catch (err) {
      reject(err);
    }
  })
}&lt;/pre&gt;
  &lt;pre id=&quot;O07M&quot;&gt;module.exports = deactivateRedirection;&lt;/pre&gt;
  &lt;pre id=&quot;cS2e&quot;&gt;if (require.main === module) {
  deactivateRedirection(&amp;#x27;451722605&amp;#x27;, &amp;#x27;39&amp;#x27;)
    .then(x =&amp;gt; console.log(x))
    .catch(x =&amp;gt; console.log(x))
}&lt;/pre&gt;
  &lt;p id=&quot;jrdk&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;3dLb&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;Iel7&quot;&gt;↪️getFilter.js&lt;/h3&gt;
  &lt;pre id=&quot;Yp6p&quot;&gt;const database = require(&amp;#x27;../db/database&amp;#x27;);&lt;/pre&gt;
  &lt;pre id=&quot;25Vg&quot;&gt;/**
 * @param {String} sender Sender Chat id
 * @param {String} filterId Filter Id
 * @typedef {Object} GetFilterResponse
 * @property {string} error - Error Message
 * @property {Object} filter - Filter Object
 * @returns {GetFilterResponse}
 */
const getFilter = async (sender, filterId) =&amp;gt; {
  /////////////////////////////////////////////
  // Sender must be the owner of redirection //
  // Linked with the given filter            //
  /////////////////////////////////////////////
  const redirections = await database.getRedirections(sender);
  const redirectionOfInterest = redirections.filter(redirection =&amp;gt; redirection.id == filterId);
  if (redirectionOfInterest.length === 0) return { error: &amp;#x27;⚠ Filter does not exist&amp;#x27; };&lt;/pre&gt;
  &lt;pre id=&quot;KvlM&quot;&gt;  ////////////////
  // Get Filter //
  ////////////////
  const filter = await database.getFilter(filterId);
  if (!filter) return { error: &amp;#x27;⚠ You have no filters&amp;#x27; };
  return { filter };
};&lt;/pre&gt;
  &lt;pre id=&quot;PfDN&quot;&gt;module.exports = getFilter;&lt;/pre&gt;
  &lt;p id=&quot;n8PZ&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;E7G9&quot;&gt;↪️getTransformations.js&lt;/h3&gt;
  &lt;pre id=&quot;l1hw&quot;&gt;const database = require(&amp;#x27;../db/database&amp;#x27;);&lt;/pre&gt;
  &lt;pre id=&quot;5Itn&quot;&gt;const getTransformations = async (sender, redirectionId) =&amp;gt; {
  /////////////////////////////////////////////
  // Sender must be the owner of redirection //
  // Linked with the given transformations   //
  /////////////////////////////////////////////
  const redirections = await database.getRedirections(sender);
  const redirectionOfInterest = redirections.filter(redirection =&amp;gt; redirection.id == redirectionId);
  if (redirectionOfInterest.length === 0) throw Error(&amp;#x27;Redirection does not exist&amp;#x27;);&lt;/pre&gt;
  &lt;pre id=&quot;vC1X&quot;&gt;  /////////////////////////
  // Get transformations //
  /////////////////////////
  return await database.getTransformationsOfRedirection(redirectionId);
};&lt;/pre&gt;
  &lt;pre id=&quot;i1aS&quot;&gt;module.exports = getTransformations;&lt;/pre&gt;
  &lt;p id=&quot;K38Q&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;8gUz&quot;&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;wwXY&quot;&gt;↪️removeRedirection.js&lt;/h3&gt;
  &lt;pre id=&quot;6mxt&quot;&gt;const database = require(&amp;#x27;../db/database&amp;#x27;);&lt;/pre&gt;
  &lt;pre id=&quot;djlx&quot;&gt;const removeRedirection = async (sender, redirectionId) =&amp;gt; {
  return new Promise(async (resolve, reject) =&amp;gt; {
    try {
      /////////////////////////////////////////////
      // Sender must be the owner of redirection //
      /////////////////////////////////////////////
      const redirections = await database.getRedirections(sender);
      let redirectionOfInterest = redirections.filter((redirection) =&amp;gt; redirection.id == redirectionId)
      if (redirectionOfInterest.length === 0) throw Error(&amp;#x27;Redirection does not exist&amp;#x27;);&lt;/pre&gt;
  &lt;pre id=&quot;oFzt&quot;&gt;      ////////////////////////
      // Update to database //
      ////////////////////////
      const dbResponse = await database.removeRedirection(redirectionId);
      await database.changeUserQuota(sender, -1);
      return resolve({ dbResponse });
    } catch (err) {
      console.log(&amp;#x60;[Error removeRedirection.js] :: ${err}&amp;#x60;);
      return reject(err);
    }
  })
}&lt;/pre&gt;
  &lt;pre id=&quot;gT9h&quot;&gt;module.exports = removeRedirection;&lt;/pre&gt;
  &lt;p id=&quot;6gmv&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;PmHC&quot;&gt;↪️removeTransformation.js&lt;/h3&gt;
  &lt;pre id=&quot;S9PD&quot;&gt;const database = require(&amp;#x27;../db/database&amp;#x27;);&lt;/pre&gt;
  &lt;pre id=&quot;pmjz&quot;&gt;const removeTransformation = (sender, transformationId) =&amp;gt; {
  return new Promise(async (resolve, reject) =&amp;gt; {
    try {
      /////////////////////////////////////////////
      // Sender must be the owner of redirection //
      // Linked with the given transformations   //
      /////////////////////////////////////////////
      const transformation = await database.getTransformation(transformationId);
      if (transformation === undefined) throw new Error(&amp;#x27;Transformation does not exist&amp;#x27;);&lt;/pre&gt;
  &lt;pre id=&quot;eU4W&quot;&gt;      const redirections = await database.getRedirections(sender);
      const isOwner = redirections.filter(r =&amp;gt; r.id == transformation[&amp;#x27;redirection_id&amp;#x27;]).length &amp;gt; 0;
      if (!isOwner) throw new Error(&amp;#x27;You are not the owner of the transformation.&amp;#x27;);&lt;/pre&gt;
  &lt;pre id=&quot;VL5a&quot;&gt;      ///////////////////////////
      // Delete transformation //
      ///////////////////////////
      await database.removeTransformation(transformationId);&lt;/pre&gt;
  &lt;pre id=&quot;mgWy&quot;&gt;      return resolve({ transformationId });
    } catch (err) {
      console.log(&amp;#x60;[ERROR removeTransformation.js] : ${err}&amp;#x60;);
      return reject(err);
    }
  });
};&lt;/pre&gt;
  &lt;pre id=&quot;Z7qi&quot;&gt;module.exports = removeTransformation;&lt;/pre&gt;
  &lt;h3 id=&quot;QZ25&quot;&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;4VfZ&quot;&gt;↪️swapTransformationRank.js&lt;/h3&gt;
  &lt;pre id=&quot;Aypu&quot;&gt;const database = require(&amp;#x27;../db/database&amp;#x27;);&lt;/pre&gt;
  &lt;pre id=&quot;8V2W&quot;&gt;const swapTransformationRank = (sender, redirectionId, rank1, rank2) =&amp;gt; {
  return new Promise( async(resolve, reject) =&amp;gt; {
    try {&lt;/pre&gt;
  &lt;pre id=&quot;qGf7&quot;&gt;      /////////////////////////////////////////////
      // Sender must be the owner of redirection //
      /////////////////////////////////////////////
      const redirections = await database.getRedirections(sender);
      let redirectionOfInterest = redirections.filter((redirection) =&amp;gt; redirection.id == redirectionId)
      if (redirectionOfInterest.length === 0) throw Error(&amp;#x27;Redirection doesnot exist&amp;#x27;);&lt;/pre&gt;
  &lt;pre id=&quot;LqEJ&quot;&gt;      /////////////////////////////////////////////
      // Both transformation ranks should exists //
      /////////////////////////////////////////////
      const transformations = await database.getTransformationsOfRedirection(redirectionId);
      const requiredTransformations = transformations.filter((tranformation) =&amp;gt; {
        return tranformation.rank == rank1 || tranformation.rank == rank2;
      });
      if (requiredTransformations.length !== 2) throw new Error(&amp;#x27;The transformation does not exist&amp;#x27;);&lt;/pre&gt;
  &lt;pre id=&quot;Lv9q&quot;&gt;      ////////////////
      // Swap ranks //
      ////////////////
      await database.changeTransformationRank(requiredTransformations[0].id, requiredTransformations[1].rank);
      await database.changeTransformationRank(requiredTransformations[1].id, requiredTransformations[0].rank);&lt;/pre&gt;
  &lt;pre id=&quot;Sz82&quot;&gt;      return resolve({ redirectionId })&lt;/pre&gt;
  &lt;pre id=&quot;qXLb&quot;&gt;    } catch (err) {
      console.log(&amp;#x60;ERROR: [swapTransformationRank()] ${err}&amp;#x60;)
      reject(err);
    }
  })
}&lt;/pre&gt;
  &lt;pre id=&quot;HsjY&quot;&gt;module.exports = swapTransformationRank;&lt;/pre&gt;
  &lt;p id=&quot;XwAG&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;mY1I&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;6Ee8&quot;&gt;✉дб&lt;/h2&gt;
  &lt;h3 id=&quot;T1Md&quot;&gt;         ↪️database.js&lt;/h3&gt;
  &lt;pre id=&quot;uG3a&quot;&gt;const knex = require(&amp;#x27;../services/database&amp;#x27;);&lt;/pre&gt;
  &lt;pre id=&quot;9IuO&quot;&gt;class Database {
  constructor() {}&lt;/pre&gt;
  &lt;pre id=&quot;QHMh&quot;&gt;  ///////////
  // USERS //
  ///////////
  getUser(chatId) {
    return knex(&amp;#x27;users&amp;#x27;).select(&amp;#x27;*&amp;#x27;).where({ chat_id: chatId }).first();
  }&lt;/pre&gt;
  &lt;pre id=&quot;EnyH&quot;&gt;  getAllUsers() {
    return knex(&amp;#x27;users&amp;#x27;).select(&amp;#x27;*&amp;#x27;);
  }&lt;/pre&gt;
  &lt;pre id=&quot;712v&quot;&gt;  saveUser(chatId, username, refCode) {
    return knex(&amp;#x27;users&amp;#x27;).insert({ chat_id: chatId, username: username, ref_code: refCode });
  }&lt;/pre&gt;
  &lt;pre id=&quot;a7Gm&quot;&gt;  changeUserQuota(chatId, change = 1) {
    const sql = &amp;#x60;UPDATE users SET quota = quota + ? WHERE chat_id = ?&amp;#x60;;
    return knex.raw(sql, [change, chatId]);
  }&lt;/pre&gt;
  &lt;pre id=&quot;VxCY&quot;&gt;  getUserQuota(chatId) {
    return knex(&amp;#x27;user&amp;#x27;).select(&amp;#x27;quote&amp;#x27;).where({ chat_id: chatId });
  }&lt;/pre&gt;
  &lt;pre id=&quot;b0I7&quot;&gt;  //////////////////
  // REDIRECTIONS //
  //////////////////
  getRedirections(userId) {
    return knex(&amp;#x27;redirections&amp;#x27;).select(&amp;#x27;*&amp;#x27;).where({ owner: userId });
  }&lt;/pre&gt;
  &lt;pre id=&quot;kYrQ&quot;&gt;  saveRedirection(owner, source, destination, srcTitle, destTitle) {
    return knex(&amp;#x27;redirections&amp;#x27;).insert({
      owner,
      source,
      destination,
      source_title: srcTitle,
      destination_title: destTitle,
    });
  }&lt;/pre&gt;
  &lt;pre id=&quot;9E4t&quot;&gt;  removeRedirection(redirectionId) {
    return knex.raw(&amp;#x27;DELETE FROM redirections WHERE id = ?&amp;#x27;, [redirectionId]);
  }&lt;/pre&gt;
  &lt;pre id=&quot;0qT9&quot;&gt;  activateRedirection(redirectionId) {
    return knex.raw(&amp;#x27;UPDATE redirections SET active = ? WHERE id = ?&amp;#x27;, [1, redirectionId]);
  }&lt;/pre&gt;
  &lt;pre id=&quot;JNen&quot;&gt;  deactivateRedirection(redirectionId) {
    return knex.raw(&amp;#x27;UPDATE redirections SET active = ? WHERE id = ?&amp;#x27;, [0, redirectionId]);
  }&lt;/pre&gt;
  &lt;pre id=&quot;G91Y&quot;&gt;  /////////////
  // FILTERS //
  /////////////&lt;/pre&gt;
  &lt;pre id=&quot;DO41&quot;&gt;  /**
   * @param {Number} redirectionId
   * @param {String} filterName One of specific filter names
   * @param {} data filter state or filter keywords
   */
  saveFilter(redirectionId, filterName, data) {
    return knex.raw(
      &amp;#x60;INSERT INTO filters (id, ${filterName}) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET ${filterName} = ?&amp;#x60;,
      [redirectionId, data, data]
    );
  }&lt;/pre&gt;
  &lt;pre id=&quot;2fyO&quot;&gt;  getFilter(redirectionId) {
    return knex(&amp;#x27;filters&amp;#x27;).select(&amp;#x27;*&amp;#x27;).where({ id: redirectionId }).first();
  }&lt;/pre&gt;
  &lt;pre id=&quot;PXLI&quot;&gt;  /////////////////////
  // Transformations //
  /////////////////////
  saveTransformation(redirectionId, oldPhrase, newPhrase, rank) {
    return knex.raw(
      &amp;#x60;INSERT INTO transformations (redirection_id, old_phrase, new_phrase, rank) VALUES (?, ?, ?, ?) RETURNING id&amp;#x60;,
      [redirectionId, oldPhrase, newPhrase, rank]
    );
  }&lt;/pre&gt;
  &lt;pre id=&quot;ZxUs&quot;&gt;  getTransformation(transformationId) {
    return knex(&amp;#x27;transformations&amp;#x27;).select(&amp;#x27;*&amp;#x27;).where({ id: transformationId }).first();
  }&lt;/pre&gt;
  &lt;pre id=&quot;srm0&quot;&gt;  getTransformationsOfRedirection(redirectionId) {
    return knex(&amp;#x27;transformations&amp;#x27;).select(&amp;#x27;*&amp;#x27;).where({ redirection_id: redirectionId });
  }&lt;/pre&gt;
  &lt;pre id=&quot;GodL&quot;&gt;  changeTransformationRank(transformationId, newRank) {
    return new Promise((resolve, reject) =&amp;gt; {
      const sql = &amp;#x27;UPDATE transformations SET rank = ? Where id = ?&amp;#x27;;
      this.connection.query(sql, [newRank, transformationId], (error, results) =&amp;gt; {
        if (error) return reject(error);
        resolve(results);
      });
    });
  }&lt;/pre&gt;
  &lt;pre id=&quot;ElnT&quot;&gt;  removeTransformation(transformationId) {
    return knex.raw(&amp;#x27;DELETE FROM transformations WHERE id = ?&amp;#x27;, [transformationId]);
  }
}&lt;/pre&gt;
  &lt;pre id=&quot;Ryuk&quot;&gt;// Single Ton
module.exports = new Database();&lt;/pre&gt;
  &lt;pre id=&quot;VztW&quot;&gt;if (require.main === module) {
  const db = new Database();
  // db.changeUserQuota(&amp;#x27;xxx&amp;#x27;, 100).then(console.log).catch(console.error);
  // db.getUser(&amp;#x27;xxx&amp;#x27;).then(console.log);
}&lt;/pre&gt;
  &lt;p id=&quot;ikPy&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;6eM5&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;zEaT&quot;&gt;✉Услуги&lt;/h2&gt;
  &lt;h3 id=&quot;vvZZ&quot;&gt;              ↪️agent.js&lt;/h3&gt;
  &lt;pre id=&quot;GCMe&quot;&gt;* An agent that communicates with the python rest api */
const request = require(&amp;#x27;request&amp;#x27;);
const { AGENT_HOSTNAME, AGENT_PORT } = require(&amp;#x27;../config&amp;#x27;);&lt;/pre&gt;
  &lt;pre id=&quot;gaQp&quot;&gt;const AGENT_URL = &amp;#x60;http://${AGENT_HOSTNAME}:${AGENT_PORT}/&amp;#x60;;&lt;/pre&gt;
  &lt;pre id=&quot;EHTc&quot;&gt;const _sendRequest = (endpoint) =&amp;gt; {
  return new Promise((resolve, reject) =&amp;gt; {
    request(AGENT_URL + endpoint, { json: true }, (err, resp, body) =&amp;gt; {
      if (err) return reject(err);
      return resolve(body);
    });
  });
};&lt;/pre&gt;
  &lt;pre id=&quot;3fQd&quot;&gt;class ForwardAgent {
  static async joinPublicUserEntity(entityName) {
    const endpoint = &amp;#x60;joinPublicUserEntity?entity=${entityName}&amp;#x60;;
    try {
      const resp = await _sendRequest(endpoint);
      return resp;
    } catch (err) {
      return err;
    }
  }&lt;/pre&gt;
  &lt;pre id=&quot;vkY1&quot;&gt;  static async joinPublicEntity(entityName) {
    const endpoint = &amp;#x60;joinPublicEntity?entity=${entityName}&amp;#x60;;
    try {
      const resp = await _sendRequest(endpoint);
      return resp;
    } catch (err) {
      return err;
    }
  }&lt;/pre&gt;
  &lt;pre id=&quot;mzTJ&quot;&gt;  static async joinPrivateEntity(hash) {
    const endpoint = &amp;#x60;joinPrivateEntity?hash=${hash}&amp;#x60;;
    try {
      const resp = await _sendRequest(endpoint);
      return resp;
    } catch (err) {
      return err;
    }
  }&lt;/pre&gt;
  &lt;pre id=&quot;3UId&quot;&gt;  /**
   * Calls Agent to get the detail of the entity
   * @param {String} entity
   * @returns {Promise} Returns a promise of object
   */
  static getEntity(entity, option = { is_id: false }) {
    return new Promise(async (resolve, reject) =&amp;gt; {
      try {
        let endpoint = &amp;#x60;getentity?entity=${entity}&amp;#x60;;
        if (option.is_id) {
          endpoint += &amp;#x60;&amp;amp;is_id=1&amp;#x60;;
        }
        const resp = await _sendRequest(endpoint);&lt;/pre&gt;
  &lt;pre id=&quot;aCwK&quot;&gt;        if (resp._ === &amp;#x27;User&amp;#x27;) {
          return resolve({
            joined: false,
            entity: {
              type: &amp;#x27;user&amp;#x27;,
              chatId: resp.id,
              title: resp.username || resp.first_name || resp.last_name,
              accessHash: resp.access_hash,
              bot: resp.bot,
            },
          });
        } else if (resp._ === &amp;#x27;Channel&amp;#x27;) {
          return resolve({
            joined: !resp.left,
            entity: {
              type: &amp;#x27;channel&amp;#x27;,
              chatId: resp.id,
              title: resp.title || resp.username,
              accessHash: resp.access_hash,
              megagroup: resp.megagroup,
              adminRights: resp.admin_rights,
            },
          });
        } else if (resp._ === &amp;#x27;Chat&amp;#x27;) {
          if (resp.kicked) {
            throw new Error(&amp;#x60;Bot was kicked from ${resp.title}&amp;#x60;);
          }&lt;/pre&gt;
  &lt;pre id=&quot;Wgpe&quot;&gt;          return resolve({
            joined: !resp.left,
            entity: {
              type: &amp;#x27;group&amp;#x27;,
              chatId: resp.id,
              title: resp.title,
              accessHash: resp.id,
            },
          });
        } else if (
          resp.error ===
          &amp;#x27;Cannot get entity from a channel (or group) that you are not part of. Join the group and retry&amp;#x27;
        ) {
          return resolve({
            joined: false,
            entity: null,
          });
        } else {
          throw new Error(resp.error);
        }
      } catch (err) {
        console.error({ err });
        return reject(err);
      }
    });
  }
}&lt;/pre&gt;
  &lt;pre id=&quot;FjLb&quot;&gt;module.exports = ForwardAgent;&lt;/pre&gt;
  &lt;pre id=&quot;s7hD&quot;&gt;if (require.main === module) {
  ForwardAgent.getEntity(&amp;#x27;1256091383&amp;#x27;, { is_id: true })
    .then((x) =&amp;gt; console.log(x))
    .catch((x) =&amp;gt; console.log(x));
}&lt;/pre&gt;
  &lt;p id=&quot;EXPU&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;iM3c&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;xM3n&quot;&gt;↪️database.js&lt;/h3&gt;
  &lt;pre id=&quot;QNhu&quot;&gt;const { DB_HOST } = require(&amp;#x27;../config&amp;#x27;);&lt;/pre&gt;
  &lt;pre id=&quot;hIUC&quot;&gt;const knex = require(&amp;#x27;knex&amp;#x27;)({
  client: &amp;#x27;pg&amp;#x27;,
  connection: {
    host: DB_HOST,
    user: &amp;#x27;postgres&amp;#x27;,
    password: &amp;#x27;mysecretpassword&amp;#x27;,
    database: &amp;#x27;telegram&amp;#x27;,
  },
});&lt;/pre&gt;
  &lt;pre id=&quot;3p4R&quot;&gt;(async () =&amp;gt; {
  // Create Tables
  const hasUserTable = await knex.schema.hasTable(&amp;#x27;users&amp;#x27;);
  if (!hasUserTable) {
    await knex.schema.createTable(&amp;#x27;users&amp;#x27;, tableBuilder =&amp;gt; {
      tableBuilder.string(&amp;#x27;chat_id&amp;#x27;).primary();
      tableBuilder.string(&amp;#x27;username&amp;#x27;).nullable().unique();
      tableBuilder.string(&amp;#x27;ref_code&amp;#x27;).notNullable().unique();
      tableBuilder.string(&amp;#x27;ref_by&amp;#x27;);
      tableBuilder.boolean(&amp;#x27;premium&amp;#x27;).defaultTo(true);
      tableBuilder.integer(&amp;#x27;quota&amp;#x27;).defaultTo(0);&lt;/pre&gt;
  &lt;pre id=&quot;NxCn&quot;&gt;      // Foreign Key
      tableBuilder.foreign(&amp;#x27;ref_by&amp;#x27;).references(&amp;#x27;ref_code&amp;#x27;).inTable(&amp;#x27;users&amp;#x27;);
    });
    console.log(&amp;#x27;User table created&amp;#x27;);
  }&lt;/pre&gt;
  &lt;pre id=&quot;7Vok&quot;&gt;  const hasRedTable = await knex.schema.hasTable(&amp;#x27;redirections&amp;#x27;);
  if (!hasRedTable) {
    await knex.schema.createTable(&amp;#x27;redirections&amp;#x27;, tableBuilder =&amp;gt; {
      tableBuilder.increments(&amp;#x27;id&amp;#x27;).primary();
      tableBuilder.string(&amp;#x27;owner&amp;#x27;).notNullable();
      tableBuilder.string(&amp;#x27;source&amp;#x27;).notNullable();
      tableBuilder.string(&amp;#x27;destination&amp;#x27;).notNullable();
      tableBuilder.string(&amp;#x27;source_title&amp;#x27;).notNullable();
      tableBuilder.string(&amp;#x27;destination_title&amp;#x27;).notNullable();
      tableBuilder.boolean(&amp;#x27;active&amp;#x27;).defaultTo(false);&lt;/pre&gt;
  &lt;pre id=&quot;U84B&quot;&gt;      // Foreign Key
      tableBuilder.foreign(&amp;#x27;owner&amp;#x27;, &amp;#x27;redirections_fk0&amp;#x27;).references(&amp;#x27;chat_id&amp;#x27;).inTable(&amp;#x27;users&amp;#x27;).onDelete(&amp;#x27;cascade&amp;#x27;);
    });
    console.log(&amp;#x27;redirections table created&amp;#x27;);
  }&lt;/pre&gt;
  &lt;pre id=&quot;QoGH&quot;&gt;  const hasFiltersTable = await knex.schema.hasTable(&amp;#x27;filters&amp;#x27;);
  if (!hasFiltersTable) {
    await knex.schema.createTable(&amp;#x27;filters&amp;#x27;, tableBuilder =&amp;gt; {
      tableBuilder.increments(&amp;#x27;id&amp;#x27;).primary();
      tableBuilder.boolean(&amp;#x27;audio&amp;#x27;).defaultTo(false);
      tableBuilder.boolean(&amp;#x27;video&amp;#x27;).defaultTo(false);
      tableBuilder.boolean(&amp;#x27;photo&amp;#x27;).defaultTo(false);
      tableBuilder.boolean(&amp;#x27;sticker&amp;#x27;).defaultTo(false);
      tableBuilder.boolean(&amp;#x27;document&amp;#x27;).defaultTo(false);
      tableBuilder.boolean(&amp;#x27;hashtag&amp;#x27;).defaultTo(false);
      tableBuilder.boolean(&amp;#x27;link&amp;#x27;).defaultTo(false);
      tableBuilder.string(&amp;#x27;contain&amp;#x27;);
      tableBuilder.string(&amp;#x27;notcontain&amp;#x27;);&lt;/pre&gt;
  &lt;pre id=&quot;JfYK&quot;&gt;      // Foreign Key
      tableBuilder.foreign(&amp;#x27;id&amp;#x27;).references(&amp;#x27;id&amp;#x27;).inTable(&amp;#x27;redirections&amp;#x27;).onDelete(&amp;#x27;cascade&amp;#x27;);
    });
    console.log(&amp;#x27;filters table created&amp;#x27;);
  }&lt;/pre&gt;
  &lt;pre id=&quot;h0Hl&quot;&gt;  const hasTransformationsTable = await knex.schema.hasTable(&amp;#x27;transformations&amp;#x27;);
  if (!hasTransformationsTable) {
    await knex.schema.createTable(&amp;#x27;transformations&amp;#x27;, tableBuilder =&amp;gt; {
      tableBuilder.increments(&amp;#x27;id&amp;#x27;).primary();
      tableBuilder.integer(&amp;#x27;redirection_id&amp;#x27;).notNullable();
      tableBuilder.string(&amp;#x27;old_phrase&amp;#x27;).notNullable();
      tableBuilder.string(&amp;#x27;new_phrase&amp;#x27;).notNullable();
      tableBuilder.integer(&amp;#x27;rank&amp;#x27;).notNullable();&lt;/pre&gt;
  &lt;pre id=&quot;59qZ&quot;&gt;      // Foreign Key
      tableBuilder.foreign(&amp;#x27;redirection_id&amp;#x27;).references(&amp;#x27;id&amp;#x27;).inTable(&amp;#x27;redirections&amp;#x27;).onDelete(&amp;#x27;cascade&amp;#x27;);
    });
    console.log(&amp;#x27;transformations table created&amp;#x27;);
  }
})();&lt;/pre&gt;
  &lt;pre id=&quot;rwEM&quot;&gt;module.exports = knex;&lt;/pre&gt;
  &lt;p id=&quot;7Iye&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;0lU3&quot;&gt;↪️telegram.js&lt;/h3&gt;
  &lt;pre id=&quot;fULi&quot;&gt;const TelegramBot = require(&amp;#x27;node-telegram-bot-api&amp;#x27;);&lt;/pre&gt;
  &lt;pre id=&quot;kvbT&quot;&gt;const config = require(&amp;#x27;../config&amp;#x27;);&lt;/pre&gt;
  &lt;pre id=&quot;MkX4&quot;&gt;const bot = new TelegramBot(config.TG.TG_API_KEY, { polling: false });
module.exports = bot;&lt;/pre&gt;
  &lt;p id=&quot;YpYR&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;A9iU&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;Ndd4&quot;&gt;↪️.красивее .&lt;/h3&gt;
  &lt;pre id=&quot;mUfX&quot; data-lang=&quot;python&quot;&gt;{
  &amp;quot;singleQuote&amp;quot;: true,
  &amp;quot;tabWidth&amp;quot;: 2,
  &amp;quot;semi&amp;quot;: true,
  &amp;quot;trailingComma&amp;quot;: &amp;quot;es5&amp;quot;,
  &amp;quot;printWidth&amp;quot;: 120,
  &amp;quot;arrowParens&amp;quot;: &amp;quot;avoid&amp;quot;
}&lt;/pre&gt;
  &lt;h3 id=&quot;5wFm&quot;&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;z1wC&quot;&gt;↪️Файл Dockerfile&lt;/h3&gt;
  &lt;pre id=&quot;S7Gy&quot;&gt;FROM node:lts-jessie&lt;/pre&gt;
  &lt;pre id=&quot;oz8G&quot;&gt;RUN apt-get update&lt;/pre&gt;
  &lt;pre id=&quot;0sSN&quot;&gt;RUN apt-get install -y netcat&lt;/pre&gt;
  &lt;pre id=&quot;igZJ&quot;&gt;WORKDIR /usr/src/telegram-forwarder/bot&lt;/pre&gt;
  &lt;pre id=&quot;gPg0&quot;&gt;COPY package*.json ./&lt;/pre&gt;
  &lt;pre id=&quot;FRoW&quot;&gt;RUN npm install&lt;/pre&gt;
  &lt;pre id=&quot;Ui6x&quot;&gt;COPY . .&lt;/pre&gt;
  &lt;pre id=&quot;ZvkY&quot;&gt;CMD [&amp;quot;node&amp;quot;, &amp;quot;bot.js&amp;quot;]&lt;/pre&gt;
  &lt;p id=&quot;0XQX&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;Db3u&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;E59Q&quot;&gt;↪️bot.js&lt;/h3&gt;
  &lt;pre id=&quot;Xrj6&quot;&gt;// @ts-check
const pino = require(&amp;#x27;pino&amp;#x27;);
const { v4: uuidv4 } = require(&amp;#x27;uuid&amp;#x27;);
const EventEmitter = require(&amp;#x27;events&amp;#x27;).EventEmitter;&lt;/pre&gt;
  &lt;pre id=&quot;0O6b&quot;&gt;const db = require(&amp;#x27;./db/database&amp;#x27;);
const bot = require(&amp;#x27;./services/telegram&amp;#x27;);
const MessageParser = require(&amp;#x27;./controllers/message/parser&amp;#x27;);&lt;/pre&gt;
  &lt;pre id=&quot;SWH9&quot;&gt;// Controllers
const addFilter = require(&amp;#x27;./controllers/addFilter&amp;#x27;);
const getFilter = require(&amp;#x27;./controllers/getFilter&amp;#x27;);
const addRedirection = require(&amp;#x27;./controllers/addRedirection&amp;#x27;);
const activateRedirection = require(&amp;#x27;./controllers/activateRedirection&amp;#x27;);
const removeRedirection = require(&amp;#x27;./controllers/removeRedirection&amp;#x27;);
const deactivateRedirection = require(&amp;#x27;./controllers/deactivateRedirection&amp;#x27;);
const addTransformation = require(&amp;#x27;./controllers/addTransformation&amp;#x27;);
// const swapTransformationRank = require(&amp;#x27;../swapTransformationRank&amp;#x27;);
const getTransformations = require(&amp;#x27;./controllers/getTransformations&amp;#x27;);
const removeTransformation = require(&amp;#x27;./controllers/removeTransformation&amp;#x27;);&lt;/pre&gt;
  &lt;pre id=&quot;mCrV&quot;&gt;const logger = pino({ level: process.env.LOG_LEVEL || &amp;#x27;info&amp;#x27; });
class CommandHandler extends EventEmitter {}
const commandHandler = new CommandHandler();&lt;/pre&gt;
  &lt;pre id=&quot;tIt7&quot;&gt;bot.onText(new RegExp(&amp;#x27;^/start$&amp;#x27;), async msgEvent =&amp;gt; {
  let reply = &amp;#x27;Welcome to MultiFeed Bot! 🔥\n\n&amp;#x27;;
  reply += &amp;#x27;Send /help to get usage instructions&amp;#x27;;
  bot.sendMessage(msgEvent.chat.id, reply).catch(logger.error);&lt;/pre&gt;
  &lt;pre id=&quot;s1zy&quot;&gt;  // Store User to Database
  try {
    await db.saveUser(msgEvent.chat.id, msgEvent.from.username, uuidv4());
  } catch (err) {
    logger.error(err);
  }
});&lt;/pre&gt;
  &lt;pre id=&quot;j2Na&quot;&gt;bot.onText(new RegExp(&amp;#x27;^/help$&amp;#x27;), msgEvent =&amp;gt; {
  let reply = &amp;#x27;Typical workflow in the bot:\n\n&amp;#x27;;
  reply += &amp;#x27;1. You have two links:\n&amp;#x27;;
  reply += &amp;#x27;- &amp;#x60;SOURCE&amp;#x60; - link to the channel to forward messages FROM&amp;#x27;;
  reply += &amp;#x27;(no admin permissions required)\n&amp;#x27;;
  reply += &amp;#x27;- &amp;#x60;DESTINATION&amp;#x60; - link to the channel to forward messages TO&amp;#x27;;
  reply += &amp;#x27;(you can add new admins there)\n\n&amp;#x27;;
  reply += &amp;#x27;2. You use &amp;#x60;/add&amp;#x60; command to create a new redirection from &amp;#x27;;
  reply += &amp;#x27;&amp;#x60;SOURCE&amp;#x60; channel to your &amp;#x60;DESTINATION&amp;#x60; channel\n\n&amp;#x27;;
  reply += &amp;#x27;3. You give posting permissions in &amp;#x60;DESTINATION&amp;#x60; channel TO THE &amp;#x27;;
  reply += &amp;#x27;ACCOUNT that was specified after successful execution of step #2&amp;#x27;;
  reply += &amp;#x27;\n\n&amp;#x27;;
  reply += &amp;#x27;4. You activate the newly created redirection using &amp;#x60;/activate&amp;#x60; &amp;#x27;;
  reply += &amp;#x27;command\n\n&amp;#x27;;
  reply += &amp;#x27;Having all 4 steps completed, you can enjoy automatic messages &amp;#x27;;
  reply += &amp;#x27;forwarding from &amp;#x60;SOURCE&amp;#x60; to &amp;#x60;DESTINATION&amp;#x60; 🔥&amp;#x27;;
  return bot
    .sendMessage(msgEvent.chat.id, reply, {
      parse_mode: &amp;#x27;Markdown&amp;#x27;,
    })
    .catch(console.error);
});&lt;/pre&gt;
  &lt;pre id=&quot;whNV&quot;&gt;bot.on(&amp;#x27;message&amp;#x27;, msgEvent =&amp;gt; {
  if (msgEvent.chat.type == &amp;#x27;private&amp;#x27;) {
    // Parse Command
    // Check Commands with MessageParser
    const isValidCommand = MessageParser.isValidCommand(msgEvent.text);
    if (!isValidCommand) {
      const reply = &amp;#x27;❌ Command does not exist.\n\nType /help&amp;#x27;;
      return bot.sendMessage(msgEvent.chat.id, reply).catch(console.error);
    }&lt;/pre&gt;
  &lt;pre id=&quot;Zyvo&quot;&gt;    const command = MessageParser.getCommand(msgEvent.text);&lt;/pre&gt;
  &lt;pre id=&quot;IyCZ&quot;&gt;    // Commands that are handled elsewhere
    const reservedCommands = [&amp;#x27;/help&amp;#x27;, &amp;#x27;/start&amp;#x27;];
    if (reservedCommands.includes(command)) return;&lt;/pre&gt;
  &lt;pre id=&quot;eFwZ&quot;&gt;    const parser = MessageParser.hashMap()[command];
    const parsedMsg = parser(msgEvent.text, msgEvent);&lt;/pre&gt;
  &lt;pre id=&quot;GFrZ&quot;&gt;    if (parsedMsg.error) {
      const reply = &amp;#x60;❌ Error in command : ${parsedMsg.command}\n\n**${parsedMsg.error}**&amp;#x60;;
      return bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: &amp;#x27;Markdown&amp;#x27; }).catch(console.error);
    }&lt;/pre&gt;
  &lt;pre id=&quot;KSHn&quot;&gt;    commandHandler.emit(command, parsedMsg, msgEvent);
  }
});&lt;/pre&gt;
  &lt;pre id=&quot;vRPr&quot;&gt;commandHandler.on(&amp;#x27;/add&amp;#x27;, async (data, msgEvent) =&amp;gt; {
  console.log(&amp;#x27;Adding redirection&amp;#x27;);
  try {
    const { error } = await addRedirection(msgEvent.chat.id, data.source, data.destination);
    if (error) {
      return bot
        .sendMessage(msgEvent.chat.id, error, {
          parse_mode: &amp;#x27;HTML&amp;#x27;,
        })
        .catch(e =&amp;gt; console.error(e.message));
    }
    const reply = &amp;#x60;✔ New Redirection added&amp;#x60;;
    return bot.sendMessage(msgEvent.chat.id, reply, {
      parse_mode: &amp;#x27;HTML&amp;#x27;,
    });
  } catch (err) {
    console.error(err);
    const reply = err.message || err || &amp;#x27;Some error occured&amp;#x27;;
    bot
      .sendMessage(msgEvent.chat.id, &amp;#x27;❌ &amp;#x27; + reply, {
        parse_mode: &amp;#x27;HTML&amp;#x27;,
      })
      .catch(console.error);
  }
});&lt;/pre&gt;
  &lt;pre id=&quot;bjoe&quot;&gt;commandHandler.on(&amp;#x27;/activate&amp;#x27;, async (data, msgEvent) =&amp;gt; {
  console.log(&amp;#x27;Activating redirection&amp;#x27;);
  try {
    await activateRedirection(msgEvent.chat.id, data.redirectionId);
    const reply = &amp;#x60;Redirection activated &amp;lt;code&amp;gt;[${data.redirectionId}]&amp;lt;/code&amp;gt;&amp;#x60;;
    bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: &amp;#x27;HTML&amp;#x27; }).catch(console.error);
  } catch (err) {
    const reply = err.message || err || &amp;#x27;Some error occured&amp;#x27;;
    bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: &amp;#x27;HTML&amp;#x27; }).catch(console.error);
  }
});&lt;/pre&gt;
  &lt;pre id=&quot;Psro&quot;&gt;commandHandler.on(&amp;#x27;/list&amp;#x27;, async (data, msgEvent) =&amp;gt; {
  try {
    const redirections = await db.getRedirections(msgEvent.chat.id);
    if (redirections.length === 0) {
      return bot
        .sendMessage(msgEvent.chat.id, &amp;#x27;You have no redirections&amp;#x27;, { parse_mode: &amp;#x27;HTML&amp;#x27; })
        .catch(err =&amp;gt; console.log(err));
    }&lt;/pre&gt;
  &lt;pre id=&quot;e7bj&quot;&gt;    let reply = &amp;#x27;&amp;#x27;;
    redirections.forEach(redirection =&amp;gt; {
      let state = redirection.active == 1 ? &amp;#x27;🔵&amp;#x27; : &amp;#x27;🔴&amp;#x27;;
      reply += &amp;#x60;--- ${state} &amp;lt;code&amp;gt;[${redirection.id}]&amp;lt;/code&amp;gt; ${redirection.source_title} =&amp;gt; ${redirection.destination_title}\n&amp;#x60;;
    });
    bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: &amp;#x27;HTML&amp;#x27; }).catch(err =&amp;gt; console.log(err));
  } catch (err) {
    console.log(err);
    bot.sendMessage(msgEvent.chat.id, err, { parse_mode: &amp;#x27;HTML&amp;#x27; });
  }
});&lt;/pre&gt;
  &lt;pre id=&quot;c3hz&quot;&gt;commandHandler.on(&amp;#x27;/deactivate&amp;#x27;, async (data, msgEvent) =&amp;gt; {
  try {
    await deactivateRedirection(msgEvent.chat.id, data.redirectionId);
    const reply = &amp;#x60;Redirection deactivated &amp;lt;code&amp;gt;[${data.redirectionId}]&amp;lt;/code&amp;gt;&amp;#x60;;
    bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: &amp;#x27;HTML&amp;#x27; }).catch(console.error);
  } catch (err) {
    const reply = err.message || err || &amp;#x27;Some error occured&amp;#x27;;
    bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: &amp;#x27;HTML&amp;#x27; }).catch(console.error);
  }
});&lt;/pre&gt;
  &lt;pre id=&quot;owKP&quot;&gt;commandHandler.on(&amp;#x27;/remove&amp;#x27;, async (data, msgEvent) =&amp;gt; {
  try {
    await removeRedirection(msgEvent.chat.id, data.redirectionId);
    const reply = &amp;#x60;Redirection removed &amp;lt;code&amp;gt;[${data.redirectionId}]&amp;lt;/code&amp;gt;&amp;#x60;;
    bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: &amp;#x27;HTML&amp;#x27; }).catch(console.error);
  } catch (err) {
    const reply = err.message || err || &amp;#x27;Some error occured&amp;#x27;;
    bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: &amp;#x27;HTML&amp;#x27; }).catch(console.error);
  }
});&lt;/pre&gt;
  &lt;pre id=&quot;kKmZ&quot;&gt;commandHandler.on(&amp;#x27;/filter&amp;#x27;, async (data, msgEvent) =&amp;gt; {
  try {
    const { error, filterData } = await addFilter(msgEvent.chat.id, data);
    if (error) {
      bot.sendMessage(msgEvent.chat.id, error).catch(logger.error);
      return;
    }
    let reply = &amp;#x60;✅ Command Success.\n\n&amp;lt;code&amp;gt;&amp;#x60;;
    reply += &amp;#x60;- Redirection id : [${filterData.redirectionId}]\n&amp;#x60;;
    reply += &amp;#x60;- Filter Name : ${filterData.name}\n&amp;#x60;;
    reply += &amp;#x60;- Filter State : ${filterData.state}&amp;lt;/code&amp;gt;&amp;#x60;;
    bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: &amp;#x27;HTML&amp;#x27; }).catch(console.error);
  } catch (err) {
    const reply = err.message || err || &amp;#x27;Some error occured&amp;#x27;;
    bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: &amp;#x27;HTML&amp;#x27; }).catch(console.error);
  }
});&lt;/pre&gt;
  &lt;pre id=&quot;Tugv&quot;&gt;commandHandler.on(&amp;#x27;/filters&amp;#x27;, async (data, msgEvent) =&amp;gt; {
  try {
    const { filter, error } = await getFilter(msgEvent.chat.id, data.filterId);
    if (error) {
      bot.sendMessage(msgEvent.chat.id, error, { parse_mode: &amp;#x27;HTML&amp;#x27; }).catch(err =&amp;gt; logger.error(err));
      return;
    }&lt;/pre&gt;
  &lt;pre id=&quot;uxJF&quot;&gt;    let reply = &amp;#x60;✅ Filters for redirection &amp;lt;code&amp;gt;[${filter.id}]&amp;lt;/code&amp;gt;\n\n&amp;#x60;;
    reply += &amp;#x27;&amp;lt;code&amp;gt;&amp;#x27;;
    reply += &amp;#x60;- ${filter.audio ? &amp;#x27;🔵&amp;#x27; : &amp;#x27;🔴&amp;#x27;} audio\n&amp;#x60;;
    reply += &amp;#x60;- ${filter.video ? &amp;#x27;🔵&amp;#x27; : &amp;#x27;🔴&amp;#x27;} video\n&amp;#x60;;
    reply += &amp;#x60;- ${filter.photo ? &amp;#x27;🔵&amp;#x27; : &amp;#x27;🔴&amp;#x27;} photo\n&amp;#x60;;
    reply += &amp;#x60;- ${filter.sticker ? &amp;#x27;🔵&amp;#x27; : &amp;#x27;🔴&amp;#x27;} sticker\n&amp;#x60;;
    reply += &amp;#x60;- ${filter.document ? &amp;#x27;🔵&amp;#x27; : &amp;#x27;🔴&amp;#x27;} document\n&amp;#x60;;
    reply += &amp;#x60;- ${filter.hashtag ? &amp;#x27;🔵&amp;#x27; : &amp;#x27;🔴&amp;#x27;} hashtag\n&amp;#x60;;
    reply += &amp;#x60;- ${filter.link ? &amp;#x27;🔵&amp;#x27; : &amp;#x27;🔴&amp;#x27;} link\n&amp;#x60;;
    reply += &amp;#x60;- ${filter.contain ? &amp;#x27;🔵&amp;#x27; : &amp;#x27;🔴&amp;#x27;} contain = ${
      filter.contain ? filter.contain.replace(/&amp;lt;stop_word&amp;gt;/g, &amp;#x27;, &amp;#x27;) : null
    }\n&amp;#x60;;
    reply += &amp;#x60;- ${filter.notcontain ? &amp;#x27;🔵&amp;#x27; : &amp;#x27;🔴&amp;#x27;} notcontain = ${
      filter.notcontain ? filter.notcontain.replace(&amp;#x27;&amp;lt;stop_word&amp;gt;&amp;#x27;, &amp;#x27;, &amp;#x27;) : null
    }&amp;#x60;;
    reply += &amp;#x27;&amp;lt;/code&amp;gt;&amp;#x27;;
    bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: &amp;#x27;HTML&amp;#x27; }).catch(err =&amp;gt; logger.error(err));
  } catch (err) {
    logger.error(err);
    bot.sendMessage(msgEvent.chat.id, &amp;#x27;❌ Some error occured&amp;#x27;).catch(err =&amp;gt; logger.error(err));
  }
});&lt;/pre&gt;
  &lt;pre id=&quot;CEjc&quot;&gt;commandHandler.on(&amp;#x27;/transform&amp;#x27;, async (data, msgEvent) =&amp;gt; {
  try {
    const response = await addTransformation(msgEvent.chat.id, data.redirectionId, data.oldPhrase, data.newPhrase);
    const reply = &amp;#x60;New transformation added with id &amp;lt;code&amp;gt;${response.transformationId}&amp;lt;/code&amp;gt;&amp;#x60;;
    bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: &amp;#x27;HTML&amp;#x27; }).catch(err =&amp;gt; logger.error(err));
  } catch (err) {
    logger.error(err);
    bot.sendMessage(msgEvent.chat.id, &amp;#x60;❌ ${err}&amp;#x60;).catch(err =&amp;gt; logger.error(err));
  }
});&lt;/pre&gt;
  &lt;pre id=&quot;y0ku&quot;&gt;commandHandler.on(&amp;#x27;/transforms&amp;#x27;, async (data, msgEvent) =&amp;gt; {
  try {
    const transformations = await getTransformations(msgEvent.chat.id, data.redirectionId);
    if (transformations.length == 0) {
      const reply = &amp;#x27;No transformation found.&amp;#x27;;
      bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: &amp;#x27;HTML&amp;#x27; }).catch(err =&amp;gt; logger.error(err));
    } else {
      let reply = &amp;#x60;Transformations for redirection [&amp;lt;code&amp;gt;${data.redirectionId}&amp;lt;/code&amp;gt;]\n\n&amp;#x60;;
      reply += &amp;#x27;&amp;lt;b&amp;gt;ID | Rank | Old Phrase | New Phrase&amp;lt;/b&amp;gt;\n&amp;#x27;;
      transformations.forEach(transformation =&amp;gt; {
        reply += &amp;#x60;&amp;lt;code&amp;gt;${transformation.id}. [${transformation.rank}] ${transformation.old_phrase} ==&amp;gt; ${transformation.new_phrase}&amp;lt;/code&amp;gt;\n&amp;#x60;;
      });
      bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: &amp;#x27;HTML&amp;#x27; }).catch(err =&amp;gt; logger.error(err));
    }
  } catch (err) {
    logger.error(err);
    bot.sendMessage(msgEvent.chat.id, &amp;#x60;❌ ${err}&amp;#x60;).catch(err =&amp;gt; logger.error(err));
  }
});&lt;/pre&gt;
  &lt;pre id=&quot;UwEB&quot;&gt;commandHandler.on(&amp;#x27;/transformremove&amp;#x27;, async (data, msgEvent) =&amp;gt; {
  try {
    await removeTransformation(msgEvent.chat.id, data.transformationId);
    const reply = &amp;#x60;✔ Transformation removed &amp;lt;code&amp;gt;${data.transformationId}&amp;lt;/code&amp;gt;&amp;#x60;;
    bot.sendMessage(msgEvent.chat.id, reply, { parse_mode: &amp;#x27;HTML&amp;#x27; }).catch(err =&amp;gt; logger.error(err));
  } catch (err) {
    logger.error(err);
    bot.sendMessage(msgEvent.chat.id, &amp;#x60;❌ ${err}&amp;#x60;).catch(err =&amp;gt; logger.error(err));
  }
});&lt;/pre&gt;
  &lt;pre id=&quot;tNdV&quot;&gt;//   } else if (command === &amp;#x27;/transformrank&amp;#x27;) {
//     try {
//       await swapTransformationRank(
//         sender,
//         parsedMsg.redirectionId,
//         parsedMsg.rank1,
//         parsedMsg.rank2
//       );
//       let reply = &amp;#x60;Transformation rank swapped for redirection id \&amp;#x60;${parsedMsg.redirectionId}\&amp;#x60;\n\n&amp;#x60;;
//       reply += &amp;#x60;\&amp;#x60;${parsedMsg.rank1} &amp;lt;==&amp;gt; ${parsedMsg.rank2}\&amp;#x60;&amp;#x60;;
//       bot
//         .send_message(sender, reply, &amp;#x27;markdown&amp;#x27;)
//         .catch(err =&amp;gt; console.log(err));
//     } catch (err) {
//       const reply = err.message || err || &amp;#x27;Some error occured&amp;#x27;;
//       bot.send_message(sender, reply).catch(err =&amp;gt; console.log(err));
//     }&lt;/pre&gt;
  &lt;pre id=&quot;LuCv&quot;&gt;if (require.main === module) {
  bot.on(&amp;#x27;message&amp;#x27;, msg =&amp;gt; logger.info(msg));
  bot.on(&amp;#x27;polling_error&amp;#x27;, err =&amp;gt; logger.error(err));
  bot.startPolling();
}&lt;/pre&gt;
  &lt;p id=&quot;BYHu&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;r0mQ&quot;&gt;↪️package-lock.json&lt;/h3&gt;
  &lt;pre id=&quot;PoKU&quot;&gt;{
  &amp;quot;name&amp;quot;: &amp;quot;telegram-forwarder-bot-agent&amp;quot;,
  &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
  &amp;quot;lockfileVersion&amp;quot;: 1,
  &amp;quot;requires&amp;quot;: true,
  &amp;quot;dependencies&amp;quot;: {
    &amp;quot;@sindresorhus/is&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.14.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;@szmarczak/http-timer&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.1.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;defer-to-connect&amp;quot;: &amp;quot;^1.0.1&amp;quot;
      }
    },
    &amp;quot;@types/color-name&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.1.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;abbrev&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.1.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;ajv&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;6.12.4&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;fast-deep-equal&amp;quot;: &amp;quot;^3.1.1&amp;quot;,
        &amp;quot;fast-json-stable-stringify&amp;quot;: &amp;quot;^2.0.0&amp;quot;,
        &amp;quot;json-schema-traverse&amp;quot;: &amp;quot;^0.4.1&amp;quot;,
        &amp;quot;uri-js&amp;quot;: &amp;quot;^4.2.2&amp;quot;
      }
    },
    &amp;quot;ansi-align&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;string-width&amp;quot;: &amp;quot;^3.0.0&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;string-width&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;3.1.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==&amp;quot;,
          &amp;quot;dev&amp;quot;: true,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;emoji-regex&amp;quot;: &amp;quot;^7.0.1&amp;quot;,
            &amp;quot;is-fullwidth-code-point&amp;quot;: &amp;quot;^2.0.0&amp;quot;,
            &amp;quot;strip-ansi&amp;quot;: &amp;quot;^5.1.0&amp;quot;
          }
        }
      }
    },
    &amp;quot;ansi-regex&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;4.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;ansi-styles&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;4.2.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;@types/color-name&amp;quot;: &amp;quot;^1.1.1&amp;quot;,
        &amp;quot;color-convert&amp;quot;: &amp;quot;^2.0.1&amp;quot;
      }
    },
    &amp;quot;anymatch&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.1.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;normalize-path&amp;quot;: &amp;quot;^3.0.0&amp;quot;,
        &amp;quot;picomatch&amp;quot;: &amp;quot;^2.0.4&amp;quot;
      }
    },
    &amp;quot;arr-diff&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;4.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=&amp;quot;
    },
    &amp;quot;arr-flatten&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==&amp;quot;
    },
    &amp;quot;arr-union&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=&amp;quot;
    },
    &amp;quot;array-each&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-p5SvDAWrF1KEbudTofIRoFugxE8=&amp;quot;
    },
    &amp;quot;array-slice&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==&amp;quot;
    },
    &amp;quot;array-unique&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.3.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=&amp;quot;
    },
    &amp;quot;array.prototype.findindex&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/array.prototype.findindex/-/array.prototype.findindex-2.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-25kJHCjXltdtljjwcyKvCTywmbUAeTJVB2ADVe0oP4jcefsd+K9pJJ3IdHPahLICoszcCLoNF+evWpEduzBlng==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;define-properties&amp;quot;: &amp;quot;^1.1.3&amp;quot;,
        &amp;quot;es-abstract&amp;quot;: &amp;quot;^1.17.4&amp;quot;
      }
    },
    &amp;quot;asn1&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.2.4&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;safer-buffer&amp;quot;: &amp;quot;~2.1.0&amp;quot;
      }
    },
    &amp;quot;assert-plus&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=&amp;quot;
    },
    &amp;quot;assign-symbols&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=&amp;quot;
    },
    &amp;quot;asynckit&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.4.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-x57Zf380y48robyXkLzDZkdLS3k=&amp;quot;
    },
    &amp;quot;atob&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.1.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/atob/-/atob-2.1.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==&amp;quot;
    },
    &amp;quot;atomic-sleep&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==&amp;quot;
    },
    &amp;quot;aws-sign2&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.7.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=&amp;quot;
    },
    &amp;quot;aws4&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.10.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==&amp;quot;
    },
    &amp;quot;balanced-match&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-ibTRmasr7kneFk6gK4nORi1xt2c=&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;base&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.11.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/base/-/base-0.11.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;cache-base&amp;quot;: &amp;quot;^1.0.1&amp;quot;,
        &amp;quot;class-utils&amp;quot;: &amp;quot;^0.3.5&amp;quot;,
        &amp;quot;component-emitter&amp;quot;: &amp;quot;^1.2.1&amp;quot;,
        &amp;quot;define-property&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;isobject&amp;quot;: &amp;quot;^3.0.1&amp;quot;,
        &amp;quot;mixin-deep&amp;quot;: &amp;quot;^1.2.0&amp;quot;,
        &amp;quot;pascalcase&amp;quot;: &amp;quot;^0.1.1&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;define-property&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha1-dp66rz9KY6rTr56NMEybvnm/sOY=&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-descriptor&amp;quot;: &amp;quot;^1.0.0&amp;quot;
          }
        },
        &amp;quot;is-accessor-descriptor&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;kind-of&amp;quot;: &amp;quot;^6.0.0&amp;quot;
          }
        },
        &amp;quot;is-data-descriptor&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;kind-of&amp;quot;: &amp;quot;^6.0.0&amp;quot;
          }
        },
        &amp;quot;is-descriptor&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;1.0.2&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-accessor-descriptor&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
            &amp;quot;is-data-descriptor&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
            &amp;quot;kind-of&amp;quot;: &amp;quot;^6.0.2&amp;quot;
          }
        }
      }
    },
    &amp;quot;bcrypt-pbkdf&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;tweetnacl&amp;quot;: &amp;quot;^0.14.3&amp;quot;
      }
    },
    &amp;quot;binary-extensions&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;bl&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.2.3&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/bl/-/bl-1.2.3.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;readable-stream&amp;quot;: &amp;quot;^2.3.5&amp;quot;,
        &amp;quot;safe-buffer&amp;quot;: &amp;quot;^5.1.1&amp;quot;
      }
    },
    &amp;quot;bluebird&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.7.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==&amp;quot;
    },
    &amp;quot;boxen&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;4.2.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;ansi-align&amp;quot;: &amp;quot;^3.0.0&amp;quot;,
        &amp;quot;camelcase&amp;quot;: &amp;quot;^5.3.1&amp;quot;,
        &amp;quot;chalk&amp;quot;: &amp;quot;^3.0.0&amp;quot;,
        &amp;quot;cli-boxes&amp;quot;: &amp;quot;^2.2.0&amp;quot;,
        &amp;quot;string-width&amp;quot;: &amp;quot;^4.1.0&amp;quot;,
        &amp;quot;term-size&amp;quot;: &amp;quot;^2.1.0&amp;quot;,
        &amp;quot;type-fest&amp;quot;: &amp;quot;^0.8.1&amp;quot;,
        &amp;quot;widest-line&amp;quot;: &amp;quot;^3.1.0&amp;quot;
      }
    },
    &amp;quot;brace-expansion&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.1.11&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;balanced-match&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;concat-map&amp;quot;: &amp;quot;0.0.1&amp;quot;
      }
    },
    &amp;quot;braces&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.3.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/braces/-/braces-2.3.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;arr-flatten&amp;quot;: &amp;quot;^1.1.0&amp;quot;,
        &amp;quot;array-unique&amp;quot;: &amp;quot;^0.3.2&amp;quot;,
        &amp;quot;extend-shallow&amp;quot;: &amp;quot;^2.0.1&amp;quot;,
        &amp;quot;fill-range&amp;quot;: &amp;quot;^4.0.0&amp;quot;,
        &amp;quot;isobject&amp;quot;: &amp;quot;^3.0.1&amp;quot;,
        &amp;quot;repeat-element&amp;quot;: &amp;quot;^1.1.2&amp;quot;,
        &amp;quot;snapdragon&amp;quot;: &amp;quot;^0.8.1&amp;quot;,
        &amp;quot;snapdragon-node&amp;quot;: &amp;quot;^2.0.1&amp;quot;,
        &amp;quot;split-string&amp;quot;: &amp;quot;^3.0.2&amp;quot;,
        &amp;quot;to-regex&amp;quot;: &amp;quot;^3.0.1&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;extend-shallow&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;2.0.1&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-extendable&amp;quot;: &amp;quot;^0.1.0&amp;quot;
          }
        }
      }
    },
    &amp;quot;buffer-writer&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==&amp;quot;
    },
    &amp;quot;cache-base&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;collection-visit&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;component-emitter&amp;quot;: &amp;quot;^1.2.1&amp;quot;,
        &amp;quot;get-value&amp;quot;: &amp;quot;^2.0.6&amp;quot;,
        &amp;quot;has-value&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;isobject&amp;quot;: &amp;quot;^3.0.1&amp;quot;,
        &amp;quot;set-value&amp;quot;: &amp;quot;^2.0.0&amp;quot;,
        &amp;quot;to-object-path&amp;quot;: &amp;quot;^0.3.0&amp;quot;,
        &amp;quot;union-value&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;unset-value&amp;quot;: &amp;quot;^1.0.0&amp;quot;
      }
    },
    &amp;quot;cacheable-request&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;6.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;clone-response&amp;quot;: &amp;quot;^1.0.2&amp;quot;,
        &amp;quot;get-stream&amp;quot;: &amp;quot;^5.1.0&amp;quot;,
        &amp;quot;http-cache-semantics&amp;quot;: &amp;quot;^4.0.0&amp;quot;,
        &amp;quot;keyv&amp;quot;: &amp;quot;^3.0.0&amp;quot;,
        &amp;quot;lowercase-keys&amp;quot;: &amp;quot;^2.0.0&amp;quot;,
        &amp;quot;normalize-url&amp;quot;: &amp;quot;^4.1.0&amp;quot;,
        &amp;quot;responselike&amp;quot;: &amp;quot;^1.0.2&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;get-stream&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;5.2.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==&amp;quot;,
          &amp;quot;dev&amp;quot;: true,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;pump&amp;quot;: &amp;quot;^3.0.0&amp;quot;
          }
        },
        &amp;quot;lowercase-keys&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;2.0.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==&amp;quot;,
          &amp;quot;dev&amp;quot;: true
        },
        &amp;quot;pump&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;3.0.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/pump/-/pump-3.0.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==&amp;quot;,
          &amp;quot;dev&amp;quot;: true,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;end-of-stream&amp;quot;: &amp;quot;^1.1.0&amp;quot;,
            &amp;quot;once&amp;quot;: &amp;quot;^1.3.1&amp;quot;
          }
        }
      }
    },
    &amp;quot;camelcase&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;5.3.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;caseless&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.12.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=&amp;quot;
    },
    &amp;quot;chalk&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;ansi-styles&amp;quot;: &amp;quot;^4.1.0&amp;quot;,
        &amp;quot;supports-color&amp;quot;: &amp;quot;^7.1.0&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;has-flag&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;4.0.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==&amp;quot;,
          &amp;quot;dev&amp;quot;: true
        },
        &amp;quot;supports-color&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;7.1.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==&amp;quot;,
          &amp;quot;dev&amp;quot;: true,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;has-flag&amp;quot;: &amp;quot;^4.0.0&amp;quot;
          }
        }
      }
    },
    &amp;quot;chokidar&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.4.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;anymatch&amp;quot;: &amp;quot;~3.1.1&amp;quot;,
        &amp;quot;braces&amp;quot;: &amp;quot;~3.0.2&amp;quot;,
        &amp;quot;fsevents&amp;quot;: &amp;quot;~2.1.2&amp;quot;,
        &amp;quot;glob-parent&amp;quot;: &amp;quot;~5.1.0&amp;quot;,
        &amp;quot;is-binary-path&amp;quot;: &amp;quot;~2.1.0&amp;quot;,
        &amp;quot;is-glob&amp;quot;: &amp;quot;~4.0.1&amp;quot;,
        &amp;quot;normalize-path&amp;quot;: &amp;quot;~3.0.0&amp;quot;,
        &amp;quot;readdirp&amp;quot;: &amp;quot;~3.4.0&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;braces&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;3.0.2&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/braces/-/braces-3.0.2.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==&amp;quot;,
          &amp;quot;dev&amp;quot;: true,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;fill-range&amp;quot;: &amp;quot;^7.0.1&amp;quot;
          }
        },
        &amp;quot;fill-range&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;7.0.1&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==&amp;quot;,
          &amp;quot;dev&amp;quot;: true,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;to-regex-range&amp;quot;: &amp;quot;^5.0.1&amp;quot;
          }
        },
        &amp;quot;is-number&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;7.0.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==&amp;quot;,
          &amp;quot;dev&amp;quot;: true
        },
        &amp;quot;to-regex-range&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;5.0.1&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==&amp;quot;,
          &amp;quot;dev&amp;quot;: true,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-number&amp;quot;: &amp;quot;^7.0.0&amp;quot;
          }
        }
      }
    },
    &amp;quot;ci-info&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;class-utils&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.3.6&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;arr-union&amp;quot;: &amp;quot;^3.1.0&amp;quot;,
        &amp;quot;define-property&amp;quot;: &amp;quot;^0.2.5&amp;quot;,
        &amp;quot;isobject&amp;quot;: &amp;quot;^3.0.0&amp;quot;,
        &amp;quot;static-extend&amp;quot;: &amp;quot;^0.1.1&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;define-property&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;0.2.5&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-descriptor&amp;quot;: &amp;quot;^0.1.0&amp;quot;
          }
        }
      }
    },
    &amp;quot;cli-boxes&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.2.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;clone-response&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;mimic-response&amp;quot;: &amp;quot;^1.0.0&amp;quot;
      }
    },
    &amp;quot;collection-visit&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;map-visit&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;object-visit&amp;quot;: &amp;quot;^1.0.0&amp;quot;
      }
    },
    &amp;quot;color-convert&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;color-name&amp;quot;: &amp;quot;~1.1.4&amp;quot;
      }
    },
    &amp;quot;color-name&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.1.4&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;colorette&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/colorette/-/colorette-1.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-6S062WDQUXi6hOfkO/sBPVwE5ASXY4G2+b4atvhJfSsuUUhIaUKlkjLe9692Ipyt5/a+IPF5aVTu3V5gvXq5cg==&amp;quot;
    },
    &amp;quot;combined-stream&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.8&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;delayed-stream&amp;quot;: &amp;quot;~1.0.0&amp;quot;
      }
    },
    &amp;quot;commander&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;4.1.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/commander/-/commander-4.1.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==&amp;quot;
    },
    &amp;quot;component-emitter&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.3.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==&amp;quot;
    },
    &amp;quot;concat-map&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;configstore&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;5.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;dot-prop&amp;quot;: &amp;quot;^5.2.0&amp;quot;,
        &amp;quot;graceful-fs&amp;quot;: &amp;quot;^4.1.2&amp;quot;,
        &amp;quot;make-dir&amp;quot;: &amp;quot;^3.0.0&amp;quot;,
        &amp;quot;unique-string&amp;quot;: &amp;quot;^2.0.0&amp;quot;,
        &amp;quot;write-file-atomic&amp;quot;: &amp;quot;^3.0.0&amp;quot;,
        &amp;quot;xdg-basedir&amp;quot;: &amp;quot;^4.0.0&amp;quot;
      }
    },
    &amp;quot;copy-descriptor&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.1.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=&amp;quot;
    },
    &amp;quot;core-util-is&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=&amp;quot;
    },
    &amp;quot;crypto-random-string&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;dashdash&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.14.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;assert-plus&amp;quot;: &amp;quot;^1.0.0&amp;quot;
      }
    },
    &amp;quot;debug&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;4.1.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/debug/-/debug-4.1.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;ms&amp;quot;: &amp;quot;^2.1.1&amp;quot;
      }
    },
    &amp;quot;decode-uri-component&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.2.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=&amp;quot;
    },
    &amp;quot;decompress-response&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.3.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;mimic-response&amp;quot;: &amp;quot;^1.0.0&amp;quot;
      }
    },
    &amp;quot;deep-extend&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.6.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;defer-to-connect&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.1.3&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;define-properties&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.1.3&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;object-keys&amp;quot;: &amp;quot;^1.0.12&amp;quot;
      }
    },
    &amp;quot;define-property&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;is-descriptor&amp;quot;: &amp;quot;^1.0.2&amp;quot;,
        &amp;quot;isobject&amp;quot;: &amp;quot;^3.0.1&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;is-accessor-descriptor&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;kind-of&amp;quot;: &amp;quot;^6.0.0&amp;quot;
          }
        },
        &amp;quot;is-data-descriptor&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;kind-of&amp;quot;: &amp;quot;^6.0.0&amp;quot;
          }
        },
        &amp;quot;is-descriptor&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;1.0.2&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-accessor-descriptor&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
            &amp;quot;is-data-descriptor&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
            &amp;quot;kind-of&amp;quot;: &amp;quot;^6.0.2&amp;quot;
          }
        }
      }
    },
    &amp;quot;delayed-stream&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-3zrhmayt+31ECqrgsp4icrJOxhk=&amp;quot;
    },
    &amp;quot;depd&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.1.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/depd/-/depd-1.1.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=&amp;quot;
    },
    &amp;quot;detect-file&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=&amp;quot;
    },
    &amp;quot;dot-prop&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;5.2.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;is-obj&amp;quot;: &amp;quot;^2.0.0&amp;quot;
      }
    },
    &amp;quot;duplexer3&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.1.4&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;ecc-jsbn&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.1.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;jsbn&amp;quot;: &amp;quot;~0.1.0&amp;quot;,
        &amp;quot;safer-buffer&amp;quot;: &amp;quot;^2.1.0&amp;quot;
      }
    },
    &amp;quot;emoji-regex&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;7.0.3&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;end-of-stream&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.4.4&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;once&amp;quot;: &amp;quot;^1.4.0&amp;quot;
      }
    },
    &amp;quot;es-abstract&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.17.6&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;es-to-primitive&amp;quot;: &amp;quot;^1.2.1&amp;quot;,
        &amp;quot;function-bind&amp;quot;: &amp;quot;^1.1.1&amp;quot;,
        &amp;quot;has&amp;quot;: &amp;quot;^1.0.3&amp;quot;,
        &amp;quot;has-symbols&amp;quot;: &amp;quot;^1.0.1&amp;quot;,
        &amp;quot;is-callable&amp;quot;: &amp;quot;^1.2.0&amp;quot;,
        &amp;quot;is-regex&amp;quot;: &amp;quot;^1.1.0&amp;quot;,
        &amp;quot;object-inspect&amp;quot;: &amp;quot;^1.7.0&amp;quot;,
        &amp;quot;object-keys&amp;quot;: &amp;quot;^1.1.1&amp;quot;,
        &amp;quot;object.assign&amp;quot;: &amp;quot;^4.1.0&amp;quot;,
        &amp;quot;string.prototype.trimend&amp;quot;: &amp;quot;^1.0.1&amp;quot;,
        &amp;quot;string.prototype.trimstart&amp;quot;: &amp;quot;^1.0.1&amp;quot;
      }
    },
    &amp;quot;es-to-primitive&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.2.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;is-callable&amp;quot;: &amp;quot;^1.1.4&amp;quot;,
        &amp;quot;is-date-object&amp;quot;: &amp;quot;^1.0.1&amp;quot;,
        &amp;quot;is-symbol&amp;quot;: &amp;quot;^1.0.2&amp;quot;
      }
    },
    &amp;quot;escape-goat&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.1.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;esm&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.2.25&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/esm/-/esm-3.2.25.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==&amp;quot;
    },
    &amp;quot;eventemitter3&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.1.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==&amp;quot;
    },
    &amp;quot;expand-brackets&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.1.4&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-t3c14xXOMPa27/D4OwQVGiJEliI=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;debug&amp;quot;: &amp;quot;^2.3.3&amp;quot;,
        &amp;quot;define-property&amp;quot;: &amp;quot;^0.2.5&amp;quot;,
        &amp;quot;extend-shallow&amp;quot;: &amp;quot;^2.0.1&amp;quot;,
        &amp;quot;posix-character-classes&amp;quot;: &amp;quot;^0.1.0&amp;quot;,
        &amp;quot;regex-not&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;snapdragon&amp;quot;: &amp;quot;^0.8.1&amp;quot;,
        &amp;quot;to-regex&amp;quot;: &amp;quot;^3.0.1&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;debug&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;2.6.9&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/debug/-/debug-2.6.9.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;ms&amp;quot;: &amp;quot;2.0.0&amp;quot;
          }
        },
        &amp;quot;define-property&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;0.2.5&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-descriptor&amp;quot;: &amp;quot;^0.1.0&amp;quot;
          }
        },
        &amp;quot;extend-shallow&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;2.0.1&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-extendable&amp;quot;: &amp;quot;^0.1.0&amp;quot;
          }
        },
        &amp;quot;ms&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;2.0.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/ms/-/ms-2.0.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=&amp;quot;
        }
      }
    },
    &amp;quot;expand-tilde&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;homedir-polyfill&amp;quot;: &amp;quot;^1.0.1&amp;quot;
      }
    },
    &amp;quot;extend&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.0.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/extend/-/extend-3.0.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==&amp;quot;
    },
    &amp;quot;extend-shallow&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.0.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;assign-symbols&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;is-extendable&amp;quot;: &amp;quot;^1.0.1&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;is-extendable&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;1.0.1&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-plain-object&amp;quot;: &amp;quot;^2.0.4&amp;quot;
          }
        }
      }
    },
    &amp;quot;extglob&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.4&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;array-unique&amp;quot;: &amp;quot;^0.3.2&amp;quot;,
        &amp;quot;define-property&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;expand-brackets&amp;quot;: &amp;quot;^2.1.4&amp;quot;,
        &amp;quot;extend-shallow&amp;quot;: &amp;quot;^2.0.1&amp;quot;,
        &amp;quot;fragment-cache&amp;quot;: &amp;quot;^0.2.1&amp;quot;,
        &amp;quot;regex-not&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;snapdragon&amp;quot;: &amp;quot;^0.8.1&amp;quot;,
        &amp;quot;to-regex&amp;quot;: &amp;quot;^3.0.1&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;define-property&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha1-dp66rz9KY6rTr56NMEybvnm/sOY=&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-descriptor&amp;quot;: &amp;quot;^1.0.0&amp;quot;
          }
        },
        &amp;quot;extend-shallow&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;2.0.1&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-extendable&amp;quot;: &amp;quot;^0.1.0&amp;quot;
          }
        },
        &amp;quot;is-accessor-descriptor&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;kind-of&amp;quot;: &amp;quot;^6.0.0&amp;quot;
          }
        },
        &amp;quot;is-data-descriptor&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;kind-of&amp;quot;: &amp;quot;^6.0.0&amp;quot;
          }
        },
        &amp;quot;is-descriptor&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;1.0.2&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-accessor-descriptor&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
            &amp;quot;is-data-descriptor&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
            &amp;quot;kind-of&amp;quot;: &amp;quot;^6.0.2&amp;quot;
          }
        }
      }
    },
    &amp;quot;extsprintf&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.3.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=&amp;quot;
    },
    &amp;quot;fast-deep-equal&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.1.3&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==&amp;quot;
    },
    &amp;quot;fast-json-stable-stringify&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==&amp;quot;
    },
    &amp;quot;fast-redact&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/fast-redact/-/fast-redact-2.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-zxpkULI9W9MNTK2sJ3BpPQrTEXFNESd2X6O1tXMFpK/XM0G5c5Rll2EVYZH2TqI3xRGK/VaJ+eEOt7pnENJpeA==&amp;quot;
    },
    &amp;quot;fast-safe-stringify&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.7&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==&amp;quot;
    },
    &amp;quot;file-type&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.9.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-JXoHg4TR24CHvESdEH1SpSZyuek=&amp;quot;
    },
    &amp;quot;fill-range&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;4.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;extend-shallow&amp;quot;: &amp;quot;^2.0.1&amp;quot;,
        &amp;quot;is-number&amp;quot;: &amp;quot;^3.0.0&amp;quot;,
        &amp;quot;repeat-string&amp;quot;: &amp;quot;^1.6.1&amp;quot;,
        &amp;quot;to-regex-range&amp;quot;: &amp;quot;^2.1.0&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;extend-shallow&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;2.0.1&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-extendable&amp;quot;: &amp;quot;^0.1.0&amp;quot;
          }
        }
      }
    },
    &amp;quot;findup-sync&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;detect-file&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;is-glob&amp;quot;: &amp;quot;^4.0.0&amp;quot;,
        &amp;quot;micromatch&amp;quot;: &amp;quot;^3.0.4&amp;quot;,
        &amp;quot;resolve-dir&amp;quot;: &amp;quot;^1.0.1&amp;quot;
      }
    },
    &amp;quot;fined&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.2.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/fined/-/fined-1.2.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;expand-tilde&amp;quot;: &amp;quot;^2.0.2&amp;quot;,
        &amp;quot;is-plain-object&amp;quot;: &amp;quot;^2.0.3&amp;quot;,
        &amp;quot;object.defaults&amp;quot;: &amp;quot;^1.1.0&amp;quot;,
        &amp;quot;object.pick&amp;quot;: &amp;quot;^1.2.0&amp;quot;,
        &amp;quot;parse-filepath&amp;quot;: &amp;quot;^1.0.1&amp;quot;
      }
    },
    &amp;quot;flagged-respawn&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==&amp;quot;
    },
    &amp;quot;flatstr&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.12&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==&amp;quot;
    },
    &amp;quot;for-in&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=&amp;quot;
    },
    &amp;quot;for-own&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;for-in&amp;quot;: &amp;quot;^1.0.1&amp;quot;
      }
    },
    &amp;quot;forever-agent&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.6.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=&amp;quot;
    },
    &amp;quot;form-data&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.3.3&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;asynckit&amp;quot;: &amp;quot;^0.4.0&amp;quot;,
        &amp;quot;combined-stream&amp;quot;: &amp;quot;^1.0.6&amp;quot;,
        &amp;quot;mime-types&amp;quot;: &amp;quot;^2.1.12&amp;quot;
      }
    },
    &amp;quot;fragment-cache&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.2.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;map-cache&amp;quot;: &amp;quot;^0.2.2&amp;quot;
      }
    },
    &amp;quot;fsevents&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.1.3&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;optional&amp;quot;: true
    },
    &amp;quot;function-bind&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.1.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==&amp;quot;
    },
    &amp;quot;get-stream&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;4.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;pump&amp;quot;: &amp;quot;^3.0.0&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;pump&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;3.0.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/pump/-/pump-3.0.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==&amp;quot;,
          &amp;quot;dev&amp;quot;: true,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;end-of-stream&amp;quot;: &amp;quot;^1.1.0&amp;quot;,
            &amp;quot;once&amp;quot;: &amp;quot;^1.3.1&amp;quot;
          }
        }
      }
    },
    &amp;quot;get-value&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.6&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=&amp;quot;
    },
    &amp;quot;getopts&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.2.5&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/getopts/-/getopts-2.2.5.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-9jb7AW5p3in+IiJWhQiZmmwkpLaR/ccTWdWQCtZM66HJcHHLegowh4q4tSD7gouUyeNvFWRavfK9GXosQHDpFA==&amp;quot;
    },
    &amp;quot;getpass&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.1.7&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;assert-plus&amp;quot;: &amp;quot;^1.0.0&amp;quot;
      }
    },
    &amp;quot;glob-parent&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;5.1.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;is-glob&amp;quot;: &amp;quot;^4.0.1&amp;quot;
      }
    },
    &amp;quot;global-dirs&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/global-dirs/-/global-dirs-2.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-5HqUqdhkEovj2Of/ms3IeS/EekcO54ytHRLV4PEY2rhRwrHXLQjeVEES0Lhka0xwNDtGYn58wyC4s5+MHsOO6A==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;ini&amp;quot;: &amp;quot;^1.3.5&amp;quot;
      }
    },
    &amp;quot;global-modules&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;global-prefix&amp;quot;: &amp;quot;^1.0.1&amp;quot;,
        &amp;quot;is-windows&amp;quot;: &amp;quot;^1.0.1&amp;quot;,
        &amp;quot;resolve-dir&amp;quot;: &amp;quot;^1.0.0&amp;quot;
      }
    },
    &amp;quot;global-prefix&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;expand-tilde&amp;quot;: &amp;quot;^2.0.2&amp;quot;,
        &amp;quot;homedir-polyfill&amp;quot;: &amp;quot;^1.0.1&amp;quot;,
        &amp;quot;ini&amp;quot;: &amp;quot;^1.3.4&amp;quot;,
        &amp;quot;is-windows&amp;quot;: &amp;quot;^1.0.1&amp;quot;,
        &amp;quot;which&amp;quot;: &amp;quot;^1.2.14&amp;quot;
      }
    },
    &amp;quot;got&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;9.6.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/got/-/got-9.6.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;@sindresorhus/is&amp;quot;: &amp;quot;^0.14.0&amp;quot;,
        &amp;quot;@szmarczak/http-timer&amp;quot;: &amp;quot;^1.1.2&amp;quot;,
        &amp;quot;cacheable-request&amp;quot;: &amp;quot;^6.0.0&amp;quot;,
        &amp;quot;decompress-response&amp;quot;: &amp;quot;^3.3.0&amp;quot;,
        &amp;quot;duplexer3&amp;quot;: &amp;quot;^0.1.4&amp;quot;,
        &amp;quot;get-stream&amp;quot;: &amp;quot;^4.1.0&amp;quot;,
        &amp;quot;lowercase-keys&amp;quot;: &amp;quot;^1.0.1&amp;quot;,
        &amp;quot;mimic-response&amp;quot;: &amp;quot;^1.0.1&amp;quot;,
        &amp;quot;p-cancelable&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;to-readable-stream&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;url-parse-lax&amp;quot;: &amp;quot;^3.0.0&amp;quot;
      }
    },
    &amp;quot;graceful-fs&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;4.2.4&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;har-schema&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=&amp;quot;
    },
    &amp;quot;har-validator&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;5.1.5&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;ajv&amp;quot;: &amp;quot;^6.12.3&amp;quot;,
        &amp;quot;har-schema&amp;quot;: &amp;quot;^2.0.0&amp;quot;
      }
    },
    &amp;quot;has&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.3&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/has/-/has-1.0.3.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;function-bind&amp;quot;: &amp;quot;^1.1.1&amp;quot;
      }
    },
    &amp;quot;has-flag&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-tdRU3CGZriJWmfNGfloH87lVuv0=&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;has-symbols&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==&amp;quot;
    },
    &amp;quot;has-value&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;get-value&amp;quot;: &amp;quot;^2.0.6&amp;quot;,
        &amp;quot;has-values&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;isobject&amp;quot;: &amp;quot;^3.0.0&amp;quot;
      }
    },
    &amp;quot;has-values&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;is-number&amp;quot;: &amp;quot;^3.0.0&amp;quot;,
        &amp;quot;kind-of&amp;quot;: &amp;quot;^4.0.0&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;kind-of&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;4.0.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha1-IIE989cSkosgc3hpGkUGb65y3Vc=&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-buffer&amp;quot;: &amp;quot;^1.1.5&amp;quot;
          }
        }
      }
    },
    &amp;quot;has-yarn&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;homedir-polyfill&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.3&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;parse-passwd&amp;quot;: &amp;quot;^1.0.0&amp;quot;
      }
    },
    &amp;quot;http-cache-semantics&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;4.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;http-signature&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.2.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;assert-plus&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;jsprim&amp;quot;: &amp;quot;^1.2.2&amp;quot;,
        &amp;quot;sshpk&amp;quot;: &amp;quot;^1.7.0&amp;quot;
      }
    },
    &amp;quot;ignore-by-default&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-SMptcvbGo68Aqa1K5odr44ieKwk=&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;import-lazy&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;imurmurhash&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.1.4&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-khi5srkoojixPcT7a21XbyMUU+o=&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;inherits&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.4&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==&amp;quot;
    },
    &amp;quot;ini&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.3.8&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/ini/-/ini-1.3.8.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==&amp;quot;
    },
    &amp;quot;interpret&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.2.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==&amp;quot;
    },
    &amp;quot;is-absolute&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;is-relative&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;is-windows&amp;quot;: &amp;quot;^1.0.1&amp;quot;
      }
    },
    &amp;quot;is-accessor-descriptor&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.1.6&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;kind-of&amp;quot;: &amp;quot;^3.0.2&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;kind-of&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;3.2.2&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-buffer&amp;quot;: &amp;quot;^1.1.5&amp;quot;
          }
        }
      }
    },
    &amp;quot;is-binary-path&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;binary-extensions&amp;quot;: &amp;quot;^2.0.0&amp;quot;
      }
    },
    &amp;quot;is-buffer&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.1.6&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==&amp;quot;
    },
    &amp;quot;is-callable&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.2.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==&amp;quot;
    },
    &amp;quot;is-ci&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;ci-info&amp;quot;: &amp;quot;^2.0.0&amp;quot;
      }
    },
    &amp;quot;is-core-module&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.3.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-core-module/-/is-core-module-2.3.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-xSphU2KG9867tsYdLD4RWQ1VqdFl4HTO9Thf3I/3dLEfr0dbPTWKsuCKrgqMljg4nPE+Gq0VCnzT3gr0CyBmsw==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;has&amp;quot;: &amp;quot;^1.0.3&amp;quot;
      }
    },
    &amp;quot;is-data-descriptor&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.1.4&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;kind-of&amp;quot;: &amp;quot;^3.0.2&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;kind-of&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;3.2.2&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-buffer&amp;quot;: &amp;quot;^1.1.5&amp;quot;
          }
        }
      }
    },
    &amp;quot;is-date-object&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==&amp;quot;
    },
    &amp;quot;is-descriptor&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.1.6&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;is-accessor-descriptor&amp;quot;: &amp;quot;^0.1.6&amp;quot;,
        &amp;quot;is-data-descriptor&amp;quot;: &amp;quot;^0.1.4&amp;quot;,
        &amp;quot;kind-of&amp;quot;: &amp;quot;^5.0.0&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;kind-of&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;5.1.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==&amp;quot;
        }
      }
    },
    &amp;quot;is-extendable&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.1.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=&amp;quot;
    },
    &amp;quot;is-extglob&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.1.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=&amp;quot;
    },
    &amp;quot;is-fullwidth-code-point&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;is-glob&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;4.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;is-extglob&amp;quot;: &amp;quot;^2.1.1&amp;quot;
      }
    },
    &amp;quot;is-installed-globally&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.3.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;global-dirs&amp;quot;: &amp;quot;^2.0.1&amp;quot;,
        &amp;quot;is-path-inside&amp;quot;: &amp;quot;^3.0.1&amp;quot;
      }
    },
    &amp;quot;is-npm&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;4.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;is-number&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;kind-of&amp;quot;: &amp;quot;^3.0.2&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;kind-of&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;3.2.2&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-buffer&amp;quot;: &amp;quot;^1.1.5&amp;quot;
          }
        }
      }
    },
    &amp;quot;is-obj&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;is-path-inside&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.0.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;is-plain-object&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.4&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;isobject&amp;quot;: &amp;quot;^3.0.1&amp;quot;
      }
    },
    &amp;quot;is-regex&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.1.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;has-symbols&amp;quot;: &amp;quot;^1.0.1&amp;quot;
      }
    },
    &amp;quot;is-relative&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;is-unc-path&amp;quot;: &amp;quot;^1.0.0&amp;quot;
      }
    },
    &amp;quot;is-symbol&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.3&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;has-symbols&amp;quot;: &amp;quot;^1.0.1&amp;quot;
      }
    },
    &amp;quot;is-typedarray&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=&amp;quot;
    },
    &amp;quot;is-unc-path&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;unc-path-regex&amp;quot;: &amp;quot;^0.1.2&amp;quot;
      }
    },
    &amp;quot;is-windows&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==&amp;quot;
    },
    &amp;quot;is-yarn-global&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.3.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;isarray&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=&amp;quot;
    },
    &amp;quot;isexe&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=&amp;quot;
    },
    &amp;quot;isobject&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-TkMekrEalzFjaqH5yNHMvP2reN8=&amp;quot;
    },
    &amp;quot;isstream&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.1.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=&amp;quot;
    },
    &amp;quot;jsbn&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.1.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-peZUwuWi3rXyAdls77yoDA7y9RM=&amp;quot;
    },
    &amp;quot;json-buffer&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;json-schema&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.2.3&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=&amp;quot;
    },
    &amp;quot;json-schema-traverse&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.4.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==&amp;quot;
    },
    &amp;quot;json-stringify-safe&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;5.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=&amp;quot;
    },
    &amp;quot;jsprim&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.4.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;assert-plus&amp;quot;: &amp;quot;1.0.0&amp;quot;,
        &amp;quot;extsprintf&amp;quot;: &amp;quot;1.3.0&amp;quot;,
        &amp;quot;json-schema&amp;quot;: &amp;quot;0.2.3&amp;quot;,
        &amp;quot;verror&amp;quot;: &amp;quot;1.10.0&amp;quot;
      }
    },
    &amp;quot;keyv&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;json-buffer&amp;quot;: &amp;quot;3.0.0&amp;quot;
      }
    },
    &amp;quot;kind-of&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;6.0.3&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==&amp;quot;
    },
    &amp;quot;knex&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.20.15&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/knex/-/knex-0.20.15.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-WHmvgfQfxA5v8pyb9zbskxCS1L1WmYgUbwBhHojlkmdouUOazvroUWlCr6KIKMQ8anXZh1NXOOtIUMnxENZG5Q==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;colorette&amp;quot;: &amp;quot;1.1.0&amp;quot;,
        &amp;quot;commander&amp;quot;: &amp;quot;^4.1.1&amp;quot;,
        &amp;quot;debug&amp;quot;: &amp;quot;4.1.1&amp;quot;,
        &amp;quot;esm&amp;quot;: &amp;quot;^3.2.25&amp;quot;,
        &amp;quot;getopts&amp;quot;: &amp;quot;2.2.5&amp;quot;,
        &amp;quot;inherits&amp;quot;: &amp;quot;~2.0.4&amp;quot;,
        &amp;quot;interpret&amp;quot;: &amp;quot;^2.0.0&amp;quot;,
        &amp;quot;liftoff&amp;quot;: &amp;quot;3.1.0&amp;quot;,
        &amp;quot;lodash&amp;quot;: &amp;quot;^4.17.15&amp;quot;,
        &amp;quot;mkdirp&amp;quot;: &amp;quot;^0.5.1&amp;quot;,
        &amp;quot;pg-connection-string&amp;quot;: &amp;quot;2.1.0&amp;quot;,
        &amp;quot;tarn&amp;quot;: &amp;quot;^2.0.0&amp;quot;,
        &amp;quot;tildify&amp;quot;: &amp;quot;2.0.0&amp;quot;,
        &amp;quot;uuid&amp;quot;: &amp;quot;^7.0.1&amp;quot;,
        &amp;quot;v8flags&amp;quot;: &amp;quot;^3.1.3&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;uuid&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;7.0.3&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==&amp;quot;
        }
      }
    },
    &amp;quot;latest-version&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;5.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;package-json&amp;quot;: &amp;quot;^6.3.0&amp;quot;
      }
    },
    &amp;quot;liftoff&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;extend&amp;quot;: &amp;quot;^3.0.0&amp;quot;,
        &amp;quot;findup-sync&amp;quot;: &amp;quot;^3.0.0&amp;quot;,
        &amp;quot;fined&amp;quot;: &amp;quot;^1.0.1&amp;quot;,
        &amp;quot;flagged-respawn&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;is-plain-object&amp;quot;: &amp;quot;^2.0.4&amp;quot;,
        &amp;quot;object.map&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;rechoir&amp;quot;: &amp;quot;^0.6.2&amp;quot;,
        &amp;quot;resolve&amp;quot;: &amp;quot;^1.1.7&amp;quot;
      }
    },
    &amp;quot;lodash&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;4.17.21&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==&amp;quot;
    },
    &amp;quot;lowercase-keys&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;make-dir&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;semver&amp;quot;: &amp;quot;^6.0.0&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;semver&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;6.3.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/semver/-/semver-6.3.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==&amp;quot;,
          &amp;quot;dev&amp;quot;: true
        }
      }
    },
    &amp;quot;make-iterator&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;kind-of&amp;quot;: &amp;quot;^6.0.2&amp;quot;
      }
    },
    &amp;quot;map-cache&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.2.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=&amp;quot;
    },
    &amp;quot;map-visit&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;object-visit&amp;quot;: &amp;quot;^1.0.0&amp;quot;
      }
    },
    &amp;quot;micromatch&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.1.10&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;arr-diff&amp;quot;: &amp;quot;^4.0.0&amp;quot;,
        &amp;quot;array-unique&amp;quot;: &amp;quot;^0.3.2&amp;quot;,
        &amp;quot;braces&amp;quot;: &amp;quot;^2.3.1&amp;quot;,
        &amp;quot;define-property&amp;quot;: &amp;quot;^2.0.2&amp;quot;,
        &amp;quot;extend-shallow&amp;quot;: &amp;quot;^3.0.2&amp;quot;,
        &amp;quot;extglob&amp;quot;: &amp;quot;^2.0.4&amp;quot;,
        &amp;quot;fragment-cache&amp;quot;: &amp;quot;^0.2.1&amp;quot;,
        &amp;quot;kind-of&amp;quot;: &amp;quot;^6.0.2&amp;quot;,
        &amp;quot;nanomatch&amp;quot;: &amp;quot;^1.2.9&amp;quot;,
        &amp;quot;object.pick&amp;quot;: &amp;quot;^1.3.0&amp;quot;,
        &amp;quot;regex-not&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;snapdragon&amp;quot;: &amp;quot;^0.8.1&amp;quot;,
        &amp;quot;to-regex&amp;quot;: &amp;quot;^3.0.2&amp;quot;
      }
    },
    &amp;quot;mime&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.6.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/mime/-/mime-1.6.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==&amp;quot;
    },
    &amp;quot;mime-db&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.43.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==&amp;quot;
    },
    &amp;quot;mime-types&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.1.26&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;mime-db&amp;quot;: &amp;quot;1.43.0&amp;quot;
      }
    },
    &amp;quot;mimic-response&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;minimatch&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.0.4&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;brace-expansion&amp;quot;: &amp;quot;^1.1.7&amp;quot;
      }
    },
    &amp;quot;minimist&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.2.5&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==&amp;quot;
    },
    &amp;quot;mixin-deep&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.3.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;for-in&amp;quot;: &amp;quot;^1.0.2&amp;quot;,
        &amp;quot;is-extendable&amp;quot;: &amp;quot;^1.0.1&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;is-extendable&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;1.0.1&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-plain-object&amp;quot;: &amp;quot;^2.0.4&amp;quot;
          }
        }
      }
    },
    &amp;quot;mkdirp&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.5.5&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;minimist&amp;quot;: &amp;quot;^1.2.5&amp;quot;
      }
    },
    &amp;quot;ms&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.1.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/ms/-/ms-2.1.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==&amp;quot;
    },
    &amp;quot;nanomatch&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.2.13&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;arr-diff&amp;quot;: &amp;quot;^4.0.0&amp;quot;,
        &amp;quot;array-unique&amp;quot;: &amp;quot;^0.3.2&amp;quot;,
        &amp;quot;define-property&amp;quot;: &amp;quot;^2.0.2&amp;quot;,
        &amp;quot;extend-shallow&amp;quot;: &amp;quot;^3.0.2&amp;quot;,
        &amp;quot;fragment-cache&amp;quot;: &amp;quot;^0.2.1&amp;quot;,
        &amp;quot;is-windows&amp;quot;: &amp;quot;^1.0.2&amp;quot;,
        &amp;quot;kind-of&amp;quot;: &amp;quot;^6.0.2&amp;quot;,
        &amp;quot;object.pick&amp;quot;: &amp;quot;^1.3.0&amp;quot;,
        &amp;quot;regex-not&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;snapdragon&amp;quot;: &amp;quot;^0.8.1&amp;quot;,
        &amp;quot;to-regex&amp;quot;: &amp;quot;^3.0.1&amp;quot;
      }
    },
    &amp;quot;node-telegram-bot-api&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.40.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/node-telegram-bot-api/-/node-telegram-bot-api-0.40.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-kDVCU1Y0L7hDnkm8oosO7cKIRyftPOvIGMvDbj7CU/FDIqqkC13VytRieHb/pFgTfFmiCpBTzAeK66YLHIfchQ==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;array.prototype.findindex&amp;quot;: &amp;quot;^2.0.2&amp;quot;,
        &amp;quot;bl&amp;quot;: &amp;quot;^1.2.1&amp;quot;,
        &amp;quot;bluebird&amp;quot;: &amp;quot;^3.5.1&amp;quot;,
        &amp;quot;debug&amp;quot;: &amp;quot;^3.1.0&amp;quot;,
        &amp;quot;depd&amp;quot;: &amp;quot;^1.1.1&amp;quot;,
        &amp;quot;eventemitter3&amp;quot;: &amp;quot;^3.0.0&amp;quot;,
        &amp;quot;file-type&amp;quot;: &amp;quot;^3.9.0&amp;quot;,
        &amp;quot;mime&amp;quot;: &amp;quot;^1.6.0&amp;quot;,
        &amp;quot;pump&amp;quot;: &amp;quot;^2.0.0&amp;quot;,
        &amp;quot;request&amp;quot;: &amp;quot;^2.83.0&amp;quot;,
        &amp;quot;request-promise&amp;quot;: &amp;quot;^4.2.2&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;debug&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;3.2.6&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/debug/-/debug-3.2.6.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;ms&amp;quot;: &amp;quot;^2.1.1&amp;quot;
          }
        }
      }
    },
    &amp;quot;nodemon&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.4&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/nodemon/-/nodemon-2.0.4.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-Ltced+hIfTmaS28Zjv1BM552oQ3dbwPqI4+zI0SLgq+wpJhSyqgYude/aZa/3i31VCQWMfXJVxvu86abcam3uQ==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;chokidar&amp;quot;: &amp;quot;^3.2.2&amp;quot;,
        &amp;quot;debug&amp;quot;: &amp;quot;^3.2.6&amp;quot;,
        &amp;quot;ignore-by-default&amp;quot;: &amp;quot;^1.0.1&amp;quot;,
        &amp;quot;minimatch&amp;quot;: &amp;quot;^3.0.4&amp;quot;,
        &amp;quot;pstree.remy&amp;quot;: &amp;quot;^1.1.7&amp;quot;,
        &amp;quot;semver&amp;quot;: &amp;quot;^5.7.1&amp;quot;,
        &amp;quot;supports-color&amp;quot;: &amp;quot;^5.5.0&amp;quot;,
        &amp;quot;touch&amp;quot;: &amp;quot;^3.1.0&amp;quot;,
        &amp;quot;undefsafe&amp;quot;: &amp;quot;^2.0.2&amp;quot;,
        &amp;quot;update-notifier&amp;quot;: &amp;quot;^4.0.0&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;debug&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;3.2.6&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/debug/-/debug-3.2.6.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==&amp;quot;,
          &amp;quot;dev&amp;quot;: true,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;ms&amp;quot;: &amp;quot;^2.1.1&amp;quot;
          }
        }
      }
    },
    &amp;quot;nopt&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.10&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;abbrev&amp;quot;: &amp;quot;1&amp;quot;
      }
    },
    &amp;quot;normalize-path&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;normalize-url&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;4.5.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;oauth-sign&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.9.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==&amp;quot;
    },
    &amp;quot;object-copy&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-fn2Fi3gb18mRpBupde04EnVOmYw=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;copy-descriptor&amp;quot;: &amp;quot;^0.1.0&amp;quot;,
        &amp;quot;define-property&amp;quot;: &amp;quot;^0.2.5&amp;quot;,
        &amp;quot;kind-of&amp;quot;: &amp;quot;^3.0.3&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;define-property&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;0.2.5&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-descriptor&amp;quot;: &amp;quot;^0.1.0&amp;quot;
          }
        },
        &amp;quot;kind-of&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;3.2.2&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-buffer&amp;quot;: &amp;quot;^1.1.5&amp;quot;
          }
        }
      }
    },
    &amp;quot;object-inspect&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.8.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==&amp;quot;
    },
    &amp;quot;object-keys&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.1.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==&amp;quot;
    },
    &amp;quot;object-visit&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;isobject&amp;quot;: &amp;quot;^3.0.0&amp;quot;
      }
    },
    &amp;quot;object.assign&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;4.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;define-properties&amp;quot;: &amp;quot;^1.1.2&amp;quot;,
        &amp;quot;function-bind&amp;quot;: &amp;quot;^1.1.1&amp;quot;,
        &amp;quot;has-symbols&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;object-keys&amp;quot;: &amp;quot;^1.0.11&amp;quot;
      }
    },
    &amp;quot;object.defaults&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;array-each&amp;quot;: &amp;quot;^1.0.1&amp;quot;,
        &amp;quot;array-slice&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;for-own&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;isobject&amp;quot;: &amp;quot;^3.0.0&amp;quot;
      }
    },
    &amp;quot;object.map&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;for-own&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;make-iterator&amp;quot;: &amp;quot;^1.0.0&amp;quot;
      }
    },
    &amp;quot;object.pick&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.3.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;isobject&amp;quot;: &amp;quot;^3.0.1&amp;quot;
      }
    },
    &amp;quot;once&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.4.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/once/-/once-1.4.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-WDsap3WWHUsROsF9nFC6753Xa9E=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;wrappy&amp;quot;: &amp;quot;1&amp;quot;
      }
    },
    &amp;quot;p-cancelable&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;package-json&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;6.5.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;got&amp;quot;: &amp;quot;^9.6.0&amp;quot;,
        &amp;quot;registry-auth-token&amp;quot;: &amp;quot;^4.0.0&amp;quot;,
        &amp;quot;registry-url&amp;quot;: &amp;quot;^5.0.0&amp;quot;,
        &amp;quot;semver&amp;quot;: &amp;quot;^6.2.0&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;semver&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;6.3.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/semver/-/semver-6.3.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==&amp;quot;,
          &amp;quot;dev&amp;quot;: true
        }
      }
    },
    &amp;quot;packet-reader&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==&amp;quot;
    },
    &amp;quot;parse-filepath&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;is-absolute&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;map-cache&amp;quot;: &amp;quot;^0.2.0&amp;quot;,
        &amp;quot;path-root&amp;quot;: &amp;quot;^0.1.1&amp;quot;
      }
    },
    &amp;quot;parse-passwd&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=&amp;quot;
    },
    &amp;quot;pascalcase&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.1.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=&amp;quot;
    },
    &amp;quot;path-parse&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.6&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==&amp;quot;
    },
    &amp;quot;path-root&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.1.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;path-root-regex&amp;quot;: &amp;quot;^0.1.0&amp;quot;
      }
    },
    &amp;quot;path-root-regex&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.1.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=&amp;quot;
    },
    &amp;quot;performance-now&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=&amp;quot;
    },
    &amp;quot;pg&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;8.6.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/pg/-/pg-8.6.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-qNS9u61lqljTDFvmk/N66EeGq3n6Ujzj0FFyNMGQr6XuEv4tgNTXvJQTfJdcvGit5p5/DWPu+wj920hAJFI+QQ==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;buffer-writer&amp;quot;: &amp;quot;2.0.0&amp;quot;,
        &amp;quot;packet-reader&amp;quot;: &amp;quot;1.0.0&amp;quot;,
        &amp;quot;pg-connection-string&amp;quot;: &amp;quot;^2.5.0&amp;quot;,
        &amp;quot;pg-pool&amp;quot;: &amp;quot;^3.3.0&amp;quot;,
        &amp;quot;pg-protocol&amp;quot;: &amp;quot;^1.5.0&amp;quot;,
        &amp;quot;pg-types&amp;quot;: &amp;quot;^2.1.0&amp;quot;,
        &amp;quot;pgpass&amp;quot;: &amp;quot;1.x&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;pg-connection-string&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;2.5.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==&amp;quot;
        }
      }
    },
    &amp;quot;pg-connection-string&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-bhlV7Eq09JrRIvo1eKngpwuqKtJnNhZdpdOlvrPrA4dxqXPjxSrbNrfnIDmTpwMyRszrcV4kU5ZA4mMsQUrjdg==&amp;quot;
    },
    &amp;quot;pg-int8&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==&amp;quot;
    },
    &amp;quot;pg-pool&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.3.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/pg-pool/-/pg-pool-3.3.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-0O5huCql8/D6PIRFAlmccjphLYWC+JIzvUhSzXSpGaf+tjTZc4nn+Lr7mLXBbFJfvwbP0ywDv73EiaBsxn7zdg==&amp;quot;
    },
    &amp;quot;pg-protocol&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.5.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==&amp;quot;
    },
    &amp;quot;pg-types&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.2.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;pg-int8&amp;quot;: &amp;quot;1.0.1&amp;quot;,
        &amp;quot;postgres-array&amp;quot;: &amp;quot;~2.0.0&amp;quot;,
        &amp;quot;postgres-bytea&amp;quot;: &amp;quot;~1.0.0&amp;quot;,
        &amp;quot;postgres-date&amp;quot;: &amp;quot;~1.0.4&amp;quot;,
        &amp;quot;postgres-interval&amp;quot;: &amp;quot;^1.1.0&amp;quot;
      }
    },
    &amp;quot;pgpass&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.4&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/pgpass/-/pgpass-1.0.4.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-YmuA56alyBq7M59vxVBfPJrGSozru8QAdoNlWuW3cz8l+UX3cWge0vTvjKhsSHSJpo3Bom8/Mm6hf0TR5GY0+w==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;split2&amp;quot;: &amp;quot;^3.1.1&amp;quot;
      }
    },
    &amp;quot;picomatch&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.2.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;pino&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;6.5.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/pino/-/pino-6.5.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-76+RUhQkqjUD4AtQcSfEzh6vlsjXmoWZK5gg+2d70aCLXZTbo4/5js4I9rN1Xk6z1h2/7pnOFX10G4c2T4qNiA==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;fast-redact&amp;quot;: &amp;quot;^2.0.0&amp;quot;,
        &amp;quot;fast-safe-stringify&amp;quot;: &amp;quot;^2.0.7&amp;quot;,
        &amp;quot;flatstr&amp;quot;: &amp;quot;^1.0.12&amp;quot;,
        &amp;quot;pino-std-serializers&amp;quot;: &amp;quot;^2.4.2&amp;quot;,
        &amp;quot;quick-format-unescaped&amp;quot;: &amp;quot;^4.0.1&amp;quot;,
        &amp;quot;sonic-boom&amp;quot;: &amp;quot;^1.0.2&amp;quot;
      }
    },
    &amp;quot;pino-std-serializers&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.5.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-2.5.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-wXqbqSrIhE58TdrxxlfLwU9eDhrzppQDvGhBEr1gYbzzM4KKo3Y63gSjiDXRKLVS2UOXdPNR2v+KnQgNrs+xUg==&amp;quot;
    },
    &amp;quot;posix-character-classes&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.1.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=&amp;quot;
    },
    &amp;quot;postgres-array&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==&amp;quot;
    },
    &amp;quot;postgres-bytea&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=&amp;quot;
    },
    &amp;quot;postgres-date&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.7&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==&amp;quot;
    },
    &amp;quot;postgres-interval&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.2.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;xtend&amp;quot;: &amp;quot;^4.0.0&amp;quot;
      }
    },
    &amp;quot;prepend-http&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;process-nextick-args&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==&amp;quot;
    },
    &amp;quot;psl&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.8.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/psl/-/psl-1.8.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==&amp;quot;
    },
    &amp;quot;pstree.remy&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.1.8&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;pump&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/pump/-/pump-2.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;end-of-stream&amp;quot;: &amp;quot;^1.1.0&amp;quot;,
        &amp;quot;once&amp;quot;: &amp;quot;^1.3.1&amp;quot;
      }
    },
    &amp;quot;punycode&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.1.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==&amp;quot;
    },
    &amp;quot;pupa&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/pupa/-/pupa-2.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-hEJH0s8PXLY/cdXh66tNEQGndDrIKNqNC5xmrysZy3i5C3oEoLna7YAOad+7u125+zH1HNXUmGEkrhb3c2VriA==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;escape-goat&amp;quot;: &amp;quot;^2.0.0&amp;quot;
      }
    },
    &amp;quot;qs&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;6.5.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/qs/-/qs-6.5.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==&amp;quot;
    },
    &amp;quot;quick-format-unescaped&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;4.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-RyYpQ6Q5/drsJyOhrWHYMWTedvjTIat+FTwv0K4yoUxzvekw2aRHMQJLlnvt8UantkZg2++bEzD9EdxXqkWf4A==&amp;quot;
    },
    &amp;quot;rc&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.2.8&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/rc/-/rc-1.2.8.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;deep-extend&amp;quot;: &amp;quot;^0.6.0&amp;quot;,
        &amp;quot;ini&amp;quot;: &amp;quot;~1.3.0&amp;quot;,
        &amp;quot;minimist&amp;quot;: &amp;quot;^1.2.0&amp;quot;,
        &amp;quot;strip-json-comments&amp;quot;: &amp;quot;~2.0.1&amp;quot;
      }
    },
    &amp;quot;readable-stream&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.3.7&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;core-util-is&amp;quot;: &amp;quot;~1.0.0&amp;quot;,
        &amp;quot;inherits&amp;quot;: &amp;quot;~2.0.3&amp;quot;,
        &amp;quot;isarray&amp;quot;: &amp;quot;~1.0.0&amp;quot;,
        &amp;quot;process-nextick-args&amp;quot;: &amp;quot;~2.0.0&amp;quot;,
        &amp;quot;safe-buffer&amp;quot;: &amp;quot;~5.1.1&amp;quot;,
        &amp;quot;string_decoder&amp;quot;: &amp;quot;~1.1.1&amp;quot;,
        &amp;quot;util-deprecate&amp;quot;: &amp;quot;~1.0.1&amp;quot;
      }
    },
    &amp;quot;readdirp&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.4.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;picomatch&amp;quot;: &amp;quot;^2.2.1&amp;quot;
      }
    },
    &amp;quot;rechoir&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.6.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;resolve&amp;quot;: &amp;quot;^1.1.6&amp;quot;
      }
    },
    &amp;quot;regex-not&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;extend-shallow&amp;quot;: &amp;quot;^3.0.2&amp;quot;,
        &amp;quot;safe-regex&amp;quot;: &amp;quot;^1.1.0&amp;quot;
      }
    },
    &amp;quot;registry-auth-token&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;4.2.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-P+lWzPrsgfN+UEpDS3U8AQKg/UjZX6mQSJueZj3EK+vNESoqBSpBUD3gmu4sF9lOsjXWjF11dQKUqemf3veq1w==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;rc&amp;quot;: &amp;quot;^1.2.8&amp;quot;
      }
    },
    &amp;quot;registry-url&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;5.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;rc&amp;quot;: &amp;quot;^1.2.8&amp;quot;
      }
    },
    &amp;quot;repeat-element&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.1.4&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==&amp;quot;
    },
    &amp;quot;repeat-string&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.6.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-jcrkcOHIirwtYA//Sndihtp15jc=&amp;quot;
    },
    &amp;quot;request&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.88.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/request/-/request-2.88.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;aws-sign2&amp;quot;: &amp;quot;~0.7.0&amp;quot;,
        &amp;quot;aws4&amp;quot;: &amp;quot;^1.8.0&amp;quot;,
        &amp;quot;caseless&amp;quot;: &amp;quot;~0.12.0&amp;quot;,
        &amp;quot;combined-stream&amp;quot;: &amp;quot;~1.0.6&amp;quot;,
        &amp;quot;extend&amp;quot;: &amp;quot;~3.0.2&amp;quot;,
        &amp;quot;forever-agent&amp;quot;: &amp;quot;~0.6.1&amp;quot;,
        &amp;quot;form-data&amp;quot;: &amp;quot;~2.3.2&amp;quot;,
        &amp;quot;har-validator&amp;quot;: &amp;quot;~5.1.3&amp;quot;,
        &amp;quot;http-signature&amp;quot;: &amp;quot;~1.2.0&amp;quot;,
        &amp;quot;is-typedarray&amp;quot;: &amp;quot;~1.0.0&amp;quot;,
        &amp;quot;isstream&amp;quot;: &amp;quot;~0.1.2&amp;quot;,
        &amp;quot;json-stringify-safe&amp;quot;: &amp;quot;~5.0.1&amp;quot;,
        &amp;quot;mime-types&amp;quot;: &amp;quot;~2.1.19&amp;quot;,
        &amp;quot;oauth-sign&amp;quot;: &amp;quot;~0.9.0&amp;quot;,
        &amp;quot;performance-now&amp;quot;: &amp;quot;^2.1.0&amp;quot;,
        &amp;quot;qs&amp;quot;: &amp;quot;~6.5.2&amp;quot;,
        &amp;quot;safe-buffer&amp;quot;: &amp;quot;^5.1.2&amp;quot;,
        &amp;quot;tough-cookie&amp;quot;: &amp;quot;~2.5.0&amp;quot;,
        &amp;quot;tunnel-agent&amp;quot;: &amp;quot;^0.6.0&amp;quot;,
        &amp;quot;uuid&amp;quot;: &amp;quot;^3.3.2&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;uuid&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;3.4.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==&amp;quot;
        }
      }
    },
    &amp;quot;request-promise&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;4.2.6&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/request-promise/-/request-promise-4.2.6.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-HCHI3DJJUakkOr8fNoCc73E5nU5bqITjOYFMDrKHYOXWXrgD/SBaC7LjwuPymUprRyuF06UK7hd/lMHkmUXglQ==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;bluebird&amp;quot;: &amp;quot;^3.5.0&amp;quot;,
        &amp;quot;request-promise-core&amp;quot;: &amp;quot;1.1.4&amp;quot;,
        &amp;quot;stealthy-require&amp;quot;: &amp;quot;^1.1.1&amp;quot;,
        &amp;quot;tough-cookie&amp;quot;: &amp;quot;^2.3.3&amp;quot;
      }
    },
    &amp;quot;request-promise-core&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.1.4&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;lodash&amp;quot;: &amp;quot;^4.17.19&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;lodash&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;4.17.20&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==&amp;quot;
        }
      }
    },
    &amp;quot;resolve&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.20.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;is-core-module&amp;quot;: &amp;quot;^2.2.0&amp;quot;,
        &amp;quot;path-parse&amp;quot;: &amp;quot;^1.0.6&amp;quot;
      }
    },
    &amp;quot;resolve-dir&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;expand-tilde&amp;quot;: &amp;quot;^2.0.0&amp;quot;,
        &amp;quot;global-modules&amp;quot;: &amp;quot;^1.0.0&amp;quot;
      }
    },
    &amp;quot;resolve-url&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.2.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=&amp;quot;
    },
    &amp;quot;responselike&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;lowercase-keys&amp;quot;: &amp;quot;^1.0.0&amp;quot;
      }
    },
    &amp;quot;ret&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.1.15&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/ret/-/ret-0.1.15.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==&amp;quot;
    },
    &amp;quot;safe-buffer&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;5.1.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==&amp;quot;
    },
    &amp;quot;safe-regex&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-QKNmnzsHfR6UPURinhV91IAjvy4=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;ret&amp;quot;: &amp;quot;~0.1.10&amp;quot;
      }
    },
    &amp;quot;safer-buffer&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.1.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==&amp;quot;
    },
    &amp;quot;semver&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;5.7.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/semver/-/semver-5.7.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;semver-diff&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.1.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;semver&amp;quot;: &amp;quot;^6.3.0&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;semver&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;6.3.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/semver/-/semver-6.3.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==&amp;quot;,
          &amp;quot;dev&amp;quot;: true
        }
      }
    },
    &amp;quot;set-value&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;extend-shallow&amp;quot;: &amp;quot;^2.0.1&amp;quot;,
        &amp;quot;is-extendable&amp;quot;: &amp;quot;^0.1.1&amp;quot;,
        &amp;quot;is-plain-object&amp;quot;: &amp;quot;^2.0.3&amp;quot;,
        &amp;quot;split-string&amp;quot;: &amp;quot;^3.0.1&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;extend-shallow&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;2.0.1&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-extendable&amp;quot;: &amp;quot;^0.1.0&amp;quot;
          }
        }
      }
    },
    &amp;quot;signal-exit&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.0.3&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;snapdragon&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.8.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;base&amp;quot;: &amp;quot;^0.11.1&amp;quot;,
        &amp;quot;debug&amp;quot;: &amp;quot;^2.2.0&amp;quot;,
        &amp;quot;define-property&amp;quot;: &amp;quot;^0.2.5&amp;quot;,
        &amp;quot;extend-shallow&amp;quot;: &amp;quot;^2.0.1&amp;quot;,
        &amp;quot;map-cache&amp;quot;: &amp;quot;^0.2.2&amp;quot;,
        &amp;quot;source-map&amp;quot;: &amp;quot;^0.5.6&amp;quot;,
        &amp;quot;source-map-resolve&amp;quot;: &amp;quot;^0.5.0&amp;quot;,
        &amp;quot;use&amp;quot;: &amp;quot;^3.1.0&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;debug&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;2.6.9&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/debug/-/debug-2.6.9.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;ms&amp;quot;: &amp;quot;2.0.0&amp;quot;
          }
        },
        &amp;quot;define-property&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;0.2.5&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-descriptor&amp;quot;: &amp;quot;^0.1.0&amp;quot;
          }
        },
        &amp;quot;extend-shallow&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;2.0.1&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-extendable&amp;quot;: &amp;quot;^0.1.0&amp;quot;
          }
        },
        &amp;quot;ms&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;2.0.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/ms/-/ms-2.0.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=&amp;quot;
        }
      }
    },
    &amp;quot;snapdragon-node&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.1.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;define-property&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;isobject&amp;quot;: &amp;quot;^3.0.0&amp;quot;,
        &amp;quot;snapdragon-util&amp;quot;: &amp;quot;^3.0.1&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;define-property&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha1-dp66rz9KY6rTr56NMEybvnm/sOY=&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-descriptor&amp;quot;: &amp;quot;^1.0.0&amp;quot;
          }
        },
        &amp;quot;is-accessor-descriptor&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;kind-of&amp;quot;: &amp;quot;^6.0.0&amp;quot;
          }
        },
        &amp;quot;is-data-descriptor&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;kind-of&amp;quot;: &amp;quot;^6.0.0&amp;quot;
          }
        },
        &amp;quot;is-descriptor&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;1.0.2&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-accessor-descriptor&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
            &amp;quot;is-data-descriptor&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
            &amp;quot;kind-of&amp;quot;: &amp;quot;^6.0.2&amp;quot;
          }
        }
      }
    },
    &amp;quot;snapdragon-util&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;kind-of&amp;quot;: &amp;quot;^3.2.0&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;kind-of&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;3.2.2&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-buffer&amp;quot;: &amp;quot;^1.1.5&amp;quot;
          }
        }
      }
    },
    &amp;quot;sonic-boom&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/sonic-boom/-/sonic-boom-1.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-JyOf+Xt7GBN4tAic/DD1Bitw6OMgSHAnswhPeOiLpfRoSjPNjEIi73UF3OxHzhSNn9WavxGuCZzprFCGFSNwog==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;atomic-sleep&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;flatstr&amp;quot;: &amp;quot;^1.0.12&amp;quot;
      }
    },
    &amp;quot;source-map&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.5.7&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=&amp;quot;
    },
    &amp;quot;source-map-resolve&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.5.3&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;atob&amp;quot;: &amp;quot;^2.1.2&amp;quot;,
        &amp;quot;decode-uri-component&amp;quot;: &amp;quot;^0.2.0&amp;quot;,
        &amp;quot;resolve-url&amp;quot;: &amp;quot;^0.2.1&amp;quot;,
        &amp;quot;source-map-url&amp;quot;: &amp;quot;^0.4.0&amp;quot;,
        &amp;quot;urix&amp;quot;: &amp;quot;^0.1.0&amp;quot;
      }
    },
    &amp;quot;source-map-url&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.4.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==&amp;quot;
    },
    &amp;quot;split-string&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;extend-shallow&amp;quot;: &amp;quot;^3.0.0&amp;quot;
      }
    },
    &amp;quot;split2&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.2.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/split2/-/split2-3.2.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;readable-stream&amp;quot;: &amp;quot;^3.0.0&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;readable-stream&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;3.6.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;inherits&amp;quot;: &amp;quot;^2.0.3&amp;quot;,
            &amp;quot;string_decoder&amp;quot;: &amp;quot;^1.1.1&amp;quot;,
            &amp;quot;util-deprecate&amp;quot;: &amp;quot;^1.0.1&amp;quot;
          }
        }
      }
    },
    &amp;quot;sshpk&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.16.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;asn1&amp;quot;: &amp;quot;~0.2.3&amp;quot;,
        &amp;quot;assert-plus&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;bcrypt-pbkdf&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;dashdash&amp;quot;: &amp;quot;^1.12.0&amp;quot;,
        &amp;quot;ecc-jsbn&amp;quot;: &amp;quot;~0.1.1&amp;quot;,
        &amp;quot;getpass&amp;quot;: &amp;quot;^0.1.1&amp;quot;,
        &amp;quot;jsbn&amp;quot;: &amp;quot;~0.1.0&amp;quot;,
        &amp;quot;safer-buffer&amp;quot;: &amp;quot;^2.0.2&amp;quot;,
        &amp;quot;tweetnacl&amp;quot;: &amp;quot;~0.14.0&amp;quot;
      }
    },
    &amp;quot;static-extend&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.1.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;define-property&amp;quot;: &amp;quot;^0.2.5&amp;quot;,
        &amp;quot;object-copy&amp;quot;: &amp;quot;^0.1.0&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;define-property&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;0.2.5&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-descriptor&amp;quot;: &amp;quot;^0.1.0&amp;quot;
          }
        }
      }
    },
    &amp;quot;stealthy-require&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.1.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=&amp;quot;
    },
    &amp;quot;string-width&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;4.2.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;emoji-regex&amp;quot;: &amp;quot;^8.0.0&amp;quot;,
        &amp;quot;is-fullwidth-code-point&amp;quot;: &amp;quot;^3.0.0&amp;quot;,
        &amp;quot;strip-ansi&amp;quot;: &amp;quot;^6.0.0&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;ansi-regex&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;5.0.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==&amp;quot;,
          &amp;quot;dev&amp;quot;: true
        },
        &amp;quot;emoji-regex&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;8.0.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==&amp;quot;,
          &amp;quot;dev&amp;quot;: true
        },
        &amp;quot;is-fullwidth-code-point&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;3.0.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==&amp;quot;,
          &amp;quot;dev&amp;quot;: true
        },
        &amp;quot;strip-ansi&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;6.0.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==&amp;quot;,
          &amp;quot;dev&amp;quot;: true,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;ansi-regex&amp;quot;: &amp;quot;^5.0.0&amp;quot;
          }
        }
      }
    },
    &amp;quot;string.prototype.trimend&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;define-properties&amp;quot;: &amp;quot;^1.1.3&amp;quot;,
        &amp;quot;es-abstract&amp;quot;: &amp;quot;^1.17.5&amp;quot;
      }
    },
    &amp;quot;string.prototype.trimstart&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;define-properties&amp;quot;: &amp;quot;^1.1.3&amp;quot;,
        &amp;quot;es-abstract&amp;quot;: &amp;quot;^1.17.5&amp;quot;
      }
    },
    &amp;quot;string_decoder&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.1.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;safe-buffer&amp;quot;: &amp;quot;~5.1.0&amp;quot;
      }
    },
    &amp;quot;strip-ansi&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;5.2.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;ansi-regex&amp;quot;: &amp;quot;^4.1.0&amp;quot;
      }
    },
    &amp;quot;strip-json-comments&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-PFMZQukIwml8DsNEhYwobHygpgo=&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;supports-color&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;5.5.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;has-flag&amp;quot;: &amp;quot;^3.0.0&amp;quot;
      }
    },
    &amp;quot;tarn&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/tarn/-/tarn-2.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-7rNMCZd3s9bhQh47ksAQd92ADFcJUjjbyOvyFjNLwTPpGieFHMC84S+LOzw0fx1uh6hnDz/19r8CPMnIjJlMMA==&amp;quot;
    },
    &amp;quot;term-size&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.2.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/term-size/-/term-size-2.2.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-a6sumDlzyHVJWb8+YofY4TW112G6p2FCPEAFk+59gIYHv3XHRhm9ltVQ9kli4hNWeQBwSpe8cRN25x0ROunMOw==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;tildify&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==&amp;quot;
    },
    &amp;quot;to-object-path&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.3.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;kind-of&amp;quot;: &amp;quot;^3.0.2&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;kind-of&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;3.2.2&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;is-buffer&amp;quot;: &amp;quot;^1.1.5&amp;quot;
          }
        }
      }
    },
    &amp;quot;to-readable-stream&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;to-regex&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.0.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;define-property&amp;quot;: &amp;quot;^2.0.2&amp;quot;,
        &amp;quot;extend-shallow&amp;quot;: &amp;quot;^3.0.2&amp;quot;,
        &amp;quot;regex-not&amp;quot;: &amp;quot;^1.0.2&amp;quot;,
        &amp;quot;safe-regex&amp;quot;: &amp;quot;^1.1.0&amp;quot;
      }
    },
    &amp;quot;to-regex-range&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.1.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;is-number&amp;quot;: &amp;quot;^3.0.0&amp;quot;,
        &amp;quot;repeat-string&amp;quot;: &amp;quot;^1.6.1&amp;quot;
      }
    },
    &amp;quot;touch&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/touch/-/touch-3.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;nopt&amp;quot;: &amp;quot;~1.0.10&amp;quot;
      }
    },
    &amp;quot;tough-cookie&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.5.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;psl&amp;quot;: &amp;quot;^1.1.28&amp;quot;,
        &amp;quot;punycode&amp;quot;: &amp;quot;^2.1.1&amp;quot;
      }
    },
    &amp;quot;tunnel-agent&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.6.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;safe-buffer&amp;quot;: &amp;quot;^5.0.1&amp;quot;
      }
    },
    &amp;quot;tweetnacl&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.14.5&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=&amp;quot;
    },
    &amp;quot;type-fest&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.8.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;typedarray-to-buffer&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.1.5&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;is-typedarray&amp;quot;: &amp;quot;^1.0.0&amp;quot;
      }
    },
    &amp;quot;unc-path-regex&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.1.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-5z3T17DXxe2G+6xrCufYxqadUPo=&amp;quot;
    },
    &amp;quot;undefsafe&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.3&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;debug&amp;quot;: &amp;quot;^2.2.0&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;debug&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;2.6.9&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/debug/-/debug-2.6.9.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==&amp;quot;,
          &amp;quot;dev&amp;quot;: true,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;ms&amp;quot;: &amp;quot;2.0.0&amp;quot;
          }
        },
        &amp;quot;ms&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;2.0.0&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/ms/-/ms-2.0.0.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=&amp;quot;,
          &amp;quot;dev&amp;quot;: true
        }
      }
    },
    &amp;quot;union-value&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;arr-union&amp;quot;: &amp;quot;^3.1.0&amp;quot;,
        &amp;quot;get-value&amp;quot;: &amp;quot;^2.0.6&amp;quot;,
        &amp;quot;is-extendable&amp;quot;: &amp;quot;^0.1.1&amp;quot;,
        &amp;quot;set-value&amp;quot;: &amp;quot;^2.0.1&amp;quot;
      }
    },
    &amp;quot;unique-string&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;2.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;crypto-random-string&amp;quot;: &amp;quot;^2.0.0&amp;quot;
      }
    },
    &amp;quot;unset-value&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;has-value&amp;quot;: &amp;quot;^0.3.1&amp;quot;,
        &amp;quot;isobject&amp;quot;: &amp;quot;^3.0.0&amp;quot;
      },
      &amp;quot;dependencies&amp;quot;: {
        &amp;quot;has-value&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;0.3.1&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=&amp;quot;,
          &amp;quot;requires&amp;quot;: {
            &amp;quot;get-value&amp;quot;: &amp;quot;^2.0.3&amp;quot;,
            &amp;quot;has-values&amp;quot;: &amp;quot;^0.1.4&amp;quot;,
            &amp;quot;isobject&amp;quot;: &amp;quot;^2.0.0&amp;quot;
          },
          &amp;quot;dependencies&amp;quot;: {
            &amp;quot;isobject&amp;quot;: {
              &amp;quot;version&amp;quot;: &amp;quot;2.1.0&amp;quot;,
              &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz&amp;quot;,
              &amp;quot;integrity&amp;quot;: &amp;quot;sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=&amp;quot;,
              &amp;quot;requires&amp;quot;: {
                &amp;quot;isarray&amp;quot;: &amp;quot;1.0.0&amp;quot;
              }
            }
          }
        },
        &amp;quot;has-values&amp;quot;: {
          &amp;quot;version&amp;quot;: &amp;quot;0.1.4&amp;quot;,
          &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz&amp;quot;,
          &amp;quot;integrity&amp;quot;: &amp;quot;sha1-bWHeldkd/Km5oCCJrThL/49it3E=&amp;quot;
        }
      }
    },
    &amp;quot;update-notifier&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;4.1.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-9y+Kds0+LoLG6yN802wVXoIfxYEwh3FlZwzMwpCZp62S2i1/Jzeqb9Eeeju3NSHccGGasfGlK5/vEHbAifYRDg==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;boxen&amp;quot;: &amp;quot;^4.2.0&amp;quot;,
        &amp;quot;chalk&amp;quot;: &amp;quot;^3.0.0&amp;quot;,
        &amp;quot;configstore&amp;quot;: &amp;quot;^5.0.1&amp;quot;,
        &amp;quot;has-yarn&amp;quot;: &amp;quot;^2.1.0&amp;quot;,
        &amp;quot;import-lazy&amp;quot;: &amp;quot;^2.1.0&amp;quot;,
        &amp;quot;is-ci&amp;quot;: &amp;quot;^2.0.0&amp;quot;,
        &amp;quot;is-installed-globally&amp;quot;: &amp;quot;^0.3.1&amp;quot;,
        &amp;quot;is-npm&amp;quot;: &amp;quot;^4.0.0&amp;quot;,
        &amp;quot;is-yarn-global&amp;quot;: &amp;quot;^0.3.0&amp;quot;,
        &amp;quot;latest-version&amp;quot;: &amp;quot;^5.0.0&amp;quot;,
        &amp;quot;pupa&amp;quot;: &amp;quot;^2.0.1&amp;quot;,
        &amp;quot;semver-diff&amp;quot;: &amp;quot;^3.1.1&amp;quot;,
        &amp;quot;xdg-basedir&amp;quot;: &amp;quot;^4.0.0&amp;quot;
      }
    },
    &amp;quot;uri-js&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;4.2.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;punycode&amp;quot;: &amp;quot;^2.1.0&amp;quot;
      }
    },
    &amp;quot;urix&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;0.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/urix/-/urix-0.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=&amp;quot;
    },
    &amp;quot;url-parse-lax&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;prepend-http&amp;quot;: &amp;quot;^2.0.0&amp;quot;
      }
    },
    &amp;quot;use&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.1.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/use/-/use-3.1.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==&amp;quot;
    },
    &amp;quot;util-deprecate&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=&amp;quot;
    },
    &amp;quot;uuid&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;8.3.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==&amp;quot;
    },
    &amp;quot;v8flags&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.2.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;homedir-polyfill&amp;quot;: &amp;quot;^1.0.1&amp;quot;
      }
    },
    &amp;quot;verror&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.10.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/verror/-/verror-1.10.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;assert-plus&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;core-util-is&amp;quot;: &amp;quot;1.0.2&amp;quot;,
        &amp;quot;extsprintf&amp;quot;: &amp;quot;^1.2.0&amp;quot;
      }
    },
    &amp;quot;which&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.3.1&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/which/-/which-1.3.1.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==&amp;quot;,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;isexe&amp;quot;: &amp;quot;^2.0.0&amp;quot;
      }
    },
    &amp;quot;widest-line&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.1.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;string-width&amp;quot;: &amp;quot;^4.0.0&amp;quot;
      }
    },
    &amp;quot;wrappy&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;1.0.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=&amp;quot;
    },
    &amp;quot;write-file-atomic&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;3.0.3&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==&amp;quot;,
      &amp;quot;dev&amp;quot;: true,
      &amp;quot;requires&amp;quot;: {
        &amp;quot;imurmurhash&amp;quot;: &amp;quot;^0.1.4&amp;quot;,
        &amp;quot;is-typedarray&amp;quot;: &amp;quot;^1.0.0&amp;quot;,
        &amp;quot;signal-exit&amp;quot;: &amp;quot;^3.0.2&amp;quot;,
        &amp;quot;typedarray-to-buffer&amp;quot;: &amp;quot;^3.1.5&amp;quot;
      }
    },
    &amp;quot;xdg-basedir&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;4.0.0&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==&amp;quot;,
      &amp;quot;dev&amp;quot;: true
    },
    &amp;quot;xtend&amp;quot;: {
      &amp;quot;version&amp;quot;: &amp;quot;4.0.2&amp;quot;,
      &amp;quot;resolved&amp;quot;: &amp;quot;https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz&amp;quot;,
      &amp;quot;integrity&amp;quot;: &amp;quot;sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==&amp;quot;
    }
  }
}&lt;/pre&gt;
  &lt;h3 id=&quot;tu9l&quot;&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;CDRR&quot;&gt;↪️package.json&lt;/h3&gt;
  &lt;pre id=&quot;F8Zv&quot;&gt;{
  &amp;quot;name&amp;quot;: &amp;quot;telegram-forwarder-bot-agent&amp;quot;,
  &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
  &amp;quot;description&amp;quot;: &amp;quot;Telegram bot that acts as a frontend for all the controls&amp;quot;,
  &amp;quot;main&amp;quot;: &amp;quot;app.js&amp;quot;,
  &amp;quot;scripts&amp;quot;: {
    &amp;quot;start&amp;quot;: &amp;quot;node bot.js&amp;quot;
  },
  &amp;quot;repository&amp;quot;: {
    &amp;quot;type&amp;quot;: &amp;quot;git&amp;quot;,
    &amp;quot;url&amp;quot;: &amp;quot;https://github.com/adityathebe/telegramForwarder.git&amp;quot;
  },
  &amp;quot;keywords&amp;quot;: [],
  &amp;quot;author&amp;quot;: &amp;quot;Aditya Thebe &amp;lt;contact@adityathebe.com&amp;gt; (https://www.adityathebe.com)&amp;quot;,
  &amp;quot;license&amp;quot;: &amp;quot;ISC&amp;quot;,
  &amp;quot;dependencies&amp;quot;: {
    &amp;quot;knex&amp;quot;: &amp;quot;^0.20.15&amp;quot;,
    &amp;quot;node-telegram-bot-api&amp;quot;: &amp;quot;^0.40.0&amp;quot;,
    &amp;quot;pg&amp;quot;: &amp;quot;^8.6.0&amp;quot;,
    &amp;quot;pino&amp;quot;: &amp;quot;^6.5.1&amp;quot;,
    &amp;quot;request&amp;quot;: &amp;quot;^2.88.2&amp;quot;,
    &amp;quot;uuid&amp;quot;: &amp;quot;^8.3.0&amp;quot;
  },
  &amp;quot;devDependencies&amp;quot;: {
    &amp;quot;nodemon&amp;quot;: &amp;quot;^2.0.4&amp;quot;
  }
}&lt;/pre&gt;
  &lt;p id=&quot;4W6y&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;EDPw&quot;&gt;↪️readme.md&lt;/h3&gt;
  &lt;h1 id=&quot;6HP1&quot;&gt;Database&lt;/h1&gt;
  &lt;h3 id=&quot;NXEO&quot;&gt;1. User&lt;/h3&gt;
  &lt;ul id=&quot;eZX4&quot;&gt;
    &lt;li id=&quot;02St&quot;&gt;chat_id* (string)&lt;/li&gt;
    &lt;li id=&quot;WKYU&quot;&gt;username (string)&lt;/li&gt;
    &lt;li id=&quot;x9My&quot;&gt;ref_code (string)&lt;/li&gt;
    &lt;li id=&quot;xupX&quot;&gt;ref_by (string)&lt;/li&gt;
    &lt;li id=&quot;suTm&quot;&gt;premium (Boolean)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;djeW&quot;&gt;2. Redirections&lt;/h3&gt;
  &lt;ul id=&quot;edt5&quot;&gt;
    &lt;li id=&quot;NPbn&quot;&gt;id* (int)&lt;/li&gt;
    &lt;li id=&quot;nCL7&quot;&gt;owner (string)&lt;/li&gt;
    &lt;li id=&quot;R1Wy&quot;&gt;source (string)&lt;/li&gt;
    &lt;li id=&quot;a93B&quot;&gt;source_title (string)&lt;/li&gt;
    &lt;li id=&quot;dBQ4&quot;&gt;destination (string)&lt;/li&gt;
    &lt;li id=&quot;vWlR&quot;&gt;destination_title (string)&lt;/li&gt;
    &lt;li id=&quot;vBV5&quot;&gt;active (boolean);&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;hvgZ&quot;&gt;3. Filters&lt;/h3&gt;
  &lt;ul id=&quot;d1JW&quot;&gt;
    &lt;li id=&quot;0JAD&quot;&gt;id* (int)&lt;/li&gt;
    &lt;li id=&quot;npLZ&quot;&gt;red_id&lt;/li&gt;
    &lt;li id=&quot;5QFD&quot;&gt;name&lt;/li&gt;
    &lt;li id=&quot;QUhS&quot;&gt;state&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h1 id=&quot;fIuO&quot;&gt;Features&lt;/h1&gt;
  &lt;h2 id=&quot;cJ8r&quot;&gt;Free&lt;/h2&gt;
  &lt;ol id=&quot;DSOL&quot;&gt;
    &lt;li id=&quot;l2GJ&quot;&gt;/add -- Adding redirections allows you to setup a automatic routing of messages from any telegram channels to your own telegram channels 10 active redirections&lt;/li&gt;
    &lt;li id=&quot;tdvI&quot;&gt;/activate -- Activate a redirection from your list. &lt;em&gt;Bot does automatic forwarding of messages only for active redirections&lt;/em&gt;&lt;/li&gt;
    &lt;li id=&quot;G2md&quot;&gt;/deactivate -- deactivates a redirection from your list. Bot does not do automatic forwarding of messages for inactive re-directions&lt;/li&gt;
    &lt;li id=&quot;7tCZ&quot;&gt;/list -- get list of redirections with their corresponding redirection IDs&lt;/li&gt;
    &lt;li id=&quot;RoHX&quot;&gt;/remove -- remove the redirection with id REDIRECTION_ID from your redirections list&lt;/li&gt;
    &lt;li id=&quot;8CrE&quot;&gt;/filters -- get information about filters, applied to redirection with id REDIRECTION_ID&lt;/li&gt;
    &lt;li id=&quot;5z6p&quot;&gt;/filter -- use filters allow you to make the bot skip messages that you do not want to be forwarded. Message can also be forwarded, but filter removes photos, documents, animations, stickers, links, hashtags, etc. Please see MultiFeed Bot for all filters it has that I want.&lt;/li&gt;
    &lt;ul id=&quot;cYFi&quot;&gt;
      &lt;li id=&quot;qOxY&quot;&gt;FILTER_NAME = name of the filter&lt;/li&gt;
      &lt;li id=&quot;qxRS&quot;&gt;REDIRECTION_ID = ID of the redirection for which you update the filter’s state&lt;/li&gt;
      &lt;li id=&quot;Bijh&quot;&gt;STATE = state of filter; use on for enabling filter and off for disabling it.&lt;/li&gt;
    &lt;/ul&gt;
    &lt;li id=&quot;4VUS&quot;&gt;/me – gets information about your account (including information about activated paid bot features &amp;amp; invited users). List all PAID features with subscription end date. PAID features may have a different subscription end date, depending on date customer purchased it. Admin has the ability to enable/disable PAID features &amp;amp; change subscription end date for each feature.&lt;/li&gt;
    &lt;li id=&quot;v5VK&quot;&gt;/ref – Share referral link with others and get bonus. +30 day subscription to all PAID features when referral pays for subscription. Assign unique referral link/ID for each subscriber. Admin will know which existing customer had referred the new subscriber. Admin will manually update subscription of subscriber’s account.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h2 id=&quot;HM8M&quot;&gt;Premium&lt;/h2&gt;
  &lt;ol id=&quot;k7rK&quot;&gt;
    &lt;li id=&quot;JOAq&quot;&gt;Use copy-paste mode of forwarding &lt;em&gt;Purpose&lt;/em&gt;: Remove &amp;quot;Forwarded from&amp;quot; header (aka &amp;quot;link to the sourcing channel&amp;quot;) from redirected messages&lt;/li&gt;
    &lt;li id=&quot;vItQ&quot;&gt;Connect Telegram account to Bot &lt;em&gt;Purpose&lt;/em&gt;: Setup redirections from channels you don&amp;#x27;t have an invitation link for, but are a member of, which is the per-requisite for setting up redirections from/to bots, groups, people and transformation feature.&lt;/li&gt;
    &lt;li id=&quot;87rI&quot;&gt;Enable Transformation &lt;em&gt;Purpose&lt;/em&gt;: Replace particular words in messages with your own words. Remove particular words or phrases in messages&lt;/li&gt;
    &lt;li id=&quot;94ZC&quot;&gt;Setup redirections from/to bots, groups, or people each direction&lt;/li&gt;
    &lt;li id=&quot;E3ZJ&quot;&gt;The FREE version only offers up to &lt;strong&gt;10 active redirections&lt;/strong&gt; on your list. I would like the ability for PAID subscribers to buy package of &lt;strong&gt;50 additional redirections&lt;/strong&gt;. Admin has the ability to specify any # of redirections. Limit up to 100,000 redirections. No limit – unlimited would be great to have as well.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h1 id=&quot;HBBq&quot;&gt;WORKFLOW&lt;/h1&gt;
  &lt;ol id=&quot;Kdqj&quot;&gt;
    &lt;li id=&quot;hQlw&quot;&gt;Setup redirections from Bot, Channel (not an Admin), user --&amp;gt; Bot, Channel or user. Will need to Connect Telegram account to Bot for this to work from what I’ve gathered in my Research. Connect to telegram account to Bot also required to enable transformation &amp;amp; copy-paste forwarding feature.&lt;/li&gt;
    &lt;li id=&quot;9GFf&quot;&gt;Enable transformation to replace a particular word from message (e.g. Tier: Gold -&amp;gt; Plan: Platinum)&lt;/li&gt;
    &lt;li id=&quot;fq38&quot;&gt;Enable copy-paste of Forwarding to remove &amp;quot;Forwarded from&amp;quot; header (aka &amp;quot;link to the sourcing channel&amp;quot;) from redirected messages – hiding source&lt;/li&gt;
    &lt;li id=&quot;HKI2&quot;&gt;Apply Filter to remove images, links, hashtags or certain keywords&lt;/li&gt;
    &lt;li id=&quot;Iz60&quot;&gt;Apply Filter to forward a message containing a specific keyword/phrase to specific Bot, Channel or user– redirection.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;ul id=&quot;HA9b&quot;&gt;
    &lt;li id=&quot;T7np&quot;&gt;Example#1: if message contains the keyword/phrase: “Plan: Platinum” -&amp;gt; Channel #1, Bot#1 or user#1.&lt;/li&gt;
    &lt;li id=&quot;ZqPx&quot;&gt;Example#2: if message contains the keyword/phrase: “Plan: Gold” -&amp;gt; Channel#2, Bot#2 or user#2.&lt;/li&gt;
    &lt;li id=&quot;F3kf&quot;&gt;Example#3: if message contains the keyword/phrase: “Plan: Silver” -&amp;gt; Channel#3, Bot#3 or user#3.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h1 id=&quot;yawy&quot;&gt;Filters (/filter)&lt;/h1&gt;
  &lt;p id=&quot;XcD0&quot;&gt;Command format:&lt;/p&gt;
  &lt;p id=&quot;ZTDU&quot;&gt;&lt;code&gt;/filter FILTER_NAME REDIRECTION_ID STATE&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;1FX7&quot;&gt;&lt;strong&gt;Available filters&lt;/strong&gt;:&lt;/p&gt;
  &lt;ul id=&quot;8zcJ&quot;&gt;
    &lt;li id=&quot;vWFJ&quot;&gt;photos&lt;/li&gt;
    &lt;li id=&quot;o8BZ&quot;&gt;documents&lt;/li&gt;
    &lt;li id=&quot;tkit&quot;&gt;audios&lt;/li&gt;
    &lt;li id=&quot;t8QJ&quot;&gt;stickers&lt;/li&gt;
    &lt;li id=&quot;MdEr&quot;&gt;videos&lt;/li&gt;
    &lt;li id=&quot;X5IP&quot;&gt;links&lt;/li&gt;
    &lt;li id=&quot;r14w&quot;&gt;hashtags&lt;/li&gt;
    &lt;li id=&quot;yAz1&quot;&gt;contains&lt;/li&gt;
    &lt;li id=&quot;mqVv&quot;&gt;notContains&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h1 id=&quot;cHeA&quot;&gt;Set commands&lt;/h1&gt;
  &lt;p id=&quot;hSXE&quot;&gt;Message this to @botfather&lt;/p&gt;
  &lt;pre id=&quot;71Wp&quot;&gt;add - Add Redirection
activate - Activate redirection
deactivate - Deactivate redirection
list - List redirection
remove - Remove redirection
filter - Add Filter
filters - Get filter
help - Get help
transform - Add Transformation
transforms - List Transformation for a given redirection
transformrank - Swap rank of transformations for a given redirection
transformremove - Remove transformation&lt;/pre&gt;
  &lt;p id=&quot;yPn3&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;n9Q8&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;DsV0&quot;&gt;↪️wait-for.sh&lt;/h3&gt;
  &lt;pre id=&quot;nkxt&quot;&gt;#!/bin/sh&lt;/pre&gt;
  &lt;pre id=&quot;xdEe&quot;&gt;# The MIT License (MIT)
#
# Copyright (c) 2017 Eficode Oy
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the &amp;quot;Software&amp;quot;), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED &amp;quot;AS IS&amp;quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.&lt;/pre&gt;
  &lt;pre id=&quot;aFSs&quot;&gt;set -- &amp;quot;$@&amp;quot; -- &amp;quot;$TIMEOUT&amp;quot; &amp;quot;$QUIET&amp;quot; &amp;quot;$HOST&amp;quot; &amp;quot;$PORT&amp;quot; &amp;quot;$result&amp;quot;
TIMEOUT=15
QUIET=0&lt;/pre&gt;
  &lt;pre id=&quot;ZCEZ&quot;&gt;echoerr() {
  if [ &amp;quot;$QUIET&amp;quot; -ne 1 ]; then printf &amp;quot;%s\n&amp;quot; &amp;quot;$*&amp;quot; 1&amp;gt;&amp;amp;2; fi
}&lt;/pre&gt;
  &lt;pre id=&quot;Vkdh&quot;&gt;usage() {
  exitcode=&amp;quot;$1&amp;quot;
  cat &amp;lt;&amp;lt; USAGE &amp;gt;&amp;amp;2
Usage:
  $cmdname host:port [-t timeout] [-- command args]
  -q | --quiet                        Do not output any status messages
  -t TIMEOUT | --timeout=timeout      Timeout in seconds, zero for no timeout
  -- COMMAND ARGS                     Execute command with args after the test finishes
USAGE
  exit &amp;quot;$exitcode&amp;quot;
}&lt;/pre&gt;
  &lt;pre id=&quot;1V2A&quot;&gt;wait_for() {
 if ! command -v nc &amp;gt;/dev/null; then
    echoerr &amp;#x27;nc command is missing!&amp;#x27;
    exit 1
  fi&lt;/pre&gt;
  &lt;pre id=&quot;bB3p&quot;&gt;  while :; do
    nc -z &amp;quot;$HOST&amp;quot; &amp;quot;$PORT&amp;quot; &amp;gt; /dev/null 2&amp;gt;&amp;amp;1
    
    result=$?
    if [ $result -eq 0 ] ; then
      if [ $# -gt 6 ] ; then
        for result in $(seq $(($# - 6))); do
          result=$1
          shift
          set -- &amp;quot;$@&amp;quot; &amp;quot;$result&amp;quot;
        done&lt;/pre&gt;
  &lt;pre id=&quot;HMjj&quot;&gt;        TIMEOUT=$2 QUIET=$3 HOST=$4 PORT=$5 result=$6
        shift 6
        exec &amp;quot;$@&amp;quot;
      fi
      exit 0
    fi&lt;/pre&gt;
  &lt;pre id=&quot;CsjT&quot;&gt;    if [ &amp;quot;$TIMEOUT&amp;quot; -le 0 ]; then
      break
    fi
    TIMEOUT=$((TIMEOUT - 1))&lt;/pre&gt;
  &lt;pre id=&quot;tUmj&quot;&gt;    sleep 1
  done
  echo &amp;quot;Operation timed out&amp;quot; &amp;gt;&amp;amp;2
  exit 1
}&lt;/pre&gt;
  &lt;pre id=&quot;kSNk&quot;&gt;while :; do
  case &amp;quot;$1&amp;quot; in
    *:* )
    HOST=$(printf &amp;quot;%s\n&amp;quot; &amp;quot;$1&amp;quot;| cut -d : -f 1)
    PORT=$(printf &amp;quot;%s\n&amp;quot; &amp;quot;$1&amp;quot;| cut -d : -f 2)
    shift 1
    ;;
    -q | --quiet)
    QUIET=1
    shift 1
    ;;
    -q-*)
    QUIET=0
    echoerr &amp;quot;Unknown option: $1&amp;quot;
    usage 1
    ;;
    -q*)
    QUIET=1
    result=$1
    shift 1
    set -- -&amp;quot;${result#-q}&amp;quot; &amp;quot;$@&amp;quot;
    ;;
    -t | --timeout)
    TIMEOUT=&amp;quot;$2&amp;quot;
    shift 2
    ;;
    -t*)
    TIMEOUT=&amp;quot;${1#-t}&amp;quot;
    shift 1
    ;;
    --timeout=*)
    TIMEOUT=&amp;quot;${1#*=}&amp;quot;
    shift 1
    ;;
    --)
    shift
    break
    ;;
    --help)
    usage 0
    ;;
    -*)
    QUIET=0
    echoerr &amp;quot;Unknown option: $1&amp;quot;
    usage 1
    ;;
    *)
    QUIET=0
    echoerr &amp;quot;Unknown argument: $1&amp;quot;
    usage 1
    ;;
  esac
done&lt;/pre&gt;
  &lt;pre id=&quot;ZPZo&quot;&gt;if ! [ &amp;quot;$TIMEOUT&amp;quot; -ge 0 ] 2&amp;gt;/dev/null; then
  echoerr &amp;quot;Error: invalid timeout &amp;#x27;$TIMEOUT&amp;#x27;&amp;quot;
  usage 3
fi&lt;/pre&gt;
  &lt;pre id=&quot;wV7R&quot;&gt;if [ &amp;quot;$HOST&amp;quot; = &amp;quot;&amp;quot; -o &amp;quot;$PORT&amp;quot; = &amp;quot;&amp;quot; ]; then
  echoerr &amp;quot;Error: you need to provide a host and port to test.&amp;quot;
  usage 2
fi&lt;/pre&gt;
  &lt;pre id=&quot;wAJU&quot;&gt;wait_for &amp;quot;$@&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;ni6i&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;P0V7&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;GYPB&quot;&gt;↪️.гитиньоре&lt;/h3&gt;
  &lt;pre id=&quot;fBaR&quot;&gt;bot/node_modules&lt;/pre&gt;
  &lt;pre id=&quot;tYPX&quot;&gt;agent/config/synaptic.py
agent/.venv/
*.db-journal
__pycache__
.env
agent/session/*&lt;/pre&gt;
  &lt;pre id=&quot;O5av&quot;&gt;!.keep
*.db&lt;/pre&gt;
  &lt;p id=&quot;sOaY&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;dlLG&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;8ld9&quot;&gt;↪️docker-compose.yml&lt;/h3&gt;
  &lt;pre id=&quot;bCwO&quot;&gt;version: &amp;#x27;3.1&amp;#x27;&lt;/pre&gt;
  &lt;pre id=&quot;SRQg&quot;&gt;services:
  node-bot:
    build: ./bot
    restart: always
    image: telegram-forwarder-node-bot
    environment:
      AGENT_PORT: 3000
      AGENT_HOSTNAME: python-agent
      TG_API_KEY: &amp;#x27;${TG_API_KEY}&amp;#x27;
      TG_BOT_USERNAME: &amp;#x27;${TG_BOT_USERNAME}&amp;#x27;
      DB_HOST: postgres-db
      NTBA_FIX_319: 1
    depends_on:
      - postgres-db
    command: [&amp;quot;./wait-for.sh&amp;quot;, &amp;quot;postgres-db:5432&amp;quot;, &amp;quot;--&amp;quot;, &amp;quot;node&amp;quot;, &amp;quot;bot.js&amp;quot;]&lt;/pre&gt;
  &lt;pre id=&quot;cyNT&quot;&gt;  python-agent:
    build: &amp;#x27;./agent&amp;#x27;
    restart: always
    image: &amp;#x27;telegram-forwarder-python-agent&amp;#x27;
    depends_on:
      - postgres-db
      - node-bot
    environment:
      API_PORT: 3000
      TG_API_ID: &amp;#x27;${TG_API_ID}&amp;#x27;
      TG_HASH_ID: &amp;#x27;${TG_HASH_ID}&amp;#x27;
      DB_HOST: postgres-db
    ports:
      - &amp;#x27;3000:3000&amp;#x27;
    volumes:
      - &amp;#x27;./agent/session:/telegram-python-agent/session&amp;#x27;&lt;/pre&gt;
  &lt;pre id=&quot;Sd79&quot;&gt;  postgres-db:
    image: postgres:alpine
    environment:
      POSTGRES_PASSWORD: mysecretpassword
      POSTGRES_DB: telegram
    volumes:
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    ports:
      - &amp;#x27;5432:5432&amp;#x27;&lt;/pre&gt;
  &lt;p id=&quot;SIFB&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;cCZ2&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;jmSB&quot;&gt;↪️инициализация.sql&lt;/h3&gt;
  &lt;pre id=&quot;k1wM&quot;&gt;— Use this file to run sql queries on database intialization&lt;/pre&gt;

</content></entry><entry><id>codeonplate:eCDM4jri-dU</id><link rel="alternate" type="text/html" href="https://teletype.in/@codeonplate/eCDM4jri-dU?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=codeonplate"></link><title>VCPlayerBot</title><published>2025-10-18T08:48:46.197Z</published><updated>2025-10-18T08:48:46.197Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img4.teletype.in/files/f4/68/f46806a7-6d09-4608-a5c4-8130af0896fa.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img3.teletype.in/files/63/7c/637c3c73-d4b7-4b58-8e7b-5b84e7cfbe05.png&quot;&gt;Telegram-бот для трансляции видео в голосовом чате Telegram как для групп, так и для каналов. Поддерживает прямые трансляции, видео с YouTube и медиафайлы Telegram. Поддерживает запись трансляций, планирование трансляций и многое другое.</summary><content type="html">
  &lt;p id=&quot;44Hj&quot;&gt;Telegram-бот для трансляции видео в голосовом чате Telegram как для групп, так и для каналов. Поддерживает прямые трансляции, видео с YouTube и медиафайлы Telegram. Поддерживает запись трансляций, планирование трансляций и многое другое.&lt;/p&gt;
  &lt;h2 id=&quot;4sRm&quot;&gt;Переменные Конфигурации:&lt;/h2&gt;
  &lt;h3 id=&quot;huaI&quot;&gt;Обязательные переменные&lt;/h3&gt;
  &lt;ol id=&quot;5tPF&quot;&gt;
    &lt;li id=&quot;ytUu&quot;&gt;&lt;code&gt;API_ID&lt;/code&gt; : Получите с &lt;a href=&quot;https://my.telegram.org/&quot; target=&quot;_blank&quot;&gt;my.telegram.org&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;Apvz&quot;&gt;&lt;code&gt;API_HASH&lt;/code&gt; : Получите на &lt;a href=&quot;https://my.telegram.org/&quot; target=&quot;_blank&quot;&gt;my.telegram.org&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;4WOv&quot;&gt;&lt;code&gt;BOT_TOKEN&lt;/code&gt; : &lt;a href=&quot;https://telegram.dog/BotFather&quot; target=&quot;_blank&quot;&gt;@Botfather&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;CcyD&quot;&gt;&lt;code&gt;SESSION_STRING&lt;/code&gt; : Генерировать отсюда&lt;/li&gt;
    &lt;li id=&quot;o0Ha&quot;&gt;&lt;code&gt;CHAT&lt;/code&gt; : идентификатор канала/группы, в которой бот воспроизводит музыку.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h2 id=&quot;SgaD&quot;&gt;Рекомендуемые Дополнительные переменные&lt;/h2&gt;
  &lt;ol id=&quot;hIAn&quot;&gt;
    &lt;li id=&quot;qodV&quot;&gt;&lt;code&gt;DATABASE_URI&lt;/code&gt;: URL-адрес базы данных MongoDB, полученный из &lt;a href=&quot;https://cloud.mongodb.com/&quot; target=&quot;_blank&quot;&gt;mongodb&lt;/a&gt;. Это необязательный параметр, но его рекомендуется использовать для доступа ко всем функциям.&lt;/li&gt;
    &lt;li id=&quot;H1SL&quot;&gt;&lt;code&gt;HEROKU_API_KEY&lt;/code&gt;: Ваш ключ API Heroku. Получите его &lt;a href=&quot;https://dashboard.heroku.com/account/applications/authorizations/new&quot; target=&quot;_blank&quot;&gt;здесь&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;NZsN&quot;&gt;&lt;code&gt;HEROKU_APP_NAME&lt;/code&gt;: Название вашего приложения Heroku.&lt;/li&gt;
    &lt;li id=&quot;fWru&quot;&gt;&lt;code&gt;FILTERS&lt;/code&gt;: Отфильтруйте результаты поиска по воспроизведению каналов. Воспроизведение каналов означает, что вы можете воспроизвести все файлы на определённом канале с помощью команды /cplay. Текущие фильтры: &lt;code&gt;video document&lt;/code&gt; . Для поиска аудиофайлов используйте &lt;code&gt;video document audio&lt;/code&gt; . Для поиска только видео используйте &lt;code&gt;video&lt;/code&gt; и так далее.&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h3 id=&quot;AsNp&quot;&gt;Необязательные переменные&lt;/h3&gt;
  &lt;ol id=&quot;qPSN&quot;&gt;
    &lt;li id=&quot;BQqA&quot;&gt;&lt;code&gt;LOG_GROUP&lt;/code&gt; : Группа для отправки плейлиста, если CHAT является группой()&lt;/li&gt;
    &lt;li id=&quot;SR6Z&quot;&gt;&lt;code&gt;ADMINS&lt;/code&gt; : идентификаторы пользователей, которые могут использовать команды администратора.&lt;/li&gt;
    &lt;li id=&quot;s6zu&quot;&gt;&lt;code&gt;STARTUP_STREAM&lt;/code&gt; : Это будет транслироваться при запуске и перезапуске бота. Вы можете использовать любой STREAM_URL, прямую ссылку на любое видео или ссылку на прямую трансляцию на Youtube. Вы также можете использовать плейлист на Youtube. Найдите ссылку на Telegram для своего плейлиста в &lt;a href=&quot;https://telegram.dog/DumpPlaylist&quot; target=&quot;_blank&quot;&gt;PlayList Dumb&lt;/a&gt; или получите плейлист в &lt;a href=&quot;https://telegram.dog/GetAPlaylistbot&quot; target=&quot;_blank&quot;&gt;PlayList Extract&lt;/a&gt;. Ссылка на плейлист должна быть в формате &lt;code&gt;https://t.me/DumpPlaylist/xxx&lt;/code&gt;.&lt;/li&gt;
    &lt;li id=&quot;vsbw&quot;&gt;&lt;code&gt;REPLY_MESSAGE&lt;/code&gt; : Ответ тем, кто отправляет сообщения в личные сообщения аккаунта USER. Оставьте поле пустым, если вам не нужна эта функция. (Настраивается через бота, если добавлен mongodb.)&lt;/li&gt;
    &lt;li id=&quot;7tfe&quot;&gt;&lt;code&gt;ADMIN_ONLY&lt;/code&gt; : Передайте &lt;code&gt;True&lt;/code&gt; Если вы хотите, чтобы команда /play была доступна только администраторам &lt;code&gt;CHAT&lt;/code&gt;. По умолчанию /play доступна всем. (Настраивается через бота, если добавлен mongodb.)&lt;/li&gt;
    &lt;li id=&quot;wzCB&quot;&gt;&lt;code&gt;DATABASE_NAME&lt;/code&gt;: Имя базы данных для вашей базы данных MongoDB.&lt;/li&gt;
    &lt;li id=&quot;9xMJ&quot;&gt;&lt;code&gt;SHUFFLE&lt;/code&gt; : Сделайте &lt;code&gt;False&lt;/code&gt; , если не хотите, чтобы плейлисты перемешивались. (Настраивается через бота, если добавлен mongodb.)&lt;/li&gt;
    &lt;li id=&quot;BPOE&quot;&gt;&lt;code&gt;EDIT_TITLE&lt;/code&gt; : Укажите &lt;code&gt;False&lt;/code&gt; , если вы не хотите, чтобы бот менял название видеочата в соответствии с воспроизводимой песней. (Настраивается через бота, если добавлен mongodb.)&lt;/li&gt;
    &lt;li id=&quot;Zn6J&quot;&gt;&lt;code&gt;RECORDING_DUMP&lt;/code&gt; : Идентификатор канала с учетной записью USER в качестве администратора для сброса записей видеочата.&lt;/li&gt;
    &lt;li id=&quot;0JEI&quot;&gt;&lt;code&gt;RECORDING_TITLE&lt;/code&gt;: Пользовательское название для записей видеочатов.&lt;/li&gt;
    &lt;li id=&quot;NwQB&quot;&gt;&lt;code&gt;TIME_ZONE&lt;/code&gt; : Часовой пояс вашей страны, по умолчанию IST&lt;/li&gt;
    &lt;li id=&quot;G962&quot;&gt;&lt;code&gt;IS_VIDEO_RECORD&lt;/code&gt; : Если вы не хотите записывать видео, укажите &lt;code&gt;False&lt;/code&gt; и будет записываться только звук. (Настраивается через бота, если добавлен mongodb.)&lt;/li&gt;
    &lt;li id=&quot;lrMB&quot;&gt;&lt;code&gt;IS_LOOP&lt;/code&gt; ; Сделайте &lt;code&gt;False&lt;/code&gt; , если вам не нужен видеочат 24/7. (Настраивается через бота, если добавлен mongodb.)&lt;/li&gt;
    &lt;li id=&quot;jV2h&quot;&gt;&lt;code&gt;IS_VIDEO&lt;/code&gt; : Сделайте его &lt;code&gt;False&lt;/code&gt;, если хотите использовать плеер как музыкальный проигрыватель без видео. (Настраивается через бота, если добавлен mongodb.)&lt;/li&gt;
    &lt;li id=&quot;yXPl&quot;&gt;&lt;code&gt;PORTRAIT&lt;/code&gt;: Сделайте &lt;code&gt;True&lt;/code&gt; , если хотите, чтобы видеозапись велась в портретном режиме. (Настраивается через бота, если добавлен mongodb.)&lt;/li&gt;
    &lt;li id=&quot;N9Eq&quot;&gt;&lt;code&gt;DELAY&lt;/code&gt; : Выберите ограничение по времени для удаления команд. По умолчанию 10 секунд.&lt;/li&gt;
    &lt;li id=&quot;lecZ&quot;&gt;&lt;code&gt;QUALITY&lt;/code&gt; : Настройте качество видеочата, выбрав один из вариантов: &lt;code&gt;high&lt;/code&gt;, &lt;code&gt;medium&lt;/code&gt;, &lt;code&gt;low&lt;/code&gt; .&lt;/li&gt;
    &lt;li id=&quot;cDS0&quot;&gt;&lt;code&gt;BITRATE&lt;/code&gt; : Битрейт аудио (не рекомендуется изменять).&lt;/li&gt;
    &lt;li id=&quot;SGOJ&quot;&gt;&lt;code&gt;FPS&lt;/code&gt; : Частота кадров воспроизводимого видео (не рекомендуется изменять).&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h2 id=&quot;AcfM&quot;&gt;Требования&lt;/h2&gt;
  &lt;ul id=&quot;jz1D&quot;&gt;
    &lt;li id=&quot;lm5D&quot;&gt;Python 3.8 или выше.&lt;/li&gt;
    &lt;li id=&quot;3d3J&quot;&gt;&lt;a href=&quot;https://www.ffmpeg.org/&quot; target=&quot;_blank&quot;&gt;FFMpeg&lt;/a&gt;.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;iQiR&quot;&gt;Развертывание в Heroku&lt;/h2&gt;
  &lt;p id=&quot;6Elw&quot;&gt;&lt;a href=&quot;https://telegram.dog/XTZ_HerokuBot?start=c3ViaW5wcy9WQ1BsYXllckJvdCBtYWlu&quot; target=&quot;_blank&quot;&gt;https://telegram.dog/XTZ_HerokuBot?start=c3ViaW5wcy9WQ1BsYXllckJvdCBtYWlu&lt;/a&gt;&lt;/p&gt;
  &lt;h2 id=&quot;UAf0&quot;&gt;Развертывание на VPS&lt;/h2&gt;
  &lt;pre id=&quot;HgAP&quot;&gt;git clone https://github.com/subinps/VCPlayerBot
cd VCPlayerBot
pip3 install -r requirements.txt
# install node js
sudo bash install_node.sh
# &amp;lt;Create Variables appropriately (.env [optional])&amp;gt;
python3 main.py&lt;/pre&gt;
  &lt;pre id=&quot;XVxN&quot;&gt;&lt;/pre&gt;
  &lt;h2 id=&quot;gcsW&quot;&gt;Характеристики&lt;/h2&gt;
  &lt;ul id=&quot;xVZ0&quot;&gt;
    &lt;li id=&quot;qxKS&quot;&gt;Список воспроизведения, очередь.&lt;/li&gt;
    &lt;li id=&quot;g3VS&quot;&gt;Нулевое время простоя в игре.&lt;/li&gt;
    &lt;li id=&quot;sMaO&quot;&gt;Поддерживает запись видео.&lt;/li&gt;
    &lt;li id=&quot;CnEr&quot;&gt;Поддерживает планирование голосовых чатов.&lt;/li&gt;
    &lt;li id=&quot;2hf9&quot;&gt;Крутой интерфейс для управления плеером.&lt;/li&gt;
    &lt;li id=&quot;HtDo&quot;&gt;Настраивается под аудио или видео.&lt;/li&gt;
    &lt;li id=&quot;0Rop&quot;&gt;Индивидуальное качество для видеочатов.&lt;/li&gt;
    &lt;li id=&quot;Rwwn&quot;&gt;Поддерживает воспроизведение из плейлиста Youtube.&lt;/li&gt;
    &lt;li id=&quot;jZ56&quot;&gt;Измените заголовок VoiceChat на название текущей воспроизводимой песни.&lt;/li&gt;
    &lt;li id=&quot;gbX0&quot;&gt;Поддерживает прямую трансляцию с YouTube&lt;/li&gt;
    &lt;li id=&quot;wr80&quot;&gt;Воспроизведение файлов из Telegram поддерживается.&lt;/li&gt;
    &lt;li id=&quot;sHzL&quot;&gt;Включает радио, если в плейлисте нет песен.&lt;/li&gt;
    &lt;li id=&quot;NA2z&quot;&gt;Автоматический перезапуск даже в случае перезапуска heroku. (настраивается)&lt;/li&gt;
    &lt;li id=&quot;bstV&quot;&gt;Поддержка экспорта и импорта списков воспроизведения.&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;AbLr&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;Qw5C&quot;&gt;Примечание&lt;/h3&gt;
  &lt;p id=&quot;23S4&quot;&gt;&lt;a href=&quot;https://telegram.dog/subin_works/203&quot; target=&quot;_blank&quot;&gt;Примечание для так называемого разработчика&lt;/a&gt;:&lt;/p&gt;
  &lt;p id=&quot;LTnV&quot;&gt;Если вы скопируете этот код, отредактируете несколько строк и выпустите версию V.x или &lt;a href=&quot;https://telegram.dog/subin_works/204&quot; target=&quot;_blank&quot;&gt;альфа&lt;/a&gt;, бета, гамма-ветки вашего репозитория, это не сделает вас разработчиком. Форкните репозиторий и редактируйте его в соответствии со своими потребностями.&lt;/p&gt;
  &lt;p id=&quot;KzJA&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;sAht&quot;&gt;Код:&lt;/h2&gt;
  &lt;h2 id=&quot;01pF&quot;&gt;✉Плагины&lt;/h2&gt;
  &lt;h3 id=&quot;WdXs&quot;&gt;       ↪️ callback.py&lt;/h3&gt;
  &lt;pre id=&quot;jOWO&quot;&gt;#!/usr/bin/env python3
# Copyright (C) @subinps
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.&lt;/pre&gt;
  &lt;pre id=&quot;xLot&quot;&gt;# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.&lt;/pre&gt;
  &lt;pre id=&quot;2F7s&quot;&gt;# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see &amp;lt;https://www.gnu.org/licenses/&amp;gt;.&lt;/pre&gt;
  &lt;pre id=&quot;5Fjw&quot;&gt;from utils import LOGGER
from pyrogram import Client
from contextlib import suppress
from config import Config
from asyncio import sleep
import datetime
import pytz
import calendar
from utils import (
    cancel_all_schedules,
    delete_messages,
    get_admins, 
    get_buttons, 
    get_playlist_str,
    leave_call, 
    mute, 
    pause,
    recorder_settings, 
    restart, 
    restart_playout, 
    resume,
    schedule_a_play, 
    seek_file, 
    set_config, 
    settings_panel, 
    shuffle_playlist, 
    skip,
    start_record_stream,
    stop_recording,
    sync_to_db, 
    unmute,
    volume,
    volume_buttons
    )
from pyrogram.types import (
    InlineKeyboardMarkup, 
    InlineKeyboardButton, 
    CallbackQuery
)
from pyrogram.errors import (
    MessageNotModified,
    MessageIdInvalid,
    QueryIdInvalid
)
from pyrogram.types import (
    InlineKeyboardButton, 
    InlineKeyboardMarkup
)&lt;/pre&gt;
  &lt;pre id=&quot;HukN&quot;&gt;IST = pytz.timezone(Config.TIME_ZONE)&lt;/pre&gt;
  &lt;pre id=&quot;vqtD&quot;&gt;@Client.on_callback_query()
async def cb_handler(client: Client, query: CallbackQuery):
    with suppress(MessageIdInvalid, MessageNotModified, QueryIdInvalid):
        admins = await get_admins(Config.CHAT)
        if query.data.startswith(&amp;quot;info&amp;quot;):
            me, you = query.data.split(&amp;quot;_&amp;quot;)
            text=&amp;quot;Join @subin_works&amp;quot;
            if you == &amp;quot;volume&amp;quot;:
                await query.answer()
                await query.message.edit_reply_markup(reply_markup=await volume_buttons())
                return
            if you == &amp;quot;player&amp;quot;:
                if not Config.CALL_STATUS:
                    return await query.answer(&amp;quot;Not Playing anything.&amp;quot;, show_alert=True)
                await query.message.edit_reply_markup(reply_markup=await get_buttons())
                await query.answer()
                return
            if you == &amp;quot;video&amp;quot;:
                text=&amp;quot;Toggle your bot to Video / Audio Player.&amp;quot;
            elif you == &amp;quot;shuffle&amp;quot;:
                text=&amp;quot;Enable or disable auto playlist shuffling&amp;quot;
            elif you == &amp;quot;admin&amp;quot;:
                text=&amp;quot;Enable to restrict the play command only for admins.&amp;quot;
            elif you == &amp;quot;mode&amp;quot;:
                text=&amp;quot;Enabling Non- stop playback will make the player running 24 / 7 and automatic startup when restarting. &amp;quot;
            elif you == &amp;quot;title&amp;quot;:
                text=&amp;quot;Enable to edit the VideoChat title to Current playing song&amp;#x27;s title.&amp;quot;
            elif you == &amp;quot;reply&amp;quot;:
                text=&amp;quot;Choose whether to auto-reply messaged for userbot. &amp;quot;
            elif you == &amp;quot;videorecord&amp;quot;:
                text = &amp;quot;Enable to record both video and audio, if disabled only audio will be recorded.&amp;quot;
            elif you == &amp;quot;videodimension&amp;quot;:
                text = &amp;quot;Choose the recording video&amp;#x27;s dimensions&amp;quot;
            elif you == &amp;quot;rectitle&amp;quot;:
                text = &amp;quot;A custom title for your chat recordings, Use /rtitle command to set a title&amp;quot;
            elif you == &amp;quot;recdumb&amp;quot;:
                text = &amp;quot;A channel to which all the recordings are forwarded. Make sure The User account is admin over there. Set one using /env or /config.&amp;quot;
            await query.answer(text=text, show_alert=True)
            return&lt;/pre&gt;
  &lt;pre id=&quot;HqTb&quot;&gt;
        elif query.data.startswith(&amp;quot;help&amp;quot;):
            if query.message.chat.type != &amp;quot;private&amp;quot; and query.message.reply_to_message.from_user is None:
                return await query.answer(&amp;quot;I cant help you here, since you are an anonymous admin, message me in private chat.&amp;quot;, show_alert=True)
            elif query.message.chat.type != &amp;quot;private&amp;quot; and query.from_user.id != query.message.reply_to_message.from_user.id:
                return await query.answer(&amp;quot;Okda&amp;quot;, show_alert=True)
            me, nyav = query.data.split(&amp;quot;_&amp;quot;)
            back=InlineKeyboardMarkup(
                [
                    [
                        InlineKeyboardButton(&amp;quot;Back&amp;quot;, callback_data=&amp;quot;help_main&amp;quot;),
                        InlineKeyboardButton(&amp;quot;Close&amp;quot;, callback_data=&amp;quot;close&amp;quot;),
                    ],
                ]
                )
            if nyav == &amp;#x27;main&amp;#x27;:
                reply_markup=InlineKeyboardMarkup(
                    [
                        [
                            InlineKeyboardButton(f&amp;quot;Play&amp;quot;, callback_data=&amp;#x27;help_play&amp;#x27;),
                            InlineKeyboardButton(f&amp;quot;Settings&amp;quot;, callback_data=f&amp;quot;help_settings&amp;quot;),
                            InlineKeyboardButton(f&amp;quot;Recording&amp;quot;, callback_data=&amp;#x27;help_record&amp;#x27;),
                        ],
                        [
                            InlineKeyboardButton(&amp;quot;Scheduling&amp;quot;, callback_data=&amp;quot;help_schedule&amp;quot;),
                            InlineKeyboardButton(&amp;quot;Controling&amp;quot;, callback_data=&amp;#x27;help_control&amp;#x27;),
                            InlineKeyboardButton(&amp;quot;Admins&amp;quot;, callback_data=&amp;quot;help_admin&amp;quot;),
                        ],
                        [
                            InlineKeyboardButton(f&amp;quot;Misc&amp;quot;, callback_data=&amp;#x27;help_misc&amp;#x27;),
                            InlineKeyboardButton(&amp;quot;Config Vars&amp;quot;, callback_data=&amp;#x27;help_env&amp;#x27;),
                            InlineKeyboardButton(&amp;quot;Close&amp;quot;, callback_data=&amp;quot;close&amp;quot;),
                        ],
                    ]
                    )
                await query.message.edit(&amp;quot;Showing help menu, Choose from the below options.&amp;quot;, reply_markup=reply_markup, disable_web_page_preview=True)
            elif nyav == &amp;#x27;play&amp;#x27;:
                await query.message.edit(Config.PLAY_HELP, reply_markup=back, disable_web_page_preview=True)
            elif nyav == &amp;#x27;settings&amp;#x27;:
                await query.message.edit(Config.SETTINGS_HELP, reply_markup=back, disable_web_page_preview=True)
            elif nyav == &amp;#x27;schedule&amp;#x27;:
                await query.message.edit(Config.SCHEDULER_HELP, reply_markup=back, disable_web_page_preview=True)
            elif nyav == &amp;#x27;control&amp;#x27;:
                await query.message.edit(Config.CONTROL_HELP, reply_markup=back, disable_web_page_preview=True)
            elif nyav == &amp;#x27;admin&amp;#x27;:
                await query.message.edit(Config.ADMIN_HELP, reply_markup=back, disable_web_page_preview=True)
            elif nyav == &amp;#x27;misc&amp;#x27;:
                await query.message.edit(Config.MISC_HELP, reply_markup=back, disable_web_page_preview=True)
            elif nyav == &amp;#x27;record&amp;#x27;:
                await query.message.edit(Config.RECORDER_HELP, reply_markup=back, disable_web_page_preview=True)
            elif nyav == &amp;#x27;env&amp;#x27;:
                await query.message.edit(Config.ENV_HELP, reply_markup=back, disable_web_page_preview=True)
            return
            
        if not query.from_user.id in admins:
            await query.answer(
                &amp;quot;😒 Played Joji.mp3&amp;quot;,
                show_alert=True
                )
            return
        #scheduler stuffs
        if query.data.startswith(&amp;quot;sch&amp;quot;):
            if query.message.chat.type != &amp;quot;private&amp;quot; and query.message.reply_to_message.from_user is None:
                return await query.answer(&amp;quot;You cant use scheduling here, since you are an anonymous admin. Schedule from private chat.&amp;quot;, show_alert=True)
            if query.message.chat.type != &amp;quot;private&amp;quot; and query.from_user.id != query.message.reply_to_message.from_user.id:
                return await query.answer(&amp;quot;Okda&amp;quot;, show_alert=True)
            data = query.data
            today = datetime.datetime.now(IST)
            smonth=today.strftime(&amp;quot;%B&amp;quot;)
            obj = calendar.Calendar()
            thisday = today.day
            year = today.year
            month = today.month
            if data.startswith(&amp;quot;sch_month&amp;quot;):
                none, none , yea_r, month_, day = data.split(&amp;quot;_&amp;quot;)
                if yea_r == &amp;quot;choose&amp;quot;:
                    year=int(year)
                    months = [&amp;quot;January&amp;quot;, &amp;quot;February&amp;quot;, &amp;quot;March&amp;quot;, &amp;quot;April&amp;quot;, &amp;quot;May&amp;quot;, &amp;quot;June&amp;quot;, &amp;quot;July&amp;quot;, &amp;quot;August&amp;quot;, &amp;quot;September&amp;quot;, &amp;quot;October&amp;quot;, &amp;quot;November&amp;quot;, &amp;quot;December&amp;quot;]
                    button=[]
                    button_=[]
                    k=0
                    for month in months:
                        k+=1
                        year_ = year
                        if k &amp;lt; int(today.month):
                            year_ += 1
                            button_.append([InlineKeyboardButton(text=f&amp;quot;{str(month)}  {str(year_)}&amp;quot;,callback_data=f&amp;quot;sch_showdate_{year_}_{k}&amp;quot;)])
                        else:
                            button.append([InlineKeyboardButton(text=f&amp;quot;{str(month)}  {str(year_)}&amp;quot;,callback_data=f&amp;quot;sch_showdate_{year_}_{k}&amp;quot;)])
                    button = button + button_
                    button.append([InlineKeyboardButton(&amp;quot;Close&amp;quot;, callback_data=&amp;quot;schclose&amp;quot;)])
                    await query.message.edit(&amp;quot;Now Choose the month to schedule a voicechatㅤ ㅤㅤ&amp;quot;, reply_markup=InlineKeyboardMarkup(button))
                elif day == &amp;quot;none&amp;quot;:
                    return
                else:
                    year = int(yea_r)
                    month = int(month_)
                    date = int(day)
                    datetime_object = datetime.datetime.strptime(str(month), &amp;quot;%m&amp;quot;)
                    smonth = datetime_object.strftime(&amp;quot;%B&amp;quot;)
                    button=[]
                    if year == today.year and month == today.month and date == today.day:
                        now = today.hour
                    else:
                        now=0
                    l = list()
                    for i in range(now, 24):
                        l.append(i)
                    splited=[l[i:i + 6] for i in range(0, len(l), 6)]
                    for i in splited:
                        k=[]
                        for d in i:
                            k.append(InlineKeyboardButton(text=f&amp;quot;{d}&amp;quot;,callback_data=f&amp;quot;sch_day_{year}_{month}_{date}_{d}&amp;quot;))
                        button.append(k)
                    if month == today.month and date &amp;lt; today.day and year==today.year+1:
                        pyear=year-1
                    else:
                        pyear=year
                    button.append([InlineKeyboardButton(&amp;quot;Back&amp;quot;, callback_data=f&amp;quot;sch_showdate_{pyear}_{month}&amp;quot;), InlineKeyboardButton(&amp;quot;Close&amp;quot;, callback_data=&amp;quot;schclose&amp;quot;)])
                    await query.message.edit(f&amp;quot;Choose the hour of {date} {smonth} {year} to schedule  a voicechat.&amp;quot;, reply_markup=InlineKeyboardMarkup(button))&lt;/pre&gt;
  &lt;pre id=&quot;UaDj&quot;&gt;            elif data.startswith(&amp;quot;sch_day&amp;quot;):
                none, none, year, month, day, hour = data.split(&amp;quot;_&amp;quot;)
                year = int(year)
                month = int(month)
                day = int(day)
                hour = int(hour)
                datetime_object = datetime.datetime.strptime(str(month), &amp;quot;%m&amp;quot;)
                smonth = datetime_object.strftime(&amp;quot;%B&amp;quot;)
                if year == today.year and month == today.month and day == today.day and hour == today.hour:
                    now=today.minute
                else:
                    now=0
                button=[]
                l = list()
                for i in range(now, 60):
                    l.append(i)
                for i in range(0, len(l), 6):
                    chunk = l[i:i + 6]
                    k=[]
                    for d in chunk:
                        k.append(InlineKeyboardButton(text=f&amp;quot;{d}&amp;quot;,callback_data=f&amp;quot;sch_minute_{year}_{month}_{day}_{hour}_{d}&amp;quot;))
                    button.append(k)
                button.append([InlineKeyboardButton(&amp;quot;Back&amp;quot;, callback_data=f&amp;quot;sch_month_{year}_{month}_{day}&amp;quot;), InlineKeyboardButton(&amp;quot;Close&amp;quot;, callback_data=&amp;quot;schclose&amp;quot;)])
                await query.message.edit(f&amp;quot;Choose minute of {hour}th hour on {day} {smonth} {year} to schedule Voicechat.&amp;quot;, reply_markup=InlineKeyboardMarkup(button))&lt;/pre&gt;
  &lt;pre id=&quot;WCgH&quot;&gt;            elif data.startswith(&amp;quot;sch_minute&amp;quot;):
                none, none, year, month, day, hour, minute = data.split(&amp;quot;_&amp;quot;)
                year = int(year)
                month = int(month)
                day = int(day)
                hour = int(hour)
                minute = int(minute)
                datetime_object = datetime.datetime.strptime(str(month), &amp;quot;%m&amp;quot;)
                smonth = datetime_object.strftime(&amp;quot;%B&amp;quot;)
                if year == today.year and month == today.month and day == today.day and hour == today.hour and minute &amp;lt;= today.minute:
                    await query.answer(&amp;quot;I dont have a timemachine to go to past!!!.&amp;quot;)
                    return 
                final=f&amp;quot;{day}th {smonth} {year} at {hour}:{minute}&amp;quot;
                button=[
                    [
                        InlineKeyboardButton(&amp;quot;Confirm&amp;quot;, callback_data=f&amp;quot;schconfirm_{year}-{month}-{day} {hour}:{minute}&amp;quot;),
                        InlineKeyboardButton(&amp;quot;Back&amp;quot;, callback_data=f&amp;quot;sch_day_{year}_{month}_{day}_{hour}&amp;quot;)
                    ],
                    [
                        InlineKeyboardButton(&amp;quot;Close&amp;quot;, callback_data=&amp;quot;schclose&amp;quot;)
                    ]
                ]
                data=Config.SCHEDULED_STREAM.get(f&amp;quot;{query.message.chat.id}_{query.message.message_id}&amp;quot;)
                if not data:
                    await query.answer(&amp;quot;This schedule is expired&amp;quot;, show_alert=True)
                if data[&amp;#x27;3&amp;#x27;] == &amp;quot;telegram&amp;quot;:
                    title=data[&amp;#x27;1&amp;#x27;]
                else:
                    title=f&amp;quot;[{data[&amp;#x27;1&amp;#x27;]}]({data[&amp;#x27;2&amp;#x27;]})&amp;quot;
                await query.message.edit(f&amp;quot;Your Stream {title} is now scheduled to start on {final}\n\nClick Confirm to confirm the time.&amp;quot;, reply_markup=InlineKeyboardMarkup(button), disable_web_page_preview=True)                &lt;/pre&gt;
  &lt;pre id=&quot;9Kxi&quot;&gt;            elif data.startswith(&amp;quot;sch_showdate&amp;quot;):
                tyear=year
                none, none, year, month = data.split(&amp;quot;_&amp;quot;)
                datetime_object = datetime.datetime.strptime(month, &amp;quot;%m&amp;quot;)
                thissmonth = datetime_object.strftime(&amp;quot;%B&amp;quot;)
                obj = calendar.Calendar()
                thisday = today.day
                year = int(year)
                month = int(month)
                m=obj.monthdayscalendar(year, month)
                button=[]
                button.append([InlineKeyboardButton(text=f&amp;quot;{str(thissmonth)}  {str(year)}&amp;quot;,callback_data=f&amp;quot;sch_month_choose_none_none&amp;quot;)])
                days=[&amp;quot;Mon&amp;quot;, &amp;quot;Tues&amp;quot;, &amp;quot;Wed&amp;quot;, &amp;quot;Thu&amp;quot;, &amp;quot;Fri&amp;quot;, &amp;quot;Sat&amp;quot;, &amp;quot;Sun&amp;quot;]
                f=[]
                for day in days:
                    f.append(InlineKeyboardButton(text=f&amp;quot;{day}&amp;quot;,callback_data=f&amp;quot;day_info_none&amp;quot;))
                button.append(f)
                for one in m:
                    f=[]
                    for d in one:
                        year_=year
                        if year==today.year and month == today.month and d &amp;lt; int(today.day):
                            year_ += 1
                        if d == 0:
                            k=&amp;quot;\u2063&amp;quot;
                            d=&amp;quot;none&amp;quot;
                        else:
                            k=d
                        f.append(InlineKeyboardButton(text=f&amp;quot;{k}&amp;quot;,callback_data=f&amp;quot;sch_month_{year_}_{month}_{d}&amp;quot;))
                    button.append(f)
                button.append([InlineKeyboardButton(&amp;quot;Close&amp;quot;, callback_data=&amp;quot;schclose&amp;quot;)])
                await query.message.edit(f&amp;quot;Choose the day of the month you want to schedule the voicechat.\nToday is {thisday} {smonth} {tyear}. Chooosing a date preceeding today will be considered as next year {year+1}&amp;quot;, reply_markup=InlineKeyboardMarkup(button))&lt;/pre&gt;
  &lt;pre id=&quot;yyFY&quot;&gt;            elif data.startswith(&amp;quot;schconfirm&amp;quot;):
                none, date = data.split(&amp;quot;_&amp;quot;)
                date = datetime.datetime.strptime(date, &amp;#x27;%Y-%m-%d %H:%M&amp;#x27;)
                local_dt = IST.localize(date, is_dst=None)
                utc_dt = local_dt.astimezone(pytz.utc).replace(tzinfo=None)
                job_id=f&amp;quot;{query.message.chat.id}_{query.message.message_id}&amp;quot;
                Config.SCHEDULE_LIST.append({&amp;quot;job_id&amp;quot;:job_id, &amp;quot;date&amp;quot;:utc_dt})
                Config.SCHEDULE_LIST = sorted(Config.SCHEDULE_LIST, key=lambda k: k[&amp;#x27;date&amp;#x27;])
                await schedule_a_play(job_id, utc_dt)
                await query.message.edit(f&amp;quot;Succesfully scheduled to stream on &amp;lt;code&amp;gt; {date.strftime(&amp;#x27;%b %d %Y, %I:%M %p&amp;#x27;)} &amp;lt;/code&amp;gt;&amp;quot;)
                await delete_messages([query.message, query.message.reply_to_message])
                
            elif query.data == &amp;#x27;schcancelall&amp;#x27;:
                await cancel_all_schedules()
                await query.message.edit(&amp;quot;All Scheduled Streams are cancelled succesfully.&amp;quot;)&lt;/pre&gt;
  &lt;pre id=&quot;9D6Q&quot;&gt;            elif query.data == &amp;quot;schcancel&amp;quot;:
                buttons = [
                    [
                        InlineKeyboardButton(&amp;#x27;Yes, Iam Sure!!&amp;#x27;, callback_data=&amp;#x27;schcancelall&amp;#x27;),
                        InlineKeyboardButton(&amp;#x27;No&amp;#x27;, callback_data=&amp;#x27;schclose&amp;#x27;),
                    ]
                ]
                await query.message.edit(&amp;quot;Are you sure that you want to cancel all the scheduled streams?&amp;quot;, reply_markup=InlineKeyboardMarkup(buttons))
            elif data == &amp;quot;schclose&amp;quot;:
                await query.answer(&amp;quot;Menu Closed&amp;quot;)
                await query.message.delete()
                await query.message.reply_to_message.delete()&lt;/pre&gt;
  &lt;pre id=&quot;Hupu&quot;&gt;        elif query.data == &amp;quot;shuffle&amp;quot;:
            if not Config.playlist:
                await query.answer(&amp;quot;Playlist is empty.&amp;quot;, show_alert=True)
                return
            await shuffle_playlist()
            await query.answer(&amp;quot;Playlist shuffled.&amp;quot;)
            await sleep(1)        
            await query.message.edit_reply_markup(reply_markup=await get_buttons())
    &lt;/pre&gt;
  &lt;pre id=&quot;Ty7Q&quot;&gt;        elif query.data.lower() == &amp;quot;pause&amp;quot;:
            if Config.PAUSE:
                await query.answer(&amp;quot;Already Paused&amp;quot;, show_alert=True)
            else:
                await pause()
                await query.answer(&amp;quot;Stream Paused&amp;quot;)
                await sleep(1)&lt;/pre&gt;
  &lt;pre id=&quot;5WNJ&quot;&gt;            await query.message.edit_reply_markup(reply_markup=await get_buttons())
 
        
        elif query.data.lower() == &amp;quot;resume&amp;quot;:   
            if not Config.PAUSE:
                await query.answer(&amp;quot;Nothing Paused to resume&amp;quot;, show_alert=True)
            else:
                await resume()
                await query.answer(&amp;quot;Redumed the stream&amp;quot;)
                await sleep(1)
            await query.message.edit_reply_markup(reply_markup=await get_buttons())
          
        elif query.data==&amp;quot;skip&amp;quot;: 
            if not Config.playlist:
                await query.answer(&amp;quot;No songs in playlist&amp;quot;, show_alert=True)
            else:
                await query.answer(&amp;quot;Trying to skip from playlist.&amp;quot;)
                await skip()
                await sleep(1)
            if Config.playlist:
                title=f&amp;quot;&amp;lt;b&amp;gt;{Config.playlist[0][1]}&amp;lt;/b&amp;gt;\nㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ&amp;quot;
            elif Config.STREAM_LINK:
                title=f&amp;quot;&amp;lt;b&amp;gt;Stream Using [Url]({Config.DATA[&amp;#x27;FILE_DATA&amp;#x27;][&amp;#x27;file&amp;#x27;]})&amp;lt;/b&amp;gt;ㅤ  ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ&amp;quot;
            else:
                title=f&amp;quot;&amp;lt;b&amp;gt;Streaming Startup [stream]({Config.STREAM_URL})&amp;lt;/b&amp;gt; ㅤ ㅤ  ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ&amp;quot;
            await query.message.edit(f&amp;quot;&amp;lt;b&amp;gt;{title}&amp;lt;/b&amp;gt;&amp;quot;,
                disable_web_page_preview=True,
                reply_markup=await get_buttons()
            )&lt;/pre&gt;
  &lt;pre id=&quot;f021&quot;&gt;        elif query.data==&amp;quot;replay&amp;quot;:
            if not Config.playlist:
                await query.answer(&amp;quot;No songs in playlist&amp;quot;, show_alert=True)
            else:
                await query.answer(&amp;quot;trying to restart player&amp;quot;)
                await restart_playout()
                await sleep(1)
            await query.message.edit_reply_markup(reply_markup=await get_buttons())&lt;/pre&gt;
  &lt;pre id=&quot;unq7&quot;&gt;
        elif query.data.lower() == &amp;quot;mute&amp;quot;:
            if Config.MUTED:
                await unmute()
                await query.answer(&amp;quot;Unmuted stream&amp;quot;)
            else:
                await mute()
                await query.answer(&amp;quot;Muted stream&amp;quot;)
            await sleep(1)
            await query.message.edit_reply_markup(reply_markup=await volume_buttons())&lt;/pre&gt;
  &lt;pre id=&quot;9Oi7&quot;&gt;        elif query.data.lower() == &amp;#x27;seek&amp;#x27;:
            if not Config.CALL_STATUS:
                return await query.answer(&amp;quot;Not Playing anything.&amp;quot;, show_alert=True)
            #if not (Config.playlist or Config.STREAM_LINK):
                #return await query.answer(&amp;quot;Startup stream cant be seeked.&amp;quot;, show_alert=True)
            await query.answer(&amp;quot;trying to seek.&amp;quot;)
            data=Config.DATA.get(&amp;#x27;FILE_DATA&amp;#x27;)
            if not data.get(&amp;#x27;dur&amp;#x27;, 0) or \
                data.get(&amp;#x27;dur&amp;#x27;) == 0:
                return await query.answer(&amp;quot;This is a live stream and cannot be seeked.&amp;quot;, show_alert=True)
            k, reply = await seek_file(10)
            if k == False:
                return await query.answer(reply, show_alert=True)
            await query.message.edit_reply_markup(reply_markup=await get_buttons())&lt;/pre&gt;
  &lt;pre id=&quot;5G8c&quot;&gt;        elif query.data.lower() == &amp;#x27;rewind&amp;#x27;:
            if not Config.CALL_STATUS:
                return await query.answer(&amp;quot;Not Playing anything.&amp;quot;, show_alert=True)
            #if not (Config.playlist or Config.STREAM_LINK):
                #return await query.answer(&amp;quot;Startup stream cant be seeked.&amp;quot;, show_alert=True)
            await query.answer(&amp;quot;trying to rewind.&amp;quot;)
            data=Config.DATA.get(&amp;#x27;FILE_DATA&amp;#x27;)
            if not data.get(&amp;#x27;dur&amp;#x27;, 0) or \
                data.get(&amp;#x27;dur&amp;#x27;) == 0:
                return await query.answer(&amp;quot;This is a live stream and cannot be seeked.&amp;quot;, show_alert=True)
            k, reply = await seek_file(-10)
            if k == False:
                return await query.answer(reply, show_alert=True)
            await query.message.edit_reply_markup(reply_markup=await get_buttons())&lt;/pre&gt;
  &lt;pre id=&quot;L4iA&quot;&gt;    
        elif query.data == &amp;#x27;restart&amp;#x27;:
            if not Config.CALL_STATUS:
                if not Config.playlist:
                    await query.answer(&amp;quot;Player is empty, starting STARTUP_STREAM.&amp;quot;)
                else:
                    await query.answer(&amp;#x27;Resuming the playlist&amp;#x27;)
            await query.answer(&amp;quot;Restrating the player&amp;quot;)
            await restart()
            await query.message.edit(text=await get_playlist_str(), reply_markup=await get_buttons(), disable_web_page_preview=True)&lt;/pre&gt;
  &lt;pre id=&quot;b9as&quot;&gt;        elif query.data.startswith(&amp;quot;volume&amp;quot;):
            me, you = query.data.split(&amp;quot;_&amp;quot;)  
            if you == &amp;quot;main&amp;quot;:
                await query.message.edit_reply_markup(reply_markup=await volume_buttons())
            if you == &amp;quot;add&amp;quot;:
                if 190 &amp;lt;= Config.VOLUME &amp;lt;=200:
                    vol=200 
                else:
                    vol=Config.VOLUME+10
                if not (1 &amp;lt;= vol &amp;lt;= 200):
                    return await query.answer(&amp;quot;Only 1-200 range accepted.&amp;quot;)
                await volume(vol)
                Config.VOLUME=vol
                await query.message.edit_reply_markup(reply_markup=await volume_buttons())
            elif you == &amp;quot;less&amp;quot;:
                if 1 &amp;lt;= Config.VOLUME &amp;lt;=10:
                    vol=1
                else:
                    vol=Config.VOLUME-10
                if not (1 &amp;lt;= vol &amp;lt;= 200):
                    return await query.answer(&amp;quot;Only 1-200 range accepted.&amp;quot;)
                await volume(vol)
                Config.VOLUME=vol
                await query.message.edit_reply_markup(reply_markup=await volume_buttons())
            elif you == &amp;quot;back&amp;quot;:
                await query.message.edit_reply_markup(reply_markup=await get_buttons())&lt;/pre&gt;
  &lt;pre id=&quot;6rqE&quot;&gt;
        elif query.data in [&amp;quot;is_loop&amp;quot;, &amp;quot;is_video&amp;quot;, &amp;quot;admin_only&amp;quot;, &amp;quot;edit_title&amp;quot;, &amp;quot;set_shuffle&amp;quot;, &amp;quot;reply_msg&amp;quot;, &amp;quot;set_new_chat&amp;quot;, &amp;quot;record&amp;quot;, &amp;quot;record_video&amp;quot;, &amp;quot;record_dim&amp;quot;]:
            if query.data == &amp;quot;is_loop&amp;quot;:
                Config.IS_LOOP = set_config(Config.IS_LOOP)
                await query.message.edit_reply_markup(reply_markup=await settings_panel())
  
            elif query.data == &amp;quot;is_video&amp;quot;:
                Config.IS_VIDEO = set_config(Config.IS_VIDEO)
                await query.message.edit_reply_markup(reply_markup=await settings_panel())
                data=Config.DATA.get(&amp;#x27;FILE_DATA&amp;#x27;)
                if not data \
                    or data.get(&amp;#x27;dur&amp;#x27;, 0) == 0:
                    await restart_playout()
                    return
                k, reply = await seek_file(0)
                if k == False:
                    await restart_playout()&lt;/pre&gt;
  &lt;pre id=&quot;YG0u&quot;&gt;            elif query.data == &amp;quot;admin_only&amp;quot;:
                Config.ADMIN_ONLY = set_config(Config.ADMIN_ONLY)
                await query.message.edit_reply_markup(reply_markup=await settings_panel())
        
            elif query.data == &amp;quot;edit_title&amp;quot;:
                Config.EDIT_TITLE = set_config(Config.EDIT_TITLE)
                await query.message.edit_reply_markup(reply_markup=await settings_panel())
        
            elif query.data == &amp;quot;set_shuffle&amp;quot;:
                Config.SHUFFLE = set_config(Config.SHUFFLE)
                await query.message.edit_reply_markup(reply_markup=await settings_panel())
        
            elif query.data == &amp;quot;reply_msg&amp;quot;:
                Config.REPLY_PM = set_config(Config.REPLY_PM)
                await query.message.edit_reply_markup(reply_markup=await settings_panel())
        
            elif query.data == &amp;quot;record_dim&amp;quot;:
                if not Config.IS_VIDEO_RECORD:
                    return await query.answer(&amp;quot;This cant be used for audio recordings&amp;quot;)
                Config.PORTRAIT=set_config(Config.PORTRAIT)
                await query.message.edit_reply_markup(reply_markup=(await recorder_settings()))
            elif query.data == &amp;#x27;record_video&amp;#x27;:
                Config.IS_VIDEO_RECORD=set_config(Config.IS_VIDEO_RECORD)
                await query.message.edit_reply_markup(reply_markup=(await recorder_settings()))&lt;/pre&gt;
  &lt;pre id=&quot;V3Np&quot;&gt;            elif query.data == &amp;#x27;record&amp;#x27;:
                if Config.IS_RECORDING:
                    k, msg = await stop_recording()
                    if k == False:
                        await query.answer(msg, show_alert=True)
                    else:
                        await query.answer(&amp;quot;Recording Stopped&amp;quot;)
                else:
                    k, msg = await start_record_stream()
                    if k == False:
                        await query.answer(msg, show_alert=True)
                    else:
                        await query.answer(&amp;quot;Recording started&amp;quot;)
                await query.message.edit_reply_markup(reply_markup=(await recorder_settings()))&lt;/pre&gt;
  &lt;pre id=&quot;27DL&quot;&gt;            elif query.data == &amp;quot;set_new_chat&amp;quot;:
                if query.from_user is None:
                    return await query.answer(&amp;quot;You cant do scheduling here, since you are an anonymous admin. Schedule from private chat.&amp;quot;, show_alert=True)
                if query.from_user.id in Config.SUDO:
                    await query.answer(&amp;quot;Setting up new CHAT&amp;quot;)
                    chat=query.message.chat.id
                    if Config.IS_RECORDING:
                        await stop_recording()
                    await cancel_all_schedules()
                    await leave_call()
                    Config.CHAT=chat
                    Config.ADMIN_CACHE=False
                    await restart()
                    await query.message.edit(&amp;quot;Succesfully Changed Chat&amp;quot;)
                    await sync_to_db()
                else:
                    await query.answer(&amp;quot;This can only be used by SUDO users&amp;quot;, show_alert=True)
            if not Config.DATABASE_URI:
                await query.answer(&amp;quot;No DATABASE found, this changes are saved temporarly and will be reverted on restart. Add MongoDb to make this permanant.&amp;quot;)
        elif query.data.startswith(&amp;quot;close&amp;quot;):
            if &amp;quot;sudo&amp;quot; in query.data:
                if query.from_user.id in Config.SUDO:
                    await query.message.delete()
                else:
                    await query.answer(&amp;quot;This can only be used by SUDO users&amp;quot;, show_alert=True)  
            else:
                if query.message.chat.type != &amp;quot;private&amp;quot; and query.message.reply_to_message:
                    if query.message.reply_to_message.from_user is None:
                        pass
                    elif query.from_user.id != query.message.reply_to_message.from_user.id:
                        return await query.answer(&amp;quot;Okda&amp;quot;, show_alert=True)
                elif query.from_user.id in Config.ADMINS:
                    pass
                else:
                    return await query.answer(&amp;quot;Okda&amp;quot;, show_alert=True)
                await query.answer(&amp;quot;Menu Closed&amp;quot;)
                await query.message.delete()
        await query.answer()&lt;/pre&gt;
  &lt;p id=&quot;996e&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;bOZp&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;q1QG&quot;&gt;   ↪️commands.py&lt;/h3&gt;
  &lt;pre id=&quot;7vSl&quot;&gt;#!/usr/bin/env python3
# Copyright (C) @subinps
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.&lt;/pre&gt;
  &lt;pre id=&quot;18HI&quot;&gt;# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.&lt;/pre&gt;
  &lt;pre id=&quot;dwQd&quot;&gt;# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see &amp;lt;https://www.gnu.org/licenses/&amp;gt;.
from utils import LOGGER
from contextlib import suppress
from config import Config
import calendar
import pytz
from datetime import datetime
import asyncio
import os
from pyrogram.errors.exceptions.bad_request_400 import (
    MessageIdInvalid, 
    MessageNotModified
)
from pyrogram.types import (
    InlineKeyboardMarkup, 
    InlineKeyboardButton
)
from utils import (
    cancel_all_schedules,
    edit_config, 
    is_admin, 
    leave_call, 
    restart,
    restart_playout,
    stop_recording, 
    sync_to_db,
    update, 
    is_admin, 
    chat_filter,
    sudo_filter,
    delete_messages,
    seek_file
)
from pyrogram import (
    Client, 
    filters
)&lt;/pre&gt;
  &lt;pre id=&quot;0Lmt&quot;&gt;IST = pytz.timezone(Config.TIME_ZONE)
if Config.DATABASE_URI:
    from utils import db&lt;/pre&gt;
  &lt;pre id=&quot;kBgz&quot;&gt;HOME_TEXT = &amp;quot;&amp;lt;b&amp;gt;Hey  [{}](tg://user?id={}) 🙋‍♂️\n\nIam A Bot Built To Play or Stream Videos In Telegram VoiceChats.\nI Can Stream Any YouTube Video Or A Telegram File Or Even A YouTube Live.&amp;lt;/b&amp;gt;&amp;quot;
admin_filter=filters.create(is_admin) &lt;/pre&gt;
  &lt;pre id=&quot;7Mec&quot;&gt;@Client.on_message(filters.command([&amp;#x27;start&amp;#x27;, f&amp;quot;start@{Config.BOT_USERNAME}&amp;quot;]))
async def start(client, message):
    if len(message.command) &amp;gt; 1:
        if message.command[1] == &amp;#x27;help&amp;#x27;:
            reply_markup=InlineKeyboardMarkup(
                [
                    [
                        InlineKeyboardButton(f&amp;quot;Play&amp;quot;, callback_data=&amp;#x27;help_play&amp;#x27;),
                        InlineKeyboardButton(f&amp;quot;Settings&amp;quot;, callback_data=f&amp;quot;help_settings&amp;quot;),
                        InlineKeyboardButton(f&amp;quot;Recording&amp;quot;, callback_data=&amp;#x27;help_record&amp;#x27;),
                    ],
                    [
                        InlineKeyboardButton(&amp;quot;Scheduling&amp;quot;, callback_data=&amp;quot;help_schedule&amp;quot;),
                        InlineKeyboardButton(&amp;quot;Controling&amp;quot;, callback_data=&amp;#x27;help_control&amp;#x27;),
                        InlineKeyboardButton(&amp;quot;Admins&amp;quot;, callback_data=&amp;quot;help_admin&amp;quot;),
                    ],
                    [
                        InlineKeyboardButton(f&amp;quot;Misc&amp;quot;, callback_data=&amp;#x27;help_misc&amp;#x27;),
                        InlineKeyboardButton(&amp;quot;Close&amp;quot;, callback_data=&amp;quot;close&amp;quot;),
                    ],
                ]
                )
            await message.reply(&amp;quot;Learn to use the VCPlayer, Showing help menu, Choose from the below options.&amp;quot;,
                reply_markup=reply_markup,
                disable_web_page_preview=True
                )
        elif &amp;#x27;sch&amp;#x27; in message.command[1]:
            msg=await message.reply(&amp;quot;Checking schedules..&amp;quot;)
            you, me = message.command[1].split(&amp;quot;_&amp;quot;, 1)
            who=Config.SCHEDULED_STREAM.get(me)
            if not who:
                return await msg.edit(&amp;quot;Something gone somewhere.&amp;quot;)
            del Config.SCHEDULED_STREAM[me]
            whom=f&amp;quot;{message.chat.id}_{msg.message_id}&amp;quot;
            Config.SCHEDULED_STREAM[whom] = who
            await sync_to_db()
            if message.from_user.id not in Config.ADMINS:
                return await msg.edit(&amp;quot;OK da&amp;quot;)
            today = datetime.now(IST)
            smonth=today.strftime(&amp;quot;%B&amp;quot;)
            obj = calendar.Calendar()
            thisday = today.day
            year = today.year
            month = today.month
            m=obj.monthdayscalendar(year, month)
            button=[]
            button.append([InlineKeyboardButton(text=f&amp;quot;{str(smonth)}  {str(year)}&amp;quot;,callback_data=f&amp;quot;sch_month_choose_none_none&amp;quot;)])
            days=[&amp;quot;Mon&amp;quot;, &amp;quot;Tues&amp;quot;, &amp;quot;Wed&amp;quot;, &amp;quot;Thu&amp;quot;, &amp;quot;Fri&amp;quot;, &amp;quot;Sat&amp;quot;, &amp;quot;Sun&amp;quot;]
            f=[]
            for day in days:
                f.append(InlineKeyboardButton(text=f&amp;quot;{day}&amp;quot;,callback_data=f&amp;quot;day_info_none&amp;quot;))
            button.append(f)
            for one in m:
                f=[]
                for d in one:
                    year_=year
                    if d &amp;lt; int(today.day):
                        year_ += 1
                    if d == 0:
                        k=&amp;quot;\u2063&amp;quot;   
                        d=&amp;quot;none&amp;quot;   
                    else:
                        k=d    
                    f.append(InlineKeyboardButton(text=f&amp;quot;{k}&amp;quot;,callback_data=f&amp;quot;sch_month_{year_}_{month}_{d}&amp;quot;))
                button.append(f)
            button.append([InlineKeyboardButton(&amp;quot;Close&amp;quot;, callback_data=&amp;quot;schclose&amp;quot;)])
            await msg.edit(f&amp;quot;Choose the day of the month you want to schedule the voicechat.\nToday is {thisday} {smonth} {year}. Chooosing a date preceeding today will be considered as next year {year+1}&amp;quot;, reply_markup=InlineKeyboardMarkup(button))&lt;/pre&gt;
  &lt;pre id=&quot;L0SN&quot;&gt;&lt;/pre&gt;
  &lt;pre id=&quot;0h6J&quot;&gt;        return
    buttons = [
        [
            InlineKeyboardButton(&amp;#x27;⚙️ Update Channel&amp;#x27;, url=&amp;#x27;https://t.me/subin_works&amp;#x27;),
            InlineKeyboardButton(&amp;#x27;🧩 Source&amp;#x27;, url=&amp;#x27;https://github.com/subinps/VCPlayerBot&amp;#x27;)
        ],
        [
            InlineKeyboardButton(&amp;#x27;👨🏼‍🦯 Help&amp;#x27;, callback_data=&amp;#x27;help_main&amp;#x27;),
            InlineKeyboardButton(&amp;#x27;🗑 Close&amp;#x27;, callback_data=&amp;#x27;close&amp;#x27;),
        ]
    ]
    reply_markup = InlineKeyboardMarkup(buttons)
    k = await message.reply(HOME_TEXT.format(message.from_user.first_name, message.from_user.id), reply_markup=reply_markup)
    await delete_messages([message, k])&lt;/pre&gt;
  &lt;pre id=&quot;a4mG&quot;&gt;&lt;/pre&gt;
  &lt;pre id=&quot;6jix&quot;&gt;@Client.on_message(filters.command([&amp;quot;help&amp;quot;, f&amp;quot;help@{Config.BOT_USERNAME}&amp;quot;]))
async def show_help(client, message):
    reply_markup=InlineKeyboardMarkup(
        [
            [
                InlineKeyboardButton(&amp;quot;Play&amp;quot;, callback_data=&amp;#x27;help_play&amp;#x27;),
                InlineKeyboardButton(&amp;quot;Settings&amp;quot;, callback_data=f&amp;quot;help_settings&amp;quot;),
                InlineKeyboardButton(&amp;quot;Recording&amp;quot;, callback_data=&amp;#x27;help_record&amp;#x27;),
            ],
            [
                InlineKeyboardButton(&amp;quot;Scheduling&amp;quot;, callback_data=&amp;quot;help_schedule&amp;quot;),
                InlineKeyboardButton(&amp;quot;Controling&amp;quot;, callback_data=&amp;#x27;help_control&amp;#x27;),
                InlineKeyboardButton(&amp;quot;Admins&amp;quot;, callback_data=&amp;quot;help_admin&amp;quot;),
            ],
            [
                InlineKeyboardButton(&amp;quot;Misc&amp;quot;, callback_data=&amp;#x27;help_misc&amp;#x27;),
                InlineKeyboardButton(&amp;quot;Config Vars&amp;quot;, callback_data=&amp;#x27;help_env&amp;#x27;),
                InlineKeyboardButton(&amp;quot;Close&amp;quot;, callback_data=&amp;quot;close&amp;quot;),
            ],
        ]
        )
    if message.chat.type != &amp;quot;private&amp;quot; and message.from_user is None:
        k=await message.reply(
            text=&amp;quot;I cant help you here, since you are an anonymous admin. Get help in PM&amp;quot;,
            reply_markup=InlineKeyboardMarkup(
                [
                    [
                        InlineKeyboardButton(f&amp;quot;Help&amp;quot;, url=f&amp;quot;https://telegram.dog/{Config.BOT_USERNAME}?start=help&amp;quot;),
                    ]
                ]
            ),)
        await delete_messages([message, k])
        return
    if Config.msg.get(&amp;#x27;help&amp;#x27;) is not None:
        await Config.msg[&amp;#x27;help&amp;#x27;].delete()
    Config.msg[&amp;#x27;help&amp;#x27;] = await message.reply_text(
        &amp;quot;Learn to use the VCPlayer, Showing help menu, Choose from the below options.&amp;quot;,
        reply_markup=reply_markup,
        disable_web_page_preview=True
        )
    #await delete_messages([message])
@Client.on_message(filters.command([&amp;#x27;repo&amp;#x27;, f&amp;quot;repo@{Config.BOT_USERNAME}&amp;quot;]))
async def repo_(client, message):
    buttons = [
        [
            InlineKeyboardButton(&amp;#x27;🧩 Repository&amp;#x27;, url=&amp;#x27;https://github.com/subinps/VCPlayerBot&amp;#x27;),
            InlineKeyboardButton(&amp;#x27;⚙️ Update Channel&amp;#x27;, url=&amp;#x27;https://t.me/subin_works&amp;#x27;),     
        ],
        [
            InlineKeyboardButton(&amp;quot;🎞 How to Deploy&amp;quot;, url=&amp;#x27;https://youtu.be/mnWgZMrNe_0&amp;#x27;),
            InlineKeyboardButton(&amp;#x27;🗑 Close&amp;#x27;, callback_data=&amp;#x27;close&amp;#x27;),
        ]
    ]
    await message.reply(&amp;quot;&amp;lt;b&amp;gt;The source code of this bot is public and can be found at &amp;lt;a href=https://github.com/subinps/VCPlayerBot&amp;gt;VCPlayerBot.&amp;lt;/a&amp;gt;\nYou can deploy your own bot and use in your group.\n\nFeel free to star☀️ the repo if you liked it 🙃.&amp;lt;/b&amp;gt;&amp;quot;, reply_markup=InlineKeyboardMarkup(buttons), disable_web_page_preview=True)
    await delete_messages([message])&lt;/pre&gt;
  &lt;pre id=&quot;8G4J&quot;&gt;@Client.on_message(filters.command([&amp;#x27;restart&amp;#x27;, &amp;#x27;update&amp;#x27;, f&amp;quot;restart@{Config.BOT_USERNAME}&amp;quot;, f&amp;quot;update@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; admin_filter &amp;amp; chat_filter)
async def update_handler(client, message):
    if Config.HEROKU_APP:
        k = await message.reply(&amp;quot;Heroku APP found, Restarting app to update.&amp;quot;)
        if Config.DATABASE_URI:
            msg = {&amp;quot;msg_id&amp;quot;:k.message_id, &amp;quot;chat_id&amp;quot;:k.chat.id}
            if not await db.is_saved(&amp;quot;RESTART&amp;quot;):
                db.add_config(&amp;quot;RESTART&amp;quot;, msg)
            else:
                await db.edit_config(&amp;quot;RESTART&amp;quot;, msg)
            await sync_to_db()
    else:
        k = await message.reply(&amp;quot;No Heroku APP found, Trying to restart.&amp;quot;)
        if Config.DATABASE_URI:
            msg = {&amp;quot;msg_id&amp;quot;:k.message_id, &amp;quot;chat_id&amp;quot;:k.chat.id}
            if not await db.is_saved(&amp;quot;RESTART&amp;quot;):
                db.add_config(&amp;quot;RESTART&amp;quot;, msg)
            else:
                await db.edit_config(&amp;quot;RESTART&amp;quot;, msg)
    try:
        await message.delete()
    except:
        pass
    await update()&lt;/pre&gt;
  &lt;pre id=&quot;fQ4T&quot;&gt;@Client.on_message(filters.command([&amp;#x27;logs&amp;#x27;, f&amp;quot;logs@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; admin_filter &amp;amp; chat_filter)
async def get_logs(client, message):
    m=await message.reply(&amp;quot;Checking logs..&amp;quot;)
    if os.path.exists(&amp;quot;botlog.txt&amp;quot;):
        await message.reply_document(&amp;#x27;botlog.txt&amp;#x27;, caption=&amp;quot;Bot Logs&amp;quot;)
        await m.delete()
        await delete_messages([message])
    else:
        k = await m.edit(&amp;quot;No log files found.&amp;quot;)
        await delete_messages([message, k])&lt;/pre&gt;
  &lt;pre id=&quot;5iel&quot;&gt;@Client.on_message(filters.command([&amp;#x27;env&amp;#x27;, f&amp;quot;env@{Config.BOT_USERNAME}&amp;quot;, &amp;quot;config&amp;quot;, f&amp;quot;config@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; sudo_filter &amp;amp; chat_filter)
async def set_heroku_var(client, message):
    with suppress(MessageIdInvalid, MessageNotModified):
        m = await message.reply(&amp;quot;Checking config vars..&amp;quot;)
        if &amp;quot; &amp;quot; in message.text:
            cmd, env = message.text.split(&amp;quot; &amp;quot;, 1)
            if &amp;quot;=&amp;quot; in env:
                var, value = env.split(&amp;quot;=&amp;quot;, 1)
            else:
                if env == &amp;quot;STARTUP_STREAM&amp;quot;:
                    env_ = &amp;quot;STREAM_URL&amp;quot;
                elif env == &amp;quot;QUALITY&amp;quot;:
                    env_ = &amp;quot;CUSTOM_QUALITY&amp;quot; 
                else:
                    env_ = env
                ENV_VARS = [&amp;quot;ADMINS&amp;quot;, &amp;quot;SUDO&amp;quot;, &amp;quot;CHAT&amp;quot;, &amp;quot;LOG_GROUP&amp;quot;, &amp;quot;STREAM_URL&amp;quot;, &amp;quot;SHUFFLE&amp;quot;, &amp;quot;ADMIN_ONLY&amp;quot;, &amp;quot;REPLY_MESSAGE&amp;quot;, 
                        &amp;quot;EDIT_TITLE&amp;quot;, &amp;quot;RECORDING_DUMP&amp;quot;, &amp;quot;RECORDING_TITLE&amp;quot;, &amp;quot;IS_VIDEO&amp;quot;, &amp;quot;IS_LOOP&amp;quot;, &amp;quot;DELAY&amp;quot;, &amp;quot;PORTRAIT&amp;quot;, 
                        &amp;quot;IS_VIDEO_RECORD&amp;quot;, &amp;quot;PTN&amp;quot;, &amp;quot;CUSTOM_QUALITY&amp;quot;]
                if env_ in ENV_VARS:
                    await m.edit(f&amp;quot;Current Value for &amp;#x60;{env}&amp;#x60;  is &amp;#x60;{getattr(Config, env_)}&amp;#x60;&amp;quot;)
                    await delete_messages([message])
                    return
                else:
                    await m.edit(&amp;quot;This is an invalid env value. Read help on env to know about available env vars.&amp;quot;)
                    await delete_messages([message, m])
                    return     
            
        else:
            await m.edit(&amp;quot;You haven&amp;#x27;t provided any value for env, you should follow the correct format.\nExample: &amp;lt;code&amp;gt;/env CHAT=-1020202020202&amp;lt;/code&amp;gt; to change or set CHAT var.\n&amp;lt;code&amp;gt;/env REPLY_MESSAGE= &amp;lt;code&amp;gt;To delete REPLY_MESSAGE.&amp;quot;)
            await delete_messages([message, m])
            return&lt;/pre&gt;
  &lt;pre id=&quot;pWls&quot;&gt;        if Config.DATABASE_URI and var in [&amp;quot;STARTUP_STREAM&amp;quot;, &amp;quot;CHAT&amp;quot;, &amp;quot;LOG_GROUP&amp;quot;, &amp;quot;REPLY_MESSAGE&amp;quot;, &amp;quot;DELAY&amp;quot;, &amp;quot;RECORDING_DUMP&amp;quot;, &amp;quot;QUALITY&amp;quot;]:      
            await m.edit(&amp;quot;Mongo DB Found, Setting up config vars...&amp;quot;)
            await asyncio.sleep(2)  
            if not value:
                await m.edit(f&amp;quot;No value for env specified. Trying to delete env {var}.&amp;quot;)
                await asyncio.sleep(2)
                if var in [&amp;quot;STARTUP_STREAM&amp;quot;, &amp;quot;CHAT&amp;quot;, &amp;quot;DELAY&amp;quot;]:
                    await m.edit(&amp;quot;This is a mandatory var and cannot be deleted.&amp;quot;)
                    await delete_messages([message, m]) 
                    return
                await edit_config(var, False)
                await m.edit(f&amp;quot;Sucessfully deleted {var}&amp;quot;)
                await delete_messages([message, m])           
                return
            else:
                if var in [&amp;quot;CHAT&amp;quot;, &amp;quot;LOG_GROUP&amp;quot;, &amp;quot;RECORDING_DUMP&amp;quot;, &amp;quot;QUALITY&amp;quot;]:
                    try:
                        value=int(value)
                    except:
                        if var == &amp;quot;QUALITY&amp;quot;:
                            if not value.lower() in [&amp;quot;low&amp;quot;, &amp;quot;medium&amp;quot;, &amp;quot;high&amp;quot;]:
                                await m.edit(&amp;quot;You should specify a value between 10 - 100.&amp;quot;)
                                await delete_messages([message, m])
                                return
                            else:
                                value = value.lower()
                                if value == &amp;quot;high&amp;quot;:
                                    value = 100
                                elif value == &amp;quot;medium&amp;quot;:
                                    value = 66.9
                                elif value == &amp;quot;low&amp;quot;:
                                    value = 50
                        else:
                            await m.edit(&amp;quot;You should give me a chat id . It should be an interger.&amp;quot;)
                            await delete_messages([message, m])
                            return
                    if var == &amp;quot;CHAT&amp;quot;:
                        await leave_call()
                        Config.ADMIN_CACHE=False
                        if Config.IS_RECORDING:
                            await stop_recording()
                        await cancel_all_schedules()
                        Config.CHAT=int(value)
                        await restart()
                    await edit_config(var, int(value))
                    if var == &amp;quot;QUALITY&amp;quot;:
                        if Config.CALL_STATUS:
                            data=Config.DATA.get(&amp;#x27;FILE_DATA&amp;#x27;)
                            if not data \
                                or data.get(&amp;#x27;dur&amp;#x27;, 0) == 0:
                                await restart_playout()
                                return
                            k, reply = await seek_file(0)
                            if k == False:
                                await restart_playout()
                    await m.edit(f&amp;quot;Succesfully changed {var} to {value}&amp;quot;)
                    await delete_messages([message, m])
                    return
                else:
                    if var == &amp;quot;STARTUP_STREAM&amp;quot;:
                        Config.STREAM_SETUP=False
                    await edit_config(var, value)
                    await m.edit(f&amp;quot;Succesfully changed {var} to {value}&amp;quot;)
                    await delete_messages([message, m])
                    await restart_playout()
                    return
        else:
            if not Config.HEROKU_APP:
                buttons = [[InlineKeyboardButton(&amp;#x27;Heroku API_KEY&amp;#x27;, url=&amp;#x27;https://dashboard.heroku.com/account/applications/authorizations/new&amp;#x27;), InlineKeyboardButton(&amp;#x27;🗑 Close&amp;#x27;, callback_data=&amp;#x27;close&amp;#x27;),]]
                await m.edit(
                    text=&amp;quot;No heroku app found, this command needs the following heroku vars to be set.\n\n1. &amp;lt;code&amp;gt;HEROKU_API_KEY&amp;lt;/code&amp;gt;: Your heroku account api key.\n2. &amp;lt;code&amp;gt;HEROKU_APP_NAME&amp;lt;/code&amp;gt;: Your heroku app name.&amp;quot;, 
                    reply_markup=InlineKeyboardMarkup(buttons)) 
                await delete_messages([message])
                return     
            config = Config.HEROKU_APP.config()
            if not value:
                await m.edit(f&amp;quot;No value for env specified. Trying to delete env {var}.&amp;quot;)
                await asyncio.sleep(2)
                if var in [&amp;quot;STARTUP_STREAM&amp;quot;, &amp;quot;CHAT&amp;quot;, &amp;quot;DELAY&amp;quot;, &amp;quot;API_ID&amp;quot;, &amp;quot;API_HASH&amp;quot;, &amp;quot;BOT_TOKEN&amp;quot;, &amp;quot;SESSION_STRING&amp;quot;, &amp;quot;ADMINS&amp;quot;]:
                    await m.edit(&amp;quot;These are mandatory vars and cannot be deleted.&amp;quot;)
                    await delete_messages([message, m])
                    return
                if var in config:
                    await m.edit(f&amp;quot;Sucessfully deleted {var}&amp;quot;)
                    await asyncio.sleep(2)
                    await m.edit(&amp;quot;Now restarting the app to make changes.&amp;quot;)
                    if Config.DATABASE_URI:
                        msg = {&amp;quot;msg_id&amp;quot;:m.message_id, &amp;quot;chat_id&amp;quot;:m.chat.id}
                        if not await db.is_saved(&amp;quot;RESTART&amp;quot;):
                            db.add_config(&amp;quot;RESTART&amp;quot;, msg)
                        else:
                            await db.edit_config(&amp;quot;RESTART&amp;quot;, msg)
                    del config[var]                
                    config[var] = None               
                else:
                    k = await m.edit(f&amp;quot;No env named {var} found. Nothing was changed.&amp;quot;)
                    await delete_messages([message, k])
                return
            if var in config:
                await m.edit(f&amp;quot;Variable already found. Now edited to {value}&amp;quot;)
            else:
                await m.edit(f&amp;quot;Variable not found, Now setting as new var.&amp;quot;)
            await asyncio.sleep(2)
            await m.edit(f&amp;quot;Succesfully set {var} with value {value}, Now Restarting to take effect of changes...&amp;quot;)
            if Config.DATABASE_URI:
                msg = {&amp;quot;msg_id&amp;quot;:m.message_id, &amp;quot;chat_id&amp;quot;:m.chat.id}
                if not await db.is_saved(&amp;quot;RESTART&amp;quot;):
                    db.add_config(&amp;quot;RESTART&amp;quot;, msg)
                else:
                    await db.edit_config(&amp;quot;RESTART&amp;quot;, msg)
            config[var] = str(value)&lt;/pre&gt;
  &lt;p id=&quot;vQzV&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;0pOV&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;Zs9u&quot;&gt;↪️controls.py&lt;/h3&gt;
  &lt;pre id=&quot;cZdI&quot;&gt;#!/usr/bin/env python3
# Copyright (C) @subinps
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.&lt;/pre&gt;
  &lt;pre id=&quot;nsTz&quot;&gt;# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.&lt;/pre&gt;
  &lt;pre id=&quot;sp1o&quot;&gt;# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see &amp;lt;https://www.gnu.org/licenses/&amp;gt;.
from utils import LOGGER
from pyrogram.types import Message
from config import Config
from pyrogram import (
    Client, 
    filters
)
from utils import (
    clear_db_playlist, 
    get_playlist_str, 
    is_admin, 
    mute, 
    restart_playout, 
    settings_panel, 
    skip, 
    pause, 
    resume, 
    unmute, 
    volume, 
    get_buttons, 
    is_admin, 
    seek_file, 
    delete_messages,
    chat_filter,
    volume_buttons
)&lt;/pre&gt;
  &lt;pre id=&quot;xdj0&quot;&gt;admin_filter=filters.create(is_admin)   &lt;/pre&gt;
  &lt;pre id=&quot;U5pk&quot;&gt;@Client.on_message(filters.command([&amp;quot;playlist&amp;quot;, f&amp;quot;playlist@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; chat_filter)
async def player(client, message):
    if not Config.CALL_STATUS:
        await message.reply_text(
            &amp;quot;Player is idle, start the player using below button. ㅤㅤㅤㅤ&amp;quot;,
            disable_web_page_preview=True,
            reply_markup=await get_buttons()
        )
        await delete_messages([message])
        return
    pl = await get_playlist_str()
    if message.chat.type == &amp;quot;private&amp;quot;:
        await message.reply_text(
            pl,
            disable_web_page_preview=True,
            reply_markup=await get_buttons(),
        )
    else:
        if Config.msg.get(&amp;#x27;player&amp;#x27;) is not None:
            await Config.msg[&amp;#x27;player&amp;#x27;].delete()
        Config.msg[&amp;#x27;player&amp;#x27;] = await message.reply_text(
            pl,
            disable_web_page_preview=True,
            reply_markup=await get_buttons(),
        )
    await delete_messages([message])&lt;/pre&gt;
  &lt;pre id=&quot;nsV9&quot;&gt;@Client.on_message(filters.command([&amp;quot;skip&amp;quot;, f&amp;quot;skip@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; admin_filter &amp;amp; chat_filter)
async def skip_track(_, m: Message):
    msg=await m.reply(&amp;#x27;trying to skip from queue..&amp;#x27;)
    if not Config.CALL_STATUS:
        await msg.edit(
            &amp;quot;Player is idle, start the player using below button. ㅤㅤㅤㅤ&amp;quot;,
            disable_web_page_preview=True,
            reply_markup=await get_buttons()
        )
        await delete_messages([m])
        return
    if not Config.playlist:
        await msg.edit(&amp;quot;Playlist is Empty.&amp;quot;)
        await delete_messages([m, msg])
        return
    if len(m.command) == 1:
        await skip()
    else:
        #https://github.com/callsmusic/tgvc-userbot/blob/dev/plugins/vc/player.py#L268-L288
        try:
            items = list(dict.fromkeys(m.command[1:]))
            items = [int(x) for x in items if x.isdigit()]
            items.sort(reverse=True)
            for i in items:
                if 2 &amp;lt;= i &amp;lt;= (len(Config.playlist) - 1):
                    await msg.edit(f&amp;quot;Succesfully Removed from Playlist- {i}. **{Config.playlist[i][1]}**&amp;quot;)
                    await clear_db_playlist(song=Config.playlist[i])
                    Config.playlist.pop(i)
                    await delete_messages([m, msg])
                else:
                    await msg.edit(f&amp;quot;You cant skip first two songs- {i}&amp;quot;)
                    await delete_messages([m, msg])
        except (ValueError, TypeError):
            await msg.edit(&amp;quot;Invalid input&amp;quot;)
            await delete_messages([m, msg])
    pl=await get_playlist_str()
    if m.chat.type == &amp;quot;private&amp;quot;:
        await msg.edit(pl, disable_web_page_preview=True, reply_markup=await get_buttons())
    elif not Config.LOG_GROUP and m.chat.type == &amp;quot;supergroup&amp;quot;:
        if Config.msg.get(&amp;#x27;player&amp;#x27;):
            await Config.msg[&amp;#x27;player&amp;#x27;].delete()
        Config.msg[&amp;#x27;player&amp;#x27;] = await msg.edit(pl, disable_web_page_preview=True, reply_markup=await get_buttons())
        await delete_messages([m])&lt;/pre&gt;
  &lt;pre id=&quot;4r1v&quot;&gt;@Client.on_message(filters.command([&amp;quot;pause&amp;quot;, f&amp;quot;pause@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; admin_filter &amp;amp; chat_filter)
async def pause_playing(_, m: Message):
    if not Config.CALL_STATUS:
        await m.reply_text(
            &amp;quot;Player is idle, start the player using below button. ㅤㅤㅤㅤㅤ&amp;quot;,
            disable_web_page_preview=True,
            reply_markup=await get_buttons()
        )
        await delete_messages([m])
        return
    if Config.PAUSE:
        k = await m.reply(&amp;quot;Already Paused&amp;quot;)
        await delete_messages([m, k])
        return
    k = await m.reply(&amp;quot;Paused Video Call&amp;quot;)
    await pause()
    await delete_messages([m, k])
    &lt;/pre&gt;
  &lt;pre id=&quot;UV0z&quot;&gt;@Client.on_message(filters.command([&amp;quot;resume&amp;quot;, f&amp;quot;resume@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; admin_filter &amp;amp; chat_filter)
async def resume_playing(_, m: Message):
    if not Config.CALL_STATUS:
        await m.reply_text(
            &amp;quot;Player is idle, start the player using below button. ㅤㅤㅤㅤㅤ&amp;quot;,
            disable_web_page_preview=True,
            reply_markup=await get_buttons()
        )
        await delete_messages([m])
        return
    if not Config.PAUSE:
        k = await m.reply(&amp;quot;Nothing paused to resume&amp;quot;)
        await delete_messages([m, k])
        return
    k = await m.reply(&amp;quot;Resumed Video Call&amp;quot;)
    await resume()
    await delete_messages([m, k])
    &lt;/pre&gt;
  &lt;pre id=&quot;SakK&quot;&gt;
@Client.on_message(filters.command([&amp;#x27;volume&amp;#x27;, f&amp;quot;volume@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; admin_filter &amp;amp; chat_filter)
async def set_vol(_, m: Message):
    if not Config.CALL_STATUS:
        await m.reply_text(
            &amp;quot;Player is idle, start the player using below button. ㅤㅤㅤㅤㅤㅤ&amp;quot;,
            disable_web_page_preview=True,
            reply_markup=await get_buttons()
        )
        await delete_messages([m])
        return
    if len(m.command) &amp;lt; 2:
        await m.reply_text(&amp;#x27;Change Volume of Your VCPlayer. ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ&amp;#x27;, reply_markup=await volume_buttons())
        await delete_messages([m])
        return
    if not 1 &amp;lt; int(m.command[1]) &amp;lt; 200:
        await m.reply_text(f&amp;quot;Only 1-200 range is accepeted. ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ&amp;quot;, reply_markup=await volume_buttons())
    else:
        await volume(int(m.command[1]))
        await m.reply_text(f&amp;quot;Succesfully set volume to {m.command[1]} ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ&amp;quot;, reply_markup=await volume_buttons())
    await delete_messages([m])&lt;/pre&gt;
  &lt;pre id=&quot;TlVg&quot;&gt;    &lt;/pre&gt;
  &lt;pre id=&quot;9BO9&quot;&gt;
@Client.on_message(filters.command([&amp;#x27;vcmute&amp;#x27;, f&amp;quot;vcmute@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; admin_filter &amp;amp; chat_filter)
async def set_mute(_, m: Message):
    if not Config.CALL_STATUS:
        await m.reply_text(
            &amp;quot;Player is idle, start the player using below button. ㅤㅤㅤㅤㅤㅤㅤㅤ&amp;quot;,
            disable_web_page_preview=True,
            reply_markup=await get_buttons()
        )
        await delete_messages([m])
        return
    if Config.MUTED:
        k = await m.reply_text(&amp;quot;Already muted.&amp;quot;)
        await delete_messages([m, k])
        return
    k=await mute()
    if k:
        k = await m.reply_text(f&amp;quot; 🔇 Succesfully Muted &amp;quot;)
        await delete_messages([m, k])
    else:
        k = await m.reply_text(&amp;quot;Already muted.&amp;quot;)
        await delete_messages([m, k])
    
@Client.on_message(filters.command([&amp;#x27;vcunmute&amp;#x27;, f&amp;quot;vcunmute@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; admin_filter &amp;amp; chat_filter)
async def set_unmute(_, m: Message):
    if not Config.CALL_STATUS:
        await m.reply_text(
            &amp;quot;Player is idle, start the player using below button. ㅤㅤㅤㅤㅤ&amp;quot;,
            disable_web_page_preview=True,
            reply_markup=await get_buttons()
        )
        await delete_messages([m])
        return
    if not Config.MUTED:
        k = await m.reply(&amp;quot;Stream already unmuted.&amp;quot;)
        await delete_messages([m, k])
        return
    k=await unmute()
    if k:
        k = await m.reply_text(f&amp;quot;🔊 Succesfully Unmuted &amp;quot;)
        await delete_messages([m, k])
        return
    else:
        k=await m.reply_text(&amp;quot;Not muted, already unmuted.&amp;quot;)    
        await delete_messages([m, k])&lt;/pre&gt;
  &lt;pre id=&quot;nUUv&quot;&gt;
@Client.on_message(filters.command([&amp;quot;replay&amp;quot;, f&amp;quot;replay@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; admin_filter &amp;amp; chat_filter)
async def replay_playout(client, m: Message):
    msg = await m.reply(&amp;#x27;Checking player&amp;#x27;)
    if not Config.CALL_STATUS:
        await msg.edit(
            &amp;quot;Player is idle, start the player using below button. ㅤㅤㅤㅤㅤ&amp;quot;,
            disable_web_page_preview=True,
            reply_markup=await get_buttons()
        )
        await delete_messages([m])
        return
    await msg.edit(f&amp;quot;Replaying from begining&amp;quot;)
    await restart_playout()
    await delete_messages([m, msg])&lt;/pre&gt;
  &lt;pre id=&quot;paBv&quot;&gt;
@Client.on_message(filters.command([&amp;quot;player&amp;quot;, f&amp;quot;player@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; chat_filter)
async def show_player(client, m: Message):
    if not Config.CALL_STATUS:
        await m.reply_text(
            &amp;quot;Player is idle, start the player using below button. ㅤㅤㅤㅤㅤ&amp;quot;,
            disable_web_page_preview=True,
            reply_markup=await get_buttons()
        )
        await delete_messages([m])
        return
    data=Config.DATA.get(&amp;#x27;FILE_DATA&amp;#x27;)
    if not data.get(&amp;#x27;dur&amp;#x27;, 0) or \
        data.get(&amp;#x27;dur&amp;#x27;) == 0:
        title=&amp;quot;&amp;lt;b&amp;gt;Playing Live Stream&amp;lt;/b&amp;gt; ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ&amp;quot;
    else:
        if Config.playlist:
            title=f&amp;quot;&amp;lt;b&amp;gt;{Config.playlist[0][1]}&amp;lt;/b&amp;gt; ㅤㅤㅤㅤ\n ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ&amp;quot;
        elif Config.STREAM_LINK:
            title=f&amp;quot;&amp;lt;b&amp;gt;Stream Using [Url]({data[&amp;#x27;file&amp;#x27;]}) &amp;lt;/b&amp;gt; ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ&amp;quot;
        else:
            title=f&amp;quot;&amp;lt;b&amp;gt;Streaming Startup [stream]({Config.STREAM_URL})&amp;lt;/b&amp;gt; ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ&amp;quot;
    if m.chat.type == &amp;quot;private&amp;quot;:
        await m.reply_text(
            title,
            disable_web_page_preview=True,
            reply_markup=await get_buttons()
        )
    else:
        if Config.msg.get(&amp;#x27;player&amp;#x27;) is not None:
            await Config.msg[&amp;#x27;player&amp;#x27;].delete()
        Config.msg[&amp;#x27;player&amp;#x27;] = await m.reply_text(
            title,
            disable_web_page_preview=True,
            reply_markup=await get_buttons()
        )
        await delete_messages([m])&lt;/pre&gt;
  &lt;pre id=&quot;ZUN1&quot;&gt;
@Client.on_message(filters.command([&amp;quot;seek&amp;quot;, f&amp;quot;seek@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; admin_filter &amp;amp; chat_filter)
async def seek_playout(client, m: Message):
    if not Config.CALL_STATUS:
        await m.reply_text(
            &amp;quot;Player is idle, start the player using below button. ㅤㅤㅤ ㅤㅤ&amp;quot;,
            disable_web_page_preview=True,
            reply_markup=await get_buttons()
        )
        await delete_messages([m])
        return
    data=Config.DATA.get(&amp;#x27;FILE_DATA&amp;#x27;)
    k=await m.reply(&amp;quot;Trying to seek..&amp;quot;)
    if not data.get(&amp;#x27;dur&amp;#x27;, 0) or \
        data.get(&amp;#x27;dur&amp;#x27;) == 0:
        await k.edit(&amp;quot;This stream cant be seeked.&amp;quot;)
        await delete_messages([m, k])
        return
    if &amp;#x27; &amp;#x27; in m.text:
        i, time = m.text.split(&amp;quot; &amp;quot;)
        try:
            time=int(time)
        except:
            await k.edit(&amp;#x27;Invalid time specified&amp;#x27;)
            await delete_messages([m, k])
            return
        nyav, string=await seek_file(time)
        if nyav == False:
            await k.edit(string)
            await delete_messages([m, k])
            return
        if not data.get(&amp;#x27;dur&amp;#x27;, 0)\
            or data.get(&amp;#x27;dur&amp;#x27;) == 0:
            title=&amp;quot;&amp;lt;b&amp;gt;Playing Live Stream&amp;lt;/b&amp;gt; ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ&amp;quot;
        else:
            if Config.playlist:
                title=f&amp;quot;&amp;lt;b&amp;gt;{Config.playlist[0][1]}&amp;lt;/b&amp;gt;\nㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ&amp;quot;
            elif Config.STREAM_LINK:
                title=f&amp;quot;&amp;lt;b&amp;gt;Stream Using [Url]({data[&amp;#x27;file&amp;#x27;]})&amp;lt;/b&amp;gt; ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ&amp;quot;
            else:
                title=f&amp;quot;&amp;lt;b&amp;gt;Streaming Startup [stream]({Config.STREAM_URL})&amp;lt;/b&amp;gt; ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ&amp;quot;
        if Config.msg.get(&amp;#x27;player&amp;#x27;):
            await Config.msg[&amp;#x27;player&amp;#x27;].delete()  
        Config.msg[&amp;#x27;player&amp;#x27;] = await k.edit(f&amp;quot;🎸{title}&amp;quot;, reply_markup=await get_buttons(), disable_web_page_preview=True)
        await delete_messages([m])
    else:
        await k.edit(&amp;#x27;No time specified&amp;#x27;)
        await delete_messages([m, k])&lt;/pre&gt;
  &lt;pre id=&quot;oypl&quot;&gt;
@Client.on_message(filters.command([&amp;quot;settings&amp;quot;, f&amp;quot;settings@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; admin_filter &amp;amp; chat_filter)
async def settings(client, m: Message):
    await m.reply(f&amp;quot;Configure Your VCPlayer Settings Here. ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ&amp;quot;, reply_markup=await settings_panel(), disable_web_page_preview=True)
    await delete_messages([m])&lt;/pre&gt;
  &lt;p id=&quot;EXSP&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;KFZN&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;4Jqg&quot;&gt;↪️export_import.py&lt;/h3&gt;
  &lt;pre id=&quot;x9MD&quot;&gt;#!/usr/bin/env python3
# Copyright (C) @subinps
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.&lt;/pre&gt;
  &lt;pre id=&quot;uxoi&quot;&gt;# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.&lt;/pre&gt;
  &lt;pre id=&quot;n04y&quot;&gt;# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see &amp;lt;https://www.gnu.org/licenses/&amp;gt;.&lt;/pre&gt;
  &lt;pre id=&quot;zrNu&quot;&gt;from utils import LOGGER
import json
import os
from pyrogram.types import Message
from contextlib import suppress
from config import Config
from utils import (
    get_buttons, 
    is_admin, 
    get_playlist_str, 
    shuffle_playlist, 
    import_play_list, 
    delete_messages,
    chat_filter
)
from pyrogram import (
    Client, 
    filters
)
from pyrogram.errors import (
    MessageNotModified, 
    MessageIdInvalid
)&lt;/pre&gt;
  &lt;pre id=&quot;hcRg&quot;&gt;
admin_filter=filters.create(is_admin)   &lt;/pre&gt;
  &lt;pre id=&quot;kaXL&quot;&gt;
@Client.on_message(filters.command([&amp;quot;export&amp;quot;, f&amp;quot;export@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; admin_filter &amp;amp; chat_filter)
async def export_play_list(client, message: Message):
    if not Config.playlist:
        k=await message.reply_text(&amp;quot;Playlist is Empty&amp;quot;)
        await delete_messages([message, k])
        return
    file=f&amp;quot;{message.chat.id}_{message.message_id}.json&amp;quot;
    with open(file, &amp;#x27;w+&amp;#x27;) as outfile:
        json.dump(Config.playlist, outfile, indent=4)
    await client.send_document(chat_id=message.chat.id, document=file, file_name=&amp;quot;PlayList.json&amp;quot;, caption=f&amp;quot;Playlist\n\nNumber Of Songs: &amp;lt;code&amp;gt;{len(Config.playlist)}&amp;lt;/code&amp;gt;\n\nJoin [XTZ Bots](https://t.me/subin_works)&amp;quot;)
    try:
        os.remove(file)
    except:
        pass
    await delete_messages([message])&lt;/pre&gt;
  &lt;pre id=&quot;U6Rg&quot;&gt;@Client.on_message(filters.command([&amp;quot;import&amp;quot;, f&amp;quot;import@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; admin_filter &amp;amp; chat_filter)
async def import_playlist(client, m: Message):
    with suppress(MessageIdInvalid, MessageNotModified):
        if m.reply_to_message is not None and m.reply_to_message.document:
            if m.reply_to_message.document.file_name != &amp;quot;PlayList.json&amp;quot;:
                k=await m.reply(&amp;quot;Invalid PlayList file given. Export your current Playlist using /export.&amp;quot;)
                await delete_messages([m, k])
                return
            myplaylist=await m.reply_to_message.download()
            status=await m.reply(&amp;quot;Trying to get details from playlist.&amp;quot;)
            n=await import_play_list(myplaylist)
            if not n:
                await status.edit(&amp;quot;Errors Occured while importing playlist.&amp;quot;)
                await delete_messages([m, status])
                return
            if Config.SHUFFLE:
                await shuffle_playlist()
            pl=await get_playlist_str()
            if m.chat.type == &amp;quot;private&amp;quot;:
                await status.edit(pl, disable_web_page_preview=True, reply_markup=await get_buttons())        
            elif not Config.LOG_GROUP and m.chat.type == &amp;quot;supergroup&amp;quot;:
                if Config.msg.get(&amp;#x27;playlist&amp;#x27;):
                    await Config.msg[&amp;#x27;playlist&amp;#x27;].delete()
                Config.msg[&amp;#x27;playlist&amp;#x27;] = await status.edit(pl, disable_web_page_preview=True, reply_markup=await get_buttons())
                await delete_messages([m])
            else:
                await delete_messages([m, status])
        else:
            k = await m.reply(&amp;quot;No playList file given.&amp;quot;)
            await delete_messages([m, k])&lt;/pre&gt;
  &lt;p id=&quot;usJX&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;34U5&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;nWGV&quot;&gt;↪️inline.py&lt;/h3&gt;
  &lt;pre id=&quot;Wqnw&quot;&gt;#!/usr/bin/env python3
# Copyright (C) @subinps
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.&lt;/pre&gt;
  &lt;pre id=&quot;w3Ze&quot;&gt;# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.&lt;/pre&gt;
  &lt;pre id=&quot;AMaZ&quot;&gt;# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see &amp;lt;https://www.gnu.org/licenses/&amp;gt;.&lt;/pre&gt;
  &lt;pre id=&quot;rHam&quot;&gt;from pyrogram.handlers import InlineQueryHandler
from youtubesearchpython import VideosSearch
from config import Config
from utils import LOGGER
from pyrogram.types import (
    InlineQueryResultArticle, 
    InputTextMessageContent, 
    InlineKeyboardButton, 
    InlineKeyboardMarkup
)
from pyrogram import (
    Client, 
    errors
)&lt;/pre&gt;
  &lt;pre id=&quot;aZhn&quot;&gt;
buttons = [
    [
        InlineKeyboardButton(&amp;#x27;⚡️Make Own Bot&amp;#x27;, url=&amp;#x27;https://github.com/subinps/VCPlayerBot&amp;#x27;),
        InlineKeyboardButton(&amp;#x27;🧩 Join Here&amp;#x27;, url=&amp;#x27;https://t.me/subin_works&amp;#x27;),
    ]
    ]
def get_cmd(dur):
    if dur:
        return &amp;quot;/play&amp;quot;
    else:
        return &amp;quot;/stream&amp;quot;
@Client.on_inline_query()
async def search(client, query):
    answers = []
    if query.query == &amp;quot;ETHO_ORUTHAN_PM_VANNU&amp;quot;:
        answers.append(
            InlineQueryResultArticle(
                title=&amp;quot;Deploy&amp;quot;,
                input_message_content=InputTextMessageContent(f&amp;quot;{Config.REPLY_MESSAGE}\n\n&amp;lt;b&amp;gt;You can&amp;#x27;t use this bot in your group, for that you have to make your own bot from the [SOURCE CODE](https://github.com/subinps/VCPlayerBot) below.&amp;lt;/b&amp;gt;&amp;quot;, disable_web_page_preview=True),
                reply_markup=InlineKeyboardMarkup(buttons)
                )
            )
        await query.answer(results=answers, cache_time=0)
        return
    string = query.query.lower().strip().rstrip()
    if string == &amp;quot;&amp;quot;:
        await client.answer_inline_query(
            query.id,
            results=answers,
            switch_pm_text=(&amp;quot;Search a youtube video&amp;quot;),
            switch_pm_parameter=&amp;quot;help&amp;quot;,
            cache_time=0
        )
    else:
        videosSearch = VideosSearch(string.lower(), limit=50)
        for v in videosSearch.result()[&amp;quot;result&amp;quot;]:
            answers.append(
                InlineQueryResultArticle(
                    title=v[&amp;quot;title&amp;quot;],
                    description=(&amp;quot;Duration: {} Views: {}&amp;quot;).format(
                        v[&amp;quot;duration&amp;quot;],
                        v[&amp;quot;viewCount&amp;quot;][&amp;quot;short&amp;quot;]
                    ),
                    input_message_content=InputTextMessageContent(
                        &amp;quot;{} https://www.youtube.com/watch?v={}&amp;quot;.format(get_cmd(v[&amp;quot;duration&amp;quot;]), v[&amp;quot;id&amp;quot;])
                    ),
                    thumb_url=v[&amp;quot;thumbnails&amp;quot;][0][&amp;quot;url&amp;quot;]
                )
            )
        try:
            await query.answer(
                results=answers,
                cache_time=0
            )
        except errors.QueryIdInvalid:
            await query.answer(
                results=answers,
                cache_time=0,
                switch_pm_text=(&amp;quot;Nothing found&amp;quot;),
                switch_pm_parameter=&amp;quot;&amp;quot;,
            )&lt;/pre&gt;
  &lt;pre id=&quot;2Wj4&quot;&gt;
__handlers__ = [
    [
        InlineQueryHandler(
            search
        )
    ]
]&lt;/pre&gt;
  &lt;p id=&quot;GTL6&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;uGyy&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;wpiD&quot;&gt;↪️файл manage_admins.py&lt;/h3&gt;
  &lt;p id=&quot;OYRt&quot;&gt;&lt;/p&gt;
  &lt;pre id=&quot;xB01&quot;&gt;#!/usr/bin/env python3
# Copyright (C) @subinps
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.&lt;/pre&gt;
  &lt;pre id=&quot;25IN&quot;&gt;# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.&lt;/pre&gt;
  &lt;pre id=&quot;JCK2&quot;&gt;# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see &amp;lt;https://www.gnu.org/licenses/&amp;gt;.
from utils import LOGGER
from config import Config
from pyrogram import (
    Client, 
    filters
)
from utils import (
    get_admins, 
    sync_to_db, 
    delete_messages,
    sudo_filter
)&lt;/pre&gt;
  &lt;pre id=&quot;CdtQ&quot;&gt;
@Client.on_message(filters.command([&amp;#x27;vcpromote&amp;#x27;, f&amp;quot;vcpromote@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; sudo_filter)
async def add_admin(client, message):
    if message.reply_to_message:
        if message.reply_to_message.from_user.id is None:
            k = await message.reply(&amp;quot;You are an anonymous admin, you can&amp;#x27;t do this.&amp;quot;)
            await delete_messages([message, k])
            return
        user_id=message.reply_to_message.from_user.id
        user=message.reply_to_message.from_user&lt;/pre&gt;
  &lt;pre id=&quot;upO7&quot;&gt;    elif &amp;#x27; &amp;#x27; in message.text:
        c, user = message.text.split(&amp;quot; &amp;quot;, 1)
        if user.startswith(&amp;quot;@&amp;quot;):
            user=user.replace(&amp;quot;@&amp;quot;, &amp;quot;&amp;quot;)
            try:
                user=await client.get_users(user)
            except Exception as e:
                k=await message.reply(f&amp;quot;I was unable to locate that user.\nError: {e}&amp;quot;)
                LOGGER.error(f&amp;quot;Unable to find the user - {e}&amp;quot;, exc_info=True)
                await delete_messages([message, k])
                return
            user_id=user.id
        else:
            try:
                user_id=int(user)
                user=await client.get_users(user_id)
            except:
                k=await message.reply(f&amp;quot;You should give a user id or his username with @.&amp;quot;)
                await delete_messages([message, k])
                return
    else:
        k=await message.reply(&amp;quot;No user specified, reply to a user with /vcpromote or pass a users user id or username.&amp;quot;)
        await delete_messages([message, k])
        return
    if user_id in Config.ADMINS:
        k = await message.reply(&amp;quot;This user is already an admin.&amp;quot;) 
        await delete_messages([message, k])
        return
    Config.ADMINS.append(user_id)
    k=await message.reply(f&amp;quot;Succesfully promoted {user.mention} as VC admin&amp;quot;)
    await sync_to_db()
    await delete_messages([message, k])&lt;/pre&gt;
  &lt;pre id=&quot;5EjO&quot;&gt;
@Client.on_message(filters.command([&amp;#x27;vcdemote&amp;#x27;, f&amp;quot;vcdemote@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; sudo_filter)
async def remove_admin(client, message):
    if message.reply_to_message:
        if message.reply_to_message.from_user.id is None:
            k = await message.reply(&amp;quot;You are an anonymous admin, you can&amp;#x27;t do this.&amp;quot;)
            await delete_messages([message, k])
            return
        user_id=message.reply_to_message.from_user.id
        user=message.reply_to_message.from_user
    elif &amp;#x27; &amp;#x27; in message.text:
        c, user = message.text.split(&amp;quot; &amp;quot;, 1)
        if user.startswith(&amp;quot;@&amp;quot;):
            user=user.replace(&amp;quot;@&amp;quot;, &amp;quot;&amp;quot;)
            try:
                user=await client.get_users(user)
            except Exception as e:
                k = await message.reply(f&amp;quot;I was unable to locate that user.\nError: {e}&amp;quot;)
                LOGGER.error(f&amp;quot;Unable to Locate user, {e}&amp;quot;, exc_info=True)
                await delete_messages([message, k])
                return
            user_id=user.id
        else:
            try:
                user_id=int(user)
                user=await client.get_users(user_id)
            except:
                k = await message.reply(f&amp;quot;You should give a user id or his username with @.&amp;quot;)
                await delete_messages([message, k])
                return
    else:
        k = await message.reply(&amp;quot;No user specified, reply to a user with /vcdemote or pass a users user id or username.&amp;quot;)
        await delete_messages([message, k])
        return
    if not user_id in Config.ADMINS:
        k = await message.reply(&amp;quot;This user is not an admin yet.&amp;quot;)
        await delete_messages([message, k])
        return
    Config.ADMINS.remove(user_id)
    k = await message.reply(f&amp;quot;Succesfully Demoted {user.mention}&amp;quot;)
    await sync_to_db()
    await delete_messages([message, k])&lt;/pre&gt;
  &lt;pre id=&quot;leZB&quot;&gt;
@Client.on_message(filters.command([&amp;#x27;refresh&amp;#x27;, f&amp;quot;refresh@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; filters.user(Config.SUDO))
async def refresh_admins(client, message):
    Config.ADMIN_CACHE=False
    await get_admins(Config.CHAT)
    k = await message.reply(&amp;quot;Admin list has been refreshed&amp;quot;)
    await sync_to_db()
    await delete_messages([message, k])&lt;/pre&gt;
  &lt;p id=&quot;noFR&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;iV3f&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;s8KC&quot;&gt;↪️player.py&lt;/h3&gt;
  &lt;pre id=&quot;M7GJ&quot;&gt;#!/usr/bin/env python3
# Copyright (C) @subinps
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.&lt;/pre&gt;
  &lt;pre id=&quot;eUr7&quot;&gt;# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.&lt;/pre&gt;
  &lt;pre id=&quot;A6U0&quot;&gt;# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see &amp;lt;https://www.gnu.org/licenses/&amp;gt;.&lt;/pre&gt;
  &lt;pre id=&quot;etNj&quot;&gt;from utils import LOGGER
from youtube_search import YoutubeSearch
from contextlib import suppress
from pyrogram.types import Message
from yt_dlp import YoutubeDL
from datetime import datetime
from pyrogram import filters
from config import Config
from PTN import parse
import re
from utils import (
    add_to_db_playlist, 
    clear_db_playlist, 
    delete_messages, 
    download, 
    get_admins, 
    get_duration,
    is_admin, 
    get_buttons, 
    get_link, 
    import_play_list, 
    is_audio, 
    leave_call, 
    play, 
    get_playlist_str, 
    send_playlist, 
    shuffle_playlist, 
    start_stream, 
    stream_from_link, 
    chat_filter,
    c_play,
    is_ytdl_supported
)
from pyrogram.types import (
    InlineKeyboardMarkup, 
    InlineKeyboardButton
    )
from pyrogram.errors import (
    MessageIdInvalid, 
    MessageNotModified,
    UserNotParticipant,
    PeerIdInvalid,
    ChannelInvalid
    )
from pyrogram import (
    Client, 
    filters
    )&lt;/pre&gt;
  &lt;pre id=&quot;JKZc&quot;&gt;
admin_filter=filters.create(is_admin) &lt;/pre&gt;
  &lt;pre id=&quot;ZhlK&quot;&gt;@Client.on_message(filters.command([&amp;quot;play&amp;quot;, &amp;quot;fplay&amp;quot;, f&amp;quot;play@{Config.BOT_USERNAME}&amp;quot;, f&amp;quot;fplay@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; chat_filter)
async def add_to_playlist(_, message: Message):
    with suppress(MessageIdInvalid, MessageNotModified):
        admins = await get_admins(Config.CHAT)
        if Config.ADMIN_ONLY:
            if not (message.from_user is None and message.sender_chat or message.from_user.id in admins):
                k=await message.reply_sticker(&amp;quot;CAADBQADsQIAAtILIVYld1n74e3JuQI&amp;quot;)
                await delete_messages([message, k])
                return
        type=&amp;quot;&amp;quot;
        yturl=&amp;quot;&amp;quot;
        ysearch=&amp;quot;&amp;quot;
        url=&amp;quot;&amp;quot;
        if message.command[0] == &amp;quot;fplay&amp;quot;:
            if not (message.from_user is None and message.sender_chat or message.from_user.id in admins):
                k=await message.reply(&amp;quot;This command is only for admins.&amp;quot;)
                await delete_messages([message, k])
                return
        msg = await message.reply_text(&amp;quot;⚡️ **Checking recived input..**&amp;quot;)
        if message.reply_to_message and message.reply_to_message.video:
            await msg.edit(&amp;quot;⚡️ **Checking Telegram Media...**&amp;quot;)
            type=&amp;#x27;video&amp;#x27;
            m_video = message.reply_to_message.video       
        elif message.reply_to_message and message.reply_to_message.document:
            await msg.edit(&amp;quot;⚡️ **Checking Telegram Media...**&amp;quot;)
            m_video = message.reply_to_message.document
            type=&amp;#x27;video&amp;#x27;
            if not &amp;quot;video&amp;quot; in m_video.mime_type:
                return await msg.edit(&amp;quot;The given file is invalid&amp;quot;)
        elif message.reply_to_message and message.reply_to_message.audio:
            #if not Config.IS_VIDEO:
                #return await message.reply(&amp;quot;Play from audio file is available only if Video Mode if turned off.\nUse /settings to configure ypur player.&amp;quot;)
            await msg.edit(&amp;quot;⚡️ **Checking Telegram Media...**&amp;quot;)
            type=&amp;#x27;audio&amp;#x27;
            m_video = message.reply_to_message.audio       
        else:
            if message.reply_to_message and message.reply_to_message.text:
                query=message.reply_to_message.text
            elif &amp;quot; &amp;quot; in message.text:
                text = message.text.split(&amp;quot; &amp;quot;, 1)
                query = text[1]
            else:
                await msg.edit(&amp;quot;You Didn&amp;#x27;t gave me anything to play.Reply to a video or a youtube link or a direct link.&amp;quot;)
                await delete_messages([message, msg])
                return
            regex = r&amp;quot;^(?:https?:\/\/)?(?:www\.)?youtu\.?be(?:\.com)?\/?.*(?:watch|embed)?(?:.*v=|v\/|\/)([\w\-_]+)\&amp;amp;?&amp;quot;
            match = re.match(regex,query)
            if match:
                type=&amp;quot;youtube&amp;quot;
                yturl=query
            elif query.startswith(&amp;quot;http&amp;quot;):
                try:
                    has_audio_ = await is_audio(query)
                except:
                    has_audio_ = False
                    LOGGER.error(&amp;quot;Unable to get Audio properties within time.&amp;quot;)
                if has_audio_:
                    try:
                        dur=await get_duration(query)
                    except:
                        dur=0
                    if dur == 0:
                        await msg.edit(&amp;quot;This is a live stream, Use /stream command.&amp;quot;)
                        await delete_messages([message, msg])
                        return 
                    type=&amp;quot;direct&amp;quot;
                    url=query
                else:
                    if is_ytdl_supported(query):
                        type=&amp;quot;ytdl_s&amp;quot;
                        url=query
                    else:
                        await msg.edit(&amp;quot;This is an invalid link, provide me a direct link or a youtube link.&amp;quot;)
                        await delete_messages([message, msg])
                        return
            else:
                type=&amp;quot;query&amp;quot;
                ysearch=query
        if not message.from_user is None:
            user=f&amp;quot;[{message.from_user.first_name}](tg://user?id={message.from_user.id})&amp;quot;
            user_id = message.from_user.id
        else:
            user=&amp;quot;Anonymous&amp;quot;
            user_id = &amp;quot;anonymous_admin&amp;quot;
        now = datetime.now()
        nyav = now.strftime(&amp;quot;%d-%m-%Y-%H:%M:%S&amp;quot;)
        if type in [&amp;quot;video&amp;quot;, &amp;quot;audio&amp;quot;]:
            if type == &amp;quot;audio&amp;quot;:
                if m_video.title is None:
                    if m_video.file_name is None:
                        title_ = &amp;quot;Music&amp;quot;
                    else:
                        title_ = m_video.file_name
                else:
                    title_ = m_video.title
                if m_video.performer is not None:
                    title = f&amp;quot;{m_video.performer} - {title_}&amp;quot;
                else:
                    title=title_
                unique = f&amp;quot;{nyav}_{m_video.file_size}_audio&amp;quot;
            else:
                title=m_video.file_name
                unique = f&amp;quot;{nyav}_{m_video.file_size}_video&amp;quot;
                if Config.PTN:
                    ny = parse(title)
                    title_ = ny.get(&amp;quot;title&amp;quot;)
                    if title_:
                        title = title_
            file_id=m_video.file_id
            if title is None:
                title = &amp;#x27;Music&amp;#x27;
            data={1:title, 2:file_id, 3:&amp;quot;telegram&amp;quot;, 4:user, 5:unique}
            if message.command[0] == &amp;quot;fplay&amp;quot;:
                pla = [data] + Config.playlist
                Config.playlist = pla
            else:
                Config.playlist.append(data)
            await add_to_db_playlist(data)        
            await msg.edit(&amp;quot;Media added to playlist&amp;quot;)
        elif type in [&amp;quot;youtube&amp;quot;, &amp;quot;query&amp;quot;, &amp;quot;ytdl_s&amp;quot;]:
            if type==&amp;quot;youtube&amp;quot;:
                await msg.edit(&amp;quot;⚡️ **Fetching Video From YouTube...**&amp;quot;)
                url=yturl
            elif type==&amp;quot;query&amp;quot;:
                try:
                    await msg.edit(&amp;quot;⚡️ **Fetching Video From YouTube...**&amp;quot;)
                    ytquery=ysearch
                    results = YoutubeSearch(ytquery, max_results=1).to_dict()
                    url = f&amp;quot;https://youtube.com{results[0][&amp;#x27;url_suffix&amp;#x27;]}&amp;quot;
                    title = results[0][&amp;quot;title&amp;quot;][:40]
                except Exception as e:
                    await msg.edit(
                        &amp;quot;Song not found.\nTry inline mode..&amp;quot;
                    )
                    LOGGER.error(str(e), exc_info=True)
                    await delete_messages([message, msg])
                    return
            elif type == &amp;quot;ytdl_s&amp;quot;:
                url=url
            else:
                return
            ydl_opts = {
                &amp;quot;quite&amp;quot;: True,
                &amp;quot;geo-bypass&amp;quot;: True,
                &amp;quot;nocheckcertificate&amp;quot;: True
            }
            ydl = YoutubeDL(ydl_opts)
            try:
                info = ydl.extract_info(url, False)
            except Exception as e:
                LOGGER.error(e, exc_info=True)
                await msg.edit(
                    f&amp;quot;YouTube Download Error ❌\nError:- {e}&amp;quot;
                    )
                LOGGER.error(str(e))
                await delete_messages([message, msg])
                return
            if type == &amp;quot;ytdl_s&amp;quot;:
                title = &amp;quot;Music&amp;quot;
                try:
                    title = info[&amp;#x27;title&amp;#x27;]
                except:
                    pass
            else:
                title = info[&amp;quot;title&amp;quot;]
                if info[&amp;#x27;duration&amp;#x27;] is None:
                    await msg.edit(&amp;quot;This is a live stream, Use /stream command.&amp;quot;)
                    await delete_messages([message, msg])
                    return 
            data={1:title, 2:url, 3:&amp;quot;youtube&amp;quot;, 4:user, 5:f&amp;quot;{nyav}_{user_id}&amp;quot;}
            if message.command[0] == &amp;quot;fplay&amp;quot;:
                pla = [data] + Config.playlist
                Config.playlist = pla
            else:
                Config.playlist.append(data)
            await add_to_db_playlist(data)
            await msg.edit(f&amp;quot;[{title}]({url}) added to playist&amp;quot;, disable_web_page_preview=True)
        elif type == &amp;quot;direct&amp;quot;:
            data={1:&amp;quot;Music&amp;quot;, 2:url, 3:&amp;quot;url&amp;quot;, 4:user, 5:f&amp;quot;{nyav}_{user_id}&amp;quot;}
            if message.command[0] == &amp;quot;fplay&amp;quot;:
                pla = [data] + Config.playlist
                Config.playlist = pla
            else:
                Config.playlist.append(data)
            await add_to_db_playlist(data)        
            await msg.edit(&amp;quot;Link added to playlist&amp;quot;)
        if not Config.CALL_STATUS \
            and len(Config.playlist) &amp;gt;= 1:
            await msg.edit(&amp;quot;Downloading and Processing...&amp;quot;)
            await download(Config.playlist[0], msg)
            await play()
        elif (len(Config.playlist) == 1 and Config.CALL_STATUS):
            await msg.edit(&amp;quot;Downloading and Processing...&amp;quot;)
            await download(Config.playlist[0], msg)  
            await play()
        elif message.command[0] == &amp;quot;fplay&amp;quot;:
            await msg.edit(&amp;quot;Downloading and Processing...&amp;quot;)
            await download(Config.playlist[0], msg)  
            await play()
        else:
            await send_playlist()  
        await msg.delete()
        pl=await get_playlist_str()
        if message.chat.type == &amp;quot;private&amp;quot;:
            await message.reply(pl, reply_markup=await get_buttons() ,disable_web_page_preview=True)       
        elif not Config.LOG_GROUP and message.chat.type == &amp;quot;supergroup&amp;quot;:
            if Config.msg.get(&amp;#x27;playlist&amp;#x27;) is not None:
                await Config.msg[&amp;#x27;playlist&amp;#x27;].delete()
            Config.msg[&amp;#x27;playlist&amp;#x27;]=await message.reply(pl, disable_web_page_preview=True, reply_markup=await get_buttons())    
            await delete_messages([message])  
        for track in Config.playlist[:2]:
            await download(track)&lt;/pre&gt;
  &lt;pre id=&quot;gb8s&quot;&gt;
@Client.on_message(filters.command([&amp;quot;leave&amp;quot;, f&amp;quot;leave@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; admin_filter &amp;amp; chat_filter)
async def leave_voice_chat(_, m: Message):
    if not Config.CALL_STATUS:        
        k=await m.reply(&amp;quot;Not joined any voicechat.&amp;quot;)
        await delete_messages([m, k])
        return
    await leave_call()
    k=await m.reply(&amp;quot;Succesfully left videochat.&amp;quot;)
    await delete_messages([m, k])&lt;/pre&gt;
  &lt;pre id=&quot;1oEF&quot;&gt;&lt;/pre&gt;
  &lt;pre id=&quot;VCtr&quot;&gt;@Client.on_message(filters.command([&amp;quot;shuffle&amp;quot;, f&amp;quot;shuffle@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; admin_filter &amp;amp; chat_filter)
async def shuffle_play_list(client, m: Message):
    if not Config.CALL_STATUS:
        k = await m.reply(&amp;quot;Not joined any voicechat.&amp;quot;)
        await delete_messages([m, k])
        return
    else:
        if len(Config.playlist) &amp;gt; 2:
            k=await m.reply_text(f&amp;quot;Playlist Shuffled.&amp;quot;)
            await shuffle_playlist()
            await delete_messages([m, k])            
        else:
            k=await m.reply_text(f&amp;quot;You cant shuffle playlist with less than 3 songs.&amp;quot;)
            await delete_messages([m, k])&lt;/pre&gt;
  &lt;pre id=&quot;5TXz&quot;&gt;
@Client.on_message(filters.command([&amp;quot;clearplaylist&amp;quot;, f&amp;quot;clearplaylist@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; admin_filter &amp;amp; chat_filter)
async def clear_play_list(client, m: Message):
    if not Config.playlist:
        k = await m.reply(&amp;quot;Playlist is empty.&amp;quot;)  
        await delete_messages([m, k])
        return
    Config.playlist.clear()
    k=await m.reply_text(f&amp;quot;Playlist Cleared.&amp;quot;)
    await clear_db_playlist(all=True)
    if Config.IS_LOOP \
        and not (Config.YPLAY or Config.CPLAY):
        await start_stream()
    else:
        await leave_call()
    await delete_messages([m, k])&lt;/pre&gt;
  &lt;pre id=&quot;fpDL&quot;&gt;&lt;/pre&gt;
  &lt;pre id=&quot;Glyr&quot;&gt;@Client.on_message(filters.command([&amp;quot;cplay&amp;quot;, f&amp;quot;cplay@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; admin_filter &amp;amp; chat_filter)
async def channel_play_list(client, m: Message):
    with suppress(MessageIdInvalid, MessageNotModified):
        k=await m.reply(&amp;quot;Setting up for channel play..&amp;quot;)
        if &amp;quot; &amp;quot; in m.text:
            you, me = m.text.split(&amp;quot; &amp;quot;, 1)
            if me.startswith(&amp;quot;-100&amp;quot;):
                try:
                    me=int(me)
                except:
                    await k.edit(&amp;quot;Invalid chat id given&amp;quot;)
                    await delete_messages([m, k])
                    return
                try:
                    await client.get_chat_member(int(me), Config.USER_ID)
                except (ValueError, PeerIdInvalid, ChannelInvalid):
                    LOGGER.error(f&amp;quot;Given channel is private and @{Config.BOT_USERNAME} is not an admin over there.&amp;quot;, exc_info=True)
                    await k.edit(f&amp;quot;Given channel is private and @{Config.BOT_USERNAME} is not an admin over there. If channel is not private , please provide username of channel.&amp;quot;)
                    await delete_messages([m, k])
                    return
                except UserNotParticipant:
                    LOGGER.error(&amp;quot;Given channel is private and USER account is not a member of channel.&amp;quot;)
                    await k.edit(&amp;quot;Given channel is private and USER account is not a member of channel.&amp;quot;)
                    await delete_messages([m, k])
                    return
                except Exception as e:
                    LOGGER.error(f&amp;quot;Errors occured while getting data abount channel - {e}&amp;quot;, exc_info=True)
                    await k.edit(f&amp;quot;Something went wrong- {e}&amp;quot;)
                    await delete_messages([m, k])
                    return
                await k.edit(&amp;quot;Searching files from channel, this may take some time, depending on number of files in the channel.&amp;quot;)
                st, msg = await c_play(me)
                if st == False:
                    await m.edit(msg)
                else:
                    await k.edit(f&amp;quot;Succesfully added {msg} files to playlist.&amp;quot;)
            elif me.startswith(&amp;quot;@&amp;quot;):
                me = me.replace(&amp;quot;@&amp;quot;, &amp;quot;&amp;quot;)
                try:
                    chat=await client.get_chat(me)
                except Exception as e:
                    LOGGER.error(f&amp;quot;Errors occured while fetching info about channel - {e}&amp;quot;, exc_info=True)
                    await k.edit(f&amp;quot;Errors occured while getting data about channel - {e}&amp;quot;)
                    await delete_messages([m, k])
                    return
                await k.edit(&amp;quot;Searching files from channel, this may take some time, depending on number of files in the channel.&amp;quot;)
                st, msg=await c_play(me)
                if st == False:
                    await k.edit(msg)
                    await delete_messages([m, k])
                else:
                    await k.edit(f&amp;quot;Succesfully Added {msg} files from {chat.title} to playlist&amp;quot;)
                    await delete_messages([m, k])
            else:
                await k.edit(&amp;quot;The given channel is invalid. For private channels it should start with -100 and for public channels it should start with @\nExamples - &amp;#x60;/cplay @VCPlayerFiles or /cplay -100125369865\n\nFor private channel, both bot and the USER account should be members of channel.&amp;quot;)
                await delete_messages([m, k])
        else:
            await k.edit(&amp;quot;You didn&amp;#x27;t gave me any channel. Give me a channel id or username from which i should play files . \nFor private channels it should start with -100 and for public channels it should start with @\nExamples - &amp;#x60;/cplay @VCPlayerFiles or /cplay -100125369865\n\nFor private channel, both bot and the USER account should be members of channel.&amp;quot;)
            await delete_messages([m, k])&lt;/pre&gt;
  &lt;pre id=&quot;Wdo7&quot;&gt;&lt;/pre&gt;
  &lt;pre id=&quot;4anN&quot;&gt;@Client.on_message(filters.command([&amp;quot;yplay&amp;quot;, f&amp;quot;yplay@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; admin_filter &amp;amp; chat_filter)
async def yt_play_list(client, m: Message):
    with suppress(MessageIdInvalid, MessageNotModified):
        if m.reply_to_message is not None and m.reply_to_message.document:
            if m.reply_to_message.document.file_name != &amp;quot;YouTube_PlayList.json&amp;quot;:
                k=await m.reply(&amp;quot;Invalid PlayList file given. Use @GetPlayListBot  or search for a playlist in @DumpPlaylist to get a playlist file.&amp;quot;)
                await delete_messages([m, k])
                return
            ytplaylist=await m.reply_to_message.download()
            status=await m.reply(&amp;quot;Trying to get details from playlist.&amp;quot;)
            n=await import_play_list(ytplaylist)
            if not n:
                await status.edit(&amp;quot;Errors Occured while importing playlist.&amp;quot;)
                await delete_messages([m, status])
                return
            if Config.SHUFFLE:
                await shuffle_playlist()
            pl=await get_playlist_str()
            if m.chat.type == &amp;quot;private&amp;quot;:
                await status.edit(pl, disable_web_page_preview=True, reply_markup=await get_buttons())        
            elif not Config.LOG_GROUP and m.chat.type == &amp;quot;supergroup&amp;quot;:
                if Config.msg.get(&amp;quot;playlist&amp;quot;) is not None:
                    await Config.msg[&amp;#x27;playlist&amp;#x27;].delete()
                Config.msg[&amp;#x27;playlist&amp;#x27;]=await status.edit(pl, disable_web_page_preview=True, reply_markup=await get_buttons())
                await delete_messages([m])
            else:
                await delete_messages([m, status])
        else:
            k=await m.reply(&amp;quot;No playList file given. Use @GetPlayListBot  or search for a playlist in @DumpPlaylist to get a playlist file.&amp;quot;)
            await delete_messages([m, k])&lt;/pre&gt;
  &lt;pre id=&quot;3EDi&quot;&gt;
@Client.on_message(filters.command([&amp;quot;stream&amp;quot;, f&amp;quot;stream@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; admin_filter &amp;amp; chat_filter)
async def stream(client, m: Message):
    with suppress(MessageIdInvalid, MessageNotModified):
        msg=await m.reply(&amp;quot;Checking the recived input.&amp;quot;)
        if m.reply_to_message and m.reply_to_message.text:
            link=m.reply_to_message.text
        elif &amp;quot; &amp;quot; in m.text:
            text = m.text.split(&amp;quot; &amp;quot;, 1)
            link = text[1]
        else:
            k = await msg.edit(&amp;quot;Provide a link to stream!&amp;quot;)
            await delete_messages([m, k])
            return
        regex = r&amp;quot;^(?:https?:\/\/)?(?:www\.)?youtu\.?be(?:\.com)?\/?.*(?:watch|embed)?(?:.*v=|v\/|\/)([\w\-_]+)\&amp;amp;?&amp;quot;
        match = re.match(regex,link)
        if match:
            stream_link=await get_link(link)
            if not stream_link:
                k = await msg.edit(&amp;quot;This is an invalid link.&amp;quot;)
                await delete_messages([m, k])
                return
        else:
            stream_link=link
        try:
            is_audio_ = await is_audio(stream_link)
        except:
            is_audio_ = False
            LOGGER.error(&amp;quot;Unable to get Audio properties within time.&amp;quot;)
        if not is_audio_:
            k = await msg.edit(&amp;quot;This is an invalid link, provide me a direct link or a youtube link.&amp;quot;)
            await delete_messages([m, k])
            return
        try:
            dur=await get_duration(stream_link)
        except:
            dur=0
        if dur != 0:
            k = await msg.edit(&amp;quot;This is not a live stream, Use /play command.&amp;quot;)
            await delete_messages([m, k])
            return
        k, msg_=await stream_from_link(stream_link)
        if k == False:
            k = await msg.edit(msg_)
            await delete_messages([m, k])
            return
        if Config.msg.get(&amp;#x27;player&amp;#x27;):
            await Config.msg[&amp;#x27;player&amp;#x27;].delete()
        Config.msg[&amp;#x27;player&amp;#x27;]=await msg.edit(f&amp;quot;[Streaming]({stream_link}) Started. ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ&amp;quot;, disable_web_page_preview=True, reply_markup=await get_buttons())
        await delete_messages([m])
        &lt;/pre&gt;
  &lt;pre id=&quot;6ddx&quot;&gt;
admincmds=[&amp;quot;yplay&amp;quot;, &amp;quot;leave&amp;quot;, &amp;quot;pause&amp;quot;, &amp;quot;resume&amp;quot;, &amp;quot;skip&amp;quot;, &amp;quot;restart&amp;quot;, &amp;quot;volume&amp;quot;, &amp;quot;shuffle&amp;quot;, &amp;quot;clearplaylist&amp;quot;, &amp;quot;export&amp;quot;, &amp;quot;import&amp;quot;, &amp;quot;update&amp;quot;, &amp;#x27;replay&amp;#x27;, &amp;#x27;logs&amp;#x27;, &amp;#x27;stream&amp;#x27;, &amp;#x27;fplay&amp;#x27;, &amp;#x27;schedule&amp;#x27;, &amp;#x27;record&amp;#x27;, &amp;#x27;slist&amp;#x27;, &amp;#x27;cancel&amp;#x27;, &amp;#x27;cancelall&amp;#x27;, &amp;#x27;vcpromote&amp;#x27;, &amp;#x27;vcdemote&amp;#x27;, &amp;#x27;refresh&amp;#x27;, &amp;#x27;rtitle&amp;#x27;, &amp;#x27;seek&amp;#x27;, &amp;#x27;vcmute&amp;#x27;, &amp;#x27;unmute&amp;#x27;,
f&amp;#x27;stream@{Config.BOT_USERNAME}&amp;#x27;, f&amp;#x27;logs@{Config.BOT_USERNAME}&amp;#x27;, f&amp;quot;replay@{Config.BOT_USERNAME}&amp;quot;, f&amp;quot;yplay@{Config.BOT_USERNAME}&amp;quot;, f&amp;quot;leave@{Config.BOT_USERNAME}&amp;quot;, f&amp;quot;pause@{Config.BOT_USERNAME}&amp;quot;, f&amp;quot;resume@{Config.BOT_USERNAME}&amp;quot;, f&amp;quot;skip@{Config.BOT_USERNAME}&amp;quot;, 
f&amp;quot;restart@{Config.BOT_USERNAME}&amp;quot;, f&amp;quot;volume@{Config.BOT_USERNAME}&amp;quot;, f&amp;quot;shuffle@{Config.BOT_USERNAME}&amp;quot;, f&amp;quot;clearplaylist@{Config.BOT_USERNAME}&amp;quot;, f&amp;quot;export@{Config.BOT_USERNAME}&amp;quot;, f&amp;quot;import@{Config.BOT_USERNAME}&amp;quot;, f&amp;quot;update@{Config.BOT_USERNAME}&amp;quot;,
f&amp;#x27;play@{Config.BOT_USERNAME}&amp;#x27;, f&amp;#x27;schedule@{Config.BOT_USERNAME}&amp;#x27;, f&amp;#x27;record@{Config.BOT_USERNAME}&amp;#x27;, f&amp;#x27;slist@{Config.BOT_USERNAME}&amp;#x27;, f&amp;#x27;cancel@{Config.BOT_USERNAME}&amp;#x27;, f&amp;#x27;cancelall@{Config.BOT_USERNAME}&amp;#x27;, f&amp;#x27;vcpromote@{Config.BOT_USERNAME}&amp;#x27;, 
f&amp;#x27;vcdemote@{Config.BOT_USERNAME}&amp;#x27;, f&amp;#x27;refresh@{Config.BOT_USERNAME}&amp;#x27;, f&amp;#x27;rtitle@{Config.BOT_USERNAME}&amp;#x27;, f&amp;#x27;seek@{Config.BOT_USERNAME}&amp;#x27;, f&amp;#x27;mute@{Config.BOT_USERNAME}&amp;#x27;, f&amp;#x27;vcunmute@{Config.BOT_USERNAME}&amp;#x27;
]&lt;/pre&gt;
  &lt;pre id=&quot;8vaA&quot;&gt;allcmd = [&amp;quot;play&amp;quot;, &amp;quot;player&amp;quot;, f&amp;quot;play@{Config.BOT_USERNAME}&amp;quot;, f&amp;quot;player@{Config.BOT_USERNAME}&amp;quot;] + admincmds&lt;/pre&gt;
  &lt;pre id=&quot;D2pB&quot;&gt;@Client.on_message(filters.command(admincmds) &amp;amp; ~admin_filter &amp;amp; chat_filter)
async def notforu(_, m: Message):
    k = await _.send_cached_media(chat_id=m.chat.id, file_id=&amp;quot;CAADBQADEgQAAtMJyFVJOe6-VqYVzAI&amp;quot;, caption=&amp;quot;You Are Not Authorized&amp;quot;, reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(&amp;#x27;⚡️Join Here&amp;#x27;, url=&amp;#x27;https://t.me/subin_works&amp;#x27;)]]))
    await delete_messages([m, k])&lt;/pre&gt;
  &lt;pre id=&quot;EtTg&quot;&gt;@Client.on_message(filters.command(allcmd) &amp;amp; ~chat_filter &amp;amp; filters.group)
async def not_chat(_, m: Message):
    if m.from_user is not None and m.from_user.id in Config.SUDO:
        buttons = [
            [
                InlineKeyboardButton(&amp;#x27;⚡️Change CHAT&amp;#x27;, callback_data=&amp;#x27;set_new_chat&amp;#x27;),
            ],
            [
                InlineKeyboardButton(&amp;#x27;No&amp;#x27;, callback_data=&amp;#x27;closesudo&amp;#x27;),
            ]
            ]
        await m.reply(&amp;quot;This is not the group which i have been configured to play, Do you want to set this group as default CHAT?&amp;quot;, reply_markup=InlineKeyboardMarkup(buttons))
        await delete_messages([m])
    else:
        buttons = [
            [
                InlineKeyboardButton(&amp;#x27;⚡️Make Own Bot&amp;#x27;, url=&amp;#x27;https://github.com/subinps/VCPlayerBot&amp;#x27;),
                InlineKeyboardButton(&amp;#x27;🧩 Join Here&amp;#x27;, url=&amp;#x27;https://t.me/subin_works&amp;#x27;),
            ]
            ]
        await m.reply(&amp;quot;&amp;lt;b&amp;gt;You can&amp;#x27;t use this bot in this group, for that you have to make your own bot from the [SOURCE CODE](https://github.com/subinps/VCPlayerBot) below.&amp;lt;/b&amp;gt;&amp;quot;, disable_web_page_preview=True, reply_markup=InlineKeyboardMarkup(buttons))&lt;/pre&gt;
  &lt;p id=&quot;LCSU&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;xsJf&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;lpln&quot;&gt;↪️recorder.py&lt;/h3&gt;
  &lt;pre id=&quot;r3JT&quot;&gt;#!/usr/bin/env python3
# Copyright (C) @subinps
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.&lt;/pre&gt;
  &lt;pre id=&quot;LMlD&quot;&gt;# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.&lt;/pre&gt;
  &lt;pre id=&quot;1IRj&quot;&gt;# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see &amp;lt;https://www.gnu.org/licenses/&amp;gt;.&lt;/pre&gt;
  &lt;pre id=&quot;StN1&quot;&gt;from utils import LOGGER
from config import Config
from pyrogram import (
    Client, 
    filters
)
from utils import (
    chat_filter, 
    is_admin, 
    is_admin, 
    delete_messages, 
    recorder_settings,
    sync_to_db
)
from pyrogram.types import (
    InlineKeyboardMarkup, 
    InlineKeyboardButton
)&lt;/pre&gt;
  &lt;pre id=&quot;x4NF&quot;&gt;admin_filter=filters.create(is_admin) &lt;/pre&gt;
  &lt;pre id=&quot;c9Kt&quot;&gt;
@Client.on_message(filters.command([&amp;quot;record&amp;quot;, f&amp;quot;record@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; admin_filter &amp;amp; chat_filter)
async def record_vc(bot, message):
    await message.reply(&amp;quot;Configure you VCPlayer Recording settings from hereㅤㅤ ㅤ&amp;quot;, reply_markup=(await recorder_settings()))
    await delete_messages([message])&lt;/pre&gt;
  &lt;pre id=&quot;HfL2&quot;&gt;@Client.on_message(filters.command([&amp;quot;rtitle&amp;quot;, f&amp;quot;rtitle@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; admin_filter &amp;amp; chat_filter)
async def recording_title(bot, message):
    m=await message.reply(&amp;quot;Checking..&amp;quot;)
    if &amp;quot; &amp;quot; in message.text:
        cmd, title = message.text.split(&amp;quot; &amp;quot;, 1)
    else:
        await m.edit(&amp;quot;Give me a new title. Use /rtitle &amp;lt; Custom Title &amp;gt;\nUse &amp;lt;code&amp;gt;False&amp;lt;/code&amp;gt; to revert to default title&amp;quot;)
        await delete_messages([message, m])
        return&lt;/pre&gt;
  &lt;pre id=&quot;dn59&quot;&gt;    if Config.DATABASE_URI:
        await m.edit(&amp;quot;Mongo DB Found, Setting up recording title...&amp;quot;) 
        if title == &amp;quot;False&amp;quot;:
            await m.edit(f&amp;quot;Sucessfully removed custom recording title.&amp;quot;)
            Config.RECORDING_TITLE=False
            await sync_to_db()
            await delete_messages([message, m])           
            return
        else:
            Config.RECORDING_TITLE=title
            await sync_to_db()
            await m.edit(f&amp;quot;Succesfully changed recording title to {title}&amp;quot;)
            await delete_messages([message, m])
            return
    else:
        if not Config.HEROKU_APP:
            buttons = [[InlineKeyboardButton(&amp;#x27;Heroku API_KEY&amp;#x27;, url=&amp;#x27;https://dashboard.heroku.com/account/applications/authorizations/new&amp;#x27;), InlineKeyboardButton(&amp;#x27;🗑 Close&amp;#x27;, callback_data=&amp;#x27;close&amp;#x27;),]]
            await m.edit(
                text=&amp;quot;No heroku app found, this command needs the following heroku vars to be set.\n\n1. &amp;lt;code&amp;gt;HEROKU_API_KEY&amp;lt;/code&amp;gt;: Your heroku account api key.\n2. &amp;lt;code&amp;gt;HEROKU_APP_NAME&amp;lt;/code&amp;gt;: Your heroku app name.&amp;quot;, 
                reply_markup=InlineKeyboardMarkup(buttons)) 
            await delete_messages([message])
            return     
        config = Config.HEROKU_APP.config()
        if title == &amp;quot;False&amp;quot;:
            if &amp;quot;RECORDING_TITLE&amp;quot; in config:
                await m.edit(f&amp;quot;Sucessfully removed custom recording title. Now restarting..&amp;quot;)
                await delete_messages([message])
                del config[&amp;quot;RECORDING_TITLE&amp;quot;]                
                config[&amp;quot;RECORDING_TITLE&amp;quot;] = None
            else:
                await m.edit(f&amp;quot;Its already default title, nothing was changed&amp;quot;)
                Config.RECORDING_TITLE=False
                await delete_messages([message, m])
        else:
            await m.edit(f&amp;quot;Succesfully changed recording title to {title}, Now restarting&amp;quot;)
            await delete_messages([message])
            config[&amp;quot;RECORDING_TITLE&amp;quot;] = title&lt;/pre&gt;
  &lt;p id=&quot;f2qb&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;FOEs&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;KnEC&quot;&gt;↪️scheduler.py&lt;/h3&gt;
  &lt;pre id=&quot;KlM3&quot;&gt;#!/usr/bin/env python3
# Copyright (C) @subinps
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.&lt;/pre&gt;
  &lt;pre id=&quot;lgzB&quot;&gt;# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.&lt;/pre&gt;
  &lt;pre id=&quot;0jOl&quot;&gt;# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see &amp;lt;https://www.gnu.org/licenses/&amp;gt;.
from utils import LOGGER
import re
import calendar
from datetime import datetime
from contextlib import suppress
import pytz
from config import Config
from PTN import parse
from youtube_search import YoutubeSearch
from yt_dlp import YoutubeDL&lt;/pre&gt;
  &lt;pre id=&quot;TXCi&quot;&gt;from pyrogram import(
    Client, 
    filters
    )
from pyrogram.types import (
    InlineKeyboardButton, 
    InlineKeyboardMarkup
)
from utils import (
    delete_messages,
    is_admin,
    sync_to_db,
    is_audio,
    chat_filter,
    scheduler,
    is_ytdl_supported
)&lt;/pre&gt;
  &lt;pre id=&quot;g3ci&quot;&gt;from pyrogram.types import (
    InlineKeyboardButton, 
    InlineKeyboardMarkup
)
from pyrogram.errors import (
    MessageIdInvalid, 
    MessageNotModified
)&lt;/pre&gt;
  &lt;pre id=&quot;xc9a&quot;&gt;
IST = pytz.timezone(Config.TIME_ZONE)&lt;/pre&gt;
  &lt;pre id=&quot;g70S&quot;&gt;admin_filter=filters.create(is_admin) &lt;/pre&gt;
  &lt;pre id=&quot;dME0&quot;&gt;&lt;/pre&gt;
  &lt;pre id=&quot;PmOj&quot;&gt;@Client.on_message(filters.command([&amp;quot;schedule&amp;quot;, f&amp;quot;schedule@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; chat_filter &amp;amp; admin_filter)
async def schedule_vc(bot, message):
    with suppress(MessageIdInvalid, MessageNotModified):
        type=&amp;quot;&amp;quot;
        yturl=&amp;quot;&amp;quot;
        ysearch=&amp;quot;&amp;quot;
        msg = await message.reply_text(&amp;quot;⚡️ **Checking recived input..**&amp;quot;)
        if message.reply_to_message and message.reply_to_message.video:
            await msg.edit(&amp;quot;⚡️ **Checking Telegram Media...**&amp;quot;)
            type=&amp;#x27;video&amp;#x27;
            m_video = message.reply_to_message.video       
        elif message.reply_to_message and message.reply_to_message.document:
            await msg.edit(&amp;quot;⚡️ **Checking Telegram Media...**&amp;quot;)
            m_video = message.reply_to_message.document
            type=&amp;#x27;video&amp;#x27;
            if not &amp;quot;video&amp;quot; in m_video.mime_type:
                return await msg.edit(&amp;quot;The given file is invalid&amp;quot;)
        elif message.reply_to_message and message.reply_to_message.audio:
            #if not Config.IS_VIDEO:
                #return await message.reply(&amp;quot;Play from audio file is available only if Video Mode if turned off.\nUse /settings to configure ypur player.&amp;quot;)
            await msg.edit(&amp;quot;⚡️ **Checking Telegram Media...**&amp;quot;)
            type=&amp;#x27;audio&amp;#x27;
            m_video = message.reply_to_message.audio       
        else:
            if message.reply_to_message and message.reply_to_message.text:
                query=message.reply_to_message.text
            elif &amp;quot; &amp;quot; in message.text:
                text = message.text.split(&amp;quot; &amp;quot;, 1)
                query = text[1]
            else:
                await msg.edit(&amp;quot;You Didn&amp;#x27;t gave me anything to schedule. Reply to a video or a youtube link or a direct link.&amp;quot;)
                await delete_messages([message, msg])
                return
            regex = r&amp;quot;^(?:https?:\/\/)?(?:www\.)?youtu\.?be(?:\.com)?\/?.*(?:watch|embed)?(?:.*v=|v\/|\/)([\w\-_]+)\&amp;amp;?&amp;quot;
            match = re.match(regex,query)
            if match:
                type=&amp;quot;youtube&amp;quot;
                yturl=query
            elif query.startswith(&amp;quot;http&amp;quot;):
                has_audio_ = await is_audio(query)
                if not has_audio_:
                    if is_ytdl_supported(query):
                        type=&amp;quot;ytdl_s&amp;quot;
                        url=query
                    else:
                        await msg.edit(&amp;quot;This is an invalid link, provide me a direct link or a youtube link.&amp;quot;)
                        await delete_messages([message, msg])
                        return
                type=&amp;quot;direct&amp;quot;
                url=query
            else:
                type=&amp;quot;query&amp;quot;
                ysearch=query
        if not message.from_user is None:
            user=f&amp;quot;[{message.from_user.first_name}](tg://user?id={message.from_user.id}) - (Scheduled)&amp;quot;
            user_id = message.from_user.id
        else:
            user=&amp;quot;Anonymous - (Scheduled)&amp;quot;
            user_id = &amp;quot;anonymous_admin&amp;quot;
        now = datetime.now()
        nyav = now.strftime(&amp;quot;%d-%m-%Y-%H:%M:%S&amp;quot;)
        if type in [&amp;quot;video&amp;quot;, &amp;quot;audio&amp;quot;]:
            if type == &amp;quot;audio&amp;quot;:
                if m_video.title is None:
                    if m_video.file_name is None:
                        title_ = &amp;quot;Music&amp;quot;
                    else:
                        title_ = m_video.file_name
                else:
                    title_ = m_video.title
                if m_video.performer is not None:
                    title = f&amp;quot;{m_video.performer} - {title_}&amp;quot;
                else:
                    title=title_
                unique = f&amp;quot;{nyav}_{m_video.file_size}_audio&amp;quot;
            else:
                title=m_video.file_name
                unique = f&amp;quot;{nyav}_{m_video.file_size}_video&amp;quot;
                if Config.PTN:
                    ny = parse(title)
                    title_ = ny.get(&amp;quot;title&amp;quot;)
                    if title_:
                        title = title_
            if title is None:
                title = &amp;#x27;Music&amp;#x27;
            data={&amp;#x27;1&amp;#x27;:title, &amp;#x27;2&amp;#x27;:m_video.file_id, &amp;#x27;3&amp;#x27;:&amp;quot;telegram&amp;quot;, &amp;#x27;4&amp;#x27;:user, &amp;#x27;5&amp;#x27;:unique}
            sid=f&amp;quot;{message.chat.id}_{msg.message_id}&amp;quot;
            Config.SCHEDULED_STREAM[sid] = data
            await sync_to_db()
        elif type in [&amp;quot;youtube&amp;quot;, &amp;quot;query&amp;quot;, &amp;quot;ytdl_s&amp;quot;]:
            if type==&amp;quot;youtube&amp;quot;:
                await msg.edit(&amp;quot;⚡️ **Fetching Video From YouTube...**&amp;quot;)
                url=yturl
            elif type==&amp;quot;query&amp;quot;:
                try:
                    await msg.edit(&amp;quot;⚡️ **Fetching Video From YouTube...**&amp;quot;)
                    ytquery=ysearch
                    results = YoutubeSearch(ytquery, max_results=1).to_dict()
                    url = f&amp;quot;https://youtube.com{results[0][&amp;#x27;url_suffix&amp;#x27;]}&amp;quot;
                    title = results[0][&amp;quot;title&amp;quot;][:40]
                except Exception as e:
                    await msg.edit(
                        &amp;quot;Song not found.\nTry inline mode..&amp;quot;
                    )
                    LOGGER.error(str(e), exc_info=True)
                    await delete_messages([message, msg])
                    return
            elif type == &amp;quot;ytdl_s&amp;quot;:
                url=url
            else:
                return
            ydl_opts = {
                &amp;quot;quite&amp;quot;: True,
                &amp;quot;geo-bypass&amp;quot;: True,
                &amp;quot;nocheckcertificate&amp;quot;: True
            }
            ydl = YoutubeDL(ydl_opts)
            try:
                info = ydl.extract_info(url, False)
            except Exception as e:
                LOGGER.error(e, exc_info=True)
                await msg.edit(
                    f&amp;quot;YouTube Download Error ❌\nError:- {e}&amp;quot;
                    )
                LOGGER.error(str(e))
                await delete_messages([message, msg])
                return
            if type == &amp;quot;ytdl_s&amp;quot;:
                title = &amp;quot;Music&amp;quot;
                try:
                    title=info[&amp;#x27;title&amp;#x27;]
                except:
                    pass
            else:
                title = info[&amp;quot;title&amp;quot;]
            data={&amp;#x27;1&amp;#x27;:title, &amp;#x27;2&amp;#x27;:url, &amp;#x27;3&amp;#x27;:&amp;quot;youtube&amp;quot;, &amp;#x27;4&amp;#x27;:user, &amp;#x27;5&amp;#x27;:f&amp;quot;{nyav}_{user_id}&amp;quot;}
            sid=f&amp;quot;{message.chat.id}_{msg.message_id}&amp;quot;
            Config.SCHEDULED_STREAM[sid] = data
            await sync_to_db()
        elif type == &amp;quot;direct&amp;quot;:
            data={&amp;quot;1&amp;quot;:&amp;quot;Music&amp;quot;, &amp;#x27;2&amp;#x27;:url, &amp;#x27;3&amp;#x27;:&amp;quot;url&amp;quot;, &amp;#x27;4&amp;#x27;:user, &amp;#x27;5&amp;#x27;:f&amp;quot;{nyav}_{user_id}&amp;quot;}
            sid=f&amp;quot;{message.chat.id}_{msg.message_id}&amp;quot;
            Config.SCHEDULED_STREAM[sid] = data
            await sync_to_db()
        if message.chat.type!=&amp;#x27;private&amp;#x27; and message.from_user is None:
            await msg.edit(
                text=&amp;quot;You cant schedule from here since you are an anonymous admin. Click the schedule button to schedule through private chat.&amp;quot;,
                reply_markup=InlineKeyboardMarkup(
                    [
                        [
                            InlineKeyboardButton(f&amp;quot;Schedule&amp;quot;, url=f&amp;quot;https://telegram.dog/{Config.BOT_USERNAME}?start=sch_{sid}&amp;quot;),
                        ]
                    ]
                ),)
            await delete_messages([message, msg])
            return
        today = datetime.now(IST)
        smonth=today.strftime(&amp;quot;%B&amp;quot;)
        obj = calendar.Calendar()
        thisday = today.day
        year = today.year
        month = today.month
        m=obj.monthdayscalendar(year, month)
        button=[]
        button.append([InlineKeyboardButton(text=f&amp;quot;{str(smonth)}  {str(year)}&amp;quot;,callback_data=f&amp;quot;sch_month_choose_none_none&amp;quot;)])
        days=[&amp;quot;Mon&amp;quot;, &amp;quot;Tues&amp;quot;, &amp;quot;Wed&amp;quot;, &amp;quot;Thu&amp;quot;, &amp;quot;Fri&amp;quot;, &amp;quot;Sat&amp;quot;, &amp;quot;Sun&amp;quot;]
        f=[]
        for day in days:
            f.append(InlineKeyboardButton(text=f&amp;quot;{day}&amp;quot;,callback_data=f&amp;quot;day_info_none&amp;quot;))
        button.append(f)
        for one in m:
            f=[]
            for d in one:
                year_=year
                if d &amp;lt; int(today.day):
                    year_ += 1
                if d == 0:
                    k=&amp;quot;\u2063&amp;quot;   
                    d=&amp;quot;none&amp;quot;   
                else:
                    k=d    
                f.append(InlineKeyboardButton(text=f&amp;quot;{k}&amp;quot;,callback_data=f&amp;quot;sch_month_{year_}_{month}_{d}&amp;quot;))
            button.append(f)
        button.append([InlineKeyboardButton(&amp;quot;Close&amp;quot;, callback_data=&amp;quot;schclose&amp;quot;)])
        await msg.edit(f&amp;quot;Choose the day of the month you want to schedule the voicechat.\nToday is {thisday} {smonth} {year}. Chooosing a date preceeding today will be considered as next year {year+1}&amp;quot;, reply_markup=InlineKeyboardMarkup(button))&lt;/pre&gt;
  &lt;pre id=&quot;MMCn&quot;&gt;&lt;/pre&gt;
  &lt;pre id=&quot;mfHh&quot;&gt;
@Client.on_message(filters.command([&amp;quot;slist&amp;quot;, f&amp;quot;slist@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; admin_filter &amp;amp; chat_filter)
async def list_schedule(bot, message):
    k=await message.reply(&amp;quot;Checking schedules...&amp;quot;)
    if not Config.SCHEDULE_LIST:
        await k.edit(&amp;quot;Nothing scheduled to play.&amp;quot;)
        await delete_messages([k, message])
        return
    text=&amp;quot;Current Schedules:\n\n&amp;quot;
    s=Config.SCHEDULE_LIST
    f=1
    for sch in s:
        details=Config.SCHEDULED_STREAM.get(sch[&amp;#x27;job_id&amp;#x27;])
        if not details[&amp;#x27;3&amp;#x27;]==&amp;quot;telegram&amp;quot;:
            text+=f&amp;quot;&amp;lt;b&amp;gt;{f}.&amp;lt;/b&amp;gt; Title: [{details[&amp;#x27;1&amp;#x27;]}]({details[&amp;#x27;2&amp;#x27;]}) By {details[&amp;#x27;4&amp;#x27;]}\n&amp;quot;
        else:
            text+=f&amp;quot;&amp;lt;b&amp;gt;{f}.&amp;lt;/b&amp;gt; Title: {details[&amp;#x27;1&amp;#x27;]} By {details[&amp;#x27;4&amp;#x27;]}\n&amp;quot;
        date = sch[&amp;#x27;date&amp;#x27;]
        f+=1
        date_=((pytz.utc.localize(date, is_dst=None).astimezone(IST)).replace(tzinfo=None)).strftime(&amp;quot;%b %d %Y, %I:%M %p&amp;quot;)
        text+=f&amp;quot;Shedule ID : &amp;lt;code&amp;gt;{sch[&amp;#x27;job_id&amp;#x27;]}&amp;lt;/code&amp;gt;\nSchedule Date : &amp;lt;code&amp;gt;{date_}&amp;lt;/code&amp;gt;\n\n&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;TCNU&quot;&gt;    await k.edit(text, disable_web_page_preview=True)
    await delete_messages([message])&lt;/pre&gt;
  &lt;pre id=&quot;VDJU&quot;&gt;
@Client.on_message(filters.command([&amp;quot;cancel&amp;quot;, f&amp;quot;cancel@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; admin_filter &amp;amp; chat_filter)
async def delete_sch(bot, message):
    with suppress(MessageIdInvalid, MessageNotModified):
        m = await message.reply(&amp;quot;Finding the scheduled stream..&amp;quot;)
        if &amp;quot; &amp;quot; in message.text:
            cmd, job_id = message.text.split(&amp;quot; &amp;quot;, 1)
        else:
            buttons = [
                [
                    InlineKeyboardButton(&amp;#x27;Cancel All Schedules&amp;#x27;, callback_data=&amp;#x27;schcancel&amp;#x27;),
                    InlineKeyboardButton(&amp;#x27;No&amp;#x27;, callback_data=&amp;#x27;schclose&amp;#x27;),
                ]
            ]
            reply_markup = InlineKeyboardMarkup(buttons)
            await m.edit(&amp;quot;No Schedule ID  specified!! Do you want to Cancel all scheduled streams? or you can find schedul id using /slist command.&amp;quot;, reply_markup=reply_markup)
            await delete_messages([message])
            return
        data=Config.SCHEDULED_STREAM.get(job_id)
        if not data:
            await m.edit(&amp;quot;You gave me an invalid schedule ID, check again and send.&amp;quot;)
            await delete_messages([message, m])
            return
        del Config.SCHEDULED_STREAM[job_id]
        k=scheduler.get_job(job_id, jobstore=None)
        if k:
            scheduler.remove_job(job_id, jobstore=None)
        old=list(filter(lambda k: k[&amp;#x27;job_id&amp;#x27;] == job_id, Config.SCHEDULE_LIST))
        if old:
            for old_ in old:
                Config.SCHEDULE_LIST.remove(old_)
        await sync_to_db()
        await m.edit(f&amp;quot;Succesfully deleted {data[&amp;#x27;1&amp;#x27;]} from scheduled list.&amp;quot;)
        await delete_messages([message, m])
        
@Client.on_message(filters.command([&amp;quot;cancelall&amp;quot;, f&amp;quot;cancelall@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; admin_filter &amp;amp; chat_filter)
async def delete_all_sch(bot, message):
    buttons = [
        [
            InlineKeyboardButton(&amp;#x27;Cancel All Schedules&amp;#x27;, callback_data=&amp;#x27;schcancel&amp;#x27;),
            InlineKeyboardButton(&amp;#x27;No&amp;#x27;, callback_data=&amp;#x27;schclose&amp;#x27;),
        ]
    ]
    reply_markup = InlineKeyboardMarkup(buttons)
    await message.reply(&amp;quot;Do you want to cancel all the scheduled streams?ㅤㅤㅤㅤ ㅤ&amp;quot;, reply_markup=reply_markup)
    await delete_messages([message])&lt;/pre&gt;
  &lt;p id=&quot;lJad&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;4wbp&quot;&gt;&lt;/h2&gt;
  &lt;h2 id=&quot;dRov&quot;&gt;✉подключаемые модули пользователя&lt;/h2&gt;
  &lt;h3 id=&quot;HnS9&quot;&gt;↪️group_call.py&lt;/h3&gt;
  &lt;pre id=&quot;96ce&quot;&gt;#!/usr/bin/env python3
# Copyright (C) @subinps
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.&lt;/pre&gt;
  &lt;pre id=&quot;nCHg&quot;&gt;# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.&lt;/pre&gt;
  &lt;pre id=&quot;2NLH&quot;&gt;# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see &amp;lt;https://www.gnu.org/licenses/&amp;gt;.
from utils import LOGGER
from pyrogram.errors import BotInlineDisabled
from pyrogram import Client, filters
from config import Config
from user import group_call
import time
from asyncio import sleep
from pyrogram.raw.base import Update
from pyrogram.raw.functions.channels import GetFullChannel
from pytgcalls import PyTgCalls
from pytgcalls.types import Update
from pyrogram.raw.types import (
    UpdateGroupCall, 
    GroupCallDiscarded, 
    UpdateGroupCallParticipants
)
from pytgcalls.types.groups import (
        JoinedVoiceChat, 
        LeftVoiceChat
    )
from pytgcalls.types.stream import (
    PausedStream, 
    ResumedStream, 
    MutedStream, 
    UnMutedStream, 
    StreamAudioEnded, 
    StreamVideoEnded
)
from utils import (
    start_record_stream,
    stop_recording, 
    edit_title, 
    stream_from_link, 
    leave_call, 
    start_stream, 
    skip, 
    sync_to_db,
    scheduler
)&lt;/pre&gt;
  &lt;pre id=&quot;224E&quot;&gt;async def is_reply(_, client, message):
    if Config.REPLY_PM:
        return True
    else:
        return False
reply_filter=filters.create(is_reply)&lt;/pre&gt;
  &lt;pre id=&quot;DGkr&quot;&gt;DUMBED=[]
async def dumb_it(_, client, message):
    if Config.RECORDING_DUMP and Config.LISTEN:
        return True
    else:
        return False
rec_filter=filters.create(dumb_it)&lt;/pre&gt;
  &lt;pre id=&quot;Ydv8&quot;&gt;@Client.on_message(reply_filter &amp;amp; filters.private &amp;amp; ~filters.bot &amp;amp; filters.incoming &amp;amp; ~filters.service &amp;amp; ~filters.me &amp;amp; ~filters.chat([777000, 454000]))
async def reply(client, message): 
    try:
        inline = await client.get_inline_bot_results(Config.BOT_USERNAME, &amp;quot;ETHO_ORUTHAN_PM_VANNU&amp;quot;)
        m=await client.send_inline_bot_result(
            message.chat.id,
            query_id=inline.query_id,
            result_id=inline.results[0].id,
            hide_via=True
            )
        old=Config.msg.get(message.chat.id)
        if old:
            await client.delete_messages(message.chat.id, [old[&amp;quot;msg&amp;quot;], old[&amp;quot;s&amp;quot;]])
        Config.msg[message.chat.id]={&amp;quot;msg&amp;quot;:m.updates[1].message.id, &amp;quot;s&amp;quot;:message.message_id}
    except BotInlineDisabled:
        LOGGER.error(f&amp;quot;Error: Inline Mode for @{Config.BOT_USERNAME} is not enabled. Enable from @Botfather to enable PM Permit.&amp;quot;)
        await message.reply(f&amp;quot;{Config.REPLY_MESSAGE}\n\n&amp;lt;b&amp;gt;You can&amp;#x27;t use this bot in your group, for that you have to make your own bot from the [SOURCE CODE](https://github.com/subinps/VCPlayerBot) below.&amp;lt;/b&amp;gt;&amp;quot;, disable_web_page_preview=True)
    except Exception as e:
        LOGGER.error(e, exc_info=True)
        pass&lt;/pre&gt;
  &lt;pre id=&quot;Uczv&quot;&gt;
@Client.on_message(filters.private &amp;amp; filters.media &amp;amp; filters.me &amp;amp; rec_filter)
async def dumb_to_log(client, message):
    if message.video and message.video.file_name == &amp;quot;record.mp4&amp;quot;:
        await message.copy(int(Config.RECORDING_DUMP))
        DUMBED.append(&amp;quot;video&amp;quot;)
    if message.audio and message.audio.file_name == &amp;quot;record.ogg&amp;quot;:
        await message.copy(int(Config.RECORDING_DUMP))
        DUMBED.append(&amp;quot;audio&amp;quot;)
    if Config.IS_VIDEO_RECORD:
        if len(DUMBED) == 2:
            DUMBED.clear()
            Config.LISTEN=False
    else:
        if len(DUMBED) == 1:
            DUMBED.clear()
            Config.LISTEN=False&lt;/pre&gt;
  &lt;pre id=&quot;Xx3s&quot;&gt;    
@Client.on_message(filters.service &amp;amp; filters.chat(Config.CHAT))
async def service_msg(client, message):
    if message.service == &amp;#x27;voice_chat_started&amp;#x27;:
        Config.IS_ACTIVE=True
        k=scheduler.get_job(str(Config.CHAT), jobstore=None) #scheduled records
        if k:
            await start_record_stream()
            LOGGER.info(&amp;quot;Resuming recording..&amp;quot;)
        elif Config.WAS_RECORDING:
            LOGGER.info(&amp;quot;Previous recording was ended unexpectedly, Now resuming recordings.&amp;quot;)
            await start_record_stream()#for unscheduled
        a = await client.send(
                GetFullChannel(
                    channel=(
                        await client.resolve_peer(
                            Config.CHAT
                            )
                        )
                    )
                )
        if a.full_chat.call is not None:
            Config.CURRENT_CALL=a.full_chat.call.id
        LOGGER.info(&amp;quot;Voice chat started.&amp;quot;)
        await sync_to_db()
    elif message.service == &amp;#x27;voice_chat_scheduled&amp;#x27;:
        LOGGER.info(&amp;quot;VoiceChat Scheduled&amp;quot;)
        Config.IS_ACTIVE=False
        Config.HAS_SCHEDULE=True
        await sync_to_db()
    elif message.service == &amp;#x27;voice_chat_ended&amp;#x27;:
        Config.IS_ACTIVE=False
        LOGGER.info(&amp;quot;Voicechat ended&amp;quot;)
        Config.CURRENT_CALL=None
        if Config.IS_RECORDING:
            Config.WAS_RECORDING=True
            await stop_recording()
        await sync_to_db()
    else:
        pass&lt;/pre&gt;
  &lt;pre id=&quot;G8fT&quot;&gt;@Client.on_raw_update()
async def handle_raw_updates(client: Client, update: Update, user: dict, chat: dict):
    if isinstance(update, UpdateGroupCallParticipants):
        if not Config.CURRENT_CALL:
            a = await client.send(
                GetFullChannel(
                    channel=(
                        await client.resolve_peer(
                            Config.CHAT
                            )
                        )
                    )
                )
            if a.full_chat.call is not None:
                Config.CURRENT_CALL=a.full_chat.call.id
        if Config.CURRENT_CALL and update.call.id == Config.CURRENT_CALL:
            all=update.participants
            old=list(filter(lambda k:k.peer.user_id if hasattr(k.peer,&amp;#x27;user_id&amp;#x27;) else k.peer.channel_id == Config.USER_ID, all))
            if old:
                for me in old:
                    if me.volume:
                        Config.VOLUME=round(int(me.volume)/100)&lt;/pre&gt;
  &lt;pre id=&quot;YaPt&quot;&gt;
    if isinstance(update, UpdateGroupCall) and (update.chat_id == int(-1000000000000-Config.CHAT)):
        if update.call is None:
            Config.IS_ACTIVE = False
            Config.CURRENT_CALL=None
            LOGGER.warning(&amp;quot;No Active Group Calls Found.&amp;quot;)
            if Config.IS_RECORDING:
                Config.WAS_RECORDING=True
                await stop_recording()
                LOGGER.warning(&amp;quot;Group call was ended and hence stoping recording.&amp;quot;)
            Config.HAS_SCHEDULE = False
            await sync_to_db()
            return&lt;/pre&gt;
  &lt;pre id=&quot;zaJf&quot;&gt;        else:
            call=update.call
            if isinstance(call, GroupCallDiscarded):
                Config.CURRENT_CALL=None
                Config.IS_ACTIVE=False
                if Config.IS_RECORDING:
                    Config.WAS_RECORDING=True
                    await stop_recording()
                LOGGER.warning(&amp;quot;Group Call Was ended&amp;quot;)
                Config.CALL_STATUS = False
                await sync_to_db()
                return
            Config.IS_ACTIVE=True
            Config.CURRENT_CALL=call.id
            if Config.IS_RECORDING and not call.record_video_active:
                Config.LISTEN=True
                await stop_recording()
                LOGGER.warning(&amp;quot;Recording was ended by user, hence stopping the schedules.&amp;quot;)
                return
            if call.schedule_date:
                Config.HAS_SCHEDULE=True
            else:
                Config.HAS_SCHEDULE=False
        await sync_to_db()
 
@group_call.on_raw_update()
async def handler(client: PyTgCalls, update: Update):
    if isinstance(update, JoinedVoiceChat):
        Config.CALL_STATUS = True
        if Config.EDIT_TITLE:
            await edit_title()
        who=await group_call.get_participants(Config.CHAT)
        you=list(filter(lambda k:k.user_id == Config.USER_ID, who))
        if you:
            for me in you:
                if me.volume:
                    Config.VOLUME=round(int(me.volume))
    elif isinstance(update, LeftVoiceChat):
        Config.CALL_STATUS = False
    elif isinstance(update, PausedStream):
        Config.DUR[&amp;#x27;PAUSE&amp;#x27;] = time.time()
        Config.PAUSE=True
    elif isinstance(update, ResumedStream):
        pause=Config.DUR.get(&amp;#x27;PAUSE&amp;#x27;)
        if pause:
            diff = time.time() - pause
            start=Config.DUR.get(&amp;#x27;TIME&amp;#x27;)
            if start:
                Config.DUR[&amp;#x27;TIME&amp;#x27;]=start+diff
        Config.PAUSE=False
    elif isinstance(update, MutedStream):
        Config.MUTED = True
    elif isinstance(update, UnMutedStream):
        Config.MUTED = False&lt;/pre&gt;
  &lt;pre id=&quot;qWsR&quot;&gt;&lt;/pre&gt;
  &lt;pre id=&quot;Li5H&quot;&gt;@group_call.on_stream_end()
async def handler(client: PyTgCalls, update: Update):
    if isinstance(update, StreamAudioEnded) or isinstance(update, StreamVideoEnded):
        if not Config.STREAM_END.get(&amp;quot;STATUS&amp;quot;):
            Config.STREAM_END[&amp;quot;STATUS&amp;quot;]=str(update)
            if Config.STREAM_LINK and len(Config.playlist) == 0:
                if Config.IS_LOOP:
                    await stream_from_link(Config.STREAM_LINK)
                else:
                    await leave_call()
            elif not Config.playlist:
                if Config.IS_LOOP:
                    await start_stream()
                else:
                    await leave_call()
            else:
                await skip()          
            await sleep(15) #wait for max 15 sec
            try:
                del Config.STREAM_END[&amp;quot;STATUS&amp;quot;]
            except:
                pass
        else:
            try:
                del Config.STREAM_END[&amp;quot;STATUS&amp;quot;]
            except:
                pass&lt;/pre&gt;
  &lt;p id=&quot;lheG&quot;&gt;       &lt;/p&gt;
  &lt;p id=&quot;GnfY&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;xa0y&quot;&gt;✉утилиты&lt;/h2&gt;
  &lt;h3 id=&quot;AWJj&quot;&gt;       ↪️__init__.py&lt;/h3&gt;
  &lt;pre id=&quot;1dpj&quot;&gt;from .logger import LOGGER
from .debug import debug
from .database import db
from .utils import *
from .pyro_dl import Downloader&lt;/pre&gt;
  &lt;p id=&quot;uUQu&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;x5Up&quot;&gt;↪️database.py&lt;/h3&gt;
  &lt;pre id=&quot;SxWs&quot;&gt;#!/usr/bin/env python3
# Copyright (C) @subinps
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.&lt;/pre&gt;
  &lt;pre id=&quot;Ymp7&quot;&gt;# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.&lt;/pre&gt;
  &lt;pre id=&quot;n7sh&quot;&gt;# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see &amp;lt;https://www.gnu.org/licenses/&amp;gt;.
from .logger import LOGGER
import motor.motor_asyncio
from config import Config&lt;/pre&gt;
  &lt;pre id=&quot;ZYkO&quot;&gt;class Database:    
    def __init__(self):
        self._client = motor.motor_asyncio.AsyncIOMotorClient(Config.DATABASE_URI)
        self.db = self._client[Config.DATABASE_NAME]
        self.col = self.db.config
        self.playlist = self.db.playlist
    def new_config(self, name, value, dvalue):
        return dict(
            name = name,
            dvalue = dvalue,
            value = value,
        )   
    def add_config(self, name, value, dvalue=None):
        config = self.new_config(name, value, dvalue)
        self.col.insert_one(config)
    
    def new_song(self, id_, song):
        return dict(
            id = id_,
            song = song,
        )&lt;/pre&gt;
  &lt;pre id=&quot;QuT6&quot;&gt;    def add_to_playlist(self, id_, song):
        song_ = self.new_song(id_, song)
        self.playlist.insert_one(song_)
    
    
    async def is_saved(self, name):
        config = await self.col.find_one({&amp;#x27;name&amp;#x27;:name})
        return True if config else False
     
    async def edit_config(self, name, value):
        await self.col.update_one({&amp;#x27;name&amp;#x27;: name}, {&amp;#x27;$set&amp;#x27;: {&amp;#x27;value&amp;#x27;: value}})
    
    async def edit_default(self, name, dvalue):
        await self.col.update_one({&amp;#x27;name&amp;#x27;: name}, {&amp;#x27;$set&amp;#x27;: {&amp;#x27;dvalue&amp;#x27;: dvalue}})
    
    async def get_default(self, name):
        config = await self.col.find_one({&amp;#x27;name&amp;#x27;:name})
        return config.get(&amp;#x27;dvalue&amp;#x27;)&lt;/pre&gt;
  &lt;pre id=&quot;w9K3&quot;&gt;    async def get_config(self, name):
        config = await self.col.find_one({&amp;#x27;name&amp;#x27;:name})
        return config.get(&amp;#x27;value&amp;#x27;)&lt;/pre&gt;
  &lt;pre id=&quot;Qv8b&quot;&gt;    async def del_config(self, name):
        await self.col.delete_one({&amp;#x27;name&amp;#x27;:name})
        return
    
    async def is_in_playlist(self, id_):
        song = await self.playlist.find_one({&amp;#x27;id&amp;#x27;:id_})
        return True if song else False
     &lt;/pre&gt;
  &lt;pre id=&quot;dP7S&quot;&gt;    async def get_song(self, id_):
        song_ = await self.playlist.find_one({&amp;#x27;id&amp;#x27;:id_})
        return song_.get(&amp;#x27;song&amp;#x27;)&lt;/pre&gt;
  &lt;pre id=&quot;GLHr&quot;&gt;    async def del_song(self, id_):
        await self.playlist.delete_one({&amp;#x27;id&amp;#x27;:id_})
        return&lt;/pre&gt;
  &lt;pre id=&quot;9ZKR&quot;&gt;    async def clear_playlist(self):
        await self.playlist.drop()
        return
    
    async def get_playlist(self):
        k = self.playlist.find({})
        l=[]
        async for song in k:
            song_ = {int(k):v for k,v in song[&amp;#x27;song&amp;#x27;].items()}
            l.append(song_)
        return l&lt;/pre&gt;
  &lt;pre id=&quot;zmaO&quot;&gt;db=Database()&lt;/pre&gt;
  &lt;p id=&quot;YECq&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;uHfe&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;ups1&quot;&gt;↪️debug.py&lt;/h3&gt;
  &lt;pre id=&quot;92wd&quot;&gt;#!/usr/bin/env python3
# Copyright (C) @subinps
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.&lt;/pre&gt;
  &lt;pre id=&quot;Bxe1&quot;&gt;# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.&lt;/pre&gt;
  &lt;pre id=&quot;zcYq&quot;&gt;# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see &amp;lt;https://www.gnu.org/licenses/&amp;gt;.&lt;/pre&gt;
  &lt;pre id=&quot;SWav&quot;&gt;from .logger import LOGGER
from config import Config
import os
import time
from threading import Thread
import sys
if Config.DATABASE_URI:
    from .database import db
from pyrogram import (
    Client, 
    filters
)
from pyrogram.errors import (
    MessageIdInvalid, 
    MessageNotModified
)
from pyrogram.types import (
    InlineKeyboardButton, 
    InlineKeyboardMarkup, 
    Message
)
from contextlib import suppress&lt;/pre&gt;
  &lt;pre id=&quot;KTwj&quot;&gt;debug = Client(
    &amp;quot;Debug&amp;quot;,
    Config.API_ID,
    Config.API_HASH,
    bot_token=Config.BOT_TOKEN,
)&lt;/pre&gt;
  &lt;pre id=&quot;Ywm7&quot;&gt;
@debug.on_message(filters.command([&amp;#x27;env&amp;#x27;, f&amp;quot;env@{Config.BOT_USERNAME}&amp;quot;, &amp;quot;config&amp;quot;, f&amp;quot;config@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; filters.private &amp;amp; filters.user(Config.ADMINS))
async def set_heroku_var(client, message):
    if message.from_user.id not in Config.SUDO:
        return await message.reply(f&amp;quot;/env command can only be used by creator of the bot, ({str(Config.SUDO)})&amp;quot;)
    with suppress(MessageIdInvalid, MessageNotModified):
        m = await message.reply(&amp;quot;Checking config vars..&amp;quot;)
        if &amp;quot; &amp;quot; in message.text:
            cmd, env = message.text.split(&amp;quot; &amp;quot;, 1)
            if  not &amp;quot;=&amp;quot; in env:
                await m.edit(&amp;quot;You should specify the value for env.\nExample: /env CHAT=-100213658211&amp;quot;)
                return
            var, value = env.split(&amp;quot;=&amp;quot;, 1)
        else:
            await m.edit(&amp;quot;You haven&amp;#x27;t provided any value for env, you should follow the correct format.\nExample: &amp;lt;code&amp;gt;/env CHAT=-1020202020202&amp;lt;/code&amp;gt; to change or set CHAT var.\n&amp;lt;code&amp;gt;/env REPLY_MESSAGE= &amp;lt;code&amp;gt;To delete REPLY_MESSAGE.&amp;quot;)
            return&lt;/pre&gt;
  &lt;pre id=&quot;rjdx&quot;&gt;        if Config.DATABASE_URI and var in [&amp;quot;STARTUP_STREAM&amp;quot;, &amp;quot;CHAT&amp;quot;, &amp;quot;LOG_GROUP&amp;quot;, &amp;quot;REPLY_MESSAGE&amp;quot;, &amp;quot;DELAY&amp;quot;, &amp;quot;RECORDING_DUMP&amp;quot;]:      
            await m.edit(&amp;quot;Mongo DB Found, Setting up config vars...&amp;quot;) 
            if not value:
                await m.edit(f&amp;quot;No value for env specified. Trying to delete env {var}.&amp;quot;)
                if var in [&amp;quot;STARTUP_STREAM&amp;quot;, &amp;quot;CHAT&amp;quot;, &amp;quot;DELAY&amp;quot;]:
                    await m.edit(&amp;quot;This is a mandatory var and cannot be deleted.&amp;quot;)
                    return
                await edit_config(var, False)
                await m.edit(f&amp;quot;Sucessfully deleted {var}&amp;quot;)
           
                return
            else:
                if var in [&amp;quot;CHAT&amp;quot;, &amp;quot;LOG_GROUP&amp;quot;, &amp;quot;RECORDING_DUMP&amp;quot;]:
                    try:
                        value=int(value)
                    except:
                        await m.edit(&amp;quot;You should give me a chat id . It should be an interger.&amp;quot;)
        
                        return
                    if var == &amp;quot;CHAT&amp;quot;:
                        Config.ADMIN_CACHE=False
                        Config.CHAT=int(value)
                    await edit_config(var, int(value))
                    await m.edit(f&amp;quot;Succesfully changed {var} to {value}&amp;quot;)
    
                    return
                else:
                    if var == &amp;quot;STARTUP_STREAM&amp;quot;:
                        Config.STREAM_SETUP=False
                    await edit_config(var, value)
                    await m.edit(f&amp;quot;Succesfully changed {var} to {value}&amp;quot;)
                    return
        else:
            if not Config.HEROKU_APP:
                buttons = [[InlineKeyboardButton(&amp;#x27;Heroku API_KEY&amp;#x27;, url=&amp;#x27;https://dashboard.heroku.com/account/applications/authorizations/new&amp;#x27;), InlineKeyboardButton(&amp;#x27;🗑 Close&amp;#x27;, callback_data=&amp;#x27;close&amp;#x27;),]]
                await m.edit(
                    text=&amp;quot;No heroku app found, this command needs the following heroku vars to be set.\n\n1. &amp;lt;code&amp;gt;HEROKU_API_KEY&amp;lt;/code&amp;gt;: Your heroku account api key.\n2. &amp;lt;code&amp;gt;HEROKU_APP_NAME&amp;lt;/code&amp;gt;: Your heroku app name.&amp;quot;, 
                    reply_markup=InlineKeyboardMarkup(buttons)) 
                return     
            config = Config.HEROKU_APP.config()
            if not value:
                await m.edit(f&amp;quot;No value for env specified. Trying to delete env {var}.&amp;quot;)
                if var in [&amp;quot;STARTUP_STREAM&amp;quot;, &amp;quot;CHAT&amp;quot;, &amp;quot;DELAY&amp;quot;, &amp;quot;API_ID&amp;quot;, &amp;quot;API_HASH&amp;quot;, &amp;quot;BOT_TOKEN&amp;quot;, &amp;quot;SESSION_STRING&amp;quot;, &amp;quot;ADMINS&amp;quot;]:
                    await m.edit(&amp;quot;These are mandatory vars and cannot be deleted.&amp;quot;)
    
                    return
                if var in config:
                    await m.edit(f&amp;quot;Sucessfully deleted {var}&amp;quot;)
                    await m.edit(&amp;quot;Now restarting the app to make changes.&amp;quot;)
                    if Config.DATABASE_URI:
                        msg = {&amp;quot;msg_id&amp;quot;:m.message_id, &amp;quot;chat_id&amp;quot;:m.chat.id}
                        if not await db.is_saved(&amp;quot;RESTART&amp;quot;):
                            db.add_config(&amp;quot;RESTART&amp;quot;, msg)
                        else:
                            await db.edit_config(&amp;quot;RESTART&amp;quot;, msg)
                    del config[var]                
                    config[var] = None               
                else:
                    k = await m.edit(f&amp;quot;No env named {var} found. Nothing was changed.&amp;quot;)
                return
            if var in config:
                await m.edit(f&amp;quot;Variable already found. Now edited to {value}&amp;quot;)
            else:
                await m.edit(f&amp;quot;Variable not found, Now setting as new var.&amp;quot;)
            await m.edit(f&amp;quot;Succesfully set {var} with value {value}, Now Restarting to take effect of changes...&amp;quot;)
            if Config.DATABASE_URI:
                msg = {&amp;quot;msg_id&amp;quot;:m.message_id, &amp;quot;chat_id&amp;quot;:m.chat.id}
                if not await db.is_saved(&amp;quot;RESTART&amp;quot;):
                    db.add_config(&amp;quot;RESTART&amp;quot;, msg)
                else:
                    await db.edit_config(&amp;quot;RESTART&amp;quot;, msg)
            config[var] = str(value)&lt;/pre&gt;
  &lt;pre id=&quot;I9xw&quot;&gt;@debug.on_message(filters.command([&amp;quot;restart&amp;quot;, f&amp;quot;restart@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; filters.private &amp;amp; filters.user(Config.ADMINS))
async def update(bot, message):
    m=await message.reply(&amp;quot;Restarting with new changes..&amp;quot;)
    if Config.DATABASE_URI:
        msg = {&amp;quot;msg_id&amp;quot;:m.message_id, &amp;quot;chat_id&amp;quot;:m.chat.id}
        if not await db.is_saved(&amp;quot;RESTART&amp;quot;):
            db.add_config(&amp;quot;RESTART&amp;quot;, msg)
        else:
            await db.edit_config(&amp;quot;RESTART&amp;quot;, msg)
    if Config.HEROKU_APP:
        Config.HEROKU_APP.restart()
    else:
        Thread(
            target=stop_and_restart()
            ).start()&lt;/pre&gt;
  &lt;pre id=&quot;OrtM&quot;&gt;@debug.on_message(filters.command([&amp;quot;clearplaylist&amp;quot;, f&amp;quot;clearplaylist@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; filters.private &amp;amp; filters.user(Config.ADMINS))
async def clear_play_list(client, m: Message):
    if not Config.playlist:
        k = await m.reply(&amp;quot;Playlist is empty.&amp;quot;)  
        return
    Config.playlist.clear()
    k=await m.reply_text(f&amp;quot;Playlist Cleared.&amp;quot;)
    await clear_db_playlist(all=True)&lt;/pre&gt;
  &lt;pre id=&quot;gaiK&quot;&gt;    
@debug.on_message(filters.command([&amp;quot;skip&amp;quot;, f&amp;quot;skip@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; filters.private &amp;amp; filters.user(Config.ADMINS))
async def skip_track(_, m: Message):
    msg=await m.reply(&amp;#x27;trying to skip from queue..&amp;#x27;)
    if not Config.playlist:
        await msg.edit(&amp;quot;Playlist is Empty.&amp;quot;)
        return
    if len(m.command) == 1:
        old_track = Config.playlist.pop(0)
        await clear_db_playlist(song=old_track)
    else:
        #https://github.com/callsmusic/tgvc-userbot/blob/dev/plugins/vc/player.py#L268-L288
        try:
            items = list(dict.fromkeys(m.command[1:]))
            items = [int(x) for x in items if x.isdigit()]
            items.sort(reverse=True)
            for i in items:
                if 2 &amp;lt;= i &amp;lt;= (len(Config.playlist) - 1):
                    await msg.edit(f&amp;quot;Succesfully Removed from Playlist- {i}. **{Config.playlist[i][1]}**&amp;quot;)
                    await clear_db_playlist(song=Config.playlist[i])
                    Config.playlist.pop(i)
                else:
                    await msg.edit(f&amp;quot;You cant skip first two songs- {i}&amp;quot;)
        except (ValueError, TypeError):
            await msg.edit(&amp;quot;Invalid input&amp;quot;)
    pl=await get_playlist_str()
    await msg.edit(pl, disable_web_page_preview=True)&lt;/pre&gt;
  &lt;pre id=&quot;9nkK&quot;&gt;
@debug.on_message(filters.command([&amp;#x27;logs&amp;#x27;, f&amp;quot;logs@{Config.BOT_USERNAME}&amp;quot;]) &amp;amp; filters.private &amp;amp; filters.user(Config.ADMINS))
async def get_logs(client, message):
    m=await message.reply(&amp;quot;Checking logs..&amp;quot;)
    if os.path.exists(&amp;quot;botlog.txt&amp;quot;):
        await message.reply_document(&amp;#x27;botlog.txt&amp;#x27;, caption=&amp;quot;Bot Logs&amp;quot;)
        await m.delete()
    else:
        k = await m.edit(&amp;quot;No log files found.&amp;quot;)&lt;/pre&gt;
  &lt;pre id=&quot;xMtM&quot;&gt;@debug.on_message(filters.text &amp;amp; filters.private)
async def reply_else(bot, message):
    await message.reply(f&amp;quot;Development mode is activated.\nThis occures when there are some errors in startup of the bot.\nOnly Configuration commands works in development mode.\nAvailabe commands are /env, /skip, /clearplaylist and /restart and /logs\n\n**The cause for activation of development mode was**\n\n&amp;#x60;{str(Config.STARTUP_ERROR)}&amp;#x60;&amp;quot;)&lt;/pre&gt;
  &lt;pre id=&quot;fKbT&quot;&gt;def stop_and_restart():
    os.system(&amp;quot;git pull&amp;quot;)
    time.sleep(10)
    os.execl(sys.executable, sys.executable, *sys.argv)&lt;/pre&gt;
  &lt;pre id=&quot;X1gv&quot;&gt;async def get_playlist_str():
    if not Config.playlist:
        pl = f&amp;quot;🔈 Playlist is empty.)ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ&amp;quot;
    else:
        if len(Config.playlist)&amp;gt;=25:
            tplaylist=Config.playlist[:25]
            pl=f&amp;quot;Listing first 25 songs of total {len(Config.playlist)} songs.\n&amp;quot;
            pl += f&amp;quot;▶️ **Playlist**: ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ\n&amp;quot; + &amp;quot;\n&amp;quot;.join([
                f&amp;quot;**{i}**. **🎸{x[1]}**\n   👤**Requested by:** {x[4]}&amp;quot;
                for i, x in enumerate(tplaylist)
                ])
            tplaylist.clear()
        else:
            pl = f&amp;quot;▶️ **Playlist**: ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ\n&amp;quot; + &amp;quot;\n&amp;quot;.join([
                f&amp;quot;**{i}**. **🎸{x[1]}**\n   👤**Requested by:** {x[4]}\n&amp;quot;
                for i, x in enumerate(Config.playlist)
            ])
    return pl&lt;/pre&gt;
  &lt;pre id=&quot;6uIw&quot;&gt;&lt;/pre&gt;
  &lt;pre id=&quot;LSmS&quot;&gt;async def sync_to_db():
    if Config.DATABASE_URI:
        await check_db() 
        await db.edit_config(&amp;quot;ADMINS&amp;quot;, Config.ADMINS)
        await db.edit_config(&amp;quot;IS_VIDEO&amp;quot;, Config.IS_VIDEO)
        await db.edit_config(&amp;quot;IS_LOOP&amp;quot;, Config.IS_LOOP)
        await db.edit_config(&amp;quot;REPLY_PM&amp;quot;, Config.REPLY_PM)
        await db.edit_config(&amp;quot;ADMIN_ONLY&amp;quot;, Config.ADMIN_ONLY)  
        await db.edit_config(&amp;quot;SHUFFLE&amp;quot;, Config.SHUFFLE)
        await db.edit_config(&amp;quot;EDIT_TITLE&amp;quot;, Config.EDIT_TITLE)
        await db.edit_config(&amp;quot;CHAT&amp;quot;, Config.CHAT)
        await db.edit_config(&amp;quot;SUDO&amp;quot;, Config.SUDO)
        await db.edit_config(&amp;quot;REPLY_MESSAGE&amp;quot;, Config.REPLY_MESSAGE)
        await db.edit_config(&amp;quot;LOG_GROUP&amp;quot;, Config.LOG_GROUP)
        await db.edit_config(&amp;quot;STREAM_URL&amp;quot;, Config.STREAM_URL)
        await db.edit_config(&amp;quot;DELAY&amp;quot;, Config.DELAY)
        await db.edit_config(&amp;quot;SCHEDULED_STREAM&amp;quot;, Config.SCHEDULED_STREAM)
        await db.edit_config(&amp;quot;SCHEDULE_LIST&amp;quot;, Config.SCHEDULE_LIST)
        await db.edit_config(&amp;quot;IS_VIDEO_RECORD&amp;quot;, Config.IS_VIDEO_RECORD)
        await db.edit_config(&amp;quot;IS_RECORDING&amp;quot;, Config.IS_RECORDING)
        await db.edit_config(&amp;quot;WAS_RECORDING&amp;quot;, Config.WAS_RECORDING)
        await db.edit_config(&amp;quot;PORTRAIT&amp;quot;, Config.PORTRAIT)
        await db.edit_config(&amp;quot;RECORDING_DUMP&amp;quot;, Config.RECORDING_DUMP)
        await db.edit_config(&amp;quot;RECORDING_TITLE&amp;quot;, Config.RECORDING_TITLE)
        await db.edit_config(&amp;quot;HAS_SCHEDULE&amp;quot;, Config.HAS_SCHEDULE)&lt;/pre&gt;
  &lt;pre id=&quot;qZEi&quot;&gt;async def sync_from_db():
    if Config.DATABASE_URI:  
        await check_db()     
        Config.ADMINS = await db.get_config(&amp;quot;ADMINS&amp;quot;) 
        Config.IS_VIDEO = await db.get_config(&amp;quot;IS_VIDEO&amp;quot;)
        Config.IS_LOOP = await db.get_config(&amp;quot;IS_LOOP&amp;quot;)
        Config.REPLY_PM = await db.get_config(&amp;quot;REPLY_PM&amp;quot;)
        Config.ADMIN_ONLY = await db.get_config(&amp;quot;ADMIN_ONLY&amp;quot;)
        Config.SHUFFLE = await db.get_config(&amp;quot;SHUFFLE&amp;quot;)
        Config.EDIT_TITLE = await db.get_config(&amp;quot;EDIT_TITLE&amp;quot;)
        Config.CHAT = int(await db.get_config(&amp;quot;CHAT&amp;quot;))
        Config.playlist = await db.get_playlist()
        Config.LOG_GROUP = await db.get_config(&amp;quot;LOG_GROUP&amp;quot;)
        Config.SUDO = await db.get_config(&amp;quot;SUDO&amp;quot;) 
        Config.REPLY_MESSAGE = await db.get_config(&amp;quot;REPLY_MESSAGE&amp;quot;) 
        Config.DELAY = await db.get_config(&amp;quot;DELAY&amp;quot;) 
        Config.STREAM_URL = await db.get_config(&amp;quot;STREAM_URL&amp;quot;) 
        Config.SCHEDULED_STREAM = await db.get_config(&amp;quot;SCHEDULED_STREAM&amp;quot;) 
        Config.SCHEDULE_LIST = await db.get_config(&amp;quot;SCHEDULE_LIST&amp;quot;)
        Config.IS_VIDEO_RECORD = await db.get_config(&amp;#x27;IS_VIDEO_RECORD&amp;#x27;)
        Config.IS_RECORDING = await db.get_config(&amp;quot;IS_RECORDING&amp;quot;)
        Config.WAS_RECORDING = await db.get_config(&amp;#x27;WAS_RECORDING&amp;#x27;)
        Config.PORTRAIT = await db.get_config(&amp;quot;PORTRAIT&amp;quot;)
        Config.RECORDING_DUMP = await db.get_config(&amp;quot;RECORDING_DUMP&amp;quot;)
        Config.RECORDING_TITLE = await db.get_config(&amp;quot;RECORDING_TITLE&amp;quot;)
        Config.HAS_SCHEDULE = await db.get_config(&amp;quot;HAS_SCHEDULE&amp;quot;)&lt;/pre&gt;
  &lt;pre id=&quot;GuRX&quot;&gt;async def add_to_db_playlist(song):
    if Config.DATABASE_URI:
        song_={str(k):v for k,v in song.items()}
        db.add_to_playlist(song[5], song_)&lt;/pre&gt;
  &lt;pre id=&quot;OGIf&quot;&gt;async def clear_db_playlist(song=None, all=False):
    if Config.DATABASE_URI:
        if all:
            await db.clear_playlist()
        else:
            await db.del_song(song[5])&lt;/pre&gt;
  &lt;pre id=&quot;lYsl&quot;&gt;async def check_db():
    if not await db.is_saved(&amp;quot;ADMINS&amp;quot;):
        db.add_config(&amp;quot;ADMINS&amp;quot;, Config.ADMINS)
    if not await db.is_saved(&amp;quot;IS_VIDEO&amp;quot;):
        db.add_config(&amp;quot;IS_VIDEO&amp;quot;, Config.IS_VIDEO)
    if not await db.is_saved(&amp;quot;IS_LOOP&amp;quot;):
        db.add_config(&amp;quot;IS_LOOP&amp;quot;, Config.IS_LOOP)
    if not await db.is_saved(&amp;quot;REPLY_PM&amp;quot;):
        db.add_config(&amp;quot;REPLY_PM&amp;quot;, Config.REPLY_PM)
    if not await db.is_saved(&amp;quot;ADMIN_ONLY&amp;quot;):
        db.add_config(&amp;quot;ADMIN_ONLY&amp;quot;, Config.ADMIN_ONLY)
    if not await db.is_saved(&amp;quot;SHUFFLE&amp;quot;):
        db.add_config(&amp;quot;SHUFFLE&amp;quot;, Config.SHUFFLE)
    if not await db.is_saved(&amp;quot;EDIT_TITLE&amp;quot;):
        db.add_config(&amp;quot;EDIT_TITLE&amp;quot;, Config.EDIT_TITLE)
    if not await db.is_saved(&amp;quot;CHAT&amp;quot;):
        db.add_config(&amp;quot;CHAT&amp;quot;, Config.CHAT)
    if not await db.is_saved(&amp;quot;SUDO&amp;quot;):
        db.add_config(&amp;quot;SUDO&amp;quot;, Config.SUDO)
    if not await db.is_saved(&amp;quot;REPLY_MESSAGE&amp;quot;):
        db.add_config(&amp;quot;REPLY_MESSAGE&amp;quot;, Config.REPLY_MESSAGE)
    if not await db.is_saved(&amp;quot;STREAM_URL&amp;quot;):
        db.add_config(&amp;quot;STREAM_URL&amp;quot;, Config.STREAM_URL)
    if not await db.is_saved(&amp;quot;DELAY&amp;quot;):
        db.add_config(&amp;quot;DELAY&amp;quot;, Config.DELAY)
    if not await db.is_saved(&amp;quot;LOG_GROUP&amp;quot;):
        db.add_config(&amp;quot;LOG_GROUP&amp;quot;, Config.LOG_GROUP)
    if not await db.is_saved(&amp;quot;SCHEDULED_STREAM&amp;quot;):
        db.add_config(&amp;quot;SCHEDULED_STREAM&amp;quot;, Config.SCHEDULED_STREAM)
    if not await db.is_saved(&amp;quot;SCHEDULE_LIST&amp;quot;):
        db.add_config(&amp;quot;SCHEDULE_LIST&amp;quot;, Config.SCHEDULE_LIST)
    if not await db.is_saved(&amp;quot;IS_VIDEO_RECORD&amp;quot;):
        db.add_config(&amp;quot;IS_VIDEO_RECORD&amp;quot;, Config.IS_VIDEO_RECORD)
    if not await db.is_saved(&amp;quot;PORTRAIT&amp;quot;):
        db.add_config(&amp;quot;PORTRAIT&amp;quot;, Config.PORTRAIT)  
    if not await db.is_saved(&amp;quot;IS_RECORDING&amp;quot;):
        db.add_config(&amp;quot;IS_RECORDING&amp;quot;, Config.IS_RECORDING)
    if not await db.is_saved(&amp;#x27;WAS_RECORDING&amp;#x27;):
        db.add_config(&amp;#x27;WAS_RECORDING&amp;#x27;, Config.WAS_RECORDING)
    if not await db.is_saved(&amp;quot;RECORDING_DUMP&amp;quot;):
        db.add_config(&amp;quot;RECORDING_DUMP&amp;quot;, Config.RECORDING_DUMP)
    if not await db.is_saved(&amp;quot;RECORDING_TITLE&amp;quot;):
        db.add_config(&amp;quot;RECORDING_TITLE&amp;quot;, Config.RECORDING_TITLE)
    if not await db.is_saved(&amp;#x27;HAS_SCHEDULE&amp;#x27;):
        db.add_config(&amp;quot;HAS_SCHEDULE&amp;quot;, Config.HAS_SCHEDULE)&lt;/pre&gt;
  &lt;pre id=&quot;TzVu&quot;&gt;async def edit_config(var, value):
    if var == &amp;quot;STARTUP_STREAM&amp;quot;:
        Config.STREAM_URL = value
    elif var == &amp;quot;CHAT&amp;quot;:
        Config.CHAT = int(value)
    elif var == &amp;quot;LOG_GROUP&amp;quot;:
        Config.LOG_GROUP = int(value)
    elif var == &amp;quot;DELAY&amp;quot;:
        Config.DELAY = int(value)
    elif var == &amp;quot;REPLY_MESSAGE&amp;quot;:
        Config.REPLY_MESSAGE = value
    elif var == &amp;quot;RECORDING_DUMP&amp;quot;:
        Config.RECORDING_DUMP = value
    await sync_to_db()&lt;/pre&gt;
  &lt;p id=&quot;Vt20&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;jZd6&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;RRuK&quot;&gt;↪️logger.py&lt;/h3&gt;
  &lt;pre id=&quot;6QgI&quot;&gt;#!/usr/bin/env python3
# Copyright (C) @subinps
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.&lt;/pre&gt;
  &lt;pre id=&quot;B3pt&quot;&gt;# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.&lt;/pre&gt;
  &lt;pre id=&quot;tnwI&quot;&gt;# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see &amp;lt;https://www.gnu.org/licenses/&amp;gt;.&lt;/pre&gt;
  &lt;pre id=&quot;b03H&quot;&gt;from logging.handlers import RotatingFileHandler
import logging&lt;/pre&gt;
  &lt;pre id=&quot;0egy&quot;&gt;logging.basicConfig(
    level=logging.INFO,
    format=&amp;quot;[%(asctime)s - %(levelname)s] - %(name)s - %(message)s&amp;quot;,
    datefmt=&amp;#x27;%d-%b-%y %H:%M:%S&amp;#x27;,
    handlers=[
        RotatingFileHandler(
            &amp;quot;botlog.txt&amp;quot;,
            maxBytes=50000000,
            backupCount=10
        ),
        logging.StreamHandler()
    ]
)&lt;/pre&gt;
  &lt;pre id=&quot;MKzo&quot;&gt;logging.getLogger(&amp;quot;pyrogram&amp;quot;).setLevel(logging.ERROR)
logging.getLogger(&amp;quot;pytgcalls&amp;quot;).setLevel(logging.ERROR)
logging.getLogger(&amp;quot;apscheduler&amp;quot;).setLevel(logging.ERROR)&lt;/pre&gt;
  &lt;pre id=&quot;iOeU&quot;&gt;LOGGER=logging.getLogger(__name__)&lt;/pre&gt;
  &lt;p id=&quot;5Eu6&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;ajEn&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;OKTq&quot;&gt;↪️pyro_dl.py&lt;/h3&gt;
  &lt;pre id=&quot;EB0o&quot;&gt;#  Pyrogram - Telegram MTProto API Client Library for Python
#  Copyright (C) 2017-2021 Dan &amp;lt;https://github.com/delivrance&amp;gt;
#
#  This file is part of Pyrogram.
#
#  Pyrogram is free software: you can redistribute it and/or modify
#  it under the terms of the GNU Lesser General Public License as published
#  by the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  Pyrogram is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU Lesser General Public License for more details.
#
#  You should have received a copy of the GNU Lesser General Public License
#  along with Pyrogram.  If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;.&lt;/pre&gt;
  &lt;pre id=&quot;X4RP&quot;&gt;
#https://github.com/pyrogram/pyrogram/blob/4f585c156c1a2c6707793a8ad7f2f111515ca23b/pyrogram/client.py#L492-L518
#https://github.com/pyrogram/pyrogram/blob/4f585c156c1a2c6707793a8ad7f2f111515ca23b/pyrogram/client.py#L806-1044&lt;/pre&gt;
  &lt;pre id=&quot;Rpel&quot;&gt;#Pyrogram downloader modified to suit my needs. 
#Downloads the file from telegram servers and retures the path of the file without waiting for the whole download to finish.
#Copyright (C) @subinps&lt;/pre&gt;
  &lt;pre id=&quot;eEkx&quot;&gt;
from .logger import LOGGER
import asyncio
import os
import re
import asyncio
import os
import time
from datetime import datetime
from hashlib import sha256
from bot import bot
import pyrogram
from pyrogram import raw
from pyrogram import utils
from pyrogram.crypto import aes
from pyrogram.errors import (
    VolumeLocNotFound,
    AuthBytesInvalid
)
from pyrogram.session import(
    Auth, 
    Session
)
from pyrogram.file_id import(
    FileId, 
    FileType, 
    ThumbnailSource
)
from pyrogram.file_id import (
    FileId, 
    FileType, 
    PHOTO_TYPES
)&lt;/pre&gt;
  &lt;pre id=&quot;HwDt&quot;&gt;
DEFAULT_DOWNLOAD_DIR = &amp;quot;downloads/&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;RRT6&quot;&gt;class Downloader():
    def __init__(
        self,
        ):
        super().__init__()
        self.client = bot&lt;/pre&gt;
  &lt;pre id=&quot;gpqM&quot;&gt;    async def pyro_dl(self, file_id):
        file_id_obj = FileId.decode(file_id)
        file_type = file_id_obj.file_type
        mime_type = &amp;quot;&amp;quot;
        date = 0
        file_name = &amp;quot;&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;Wm8R&quot;&gt;        directory, file_name = os.path.split(file_name)
        if not os.path.isabs(file_name):
            directory = self.client.PARENT_DIR / (directory or DEFAULT_DOWNLOAD_DIR)
        if not file_name:
            guessed_extension = self.client.guess_extension(mime_type)&lt;/pre&gt;
  &lt;pre id=&quot;pcKn&quot;&gt;            if file_type in PHOTO_TYPES:
                extension = &amp;quot;.jpg&amp;quot;
            elif file_type == FileType.VOICE:
                extension = guessed_extension or &amp;quot;.ogg&amp;quot;
            elif file_type in (FileType.VIDEO, FileType.ANIMATION, FileType.VIDEO_NOTE):
                extension = guessed_extension or &amp;quot;.mp4&amp;quot;
            elif file_type == FileType.DOCUMENT:
                extension = guessed_extension or &amp;quot;.zip&amp;quot;
            elif file_type == FileType.STICKER:
                extension = guessed_extension or &amp;quot;.webp&amp;quot;
            elif file_type == FileType.AUDIO:
                extension = guessed_extension or &amp;quot;.mp3&amp;quot;
            else:
                extension = &amp;quot;.unknown&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;TnaP&quot;&gt;            file_name = &amp;quot;{}_{}_{}{}&amp;quot;.format(
                FileType(file_id_obj.file_type).name.lower(),
                datetime.fromtimestamp(date or time.time()).strftime(&amp;quot;%Y-%m-%d_%H-%M-%S&amp;quot;),
                self.client.rnd_id(),
                extension
            )
        final_file_path = os.path.abspath(re.sub(&amp;quot;\\\\&amp;quot;, &amp;quot;/&amp;quot;, os.path.join(directory, file_name)))
        os.makedirs(directory, exist_ok=True)
        downloaderr = self.handle_download(file_id_obj, final_file_path)
        asyncio.get_event_loop().create_task(downloaderr)
        return final_file_path
    
    async def handle_download(self, file_id_obj, final_file_path):  
        try:
            await self.get_file(
                file_id=file_id_obj,
                filename=final_file_path
            )
        except Exception as e:
            LOGGER.error(str(e), exc_info=True)&lt;/pre&gt;
  &lt;pre id=&quot;CKrV&quot;&gt;            try:
                os.remove(final_file_path)
            except OSError:
                pass
        else:
            return final_file_path or None&lt;/pre&gt;
  &lt;pre id=&quot;JmcK&quot;&gt;    async def get_file(
        self,
        file_id: FileId,
        filename: str,
    ) -&amp;gt; str:
        dc_id = file_id.dc_id&lt;/pre&gt;
  &lt;pre id=&quot;lFSa&quot;&gt;        async with self.client.media_sessions_lock:
            session = self.client.media_sessions.get(dc_id, None)&lt;/pre&gt;
  &lt;pre id=&quot;vdfS&quot;&gt;            if session is None:
                if dc_id != await self.client.storage.dc_id():
                    session = Session(
                        self.client, dc_id, await Auth(self.client, dc_id, await self.client.storage.test_mode()).create(),
                        await self.client.storage.test_mode(), is_media=True
                    )
                    await session.start()&lt;/pre&gt;
  &lt;pre id=&quot;hqUJ&quot;&gt;                    for _ in range(3):
                        exported_auth = await self.client.send(
                            raw.functions.auth.ExportAuthorization(
                                dc_id=dc_id
                            )
                        )&lt;/pre&gt;
  &lt;pre id=&quot;wO2f&quot;&gt;                        try:
                            await session.send(
                                raw.functions.auth.ImportAuthorization(
                                    id=exported_auth.id,
                                    bytes=exported_auth.bytes
                                )
                            )
                        except AuthBytesInvalid:
                            continue
                        else:
                            break
                    else:
                        await session.stop()
                        raise AuthBytesInvalid
                else:
                    session = Session(
                        self.client, dc_id, await self.client.storage.auth_key(),
                        await self.client.storage.test_mode(), is_media=True
                    )
                    await session.start()&lt;/pre&gt;
  &lt;pre id=&quot;pc0P&quot;&gt;                self.client.media_sessions[dc_id] = session&lt;/pre&gt;
  &lt;pre id=&quot;Aj3d&quot;&gt;        file_type = file_id.file_type&lt;/pre&gt;
  &lt;pre id=&quot;8Lob&quot;&gt;        if file_type == FileType.CHAT_PHOTO:
            if file_id.chat_id &amp;gt; 0:
                peer = raw.types.InputPeerUser(
                    user_id=file_id.chat_id,
                    access_hash=file_id.chat_access_hash
                )
            else:
                if file_id.chat_access_hash == 0:
                    peer = raw.types.InputPeerChat(
                        chat_id=-file_id.chat_id
                    )
                else:
                    peer = raw.types.InputPeerChannel(
                        channel_id=utils.get_channel_id(file_id.chat_id),
                        access_hash=file_id.chat_access_hash
                    )&lt;/pre&gt;
  &lt;pre id=&quot;89Yr&quot;&gt;            location = raw.types.InputPeerPhotoFileLocation(
                peer=peer,
                volume_id=file_id.volume_id,
                local_id=file_id.local_id,
                big=file_id.thumbnail_source == ThumbnailSource.CHAT_PHOTO_BIG
            )
        elif file_type == FileType.PHOTO:
            location = raw.types.InputPhotoFileLocation(
                id=file_id.media_id,
                access_hash=file_id.access_hash,
                file_reference=file_id.file_reference,
                thumb_size=file_id.thumbnail_size
            )
        else:
            location = raw.types.InputDocumentFileLocation(
                id=file_id.media_id,
                access_hash=file_id.access_hash,
                file_reference=file_id.file_reference,
                thumb_size=file_id.thumbnail_size
            )&lt;/pre&gt;
  &lt;pre id=&quot;NS9Z&quot;&gt;        limit = 1024 * 1024
        offset = 0
        file_name = &amp;quot;&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;8rEb&quot;&gt;        try:
            r = await session.send(
                raw.functions.upload.GetFile(
                    location=location,
                    offset=offset,
                    limit=limit
                ),
                sleep_threshold=30
            )&lt;/pre&gt;
  &lt;pre id=&quot;mYSz&quot;&gt;            if isinstance(r, raw.types.upload.File):
                #with tempfile.NamedTemporaryFile(&amp;quot;wb&amp;quot;, delete=False) as f:
                with open(filename, &amp;#x27;wb&amp;#x27;) as f:
                    file_name = filename
                    while True:
                        chunk = r.bytes&lt;/pre&gt;
  &lt;pre id=&quot;tIru&quot;&gt;                        if not chunk:
                            break&lt;/pre&gt;
  &lt;pre id=&quot;jhnw&quot;&gt;                        f.write(chunk)&lt;/pre&gt;
  &lt;pre id=&quot;xU95&quot;&gt;                        offset += limit
                        r = await session.send(
                            raw.functions.upload.GetFile(
                                location=location,
                                offset=offset,
                                limit=limit
                            ),
                            sleep_threshold=30
                        )&lt;/pre&gt;
  &lt;pre id=&quot;YTry&quot;&gt;            elif isinstance(r, raw.types.upload.FileCdnRedirect):
                async with self.client.media_sessions_lock:
                    cdn_session = self.client.media_sessions.get(r.dc_id, None)&lt;/pre&gt;
  &lt;pre id=&quot;AySK&quot;&gt;                    if cdn_session is None:
                        cdn_session = Session(
                            self.client, r.dc_id, await Auth(self.client, r.dc_id, await self.client.storage.test_mode()).create(),
                            await self.client.storage.test_mode(), is_media=True, is_cdn=True
                        )&lt;/pre&gt;
  &lt;pre id=&quot;Gfcy&quot;&gt;                        await cdn_session.start()&lt;/pre&gt;
  &lt;pre id=&quot;V2Vk&quot;&gt;                        self.client.media_sessions[r.dc_id] = cdn_session&lt;/pre&gt;
  &lt;pre id=&quot;tKU2&quot;&gt;                try:
                    with open(filename, &amp;#x27;wb&amp;#x27;) as f:
                        file_name = f
                        while True:
                            r2 = await cdn_session.send(
                                raw.functions.upload.GetCdnFile(
                                    file_token=r.file_token,
                                    offset=offset,
                                    limit=limit
                                )
                            )&lt;/pre&gt;
  &lt;pre id=&quot;yPGn&quot;&gt;                            if isinstance(r2, raw.types.upload.CdnFileReuploadNeeded):
                                try:
                                    await session.send(
                                        raw.functions.upload.ReuploadCdnFile(
                                            file_token=r.file_token,
                                            request_token=r2.request_token
                                        )
                                    )
                                except VolumeLocNotFound:
                                    break
                                else:
                                    continue&lt;/pre&gt;
  &lt;pre id=&quot;v72f&quot;&gt;                            chunk = r2.bytes&lt;/pre&gt;
  &lt;pre id=&quot;5wgi&quot;&gt;                            # https://core.telegram.org/cdn#decrypting-files
                            decrypted_chunk = aes.ctr256_decrypt(
                                chunk,
                                r.encryption_key,
                                bytearray(
                                    r.encryption_iv[:-4]
                                    + (offset // 16).to_bytes(4, &amp;quot;big&amp;quot;)
                                )
                            )&lt;/pre&gt;
  &lt;pre id=&quot;sur0&quot;&gt;                            hashes = await session.send(
                                raw.functions.upload.GetCdnFileHashes(
                                    file_token=r.file_token,
                                    offset=offset
                                )
                            )&lt;/pre&gt;
  &lt;pre id=&quot;V8ty&quot;&gt;                            # https://core.telegram.org/cdn#verifying-files
                            for i, h in enumerate(hashes):
                                cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)]
                                assert h.hash == sha256(cdn_chunk).digest(), f&amp;quot;Invalid CDN hash part {i}&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;Yjv8&quot;&gt;                            f.write(decrypted_chunk)&lt;/pre&gt;
  &lt;pre id=&quot;9t6E&quot;&gt;                            offset += limit&lt;/pre&gt;
  &lt;pre id=&quot;ZNuY&quot;&gt;                            if len(chunk) &amp;lt; limit:
                                break
                except Exception as e:
                    LOGGER.error(e, exc_info=True)
                    raise e
        except Exception as e:
            if not isinstance(e, pyrogram.StopTransmission):
                LOGGER.error(str(e), exc_info=True)
            try:
                os.remove(file_name)
            except OSError:
                pass&lt;/pre&gt;
  &lt;pre id=&quot;iEx2&quot;&gt;            return &amp;quot;&amp;quot;
        else:
            return file_name&lt;/pre&gt;
  &lt;p id=&quot;u4ag&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;xdNy&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;KvpT&quot;&gt;↪️utils.py&lt;/h3&gt;
  &lt;pre id=&quot;m2wz&quot;&gt;#!/usr/bin/env python3
# Copyright (C) @subinps
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.&lt;/pre&gt;
  &lt;pre id=&quot;poYJ&quot;&gt;# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.&lt;/pre&gt;
  &lt;pre id=&quot;myNz&quot;&gt;# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see &amp;lt;https://www.gnu.org/licenses/&amp;gt;.&lt;/pre&gt;
  &lt;pre id=&quot;LZwu&quot;&gt;from .logger import LOGGER
try:
    from pyrogram.raw.types import InputChannel
    from apscheduler.schedulers.asyncio import AsyncIOScheduler   
    from apscheduler.jobstores.mongodb import MongoDBJobStore
    from apscheduler.jobstores.base import ConflictingIdError
    from pyrogram.raw.functions.channels import GetFullChannel
    from pytgcalls import StreamType
    import yt_dlp
    from pyrogram import filters
    from pymongo import MongoClient
    from datetime import datetime
    from threading import Thread
    from math import gcd
    from .pyro_dl import Downloader
    from config import Config
    from asyncio import sleep  
    from bot import bot
    from PTN import parse
    import subprocess
    import asyncio
    import json
    import random
    import time
    import sys
    import os
    import math
    from pyrogram.errors.exceptions.bad_request_400 import (
        BadRequest, 
        ScheduleDateInvalid,
        PeerIdInvalid,
        ChannelInvalid
    )
    from pytgcalls.types.input_stream import (
        AudioVideoPiped, 
        AudioPiped,
        AudioImagePiped
    )
    from pytgcalls.types.input_stream import (
        AudioParameters,
        VideoParameters
    )
    from pyrogram.types import (
        InlineKeyboardButton, 
        InlineKeyboardMarkup, 
        Message
    )
    from pyrogram.raw.functions.phone import (
        EditGroupCallTitle, 
        CreateGroupCall,
        ToggleGroupCallRecord,
        StartScheduledGroupCall 
    )
    from pytgcalls.exceptions import (
        GroupCallNotFound, 
        NoActiveGroupCall,
        InvalidVideoProportion
    )
    from PIL import (
        Image, 
        ImageFont, 
        ImageDraw 
    )&lt;/pre&gt;
  &lt;pre id=&quot;uAD1&quot;&gt;    from user import (
        group_call, 
        USER
    )
except ModuleNotFoundError:
    import os
    import sys
    import subprocess
    file=os.path.abspath(&amp;quot;requirements.txt&amp;quot;)
    subprocess.check_call([sys.executable, &amp;#x27;-m&amp;#x27;, &amp;#x27;pip&amp;#x27;, &amp;#x27;install&amp;#x27;, &amp;#x27;-r&amp;#x27;, file, &amp;#x27;--upgrade&amp;#x27;])
    os.execl(sys.executable, sys.executable, *sys.argv)&lt;/pre&gt;
  &lt;pre id=&quot;peqk&quot;&gt;if Config.DATABASE_URI:
    from .database import db
    monclient = MongoClient(Config.DATABASE_URI)
    jobstores = {
        &amp;#x27;default&amp;#x27;: MongoDBJobStore(client=monclient, database=Config.DATABASE_NAME, collection=&amp;#x27;scheduler&amp;#x27;)
        }
    scheduler = AsyncIOScheduler(jobstores=jobstores)
else:
    scheduler = AsyncIOScheduler()
scheduler.start()
dl=Downloader()&lt;/pre&gt;
  &lt;pre id=&quot;qeuI&quot;&gt;async def play():
    song=Config.playlist[0]    
    if song[3] == &amp;quot;telegram&amp;quot;:
        file=Config.GET_FILE.get(song[5])
        if not file:
            file = await dl.pyro_dl(song[2])
            if not file:
                LOGGER.info(&amp;quot;Downloading file from telegram&amp;quot;)
                file = await bot.download_media(song[2])
            Config.GET_FILE[song[5]] = file
            await sleep(3)
        while not os.path.exists(file):
            file=Config.GET_FILE.get(song[5])
            await sleep(1)
        total=int(((song[5].split(&amp;quot;_&amp;quot;))[1])) * 0.005
        while not (os.stat(file).st_size) &amp;gt;= total:
            LOGGER.info(&amp;quot;Waiting for download&amp;quot;)
            LOGGER.info(str((os.stat(file).st_size)))
            await sleep(1)
    elif song[3] == &amp;quot;url&amp;quot;:
        file=song[2]
    else:
        file=await get_link(song[2])
    if not file:
        if Config.playlist or Config.STREAM_LINK:
            return await skip()     
        else:
            LOGGER.error(&amp;quot;This stream is not supported , leaving VC.&amp;quot;)
            await leave_call()
            return False 
    link, seek, pic, width, height = await chek_the_media(file, title=f&amp;quot;{song[1]}&amp;quot;)
    if not link:
        LOGGER.warning(&amp;quot;Unsupported link, Skiping from queue.&amp;quot;)
        return
    await sleep(1)
    if Config.STREAM_LINK:
        Config.STREAM_LINK=False
    LOGGER.info(f&amp;quot;STARTING PLAYING: {song[1]}&amp;quot;)
    await join_call(link, seek, pic, width, height)&lt;/pre&gt;
  &lt;pre id=&quot;61aP&quot;&gt;async def schedule_a_play(job_id, date):
    try:
        scheduler.add_job(run_schedule, &amp;quot;date&amp;quot;, [job_id], id=job_id, run_date=date, max_instances=50, misfire_grace_time=None)
    except ConflictingIdError:
        LOGGER.warning(&amp;quot;This already scheduled&amp;quot;)
        return
    if not Config.CALL_STATUS or not Config.IS_ACTIVE:
        if Config.SCHEDULE_LIST[0][&amp;#x27;job_id&amp;#x27;] == job_id \
            and (date - datetime.now()).total_seconds() &amp;lt; 86400:
            song=Config.SCHEDULED_STREAM.get(job_id)
            if Config.IS_RECORDING:
                scheduler.add_job(start_record_stream, &amp;quot;date&amp;quot;, id=str(Config.CHAT), run_date=date, max_instances=50, misfire_grace_time=None)
            try:
                await USER.send(CreateGroupCall(
                    peer=(await USER.resolve_peer(Config.CHAT)),
                    random_id=random.randint(10000, 999999999),
                    schedule_date=int(date.timestamp()),
                    title=song[&amp;#x27;1&amp;#x27;]
                    )
                )
                Config.HAS_SCHEDULE=True
            except ScheduleDateInvalid:
                LOGGER.error(&amp;quot;Unable to schedule VideoChat, since date is invalid&amp;quot;)
            except Exception as e:
                LOGGER.error(f&amp;quot;Error in scheduling voicechat- {e}&amp;quot;, exc_info=True)
    await sync_to_db()&lt;/pre&gt;
  &lt;pre id=&quot;5Sjs&quot;&gt;async def run_schedule(job_id):
    data=Config.SCHEDULED_STREAM.get(job_id)
    if not data:
        LOGGER.error(&amp;quot;The Scheduled stream was not played, since data is missing&amp;quot;)
        old=filter(lambda k: k[&amp;#x27;job_id&amp;#x27;] == job_id, Config.SCHEDULE_LIST)
        if old:
            Config.SCHEDULE_LIST.remove(old)
        await sync_to_db()
        pass
    else:
        if Config.HAS_SCHEDULE:
            if not await start_scheduled():
                LOGGER.error(&amp;quot;Scheduled stream skipped, Reason - Unable to start a voice chat.&amp;quot;)
                return
        data_ = [{1:data[&amp;#x27;1&amp;#x27;], 2:data[&amp;#x27;2&amp;#x27;], 3:data[&amp;#x27;3&amp;#x27;], 4:data[&amp;#x27;4&amp;#x27;], 5:data[&amp;#x27;5&amp;#x27;]}]
        Config.playlist = data_ + Config.playlist
        await play()
        LOGGER.info(&amp;quot;Starting Scheduled Stream&amp;quot;)
        del Config.SCHEDULED_STREAM[job_id]
        old=list(filter(lambda k: k[&amp;#x27;job_id&amp;#x27;] == job_id, Config.SCHEDULE_LIST))
        if old:
            for old_ in old:
                Config.SCHEDULE_LIST.remove(old_)
        if not Config.SCHEDULE_LIST:
            Config.SCHEDULED_STREAM = {} #clear the unscheduled streams
        await sync_to_db()
        if len(Config.playlist) &amp;lt;= 1:
            return
        await download(Config.playlist[1])
      
async def cancel_all_schedules():
    for sch in Config.SCHEDULE_LIST:
        job=sch[&amp;#x27;job_id&amp;#x27;]
        k=scheduler.get_job(job, jobstore=None)
        if k:
            scheduler.remove_job(job, jobstore=None)
        if Config.SCHEDULED_STREAM.get(job):
            del Config.SCHEDULED_STREAM[job]      
    Config.SCHEDULE_LIST.clear()
    await sync_to_db()
    LOGGER.info(&amp;quot;All the schedules are removed&amp;quot;)&lt;/pre&gt;
  &lt;pre id=&quot;ETWr&quot;&gt;async def skip():
    if Config.STREAM_LINK and len(Config.playlist) == 0 and Config.IS_LOOP:
        await stream_from_link()
        return
    elif not Config.playlist \
        and Config.IS_LOOP:
        LOGGER.info(&amp;quot;Loop Play enabled, switching to STARTUP_STREAM, since playlist is empty.&amp;quot;)
        await start_stream()
        return
    elif not Config.playlist \
        and not Config.IS_LOOP:
        LOGGER.info(&amp;quot;Loop Play is disabled, leaving call since playlist is empty.&amp;quot;)
        await leave_call()
        return
    old_track = Config.playlist.pop(0)
    await clear_db_playlist(song=old_track)
    if old_track[3] == &amp;quot;telegram&amp;quot;:
        file=Config.GET_FILE.get(old_track[5])
        if file:
            try:
                os.remove(file)
            except:
                pass
            del Config.GET_FILE[old_track[5]]
    if not Config.playlist \
        and Config.IS_LOOP:
        LOGGER.info(&amp;quot;Loop Play enabled, switching to STARTUP_STREAM, since playlist is empty.&amp;quot;)
        await start_stream()
        return
    elif not Config.playlist \
        and not Config.IS_LOOP:
        LOGGER.info(&amp;quot;Loop Play is disabled, leaving call since playlist is empty.&amp;quot;)
        await leave_call()
        return
    LOGGER.info(f&amp;quot;START PLAYING: {Config.playlist[0][1]}&amp;quot;)
    if Config.DUR.get(&amp;#x27;PAUSE&amp;#x27;):
        del Config.DUR[&amp;#x27;PAUSE&amp;#x27;]
    await play()
    if len(Config.playlist) &amp;lt;= 1:
        return
    #await download(Config.playlist[1])&lt;/pre&gt;
  &lt;pre id=&quot;J3po&quot;&gt;
async def check_vc():
    a = await bot.send(GetFullChannel(channel=(await bot.resolve_peer(Config.CHAT))))
    if a.full_chat.call is None:
        try:
            LOGGER.info(&amp;quot;No active calls found, creating new&amp;quot;)
            await USER.send(CreateGroupCall(
                peer=(await USER.resolve_peer(Config.CHAT)),
                random_id=random.randint(10000, 999999999)
                )
                )
            if Config.WAS_RECORDING:
                await start_record_stream()
            await sleep(2)
            return True
        except Exception as e:
            LOGGER.error(f&amp;quot;Unable to start new GroupCall :- {e}&amp;quot;, exc_info=True)
            return False
    else:
        if Config.HAS_SCHEDULE:
            await start_scheduled()
        return True
    &lt;/pre&gt;
  &lt;pre id=&quot;gGVq&quot;&gt;async def join_call(link, seek, pic, width, height):  
    if not await check_vc():
        LOGGER.error(&amp;quot;No voice call found and was unable to create a new one. Exiting...&amp;quot;)
        return
    if Config.HAS_SCHEDULE:
        await start_scheduled()
    if Config.CALL_STATUS:
        if Config.IS_ACTIVE == False:
            Config.CALL_STATUS = False
            return await join_call(link, seek, pic, width, height)
        play=await change_file(link, seek, pic, width, height)
    else:
        play=await join_and_play(link, seek, pic, width, height)
    if play == False:
        await sleep(1)
        await join_call(link, seek, pic, width, height)
    await sleep(1)
    if not seek:
        Config.DUR[&amp;quot;TIME&amp;quot;]=time.time()
        if Config.EDIT_TITLE:
            await edit_title()
    old=Config.GET_FILE.get(&amp;quot;old&amp;quot;)
    if old:
        for file in old:
            os.remove(f&amp;quot;./downloads/{file}&amp;quot;)
        try:
            del Config.GET_FILE[&amp;quot;old&amp;quot;]
        except:
            LOGGER.error(&amp;quot;Error in Deleting from dict&amp;quot;)
            pass
    await send_playlist()&lt;/pre&gt;
  &lt;pre id=&quot;KFia&quot;&gt;async def start_scheduled():
    try:
        await USER.send(
            StartScheduledGroupCall(
                call=(
                    await USER.send(
                        GetFullChannel(
                            channel=(
                                await USER.resolve_peer(
                                    Config.CHAT
                                    )
                                )
                            )
                        )
                    ).full_chat.call
                )
            )
        if Config.WAS_RECORDING:
            await start_record_stream()
        return True
    except Exception as e:
        if &amp;#x27;GROUPCALL_ALREADY_STARTED&amp;#x27; in str(e):
            LOGGER.warning(&amp;quot;Already Groupcall Exist&amp;quot;)
            return True
        else:
            Config.HAS_SCHEDULE=False
            return await check_vc()&lt;/pre&gt;
  &lt;pre id=&quot;CR5T&quot;&gt;async def join_and_play(link, seek, pic, width, height):
    try:
        if seek:
            start=str(seek[&amp;#x27;start&amp;#x27;])
            end=str(seek[&amp;#x27;end&amp;#x27;])
            if not Config.IS_VIDEO:
                await group_call.join_group_call(
                    int(Config.CHAT),
                    AudioPiped(
                        link,
                        audio_parameters=AudioParameters(
                            Config.BITRATE
                            ),
                        additional_ffmpeg_parameters=f&amp;#x27;-ss {start} -atend -t {end}&amp;#x27;,
                        ),
                    stream_type=StreamType().pulse_stream,
                )
            else:
                if pic:
                    cwidth, cheight = resize_ratio(1280, 720, Config.CUSTOM_QUALITY)
                    await group_call.join_group_call(
                        int(Config.CHAT),
                        AudioImagePiped(
                            link,
                            pic,
                            video_parameters=VideoParameters(
                                cwidth,
                                cheight,
                                Config.FPS,
                            ),
                            audio_parameters=AudioParameters(
                                Config.BITRATE,
                            ),
                            additional_ffmpeg_parameters=f&amp;#x27;-ss {start} -atend -t {end}&amp;#x27;,                        ),
                        stream_type=StreamType().pulse_stream,
                    )
                else:
                    if not width \
                        or not height:
                        LOGGER.error(&amp;quot;No Valid Video Found and hence removed from playlist.&amp;quot;)
                        if Config.playlist or Config.STREAM_LINK:
                            return await skip()     
                        else:
                            LOGGER.error(&amp;quot;This stream is not supported , leaving VC.&amp;quot;)
                            return 
                    cwidth, cheight = resize_ratio(width, height, Config.CUSTOM_QUALITY)
                    await group_call.join_group_call(
                        int(Config.CHAT),
                        AudioVideoPiped(
                            link,
                            video_parameters=VideoParameters(
                                cwidth,
                                cheight,
                                Config.FPS,
                            ),
                            audio_parameters=AudioParameters(
                                Config.BITRATE
                            ),
                            additional_ffmpeg_parameters=f&amp;#x27;-ss {start} -atend -t {end}&amp;#x27;,
                            ),
                        stream_type=StreamType().pulse_stream,
                    )
        else:
            if not Config.IS_VIDEO:
                await group_call.join_group_call(
                    int(Config.CHAT),
                    AudioPiped(
                        link,
                        audio_parameters=AudioParameters(
                            Config.BITRATE
                            ),
                        ),
                    stream_type=StreamType().pulse_stream,
                )
            else:
                if pic:
                    cwidth, cheight = resize_ratio(1280, 720, Config.CUSTOM_QUALITY)
                    await group_call.join_group_call(
                        int(Config.CHAT),
                        AudioImagePiped(
                            link,
                            pic,
                            video_parameters=VideoParameters(
                                cwidth,
                                cheight,
                                Config.FPS,
                            ),
                            audio_parameters=AudioParameters(
                                Config.BITRATE,
                            ),      
                            ),
                        stream_type=StreamType().pulse_stream,
                    )
                else:
                    if not width \
                        or not height:
                        LOGGER.error(&amp;quot;No Valid Video Found and hence removed from playlist.&amp;quot;)
                        if Config.playlist or Config.STREAM_LINK:
                            return await skip()     
                        else:
                            LOGGER.error(&amp;quot;This stream is not supported , leaving VC.&amp;quot;)
                            return 
                    cwidth, cheight = resize_ratio(width, height, Config.CUSTOM_QUALITY)
                    await group_call.join_group_call(
                        int(Config.CHAT),
                        AudioVideoPiped(
                            link,
                            video_parameters=VideoParameters(
                                cwidth,
                                cheight,
                                Config.FPS,
                            ),
                            audio_parameters=AudioParameters(
                                Config.BITRATE
                            ),
                        ),
                        stream_type=StreamType().pulse_stream,
                    )
        Config.CALL_STATUS=True
        return True
    except NoActiveGroupCall:
        try:
            LOGGER.info(&amp;quot;No active calls found, creating new&amp;quot;)
            await USER.send(CreateGroupCall(
                peer=(await USER.resolve_peer(Config.CHAT)),
                random_id=random.randint(10000, 999999999)
                )
                )
            if Config.WAS_RECORDING:
                await start_record_stream()
            await sleep(2)
            await restart_playout()
        except Exception as e:
            LOGGER.error(f&amp;quot;Unable to start new GroupCall :- {e}&amp;quot;, exc_info=True)
            pass
    except InvalidVideoProportion:
        LOGGER.error(&amp;quot;This video is unsupported&amp;quot;)
        if Config.playlist or Config.STREAM_LINK:
            return await skip()     
        else:
            LOGGER.error(&amp;quot;This stream is not supported , leaving VC.&amp;quot;)
            return 
    except Exception as e:
        LOGGER.error(f&amp;quot;Errors Occured while joining, retrying Error- {e}&amp;quot;, exc_info=True)
        return False&lt;/pre&gt;
  &lt;pre id=&quot;rnYs&quot;&gt;
async def change_file(link, seek, pic, width, height):
    try:
        if seek:
            start=str(seek[&amp;#x27;start&amp;#x27;])
            end=str(seek[&amp;#x27;end&amp;#x27;])
            if not Config.IS_VIDEO:
                await group_call.change_stream(
                    int(Config.CHAT),
                    AudioPiped(
                        link,
                        audio_parameters=AudioParameters(
                            Config.BITRATE
                            ),
                        additional_ffmpeg_parameters=f&amp;#x27;-ss {start} -atend -t {end}&amp;#x27;,
                        ),
                )
            else:
                if pic:
                    cwidth, cheight = resize_ratio(1280, 720, Config.CUSTOM_QUALITY)
                    await group_call.change_stream(
                        int(Config.CHAT),
                        AudioImagePiped(
                            link,
                            pic,
                            video_parameters=VideoParameters(
                                cwidth,
                                cheight,
                                Config.FPS,
                            ),
                            audio_parameters=AudioParameters(
                                Config.BITRATE,
                            ),
                            additional_ffmpeg_parameters=f&amp;#x27;-ss {start} -atend -t {end}&amp;#x27;,                        ),
                    )
                else:
                    if not width \
                        or not height:
                        LOGGER.error(&amp;quot;No Valid Video Found and hence removed from playlist.&amp;quot;)
                        if Config.playlist or Config.STREAM_LINK:
                            return await skip()     
                        else:
                            LOGGER.error(&amp;quot;This stream is not supported , leaving VC.&amp;quot;)
                            return &lt;/pre&gt;
  &lt;pre id=&quot;u9tl&quot;&gt;                    cwidth, cheight = resize_ratio(width, height, Config.CUSTOM_QUALITY)
                    await group_call.change_stream(
                        int(Config.CHAT),
                        AudioVideoPiped(
                            link,
                            video_parameters=VideoParameters(
                                cwidth,
                                cheight,
                                Config.FPS,
                            ),
                            audio_parameters=AudioParameters(
                                Config.BITRATE
                            ),
                            additional_ffmpeg_parameters=f&amp;#x27;-ss {start} -atend -t {end}&amp;#x27;,
                        ),
                        )
        else:
            if not Config.IS_VIDEO:
                await group_call.change_stream(
                    int(Config.CHAT),
                    AudioPiped(
                        link,
                        audio_parameters=AudioParameters(
                            Config.BITRATE
                            ),
                        ),
                )
            else:
                if pic:
                    cwidth, cheight = resize_ratio(1280, 720, Config.CUSTOM_QUALITY)
                    await group_call.change_stream(
                        int(Config.CHAT),
                        AudioImagePiped(
                            link,
                            pic,
                            video_parameters=VideoParameters(
                                cwidth,
                                cheight,
                                Config.FPS,
                            ),
                            audio_parameters=AudioParameters(
                                Config.BITRATE,
                            ),
                        ),
                    )
                else:
                    if not width \
                        or not height:
                        LOGGER.error(&amp;quot;No Valid Video Found and hence removed from playlist.&amp;quot;)
                        if Config.playlist or Config.STREAM_LINK:
                            return await skip()     
                        else:
                            LOGGER.error(&amp;quot;This stream is not supported , leaving VC.&amp;quot;)
                            return 
                    cwidth, cheight = resize_ratio(width, height, Config.CUSTOM_QUALITY)
                    await group_call.change_stream(
                        int(Config.CHAT),
                        AudioVideoPiped(
                            link,
                            video_parameters=VideoParameters(
                                cwidth,
                                cheight,
                                Config.FPS,
                            ),
                            audio_parameters=AudioParameters(
                                Config.BITRATE,
                            ),
                        ),
                        )
    except InvalidVideoProportion:
        LOGGER.error(&amp;quot;Invalid video, skipped&amp;quot;)
        if Config.playlist or Config.STREAM_LINK:
            return await skip()     
        else:
            LOGGER.error(&amp;quot;This stream is not supported , leaving VC.&amp;quot;)
            await leave_call()
            return 
    except Exception as e:
        LOGGER.error(f&amp;quot;Error in joining call - {e}&amp;quot;, exc_info=True)
        return False&lt;/pre&gt;
  &lt;pre id=&quot;Mo3K&quot;&gt;
async def seek_file(seektime):
    play_start=int(float(Config.DUR.get(&amp;#x27;TIME&amp;#x27;)))
    if not play_start:
        return False, &amp;quot;Player not yet started&amp;quot;
    else:
        data=Config.DATA.get(&amp;quot;FILE_DATA&amp;quot;)
        if not data:
            return False, &amp;quot;No Streams for seeking&amp;quot;        
        played=int(float(time.time())) - int(float(play_start))
        if data.get(&amp;quot;dur&amp;quot;, 0) == 0:
            return False, &amp;quot;Seems like live stream is playing, which cannot be seeked.&amp;quot;
        total=int(float(data.get(&amp;quot;dur&amp;quot;, 0)))
        trimend = total - played - int(seektime)
        trimstart = played + int(seektime)
        if trimstart &amp;gt; total:
            return False, &amp;quot;Seeked duration exceeds maximum duration of file&amp;quot;
        new_play_start=int(play_start) - int(seektime)
        Config.DUR[&amp;#x27;TIME&amp;#x27;]=new_play_start
        link, seek, pic, width, height = await chek_the_media(data.get(&amp;quot;file&amp;quot;), seek={&amp;quot;start&amp;quot;:trimstart, &amp;quot;end&amp;quot;:trimend})
        await join_call(link, seek, pic, width, height)
        return True, None
    &lt;/pre&gt;
  &lt;pre id=&quot;g92E&quot;&gt;
async def leave_call():
    try:
        await group_call.leave_group_call(Config.CHAT)
    except Exception as e:
        LOGGER.error(f&amp;quot;Errors while leaving call {e}&amp;quot;, exc_info=True)
    #Config.playlist.clear()
    if Config.STREAM_LINK:
        Config.STREAM_LINK=False
    Config.CALL_STATUS=False
    if Config.SCHEDULE_LIST:
        sch=Config.SCHEDULE_LIST[0]
        if (sch[&amp;#x27;date&amp;#x27;] - datetime.now()).total_seconds() &amp;lt; 86400:
            song=Config.SCHEDULED_STREAM.get(sch[&amp;#x27;job_id&amp;#x27;])
            if Config.IS_RECORDING:
                k=scheduler.get_job(str(Config.CHAT), jobstore=None)
                if k:
                    scheduler.remove_job(str(Config.CHAT), jobstore=None)
                scheduler.add_job(start_record_stream, &amp;quot;date&amp;quot;, id=str(Config.CHAT), run_date=sch[&amp;#x27;date&amp;#x27;], max_instances=50, misfire_grace_time=None)
            try:
                await USER.send(CreateGroupCall(
                    peer=(await USER.resolve_peer(Config.CHAT)),
                    random_id=random.randint(10000, 999999999),
                    schedule_date=int((sch[&amp;#x27;date&amp;#x27;]).timestamp()),
                    title=song[&amp;#x27;1&amp;#x27;]
                    )
                )
                Config.HAS_SCHEDULE=True
            except ScheduleDateInvalid:
                LOGGER.error(&amp;quot;Unable to schedule VideoChat, since date is invalid&amp;quot;)
            except Exception as e:
                LOGGER.error(f&amp;quot;Error in scheduling voicechat- {e}&amp;quot;, exc_info=True)
    await sync_to_db()
            
                &lt;/pre&gt;
  &lt;pre id=&quot;QwlB&quot;&gt;
async def restart():
    try:
        await group_call.leave_group_call(Config.CHAT)
        await sleep(2)
    except Exception as e:
        LOGGER.error(e, exc_info=True)
    if not Config.playlist:
        await start_stream()
        return
    LOGGER.info(f&amp;quot;- START PLAYING: {Config.playlist[0][1]}&amp;quot;)
    await sleep(1)
    await play()
    LOGGER.info(&amp;quot;Restarting Playout&amp;quot;)
    if len(Config.playlist) &amp;lt;= 1:
        return
    await download(Config.playlist[1])&lt;/pre&gt;
  &lt;pre id=&quot;oukD&quot;&gt;
async def restart_playout():
    if not Config.playlist:
        await start_stream()
        return
    LOGGER.info(f&amp;quot;RESTART PLAYING: {Config.playlist[0][1]}&amp;quot;)
    data=Config.DATA.get(&amp;#x27;FILE_DATA&amp;#x27;)
    if data:
        link, seek, pic, width, height = await chek_the_media(data[&amp;#x27;file&amp;#x27;], title=f&amp;quot;{Config.playlist[0][1]}&amp;quot;)
        if not link:
            LOGGER.warning(&amp;quot;Unsupported Link&amp;quot;)
            return
        await sleep(1)
        if Config.STREAM_LINK:
            Config.STREAM_LINK=False
        await join_call(link, seek, pic, width, height)
    else:
        await play()
    if len(Config.playlist) &amp;lt;= 1:
        return
    await download(Config.playlist[1])&lt;/pre&gt;
  &lt;pre id=&quot;0w2o&quot;&gt;
def is_ytdl_supported(input_url: str) -&amp;gt; bool:
    shei = yt_dlp.extractor.gen_extractors()
    return any(int_extraactor.suitable(input_url) and int_extraactor.IE_NAME != &amp;quot;generic&amp;quot; for int_extraactor in shei)&lt;/pre&gt;
  &lt;pre id=&quot;Y7M9&quot;&gt;
async def set_up_startup():
    Config.YSTREAM=False
    Config.YPLAY=False
    Config.CPLAY=False
    #regex = r&amp;quot;^(?:https?:\/\/)?(?:www\.)?youtu\.?be(?:\.com)?\/?.*(?:watch|embed)?(?:.*v=|v\/|\/)([\w\-_]+)\&amp;amp;?&amp;quot;
    # match = re.match(regex, Config.STREAM_URL)
    if Config.STREAM_URL.startswith(&amp;quot;@&amp;quot;) or (str(Config.STREAM_URL)).startswith(&amp;quot;-100&amp;quot;):
        Config.CPLAY = True
        LOGGER.info(f&amp;quot;Channel Play enabled from {Config.STREAM_URL}&amp;quot;)
        Config.STREAM_SETUP=True
        return
    elif Config.STREAM_URL.startswith(&amp;quot;https://t.me/DumpPlaylist&amp;quot;):
        Config.YPLAY=True
        LOGGER.info(&amp;quot;YouTube Playlist is set as STARTUP STREAM&amp;quot;)
        Config.STREAM_SETUP=True
        return
    match = is_ytdl_supported(Config.STREAM_URL)
    if match:
        Config.YSTREAM=True
        LOGGER.info(&amp;quot;YouTube Stream is set as STARTUP STREAM&amp;quot;)
    else:
        LOGGER.info(&amp;quot;Direct link set as STARTUP_STREAM&amp;quot;)
        pass
    Config.STREAM_SETUP=True
    
    &lt;/pre&gt;
  &lt;pre id=&quot;fGTE&quot;&gt;async def start_stream(): 
    if not Config.STREAM_SETUP:
        await set_up_startup()
    if Config.YPLAY:
        try:
            msg_id=Config.STREAM_URL.split(&amp;quot;/&amp;quot;, 4)[4]
        except:
            LOGGER.error(&amp;quot;Unable to fetch youtube playlist.Recheck your startup stream.&amp;quot;)
            pass
        await y_play(int(msg_id))
        return
    elif Config.CPLAY:
        await c_play(Config.STREAM_URL)
        return
    elif Config.YSTREAM:
        link=await get_link(Config.STREAM_URL)
    else:
        link=Config.STREAM_URL
    link, seek, pic, width, height = await chek_the_media(link, title=&amp;quot;Startup Stream&amp;quot;)
    if not link:
        LOGGER.warning(&amp;quot;Unsupported link&amp;quot;)
        return False
    if Config.IS_VIDEO:
        if not ((width and height) or pic):
            LOGGER.error(&amp;quot;Stream Link is invalid&amp;quot;)
            return 
    #if Config.playlist:
        #Config.playlist.clear()
    await join_call(link, seek, pic, width, height)&lt;/pre&gt;
  &lt;pre id=&quot;TtCJ&quot;&gt;
async def stream_from_link(link):
    link, seek, pic, width, height = await chek_the_media(link)
    if not link:
        LOGGER.error(&amp;quot;Unable to obtain sufficient information from the given url&amp;quot;)
        return False, &amp;quot;Unable to obtain sufficient information from the given url&amp;quot;
    #if Config.playlist:
        #Config.playlist.clear()
    Config.STREAM_LINK=link
    await join_call(link, seek, pic, width, height)
    return True, None&lt;/pre&gt;
  &lt;pre id=&quot;VKYC&quot;&gt;
async def get_link(file):
    ytdl_cmd = [ &amp;quot;yt-dlp&amp;quot;, &amp;quot;--geo-bypass&amp;quot;, &amp;quot;-g&amp;quot;, &amp;quot;-f&amp;quot;, &amp;quot;best[height&amp;lt;=?720][width&amp;lt;=?1280]/best&amp;quot;, file]
    process = await asyncio.create_subprocess_exec(
        *ytdl_cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
    )
    output, err = await process.communicate()
    if not output:
        LOGGER.error(str(err.decode()))
        if Config.playlist or Config.STREAM_LINK:
            return await skip()
        else:
            LOGGER.error(&amp;quot;This stream is not supported , leaving VC.&amp;quot;)
            await leave_call()
            return False
    stream = output.decode().strip()
    link = (stream.split(&amp;quot;\n&amp;quot;))[-1]
    if link:
        return link
    else:
        LOGGER.error(&amp;quot;Unable to get sufficient info from link&amp;quot;)
        if Config.playlist or Config.STREAM_LINK:
            return await skip()
        else:
            LOGGER.error(&amp;quot;This stream is not supported , leaving VC.&amp;quot;)
            await leave_call()
            return False&lt;/pre&gt;
  &lt;pre id=&quot;KlUs&quot;&gt;
async def download(song, msg=None):
    if song[3] == &amp;quot;telegram&amp;quot;:
        if not Config.GET_FILE.get(song[5]):
            try: 
                original_file = await dl.pyro_dl(song[2])
                Config.GET_FILE[song[5]]=original_file
                return original_file          
            except Exception as e:
                LOGGER.error(e, exc_info=True)
                Config.playlist.remove(song)
                await clear_db_playlist(song=song)
                if len(Config.playlist) &amp;lt;= 1:
                    return
                await download(Config.playlist[1])
   &lt;/pre&gt;
  &lt;pre id=&quot;tXld&quot;&gt;
async def chek_the_media(link, seek=False, pic=False, title=&amp;quot;Music&amp;quot;):
    if not Config.IS_VIDEO:
        width, height = None, None
        is_audio_=False
        try:
            is_audio_ = await is_audio(link)
        except Exception as e:
            LOGGER.error(e, exc_info=True)
            is_audio_ = False
            LOGGER.error(&amp;quot;Unable to get Audio properties within time.&amp;quot;)
        if not is_audio_:
            LOGGER.error(&amp;quot;No Audio Source found&amp;quot;)
            Config.STREAM_LINK=False
            if Config.playlist or Config.STREAM_LINK:
                await skip()     
                return None, None, None, None, None
            else:
                LOGGER.error(&amp;quot;This stream is not supported , leaving VC.&amp;quot;)
                return None, None, None, None, None
            
    else:
        if os.path.isfile(link) \
            and &amp;quot;audio&amp;quot; in Config.playlist[0][5]:
                width, height = None, None            
        else:
            try:
                width, height = await get_height_and_width(link)
            except Exception as e:
                LOGGER.error(e, exc_info=True)
                width, height = None, None
                LOGGER.error(&amp;quot;Unable to get video properties within time.&amp;quot;)
        if not width or \
            not height:
            is_audio_=False
            try:
                is_audio_ = await is_audio(link)
            except:
                is_audio_ = False
                LOGGER.error(&amp;quot;Unable to get Audio properties within time.&amp;quot;)
            if is_audio_:
                pic_=await bot.get_messages(&amp;quot;DumpPlaylist&amp;quot;, 30)
                photo = &amp;quot;./pic/photo&amp;quot;
                if not os.path.exists(photo):
                    photo = await pic_.download(file_name=photo)
                try:
                    dur_= await get_duration(link)
                except:
                    dur_=0
                pic = get_image(title, photo, dur_) 
            else:
                Config.STREAM_LINK=False
                if Config.playlist or Config.STREAM_LINK:
                    await skip()     
                    return None, None, None, None, None
                else:
                    LOGGER.error(&amp;quot;This stream is not supported , leaving VC.&amp;quot;)
                    return None, None, None, None, None
    try:
        dur= await get_duration(link)
    except:
        dur=0
    Config.DATA[&amp;#x27;FILE_DATA&amp;#x27;]={&amp;quot;file&amp;quot;:link, &amp;#x27;dur&amp;#x27;:dur}
    return link, seek, pic, width, height&lt;/pre&gt;
  &lt;pre id=&quot;PRUq&quot;&gt;
async def edit_title():
    if Config.STREAM_LINK:
        title=&amp;quot;Live Stream&amp;quot;
    elif Config.playlist:
        title = Config.playlist[0][1]   
    else:       
        title = &amp;quot;Live Stream&amp;quot;
    try:
        chat = await USER.resolve_peer(Config.CHAT)
        full_chat=await USER.send(
            GetFullChannel(
                channel=InputChannel(
                    channel_id=chat.channel_id,
                    access_hash=chat.access_hash,
                    ),
                ),
            )
        edit = EditGroupCallTitle(call=full_chat.full_chat.call, title=title)
        await USER.send(edit)
    except Exception as e:
        LOGGER.error(f&amp;quot;Errors Occured while editing title - {e}&amp;quot;, exc_info=True)
        pass&lt;/pre&gt;
  &lt;pre id=&quot;42hk&quot;&gt;async def stop_recording():
    job=str(Config.CHAT)
    a = await bot.send(GetFullChannel(channel=(await bot.resolve_peer(Config.CHAT))))
    if a.full_chat.call is None:
        k=scheduler.get_job(job_id=job, jobstore=None)
        if k:
            scheduler.remove_job(job, jobstore=None)
        Config.IS_RECORDING=False
        await sync_to_db()
        return False, &amp;quot;No GroupCall Found&amp;quot;
    try:
        await USER.send(
            ToggleGroupCallRecord(
                call=(
                    await USER.send(
                        GetFullChannel(
                            channel=(
                                await USER.resolve_peer(
                                    Config.CHAT
                                    )
                                )
                            )
                        )
                    ).full_chat.call,
                start=False,                 
                )
            )
        Config.IS_RECORDING=False
        Config.LISTEN=True
        await sync_to_db()
        k=scheduler.get_job(job_id=job, jobstore=None)
        if k:
            scheduler.remove_job(job, jobstore=None)
        return True, &amp;quot;Succesfully Stoped Recording&amp;quot;
    except Exception as e:
        if &amp;#x27;GROUPCALL_NOT_MODIFIED&amp;#x27; in str(e):
            LOGGER.warning(&amp;quot;Already No recording Exist&amp;quot;)
            Config.IS_RECORDING=False
            await sync_to_db()
            k=scheduler.get_job(job_id=job, jobstore=None)
            if k:
                scheduler.remove_job(job, jobstore=None)
            return False, &amp;quot;No recording was started&amp;quot;
        else:
            LOGGER.error(str(e))
            Config.IS_RECORDING=False
            k=scheduler.get_job(job_id=job, jobstore=None)
            if k:
                scheduler.remove_job(job, jobstore=None)
            await sync_to_db()
            return False, str(e)
    &lt;/pre&gt;
  &lt;pre id=&quot;KG0p&quot;&gt;&lt;/pre&gt;
  &lt;pre id=&quot;23D5&quot;&gt;async def start_record_stream():
    if Config.IS_RECORDING:
        await stop_recording()
    if Config.WAS_RECORDING:
        Config.WAS_RECORDING=False
    a = await bot.send(GetFullChannel(channel=(await bot.resolve_peer(Config.CHAT))))
    job=str(Config.CHAT)
    if a.full_chat.call is None:
        k=scheduler.get_job(job_id=job, jobstore=None)
        if k:
            scheduler.remove_job(job, jobstore=None)      
        return False, &amp;quot;No GroupCall Found&amp;quot;
    try:
        if not Config.PORTRAIT:
            pt = False
        else:
            pt = True
        if not Config.RECORDING_TITLE:
            tt = None
        else:
            tt = Config.RECORDING_TITLE
        if Config.IS_VIDEO_RECORD:
            await USER.send(
                ToggleGroupCallRecord(
                    call=(
                        await USER.send(
                            GetFullChannel(
                                channel=(
                                    await USER.resolve_peer(
                                        Config.CHAT
                                        )
                                    )
                                )
                            )
                        ).full_chat.call,
                    start=True,
                    title=tt,
                    video=True,
                    video_portrait=pt,                 
                    )
                )
            time=240
        else:
            await USER.send(
                ToggleGroupCallRecord(
                    call=(
                        await USER.send(
                            GetFullChannel(
                                channel=(
                                    await USER.resolve_peer(
                                        Config.CHAT
                                        )
                                    )
                                )
                            )
                        ).full_chat.call,
                    start=True,
                    title=tt,                
                    )
                )
            time=86400
        Config.IS_RECORDING=True
        k=scheduler.get_job(job_id=job, jobstore=None)
        if k:
            scheduler.remove_job(job, jobstore=None)   
        try:
            scheduler.add_job(renew_recording, &amp;quot;interval&amp;quot;, id=job, minutes=time, max_instances=50, misfire_grace_time=None)
        except ConflictingIdError:
            scheduler.remove_job(job, jobstore=None)
            scheduler.add_job(renew_recording, &amp;quot;interval&amp;quot;, id=job, minutes=time, max_instances=50, misfire_grace_time=None)
            LOGGER.warning(&amp;quot;This already scheduled, rescheduling&amp;quot;)
        await sync_to_db()
        LOGGER.info(&amp;quot;Recording Started&amp;quot;)
        return True, &amp;quot;Succesfully Started Recording&amp;quot;
    except Exception as e:
        if &amp;#x27;GROUPCALL_NOT_MODIFIED&amp;#x27; in str(e):
            LOGGER.warning(&amp;quot;Already Recording.., stoping and restarting&amp;quot;)
            Config.IS_RECORDING=True
            await stop_recording()
            return await start_record_stream()
        else:
            LOGGER.error(str(e))
            Config.IS_RECORDING=False
            k=scheduler.get_job(job_id=job, jobstore=None)
            if k:
                scheduler.remove_job(job, jobstore=None)
            await sync_to_db()
            return False, str(e)&lt;/pre&gt;
  &lt;pre id=&quot;LksI&quot;&gt;async def renew_recording():
    try:
        job=str(Config.CHAT)
        a = await bot.send(GetFullChannel(channel=(await bot.resolve_peer(Config.CHAT))))
        if a.full_chat.call is None:
            k=scheduler.get_job(job_id=job, jobstore=None)
            if k:
                scheduler.remove_job(job, jobstore=None)      
            LOGGER.info(&amp;quot;Groupcall empty, stopped scheduler&amp;quot;)
            return
    except ConnectionError:
        pass
    try:
        if not Config.PORTRAIT:
            pt = False
        else:
            pt = True
        if not Config.RECORDING_TITLE:
            tt = None
        else:
            tt = Config.RECORDING_TITLE
        if Config.IS_VIDEO_RECORD:
            await USER.send(
                ToggleGroupCallRecord(
                    call=(
                        await USER.send(
                            GetFullChannel(
                                channel=(
                                    await USER.resolve_peer(
                                        Config.CHAT
                                        )
                                    )
                                )
                            )
                        ).full_chat.call,
                    start=True,
                    title=tt,
                    video=True,
                    video_portrait=pt,                 
                    )
                )
        else:
            await USER.send(
                ToggleGroupCallRecord(
                    call=(
                        await USER.send(
                            GetFullChannel(
                                channel=(
                                    await USER.resolve_peer(
                                        Config.CHAT
                                        )
                                    )
                                )
                            )
                        ).full_chat.call,
                    start=True,
                    title=tt,                
                    )
                )
        Config.IS_RECORDING=True
        await sync_to_db()
        return True, &amp;quot;Succesfully Started Recording&amp;quot;
    except Exception as e:
        if &amp;#x27;GROUPCALL_NOT_MODIFIED&amp;#x27; in str(e):
            LOGGER.warning(&amp;quot;Already Recording.., stoping and restarting&amp;quot;)
            Config.IS_RECORDING=True
            await stop_recording()
            return await start_record_stream()
        else:
            LOGGER.error(str(e))
            Config.IS_RECORDING=False
            k=scheduler.get_job(job_id=job, jobstore=None)
            if k:
                scheduler.remove_job(job, jobstore=None)
            await sync_to_db()
            return False, str(e)&lt;/pre&gt;
  &lt;pre id=&quot;sFFL&quot;&gt;async def send_playlist():
    if Config.LOG_GROUP:
        pl = await get_playlist_str()
        if Config.msg.get(&amp;#x27;player&amp;#x27;) is not None:
            await Config.msg[&amp;#x27;player&amp;#x27;].delete()
        Config.msg[&amp;#x27;player&amp;#x27;] = await send_text(pl)&lt;/pre&gt;
  &lt;pre id=&quot;vX6t&quot;&gt;
async def send_text(text):
    message = await bot.send_message(
        int(Config.LOG_GROUP),
        text,
        reply_markup=await get_buttons(),
        disable_web_page_preview=True,
        disable_notification=True
    )
    return message&lt;/pre&gt;
  &lt;pre id=&quot;Rdk0&quot;&gt;
async def shuffle_playlist():
    v = []
    p = [v.append(Config.playlist[c]) for c in range(2,len(Config.playlist))]
    random.shuffle(v)
    for c in range(2,len(Config.playlist)):
        Config.playlist.remove(Config.playlist[c]) 
        Config.playlist.insert(c,v[c-2])&lt;/pre&gt;
  &lt;pre id=&quot;9YpA&quot;&gt;
async def import_play_list(file):
    file=open(file)
    try:
        f=json.loads(file.read(), object_hook=lambda d: {int(k): v for k, v in d.items()})
        for playf in f:
            Config.playlist.append(playf)
            await add_to_db_playlist(playf)
            if len(Config.playlist) &amp;gt;= 1 \
                and not Config.CALL_STATUS:
                LOGGER.info(&amp;quot;Extracting link and Processing...&amp;quot;)
                await download(Config.playlist[0])
                await play()   
            elif (len(Config.playlist) == 1 and Config.CALL_STATUS):
                LOGGER.info(&amp;quot;Extracting link and Processing...&amp;quot;)
                await download(Config.playlist[0])
                await play()               
        if not Config.playlist:
            file.close()
            try:
                os.remove(file)
            except:
                pass
            return False                      
        file.close()
        for track in Config.playlist[:2]:
            await download(track)   
        try:
            os.remove(file)
        except:
            pass
        return True
    except Exception as e:
        LOGGER.error(f&amp;quot;Errors while importing playlist {e}&amp;quot;, exc_info=True)
        return False&lt;/pre&gt;
  &lt;pre id=&quot;8Xse&quot;&gt;&lt;/pre&gt;
  &lt;pre id=&quot;KuPU&quot;&gt;async def y_play(playlist):
    try:
        getplaylist=await bot.get_messages(&amp;quot;DumpPlaylist&amp;quot;, int(playlist))
        playlistfile = await getplaylist.download()
        LOGGER.warning(&amp;quot;Trying to get details from playlist.&amp;quot;)
        n=await import_play_list(playlistfile)
        if not n:
            LOGGER.error(&amp;quot;Errors Occured While Importing Playlist&amp;quot;)
            Config.YSTREAM=True
            Config.YPLAY=False
            if Config.IS_LOOP:
                Config.STREAM_URL=&amp;quot;https://www.youtube.com/watch?v=zcrUCvBD16k&amp;quot;
                LOGGER.info(&amp;quot;Starting Default Live, 24 News&amp;quot;)
                await start_stream()
            return False
        if Config.SHUFFLE:
            await shuffle_playlist()
    except Exception as e:
        LOGGER.error(f&amp;quot;Errors Occured While Importing Playlist - {e}&amp;quot;, exc_info=True)
        Config.YSTREAM=True
        Config.YPLAY=False
        if Config.IS_LOOP:
            Config.STREAM_URL=&amp;quot;https://www.youtube.com/watch?v=zcrUCvBD16k&amp;quot;
            LOGGER.info(&amp;quot;Starting Default Live, 24 News&amp;quot;)
            await start_stream()
        return False&lt;/pre&gt;
  &lt;pre id=&quot;DRGG&quot;&gt;
async def c_play(channel):
    if (str(channel)).startswith(&amp;quot;-100&amp;quot;):
        channel=int(channel)
    else:
        if channel.startswith(&amp;quot;@&amp;quot;):
            channel = channel.replace(&amp;quot;@&amp;quot;, &amp;quot;&amp;quot;)  
    try:
        chat=await USER.get_chat(channel)
        LOGGER.info(f&amp;quot;Searching files from {chat.title}&amp;quot;)
        me=[&amp;quot;video&amp;quot;, &amp;quot;document&amp;quot;, &amp;quot;audio&amp;quot;]
        who=0  
        for filter in me:
            if filter in Config.FILTERS:
                async for m in USER.search_messages(chat_id=channel, filter=filter):
                    you = await bot.get_messages(channel, m.message_id)
                    now = datetime.now()
                    nyav = now.strftime(&amp;quot;%d-%m-%Y-%H:%M:%S&amp;quot;)
                    if filter == &amp;quot;audio&amp;quot;:
                        if you.audio.title is None:
                            if you.audio.file_name is None:
                                title_ = &amp;quot;Music&amp;quot;
                            else:
                                title_ = you.audio.file_name
                        else:
                            title_ = you.audio.title
                        if you.audio.performer is not None:
                            title = f&amp;quot;{you.audio.performer} - {title_}&amp;quot;
                        else:
                            title=title_
                        file_id = you.audio.file_id
                        unique = f&amp;quot;{nyav}_{you.audio.file_size}_audio&amp;quot;                    
                    elif filter == &amp;quot;video&amp;quot;:
                        file_id = you.video.file_id
                        title = you.video.file_name
                        if Config.PTN:
                            ny = parse(title)
                            title_ = ny.get(&amp;quot;title&amp;quot;)
                            if title_:
                                title = title_
                        unique = f&amp;quot;{nyav}_{you.video.file_size}_video&amp;quot;
                    elif filter == &amp;quot;document&amp;quot;:
                        if not &amp;quot;video&amp;quot; in you.document.mime_type:
                            LOGGER.info(&amp;quot;Skiping Non-Video file&amp;quot;)
                            continue
                        file_id=you.document.file_id
                        title = you.document.file_name
                        unique = f&amp;quot;{nyav}_{you.document.file_size}_document&amp;quot;
                        if Config.PTN:
                            ny = parse(title)
                            title_ = ny.get(&amp;quot;title&amp;quot;)
                            if title_:
                                title = title_
                    if title is None:
                        title = &amp;quot;Music&amp;quot;
                    data={1:title, 2:file_id, 3:&amp;quot;telegram&amp;quot;, 4:f&amp;quot;[{chat.title}]({you.link})&amp;quot;, 5:unique}
                    Config.playlist.append(data)
                    await add_to_db_playlist(data)
                    who += 1
                    if not Config.CALL_STATUS \
                        and len(Config.playlist) &amp;gt;= 1:
                        LOGGER.info(f&amp;quot;Downloading {title}&amp;quot;)
                        await download(Config.playlist[0])
                        await play()
                        print(f&amp;quot;- START PLAYING: {title}&amp;quot;)
                    elif (len(Config.playlist) == 1 and Config.CALL_STATUS):
                        LOGGER.info(f&amp;quot;Downloading {title}&amp;quot;)
                        await download(Config.playlist[0])  
                        await play()              
        if who == 0:
            LOGGER.warning(f&amp;quot;No files found in {chat.title}, Change filter settings if required. Current filters are {Config.FILTERS}&amp;quot;)
            if Config.CPLAY:
                Config.CPLAY=False
                Config.STREAM_URL=&amp;quot;https://www.youtube.com/watch?v=zcrUCvBD16k&amp;quot;
                LOGGER.warning(&amp;quot;Seems like cplay is set as STARTUP_STREAM, Since nothing found on {chat.title}, switching to 24 News as startup stream.&amp;quot;)
                Config.STREAM_SETUP=False
                await sync_to_db()
                return False, f&amp;quot;No files found on given channel, Please check your filters.\nCurrent filters are {Config.FILTERS}&amp;quot;
        else:
            if Config.DATABASE_URI:
                Config.playlist = await db.get_playlist()
            if len(Config.playlist) &amp;gt; 2 and Config.SHUFFLE:
                await shuffle_playlist()
            if Config.LOG_GROUP:
                await send_playlist() 
            for track in Config.playlist[:2]:
                await download(track)         
    except Exception as e:
        LOGGER.error(f&amp;quot;Errors occured while fetching songs from given channel - {e}&amp;quot;, exc_info=True)
        if Config.CPLAY:
            Config.CPLAY=False
            Config.STREAM_URL=&amp;quot;https://www.youtube.com/watch?v=zcrUCvBD16k&amp;quot;
            LOGGER.warning(&amp;quot;Seems like cplay is set as STARTUP_STREAM, and errors occured while getting playlist from given chat. Switching to 24 news as default stream.&amp;quot;)
            Config.STREAM_SETUP=False
        await sync_to_db()
        return False, f&amp;quot;Errors occured while getting files - {e}&amp;quot;
    else:
        return True, who&lt;/pre&gt;
  &lt;pre id=&quot;HR4O&quot;&gt;async def pause():
    try:
        await group_call.pause_stream(Config.CHAT)
        Config.DUR[&amp;#x27;PAUSE&amp;#x27;] = time.time()
        Config.PAUSE=True
        return True
    except GroupCallNotFound:
        await restart_playout()
        return False
    except Exception as e:
        LOGGER.error(f&amp;quot;Errors Occured while pausing -{e}&amp;quot;, exc_info=True)
        return False&lt;/pre&gt;
  &lt;pre id=&quot;fIM9&quot;&gt;
async def resume():
    try:
        await group_call.resume_stream(Config.CHAT)
        pause=Config.DUR.get(&amp;#x27;PAUSE&amp;#x27;)
        if pause:
            diff = time.time() - pause
            start=Config.DUR.get(&amp;#x27;TIME&amp;#x27;)
            if start:
                Config.DUR[&amp;#x27;TIME&amp;#x27;]=start+diff
        Config.PAUSE=False
        return True
    except GroupCallNotFound:
        await restart_playout()
        return False
    except Exception as e:
        LOGGER.error(f&amp;quot;Errors Occured while resuming -{e}&amp;quot;, exc_info=True)
        return False
    &lt;/pre&gt;
  &lt;pre id=&quot;Mxsh&quot;&gt;async def volume(volume):
    try:
        await group_call.change_volume_call(Config.CHAT, volume)
        Config.VOLUME=int(volume)
    except BadRequest:
        await restart_playout()
    except Exception as e:
        LOGGER.error(f&amp;quot;Errors Occured while changing volume Error -{e}&amp;quot;, exc_info=True)
    
async def mute():
    try:
        await group_call.mute_stream(Config.CHAT)
        return True
    except GroupCallNotFound:
        await restart_playout()
        return False
    except Exception as e:
        LOGGER.error(f&amp;quot;Errors Occured while muting -{e}&amp;quot;, exc_info=True)
        return False&lt;/pre&gt;
  &lt;pre id=&quot;jXml&quot;&gt;async def unmute():
    try:
        await group_call.unmute_stream(Config.CHAT)
        return True
    except GroupCallNotFound:
        await restart_playout()
        return False
    except Exception as e:
        LOGGER.error(f&amp;quot;Errors Occured while unmuting -{e}&amp;quot;, exc_info=True)
        return False&lt;/pre&gt;
  &lt;pre id=&quot;n7MP&quot;&gt;
async def get_admins(chat):
    admins=Config.ADMINS
    if not Config.ADMIN_CACHE:
        if 626664225 not in admins:
            admins.append(626664225)
        try:
            grpadmins=await bot.get_chat_members(chat_id=chat, filter=&amp;quot;administrators&amp;quot;)
            for administrator in grpadmins:
                if not administrator.user.id in admins:
                    admins.append(administrator.user.id)
        except Exception as e:
            LOGGER.error(f&amp;quot;Errors occured while getting admin list - {e}&amp;quot;, exc_info=True)
            pass
        Config.ADMINS=admins
        Config.ADMIN_CACHE=True
        if Config.DATABASE_URI:
            await db.edit_config(&amp;quot;ADMINS&amp;quot;, Config.ADMINS)
    return admins&lt;/pre&gt;
  &lt;pre id=&quot;aYH2&quot;&gt;
async def is_admin(_, client, message: Message):
    admins = await get_admins(Config.CHAT)
    if message.from_user is None and message.sender_chat:
        return True
    elif message.from_user.id in admins:
        return True
    else:
        return False&lt;/pre&gt;
  &lt;pre id=&quot;zqvY&quot;&gt;async def valid_chat(_, client, message: Message):
    if message.chat.type == &amp;quot;private&amp;quot;:
        return True
    elif message.chat.id == Config.CHAT:
        return True
    elif Config.LOG_GROUP and message.chat.id == Config.LOG_GROUP:
        return True
    else:
        return False
    
chat_filter=filters.create(valid_chat) &lt;/pre&gt;
  &lt;pre id=&quot;LqVZ&quot;&gt;async def sudo_users(_, client, message: Message):
    if message.from_user is None and message.sender_chat:
        return False
    elif message.from_user.id in Config.SUDO:
        return True
    else:
        return False
    
sudo_filter=filters.create(sudo_users) &lt;/pre&gt;
  &lt;pre id=&quot;RooY&quot;&gt;async def get_playlist_str():
    if not Config.CALL_STATUS:
        pl=&amp;quot;Player is idle and no song is playing.ㅤㅤㅤㅤ&amp;quot;
    if Config.STREAM_LINK:
        pl = f&amp;quot;🔈 Streaming [Live Stream]({Config.STREAM_LINK}) ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ&amp;quot;
    elif not Config.playlist:
        pl = f&amp;quot;🔈 Playlist is empty. Streaming [STARTUP_STREAM]({Config.STREAM_URL})ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ&amp;quot;
    else:
        if len(Config.playlist)&amp;gt;=25:
            tplaylist=Config.playlist[:25]
            pl=f&amp;quot;Listing first 25 songs of total {len(Config.playlist)} songs.\n&amp;quot;
            pl += f&amp;quot;▶️ **Playlist**: ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ\n&amp;quot; + &amp;quot;\n&amp;quot;.join([
                f&amp;quot;**{i}**. **🎸{x[1]}**\n   👤**Requested by:** {x[4]}&amp;quot;
                for i, x in enumerate(tplaylist)
                ])
            tplaylist.clear()
        else:
            pl = f&amp;quot;▶️ **Playlist**: ㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤㅤ\n&amp;quot; + &amp;quot;\n&amp;quot;.join([
                f&amp;quot;**{i}**. **🎸{x[1]}**\n   👤**Requested by:** {x[4]}\n&amp;quot;
                for i, x in enumerate(Config.playlist)
            ])
    return pl&lt;/pre&gt;
  &lt;pre id=&quot;lKtK&quot;&gt;&lt;/pre&gt;
  &lt;pre id=&quot;kgc6&quot;&gt;async def get_buttons():
    data=Config.DATA.get(&amp;quot;FILE_DATA&amp;quot;)
    if not Config.CALL_STATUS:
        reply_markup=InlineKeyboardMarkup(
            [
                [
                    InlineKeyboardButton(f&amp;quot;🎸 Start the Player&amp;quot;, callback_data=&amp;quot;restart&amp;quot;),
                    InlineKeyboardButton(&amp;#x27;🗑 Close&amp;#x27;, callback_data=&amp;#x27;close&amp;#x27;),
                ],
            ]
            )
    elif data.get(&amp;#x27;dur&amp;#x27;, 0) == 0:
        reply_markup=InlineKeyboardMarkup(
            [
                [
                    InlineKeyboardButton(f&amp;quot;{get_player_string()}&amp;quot;, callback_data=&amp;quot;info_player&amp;quot;),
                ],
                [
                    InlineKeyboardButton(f&amp;quot;⏯ {get_pause(Config.PAUSE)}&amp;quot;, callback_data=f&amp;quot;{get_pause(Config.PAUSE)}&amp;quot;),
                    InlineKeyboardButton(&amp;#x27;🔊 Volume Control&amp;#x27;, callback_data=&amp;#x27;volume_main&amp;#x27;),
                    InlineKeyboardButton(&amp;#x27;🗑 Close&amp;#x27;, callback_data=&amp;#x27;close&amp;#x27;),
                ],
            ]
            )
    else:
        reply_markup=InlineKeyboardMarkup(
            [
                [
                    InlineKeyboardButton(f&amp;quot;{get_player_string()}&amp;quot;, callback_data=&amp;#x27;info_player&amp;#x27;),
                ],
                [
                    InlineKeyboardButton(&amp;quot;⏮ Rewind&amp;quot;, callback_data=&amp;#x27;rewind&amp;#x27;),
                    InlineKeyboardButton(f&amp;quot;⏯ {get_pause(Config.PAUSE)}&amp;quot;, callback_data=f&amp;quot;{get_pause(Config.PAUSE)}&amp;quot;),
                    InlineKeyboardButton(f&amp;quot;⏭ Seek&amp;quot;, callback_data=&amp;#x27;seek&amp;#x27;),
                ],
                [
                    InlineKeyboardButton(&amp;quot;🔄 Shuffle&amp;quot;, callback_data=&amp;quot;shuffle&amp;quot;),
                    InlineKeyboardButton(&amp;quot;⏩ Skip&amp;quot;, callback_data=&amp;quot;skip&amp;quot;),
                    InlineKeyboardButton(&amp;quot;⏮ Replay&amp;quot;, callback_data=&amp;quot;replay&amp;quot;),
                ],
                [
                    InlineKeyboardButton(&amp;#x27;🔊 Volume Control&amp;#x27;, callback_data=&amp;#x27;volume_main&amp;#x27;),
                    InlineKeyboardButton(&amp;#x27;🗑 Close&amp;#x27;, callback_data=&amp;#x27;close&amp;#x27;),
                ]
            ]
            )
    return reply_markup&lt;/pre&gt;
  &lt;pre id=&quot;eZSU&quot;&gt;
async def settings_panel():
    reply_markup=InlineKeyboardMarkup(
        [
            [
               InlineKeyboardButton(f&amp;quot;Player Mode&amp;quot;, callback_data=&amp;#x27;info_mode&amp;#x27;),
               InlineKeyboardButton(f&amp;quot;{&amp;#x27;🔂 Non Stop Playback&amp;#x27; if Config.IS_LOOP else &amp;#x27;▶️ Play and Leave&amp;#x27;}&amp;quot;, callback_data=&amp;#x27;is_loop&amp;#x27;),
            ],
            [
                InlineKeyboardButton(&amp;quot;🎞 Video&amp;quot;, callback_data=f&amp;quot;info_video&amp;quot;),
                InlineKeyboardButton(f&amp;quot;{&amp;#x27;📺 Enabled&amp;#x27; if Config.IS_VIDEO else &amp;#x27;🎙 Disabled&amp;#x27;}&amp;quot;, callback_data=&amp;#x27;is_video&amp;#x27;),
            ],
            [
                InlineKeyboardButton(&amp;quot;🤴 Admin Only&amp;quot;, callback_data=f&amp;quot;info_admin&amp;quot;),
                InlineKeyboardButton(f&amp;quot;{&amp;#x27;🔒 Enabled&amp;#x27; if Config.ADMIN_ONLY else &amp;#x27;🔓 Disabled&amp;#x27;}&amp;quot;, callback_data=&amp;#x27;admin_only&amp;#x27;),
            ],
            [
                InlineKeyboardButton(&amp;quot;🪶 Edit Title&amp;quot;, callback_data=f&amp;quot;info_title&amp;quot;),
                InlineKeyboardButton(f&amp;quot;{&amp;#x27;✏️ Enabled&amp;#x27; if Config.EDIT_TITLE else &amp;#x27;🚫 Disabled&amp;#x27;}&amp;quot;, callback_data=&amp;#x27;edit_title&amp;#x27;),
            ],
            [
                InlineKeyboardButton(&amp;quot;🔀 Shuffle Mode&amp;quot;, callback_data=f&amp;quot;info_shuffle&amp;quot;),
                InlineKeyboardButton(f&amp;quot;{&amp;#x27;✅ Enabled&amp;#x27; if Config.SHUFFLE else &amp;#x27;🚫 Disabled&amp;#x27;}&amp;quot;, callback_data=&amp;#x27;set_shuffle&amp;#x27;),
            ],
            [
                InlineKeyboardButton(&amp;quot;👮 Auto Reply (PM Permit)&amp;quot;, callback_data=f&amp;quot;info_reply&amp;quot;),
                InlineKeyboardButton(f&amp;quot;{&amp;#x27;✅ Enabled&amp;#x27; if Config.REPLY_PM else &amp;#x27;🚫 Disabled&amp;#x27;}&amp;quot;, callback_data=&amp;#x27;reply_msg&amp;#x27;),
            ],
            [
                InlineKeyboardButton(&amp;#x27;🗑 Close&amp;#x27;, callback_data=&amp;#x27;close&amp;#x27;),
            ]
            
        ]
        )
    await sync_to_db()
    return reply_markup&lt;/pre&gt;
  &lt;pre id=&quot;F5G2&quot;&gt;
async def recorder_settings():
    reply_markup=InlineKeyboardMarkup(
        [
        [
            InlineKeyboardButton(f&amp;quot;{&amp;#x27;⏹ Stop Recording&amp;#x27; if Config.IS_RECORDING else &amp;#x27;⏺ Start Recording&amp;#x27;}&amp;quot;, callback_data=&amp;#x27;record&amp;#x27;),
        ],
        [
            InlineKeyboardButton(f&amp;quot;Record Video&amp;quot;, callback_data=&amp;#x27;info_videorecord&amp;#x27;),
            InlineKeyboardButton(f&amp;quot;{&amp;#x27;Enabled&amp;#x27; if Config.IS_VIDEO_RECORD else &amp;#x27;Disabled&amp;#x27;}&amp;quot;, callback_data=&amp;#x27;record_video&amp;#x27;),
        ],
        [
            InlineKeyboardButton(f&amp;quot;Video Dimension&amp;quot;, callback_data=&amp;#x27;info_videodimension&amp;#x27;),
            InlineKeyboardButton(f&amp;quot;{&amp;#x27;Portrait&amp;#x27; if Config.PORTRAIT else &amp;#x27;Landscape&amp;#x27;}&amp;quot;, callback_data=&amp;#x27;record_dim&amp;#x27;),
        ],
        [
            InlineKeyboardButton(f&amp;quot;Custom Recording Title&amp;quot;, callback_data=&amp;#x27;info_rectitle&amp;#x27;),
            InlineKeyboardButton(f&amp;quot;{Config.RECORDING_TITLE if Config.RECORDING_TITLE else &amp;#x27;Default&amp;#x27;}&amp;quot;, callback_data=&amp;#x27;info_rectitle&amp;#x27;),
        ],
        [
            InlineKeyboardButton(f&amp;quot;Recording Dump Channel&amp;quot;, callback_data=&amp;#x27;info_recdumb&amp;#x27;),
            InlineKeyboardButton(f&amp;quot;{Config.RECORDING_DUMP if Config.RECORDING_DUMP else &amp;#x27;Not Dumping&amp;#x27;}&amp;quot;, callback_data=&amp;#x27;info_recdumb&amp;#x27;),
        ],
        [
            InlineKeyboardButton(&amp;#x27;🗑 Close&amp;#x27;, callback_data=&amp;#x27;close&amp;#x27;),
        ]
        ]
    )
    await sync_to_db()
    return reply_markup&lt;/pre&gt;
  &lt;pre id=&quot;VHVF&quot;&gt;async def volume_buttons():
    reply_markup=InlineKeyboardMarkup(
        [
        [
            InlineKeyboardButton(f&amp;quot;{get_volume_string()}&amp;quot;, callback_data=&amp;#x27;info_volume&amp;#x27;),
        ],
        [
            InlineKeyboardButton(f&amp;quot;{&amp;#x27;🔊&amp;#x27; if Config.MUTED else &amp;#x27;🔇&amp;#x27;}&amp;quot;, callback_data=&amp;#x27;mute&amp;#x27;),
            InlineKeyboardButton(f&amp;quot;- 10&amp;quot;, callback_data=&amp;#x27;volume_less&amp;#x27;),
            InlineKeyboardButton(f&amp;quot;+ 10&amp;quot;, callback_data=&amp;#x27;volume_add&amp;#x27;),
        ],
        [
            InlineKeyboardButton(f&amp;quot;🔙 Back&amp;quot;, callback_data=&amp;#x27;volume_back&amp;#x27;),
            InlineKeyboardButton(&amp;#x27;🗑 Close&amp;#x27;, callback_data=&amp;#x27;close&amp;#x27;),
        ]
        ]
    )
    return reply_markup&lt;/pre&gt;
  &lt;pre id=&quot;An24&quot;&gt;
async def delete_messages(messages):
    await asyncio.sleep(Config.DELAY)
    for msg in messages:
        try:
            if msg.chat.type == &amp;quot;supergroup&amp;quot;:
                await msg.delete()
        except:
            pass&lt;/pre&gt;
  &lt;pre id=&quot;rOEW&quot;&gt;#Database Config
async def sync_to_db():
    if Config.DATABASE_URI:
        await check_db()
        for var in Config.CONFIG_LIST:
            await db.edit_config(var, getattr(Config, var))&lt;/pre&gt;
  &lt;pre id=&quot;iRjS&quot;&gt;async def sync_from_db():
    if Config.DATABASE_URI:  
        await check_db() 
        for var in Config.CONFIG_LIST:
            setattr(Config, var, await db.get_config(var))
        Config.playlist = await db.get_playlist()
        if Config.playlist and Config.SHUFFLE:
            await shuffle_playlist()&lt;/pre&gt;
  &lt;pre id=&quot;7RSp&quot;&gt;async def add_to_db_playlist(song):
    if Config.DATABASE_URI:
        song_={str(k):v for k,v in song.items()}
        db.add_to_playlist(song[5], song_)&lt;/pre&gt;
  &lt;pre id=&quot;OQxW&quot;&gt;async def clear_db_playlist(song=None, all=False):
    if Config.DATABASE_URI:
        if all:
            await db.clear_playlist()
        else:
            await db.del_song(song[5])&lt;/pre&gt;
  &lt;pre id=&quot;SpLA&quot;&gt;async def check_db():
    for var in Config.CONFIG_LIST:
        if not await db.is_saved(var):
            db.add_config(var, getattr(Config, var))&lt;/pre&gt;
  &lt;pre id=&quot;sgPI&quot;&gt;async def check_changes():
    if Config.DATABASE_URI:
        await check_db() 
        ENV_VARS = [&amp;quot;ADMINS&amp;quot;, &amp;quot;SUDO&amp;quot;, &amp;quot;CHAT&amp;quot;, &amp;quot;LOG_GROUP&amp;quot;, &amp;quot;STREAM_URL&amp;quot;, &amp;quot;SHUFFLE&amp;quot;, &amp;quot;ADMIN_ONLY&amp;quot;, &amp;quot;REPLY_MESSAGE&amp;quot;, 
    &amp;quot;EDIT_TITLE&amp;quot;, &amp;quot;RECORDING_DUMP&amp;quot;, &amp;quot;RECORDING_TITLE&amp;quot;, &amp;quot;IS_VIDEO&amp;quot;, &amp;quot;IS_LOOP&amp;quot;, &amp;quot;DELAY&amp;quot;, &amp;quot;PORTRAIT&amp;quot;, &amp;quot;IS_VIDEO_RECORD&amp;quot;, &amp;quot;CUSTOM_QUALITY&amp;quot;]
        for var in ENV_VARS:
            prev_default = await db.get_default(var)
            if prev_default is None:
                await db.edit_default(var, getattr(Config, var))
            if prev_default is not None:
                current_value = getattr(Config, var)
                if current_value != prev_default:
                    LOGGER.info(&amp;quot;ENV change detected, Changing value in database.&amp;quot;)
                    await db.edit_config(var, current_value)
                    await db.edit_default(var, current_value)         
    
    
async def is_audio(file):
    have_audio=False
    ffprobe_cmd = [&amp;quot;ffprobe&amp;quot;, &amp;quot;-i&amp;quot;, file, &amp;quot;-v&amp;quot;, &amp;quot;quiet&amp;quot;, &amp;quot;-of&amp;quot;, &amp;quot;json&amp;quot;, &amp;quot;-show_streams&amp;quot;]
    process = await asyncio.create_subprocess_exec(
            *ffprobe_cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
        )
    output = await process.communicate()
    stream = output[0].decode(&amp;#x27;utf-8&amp;#x27;)
    out = json.loads(stream)
    l = out.get(&amp;quot;streams&amp;quot;)
    if not l:
        return have_audio
    for n in l:
        k = n.get(&amp;quot;codec_type&amp;quot;)
        if k:
            if k == &amp;quot;audio&amp;quot;:
                have_audio =True
                break
    return have_audio
    &lt;/pre&gt;
  &lt;pre id=&quot;j9e7&quot;&gt;async def get_height_and_width(file):
    ffprobe_cmd = [&amp;quot;ffprobe&amp;quot;, &amp;quot;-v&amp;quot;, &amp;quot;error&amp;quot;, &amp;quot;-select_streams&amp;quot;, &amp;quot;v&amp;quot;, &amp;quot;-show_entries&amp;quot;, &amp;quot;stream=width,height&amp;quot;, &amp;quot;-of&amp;quot;, &amp;quot;json&amp;quot;, file]
    process = await asyncio.create_subprocess_exec(
        *ffprobe_cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
    )
    output, err = await process.communicate()
    stream = output.decode(&amp;#x27;utf-8&amp;#x27;)
    out = json.loads(stream)
    try:
        n = out.get(&amp;quot;streams&amp;quot;)
        if not n:
            LOGGER.error(err.decode())
            if os.path.isfile(file):#if ts a file, its a tg file
                LOGGER.info(&amp;quot;Play from DC6 Failed, Downloading the file&amp;quot;)
                total=int((((Config.playlist[0][5]).split(&amp;quot;_&amp;quot;))[1]))
                while not (os.stat(file).st_size) &amp;gt;= total:
                    LOGGER.info(f&amp;quot;Downloading {Config.playlist[0][1]} - Completed - {round(((int(os.stat(file).st_size)) / int(total))*100)} %&amp;quot; )
                    await sleep(5)
                return await get_height_and_width(file)
            width, height = False, False
        else:
            width=n[0].get(&amp;quot;width&amp;quot;)
            height=n[0].get(&amp;quot;height&amp;quot;)
    except Exception as e:
        width, height = False, False
        LOGGER.error(f&amp;quot;Unable to get video properties {e}&amp;quot;, exc_info=True)
    return width, height&lt;/pre&gt;
  &lt;pre id=&quot;vLew&quot;&gt;
async def get_duration(file):
    dur = 0
    ffprobe_cmd = [&amp;quot;ffprobe&amp;quot;, &amp;quot;-i&amp;quot;, file, &amp;quot;-v&amp;quot;, &amp;quot;error&amp;quot;, &amp;quot;-show_entries&amp;quot;, &amp;quot;format=duration&amp;quot;, &amp;quot;-of&amp;quot;, &amp;quot;json&amp;quot;, &amp;quot;-select_streams&amp;quot;, &amp;quot;v:0&amp;quot;]
    process = await asyncio.create_subprocess_exec(
        *ffprobe_cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
    )
    output = await process.communicate()
    try:
        stream = output[0].decode(&amp;#x27;utf-8&amp;#x27;)
        out = json.loads(stream)
        if out.get(&amp;quot;format&amp;quot;):
            if (out.get(&amp;quot;format&amp;quot;)).get(&amp;quot;duration&amp;quot;):
                dur = int(float((out.get(&amp;quot;format&amp;quot;)).get(&amp;quot;duration&amp;quot;)))
            else:
                dur = 0
        else:
            dur = 0
    except Exception as e:
        LOGGER.error(e, exc_info=True)
        dur  = 0
    return dur&lt;/pre&gt;
  &lt;pre id=&quot;eE4w&quot;&gt;
def get_player_string():
    now = time.time()
    data=Config.DATA.get(&amp;#x27;FILE_DATA&amp;#x27;)
    dur=int(float(data.get(&amp;#x27;dur&amp;#x27;, 0)))
    start = int(Config.DUR.get(&amp;#x27;TIME&amp;#x27;, 0))
    played = round(now-start)
    if played == 0:
        played += 1
    if dur == 0:
        dur=played
    played = round(now-start)
    percentage = played * 100 / dur
    progressbar = &amp;quot;▷ {0}◉{1}&amp;quot;.format(\
            &amp;#x27;&amp;#x27;.join([&amp;quot;━&amp;quot; for i in range(math.floor(percentage / 5))]),
            &amp;#x27;&amp;#x27;.join([&amp;quot;─&amp;quot; for i in range(20 - math.floor(percentage / 5))])
            )
    final=f&amp;quot;{convert(played)}   {progressbar}    {convert(dur)}&amp;quot;
    return final&lt;/pre&gt;
  &lt;pre id=&quot;ZAS0&quot;&gt;def get_volume_string():
    current = int(Config.VOLUME)
    if current == 0:
        current += 1
    if Config.MUTED:
        e=&amp;#x27;🔇&amp;#x27;
    elif 0 &amp;lt; current &amp;lt; 75:
        e=&amp;quot;🔈&amp;quot; 
    elif 75 &amp;lt; current &amp;lt; 150:
        e=&amp;quot;🔉&amp;quot;
    else:
        e=&amp;quot;🔊&amp;quot;
    percentage = current * 100 / 200
    progressbar = &amp;quot;🎙 {0}◉{1}&amp;quot;.format(\
            &amp;#x27;&amp;#x27;.join([&amp;quot;━&amp;quot; for i in range(math.floor(percentage / 5))]),
            &amp;#x27;&amp;#x27;.join([&amp;quot;─&amp;quot; for i in range(20 - math.floor(percentage / 5))])
            )
    final=f&amp;quot; {str(current)} / {str(200)} {progressbar}  {e}&amp;quot;
    return final&lt;/pre&gt;
  &lt;pre id=&quot;28ta&quot;&gt;def set_config(value):
    if value:
        return False
    else:
        return True&lt;/pre&gt;
  &lt;pre id=&quot;5xXQ&quot;&gt;def convert(seconds):
    seconds = seconds % (24 * 3600)
    hour = seconds // 3600
    seconds %= 3600
    minutes = seconds // 60
    seconds %= 60      
    return &amp;quot;%d:%02d:%02d&amp;quot; % (hour, minutes, seconds)&lt;/pre&gt;
  &lt;pre id=&quot;h4yK&quot;&gt;def get_pause(status):
    if status == True:
        return &amp;quot;Resume&amp;quot;
    else:
        return &amp;quot;Pause&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;uSzz&quot;&gt;#https://github.com/pytgcalls/pytgcalls/blob/dev/pytgcalls/types/input_stream/video_tools.py#L27-L38
def resize_ratio(w, h, factor):
    if w &amp;gt; h:
        rescaling = ((1280 if w &amp;gt; 1280 else w) * 100) / w
    else:
        rescaling = ((720 if h &amp;gt; 720 else h) * 100) / h
    h = round((h * rescaling) / 100)
    w = round((w * rescaling) / 100)
    divisor = gcd(w, h)
    ratio_w = w / divisor
    ratio_h = h / divisor
    factor = (divisor * factor) / 100
    width = round(ratio_w * factor)
    height = round(ratio_h * factor)
    return width - 1 if width % 2 else width, height - 1 if height % 2 else height #https://github.com/pytgcalls/pytgcalls/issues/118&lt;/pre&gt;
  &lt;pre id=&quot;h9Sf&quot;&gt;def stop_and_restart():
    os.system(&amp;quot;git pull&amp;quot;)
    time.sleep(5)
    os.execl(sys.executable, sys.executable, *sys.argv)&lt;/pre&gt;
  &lt;pre id=&quot;nmCB&quot;&gt;
def get_image(title, pic, dur=&amp;quot;Live&amp;quot;):
    newimage = &amp;quot;converted.jpg&amp;quot;
    image = Image.open(pic) 
    draw = ImageDraw.Draw(image) 
    font = ImageFont.truetype(&amp;#x27;./utils/font.ttf&amp;#x27;, 60)
    title = title[0:45]
    MAX_W = 1790
    dur=convert(int(float(dur)))
    if dur==&amp;quot;0:00:00&amp;quot;:
        dur = &amp;quot;Live Stream&amp;quot;
    para=[f&amp;#x27;Playing: {title}&amp;#x27;, f&amp;#x27;Duration: {dur}&amp;#x27;]
    current_h, pad = 450, 20
    for line in para:
        w, h = draw.textsize(line, font=font)
        draw.text(((MAX_W - w) / 2, current_h), line, font=font, fill =&amp;quot;skyblue&amp;quot;)
        current_h += h + pad
    image.save(newimage)
    return newimage&lt;/pre&gt;
  &lt;pre id=&quot;AZsZ&quot;&gt;async def edit_config(var, value):
    if var == &amp;quot;STARTUP_STREAM&amp;quot;:
        Config.STREAM_URL = value
    elif var == &amp;quot;CHAT&amp;quot;:
        Config.CHAT = int(value)
    elif var == &amp;quot;LOG_GROUP&amp;quot;:
        Config.LOG_GROUP = int(value)
    elif var == &amp;quot;DELAY&amp;quot;:
        Config.DELAY = int(value)
    elif var == &amp;quot;REPLY_MESSAGE&amp;quot;:
        Config.REPLY_MESSAGE = value
    elif var == &amp;quot;RECORDING_DUMP&amp;quot;:
        Config.RECORDING_DUMP = value
    elif var == &amp;quot;QUALITY&amp;quot;:
        Config.CUSTOM_QUALITY = value
    await sync_to_db()&lt;/pre&gt;
  &lt;pre id=&quot;J0Gn&quot;&gt;
async def update():
    await leave_call()
    if Config.HEROKU_APP:
        Config.HEROKU_APP.restart()
    else:
        Thread(
            target=stop_and_restart()
            ).start()&lt;/pre&gt;
  &lt;pre id=&quot;AoA5&quot;&gt;
async def startup_check():
    if Config.LOG_GROUP:
        try:
            k=await bot.get_chat_member(int(Config.LOG_GROUP), Config.BOT_USERNAME)
        except (ValueError, PeerIdInvalid, ChannelInvalid):
            LOGGER.error(f&amp;quot;LOG_GROUP var Found and @{Config.BOT_USERNAME} is not a member of the group.&amp;quot;)
            Config.STARTUP_ERROR=f&amp;quot;LOG_GROUP var Found and @{Config.BOT_USERNAME} is not a member of the group.&amp;quot;
            return False
    if Config.RECORDING_DUMP:
        try:
            k=await USER.get_chat_member(Config.RECORDING_DUMP, Config.USER_ID)
        except (ValueError, PeerIdInvalid, ChannelInvalid):
            LOGGER.error(f&amp;quot;RECORDING_DUMP var Found and @{Config.USER_ID} is not a member of the group./ Channel&amp;quot;)
            Config.STARTUP_ERROR=f&amp;quot;RECORDING_DUMP var Found and @{Config.USER_ID} is not a member of the group./ Channel&amp;quot;
            return False
        if not k.status in [&amp;quot;administrator&amp;quot;, &amp;quot;creator&amp;quot;]:
            LOGGER.error(f&amp;quot;RECORDING_DUMP var Found and @{Config.USER_ID} is not a admin of the group./ Channel&amp;quot;)
            Config.STARTUP_ERROR=f&amp;quot;RECORDING_DUMP var Found and @{Config.USER_ID} is not a admin of the group./ Channel&amp;quot;
            return False
    if Config.CHAT:
        try:
            k=await USER.get_chat_member(Config.CHAT, Config.USER_ID)
            if not k.status in [&amp;quot;administrator&amp;quot;, &amp;quot;creator&amp;quot;]:
                LOGGER.warning(f&amp;quot;{Config.USER_ID} is not an admin in {Config.CHAT}, it is recommended to run the user as admin.&amp;quot;)
            elif k.status in [&amp;quot;administrator&amp;quot;, &amp;quot;creator&amp;quot;] and not k.can_manage_voice_chats:
                LOGGER.warning(f&amp;quot;{Config.USER_ID} is not having right to manage voicechat, it is recommended to promote with this right.&amp;quot;)
        except (ValueError, PeerIdInvalid, ChannelInvalid):
            Config.STARTUP_ERROR=f&amp;quot;The user account by which you generated the SESSION_STRING is not found on CHAT ({Config.CHAT})&amp;quot;
            LOGGER.error(f&amp;quot;The user account by which you generated the SESSION_STRING is not found on CHAT ({Config.CHAT})&amp;quot;)
            return False
        try:
            k=await bot.get_chat_member(Config.CHAT, Config.BOT_USERNAME)
            if not k.status == &amp;quot;administrator&amp;quot;:
                LOGGER.warning(f&amp;quot;{Config.BOT_USERNAME}, is not an admin in {Config.CHAT}, it is recommended to run the bot as admin.&amp;quot;)
        except (ValueError, PeerIdInvalid, ChannelInvalid):
            Config.STARTUP_ERROR=f&amp;quot;Bot Was Not Found on CHAT, it is recommended to add {Config.BOT_USERNAME} to {Config.CHAT}&amp;quot;
            LOGGER.warning(f&amp;quot;Bot Was Not Found on CHAT, it is recommended to add {Config.BOT_USERNAME} to {Config.CHAT}&amp;quot;)
            pass
    if not Config.DATABASE_URI:
        LOGGER.warning(&amp;quot;No DATABASE_URI , found. It is recommended to use a database.&amp;quot;)
    return True&lt;/pre&gt;
  &lt;p id=&quot;Ot6z&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;wyNU&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;IXXU&quot;&gt;↪️.атрибуты gitattributes&lt;/h3&gt;
  &lt;pre id=&quot;bRld&quot; data-lang=&quot;python&quot;&gt;# Auto detect text files and perform LF normalization
* text=auto&lt;/pre&gt;
  &lt;p id=&quot;KnPQ&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;I37V&quot;&gt;↪️.гитиньоре&lt;/h3&gt;
  &lt;pre id=&quot;1EwR&quot; data-lang=&quot;python&quot;&gt;
*.pyc
*.log
*.session
*.session-journal
test.py
todo.txt&lt;/pre&gt;
  &lt;h3 id=&quot;dQDh&quot;&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;bMbO&quot;&gt;↪️Файл Dockerfile&lt;/h3&gt;
  &lt;pre id=&quot;IgWl&quot; data-lang=&quot;python&quot;&gt;FROM nikolaik/python-nodejs:python3.9-nodejs16&lt;/pre&gt;
  &lt;pre id=&quot;UnCR&quot;&gt;RUN apt update &amp;amp;&amp;amp; apt upgrade -y
RUN apt install ffmpeg -y&lt;/pre&gt;
  &lt;pre id=&quot;7JTF&quot;&gt;COPY requirements.txt /requirements.txt
RUN cd /
RUN pip3 install -U pip &amp;amp;&amp;amp; pip3 install -U -r requirements.txt
RUN mkdir /VCPlayerBot
WORKDIR /VCPlayerBot
COPY start.sh /start.sh
CMD [&amp;quot;/bin/bash&amp;quot;, &amp;quot;/start.sh&amp;quot;]&lt;/pre&gt;
  &lt;p id=&quot;RQkd&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;LBnU&quot;&gt;↪️app.json&lt;/h3&gt;
  &lt;pre id=&quot;ue0J&quot; data-lang=&quot;python&quot;&gt;{
    &amp;quot;name&amp;quot;: &amp;quot;VCPlayerBot&amp;quot;,
    &amp;quot;description&amp;quot;: &amp;quot;Telegram bot to stream video in telegram VC&amp;quot;,
    &amp;quot;repository&amp;quot;: &amp;quot;https://github.com/subinps/VCPlayerBot&amp;quot;,
    &amp;quot;stack&amp;quot;: &amp;quot;container&amp;quot;,
    &amp;quot;keywords&amp;quot;: [
      &amp;quot;telegram&amp;quot;,
      &amp;quot;bot&amp;quot;,
      &amp;quot;voicechat&amp;quot;,
      &amp;quot;music&amp;quot;,
      &amp;quot;python&amp;quot;,
      &amp;quot;pyrogram&amp;quot;,
      &amp;quot;pytgcalls&amp;quot;,
      &amp;quot;tgcalls&amp;quot;,
      &amp;quot;voip&amp;quot;
    ],
    &amp;quot;env&amp;quot;: {
      &amp;quot;API_ID&amp;quot;: {
        &amp;quot;description&amp;quot;: &amp;quot;api_id part of your Telegram API Key from my.telegram.org/apps&amp;quot;,
        &amp;quot;required&amp;quot;: true
      },
      &amp;quot;API_HASH&amp;quot;: {
        &amp;quot;description&amp;quot;: &amp;quot;api_hash part of your Telegram API Key from my.telegram.org/apps&amp;quot;,
        &amp;quot;required&amp;quot;: true
      },
      &amp;quot;BOT_TOKEN&amp;quot;: {
        &amp;quot;description&amp;quot;: &amp;quot;Bot token of Bot, get from @Botfather&amp;quot;,
        &amp;quot;required&amp;quot;: true
      },
      &amp;quot;SESSION_STRING&amp;quot;: {
        &amp;quot;description&amp;quot;: &amp;quot;Session string, read the README to learn how to export it with Pyrogram&amp;quot;,
        &amp;quot;required&amp;quot;: true
      },
      &amp;quot;CHAT&amp;quot;: {
        &amp;quot;description&amp;quot;: &amp;quot;ID of Channel or Group where the Bot plays Music&amp;quot;,
        &amp;quot;required&amp;quot;: true
      },
      &amp;quot;LOG_GROUP&amp;quot;: {
        &amp;quot;description&amp;quot;: &amp;quot;ID of the group to send playlist If CHAT is a Group, if channel then leave blank&amp;quot;,
        &amp;quot;required&amp;quot;: false
      },
      &amp;quot;QUALITY&amp;quot;: {
        &amp;quot;description&amp;quot;: &amp;quot;Default quality of your video player, Use one of high, medium or low.&amp;quot;,
        &amp;quot;value&amp;quot;: &amp;quot;high&amp;quot;,
        &amp;quot;required&amp;quot;: false
      },
      &amp;quot;DATABASE_URI&amp;quot;: {
        &amp;quot;description&amp;quot;: &amp;quot;Mongo DB database URI , get from https://cloud.mongodb.com, even if this is optional, many of functions may not work if this is not set.&amp;quot;,
        &amp;quot;required&amp;quot;: false
      },
      &amp;quot;ADMINS&amp;quot;: {
        &amp;quot;description&amp;quot;: &amp;quot;ID of Users who can use Admin commands(for multiple users seperated by space)&amp;quot;,
        &amp;quot;required&amp;quot;: true
      },
      &amp;quot;ADMIN_ONLY&amp;quot;: {
        &amp;quot;description&amp;quot;: &amp;quot;Change it to True if you want to make /play command available for everyone.&amp;quot;,
        &amp;quot;value&amp;quot;: &amp;quot;False&amp;quot;,
        &amp;quot;required&amp;quot;: false
      },
      &amp;quot;HEROKU_API_KEY&amp;quot;: {
        &amp;quot;description&amp;quot;: &amp;quot;Your heroku api key, get it from https://dashboard.heroku.com/account/applications/authorizations/new.&amp;quot;,
        &amp;quot;required&amp;quot;: false
      },
      &amp;quot;HEROKU_APP_NAME&amp;quot;: {
        &amp;quot;description&amp;quot;: &amp;quot;Heroku App Name.&amp;quot;,
        &amp;quot;required&amp;quot;: false
      },
      &amp;quot;STARTUP_STREAM&amp;quot;: {
        &amp;quot;description&amp;quot;: &amp;quot;YouTube live / Direct link to a video / Telegram link to a YouTube playlist.(Read the README for more info.) &amp;quot;,
        &amp;quot;value&amp;quot;: &amp;quot;https://youtu.be/zcrUCvBD16k&amp;quot;,
        &amp;quot;required&amp;quot;: false
      },
      &amp;quot;REPLY_MESSAGE&amp;quot;: {
        &amp;quot;description&amp;quot;: &amp;quot;A reply message to those who message the USER account in PM. Make it blank if you do not need this feature.&amp;quot;,
        &amp;quot;value&amp;quot;: &amp;quot;Hey, Iam a bot to play music, not having time to chat with you.&amp;quot;,
        &amp;quot;required&amp;quot;: false
      }
    },
    &amp;quot;formation&amp;quot;: {
      &amp;quot;worker&amp;quot;: {
        &amp;quot;quantity&amp;quot;: 1,
        &amp;quot;size&amp;quot;: &amp;quot;free&amp;quot;
      }
    }
  }&lt;/pre&gt;
  &lt;p id=&quot;Ddn7&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;NpvB&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;lfKp&quot;&gt;↪️bot.py&lt;/h3&gt;
  &lt;pre id=&quot;PV9f&quot; data-lang=&quot;python&quot;&gt;#!/usr/bin/env python3
# Copyright (C) @subinps
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.&lt;/pre&gt;
  &lt;pre id=&quot;GfbR&quot; data-lang=&quot;python&quot;&gt;# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.&lt;/pre&gt;
  &lt;pre id=&quot;FGhK&quot; data-lang=&quot;python&quot;&gt;# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see &amp;lt;https://www.gnu.org/licenses/&amp;gt;.&lt;/pre&gt;
  &lt;pre id=&quot;dqVt&quot; data-lang=&quot;python&quot;&gt;from pyrogram import Client
from config import Config
bot = Client(
    &amp;quot;VCPlayer&amp;quot;,
    Config.API_ID,
    Config.API_HASH,
    bot_token=Config.BOT_TOKEN,
    plugins=dict(root=&amp;quot;plugins&amp;quot;)
)&lt;/pre&gt;
  &lt;p id=&quot;YPld&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;xJPm&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;On7W&quot;&gt;↪️config.py&lt;/h3&gt;
  &lt;pre id=&quot;1UdM&quot;&gt;#!/usr/bin/env python3
# Copyright (C) @subinps
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.&lt;/pre&gt;
  &lt;pre id=&quot;EYah&quot;&gt;# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.&lt;/pre&gt;
  &lt;pre id=&quot;oWKz&quot;&gt;# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see &amp;lt;https://www.gnu.org/licenses/&amp;gt;.
from utils import LOGGER
try:
   import os
   import heroku3
   from dotenv import load_dotenv
   from ast import literal_eval as is_enabled&lt;/pre&gt;
  &lt;pre id=&quot;RitQ&quot;&gt;except ModuleNotFoundError:
    import os
    import sys
    import subprocess
    file=os.path.abspath(&amp;quot;requirements.txt&amp;quot;)
    subprocess.check_call([sys.executable, &amp;#x27;-m&amp;#x27;, &amp;#x27;pip&amp;#x27;, &amp;#x27;install&amp;#x27;, &amp;#x27;-r&amp;#x27;, file, &amp;#x27;--upgrade&amp;#x27;])
    os.execl(sys.executable, sys.executable, *sys.argv)&lt;/pre&gt;
  &lt;pre id=&quot;KBeY&quot;&gt;
class Config:
    #Telegram API Stuffs
    load_dotenv()  # load enviroment variables from .env file
    ADMIN = os.environ.get(&amp;quot;ADMINS&amp;quot;, &amp;#x27;&amp;#x27;)
    SUDO = [int(admin) for admin in (ADMIN).split()] # Exclusive for heroku vars configuration.
    ADMINS = [int(admin) for admin in (ADMIN).split()] #group admins will be appended to this list.
    API_ID = int(os.environ.get(&amp;quot;API_ID&amp;quot;, &amp;#x27;&amp;#x27;))
    API_HASH = os.environ.get(&amp;quot;API_HASH&amp;quot;, &amp;quot;&amp;quot;)
    BOT_TOKEN = os.environ.get(&amp;quot;BOT_TOKEN&amp;quot;, &amp;quot;&amp;quot;)     
    SESSION = os.environ.get(&amp;quot;SESSION_STRING&amp;quot;, &amp;quot;&amp;quot;)&lt;/pre&gt;
  &lt;pre id=&quot;H327&quot;&gt;    #Stream Chat and Log Group
    CHAT = int(os.environ.get(&amp;quot;CHAT&amp;quot;, &amp;quot;&amp;quot;))
    LOG_GROUP=os.environ.get(&amp;quot;LOG_GROUP&amp;quot;, &amp;quot;&amp;quot;)&lt;/pre&gt;
  &lt;pre id=&quot;8NUZ&quot;&gt;    #Stream 
    STREAM_URL=os.environ.get(&amp;quot;STARTUP_STREAM&amp;quot;, &amp;quot;https://www.youtube.com/watch?v=zcrUCvBD16k&amp;quot;)
   
    #Database
    DATABASE_URI=os.environ.get(&amp;quot;DATABASE_URI&amp;quot;, None)
    DATABASE_NAME=os.environ.get(&amp;quot;DATABASE_NAME&amp;quot;, &amp;quot;VCPlayerBot&amp;quot;)&lt;/pre&gt;
  &lt;pre id=&quot;PWls&quot;&gt;
    #heroku
    API_KEY=os.environ.get(&amp;quot;HEROKU_API_KEY&amp;quot;, None)
    APP_NAME=os.environ.get(&amp;quot;HEROKU_APP_NAME&amp;quot;, None)&lt;/pre&gt;
  &lt;pre id=&quot;hso5&quot;&gt;
    #Optional Configuration
    SHUFFLE=is_enabled(os.environ.get(&amp;quot;SHUFFLE&amp;quot;, &amp;#x27;True&amp;#x27;))
    ADMIN_ONLY=is_enabled(os.environ.get(&amp;quot;ADMIN_ONLY&amp;quot;, &amp;quot;False&amp;quot;))
    REPLY_MESSAGE=os.environ.get(&amp;quot;REPLY_MESSAGE&amp;quot;, False)
    EDIT_TITLE = os.environ.get(&amp;quot;EDIT_TITLE&amp;quot;, True)
    #others
    
    RECORDING_DUMP=os.environ.get(&amp;quot;RECORDING_DUMP&amp;quot;, False)
    RECORDING_TITLE=os.environ.get(&amp;quot;RECORDING_TITLE&amp;quot;, False)
    TIME_ZONE = os.environ.get(&amp;quot;TIME_ZONE&amp;quot;, &amp;quot;Asia/Kolkata&amp;quot;)    
    IS_VIDEO=is_enabled(os.environ.get(&amp;quot;IS_VIDEO&amp;quot;, &amp;#x27;True&amp;#x27;))
    IS_LOOP=is_enabled(os.environ.get(&amp;quot;IS_LOOP&amp;quot;, &amp;#x27;True&amp;#x27;))
    DELAY=int(os.environ.get(&amp;quot;DELAY&amp;quot;, &amp;#x27;10&amp;#x27;))
    PORTRAIT=is_enabled(os.environ.get(&amp;quot;PORTRAIT&amp;quot;, &amp;#x27;False&amp;#x27;))
    IS_VIDEO_RECORD=is_enabled(os.environ.get(&amp;quot;IS_VIDEO_RECORD&amp;quot;, &amp;#x27;True&amp;#x27;))
    DEBUG=is_enabled(os.environ.get(&amp;quot;DEBUG&amp;quot;, &amp;#x27;False&amp;#x27;))
    PTN=is_enabled(os.environ.get(&amp;quot;PTN&amp;quot;, &amp;quot;False&amp;quot;))&lt;/pre&gt;
  &lt;pre id=&quot;KKgi&quot;&gt;    #Quality vars
    E_BITRATE=os.environ.get(&amp;quot;BITRATE&amp;quot;, False)
    E_FPS=os.environ.get(&amp;quot;FPS&amp;quot;, False)
    CUSTOM_QUALITY=os.environ.get(&amp;quot;QUALITY&amp;quot;, &amp;quot;100&amp;quot;)&lt;/pre&gt;
  &lt;pre id=&quot;vrFD&quot;&gt;    #Search filters for cplay
    FILTERS =  [filter.lower() for filter in (os.environ.get(&amp;quot;FILTERS&amp;quot;, &amp;quot;video document&amp;quot;)).split(&amp;quot; &amp;quot;)]&lt;/pre&gt;
  &lt;pre id=&quot;Fp3x&quot;&gt;
    #Dont touch these, these are not for configuring player
    GET_FILE={}
    DATA={}
    STREAM_END={}
    SCHEDULED_STREAM={}
    DUR={}
    msg = {}&lt;/pre&gt;
  &lt;pre id=&quot;cdzF&quot;&gt;    SCHEDULE_LIST=[]
    playlist=[]
    CONFIG_LIST = [&amp;quot;ADMINS&amp;quot;, &amp;quot;IS_VIDEO&amp;quot;, &amp;quot;IS_LOOP&amp;quot;, &amp;quot;REPLY_PM&amp;quot;, &amp;quot;ADMIN_ONLY&amp;quot;, &amp;quot;SHUFFLE&amp;quot;, &amp;quot;EDIT_TITLE&amp;quot;, &amp;quot;CHAT&amp;quot;, 
    &amp;quot;SUDO&amp;quot;, &amp;quot;REPLY_MESSAGE&amp;quot;, &amp;quot;STREAM_URL&amp;quot;, &amp;quot;DELAY&amp;quot;, &amp;quot;LOG_GROUP&amp;quot;, &amp;quot;SCHEDULED_STREAM&amp;quot;, &amp;quot;SCHEDULE_LIST&amp;quot;, 
    &amp;quot;IS_VIDEO_RECORD&amp;quot;, &amp;quot;IS_RECORDING&amp;quot;, &amp;quot;WAS_RECORDING&amp;quot;, &amp;quot;RECORDING_TITLE&amp;quot;, &amp;quot;PORTRAIT&amp;quot;, &amp;quot;RECORDING_DUMP&amp;quot;, &amp;quot;HAS_SCHEDULE&amp;quot;, 
    &amp;quot;CUSTOM_QUALITY&amp;quot;]&lt;/pre&gt;
  &lt;pre id=&quot;Enpe&quot;&gt;    STARTUP_ERROR=None&lt;/pre&gt;
  &lt;pre id=&quot;kq6Z&quot;&gt;    ADMIN_CACHE=False
    CALL_STATUS=False
    YPLAY=False
    YSTREAM=False
    CPLAY=False
    STREAM_SETUP=False
    LISTEN=False
    STREAM_LINK=False
    IS_RECORDING=False
    WAS_RECORDING=False
    PAUSE=False
    MUTED=False
    HAS_SCHEDULE=None
    IS_ACTIVE=None
    VOLUME=100
    CURRENT_CALL=None
    BOT_USERNAME=None
    USER_ID=None&lt;/pre&gt;
  &lt;pre id=&quot;r7vG&quot;&gt;    if LOG_GROUP:
        LOG_GROUP=int(LOG_GROUP)
    else:
        LOG_GROUP=None
    if not API_KEY or \
       not APP_NAME:
       HEROKU_APP=None
    else:
       HEROKU_APP=heroku3.from_key(API_KEY).apps()[APP_NAME]&lt;/pre&gt;
  &lt;pre id=&quot;ATIh&quot;&gt;
    if EDIT_TITLE in [&amp;quot;NO&amp;quot;, &amp;#x27;False&amp;#x27;]:
        EDIT_TITLE=False
        LOGGER.info(&amp;quot;Title Editing turned off&amp;quot;)
    if REPLY_MESSAGE:
        REPLY_MESSAGE=REPLY_MESSAGE
        REPLY_PM=True
        LOGGER.info(&amp;quot;Reply Message Found, Enabled PM MSG&amp;quot;)
    else:
        REPLY_MESSAGE=False
        REPLY_PM=False&lt;/pre&gt;
  &lt;pre id=&quot;ZC6M&quot;&gt;    if E_BITRATE:
       try:
          BITRATE=int(E_BITRATE)
       except:
          LOGGER.error(&amp;quot;Invalid bitrate specified.&amp;quot;)
          E_BITRATE=False
          BITRATE=48000
       if not BITRATE &amp;gt;= 48000:
          BITRATE=48000
    else:
       BITRATE=48000
    
    if E_FPS:
       try:
          FPS=int(E_FPS)
       except:
          LOGGER.error(&amp;quot;Invalid FPS specified&amp;quot;)
          E_FPS=False
       if not FPS &amp;gt;= 30:
          FPS=30
    else:
       FPS=30
    try:
       CUSTOM_QUALITY=int(CUSTOM_QUALITY)
       if CUSTOM_QUALITY &amp;gt; 100:
          CUSTOM_QUALITY = 100
          LOGGER.warning(&amp;quot;maximum quality allowed is 100, invalid quality specified. Quality set to 100&amp;quot;)
       elif CUSTOM_QUALITY &amp;lt; 10:
          LOGGER.warning(&amp;quot;Minimum Quality allowed is 10., Qulaity set to 10&amp;quot;)
          CUSTOM_QUALITY = 10
       if  66.9  &amp;lt; CUSTOM_QUALITY &amp;lt; 100:
          if not E_BITRATE:
             BITRATE=48000
       elif 50 &amp;lt; CUSTOM_QUALITY &amp;lt; 66.9:
          if not E_BITRATE:
             BITRATE=36000
       else:
          if not E_BITRATE:
             BITRATE=24000
    except:
       if CUSTOM_QUALITY.lower() == &amp;#x27;high&amp;#x27;:
          CUSTOM_QUALITY=100
       elif CUSTOM_QUALITY.lower() == &amp;#x27;medium&amp;#x27;:
          CUSTOM_QUALITY=66.9
       elif CUSTOM_QUALITY.lower() == &amp;#x27;low&amp;#x27;:
          CUSTOM_QUALITY=50
       else:
          LOGGER.warning(&amp;quot;Invalid QUALITY specified.Defaulting to High.&amp;quot;)
          CUSTOM_QUALITY=100&lt;/pre&gt;
  &lt;pre id=&quot;igpY&quot;&gt;&lt;/pre&gt;
  &lt;pre id=&quot;V4Qp&quot;&gt;    #help strings 
    PLAY_HELP=&amp;quot;&amp;quot;&amp;quot;
__You can play using any of these options__&lt;/pre&gt;
  &lt;pre id=&quot;DAjR&quot;&gt;1. Play a video from a YouTube link.
Command: **/play**
__You can use this as a reply to a YouTube link or pass link along command. or as a reply to message to search that in YouTube.__&lt;/pre&gt;
  &lt;pre id=&quot;jfr1&quot;&gt;2. Play from a telegram file.
Command: **/play**
__Reply to a supported media(video and documents or audio file ).__
Note: __For both the cases /fplay also can be used by admins to play the song immediately without waiting for queue to end.__&lt;/pre&gt;
  &lt;pre id=&quot;BWU6&quot;&gt;3. Play from a YouTube playlist
Command: **/yplay**
__First get a playlist file from @GetPlaylistBot or @DumpPlaylist and reply to playlist file.__&lt;/pre&gt;
  &lt;pre id=&quot;yG95&quot;&gt;4. Live Stream
Command: **/stream**
__Pass a live stream URL or any direct URL to play it as stream.__&lt;/pre&gt;
  &lt;pre id=&quot;fscV&quot;&gt;5. Import an old playlist.
Command: **/import**
__Reply to a previously exported playlist file. __&lt;/pre&gt;
  &lt;pre id=&quot;w8ao&quot;&gt;6. Channel Play
Command: **/cplay**
__Use &amp;#x60;/cplay channel username or channel id&amp;#x60; to play all the files from the given channel.
By default both video files and documents will be played . You can add or remove the file type using &amp;#x60;FILTERS&amp;#x60; var. 
For example , to stream audio, video and document from the channel use &amp;#x60;/env FILTERS video document audio&amp;#x60; . If you need only audio , you can use &amp;#x60;/env FILTERS video audio&amp;#x60; and so on.
To set up the files from a channel as STARTUP_STREAM, so that the files will be automatically added to playlist on startup of bot. use &amp;#x60;/env STARTUP_STREAM channel username or channel id&amp;#x60;&lt;/pre&gt;
  &lt;pre id=&quot;421Y&quot;&gt;Note that for public channels you should use username of channels along with &amp;#x27;@&amp;#x27; and for private channels you should use channel id.
For private channels , make sure both the bot and USER account is a member of channel.__
&amp;quot;&amp;quot;&amp;quot;
    SETTINGS_HELP=&amp;quot;&amp;quot;&amp;quot;
**You can easily customize you player as per you needs. The following configurations are available:**&lt;/pre&gt;
  &lt;pre id=&quot;ivJg&quot;&gt;🔹Command: **/settings**&lt;/pre&gt;
  &lt;pre id=&quot;WDJM&quot;&gt;🔹AVAILABLE CONFIGURATIONS:&lt;/pre&gt;
  &lt;pre id=&quot;rMx0&quot;&gt;**Player Mode** -  __This allows you to run your player as 24/7 music player or only when there is song in queue. 
If disabled, player will leave from the call when the playlist is empty.
Otherwise STARTUP_STREAM will be streamed when playlist id empty.__&lt;/pre&gt;
  &lt;pre id=&quot;szWn&quot;&gt;**Video Enabled** -  __This allows you to switch between audio and video.
if disabled, video files will be played as audio.__&lt;/pre&gt;
  &lt;pre id=&quot;Vlji&quot;&gt;**Admin Only** - __Enabling this will restrict non-admin users from using play command.__&lt;/pre&gt;
  &lt;pre id=&quot;fxSb&quot;&gt;**Edit Title** - __Enabling this will edit your VideoChat title to current playing songs name.__&lt;/pre&gt;
  &lt;pre id=&quot;YQD2&quot;&gt;**Shuffle Mode** - __Enabling this will shuffle the playlist whenever you import a playlist or using /yplay __&lt;/pre&gt;
  &lt;pre id=&quot;BQla&quot;&gt;**Auto Reply** - __Choose whether to reply the PM messages of playing user account.
You can  set up a custom reply message using &amp;#x60;REPLY_MESSAGE&amp;#x60; confug.__&lt;/pre&gt;
  &lt;pre id=&quot;aGZ0&quot;&gt;&amp;quot;&amp;quot;&amp;quot;
    SCHEDULER_HELP=&amp;quot;&amp;quot;&amp;quot;
__VCPlayer allows you to schedule a stream. 
This means you can schedule a stream for a future date and on the scheduled date, stream will be played automatically.
At present you can schedule a stream for even one year!!. Make sure you have set up a databse, else you will loose your schedules whenever the player restarts. __&lt;/pre&gt;
  &lt;pre id=&quot;FTLS&quot;&gt;Command: **/schedule**&lt;/pre&gt;
  &lt;pre id=&quot;lyHs&quot;&gt;__Reply to a file or a youtube video or even a text message with schedule command.
The replied media or youtube video will be scheduled and will be played on the scheduled date.
The scheduling time is by default in IST and you can change the timezone using &amp;#x60;TIME_ZONE&amp;#x60; config.__&lt;/pre&gt;
  &lt;pre id=&quot;rP7N&quot;&gt;Command: **/slist**
__View your current scheduled streams.__&lt;/pre&gt;
  &lt;pre id=&quot;WmOD&quot;&gt;Command: **/cancel**
__Cancel a schedule by its schedule id, You can get the schedule id using /slist command__&lt;/pre&gt;
  &lt;pre id=&quot;KMD2&quot;&gt;Command: **/cancelall**
__Cancel all the scheduled streams__
&amp;quot;&amp;quot;&amp;quot;
    RECORDER_HELP=&amp;quot;&amp;quot;&amp;quot;
__With VCPlayer you can easily record all your video chats.
By default telegram allows you to record for a maximum duration of 4 hours. 
An attempt to overcome this limit has been made by automatically restarting the recording after  4 hours__&lt;/pre&gt;
  &lt;pre id=&quot;8arL&quot;&gt;Command: **/record**&lt;/pre&gt;
  &lt;pre id=&quot;mh92&quot;&gt;AVAILABLE CONFIGURATIONS:
1. Record Video: __If enabled both the video and audio of the stream will be recorded, otherwise only audio will be recorded.__&lt;/pre&gt;
  &lt;pre id=&quot;x7Wi&quot;&gt;2. Video dimension: __Choose between portrait and landscape dimensions for your recording__&lt;/pre&gt;
  &lt;pre id=&quot;olPG&quot;&gt;3. Custom Recording Title: __Set up a custom recording title for your recordings. Use a command /rtitle to configure this.
To turn off the custom title, use &amp;#x60;/rtitle False &amp;#x60;__&lt;/pre&gt;
  &lt;pre id=&quot;XNQI&quot;&gt;4. Recording Dumb: __You can set up forwarding all your recordings to a channel, this will be useful since otherwise recordings are sent to saved messages of streaming account.
Setup using &amp;#x60;RECORDING_DUMP&amp;#x60; config.__&lt;/pre&gt;
  &lt;pre id=&quot;vxUu&quot;&gt;⚠️ If you start a recording with vcplayer, make sure you stop the same with vcplayer.&lt;/pre&gt;
  &lt;pre id=&quot;D7bo&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;1d0H&quot;&gt;    CONTROL_HELP=&amp;quot;&amp;quot;&amp;quot;
__VCPlayer allows you to control your streams easily__
1. Skip a song.
Command: **/skip**
__You can pass a number greater than 2 to skip the song in that position.__&lt;/pre&gt;
  &lt;pre id=&quot;dqMB&quot;&gt;2. Pause the player.
Command: **/pause**&lt;/pre&gt;
  &lt;pre id=&quot;B22e&quot;&gt;3. Resume the player.
Command: **/resume**&lt;/pre&gt;
  &lt;pre id=&quot;Q6ku&quot;&gt;4. Change Volume.
Command: **/volume**
__Pass the volume in between 1-200.__&lt;/pre&gt;
  &lt;pre id=&quot;ygTA&quot;&gt;5. Leave the VC.
Command: **/leave**&lt;/pre&gt;
  &lt;pre id=&quot;AlnF&quot;&gt;6. Shuffle the playlist.
Command: **/shuffle**&lt;/pre&gt;
  &lt;pre id=&quot;CVak&quot;&gt;7. Clear the current playlist queue.
Command: **/clearplaylist**&lt;/pre&gt;
  &lt;pre id=&quot;6nL2&quot;&gt;8. Seek the video.
Command: **/seek**
__You can pass number of seconds to be skipped. Example: /seek 10 to skip 10 sec. /seek -10 to rewind 10 sec.__&lt;/pre&gt;
  &lt;pre id=&quot;2Sd6&quot;&gt;9. Mute the player.
Command: **/vcmute**&lt;/pre&gt;
  &lt;pre id=&quot;jMDu&quot;&gt;10. Unmute the player.
Command : **/vcunmute**&lt;/pre&gt;
  &lt;pre id=&quot;gSul&quot;&gt;11. Shows the playlist.
Command: **/playlist** 
__Use /player to show with control buttons__
&amp;quot;&amp;quot;&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;sTtJ&quot;&gt;    ADMIN_HELP=&amp;quot;&amp;quot;&amp;quot;
__VCPlayer allows to control admins, that is you can add admins and remove them easily.
It is recommended to use a MongoDb database for better experience, else all you admins will get reset after restart.__&lt;/pre&gt;
  &lt;pre id=&quot;vkyu&quot;&gt;Command: **/vcpromote**
__You can promote a admin with their username or user id or by replying to that users message.__&lt;/pre&gt;
  &lt;pre id=&quot;wqUf&quot;&gt;Command: **/vcdemote**
__Remove an admin from admin list__&lt;/pre&gt;
  &lt;pre id=&quot;mrWj&quot;&gt;Command: **/refresh**
__Refresh the admin list of chat__
&amp;quot;&amp;quot;&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;zPg7&quot;&gt;    MISC_HELP=&amp;quot;&amp;quot;&amp;quot;
Command: **/export**
__VCPlayer allows you to export your current playlist for future use.__
__A json file will be sent to you and the same can be used along /import command.__&lt;/pre&gt;
  &lt;pre id=&quot;tnzO&quot;&gt;Command : **/logs**
__If your player went something gone wrong, you can easily check the logs using /logs__
 
Command : **/env**
__Setup your config vars with /env command.__
__Example: To set up a__ &amp;#x60;REPLY_MESSAGE&amp;#x60; __use__ &amp;#x60;/env REPLY_MESSAGE=Hey, Check out @subin_works rather than spamming in my PM&amp;#x60;__
__You can delete a config var by ommiting a value for that, Example:__ &amp;#x60;/env LOG_GROUP=&amp;#x60; __this will delete the existing LOG_GROUP config.&lt;/pre&gt;
  &lt;pre id=&quot;7T0o&quot;&gt;Command: **/config**
__Same as using /env**&lt;/pre&gt;
  &lt;pre id=&quot;GwqH&quot;&gt;Command: **/update**
__Updates youe bot with latest changes__&lt;/pre&gt;
  &lt;pre id=&quot;7FU6&quot;&gt;Tip: __You can easily change the CHAT config by adding the user account and bot account to any other group and any command in new group__&lt;/pre&gt;
  &lt;pre id=&quot;Rujs&quot;&gt;&amp;quot;&amp;quot;&amp;quot;
    ENV_HELP=&amp;quot;&amp;quot;&amp;quot;
**These are the configurable vars available and you can set each one of them using /env command**&lt;/pre&gt;
  &lt;pre id=&quot;578b&quot;&gt;
**Mandatory Vars**&lt;/pre&gt;
  &lt;pre id=&quot;Q4W5&quot;&gt;1. &amp;#x60;API_ID&amp;#x60; : __Get From [my.telegram.org](https://my.telegram.org/)__&lt;/pre&gt;
  &lt;pre id=&quot;hAGU&quot;&gt;2. &amp;#x60;API_HASH&amp;#x60; : __Get from [my.telegram.org](https://my.telegram.org)__&lt;/pre&gt;
  &lt;pre id=&quot;mVJl&quot;&gt;3. &amp;#x60;BOT_TOKEN&amp;#x60; : __[@Botfather](https://telegram.dog/BotFather)__&lt;/pre&gt;
  &lt;pre id=&quot;zPyo&quot;&gt;4. &amp;#x60;SESSION_STRING&amp;#x60; : __Generate From here [GenerateStringName](https://repl.it/@subinps/getStringName)__&lt;/pre&gt;
  &lt;pre id=&quot;ZpT8&quot;&gt;5. &amp;#x60;CHAT&amp;#x60; : __ID of Channel/Group where the bot plays Music.__&lt;/pre&gt;
  &lt;pre id=&quot;bWAO&quot;&gt;6. &amp;#x60;STARTUP_STREAM&amp;#x60; : __This will be streamed on startups and restarts of bot. 
You can use either any STREAM_URL or a direct link of any video or a Youtube Live link. 
You can also use YouTube Playlist.Find a Telegram Link for your playlist from [PlayList Dumb](https://telegram.dog/DumpPlaylist) or get a PlayList from [PlayList Extract](https://telegram.dog/GetAPlaylistbot). 
The PlayList link should in form &amp;#x60;https://t.me/DumpPlaylist/xxx&amp;#x60;
You can also use the files from a channel as startup stream. For that just use the channel id or channel username of channel as STARTUP_STREAM value.
For more info on channel play , read help from player section.__&lt;/pre&gt;
  &lt;pre id=&quot;bTZi&quot;&gt;**Recommended Optional Vars**&lt;/pre&gt;
  &lt;pre id=&quot;Gvnn&quot;&gt;1. &amp;#x60;DATABASE_URI&amp;#x60;: __MongoDB database Url, get from [mongodb](https://cloud.mongodb.com). This is an optional var, but it is recomonded to use this to experiance the full features.__&lt;/pre&gt;
  &lt;pre id=&quot;tFuV&quot;&gt;2. &amp;#x60;HEROKU_API_KEY&amp;#x60;: __Your heroku api key. Get one from [here](https://dashboard.heroku.com/account/applications/authorizations/new)__&lt;/pre&gt;
  &lt;pre id=&quot;gCOL&quot;&gt;3. &amp;#x60;HEROKU_APP_NAME&amp;#x60;: __Your heroku app&amp;#x27;s name.__&lt;/pre&gt;
  &lt;pre id=&quot;SpgI&quot;&gt;4. &amp;#x60;FILTERS&amp;#x60;: __Filters for channel play file search. Read help about cplay in player section.__&lt;/pre&gt;
  &lt;pre id=&quot;ZAzL&quot;&gt;**Other Optional Vars**
1. &amp;#x60;LOG_GROUP&amp;#x60; : __Group to send Playlist, if CHAT is a Group__&lt;/pre&gt;
  &lt;pre id=&quot;w9Dm&quot;&gt;2. &amp;#x60;ADMINS&amp;#x60; : __ID of users who can use admin commands.__&lt;/pre&gt;
  &lt;pre id=&quot;ayZR&quot;&gt;3. &amp;#x60;REPLY_MESSAGE&amp;#x60; : __A reply to those who message the USER account in PM. Leave it blank if you do not need this feature. (Configurable through buttons if mongodb added. Use /settings)__&lt;/pre&gt;
  &lt;pre id=&quot;pAuj&quot;&gt;4. &amp;#x60;ADMIN_ONLY&amp;#x60; : __Pass &amp;#x60;True&amp;#x60; If you want to make /play command only for admins of &amp;#x60;CHAT&amp;#x60;. By default /play is available for all.(Configurable through buttons if mongodb added. Use /settings)__&lt;/pre&gt;
  &lt;pre id=&quot;wdBI&quot;&gt;5. &amp;#x60;DATABASE_NAME&amp;#x60;: __Database name for your mongodb database.mongodb__&lt;/pre&gt;
  &lt;pre id=&quot;ghcB&quot;&gt;6. &amp;#x60;SHUFFLE&amp;#x60; : __Make it &amp;#x60;False&amp;#x60; if you dont want to shuffle playlists. (Configurable through buttons)__&lt;/pre&gt;
  &lt;pre id=&quot;rGqf&quot;&gt;7. &amp;#x60;EDIT_TITLE&amp;#x60; : __Make it &amp;#x60;False&amp;#x60; if you do not want the bot to edit video chat title according to playing song. (Configurable through buttons if mongodb added. Use /settings)__&lt;/pre&gt;
  &lt;pre id=&quot;TPPP&quot;&gt;8. &amp;#x60;RECORDING_DUMP&amp;#x60; : __A Channel ID with the USER account as admin, to dump video chat recordings.__&lt;/pre&gt;
  &lt;pre id=&quot;KmLp&quot;&gt;9. &amp;#x60;RECORDING_TITLE&amp;#x60;: __A custom title for your videochat recordings.__&lt;/pre&gt;
  &lt;pre id=&quot;Ujy6&quot;&gt;10. &amp;#x60;TIME_ZONE&amp;#x60; : __Time Zone of your country, by default IST__&lt;/pre&gt;
  &lt;pre id=&quot;lKKO&quot;&gt;11. &amp;#x60;IS_VIDEO_RECORD&amp;#x60; : __Make it &amp;#x60;False&amp;#x60; if you do not want to record video, and only audio will be recorded.(Configurable through buttons if mongodb added. Use /record)__&lt;/pre&gt;
  &lt;pre id=&quot;6UD4&quot;&gt;12. &amp;#x60;IS_LOOP&amp;#x60; ; __Make it &amp;#x60;False&amp;#x60; if you do not want 24 / 7 Video Chat. (Configurable through buttons if mongodb added.Use /settings)__&lt;/pre&gt;
  &lt;pre id=&quot;9yad&quot;&gt;13. &amp;#x60;IS_VIDEO&amp;#x60; : __Make it &amp;#x60;False&amp;#x60; if you want to use the player as a musicplayer without video. (Configurable through buttons if mongodb added. Use /settings)__&lt;/pre&gt;
  &lt;pre id=&quot;c2N4&quot;&gt;14. &amp;#x60;PORTRAIT&amp;#x60;: __Make it &amp;#x60;True&amp;#x60; if you want the video recording in portrait mode. (Configurable through buttons if mongodb added. Use /record)__&lt;/pre&gt;
  &lt;pre id=&quot;pZ2V&quot;&gt;15. &amp;#x60;DELAY&amp;#x60; : __Choose the time limit for commands deletion. 10 sec by default.__&lt;/pre&gt;
  &lt;pre id=&quot;ggCv&quot;&gt;16. &amp;#x60;QUALITY&amp;#x60; : __Customize the quality of video chat, use one of &amp;#x60;high&amp;#x60;, &amp;#x60;medium&amp;#x60;, &amp;#x60;low&amp;#x60; . __&lt;/pre&gt;
  &lt;pre id=&quot;X36E&quot;&gt;17. &amp;#x60;BITRATE&amp;#x60; : __Bitrate of audio (Not recommended to change).__&lt;/pre&gt;
  &lt;pre id=&quot;9VaQ&quot;&gt;18. &amp;#x60;FPS&amp;#x60; : __Fps of video to be played (Not recommended to change.)__&lt;/pre&gt;
  &lt;pre id=&quot;n31u&quot;&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;G6MH&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;HQiA&quot;&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;xcQ1&quot;&gt;↪️env.образец&lt;/h3&gt;
  &lt;pre id=&quot;dkP1&quot; data-lang=&quot;python&quot;&gt;API_ID=&amp;quot;your-app-id&amp;quot;
API_HASH=&amp;quot;your-api-hash&amp;quot;
BOT_TOKEN=&amp;quot;your-bot-token&amp;quot;
SESSION_STRING=&amp;quot;your-session-string&amp;quot;
CHAT=&amp;quot;your-chat-id&amp;quot;
ADMINS=&amp;quot;your-admins&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;OBS5&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;N1dd&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;w4xq&quot;&gt;↪️heroku.yml&lt;/h3&gt;
  &lt;pre id=&quot;nC6a&quot; data-lang=&quot;python&quot;&gt;build:
  docker:
      worker: Dockerfile&lt;/pre&gt;
  &lt;p id=&quot;xAEX&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;I5WD&quot;&gt;↪️install_node.sh&lt;/h3&gt;
  &lt;pre id=&quot;8vpF&quot; data-lang=&quot;python&quot;&gt;echo &amp;quot;deb https://deb.nodesource.com/node_16.x buster main&amp;quot; &amp;gt; /etc/apt/sources.list.d/nodesource.list &amp;amp;&amp;amp; \
  wget -qO- https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - &amp;amp;&amp;amp; \
  echo &amp;quot;deb https://dl.yarnpkg.com/debian/ stable main&amp;quot; &amp;gt; /etc/apt/sources.list.d/yarn.list &amp;amp;&amp;amp; \
  wget -qO- https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - &amp;amp;&amp;amp; \
  apt-get update &amp;amp;&amp;amp; \
  apt-get install -yqq nodejs yarn &amp;amp;&amp;amp; \
  pip install -U pip &amp;amp;&amp;amp; pip install pipenv &amp;amp;&amp;amp; \
  npm i -g npm@^7 &amp;amp;&amp;amp; \
  curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python &amp;amp;&amp;amp; ln -s /root/.poetry/bin/poetry /usr/local/bin &amp;amp;&amp;amp; \
  rm -rf /var/lib/apt/lists/*&lt;/pre&gt;
  &lt;p id=&quot;Crdn&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;EZkg&quot;&gt;↪️main.py&lt;/h3&gt;
  &lt;pre id=&quot;F8gN&quot; data-lang=&quot;python&quot;&gt;#!/usr/bin/env python3
# Copyright (C) @subinps
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.&lt;/pre&gt;
  &lt;pre id=&quot;ABhc&quot; data-lang=&quot;python&quot;&gt;# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.&lt;/pre&gt;
  &lt;pre id=&quot;i9i5&quot; data-lang=&quot;python&quot;&gt;# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see &amp;lt;https://www.gnu.org/licenses/&amp;gt;.
from utils import (
    play, 
    start_stream,
    startup_check, 
    sync_from_db,
    check_changes
)
from user import group_call, USER
from utils import LOGGER
from config import Config
from pyrogram import idle
from bot import bot
import asyncio
import os&lt;/pre&gt;
  &lt;pre id=&quot;jbZM&quot; data-lang=&quot;python&quot;&gt;if Config.DATABASE_URI:
    from utils import db&lt;/pre&gt;
  &lt;pre id=&quot;kRP5&quot; data-lang=&quot;python&quot;&gt;
async def main():
    await bot.start()
    Config.BOT_USERNAME = (await bot.get_me()).username
    LOGGER.info(f&amp;quot;{Config.BOT_USERNAME} Started.&amp;quot;)
    if Config.DATABASE_URI:
        try:
            if await db.is_saved(&amp;quot;RESTART&amp;quot;):
                msg=await db.get_config(&amp;quot;RESTART&amp;quot;)
                if msg:
                    try:
                        k=await bot.edit_message_text(msg[&amp;#x27;chat_id&amp;#x27;], msg[&amp;#x27;msg_id&amp;#x27;], text=&amp;quot;Succesfully restarted.&amp;quot;)
                        await db.del_config(&amp;quot;RESTART&amp;quot;)
                    except:
                        pass
            await check_changes()
            await sync_from_db()
        except Exception as e:
            LOGGER.error(f&amp;quot;Errors occured while setting up database for VCPlayerBot, check the value of DATABASE_URI. Full error - {str(e)}&amp;quot;, exc_info=True)
            Config.STARTUP_ERROR=&amp;quot;Errors occured while setting up database for VCPlayerBot, check the value of DATABASE_URI. Full error - {str(e)}&amp;quot;
            LOGGER.info(&amp;quot;Activating debug mode, you can reconfigure your bot with /env command.&amp;quot;)
            await bot.stop()
            from utils import debug
            await debug.start()
            await idle()
            return&lt;/pre&gt;
  &lt;pre id=&quot;IC6J&quot; data-lang=&quot;python&quot;&gt;    if Config.DEBUG:
        LOGGER.info(&amp;quot;Debugging enabled by user, Now in debug mode.&amp;quot;)
        Config.STARTUP_ERROR=&amp;quot;Debugging enabled by user, Now in debug mode.&amp;quot;
        from utils import debug
        await bot.stop()
        await debug.start()
        await idle()
        return&lt;/pre&gt;
  &lt;pre id=&quot;Qizt&quot; data-lang=&quot;python&quot;&gt;    try:
        await group_call.start()
        Config.USER_ID = (await USER.get_me()).id
        k=await startup_check()
        if k == False:
            LOGGER.error(&amp;quot;Startup checks not passed , bot is quiting&amp;quot;)
            await bot.stop()
            LOGGER.info(&amp;quot;Activating debug mode, you can reconfigure your bot with /env command.&amp;quot;)
            from utils import debug
            await debug.start()
            await idle()
            return&lt;/pre&gt;
  &lt;pre id=&quot;VcAy&quot; data-lang=&quot;python&quot;&gt;        if Config.IS_LOOP:
            if Config.playlist:
                await play()
                LOGGER.info(&amp;quot;Loop play enabled and playlist is not empty, resuming playlist.&amp;quot;)
            else:
                LOGGER.info(&amp;quot;Loop play enabled , starting playing startup stream.&amp;quot;)
                await start_stream()
    except Exception as e:
        if &amp;quot;unpack requires&amp;quot; in str(e):
            LOGGER.error(&amp;quot;You Have to generate a new session string from the link given in README of the repo and replace the existing one with the new.&amp;quot;)
            LOGGER.info(&amp;quot;Activating debug mode, you can reconfigure your bot with /env command.&amp;quot;)
            Config.STARTUP_ERROR=f&amp;quot;You Have to generate a new session string from the link given in README of the repo and replace the existing one with the new. \nGenerate string session from https://repl.it/@subinps/getStringName&amp;quot;
        else:
            LOGGER.error(f&amp;quot;Startup was unsuccesfull, Errors - {e}&amp;quot;, exc_info=True)
            LOGGER.info(&amp;quot;Activating debug mode, you can reconfigure your bot with /env command.&amp;quot;)
            Config.STARTUP_ERROR=f&amp;quot;Startup was unsuccesfull, Errors - {e}&amp;quot;
        from utils import debug
        await bot.stop()
        await debug.start()
        await idle()
        return&lt;/pre&gt;
  &lt;pre id=&quot;MTKi&quot; data-lang=&quot;python&quot;&gt;    await idle()
    await bot.stop()&lt;/pre&gt;
  &lt;pre id=&quot;BRIq&quot; data-lang=&quot;python&quot;&gt;if __name__ == &amp;#x27;__main__&amp;#x27;:
    asyncio.get_event_loop().run_until_complete(main())&lt;/pre&gt;
  &lt;p id=&quot;rws2&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;nlgP&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;Sh7f&quot;&gt;↪️requirements.txt&lt;/h3&gt;
  &lt;pre id=&quot;rp0q&quot; data-lang=&quot;python&quot;&gt;pyrogram==1.3.6
py-tgcalls==0.8.2
parse-torrent-name
tgcrypto
yt-dlp
youtube-search-python
httpx==0.23.0
youtube-search
heroku3
pillow
motor
dnspython
pytz
apscheduler
python-dotenv&lt;/pre&gt;
  &lt;h3 id=&quot;xsml&quot;&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;jD0n&quot;&gt;↪️start.sh&lt;/h3&gt;
  &lt;pre id=&quot;l2i4&quot; data-lang=&quot;python&quot;&gt;echo &amp;quot;Cloning Repo....&amp;quot;
if [ -z $BRANCH ]
then
  echo &amp;quot;Cloning main branch....&amp;quot;
  git clone https://github.com/subinps/VCPlayerBot /VCPlayerBot
else
  echo &amp;quot;Cloning $BRANCH branch....&amp;quot;
  git clone https://github.com/subinps/VCPlayerBot -b $BRANCH /VCPlayerBot
fi
cd /VCPlayerBot
pip3 install -U -r requirements.txt
echo &amp;quot;Starting Bot....&amp;quot;
python3 main.py&lt;/pre&gt;
  &lt;p id=&quot;WzGf&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;fieA&quot;&gt;↪️user.py&lt;/h3&gt;
  &lt;pre id=&quot;e4IA&quot; data-lang=&quot;python&quot;&gt;#!/usr/bin/env python3
# Copyright (C) @subinps
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.&lt;/pre&gt;
  &lt;pre id=&quot;2miW&quot; data-lang=&quot;python&quot;&gt;# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.&lt;/pre&gt;
  &lt;pre id=&quot;jX49&quot; data-lang=&quot;python&quot;&gt;# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see &amp;lt;https://www.gnu.org/licenses/&amp;gt;.
from pytgcalls import PyTgCalls
from pyrogram import Client
from config import Config
from utils import LOGGER&lt;/pre&gt;
  &lt;pre id=&quot;x15j&quot; data-lang=&quot;python&quot;&gt;USER = Client(
    Config.SESSION,
    Config.API_ID,
    Config.API_HASH,
    plugins=dict(root=&amp;quot;userplugins&amp;quot;)
    )
group_call = PyTgCalls(USER, cache_duration=180)&lt;/pre&gt;

</content></entry><entry><id>codeonplate:LkNBi_G3tV8</id><link rel="alternate" type="text/html" href="https://teletype.in/@codeonplate/LkNBi_G3tV8?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=codeonplate"></link><title>Телеграмм-бот ChatGPT</title><published>2025-10-18T06:11:32.007Z</published><updated>2025-10-18T06:11:32.007Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img4.teletype.in/files/3c/80/3c80a61e-9a60-42f7-b4a3-deb9a36ef7db.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://user-images.githubusercontent.com/11541888/225114786-0d639854-b3e1-4214-b49a-e51ce8c40387.png&quot;&gt;Телеграм-бот, который интегрируется с официальными ChatGPT, DALL·E и Whisper для предоставления ответов. Готов к использованию, требуется минимальная настройка.</summary><content type="html">
  &lt;p id=&quot;r6rT&quot;&gt;&lt;a href=&quot;https://core.telegram.org/bots/api&quot; target=&quot;_blank&quot;&gt;Телеграм-бот&lt;/a&gt;, который интегрируется с &lt;em&gt;официальными&lt;/em&gt; &lt;a href=&quot;https://openai.com/blog/chatgpt/&quot; target=&quot;_blank&quot;&gt;ChatGPT&lt;/a&gt;, &lt;a href=&quot;https://openai.com/product/dall-e-2&quot; target=&quot;_blank&quot;&gt;DALL·E&lt;/a&gt; и &lt;a href=&quot;https://openai.com/research/whisper&quot; target=&quot;_blank&quot;&gt;Whisper&lt;/a&gt; для предоставления ответов. Готов к использованию, требуется минимальная настройка.&lt;/p&gt;
  &lt;h3 id=&quot;EgYP&quot;&gt;ДЕМОНСТРАЦИЯ&lt;/h3&gt;
  &lt;p id=&quot;ffQF&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;SQSN&quot;&gt;&lt;/p&gt;
  &lt;figure id=&quot;jpnS&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://user-images.githubusercontent.com/11541888/225114786-0d639854-b3e1-4214-b49a-e51ce8c40387.png&quot; width=&quot;1042&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;939T&quot;&gt;&lt;strong&gt;Плагины&lt;/strong&gt;&lt;/p&gt;
  &lt;figure id=&quot;4ogb&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://private-user-images.githubusercontent.com/11541888/258479877-83d5e0cd-e09a-463d-a292-722f919e929f.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NjA3NjI0MTcsIm5iZiI6MTc2MDc2MjExNywicGF0aCI6Ii8xMTU0MTg4OC8yNTg0Nzk4NzctODNkNWUwY2QtZTA5YS00NjNkLWEyOTItNzIyZjkxOWU5MjlmLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTEwMTglMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUxMDE4VDA0MzUxN1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTYzNzQwMTBkNWFmYWQyMDFjY2YzMzJjMGM3MzljZDA3ODI0OGY5YjdkMzc3ZTJhYTc4ZWQ5Y2ZhNTVkYjZhMDkmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.KbTmdOrEQuS3gUsdrOCE-Hm3F0pt_gVuR9D-v9CBjMs&quot; /&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;tW5r&quot;&gt;Характеристики&lt;/h2&gt;
  &lt;ul id=&quot;m6Ly&quot;&gt;
    &lt;li id=&quot;G3MN&quot;&gt;Поддержите уценку в ответах&lt;/li&gt;
    &lt;li id=&quot;9YcF&quot;&gt;Сбросить диалог с помощью команды &lt;code&gt;/reset&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;o7Wr&quot;&gt;Индикатор набора текста при создании ответа&lt;/li&gt;
    &lt;li id=&quot;Jn4v&quot;&gt;Доступ можно ограничить, указав список разрешённых пользователей&lt;/li&gt;
    &lt;li id=&quot;JW1B&quot;&gt;Поддержка Docker и прокси-сервера&lt;/li&gt;
    &lt;li id=&quot;PvFy&quot;&gt;Генерация изображений с помощью DALL·E через команду &lt;code&gt;/image&lt;/code&gt;&lt;/li&gt;
    &lt;li id=&quot;5a9Y&quot;&gt;Расшифровка аудио- и видеосообщений с помощью Whisper (может потребоваться &lt;a href=&quot;https://ffmpeg.org/&quot; target=&quot;_blank&quot;&gt;ffmpeg&lt;/a&gt;)&lt;/li&gt;
    &lt;li id=&quot;WGn0&quot;&gt;Автоматическое обобщение разговора во избежание чрезмерного использования токенов&lt;/li&gt;
    &lt;li id=&quot;xX3H&quot;&gt;Отслеживание использования токенов каждым пользователем — от &lt;a href=&quot;https://github.com/AlexHTW&quot; target=&quot;_blank&quot;&gt;@AlexHTW&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;9XmT&quot;&gt;Получите статистику использования личных токенов с помощью команды &lt;code&gt;/stats&lt;/code&gt; от &lt;a href=&quot;https://github.com/AlexHTW&quot; target=&quot;_blank&quot;&gt;@AlexHTW&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;qh0X&quot;&gt;Бюджеты пользователей и гостевые бюджеты — от &lt;a href=&quot;https://github.com/AlexHTW&quot; target=&quot;_blank&quot;&gt;@AlexHTW&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;vp9n&quot;&gt;Поддержка потокового вещания&lt;/li&gt;
    &lt;li id=&quot;ABjW&quot;&gt;Поддержка GPT-4&lt;/li&gt;
    &lt;ul id=&quot;kv90&quot;&gt;
      &lt;li id=&quot;QF3M&quot;&gt;Если у вас есть доступ к API GPT-4, просто замените &lt;code&gt;OPENAI_MODEL&lt;/code&gt; на &lt;code&gt;gpt-4&lt;/code&gt;&lt;/li&gt;
    &lt;/ul&gt;
    &lt;li id=&quot;joPO&quot;&gt;Локализованный язык бота&lt;/li&gt;
    &lt;ul id=&quot;Utwp&quot;&gt;
      &lt;li id=&quot;GhZ5&quot;&gt;Доступные языки 🇧🇷 🇨🇳 🇫🇮 🇩🇪 🇮🇩 🇮🇷 🇮🇹 🇲🇾 🇳🇱 🇵🇱 🇷🇺 🇸🇦 🇪🇸 🇹🇼 🇹🇷 🇺🇦 🇬🇧 🇺🇿 🇻🇳 🇮🇱&lt;/li&gt;
    &lt;/ul&gt;
    &lt;li id=&quot;OfWW&quot;&gt;Улучшена поддержка встроенных запросов для групповых и личных чатов — &lt;a href=&quot;https://github.com/bugfloyd&quot; target=&quot;_blank&quot;&gt;@bugfloyd&lt;/a&gt;&lt;/li&gt;
    &lt;ul id=&quot;Lzdd&quot;&gt;
      &lt;li id=&quot;W1Ca&quot;&gt;Чтобы воспользоваться этой функцией, включите встроенные запросы для своего бота в BotFather с помощью &lt;code&gt;/setinline&lt;/code&gt; &lt;a href=&quot;https://core.telegram.org/bots/inline&quot; target=&quot;_blank&quot;&gt;команды&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
    &lt;li id=&quot;7pOT&quot;&gt;Поддержка &lt;em&gt;новых моделей&lt;/em&gt; &lt;a href=&quot;https://openai.com/blog/function-calling-and-other-api-updates&quot; target=&quot;_blank&quot;&gt;объявлены 13 июня 2023 года&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;M8If&quot;&gt;Поддержка &lt;em&gt;функций&lt;/em&gt; (плагинов) для расширения функциональности бота с помощью сторонних сервисов&lt;/li&gt;
    &lt;ul id=&quot;1WBv&quot;&gt;
      &lt;li id=&quot;Mo8k&quot;&gt;Погода, Spotify, веб-поиск, преобразование текста в речь и многое другое. Список доступных плагинов см. &lt;a href=&quot;https://github.com/n3d1117/chatgpt-telegram-bot?tab=readme-ov-file#available-plugins&quot; target=&quot;_blank&quot;&gt;здесь&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
    &lt;li id=&quot;qy9T&quot;&gt;Поддержка неофициальных API, совместимых с OpenAI, от &lt;a href=&quot;https://github.com/kristaller486&quot; target=&quot;_blank&quot;&gt;@kristaller486&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;8iOs&quot;&gt;(НОВИНКА!) Поддержка GPT-4 Turbo и DALL·E 3 &lt;a href=&quot;https://openai.com/blog/new-models-and-developer-products-announced-at-devday&quot; target=&quot;_blank&quot;&gt;объявлено 6 ноября 2023 года&lt;/a&gt; — от &lt;a href=&quot;https://github.com/AlexHTW&quot; target=&quot;_blank&quot;&gt;@AlexHTW&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;NkPC&quot;&gt;(НОВИНКА!) Поддержка преобразования текста в речь &lt;a href=&quot;https://platform.openai.com/docs/guides/text-to-speech&quot; target=&quot;_blank&quot;&gt;объявлено 6 ноября 2023 года&lt;/a&gt; — от &lt;a href=&quot;https://github.com/gilcu3&quot; target=&quot;_blank&quot;&gt;@gilcu3&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;LaDm&quot;&gt;(НОВИНКА!) Поддержка Vision &lt;a href=&quot;https://platform.openai.com/docs/guides/vision&quot; target=&quot;_blank&quot;&gt;объявлена 6 ноября 2023 года&lt;/a&gt; — от &lt;a href=&quot;https://github.com/gilcu3&quot; target=&quot;_blank&quot;&gt;@gilcu3&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;8gjL&quot;&gt;(НОВИНКА!) Поддержка модели GPT-4o &lt;a href=&quot;https://openai.com/index/hello-gpt-4o/&quot; target=&quot;_blank&quot;&gt;объявлена 12 мая 2024 года&lt;/a&gt; — от &lt;a href=&quot;https://github.com/err09r&quot; target=&quot;_blank&quot;&gt;@err09r&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;Uiqe&quot;&gt;(НОВИНКА!) предварительная поддержка моделей o1 и o1-mini&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;NIAc&quot;&gt;Дополнительные функции — нужна помощь!&lt;/h2&gt;
  &lt;p id=&quot;MOmW&quot;&gt;Если вы хотите помочь, загляните в раздел &lt;a href=&quot;https://github.com/n3d1117/chatgpt-telegram-bot/issues&quot; target=&quot;_blank&quot;&gt;проблемы&lt;/a&gt; и внесите свой вклад!&lt;br /&gt;Если вы хотите помочь с переводами, загляните в &lt;a href=&quot;https://github.com/n3d1117/chatgpt-telegram-bot/discussions/219&quot; target=&quot;_blank&quot;&gt;Руководство по переводам&lt;/a&gt;&lt;/p&gt;
  &lt;p id=&quot;dGYU&quot;&gt;Пиарщики всегда нужны!&lt;/p&gt;
  &lt;h2 id=&quot;imtw&quot;&gt;Предварительные условия&lt;/h2&gt;
  &lt;ul id=&quot;WaN3&quot;&gt;
    &lt;li id=&quot;5nGD&quot;&gt;Python 3.9+&lt;/li&gt;
    &lt;li id=&quot;bd8I&quot;&gt;&lt;a href=&quot;https://core.telegram.org/bots#6-botfather&quot; target=&quot;_blank&quot;&gt;Телеграм-бот&lt;/a&gt; и его токен (см. &lt;a href=&quot;https://core.telegram.org/bots/tutorial#obtain-your-bot-token&quot; target=&quot;_blank&quot;&gt;руководство&lt;/a&gt;)&lt;/li&gt;
    &lt;li id=&quot;1wsE&quot;&gt;Аккаунт &lt;a href=&quot;https://openai.com/&quot; target=&quot;_blank&quot;&gt;OpenAI&lt;/a&gt; (см. раздел &lt;a href=&quot;https://github.com/n3d1117/chatgpt-telegram-bot?tab=readme-ov-file#configuration&quot; target=&quot;_blank&quot;&gt;конфигурация&lt;/a&gt;)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;xqsJ&quot;&gt;Приступая к работе&lt;/h2&gt;
  &lt;h3 id=&quot;DfpR&quot;&gt;Конфигурация&lt;/h3&gt;
  &lt;p id=&quot;UY6B&quot;&gt;Настройте конфигурацию, скопировав &lt;code&gt;.env.example&lt;/code&gt; и переименовав его в &lt;code&gt;.env&lt;/code&gt;, а затем отредактировав необходимые параметры по своему усмотрению:&lt;/p&gt;
  &lt;p id=&quot;MTlG&quot;&gt;Параметр&lt;/p&gt;
  &lt;p id=&quot;CPqh&quot;&gt;Описание&lt;/p&gt;
  &lt;p id=&quot;GSRJ&quot;&gt;&lt;code&gt;OPENAI_API_KEY&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;3gnD&quot;&gt;Ваш ключ API OpenAI можно получить&lt;/p&gt;
  &lt;p id=&quot;heqN&quot;&gt;&lt;a href=&quot;https://platform.openai.com/account/api-keys&quot; target=&quot;_blank&quot;&gt;здесь&lt;/a&gt;&lt;code&gt;TELEGRAM_BOT_TOKEN&lt;/code&gt;Токен вашего Telegram-бота, полученный с помощью&lt;/p&gt;
  &lt;p id=&quot;2MgK&quot;&gt;&lt;a href=&quot;http://t.me/botfather&quot; target=&quot;_blank&quot;&gt;BotFather&lt;/a&gt; (см.&lt;/p&gt;
  &lt;p id=&quot;fw9e&quot;&gt;&lt;a href=&quot;https://core.telegram.org/bots/tutorial#obtain-your-bot-token&quot; target=&quot;_blank&quot;&gt;руководство&lt;/a&gt;)&lt;/p&gt;
  &lt;p id=&quot;3sRn&quot;&gt;&lt;code&gt;ADMIN_USER_IDS&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;f1s3&quot;&gt;Идентификаторы пользователей Telegram, являющихся администраторами.&lt;/p&gt;
  &lt;p id=&quot;dfAo&quot;&gt;Эти пользователи имеют доступ к специальным административным командам, информации и не ограничены в бюджете.&lt;/p&gt;
  &lt;p id=&quot;SbzR&quot;&gt;Идентификаторы администраторов не нужно добавлять в&lt;/p&gt;
  &lt;p id=&quot;lmiw&quot;&gt;&lt;code&gt;ALLOWED_TELEGRAM_USER_IDS&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;igKJ&quot;&gt;&lt;strong&gt;Примечание&lt;/strong&gt;: по умолчанию администраторы (&lt;/p&gt;
  &lt;p id=&quot;9yyU&quot;&gt;&lt;code&gt;-&lt;/code&gt;) отсутствуют&lt;/p&gt;
  &lt;p id=&quot;qXdZ&quot;&gt;&lt;code&gt;ALLOWED_TELEGRAM_USER_IDS&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;ZqaL&quot;&gt;Список идентификаторов пользователей Telegram, разделённых запятыми, которым разрешено взаимодействовать с ботом (используйте&lt;/p&gt;
  &lt;p id=&quot;OPNv&quot;&gt;&lt;a href=&quot;https://t.me/getidsbot&quot; target=&quot;_blank&quot;&gt;getidsbot&lt;/a&gt;, чтобы узнать свой идентификатор пользователя).&lt;/p&gt;
  &lt;p id=&quot;E1qG&quot;&gt;&lt;strong&gt;Примечание&lt;/strong&gt;: по умолчанию разрешено&lt;/p&gt;
  &lt;p id=&quot;lBqp&quot;&gt;&lt;em&gt;всем&lt;/em&gt; (&lt;/p&gt;
  &lt;p id=&quot;7SLm&quot;&gt;&lt;code&gt;*&lt;/code&gt;)&lt;/p&gt;
  &lt;h3 id=&quot;XyKV&quot;&gt;Дополнительная конфигурация&lt;/h3&gt;
  &lt;p id=&quot;dJks&quot;&gt;Следующие параметры являются необязательными и могут быть заданы в файле &lt;code&gt;.env&lt;/code&gt;:&lt;/p&gt;
  &lt;h4 id=&quot;FSJc&quot;&gt;Бюджеты&lt;/h4&gt;
  &lt;p id=&quot;91Nw&quot;&gt;Параметр&lt;/p&gt;
  &lt;p id=&quot;a2JM&quot;&gt;Описание&lt;/p&gt;
  &lt;p id=&quot;1oBA&quot;&gt;Значение по умолчанию&lt;/p&gt;
  &lt;p id=&quot;w6zJ&quot;&gt;&lt;code&gt;BUDGET_PERIOD&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;s8Ww&quot;&gt;Определяет временные рамки, к которым применяются все бюджеты.&lt;/p&gt;
  &lt;p id=&quot;hHBe&quot;&gt;Доступные периоды:&lt;/p&gt;
  &lt;p id=&quot;DrbM&quot;&gt;&lt;code&gt;daily&lt;/code&gt; &lt;em&gt;(бюджет сбрасывается каждый день)&lt;/em&gt;,&lt;/p&gt;
  &lt;p id=&quot;vHAO&quot;&gt;&lt;code&gt;monthly&lt;/code&gt; &lt;em&gt;(бюджет сбрасывается первого числа каждого месяца)&lt;/em&gt;,&lt;/p&gt;
  &lt;p id=&quot;khRw&quot;&gt;&lt;code&gt;all-time&lt;/code&gt; &lt;em&gt;(бюджет никогда не сбрасывается)&lt;/em&gt;.&lt;/p&gt;
  &lt;p id=&quot;VSDq&quot;&gt;Дополнительную информацию см. в&lt;/p&gt;
  &lt;p id=&quot;wfTe&quot;&gt;&lt;a href=&quot;https://github.com/n3d1117/chatgpt-telegram-bot/discussions/184&quot; target=&quot;_blank&quot;&gt;Руководстве по бюджетированию&lt;/a&gt;&lt;code&gt;monthlyUSER_BUDGETS&lt;/code&gt;Список сумм в долларах США на пользователя, разделённых запятыми, из списка&lt;/p&gt;
  &lt;p id=&quot;B7aA&quot;&gt;&lt;code&gt;ALLOWED_TELEGRAM_USER_IDS&lt;/code&gt; для установки индивидуального лимита расходов на OpenAI API для каждого пользователя.&lt;/p&gt;
  &lt;p id=&quot;Fy9S&quot;&gt;Для списков из&lt;/p&gt;
  &lt;p id=&quot;zCdQ&quot;&gt;&lt;code&gt;*&lt;/code&gt; пользователей каждому пользователю присваивается первое&lt;/p&gt;
  &lt;p id=&quot;JLXT&quot;&gt;&lt;code&gt;USER_BUDGETS&lt;/code&gt; значение.&lt;/p&gt;
  &lt;p id=&quot;prq4&quot;&gt;&lt;strong&gt;Примечание&lt;/strong&gt;: по умолчанию&lt;/p&gt;
  &lt;p id=&quot;MyXU&quot;&gt;&lt;em&gt;без ограничений&lt;/em&gt; для всех пользователей (&lt;/p&gt;
  &lt;p id=&quot;LKOn&quot;&gt;&lt;code&gt;*&lt;/code&gt;).&lt;/p&gt;
  &lt;p id=&quot;aqEE&quot;&gt;Дополнительную информацию см. в&lt;/p&gt;
  &lt;p id=&quot;BVC2&quot;&gt;&lt;a href=&quot;https://github.com/n3d1117/chatgpt-telegram-bot/discussions/184&quot; target=&quot;_blank&quot;&gt;Руководстве по бюджету&lt;/a&gt;&lt;code&gt;*GUEST_BUDGET&lt;/code&gt;$-сумма в качестве лимита использования для всех гостевых пользователей.&lt;/p&gt;
  &lt;p id=&quot;Ha3Z&quot;&gt;Гостевые пользователи — это участники групповых чатов, которых нет в списке&lt;/p&gt;
  &lt;p id=&quot;UOb9&quot;&gt;&lt;code&gt;ALLOWED_TELEGRAM_USER_IDS&lt;/code&gt; .&lt;/p&gt;
  &lt;p id=&quot;aQo0&quot;&gt;Значение игнорируется, если в бюджетах пользователей не установлены лимиты использования (&lt;/p&gt;
  &lt;p id=&quot;7HAW&quot;&gt;&lt;code&gt;USER_BUDGETS&lt;/code&gt;=&lt;/p&gt;
  &lt;p id=&quot;o8kT&quot;&gt;&lt;code&gt;*&lt;/code&gt;).&lt;/p&gt;
  &lt;p id=&quot;w5NE&quot;&gt;Дополнительную информацию см. в&lt;/p&gt;
  &lt;p id=&quot;VoPk&quot;&gt;&lt;a href=&quot;https://github.com/n3d1117/chatgpt-telegram-bot/discussions/184&quot; target=&quot;_blank&quot;&gt;Руководстве по бюджету&lt;/a&gt;&lt;code&gt;100.0TOKEN_PRICE&lt;/code&gt;Цена в долларах за 1000 токенов, используемых для расчёта стоимости в статистике использования.&lt;/p&gt;
  &lt;p id=&quot;Gdnu&quot;&gt;Источник:&lt;/p&gt;
  &lt;p id=&quot;QPsx&quot;&gt;&lt;a href=&quot;https://openai.com/pricing&quot; target=&quot;_blank&quot;&gt;https://openai.com/pricing&lt;/a&gt;&lt;code&gt;0.002IMAGE_PRICES&lt;/code&gt;Список из трёх элементов, разделённых запятыми, с указанием цен на изображения разных размеров:&lt;/p&gt;
  &lt;p id=&quot;vrYw&quot;&gt;&lt;code&gt;256x256&lt;/code&gt;,&lt;/p&gt;
  &lt;p id=&quot;ZtiS&quot;&gt;&lt;code&gt;512x512&lt;/code&gt; и&lt;/p&gt;
  &lt;p id=&quot;8eeJ&quot;&gt;&lt;code&gt;1024x1024&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;KIgw&quot;&gt;Источник:&lt;/p&gt;
  &lt;p id=&quot;FlAb&quot;&gt;&lt;a href=&quot;https://openai.com/pricing&quot; target=&quot;_blank&quot;&gt;https://openai.com/pricing&lt;/a&gt;&lt;code&gt;0.016,0.018,0.02TRANSCRIPTION_PRICE&lt;/code&gt;Цена в долларах США за одну минуту аудиозаписи.&lt;/p&gt;
  &lt;p id=&quot;1cDB&quot;&gt;Источник:&lt;/p&gt;
  &lt;p id=&quot;qksq&quot;&gt;&lt;a href=&quot;https://openai.com/pricing&quot; target=&quot;_blank&quot;&gt;https://openai.com/pricing&lt;/a&gt;&lt;code&gt;0.006VISION_TOKEN_PRICE&lt;/code&gt;Цена в долларах США за 1000 токенов интерпретации изображений.&lt;/p&gt;
  &lt;p id=&quot;aY2q&quot;&gt;Источник:&lt;/p&gt;
  &lt;p id=&quot;WF9r&quot;&gt;&lt;a href=&quot;https://openai.com/pricing&quot; target=&quot;_blank&quot;&gt;https://openai.com/pricing&lt;/a&gt;&lt;code&gt;0.01TTS_PRICES&lt;/code&gt;Список цен на модели tts, разделённый запятыми:&lt;/p&gt;
  &lt;p id=&quot;6ggE&quot;&gt;&lt;code&gt;tts-1&lt;/code&gt;,&lt;/p&gt;
  &lt;p id=&quot;iunt&quot;&gt;&lt;code&gt;tts-1-hd&lt;/code&gt;. Источник:&lt;/p&gt;
  &lt;p id=&quot;jV3S&quot;&gt;&lt;a href=&quot;https://openai.com/pricing&quot; target=&quot;_blank&quot;&gt;https://openai.com/pricing&lt;/a&gt;&lt;code&gt;0.015,0.030&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;PruK&quot;&gt;Ознакомьтесь с &lt;a href=&quot;https://github.com/n3d1117/chatgpt-telegram-bot/discussions/184&quot; target=&quot;_blank&quot;&gt;руководством по бюджету&lt;/a&gt;, чтобы узнать о возможных конфигурациях бюджета.&lt;/p&gt;
  &lt;h4 id=&quot;U647&quot;&gt;Дополнительные необязательные параметры конфигурации&lt;/h4&gt;
  &lt;p id=&quot;78zt&quot;&gt;Параметр&lt;/p&gt;
  &lt;p id=&quot;IsWL&quot;&gt;Описание&lt;/p&gt;
  &lt;p id=&quot;9naA&quot;&gt;Значение по умолчанию&lt;/p&gt;
  &lt;p id=&quot;gR0L&quot;&gt;&lt;code&gt;ENABLE_QUOTING&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;QX4h&quot;&gt;Следует ли включать цитирование сообщений в личных чатах&lt;/p&gt;
  &lt;p id=&quot;3ieI&quot;&gt;&lt;code&gt;true&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;MxQd&quot;&gt;&lt;code&gt;ENABLE_IMAGE_GENERATION&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;tr2e&quot;&gt;Следует ли включать генерацию изображений с помощью команды&lt;/p&gt;
  &lt;p id=&quot;G0lt&quot;&gt;&lt;code&gt;/imagetrueENABLE_TRANSCRIPTION&lt;/code&gt;Следует ли включать расшифровку аудио- и видеосообщений&lt;/p&gt;
  &lt;p id=&quot;Udmt&quot;&gt;&lt;code&gt;true&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;h0On&quot;&gt;&lt;code&gt;ENABLE_TTS_GENERATION&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;42wX&quot;&gt;Следует ли включить преобразование текста в речь с помощью&lt;/p&gt;
  &lt;p id=&quot;G4Hn&quot;&gt;&lt;code&gt;/ttstrueENABLE_VISION&lt;/code&gt;Следует ли включать функции машинного зрения в поддерживаемых моделях&lt;/p&gt;
  &lt;p id=&quot;GoBZ&quot;&gt;&lt;code&gt;true&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;Yf4p&quot;&gt;&lt;code&gt;PROXY&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;apiQ&quot;&gt;Прокси-сервер для использования с OpenAI и ботом Telegram (например,&lt;/p&gt;
  &lt;p id=&quot;L6Uw&quot;&gt;&lt;code&gt;http://localhost:8080&lt;/code&gt;)&lt;/p&gt;
  &lt;p id=&quot;4A5w&quot;&gt;-&lt;/p&gt;
  &lt;p id=&quot;3JAq&quot;&gt;&lt;code&gt;OPENAI_PROXY&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;FA7J&quot;&gt;Прокси-сервер используется только для OpenAI (например,&lt;/p&gt;
  &lt;p id=&quot;jKLP&quot;&gt;&lt;code&gt;http://localhost:8080&lt;/code&gt;)&lt;/p&gt;
  &lt;p id=&quot;kcZw&quot;&gt;-&lt;/p&gt;
  &lt;p id=&quot;nf8l&quot;&gt;&lt;code&gt;TELEGRAM_PROXY&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;5JOs&quot;&gt;Прокси-сервер используется только для Telegram-бота (например,&lt;/p&gt;
  &lt;p id=&quot;KYQs&quot;&gt;&lt;code&gt;http://localhost:8080&lt;/code&gt;)&lt;/p&gt;
  &lt;p id=&quot;koqH&quot;&gt;-&lt;/p&gt;
  &lt;p id=&quot;8sJU&quot;&gt;&lt;code&gt;OPENAI_MODEL&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;Kafj&quot;&gt;Модель OpenAI для генерации ответов.&lt;/p&gt;
  &lt;p id=&quot;LFKo&quot;&gt;Все доступные модели можно найти&lt;/p&gt;
  &lt;p id=&quot;zPNw&quot;&gt;&lt;a href=&quot;https://platform.openai.com/docs/models/&quot; target=&quot;_blank&quot;&gt;здесь&lt;/a&gt;&lt;code&gt;gpt-4oOPENAI_BASE_URL&lt;/code&gt;Конечный URL для неофициальных API, совместимых с OpenAI (например, LocalAI или text-generation-webui)&lt;/p&gt;
  &lt;p id=&quot;ivxw&quot;&gt;URL-адрес OpenAI API по умолчанию&lt;/p&gt;
  &lt;p id=&quot;4wMG&quot;&gt;&lt;code&gt;ASSISTANT_PROMPT&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;7Emv&quot;&gt;Системное сообщение, которое задаёт тон и управляет поведением помощника&lt;/p&gt;
  &lt;p id=&quot;2rXF&quot;&gt;&lt;code&gt;You are a helpful assistant.&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;T0L8&quot;&gt;&lt;code&gt;SHOW_USAGE&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;Epnj&quot;&gt;Следует ли отображать информацию об использовании токенов OpenAI после каждого ответа&lt;/p&gt;
  &lt;p id=&quot;NG1X&quot;&gt;&lt;code&gt;false&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;Xk5M&quot;&gt;&lt;code&gt;STREAM&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;5toY&quot;&gt;Следует ли отправлять ответы потоком.&lt;/p&gt;
  &lt;p id=&quot;NnCJ&quot;&gt;&lt;strong&gt;Примечание&lt;/strong&gt;: при включении несовместимо с&lt;/p&gt;
  &lt;p id=&quot;3Rxu&quot;&gt;&lt;code&gt;N_CHOICES&lt;/code&gt; выше 1&lt;/p&gt;
  &lt;p id=&quot;0Dho&quot;&gt;&lt;code&gt;true&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;GkdU&quot;&gt;&lt;code&gt;MAX_TOKENS&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;Drym&quot;&gt;Максимальное количество токенов, которое может вернуть API ChatGPT&lt;/p&gt;
  &lt;p id=&quot;RsA5&quot;&gt;&lt;code&gt;1200&lt;/code&gt; для GPT-3, &lt;code&gt;2400&lt;/code&gt; для GPT-4&lt;/p&gt;
  &lt;p id=&quot;gWYl&quot;&gt;&lt;code&gt;VISION_MAX_TOKENS&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;Rrfg&quot;&gt;Верхняя граница количества токенов, возвращаемых моделями машинного зрения&lt;/p&gt;
  &lt;p id=&quot;LFmF&quot;&gt;&lt;code&gt;300&lt;/code&gt; для gpt-4o&lt;/p&gt;
  &lt;p id=&quot;XCGV&quot;&gt;&lt;code&gt;VISION_MODEL&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;YbwR&quot;&gt;Модель преобразования изображения в речь для использования.&lt;/p&gt;
  &lt;p id=&quot;kWfB&quot;&gt;Допустимые значения:&lt;/p&gt;
  &lt;p id=&quot;oiIC&quot;&gt;&lt;code&gt;gpt-4ogpt-4oENABLE_VISION_FOLLOW_UP_QUESTIONS&lt;/code&gt;Если это так, то после отправки изображения боту он будет использовать настроенную модель VISION_MODEL до завершения диалога.&lt;/p&gt;
  &lt;p id=&quot;g8sD&quot;&gt;В противном случае он будет использовать модель OPENAI_MODEL для продолжения диалога.&lt;/p&gt;
  &lt;p id=&quot;E0pB&quot;&gt;Допустимые значения:&lt;/p&gt;
  &lt;p id=&quot;CX2o&quot;&gt;&lt;code&gt;true&lt;/code&gt; или&lt;/p&gt;
  &lt;p id=&quot;btsj&quot;&gt;&lt;code&gt;falsetrueMAX_HISTORY_SIZE&lt;/code&gt;Максимальное количество сообщений, которые можно сохранить в памяти, после чего разговор будет обобщён, чтобы избежать чрезмерного использования токенов&lt;/p&gt;
  &lt;p id=&quot;9edM&quot;&gt;&lt;code&gt;15&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;VGyE&quot;&gt;&lt;code&gt;MAX_CONVERSATION_AGE_MINUTES&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;7gKx&quot;&gt;Максимальное количество минут, в течение которых диалог должен оставаться активным после последнего сообщения, после чего диалог будет сброшен&lt;/p&gt;
  &lt;p id=&quot;MEv5&quot;&gt;&lt;code&gt;180&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;DkeM&quot;&gt;&lt;code&gt;VOICE_REPLY_WITH_TRANSCRIPT_ONLY&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;dE70&quot;&gt;Стоит ли отвечать на голосовые сообщения только расшифровкой или ответом ChatGPT на расшифровку&lt;/p&gt;
  &lt;p id=&quot;Oq3F&quot;&gt;&lt;code&gt;false&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;z6SY&quot;&gt;&lt;code&gt;VOICE_REPLY_PROMPTS&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;hiqk&quot;&gt;Список фраз, разделённых точкой с запятой (т. е.&lt;/p&gt;
  &lt;p id=&quot;U6wd&quot;&gt;&lt;code&gt;Hi bot;Hello chat&lt;/code&gt;).&lt;/p&gt;
  &lt;p id=&quot;Tl23&quot;&gt;Если расшифровка начинается с одной из этих фраз, она будет рассматриваться как подсказка, даже если&lt;/p&gt;
  &lt;p id=&quot;RUEy&quot;&gt;&lt;code&gt;VOICE_REPLY_WITH_TRANSCRIPT_ONLY&lt;/code&gt; установлено на&lt;/p&gt;
  &lt;p id=&quot;9ohW&quot;&gt;&lt;code&gt;true&lt;/code&gt;-&lt;code&gt;VISION_PROMPT&lt;/code&gt;Фраза (например,&lt;/p&gt;
  &lt;p id=&quot;8LDL&quot;&gt;&lt;code&gt;What is in this image&lt;/code&gt;).&lt;/p&gt;
  &lt;p id=&quot;fae0&quot;&gt;Визуальные модели используют её в качестве подсказки для интерпретации заданного изображения.&lt;/p&gt;
  &lt;p id=&quot;6Hyt&quot;&gt;Если на изображении, отправленном боту, есть подпись, она заменяет этот параметр&lt;/p&gt;
  &lt;p id=&quot;1vok&quot;&gt;&lt;code&gt;What is in this image&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;6CY1&quot;&gt;&lt;code&gt;N_CHOICES&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;20tJ&quot;&gt;Количество ответов, которые нужно сгенерировать для каждого входного сообщения.&lt;/p&gt;
  &lt;p id=&quot;7voJ&quot;&gt;&lt;strong&gt;Примечание&lt;/strong&gt;: если&lt;/p&gt;
  &lt;p id=&quot;EPZO&quot;&gt;&lt;code&gt;STREAM&lt;/code&gt; включено, установка значения выше 1 не будет работать должным образом&lt;/p&gt;
  &lt;p id=&quot;LE5B&quot;&gt;&lt;code&gt;1&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;tAM0&quot;&gt;&lt;code&gt;TEMPERATURE&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;SlbU&quot;&gt;Число от 0 до 2. Чем выше значение, тем более случайным будет результат&lt;/p&gt;
  &lt;p id=&quot;oQYZ&quot;&gt;&lt;code&gt;1.0&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;hg3X&quot;&gt;&lt;code&gt;PRESENCE_PENALTY&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;ARsj&quot;&gt;Число от -2,0 до 2,0. Положительные значения снижают значимость новых токенов в зависимости от того, встречаются ли они в тексте&lt;/p&gt;
  &lt;p id=&quot;5ijL&quot;&gt;&lt;code&gt;0.0&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;u1ZI&quot;&gt;&lt;code&gt;FREQUENCY_PENALTY&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;Y2mo&quot;&gt;Число от -2,0 до 2,0. Положительные значения снижают значимость новых токенов в зависимости от их частоты в тексте.&lt;/p&gt;
  &lt;p id=&quot;MJY1&quot;&gt;&lt;code&gt;0.0&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;5fLI&quot;&gt;&lt;code&gt;IMAGE_FORMAT&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;E9rw&quot;&gt;Режим получения изображений в Telegram.&lt;/p&gt;
  &lt;p id=&quot;VOHq&quot;&gt;Допустимые значения:&lt;/p&gt;
  &lt;p id=&quot;knCQ&quot;&gt;&lt;code&gt;document&lt;/code&gt; или&lt;/p&gt;
  &lt;p id=&quot;ggFS&quot;&gt;&lt;code&gt;photophotoIMAGE_MODEL&lt;/code&gt;Используемая модель DALL·E.&lt;/p&gt;
  &lt;p id=&quot;B6b1&quot;&gt;Доступные модели:&lt;/p&gt;
  &lt;p id=&quot;OCnV&quot;&gt;&lt;code&gt;dall-e-2&lt;/code&gt; и&lt;/p&gt;
  &lt;p id=&quot;9PWq&quot;&gt;&lt;code&gt;dall-e-3&lt;/code&gt;, актуальные доступные модели можно найти&lt;/p&gt;
  &lt;p id=&quot;5jHN&quot;&gt;&lt;a href=&quot;https://platform.openai.com/docs/models/dall-e&quot; target=&quot;_blank&quot;&gt;здесь&lt;/a&gt;&lt;code&gt;dall-e-2IMAGE_QUALITY&lt;/code&gt;Качество изображений DALL·E доступно только для&lt;/p&gt;
  &lt;p id=&quot;KNjv&quot;&gt;&lt;code&gt;dall-e-3&lt;/code&gt;-модели.&lt;/p&gt;
  &lt;p id=&quot;ATDW&quot;&gt;Возможные варианты:&lt;/p&gt;
  &lt;p id=&quot;WZXT&quot;&gt;&lt;code&gt;standard&lt;/code&gt; или&lt;/p&gt;
  &lt;p id=&quot;H0xf&quot;&gt;&lt;code&gt;hd&lt;/code&gt;, обратите внимание на&lt;/p&gt;
  &lt;p id=&quot;3Zpy&quot;&gt;&lt;a href=&quot;https://openai.com/pricing#image-models&quot; target=&quot;_blank&quot;&gt;разницу в цене&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;ZJxb&quot;&gt;&lt;code&gt;standard&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;0jeZ&quot;&gt;&lt;code&gt;IMAGE_STYLE&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;JNeV&quot;&gt;Стиль для генерации изображений DALL·E доступен только для&lt;/p&gt;
  &lt;p id=&quot;Vl3F&quot;&gt;&lt;code&gt;dall-e-3&lt;/code&gt;-модели.&lt;/p&gt;
  &lt;p id=&quot;JPW0&quot;&gt;Возможные варианты:&lt;/p&gt;
  &lt;p id=&quot;ydCs&quot;&gt;&lt;code&gt;vivid&lt;/code&gt; или&lt;/p&gt;
  &lt;p id=&quot;VqNJ&quot;&gt;&lt;code&gt;natural&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;lOCk&quot;&gt;Доступные стили можно посмотреть&lt;/p&gt;
  &lt;p id=&quot;ZfV4&quot;&gt;&lt;a href=&quot;https://platform.openai.com/docs/api-reference/images/create&quot; target=&quot;_blank&quot;&gt;здесь&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;nPMB&quot;&gt;&lt;code&gt;vivid&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;MRkj&quot;&gt;&lt;code&gt;IMAGE_SIZE&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;HOBG&quot;&gt;Размер изображения, сгенерированного DALL·E.&lt;/p&gt;
  &lt;p id=&quot;9B1b&quot;&gt;Должно быть&lt;/p&gt;
  &lt;p id=&quot;iCp9&quot;&gt;&lt;code&gt;256x256&lt;/code&gt;,&lt;/p&gt;
  &lt;p id=&quot;Sneh&quot;&gt;&lt;code&gt;512x512&lt;/code&gt;, или&lt;/p&gt;
  &lt;p id=&quot;zKCR&quot;&gt;&lt;code&gt;1024x1024&lt;/code&gt; для dall-e-2.&lt;/p&gt;
  &lt;p id=&quot;WmgU&quot;&gt;Должно быть&lt;/p&gt;
  &lt;p id=&quot;Ttav&quot;&gt;&lt;code&gt;1024x1024&lt;/code&gt; для моделей dall-e-3.&lt;/p&gt;
  &lt;p id=&quot;ZiM1&quot;&gt;&lt;code&gt;512x512&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;9UrY&quot;&gt;&lt;code&gt;VISION_DETAIL&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;xp9B&quot;&gt;Параметр детализации для моделей машинного зрения, описанный в&lt;/p&gt;
  &lt;p id=&quot;YhR3&quot;&gt;&lt;a href=&quot;https://platform.openai.com/docs/guides/vision&quot; target=&quot;_blank&quot;&gt;Руководстве по машинному зрению&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;1Wkp&quot;&gt;Допустимые значения:&lt;/p&gt;
  &lt;p id=&quot;ZZ9E&quot;&gt;&lt;code&gt;low&lt;/code&gt; или&lt;/p&gt;
  &lt;p id=&quot;FLQ5&quot;&gt;&lt;code&gt;highautoGROUP_TRIGGER_KEYWORD&lt;/code&gt;Если этот параметр установлен, бот в групповых чатах будет отвечать только на сообщения, начинающиеся с этого ключевого слова&lt;/p&gt;
  &lt;p id=&quot;TjO5&quot;&gt;-&lt;/p&gt;
  &lt;p id=&quot;ZLMX&quot;&gt;&lt;code&gt;IGNORE_GROUP_TRANSCRIPTIONS&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;tqmd&quot;&gt;Если установлено значение true, бот не будет обрабатывать расшифровки в групповых чатах&lt;/p&gt;
  &lt;p id=&quot;AgUx&quot;&gt;&lt;code&gt;true&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;vJJz&quot;&gt;&lt;code&gt;IGNORE_GROUP_VISION&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;neY4&quot;&gt;Если установлено значение true, бот не будет обрабатывать запросы на распознавание изображений в групповых чатах&lt;/p&gt;
  &lt;p id=&quot;jn06&quot;&gt;&lt;code&gt;true&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;8mCe&quot;&gt;&lt;code&gt;BOT_LANGUAGE&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;hF0o&quot;&gt;Язык общих сообщений бота.&lt;/p&gt;
  &lt;p id=&quot;ydl3&quot;&gt;На данный момент доступны:&lt;/p&gt;
  &lt;p id=&quot;DQM8&quot;&gt;&lt;code&gt;en&lt;/code&gt;,&lt;/p&gt;
  &lt;p id=&quot;nLji&quot;&gt;&lt;code&gt;de&lt;/code&gt;,&lt;/p&gt;
  &lt;p id=&quot;1bwp&quot;&gt;&lt;code&gt;ru&lt;/code&gt;,&lt;/p&gt;
  &lt;p id=&quot;pbn8&quot;&gt;&lt;code&gt;tr&lt;/code&gt;,&lt;/p&gt;
  &lt;p id=&quot;4iNn&quot;&gt;&lt;code&gt;it&lt;/code&gt;,&lt;/p&gt;
  &lt;p id=&quot;cIvF&quot;&gt;&lt;code&gt;fi&lt;/code&gt;,&lt;/p&gt;
  &lt;p id=&quot;Zymz&quot;&gt;&lt;code&gt;es&lt;/code&gt;,&lt;/p&gt;
  &lt;p id=&quot;uVy3&quot;&gt;&lt;code&gt;id&lt;/code&gt;,&lt;/p&gt;
  &lt;p id=&quot;6z2a&quot;&gt;&lt;code&gt;nl&lt;/code&gt;,&lt;/p&gt;
  &lt;p id=&quot;59s3&quot;&gt;&lt;code&gt;zh-cn&lt;/code&gt;,&lt;/p&gt;
  &lt;p id=&quot;twkV&quot;&gt;&lt;code&gt;zh-tw&lt;/code&gt;,&lt;/p&gt;
  &lt;p id=&quot;gJHm&quot;&gt;&lt;code&gt;vi&lt;/code&gt;,&lt;/p&gt;
  &lt;p id=&quot;Rm8m&quot;&gt;&lt;code&gt;fa&lt;/code&gt;,&lt;/p&gt;
  &lt;p id=&quot;0Pf4&quot;&gt;&lt;code&gt;pt-br&lt;/code&gt;,&lt;/p&gt;
  &lt;p id=&quot;pHva&quot;&gt;&lt;code&gt;uk&lt;/code&gt;,&lt;/p&gt;
  &lt;p id=&quot;RgMc&quot;&gt;&lt;code&gt;ms&lt;/code&gt;,&lt;/p&gt;
  &lt;p id=&quot;CFqw&quot;&gt;&lt;code&gt;uz&lt;/code&gt;,&lt;/p&gt;
  &lt;p id=&quot;bqFT&quot;&gt;&lt;code&gt;ar&lt;/code&gt;.&lt;/p&gt;
  &lt;p id=&quot;64gd&quot;&gt;&lt;a href=&quot;https://github.com/n3d1117/chatgpt-telegram-bot/discussions/219&quot; target=&quot;_blank&quot;&gt;Вклад с дополнительными перевод&lt;/a&gt;&lt;code&gt;enWHISPER_PROMPT&lt;/code&gt;Чтобы повысить точность службы расшифровки Whisper, особенно в отношении конкретных имён или терминов, вы можете настроить пользовательское сообщение.&lt;/p&gt;
  &lt;p id=&quot;CPSG&quot;&gt;&lt;a href=&quot;https://platform.openai.com/docs/guides/speech-to-text/prompting&quot; target=&quot;_blank&quot;&gt;Преобразование речи в текст — подсказки&lt;/a&gt;&lt;code&gt;-TTS_VOICE&lt;/code&gt;Голос для преобразования текста в речь.&lt;/p&gt;
  &lt;p id=&quot;Z69w&quot;&gt;Допустимые значения:&lt;/p&gt;
  &lt;p id=&quot;K3tx&quot;&gt;&lt;code&gt;alloy&lt;/code&gt;,&lt;/p&gt;
  &lt;p id=&quot;7Lxh&quot;&gt;&lt;code&gt;echo&lt;/code&gt;,&lt;/p&gt;
  &lt;p id=&quot;uO50&quot;&gt;&lt;code&gt;fable&lt;/code&gt;,&lt;/p&gt;
  &lt;p id=&quot;Qppo&quot;&gt;&lt;code&gt;onyx&lt;/code&gt;,&lt;/p&gt;
  &lt;p id=&quot;uNaK&quot;&gt;&lt;code&gt;nova&lt;/code&gt;, или&lt;/p&gt;
  &lt;p id=&quot;Mt38&quot;&gt;&lt;code&gt;shimmeralloyTTS_MODEL&lt;/code&gt;Используемая модель преобразования текста в речь.&lt;/p&gt;
  &lt;p id=&quot;4o9U&quot;&gt;Допустимые значения:&lt;/p&gt;
  &lt;p id=&quot;m0xY&quot;&gt;&lt;code&gt;tts-1&lt;/code&gt; или&lt;/p&gt;
  &lt;p id=&quot;WHaF&quot;&gt;&lt;code&gt;tts-1-hdtts-1&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;e1HH&quot;&gt;Более подробную информацию можно найти в &lt;a href=&quot;https://platform.openai.com/docs/api-reference/chat&quot; target=&quot;_blank&quot;&gt;официальном справочнике по API&lt;/a&gt;.&lt;/p&gt;
  &lt;h4 id=&quot;4IQo&quot;&gt;Функции&lt;/h4&gt;
  &lt;p id=&quot;1J6t&quot;&gt;Параметр&lt;/p&gt;
  &lt;p id=&quot;xYon&quot;&gt;Описание&lt;/p&gt;
  &lt;p id=&quot;7gur&quot;&gt;Значение по умолчанию&lt;/p&gt;
  &lt;p id=&quot;vZLo&quot;&gt;&lt;code&gt;ENABLE_FUNCTIONS&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;rS5P&quot;&gt;Стоит ли использовать функции (они же плагины).&lt;/p&gt;
  &lt;p id=&quot;EAfP&quot;&gt;Подробнее о функциях можно прочитать&lt;/p&gt;
  &lt;p id=&quot;rr5R&quot;&gt;&lt;a href=&quot;https://openai.com/blog/function-calling-and-other-api-updates&quot; target=&quot;_blank&quot;&gt;здесь&lt;/a&gt;&lt;code&gt;true&lt;/code&gt; (если доступно для данной модели)&lt;/p&gt;
  &lt;p id=&quot;vPPq&quot;&gt;&lt;code&gt;FUNCTIONS_MAX_CONSECUTIVE_CALLS&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;dV36&quot;&gt;Максимальное количество последовательных вызовов функций, которые модель может выполнить за один ответ, прежде чем отобразится сообщение для пользователя&lt;/p&gt;
  &lt;p id=&quot;k6nc&quot;&gt;&lt;code&gt;10&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;CmsD&quot;&gt;&lt;code&gt;PLUGINS&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;LWxW&quot;&gt;Список плагинов, которые нужно включить (полный список см. ниже), например:&lt;/p&gt;
  &lt;p id=&quot;yf0O&quot;&gt;&lt;code&gt;PLUGINS=wolfram,weather&lt;/code&gt;-&lt;code&gt;SHOW_PLUGINS_USED&lt;/code&gt;Нужно ли показывать, какие плагины использовались для ответа&lt;/p&gt;
  &lt;p id=&quot;Q9kg&quot;&gt;&lt;code&gt;false&lt;/code&gt;&lt;/p&gt;
  &lt;h4 id=&quot;mNkA&quot;&gt;Доступные плагины&lt;/h4&gt;
  &lt;p id=&quot;tQLc&quot;&gt;Имя&lt;/p&gt;
  &lt;p id=&quot;Jb1Y&quot;&gt;Описание&lt;/p&gt;
  &lt;p id=&quot;z842&quot;&gt;Требуемая переменная (ы) окружения&lt;/p&gt;
  &lt;p id=&quot;vZm8&quot;&gt;Зависимость&lt;/p&gt;
  &lt;p id=&quot;L8aA&quot;&gt;&lt;code&gt;weather&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;jCbi&quot;&gt;Погода на сегодня и прогноз на 7 дней для любого региона (на основе&lt;/p&gt;
  &lt;p id=&quot;2eEM&quot;&gt;&lt;a href=&quot;https://open-meteo.com/&quot; target=&quot;_blank&quot;&gt;Open-Meteo&lt;/a&gt;)&lt;/p&gt;
  &lt;p id=&quot;jroN&quot;&gt;-&lt;/p&gt;
  &lt;p id=&quot;ahlq&quot;&gt;&lt;code&gt;wolfram&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;IYNb&quot;&gt;Запросы WolframAlpha (на базе&lt;/p&gt;
  &lt;p id=&quot;0MiV&quot;&gt;&lt;a href=&quot;https://www.wolframalpha.com/&quot; target=&quot;_blank&quot;&gt;WolframAlpha&lt;/a&gt;)&lt;/p&gt;
  &lt;p id=&quot;Eyyn&quot;&gt;&lt;code&gt;WOLFRAM_APP_ID&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;bEg0&quot;&gt;&lt;code&gt;wolframalpha&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;CCzJ&quot;&gt;&lt;code&gt;ddg_web_search&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;SPEF&quot;&gt;Веб-поиск (на базе&lt;/p&gt;
  &lt;p id=&quot;jWAt&quot;&gt;&lt;a href=&quot;https://duckduckgo.com/&quot; target=&quot;_blank&quot;&gt;DuckDuckGo&lt;/a&gt;)&lt;/p&gt;
  &lt;p id=&quot;j325&quot;&gt;-&lt;/p&gt;
  &lt;p id=&quot;36DA&quot;&gt;&lt;code&gt;duckduckgo_search&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;kygQ&quot;&gt;&lt;code&gt;ddg_image_search&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;0Y8S&quot;&gt;Поиск изображения или GIF-файла (на базе&lt;/p&gt;
  &lt;p id=&quot;ANVa&quot;&gt;&lt;a href=&quot;https://duckduckgo.com/&quot; target=&quot;_blank&quot;&gt;DuckDuckGo&lt;/a&gt;)&lt;/p&gt;
  &lt;p id=&quot;VD3q&quot;&gt;-&lt;/p&gt;
  &lt;p id=&quot;eH4D&quot;&gt;&lt;code&gt;duckduckgo_search&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;sfuU&quot;&gt;&lt;code&gt;crypto&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;KBXQ&quot;&gt;Курс криптовалют в реальном времени (по данным&lt;/p&gt;
  &lt;p id=&quot;dL5U&quot;&gt;&lt;a href=&quot;https://coincap.io/&quot; target=&quot;_blank&quot;&gt;CoinCap&lt;/a&gt;) — от&lt;/p&gt;
  &lt;p id=&quot;h1FQ&quot;&gt;&lt;a href=&quot;https://github.com/stumpyfr&quot; target=&quot;_blank&quot;&gt;@stumpyfr&lt;/a&gt;-&lt;code&gt;spotify&lt;/code&gt;Лучшие треки/исполнители Spotify, текущая воспроизводимая песня и поиск контента (на базе&lt;/p&gt;
  &lt;p id=&quot;h5RB&quot;&gt;&lt;a href=&quot;https://spotify.com/&quot; target=&quot;_blank&quot;&gt;Spotify&lt;/a&gt;).&lt;/p&gt;
  &lt;p id=&quot;YW8U&quot;&gt;Требуется однократная авторизация.&lt;/p&gt;
  &lt;p id=&quot;VHEP&quot;&gt;&lt;code&gt;SPOTIFY_CLIENT_ID&lt;/code&gt;, &lt;code&gt;SPOTIFY_CLIENT_SECRET&lt;/code&gt;, &lt;code&gt;SPOTIFY_REDIRECT_URI&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;eE0K&quot;&gt;&lt;code&gt;spotipy&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;q1xh&quot;&gt;&lt;code&gt;worldtimeapi&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;w1zk&quot;&gt;Получите актуальное мировое время (с помощью&lt;/p&gt;
  &lt;p id=&quot;D6M2&quot;&gt;&lt;a href=&quot;https://worldtimeapi.org/&quot; target=&quot;_blank&quot;&gt;WorldTimeAPI&lt;/a&gt;) — от&lt;/p&gt;
  &lt;p id=&quot;Hm2J&quot;&gt;&lt;a href=&quot;https://github.com/noriellecruz&quot; target=&quot;_blank&quot;&gt;@noriellecruz&lt;/a&gt;&lt;code&gt;WORLDTIME_DEFAULT_TIMEZONEdice&lt;/code&gt;Отправьте кубик в чат!&lt;/p&gt;
  &lt;p id=&quot;T1Dc&quot;&gt;-&lt;/p&gt;
  &lt;p id=&quot;VzP6&quot;&gt;&lt;code&gt;youtube_audio_extractor&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;pxSo&quot;&gt;Извлечение аудио из видео с YouTube&lt;/p&gt;
  &lt;p id=&quot;oSfp&quot;&gt;-&lt;/p&gt;
  &lt;p id=&quot;raPP&quot;&gt;&lt;code&gt;pytube&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;bRjo&quot;&gt;&lt;code&gt;deepl_translate&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;6tSP&quot;&gt;Перевод текста на любой язык (на основе&lt;/p&gt;
  &lt;p id=&quot;SuuE&quot;&gt;&lt;a href=&quot;https://deepl.com/&quot; target=&quot;_blank&quot;&gt;DeepL&lt;/a&gt;) — от&lt;/p&gt;
  &lt;p id=&quot;eYa9&quot;&gt;&lt;a href=&quot;https://github.com/LedyBacer&quot; target=&quot;_blank&quot;&gt;@LedyBacer&lt;/a&gt;&lt;code&gt;DEEPL_API_KEYgtts_text_to_speech&lt;/code&gt;Преобразование текста в речь (с помощью API Google Translate)&lt;/p&gt;
  &lt;p id=&quot;Ej6n&quot;&gt;-&lt;/p&gt;
  &lt;p id=&quot;ELRM&quot;&gt;&lt;code&gt;gtts&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;xCVW&quot;&gt;&lt;code&gt;whois&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;xcEJ&quot;&gt;Запрос к базе данных доменов whois — от&lt;/p&gt;
  &lt;p id=&quot;G6OF&quot;&gt;&lt;a href=&quot;https://github.com/jnaskali&quot; target=&quot;_blank&quot;&gt;@jnaskali&lt;/a&gt;-&lt;code&gt;whoiswebshot&lt;/code&gt;Снимок экрана веб-сайта по указанному URL-адресу или доменному имени — от&lt;/p&gt;
  &lt;p id=&quot;O8uh&quot;&gt;&lt;a href=&quot;https://github.com/noriellecruz&quot; target=&quot;_blank&quot;&gt;@noriellecruz&lt;/a&gt;-&lt;code&gt;auto_tts&lt;/code&gt;Преобразование текста в речь с помощью API OpenAI — от&lt;/p&gt;
  &lt;p id=&quot;cLR0&quot;&gt;&lt;a href=&quot;https://github.com/Jipok&quot; target=&quot;_blank&quot;&gt;@Jipok&lt;/a&gt;-&lt;/p&gt;
  &lt;h4 id=&quot;DVfI&quot;&gt;Переменные среды&lt;/h4&gt;
  &lt;p id=&quot;Ij8k&quot;&gt;Переменная&lt;/p&gt;
  &lt;p id=&quot;5Dke&quot;&gt;Описание&lt;/p&gt;
  &lt;p id=&quot;giLC&quot;&gt;Значение по умолчанию&lt;/p&gt;
  &lt;p id=&quot;xbq4&quot;&gt;&lt;code&gt;WOLFRAM_APP_ID&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;vroE&quot;&gt;Идентификатор приложения Wolfram Alpha (требуется только для плагина&lt;/p&gt;
  &lt;p id=&quot;dGL0&quot;&gt;&lt;code&gt;wolfram&lt;/code&gt;, его можно получить&lt;/p&gt;
  &lt;p id=&quot;rWG2&quot;&gt;&lt;a href=&quot;https://products.wolframalpha.com/simple-api/documentation&quot; target=&quot;_blank&quot;&gt;здесь&lt;/a&gt;)&lt;/p&gt;
  &lt;p id=&quot;VVHg&quot;&gt;-&lt;/p&gt;
  &lt;p id=&quot;nBIP&quot;&gt;&lt;code&gt;SPOTIFY_CLIENT_ID&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;kg77&quot;&gt;Идентификатор клиента Spotify (требуется только для плагина&lt;/p&gt;
  &lt;p id=&quot;Blkt&quot;&gt;&lt;code&gt;spotify&lt;/code&gt;, его можно найти на&lt;/p&gt;
  &lt;p id=&quot;LvsX&quot;&gt;&lt;a href=&quot;https://developer.spotify.com/dashboard/&quot; target=&quot;_blank&quot;&gt;панели управления&lt;/a&gt;)&lt;/p&gt;
  &lt;p id=&quot;oF28&quot;&gt;-&lt;/p&gt;
  &lt;p id=&quot;99Ul&quot;&gt;&lt;code&gt;SPOTIFY_CLIENT_SECRET&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;2jBz&quot;&gt;Секретный ключ клиента Spotify (требуется только для плагина&lt;/p&gt;
  &lt;p id=&quot;De3K&quot;&gt;&lt;code&gt;spotify&lt;/code&gt;, его можно найти на&lt;/p&gt;
  &lt;p id=&quot;Wng4&quot;&gt;&lt;a href=&quot;https://developer.spotify.com/dashboard/&quot; target=&quot;_blank&quot;&gt;панели управления&lt;/a&gt;)&lt;/p&gt;
  &lt;p id=&quot;WGsj&quot;&gt;-&lt;/p&gt;
  &lt;p id=&quot;ylQT&quot;&gt;&lt;code&gt;SPOTIFY_REDIRECT_URI&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;B3GA&quot;&gt;Перенаправляющий URI приложения Spotify (требуется только для плагина&lt;/p&gt;
  &lt;p id=&quot;I0z1&quot;&gt;&lt;code&gt;spotify&lt;/code&gt;, его можно найти на&lt;/p&gt;
  &lt;p id=&quot;M77H&quot;&gt;&lt;a href=&quot;https://developer.spotify.com/dashboard/&quot; target=&quot;_blank&quot;&gt;панели управления&lt;/a&gt;)&lt;/p&gt;
  &lt;p id=&quot;kXzL&quot;&gt;-&lt;/p&gt;
  &lt;p id=&quot;Zp07&quot;&gt;&lt;code&gt;WORLDTIME_DEFAULT_TIMEZONE&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;BH9e&quot;&gt;Часовой пояс по умолчанию, то есть&lt;/p&gt;
  &lt;p id=&quot;abKg&quot;&gt;&lt;code&gt;Europe/Rome&lt;/code&gt; (требуется только для плагина&lt;/p&gt;
  &lt;p id=&quot;EeWM&quot;&gt;&lt;code&gt;worldtimeapi&lt;/code&gt;, идентификаторы часовых поясов можно получить&lt;/p&gt;
  &lt;p id=&quot;M4hf&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/List_of_tz_database_time_zones&quot; target=&quot;_blank&quot;&gt;здесь&lt;/a&gt;)&lt;/p&gt;
  &lt;p id=&quot;hGuP&quot;&gt;-&lt;/p&gt;
  &lt;p id=&quot;3PLd&quot;&gt;&lt;code&gt;DUCKDUCKGO_SAFESEARCH&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;aiyW&quot;&gt;Безопасный поиск DuckDuckGo (&lt;/p&gt;
  &lt;p id=&quot;4hkP&quot;&gt;&lt;code&gt;on&lt;/code&gt;,&lt;/p&gt;
  &lt;p id=&quot;jZVG&quot;&gt;&lt;code&gt;off&lt;/code&gt; или&lt;/p&gt;
  &lt;p id=&quot;rb2N&quot;&gt;&lt;code&gt;moderate&lt;/code&gt;) (необязательно, применяется к&lt;/p&gt;
  &lt;p id=&quot;gW1Z&quot;&gt;&lt;code&gt;ddg_web_search&lt;/code&gt; и&lt;/p&gt;
  &lt;p id=&quot;3AJP&quot;&gt;&lt;code&gt;ddg_image_search&lt;/code&gt;)&lt;/p&gt;
  &lt;p id=&quot;bLX4&quot;&gt;&lt;code&gt;moderate&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;oDv2&quot;&gt;&lt;code&gt;DEEPL_API_KEY&lt;/code&gt;&lt;/p&gt;
  &lt;p id=&quot;FAyb&quot;&gt;Ключ API DeepL (требуется для плагина&lt;/p&gt;
  &lt;p id=&quot;uFPN&quot;&gt;&lt;code&gt;deepl&lt;/code&gt;, его можно получить&lt;/p&gt;
  &lt;p id=&quot;bC9R&quot;&gt;&lt;a href=&quot;https://www.deepl.com/pro-api?cta=header-pro-api&quot; target=&quot;_blank&quot;&gt;здесь&lt;/a&gt;)&lt;/p&gt;
  &lt;p id=&quot;Ot7p&quot;&gt;-&lt;/p&gt;
  &lt;h3 id=&quot;ccPA&quot;&gt;Установка&lt;/h3&gt;
  &lt;p id=&quot;gmHx&quot;&gt;Клонируйте репозиторий и перейдите в каталог проекта:&lt;/p&gt;
  &lt;pre id=&quot;EkKX&quot;&gt;git clone https://github.com/n3d1117/chatgpt-telegram-bot.git
cd chatgpt-telegram-bot&lt;/pre&gt;
  &lt;h4 id=&quot;3yzL&quot;&gt;Из источника&lt;/h4&gt;
  &lt;ol id=&quot;ZHYh&quot;&gt;
    &lt;li id=&quot;uVbh&quot;&gt;Создайте виртуальную среду:&lt;/li&gt;
  &lt;/ol&gt;
  &lt;pre id=&quot;49dx&quot;&gt;python -m venv venv&lt;/pre&gt;
  &lt;ol id=&quot;Harw&quot;&gt;
    &lt;li id=&quot;UgU4&quot;&gt;Активируйте виртуальную среду:&lt;/li&gt;
  &lt;/ol&gt;
  &lt;pre id=&quot;KK0m&quot;&gt;источник#
 Для Linux или macOS: venv/bin/activate

# Для Windows:
venv\Scripts\activate&lt;/pre&gt;
  &lt;ol id=&quot;doL4&quot;&gt;
    &lt;li id=&quot;ng1T&quot;&gt;Установите зависимости, используя файл &lt;code&gt;requirements.txt&lt;/code&gt;:&lt;/li&gt;
  &lt;/ol&gt;
  &lt;pre id=&quot;YDn3&quot;&gt;pip install -r requirements.txt&lt;/pre&gt;
  &lt;ol id=&quot;Nqjn&quot;&gt;
    &lt;li id=&quot;iVgr&quot;&gt;Чтобы запустить бота, используйте следующую команду:&lt;/li&gt;
  &lt;/ol&gt;
  &lt;pre id=&quot;J1hd&quot;&gt;python bot/main.py
&lt;/pre&gt;
  &lt;h4 id=&quot;DjYT&quot;&gt;Использование Docker Compose&lt;/h4&gt;
  &lt;p id=&quot;Rnsf&quot;&gt;Чтобы собрать и запустить образ Docker, выполните следующую команду:&lt;/p&gt;
  &lt;pre id=&quot;ejn1&quot;&gt;создание докера&lt;/pre&gt;
  &lt;h4 id=&quot;Fv93&quot;&gt;Готовые к использованию образы Docker&lt;/h4&gt;
  &lt;p id=&quot;CdoP&quot;&gt;Вы также можете использовать образ Docker из &lt;a href=&quot;https://hub.docker.com/r/n3d1117/chatgpt-telegram-bot&quot; target=&quot;_blank&quot;&gt;Docker Hub&lt;/a&gt;:&lt;/p&gt;
  &lt;pre id=&quot;YXXy&quot;&gt;docker pull n3d1117/chatgpt-telegram-bot: последняя версия
docker run -it --env-file .env n3d1117/chatgpt-telegram-bot&lt;/pre&gt;
  &lt;p id=&quot;NOQI&quot;&gt;или с помощью &lt;a href=&quot;https://github.com/n3d1117/chatgpt-telegram-bot/pkgs/container/chatgpt-telegram-bot/&quot; target=&quot;_blank&quot;&gt;GitHub Container Registry&lt;/a&gt;:&lt;/p&gt;
  &lt;pre id=&quot;svQx&quot;&gt;docker pull ghcr.io/n3d1117/chatgpt-telegram-bot:latest
docker run -it --env-file .env ghcr.io/n3d1117/chatgpt-telegram-bot&lt;/pre&gt;
  &lt;h4 id=&quot;Rk7r&quot;&gt;Ручная сборка Docker&lt;/h4&gt;
  &lt;pre id=&quot;cJsk&quot;&gt;docker build -t chatgpt-telegram-bot .
docker run -it --env-file .env chatgpt-telegram-bot&lt;/pre&gt;
  &lt;h4 id=&quot;0gSd&quot;&gt;Герой&lt;/h4&gt;
  &lt;p id=&quot;Gdg7&quot;&gt;Вот пример &lt;code&gt;Procfile&lt;/code&gt; для развёртывания с помощью Heroku (спасибо &lt;a href=&quot;https://github.com/err09r&quot; target=&quot;_blank&quot;&gt;err09r&lt;/a&gt;!):&lt;/p&gt;
  &lt;pre id=&quot;jM3X&quot;&gt;worker: python -m venv venv &amp;amp;&amp;amp; source venv/bin/activate &amp;amp;&amp;amp; pip install -r requirements.txt &amp;amp;&amp;amp; python bot/main.py
&lt;/pre&gt;
  &lt;h2 id=&quot;Fewr&quot;&gt;Реквизиты&lt;/h2&gt;
  &lt;ul id=&quot;J4ua&quot;&gt;
    &lt;li id=&quot;CqR8&quot;&gt;&lt;a href=&quot;https://chat.openai.com/chat&quot; target=&quot;_blank&quot;&gt;ChatGPT&lt;/a&gt; от &lt;a href=&quot;https://openai.com/&quot; target=&quot;_blank&quot;&gt;OpenAI&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;lpCj&quot;&gt;&lt;a href=&quot;https://python-telegram-bot.org/&quot; target=&quot;_blank&quot;&gt;python-telegram-бот&lt;/a&gt;&lt;/li&gt;
    &lt;li id=&quot;7NCb&quot;&gt;&lt;a href=&quot;https://github.com/jiaaro/pydub&quot; target=&quot;_blank&quot;&gt;jiaaro/pydub&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;seAO&quot;&gt;Отказ от ответственности&lt;/h2&gt;
  &lt;p id=&quot;WoRd&quot;&gt;Это личный проект, который никоим образом не связан с OpenAI.&lt;/p&gt;
  &lt;h2 id=&quot;VEmw&quot;&gt;Сам код:&lt;/h2&gt;
  &lt;h2 id=&quot;GnKn&quot;&gt;✉ .github/ рабочие процессы&lt;/h2&gt;
  &lt;p id=&quot;tlSM&quot;&gt;                 ↪️Опубликовать обновление yaml.&lt;/p&gt;
  &lt;pre id=&quot;Wr3i&quot; data-lang=&quot;python&quot;&gt;# https://docs.github.com/en/actions/publishing-packages/publishing-docker-images&lt;/pre&gt;
  &lt;pre id=&quot;hf9l&quot; data-lang=&quot;python&quot;&gt;name: Publish Docker image&lt;/pre&gt;
  &lt;pre id=&quot;OFw1&quot; data-lang=&quot;python&quot;&gt;on:
  release:
    types: [published]&lt;/pre&gt;
  &lt;pre id=&quot;DE6a&quot; data-lang=&quot;python&quot;&gt;jobs:
  push_to_registries:
    name: Push Docker image to multiple registries
    runs-on: ubuntu-latest
    permissions:
      packages: write
      contents: read
    steps:
      - name: Check out the repo
        uses: actions/checkout@v3
        
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v2&lt;/pre&gt;
  &lt;pre id=&quot;VkFH&quot;&gt;      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2&lt;/pre&gt;
  &lt;pre id=&quot;wvkh&quot;&gt;      - name: Log in to Docker Hub
        uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}&lt;/pre&gt;
  &lt;pre id=&quot;T5jW&quot;&gt;      - name: Log in to the Container registry
        uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}&lt;/pre&gt;
  &lt;pre id=&quot;etlc&quot;&gt;      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
        with:
          images: |
            n3d1117/chatgpt-telegram-bot
            ghcr.io/${{ github.repository }}&lt;/pre&gt;
  &lt;pre id=&quot;6dwa&quot;&gt;      - name: Build and push Docker images
        uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}&lt;/pre&gt;
  &lt;h2 id=&quot;CxVR&quot;&gt;&lt;/h2&gt;
  &lt;h2 id=&quot;jygQ&quot;&gt;✉Бот&lt;/h2&gt;
  &lt;h3 id=&quot;06da&quot;&gt;     ✉ Плагины&lt;/h3&gt;
  &lt;h3 id=&quot;Rmlj&quot;&gt;              ↪️ auto_tts.py &lt;/h3&gt;
  &lt;pre id=&quot;UO1i&quot;&gt;import logging
import tempfile
from typing import Dict&lt;/pre&gt;
  &lt;pre id=&quot;HCL6&quot;&gt;from .plugin import Plugin&lt;/pre&gt;
  &lt;pre id=&quot;G8M0&quot;&gt;
class AutoTextToSpeech(Plugin):
    &amp;quot;&amp;quot;&amp;quot;
    A plugin to convert text to speech using Openai Speech API
    &amp;quot;&amp;quot;&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;SU6t&quot;&gt;    def get_source_name(self) -&amp;gt; str:
        return &amp;quot;TTS&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;mhm9&quot;&gt;    def get_spec(self) -&amp;gt; [Dict]:
        return [{
            &amp;quot;name&amp;quot;: &amp;quot;translate_text_to_speech&amp;quot;,
            &amp;quot;description&amp;quot;: &amp;quot;Translate text to speech using OpenAI API&amp;quot;,
            &amp;quot;parameters&amp;quot;: {
                &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
                &amp;quot;properties&amp;quot;: {
                    &amp;quot;text&amp;quot;: {&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;, &amp;quot;description&amp;quot;: &amp;quot;The text to translate to speech&amp;quot;},
                },
                &amp;quot;required&amp;quot;: [&amp;quot;text&amp;quot;],
            },
        }]&lt;/pre&gt;
  &lt;pre id=&quot;HgPE&quot;&gt;    async def execute(self, function_name, helper, **kwargs) -&amp;gt; Dict:
        try:
            bytes, text_length = await helper.generate_speech(text=kwargs[&amp;#x27;text&amp;#x27;])
            with tempfile.NamedTemporaryFile(delete=False, suffix=&amp;#x27;.opus&amp;#x27;) as temp_file:
                temp_file.write(bytes.getvalue())
                temp_file_path = temp_file.name
        except Exception as e:
            logging.exception(e)
            return {&amp;quot;Result&amp;quot;: &amp;quot;Exception: &amp;quot; + str(e)}
        return {
            &amp;#x27;direct_result&amp;#x27;: {
                &amp;#x27;kind&amp;#x27;: &amp;#x27;file&amp;#x27;,
                &amp;#x27;format&amp;#x27;: &amp;#x27;path&amp;#x27;,
                &amp;#x27;value&amp;#x27;: temp_file_path
            }&lt;/pre&gt;
  &lt;p id=&quot;GOPb&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;DnGt&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;rR1i&quot;&gt;Плагин ↪️crypto.py&lt;/h3&gt;
  &lt;pre id=&quot;e3li&quot;&gt;from typing import Dict&lt;/pre&gt;
  &lt;pre id=&quot;gKnx&quot;&gt;import requests&lt;/pre&gt;
  &lt;pre id=&quot;kNQd&quot;&gt;from .plugin import Plugin&lt;/pre&gt;
  &lt;pre id=&quot;GYJh&quot;&gt;
# Author: https://github.com/stumpyfr
class CryptoPlugin(Plugin):
    &amp;quot;&amp;quot;&amp;quot;
    A plugin to fetch the current rate of various cryptocurrencies
    &amp;quot;&amp;quot;&amp;quot;
    def get_source_name(self) -&amp;gt; str:
        return &amp;quot;CoinCap&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;8s1k&quot;&gt;    def get_spec(self) -&amp;gt; [Dict]:
        return [{
            &amp;quot;name&amp;quot;: &amp;quot;get_crypto_rate&amp;quot;,
            &amp;quot;description&amp;quot;: &amp;quot;Get the current rate of various crypto currencies&amp;quot;,
            &amp;quot;parameters&amp;quot;: {
                &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
                &amp;quot;properties&amp;quot;: {
                    &amp;quot;asset&amp;quot;: {&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;, &amp;quot;description&amp;quot;: &amp;quot;Asset of the crypto&amp;quot;}
                },
                &amp;quot;required&amp;quot;: [&amp;quot;asset&amp;quot;],
            },
        }]&lt;/pre&gt;
  &lt;pre id=&quot;eUhe&quot;&gt;    async def execute(self, function_name, helper, **kwargs) -&amp;gt; Dict:
        return requests.get(f&amp;quot;https://api.coincap.io/v2/rates/{kwargs[&amp;#x27;asset&amp;#x27;]}&amp;quot;).json()&lt;/pre&gt;
  &lt;p id=&quot;9f6f&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;UhKq&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;m5g7&quot;&gt;Плагин ↪️ddg_image_search.py&lt;/h3&gt;
  &lt;pre id=&quot;ce8g&quot;&gt;import os
import random
from itertools import islice
from typing import Dict&lt;/pre&gt;
  &lt;pre id=&quot;gPEa&quot;&gt;from duckduckgo_search import DDGS&lt;/pre&gt;
  &lt;pre id=&quot;0CTP&quot;&gt;from .plugin import Plugin&lt;/pre&gt;
  &lt;pre id=&quot;lmkQ&quot;&gt;
class DDGImageSearchPlugin(Plugin):
    &amp;quot;&amp;quot;&amp;quot;
    A plugin to search images and GIFs for a given query, using DuckDuckGo
    &amp;quot;&amp;quot;&amp;quot;
    def __init__(self):
        self.safesearch = os.getenv(&amp;#x27;DUCKDUCKGO_SAFESEARCH&amp;#x27;, &amp;#x27;moderate&amp;#x27;)&lt;/pre&gt;
  &lt;pre id=&quot;DDHa&quot;&gt;    def get_source_name(self) -&amp;gt; str:
        return &amp;quot;DuckDuckGo Images&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;zhUv&quot;&gt;    def get_spec(self) -&amp;gt; [Dict]:
        return [{
            &amp;quot;name&amp;quot;: &amp;quot;search_images&amp;quot;,
            &amp;quot;description&amp;quot;: &amp;quot;Search image or GIFs for a given query&amp;quot;,
            &amp;quot;parameters&amp;quot;: {
                &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
                &amp;quot;properties&amp;quot;: {
                    &amp;quot;query&amp;quot;: {&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;, &amp;quot;description&amp;quot;: &amp;quot;The query to search for&amp;quot;},
                    &amp;quot;type&amp;quot;: {
                        &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
                        &amp;quot;enum&amp;quot;: [&amp;quot;photo&amp;quot;, &amp;quot;gif&amp;quot;],
                        &amp;quot;description&amp;quot;: &amp;quot;The type of image to search for. Default to &amp;#x60;photo&amp;#x60; if not specified&amp;quot;,
                    },
                    &amp;quot;region&amp;quot;: {
                        &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
                        &amp;quot;enum&amp;quot;: [&amp;#x27;xa-ar&amp;#x27;, &amp;#x27;xa-en&amp;#x27;, &amp;#x27;ar-es&amp;#x27;, &amp;#x27;au-en&amp;#x27;, &amp;#x27;at-de&amp;#x27;, &amp;#x27;be-fr&amp;#x27;, &amp;#x27;be-nl&amp;#x27;, &amp;#x27;br-pt&amp;#x27;, &amp;#x27;bg-bg&amp;#x27;,
                                 &amp;#x27;ca-en&amp;#x27;, &amp;#x27;ca-fr&amp;#x27;, &amp;#x27;ct-ca&amp;#x27;, &amp;#x27;cl-es&amp;#x27;, &amp;#x27;cn-zh&amp;#x27;, &amp;#x27;co-es&amp;#x27;, &amp;#x27;hr-hr&amp;#x27;, &amp;#x27;cz-cs&amp;#x27;, &amp;#x27;dk-da&amp;#x27;,
                                 &amp;#x27;ee-et&amp;#x27;, &amp;#x27;fi-fi&amp;#x27;, &amp;#x27;fr-fr&amp;#x27;, &amp;#x27;de-de&amp;#x27;, &amp;#x27;gr-el&amp;#x27;, &amp;#x27;hk-tzh&amp;#x27;, &amp;#x27;hu-hu&amp;#x27;, &amp;#x27;in-en&amp;#x27;, &amp;#x27;id-id&amp;#x27;,
                                 &amp;#x27;id-en&amp;#x27;, &amp;#x27;ie-en&amp;#x27;, &amp;#x27;il-he&amp;#x27;, &amp;#x27;it-it&amp;#x27;, &amp;#x27;jp-jp&amp;#x27;, &amp;#x27;kr-kr&amp;#x27;, &amp;#x27;lv-lv&amp;#x27;, &amp;#x27;lt-lt&amp;#x27;, &amp;#x27;xl-es&amp;#x27;,
                                 &amp;#x27;my-ms&amp;#x27;, &amp;#x27;my-en&amp;#x27;, &amp;#x27;mx-es&amp;#x27;, &amp;#x27;nl-nl&amp;#x27;, &amp;#x27;nz-en&amp;#x27;, &amp;#x27;no-no&amp;#x27;, &amp;#x27;pe-es&amp;#x27;, &amp;#x27;ph-en&amp;#x27;, &amp;#x27;ph-tl&amp;#x27;,
                                 &amp;#x27;pl-pl&amp;#x27;, &amp;#x27;pt-pt&amp;#x27;, &amp;#x27;ro-ro&amp;#x27;, &amp;#x27;ru-ru&amp;#x27;, &amp;#x27;sg-en&amp;#x27;, &amp;#x27;sk-sk&amp;#x27;, &amp;#x27;sl-sl&amp;#x27;, &amp;#x27;za-en&amp;#x27;, &amp;#x27;es-es&amp;#x27;,
                                 &amp;#x27;se-sv&amp;#x27;, &amp;#x27;ch-de&amp;#x27;, &amp;#x27;ch-fr&amp;#x27;, &amp;#x27;ch-it&amp;#x27;, &amp;#x27;tw-tzh&amp;#x27;, &amp;#x27;th-th&amp;#x27;, &amp;#x27;tr-tr&amp;#x27;, &amp;#x27;ua-uk&amp;#x27;, &amp;#x27;uk-en&amp;#x27;,
                                 &amp;#x27;us-en&amp;#x27;, &amp;#x27;ue-es&amp;#x27;, &amp;#x27;ve-es&amp;#x27;, &amp;#x27;vn-vi&amp;#x27;, &amp;#x27;wt-wt&amp;#x27;],
                        &amp;quot;description&amp;quot;: &amp;quot;The region to use for the search. Infer this from the language used for the&amp;quot;
                                       &amp;quot;query. Default to &amp;#x60;wt-wt&amp;#x60; if not specified&amp;quot;,
                    }
                },
                &amp;quot;required&amp;quot;: [&amp;quot;query&amp;quot;, &amp;quot;type&amp;quot;, &amp;quot;region&amp;quot;],
            },
        }]&lt;/pre&gt;
  &lt;pre id=&quot;Jm00&quot;&gt;    async def execute(self, function_name, helper, **kwargs) -&amp;gt; Dict:
        with DDGS() as ddgs:
            image_type = kwargs.get(&amp;#x27;type&amp;#x27;, &amp;#x27;photo&amp;#x27;)
            ddgs_images_gen = ddgs.images(
                kwargs[&amp;#x27;query&amp;#x27;],
                region=kwargs.get(&amp;#x27;region&amp;#x27;, &amp;#x27;wt-wt&amp;#x27;),
                safesearch=self.safesearch,
                type_image=image_type,
            )
            results = list(islice(ddgs_images_gen, 10))
            if not results or len(results) == 0:
                return {&amp;quot;result&amp;quot;: &amp;quot;No results found&amp;quot;}&lt;/pre&gt;
  &lt;pre id=&quot;MfDH&quot;&gt;            # Shuffle the results to avoid always returning the same image
            random.shuffle(results)&lt;/pre&gt;
  &lt;pre id=&quot;bSUL&quot;&gt;            return {
                &amp;#x27;direct_result&amp;#x27;: {
                    &amp;#x27;kind&amp;#x27;: image_type,
                    &amp;#x27;format&amp;#x27;: &amp;#x27;url&amp;#x27;,
                    &amp;#x27;value&amp;#x27;: results[0][&amp;#x27;image&amp;#x27;]
                }
            }&lt;/pre&gt;
  &lt;p id=&quot;ydh8&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;CJE2&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;fVWS&quot;&gt;Плагин ↪️ddg_web_search.py&lt;/h3&gt;
  &lt;pre id=&quot;v8v4&quot;&gt;import os
from itertools import islice
from typing import Dict&lt;/pre&gt;
  &lt;pre id=&quot;VIu0&quot;&gt;from duckduckgo_search import DDGS&lt;/pre&gt;
  &lt;pre id=&quot;thw7&quot;&gt;from .plugin import Plugin&lt;/pre&gt;
  &lt;pre id=&quot;lCgO&quot;&gt;
class DDGWebSearchPlugin(Plugin):
    &amp;quot;&amp;quot;&amp;quot;
    A plugin to search the web for a given query, using DuckDuckGo
    &amp;quot;&amp;quot;&amp;quot;
    def __init__(self):
        self.safesearch = os.getenv(&amp;#x27;DUCKDUCKGO_SAFESEARCH&amp;#x27;, &amp;#x27;moderate&amp;#x27;)&lt;/pre&gt;
  &lt;pre id=&quot;5Brw&quot;&gt;    def get_source_name(self) -&amp;gt; str:
        return &amp;quot;DuckDuckGo&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;I4Xj&quot;&gt;    def get_spec(self) -&amp;gt; [Dict]:
        return [{
            &amp;quot;name&amp;quot;: &amp;quot;web_search&amp;quot;,
            &amp;quot;description&amp;quot;: &amp;quot;Execute a web search for the given query and return a list of results&amp;quot;,
            &amp;quot;parameters&amp;quot;: {
                &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
                &amp;quot;properties&amp;quot;: {
                    &amp;quot;query&amp;quot;: {
                        &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
                        &amp;quot;description&amp;quot;: &amp;quot;the user query&amp;quot;
                    },
                    &amp;quot;region&amp;quot;: {
                        &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
                        &amp;quot;enum&amp;quot;: [&amp;#x27;xa-ar&amp;#x27;, &amp;#x27;xa-en&amp;#x27;, &amp;#x27;ar-es&amp;#x27;, &amp;#x27;au-en&amp;#x27;, &amp;#x27;at-de&amp;#x27;, &amp;#x27;be-fr&amp;#x27;, &amp;#x27;be-nl&amp;#x27;, &amp;#x27;br-pt&amp;#x27;, &amp;#x27;bg-bg&amp;#x27;,
                                 &amp;#x27;ca-en&amp;#x27;, &amp;#x27;ca-fr&amp;#x27;, &amp;#x27;ct-ca&amp;#x27;, &amp;#x27;cl-es&amp;#x27;, &amp;#x27;cn-zh&amp;#x27;, &amp;#x27;co-es&amp;#x27;, &amp;#x27;hr-hr&amp;#x27;, &amp;#x27;cz-cs&amp;#x27;, &amp;#x27;dk-da&amp;#x27;,
                                 &amp;#x27;ee-et&amp;#x27;, &amp;#x27;fi-fi&amp;#x27;, &amp;#x27;fr-fr&amp;#x27;, &amp;#x27;de-de&amp;#x27;, &amp;#x27;gr-el&amp;#x27;, &amp;#x27;hk-tzh&amp;#x27;, &amp;#x27;hu-hu&amp;#x27;, &amp;#x27;in-en&amp;#x27;, &amp;#x27;id-id&amp;#x27;,
                                 &amp;#x27;id-en&amp;#x27;, &amp;#x27;ie-en&amp;#x27;, &amp;#x27;il-he&amp;#x27;, &amp;#x27;it-it&amp;#x27;, &amp;#x27;jp-jp&amp;#x27;, &amp;#x27;kr-kr&amp;#x27;, &amp;#x27;lv-lv&amp;#x27;, &amp;#x27;lt-lt&amp;#x27;, &amp;#x27;xl-es&amp;#x27;,
                                 &amp;#x27;my-ms&amp;#x27;, &amp;#x27;my-en&amp;#x27;, &amp;#x27;mx-es&amp;#x27;, &amp;#x27;nl-nl&amp;#x27;, &amp;#x27;nz-en&amp;#x27;, &amp;#x27;no-no&amp;#x27;, &amp;#x27;pe-es&amp;#x27;, &amp;#x27;ph-en&amp;#x27;, &amp;#x27;ph-tl&amp;#x27;,
                                 &amp;#x27;pl-pl&amp;#x27;, &amp;#x27;pt-pt&amp;#x27;, &amp;#x27;ro-ro&amp;#x27;, &amp;#x27;ru-ru&amp;#x27;, &amp;#x27;sg-en&amp;#x27;, &amp;#x27;sk-sk&amp;#x27;, &amp;#x27;sl-sl&amp;#x27;, &amp;#x27;za-en&amp;#x27;, &amp;#x27;es-es&amp;#x27;,
                                 &amp;#x27;se-sv&amp;#x27;, &amp;#x27;ch-de&amp;#x27;, &amp;#x27;ch-fr&amp;#x27;, &amp;#x27;ch-it&amp;#x27;, &amp;#x27;tw-tzh&amp;#x27;, &amp;#x27;th-th&amp;#x27;, &amp;#x27;tr-tr&amp;#x27;, &amp;#x27;ua-uk&amp;#x27;, &amp;#x27;uk-en&amp;#x27;,
                                 &amp;#x27;us-en&amp;#x27;, &amp;#x27;ue-es&amp;#x27;, &amp;#x27;ve-es&amp;#x27;, &amp;#x27;vn-vi&amp;#x27;, &amp;#x27;wt-wt&amp;#x27;],
                        &amp;quot;description&amp;quot;: &amp;quot;The region to use for the search. Infer this from the language used for the&amp;quot;
                                       &amp;quot;query. Default to &amp;#x60;wt-wt&amp;#x60; if not specified&amp;quot;,
                    }
                },
                &amp;quot;required&amp;quot;: [&amp;quot;query&amp;quot;, &amp;quot;region&amp;quot;],
            },
        }]&lt;/pre&gt;
  &lt;pre id=&quot;HvAy&quot;&gt;    async def execute(self, function_name, helper, **kwargs) -&amp;gt; Dict:
        with DDGS() as ddgs:
            ddgs_gen = ddgs.text(
                kwargs[&amp;#x27;query&amp;#x27;],
                region=kwargs.get(&amp;#x27;region&amp;#x27;, &amp;#x27;wt-wt&amp;#x27;),
                safesearch=self.safesearch
            )
            results = list(islice(ddgs_gen, 3))&lt;/pre&gt;
  &lt;pre id=&quot;OBDy&quot;&gt;            if results is None or len(results) == 0:
                return {&amp;quot;Result&amp;quot;: &amp;quot;No good DuckDuckGo Search Result was found&amp;quot;}&lt;/pre&gt;
  &lt;pre id=&quot;3wyr&quot;&gt;            def to_metadata(result: Dict) -&amp;gt; Dict[str, str]:
                return {
                    &amp;quot;snippet&amp;quot;: result[&amp;quot;body&amp;quot;],
                    &amp;quot;title&amp;quot;: result[&amp;quot;title&amp;quot;],
                    &amp;quot;link&amp;quot;: result[&amp;quot;href&amp;quot;],
                }
            return {&amp;quot;result&amp;quot;: [to_metadata(result) for result in results]}&lt;/pre&gt;
  &lt;p id=&quot;yS91&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;bsB4&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;dfjT&quot;&gt;Плагин  ↪️deepl.py&lt;/h3&gt;
  &lt;pre id=&quot;X7Rj&quot;&gt;mport os
from typing import Dict&lt;/pre&gt;
  &lt;pre id=&quot;o5ih&quot;&gt;import requests&lt;/pre&gt;
  &lt;pre id=&quot;KGnB&quot;&gt;from .plugin import Plugin&lt;/pre&gt;
  &lt;pre id=&quot;uEJo&quot;&gt;
class DeeplTranslatePlugin(Plugin):
    &amp;quot;&amp;quot;&amp;quot;
    A plugin to translate a given text from a language to another, using DeepL
    &amp;quot;&amp;quot;&amp;quot;
    def __init__(self):
        deepl_api_key = os.getenv(&amp;#x27;DEEPL_API_KEY&amp;#x27;)
        if not deepl_api_key:
            raise ValueError(&amp;#x27;DEEPL_API_KEY environment variable must be set to use DeepL Plugin&amp;#x27;)
        self.api_key = deepl_api_key&lt;/pre&gt;
  &lt;pre id=&quot;SLwu&quot;&gt;    def get_source_name(self) -&amp;gt; str:
        return &amp;quot;DeepL Translate&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;QCdK&quot;&gt;    def get_spec(self) -&amp;gt; [Dict]:
        return [{
            &amp;quot;name&amp;quot;: &amp;quot;translate&amp;quot;,
            &amp;quot;description&amp;quot;: &amp;quot;Translate a given text from a language to another&amp;quot;,
            &amp;quot;parameters&amp;quot;: {
                &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
                &amp;quot;properties&amp;quot;: {
                    &amp;quot;text&amp;quot;: {&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;, &amp;quot;description&amp;quot;: &amp;quot;The text to translate&amp;quot;},
                    &amp;quot;to_language&amp;quot;: {&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;, &amp;quot;description&amp;quot;: &amp;quot;The language to translate to (e.g. &amp;#x27;it&amp;#x27;)&amp;quot;}
                },
                &amp;quot;required&amp;quot;: [&amp;quot;text&amp;quot;, &amp;quot;to_language&amp;quot;],
            },
        }]&lt;/pre&gt;
  &lt;pre id=&quot;9EiH&quot;&gt;    async def execute(self, function_name, helper, **kwargs) -&amp;gt; Dict:
        if self.api_key.endswith(&amp;#x27;:fx&amp;#x27;):
            url = &amp;quot;https://api-free.deepl.com/v2/translate&amp;quot;
        else:
            url = &amp;quot;https://api.deepl.com/v2/translate&amp;quot;
             
        headers = {
            &amp;quot;Authorization&amp;quot;: f&amp;quot;DeepL-Auth-Key {self.api_key}&amp;quot;,
            &amp;quot;User-Agent&amp;quot;: &amp;quot;chatgpt-telegram-bot&amp;quot;,
            &amp;quot;Content-Type&amp;quot;: &amp;quot;application/x-www-form-urlencoded&amp;quot;,
            &amp;quot;Accept-Encoding&amp;quot;: &amp;quot;utf-8&amp;quot;
        }
        data = {
            &amp;quot;text&amp;quot;: kwargs[&amp;#x27;text&amp;#x27;],
            &amp;quot;target_lang&amp;quot;: kwargs[&amp;#x27;to_language&amp;#x27;]
        }
        translated_text = requests.post(url, headers=headers, data=data).json()[&amp;quot;translations&amp;quot;][0][&amp;quot;text&amp;quot;]
        return translated_text.encode(&amp;#x27;unicode-escape&amp;#x27;).decode(&amp;#x27;unicode-escape&amp;#x27;)&lt;/pre&gt;
  &lt;p id=&quot;LSJP&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;FLuE&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;0V67&quot;&gt;Плагин   ↪️dice.py&lt;/h3&gt;
  &lt;pre id=&quot;1DBp&quot;&gt;from typing import Dict&lt;/pre&gt;
  &lt;pre id=&quot;cqtx&quot;&gt;from .plugin import Plugin&lt;/pre&gt;
  &lt;pre id=&quot;5Qm9&quot;&gt;
class DicePlugin(Plugin):
    &amp;quot;&amp;quot;&amp;quot;
    A plugin to send a die in the chat
    &amp;quot;&amp;quot;&amp;quot;
    def get_source_name(self) -&amp;gt; str:
        return &amp;quot;Dice&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;64fl&quot;&gt;    def get_spec(self) -&amp;gt; [Dict]:
        return [{
            &amp;quot;name&amp;quot;: &amp;quot;send_dice&amp;quot;,
            &amp;quot;description&amp;quot;: &amp;quot;Send a dice in the chat, with a random number between 1 and 6&amp;quot;,
            &amp;quot;parameters&amp;quot;: {
                &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
                &amp;quot;properties&amp;quot;: {
                    &amp;quot;emoji&amp;quot;: {
                        &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
                        &amp;quot;enum&amp;quot;: [&amp;quot;🎲&amp;quot;, &amp;quot;🎯&amp;quot;, &amp;quot;🏀&amp;quot;, &amp;quot;⚽&amp;quot;, &amp;quot;🎳&amp;quot;, &amp;quot;🎰&amp;quot;],
                        &amp;quot;description&amp;quot;: &amp;quot;Emoji on which the dice throw animation is based.&amp;quot;
                                       &amp;quot;Dice can have values 1-6 for “🎲”, “🎯” and “🎳”, values 1-5 for “🏀” &amp;quot;
                                       &amp;quot;and “⚽”, and values 1-64 for “🎰”. Defaults to “🎲”.&amp;quot;,
                    }
                },
            },
        }]&lt;/pre&gt;
  &lt;pre id=&quot;kDY8&quot;&gt;    async def execute(self, function_name, helper, **kwargs) -&amp;gt; Dict:
        return {
            &amp;#x27;direct_result&amp;#x27;: {
                &amp;#x27;kind&amp;#x27;: &amp;#x27;dice&amp;#x27;,
                &amp;#x27;format&amp;#x27;: &amp;#x27;dice&amp;#x27;,
                &amp;#x27;value&amp;#x27;: kwargs.get(&amp;#x27;emoji&amp;#x27;, &amp;#x27;🎲&amp;#x27;)
            }
        }&lt;/pre&gt;
  &lt;p id=&quot;UiAN&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;7HG7&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;dkzV&quot;&gt;Плагин   ↪️gtts_преобразование_текста_в речь с помощью API&lt;/h3&gt;
  &lt;pre id=&quot;6IS0&quot;&gt;import datetime
from typing import Dict&lt;/pre&gt;
  &lt;pre id=&quot;OM6s&quot;&gt;from gtts import gTTS&lt;/pre&gt;
  &lt;pre id=&quot;4eYP&quot;&gt;from .plugin import Plugin&lt;/pre&gt;
  &lt;pre id=&quot;9PJT&quot;&gt;
class GTTSTextToSpeech(Plugin):
    &amp;quot;&amp;quot;&amp;quot;
    A plugin to convert text to speech using Google Translate&amp;#x27;s Text to Speech API
    &amp;quot;&amp;quot;&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;wIzf&quot;&gt;    def get_source_name(self) -&amp;gt; str:
        return &amp;quot;gTTS&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;bqFG&quot;&gt;    def get_spec(self) -&amp;gt; [Dict]:
        return [{
            &amp;quot;name&amp;quot;: &amp;quot;google_translate_text_to_speech&amp;quot;,
            &amp;quot;description&amp;quot;: &amp;quot;Translate text to speech using Google Translate&amp;#x27;s Text to Speech API&amp;quot;,
            &amp;quot;parameters&amp;quot;: {
                &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
                &amp;quot;properties&amp;quot;: {
                    &amp;quot;text&amp;quot;: {&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;, &amp;quot;description&amp;quot;: &amp;quot;The text to translate to speech&amp;quot;},
                    &amp;quot;lang&amp;quot;: {
                        &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;, &amp;quot;description&amp;quot;: &amp;quot;The language of the text to translate to speech.&amp;quot;
                                                         &amp;quot;Infer this from the language of the text.&amp;quot;,
                    },
                },
                &amp;quot;required&amp;quot;: [&amp;quot;text&amp;quot;, &amp;quot;lang&amp;quot;],
            },
        }]&lt;/pre&gt;
  &lt;pre id=&quot;q4Rt&quot;&gt;    async def execute(self, function_name, helper, **kwargs) -&amp;gt; Dict:
        tts = gTTS(kwargs[&amp;#x27;text&amp;#x27;], lang=kwargs.get(&amp;#x27;lang&amp;#x27;, &amp;#x27;en&amp;#x27;))
        output = f&amp;#x27;gtts_{datetime.datetime.now().timestamp()}.mp3&amp;#x27;
        tts.save(output)
        return {
            &amp;#x27;direct_result&amp;#x27;: {
                &amp;#x27;kind&amp;#x27;: &amp;#x27;file&amp;#x27;,
                &amp;#x27;format&amp;#x27;: &amp;#x27;path&amp;#x27;,
                &amp;#x27;value&amp;#x27;: output
            }
        }&lt;/pre&gt;
  &lt;p id=&quot;cInU&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;8w8D&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;vUKv&quot;&gt;Плагин   ↪️iplocation.py&lt;/h3&gt;
  &lt;pre id=&quot;fPaV&quot;&gt;import requests
from typing import Dict&lt;/pre&gt;
  &lt;pre id=&quot;2hsx&quot;&gt;from .plugin import Plugin&lt;/pre&gt;
  &lt;pre id=&quot;l30K&quot;&gt;
class IpLocationPlugin(Plugin):
    &amp;quot;&amp;quot;&amp;quot;
    A plugin to get geolocation and other information for a given IP address
    &amp;quot;&amp;quot;&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;Uuih&quot;&gt;    def get_source_name(self) -&amp;gt; str:
        return &amp;quot;IP.FM&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;kG4r&quot;&gt;    def get_spec(self) -&amp;gt; [Dict]:
        return [{
            &amp;quot;name&amp;quot;: &amp;quot;iplocation&amp;quot;,
            &amp;quot;description&amp;quot;: &amp;quot;Get information for an IP address using the IP.FM API.&amp;quot;,
            &amp;quot;parameters&amp;quot;: {
                &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
                &amp;quot;properties&amp;quot;: {
                    &amp;quot;ip&amp;quot;: {&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;, &amp;quot;description&amp;quot;: &amp;quot;IP Address&amp;quot;}
                },
                &amp;quot;required&amp;quot;: [&amp;quot;ip&amp;quot;],
            },
        }]
        
    async def execute(self, function_name, helper, **kwargs) -&amp;gt; Dict:
        ip = kwargs.get(&amp;#x27;ip&amp;#x27;)
        BASE_URL = &amp;quot;https://api.ip.fm/?ip={}&amp;quot;
        url = BASE_URL.format(ip)
        try:
            response = requests.get(url)
            response_data = response.json()
            country = response_data.get(&amp;#x27;data&amp;#x27;, {}).get(&amp;#x27;country&amp;#x27;, &amp;quot;None&amp;quot;)
            subdivisions = response_data.get(&amp;#x27;data&amp;#x27;, {}).get(&amp;#x27;subdivisions&amp;#x27;, &amp;quot;None&amp;quot;)
            city = response_data.get(&amp;#x27;data&amp;#x27;, {}).get(&amp;#x27;city&amp;#x27;, &amp;quot;None&amp;quot;)
            location = &amp;#x27;, &amp;#x27;.join(filter(None, [country, subdivisions, city])) or &amp;quot;None&amp;quot;
        
            asn = response_data.get(&amp;#x27;data&amp;#x27;, {}).get(&amp;#x27;asn&amp;#x27;, &amp;quot;None&amp;quot;)
            as_name = response_data.get(&amp;#x27;data&amp;#x27;, {}).get(&amp;#x27;as_name&amp;#x27;, &amp;quot;None&amp;quot;)
            as_domain = response_data.get(&amp;#x27;data&amp;#x27;, {}).get(&amp;#x27;as_domain&amp;#x27;, &amp;quot;None&amp;quot;)       
            return {&amp;quot;Location&amp;quot;: location, &amp;quot;ASN&amp;quot;: asn, &amp;quot;AS Name&amp;quot;: as_name, &amp;quot;AS Domain&amp;quot;: as_domain}
        except Exception as e:
            return {&amp;quot;Error&amp;quot;: str(e)}&lt;/pre&gt;
  &lt;p id=&quot;73n0&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;j5vl&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;lJ0P&quot;&gt;Плагин   ↪️plugin.py&lt;/h3&gt;
  &lt;pre id=&quot;Wau0&quot;&gt;from abc import abstractmethod, ABC
from typing import Dict&lt;/pre&gt;
  &lt;pre id=&quot;ewe8&quot;&gt;
class Plugin(ABC):
    &amp;quot;&amp;quot;&amp;quot;
    A plugin interface which can be used to create plugins for the ChatGPT API.
    &amp;quot;&amp;quot;&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;m29e&quot;&gt;    @abstractmethod
    def get_source_name(self) -&amp;gt; str:
        &amp;quot;&amp;quot;&amp;quot;
        Return the name of the source of the plugin.
        &amp;quot;&amp;quot;&amp;quot;
        pass&lt;/pre&gt;
  &lt;pre id=&quot;5YxX&quot;&gt;    @abstractmethod
    def get_spec(self) -&amp;gt; [Dict]:
        &amp;quot;&amp;quot;&amp;quot;
        Function specs in the form of JSON schema as specified in the OpenAI documentation:
        https://platform.openai.com/docs/api-reference/chat/create#chat/create-functions
        &amp;quot;&amp;quot;&amp;quot;
        pass&lt;/pre&gt;
  &lt;pre id=&quot;F5vV&quot;&gt;    @abstractmethod
    async def execute(self, function_name, helper, **kwargs) -&amp;gt; Dict:
        &amp;quot;&amp;quot;&amp;quot;
        Execute the plugin and return a JSON serializable response
        &amp;quot;&amp;quot;&amp;quot;
        pass&lt;/pre&gt;
  &lt;p id=&quot;iFMr&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;3gel&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;1bcq&quot;&gt;Плагин   ↪️spotify.py&lt;/h3&gt;
  &lt;pre id=&quot;oi6n&quot;&gt;import os
from typing import Dict&lt;/pre&gt;
  &lt;pre id=&quot;6dzv&quot;&gt;import spotipy
from spotipy import SpotifyOAuth&lt;/pre&gt;
  &lt;pre id=&quot;NRbQ&quot;&gt;from .plugin import Plugin&lt;/pre&gt;
  &lt;pre id=&quot;0Gfr&quot;&gt;
class SpotifyPlugin(Plugin):
    &amp;quot;&amp;quot;&amp;quot;
    A plugin to fetch information from Spotify
    &amp;quot;&amp;quot;&amp;quot;
    def __init__(self):
        spotify_client_id = os.getenv(&amp;#x27;SPOTIFY_CLIENT_ID&amp;#x27;)
        spotify_client_secret = os.getenv(&amp;#x27;SPOTIFY_CLIENT_SECRET&amp;#x27;)
        spotify_redirect_uri = os.getenv(&amp;#x27;SPOTIFY_REDIRECT_URI&amp;#x27;)
        if not spotify_client_id or not spotify_client_secret or not spotify_redirect_uri:
            raise ValueError(&amp;#x27;SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET and SPOTIFY_REDIRECT_URI environment variables&amp;#x27;
                             &amp;#x27; are required to use SpotifyPlugin&amp;#x27;)
        self.spotify = spotipy.Spotify(
            auth_manager=SpotifyOAuth(
                client_id=spotify_client_id,
                client_secret=spotify_client_secret,
                redirect_uri=spotify_redirect_uri,
                scope=&amp;quot;user-top-read,user-read-currently-playing&amp;quot;,
                open_browser=False
            )
        )&lt;/pre&gt;
  &lt;pre id=&quot;LXid&quot;&gt;    def get_source_name(self) -&amp;gt; str:
        return &amp;quot;Spotify&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;cIIg&quot;&gt;    def get_spec(self) -&amp;gt; [Dict]:
        time_range_param = {
            &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
            &amp;quot;enum&amp;quot;: [&amp;quot;short_term&amp;quot;, &amp;quot;medium_term&amp;quot;, &amp;quot;long_term&amp;quot;],
            &amp;quot;description&amp;quot;: &amp;quot;The time range of the data to be returned. Short term is the last 4 weeks, &amp;quot;
                           &amp;quot;medium term is last 6 months, long term is last several years. Default to &amp;quot;
                           &amp;quot;short_term if not specified.&amp;quot;
        }
        limit_param = {
            &amp;quot;type&amp;quot;: &amp;quot;integer&amp;quot;,
            &amp;quot;description&amp;quot;: &amp;quot;The number of results to return. Max is 50. Default to 5 if not specified.&amp;quot;,
        }
        type_param = {
            &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
            &amp;quot;enum&amp;quot;: [&amp;quot;album&amp;quot;, &amp;quot;artist&amp;quot;, &amp;quot;track&amp;quot;],
            &amp;quot;description&amp;quot;: &amp;quot;Type of content to search&amp;quot;,
        }
        return [
            {
                &amp;quot;name&amp;quot;: &amp;quot;spotify_get_currently_playing_song&amp;quot;,
                &amp;quot;description&amp;quot;: &amp;quot;Get the user&amp;#x27;s currently playing song&amp;quot;,
                &amp;quot;parameters&amp;quot;: {
                    &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
                    &amp;quot;properties&amp;quot;: {}
                }
            },
            {
                &amp;quot;name&amp;quot;: &amp;quot;spotify_get_users_top_artists&amp;quot;,
                &amp;quot;description&amp;quot;: &amp;quot;Get the user&amp;#x27;s top listened artists&amp;quot;,
                &amp;quot;parameters&amp;quot;: {
                    &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
                    &amp;quot;properties&amp;quot;: {
                        &amp;quot;time_range&amp;quot;: time_range_param,
                        &amp;quot;limit&amp;quot;: limit_param
                    }
                }
            },
            {
                &amp;quot;name&amp;quot;: &amp;quot;spotify_get_users_top_tracks&amp;quot;,
                &amp;quot;description&amp;quot;: &amp;quot;Get the user&amp;#x27;s top listened tracks&amp;quot;,
                &amp;quot;parameters&amp;quot;: {
                    &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
                    &amp;quot;properties&amp;quot;: {
                        &amp;quot;time_range&amp;quot;: time_range_param,
                        &amp;quot;limit&amp;quot;: limit_param
                    }
                }
            },
            {
                &amp;quot;name&amp;quot;: &amp;quot;spotify_search_by_query&amp;quot;,
                &amp;quot;description&amp;quot;: &amp;quot;Search spotify content by query&amp;quot;,
                &amp;quot;parameters&amp;quot;: {
                    &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
                    &amp;quot;properties&amp;quot;: {
                        &amp;quot;query&amp;quot;: {
                            &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
                            &amp;quot;description&amp;quot;: &amp;quot;The search query&amp;quot;,
                        },
                        &amp;quot;type&amp;quot;: type_param
                    },
                    &amp;quot;required&amp;quot;: [&amp;quot;query&amp;quot;, &amp;quot;type&amp;quot;]
                }
            },
            {
                &amp;quot;name&amp;quot;: &amp;quot;spotify_lookup_by_id&amp;quot;,
                &amp;quot;description&amp;quot;: &amp;quot;Lookup spotify content by id&amp;quot;,
                &amp;quot;parameters&amp;quot;: {
                    &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
                    &amp;quot;properties&amp;quot;: {
                        &amp;quot;id&amp;quot;: {
                            &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
                            &amp;quot;description&amp;quot;: &amp;quot;The exact id to lookup. Can be a track id, an artist id or an album id&amp;quot;,
                        },
                        &amp;quot;type&amp;quot;: type_param
                    },
                    &amp;quot;required&amp;quot;: [&amp;quot;id&amp;quot;, &amp;quot;type&amp;quot;]
                }
            }
        ]&lt;/pre&gt;
  &lt;pre id=&quot;QZl0&quot;&gt;    async def execute(self, function_name, helper, **kwargs) -&amp;gt; Dict:
        time_range = kwargs.get(&amp;#x27;time_range&amp;#x27;, &amp;#x27;short_term&amp;#x27;)
        limit = kwargs.get(&amp;#x27;limit&amp;#x27;, 5)&lt;/pre&gt;
  &lt;pre id=&quot;ELf0&quot;&gt;        if function_name == &amp;#x27;spotify_get_currently_playing_song&amp;#x27;:
            return self.fetch_currently_playing()
        elif function_name == &amp;#x27;spotify_get_users_top_artists&amp;#x27;:
            return self.fetch_top_artists(time_range, limit)
        elif function_name == &amp;#x27;spotify_get_users_top_tracks&amp;#x27;:
            return self.fetch_top_tracks(time_range, limit)
        elif function_name == &amp;#x27;spotify_search_by_query&amp;#x27;:
            query = kwargs.get(&amp;#x27;query&amp;#x27;, &amp;#x27;&amp;#x27;)
            search_type = kwargs.get(&amp;#x27;type&amp;#x27;, &amp;#x27;track&amp;#x27;)
            return self.search_by_query(query, search_type, limit)
        elif function_name == &amp;#x27;spotify_lookup_by_id&amp;#x27;:
            content_id = kwargs.get(&amp;#x27;id&amp;#x27;)
            search_type = kwargs.get(&amp;#x27;type&amp;#x27;, &amp;#x27;track&amp;#x27;)
            return self.search_by_id(content_id, search_type)&lt;/pre&gt;
  &lt;pre id=&quot;k605&quot;&gt;    def fetch_currently_playing(self) -&amp;gt; Dict:
        &amp;quot;&amp;quot;&amp;quot;
        Fetch user&amp;#x27;s currently playing song from Spotify
        &amp;quot;&amp;quot;&amp;quot;
        currently_playing = self.spotify.current_user_playing_track()
        if not currently_playing:
            return {&amp;quot;result&amp;quot;: &amp;quot;No song is currently playing&amp;quot;}
        result = {
            &amp;#x27;name&amp;#x27;: currently_playing[&amp;#x27;item&amp;#x27;][&amp;#x27;name&amp;#x27;],
            &amp;#x27;artist&amp;#x27;: currently_playing[&amp;#x27;item&amp;#x27;][&amp;#x27;artists&amp;#x27;][0][&amp;#x27;name&amp;#x27;],
            &amp;#x27;album&amp;#x27;: currently_playing[&amp;#x27;item&amp;#x27;][&amp;#x27;album&amp;#x27;][&amp;#x27;name&amp;#x27;],
            &amp;#x27;url&amp;#x27;: currently_playing[&amp;#x27;item&amp;#x27;][&amp;#x27;external_urls&amp;#x27;][&amp;#x27;spotify&amp;#x27;],
            &amp;#x27;__album_id&amp;#x27;: currently_playing[&amp;#x27;item&amp;#x27;][&amp;#x27;album&amp;#x27;][&amp;#x27;id&amp;#x27;],
            &amp;#x27;__artist_id&amp;#x27;: currently_playing[&amp;#x27;item&amp;#x27;][&amp;#x27;artists&amp;#x27;][0][&amp;#x27;id&amp;#x27;],
            &amp;#x27;__track_id&amp;#x27;: currently_playing[&amp;#x27;item&amp;#x27;][&amp;#x27;id&amp;#x27;],
        }
        return {&amp;quot;result&amp;quot;: result}&lt;/pre&gt;
  &lt;pre id=&quot;fOum&quot;&gt;    def fetch_top_tracks(self, time_range=&amp;#x27;short_term&amp;#x27;, limit=5) -&amp;gt; Dict:
        &amp;quot;&amp;quot;&amp;quot;
        Fetch user&amp;#x27;s top tracks from Spotify
        &amp;quot;&amp;quot;&amp;quot;
        results = []
        top_tracks = self.spotify.current_user_top_tracks(limit=limit, time_range=time_range)
        if not top_tracks or &amp;#x27;items&amp;#x27; not in top_tracks or len(top_tracks[&amp;#x27;items&amp;#x27;]) == 0:
            return {&amp;quot;results&amp;quot;: &amp;quot;No top tracks found&amp;quot;}
        for item in top_tracks[&amp;#x27;items&amp;#x27;]:
            results.append({
                &amp;#x27;name&amp;#x27;: item[&amp;#x27;name&amp;#x27;],
                &amp;#x27;artist&amp;#x27;: item[&amp;#x27;artists&amp;#x27;][0][&amp;#x27;name&amp;#x27;],
                &amp;#x27;album&amp;#x27;: item[&amp;#x27;album&amp;#x27;][&amp;#x27;name&amp;#x27;],
                &amp;#x27;album_release_date&amp;#x27;: item[&amp;#x27;album&amp;#x27;][&amp;#x27;release_date&amp;#x27;],
                &amp;#x27;url&amp;#x27;: item[&amp;#x27;external_urls&amp;#x27;][&amp;#x27;spotify&amp;#x27;],
                &amp;#x27;album_url&amp;#x27;: item[&amp;#x27;album&amp;#x27;][&amp;#x27;external_urls&amp;#x27;][&amp;#x27;spotify&amp;#x27;],
                &amp;#x27;artist_url&amp;#x27;: item[&amp;#x27;artists&amp;#x27;][0][&amp;#x27;external_urls&amp;#x27;][&amp;#x27;spotify&amp;#x27;],
                &amp;#x27;__track_id&amp;#x27;: item[&amp;#x27;id&amp;#x27;],
                &amp;#x27;__album_id&amp;#x27;: item[&amp;#x27;album&amp;#x27;][&amp;#x27;id&amp;#x27;],
                &amp;#x27;__artist_id&amp;#x27;: item[&amp;#x27;artists&amp;#x27;][0][&amp;#x27;id&amp;#x27;],
            })
        return {&amp;#x27;results&amp;#x27;: results}&lt;/pre&gt;
  &lt;pre id=&quot;8eJs&quot;&gt;    def fetch_top_artists(self, time_range=&amp;#x27;short_term&amp;#x27;, limit=5) -&amp;gt; Dict:
        &amp;quot;&amp;quot;&amp;quot;
        Fetch user&amp;#x27;s top artists from Spotify
        &amp;quot;&amp;quot;&amp;quot;
        results = []
        top_artists = self.spotify.current_user_top_artists(limit=limit, time_range=time_range)
        if not top_artists or &amp;#x27;items&amp;#x27; not in top_artists or len(top_artists[&amp;#x27;items&amp;#x27;]) == 0:
            return {&amp;quot;results&amp;quot;: &amp;quot;No top artists found&amp;quot;}
        for item in top_artists[&amp;#x27;items&amp;#x27;]:
            results.append({
                &amp;#x27;name&amp;#x27;: item[&amp;#x27;name&amp;#x27;],
                &amp;#x27;url&amp;#x27;: item[&amp;#x27;external_urls&amp;#x27;][&amp;#x27;spotify&amp;#x27;],
                &amp;#x27;__artist_id&amp;#x27;: item[&amp;#x27;id&amp;#x27;]
            })
        return {&amp;#x27;results&amp;#x27;: results}&lt;/pre&gt;
  &lt;pre id=&quot;EMlW&quot;&gt;    def search_by_query(self, query, search_type, limit=5) -&amp;gt; Dict:
        &amp;quot;&amp;quot;&amp;quot;
        Search content by query on Spotify
        &amp;quot;&amp;quot;&amp;quot;
        results = {}
        search_response = self.spotify.search(q=query, limit=limit, type=search_type)
        if not search_response:
            return {&amp;quot;results&amp;quot;: &amp;quot;No content found&amp;quot;}&lt;/pre&gt;
  &lt;pre id=&quot;mjeG&quot;&gt;        if &amp;#x27;tracks&amp;#x27; in search_response:
            results[&amp;#x27;tracks&amp;#x27;] = []
            for item in search_response[&amp;#x27;tracks&amp;#x27;][&amp;#x27;items&amp;#x27;]:
                results[&amp;#x27;tracks&amp;#x27;].append({
                    &amp;#x27;name&amp;#x27;: item[&amp;#x27;name&amp;#x27;],
                    &amp;#x27;artist&amp;#x27;: item[&amp;#x27;artists&amp;#x27;][0][&amp;#x27;name&amp;#x27;],
                    &amp;#x27;album&amp;#x27;: item[&amp;#x27;album&amp;#x27;][&amp;#x27;name&amp;#x27;],
                    &amp;#x27;album_release_date&amp;#x27;: item[&amp;#x27;album&amp;#x27;][&amp;#x27;release_date&amp;#x27;],
                    &amp;#x27;url&amp;#x27;: item[&amp;#x27;external_urls&amp;#x27;][&amp;#x27;spotify&amp;#x27;],
                    &amp;#x27;album_url&amp;#x27;: item[&amp;#x27;album&amp;#x27;][&amp;#x27;external_urls&amp;#x27;][&amp;#x27;spotify&amp;#x27;],
                    &amp;#x27;artist_url&amp;#x27;: item[&amp;#x27;artists&amp;#x27;][0][&amp;#x27;external_urls&amp;#x27;][&amp;#x27;spotify&amp;#x27;],
                    &amp;#x27;__artist_id&amp;#x27;: item[&amp;#x27;artists&amp;#x27;][0][&amp;#x27;id&amp;#x27;],
                    &amp;#x27;__album_id&amp;#x27;: item[&amp;#x27;album&amp;#x27;][&amp;#x27;id&amp;#x27;],
                    &amp;#x27;__track_id&amp;#x27;: item[&amp;#x27;id&amp;#x27;],
                })
        if &amp;#x27;artists&amp;#x27; in search_response:
            results[&amp;#x27;artists&amp;#x27;] = []
            for item in search_response[&amp;#x27;artists&amp;#x27;][&amp;#x27;items&amp;#x27;]:
                results[&amp;#x27;artists&amp;#x27;].append({
                    &amp;#x27;name&amp;#x27;: item[&amp;#x27;name&amp;#x27;],
                    &amp;#x27;url&amp;#x27;: item[&amp;#x27;external_urls&amp;#x27;][&amp;#x27;spotify&amp;#x27;],
                    &amp;#x27;__artist_id&amp;#x27;: item[&amp;#x27;id&amp;#x27;],
                })
        if &amp;#x27;albums&amp;#x27; in search_response:
            results[&amp;#x27;albums&amp;#x27;] = []
            for item in search_response[&amp;#x27;albums&amp;#x27;][&amp;#x27;items&amp;#x27;]:
                results[&amp;#x27;albums&amp;#x27;].append({
                    &amp;#x27;name&amp;#x27;: item[&amp;#x27;name&amp;#x27;],
                    &amp;#x27;artist&amp;#x27;: item[&amp;#x27;artists&amp;#x27;][0][&amp;#x27;name&amp;#x27;],
                    &amp;#x27;url&amp;#x27;: item[&amp;#x27;external_urls&amp;#x27;][&amp;#x27;spotify&amp;#x27;],
                    &amp;#x27;artist_url&amp;#x27;: item[&amp;#x27;artists&amp;#x27;][0][&amp;#x27;external_urls&amp;#x27;][&amp;#x27;spotify&amp;#x27;],
                    &amp;#x27;release_date&amp;#x27;: item[&amp;#x27;release_date&amp;#x27;],
                    &amp;#x27;__artist_id&amp;#x27;: item[&amp;#x27;artists&amp;#x27;][0][&amp;#x27;id&amp;#x27;],
                    &amp;#x27;__album_id&amp;#x27;: item[&amp;#x27;id&amp;#x27;],
                })
        return {&amp;#x27;results&amp;#x27;: results}&lt;/pre&gt;
  &lt;pre id=&quot;oAty&quot;&gt;    def search_by_id(self, content_id, search_type) -&amp;gt; Dict:
        &amp;quot;&amp;quot;&amp;quot;
        Search content by exact id on Spotify
        &amp;quot;&amp;quot;&amp;quot;
        if search_type == &amp;#x27;track&amp;#x27;:
            search_response = self.spotify.track(content_id)
            if not search_response:
                return {&amp;quot;result&amp;quot;: &amp;quot;No track found&amp;quot;}
            return {&amp;#x27;result&amp;#x27;: self._get_track(search_response)}&lt;/pre&gt;
  &lt;pre id=&quot;GKhN&quot;&gt;        elif search_type == &amp;#x27;artist&amp;#x27;:
            search_response = self.spotify.artist(content_id)
            if not search_response:
                return {&amp;quot;result&amp;quot;: &amp;quot;No artisti found&amp;quot;}
            albums_response = self.spotify.artist_albums(artist_id=content_id, limit=3)
            if not albums_response:
                albums_response = {&amp;quot;items&amp;quot;: []}
            return {&amp;#x27;result&amp;#x27;: self._get_artist(search_response, albums_response)}&lt;/pre&gt;
  &lt;pre id=&quot;Uf1E&quot;&gt;        elif search_type == &amp;#x27;album&amp;#x27;:
            search_response = self.spotify.album(content_id)
            if not search_response:
                return {&amp;quot;result&amp;quot;: &amp;quot;No album found&amp;quot;}
            return {&amp;#x27;result&amp;#x27;: self._get_album(search_response)}&lt;/pre&gt;
  &lt;pre id=&quot;GKWx&quot;&gt;        else:
            return {&amp;#x27;error&amp;#x27;: &amp;#x27;Invalid search type. Must be track, artist or album&amp;#x27;}&lt;/pre&gt;
  &lt;pre id=&quot;vCro&quot;&gt;    @staticmethod
    def _get_artist(response, albums):
        return {
            &amp;#x27;name&amp;#x27;: response[&amp;#x27;name&amp;#x27;],
            &amp;#x27;url&amp;#x27;: response[&amp;#x27;external_urls&amp;#x27;][&amp;#x27;spotify&amp;#x27;],
            &amp;#x27;__artist_id&amp;#x27;: response[&amp;#x27;id&amp;#x27;],
            &amp;#x27;followers&amp;#x27;: response[&amp;#x27;followers&amp;#x27;][&amp;#x27;total&amp;#x27;],
            &amp;#x27;genres&amp;#x27;: response[&amp;#x27;genres&amp;#x27;],
            &amp;#x27;albums&amp;#x27;: [
                {
                    &amp;#x27;name&amp;#x27;: album[&amp;#x27;name&amp;#x27;],
                    &amp;#x27;__album_id&amp;#x27;: album[&amp;#x27;id&amp;#x27;],
                    &amp;#x27;url&amp;#x27;: album[&amp;#x27;external_urls&amp;#x27;][&amp;#x27;spotify&amp;#x27;],
                    &amp;#x27;release_date&amp;#x27;: album[&amp;#x27;release_date&amp;#x27;],
                    &amp;#x27;total_tracks&amp;#x27;: album[&amp;#x27;total_tracks&amp;#x27;],
                }
                for album in albums[&amp;#x27;items&amp;#x27;]
            ],
        }&lt;/pre&gt;
  &lt;pre id=&quot;Z6Je&quot;&gt;    @staticmethod
    def _get_track(response):
        return {
            &amp;#x27;name&amp;#x27;: response[&amp;#x27;name&amp;#x27;],
            &amp;#x27;artist&amp;#x27;: response[&amp;#x27;artists&amp;#x27;][0][&amp;#x27;name&amp;#x27;],
            &amp;#x27;__artist_id&amp;#x27;: response[&amp;#x27;artists&amp;#x27;][0][&amp;#x27;id&amp;#x27;],
            &amp;#x27;album&amp;#x27;: response[&amp;#x27;album&amp;#x27;][&amp;#x27;name&amp;#x27;],
            &amp;#x27;__album_id&amp;#x27;: response[&amp;#x27;album&amp;#x27;][&amp;#x27;id&amp;#x27;],
            &amp;#x27;url&amp;#x27;: response[&amp;#x27;external_urls&amp;#x27;][&amp;#x27;spotify&amp;#x27;],
            &amp;#x27;__track_id&amp;#x27;: response[&amp;#x27;id&amp;#x27;],
            &amp;#x27;duration_ms&amp;#x27;: response[&amp;#x27;duration_ms&amp;#x27;],
            &amp;#x27;track_number&amp;#x27;: response[&amp;#x27;track_number&amp;#x27;],
            &amp;#x27;explicit&amp;#x27;: response[&amp;#x27;explicit&amp;#x27;],
        }&lt;/pre&gt;
  &lt;pre id=&quot;huSb&quot;&gt;    @staticmethod
    def _get_album(response):
        return {
            &amp;#x27;name&amp;#x27;: response[&amp;#x27;name&amp;#x27;],
            &amp;#x27;artist&amp;#x27;: response[&amp;#x27;artists&amp;#x27;][0][&amp;#x27;name&amp;#x27;],
            &amp;#x27;__artist_id&amp;#x27;: response[&amp;#x27;artists&amp;#x27;][0][&amp;#x27;id&amp;#x27;],
            &amp;#x27;url&amp;#x27;: response[&amp;#x27;external_urls&amp;#x27;][&amp;#x27;spotify&amp;#x27;],
            &amp;#x27;release_date&amp;#x27;: response[&amp;#x27;release_date&amp;#x27;],
            &amp;#x27;total_tracks&amp;#x27;: response[&amp;#x27;total_tracks&amp;#x27;],
            &amp;#x27;__album_id&amp;#x27;: response[&amp;#x27;id&amp;#x27;],
            &amp;#x27;label&amp;#x27;: response[&amp;#x27;label&amp;#x27;],
            &amp;#x27;tracks&amp;#x27;: [
                {
                    &amp;#x27;name&amp;#x27;: track[&amp;#x27;name&amp;#x27;],
                    &amp;#x27;url&amp;#x27;: track[&amp;#x27;external_urls&amp;#x27;][&amp;#x27;spotify&amp;#x27;],
                    &amp;#x27;__track_id&amp;#x27;: track[&amp;#x27;id&amp;#x27;],
                    &amp;#x27;duration_ms&amp;#x27;: track[&amp;#x27;duration_ms&amp;#x27;],
                    &amp;#x27;track_number&amp;#x27;: track[&amp;#x27;track_number&amp;#x27;],
                    &amp;#x27;explicit&amp;#x27;: track[&amp;#x27;explicit&amp;#x27;],
                }
                for track in response[&amp;#x27;tracks&amp;#x27;][&amp;#x27;items&amp;#x27;]
            ]
        }&lt;/pre&gt;
  &lt;p id=&quot;gjgd&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;WN4t&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;LlOn&quot;&gt;Плагин   ↪️weather.py&lt;/h3&gt;
  &lt;pre id=&quot;2ewT&quot;&gt;from datetime import datetime
from typing import Dict&lt;/pre&gt;
  &lt;pre id=&quot;oE64&quot;&gt;import requests&lt;/pre&gt;
  &lt;pre id=&quot;FkIH&quot;&gt;from .plugin import Plugin&lt;/pre&gt;
  &lt;pre id=&quot;bFVN&quot;&gt;
class WeatherPlugin(Plugin):
    &amp;quot;&amp;quot;&amp;quot;
    A plugin to get the current weather and 7-day daily forecast for a location
    &amp;quot;&amp;quot;&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;v8Xe&quot;&gt;    def get_source_name(self) -&amp;gt; str:
        return &amp;quot;OpenMeteo&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;5Kmp&quot;&gt;    def get_spec(self) -&amp;gt; [Dict]:
        latitude_param = {&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;, &amp;quot;description&amp;quot;: &amp;quot;Latitude of the location&amp;quot;}
        longitude_param = {&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;, &amp;quot;description&amp;quot;: &amp;quot;Longitude of the location&amp;quot;}
        unit_param = {
            &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
            &amp;quot;enum&amp;quot;: [&amp;quot;celsius&amp;quot;, &amp;quot;fahrenheit&amp;quot;],
            &amp;quot;description&amp;quot;: &amp;quot;The temperature unit to use. Infer this from the provided location.&amp;quot;,
        }
        return [
            {
                &amp;quot;name&amp;quot;: &amp;quot;get_current_weather&amp;quot;,
                &amp;quot;description&amp;quot;: &amp;quot;Get the current weather for a location using Open Meteo APIs.&amp;quot;,
                &amp;quot;parameters&amp;quot;: {
                    &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
                    &amp;quot;properties&amp;quot;: {
                        &amp;quot;latitude&amp;quot;: latitude_param,
                        &amp;quot;longitude&amp;quot;: longitude_param,
                        &amp;quot;unit&amp;quot;: unit_param,
                    },
                    &amp;quot;required&amp;quot;: [&amp;quot;latitude&amp;quot;, &amp;quot;longitude&amp;quot;, &amp;quot;unit&amp;quot;],
                },
            },
            {
                &amp;quot;name&amp;quot;: &amp;quot;get_forecast_weather&amp;quot;,
                &amp;quot;description&amp;quot;: &amp;quot;Get daily weather forecast for a location using Open Meteo APIs.&amp;quot;
                               f&amp;quot;Today is {datetime.today().strftime(&amp;#x27;%A, %B %d, %Y&amp;#x27;)}&amp;quot;,
                &amp;quot;parameters&amp;quot;: {
                    &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
                    &amp;quot;properties&amp;quot;: {
                        &amp;quot;latitude&amp;quot;: latitude_param,
                        &amp;quot;longitude&amp;quot;: longitude_param,
                        &amp;quot;unit&amp;quot;: unit_param,
                        &amp;quot;forecast_days&amp;quot;: {
                            &amp;quot;type&amp;quot;: &amp;quot;integer&amp;quot;,
                            &amp;quot;description&amp;quot;: &amp;quot;The number of days to forecast, including today. Default is 7. Max 14. &amp;quot;
                                           &amp;quot;Use 1 for today, 2 for today and tomorrow, and so on.&amp;quot;,
                        },
                    },
                    &amp;quot;required&amp;quot;: [&amp;quot;latitude&amp;quot;, &amp;quot;longitude&amp;quot;, &amp;quot;unit&amp;quot;, &amp;quot;forecast_days&amp;quot;],
                },
            }
        ]&lt;/pre&gt;
  &lt;pre id=&quot;QKvS&quot;&gt;    async def execute(self, function_name, helper, **kwargs) -&amp;gt; Dict:
        url = &amp;#x27;https://api.open-meteo.com/v1/forecast&amp;#x27; \
              f&amp;#x27;?latitude={kwargs[&amp;quot;latitude&amp;quot;]}&amp;#x27; \
              f&amp;#x27;&amp;amp;longitude={kwargs[&amp;quot;longitude&amp;quot;]}&amp;#x27; \
              f&amp;#x27;&amp;amp;temperature_unit={kwargs[&amp;quot;unit&amp;quot;]}&amp;#x27;
        if function_name == &amp;#x27;get_current_weather&amp;#x27;:
            url += &amp;#x27;&amp;amp;current_weather=true&amp;#x27;
            return requests.get(url).json()&lt;/pre&gt;
  &lt;pre id=&quot;YvQu&quot;&gt;        elif function_name == &amp;#x27;get_forecast_weather&amp;#x27;:
            url += &amp;#x27;&amp;amp;daily=weathercode,temperature_2m_max,temperature_2m_min,precipitation_probability_mean,&amp;#x27;
            url += f&amp;#x27;&amp;amp;forecast_days={kwargs[&amp;quot;forecast_days&amp;quot;]}&amp;#x27;
            url += &amp;#x27;&amp;amp;timezone=auto&amp;#x27;
            response = requests.get(url).json()
            results = {}
            for i, time in enumerate(response[&amp;quot;daily&amp;quot;][&amp;quot;time&amp;quot;]):
                results[datetime.strptime(time, &amp;quot;%Y-%m-%d&amp;quot;).strftime(&amp;quot;%A, %B %d, %Y&amp;quot;)] = {
                    &amp;quot;weathercode&amp;quot;: response[&amp;quot;daily&amp;quot;][&amp;quot;weathercode&amp;quot;][i],
                    &amp;quot;temperature_2m_max&amp;quot;: response[&amp;quot;daily&amp;quot;][&amp;quot;temperature_2m_max&amp;quot;][i],
                    &amp;quot;temperature_2m_min&amp;quot;: response[&amp;quot;daily&amp;quot;][&amp;quot;temperature_2m_min&amp;quot;][i],
                    &amp;quot;precipitation_probability_mean&amp;quot;: response[&amp;quot;daily&amp;quot;][&amp;quot;precipitation_probability_mean&amp;quot;][i]
                }
            return {&amp;quot;today&amp;quot;: datetime.today().strftime(&amp;quot;%A, %B %d, %Y&amp;quot;), &amp;quot;forecast&amp;quot;: results}&lt;/pre&gt;
  &lt;p id=&quot;dhJy&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;2lWh&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;2HIL&quot;&gt;Плагин   ↪️webshot.py&lt;/h3&gt;
  &lt;pre id=&quot;H2I8&quot;&gt;import os, requests, random, string
from typing import Dict
from .plugin import Plugin&lt;/pre&gt;
  &lt;pre id=&quot;9DXm&quot;&gt;class WebshotPlugin(Plugin):
    &amp;quot;&amp;quot;&amp;quot;
    A plugin to screenshot a website
    &amp;quot;&amp;quot;&amp;quot;
    def get_source_name(self) -&amp;gt; str:
        return &amp;quot;WebShot&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;ToQy&quot;&gt;    def get_spec(self) -&amp;gt; [Dict]:
        return [{
            &amp;quot;name&amp;quot;: &amp;quot;screenshot_website&amp;quot;,
            &amp;quot;description&amp;quot;: &amp;quot;Show screenshot/image of a website from a given url or domain name.&amp;quot;,
            &amp;quot;parameters&amp;quot;: {
                &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
                &amp;quot;properties&amp;quot;: {
                    &amp;quot;url&amp;quot;: {&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;, &amp;quot;description&amp;quot;: &amp;quot;Website url or domain name. Correctly formatted url is required. Example: https://www.google.com&amp;quot;}
                },
                &amp;quot;required&amp;quot;: [&amp;quot;url&amp;quot;],
            },
        }]
    
    def generate_random_string(self, length):
        characters = string.ascii_letters + string.digits
        return &amp;#x27;&amp;#x27;.join(random.choice(characters) for _ in range(length))&lt;/pre&gt;
  &lt;pre id=&quot;bVq6&quot;&gt;    async def execute(self, function_name, helper, **kwargs) -&amp;gt; Dict:
        try:
            image_url = f&amp;#x27;https://image.thum.io/get/maxAge/12/width/720/{kwargs[&amp;quot;url&amp;quot;]}&amp;#x27;
            
            # preload url first
            requests.get(image_url)&lt;/pre&gt;
  &lt;pre id=&quot;cQki&quot;&gt;            # download the actual image
            response = requests.get(image_url, timeout=30)&lt;/pre&gt;
  &lt;pre id=&quot;7CVB&quot;&gt;            if response.status_code == 200:
                if not os.path.exists(&amp;quot;uploads/webshot&amp;quot;):
                    os.makedirs(&amp;quot;uploads/webshot&amp;quot;)&lt;/pre&gt;
  &lt;pre id=&quot;U82N&quot;&gt;                image_file_path = os.path.join(&amp;quot;uploads/webshot&amp;quot;, f&amp;quot;{self.generate_random_string(15)}.png&amp;quot;)
                with open(image_file_path, &amp;quot;wb&amp;quot;) as f:
                    f.write(response.content)&lt;/pre&gt;
  &lt;pre id=&quot;KES0&quot;&gt;                return {
                    &amp;#x27;direct_result&amp;#x27;: {
                        &amp;#x27;kind&amp;#x27;: &amp;#x27;photo&amp;#x27;,
                        &amp;#x27;format&amp;#x27;: &amp;#x27;path&amp;#x27;,
                        &amp;#x27;value&amp;#x27;: image_file_path
                    }
                }
            else:
                return {&amp;#x27;result&amp;#x27;: &amp;#x27;Unable to screenshot website&amp;#x27;}
        except:
            if &amp;#x27;image_file_path&amp;#x27; in locals():
                os.remove(image_file_path)
                
            return {&amp;#x27;result&amp;#x27;: &amp;#x27;Unable to screenshot website&amp;#x27;}&lt;/pre&gt;
  &lt;p id=&quot;xrzK&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;dLPU&quot;&gt;Плагин   ↪️whois_.py&lt;/h3&gt;
  &lt;pre id=&quot;RciL&quot;&gt;from typing import Dict
from .plugin import Plugin&lt;/pre&gt;
  &lt;pre id=&quot;xWX3&quot;&gt;import whois&lt;/pre&gt;
  &lt;pre id=&quot;zP6A&quot;&gt;
class WhoisPlugin(Plugin):
    &amp;quot;&amp;quot;&amp;quot;
    A plugin to query whois database
    &amp;quot;&amp;quot;&amp;quot;
    def get_source_name(self) -&amp;gt; str:
        return &amp;quot;Whois&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;bRpM&quot;&gt;    def get_spec(self) -&amp;gt; [Dict]:
        return [{
            &amp;quot;name&amp;quot;: &amp;quot;get_whois&amp;quot;,
            &amp;quot;description&amp;quot;: &amp;quot;Get whois registration and expiry information for a domain&amp;quot;,
            &amp;quot;parameters&amp;quot;: {
                &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
                &amp;quot;properties&amp;quot;: {
                    &amp;quot;domain&amp;quot;: {&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;, &amp;quot;description&amp;quot;: &amp;quot;Domain name&amp;quot;}
                },
                &amp;quot;required&amp;quot;: [&amp;quot;domain&amp;quot;],
            },
        }]&lt;/pre&gt;
  &lt;pre id=&quot;CXFz&quot;&gt;    async def execute(self, function_name, helper, **kwargs) -&amp;gt; Dict:
        try:
            whois_result = whois.query(kwargs[&amp;#x27;domain&amp;#x27;])
            if whois_result is None:
                return {&amp;#x27;result&amp;#x27;: &amp;#x27;No such domain found&amp;#x27;}
            return whois_result.__dict__
        except Exception as e:
            return {&amp;#x27;error&amp;#x27;: &amp;#x27;An unexpected error occurred: &amp;#x27; + str(e)}&lt;/pre&gt;
  &lt;p id=&quot;HKla&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;k0le&quot;&gt;Плагин   ↪️wolfram_alpha.py&lt;/h3&gt;
  &lt;pre id=&quot;sKPm&quot;&gt;import os
from typing import Dict&lt;/pre&gt;
  &lt;pre id=&quot;HwGS&quot;&gt;import wolframalpha&lt;/pre&gt;
  &lt;pre id=&quot;PL1W&quot;&gt;from .plugin import Plugin&lt;/pre&gt;
  &lt;pre id=&quot;k8bD&quot;&gt;
class WolframAlphaPlugin(Plugin):
    &amp;quot;&amp;quot;&amp;quot;
    A plugin to answer questions using WolframAlpha.
    &amp;quot;&amp;quot;&amp;quot;
    def __init__(self):
        wolfram_app_id = os.getenv(&amp;#x27;WOLFRAM_APP_ID&amp;#x27;)
        if not wolfram_app_id:
            raise ValueError(&amp;#x27;WOLFRAM_APP_ID environment variable must be set to use WolframAlphaPlugin&amp;#x27;)
        self.app_id = wolfram_app_id&lt;/pre&gt;
  &lt;pre id=&quot;GCIV&quot;&gt;    def get_source_name(self) -&amp;gt; str:
        return &amp;quot;WolframAlpha&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;UoP3&quot;&gt;    def get_spec(self) -&amp;gt; [Dict]:
        return [{
            &amp;quot;name&amp;quot;: &amp;quot;answer_with_wolfram_alpha&amp;quot;,
            &amp;quot;description&amp;quot;: &amp;quot;Get an answer to a question using Wolfram Alpha. Input should the the query in English.&amp;quot;,
            &amp;quot;parameters&amp;quot;: {
                &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
                &amp;quot;properties&amp;quot;: {
                    &amp;quot;query&amp;quot;: {&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;, &amp;quot;description&amp;quot;: &amp;quot;The search query, in english (translate if necessary)&amp;quot;}
                },
                &amp;quot;required&amp;quot;: [&amp;quot;query&amp;quot;]
            }
        }]&lt;/pre&gt;
  &lt;pre id=&quot;NywD&quot;&gt;    async def execute(self, function_name, helper, **kwargs) -&amp;gt; Dict:
        client = wolframalpha.Client(self.app_id)
        res = client.query(kwargs[&amp;#x27;query&amp;#x27;])
        try:
            assumption = next(res.pods).text
            answer = next(res.results).text
        except StopIteration:
            return {&amp;#x27;answer&amp;#x27;: &amp;#x27;Wolfram Alpha wasn\&amp;#x27;t able to answer it&amp;#x27;}&lt;/pre&gt;
  &lt;pre id=&quot;S0dL&quot;&gt;        if answer is None or answer == &amp;quot;&amp;quot;:
            return {&amp;#x27;answer&amp;#x27;: &amp;#x27;No good Wolfram Alpha Result was found&amp;#x27;}
        else:
            return {&amp;#x27;assumption&amp;#x27;: assumption, &amp;#x27;answer&amp;#x27;: answer}&lt;/pre&gt;
  &lt;p id=&quot;kWEl&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;sltf&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;xlv9&quot;&gt;Плагин   ↪️worldtimeapi.py&lt;/h3&gt;
  &lt;pre id=&quot;03Yg&quot;&gt;import os, requests
from typing import Dict
from datetime import datetime&lt;/pre&gt;
  &lt;pre id=&quot;HKeE&quot;&gt;from .plugin import Plugin&lt;/pre&gt;
  &lt;pre id=&quot;IlpW&quot;&gt;
class WorldTimeApiPlugin(Plugin):
    &amp;quot;&amp;quot;&amp;quot;
    A plugin to get the current time from a given timezone, using WorldTimeAPI
    &amp;quot;&amp;quot;&amp;quot;
    def __init__(self):
        default_timezone = os.getenv(&amp;#x27;WORLDTIME_DEFAULT_TIMEZONE&amp;#x27;)
        if not default_timezone:
            raise ValueError(&amp;#x27;WORLDTIME_DEFAULT_TIMEZONE environment variable must be set to use WorldTimeApiPlugin&amp;#x27;)
        self.default_timezone = default_timezone&lt;/pre&gt;
  &lt;pre id=&quot;jBzn&quot;&gt;    def get_source_name(self) -&amp;gt; str:
        return &amp;quot;WorldTimeAPI&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;ZlNt&quot;&gt;    def get_spec(self) -&amp;gt; [Dict]:
        return [{
            &amp;quot;name&amp;quot;: &amp;quot;worldtimeapi&amp;quot;,
            &amp;quot;description&amp;quot;: &amp;quot;Get the current time from a given timezone&amp;quot;,
            &amp;quot;parameters&amp;quot;: {
                &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
                &amp;quot;properties&amp;quot;: {
                    &amp;quot;timezone&amp;quot;: {
                        &amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,
                        &amp;quot;description&amp;quot;: &amp;quot;The timezone identifier (e.g: &amp;#x60;Europe/Rome&amp;#x60;). Infer this from the location.&amp;quot;
                                       f&amp;quot;Use {self.default_timezone} if not specified.&amp;quot;
                    }
                },
                &amp;quot;required&amp;quot;: [&amp;quot;timezone&amp;quot;],
            },
        }]&lt;/pre&gt;
  &lt;pre id=&quot;QrS9&quot;&gt;    async def execute(self, function_name, helper, **kwargs) -&amp;gt; Dict:
        timezone = kwargs.get(&amp;#x27;timezone&amp;#x27;, self.default_timezone)
        url = f&amp;#x27;https://worldtimeapi.org/api/timezone/{timezone}&amp;#x27;&lt;/pre&gt;
  &lt;pre id=&quot;46SV&quot;&gt;        try:
            wtr = requests.get(url).json().get(&amp;#x27;datetime&amp;#x27;)
            wtr_obj = datetime.strptime(wtr, &amp;quot;%Y-%m-%dT%H:%M:%S.%f%z&amp;quot;)
            time_24hr = wtr_obj.strftime(&amp;quot;%H:%M:%S&amp;quot;)
            time_12hr = wtr_obj.strftime(&amp;quot;%I:%M:%S %p&amp;quot;)
            return {&amp;quot;24hr&amp;quot;: time_24hr, &amp;quot;12hr&amp;quot;: time_12hr}
        except:
            return {&amp;quot;result&amp;quot;: &amp;quot;No result was found&amp;quot;}&lt;/pre&gt;
  &lt;p id=&quot;a6Hw&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;UYpW&quot;&gt;Плагин   ↪️youtube_audio_extractor.py&lt;/h3&gt;
  &lt;pre id=&quot;mCmi&quot;&gt;import logging
import re
from typing import Dict&lt;/pre&gt;
  &lt;pre id=&quot;9QwV&quot;&gt;from pytube import YouTube&lt;/pre&gt;
  &lt;pre id=&quot;xGzJ&quot;&gt;from .plugin import Plugin&lt;/pre&gt;
  &lt;pre id=&quot;zbQ7&quot;&gt;
class YouTubeAudioExtractorPlugin(Plugin):
    &amp;quot;&amp;quot;&amp;quot;
    A plugin to extract audio from a YouTube video
    &amp;quot;&amp;quot;&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;JC5k&quot;&gt;    def get_source_name(self) -&amp;gt; str:
        return &amp;quot;YouTube Audio Extractor&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;Fysg&quot;&gt;    def get_spec(self) -&amp;gt; [Dict]:
        return [{
            &amp;quot;name&amp;quot;: &amp;quot;extract_youtube_audio&amp;quot;,
            &amp;quot;description&amp;quot;: &amp;quot;Extract audio from a YouTube video&amp;quot;,
            &amp;quot;parameters&amp;quot;: {
                &amp;quot;type&amp;quot;: &amp;quot;object&amp;quot;,
                &amp;quot;properties&amp;quot;: {
                    &amp;quot;youtube_link&amp;quot;: {&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;, &amp;quot;description&amp;quot;: &amp;quot;YouTube video link to extract audio from&amp;quot;}
                },
                &amp;quot;required&amp;quot;: [&amp;quot;youtube_link&amp;quot;],
            },
        }]&lt;/pre&gt;
  &lt;pre id=&quot;Wj2w&quot;&gt;    async def execute(self, function_name, helper, **kwargs) -&amp;gt; Dict:
        link = kwargs[&amp;#x27;youtube_link&amp;#x27;]
        try:
            video = YouTube(link)
            audio = video.streams.filter(only_audio=True, file_extension=&amp;#x27;mp4&amp;#x27;).first()
            output = re.sub(r&amp;#x27;[^\w\-_\. ]&amp;#x27;, &amp;#x27;_&amp;#x27;, video.title) + &amp;#x27;.mp3&amp;#x27;
            audio.download(filename=output)
            return {
                &amp;#x27;direct_result&amp;#x27;: {
                    &amp;#x27;kind&amp;#x27;: &amp;#x27;file&amp;#x27;,
                    &amp;#x27;format&amp;#x27;: &amp;#x27;path&amp;#x27;,
                    &amp;#x27;value&amp;#x27;: output
                }
            }
        except Exception as e:
            logging.warning(f&amp;#x27;Failed to extract audio from YouTube video: {str(e)}&amp;#x27;)
            return {&amp;#x27;result&amp;#x27;: &amp;#x27;Failed to extract audio&amp;#x27;}&lt;/pre&gt;
  &lt;p id=&quot;16hX&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;TkRK&quot;&gt;↪️main.py&lt;/h3&gt;
  &lt;pre id=&quot;nVxT&quot; data-lang=&quot;python&quot;&gt;import logging
import os&lt;/pre&gt;
  &lt;pre id=&quot;1NoL&quot; data-lang=&quot;python&quot;&gt;from dotenv import load_dotenv&lt;/pre&gt;
  &lt;pre id=&quot;c3Xm&quot; data-lang=&quot;python&quot;&gt;from plugin_manager import PluginManager
from openai_helper import OpenAIHelper, default_max_tokens, are_functions_available
from telegram_bot import ChatGPTTelegramBot&lt;/pre&gt;
  &lt;pre id=&quot;anOj&quot; data-lang=&quot;python&quot;&gt;
def main():
    # Read .env file
    load_dotenv()&lt;/pre&gt;
  &lt;pre id=&quot;sQyb&quot; data-lang=&quot;python&quot;&gt;    # Setup logging
    logging.basicConfig(
        format=&amp;#x27;%(asctime)s - %(name)s - %(levelname)s - %(message)s&amp;#x27;,
        level=logging.INFO
    )
    logging.getLogger(&amp;quot;httpx&amp;quot;).setLevel(logging.WARNING)&lt;/pre&gt;
  &lt;pre id=&quot;1V0U&quot; data-lang=&quot;r&quot;&gt;    # Check if the required environment variables are set
    required_values = [&amp;#x27;TELEGRAM_BOT_TOKEN&amp;#x27;, &amp;#x27;OPENAI_API_KEY&amp;#x27;]
    missing_values = [value for value in required_values if os.environ.get(value) is None]
    if len(missing_values) &amp;gt; 0:
        logging.error(f&amp;#x27;The following environment values are missing in your .env: {&amp;quot;, &amp;quot;.join(missing_values)}&amp;#x27;)
        exit(1)&lt;/pre&gt;
  &lt;pre id=&quot;A20h&quot; data-lang=&quot;python&quot;&gt;    # Setup configurations
    model = os.environ.get(&amp;#x27;OPENAI_MODEL&amp;#x27;, &amp;#x27;gpt-4o&amp;#x27;)
    functions_available = are_functions_available(model=model)
    max_tokens_default = default_max_tokens(model=model)
    openai_config = {
        &amp;#x27;api_key&amp;#x27;: os.environ[&amp;#x27;OPENAI_API_KEY&amp;#x27;],
        &amp;#x27;show_usage&amp;#x27;: os.environ.get(&amp;#x27;SHOW_USAGE&amp;#x27;, &amp;#x27;false&amp;#x27;).lower() == &amp;#x27;true&amp;#x27;,
        &amp;#x27;stream&amp;#x27;: os.environ.get(&amp;#x27;STREAM&amp;#x27;, &amp;#x27;true&amp;#x27;).lower() == &amp;#x27;true&amp;#x27;,
        &amp;#x27;proxy&amp;#x27;: os.environ.get(&amp;#x27;PROXY&amp;#x27;, None) or os.environ.get(&amp;#x27;OPENAI_PROXY&amp;#x27;, None),
        &amp;#x27;max_history_size&amp;#x27;: int(os.environ.get(&amp;#x27;MAX_HISTORY_SIZE&amp;#x27;, 15)),
        &amp;#x27;max_conversation_age_minutes&amp;#x27;: int(os.environ.get(&amp;#x27;MAX_CONVERSATION_AGE_MINUTES&amp;#x27;, 180)),
        &amp;#x27;assistant_prompt&amp;#x27;: os.environ.get(&amp;#x27;ASSISTANT_PROMPT&amp;#x27;, &amp;#x27;You are a helpful assistant.&amp;#x27;),
        &amp;#x27;max_tokens&amp;#x27;: int(os.environ.get(&amp;#x27;MAX_TOKENS&amp;#x27;, max_tokens_default)),
        &amp;#x27;n_choices&amp;#x27;: int(os.environ.get(&amp;#x27;N_CHOICES&amp;#x27;, 1)),
        &amp;#x27;temperature&amp;#x27;: float(os.environ.get(&amp;#x27;TEMPERATURE&amp;#x27;, 1.0)),
        &amp;#x27;image_model&amp;#x27;: os.environ.get(&amp;#x27;IMAGE_MODEL&amp;#x27;, &amp;#x27;dall-e-2&amp;#x27;),
        &amp;#x27;image_quality&amp;#x27;: os.environ.get(&amp;#x27;IMAGE_QUALITY&amp;#x27;, &amp;#x27;standard&amp;#x27;),
        &amp;#x27;image_style&amp;#x27;: os.environ.get(&amp;#x27;IMAGE_STYLE&amp;#x27;, &amp;#x27;vivid&amp;#x27;),
        &amp;#x27;image_size&amp;#x27;: os.environ.get(&amp;#x27;IMAGE_SIZE&amp;#x27;, &amp;#x27;512x512&amp;#x27;),
        &amp;#x27;model&amp;#x27;: model,
        &amp;#x27;enable_functions&amp;#x27;: os.environ.get(&amp;#x27;ENABLE_FUNCTIONS&amp;#x27;, str(functions_available)).lower() == &amp;#x27;true&amp;#x27;,
        &amp;#x27;functions_max_consecutive_calls&amp;#x27;: int(os.environ.get(&amp;#x27;FUNCTIONS_MAX_CONSECUTIVE_CALLS&amp;#x27;, 10)),
        &amp;#x27;presence_penalty&amp;#x27;: float(os.environ.get(&amp;#x27;PRESENCE_PENALTY&amp;#x27;, 0.0)),
        &amp;#x27;frequency_penalty&amp;#x27;: float(os.environ.get(&amp;#x27;FREQUENCY_PENALTY&amp;#x27;, 0.0)),
        &amp;#x27;bot_language&amp;#x27;: os.environ.get(&amp;#x27;BOT_LANGUAGE&amp;#x27;, &amp;#x27;en&amp;#x27;),
        &amp;#x27;show_plugins_used&amp;#x27;: os.environ.get(&amp;#x27;SHOW_PLUGINS_USED&amp;#x27;, &amp;#x27;false&amp;#x27;).lower() == &amp;#x27;true&amp;#x27;,
        &amp;#x27;whisper_prompt&amp;#x27;: os.environ.get(&amp;#x27;WHISPER_PROMPT&amp;#x27;, &amp;#x27;&amp;#x27;),
        &amp;#x27;vision_model&amp;#x27;: os.environ.get(&amp;#x27;VISION_MODEL&amp;#x27;, &amp;#x27;gpt-4o&amp;#x27;),
        &amp;#x27;enable_vision_follow_up_questions&amp;#x27;: os.environ.get(&amp;#x27;ENABLE_VISION_FOLLOW_UP_QUESTIONS&amp;#x27;, &amp;#x27;true&amp;#x27;).lower() == &amp;#x27;true&amp;#x27;,
        &amp;#x27;vision_prompt&amp;#x27;: os.environ.get(&amp;#x27;VISION_PROMPT&amp;#x27;, &amp;#x27;What is in this image&amp;#x27;),
        &amp;#x27;vision_detail&amp;#x27;: os.environ.get(&amp;#x27;VISION_DETAIL&amp;#x27;, &amp;#x27;auto&amp;#x27;),
        &amp;#x27;vision_max_tokens&amp;#x27;: int(os.environ.get(&amp;#x27;VISION_MAX_TOKENS&amp;#x27;, &amp;#x27;300&amp;#x27;)),
        &amp;#x27;tts_model&amp;#x27;: os.environ.get(&amp;#x27;TTS_MODEL&amp;#x27;, &amp;#x27;tts-1&amp;#x27;),
        &amp;#x27;tts_voice&amp;#x27;: os.environ.get(&amp;#x27;TTS_VOICE&amp;#x27;, &amp;#x27;alloy&amp;#x27;),
    }&lt;/pre&gt;
  &lt;pre id=&quot;HNLw&quot; data-lang=&quot;python&quot;&gt;    if openai_config[&amp;#x27;enable_functions&amp;#x27;] and not functions_available:
        logging.error(f&amp;#x27;ENABLE_FUNCTIONS is set to true, but the model {model} does not support it. &amp;#x27;
                        &amp;#x27;Please set ENABLE_FUNCTIONS to false or use a model that supports it.&amp;#x27;)
        exit(1)
    if os.environ.get(&amp;#x27;MONTHLY_USER_BUDGETS&amp;#x27;) is not None:
        logging.warning(&amp;#x27;The environment variable MONTHLY_USER_BUDGETS is deprecated. &amp;#x27;
                        &amp;#x27;Please use USER_BUDGETS with BUDGET_PERIOD instead.&amp;#x27;)
    if os.environ.get(&amp;#x27;MONTHLY_GUEST_BUDGET&amp;#x27;) is not None:
        logging.warning(&amp;#x27;The environment variable MONTHLY_GUEST_BUDGET is deprecated. &amp;#x27;
                        &amp;#x27;Please use GUEST_BUDGET with BUDGET_PERIOD instead.&amp;#x27;)&lt;/pre&gt;
  &lt;pre id=&quot;JYQK&quot; data-lang=&quot;python&quot;&gt;    telegram_config = {
        &amp;#x27;token&amp;#x27;: os.environ[&amp;#x27;TELEGRAM_BOT_TOKEN&amp;#x27;],
        &amp;#x27;admin_user_ids&amp;#x27;: os.environ.get(&amp;#x27;ADMIN_USER_IDS&amp;#x27;, &amp;#x27;-&amp;#x27;),
        &amp;#x27;allowed_user_ids&amp;#x27;: os.environ.get(&amp;#x27;ALLOWED_TELEGRAM_USER_IDS&amp;#x27;, &amp;#x27;*&amp;#x27;),
        &amp;#x27;enable_quoting&amp;#x27;: os.environ.get(&amp;#x27;ENABLE_QUOTING&amp;#x27;, &amp;#x27;true&amp;#x27;).lower() == &amp;#x27;true&amp;#x27;,
        &amp;#x27;enable_image_generation&amp;#x27;: os.environ.get(&amp;#x27;ENABLE_IMAGE_GENERATION&amp;#x27;, &amp;#x27;true&amp;#x27;).lower() == &amp;#x27;true&amp;#x27;,
        &amp;#x27;enable_transcription&amp;#x27;: os.environ.get(&amp;#x27;ENABLE_TRANSCRIPTION&amp;#x27;, &amp;#x27;true&amp;#x27;).lower() == &amp;#x27;true&amp;#x27;,
        &amp;#x27;enable_vision&amp;#x27;: os.environ.get(&amp;#x27;ENABLE_VISION&amp;#x27;, &amp;#x27;true&amp;#x27;).lower() == &amp;#x27;true&amp;#x27;,
        &amp;#x27;enable_tts_generation&amp;#x27;: os.environ.get(&amp;#x27;ENABLE_TTS_GENERATION&amp;#x27;, &amp;#x27;true&amp;#x27;).lower() == &amp;#x27;true&amp;#x27;,
        &amp;#x27;budget_period&amp;#x27;: os.environ.get(&amp;#x27;BUDGET_PERIOD&amp;#x27;, &amp;#x27;monthly&amp;#x27;).lower(),
        &amp;#x27;user_budgets&amp;#x27;: os.environ.get(&amp;#x27;USER_BUDGETS&amp;#x27;, os.environ.get(&amp;#x27;MONTHLY_USER_BUDGETS&amp;#x27;, &amp;#x27;*&amp;#x27;)),
        &amp;#x27;guest_budget&amp;#x27;: float(os.environ.get(&amp;#x27;GUEST_BUDGET&amp;#x27;, os.environ.get(&amp;#x27;MONTHLY_GUEST_BUDGET&amp;#x27;, &amp;#x27;100.0&amp;#x27;))),
        &amp;#x27;stream&amp;#x27;: os.environ.get(&amp;#x27;STREAM&amp;#x27;, &amp;#x27;true&amp;#x27;).lower() == &amp;#x27;true&amp;#x27;,
        &amp;#x27;proxy&amp;#x27;: os.environ.get(&amp;#x27;PROXY&amp;#x27;, None) or os.environ.get(&amp;#x27;TELEGRAM_PROXY&amp;#x27;, None),
        &amp;#x27;voice_reply_transcript&amp;#x27;: os.environ.get(&amp;#x27;VOICE_REPLY_WITH_TRANSCRIPT_ONLY&amp;#x27;, &amp;#x27;false&amp;#x27;).lower() == &amp;#x27;true&amp;#x27;,
        &amp;#x27;voice_reply_prompts&amp;#x27;: os.environ.get(&amp;#x27;VOICE_REPLY_PROMPTS&amp;#x27;, &amp;#x27;&amp;#x27;).split(&amp;#x27;;&amp;#x27;),
        &amp;#x27;ignore_group_transcriptions&amp;#x27;: os.environ.get(&amp;#x27;IGNORE_GROUP_TRANSCRIPTIONS&amp;#x27;, &amp;#x27;true&amp;#x27;).lower() == &amp;#x27;true&amp;#x27;,
        &amp;#x27;ignore_group_vision&amp;#x27;: os.environ.get(&amp;#x27;IGNORE_GROUP_VISION&amp;#x27;, &amp;#x27;true&amp;#x27;).lower() == &amp;#x27;true&amp;#x27;,
        &amp;#x27;group_trigger_keyword&amp;#x27;: os.environ.get(&amp;#x27;GROUP_TRIGGER_KEYWORD&amp;#x27;, &amp;#x27;&amp;#x27;),
        &amp;#x27;token_price&amp;#x27;: float(os.environ.get(&amp;#x27;TOKEN_PRICE&amp;#x27;, 0.002)),
        &amp;#x27;image_prices&amp;#x27;: [float(i) for i in os.environ.get(&amp;#x27;IMAGE_PRICES&amp;#x27;, &amp;quot;0.016,0.018,0.02&amp;quot;).split(&amp;quot;,&amp;quot;)],
        &amp;#x27;vision_token_price&amp;#x27;: float(os.environ.get(&amp;#x27;VISION_TOKEN_PRICE&amp;#x27;, &amp;#x27;0.01&amp;#x27;)),
        &amp;#x27;image_receive_mode&amp;#x27;: os.environ.get(&amp;#x27;IMAGE_FORMAT&amp;#x27;, &amp;quot;photo&amp;quot;),
        &amp;#x27;tts_model&amp;#x27;: os.environ.get(&amp;#x27;TTS_MODEL&amp;#x27;, &amp;#x27;tts-1&amp;#x27;),
        &amp;#x27;tts_prices&amp;#x27;: [float(i) for i in os.environ.get(&amp;#x27;TTS_PRICES&amp;#x27;, &amp;quot;0.015,0.030&amp;quot;).split(&amp;quot;,&amp;quot;)],
        &amp;#x27;transcription_price&amp;#x27;: float(os.environ.get(&amp;#x27;TRANSCRIPTION_PRICE&amp;#x27;, 0.006)),
        &amp;#x27;bot_language&amp;#x27;: os.environ.get(&amp;#x27;BOT_LANGUAGE&amp;#x27;, &amp;#x27;en&amp;#x27;),
    }&lt;/pre&gt;
  &lt;pre id=&quot;AAcm&quot; data-lang=&quot;python&quot;&gt;    plugin_config = {
        &amp;#x27;plugins&amp;#x27;: os.environ.get(&amp;#x27;PLUGINS&amp;#x27;, &amp;#x27;&amp;#x27;).split(&amp;#x27;,&amp;#x27;)
    }&lt;/pre&gt;
  &lt;pre id=&quot;ToUb&quot; data-lang=&quot;python&quot;&gt;    # Setup and run ChatGPT and Telegram bot
    plugin_manager = PluginManager(config=plugin_config)
    openai_helper = OpenAIHelper(config=openai_config, plugin_manager=plugin_manager)
    telegram_bot = ChatGPTTelegramBot(config=telegram_config, openai=openai_helper)
    telegram_bot.run()&lt;/pre&gt;
  &lt;pre id=&quot;NdMp&quot; data-lang=&quot;python&quot;&gt;
if __name__ == &amp;#x27;__main__&amp;#x27;:
    main()&lt;/pre&gt;
  &lt;p id=&quot;n56p&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;SHFw&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;9U0G&quot;&gt;↪️openai_helper.py&lt;/h3&gt;
  &lt;pre id=&quot;ABhK&quot;&gt;&lt;/pre&gt;
  &lt;pre id=&quot;TwSZ&quot;&gt;from __future__ import annotations
import datetime
import logging
import os&lt;/pre&gt;
  &lt;pre id=&quot;82bY&quot;&gt;import tiktoken&lt;/pre&gt;
  &lt;pre id=&quot;ukFh&quot;&gt;import openai&lt;/pre&gt;
  &lt;pre id=&quot;jvIg&quot;&gt;import json
import httpx
import io
from PIL import Image&lt;/pre&gt;
  &lt;pre id=&quot;Uz7n&quot;&gt;from tenacity import retry, stop_after_attempt, wait_fixed, retry_if_exception_type&lt;/pre&gt;
  &lt;pre id=&quot;0c7J&quot;&gt;from utils import is_direct_result, encode_image, decode_image
from plugin_manager import PluginManager&lt;/pre&gt;
  &lt;pre id=&quot;sJ4E&quot;&gt;# Models can be found here: https://platform.openai.com/docs/models/overview
# Models gpt-3.5-turbo-0613 and  gpt-3.5-turbo-16k-0613 will be deprecated on June 13, 2024
GPT_3_MODELS = (&amp;quot;gpt-3.5-turbo&amp;quot;, &amp;quot;gpt-3.5-turbo-0301&amp;quot;, &amp;quot;gpt-3.5-turbo-0613&amp;quot;)
GPT_3_16K_MODELS = (&amp;quot;gpt-3.5-turbo-16k&amp;quot;, &amp;quot;gpt-3.5-turbo-16k-0613&amp;quot;, &amp;quot;gpt-3.5-turbo-1106&amp;quot;, &amp;quot;gpt-3.5-turbo-0125&amp;quot;)
GPT_4_MODELS = (&amp;quot;gpt-4&amp;quot;, &amp;quot;gpt-4-0314&amp;quot;, &amp;quot;gpt-4-0613&amp;quot;, &amp;quot;gpt-4-turbo-preview&amp;quot;)
GPT_4_32K_MODELS = (&amp;quot;gpt-4-32k&amp;quot;, &amp;quot;gpt-4-32k-0314&amp;quot;, &amp;quot;gpt-4-32k-0613&amp;quot;)
GPT_4_VISION_MODELS = (&amp;quot;gpt-4o&amp;quot;,)
GPT_4_128K_MODELS = (&amp;quot;gpt-4-1106-preview&amp;quot;, &amp;quot;gpt-4-0125-preview&amp;quot;, &amp;quot;gpt-4-turbo-preview&amp;quot;, &amp;quot;gpt-4-turbo&amp;quot;, &amp;quot;gpt-4-turbo-2024-04-09&amp;quot;)
GPT_4O_MODELS = (&amp;quot;gpt-4o&amp;quot;, &amp;quot;gpt-4o-mini&amp;quot;, &amp;quot;chatgpt-4o-latest&amp;quot;)
O_MODELS = (&amp;quot;o1&amp;quot;, &amp;quot;o1-mini&amp;quot;, &amp;quot;o1-preview&amp;quot;)
GPT_ALL_MODELS = GPT_3_MODELS + GPT_3_16K_MODELS + GPT_4_MODELS + GPT_4_32K_MODELS + GPT_4_VISION_MODELS + GPT_4_128K_MODELS + GPT_4O_MODELS + O_MODELS&lt;/pre&gt;
  &lt;pre id=&quot;ZvFG&quot;&gt;def default_max_tokens(model: str) -&amp;gt; int:
    &amp;quot;&amp;quot;&amp;quot;
    Gets the default number of max tokens for the given model.
    :param model: The model name
    :return: The default number of max tokens
    &amp;quot;&amp;quot;&amp;quot;
    base = 1200
    if model in GPT_3_MODELS:
        return base
    elif model in GPT_4_MODELS:
        return base * 2
    elif model in GPT_3_16K_MODELS:
        if model == &amp;quot;gpt-3.5-turbo-1106&amp;quot;:
            return 4096
        return base * 4
    elif model in GPT_4_32K_MODELS:
        return base * 8
    elif model in GPT_4_VISION_MODELS:
        return 4096
    elif model in GPT_4_128K_MODELS:
        return 4096
    elif model in GPT_4O_MODELS:
        return 4096
    elif model in O_MODELS:
        return 4096&lt;/pre&gt;
  &lt;pre id=&quot;yar9&quot;&gt;
def are_functions_available(model: str) -&amp;gt; bool:
    &amp;quot;&amp;quot;&amp;quot;
    Whether the given model supports functions
    &amp;quot;&amp;quot;&amp;quot;
    if model in (&amp;quot;gpt-3.5-turbo-0301&amp;quot;, &amp;quot;gpt-4-0314&amp;quot;, &amp;quot;gpt-4-32k-0314&amp;quot;, &amp;quot;gpt-3.5-turbo-0613&amp;quot;, &amp;quot;gpt-3.5-turbo-16k-0613&amp;quot;):
        return False
    if model in O_MODELS:
        return False
    return True&lt;/pre&gt;
  &lt;pre id=&quot;Ku26&quot;&gt;
# Load translations
parent_dir_path = os.path.join(os.path.dirname(__file__), os.pardir)
translations_file_path = os.path.join(parent_dir_path, &amp;#x27;translations.json&amp;#x27;)
with open(translations_file_path, &amp;#x27;r&amp;#x27;, encoding=&amp;#x27;utf-8&amp;#x27;) as f:
    translations = json.load(f)&lt;/pre&gt;
  &lt;pre id=&quot;orPt&quot;&gt;
def localized_text(key, bot_language):
    &amp;quot;&amp;quot;&amp;quot;
    Return translated text for a key in specified bot_language.
    Keys and translations can be found in the translations.json.
    &amp;quot;&amp;quot;&amp;quot;
    try:
        return translations[bot_language][key]
    except KeyError:
        logging.warning(f&amp;quot;No translation available for bot_language code &amp;#x27;{bot_language}&amp;#x27; and key &amp;#x27;{key}&amp;#x27;&amp;quot;)
        # Fallback to English if the translation is not available
        if key in translations[&amp;#x27;en&amp;#x27;]:
            return translations[&amp;#x27;en&amp;#x27;][key]
        else:
            logging.warning(f&amp;quot;No english definition found for key &amp;#x27;{key}&amp;#x27; in translations.json&amp;quot;)
            # return key as text
            return key&lt;/pre&gt;
  &lt;pre id=&quot;NOcQ&quot;&gt;
class OpenAIHelper:
    &amp;quot;&amp;quot;&amp;quot;
    ChatGPT helper class.
    &amp;quot;&amp;quot;&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;ydZ7&quot;&gt;    def __init__(self, config: dict, plugin_manager: PluginManager):
        &amp;quot;&amp;quot;&amp;quot;
        Initializes the OpenAI helper class with the given configuration.
        :param config: A dictionary containing the GPT configuration
        :param plugin_manager: The plugin manager
        &amp;quot;&amp;quot;&amp;quot;
        http_client = httpx.AsyncClient(proxy=config[&amp;#x27;proxy&amp;#x27;]) if &amp;#x27;proxy&amp;#x27; in config else None
        self.client = openai.AsyncOpenAI(api_key=config[&amp;#x27;api_key&amp;#x27;], http_client=http_client)
        self.config = config
        self.plugin_manager = plugin_manager
        self.conversations: dict[int: list] = {}  # {chat_id: history}
        self.conversations_vision: dict[int: bool] = {}  # {chat_id: is_vision}
        self.last_updated: dict[int: datetime] = {}  # {chat_id: last_update_timestamp}&lt;/pre&gt;
  &lt;pre id=&quot;7D4B&quot;&gt;    def get_conversation_stats(self, chat_id: int) -&amp;gt; tuple[int, int]:
        &amp;quot;&amp;quot;&amp;quot;
        Gets the number of messages and tokens used in the conversation.
        :param chat_id: The chat ID
        :return: A tuple containing the number of messages and tokens used
        &amp;quot;&amp;quot;&amp;quot;
        if chat_id not in self.conversations:
            self.reset_chat_history(chat_id)
        return len(self.conversations[chat_id]), self.__count_tokens(self.conversations[chat_id])&lt;/pre&gt;
  &lt;pre id=&quot;poEk&quot;&gt;    async def get_chat_response(self, chat_id: int, query: str) -&amp;gt; tuple[str, str]:
        &amp;quot;&amp;quot;&amp;quot;
        Gets a full response from the GPT model.
        :param chat_id: The chat ID
        :param query: The query to send to the model
        :return: The answer from the model and the number of tokens used
        &amp;quot;&amp;quot;&amp;quot;
        plugins_used = ()
        response = await self.__common_get_chat_response(chat_id, query)
        if self.config[&amp;#x27;enable_functions&amp;#x27;] and not self.conversations_vision[chat_id]:
            response, plugins_used = await self.__handle_function_call(chat_id, response)
            if is_direct_result(response):
                return response, &amp;#x27;0&amp;#x27;&lt;/pre&gt;
  &lt;pre id=&quot;9bFN&quot;&gt;        answer = &amp;#x27;&amp;#x27;&lt;/pre&gt;
  &lt;pre id=&quot;mCU8&quot;&gt;        if len(response.choices) &amp;gt; 1 and self.config[&amp;#x27;n_choices&amp;#x27;] &amp;gt; 1:
            for index, choice in enumerate(response.choices):
                content = choice.message.content.strip()
                if index == 0:
                    self.__add_to_history(chat_id, role=&amp;quot;assistant&amp;quot;, content=content)
                answer += f&amp;#x27;{index + 1}\u20e3\n&amp;#x27;
                answer += content
                answer += &amp;#x27;\n\n&amp;#x27;
        else:
            answer = response.choices[0].message.content.strip()
            self.__add_to_history(chat_id, role=&amp;quot;assistant&amp;quot;, content=answer)&lt;/pre&gt;
  &lt;pre id=&quot;xQny&quot;&gt;        bot_language = self.config[&amp;#x27;bot_language&amp;#x27;]
        show_plugins_used = len(plugins_used) &amp;gt; 0 and self.config[&amp;#x27;show_plugins_used&amp;#x27;]
        plugin_names = tuple(self.plugin_manager.get_plugin_source_name(plugin) for plugin in plugins_used)
        if self.config[&amp;#x27;show_usage&amp;#x27;]:
            answer += &amp;quot;\n\n---\n&amp;quot; \
                      f&amp;quot;💰 {str(response.usage.total_tokens)} {localized_text(&amp;#x27;stats_tokens&amp;#x27;, bot_language)}&amp;quot; \
                      f&amp;quot; ({str(response.usage.prompt_tokens)} {localized_text(&amp;#x27;prompt&amp;#x27;, bot_language)},&amp;quot; \
                      f&amp;quot; {str(response.usage.completion_tokens)} {localized_text(&amp;#x27;completion&amp;#x27;, bot_language)})&amp;quot;
            if show_plugins_used:
                answer += f&amp;quot;\n🔌 {&amp;#x27;, &amp;#x27;.join(plugin_names)}&amp;quot;
        elif show_plugins_used:
            answer += f&amp;quot;\n\n---\n🔌 {&amp;#x27;, &amp;#x27;.join(plugin_names)}&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;dCQ1&quot;&gt;        return answer, response.usage.total_tokens&lt;/pre&gt;
  &lt;pre id=&quot;AIkh&quot;&gt;    async def get_chat_response_stream(self, chat_id: int, query: str):
        &amp;quot;&amp;quot;&amp;quot;
        Stream response from the GPT model.
        :param chat_id: The chat ID
        :param query: The query to send to the model
        :return: The answer from the model and the number of tokens used, or &amp;#x27;not_finished&amp;#x27;
        &amp;quot;&amp;quot;&amp;quot;
        plugins_used = ()
        response = await self.__common_get_chat_response(chat_id, query, stream=True)
        if self.config[&amp;#x27;enable_functions&amp;#x27;] and not self.conversations_vision[chat_id]:
            response, plugins_used = await self.__handle_function_call(chat_id, response, stream=True)
            if is_direct_result(response):
                yield response, &amp;#x27;0&amp;#x27;
                return&lt;/pre&gt;
  &lt;pre id=&quot;Yc5U&quot;&gt;        answer = &amp;#x27;&amp;#x27;
        async for chunk in response:
            if len(chunk.choices) == 0:
                continue
            delta = chunk.choices[0].delta
            if delta.content:
                answer += delta.content
                yield answer, &amp;#x27;not_finished&amp;#x27;
        answer = answer.strip()
        self.__add_to_history(chat_id, role=&amp;quot;assistant&amp;quot;, content=answer)
        tokens_used = str(self.__count_tokens(self.conversations[chat_id]))&lt;/pre&gt;
  &lt;pre id=&quot;kBHW&quot;&gt;        show_plugins_used = len(plugins_used) &amp;gt; 0 and self.config[&amp;#x27;show_plugins_used&amp;#x27;]
        plugin_names = tuple(self.plugin_manager.get_plugin_source_name(plugin) for plugin in plugins_used)
        if self.config[&amp;#x27;show_usage&amp;#x27;]:
            answer += f&amp;quot;\n\n---\n💰 {tokens_used} {localized_text(&amp;#x27;stats_tokens&amp;#x27;, self.config[&amp;#x27;bot_language&amp;#x27;])}&amp;quot;
            if show_plugins_used:
                answer += f&amp;quot;\n🔌 {&amp;#x27;, &amp;#x27;.join(plugin_names)}&amp;quot;
        elif show_plugins_used:
            answer += f&amp;quot;\n\n---\n🔌 {&amp;#x27;, &amp;#x27;.join(plugin_names)}&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;muOC&quot;&gt;        yield answer, tokens_used&lt;/pre&gt;
  &lt;pre id=&quot;i33r&quot;&gt;    @retry(
        reraise=True,
        retry=retry_if_exception_type(openai.RateLimitError),
        wait=wait_fixed(20),
        stop=stop_after_attempt(3)
    )
    async def __common_get_chat_response(self, chat_id: int, query: str, stream=False):
        &amp;quot;&amp;quot;&amp;quot;
        Request a response from the GPT model.
        :param chat_id: The chat ID
        :param query: The query to send to the model
        :return: The answer from the model and the number of tokens used
        &amp;quot;&amp;quot;&amp;quot;
        bot_language = self.config[&amp;#x27;bot_language&amp;#x27;]
        try:
            if chat_id not in self.conversations or self.__max_age_reached(chat_id):
                self.reset_chat_history(chat_id)&lt;/pre&gt;
  &lt;pre id=&quot;szrM&quot;&gt;            self.last_updated[chat_id] = datetime.datetime.now()&lt;/pre&gt;
  &lt;pre id=&quot;UBcg&quot;&gt;            self.__add_to_history(chat_id, role=&amp;quot;user&amp;quot;, content=query)&lt;/pre&gt;
  &lt;pre id=&quot;vC6V&quot;&gt;            # Summarize the chat history if it&amp;#x27;s too long to avoid excessive token usage
            token_count = self.__count_tokens(self.conversations[chat_id])
            exceeded_max_tokens = token_count + self.config[&amp;#x27;max_tokens&amp;#x27;] &amp;gt; self.__max_model_tokens()
            exceeded_max_history_size = len(self.conversations[chat_id]) &amp;gt; self.config[&amp;#x27;max_history_size&amp;#x27;]&lt;/pre&gt;
  &lt;pre id=&quot;JHBQ&quot;&gt;            if exceeded_max_tokens or exceeded_max_history_size:
                logging.info(f&amp;#x27;Chat history for chat ID {chat_id} is too long. Summarising...&amp;#x27;)
                try:
                    summary = await self.__summarise(self.conversations[chat_id][:-1])
                    logging.debug(f&amp;#x27;Summary: {summary}&amp;#x27;)
                    self.reset_chat_history(chat_id, self.conversations[chat_id][0][&amp;#x27;content&amp;#x27;])
                    self.__add_to_history(chat_id, role=&amp;quot;assistant&amp;quot;, content=summary)
                    self.__add_to_history(chat_id, role=&amp;quot;user&amp;quot;, content=query)
                except Exception as e:
                    logging.warning(f&amp;#x27;Error while summarising chat history: {str(e)}. Popping elements instead...&amp;#x27;)
                    self.conversations[chat_id] = self.conversations[chat_id][-self.config[&amp;#x27;max_history_size&amp;#x27;]:]&lt;/pre&gt;
  &lt;pre id=&quot;XlhW&quot;&gt;            max_tokens_str = &amp;#x27;max_completion_tokens&amp;#x27; if self.config[&amp;#x27;model&amp;#x27;] in O_MODELS else &amp;#x27;max_tokens&amp;#x27;
            common_args = {
                &amp;#x27;model&amp;#x27;: self.config[&amp;#x27;model&amp;#x27;] if not self.conversations_vision[chat_id] else self.config[&amp;#x27;vision_model&amp;#x27;],
                &amp;#x27;messages&amp;#x27;: self.conversations[chat_id],
                &amp;#x27;temperature&amp;#x27;: self.config[&amp;#x27;temperature&amp;#x27;],
                &amp;#x27;n&amp;#x27;: self.config[&amp;#x27;n_choices&amp;#x27;],
                max_tokens_str: self.config[&amp;#x27;max_tokens&amp;#x27;],
                &amp;#x27;presence_penalty&amp;#x27;: self.config[&amp;#x27;presence_penalty&amp;#x27;],
                &amp;#x27;frequency_penalty&amp;#x27;: self.config[&amp;#x27;frequency_penalty&amp;#x27;],
                &amp;#x27;stream&amp;#x27;: stream
            }&lt;/pre&gt;
  &lt;pre id=&quot;O9GB&quot;&gt;            if self.config[&amp;#x27;enable_functions&amp;#x27;] and not self.conversations_vision[chat_id]:
                functions = self.plugin_manager.get_functions_specs()
                if len(functions) &amp;gt; 0:
                    common_args[&amp;#x27;functions&amp;#x27;] = self.plugin_manager.get_functions_specs()
                    common_args[&amp;#x27;function_call&amp;#x27;] = &amp;#x27;auto&amp;#x27;
            return await self.client.chat.completions.create(**common_args)&lt;/pre&gt;
  &lt;pre id=&quot;Rjnt&quot;&gt;        except openai.RateLimitError as e:
            raise e&lt;/pre&gt;
  &lt;pre id=&quot;o18n&quot;&gt;        except openai.BadRequestError as e:
            raise Exception(f&amp;quot;⚠️ _{localized_text(&amp;#x27;openai_invalid&amp;#x27;, bot_language)}._ ⚠️\n{str(e)}&amp;quot;) from e&lt;/pre&gt;
  &lt;pre id=&quot;yto8&quot;&gt;        except Exception as e:
            raise Exception(f&amp;quot;⚠️ _{localized_text(&amp;#x27;error&amp;#x27;, bot_language)}._ ⚠️\n{str(e)}&amp;quot;) from e&lt;/pre&gt;
  &lt;pre id=&quot;7Kbg&quot;&gt;    async def __handle_function_call(self, chat_id, response, stream=False, times=0, plugins_used=()):
        function_name = &amp;#x27;&amp;#x27;
        arguments = &amp;#x27;&amp;#x27;
        if stream:
            async for item in response:
                if len(item.choices) &amp;gt; 0:
                    first_choice = item.choices[0]
                    if first_choice.delta and first_choice.delta.function_call:
                        if first_choice.delta.function_call.name:
                            function_name += first_choice.delta.function_call.name
                        if first_choice.delta.function_call.arguments:
                            arguments += first_choice.delta.function_call.arguments
                    elif first_choice.finish_reason and first_choice.finish_reason == &amp;#x27;function_call&amp;#x27;:
                        break
                    else:
                        return response, plugins_used
                else:
                    return response, plugins_used
        else:
            if len(response.choices) &amp;gt; 0:
                first_choice = response.choices[0]
                if first_choice.message.function_call:
                    if first_choice.message.function_call.name:
                        function_name += first_choice.message.function_call.name
                    if first_choice.message.function_call.arguments:
                        arguments += first_choice.message.function_call.arguments
                else:
                    return response, plugins_used
            else:
                return response, plugins_used&lt;/pre&gt;
  &lt;pre id=&quot;qAA8&quot;&gt;        logging.info(f&amp;#x27;Calling function {function_name} with arguments {arguments}&amp;#x27;)
        function_response = await self.plugin_manager.call_function(function_name, self, arguments)&lt;/pre&gt;
  &lt;pre id=&quot;XaT1&quot;&gt;        if function_name not in plugins_used:
            plugins_used += (function_name,)&lt;/pre&gt;
  &lt;pre id=&quot;VvBZ&quot;&gt;        if is_direct_result(function_response):
            self.__add_function_call_to_history(chat_id=chat_id, function_name=function_name,
                                                content=json.dumps({&amp;#x27;result&amp;#x27;: &amp;#x27;Done, the content has been sent&amp;#x27;
                                                                              &amp;#x27;to the user.&amp;#x27;}))
            return function_response, plugins_used&lt;/pre&gt;
  &lt;pre id=&quot;aPQ5&quot;&gt;        self.__add_function_call_to_history(chat_id=chat_id, function_name=function_name, content=function_response)
        response = await self.client.chat.completions.create(
            model=self.config[&amp;#x27;model&amp;#x27;],
            messages=self.conversations[chat_id],
            functions=self.plugin_manager.get_functions_specs(),
            function_call=&amp;#x27;auto&amp;#x27; if times &amp;lt; self.config[&amp;#x27;functions_max_consecutive_calls&amp;#x27;] else &amp;#x27;none&amp;#x27;,
            stream=stream
        )
        return await self.__handle_function_call(chat_id, response, stream, times + 1, plugins_used)&lt;/pre&gt;
  &lt;pre id=&quot;DIus&quot;&gt;    async def generate_image(self, prompt: str) -&amp;gt; tuple[str, str]:
        &amp;quot;&amp;quot;&amp;quot;
        Generates an image from the given prompt using DALL·E model.
        :param prompt: The prompt to send to the model
        :return: The image URL and the image size
        &amp;quot;&amp;quot;&amp;quot;
        bot_language = self.config[&amp;#x27;bot_language&amp;#x27;]
        try:
            response = await self.client.images.generate(
                prompt=prompt,
                n=1,
                model=self.config[&amp;#x27;image_model&amp;#x27;],
                quality=self.config[&amp;#x27;image_quality&amp;#x27;],
                style=self.config[&amp;#x27;image_style&amp;#x27;],
                size=self.config[&amp;#x27;image_size&amp;#x27;]
            )&lt;/pre&gt;
  &lt;pre id=&quot;TzAZ&quot;&gt;            if len(response.data) == 0:
                logging.error(f&amp;#x27;No response from GPT: {str(response)}&amp;#x27;)
                raise Exception(
                    f&amp;quot;⚠️ _{localized_text(&amp;#x27;error&amp;#x27;, bot_language)}._ &amp;quot;
                    f&amp;quot;⚠️\n{localized_text(&amp;#x27;try_again&amp;#x27;, bot_language)}.&amp;quot;
                )&lt;/pre&gt;
  &lt;pre id=&quot;q7Jj&quot;&gt;            return response.data[0].url, self.config[&amp;#x27;image_size&amp;#x27;]
        except Exception as e:
            raise Exception(f&amp;quot;⚠️ _{localized_text(&amp;#x27;error&amp;#x27;, bot_language)}._ ⚠️\n{str(e)}&amp;quot;) from e&lt;/pre&gt;
  &lt;pre id=&quot;YxyA&quot;&gt;    async def generate_speech(self, text: str) -&amp;gt; tuple[any, int]:
        &amp;quot;&amp;quot;&amp;quot;
        Generates an audio from the given text using TTS model.
        :param prompt: The text to send to the model
        :return: The audio in bytes and the text size
        &amp;quot;&amp;quot;&amp;quot;
        bot_language = self.config[&amp;#x27;bot_language&amp;#x27;]
        try:
            response = await self.client.audio.speech.create(
                model=self.config[&amp;#x27;tts_model&amp;#x27;],
                voice=self.config[&amp;#x27;tts_voice&amp;#x27;],
                input=text,
                response_format=&amp;#x27;opus&amp;#x27;
            )&lt;/pre&gt;
  &lt;pre id=&quot;6zKZ&quot;&gt;            temp_file = io.BytesIO()
            temp_file.write(response.read())
            temp_file.seek(0)
            return temp_file, len(text)
        except Exception as e:
            raise Exception(f&amp;quot;⚠️ _{localized_text(&amp;#x27;error&amp;#x27;, bot_language)}._ ⚠️\n{str(e)}&amp;quot;) from e&lt;/pre&gt;
  &lt;pre id=&quot;OXQR&quot;&gt;    async def transcribe(self, filename):
        &amp;quot;&amp;quot;&amp;quot;
        Transcribes the audio file using the Whisper model.
        &amp;quot;&amp;quot;&amp;quot;
        try:
            with open(filename, &amp;quot;rb&amp;quot;) as audio:
                prompt_text = self.config[&amp;#x27;whisper_prompt&amp;#x27;]
                result = await self.client.audio.transcriptions.create(model=&amp;quot;whisper-1&amp;quot;, file=audio, prompt=prompt_text)
                return result.text
        except Exception as e:
            logging.exception(e)
            raise Exception(f&amp;quot;⚠️ _{localized_text(&amp;#x27;error&amp;#x27;, self.config[&amp;#x27;bot_language&amp;#x27;])}._ ⚠️\n{str(e)}&amp;quot;) from e&lt;/pre&gt;
  &lt;pre id=&quot;YqaX&quot;&gt;    @retry(
        reraise=True,
        retry=retry_if_exception_type(openai.RateLimitError),
        wait=wait_fixed(20),
        stop=stop_after_attempt(3)
    )
    async def __common_get_chat_response_vision(self, chat_id: int, content: list, stream=False):
        &amp;quot;&amp;quot;&amp;quot;
        Request a response from the GPT model.
        :param chat_id: The chat ID
        :param query: The query to send to the model
        :return: The answer from the model and the number of tokens used
        &amp;quot;&amp;quot;&amp;quot;
        bot_language = self.config[&amp;#x27;bot_language&amp;#x27;]
        try:
            if chat_id not in self.conversations or self.__max_age_reached(chat_id):
                self.reset_chat_history(chat_id)&lt;/pre&gt;
  &lt;pre id=&quot;mTnE&quot;&gt;            self.last_updated[chat_id] = datetime.datetime.now()&lt;/pre&gt;
  &lt;pre id=&quot;Igqd&quot;&gt;            if self.config[&amp;#x27;enable_vision_follow_up_questions&amp;#x27;]:
                self.conversations_vision[chat_id] = True
                self.__add_to_history(chat_id, role=&amp;quot;user&amp;quot;, content=content)
            else:
                for message in content:
                    if message[&amp;#x27;type&amp;#x27;] == &amp;#x27;text&amp;#x27;:
                        query = message[&amp;#x27;text&amp;#x27;]
                        break
                self.__add_to_history(chat_id, role=&amp;quot;user&amp;quot;, content=query)&lt;/pre&gt;
  &lt;pre id=&quot;CTwb&quot;&gt;            # Summarize the chat history if it&amp;#x27;s too long to avoid excessive token usage
            token_count = self.__count_tokens(self.conversations[chat_id])
            exceeded_max_tokens = token_count + self.config[&amp;#x27;max_tokens&amp;#x27;] &amp;gt; self.__max_model_tokens()
            exceeded_max_history_size = len(self.conversations[chat_id]) &amp;gt; self.config[&amp;#x27;max_history_size&amp;#x27;]&lt;/pre&gt;
  &lt;pre id=&quot;33zd&quot;&gt;            if exceeded_max_tokens or exceeded_max_history_size:
                logging.info(f&amp;#x27;Chat history for chat ID {chat_id} is too long. Summarising...&amp;#x27;)
                try:
                    
                    last = self.conversations[chat_id][-1]
                    summary = await self.__summarise(self.conversations[chat_id][:-1])
                    logging.debug(f&amp;#x27;Summary: {summary}&amp;#x27;)
                    self.reset_chat_history(chat_id, self.conversations[chat_id][0][&amp;#x27;content&amp;#x27;])
                    self.__add_to_history(chat_id, role=&amp;quot;assistant&amp;quot;, content=summary)
                    self.conversations[chat_id] += [last]
                except Exception as e:
                    logging.warning(f&amp;#x27;Error while summarising chat history: {str(e)}. Popping elements instead...&amp;#x27;)
                    self.conversations[chat_id] = self.conversations[chat_id][-self.config[&amp;#x27;max_history_size&amp;#x27;]:]&lt;/pre&gt;
  &lt;pre id=&quot;Mg6u&quot;&gt;            message = {&amp;#x27;role&amp;#x27;:&amp;#x27;user&amp;#x27;, &amp;#x27;content&amp;#x27;:content}&lt;/pre&gt;
  &lt;pre id=&quot;Is5F&quot;&gt;            common_args = {
                &amp;#x27;model&amp;#x27;: self.config[&amp;#x27;vision_model&amp;#x27;],
                &amp;#x27;messages&amp;#x27;: self.conversations[chat_id][:-1] + [message],
                &amp;#x27;temperature&amp;#x27;: self.config[&amp;#x27;temperature&amp;#x27;],
                &amp;#x27;n&amp;#x27;: 1, # several choices is not implemented yet
                &amp;#x27;max_tokens&amp;#x27;: self.config[&amp;#x27;vision_max_tokens&amp;#x27;],
                &amp;#x27;presence_penalty&amp;#x27;: self.config[&amp;#x27;presence_penalty&amp;#x27;],
                &amp;#x27;frequency_penalty&amp;#x27;: self.config[&amp;#x27;frequency_penalty&amp;#x27;],
                &amp;#x27;stream&amp;#x27;: stream
            }&lt;/pre&gt;
  &lt;pre id=&quot;2z6n&quot;&gt;
            # vision model does not yet support functions&lt;/pre&gt;
  &lt;pre id=&quot;A09q&quot;&gt;            # if self.config[&amp;#x27;enable_functions&amp;#x27;]:
            #     functions = self.plugin_manager.get_functions_specs()
            #     if len(functions) &amp;gt; 0:
            #         common_args[&amp;#x27;functions&amp;#x27;] = self.plugin_manager.get_functions_specs()
            #         common_args[&amp;#x27;function_call&amp;#x27;] = &amp;#x27;auto&amp;#x27;
            
            return await self.client.chat.completions.create(**common_args)&lt;/pre&gt;
  &lt;pre id=&quot;i6rK&quot;&gt;        except openai.RateLimitError as e:
            raise e&lt;/pre&gt;
  &lt;pre id=&quot;jFn4&quot;&gt;        except openai.BadRequestError as e:
            raise Exception(f&amp;quot;⚠️ _{localized_text(&amp;#x27;openai_invalid&amp;#x27;, bot_language)}._ ⚠️\n{str(e)}&amp;quot;) from e&lt;/pre&gt;
  &lt;pre id=&quot;uqd8&quot;&gt;        except Exception as e:
            raise Exception(f&amp;quot;⚠️ _{localized_text(&amp;#x27;error&amp;#x27;, bot_language)}._ ⚠️\n{str(e)}&amp;quot;) from e&lt;/pre&gt;
  &lt;pre id=&quot;eZ23&quot;&gt;
    async def interpret_image(self, chat_id, fileobj, prompt=None):
        &amp;quot;&amp;quot;&amp;quot;
        Interprets a given PNG image file using the Vision model.
        &amp;quot;&amp;quot;&amp;quot;
        image = encode_image(fileobj)
        prompt = self.config[&amp;#x27;vision_prompt&amp;#x27;] if prompt is None else prompt&lt;/pre&gt;
  &lt;pre id=&quot;L5Xy&quot;&gt;        content = [{&amp;#x27;type&amp;#x27;:&amp;#x27;text&amp;#x27;, &amp;#x27;text&amp;#x27;:prompt}, {&amp;#x27;type&amp;#x27;:&amp;#x27;image_url&amp;#x27;, \
                    &amp;#x27;image_url&amp;#x27;: {&amp;#x27;url&amp;#x27;:image, &amp;#x27;detail&amp;#x27;:self.config[&amp;#x27;vision_detail&amp;#x27;] } }]&lt;/pre&gt;
  &lt;pre id=&quot;6P0d&quot;&gt;        response = await self.__common_get_chat_response_vision(chat_id, content)&lt;/pre&gt;
  &lt;pre id=&quot;BgzS&quot;&gt;        &lt;/pre&gt;
  &lt;pre id=&quot;w06j&quot;&gt;        # functions are not available for this model
        
        # if self.config[&amp;#x27;enable_functions&amp;#x27;]:
        #     response, plugins_used = await self.__handle_function_call(chat_id, response)
        #     if is_direct_result(response):
        #         return response, &amp;#x27;0&amp;#x27;&lt;/pre&gt;
  &lt;pre id=&quot;KUV5&quot;&gt;        answer = &amp;#x27;&amp;#x27;&lt;/pre&gt;
  &lt;pre id=&quot;aoeT&quot;&gt;        if len(response.choices) &amp;gt; 1 and self.config[&amp;#x27;n_choices&amp;#x27;] &amp;gt; 1:
            for index, choice in enumerate(response.choices):
                content = choice.message.content.strip()
                if index == 0:
                    self.__add_to_history(chat_id, role=&amp;quot;assistant&amp;quot;, content=content)
                answer += f&amp;#x27;{index + 1}\u20e3\n&amp;#x27;
                answer += content
                answer += &amp;#x27;\n\n&amp;#x27;
        else:
            answer = response.choices[0].message.content.strip()
            self.__add_to_history(chat_id, role=&amp;quot;assistant&amp;quot;, content=answer)&lt;/pre&gt;
  &lt;pre id=&quot;0fhN&quot;&gt;        bot_language = self.config[&amp;#x27;bot_language&amp;#x27;]
        # Plugins are not enabled either
        # show_plugins_used = len(plugins_used) &amp;gt; 0 and self.config[&amp;#x27;show_plugins_used&amp;#x27;]
        # plugin_names = tuple(self.plugin_manager.get_plugin_source_name(plugin) for plugin in plugins_used)
        if self.config[&amp;#x27;show_usage&amp;#x27;]:
            answer += &amp;quot;\n\n---\n&amp;quot; \
                      f&amp;quot;💰 {str(response.usage.total_tokens)} {localized_text(&amp;#x27;stats_tokens&amp;#x27;, bot_language)}&amp;quot; \
                      f&amp;quot; ({str(response.usage.prompt_tokens)} {localized_text(&amp;#x27;prompt&amp;#x27;, bot_language)},&amp;quot; \
                      f&amp;quot; {str(response.usage.completion_tokens)} {localized_text(&amp;#x27;completion&amp;#x27;, bot_language)})&amp;quot;
            # if show_plugins_used:
            #     answer += f&amp;quot;\n🔌 {&amp;#x27;, &amp;#x27;.join(plugin_names)}&amp;quot;
        # elif show_plugins_used:
        #     answer += f&amp;quot;\n\n---\n🔌 {&amp;#x27;, &amp;#x27;.join(plugin_names)}&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;DXxQ&quot;&gt;        return answer, response.usage.total_tokens&lt;/pre&gt;
  &lt;pre id=&quot;0930&quot;&gt;    async def interpret_image_stream(self, chat_id, fileobj, prompt=None):
        &amp;quot;&amp;quot;&amp;quot;
        Interprets a given PNG image file using the Vision model.
        &amp;quot;&amp;quot;&amp;quot;
        image = encode_image(fileobj)
        prompt = self.config[&amp;#x27;vision_prompt&amp;#x27;] if prompt is None else prompt&lt;/pre&gt;
  &lt;pre id=&quot;HPRY&quot;&gt;        content = [{&amp;#x27;type&amp;#x27;:&amp;#x27;text&amp;#x27;, &amp;#x27;text&amp;#x27;:prompt}, {&amp;#x27;type&amp;#x27;:&amp;#x27;image_url&amp;#x27;, \
                    &amp;#x27;image_url&amp;#x27;: {&amp;#x27;url&amp;#x27;:image, &amp;#x27;detail&amp;#x27;:self.config[&amp;#x27;vision_detail&amp;#x27;] } }]&lt;/pre&gt;
  &lt;pre id=&quot;eQ4h&quot;&gt;        response = await self.__common_get_chat_response_vision(chat_id, content, stream=True)&lt;/pre&gt;
  &lt;pre id=&quot;mMGR&quot;&gt;        &lt;/pre&gt;
  &lt;pre id=&quot;zRO7&quot;&gt;        # if self.config[&amp;#x27;enable_functions&amp;#x27;]:
        #     response, plugins_used = await self.__handle_function_call(chat_id, response, stream=True)
        #     if is_direct_result(response):
        #         yield response, &amp;#x27;0&amp;#x27;
        #         return&lt;/pre&gt;
  &lt;pre id=&quot;J6b5&quot;&gt;        answer = &amp;#x27;&amp;#x27;
        async for chunk in response:
            if len(chunk.choices) == 0:
                continue
            delta = chunk.choices[0].delta
            if delta.content:
                answer += delta.content
                yield answer, &amp;#x27;not_finished&amp;#x27;
        answer = answer.strip()
        self.__add_to_history(chat_id, role=&amp;quot;assistant&amp;quot;, content=answer)
        tokens_used = str(self.__count_tokens(self.conversations[chat_id]))&lt;/pre&gt;
  &lt;pre id=&quot;O1WB&quot;&gt;        #show_plugins_used = len(plugins_used) &amp;gt; 0 and self.config[&amp;#x27;show_plugins_used&amp;#x27;]
        #plugin_names = tuple(self.plugin_manager.get_plugin_source_name(plugin) for plugin in plugins_used)
        if self.config[&amp;#x27;show_usage&amp;#x27;]:
            answer += f&amp;quot;\n\n---\n💰 {tokens_used} {localized_text(&amp;#x27;stats_tokens&amp;#x27;, self.config[&amp;#x27;bot_language&amp;#x27;])}&amp;quot;
        #     if show_plugins_used:
        #         answer += f&amp;quot;\n🔌 {&amp;#x27;, &amp;#x27;.join(plugin_names)}&amp;quot;
        # elif show_plugins_used:
        #     answer += f&amp;quot;\n\n---\n🔌 {&amp;#x27;, &amp;#x27;.join(plugin_names)}&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;bfR7&quot;&gt;        yield answer, tokens_used&lt;/pre&gt;
  &lt;pre id=&quot;l9zn&quot;&gt;    def reset_chat_history(self, chat_id, content=&amp;#x27;&amp;#x27;):
        &amp;quot;&amp;quot;&amp;quot;
        Resets the conversation history.
        &amp;quot;&amp;quot;&amp;quot;
        if content == &amp;#x27;&amp;#x27;:
            content = self.config[&amp;#x27;assistant_prompt&amp;#x27;]
        self.conversations[chat_id] = [{&amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot; if self.config[&amp;#x27;model&amp;#x27;] in O_MODELS else &amp;quot;system&amp;quot;, &amp;quot;content&amp;quot;: content}]
        self.conversations_vision[chat_id] = False&lt;/pre&gt;
  &lt;pre id=&quot;3MbH&quot;&gt;    def __max_age_reached(self, chat_id) -&amp;gt; bool:
        &amp;quot;&amp;quot;&amp;quot;
        Checks if the maximum conversation age has been reached.
        :param chat_id: The chat ID
        :return: A boolean indicating whether the maximum conversation age has been reached
        &amp;quot;&amp;quot;&amp;quot;
        if chat_id not in self.last_updated:
            return False
        last_updated = self.last_updated[chat_id]
        now = datetime.datetime.now()
        max_age_minutes = self.config[&amp;#x27;max_conversation_age_minutes&amp;#x27;]
        return last_updated &amp;lt; now - datetime.timedelta(minutes=max_age_minutes)&lt;/pre&gt;
  &lt;pre id=&quot;LUle&quot;&gt;    def __add_function_call_to_history(self, chat_id, function_name, content):
        &amp;quot;&amp;quot;&amp;quot;
        Adds a function call to the conversation history
        &amp;quot;&amp;quot;&amp;quot;
        self.conversations[chat_id].append({&amp;quot;role&amp;quot;: &amp;quot;function&amp;quot;, &amp;quot;name&amp;quot;: function_name, &amp;quot;content&amp;quot;: content})&lt;/pre&gt;
  &lt;pre id=&quot;3l69&quot;&gt;    def __add_to_history(self, chat_id, role, content):
        &amp;quot;&amp;quot;&amp;quot;
        Adds a message to the conversation history.
        :param chat_id: The chat ID
        :param role: The role of the message sender
        :param content: The message content
        &amp;quot;&amp;quot;&amp;quot;
        self.conversations[chat_id].append({&amp;quot;role&amp;quot;: role, &amp;quot;content&amp;quot;: content})&lt;/pre&gt;
  &lt;pre id=&quot;TC9n&quot;&gt;    async def __summarise(self, conversation) -&amp;gt; str:
        &amp;quot;&amp;quot;&amp;quot;
        Summarises the conversation history.
        :param conversation: The conversation history
        :return: The summary
        &amp;quot;&amp;quot;&amp;quot;
        messages = [
            {&amp;quot;role&amp;quot;: &amp;quot;assistant&amp;quot;, &amp;quot;content&amp;quot;: &amp;quot;Summarize this conversation in 700 characters or less&amp;quot;},
            {&amp;quot;role&amp;quot;: &amp;quot;user&amp;quot;, &amp;quot;content&amp;quot;: str(conversation)}
        ]
        response = await self.client.chat.completions.create(
            model=self.config[&amp;#x27;model&amp;#x27;],
            messages=messages,
            temperature=1 if self.config[&amp;#x27;model&amp;#x27;] in O_MODELS else 0.4
        )
        return response.choices[0].message.content&lt;/pre&gt;
  &lt;pre id=&quot;nPIJ&quot;&gt;    def __max_model_tokens(self):
        base = 4096
        if self.config[&amp;#x27;model&amp;#x27;] in GPT_3_MODELS:
            return base
        if self.config[&amp;#x27;model&amp;#x27;] in GPT_3_16K_MODELS:
            return base * 4
        if self.config[&amp;#x27;model&amp;#x27;] in GPT_4_MODELS:
            return base * 2
        if self.config[&amp;#x27;model&amp;#x27;] in GPT_4_32K_MODELS:
            return base * 8
        if self.config[&amp;#x27;model&amp;#x27;] in GPT_4_VISION_MODELS:
            return base * 31
        if self.config[&amp;#x27;model&amp;#x27;] in GPT_4_128K_MODELS:
            return base * 31
        if self.config[&amp;#x27;model&amp;#x27;] in GPT_4O_MODELS:
            return base * 31
        elif self.config[&amp;#x27;model&amp;#x27;] in O_MODELS:
            # https://platform.openai.com/docs/models#o1
            if self.config[&amp;#x27;model&amp;#x27;] == &amp;quot;o1&amp;quot;:
                return 100_000
            elif self.config[&amp;#x27;model&amp;#x27;] == &amp;quot;o1-preview&amp;quot;:
                return 32_768
            else:
                return 65_536
        raise NotImplementedError(
            f&amp;quot;Max tokens for model {self.config[&amp;#x27;model&amp;#x27;]} is not implemented yet.&amp;quot;
        )&lt;/pre&gt;
  &lt;pre id=&quot;KdRR&quot;&gt;    # https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
    def __count_tokens(self, messages) -&amp;gt; int:
        &amp;quot;&amp;quot;&amp;quot;
        Counts the number of tokens required to send the given messages.
        :param messages: the messages to send
        :return: the number of tokens required
        &amp;quot;&amp;quot;&amp;quot;
        model = self.config[&amp;#x27;model&amp;#x27;]
        try:
            encoding = tiktoken.encoding_for_model(model)
        except KeyError:
            encoding = tiktoken.get_encoding(&amp;quot;o200k_base&amp;quot;)&lt;/pre&gt;
  &lt;pre id=&quot;jaPW&quot;&gt;        if model in GPT_ALL_MODELS:
            tokens_per_message = 3
            tokens_per_name = 1
        else:
            raise NotImplementedError(f&amp;quot;&amp;quot;&amp;quot;num_tokens_from_messages() is not implemented for model {model}.&amp;quot;&amp;quot;&amp;quot;)
        num_tokens = 0
        for message in messages:
            num_tokens += tokens_per_message
            for key, value in message.items():
                if key == &amp;#x27;content&amp;#x27;:
                    if isinstance(value, str):
                        num_tokens += len(encoding.encode(value))
                    else:
                        for message1 in value:
                            if message1[&amp;#x27;type&amp;#x27;] == &amp;#x27;image_url&amp;#x27;:
                                image = decode_image(message1[&amp;#x27;image_url&amp;#x27;][&amp;#x27;url&amp;#x27;])
                                num_tokens += self.__count_tokens_vision(image)
                            else:
                                num_tokens += len(encoding.encode(message1[&amp;#x27;text&amp;#x27;]))
                else:
                    num_tokens += len(encoding.encode(value))
                    if key == &amp;quot;name&amp;quot;:
                        num_tokens += tokens_per_name
        num_tokens += 3  # every reply is primed with &amp;lt;|start|&amp;gt;assistant&amp;lt;|message|&amp;gt;
        return num_tokens&lt;/pre&gt;
  &lt;pre id=&quot;1fxd&quot;&gt;    # no longer needed&lt;/pre&gt;
  &lt;pre id=&quot;vUFV&quot;&gt;    def __count_tokens_vision(self, image_bytes: bytes) -&amp;gt; int:
        &amp;quot;&amp;quot;&amp;quot;
        Counts the number of tokens for interpreting an image.
        :param image_bytes: image to interpret
        :return: the number of tokens required
        &amp;quot;&amp;quot;&amp;quot;
        image_file = io.BytesIO(image_bytes)
        image = Image.open(image_file)
        model = self.config[&amp;#x27;vision_model&amp;#x27;]
        if model not in GPT_4_VISION_MODELS:
            raise NotImplementedError(f&amp;quot;&amp;quot;&amp;quot;count_tokens_vision() is not implemented for model {model}.&amp;quot;&amp;quot;&amp;quot;)
        
        w, h = image.size
        if w &amp;gt; h: w, h = h, w
        # this computation follows https://platform.openai.com/docs/guides/vision and https://openai.com/pricing#gpt-4-turbo
        base_tokens = 85
        detail = self.config[&amp;#x27;vision_detail&amp;#x27;]
        if detail == &amp;#x27;low&amp;#x27;:
            return base_tokens
        elif detail == &amp;#x27;high&amp;#x27; or detail == &amp;#x27;auto&amp;#x27;: # assuming worst cost for auto
            f = max(w / 768, h / 2048)
            if f &amp;gt; 1:
                w, h = int(w / f), int(h / f)
            tw, th = (w + 511) // 512, (h + 511) // 512
            tiles = tw * th
            num_tokens = base_tokens + tiles * 170
            return num_tokens
        else:
            raise NotImplementedError(f&amp;quot;&amp;quot;&amp;quot;unknown parameter detail={detail} for model {model}.&amp;quot;&amp;quot;&amp;quot;)&lt;/pre&gt;
  &lt;pre id=&quot;VesY&quot;&gt;    # No longer works as of July 21st 2023, as OpenAI has removed the billing API
    # def get_billing_current_month(self):
    #     &amp;quot;&amp;quot;&amp;quot;Gets billed usage for current month from OpenAI API.
    #
    #     :return: dollar amount of usage this month
    #     &amp;quot;&amp;quot;&amp;quot;
    #     headers = {
    #         &amp;quot;Authorization&amp;quot;: f&amp;quot;Bearer {openai.api_key}&amp;quot;
    #     }
    #     # calculate first and last day of current month
    #     today = date.today()
    #     first_day = date(today.year, today.month, 1)
    #     _, last_day_of_month = monthrange(today.year, today.month)
    #     last_day = date(today.year, today.month, last_day_of_month)
    #     params = {
    #         &amp;quot;start_date&amp;quot;: first_day,
    #         &amp;quot;end_date&amp;quot;: last_day
    #     }
    #     response = requests.get(&amp;quot;https://api.openai.com/dashboard/billing/usage&amp;quot;, headers=headers, params=params)
    #     billing_data = json.loads(response.text)
    #     usage_month = billing_data[&amp;quot;total_usage&amp;quot;] / 100  # convert cent amount to dollars
    #     return usage_month&lt;/pre&gt;
  &lt;p id=&quot;91mp&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;Vu88&quot;&gt;↪️plugin_manager.py&lt;/h3&gt;
  &lt;pre id=&quot;RIPE&quot;&gt;import json&lt;/pre&gt;
  &lt;pre id=&quot;D9qu&quot;&gt;from plugins.gtts_text_to_speech import GTTSTextToSpeech
from plugins.auto_tts import AutoTextToSpeech
from plugins.dice import DicePlugin
from plugins.youtube_audio_extractor import YouTubeAudioExtractorPlugin
from plugins.ddg_image_search import DDGImageSearchPlugin
from plugins.spotify import SpotifyPlugin
from plugins.crypto import CryptoPlugin
from plugins.weather import WeatherPlugin
from plugins.ddg_web_search import DDGWebSearchPlugin
from plugins.wolfram_alpha import WolframAlphaPlugin
from plugins.deepl import DeeplTranslatePlugin
from plugins.worldtimeapi import WorldTimeApiPlugin
from plugins.whois_ import WhoisPlugin
from plugins.webshot import WebshotPlugin
from plugins.iplocation import IpLocationPlugin&lt;/pre&gt;
  &lt;pre id=&quot;ORaF&quot;&gt;
class PluginManager:
    &amp;quot;&amp;quot;&amp;quot;
    A class to manage the plugins and call the correct functions
    &amp;quot;&amp;quot;&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;M9iI&quot;&gt;    def __init__(self, config):
        enabled_plugins = config.get(&amp;#x27;plugins&amp;#x27;, [])
        plugin_mapping = {
            &amp;#x27;wolfram&amp;#x27;: WolframAlphaPlugin,
            &amp;#x27;weather&amp;#x27;: WeatherPlugin,
            &amp;#x27;crypto&amp;#x27;: CryptoPlugin,
            &amp;#x27;ddg_web_search&amp;#x27;: DDGWebSearchPlugin,
            &amp;#x27;ddg_image_search&amp;#x27;: DDGImageSearchPlugin,
            &amp;#x27;spotify&amp;#x27;: SpotifyPlugin,
            &amp;#x27;worldtimeapi&amp;#x27;: WorldTimeApiPlugin,
            &amp;#x27;youtube_audio_extractor&amp;#x27;: YouTubeAudioExtractorPlugin,
            &amp;#x27;dice&amp;#x27;: DicePlugin,
            &amp;#x27;deepl_translate&amp;#x27;: DeeplTranslatePlugin,
            &amp;#x27;gtts_text_to_speech&amp;#x27;: GTTSTextToSpeech,
            &amp;#x27;auto_tts&amp;#x27;: AutoTextToSpeech,
            &amp;#x27;whois&amp;#x27;: WhoisPlugin,
            &amp;#x27;webshot&amp;#x27;: WebshotPlugin,
            &amp;#x27;iplocation&amp;#x27;: IpLocationPlugin,
        }
        self.plugins = [plugin_mapping[plugin]() for plugin in enabled_plugins if plugin in plugin_mapping]&lt;/pre&gt;
  &lt;pre id=&quot;QCCD&quot;&gt;    def get_functions_specs(self):
        &amp;quot;&amp;quot;&amp;quot;
        Return the list of function specs that can be called by the model
        &amp;quot;&amp;quot;&amp;quot;
        return [spec for specs in map(lambda plugin: plugin.get_spec(), self.plugins) for spec in specs]&lt;/pre&gt;
  &lt;pre id=&quot;4paD&quot;&gt;    async def call_function(self, function_name, helper, arguments):
        &amp;quot;&amp;quot;&amp;quot;
        Call a function based on the name and parameters provided
        &amp;quot;&amp;quot;&amp;quot;
        plugin = self.__get_plugin_by_function_name(function_name)
        if not plugin:
            return json.dumps({&amp;#x27;error&amp;#x27;: f&amp;#x27;Function {function_name} not found&amp;#x27;})
        return json.dumps(await plugin.execute(function_name, helper, **json.loads(arguments)), default=str)&lt;/pre&gt;
  &lt;pre id=&quot;RWed&quot;&gt;    def get_plugin_source_name(self, function_name) -&amp;gt; str:
        &amp;quot;&amp;quot;&amp;quot;
        Return the source name of the plugin
        &amp;quot;&amp;quot;&amp;quot;
        plugin = self.__get_plugin_by_function_name(function_name)
        if not plugin:
            return &amp;#x27;&amp;#x27;
        return plugin.get_source_name()&lt;/pre&gt;
  &lt;pre id=&quot;jCMt&quot;&gt;    def __get_plugin_by_function_name(self, function_name):
        return next((plugin for plugin in self.plugins
                    if function_name in map(lambda spec: spec.get(&amp;#x27;name&amp;#x27;), plugin.get_spec())), N&lt;/pre&gt;
  &lt;p id=&quot;8Ozk&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;C5vp&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;wZjM&quot;&gt;↪️telegram_bot.py&lt;/h3&gt;
  &lt;pre id=&quot;jr5U&quot;&gt;from __future__ import annotations&lt;/pre&gt;
  &lt;pre id=&quot;gUWb&quot;&gt;import asyncio
import logging
import os
import io&lt;/pre&gt;
  &lt;pre id=&quot;qX0a&quot;&gt;from uuid import uuid4
from telegram import BotCommandScopeAllGroupChats, Update, constants
from telegram import InlineKeyboardMarkup, InlineKeyboardButton, InlineQueryResultArticle
from telegram import InputTextMessageContent, BotCommand
from telegram.error import RetryAfter, TimedOut, BadRequest
from telegram.ext import ApplicationBuilder, CommandHandler, MessageHandler, \
    filters, InlineQueryHandler, CallbackQueryHandler, Application, ContextTypes, CallbackContext&lt;/pre&gt;
  &lt;pre id=&quot;lkqL&quot;&gt;from pydub import AudioSegment
from PIL import Image&lt;/pre&gt;
  &lt;pre id=&quot;a72h&quot;&gt;from utils import is_group_chat, get_thread_id, message_text, wrap_with_indicator, split_into_chunks, \
    edit_message_with_retry, get_stream_cutoff_values, is_allowed, get_remaining_budget, is_admin, is_within_budget, \
    get_reply_to_message_id, add_chat_request_to_usage_tracker, error_handler, is_direct_result, handle_direct_result, \
    cleanup_intermediate_files
from openai_helper import OpenAIHelper, localized_text
from usage_tracker import UsageTracker&lt;/pre&gt;
  &lt;pre id=&quot;7kTp&quot;&gt;
class ChatGPTTelegramBot:
    &amp;quot;&amp;quot;&amp;quot;
    Class representing a ChatGPT Telegram Bot.
    &amp;quot;&amp;quot;&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;2Ua1&quot;&gt;    def __init__(self, config: dict, openai: OpenAIHelper):
        &amp;quot;&amp;quot;&amp;quot;
        Initializes the bot with the given configuration and GPT bot object.
        :param config: A dictionary containing the bot configuration
        :param openai: OpenAIHelper object
        &amp;quot;&amp;quot;&amp;quot;
        self.config = config
        self.openai = openai
        bot_language = self.config[&amp;#x27;bot_language&amp;#x27;]
        self.commands = [
            BotCommand(command=&amp;#x27;help&amp;#x27;, description=localized_text(&amp;#x27;help_description&amp;#x27;, bot_language)),
            BotCommand(command=&amp;#x27;reset&amp;#x27;, description=localized_text(&amp;#x27;reset_description&amp;#x27;, bot_language)),
            BotCommand(command=&amp;#x27;stats&amp;#x27;, description=localized_text(&amp;#x27;stats_description&amp;#x27;, bot_language)),
            BotCommand(command=&amp;#x27;resend&amp;#x27;, description=localized_text(&amp;#x27;resend_description&amp;#x27;, bot_language))
        ]
        # If imaging is enabled, add the &amp;quot;image&amp;quot; command to the list
        if self.config.get(&amp;#x27;enable_image_generation&amp;#x27;, False):
            self.commands.append(BotCommand(command=&amp;#x27;image&amp;#x27;, description=localized_text(&amp;#x27;image_description&amp;#x27;, bot_language)))&lt;/pre&gt;
  &lt;pre id=&quot;vBdb&quot;&gt;        if self.config.get(&amp;#x27;enable_tts_generation&amp;#x27;, False):
            self.commands.append(BotCommand(command=&amp;#x27;tts&amp;#x27;, description=localized_text(&amp;#x27;tts_description&amp;#x27;, bot_language)))&lt;/pre&gt;
  &lt;pre id=&quot;mKL7&quot;&gt;        self.group_commands = [BotCommand(
            command=&amp;#x27;chat&amp;#x27;, description=localized_text(&amp;#x27;chat_description&amp;#x27;, bot_language)
        )] + self.commands
        self.disallowed_message = localized_text(&amp;#x27;disallowed&amp;#x27;, bot_language)
        self.budget_limit_message = localized_text(&amp;#x27;budget_limit&amp;#x27;, bot_language)
        self.usage = {}
        self.last_message = {}
        self.inline_queries_cache = {}&lt;/pre&gt;
  &lt;pre id=&quot;FR5N&quot;&gt;    async def help(self, update: Update, _: ContextTypes.DEFAULT_TYPE) -&amp;gt; None:
        &amp;quot;&amp;quot;&amp;quot;
        Shows the help menu.
        &amp;quot;&amp;quot;&amp;quot;
        commands = self.group_commands if is_group_chat(update) else self.commands
        commands_description = [f&amp;#x27;/{command.command} - {command.description}&amp;#x27; for command in commands]
        bot_language = self.config[&amp;#x27;bot_language&amp;#x27;]
        help_text = (
                localized_text(&amp;#x27;help_text&amp;#x27;, bot_language)[0] +
                &amp;#x27;\n\n&amp;#x27; +
                &amp;#x27;\n&amp;#x27;.join(commands_description) +
                &amp;#x27;\n\n&amp;#x27; +
                localized_text(&amp;#x27;help_text&amp;#x27;, bot_language)[1] +
                &amp;#x27;\n\n&amp;#x27; +
                localized_text(&amp;#x27;help_text&amp;#x27;, bot_language)[2]
        )
        await update.message.reply_text(help_text, disable_web_page_preview=True)&lt;/pre&gt;
  &lt;pre id=&quot;5mgd&quot;&gt;    async def stats(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        &amp;quot;&amp;quot;&amp;quot;
        Returns token usage statistics for current day and month.
        &amp;quot;&amp;quot;&amp;quot;
        if not await is_allowed(self.config, update, context):
            logging.warning(f&amp;#x27;User {update.message.from_user.name} (id: {update.message.from_user.id}) &amp;#x27;
                            &amp;#x27;is not allowed to request their usage statistics&amp;#x27;)
            await self.send_disallowed_message(update, context)
            return&lt;/pre&gt;
  &lt;pre id=&quot;cNok&quot;&gt;        logging.info(f&amp;#x27;User {update.message.from_user.name} (id: {update.message.from_user.id}) &amp;#x27;
                     &amp;#x27;requested their usage statistics&amp;#x27;)&lt;/pre&gt;
  &lt;pre id=&quot;RQUS&quot;&gt;        user_id = update.message.from_user.id
        if user_id not in self.usage:
            self.usage[user_id] = UsageTracker(user_id, update.message.from_user.name)&lt;/pre&gt;
  &lt;pre id=&quot;P5Mc&quot;&gt;        tokens_today, tokens_month = self.usage[user_id].get_current_token_usage()
        images_today, images_month = self.usage[user_id].get_current_image_count()
        (transcribe_minutes_today, transcribe_seconds_today, transcribe_minutes_month,
         transcribe_seconds_month) = self.usage[user_id].get_current_transcription_duration()
        vision_today, vision_month = self.usage[user_id].get_current_vision_tokens()
        characters_today, characters_month = self.usage[user_id].get_current_tts_usage()
        current_cost = self.usage[user_id].get_current_cost()&lt;/pre&gt;
  &lt;pre id=&quot;TCDO&quot;&gt;        chat_id = update.effective_chat.id
        chat_messages, chat_token_length = self.openai.get_conversation_stats(chat_id)
        remaining_budget = get_remaining_budget(self.config, self.usage, update)
        bot_language = self.config[&amp;#x27;bot_language&amp;#x27;]
        
        text_current_conversation = (
            f&amp;quot;*{localized_text(&amp;#x27;stats_conversation&amp;#x27;, bot_language)[0]}*:\n&amp;quot;
            f&amp;quot;{chat_messages} {localized_text(&amp;#x27;stats_conversation&amp;#x27;, bot_language)[1]}\n&amp;quot;
            f&amp;quot;{chat_token_length} {localized_text(&amp;#x27;stats_conversation&amp;#x27;, bot_language)[2]}\n&amp;quot;
            &amp;quot;----------------------------\n&amp;quot;
        )
        
        # Check if image generation is enabled and, if so, generate the image statistics for today
        text_today_images = &amp;quot;&amp;quot;
        if self.config.get(&amp;#x27;enable_image_generation&amp;#x27;, False):
            text_today_images = f&amp;quot;{images_today} {localized_text(&amp;#x27;stats_images&amp;#x27;, bot_language)}\n&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;Qosz&quot;&gt;        text_today_vision = &amp;quot;&amp;quot;
        if self.config.get(&amp;#x27;enable_vision&amp;#x27;, False):
            text_today_vision = f&amp;quot;{vision_today} {localized_text(&amp;#x27;stats_vision&amp;#x27;, bot_language)}\n&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;Be1w&quot;&gt;        text_today_tts = &amp;quot;&amp;quot;
        if self.config.get(&amp;#x27;enable_tts_generation&amp;#x27;, False):
            text_today_tts = f&amp;quot;{characters_today} {localized_text(&amp;#x27;stats_tts&amp;#x27;, bot_language)}\n&amp;quot;
        
        text_today = (
            f&amp;quot;*{localized_text(&amp;#x27;usage_today&amp;#x27;, bot_language)}:*\n&amp;quot;
            f&amp;quot;{tokens_today} {localized_text(&amp;#x27;stats_tokens&amp;#x27;, bot_language)}\n&amp;quot;
            f&amp;quot;{text_today_images}&amp;quot;  # Include the image statistics for today if applicable
            f&amp;quot;{text_today_vision}&amp;quot;
            f&amp;quot;{text_today_tts}&amp;quot;
            f&amp;quot;{transcribe_minutes_today} {localized_text(&amp;#x27;stats_transcribe&amp;#x27;, bot_language)[0]} &amp;quot;
            f&amp;quot;{transcribe_seconds_today} {localized_text(&amp;#x27;stats_transcribe&amp;#x27;, bot_language)[1]}\n&amp;quot;
            f&amp;quot;{localized_text(&amp;#x27;stats_total&amp;#x27;, bot_language)}{current_cost[&amp;#x27;cost_today&amp;#x27;]:.2f}\n&amp;quot;
            &amp;quot;----------------------------\n&amp;quot;
        )
        
        text_month_images = &amp;quot;&amp;quot;
        if self.config.get(&amp;#x27;enable_image_generation&amp;#x27;, False):
            text_month_images = f&amp;quot;{images_month} {localized_text(&amp;#x27;stats_images&amp;#x27;, bot_language)}\n&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;TmRC&quot;&gt;        text_month_vision = &amp;quot;&amp;quot;
        if self.config.get(&amp;#x27;enable_vision&amp;#x27;, False):
            text_month_vision = f&amp;quot;{vision_month} {localized_text(&amp;#x27;stats_vision&amp;#x27;, bot_language)}\n&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;9pon&quot;&gt;        text_month_tts = &amp;quot;&amp;quot;
        if self.config.get(&amp;#x27;enable_tts_generation&amp;#x27;, False):
            text_month_tts = f&amp;quot;{characters_month} {localized_text(&amp;#x27;stats_tts&amp;#x27;, bot_language)}\n&amp;quot;
        
        # Check if image generation is enabled and, if so, generate the image statistics for the month
        text_month = (
            f&amp;quot;*{localized_text(&amp;#x27;usage_month&amp;#x27;, bot_language)}:*\n&amp;quot;
            f&amp;quot;{tokens_month} {localized_text(&amp;#x27;stats_tokens&amp;#x27;, bot_language)}\n&amp;quot;
            f&amp;quot;{text_month_images}&amp;quot;  # Include the image statistics for the month if applicable
            f&amp;quot;{text_month_vision}&amp;quot;
            f&amp;quot;{text_month_tts}&amp;quot;
            f&amp;quot;{transcribe_minutes_month} {localized_text(&amp;#x27;stats_transcribe&amp;#x27;, bot_language)[0]} &amp;quot;
            f&amp;quot;{transcribe_seconds_month} {localized_text(&amp;#x27;stats_transcribe&amp;#x27;, bot_language)[1]}\n&amp;quot;
            f&amp;quot;{localized_text(&amp;#x27;stats_total&amp;#x27;, bot_language)}{current_cost[&amp;#x27;cost_month&amp;#x27;]:.2f}&amp;quot;
        )&lt;/pre&gt;
  &lt;pre id=&quot;EcaE&quot;&gt;        # text_budget filled with conditional content
        text_budget = &amp;quot;\n\n&amp;quot;
        budget_period = self.config[&amp;#x27;budget_period&amp;#x27;]
        if remaining_budget &amp;lt; float(&amp;#x27;inf&amp;#x27;):
            text_budget += (
                f&amp;quot;{localized_text(&amp;#x27;stats_budget&amp;#x27;, bot_language)}&amp;quot;
                f&amp;quot;{localized_text(budget_period, bot_language)}: &amp;quot;
                f&amp;quot;${remaining_budget:.2f}.\n&amp;quot;
            )
        # No longer works as of July 21st 2023, as OpenAI has removed the billing API
        # add OpenAI account information for admin request
        # if is_admin(self.config, user_id):
        #     text_budget += (
        #         f&amp;quot;{localized_text(&amp;#x27;stats_openai&amp;#x27;, bot_language)}&amp;quot;
        #         f&amp;quot;{self.openai.get_billing_current_month():.2f}&amp;quot;
        #     )&lt;/pre&gt;
  &lt;pre id=&quot;8dZm&quot;&gt;        usage_text = text_current_conversation + text_today + text_month + text_budget
        await update.message.reply_text(usage_text, parse_mode=constants.ParseMode.MARKDOWN)&lt;/pre&gt;
  &lt;pre id=&quot;RYgW&quot;&gt;    async def resend(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        &amp;quot;&amp;quot;&amp;quot;
        Resend the last request
        &amp;quot;&amp;quot;&amp;quot;
        if not await is_allowed(self.config, update, context):
            logging.warning(f&amp;#x27;User {update.message.from_user.name}  (id: {update.message.from_user.id})&amp;#x27;
                            &amp;#x27; is not allowed to resend the message&amp;#x27;)
            await self.send_disallowed_message(update, context)
            return&lt;/pre&gt;
  &lt;pre id=&quot;Gj3f&quot;&gt;        chat_id = update.effective_chat.id
        if chat_id not in self.last_message:
            logging.warning(f&amp;#x27;User {update.message.from_user.name} (id: {update.message.from_user.id})&amp;#x27;
                            &amp;#x27; does not have anything to resend&amp;#x27;)
            await update.effective_message.reply_text(
                message_thread_id=get_thread_id(update),
                text=localized_text(&amp;#x27;resend_failed&amp;#x27;, self.config[&amp;#x27;bot_language&amp;#x27;])
            )
            return&lt;/pre&gt;
  &lt;pre id=&quot;tjsE&quot;&gt;        # Update message text, clear self.last_message and send the request to prompt
        logging.info(f&amp;#x27;Resending the last prompt from user: {update.message.from_user.name} &amp;#x27;
                     f&amp;#x27;(id: {update.message.from_user.id})&amp;#x27;)
        with update.message._unfrozen() as message:
            message.text = self.last_message.pop(chat_id)&lt;/pre&gt;
  &lt;pre id=&quot;zOKG&quot;&gt;        await self.prompt(update=update, context=context)&lt;/pre&gt;
  &lt;pre id=&quot;CAyA&quot;&gt;    async def reset(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        &amp;quot;&amp;quot;&amp;quot;
        Resets the conversation.
        &amp;quot;&amp;quot;&amp;quot;
        if not await is_allowed(self.config, update, context):
            logging.warning(f&amp;#x27;User {update.message.from_user.name} (id: {update.message.from_user.id}) &amp;#x27;
                            &amp;#x27;is not allowed to reset the conversation&amp;#x27;)
            await self.send_disallowed_message(update, context)
            return&lt;/pre&gt;
  &lt;pre id=&quot;9YTP&quot;&gt;        logging.info(f&amp;#x27;Resetting the conversation for user {update.message.from_user.name} &amp;#x27;
                     f&amp;#x27;(id: {update.message.from_user.id})...&amp;#x27;)&lt;/pre&gt;
  &lt;pre id=&quot;Lciy&quot;&gt;        chat_id = update.effective_chat.id
        reset_content = message_text(update.message)
        self.openai.reset_chat_history(chat_id=chat_id, content=reset_content)
        await update.effective_message.reply_text(
            message_thread_id=get_thread_id(update),
            text=localized_text(&amp;#x27;reset_done&amp;#x27;, self.config[&amp;#x27;bot_language&amp;#x27;])
        )&lt;/pre&gt;
  &lt;pre id=&quot;bYhk&quot;&gt;    async def image(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        &amp;quot;&amp;quot;&amp;quot;
        Generates an image for the given prompt using DALL·E APIs
        &amp;quot;&amp;quot;&amp;quot;
        if not self.config[&amp;#x27;enable_image_generation&amp;#x27;] \
                or not await self.check_allowed_and_within_budget(update, context):
            return&lt;/pre&gt;
  &lt;pre id=&quot;4Kxn&quot;&gt;        image_query = message_text(update.message)
        if image_query == &amp;#x27;&amp;#x27;:
            await update.effective_message.reply_text(
                message_thread_id=get_thread_id(update),
                text=localized_text(&amp;#x27;image_no_prompt&amp;#x27;, self.config[&amp;#x27;bot_language&amp;#x27;])
            )
            return&lt;/pre&gt;
  &lt;pre id=&quot;I8uy&quot;&gt;        logging.info(f&amp;#x27;New image generation request received from user {update.message.from_user.name} &amp;#x27;
                     f&amp;#x27;(id: {update.message.from_user.id})&amp;#x27;)&lt;/pre&gt;
  &lt;pre id=&quot;r4y2&quot;&gt;        async def _generate():
            try:
                image_url, image_size = await self.openai.generate_image(prompt=image_query)
                if self.config[&amp;#x27;image_receive_mode&amp;#x27;] == &amp;#x27;photo&amp;#x27;:
                    await update.effective_message.reply_photo(
                        reply_to_message_id=get_reply_to_message_id(self.config, update),
                        photo=image_url
                    )
                elif self.config[&amp;#x27;image_receive_mode&amp;#x27;] == &amp;#x27;document&amp;#x27;:
                    await update.effective_message.reply_document(
                        reply_to_message_id=get_reply_to_message_id(self.config, update),
                        document=image_url
                    )
                else:
                    raise Exception(f&amp;quot;env variable IMAGE_RECEIVE_MODE has invalid value {self.config[&amp;#x27;image_receive_mode&amp;#x27;]}&amp;quot;)
                # add image request to users usage tracker
                user_id = update.message.from_user.id
                self.usage[user_id].add_image_request(image_size, self.config[&amp;#x27;image_prices&amp;#x27;])
                # add guest chat request to guest usage tracker
                if str(user_id) not in self.config[&amp;#x27;allowed_user_ids&amp;#x27;].split(&amp;#x27;,&amp;#x27;) and &amp;#x27;guests&amp;#x27; in self.usage:
                    self.usage[&amp;quot;guests&amp;quot;].add_image_request(image_size, self.config[&amp;#x27;image_prices&amp;#x27;])&lt;/pre&gt;
  &lt;pre id=&quot;PYly&quot;&gt;            except Exception as e:
                logging.exception(e)
                await update.effective_message.reply_text(
                    message_thread_id=get_thread_id(update),
                    reply_to_message_id=get_reply_to_message_id(self.config, update),
                    text=f&amp;quot;{localized_text(&amp;#x27;image_fail&amp;#x27;, self.config[&amp;#x27;bot_language&amp;#x27;])}: {str(e)}&amp;quot;,
                    parse_mode=constants.ParseMode.MARKDOWN
                )&lt;/pre&gt;
  &lt;pre id=&quot;wo5n&quot;&gt;        await wrap_with_indicator(update, context, _generate, constants.ChatAction.UPLOAD_PHOTO)&lt;/pre&gt;
  &lt;pre id=&quot;Cj1h&quot;&gt;    async def tts(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        &amp;quot;&amp;quot;&amp;quot;
        Generates an speech for the given input using TTS APIs
        &amp;quot;&amp;quot;&amp;quot;
        if not self.config[&amp;#x27;enable_tts_generation&amp;#x27;] \
                or not await self.check_allowed_and_within_budget(update, context):
            return&lt;/pre&gt;
  &lt;pre id=&quot;8Vh6&quot;&gt;        tts_query = message_text(update.message)
        if tts_query == &amp;#x27;&amp;#x27;:
            await update.effective_message.reply_text(
                message_thread_id=get_thread_id(update),
                text=localized_text(&amp;#x27;tts_no_prompt&amp;#x27;, self.config[&amp;#x27;bot_language&amp;#x27;])
            )
            return&lt;/pre&gt;
  &lt;pre id=&quot;p1qv&quot;&gt;        logging.info(f&amp;#x27;New speech generation request received from user {update.message.from_user.name} &amp;#x27;
                     f&amp;#x27;(id: {update.message.from_user.id})&amp;#x27;)&lt;/pre&gt;
  &lt;pre id=&quot;SA1F&quot;&gt;        async def _generate():
            try:
                speech_file, text_length = await self.openai.generate_speech(text=tts_query)&lt;/pre&gt;
  &lt;pre id=&quot;BWjZ&quot;&gt;                await update.effective_message.reply_voice(
                    reply_to_message_id=get_reply_to_message_id(self.config, update),
                    voice=speech_file
                )
                speech_file.close()
                # add image request to users usage tracker
                user_id = update.message.from_user.id
                self.usage[user_id].add_tts_request(text_length, self.config[&amp;#x27;tts_model&amp;#x27;], self.config[&amp;#x27;tts_prices&amp;#x27;])
                # add guest chat request to guest usage tracker
                if str(user_id) not in self.config[&amp;#x27;allowed_user_ids&amp;#x27;].split(&amp;#x27;,&amp;#x27;) and &amp;#x27;guests&amp;#x27; in self.usage:
                    self.usage[&amp;quot;guests&amp;quot;].add_tts_request(text_length, self.config[&amp;#x27;tts_model&amp;#x27;], self.config[&amp;#x27;tts_prices&amp;#x27;])&lt;/pre&gt;
  &lt;pre id=&quot;aKIm&quot;&gt;            except Exception as e:
                logging.exception(e)
                await update.effective_message.reply_text(
                    message_thread_id=get_thread_id(update),
                    reply_to_message_id=get_reply_to_message_id(self.config, update),
                    text=f&amp;quot;{localized_text(&amp;#x27;tts_fail&amp;#x27;, self.config[&amp;#x27;bot_language&amp;#x27;])}: {str(e)}&amp;quot;,
                    parse_mode=constants.ParseMode.MARKDOWN
                )&lt;/pre&gt;
  &lt;pre id=&quot;SeSU&quot;&gt;        await wrap_with_indicator(update, context, _generate, constants.ChatAction.UPLOAD_VOICE)&lt;/pre&gt;
  &lt;pre id=&quot;IJUQ&quot;&gt;    async def transcribe(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        &amp;quot;&amp;quot;&amp;quot;
        Transcribe audio messages.
        &amp;quot;&amp;quot;&amp;quot;
        if not self.config[&amp;#x27;enable_transcription&amp;#x27;] or not await self.check_allowed_and_within_budget(update, context):
            return&lt;/pre&gt;
  &lt;pre id=&quot;mETF&quot;&gt;        if is_group_chat(update) and self.config[&amp;#x27;ignore_group_transcriptions&amp;#x27;]:
            logging.info(&amp;#x27;Transcription coming from group chat, ignoring...&amp;#x27;)
            return&lt;/pre&gt;
  &lt;pre id=&quot;MHI0&quot;&gt;        chat_id = update.effective_chat.id
        filename = update.message.effective_attachment.file_unique_id&lt;/pre&gt;
  &lt;pre id=&quot;FECZ&quot;&gt;        async def _execute():
            filename_mp3 = f&amp;#x27;{filename}.mp3&amp;#x27;
            bot_language = self.config[&amp;#x27;bot_language&amp;#x27;]
            try:
                media_file = await context.bot.get_file(update.message.effective_attachment.file_id)
                await media_file.download_to_drive(filename)
            except Exception as e:
                logging.exception(e)
                await update.effective_message.reply_text(
                    message_thread_id=get_thread_id(update),
                    reply_to_message_id=get_reply_to_message_id(self.config, update),
                    text=(
                        f&amp;quot;{localized_text(&amp;#x27;media_download_fail&amp;#x27;, bot_language)[0]}: &amp;quot;
                        f&amp;quot;{str(e)}. {localized_text(&amp;#x27;media_download_fail&amp;#x27;, bot_language)[1]}&amp;quot;
                    ),
                    parse_mode=constants.ParseMode.MARKDOWN
                )
                return&lt;/pre&gt;
  &lt;pre id=&quot;tm9S&quot;&gt;            try:
                audio_track = AudioSegment.from_file(filename)
                audio_track.export(filename_mp3, format=&amp;quot;mp3&amp;quot;)
                logging.info(f&amp;#x27;New transcribe request received from user {update.message.from_user.name} &amp;#x27;
                             f&amp;#x27;(id: {update.message.from_user.id})&amp;#x27;)&lt;/pre&gt;
  &lt;pre id=&quot;miEH&quot;&gt;            except Exception as e:
                logging.exception(e)
                await update.effective_message.reply_text(
                    message_thread_id=get_thread_id(update),
                    reply_to_message_id=get_reply_to_message_id(self.config, update),
                    text=localized_text(&amp;#x27;media_type_fail&amp;#x27;, bot_language)
                )
                if os.path.exists(filename):
                    os.remove(filename)
                return&lt;/pre&gt;
  &lt;pre id=&quot;m0rz&quot;&gt;            user_id = update.message.from_user.id
            if user_id not in self.usage:
                self.usage[user_id] = UsageTracker(user_id, update.message.from_user.name)&lt;/pre&gt;
  &lt;pre id=&quot;4DUi&quot;&gt;            try:
                transcript = await self.openai.transcribe(filename_mp3)&lt;/pre&gt;
  &lt;pre id=&quot;JFwq&quot;&gt;                transcription_price = self.config[&amp;#x27;transcription_price&amp;#x27;]
                self.usage[user_id].add_transcription_seconds(audio_track.duration_seconds, transcription_price)&lt;/pre&gt;
  &lt;pre id=&quot;ivYF&quot;&gt;                allowed_user_ids = self.config[&amp;#x27;allowed_user_ids&amp;#x27;].split(&amp;#x27;,&amp;#x27;)
                if str(user_id) not in allowed_user_ids and &amp;#x27;guests&amp;#x27; in self.usage:
                    self.usage[&amp;quot;guests&amp;quot;].add_transcription_seconds(audio_track.duration_seconds, transcription_price)&lt;/pre&gt;
  &lt;pre id=&quot;n50q&quot;&gt;                # check if transcript starts with any of the prefixes
                response_to_transcription = any(transcript.lower().startswith(prefix.lower()) if prefix else False
                                                for prefix in self.config[&amp;#x27;voice_reply_prompts&amp;#x27;])&lt;/pre&gt;
  &lt;pre id=&quot;mp4K&quot;&gt;                if self.config[&amp;#x27;voice_reply_transcript&amp;#x27;] and not response_to_transcription:&lt;/pre&gt;
  &lt;pre id=&quot;gsQL&quot;&gt;                    # Split into chunks of 4096 characters (Telegram&amp;#x27;s message limit)
                    transcript_output = f&amp;quot;_{localized_text(&amp;#x27;transcript&amp;#x27;, bot_language)}:_\n\&amp;quot;{transcript}\&amp;quot;&amp;quot;
                    chunks = split_into_chunks(transcript_output)&lt;/pre&gt;
  &lt;pre id=&quot;KZ3x&quot;&gt;                    for index, transcript_chunk in enumerate(chunks):
                        await update.effective_message.reply_text(
                            message_thread_id=get_thread_id(update),
                            reply_to_message_id=get_reply_to_message_id(self.config, update) if index == 0 else None,
                            text=transcript_chunk,
                            parse_mode=constants.ParseMode.MARKDOWN
                        )
                else:
                    # Get the response of the transcript
                    response, total_tokens = await self.openai.get_chat_response(chat_id=chat_id, query=transcript)&lt;/pre&gt;
  &lt;pre id=&quot;i6UR&quot;&gt;                    self.usage[user_id].add_chat_tokens(total_tokens, self.config[&amp;#x27;token_price&amp;#x27;])
                    if str(user_id) not in allowed_user_ids and &amp;#x27;guests&amp;#x27; in self.usage:
                        self.usage[&amp;quot;guests&amp;quot;].add_chat_tokens(total_tokens, self.config[&amp;#x27;token_price&amp;#x27;])&lt;/pre&gt;
  &lt;pre id=&quot;qE1n&quot;&gt;                    # Split into chunks of 4096 characters (Telegram&amp;#x27;s message limit)
                    transcript_output = (
                        f&amp;quot;_{localized_text(&amp;#x27;transcript&amp;#x27;, bot_language)}:_\n\&amp;quot;{transcript}\&amp;quot;\n\n&amp;quot;
                        f&amp;quot;_{localized_text(&amp;#x27;answer&amp;#x27;, bot_language)}:_\n{response}&amp;quot;
                    )
                    chunks = split_into_chunks(transcript_output)&lt;/pre&gt;
  &lt;pre id=&quot;CDPi&quot;&gt;                    for index, transcript_chunk in enumerate(chunks):
                        await update.effective_message.reply_text(
                            message_thread_id=get_thread_id(update),
                            reply_to_message_id=get_reply_to_message_id(self.config, update) if index == 0 else None,
                            text=transcript_chunk,
                            parse_mode=constants.ParseMode.MARKDOWN
                        )&lt;/pre&gt;
  &lt;pre id=&quot;CuUL&quot;&gt;            except Exception as e:
                logging.exception(e)
                await update.effective_message.reply_text(
                    message_thread_id=get_thread_id(update),
                    reply_to_message_id=get_reply_to_message_id(self.config, update),
                    text=f&amp;quot;{localized_text(&amp;#x27;transcribe_fail&amp;#x27;, bot_language)}: {str(e)}&amp;quot;,
                    parse_mode=constants.ParseMode.MARKDOWN
                )
            finally:
                if os.path.exists(filename_mp3):
                    os.remove(filename_mp3)
                if os.path.exists(filename):
                    os.remove(filename)&lt;/pre&gt;
  &lt;pre id=&quot;x9PV&quot;&gt;        await wrap_with_indicator(update, context, _execute, constants.ChatAction.TYPING)&lt;/pre&gt;
  &lt;pre id=&quot;k2Ss&quot;&gt;    async def vision(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        &amp;quot;&amp;quot;&amp;quot;
        Interpret image using vision model.
        &amp;quot;&amp;quot;&amp;quot;
        if not self.config[&amp;#x27;enable_vision&amp;#x27;] or not await self.check_allowed_and_within_budget(update, context):
            return&lt;/pre&gt;
  &lt;pre id=&quot;dMRw&quot;&gt;        chat_id = update.effective_chat.id
        prompt = update.message.caption&lt;/pre&gt;
  &lt;pre id=&quot;v03F&quot;&gt;        if is_group_chat(update):
            if self.config[&amp;#x27;ignore_group_vision&amp;#x27;]:
                logging.info(&amp;#x27;Vision coming from group chat, ignoring...&amp;#x27;)
                return
            else:
                trigger_keyword = self.config[&amp;#x27;group_trigger_keyword&amp;#x27;]
                if (prompt is None and trigger_keyword != &amp;#x27;&amp;#x27;) or \
                   (prompt is not None and not prompt.lower().startswith(trigger_keyword.lower())):
                    logging.info(&amp;#x27;Vision coming from group chat with wrong keyword, ignoring...&amp;#x27;)
                    return
        
        image = update.message.effective_attachment[-1]
        &lt;/pre&gt;
  &lt;pre id=&quot;u7JV&quot;&gt;        async def _execute():
            bot_language = self.config[&amp;#x27;bot_language&amp;#x27;]
            try:
                media_file = await context.bot.get_file(image.file_id)
                temp_file = io.BytesIO(await media_file.download_as_bytearray())
            except Exception as e:
                logging.exception(e)
                await update.effective_message.reply_text(
                    message_thread_id=get_thread_id(update),
                    reply_to_message_id=get_reply_to_message_id(self.config, update),
                    text=(
                        f&amp;quot;{localized_text(&amp;#x27;media_download_fail&amp;#x27;, bot_language)[0]}: &amp;quot;
                        f&amp;quot;{str(e)}. {localized_text(&amp;#x27;media_download_fail&amp;#x27;, bot_language)[1]}&amp;quot;
                    ),
                    parse_mode=constants.ParseMode.MARKDOWN
                )
                return
            
            # convert jpg from telegram to png as understood by openai&lt;/pre&gt;
  &lt;pre id=&quot;lui0&quot;&gt;            temp_file_png = io.BytesIO()&lt;/pre&gt;
  &lt;pre id=&quot;fjtN&quot;&gt;            try:
                original_image = Image.open(temp_file)
                
                original_image.save(temp_file_png, format=&amp;#x27;PNG&amp;#x27;)
                logging.info(f&amp;#x27;New vision request received from user {update.message.from_user.name} &amp;#x27;
                             f&amp;#x27;(id: {update.message.from_user.id})&amp;#x27;)&lt;/pre&gt;
  &lt;pre id=&quot;soAR&quot;&gt;            except Exception as e:
                logging.exception(e)
                await update.effective_message.reply_text(
                    message_thread_id=get_thread_id(update),
                    reply_to_message_id=get_reply_to_message_id(self.config, update),
                    text=localized_text(&amp;#x27;media_type_fail&amp;#x27;, bot_language)
                )
            
            &lt;/pre&gt;
  &lt;pre id=&quot;xU5i&quot;&gt;            user_id = update.message.from_user.id
            if user_id not in self.usage:
                self.usage[user_id] = UsageTracker(user_id, update.message.from_user.name)&lt;/pre&gt;
  &lt;pre id=&quot;pptU&quot;&gt;            if self.config[&amp;#x27;stream&amp;#x27;]:&lt;/pre&gt;
  &lt;pre id=&quot;3LOf&quot;&gt;                stream_response = self.openai.interpret_image_stream(chat_id=chat_id, fileobj=temp_file_png, prompt=prompt)
                i = 0
                prev = &amp;#x27;&amp;#x27;
                sent_message = None
                backoff = 0
                stream_chunk = 0&lt;/pre&gt;
  &lt;pre id=&quot;0Qgj&quot;&gt;                async for content, tokens in stream_response:
                    if is_direct_result(content):
                        return await handle_direct_result(self.config, update, content)&lt;/pre&gt;
  &lt;pre id=&quot;O0KM&quot;&gt;                    if len(content.strip()) == 0:
                        continue&lt;/pre&gt;
  &lt;pre id=&quot;Kn99&quot;&gt;                    stream_chunks = split_into_chunks(content)
                    if len(stream_chunks) &amp;gt; 1:
                        content = stream_chunks[-1]
                        if stream_chunk != len(stream_chunks) - 1:
                            stream_chunk += 1
                            try:
                                await edit_message_with_retry(context, chat_id, str(sent_message.message_id),
                                                              stream_chunks[-2])
                            except:
                                pass
                            try:
                                sent_message = await update.effective_message.reply_text(
                                    message_thread_id=get_thread_id(update),
                                    text=content if len(content) &amp;gt; 0 else &amp;quot;...&amp;quot;
                                )
                            except:
                                pass
                            continue&lt;/pre&gt;
  &lt;pre id=&quot;VKOi&quot;&gt;                    cutoff = get_stream_cutoff_values(update, content)
                    cutoff += backoff&lt;/pre&gt;
  &lt;pre id=&quot;czMK&quot;&gt;                    if i == 0:
                        try:
                            if sent_message is not None:
                                await context.bot.delete_message(chat_id=sent_message.chat_id,
                                                                 message_id=sent_message.message_id)
                            sent_message = await update.effective_message.reply_text(
                                message_thread_id=get_thread_id(update),
                                reply_to_message_id=get_reply_to_message_id(self.config, update),
                                text=content,
                            )
                        except:
                            continue&lt;/pre&gt;
  &lt;pre id=&quot;n3gd&quot;&gt;                    elif abs(len(content) - len(prev)) &amp;gt; cutoff or tokens != &amp;#x27;not_finished&amp;#x27;:
                        prev = content&lt;/pre&gt;
  &lt;pre id=&quot;MdEZ&quot;&gt;                        try:
                            use_markdown = tokens != &amp;#x27;not_finished&amp;#x27;
                            await edit_message_with_retry(context, chat_id, str(sent_message.message_id),
                                                          text=content, markdown=use_markdown)&lt;/pre&gt;
  &lt;pre id=&quot;uvhm&quot;&gt;                        except RetryAfter as e:
                            backoff += 5
                            await asyncio.sleep(e.retry_after)
                            continue&lt;/pre&gt;
  &lt;pre id=&quot;PImA&quot;&gt;                        except TimedOut:
                            backoff += 5
                            await asyncio.sleep(0.5)
                            continue&lt;/pre&gt;
  &lt;pre id=&quot;XLQS&quot;&gt;                        except Exception:
                            backoff += 5
                            continue&lt;/pre&gt;
  &lt;pre id=&quot;ztWr&quot;&gt;                        await asyncio.sleep(0.01)&lt;/pre&gt;
  &lt;pre id=&quot;jBPz&quot;&gt;                    i += 1
                    if tokens != &amp;#x27;not_finished&amp;#x27;:
                        total_tokens = int(tokens)&lt;/pre&gt;
  &lt;pre id=&quot;7X18&quot;&gt;                
            else:&lt;/pre&gt;
  &lt;pre id=&quot;AGGb&quot;&gt;                try:
                    interpretation, total_tokens = await self.openai.interpret_image(chat_id, temp_file_png, prompt=prompt)&lt;/pre&gt;
  &lt;pre id=&quot;ghmF&quot;&gt;
                    try:
                        await update.effective_message.reply_text(
                            message_thread_id=get_thread_id(update),
                            reply_to_message_id=get_reply_to_message_id(self.config, update),
                            text=interpretation,
                            parse_mode=constants.ParseMode.MARKDOWN
                        )
                    except BadRequest:
                        try:
                            await update.effective_message.reply_text(
                                message_thread_id=get_thread_id(update),
                                reply_to_message_id=get_reply_to_message_id(self.config, update),
                                text=interpretation
                            )
                        except Exception as e:
                            logging.exception(e)
                            await update.effective_message.reply_text(
                                message_thread_id=get_thread_id(update),
                                reply_to_message_id=get_reply_to_message_id(self.config, update),
                                text=f&amp;quot;{localized_text(&amp;#x27;vision_fail&amp;#x27;, bot_language)}: {str(e)}&amp;quot;,
                                parse_mode=constants.ParseMode.MARKDOWN
                            )
                except Exception as e:
                    logging.exception(e)
                    await update.effective_message.reply_text(
                        message_thread_id=get_thread_id(update),
                        reply_to_message_id=get_reply_to_message_id(self.config, update),
                        text=f&amp;quot;{localized_text(&amp;#x27;vision_fail&amp;#x27;, bot_language)}: {str(e)}&amp;quot;,
                        parse_mode=constants.ParseMode.MARKDOWN
                    )
            vision_token_price = self.config[&amp;#x27;vision_token_price&amp;#x27;]
            self.usage[user_id].add_vision_tokens(total_tokens, vision_token_price)&lt;/pre&gt;
  &lt;pre id=&quot;ImCw&quot;&gt;            allowed_user_ids = self.config[&amp;#x27;allowed_user_ids&amp;#x27;].split(&amp;#x27;,&amp;#x27;)
            if str(user_id) not in allowed_user_ids and &amp;#x27;guests&amp;#x27; in self.usage:
                self.usage[&amp;quot;guests&amp;quot;].add_vision_tokens(total_tokens, vision_token_price)&lt;/pre&gt;
  &lt;pre id=&quot;EnWn&quot;&gt;        await wrap_with_indicator(update, context, _execute, constants.ChatAction.TYPING)&lt;/pre&gt;
  &lt;pre id=&quot;zDtH&quot;&gt;    async def prompt(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
        &amp;quot;&amp;quot;&amp;quot;
        React to incoming messages and respond accordingly.
        &amp;quot;&amp;quot;&amp;quot;
        if update.edited_message or not update.message or update.message.via_bot:
            return&lt;/pre&gt;
  &lt;pre id=&quot;CTpJ&quot;&gt;        if not await self.check_allowed_and_within_budget(update, context):
            return&lt;/pre&gt;
  &lt;pre id=&quot;WBJC&quot;&gt;        logging.info(
            f&amp;#x27;New message received from user {update.message.from_user.name} (id: {update.message.from_user.id})&amp;#x27;)
        chat_id = update.effective_chat.id
        user_id = update.message.from_user.id
        prompt = message_text(update.message)
        self.last_message[chat_id] = prompt&lt;/pre&gt;
  &lt;pre id=&quot;fHN7&quot;&gt;        if is_group_chat(update):
            trigger_keyword = self.config[&amp;#x27;group_trigger_keyword&amp;#x27;]&lt;/pre&gt;
  &lt;pre id=&quot;2RwA&quot;&gt;            if prompt.lower().startswith(trigger_keyword.lower()) or update.message.text.lower().startswith(&amp;#x27;/chat&amp;#x27;):
                if prompt.lower().startswith(trigger_keyword.lower()):
                    prompt = prompt[len(trigger_keyword):].strip()&lt;/pre&gt;
  &lt;pre id=&quot;p6pk&quot;&gt;                if update.message.reply_to_message and \
                        update.message.reply_to_message.text and \
                        update.message.reply_to_message.from_user.id != context.bot.id:
                    prompt = f&amp;#x27;&amp;quot;{update.message.reply_to_message.text}&amp;quot; {prompt}&amp;#x27;
            else:
                if update.message.reply_to_message and update.message.reply_to_message.from_user.id == context.bot.id:
                    logging.info(&amp;#x27;Message is a reply to the bot, allowing...&amp;#x27;)
                else:
                    logging.warning(&amp;#x27;Message does not start with trigger keyword, ignoring...&amp;#x27;)
                    return&lt;/pre&gt;
  &lt;pre id=&quot;qo9g&quot;&gt;        try:
            total_tokens = 0&lt;/pre&gt;
  &lt;pre id=&quot;Pyt7&quot;&gt;            if self.config[&amp;#x27;stream&amp;#x27;]:
                await update.effective_message.reply_chat_action(
                    action=constants.ChatAction.TYPING,
                    message_thread_id=get_thread_id(update)
                )&lt;/pre&gt;
  &lt;pre id=&quot;MMjJ&quot;&gt;                stream_response = self.openai.get_chat_response_stream(chat_id=chat_id, query=prompt)
                i = 0
                prev = &amp;#x27;&amp;#x27;
                sent_message = None
                backoff = 0
                stream_chunk = 0&lt;/pre&gt;
  &lt;pre id=&quot;99nA&quot;&gt;                async for content, tokens in stream_response:
                    if is_direct_result(content):
                        return await handle_direct_result(self.config, update, content)&lt;/pre&gt;
  &lt;pre id=&quot;fSJG&quot;&gt;                    if len(content.strip()) == 0:
                        continue&lt;/pre&gt;
  &lt;pre id=&quot;xI41&quot;&gt;                    stream_chunks = split_into_chunks(content)
                    if len(stream_chunks) &amp;gt; 1:
                        content = stream_chunks[-1]
                        if stream_chunk != len(stream_chunks) - 1:
                            stream_chunk += 1
                            try:
                                await edit_message_with_retry(context, chat_id, str(sent_message.message_id),
                                                              stream_chunks[-2])
                            except:
                                pass
                            try:
                                sent_message = await update.effective_message.reply_text(
                                    message_thread_id=get_thread_id(update),
                                    text=content if len(content) &amp;gt; 0 else &amp;quot;...&amp;quot;
                                )
                            except:
                                pass
                            continue&lt;/pre&gt;
  &lt;pre id=&quot;SoIu&quot;&gt;                    cutoff = get_stream_cutoff_values(update, content)
                    cutoff += backoff&lt;/pre&gt;
  &lt;pre id=&quot;vPr8&quot;&gt;                    if i == 0:
                        try:
                            if sent_message is not None:
                                await context.bot.delete_message(chat_id=sent_message.chat_id,
                                                                 message_id=sent_message.message_id)
                            sent_message = await update.effective_message.reply_text(
                                message_thread_id=get_thread_id(update),
                                reply_to_message_id=get_reply_to_message_id(self.config, update),
                                text=content,
                            )
                        except:
                            continue&lt;/pre&gt;
  &lt;pre id=&quot;NOWF&quot;&gt;                    elif abs(len(content) - len(prev)) &amp;gt; cutoff or tokens != &amp;#x27;not_finished&amp;#x27;:
                        prev = content&lt;/pre&gt;
  &lt;pre id=&quot;HW3r&quot;&gt;                        try:
                            use_markdown = tokens != &amp;#x27;not_finished&amp;#x27;
                            await edit_message_with_retry(context, chat_id, str(sent_message.message_id),
                                                          text=content, markdown=use_markdown)&lt;/pre&gt;
  &lt;pre id=&quot;oFJZ&quot;&gt;                        except RetryAfter as e:
                            backoff += 5
                            await asyncio.sleep(e.retry_after)
                            continue&lt;/pre&gt;
  &lt;pre id=&quot;mxyw&quot;&gt;                        except TimedOut:
                            backoff += 5
                            await asyncio.sleep(0.5)
                            continue&lt;/pre&gt;
  &lt;pre id=&quot;1z6F&quot;&gt;                        except Exception:
                            backoff += 5
                            continue&lt;/pre&gt;
  &lt;pre id=&quot;VW73&quot;&gt;                        await asyncio.sleep(0.01)&lt;/pre&gt;
  &lt;pre id=&quot;q54H&quot;&gt;                    i += 1
                    if tokens != &amp;#x27;not_finished&amp;#x27;:
                        total_tokens = int(tokens)&lt;/pre&gt;
  &lt;pre id=&quot;hPBa&quot;&gt;            else:
                async def _reply():
                    nonlocal total_tokens
                    response, total_tokens = await self.openai.get_chat_response(chat_id=chat_id, query=prompt)&lt;/pre&gt;
  &lt;pre id=&quot;mIao&quot;&gt;                    if is_direct_result(response):
                        return await handle_direct_result(self.config, update, response)&lt;/pre&gt;
  &lt;pre id=&quot;hYeW&quot;&gt;                    # Split into chunks of 4096 characters (Telegram&amp;#x27;s message limit)
                    chunks = split_into_chunks(response)&lt;/pre&gt;
  &lt;pre id=&quot;2T80&quot;&gt;                    for index, chunk in enumerate(chunks):
                        try:
                            await update.effective_message.reply_text(
                                message_thread_id=get_thread_id(update),
                                reply_to_message_id=get_reply_to_message_id(self.config,
                                                                            update) if index == 0 else None,
                                text=chunk,
                                parse_mode=constants.ParseMode.MARKDOWN
                            )
                        except Exception:
                            try:
                                await update.effective_message.reply_text(
                                    message_thread_id=get_thread_id(update),
                                    reply_to_message_id=get_reply_to_message_id(self.config,
                                                                                update) if index == 0 else None,
                                    text=chunk
                                )
                            except Exception as exception:
                                raise exception&lt;/pre&gt;
  &lt;pre id=&quot;epVb&quot;&gt;                await wrap_with_indicator(update, context, _reply, constants.ChatAction.TYPING)&lt;/pre&gt;
  &lt;pre id=&quot;UW16&quot;&gt;            add_chat_request_to_usage_tracker(self.usage, self.config, user_id, total_tokens)&lt;/pre&gt;
  &lt;pre id=&quot;qJfc&quot;&gt;        except Exception as e:
            logging.exception(e)
            await update.effective_message.reply_text(
                message_thread_id=get_thread_id(update),
                reply_to_message_id=get_reply_to_message_id(self.config, update),
                text=f&amp;quot;{localized_text(&amp;#x27;chat_fail&amp;#x27;, self.config[&amp;#x27;bot_language&amp;#x27;])} {str(e)}&amp;quot;,
                parse_mode=constants.ParseMode.MARKDOWN
            )&lt;/pre&gt;
  &lt;pre id=&quot;EX08&quot;&gt;    async def inline_query(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -&amp;gt; None:
        &amp;quot;&amp;quot;&amp;quot;
        Handle the inline query. This is run when you type: @botusername &amp;lt;query&amp;gt;
        &amp;quot;&amp;quot;&amp;quot;
        query = update.inline_query.query
        if len(query) &amp;lt; 3:
            return
        if not await self.check_allowed_and_within_budget(update, context, is_inline=True):
            return&lt;/pre&gt;
  &lt;pre id=&quot;4M0C&quot;&gt;        callback_data_suffix = &amp;quot;gpt:&amp;quot;
        result_id = str(uuid4())
        self.inline_queries_cache[result_id] = query
        callback_data = f&amp;#x27;{callback_data_suffix}{result_id}&amp;#x27;&lt;/pre&gt;
  &lt;pre id=&quot;XSC3&quot;&gt;        await self.send_inline_query_result(update, result_id, message_content=query, callback_data=callback_data)&lt;/pre&gt;
  &lt;pre id=&quot;f8Wd&quot;&gt;    async def send_inline_query_result(self, update: Update, result_id, message_content, callback_data=&amp;quot;&amp;quot;):
        &amp;quot;&amp;quot;&amp;quot;
        Send inline query result
        &amp;quot;&amp;quot;&amp;quot;
        try:
            reply_markup = None
            bot_language = self.config[&amp;#x27;bot_language&amp;#x27;]
            if callback_data:
                reply_markup = InlineKeyboardMarkup([[
                    InlineKeyboardButton(text=f&amp;#x27;🤖 {localized_text(&amp;quot;answer_with_chatgpt&amp;quot;, bot_language)}&amp;#x27;,
                                         callback_data=callback_data)
                ]])&lt;/pre&gt;
  &lt;pre id=&quot;pTLa&quot;&gt;            inline_query_result = InlineQueryResultArticle(
                id=result_id,
                title=localized_text(&amp;quot;ask_chatgpt&amp;quot;, bot_language),
                input_message_content=InputTextMessageContent(message_content),
                description=message_content,
                thumbnail_url=&amp;#x27;https://user-images.githubusercontent.com/11541888/223106202-7576ff11-2c8e-408d-94ea-b02a7a32149a.png&amp;#x27;,
                reply_markup=reply_markup
            )&lt;/pre&gt;
  &lt;pre id=&quot;XtP1&quot;&gt;            await update.inline_query.answer([inline_query_result], cache_time=0)
        except Exception as e:
            logging.error(f&amp;#x27;An error occurred while generating the result card for inline query {e}&amp;#x27;)&lt;/pre&gt;
  &lt;pre id=&quot;FRIb&quot;&gt;    async def handle_callback_inline_query(self, update: Update, context: CallbackContext):
        &amp;quot;&amp;quot;&amp;quot;
        Handle the callback query from the inline query result
        &amp;quot;&amp;quot;&amp;quot;
        callback_data = update.callback_query.data
        user_id = update.callback_query.from_user.id
        inline_message_id = update.callback_query.inline_message_id
        name = update.callback_query.from_user.name
        callback_data_suffix = &amp;quot;gpt:&amp;quot;
        query = &amp;quot;&amp;quot;
        bot_language = self.config[&amp;#x27;bot_language&amp;#x27;]
        answer_tr = localized_text(&amp;quot;answer&amp;quot;, bot_language)
        loading_tr = localized_text(&amp;quot;loading&amp;quot;, bot_language)&lt;/pre&gt;
  &lt;pre id=&quot;dJoH&quot;&gt;        try:
            if callback_data.startswith(callback_data_suffix):
                unique_id = callback_data.split(&amp;#x27;:&amp;#x27;)[1]
                total_tokens = 0&lt;/pre&gt;
  &lt;pre id=&quot;ml6O&quot;&gt;                # Retrieve the prompt from the cache
                query = self.inline_queries_cache.get(unique_id)
                if query:
                    self.inline_queries_cache.pop(unique_id)
                else:
                    error_message = (
                        f&amp;#x27;{localized_text(&amp;quot;error&amp;quot;, bot_language)}. &amp;#x27;
                        f&amp;#x27;{localized_text(&amp;quot;try_again&amp;quot;, bot_language)}&amp;#x27;
                    )
                    await edit_message_with_retry(context, chat_id=None, message_id=inline_message_id,
                                                  text=f&amp;#x27;{query}\n\n_{answer_tr}:_\n{error_message}&amp;#x27;,
                                                  is_inline=True)
                    return&lt;/pre&gt;
  &lt;pre id=&quot;TfJn&quot;&gt;                unavailable_message = localized_text(&amp;quot;function_unavailable_in_inline_mode&amp;quot;, bot_language)
                if self.config[&amp;#x27;stream&amp;#x27;]:
                    stream_response = self.openai.get_chat_response_stream(chat_id=user_id, query=query)
                    i = 0
                    prev = &amp;#x27;&amp;#x27;
                    backoff = 0
                    async for content, tokens in stream_response:
                        if is_direct_result(content):
                            cleanup_intermediate_files(content)
                            await edit_message_with_retry(context, chat_id=None,
                                                          message_id=inline_message_id,
                                                          text=f&amp;#x27;{query}\n\n_{answer_tr}:_\n{unavailable_message}&amp;#x27;,
                                                          is_inline=True)
                            return&lt;/pre&gt;
  &lt;pre id=&quot;iTqy&quot;&gt;                        if len(content.strip()) == 0:
                            continue&lt;/pre&gt;
  &lt;pre id=&quot;ET25&quot;&gt;                        cutoff = get_stream_cutoff_values(update, content)
                        cutoff += backoff&lt;/pre&gt;
  &lt;pre id=&quot;j502&quot;&gt;                        if i == 0:
                            try:
                                await edit_message_with_retry(context, chat_id=None,
                                                              message_id=inline_message_id,
                                                              text=f&amp;#x27;{query}\n\n{answer_tr}:\n{content}&amp;#x27;,
                                                              is_inline=True)
                            except:
                                continue&lt;/pre&gt;
  &lt;pre id=&quot;En7V&quot;&gt;                        elif abs(len(content) - len(prev)) &amp;gt; cutoff or tokens != &amp;#x27;not_finished&amp;#x27;:
                            prev = content
                            try:
                                use_markdown = tokens != &amp;#x27;not_finished&amp;#x27;
                                divider = &amp;#x27;_&amp;#x27; if use_markdown else &amp;#x27;&amp;#x27;
                                text = f&amp;#x27;{query}\n\n{divider}{answer_tr}:{divider}\n{content}&amp;#x27;&lt;/pre&gt;
  &lt;pre id=&quot;LC0v&quot;&gt;                                # We only want to send the first 4096 characters. No chunking allowed in inline mode.
                                text = text[:4096]&lt;/pre&gt;
  &lt;pre id=&quot;B0bw&quot;&gt;                                await edit_message_with_retry(context, chat_id=None, message_id=inline_message_id,
                                                              text=text, markdown=use_markdown, is_inline=True)&lt;/pre&gt;
  &lt;pre id=&quot;6jd9&quot;&gt;                            except RetryAfter as e:
                                backoff += 5
                                await asyncio.sleep(e.retry_after)
                                continue
                            except TimedOut:
                                backoff += 5
                                await asyncio.sleep(0.5)
                                continue
                            except Exception:
                                backoff += 5
                                continue&lt;/pre&gt;
  &lt;pre id=&quot;vHby&quot;&gt;                            await asyncio.sleep(0.01)&lt;/pre&gt;
  &lt;pre id=&quot;PosJ&quot;&gt;                        i += 1
                        if tokens != &amp;#x27;not_finished&amp;#x27;:
                            total_tokens = int(tokens)&lt;/pre&gt;
  &lt;pre id=&quot;CESi&quot;&gt;                else:
                    async def _send_inline_query_response():
                        nonlocal total_tokens
                        # Edit the current message to indicate that the answer is being processed
                        await context.bot.edit_message_text(inline_message_id=inline_message_id,
                                                            text=f&amp;#x27;{query}\n\n_{answer_tr}:_\n{loading_tr}&amp;#x27;,
                                                            parse_mode=constants.ParseMode.MARKDOWN)&lt;/pre&gt;
  &lt;pre id=&quot;sDgv&quot;&gt;                        logging.info(f&amp;#x27;Generating response for inline query by {name}&amp;#x27;)
                        response, total_tokens = await self.openai.get_chat_response(chat_id=user_id, query=query)&lt;/pre&gt;
  &lt;pre id=&quot;icra&quot;&gt;                        if is_direct_result(response):
                            cleanup_intermediate_files(response)
                            await edit_message_with_retry(context, chat_id=None,
                                                          message_id=inline_message_id,
                                                          text=f&amp;#x27;{query}\n\n_{answer_tr}:_\n{unavailable_message}&amp;#x27;,
                                                          is_inline=True)
                            return&lt;/pre&gt;
  &lt;pre id=&quot;yYLB&quot;&gt;                        text_content = f&amp;#x27;{query}\n\n_{answer_tr}:_\n{response}&amp;#x27;&lt;/pre&gt;
  &lt;pre id=&quot;C8Yb&quot;&gt;                        # We only want to send the first 4096 characters. No chunking allowed in inline mode.
                        text_content = text_content[:4096]&lt;/pre&gt;
  &lt;pre id=&quot;7zzo&quot;&gt;                        # Edit the original message with the generated content
                        await edit_message_with_retry(context, chat_id=None, message_id=inline_message_id,
                                                      text=text_content, is_inline=True)&lt;/pre&gt;
  &lt;pre id=&quot;k4zw&quot;&gt;                    await wrap_with_indicator(update, context, _send_inline_query_response,
                                              constants.ChatAction.TYPING, is_inline=True)&lt;/pre&gt;
  &lt;pre id=&quot;xrN9&quot;&gt;                add_chat_request_to_usage_tracker(self.usage, self.config, user_id, total_tokens)&lt;/pre&gt;
  &lt;pre id=&quot;7SBu&quot;&gt;        except Exception as e:
            logging.error(f&amp;#x27;Failed to respond to an inline query via button callback: {e}&amp;#x27;)
            logging.exception(e)
            localized_answer = localized_text(&amp;#x27;chat_fail&amp;#x27;, self.config[&amp;#x27;bot_language&amp;#x27;])
            await edit_message_with_retry(context, chat_id=None, message_id=inline_message_id,
                                          text=f&amp;quot;{query}\n\n_{answer_tr}:_\n{localized_answer} {str(e)}&amp;quot;,
                                          is_inline=True)&lt;/pre&gt;
  &lt;pre id=&quot;24te&quot;&gt;    async def check_allowed_and_within_budget(self, update: Update, context: ContextTypes.DEFAULT_TYPE,
                                              is_inline=False) -&amp;gt; bool:
        &amp;quot;&amp;quot;&amp;quot;
        Checks if the user is allowed to use the bot and if they are within their budget
        :param update: Telegram update object
        :param context: Telegram context object
        :param is_inline: Boolean flag for inline queries
        :return: Boolean indicating if the user is allowed to use the bot
        &amp;quot;&amp;quot;&amp;quot;
        name = update.inline_query.from_user.name if is_inline else update.message.from_user.name
        user_id = update.inline_query.from_user.id if is_inline else update.message.from_user.id&lt;/pre&gt;
  &lt;pre id=&quot;GTFM&quot;&gt;        if not await is_allowed(self.config, update, context, is_inline=is_inline):
            logging.warning(f&amp;#x27;User {name} (id: {user_id}) is not allowed to use the bot&amp;#x27;)
            await self.send_disallowed_message(update, context, is_inline)
            return False
        if not is_within_budget(self.config, self.usage, update, is_inline=is_inline):
            logging.warning(f&amp;#x27;User {name} (id: {user_id}) reached their usage limit&amp;#x27;)
            await self.send_budget_reached_message(update, context, is_inline)
            return False&lt;/pre&gt;
  &lt;pre id=&quot;ZnXr&quot;&gt;        return True&lt;/pre&gt;
  &lt;pre id=&quot;HC2c&quot;&gt;    async def send_disallowed_message(self, update: Update, _: ContextTypes.DEFAULT_TYPE, is_inline=False):
        &amp;quot;&amp;quot;&amp;quot;
        Sends the disallowed message to the user.
        &amp;quot;&amp;quot;&amp;quot;
        if not is_inline:
            await update.effective_message.reply_text(
                message_thread_id=get_thread_id(update),
                text=self.disallowed_message,
                disable_web_page_preview=True
            )
        else:
            result_id = str(uuid4())
            await self.send_inline_query_result(update, result_id, message_content=self.disallowed_message)&lt;/pre&gt;
  &lt;pre id=&quot;Rvku&quot;&gt;    async def send_budget_reached_message(self, update: Update, _: ContextTypes.DEFAULT_TYPE, is_inline=False):
        &amp;quot;&amp;quot;&amp;quot;
        Sends the budget reached message to the user.
        &amp;quot;&amp;quot;&amp;quot;
        if not is_inline:
            await update.effective_message.reply_text(
                message_thread_id=get_thread_id(update),
                text=self.budget_limit_message
            )
        else:
            result_id = str(uuid4())
            await self.send_inline_query_result(update, result_id, message_content=self.budget_limit_message)&lt;/pre&gt;
  &lt;pre id=&quot;0Asp&quot;&gt;    async def post_init(self, application: Application) -&amp;gt; None:
        &amp;quot;&amp;quot;&amp;quot;
        Post initialization hook for the bot.
        &amp;quot;&amp;quot;&amp;quot;
        await application.bot.set_my_commands(self.group_commands, scope=BotCommandScopeAllGroupChats())
        await application.bot.set_my_commands(self.commands)&lt;/pre&gt;
  &lt;pre id=&quot;loKq&quot;&gt;    def run(self):
        &amp;quot;&amp;quot;&amp;quot;
        Runs the bot indefinitely until the user presses Ctrl+C
        &amp;quot;&amp;quot;&amp;quot;
        application = ApplicationBuilder() \
            .token(self.config[&amp;#x27;token&amp;#x27;]) \
            .proxy_url(self.config[&amp;#x27;proxy&amp;#x27;]) \
            .get_updates_proxy_url(self.config[&amp;#x27;proxy&amp;#x27;]) \
            .post_init(self.post_init) \
            .concurrent_updates(True) \
            .build()&lt;/pre&gt;
  &lt;pre id=&quot;Krrx&quot;&gt;        application.add_handler(CommandHandler(&amp;#x27;reset&amp;#x27;, self.reset))
        application.add_handler(CommandHandler(&amp;#x27;help&amp;#x27;, self.help))
        application.add_handler(CommandHandler(&amp;#x27;image&amp;#x27;, self.image))
        application.add_handler(CommandHandler(&amp;#x27;tts&amp;#x27;, self.tts))
        application.add_handler(CommandHandler(&amp;#x27;start&amp;#x27;, self.help))
        application.add_handler(CommandHandler(&amp;#x27;stats&amp;#x27;, self.stats))
        application.add_handler(CommandHandler(&amp;#x27;resend&amp;#x27;, self.resend))
        application.add_handler(CommandHandler(
            &amp;#x27;chat&amp;#x27;, self.prompt, filters=filters.ChatType.GROUP | filters.ChatType.SUPERGROUP)
        )
        application.add_handler(MessageHandler(
            filters.PHOTO | filters.Document.IMAGE,
            self.vision))
        application.add_handler(MessageHandler(
            filters.AUDIO | filters.VOICE | filters.Document.AUDIO |
            filters.VIDEO | filters.VIDEO_NOTE | filters.Document.VIDEO,
            self.transcribe))
        application.add_handler(MessageHandler(filters.TEXT &amp;amp; (~filters.COMMAND), self.prompt))
        application.add_handler(InlineQueryHandler(self.inline_query, chat_types=[
            constants.ChatType.GROUP, constants.ChatType.SUPERGROUP, constants.ChatType.PRIVATE
        ]))
        application.add_handler(CallbackQueryHandler(self.handle_callback_inline_query))&lt;/pre&gt;
  &lt;pre id=&quot;WFTB&quot;&gt;        application.add_error_handler(error_handler)&lt;/pre&gt;
  &lt;pre id=&quot;fCwT&quot;&gt;        application.run_polling()&lt;/pre&gt;
  &lt;p id=&quot;JmLM&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;DeS5&quot;&gt;&lt;/h3&gt;
  &lt;h3 id=&quot;UfRP&quot;&gt;↪️usage_tracker.py&lt;/h3&gt;
  &lt;pre id=&quot;Q5On&quot;&gt;import os.path
import pathlib
import json
from datetime import date&lt;/pre&gt;
  &lt;pre id=&quot;n9a0&quot;&gt;
def year_month(date_str):
    # extract string of year-month from date, eg: &amp;#x27;2023-03&amp;#x27;
    return str(date_str)[:7]&lt;/pre&gt;
  &lt;pre id=&quot;iDMI&quot;&gt;
class UsageTracker:
    &amp;quot;&amp;quot;&amp;quot;
    UsageTracker class
    Enables tracking of daily/monthly usage per user.
    User files are stored as JSON in /usage_logs directory.
    JSON example:
    {
        &amp;quot;user_name&amp;quot;: &amp;quot;@user_name&amp;quot;,
        &amp;quot;current_cost&amp;quot;: {
            &amp;quot;day&amp;quot;: 0.45,
            &amp;quot;month&amp;quot;: 3.23,
            &amp;quot;all_time&amp;quot;: 3.23,
            &amp;quot;last_update&amp;quot;: &amp;quot;2023-03-14&amp;quot;},
        &amp;quot;usage_history&amp;quot;: {
            &amp;quot;chat_tokens&amp;quot;: {
                &amp;quot;2023-03-13&amp;quot;: 520,
                &amp;quot;2023-03-14&amp;quot;: 1532
            },
            &amp;quot;transcription_seconds&amp;quot;: {
                &amp;quot;2023-03-13&amp;quot;: 125,
                &amp;quot;2023-03-14&amp;quot;: 64
            },
            &amp;quot;number_images&amp;quot;: {
                &amp;quot;2023-03-12&amp;quot;: [0, 2, 3],
                &amp;quot;2023-03-13&amp;quot;: [1, 2, 3],
                &amp;quot;2023-03-14&amp;quot;: [0, 1, 2]
            }
        }
    }
    &amp;quot;&amp;quot;&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;F9z4&quot;&gt;    def __init__(self, user_id, user_name, logs_dir=&amp;quot;usage_logs&amp;quot;):
        &amp;quot;&amp;quot;&amp;quot;
        Initializes UsageTracker for a user with current date.
        Loads usage data from usage log file.
        :param user_id: Telegram ID of the user
        :param user_name: Telegram user name
        :param logs_dir: path to directory of usage logs, defaults to &amp;quot;usage_logs&amp;quot;
        &amp;quot;&amp;quot;&amp;quot;
        self.user_id = user_id
        self.logs_dir = logs_dir
        # path to usage file of given user
        self.user_file = f&amp;quot;{logs_dir}/{user_id}.json&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;ZPRB&quot;&gt;        if os.path.isfile(self.user_file):
            with open(self.user_file, &amp;quot;r&amp;quot;) as file:
                self.usage = json.load(file)
            if &amp;#x27;vision_tokens&amp;#x27; not in self.usage[&amp;#x27;usage_history&amp;#x27;]:
                self.usage[&amp;#x27;usage_history&amp;#x27;][&amp;#x27;vision_tokens&amp;#x27;] = {}
            if &amp;#x27;tts_characters&amp;#x27; not in self.usage[&amp;#x27;usage_history&amp;#x27;]:
                self.usage[&amp;#x27;usage_history&amp;#x27;][&amp;#x27;tts_characters&amp;#x27;] = {}
        else:
            # ensure directory exists
            pathlib.Path(logs_dir).mkdir(exist_ok=True)
            # create new dictionary for this user
            self.usage = {
                &amp;quot;user_name&amp;quot;: user_name,
                &amp;quot;current_cost&amp;quot;: {&amp;quot;day&amp;quot;: 0.0, &amp;quot;month&amp;quot;: 0.0, &amp;quot;all_time&amp;quot;: 0.0, &amp;quot;last_update&amp;quot;: str(date.today())},
                &amp;quot;usage_history&amp;quot;: {&amp;quot;chat_tokens&amp;quot;: {}, &amp;quot;transcription_seconds&amp;quot;: {}, &amp;quot;number_images&amp;quot;: {}, &amp;quot;tts_characters&amp;quot;: {}, &amp;quot;vision_tokens&amp;quot;:{}}
            }&lt;/pre&gt;
  &lt;pre id=&quot;q6bD&quot;&gt;    # token usage functions:&lt;/pre&gt;
  &lt;pre id=&quot;rqif&quot;&gt;    def add_chat_tokens(self, tokens, tokens_price=0.002):
        &amp;quot;&amp;quot;&amp;quot;Adds used tokens from a request to a users usage history and updates current cost
        :param tokens: total tokens used in last request
        :param tokens_price: price per 1000 tokens, defaults to 0.002
        &amp;quot;&amp;quot;&amp;quot;
        today = date.today()
        token_cost = round(float(tokens) * tokens_price / 1000, 6)
        self.add_current_costs(token_cost)&lt;/pre&gt;
  &lt;pre id=&quot;Wfzr&quot;&gt;        # update usage_history
        if str(today) in self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;chat_tokens&amp;quot;]:
            # add token usage to existing date
            self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;chat_tokens&amp;quot;][str(today)] += tokens
        else:
            # create new entry for current date
            self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;chat_tokens&amp;quot;][str(today)] = tokens&lt;/pre&gt;
  &lt;pre id=&quot;mnpU&quot;&gt;        # write updated token usage to user file
        with open(self.user_file, &amp;quot;w&amp;quot;) as outfile:
            json.dump(self.usage, outfile)&lt;/pre&gt;
  &lt;pre id=&quot;ZXZq&quot;&gt;    def get_current_token_usage(self):
        &amp;quot;&amp;quot;&amp;quot;Get token amounts used for today and this month&lt;/pre&gt;
  &lt;pre id=&quot;mw6y&quot;&gt;        :return: total number of tokens used per day and per month
        &amp;quot;&amp;quot;&amp;quot;
        today = date.today()
        if str(today) in self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;chat_tokens&amp;quot;]:
            usage_day = self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;chat_tokens&amp;quot;][str(today)]
        else:
            usage_day = 0
        month = str(today)[:7]  # year-month as string
        usage_month = 0
        for today, tokens in self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;chat_tokens&amp;quot;].items():
            if today.startswith(month):
                usage_month += tokens
        return usage_day, usage_month&lt;/pre&gt;
  &lt;pre id=&quot;wy1A&quot;&gt;    # image usage functions:&lt;/pre&gt;
  &lt;pre id=&quot;OvH8&quot;&gt;    def add_image_request(self, image_size, image_prices=&amp;quot;0.016,0.018,0.02&amp;quot;):
        &amp;quot;&amp;quot;&amp;quot;Add image request to users usage history and update current costs.&lt;/pre&gt;
  &lt;pre id=&quot;9rkc&quot;&gt;        :param image_size: requested image size
        :param image_prices: prices for images of sizes [&amp;quot;256x256&amp;quot;, &amp;quot;512x512&amp;quot;, &amp;quot;1024x1024&amp;quot;],
                             defaults to [0.016, 0.018, 0.02]
        &amp;quot;&amp;quot;&amp;quot;
        sizes = [&amp;quot;256x256&amp;quot;, &amp;quot;512x512&amp;quot;, &amp;quot;1024x1024&amp;quot;]
        requested_size = sizes.index(image_size)
        image_cost = image_prices[requested_size]
        today = date.today()
        self.add_current_costs(image_cost)&lt;/pre&gt;
  &lt;pre id=&quot;IUYP&quot;&gt;        # update usage_history
        if str(today) in self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;number_images&amp;quot;]:
            # add token usage to existing date
            self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;number_images&amp;quot;][str(today)][requested_size] += 1
        else:
            # create new entry for current date
            self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;number_images&amp;quot;][str(today)] = [0, 0, 0]
            self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;number_images&amp;quot;][str(today)][requested_size] += 1&lt;/pre&gt;
  &lt;pre id=&quot;I23y&quot;&gt;        # write updated image number to user file
        with open(self.user_file, &amp;quot;w&amp;quot;) as outfile:
            json.dump(self.usage, outfile)&lt;/pre&gt;
  &lt;pre id=&quot;LsVb&quot;&gt;    def get_current_image_count(self):
        &amp;quot;&amp;quot;&amp;quot;Get number of images requested for today and this month.&lt;/pre&gt;
  &lt;pre id=&quot;8uMm&quot;&gt;        :return: total number of images requested per day and per month
        &amp;quot;&amp;quot;&amp;quot;
        today = date.today()
        if str(today) in self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;number_images&amp;quot;]:
            usage_day = sum(self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;number_images&amp;quot;][str(today)])
        else:
            usage_day = 0
        month = str(today)[:7]  # year-month as string
        usage_month = 0
        for today, images in self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;number_images&amp;quot;].items():
            if today.startswith(month):
                usage_month += sum(images)
        return usage_day, usage_month&lt;/pre&gt;
  &lt;pre id=&quot;BtpR&quot;&gt;
    # vision usage functions
    def add_vision_tokens(self, tokens, vision_token_price=0.01):
        &amp;quot;&amp;quot;&amp;quot;
         Adds requested vision tokens to a users usage history and updates current cost.
        :param tokens: total tokens used in last request
        :param vision_token_price: price per 1K tokens transcription, defaults to 0.01
        &amp;quot;&amp;quot;&amp;quot;
        today = date.today()
        token_price = round(tokens * vision_token_price / 1000, 2)
        self.add_current_costs(token_price)&lt;/pre&gt;
  &lt;pre id=&quot;6bfL&quot;&gt;        # update usage_history
        if str(today) in self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;vision_tokens&amp;quot;]:
            # add requested seconds to existing date
            self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;vision_tokens&amp;quot;][str(today)] += tokens
        else:
            # create new entry for current date
            self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;vision_tokens&amp;quot;][str(today)] = tokens&lt;/pre&gt;
  &lt;pre id=&quot;0nfm&quot;&gt;        # write updated token usage to user file
        with open(self.user_file, &amp;quot;w&amp;quot;) as outfile:
            json.dump(self.usage, outfile)&lt;/pre&gt;
  &lt;pre id=&quot;Z2De&quot;&gt;    def get_current_vision_tokens(self):
        &amp;quot;&amp;quot;&amp;quot;Get vision tokens for today and this month.&lt;/pre&gt;
  &lt;pre id=&quot;tjn8&quot;&gt;        :return: total amount of vision tokens per day and per month
        &amp;quot;&amp;quot;&amp;quot;
        today = date.today()
        if str(today) in self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;vision_tokens&amp;quot;]:
            tokens_day = self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;vision_tokens&amp;quot;][str(today)]
        else:
            tokens_day = 0
        month = str(today)[:7]  # year-month as string
        tokens_month = 0
        for today, tokens in self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;vision_tokens&amp;quot;].items():
            if today.startswith(month):
                tokens_month += tokens
        return tokens_day, tokens_month&lt;/pre&gt;
  &lt;pre id=&quot;Bkhu&quot;&gt;    # tts usage functions:&lt;/pre&gt;
  &lt;pre id=&quot;WRuF&quot;&gt;    def add_tts_request(self, text_length, tts_model, tts_prices):
        tts_models = [&amp;#x27;tts-1&amp;#x27;, &amp;#x27;tts-1-hd&amp;#x27;]
        price = tts_prices[tts_models.index(tts_model)]
        today = date.today()
        tts_price = round(text_length * price / 1000, 2)
        self.add_current_costs(tts_price)&lt;/pre&gt;
  &lt;pre id=&quot;UdVG&quot;&gt;        if &amp;#x27;tts_characters&amp;#x27; not in self.usage[&amp;#x27;usage_history&amp;#x27;]:
            self.usage[&amp;#x27;usage_history&amp;#x27;][&amp;#x27;tts_characters&amp;#x27;] = {}
        
        if tts_model not in self.usage[&amp;#x27;usage_history&amp;#x27;][&amp;#x27;tts_characters&amp;#x27;]:
            self.usage[&amp;#x27;usage_history&amp;#x27;][&amp;#x27;tts_characters&amp;#x27;][tts_model] = {}&lt;/pre&gt;
  &lt;pre id=&quot;ah5h&quot;&gt;        # update usage_history
        if str(today) in self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;tts_characters&amp;quot;][tts_model]:
            # add requested text length to existing date
            self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;tts_characters&amp;quot;][tts_model][str(today)] += text_length
        else:
            # create new entry for current date
            self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;tts_characters&amp;quot;][tts_model][str(today)] = text_length&lt;/pre&gt;
  &lt;pre id=&quot;NWjD&quot;&gt;        # write updated token usage to user file
        with open(self.user_file, &amp;quot;w&amp;quot;) as outfile:
            json.dump(self.usage, outfile)&lt;/pre&gt;
  &lt;pre id=&quot;sxO3&quot;&gt;    def get_current_tts_usage(self):
        &amp;quot;&amp;quot;&amp;quot;Get length of speech generated for today and this month.&lt;/pre&gt;
  &lt;pre id=&quot;sU7Y&quot;&gt;        :return: total amount of characters converted to speech per day and per month
        &amp;quot;&amp;quot;&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;7kWu&quot;&gt;        tts_models = [&amp;#x27;tts-1&amp;#x27;, &amp;#x27;tts-1-hd&amp;#x27;]
        today = date.today()
        characters_day = 0
        for tts_model in tts_models:
            if tts_model in self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;tts_characters&amp;quot;] and \
                str(today) in self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;tts_characters&amp;quot;][tts_model]:
                characters_day += self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;tts_characters&amp;quot;][tts_model][str(today)]&lt;/pre&gt;
  &lt;pre id=&quot;GeUi&quot;&gt;        month = str(today)[:7]  # year-month as string
        characters_month = 0
        for tts_model in tts_models:
            if tts_model in self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;tts_characters&amp;quot;]: 
                for today, characters in self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;tts_characters&amp;quot;][tts_model].items():
                    if today.startswith(month):
                        characters_month += characters
        return int(characters_day), int(characters_month)&lt;/pre&gt;
  &lt;pre id=&quot;LMeh&quot;&gt;
    # transcription usage functions:&lt;/pre&gt;
  &lt;pre id=&quot;UYNu&quot;&gt;    def add_transcription_seconds(self, seconds, minute_price=0.006):
        &amp;quot;&amp;quot;&amp;quot;Adds requested transcription seconds to a users usage history and updates current cost.
        :param seconds: total seconds used in last request
        :param minute_price: price per minute transcription, defaults to 0.006
        &amp;quot;&amp;quot;&amp;quot;
        today = date.today()
        transcription_price = round(seconds * minute_price / 60, 2)
        self.add_current_costs(transcription_price)&lt;/pre&gt;
  &lt;pre id=&quot;LMkl&quot;&gt;        # update usage_history
        if str(today) in self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;transcription_seconds&amp;quot;]:
            # add requested seconds to existing date
            self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;transcription_seconds&amp;quot;][str(today)] += seconds
        else:
            # create new entry for current date
            self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;transcription_seconds&amp;quot;][str(today)] = seconds&lt;/pre&gt;
  &lt;pre id=&quot;J8YE&quot;&gt;        # write updated token usage to user file
        with open(self.user_file, &amp;quot;w&amp;quot;) as outfile:
            json.dump(self.usage, outfile)&lt;/pre&gt;
  &lt;pre id=&quot;UScA&quot;&gt;    def add_current_costs(self, request_cost):
        &amp;quot;&amp;quot;&amp;quot;
        Add current cost to all_time, day and month cost and update last_update date.
        &amp;quot;&amp;quot;&amp;quot;
        today = date.today()
        last_update = date.fromisoformat(self.usage[&amp;quot;current_cost&amp;quot;][&amp;quot;last_update&amp;quot;])&lt;/pre&gt;
  &lt;pre id=&quot;dezN&quot;&gt;        # add to all_time cost, initialize with calculation of total_cost if key doesn&amp;#x27;t exist
        self.usage[&amp;quot;current_cost&amp;quot;][&amp;quot;all_time&amp;quot;] = \
            self.usage[&amp;quot;current_cost&amp;quot;].get(&amp;quot;all_time&amp;quot;, self.initialize_all_time_cost()) + request_cost
        # add current cost, update new day
        if today == last_update:
            self.usage[&amp;quot;current_cost&amp;quot;][&amp;quot;day&amp;quot;] += request_cost
            self.usage[&amp;quot;current_cost&amp;quot;][&amp;quot;month&amp;quot;] += request_cost
        else:
            if today.month == last_update.month:
                self.usage[&amp;quot;current_cost&amp;quot;][&amp;quot;month&amp;quot;] += request_cost
            else:
                self.usage[&amp;quot;current_cost&amp;quot;][&amp;quot;month&amp;quot;] = request_cost
            self.usage[&amp;quot;current_cost&amp;quot;][&amp;quot;day&amp;quot;] = request_cost
            self.usage[&amp;quot;current_cost&amp;quot;][&amp;quot;last_update&amp;quot;] = str(today)&lt;/pre&gt;
  &lt;pre id=&quot;yNDc&quot;&gt;    def get_current_transcription_duration(self):
        &amp;quot;&amp;quot;&amp;quot;Get minutes and seconds of audio transcribed for today and this month.&lt;/pre&gt;
  &lt;pre id=&quot;BChp&quot;&gt;        :return: total amount of time transcribed per day and per month (4 values)
        &amp;quot;&amp;quot;&amp;quot;
        today = date.today()
        if str(today) in self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;transcription_seconds&amp;quot;]:
            seconds_day = self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;transcription_seconds&amp;quot;][str(today)]
        else:
            seconds_day = 0
        month = str(today)[:7]  # year-month as string
        seconds_month = 0
        for today, seconds in self.usage[&amp;quot;usage_history&amp;quot;][&amp;quot;transcription_seconds&amp;quot;].items():
            if today.startswith(month):
                seconds_month += seconds
        minutes_day, seconds_day = divmod(seconds_day, 60)
        minutes_month, seconds_month = divmod(seconds_month, 60)
        return int(minutes_day), round(seconds_day, 2), int(minutes_month), round(seconds_month, 2)&lt;/pre&gt;
  &lt;pre id=&quot;uzTO&quot;&gt;    # general functions
    def get_current_cost(self):
        &amp;quot;&amp;quot;&amp;quot;Get total USD amount of all requests of the current day and month&lt;/pre&gt;
  &lt;pre id=&quot;tx7K&quot;&gt;        :return: cost of current day and month
        &amp;quot;&amp;quot;&amp;quot;
        today = date.today()
        last_update = date.fromisoformat(self.usage[&amp;quot;current_cost&amp;quot;][&amp;quot;last_update&amp;quot;])
        if today == last_update:
            cost_day = self.usage[&amp;quot;current_cost&amp;quot;][&amp;quot;day&amp;quot;]
            cost_month = self.usage[&amp;quot;current_cost&amp;quot;][&amp;quot;month&amp;quot;]
        else:
            cost_day = 0.0
            if today.month == last_update.month:
                cost_month = self.usage[&amp;quot;current_cost&amp;quot;][&amp;quot;month&amp;quot;]
            else:
                cost_month = 0.0
        # add to all_time cost, initialize with calculation of total_cost if key doesn&amp;#x27;t exist
        cost_all_time = self.usage[&amp;quot;current_cost&amp;quot;].get(&amp;quot;all_time&amp;quot;, self.initialize_all_time_cost())
        return {&amp;quot;cost_today&amp;quot;: cost_day, &amp;quot;cost_month&amp;quot;: cost_month, &amp;quot;cost_all_time&amp;quot;: cost_all_time}&lt;/pre&gt;
  &lt;pre id=&quot;RrTt&quot;&gt;    def initialize_all_time_cost(self, tokens_price=0.002, image_prices=&amp;quot;0.016,0.018,0.02&amp;quot;, minute_price=0.006, vision_token_price=0.01, tts_prices=&amp;#x27;0.015,0.030&amp;#x27;):
        &amp;quot;&amp;quot;&amp;quot;Get total USD amount of all requests in history
        
        :param tokens_price: price per 1000 tokens, defaults to 0.002
        :param image_prices: prices for images of sizes [&amp;quot;256x256&amp;quot;, &amp;quot;512x512&amp;quot;, &amp;quot;1024x1024&amp;quot;],
            defaults to [0.016, 0.018, 0.02]
        :param minute_price: price per minute transcription, defaults to 0.006
        :param vision_token_price: price per 1K vision token interpretation, defaults to 0.01
        :param tts_prices: price per 1K characters tts per model [&amp;#x27;tts-1&amp;#x27;, &amp;#x27;tts-1-hd&amp;#x27;], defaults to [0.015, 0.030]
        :return: total cost of all requests
        &amp;quot;&amp;quot;&amp;quot;
        total_tokens = sum(self.usage[&amp;#x27;usage_history&amp;#x27;][&amp;#x27;chat_tokens&amp;#x27;].values())
        token_cost = round(total_tokens * tokens_price / 1000, 6)&lt;/pre&gt;
  &lt;pre id=&quot;Vcix&quot;&gt;        total_images = [sum(values) for values in zip(*self.usage[&amp;#x27;usage_history&amp;#x27;][&amp;#x27;number_images&amp;#x27;].values())]
        image_prices_list = [float(x) for x in image_prices.split(&amp;#x27;,&amp;#x27;)]
        image_cost = sum([count * price for count, price in zip(total_images, image_prices_list)])&lt;/pre&gt;
  &lt;pre id=&quot;bBR0&quot;&gt;        total_transcription_seconds = sum(self.usage[&amp;#x27;usage_history&amp;#x27;][&amp;#x27;transcription_seconds&amp;#x27;].values())
        transcription_cost = round(total_transcription_seconds * minute_price / 60, 2)&lt;/pre&gt;
  &lt;pre id=&quot;BZIj&quot;&gt;        total_vision_tokens = sum(self.usage[&amp;#x27;usage_history&amp;#x27;][&amp;#x27;vision_tokens&amp;#x27;].values())
        vision_cost = round(total_vision_tokens * vision_token_price / 1000, 2)&lt;/pre&gt;
  &lt;pre id=&quot;DOpu&quot;&gt;        total_characters = [sum(tts_model.values()) for tts_model in self.usage[&amp;#x27;usage_history&amp;#x27;][&amp;#x27;tts_characters&amp;#x27;].values()]
        tts_prices_list = [float(x) for x in tts_prices.split(&amp;#x27;,&amp;#x27;)]
        tts_cost = round(sum([count * price / 1000 for count, price in zip(total_characters, tts_prices_list)]), 2)&lt;/pre&gt;
  &lt;pre id=&quot;zONP&quot;&gt;        all_time_cost = token_cost + transcription_cost + image_cost + vision_cost + tts_cost
        return all_time_cost&lt;/pre&gt;
  &lt;p id=&quot;QhKU&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;3bp2&quot;&gt;↪️utils.py&lt;/h3&gt;
  &lt;pre id=&quot;YSvC&quot;&gt;from __future__ import annotations&lt;/pre&gt;
  &lt;pre id=&quot;ncR4&quot;&gt;import asyncio
import itertools
import json
import logging
import os
import base64&lt;/pre&gt;
  &lt;pre id=&quot;gaSD&quot;&gt;import telegram
from telegram import Message, MessageEntity, Update, ChatMember, constants
from telegram.ext import CallbackContext, ContextTypes&lt;/pre&gt;
  &lt;pre id=&quot;KaIW&quot;&gt;from usage_tracker import UsageTracker&lt;/pre&gt;
  &lt;pre id=&quot;g0TX&quot;&gt;
def message_text(message: Message) -&amp;gt; str:
    &amp;quot;&amp;quot;&amp;quot;
    Returns the text of a message, excluding any bot commands.
    &amp;quot;&amp;quot;&amp;quot;
    message_txt = message.text
    if message_txt is None:
        return &amp;#x27;&amp;#x27;&lt;/pre&gt;
  &lt;pre id=&quot;NfQA&quot;&gt;    for _, text in sorted(message.parse_entities([MessageEntity.BOT_COMMAND]).items(),
                          key=(lambda item: item[0].offset)):
        message_txt = message_txt.replace(text, &amp;#x27;&amp;#x27;).strip()&lt;/pre&gt;
  &lt;pre id=&quot;6oTf&quot;&gt;    return message_txt if len(message_txt) &amp;gt; 0 else &amp;#x27;&amp;#x27;&lt;/pre&gt;
  &lt;pre id=&quot;1AdT&quot;&gt;
async def is_user_in_group(update: Update, context: CallbackContext, user_id: int) -&amp;gt; bool:
    &amp;quot;&amp;quot;&amp;quot;
    Checks if user_id is a member of the group
    &amp;quot;&amp;quot;&amp;quot;
    try:
        chat_member = await context.bot.get_chat_member(update.message.chat_id, user_id)
        return chat_member.status in [ChatMember.OWNER, ChatMember.ADMINISTRATOR, ChatMember.MEMBER]
    except telegram.error.BadRequest as e:
        if str(e) == &amp;quot;User not found&amp;quot;:
            return False
        else:
            raise e
    except Exception as e:
        raise e&lt;/pre&gt;
  &lt;pre id=&quot;i4Kn&quot;&gt;
def get_thread_id(update: Update) -&amp;gt; int | None:
    &amp;quot;&amp;quot;&amp;quot;
    Gets the message thread id for the update, if any
    &amp;quot;&amp;quot;&amp;quot;
    if update.effective_message and update.effective_message.is_topic_message:
        return update.effective_message.message_thread_id
    return None&lt;/pre&gt;
  &lt;pre id=&quot;QuSD&quot;&gt;
def get_stream_cutoff_values(update: Update, content: str) -&amp;gt; int:
    &amp;quot;&amp;quot;&amp;quot;
    Gets the stream cutoff values for the message length
    &amp;quot;&amp;quot;&amp;quot;
    if is_group_chat(update):
        # group chats have stricter flood limits
        return 180 if len(content) &amp;gt; 1000 else 120 if len(content) &amp;gt; 200 \
            else 90 if len(content) &amp;gt; 50 else 50
    return 90 if len(content) &amp;gt; 1000 else 45 if len(content) &amp;gt; 200 \
        else 25 if len(content) &amp;gt; 50 else 15&lt;/pre&gt;
  &lt;pre id=&quot;ah75&quot;&gt;
def is_group_chat(update: Update) -&amp;gt; bool:
    &amp;quot;&amp;quot;&amp;quot;
    Checks if the message was sent from a group chat
    &amp;quot;&amp;quot;&amp;quot;
    if not update.effective_chat:
        return False
    return update.effective_chat.type in [
        constants.ChatType.GROUP,
        constants.ChatType.SUPERGROUP
    ]&lt;/pre&gt;
  &lt;pre id=&quot;fkcm&quot;&gt;
def split_into_chunks(text: str, chunk_size: int = 4096) -&amp;gt; list[str]:
    &amp;quot;&amp;quot;&amp;quot;
    Splits a string into chunks of a given size.
    &amp;quot;&amp;quot;&amp;quot;
    return [text[i:i + chunk_size] for i in range(0, len(text), chunk_size)]&lt;/pre&gt;
  &lt;pre id=&quot;ib0K&quot;&gt;
async def wrap_with_indicator(update: Update, context: CallbackContext, coroutine,
                              chat_action: constants.ChatAction = &amp;quot;&amp;quot;, is_inline=False):
    &amp;quot;&amp;quot;&amp;quot;
    Wraps a coroutine while repeatedly sending a chat action to the user.
    &amp;quot;&amp;quot;&amp;quot;
    task = context.application.create_task(coroutine(), update=update)
    while not task.done():
        if not is_inline:
            context.application.create_task(
                update.effective_chat.send_action(chat_action, message_thread_id=get_thread_id(update))
            )
        try:
            await asyncio.wait_for(asyncio.shield(task), 4.5)
        except asyncio.TimeoutError:
            pass&lt;/pre&gt;
  &lt;pre id=&quot;3CAZ&quot;&gt;
async def edit_message_with_retry(context: ContextTypes.DEFAULT_TYPE, chat_id: int | None,
                                  message_id: str, text: str, markdown: bool = True, is_inline: bool = False):
    &amp;quot;&amp;quot;&amp;quot;
    Edit a message with retry logic in case of failure (e.g. broken markdown)
    :param context: The context to use
    :param chat_id: The chat id to edit the message in
    :param message_id: The message id to edit
    :param text: The text to edit the message with
    :param markdown: Whether to use markdown parse mode
    :param is_inline: Whether the message to edit is an inline message
    :return: None
    &amp;quot;&amp;quot;&amp;quot;
    try:
        await context.bot.edit_message_text(
            chat_id=chat_id,
            message_id=int(message_id) if not is_inline else None,
            inline_message_id=message_id if is_inline else None,
            text=text,
            parse_mode=constants.ParseMode.MARKDOWN if markdown else None,
        )
    except telegram.error.BadRequest as e:
        if str(e).startswith(&amp;quot;Message is not modified&amp;quot;):
            return
        try:
            await context.bot.edit_message_text(
                chat_id=chat_id,
                message_id=int(message_id) if not is_inline else None,
                inline_message_id=message_id if is_inline else None,
                text=text,
            )
        except Exception as e:
            logging.warning(f&amp;#x27;Failed to edit message: {str(e)}&amp;#x27;)
            raise e&lt;/pre&gt;
  &lt;pre id=&quot;ggqY&quot;&gt;    except Exception as e:
        logging.warning(str(e))
        raise e&lt;/pre&gt;
  &lt;pre id=&quot;0m5w&quot;&gt;
async def error_handler(_: object, context: ContextTypes.DEFAULT_TYPE) -&amp;gt; None:
    &amp;quot;&amp;quot;&amp;quot;
    Handles errors in the telegram-python-bot library.
    &amp;quot;&amp;quot;&amp;quot;
    logging.error(f&amp;#x27;Exception while handling an update: {context.error}&amp;#x27;)&lt;/pre&gt;
  &lt;pre id=&quot;upoN&quot;&gt;
async def is_allowed(config, update: Update, context: CallbackContext, is_inline=False) -&amp;gt; bool:
    &amp;quot;&amp;quot;&amp;quot;
    Checks if the user is allowed to use the bot.
    &amp;quot;&amp;quot;&amp;quot;
    if config[&amp;#x27;allowed_user_ids&amp;#x27;] == &amp;#x27;*&amp;#x27;:
        return True&lt;/pre&gt;
  &lt;pre id=&quot;jFjR&quot;&gt;    user_id = update.inline_query.from_user.id if is_inline else update.message.from_user.id
    if is_admin(config, user_id):
        return True
    name = update.inline_query.from_user.name if is_inline else update.message.from_user.name
    allowed_user_ids = config[&amp;#x27;allowed_user_ids&amp;#x27;].split(&amp;#x27;,&amp;#x27;)
    # Check if user is allowed
    if str(user_id) in allowed_user_ids:
        return True
    # Check if it&amp;#x27;s a group a chat with at least one authorized member
    if not is_inline and is_group_chat(update):
        admin_user_ids = config[&amp;#x27;admin_user_ids&amp;#x27;].split(&amp;#x27;,&amp;#x27;)
        for user in itertools.chain(allowed_user_ids, admin_user_ids):
            if not user.strip():
                continue
            if await is_user_in_group(update, context, user):
                logging.info(f&amp;#x27;{user} is a member. Allowing group chat message...&amp;#x27;)
                return True
        logging.info(f&amp;#x27;Group chat messages from user {name} &amp;#x27;
                     f&amp;#x27;(id: {user_id}) are not allowed&amp;#x27;)
    return False&lt;/pre&gt;
  &lt;pre id=&quot;0R6E&quot;&gt;
def is_admin(config, user_id: int, log_no_admin=False) -&amp;gt; bool:
    &amp;quot;&amp;quot;&amp;quot;
    Checks if the user is the admin of the bot.
    The first user in the user list is the admin.
    &amp;quot;&amp;quot;&amp;quot;
    if config[&amp;#x27;admin_user_ids&amp;#x27;] == &amp;#x27;-&amp;#x27;:
        if log_no_admin:
            logging.info(&amp;#x27;No admin user defined.&amp;#x27;)
        return False&lt;/pre&gt;
  &lt;pre id=&quot;kVu7&quot;&gt;    admin_user_ids = config[&amp;#x27;admin_user_ids&amp;#x27;].split(&amp;#x27;,&amp;#x27;)&lt;/pre&gt;
  &lt;pre id=&quot;sFlp&quot;&gt;    # Check if user is in the admin user list
    if str(user_id) in admin_user_ids:
        return True&lt;/pre&gt;
  &lt;pre id=&quot;t7UI&quot;&gt;    return False&lt;/pre&gt;
  &lt;pre id=&quot;QoUX&quot;&gt;
def get_user_budget(config, user_id) -&amp;gt; float | None:
    &amp;quot;&amp;quot;&amp;quot;
    Get the user&amp;#x27;s budget based on their user ID and the bot configuration.
    :param config: The bot configuration object
    :param user_id: User id
    :return: The user&amp;#x27;s budget as a float, or None if the user is not found in the allowed user list
    &amp;quot;&amp;quot;&amp;quot;&lt;/pre&gt;
  &lt;pre id=&quot;pOOV&quot;&gt;    # no budget restrictions for admins and &amp;#x27;*&amp;#x27;-budget lists
    if is_admin(config, user_id) or config[&amp;#x27;user_budgets&amp;#x27;] == &amp;#x27;*&amp;#x27;:
        return float(&amp;#x27;inf&amp;#x27;)&lt;/pre&gt;
  &lt;pre id=&quot;m1kn&quot;&gt;    user_budgets = config[&amp;#x27;user_budgets&amp;#x27;].split(&amp;#x27;,&amp;#x27;)
    if config[&amp;#x27;allowed_user_ids&amp;#x27;] == &amp;#x27;*&amp;#x27;:
        # same budget for all users, use value in first position of budget list
        if len(user_budgets) &amp;gt; 1:
            logging.warning(&amp;#x27;multiple values for budgets set with unrestricted user list &amp;#x27;
                            &amp;#x27;only the first value is used as budget for everyone.&amp;#x27;)
        return float(user_budgets[0])&lt;/pre&gt;
  &lt;pre id=&quot;VHLq&quot;&gt;    allowed_user_ids = config[&amp;#x27;allowed_user_ids&amp;#x27;].split(&amp;#x27;,&amp;#x27;)
    if str(user_id) in allowed_user_ids:
        user_index = allowed_user_ids.index(str(user_id))
        if len(user_budgets) &amp;lt;= user_index:
            logging.warning(f&amp;#x27;No budget set for user id: {user_id}. Budget list shorter than user list.&amp;#x27;)
            return 0.0
        return float(user_budgets[user_index])
    return None&lt;/pre&gt;
  &lt;pre id=&quot;Vf7q&quot;&gt;
def get_remaining_budget(config, usage, update: Update, is_inline=False) -&amp;gt; float:
    &amp;quot;&amp;quot;&amp;quot;
    Calculate the remaining budget for a user based on their current usage.
    :param config: The bot configuration object
    :param usage: The usage tracker object
    :param update: Telegram update object
    :param is_inline: Boolean flag for inline queries
    :return: The remaining budget for the user as a float
    &amp;quot;&amp;quot;&amp;quot;
    # Mapping of budget period to cost period
    budget_cost_map = {
        &amp;quot;monthly&amp;quot;: &amp;quot;cost_month&amp;quot;,
        &amp;quot;daily&amp;quot;: &amp;quot;cost_today&amp;quot;,
        &amp;quot;all-time&amp;quot;: &amp;quot;cost_all_time&amp;quot;
    }&lt;/pre&gt;
  &lt;pre id=&quot;wy5y&quot;&gt;    user_id = update.inline_query.from_user.id if is_inline else update.message.from_user.id
    name = update.inline_query.from_user.name if is_inline else update.message.from_user.name
    if user_id not in usage:
        usage[user_id] = UsageTracker(user_id, name)&lt;/pre&gt;
  &lt;pre id=&quot;MiqL&quot;&gt;    # Get budget for users
    user_budget = get_user_budget(config, user_id)
    budget_period = config[&amp;#x27;budget_period&amp;#x27;]
    if user_budget is not None:
        cost = usage[user_id].get_current_cost()[budget_cost_map[budget_period]]
        return user_budget - cost&lt;/pre&gt;
  &lt;pre id=&quot;nPTD&quot;&gt;    # Get budget for guests
    if &amp;#x27;guests&amp;#x27; not in usage:
        usage[&amp;#x27;guests&amp;#x27;] = UsageTracker(&amp;#x27;guests&amp;#x27;, &amp;#x27;all guest users in group chats&amp;#x27;)
    cost = usage[&amp;#x27;guests&amp;#x27;].get_current_cost()[budget_cost_map[budget_period]]
    return config[&amp;#x27;guest_budget&amp;#x27;] - cost&lt;/pre&gt;
  &lt;pre id=&quot;Or4m&quot;&gt;
def is_within_budget(config, usage, update: Update, is_inline=False) -&amp;gt; bool:
    &amp;quot;&amp;quot;&amp;quot;
    Checks if the user reached their usage limit.
    Initializes UsageTracker for user and guest when needed.
    :param config: The bot configuration object
    :param usage: The usage tracker object
    :param update: Telegram update object
    :param is_inline: Boolean flag for inline queries
    :return: Boolean indicating if the user has a positive budget
    &amp;quot;&amp;quot;&amp;quot;
    user_id = update.inline_query.from_user.id if is_inline else update.message.from_user.id
    name = update.inline_query.from_user.name if is_inline else update.message.from_user.name
    if user_id not in usage:
        usage[user_id] = UsageTracker(user_id, name)
    remaining_budget = get_remaining_budget(config, usage, update, is_inline=is_inline)
    return remaining_budget &amp;gt; 0&lt;/pre&gt;
  &lt;pre id=&quot;IDvd&quot;&gt;
def add_chat_request_to_usage_tracker(usage, config, user_id, used_tokens):
    &amp;quot;&amp;quot;&amp;quot;
    Add chat request to usage tracker
    :param usage: The usage tracker object
    :param config: The bot configuration object
    :param user_id: The user id
    :param used_tokens: The number of tokens used
    &amp;quot;&amp;quot;&amp;quot;
    try:
        if int(used_tokens) == 0:
            logging.warning(&amp;#x27;No tokens used. Not adding chat request to usage tracker.&amp;#x27;)
            return
        # add chat request to users usage tracker
        usage[user_id].add_chat_tokens(used_tokens, config[&amp;#x27;token_price&amp;#x27;])
        # add guest chat request to guest usage tracker
        allowed_user_ids = config[&amp;#x27;allowed_user_ids&amp;#x27;].split(&amp;#x27;,&amp;#x27;)
        if str(user_id) not in allowed_user_ids and &amp;#x27;guests&amp;#x27; in usage:
            usage[&amp;quot;guests&amp;quot;].add_chat_tokens(used_tokens, config[&amp;#x27;token_price&amp;#x27;])
    except Exception as e:
        logging.warning(f&amp;#x27;Failed to add tokens to usage_logs: {str(e)}&amp;#x27;)
        pass&lt;/pre&gt;
  &lt;pre id=&quot;jsyL&quot;&gt;
def get_reply_to_message_id(config, update: Update):
    &amp;quot;&amp;quot;&amp;quot;
    Returns the message id of the message to reply to
    :param config: Bot configuration object
    :param update: Telegram update object
    :return: Message id of the message to reply to, or None if quoting is disabled
    &amp;quot;&amp;quot;&amp;quot;
    if config[&amp;#x27;enable_quoting&amp;#x27;] or is_group_chat(update):
        return update.message.message_id
    return None&lt;/pre&gt;
  &lt;pre id=&quot;5hab&quot;&gt;
def is_direct_result(response: any) -&amp;gt; bool:
    &amp;quot;&amp;quot;&amp;quot;
    Checks if the dict contains a direct result that can be sent directly to the user
    :param response: The response value
    :return: Boolean indicating if the result is a direct result
    &amp;quot;&amp;quot;&amp;quot;
    if type(response) is not dict:
        try:
            json_response = json.loads(response)
            return json_response.get(&amp;#x27;direct_result&amp;#x27;, False)
        except:
            return False
    else:
        return response.get(&amp;#x27;direct_result&amp;#x27;, False)&lt;/pre&gt;
  &lt;pre id=&quot;Xv2J&quot;&gt;
async def handle_direct_result(config, update: Update, response: any):
    &amp;quot;&amp;quot;&amp;quot;
    Handles a direct result from a plugin
    &amp;quot;&amp;quot;&amp;quot;
    if type(response) is not dict:
        response = json.loads(response)&lt;/pre&gt;
  &lt;pre id=&quot;8RcC&quot;&gt;    result = response[&amp;#x27;direct_result&amp;#x27;]
    kind = result[&amp;#x27;kind&amp;#x27;]
    format = result[&amp;#x27;format&amp;#x27;]
    value = result[&amp;#x27;value&amp;#x27;]&lt;/pre&gt;
  &lt;pre id=&quot;4e1K&quot;&gt;    common_args = {
        &amp;#x27;message_thread_id&amp;#x27;: get_thread_id(update),
        &amp;#x27;reply_to_message_id&amp;#x27;: get_reply_to_message_id(config, update),
    }&lt;/pre&gt;
  &lt;pre id=&quot;kMWF&quot;&gt;    if kind == &amp;#x27;photo&amp;#x27;:
        if format == &amp;#x27;url&amp;#x27;:
            await update.effective_message.reply_photo(**common_args, photo=value)
        elif format == &amp;#x27;path&amp;#x27;:
            await update.effective_message.reply_photo(**common_args, photo=open(value, &amp;#x27;rb&amp;#x27;))
    elif kind == &amp;#x27;gif&amp;#x27; or kind == &amp;#x27;file&amp;#x27;:
        if format == &amp;#x27;url&amp;#x27;:
            await update.effective_message.reply_document(**common_args, document=value)
        if format == &amp;#x27;path&amp;#x27;:
            await update.effective_message.reply_document(**common_args, document=open(value, &amp;#x27;rb&amp;#x27;))
    elif kind == &amp;#x27;dice&amp;#x27;:
        await update.effective_message.reply_dice(**common_args, emoji=value)&lt;/pre&gt;
  &lt;pre id=&quot;q7IO&quot;&gt;    if format == &amp;#x27;path&amp;#x27;:
        cleanup_intermediate_files(response)&lt;/pre&gt;
  &lt;pre id=&quot;7VLc&quot;&gt;
def cleanup_intermediate_files(response: any):
    &amp;quot;&amp;quot;&amp;quot;
    Deletes intermediate files created by plugins
    &amp;quot;&amp;quot;&amp;quot;
    if type(response) is not dict:
        response = json.loads(response)&lt;/pre&gt;
  &lt;pre id=&quot;U9iD&quot;&gt;    result = response[&amp;#x27;direct_result&amp;#x27;]
    format = result[&amp;#x27;format&amp;#x27;]
    value = result[&amp;#x27;value&amp;#x27;]&lt;/pre&gt;
  &lt;pre id=&quot;4tAF&quot;&gt;    if format == &amp;#x27;path&amp;#x27;:
        if os.path.exists(value):
            os.remove(value)&lt;/pre&gt;
  &lt;pre id=&quot;zH7A&quot;&gt;
# Function to encode the image
def encode_image(fileobj):
    image = base64.b64encode(fileobj.getvalue()).decode(&amp;#x27;utf-8&amp;#x27;)
    return f&amp;#x27;data:image/jpeg;base64,{image}&amp;#x27;&lt;/pre&gt;
  &lt;pre id=&quot;LXj2&quot;&gt;
def decode_image(imgbase64):
    image = imgbase64[len(&amp;#x27;data:image/jpeg;base64,&amp;#x27;):]
    return base64.b64decode(image)&lt;/pre&gt;
  &lt;p id=&quot;odOk&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;Huqe&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;Yzd7&quot;&gt;↪️.докеригнор&lt;/h3&gt;
  &lt;p id=&quot;L2VL&quot;&gt;.env&lt;br /&gt;.dockerignore&lt;br /&gt;.github&lt;br /&gt;.gitignore&lt;br /&gt;docker-compose.yml&lt;br /&gt;Dockerfile&lt;/p&gt;
  &lt;p id=&quot;J22V&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;yesx&quot;&gt;↪️.env.пример&lt;/h3&gt;
  &lt;pre id=&quot;ZHBl&quot;&gt;# Your OpenAI API key
OPENAI_API_KEY=XXX&lt;/pre&gt;
  &lt;pre id=&quot;0FEh&quot;&gt;# Your Telegram bot token obtained using @BotFather
TELEGRAM_BOT_TOKEN=XXX&lt;/pre&gt;
  &lt;pre id=&quot;3qOr&quot;&gt;# Telegram user ID of admins, or - to assign no admin
ADMIN_USER_IDS=ADMIN_1_USER_ID,ADMIN_2_USER_ID&lt;/pre&gt;
  &lt;pre id=&quot;Aoqf&quot;&gt;# Comma separated list of telegram user IDs, or * to allow all
ALLOWED_TELEGRAM_USER_IDS=USER_ID_1,USER_ID_2&lt;/pre&gt;
  &lt;pre id=&quot;9yoA&quot;&gt;# Optional configuration, refer to the README for more details
# BUDGET_PERIOD=monthly
# USER_BUDGETS=*
# GUEST_BUDGET=100.0
# TOKEN_PRICE=0.002
# IMAGE_PRICES=0.016,0.018,0.02
# TRANSCRIPTION_PRICE=0.006
# VISION_TOKEN_PRICE=0.01
# ENABLE_QUOTING=true
# ENABLE_IMAGE_GENERATION=true
# ENABLE_TTS_GENERATION=true
# ENABLE_TRANSCRIPTION=true
# ENABLE_VISION=true
# PROXY=http://localhost:8080
# OPENAI_MODEL=gpt-4o
# OPENAI_BASE_URL=https://example.com/v1/
# ASSISTANT_PROMPT=&amp;quot;You are a helpful assistant.&amp;quot;
# SHOW_USAGE=false
# STREAM=true
# MAX_TOKENS=1200
# VISION_MAX_TOKENS=300
# MAX_HISTORY_SIZE=15
# MAX_CONVERSATION_AGE_MINUTES=180
# VOICE_REPLY_WITH_TRANSCRIPT_ONLY=true
# VOICE_REPLY_PROMPTS=&amp;quot;Hi bot;Hey bot;Hi chat;Hey chat&amp;quot;
# VISION_PROMPT=&amp;quot;What is in this image&amp;quot;
# N_CHOICES=1
# TEMPERATURE=1.0
# PRESENCE_PENALTY=0.0
# FREQUENCY_PENALTY=0.0
# IMAGE_MODEL=dall-e-3
# IMAGE_QUALITY=hd
# IMAGE_STYLE=natural
# IMAGE_SIZE=1024x1024
# IMAGE_FORMAT=document
# VISION_DETAIL=&amp;quot;low&amp;quot;
# GROUP_TRIGGER_KEYWORD=&amp;quot;&amp;quot;
# IGNORE_GROUP_TRANSCRIPTIONS=true
# IGNORE_GROUP_VISION=true
# TTS_MODEL=&amp;quot;tts-1&amp;quot;
# TTS_VOICE=&amp;quot;alloy&amp;quot;
# TTS_PRICES=0.015,0.030
# BOT_LANGUAGE=en
# ENABLE_VISION_FOLLOW_UP_QUESTIONS=&amp;quot;true&amp;quot;
# VISION_MODEL=&amp;quot;gpt-4o&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;gJXF&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;5bif&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;HYEg&quot;&gt;↪️.гитиньоре&lt;/h3&gt;
  &lt;pre id=&quot;u9Yd&quot; data-lang=&quot;python&quot;&gt;__pycache__
/.idea
.env
.DS_Store
/usage_logs
venv
/.cache&lt;/pre&gt;
  &lt;p id=&quot;EzY2&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;HCVJ&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;xw8W&quot;&gt;↪️Файл Dockerfile&lt;/h3&gt;
  &lt;pre id=&quot;pudL&quot;&gt;FROM python:3.9-alpine&lt;/pre&gt;
  &lt;pre id=&quot;gHPS&quot;&gt;ENV PYTHONFAULTHANDLER=1 \
     PYTHONUNBUFFERED=1 \
     PYTHONDONTWRITEBYTECODE=1 \
     PIP_DISABLE_PIP_VERSION_CHECK=on&lt;/pre&gt;
  &lt;pre id=&quot;I399&quot;&gt;RUN apk --no-cache add ffmpeg&lt;/pre&gt;
  &lt;pre id=&quot;QUoV&quot;&gt;WORKDIR /app
COPY . .
RUN pip install -r requirements.txt --no-cache-dir&lt;/pre&gt;
  &lt;pre id=&quot;c5PF&quot;&gt;CMD [&amp;quot;python&amp;quot;, &amp;quot;bot/main.py&amp;quot;]&lt;/pre&gt;
  &lt;p id=&quot;Emyt&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;YMb1&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;QRHA&quot;&gt;↪️requirements.txt&lt;/h3&gt;
  &lt;pre id=&quot;nJ58&quot; data-lang=&quot;python&quot;&gt;python-dotenv~=1.0.0
pydub~=0.25.1
tiktoken==0.7.0
openai==1.58.1
python-telegram-bot==21.9
requests~=2.32.3
tenacity==8.3.0
wolframalpha~=5.1.3
duckduckgo_search==7.1.1
spotipy~=2.24.0
pytube~=15.0.0
gtts~=2.5.4
whois~=0.9.27
Pillow~=11.0.0&lt;/pre&gt;
  &lt;p id=&quot;UzfX&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;fISi&quot;&gt;&lt;/p&gt;
  &lt;h3 id=&quot;dmCJ&quot;&gt;↪️переводы.json&lt;/h3&gt;
  &lt;pre id=&quot;NDLP&quot; data-lang=&quot;python&quot;&gt;{
    &amp;quot;en&amp;quot;: {
        &amp;quot;help_description&amp;quot;:&amp;quot;Show help message&amp;quot;,
        &amp;quot;reset_description&amp;quot;:&amp;quot;Reset the conversation. Optionally pass high-level instructions (e.g. /reset You are a helpful assistant)&amp;quot;,
        &amp;quot;image_description&amp;quot;:&amp;quot;Generate image from prompt (e.g. /image cat)&amp;quot;,
        &amp;quot;tts_description&amp;quot;:&amp;quot;Generate speech from text (e.g. /tts my house)&amp;quot;,
        &amp;quot;stats_description&amp;quot;:&amp;quot;Get your current usage statistics&amp;quot;,
        &amp;quot;resend_description&amp;quot;:&amp;quot;Resend the latest message&amp;quot;,
        &amp;quot;chat_description&amp;quot;:&amp;quot;Chat with the bot!&amp;quot;,
        &amp;quot;disallowed&amp;quot;:&amp;quot;Sorry, you are not allowed to use this bot. You can check out the source code at https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;,
        &amp;quot;budget_limit&amp;quot;:&amp;quot;Sorry, you have reached your usage limit.&amp;quot;,
        &amp;quot;help_text&amp;quot;:[&amp;quot;I&amp;#x27;m a ChatGPT bot, talk to me!&amp;quot;, &amp;quot;Send me a voice message or file and I&amp;#x27;ll transcribe it for you&amp;quot;, &amp;quot;Open source at https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;],
        &amp;quot;stats_conversation&amp;quot;:[&amp;quot;Current conversation&amp;quot;, &amp;quot;chat messages in history&amp;quot;, &amp;quot;chat tokens in history&amp;quot;],
        &amp;quot;usage_today&amp;quot;:&amp;quot;Usage today&amp;quot;,
        &amp;quot;usage_month&amp;quot;:&amp;quot;Usage this month&amp;quot;,
        &amp;quot;stats_tokens&amp;quot;:&amp;quot;tokens&amp;quot;,
        &amp;quot;stats_images&amp;quot;:&amp;quot;images generated&amp;quot;,
        &amp;quot;stats_vision&amp;quot;:&amp;quot;image tokens interpreted&amp;quot;,
        &amp;quot;stats_tts&amp;quot;:&amp;quot;characters converted to speech&amp;quot;,
        &amp;quot;stats_transcribe&amp;quot;:[&amp;quot;minutes and&amp;quot;, &amp;quot;seconds transcribed&amp;quot;],
        &amp;quot;stats_total&amp;quot;:&amp;quot;💰 For a total amount of $&amp;quot;,
        &amp;quot;stats_budget&amp;quot;:&amp;quot;Your remaining budget&amp;quot;,
        &amp;quot;monthly&amp;quot;:&amp;quot; for this month&amp;quot;,
        &amp;quot;daily&amp;quot;:&amp;quot; for today&amp;quot;,
        &amp;quot;all-time&amp;quot;:&amp;quot;&amp;quot;,
        &amp;quot;stats_openai&amp;quot;:&amp;quot;This month your OpenAI account was billed $&amp;quot;,
        &amp;quot;resend_failed&amp;quot;:&amp;quot;You have nothing to resend&amp;quot;,
        &amp;quot;reset_done&amp;quot;:&amp;quot;Done!&amp;quot;,
        &amp;quot;image_no_prompt&amp;quot;:&amp;quot;Please provide a prompt! (e.g. /image cat)&amp;quot;,
        &amp;quot;image_fail&amp;quot;:&amp;quot;Failed to generate image&amp;quot;,
        &amp;quot;vision_fail&amp;quot;:&amp;quot;Failed to interpret image&amp;quot;,
        &amp;quot;tts_no_prompt&amp;quot;:&amp;quot;Please provide text! (e.g. /tts my house)&amp;quot;,
        &amp;quot;tts_fail&amp;quot;:&amp;quot;Failed to generate speech&amp;quot;,
        &amp;quot;media_download_fail&amp;quot;:[&amp;quot;Failed to download audio file&amp;quot;, &amp;quot;Make sure the file is not too large. (max 20MB)&amp;quot;],
        &amp;quot;media_type_fail&amp;quot;:&amp;quot;Unsupported file type&amp;quot;,
        &amp;quot;transcript&amp;quot;:&amp;quot;Transcript&amp;quot;,
        &amp;quot;answer&amp;quot;:&amp;quot;Answer&amp;quot;,
        &amp;quot;transcribe_fail&amp;quot;:&amp;quot;Failed to transcribe text&amp;quot;,
        &amp;quot;chat_fail&amp;quot;:&amp;quot;Failed to get response&amp;quot;,
        &amp;quot;prompt&amp;quot;:&amp;quot;prompt&amp;quot;,
        &amp;quot;completion&amp;quot;:&amp;quot;completion&amp;quot;,
        &amp;quot;openai_rate_limit&amp;quot;:&amp;quot;OpenAI Rate Limit exceeded&amp;quot;,
        &amp;quot;openai_invalid&amp;quot;:&amp;quot;OpenAI Invalid request&amp;quot;,
        &amp;quot;error&amp;quot;:&amp;quot;An error has occurred&amp;quot;,
        &amp;quot;try_again&amp;quot;:&amp;quot;Please try again in a while&amp;quot;,
        &amp;quot;answer_with_chatgpt&amp;quot;:&amp;quot;Answer with ChatGPT&amp;quot;,
        &amp;quot;ask_chatgpt&amp;quot;:&amp;quot;Ask ChatGPT&amp;quot;,
        &amp;quot;loading&amp;quot;:&amp;quot;Loading...&amp;quot;,
        &amp;quot;function_unavailable_in_inline_mode&amp;quot;: &amp;quot;This function is unavailable in inline mode&amp;quot;
    },
    &amp;quot;ar&amp;quot;: {
        &amp;quot;help_description&amp;quot;:&amp;quot;عرض رسالة المساعدة&amp;quot;,
        &amp;quot;reset_description&amp;quot;:&amp;quot;إعادة تعيين المحادثة. يمكنك منح بعض السياق (مثلًا /reset أنت مساعد مفيد)&amp;quot;,
        &amp;quot;image_description&amp;quot;:&amp;quot;إنشاء صورة من المطالبة (مثلًا /image مسجد)&amp;quot;,
        &amp;quot;tts_description&amp;quot;:&amp;quot;تحويل النص إلى كلام (مثلًا /tts السلام عليكم)&amp;quot;,
        &amp;quot;stats_description&amp;quot;:&amp;quot;الحصول على إحصائيات الاستخدام الحالية&amp;quot;,
        &amp;quot;resend_description&amp;quot;:&amp;quot;إعادة إرسال آخر رسالة&amp;quot;,
        &amp;quot;chat_description&amp;quot;:&amp;quot;الدردشة مع البوت!&amp;quot;,
        &amp;quot;disallowed&amp;quot;:&amp;quot;عذرًا، غير مسموح لك باستخدام هذا البوت. يمكنك التحقق من شفرة المصدر عبر https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;,
        &amp;quot;budget_limit&amp;quot;:&amp;quot;عفوًا، لقد وصلت إلى حد الاستخدام.&amp;quot;,
        &amp;quot;help_text&amp;quot;:[&amp;quot;أنا بوت ChatGPT، يمكنك التحدث إلي!&amp;quot;, &amp;quot;إرسال رسالة صوتية أو ملف لتحويلهما إلى نص&amp;quot;, &amp;quot;مفتوح المصدر على https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;],
        &amp;quot;stats_conversation&amp;quot;:[&amp;quot;المحادثة الحالية&amp;quot;, &amp;quot;رسائل الدردشة بالسجل&amp;quot;, &amp;quot;رموز الدردشة بالسجل&amp;quot;],
        &amp;quot;usage_today&amp;quot;:&amp;quot;استخدام هذا اليوم&amp;quot;,
        &amp;quot;usage_month&amp;quot;:&amp;quot;استخدام هذا الشهر&amp;quot;,
        &amp;quot;stats_tokens&amp;quot;:&amp;quot;الرموز&amp;quot;,
        &amp;quot;stats_images&amp;quot;:&amp;quot;الصور المنشئة&amp;quot;,
        &amp;quot;stats_vision&amp;quot;:&amp;quot;تم تفسير رموز الصورة&amp;quot;,
        &amp;quot;stats_tts&amp;quot;:&amp;quot;الأحرف المحولة إلى كلام&amp;quot;,
        &amp;quot;stats_transcribe&amp;quot;:[&amp;quot;من الدقائق و&amp;quot;, &amp;quot;من الثواني تم تحويلهم إلى نص&amp;quot;],
        &amp;quot;stats_total&amp;quot;:&amp;quot;💰 الإجمالي $&amp;quot;,
        &amp;quot;stats_budget&amp;quot;:&amp;quot;ميزانيتك المتبقية&amp;quot;,
        &amp;quot;monthly&amp;quot;:&amp;quot; لهذا الشهر&amp;quot;,
        &amp;quot;daily&amp;quot;:&amp;quot; لليوم&amp;quot;,
        &amp;quot;all-time&amp;quot;:&amp;quot;&amp;quot;,
        &amp;quot;stats_openai&amp;quot;:&amp;quot;هذا الشهر، تم إصدار فاتورة لحساب OpenAI الخاص بك بمبلغ $&amp;quot;,
        &amp;quot;resend_failed&amp;quot;:&amp;quot;لا شيء لديك لإعادة إرساله&amp;quot;,
        &amp;quot;reset_done&amp;quot;:&amp;quot;تم!&amp;quot;,
        &amp;quot;image_no_prompt&amp;quot;:&amp;quot;الرجاء تقديم مطالبة! (مثلًا /image مسجد)&amp;quot;,
        &amp;quot;image_fail&amp;quot;:&amp;quot;فشل إنشاء الصورة&amp;quot;,
        &amp;quot;vision_fail&amp;quot;:&amp;quot;فشل في تفسير الصورة&amp;quot;,
        &amp;quot;tts_no_prompt&amp;quot;:&amp;quot;الرجاء تقديم نص! (مثلًا /tts السلام عليكم)&amp;quot;,
        &amp;quot;tts_fail&amp;quot;:&amp;quot;فشل تحويل النص إلى كلام&amp;quot;,
        &amp;quot;media_download_fail&amp;quot;:[&amp;quot;فشل تنزيل الملف الصوتي&amp;quot;, &amp;quot;التحقق من أن حجم ملفك ليس كبيرًا. (بحد أقصى 20 م.ب.)&amp;quot;],
        &amp;quot;media_type_fail&amp;quot;:&amp;quot;صيغة الملف غير مدعومة&amp;quot;,
        &amp;quot;transcript&amp;quot;:&amp;quot;النص&amp;quot;,
        &amp;quot;answer&amp;quot;:&amp;quot;الإجابة&amp;quot;,
        &amp;quot;transcribe_fail&amp;quot;:&amp;quot;فشل تحويل الكلام إلى نص&amp;quot;,
        &amp;quot;chat_fail&amp;quot;:&amp;quot;فشل الحصول على إجابة&amp;quot;,
        &amp;quot;prompt&amp;quot;:&amp;quot;المطالبة&amp;quot;,
        &amp;quot;completion&amp;quot;:&amp;quot;الاكتمال&amp;quot;,
        &amp;quot;openai_rate_limit&amp;quot;:&amp;quot;تم تجاوز حد OpenAI&amp;quot;,
        &amp;quot;openai_invalid&amp;quot;:&amp;quot;طلب OpenAI غير صالح&amp;quot;,
        &amp;quot;error&amp;quot;:&amp;quot;حدث خطأ ما&amp;quot;,
        &amp;quot;try_again&amp;quot;:&amp;quot;الرجاء المحاولة مجددًا لاحقًا&amp;quot;,
        &amp;quot;answer_with_chatgpt&amp;quot;:&amp;quot;الإجابة بواسطة ChatGPT&amp;quot;,
        &amp;quot;ask_chatgpt&amp;quot;:&amp;quot;سؤال ChatGPT&amp;quot;,
        &amp;quot;loading&amp;quot;:&amp;quot;قيد التحميل...&amp;quot;,
        &amp;quot;function_unavailable_in_inline_mode&amp;quot;: &amp;quot;هذه الوظيفة غير متوفرة في الوضع المضمن&amp;quot;
    },
    &amp;quot;de&amp;quot;: {
        &amp;quot;help_description&amp;quot;:&amp;quot;Zeige die Hilfenachricht&amp;quot;,
        &amp;quot;reset_description&amp;quot;:&amp;quot;Setze die Konversation zurück. Optionale Eingabe einer grundlegenden Anweisung (z.B. /reset Du bist ein hilfreicher Assistent)&amp;quot;,
        &amp;quot;image_description&amp;quot;:&amp;quot;Erzeuge ein Bild aus einer Aufforderung (z.B. /image Katze)&amp;quot;,
        &amp;quot;tts_description&amp;quot;:&amp;quot;Erzeuge Sprache aus Text (z.B. /tts mein Haus)&amp;quot;,
        &amp;quot;stats_description&amp;quot;:&amp;quot;Zeige aktuelle Benutzungstatistiken&amp;quot;,
        &amp;quot;resend_description&amp;quot;:&amp;quot;Wiederhole das Senden der letzten Nachricht&amp;quot;,
        &amp;quot;chat_description&amp;quot;:&amp;quot;Schreibe mit dem Bot!&amp;quot;,
        &amp;quot;disallowed&amp;quot;:&amp;quot;Sorry, du darfst diesen Bot nicht verwenden. Den Quellcode findest du hier https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;,
        &amp;quot;budget_limit&amp;quot;:&amp;quot;Sorry, du hast dein Benutzungslimit erreicht&amp;quot;,
        &amp;quot;help_text&amp;quot;:[&amp;quot;Ich bin ein ChatGPT Bot, rede mit mir!&amp;quot;, &amp;quot;Sende eine Sprachnachricht oder Datei und ich fertige dir eine Abschrift an&amp;quot;, &amp;quot;Quelloffener Code unter https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;],
        &amp;quot;stats_conversation&amp;quot;:[&amp;quot;Aktuelle Konversation&amp;quot;, &amp;quot;Nachrichten im Verlauf&amp;quot;, &amp;quot;Chat Tokens im Verlauf&amp;quot;],
        &amp;quot;usage_today&amp;quot;:&amp;quot;Nutzung heute&amp;quot;,
        &amp;quot;usage_month&amp;quot;:&amp;quot;Nutzung diesen Monat&amp;quot;,
        &amp;quot;stats_tokens&amp;quot;:&amp;quot;Chat Tokens verwendet&amp;quot;,
        &amp;quot;stats_images&amp;quot;:&amp;quot;Bilder generiert&amp;quot;,
        &amp;quot;stats_vision&amp;quot;:&amp;quot;Bilder-Token interpretiert&amp;quot;,
        &amp;quot;stats_tts&amp;quot;:&amp;quot;Zeichen in Sprache umgewandelt&amp;quot;,
        &amp;quot;stats_transcribe&amp;quot;:[&amp;quot;Minuten und&amp;quot;, &amp;quot;Sekunden abgeschrieben&amp;quot;],
        &amp;quot;stats_total&amp;quot;:&amp;quot;💰 Für einem Gesamtbetrag von $&amp;quot;,
        &amp;quot;stats_budget&amp;quot;:&amp;quot;Dein verbliebenes Budget&amp;quot;,
        &amp;quot;monthly&amp;quot;:&amp;quot; für diesen Monat&amp;quot;,
        &amp;quot;daily&amp;quot;:&amp;quot; für heute&amp;quot;,
        &amp;quot;all-time&amp;quot;:&amp;quot;&amp;quot;,
        &amp;quot;stats_openai&amp;quot;:&amp;quot;Deine OpenAI Rechnung für den aktuellen Monat beträgt $&amp;quot;,
        &amp;quot;resend_failed&amp;quot;:&amp;quot;Es gibt keine Nachricht zum wiederholten Senden&amp;quot;,
        &amp;quot;reset_done&amp;quot;:&amp;quot;Fertig!&amp;quot;,
        &amp;quot;image_no_prompt&amp;quot;:&amp;quot;Bitte füge eine Aufforderung hinzu (z.B. /image Katze)&amp;quot;,
        &amp;quot;image_fail&amp;quot;:&amp;quot;Fehler beim Generieren eines Bildes&amp;quot;,
        &amp;quot;vision_fail&amp;quot;:&amp;quot;Bildinterpretation fehlgeschlagen&amp;quot;,
        &amp;quot;tts_no_prompt&amp;quot;:&amp;quot;Bitte füge Text hinzu! (z.B. /tts mein Haus)&amp;quot;,
        &amp;quot;tts_fail&amp;quot;:&amp;quot;Fehler beim Generieren von Sprache&amp;quot;,
        &amp;quot;media_download_fail&amp;quot;:[&amp;quot;Fehler beim Herunterladen der Audiodatei&amp;quot;, &amp;quot;Die Datei könnte zu groß sein. (max 20MB)&amp;quot;],
        &amp;quot;media_type_fail&amp;quot;:&amp;quot;Dateityp nicht unterstützt&amp;quot;,
        &amp;quot;transcript&amp;quot;:&amp;quot;Abschrift&amp;quot;,
        &amp;quot;answer&amp;quot;:&amp;quot;Antwort&amp;quot;,
        &amp;quot;transcribe_fail&amp;quot;:&amp;quot;Fehler bei der Abschrift&amp;quot;,
        &amp;quot;chat_fail&amp;quot;:&amp;quot;Fehler beim Erhalt der Antwort&amp;quot;,
        &amp;quot;prompt&amp;quot;:&amp;quot;Anfrage&amp;quot;,
        &amp;quot;completion&amp;quot;:&amp;quot;Antwort&amp;quot;,
        &amp;quot;openai_rate_limit&amp;quot;:&amp;quot;OpenAI Nutzungslimit überschritten&amp;quot;,
        &amp;quot;openai_invalid&amp;quot;:&amp;quot;OpenAI ungültige Anfrage&amp;quot;,
        &amp;quot;error&amp;quot;:&amp;quot;Ein Fehler ist aufgetreten&amp;quot;,
        &amp;quot;try_again&amp;quot;:&amp;quot;Bitte versuche es später erneut&amp;quot;,
        &amp;quot;answer_with_chatgpt&amp;quot;:&amp;quot;Antworte mit ChatGPT&amp;quot;,
        &amp;quot;ask_chatgpt&amp;quot;:&amp;quot;Frage ChatGPT&amp;quot;,
        &amp;quot;loading&amp;quot;:&amp;quot;Lade...&amp;quot;,
        &amp;quot;function_unavailable_in_inline_mode&amp;quot;: &amp;quot;Diese Funktion ist im Inline-Modus nicht verfügbar&amp;quot;
    },
    &amp;quot;es&amp;quot;: {
        &amp;quot;help_description&amp;quot;:&amp;quot;Muestra el mensaje de ayuda&amp;quot;,
        &amp;quot;reset_description&amp;quot;:&amp;quot;Reinicia la conversación. Opcionalmente, pasa instrucciones de alto nivel (por ejemplo, /reset Eres un asistente útil)&amp;quot;,
        &amp;quot;image_description&amp;quot;:&amp;quot;Genera una imagen a partir de una sugerencia (por ejemplo, /image gato)&amp;quot;,
        &amp;quot;tts_description&amp;quot;:&amp;quot;Genera voz a partir de texto (por ejemplo, /tts mi casa)&amp;quot;,
        &amp;quot;stats_description&amp;quot;:&amp;quot;Obtén tus estadísticas de uso actuales&amp;quot;,
        &amp;quot;resend_description&amp;quot;:&amp;quot;Reenvía el último mensaje&amp;quot;,
        &amp;quot;chat_description&amp;quot;:&amp;quot;¡Chatea con el bot!&amp;quot;,
        &amp;quot;disallowed&amp;quot;:&amp;quot;Lo siento, no tienes permiso para usar este bot. Puedes revisar el código fuente en https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;,
        &amp;quot;budget_limit&amp;quot;:&amp;quot;Lo siento, has alcanzado tu límite de uso.&amp;quot;,
        &amp;quot;help_text&amp;quot;:[&amp;quot;Soy un bot de ChatGPT, ¡háblame!&amp;quot;, &amp;quot;Envíame un mensaje de voz o un archivo y lo transcribiré para ti&amp;quot;, &amp;quot;Código abierto en https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;],
        &amp;quot;stats_conversation&amp;quot;:[&amp;quot;Conversación actual&amp;quot;, &amp;quot;mensajes de chat en el historial&amp;quot;, &amp;quot;tokens de chat en el historial&amp;quot;],
        &amp;quot;usage_today&amp;quot;:&amp;quot;Uso hoy&amp;quot;,
        &amp;quot;usage_month&amp;quot;:&amp;quot;Uso este mes&amp;quot;,
        &amp;quot;stats_tokens&amp;quot;:&amp;quot;tokens de chat usados&amp;quot;,
        &amp;quot;stats_images&amp;quot;:&amp;quot;imágenes generadas&amp;quot;,
        &amp;quot;stats_vision&amp;quot;:&amp;quot;Tokens de imagen interpretados&amp;quot;,
        &amp;quot;stats_tts&amp;quot;:&amp;quot;caracteres convertidos a voz&amp;quot;,
        &amp;quot;stats_transcribe&amp;quot;:[&amp;quot;minutos y&amp;quot;, &amp;quot;segundos transcritos&amp;quot;],
        &amp;quot;stats_total&amp;quot;:&amp;quot;💰 Por un monto total de $&amp;quot;,
        &amp;quot;stats_budget&amp;quot;:&amp;quot;Tu presupuesto restante&amp;quot;,
        &amp;quot;monthly&amp;quot;:&amp;quot; para este mes&amp;quot;,
        &amp;quot;daily&amp;quot;:&amp;quot; para hoy&amp;quot;,
        &amp;quot;all-time&amp;quot;:&amp;quot;&amp;quot;,
        &amp;quot;stats_openai&amp;quot;:&amp;quot;Este mes se facturó $ a tu cuenta de OpenAI&amp;quot;,
        &amp;quot;resend_failed&amp;quot;:&amp;quot;No tienes nada que reenviar&amp;quot;,
        &amp;quot;reset_done&amp;quot;:&amp;quot;¡Listo!&amp;quot;,
        &amp;quot;image_no_prompt&amp;quot;:&amp;quot;¡Por favor proporciona una sugerencia! (por ejemplo, /image gato)&amp;quot;,
        &amp;quot;image_fail&amp;quot;:&amp;quot;No se pudo generar la imagen&amp;quot;,
        &amp;quot;vision_fail&amp;quot;:&amp;quot;Error al interpretar la imagen&amp;quot;,
        &amp;quot;tts_no_prompt&amp;quot;:&amp;quot;¡Por favor proporciona texto! (por ejemplo, /tts mi casa)&amp;quot;,
        &amp;quot;tts_fail&amp;quot;:&amp;quot;No se pudo generar la voz&amp;quot;,
        &amp;quot;media_download_fail&amp;quot;:[&amp;quot;No se pudo descargar el archivo de audio&amp;quot;, &amp;quot;Asegúrate de que el archivo no sea demasiado grande. (máx. 20MB)&amp;quot;],
        &amp;quot;media_type_fail&amp;quot;:&amp;quot;Tipo de archivo no compatible&amp;quot;,
        &amp;quot;transcript&amp;quot;:&amp;quot;Transcripción&amp;quot;,
        &amp;quot;answer&amp;quot;:&amp;quot;Respuesta&amp;quot;,
        &amp;quot;transcribe_fail&amp;quot;:&amp;quot;No se pudo transcribir el texto&amp;quot;,
        &amp;quot;chat_fail&amp;quot;:&amp;quot;No se pudo obtener la respuesta&amp;quot;,
        &amp;quot;prompt&amp;quot;:&amp;quot;sugerencia&amp;quot;,
        &amp;quot;completion&amp;quot;:&amp;quot;completado&amp;quot;,
        &amp;quot;openai_rate_limit&amp;quot;:&amp;quot;Límite de tasa de OpenAI excedido&amp;quot;,
        &amp;quot;openai_invalid&amp;quot;:&amp;quot;Solicitud inválida de OpenAI&amp;quot;,
        &amp;quot;error&amp;quot;:&amp;quot;Ha ocurrido un error&amp;quot;,
        &amp;quot;try_again&amp;quot;:&amp;quot;Por favor, inténtalo de nuevo más tarde&amp;quot;,
        &amp;quot;answer_with_chatgpt&amp;quot;:&amp;quot;Responder con ChatGPT&amp;quot;,
        &amp;quot;ask_chatgpt&amp;quot;:&amp;quot;Preguntar a ChatGPT&amp;quot;,
        &amp;quot;loading&amp;quot;:&amp;quot;Cargando...&amp;quot;,
        &amp;quot;function_unavailable_in_inline_mode&amp;quot;: &amp;quot;Esta función no está disponible en el modo inline&amp;quot;
    },
    &amp;quot;fa&amp;quot;: {
        &amp;quot;help_description&amp;quot;:&amp;quot;نمایش پیغام راهنما&amp;quot;,
        &amp;quot;reset_description&amp;quot;:&amp;quot;مکالمه را تنظیم مجدد کنید. به صورت اختیاری می‌توانید دستورالعمل های سطح بالا را ارسال کنید (به عنوان مثال /reset تو یک دستیار مفید هستی)&amp;quot;,
        &amp;quot;image_description&amp;quot;:&amp;quot;ایجاد تصویر بر اساس فرمان (به عنوان مثال /image گربه)&amp;quot;,
        &amp;quot;tts_description&amp;quot;:&amp;quot;تبدیل متن به صدا (به عنوان مثال /tts خانه من)&amp;quot;,
        &amp;quot;stats_description&amp;quot;:&amp;quot;آمار استفاده فعلی خود را دریافت کنید&amp;quot;,
        &amp;quot;resend_description&amp;quot;:&amp;quot;آخرین پیام را دوباره ارسال کنید&amp;quot;,
        &amp;quot;chat_description&amp;quot;:&amp;quot;چت با ربات!&amp;quot;,
        &amp;quot;disallowed&amp;quot;:&amp;quot;با عرض پوزش، شما مجاز به استفاده از این ربات نیستید. می‌توانید کد منبع را در https://github.com/n3d1117/chatgpt-telegram-bot بررسی کنید&amp;quot;,
        &amp;quot;budget_limit&amp;quot;:&amp;quot;با عرض پوزش، شما به حد مجاز استفاده خود رسیده‌اید.&amp;quot;,
        &amp;quot;help_text&amp;quot;:[&amp;quot;من یک ربات ChatGPT هستم، با من صحبت کنید!&amp;quot;, &amp;quot;برای من پیام صوتی یا فایل بفرستید تا آن را برای شما رونویسی کنم&amp;quot;, &amp;quot;منبع باز در https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;],
        &amp;quot;stats_conversation&amp;quot;:[&amp;quot;مکالمه فعلی&amp;quot;, &amp;quot;پیام چت در تاریخچه&amp;quot;, &amp;quot;توکن چت در تاریخچه&amp;quot;],
        &amp;quot;usage_today&amp;quot;:&amp;quot;استفاده امروز&amp;quot;,
        &amp;quot;usage_month&amp;quot;:&amp;quot;استفاده این ماه&amp;quot;,
        &amp;quot;stats_tokens&amp;quot;:&amp;quot;توکن چت استفاده شده است&amp;quot;,
        &amp;quot;stats_images&amp;quot;:&amp;quot;تصویر تولید شده است&amp;quot;,
        &amp;quot;stats_vision&amp;quot;:&amp;quot;توکن‌های تصویر تفسیر شدند&amp;quot;,
        &amp;quot;stats_tts&amp;quot;:&amp;quot;کاراکترهای تبدیل شده به صدا&amp;quot;,
        &amp;quot;stats_transcribe&amp;quot;:[&amp;quot;دقیقه و&amp;quot;, &amp;quot;ثانیه رونویسی شده است&amp;quot;],
        &amp;quot;stats_total&amp;quot;:&amp;quot;💰 مقدار کل مصرف: $&amp;quot;,
        &amp;quot;stats_budget&amp;quot;:&amp;quot;بودجه باقی‌مانده شما&amp;quot;,
        &amp;quot;monthly&amp;quot;:&amp;quot; برای این ماه&amp;quot;,
        &amp;quot;daily&amp;quot;:&amp;quot; برای امروز&amp;quot;,
        &amp;quot;all-time&amp;quot;:&amp;quot;&amp;quot;,
        &amp;quot;stats_openai&amp;quot;:&amp;quot;صورتحساب این ماه حساب OpenAI شما: $&amp;quot;,
        &amp;quot;resend_failed&amp;quot;:&amp;quot;شما چیزی برای ارسال مجدد ندارید&amp;quot;,
        &amp;quot;reset_done&amp;quot;:&amp;quot;انجام شد!&amp;quot;,
        &amp;quot;image_no_prompt&amp;quot;:&amp;quot;لطفا یک فرمان ارائه دهید! (به عنوان مثال /image گربه)&amp;quot;,
        &amp;quot;image_fail&amp;quot;:&amp;quot;در تولید تصویر خطایی رخ داد&amp;quot;,
        &amp;quot;vision_fail&amp;quot;:&amp;quot;تفسیر تصویر ناموفق بود&amp;quot;,
        &amp;quot;tts_no_prompt&amp;quot;:&amp;quot;لطفا متنی را وارد کنید! (به عنوان مثال /tts خانه من)&amp;quot;,
        &amp;quot;tts_fail&amp;quot;:&amp;quot;در تولید صدا خطایی رخ داد&amp;quot;,
        &amp;quot;media_download_fail&amp;quot;:[&amp;quot;فایل صوتی دانلود نشد&amp;quot;, &amp;quot;دقت کنید که فایل خیلی بزرگ نباشد. (حداکثر 20 مگابایت)&amp;quot;],
        &amp;quot;media_type_fail&amp;quot;:&amp;quot;نوع فایل پشتیبانی نمی‌شود&amp;quot;,
        &amp;quot;transcript&amp;quot;:&amp;quot;رونوشت&amp;quot;,
        &amp;quot;answer&amp;quot;:&amp;quot;پاسخ&amp;quot;,
        &amp;quot;transcribe_fail&amp;quot;:&amp;quot;در رونویسی متن خطایی رخ داد&amp;quot;,
        &amp;quot;chat_fail&amp;quot;:&amp;quot;در دریافت پاسخ خطایی رخ داد&amp;quot;,
        &amp;quot;prompt&amp;quot;:&amp;quot;فرمان&amp;quot;,
        &amp;quot;completion&amp;quot;:&amp;quot;تکمیل&amp;quot;,
        &amp;quot;openai_rate_limit&amp;quot;:&amp;quot;بیشتر از حد مجاز درخواست به OpenAI استفاده شده است&amp;quot;,
        &amp;quot;openai_invalid&amp;quot;:&amp;quot;درخواست نامعتبر OpenAI&amp;quot;,
        &amp;quot;error&amp;quot;:&amp;quot;خطایی رخ داده است&amp;quot;,
        &amp;quot;try_again&amp;quot;:&amp;quot;لطفا بعد از مدتی دوباره امتحان کنید&amp;quot;,
        &amp;quot;answer_with_chatgpt&amp;quot;:&amp;quot;با ChatGPT پاسخ دهید&amp;quot;,
        &amp;quot;ask_chatgpt&amp;quot;:&amp;quot;از ChatGPT بپرسید&amp;quot;,
        &amp;quot;loading&amp;quot;:&amp;quot;در حال بارگذاری...&amp;quot;,
        &amp;quot;function_unavailable_in_inline_mode&amp;quot;: &amp;quot;این عملکرد در حالت آنلاین در دسترس نیست&amp;quot;
    },
    &amp;quot;fi&amp;quot;: {
        &amp;quot;help_description&amp;quot;:&amp;quot;Näytä ohjeet&amp;quot;,
        &amp;quot;reset_description&amp;quot;:&amp;quot;Nollaa keskustelu. Voit myös antaa korkean tason ohjeita (esim. /reset Olet avulias avustaja)&amp;quot;,
        &amp;quot;image_description&amp;quot;:&amp;quot;Luo kuva tekstistä (esim. /image kissa)&amp;quot;,
        &amp;quot;tts_description&amp;quot;:&amp;quot;Muuta teksti puheeksi (esim. /tts taloni)&amp;quot;,
        &amp;quot;stats_description&amp;quot;:&amp;quot;Hae tämän hetken käyttötilastot&amp;quot;,
        &amp;quot;resend_description&amp;quot;:&amp;quot;Lähetä viimeisin viesti uudestaan&amp;quot;,
        &amp;quot;chat_description&amp;quot;:&amp;quot;Keskustele botin kanssa!&amp;quot;,
        &amp;quot;disallowed&amp;quot;:&amp;quot;Pahoittelut, mutta sinulla ei ole oikeuksia käyttää tätä bottia. Voit lukea sen lähdekoodin osoitteessa https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;,
        &amp;quot;budget_limit&amp;quot;:&amp;quot;Pahoittelut, mutta olet ylittänyt käyttörajasi.&amp;quot;,
        &amp;quot;help_text&amp;quot;:[&amp;quot;Olen ChatGPT-botti, keskustele kanssani!&amp;quot;, &amp;quot;Lähetä minulle ääniviesti tai -tiedosto, ja litteroin sen sinulle&amp;quot;, &amp;quot;Avoin lähdekoodi osoittessa https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;],
        &amp;quot;stats_conversation&amp;quot;:[&amp;quot;Nykyinen keskustelu&amp;quot;, &amp;quot;viestiä muistissa&amp;quot;, &amp;quot;viestipolettia muistissa&amp;quot;],
        &amp;quot;usage_today&amp;quot;:&amp;quot;Käyttö tänään&amp;quot;,
        &amp;quot;usage_month&amp;quot;:&amp;quot;Käyttö tässä kuussa&amp;quot;,
        &amp;quot;stats_tokens&amp;quot;:&amp;quot;viestipolettia käytetty&amp;quot;,
        &amp;quot;stats_images&amp;quot;:&amp;quot;kuvaa luotu&amp;quot;,
        &amp;quot;stats_vision&amp;quot;:&amp;quot;Kuvatulkittujen tokenien määrä&amp;quot;,
        &amp;quot;stats_tts&amp;quot;:&amp;quot;merkkiä muutettu puheeksi&amp;quot;,
        &amp;quot;stats_transcribe&amp;quot;:[&amp;quot;minuuttia ja&amp;quot;, &amp;quot;sekuntia litteroitu&amp;quot;],
        &amp;quot;stats_total&amp;quot;:&amp;quot;💰 Yhteensä $&amp;quot;,
        &amp;quot;stats_budget&amp;quot;:&amp;quot;Jäljellä oleva budjettisi&amp;quot;,
        &amp;quot;monthly&amp;quot;:&amp;quot; tälle kuulle&amp;quot;,
        &amp;quot;daily&amp;quot;:&amp;quot; tälle päivälle&amp;quot;,
        &amp;quot;all-time&amp;quot;:&amp;quot;&amp;quot;,
        &amp;quot;stats_openai&amp;quot;:&amp;quot;Tässä kuussa OpenAI-tiliäsi on laskutettu $&amp;quot;,
        &amp;quot;resend_failed&amp;quot;:&amp;quot;Ei uudelleenlähetettävää&amp;quot;,
        &amp;quot;reset_done&amp;quot;:&amp;quot;Valmis!&amp;quot;,
        &amp;quot;image_no_prompt&amp;quot;:&amp;quot;Ole hyvä ja anna ohjeet! (esim. /image kissa)&amp;quot;,
        &amp;quot;image_fail&amp;quot;:&amp;quot;Kuvan luonti epäonnistui&amp;quot;,
        &amp;quot;vision_fail&amp;quot;:&amp;quot;Kuvan tulkinta epäonnistui&amp;quot;,
        &amp;quot;tts_no_prompt&amp;quot;:&amp;quot;Ole hyvä ja anna teksti! (esim. /tts taloni)&amp;quot;,
        &amp;quot;tts_fail&amp;quot;:&amp;quot;Puheen luonti epäonnistui&amp;quot;,
        &amp;quot;media_download_fail&amp;quot;:[&amp;quot;Äänitiedoston lataus epäonnistui&amp;quot;, &amp;quot;Varmista että se ei ole liian iso. (enintään 20MB)&amp;quot;],
        &amp;quot;media_type_fail&amp;quot;:&amp;quot;Tiedostomuoto ei tuettu&amp;quot;,
        &amp;quot;transcript&amp;quot;:&amp;quot;Litteroitu teksti&amp;quot;,
        &amp;quot;answer&amp;quot;:&amp;quot;Vastaa&amp;quot;,
        &amp;quot;transcribe_fail&amp;quot;:&amp;quot;Litterointi epäonnistui&amp;quot;,
        &amp;quot;chat_fail&amp;quot;:&amp;quot;Vastaus epäonnistui&amp;quot;,
        &amp;quot;prompt&amp;quot;:&amp;quot;ohjeistus&amp;quot;,
        &amp;quot;completion&amp;quot;:&amp;quot;viimeistely&amp;quot;,
        &amp;quot;openai_rate_limit&amp;quot;:&amp;quot;OpenAI-nopeusraja ylitetty&amp;quot;,
        &amp;quot;openai_invalid&amp;quot;:&amp;quot;OpenAI-pyyntö virheellinen&amp;quot;,
        &amp;quot;error&amp;quot;:&amp;quot;Virhe&amp;quot;,
        &amp;quot;try_again&amp;quot;:&amp;quot;Yritä myöhemmin uudelleen&amp;quot;,
        &amp;quot;answer_with_chatgpt&amp;quot;:&amp;quot;Vastaa ChatGPT:n avulla&amp;quot;,
        &amp;quot;ask_chatgpt&amp;quot;:&amp;quot;Kysy ChatGPT:ltä&amp;quot;,
        &amp;quot;loading&amp;quot;:&amp;quot;Lataa...&amp;quot;,
        &amp;quot;function_unavailable_in_inline_mode&amp;quot;: &amp;quot;Tämä toiminto ei ole käytettävissä sisäisessä tilassa&amp;quot;
    },
    &amp;quot;he&amp;quot;: {
        &amp;quot;help_description&amp;quot;: &amp;quot;הצג הודעת עזרה&amp;quot;,
        &amp;quot;reset_description&amp;quot;: &amp;quot;אתחל את השיחה. ניתן להעביר הוראות ברמה גבוהה (למשל, /reset אתה עוזר מועיל)&amp;quot;,
        &amp;quot;image_description&amp;quot;: &amp;quot;צור תמונה מהפרומפט (למשל, /image חתול)&amp;quot;,
        &amp;quot;tts_description&amp;quot;: &amp;quot;צור דיבור מטקסט (למשל, /tts הבית שלי)&amp;quot;,
        &amp;quot;stats_description&amp;quot;: &amp;quot;קבל את סטטיסטיקות השימוש הנוכחיות שלך&amp;quot;,
        &amp;quot;resend_description&amp;quot;: &amp;quot;שלח מחדש את ההודעה האחרונה&amp;quot;,
        &amp;quot;chat_description&amp;quot;: &amp;quot;שוחח עם הבוט!&amp;quot;,
        &amp;quot;disallowed&amp;quot;: &amp;quot;מצטערים, אינך מורשה להשתמש בבוט זה. תוכל לבדוק את הקוד המקור ב https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;,
        &amp;quot;budget_limit&amp;quot;: &amp;quot;מצטערים, הגעת למגבלת השימוש שלך.&amp;quot;,
        &amp;quot;help_text&amp;quot;: [&amp;quot;אני בוט של ChatGPT, דבר איתי!&amp;quot;, &amp;quot;שלח לי הודעה קולית או קובץ ואני אתרגם אותו בשבילך&amp;quot;, &amp;quot;קוד פתוח ב https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;],
        &amp;quot;stats_conversation&amp;quot;: [&amp;quot;שיחה נוכחית&amp;quot;, &amp;quot;הודעות צ&amp;#x27;אט בהיסטוריה&amp;quot;, &amp;quot;אסימוני צ&amp;#x27;אט בהיסטוריה&amp;quot;],
        &amp;quot;usage_today&amp;quot;: &amp;quot;שימוש היום&amp;quot;,
        &amp;quot;usage_month&amp;quot;: &amp;quot;שימוש החודש&amp;quot;,
        &amp;quot;stats_tokens&amp;quot;: &amp;quot;אסימונים&amp;quot;,
        &amp;quot;stats_images&amp;quot;: &amp;quot;תמונות שנוצרו&amp;quot;,
        &amp;quot;stats_vision&amp;quot;: &amp;quot;אסימוני תמונה שפורשו&amp;quot;,
        &amp;quot;stats_tts&amp;quot;: &amp;quot;תווים שהומרו לדיבור&amp;quot;,
        &amp;quot;stats_transcribe&amp;quot;: [&amp;quot;דקות ו&amp;quot;, &amp;quot;שניות שהוקלטו&amp;quot;],
        &amp;quot;stats_total&amp;quot;: &amp;quot;💰 לסך כל של $&amp;quot;,
        &amp;quot;stats_budget&amp;quot;: &amp;quot;התקציב הנותר שלך&amp;quot;,
        &amp;quot;monthly&amp;quot;: &amp;quot; לחודש זה&amp;quot;,
        &amp;quot;daily&amp;quot;: &amp;quot; להיום&amp;quot;,
        &amp;quot;all-time&amp;quot;: &amp;quot;&amp;quot;,
        &amp;quot;stats_openai&amp;quot;: &amp;quot;החשבון שלך ב-OpenAI חויב החודש ב-$&amp;quot;,
        &amp;quot;resend_failed&amp;quot;: &amp;quot;אין לך מה לשלוח מחדש&amp;quot;,
        &amp;quot;reset_done&amp;quot;: &amp;quot;בוצע!&amp;quot;,
        &amp;quot;image_no_prompt&amp;quot;: &amp;quot;נא לספק פרומפט! (למשל, /image חתול)&amp;quot;,
        &amp;quot;image_fail&amp;quot;: &amp;quot;נכשל ביצירת התמונה&amp;quot;,
        &amp;quot;vision_fail&amp;quot;: &amp;quot;נכשל בפרשנות התמונה&amp;quot;,
        &amp;quot;tts_no_prompt&amp;quot;: &amp;quot;נא לספק טקסט! (למשל, /tts הבית שלי)&amp;quot;,
        &amp;quot;tts_fail&amp;quot;: &amp;quot;נכשל ביצירת הדיבור&amp;quot;,
        &amp;quot;media_download_fail&amp;quot;: [&amp;quot;נכשל בהורדת קובץ השמע&amp;quot;, &amp;quot;ודא שהקובץ אינו גדול מדי. (מקסימום 20MB)&amp;quot;],
        &amp;quot;media_type_fail&amp;quot;: &amp;quot;סוג קובץ לא נתמך&amp;quot;,
        &amp;quot;transcript&amp;quot;: &amp;quot;תמליל&amp;quot;,
        &amp;quot;answer&amp;quot;: &amp;quot;תשובה&amp;quot;,
        &amp;quot;transcribe_fail&amp;quot;: &amp;quot;נכשל בתרגום הטקסט&amp;quot;,
        &amp;quot;chat_fail&amp;quot;: &amp;quot;נכשל בקבלת תגובה&amp;quot;,
        &amp;quot;prompt&amp;quot;: &amp;quot;פרומפט&amp;quot;,
        &amp;quot;completion&amp;quot;: &amp;quot;השלמה&amp;quot;,
        &amp;quot;openai_rate_limit&amp;quot;: &amp;quot;חריגה מהגבלת השימוש של OpenAI&amp;quot;,
        &amp;quot;openai_invalid&amp;quot;: &amp;quot;בקשה לא חוקית של OpenAI&amp;quot;,
        &amp;quot;error&amp;quot;: &amp;quot;אירעה שגיאה&amp;quot;,
        &amp;quot;try_again&amp;quot;: &amp;quot;נא לנסות שוב מאוחר יותר&amp;quot;,
        &amp;quot;answer_with_chatgpt&amp;quot;: &amp;quot;ענה באמצעות ChatGPT&amp;quot;,
        &amp;quot;ask_chatgpt&amp;quot;: &amp;quot;שאל את ChatGPT&amp;quot;,
        &amp;quot;loading&amp;quot;: &amp;quot;טוען...&amp;quot;,
        &amp;quot;function_unavailable_in_inline_mode&amp;quot;: &amp;quot;הפונקציה לא זמינה במצב inline&amp;quot;
    },
    &amp;quot;id&amp;quot;: {
        &amp;quot;help_description&amp;quot;: &amp;quot;Menampilkan pesan bantuan&amp;quot;,
        &amp;quot;reset_description&amp;quot;: &amp;quot;Merestart percakapan. Opsional memasukkan instruksi tingkat tinggi (misalnya /reset Anda adalah asisten yang membantu)&amp;quot;,
        &amp;quot;image_description&amp;quot;: &amp;quot;Menghasilkan gambar dari input prompt (misalnya /image kucing)&amp;quot;,
        &amp;quot;tts_description&amp;quot;: &amp;quot;Menghasilkan suara dari teks (misalnya /tts rumah saya)&amp;quot;,
        &amp;quot;stats_description&amp;quot;: &amp;quot;Mendapatkan statistik penggunaan saat ini&amp;quot;,
        &amp;quot;resend_description&amp;quot;: &amp;quot;Mengirim kembali pesan terakhir&amp;quot;,
        &amp;quot;chat_description&amp;quot;: &amp;quot;Berkonversasi dengan bot!&amp;quot;,
        &amp;quot;disallowed&amp;quot;: &amp;quot;Maaf, Anda tidak diizinkan menggunakan bot ini. Anda dapat melihat kode sumber di https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;,
        &amp;quot;budget_limit&amp;quot;: &amp;quot;Maaf, Anda telah mencapai batas penggunaan Anda.&amp;quot;,
        &amp;quot;help_text&amp;quot;: [&amp;quot;Saya adalah bot ChatGPT, obrolan dengan saya!&amp;quot;, &amp;quot;Kirimkan pesan suara atau file dan saya akan mentranskripsinya untuk Anda&amp;quot;, &amp;quot;Sumber terbuka di https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;],
        &amp;quot;stats_conversation&amp;quot;: [&amp;quot;Percakapan saat ini&amp;quot;, &amp;quot;pesan obrolan dalam riwayat&amp;quot;, &amp;quot;token obrolan dalam riwayat&amp;quot;],
        &amp;quot;usage_today&amp;quot;: &amp;quot;Penggunaan hari ini&amp;quot;,
        &amp;quot;usage_month&amp;quot;: &amp;quot;Penggunaan bulan ini&amp;quot;,
        &amp;quot;stats_tokens&amp;quot;: &amp;quot;token obrolan yang digunakan&amp;quot;,
        &amp;quot;stats_images&amp;quot;: &amp;quot;gambar yang dihasilkan&amp;quot;,
        &amp;quot;stats_vision&amp;quot;:&amp;quot;Token gambar diinterpretasi&amp;quot;,
        &amp;quot;stats_tts&amp;quot;: &amp;quot;karakter dikonversi ke suara&amp;quot;,
        &amp;quot;stats_transcribe&amp;quot;: [&amp;quot;menit dan&amp;quot;, &amp;quot;detik ditranskripsi&amp;quot;],
        &amp;quot;stats_total&amp;quot;: &amp;quot;💰 Untuk total sebesar $&amp;quot;,
        &amp;quot;stats_budget&amp;quot;: &amp;quot;Sisa anggaran Anda&amp;quot;,
        &amp;quot;monthly&amp;quot;: &amp;quot; untuk bulan ini&amp;quot;,
        &amp;quot;daily&amp;quot;: &amp;quot; untuk hari ini&amp;quot;,
        &amp;quot;all-time&amp;quot;: &amp;quot;&amp;quot;,
        &amp;quot;stats_openai&amp;quot;: &amp;quot;Bulan ini akun OpenAI Anda dikenakan biaya sebesar $&amp;quot;,
        &amp;quot;resend_failed&amp;quot;: &amp;quot;Anda tidak memiliki pesan untuk dikirim ulang&amp;quot;,
        &amp;quot;reset_done&amp;quot;: &amp;quot;Selesai!&amp;quot;,
        &amp;quot;image_no_prompt&amp;quot;: &amp;quot;Harap berikan prompt! (misalnya /image kucing)&amp;quot;,
        &amp;quot;image_fail&amp;quot;: &amp;quot;Gagal menghasilkan gambar&amp;quot;,
        &amp;quot;vision_fail&amp;quot;:&amp;quot;Gagal menginterpretasi gambar&amp;quot;,
        &amp;quot;tts_no_prompt&amp;quot;: &amp;quot;Harap berikan teks! (misalnya /tts rumah saya)&amp;quot;,
        &amp;quot;tts_fail&amp;quot;: &amp;quot;Gagal menghasilkan suara&amp;quot;,
        &amp;quot;media_download_fail&amp;quot;: [&amp;quot;Gagal mengunduh file audio&amp;quot;, &amp;quot;Pastikan file tidak terlalu besar. (maksimal 20MB)&amp;quot;],
        &amp;quot;media_type_fail&amp;quot;: &amp;quot;Tipe file tidak didukung&amp;quot;,
        &amp;quot;transcript&amp;quot;: &amp;quot;Transkrip&amp;quot;,
        &amp;quot;answer&amp;quot;: &amp;quot;Jawaban&amp;quot;,
        &amp;quot;transcribe_fail&amp;quot;: &amp;quot;Gagal mentranskripsi teks&amp;quot;,
        &amp;quot;chat_fail&amp;quot;: &amp;quot;Gagal mendapatkan respons&amp;quot;,
        &amp;quot;prompt&amp;quot;: &amp;quot;input prompt&amp;quot;,
        &amp;quot;completion&amp;quot;: &amp;quot;input selesai&amp;quot;,
        &amp;quot;openai_rate_limit&amp;quot;: &amp;quot;Batas Rate OpenAI terlampaui&amp;quot;,
        &amp;quot;openai_invalid&amp;quot;: &amp;quot;Permintaan OpenAI tidak valid&amp;quot;,
        &amp;quot;error&amp;quot;: &amp;quot;Terjadi kesalahan&amp;quot;,
        &amp;quot;try_again&amp;quot;: &amp;quot;Silakan coba lagi nanti&amp;quot;,
        &amp;quot;answer_with_chatgpt&amp;quot;: &amp;quot;Jawaban dengan ChatGPT&amp;quot;,
        &amp;quot;ask_chatgpt&amp;quot;: &amp;quot;Tanya ChatGPT&amp;quot;,
        &amp;quot;loading&amp;quot;: &amp;quot;Sedang memuat...&amp;quot;,
        &amp;quot;function_unavailable_in_inline_mode&amp;quot;: &amp;quot;Fungsi ini tidak tersedia dalam mode inline&amp;quot;
    },
    &amp;quot;it&amp;quot;: {
        &amp;quot;help_description&amp;quot;:&amp;quot;Mostra il messaggio di aiuto&amp;quot;,
        &amp;quot;reset_description&amp;quot;:&amp;quot;Resetta la conversazione. Puoi anche specificare ulteriori istruzioni (ad esempio, /reset Sei un assistente utile).&amp;quot;,
        &amp;quot;image_description&amp;quot;:&amp;quot;Genera immagine da un testo (ad es. /image gatto)&amp;quot;,
        &amp;quot;tts_description&amp;quot;:&amp;quot;Genera audio da un testo (ad es. /tts la mia casa)&amp;quot;,
        &amp;quot;stats_description&amp;quot;:&amp;quot;Mostra le statistiche di utilizzo&amp;quot;,
        &amp;quot;resend_description&amp;quot;:&amp;quot;Reinvia l&amp;#x27;ultimo messaggio&amp;quot;,
        &amp;quot;chat_description&amp;quot;:&amp;quot;Chatta con il bot!&amp;quot;,
        &amp;quot;disallowed&amp;quot;:&amp;quot;Spiacente, non sei autorizzato ad usare questo bot. Se vuoi vedere il codice sorgente, vai su https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;,
        &amp;quot;budget_limit&amp;quot;:&amp;quot;Spiacente, hai raggiunto il limite di utilizzo&amp;quot;,
        &amp;quot;help_text&amp;quot;:[&amp;quot;Sono un bot di ChatGPT, chiedimi qualcosa!&amp;quot;, &amp;quot;Invia un messaggio vocale o un file e lo trascriverò per te.&amp;quot;, &amp;quot;Open source su https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;],
        &amp;quot;stats_conversation&amp;quot;:[&amp;quot;Conversazione attuale&amp;quot;, &amp;quot;messaggi totali&amp;quot;, &amp;quot;token totali&amp;quot;],
        &amp;quot;usage_today&amp;quot;:&amp;quot;Utilizzo oggi&amp;quot;,
        &amp;quot;usage_month&amp;quot;:&amp;quot;Utilizzo questo mese&amp;quot;,
        &amp;quot;stats_tokens&amp;quot;:&amp;quot;token utilizzati&amp;quot;,
        &amp;quot;stats_images&amp;quot;:&amp;quot;immagini generate&amp;quot;,
        &amp;quot;stats_vision&amp;quot;:&amp;quot;Token immagine interpretati&amp;quot;,
        &amp;quot;stats_tts&amp;quot;:&amp;quot;caratteri convertiti in audio&amp;quot;,
        &amp;quot;stats_transcribe&amp;quot;:[&amp;quot;minuti&amp;quot;, &amp;quot;secondi trascritti&amp;quot;],
        &amp;quot;stats_total&amp;quot;:&amp;quot;💰 Per un totale di $&amp;quot;,
        &amp;quot;stats_budget&amp;quot;:&amp;quot;Budget rimanente&amp;quot;,
        &amp;quot;monthly&amp;quot;:&amp;quot; per questo mese&amp;quot;,
        &amp;quot;daily&amp;quot;:&amp;quot; per oggi&amp;quot;,
        &amp;quot;all-time&amp;quot;:&amp;quot;&amp;quot;,
        &amp;quot;stats_openai&amp;quot;:&amp;quot;Spesa OpenAI per questo mese: $&amp;quot;,
        &amp;quot;resend_failed&amp;quot;:&amp;quot;Non c&amp;#x27;è nulla da reinviare&amp;quot;,
        &amp;quot;reset_done&amp;quot;:&amp;quot;Fatto!&amp;quot;,
        &amp;quot;image_no_prompt&amp;quot;:&amp;quot;Inserisci un testo (ad es. /image gatto)&amp;quot;,
        &amp;quot;image_fail&amp;quot;:&amp;quot;Impossibile generare l&amp;#x27;immagine&amp;quot;,
        &amp;quot;vision_fail&amp;quot;:&amp;quot;Interpretazione dell&amp;#x27;immagine fallita&amp;quot;,
        &amp;quot;tts_no_prompt&amp;quot;:&amp;quot;Inserisci un testo (ad es. /tts la mia casa)&amp;quot;,
        &amp;quot;tts_fail&amp;quot;:&amp;quot;Impossibile generare l&amp;#x27;audio&amp;quot;,
        &amp;quot;media_download_fail&amp;quot;:[&amp;quot;Impossibile processare il file audio&amp;quot;, &amp;quot;Assicurati che il file non sia troppo pesante (massimo 20MB)&amp;quot;],
        &amp;quot;media_type_fail&amp;quot;:&amp;quot;Tipo di file non supportato&amp;quot;,
        &amp;quot;transcript&amp;quot;:&amp;quot;Trascrizione&amp;quot;,
        &amp;quot;answer&amp;quot;:&amp;quot;Risposta&amp;quot;,
        &amp;quot;transcribe_fail&amp;quot;:&amp;quot;Impossibile trascrivere l&amp;#x27;audio&amp;quot;,
        &amp;quot;chat_fail&amp;quot;:&amp;quot;Impossibile ottenere una risposta&amp;quot;,
        &amp;quot;prompt&amp;quot;:&amp;quot;testo&amp;quot;,
        &amp;quot;completion&amp;quot;:&amp;quot;completamento&amp;quot;,
        &amp;quot;openai_rate_limit&amp;quot;:&amp;quot;Limite massimo di richieste OpenAI raggiunto&amp;quot;,
        &amp;quot;openai_invalid&amp;quot;:&amp;quot;Richiesta OpenAI non valida&amp;quot;,
        &amp;quot;error&amp;quot;:&amp;quot;Si è verificato un errore&amp;quot;,
        &amp;quot;try_again&amp;quot;:&amp;quot;Riprova più tardi&amp;quot;,
        &amp;quot;answer_with_chatgpt&amp;quot;:&amp;quot;Rispondi con ChatGPT&amp;quot;,
        &amp;quot;ask_chatgpt&amp;quot;:&amp;quot;Chiedi a ChatGPT&amp;quot;,
        &amp;quot;loading&amp;quot;:&amp;quot;Carico...&amp;quot;,
        &amp;quot;function_unavailable_in_inline_mode&amp;quot;: &amp;quot;Questa funzione non è disponibile in modalità inline&amp;quot;
    },
    &amp;quot;ms&amp;quot;: {
        &amp;quot;help_description&amp;quot;:&amp;quot;Lihat Mesej Bantuan&amp;quot;,
        &amp;quot;reset_description&amp;quot;:&amp;quot;Tetapkan semula perbualan. Secara pilihan, hantar arahan peringkat tinggi (cth. /reset Anda adalah pembantu yang membantu)&amp;quot;,
        &amp;quot;image_description&amp;quot;:&amp;quot;Jana imej daripada gesaan (cth. /image cat)&amp;quot;,
        &amp;quot;tts_description&amp;quot;:&amp;quot;Tukar teks kepada suara (cth. /tts rumah saya)&amp;quot;,
        &amp;quot;stats_description&amp;quot;:&amp;quot;Dapatkan statistik penggunaan semasa anda&amp;quot;,
        &amp;quot;resend_description&amp;quot;:&amp;quot;Hantar semula mesej terkini&amp;quot;,
        &amp;quot;chat_description&amp;quot;:&amp;quot;Sembang dengan bot!&amp;quot;,
        &amp;quot;disallowed&amp;quot;:&amp;quot;Maaf, anda tidak dibenarkan menggunakan bot ini. Anda boleh menyemak kod sumber di https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;,
        &amp;quot;budget_limit&amp;quot;:&amp;quot;Maaf, anda telah mencapai had penggunaan anda.&amp;quot;,
        &amp;quot;help_text&amp;quot;:[&amp;quot;Saya bot ChatGPT, bercakap dengan saya!&amp;quot;, &amp;quot;Hantar mesej suara atau fail kepada saya dan saya akan menyalinnya untuk anda&amp;quot;, &amp;quot;Sumber terbuka di https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;],
        &amp;quot;stats_conversation&amp;quot;:[&amp;quot;Perbualan semasa&amp;quot;, &amp;quot;Lihat mesej terdahulu&amp;quot;, &amp;quot;chat token terdahulu&amp;quot;],
        &amp;quot;usage_today&amp;quot;:&amp;quot;Penggunaan Hari ini&amp;quot;,
        &amp;quot;usage_month&amp;quot;:&amp;quot;Penggunaan bulan ini&amp;quot;,
        &amp;quot;stats_tokens&amp;quot;:&amp;quot;Token&amp;quot;,
        &amp;quot;stats_images&amp;quot;:&amp;quot;Penghasilan&amp;quot;,
        &amp;quot;stats_vision&amp;quot;:&amp;quot;Token imej diinterpretasikan&amp;quot;,
        &amp;quot;stats_tts&amp;quot;:&amp;quot;Aksara yang ditukar kepada suara&amp;quot;,
        &amp;quot;stats_transcribe&amp;quot;:[&amp;quot;Minit dan&amp;quot;, &amp;quot;Penterjemah yang kedua&amp;quot;],
        &amp;quot;stats_total&amp;quot;:&amp;quot;Jumlah semua 💰 dalam $&amp;quot;,
        &amp;quot;stats_budget&amp;quot;:&amp;quot;Baki yang tersisa&amp;quot;,
        &amp;quot;monthly&amp;quot;:&amp;quot; Untuk bulan ini&amp;quot;,
        &amp;quot;daily&amp;quot;:&amp;quot; Untuk hari ini&amp;quot;,
        &amp;quot;all-time&amp;quot;:&amp;quot;&amp;quot;,
        &amp;quot;stats_openai&amp;quot;:&amp;quot;Bulan ini akaun OpenAI anda telah dibilkan $&amp;quot;,
        &amp;quot;resend_failed&amp;quot;:&amp;quot;Anda tiada apa-apa untuk dihantar semula&amp;quot;,
        &amp;quot;reset_done&amp;quot;:&amp;quot;Selesai!&amp;quot;,
        &amp;quot;image_no_prompt&amp;quot;:&amp;quot;Sila berikan gesaan! (cth. /image cat)&amp;quot;,
        &amp;quot;image_fail&amp;quot;:&amp;quot;Gagal menjana imej&amp;quot;,
        &amp;quot;vision_fail&amp;quot;:&amp;quot;Gagal menginterpretasikan imej&amp;quot;,
        &amp;quot;tts_no_prompt&amp;quot;:&amp;quot;Sila berikan teks! (cth. /tts rumah saya)&amp;quot;,
        &amp;quot;tts_fail&amp;quot;:&amp;quot;Gagal menjana suara&amp;quot;,
        &amp;quot;media_download_fail&amp;quot;:[&amp;quot;Gagal memuat turun fail audio&amp;quot;, &amp;quot;Pastikan fail tidak terlalu besar. (maks 20MB)&amp;quot;],
        &amp;quot;media_type_fail&amp;quot;:&amp;quot;Jenis fail tidak disokong&amp;quot;,
        &amp;quot;transcript&amp;quot;:&amp;quot;Transkrip&amp;quot;,
        &amp;quot;answer&amp;quot;:&amp;quot;Jawapan&amp;quot;,
        &amp;quot;transcribe_fail&amp;quot;:&amp;quot;Gagal menyalin teks&amp;quot;,
        &amp;quot;chat_fail&amp;quot;:&amp;quot;Gagal mendapat respons&amp;quot;,
        &amp;quot;prompt&amp;quot;:&amp;quot;segera&amp;quot;,
        &amp;quot;completion&amp;quot;:&amp;quot;selesai&amp;quot;,
        &amp;quot;openai_rate_limit&amp;quot;:&amp;quot;Had Kadar OpenAI melebihi&amp;quot;,
        &amp;quot;openai_invalid&amp;quot;:&amp;quot;Permintaan tidak sah OpenAI&amp;quot;,
        &amp;quot;error&amp;quot;:&amp;quot;Ralat telah berlaku&amp;quot;,
        &amp;quot;try_again&amp;quot;:&amp;quot;Sila cuba lagi sebentar lagi&amp;quot;,
        &amp;quot;answer_with_chatgpt&amp;quot;:&amp;quot;Jawab dengan ChatGPT&amp;quot;,
        &amp;quot;ask_chatgpt&amp;quot;:&amp;quot;Tanya ChatGPT&amp;quot;,
        &amp;quot;loading&amp;quot;:&amp;quot;Memuatkan...&amp;quot;,
        &amp;quot;function_unavailable_in_inline_mode&amp;quot;: &amp;quot;Fungsi ini tidak tersedia dalam mod sebaris&amp;quot;
    },
    &amp;quot;nl&amp;quot;: {
        &amp;quot;help_description&amp;quot;:&amp;quot;Toon uitleg&amp;quot;,
        &amp;quot;reset_description&amp;quot;:&amp;quot;Herstart het gesprek. Voeg eventueel instructies toe (bijv. /reset Je bent een behulpzame assistent)&amp;quot;,
        &amp;quot;image_description&amp;quot;:&amp;quot;Genereer een afbeelding van een prompt (bijv. /image kat)&amp;quot;,
        &amp;quot;tts_description&amp;quot;:&amp;quot;Genereer spraak van tekst (bijv. /tts mijn huis)&amp;quot;,
        &amp;quot;stats_description&amp;quot;:&amp;quot;Bekijk je huidige gebruiksstatistieken&amp;quot;,
        &amp;quot;resend_description&amp;quot;:&amp;quot;Verstuur het laatste bericht opnieuw&amp;quot;,
        &amp;quot;chat_description&amp;quot;:&amp;quot;Chat met de bot!&amp;quot;,
        &amp;quot;disallowed&amp;quot;:&amp;quot;Sorry, je hebt geen bevoegdheid om deze bot te gebruiken. Je kunt de sourcecode bekijken op https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;,
        &amp;quot;budget_limit&amp;quot;:&amp;quot;Sorry, je hebt je gebruikslimiet bereikt.&amp;quot;,
        &amp;quot;help_text&amp;quot;:[&amp;quot;Ik ben een ChatGPT bot, praat met me.&amp;quot;, &amp;quot;Stuur me een spraakbericht en ik zet het om naar tekst&amp;quot;, &amp;quot;Open de source code op https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;],
        &amp;quot;stats_conversation&amp;quot;:[&amp;quot;Huidig gesprek&amp;quot;, &amp;quot;chatberichten in geschiedenis&amp;quot;, &amp;quot;chat tokens in geschiedenis&amp;quot;],
        &amp;quot;usage_today&amp;quot;:&amp;quot;Gebruik vandaag&amp;quot;,
        &amp;quot;usage_month&amp;quot;:&amp;quot;Gebruik deze maand&amp;quot;,
        &amp;quot;stats_tokens&amp;quot;:&amp;quot;chat tokens gebruikt&amp;quot;,
        &amp;quot;stats_images&amp;quot;:&amp;quot;afbeeldingen gegenereerd&amp;quot;,
        &amp;quot;stats_vision&amp;quot;:&amp;quot;Afbeeldingstokens geïnterpreteerd&amp;quot;,
        &amp;quot;stats_tts&amp;quot;:&amp;quot;karakters omgezet naar spraak&amp;quot;,
        &amp;quot;stats_transcribe&amp;quot;:[&amp;quot;minuten en&amp;quot;, &amp;quot;seconden audio naar tekst omgezet&amp;quot;],
        &amp;quot;stats_total&amp;quot;:&amp;quot;💰 Voor een totaal van $&amp;quot;,
        &amp;quot;stats_budget&amp;quot;:&amp;quot;Je resterende budget&amp;quot;,
        &amp;quot;monthly&amp;quot;:&amp;quot; voor deze maand&amp;quot;,
        &amp;quot;daily&amp;quot;:&amp;quot; voor vandaag&amp;quot;,
        &amp;quot;all-time&amp;quot;:&amp;quot;&amp;quot;,
        &amp;quot;stats_openai&amp;quot;:&amp;quot;Deze maand is je OpenAI account gefactureerd voor $&amp;quot;,
        &amp;quot;resend_failed&amp;quot;:&amp;quot;Je hebt niks om opnieuw te sturen&amp;quot;,
        &amp;quot;reset_done&amp;quot;:&amp;quot;Klaar!&amp;quot;,
        &amp;quot;image_no_prompt&amp;quot;:&amp;quot;Geef a.u.b. een prompt! (bijv. /image kat)&amp;quot;,
        &amp;quot;image_fail&amp;quot;:&amp;quot;Afbeelding genereren mislukt&amp;quot;,
        &amp;quot;vision_fail&amp;quot;:&amp;quot;Interpretatie van afbeelding mislukt&amp;quot;,
        &amp;quot;tts_no_prompt&amp;quot;:&amp;quot;Geef a.u.b. tekst! (bijv. /tts mijn huis)&amp;quot;,
        &amp;quot;tts_fail&amp;quot;:&amp;quot;Spraak genereren mislukt&amp;quot;,
        &amp;quot;media_download_fail&amp;quot;:[&amp;quot;Audio bestand downloaden mislukt&amp;quot;, &amp;quot;Check of het niet te groot is. (max 20MB)&amp;quot;],
        &amp;quot;media_type_fail&amp;quot;:&amp;quot;Niet ondersteund bestandsformaat&amp;quot;,
        &amp;quot;transcript&amp;quot;:&amp;quot;Uitgeschreven tekst&amp;quot;,
        &amp;quot;answer&amp;quot;:&amp;quot;Antwoord&amp;quot;,
        &amp;quot;transcribe_fail&amp;quot;:&amp;quot;Audio naar tekst omzetten mislukt&amp;quot;,
        &amp;quot;chat_fail&amp;quot;:&amp;quot;Reactie verkrijgen mislukt&amp;quot;,
        &amp;quot;prompt&amp;quot;:&amp;quot;prompt&amp;quot;,
        &amp;quot;completion&amp;quot;:&amp;quot;completion&amp;quot;,
        &amp;quot;openai_rate_limit&amp;quot;:&amp;quot;OpenAI Rate Limit overschreden&amp;quot;,
        &amp;quot;openai_invalid&amp;quot;:&amp;quot;OpenAI ongeldig verzoek&amp;quot;,
        &amp;quot;error&amp;quot;:&amp;quot;Er is een fout opgetreden&amp;quot;,
        &amp;quot;try_again&amp;quot;:&amp;quot;Probeer het a.u.b. later opnieuw&amp;quot;,
        &amp;quot;answer_with_chatgpt&amp;quot;:&amp;quot;Antwoord met ChatGPT&amp;quot;,
        &amp;quot;ask_chatgpt&amp;quot;:&amp;quot;Vraag ChatGPT&amp;quot;,
        &amp;quot;loading&amp;quot;:&amp;quot;Laden...&amp;quot;,
        &amp;quot;function_unavailable_in_inline_mode&amp;quot;: &amp;quot;Deze functie is niet beschikbaar in de inline modus&amp;quot;
    },
    &amp;quot;pl&amp;quot;: {
        &amp;quot;help_description&amp;quot;: &amp;quot;Pokaż wiadomości pomocnicze&amp;quot;,
        &amp;quot;reset_description&amp;quot;: &amp;quot;Zresetuj konwersację. Opcjonalnie dołącz nową frazę (np. /reset Jesteś pomocnym asystentem)&amp;quot;,
        &amp;quot;image_description&amp;quot;: &amp;quot;Generuj obraz na podstawie promptu (np. /image kot)&amp;quot;,
        &amp;quot;tts_description&amp;quot;: &amp;quot;Generuj mowę na podstawie tekstu (np. /tts mój dom)&amp;quot;,
        &amp;quot;stats_description&amp;quot;: &amp;quot;Pokaż obecne statystyki użycia&amp;quot;,
        &amp;quot;resend_description&amp;quot;: &amp;quot;Wyślij ponownie ostatnią wiadomość&amp;quot;,
        &amp;quot;chat_description&amp;quot;: &amp;quot;Rozmawiaj z botem!&amp;quot;,
        &amp;quot;disallowed&amp;quot;: &amp;quot;Przepraszam, nie masz uprawnień do korzystania z tego bota. Sprawdź kod źródłowy pod adresem https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;,
        &amp;quot;budget_limit&amp;quot;: &amp;quot;Niestety osiągnąłeś swój limit użytkowania.&amp;quot;,
        &amp;quot;help_text&amp;quot;: [&amp;quot;Jestem botem ChatGPT, porozmawiaj ze mną!&amp;quot;, &amp;quot;Wyślij mi wiadomość głosową lub plik, a ja ją dla Ciebie przepiszę&amp;quot;, &amp;quot;Otwarty kod źródłowy dostępny na https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;],
        &amp;quot;stats_conversation&amp;quot;: [&amp;quot;Bieżąca rozmowa&amp;quot;, &amp;quot;wiadomości w historii&amp;quot;, &amp;quot;tokeny w historii rozmowy&amp;quot;],
        &amp;quot;usage_today&amp;quot;: &amp;quot;Użycie dzisiaj&amp;quot;,
        &amp;quot;usage_month&amp;quot;: &amp;quot;Użycie w tym miesiącu&amp;quot;,
        &amp;quot;stats_tokens&amp;quot;: &amp;quot;tokeny&amp;quot;,
        &amp;quot;stats_images&amp;quot;: &amp;quot;wygenerowane obrazy&amp;quot;,
        &amp;quot;stats_vision&amp;quot;:&amp;quot;Tokeny obrazu zinterpretowane&amp;quot;,
        &amp;quot;stats_tts&amp;quot;: &amp;quot;znaki przekształcone na mowę&amp;quot;,
        &amp;quot;stats_transcribe&amp;quot;: [&amp;quot;minut i&amp;quot;, &amp;quot;sekund transkrybowano&amp;quot;],
        &amp;quot;stats_total&amp;quot;: &amp;quot;💰 Łącznie za kwotę $&amp;quot;,
        &amp;quot;stats_budget&amp;quot;: &amp;quot;Twój pozostały budżet&amp;quot;,
        &amp;quot;monthly&amp;quot;: &amp;quot; w tym miesiącu&amp;quot;,
        &amp;quot;daily&amp;quot;: &amp;quot; dzisiaj&amp;quot;,
        &amp;quot;all-time&amp;quot;: &amp;quot;&amp;quot;,
        &amp;quot;stats_openai&amp;quot;: &amp;quot;W tym miesiącu Twoje konto OpenAI zostało obciążone kwotą $&amp;quot;,
        &amp;quot;resend_failed&amp;quot;: &amp;quot;Nie masz nic do ponownego przesłania&amp;quot;,
        &amp;quot;reset_done&amp;quot;: &amp;quot;Gotowe!&amp;quot;,
        &amp;quot;image_no_prompt&amp;quot;: &amp;quot;Proszę podać jakiś prompt! (np. /image kot)&amp;quot;,
        &amp;quot;image_fail&amp;quot;: &amp;quot;Nie udało się wygenerować obrazu&amp;quot;,
        &amp;quot;vision_fail&amp;quot;:&amp;quot;Nie udało się zinterpretować obrazu&amp;quot;,
        &amp;quot;tts_no_prompt&amp;quot;: &amp;quot;Proszę podać tekst! (np. /tts mój dom)&amp;quot;,
        &amp;quot;tts_fail&amp;quot;: &amp;quot;Nie udało się wygenerować mowy&amp;quot;,
        &amp;quot;media_download_fail&amp;quot;: [&amp;quot;Nie udało się pobrać pliku dźwiękowego&amp;quot;, &amp;quot;Sprawdź rozmiar pliku (maks. 20 MB)&amp;quot;],
        &amp;quot;media_type_fail&amp;quot;: &amp;quot;Nieobsługiwany typ pliku&amp;quot;,
        &amp;quot;transcript&amp;quot;: &amp;quot;Transkrypt&amp;quot;,
        &amp;quot;answer&amp;quot;: &amp;quot;Odpowiedź&amp;quot;,
        &amp;quot;transcribe_fail&amp;quot;: &amp;quot;Transkrypcja nieudana&amp;quot;,
        &amp;quot;chat_fail&amp;quot;: &amp;quot;Nie udało się uzyskać odpowiedzi&amp;quot;,
        &amp;quot;prompt&amp;quot;: &amp;quot;prompt&amp;quot;,
        &amp;quot;completion&amp;quot;: &amp;quot;ukończenie&amp;quot;,
        &amp;quot;openai_rate_limit&amp;quot;: &amp;quot;Przekroczono limit OpenAI&amp;quot;,
        &amp;quot;openai_invalid&amp;quot;: &amp;quot;Błędne żądanie od OpenAI&amp;quot;,
        &amp;quot;error&amp;quot;: &amp;quot;Wystąpił błąd&amp;quot;,
        &amp;quot;try_again&amp;quot;: &amp;quot;Spróbuj ponownie za chwilę&amp;quot;,
        &amp;quot;answer_with_chatgpt&amp;quot;: &amp;quot;Odpowiedz z ChatGPT&amp;quot;,
        &amp;quot;ask_chatgpt&amp;quot;: &amp;quot;Zapytaj ChatGPT&amp;quot;,
        &amp;quot;loading&amp;quot;: &amp;quot;Ładowanie...&amp;quot;,
        &amp;quot;function_unavailable_in_inline_mode&amp;quot;: &amp;quot;Ta funkcja jest niedostępna w trybie inline&amp;quot;
    },
    &amp;quot;pt-br&amp;quot;: {
        &amp;quot;help_description&amp;quot;: &amp;quot;Mostra a mensagem de ajuda&amp;quot;,
        &amp;quot;reset_description&amp;quot;: &amp;quot;Redefine a conversa. Opcionalmente, passe instruções de alto nível (por exemplo, /reset Você é um assistente útil)&amp;quot;,
        &amp;quot;image_description&amp;quot;: &amp;quot;Gera uma imagem a partir do prompt (por exemplo, /image gato)&amp;quot;,
        &amp;quot;tts_description&amp;quot;: &amp;quot;Gera fala a partir do texto (por exemplo, /tts minha casa)&amp;quot;,
        &amp;quot;stats_description&amp;quot;: &amp;quot;Obtenha suas estatísticas de uso atuais&amp;quot;,
        &amp;quot;resend_description&amp;quot;: &amp;quot;Reenvia a última mensagem&amp;quot;,
        &amp;quot;chat_description&amp;quot;: &amp;quot;Converse com o bot!&amp;quot;,
        &amp;quot;disallowed&amp;quot;: &amp;quot;Desculpe, você não tem permissão para usar este bot. Você pode verificar o código-fonte em https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;,
        &amp;quot;budget_limit&amp;quot;: &amp;quot;Desculpe, você atingiu seu limite de uso.&amp;quot;,
        &amp;quot;help_text&amp;quot;: [&amp;quot;Eu sou um bot ChatGPT, fale comigo!&amp;quot;, &amp;quot;Envie-me uma mensagem de voz ou arquivo e eu o transcreverei para você&amp;quot;, &amp;quot;Código-fonte aberto em https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;],
        &amp;quot;stats_conversation&amp;quot;: [&amp;quot;Conversa atual&amp;quot;, &amp;quot;mensagens do chat na história&amp;quot;, &amp;quot;tokens do chat na história&amp;quot;],
        &amp;quot;usage_today&amp;quot;: &amp;quot;Uso hoje&amp;quot;,
        &amp;quot;usage_month&amp;quot;: &amp;quot;Uso este mês&amp;quot;,
        &amp;quot;stats_tokens&amp;quot;: &amp;quot;tokens de chat usados&amp;quot;,
        &amp;quot;stats_images&amp;quot;: &amp;quot;imagens geradas&amp;quot;,
        &amp;quot;stats_vision&amp;quot;:&amp;quot;Tokens de imagem interpretados&amp;quot;,
        &amp;quot;stats_tts&amp;quot;: &amp;quot;caracteres convertidos em fala&amp;quot;,
        &amp;quot;stats_transcribe&amp;quot;: [&amp;quot;minutos e&amp;quot;, &amp;quot;segundos transcritos&amp;quot;],
        &amp;quot;stats_total&amp;quot;: &amp;quot;💰 Para um valor total de $&amp;quot;,
        &amp;quot;stats_budget&amp;quot;: &amp;quot;Seu orçamento restante&amp;quot;,
        &amp;quot;monthly&amp;quot;: &amp;quot; para este mês&amp;quot;,
        &amp;quot;daily&amp;quot;: &amp;quot; para hoje&amp;quot;,
        &amp;quot;all-time&amp;quot;: &amp;quot;&amp;quot;,
        &amp;quot;stats_openai&amp;quot;: &amp;quot;Este mês sua conta OpenAI foi cobrada em $&amp;quot;,
        &amp;quot;resend_failed&amp;quot;: &amp;quot;Você não tem nada para reenviar&amp;quot;,
        &amp;quot;reset_done&amp;quot;: &amp;quot;Feito!&amp;quot;,
        &amp;quot;image_no_prompt&amp;quot;: &amp;quot;Por favor, forneça um prompt! (por exemplo, /image gato)&amp;quot;,
        &amp;quot;image_fail&amp;quot;: &amp;quot;Falha ao gerar imagem&amp;quot;,
        &amp;quot;vision_fail&amp;quot;:&amp;quot;Falha ao interpretar a imagem&amp;quot;,
        &amp;quot;tts_no_prompt&amp;quot;: &amp;quot;Por favor, forneça texto! (por exemplo, /tts minha casa)&amp;quot;,
        &amp;quot;tts_fail&amp;quot;: &amp;quot;Falha ao gerar fala&amp;quot;,
        &amp;quot;media_download_fail&amp;quot;: [&amp;quot;Falha ao baixar arquivo de áudio&amp;quot;, &amp;quot;Certifique-se de que o arquivo não seja muito grande. (máx. 20 MB)&amp;quot;],
        &amp;quot;media_type_fail&amp;quot;: &amp;quot;Tipo de arquivo não suportado&amp;quot;,
        &amp;quot;transcript&amp;quot;: &amp;quot;Transcrição&amp;quot;,
        &amp;quot;answer&amp;quot;: &amp;quot;Resposta&amp;quot;,
        &amp;quot;transcribe_fail&amp;quot;: &amp;quot;Falha ao transcrever texto&amp;quot;,
        &amp;quot;chat_fail&amp;quot;: &amp;quot;Falha ao obter resposta&amp;quot;,
        &amp;quot;prompt&amp;quot;: &amp;quot;prompt&amp;quot;,
        &amp;quot;completion&amp;quot;: &amp;quot;conclusão&amp;quot;,
        &amp;quot;openai_rate_limit&amp;quot;: &amp;quot;Limite de taxa OpenAI excedido&amp;quot;,
        &amp;quot;openai_invalid&amp;quot;: &amp;quot;Solicitação inválida OpenAI&amp;quot;,
        &amp;quot;error&amp;quot;: &amp;quot;Ocorreu um erro&amp;quot;,
        &amp;quot;try_again&amp;quot;: &amp;quot;Por favor, tente novamente mais tarde&amp;quot;,
        &amp;quot;answer_with_chatgpt&amp;quot;: &amp;quot;Responder com ChatGPT&amp;quot;,
        &amp;quot;ask_chatgpt&amp;quot;: &amp;quot;Perguntar ao ChatGPT&amp;quot;,
        &amp;quot;loading&amp;quot;: &amp;quot;Carregando...&amp;quot;,
        &amp;quot;function_unavailable_in_inline_mode&amp;quot;: &amp;quot;Esta função não está disponível no modo inline&amp;quot;
    },
    &amp;quot;ru&amp;quot;: {
        &amp;quot;help_description&amp;quot;:&amp;quot;Показать справочное сообщение&amp;quot;,
        &amp;quot;reset_description&amp;quot;:&amp;quot;Перезагрузить разговор. По желанию передай общие инструкции (например, /reset ты полезный помощник)&amp;quot;,
        &amp;quot;image_description&amp;quot;:&amp;quot;Создать изображение по запросу (например, /image кошка)&amp;quot;,
        &amp;quot;tts_description&amp;quot;:&amp;quot;Создать речь из текста (например, /tts мой дом)&amp;quot;,
        &amp;quot;stats_description&amp;quot;:&amp;quot;Получить статистику использования&amp;quot;,
        &amp;quot;resend_description&amp;quot;:&amp;quot;Повторная отправка последнего сообщения&amp;quot;,
        &amp;quot;chat_description&amp;quot;:&amp;quot;Общайся с ботом!&amp;quot;,
        &amp;quot;disallowed&amp;quot;:&amp;quot;Извини, тебе запрещено использовать этого бота. Исходный код можно найти здесь https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;,
        &amp;quot;budget_limit&amp;quot;:&amp;quot;Извини, ты достиг предела использования&amp;quot;,
        &amp;quot;help_text&amp;quot;:[&amp;quot;Я бот ChatGPT, поговори со мной!&amp;quot;, &amp;quot;Пришли мне голосовое сообщение или файл, и я сделаю тебе расшифровку&amp;quot;, &amp;quot;Открытый исходный код на https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;],
        &amp;quot;stats_conversation&amp;quot;:[&amp;quot;Текущий разговор&amp;quot;, &amp;quot;сообщения в истории&amp;quot;, &amp;quot;токены чата в истории&amp;quot;],
        &amp;quot;usage_today&amp;quot;:&amp;quot;Использование сегодня&amp;quot;,
        &amp;quot;usage_month&amp;quot;:&amp;quot;Использование в этом месяце&amp;quot;,
        &amp;quot;stats_tokens&amp;quot;:&amp;quot;токенов чата использовано&amp;quot;,
        &amp;quot;stats_images&amp;quot;:&amp;quot;изображений создано&amp;quot;,
        &amp;quot;stats_vision&amp;quot;:&amp;quot;Токенов на интерпритацию изображений&amp;quot;,
        &amp;quot;stats_tts&amp;quot;:&amp;quot;символов преобразовано в речь&amp;quot;,
        &amp;quot;stats_transcribe&amp;quot;:[&amp;quot;минут(ы) и&amp;quot;, &amp;quot;секунд(ы) расшифровки&amp;quot;],
        &amp;quot;stats_total&amp;quot;:&amp;quot;💰 На общую сумму $&amp;quot;,
        &amp;quot;stats_budget&amp;quot;:&amp;quot;Остаточный бюджет&amp;quot;,
        &amp;quot;monthly&amp;quot;:&amp;quot; на этот месяц&amp;quot;,
        &amp;quot;daily&amp;quot;:&amp;quot; на сегодня&amp;quot;,
        &amp;quot;all-time&amp;quot;:&amp;quot;&amp;quot;,
        &amp;quot;stats_openai&amp;quot;:&amp;quot;В этом месяце на ваш аккаунт OpenAI был выставлен счет на $&amp;quot;,
        &amp;quot;resend_failed&amp;quot;:&amp;quot;Вам нечего пересылать&amp;quot;,
        &amp;quot;reset_done&amp;quot;:&amp;quot;Готово!&amp;quot;,
        &amp;quot;image_no_prompt&amp;quot;:&amp;quot;Пожалуйста, подайте запрос! (например, /image кошка)&amp;quot;,
        &amp;quot;image_fail&amp;quot;:&amp;quot;Не удалось создать изображение&amp;quot;,
        &amp;quot;vision_fail&amp;quot;:&amp;quot;Сбой при интерпретации изображения&amp;quot;,
        &amp;quot;tts_no_prompt&amp;quot;:&amp;quot;Пожалуйста, подайте текст! (например, /tts мой дом)&amp;quot;,
        &amp;quot;tts_fail&amp;quot;:&amp;quot;Не удалось создать речь&amp;quot;,
        &amp;quot;media_download_fail&amp;quot;:[&amp;quot;Не удалось загрузить аудиофайл&amp;quot;, &amp;quot;Проверьте, чтобы файл не был слишком большим. (не более 20 МБ)&amp;quot;],
        &amp;quot;media_type_fail&amp;quot;:&amp;quot;Неподдержанный тип файла&amp;quot;,
        &amp;quot;transcript&amp;quot;:&amp;quot;Расшифровка&amp;quot;,
        &amp;quot;answer&amp;quot;:&amp;quot;Ответ&amp;quot;,
        &amp;quot;transcribe_fail&amp;quot;:&amp;quot;Не удалось расшифровать текст&amp;quot;,
        &amp;quot;chat_fail&amp;quot;:&amp;quot;Не удалось получить ответ&amp;quot;,
        &amp;quot;prompt&amp;quot;:&amp;quot;запрос&amp;quot;,
        &amp;quot;completion&amp;quot;:&amp;quot;ответ&amp;quot;,
        &amp;quot;openai_rate_limit&amp;quot;:&amp;quot;Превышен предел использования OpenAI&amp;quot;,
        &amp;quot;openai_invalid&amp;quot;:&amp;quot;ошибочный запрос OpenAI&amp;quot;,
        &amp;quot;error&amp;quot;:&amp;quot;Произошла ошибка&amp;quot;,
        &amp;quot;try_again&amp;quot;:&amp;quot;Пожалуйста, повторите попытку позже&amp;quot;,
        &amp;quot;answer_with_chatgpt&amp;quot;:&amp;quot;Ответить с помощью ChatGPT&amp;quot;,
        &amp;quot;ask_chatgpt&amp;quot;:&amp;quot;Спросить ChatGPT&amp;quot;,
        &amp;quot;loading&amp;quot;:&amp;quot;Загрузка...&amp;quot;,
        &amp;quot;function_unavailable_in_inline_mode&amp;quot;: &amp;quot;Эта функция недоступна в режиме inline&amp;quot;
    },
    &amp;quot;tr&amp;quot;: {
        &amp;quot;help_description&amp;quot;:&amp;quot;Yardım mesajını göster&amp;quot;,
        &amp;quot;reset_description&amp;quot;:&amp;quot;Konuşmayı sıfırla. İsteğe bağlı olarak botun nasıl davranacağını belirleyin (Örneğin: /reset Sen yardımcı bir asistansın)&amp;quot;,
        &amp;quot;image_description&amp;quot;:&amp;quot;Verilen komuta göre görüntü üret (Örneğin /image kedi)&amp;quot;,
        &amp;quot;tts_description&amp;quot;:&amp;quot;Verilen metni seslendir (Örneğin /tts evim)&amp;quot;,
        &amp;quot;stats_description&amp;quot;:&amp;quot;Mevcut kullanım istatistiklerinizi alın&amp;quot;,
        &amp;quot;resend_description&amp;quot;:&amp;quot;En son mesajı yeniden gönder&amp;quot;,
        &amp;quot;chat_description&amp;quot;:&amp;quot;Bot ile sohbet edin!&amp;quot;,
        &amp;quot;disallowed&amp;quot;:&amp;quot;Üzgünüz, bu botu kullanmanıza izin verilmiyor. Botun kaynak koduna göz atmak isterseniz: https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;,
        &amp;quot;budget_limit&amp;quot;:&amp;quot;Üzgünüz, kullanım limitinize ulaştınız&amp;quot;,
        &amp;quot;help_text&amp;quot;:[&amp;quot;Ben bir ChatGPT botuyum, konuş benimle!&amp;quot;, &amp;quot;Bana bir sesli mesaj veya dosya gönderin, sizin için yazıya çevireyim&amp;quot;, &amp;quot;Açık kaynak kodu: https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;],
        &amp;quot;stats_conversation&amp;quot;:[&amp;quot;Güncel sohbet&amp;quot;, &amp;quot;sohbet mesajı&amp;quot;, &amp;quot;sohbet token&amp;#x27;i&amp;quot;],
        &amp;quot;usage_today&amp;quot;:&amp;quot;Bugünkü kullanım&amp;quot;,
        &amp;quot;usage_month&amp;quot;:&amp;quot;Bu ayki kullanım&amp;quot;,
        &amp;quot;stats_tokens&amp;quot;:&amp;quot;sohbet token&amp;#x27;i kullanıldı&amp;quot;,
        &amp;quot;stats_images&amp;quot;:&amp;quot;görüntüler oluşturuldu&amp;quot;,
        &amp;quot;stats_vision&amp;quot;:&amp;quot;Resim belirteçleri yorumlandı&amp;quot;,
        &amp;quot;stats_tts&amp;quot;:&amp;quot;seslendirilen karakterler&amp;quot;,
        &amp;quot;stats_transcribe&amp;quot;:[&amp;quot;dakika&amp;quot;, &amp;quot;saniye sesten yazıya çeviri yapıldı&amp;quot;],
        &amp;quot;stats_total&amp;quot;:&amp;quot;💰 Bu kullanımların toplam maliyeti $&amp;quot;,
        &amp;quot;stats_budget&amp;quot;:&amp;quot;Kalan bütçeniz&amp;quot;,
        &amp;quot;monthly&amp;quot;:&amp;quot; bu ay için&amp;quot;,
        &amp;quot;daily&amp;quot;:&amp;quot; bugün için&amp;quot;,
        &amp;quot;all-time&amp;quot;:&amp;quot;&amp;quot;,
        &amp;quot;stats_openai&amp;quot;:&amp;quot;Bu ay OpenAI hesabınıza kesilen fatura tutarı: $&amp;quot;,
        &amp;quot;resend_failed&amp;quot;:&amp;quot;Yeniden gönderilecek bir şey yok&amp;quot;,
        &amp;quot;reset_done&amp;quot;:&amp;quot;Tamamlandı!&amp;quot;,
        &amp;quot;image_no_prompt&amp;quot;:&amp;quot;Lütfen komut giriniz (Örneğin /image kedi)&amp;quot;,
        &amp;quot;image_fail&amp;quot;:&amp;quot;Görüntü oluşturulamadı&amp;quot;,
        &amp;quot;vision_fail&amp;quot;:&amp;quot;Resmi yorumlama başarısız oldu&amp;quot;,
        &amp;quot;tts_no_prompt&amp;quot;:&amp;quot;Lütfen metin giriniz (Örneğin /tts evim)&amp;quot;,
        &amp;quot;tts_fail&amp;quot;:&amp;quot;Seslendirme oluşturulamadı&amp;quot;,
        &amp;quot;media_download_fail&amp;quot;:[&amp;quot;Ses dosyası indirilemedi&amp;quot;, &amp;quot;Dosyanın çok büyük olmadığından emin olun. (maksimum 20MB)&amp;quot;],
        &amp;quot;media_type_fail&amp;quot;:&amp;quot;Desteklenmeyen dosya türü&amp;quot;,
        &amp;quot;transcript&amp;quot;:&amp;quot;Yazıya çevirilmiş hali&amp;quot;,
        &amp;quot;answer&amp;quot;:&amp;quot;Cevap&amp;quot;,
        &amp;quot;transcribe_fail&amp;quot;:&amp;quot;Sesin yazıya çevirilme işlemi yapılamadı&amp;quot;,
        &amp;quot;chat_fail&amp;quot;:&amp;quot;Yanıt alınamadı&amp;quot;,
        &amp;quot;prompt&amp;quot;:&amp;quot;Komut&amp;quot;,
        &amp;quot;completion&amp;quot;:&amp;quot;Tamamlama&amp;quot;,
        &amp;quot;openai_rate_limit&amp;quot;:&amp;quot;OpenAI maksimum istek limiti aşıldı&amp;quot;,
        &amp;quot;openai_invalid&amp;quot;:&amp;quot;OpenAI Geçersiz istek&amp;quot;,
        &amp;quot;error&amp;quot;:&amp;quot;Bir hata oluştu&amp;quot;,
        &amp;quot;try_again&amp;quot;:&amp;quot;Lütfen birazdan tekrar deneyiniz&amp;quot;,
        &amp;quot;answer_with_chatgpt&amp;quot;:&amp;quot;ChatGPT ile cevapla&amp;quot;,
        &amp;quot;ask_chatgpt&amp;quot;:&amp;quot;ChatGPT&amp;#x27;ye sor&amp;quot;,
        &amp;quot;loading&amp;quot;:&amp;quot;Yükleniyor...&amp;quot;,
        &amp;quot;function_unavailable_in_inline_mode&amp;quot;: &amp;quot;Bu işlev inline modda kullanılamaz&amp;quot;
    },
    &amp;quot;uk&amp;quot;: {
        &amp;quot;help_description&amp;quot;:&amp;quot;Показати повідомлення допомоги&amp;quot;,
        &amp;quot;reset_description&amp;quot;:&amp;quot;Скинути розмову. Опціонально передайте високорівневі інструкції (наприклад, /reset Ви корисний помічник)&amp;quot;,
        &amp;quot;image_description&amp;quot;:&amp;quot;Створити зображення за вашим запитом (наприклад, /image кіт)&amp;quot;,
        &amp;quot;tts_description&amp;quot;:&amp;quot;Створити голос з тексту (наприклад, /tts мій будинок)&amp;quot;,
        &amp;quot;stats_description&amp;quot;:&amp;quot;Отримати вашу поточну статистику використання&amp;quot;,
        &amp;quot;resend_description&amp;quot;:&amp;quot;Повторно відправити останнє повідомлення&amp;quot;,
        &amp;quot;chat_description&amp;quot;:&amp;quot;Розмовляйте з ботом!&amp;quot;,
        &amp;quot;disallowed&amp;quot;:&amp;quot;Вибачте, вам не дозволено використовувати цього бота. Ви можете переглянути його код за адресою https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;,
        &amp;quot;budget_limit&amp;quot;:&amp;quot;Вибачте, ви вичерпали ліміт використання.&amp;quot;,
        &amp;quot;help_text&amp;quot;:[&amp;quot;Я бот ChatGPT, розмовляйте зі мною!&amp;quot;, &amp;quot;Надішліть мені голосове повідомлення або файл, і я транскрибую його для вас&amp;quot;, &amp;quot;Відкритий код за адресою https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;],
        &amp;quot;stats_conversation&amp;quot;:[&amp;quot;Поточна розмова&amp;quot;, &amp;quot;повідомлень у історії чату&amp;quot;, &amp;quot;токенів у історії чату&amp;quot;],
        &amp;quot;usage_today&amp;quot;:&amp;quot;Використання сьогодні&amp;quot;,
        &amp;quot;usage_month&amp;quot;:&amp;quot;Використання цього місяця&amp;quot;,
        &amp;quot;stats_tokens&amp;quot;:&amp;quot;використано токенів чату&amp;quot;,
        &amp;quot;stats_images&amp;quot;:&amp;quot;згенеровано зображень&amp;quot;,
        &amp;quot;stats_vision&amp;quot;:&amp;quot;використано токенів на інтерпритрацію зображень&amp;quot;,
        &amp;quot;stats_tts&amp;quot;:&amp;quot;символів перетворено на голос&amp;quot;,
        &amp;quot;stats_transcribe&amp;quot;:[&amp;quot;хвилин і&amp;quot;, &amp;quot;секунд транскрибовано&amp;quot;],
        &amp;quot;stats_total&amp;quot;:&amp;quot;💰 Загальна сума $&amp;quot;,
        &amp;quot;stats_budget&amp;quot;:&amp;quot;Ваш залишок бюджету&amp;quot;,
        &amp;quot;monthly&amp;quot;:&amp;quot; за цей місяць&amp;quot;,
        &amp;quot;daily&amp;quot;:&amp;quot; за сьогодні&amp;quot;,
        &amp;quot;all-time&amp;quot;:&amp;quot;&amp;quot;,
        &amp;quot;stats_openai&amp;quot;:&amp;quot;Цього місяця з вашого облікового запису OpenAI було списано $&amp;quot;,
        &amp;quot;resend_failed&amp;quot;:&amp;quot;У вас немає повідомлень для повторної відправки&amp;quot;,
        &amp;quot;reset_done&amp;quot;:&amp;quot;Готово!&amp;quot;,
        &amp;quot;image_no_prompt&amp;quot;:&amp;quot;Будь ласка, надайте свій запит! (наприклад, /image кіт)&amp;quot;,
        &amp;quot;image_fail&amp;quot;:&amp;quot;Не вдалося створити зображення&amp;quot;,
        &amp;quot;vision_fail&amp;quot;:&amp;quot;Не вдалося інтерпретувати зображення&amp;quot;,
        &amp;quot;tts_no_prompt&amp;quot;:&amp;quot;Будь ласка, надайте текст! (наприклад, /tts мій будинок)&amp;quot;,
        &amp;quot;tts_fail&amp;quot;:&amp;quot;Не вдалося створити голос&amp;quot;,
        &amp;quot;media_download_fail&amp;quot;:[&amp;quot;Не вдалося завантажити аудіофайл&amp;quot;, &amp;quot;Переконайтеся, що файл не занадто великий. (максимум 20 МБ)&amp;quot;],
        &amp;quot;media_type_fail&amp;quot;:&amp;quot;Непідтримуваний тип файлу&amp;quot;,
        &amp;quot;transcript&amp;quot;:&amp;quot;Транскрипт&amp;quot;,
        &amp;quot;answer&amp;quot;:&amp;quot;Відповідь&amp;quot;,
        &amp;quot;transcribe_fail&amp;quot;:&amp;quot;Не вдалося транскрибувати текст&amp;quot;,
        &amp;quot;chat_fail&amp;quot;:&amp;quot;Не вдалося отримати відповідь&amp;quot;,
        &amp;quot;prompt&amp;quot;:&amp;quot;запит&amp;quot;,
        &amp;quot;completion&amp;quot;:&amp;quot;завершення&amp;quot;,
        &amp;quot;openai_rate_limit&amp;quot;:&amp;quot;Перевищено ліміт частоти запитів до OpenAI&amp;quot;,
        &amp;quot;openai_invalid&amp;quot;:&amp;quot;Неправильний запит до OpenAI&amp;quot;,
        &amp;quot;error&amp;quot;:&amp;quot;Сталася помилка&amp;quot;,
        &amp;quot;try_again&amp;quot;:&amp;quot;Будь ласка, спробуйте знову через деякий час&amp;quot;,
        &amp;quot;answer_with_chatgpt&amp;quot;:&amp;quot;Відповідь за допомогою ChatGPT&amp;quot;,
        &amp;quot;ask_chatgpt&amp;quot;:&amp;quot;Запитати ChatGPT&amp;quot;,
        &amp;quot;loading&amp;quot;:&amp;quot;Завантаження...&amp;quot;,
        &amp;quot;function_unavailable_in_inline_mode&amp;quot;: &amp;quot;Ця функція недоступна в режимі Inline&amp;quot;
    },
    &amp;quot;uz&amp;quot;: {
        &amp;quot;help_description&amp;quot;: &amp;quot;Yordam xabarini ko&amp;#x27;rsatish&amp;quot;,
        &amp;quot;reset_description&amp;quot;: &amp;quot;Suhbatni qayta boshlang. Agar xohlasangiz, umumiy ko&amp;#x27;rsatmalar bering (masalan, /reset siz foydali yordamchisiz)&amp;quot;,
        &amp;quot;image_description&amp;quot;: &amp;quot;Tasvirni so&amp;#x27;rov bo&amp;#x27;yicha yaratish (masalan, /image mushuk)&amp;quot;,
        &amp;quot;tts_description&amp;quot;: &amp;quot;Matnni ovozga aylantirish (masalan, /tts uyim)&amp;quot;,
        &amp;quot;stats_description&amp;quot;: &amp;quot;Hozirgi foydalanilgan statistikani olish&amp;quot;,
        &amp;quot;resend_description&amp;quot;: &amp;quot;Oxirgi xabarni qayta yuborish&amp;quot;,
        &amp;quot;chat_description&amp;quot;: &amp;quot;Bot bilan suxbat!&amp;quot;,
        &amp;quot;disallowed&amp;quot;: &amp;quot;Kechirasiz, sizga bu botdan foydalanish taqiqlangan. Siz manba kodini tekshirishingiz mumkin https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;,
        &amp;quot;budget_limit&amp;quot;: &amp;quot;Kechirasiz, siz foydalanish chegarasiga yetdingiz.&amp;quot;,
        &amp;quot;help_text&amp;quot;: [&amp;quot;Men ChatGPT botman, men bilan gaplashing!&amp;quot;, &amp;quot;Menga ovozli xabar yoki fayl yuboring, men uni siz uchun transkripsiya qilaman&amp;quot;, &amp;quot;Ochiq manba: https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;],
        &amp;quot;stats_conversation&amp;quot;: [&amp;quot;Hozirgi suhbat&amp;quot;, &amp;quot;tarixdagi chat xabarlari&amp;quot;, &amp;quot;tarixdagi suhbat tokenlari&amp;quot;],
        &amp;quot;usage_today&amp;quot;: &amp;quot;Bugungi foydalanish&amp;quot;,
        &amp;quot;usage_month&amp;quot;: &amp;quot;Bu oydagi foydalanish&amp;quot;,
        &amp;quot;stats_tokens&amp;quot;: &amp;quot;tokenlar&amp;quot;,
        &amp;quot;stats_images&amp;quot;: &amp;quot;yaratilgan tasvirlar&amp;quot;,
        &amp;quot;stats_vision&amp;quot;:&amp;quot;Tasvir belgilari tarjima qilindi&amp;quot;,
        &amp;quot;stats_tts&amp;quot;: &amp;quot;ovozga aylangan belgilar&amp;quot;,
        &amp;quot;stats_transcribe&amp;quot;: [&amp;quot;minutlar va&amp;quot;, &amp;quot;soniyalar transkripsiya qilingan&amp;quot;],
        &amp;quot;stats_total&amp;quot;: &amp;quot;💰 Jami miqdor $&amp;quot;,
        &amp;quot;stats_budget&amp;quot;: &amp;quot;Qolgan budjetingiz&amp;quot;,
        &amp;quot;monthly&amp;quot;: &amp;quot; bu oy uchun&amp;quot;,
        &amp;quot;daily&amp;quot;: &amp;quot; bugun uchun&amp;quot;,
        &amp;quot;all-time&amp;quot;: &amp;quot;&amp;quot;,
        &amp;quot;stats_openai&amp;quot;: &amp;quot;Shu oyda OpenAI hisobingizdan to&amp;#x27;lov amalga oshirildi $&amp;quot;,
        &amp;quot;resend_failed&amp;quot;: &amp;quot;Sizda qayta yuborish uchun hech narsa yo&amp;#x27;q&amp;quot;,
        &amp;quot;reset_done&amp;quot;: &amp;quot;Bajarildi!&amp;quot;,
        &amp;quot;image_no_prompt&amp;quot;: &amp;quot;Iltimos, so&amp;#x27;rov yozing! (masalan, /image mushuk)&amp;quot;,
        &amp;quot;image_fail&amp;quot;: &amp;quot;Tasvir yaratish amalga oshmadi&amp;quot;,
        &amp;quot;vision_fail&amp;quot;:&amp;quot;Tasvirni tarjima qilishda xatolik yuz berdi&amp;quot;,
        &amp;quot;tts_no_prompt&amp;quot;: &amp;quot;Iltimos, matn kiriting! (masalan, /tts uyim)&amp;quot;,
        &amp;quot;tts_fail&amp;quot;: &amp;quot;Ovoz yaratish amalga oshmadi&amp;quot;,
        &amp;quot;media_download_fail&amp;quot;: [&amp;quot;Audio faylni yuklab olish amalga oshmadi&amp;quot;, &amp;quot;Fayl hajmi katta emasligiga ishonch hosil qiling. (max 20MB)&amp;quot;],
        &amp;quot;media_type_fail&amp;quot;: &amp;quot;Qo&amp;#x27;llab-quvvatlanmaydigan fayl turi&amp;quot;,
        &amp;quot;transcript&amp;quot;: &amp;quot;Transkript&amp;quot;,
        &amp;quot;answer&amp;quot;: &amp;quot;Javob&amp;quot;,
        &amp;quot;transcribe_fail&amp;quot;: &amp;quot;Matnni transkripsiya qilib bo&amp;#x27;lmadi&amp;quot;,
        &amp;quot;chat_fail&amp;quot;: &amp;quot;Javob olish amalga oshmadi&amp;quot;,
        &amp;quot;prompt&amp;quot;: &amp;quot;so&amp;#x27;rov&amp;quot;,
        &amp;quot;completion&amp;quot;: &amp;quot;yakunlash&amp;quot;,
        &amp;quot;openai_rate_limit&amp;quot;: &amp;quot;OpenAI ta&amp;#x27;rif chegarasidan oshib ketdi&amp;quot;,
        &amp;quot;openai_invalid&amp;quot;: &amp;quot;OpenAI So&amp;#x27;rov noto&amp;#x27;g&amp;#x27;ri&amp;quot;,
        &amp;quot;error&amp;quot;: &amp;quot;Xatolik yuz berdi&amp;quot;,
        &amp;quot;try_again&amp;quot;: &amp;quot;Birozdan keyin qayta urinib ko&amp;#x27;ring&amp;quot;,
        &amp;quot;answer_with_chatgpt&amp;quot;: &amp;quot;ChatGPT bilan javob berish&amp;quot;,
        &amp;quot;ask_chatgpt&amp;quot;: &amp;quot;ChatGPTdan so&amp;#x27;rash&amp;quot;,
        &amp;quot;loading&amp;quot;: &amp;quot;Yuklanmoqda...&amp;quot;,
        &amp;quot;function_unavailable_in_inline_mode&amp;quot;: &amp;quot;Bu funksiya inline rejimida mavjud emas&amp;quot;
    },
    &amp;quot;vi&amp;quot;: {
        &amp;quot;help_description&amp;quot;:&amp;quot;Hiển thị trợ giúp&amp;quot;,
        &amp;quot;reset_description&amp;quot;:&amp;quot;Đặt lại cuộc trò chuyện. Tùy ý chuyển hướng dẫn cấp cao (ví dụ: /reset Bạn là một trợ lý hữu ích)&amp;quot;,
        &amp;quot;image_description&amp;quot;:&amp;quot;Tạo hình ảnh từ câu lệnh (ví dụ: /image cat)&amp;quot;,
        &amp;quot;tts_description&amp;quot;:&amp;quot;Tạo giọng nói từ văn bản (ví dụ: /tts nhà của tôi)&amp;quot;,
        &amp;quot;stats_description&amp;quot;:&amp;quot;Nhận số liệu thống kê sử dụng hiện tại của bạn&amp;quot;,
        &amp;quot;resend_description&amp;quot;:&amp;quot;Gửi lại tin nhắn mới nhất&amp;quot;,
        &amp;quot;chat_description&amp;quot;:&amp;quot;Trò chuyện với bot!&amp;quot;,
        &amp;quot;disallowed&amp;quot;:&amp;quot;Xin lỗi, bạn không được phép sử dụng bot này. Bạn có thể kiểm tra mã nguồn tại https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;,
        &amp;quot;budget_limit&amp;quot;:&amp;quot;Rất tiếc, bạn đã đạt đến giới hạn sử dụng.&amp;quot;,
        &amp;quot;help_text&amp;quot;:[&amp;quot;Tôi là bot ChatGPT, hãy nói chuyện với tôi!&amp;quot;, &amp;quot;Hãy gửi cho tôi một tin nhắn thoại hoặc tệp và tôi sẽ phiên âm nó cho bạn&amp;quot;, &amp;quot;Mã nguồn mở tại https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;],
        &amp;quot;stats_conversation&amp;quot;:[&amp;quot;Cuộc trò chuyện hiện tại&amp;quot;, &amp;quot;tin nhắn trò chuyện trong lịch sử&amp;quot;, &amp;quot;thông báo trò chuyện trong lịch sử&amp;quot;],
        &amp;quot;usage_today&amp;quot;:&amp;quot;Sử dụng ngày hôm nay&amp;quot;,
        &amp;quot;usage_month&amp;quot;:&amp;quot;Sử dụng trong tháng này&amp;quot;,
        &amp;quot;stats_tokens&amp;quot;:&amp;quot;mã thông báo trò chuyện được sử dụng&amp;quot;,
        &amp;quot;stats_images&amp;quot;:&amp;quot;hình ảnh tạo ra&amp;quot;,
        &amp;quot;stats_vision&amp;quot;:&amp;quot;Dịch thông tin từ mã thông báo hình ảnh&amp;quot;,
        &amp;quot;stats_tts&amp;quot;:&amp;quot;ký tự được chuyển đổi thành giọng nói&amp;quot;,
        &amp;quot;stats_transcribe&amp;quot;:[&amp;quot;phút và&amp;quot;, &amp;quot;giây&amp;quot;],
        &amp;quot;stats_total&amp;quot;:&amp;quot;💰 Với tổng số tiền $&amp;quot;,
        &amp;quot;stats_budget&amp;quot;:&amp;quot;Ngân sách còn lại của bạn&amp;quot;,
        &amp;quot;monthly&amp;quot;:&amp;quot; cho tháng này&amp;quot;,
        &amp;quot;daily&amp;quot;:&amp;quot; cho hôm nay&amp;quot;,
        &amp;quot;all-time&amp;quot;:&amp;quot;&amp;quot;,
        &amp;quot;stats_openai&amp;quot;:&amp;quot;Tháng này, tài khoản OpenAI của bạn đã bị tính phí $&amp;quot;,
        &amp;quot;resend_failed&amp;quot;:&amp;quot;Bạn không có gì để gửi lại&amp;quot;,
        &amp;quot;reset_done&amp;quot;:&amp;quot;Xong!&amp;quot;,
        &amp;quot;image_no_prompt&amp;quot;:&amp;quot;Vui lòng cung cấp lời nhắc! (ví dụ: /image con mèo&amp;quot;,
        &amp;quot;image_fail&amp;quot;:&amp;quot;Không thể tạo hình ảnh&amp;quot;,
        &amp;quot;vision_fail&amp;quot;:&amp;quot;Không thể dịch thông tin từ hình ảnh&amp;quot;,
        &amp;quot;tts_no_prompt&amp;quot;:&amp;quot;Vui lòng cung cấp văn bản! (ví dụ: /tts nhà của tôi)&amp;quot;,
        &amp;quot;tts_fail&amp;quot;:&amp;quot;Không thể tạo giọng nói&amp;quot;,
        &amp;quot;media_download_fail&amp;quot;:[&amp;quot;Không thể tải xuống tệp âm thanh&amp;quot;, &amp;quot;Đảm bảo tệp không quá lớn. (tối đa 20 MB)&amp;quot;],
        &amp;quot;media_type_fail&amp;quot;:&amp;quot;Loại tập tin không được hỗ trợ&amp;quot;,
        &amp;quot;transcript&amp;quot;:&amp;quot;Dịch&amp;quot;,
        &amp;quot;answer&amp;quot;:&amp;quot;Trả lời&amp;quot;,
        &amp;quot;transcribe_fail&amp;quot;:&amp;quot;Không thể sao chép văn bản&amp;quot;,
        &amp;quot;chat_fail&amp;quot;:&amp;quot;Không nhận được phản hồi&amp;quot;,
        &amp;quot;prompt&amp;quot;:&amp;quot;Lời nhắc&amp;quot;,
        &amp;quot;completion&amp;quot;:&amp;quot;hoàn thành&amp;quot;,
        &amp;quot;openai_rate_limit&amp;quot;:&amp;quot;Đã vượt quá giới hạn tỷ lệ OpenAI&amp;quot;,
        &amp;quot;openai_invalid&amp;quot;:&amp;quot;OpenAI Yêu cầu không hợp lệ&amp;quot;,
        &amp;quot;error&amp;quot;:&amp;quot;Một lỗi đã xảy ra&amp;quot;,
        &amp;quot;try_again&amp;quot;:&amp;quot;Vui lòng thử lại sau một lúc&amp;quot;,
        &amp;quot;answer_with_chatgpt&amp;quot;:&amp;quot;Trả lời với ChatGPT&amp;quot;,
        &amp;quot;ask_chatgpt&amp;quot;:&amp;quot;Hỏi ChatGPT&amp;quot;,
        &amp;quot;loading&amp;quot;:&amp;quot;Đang tải...&amp;quot;,
        &amp;quot;function_unavailable_in_inline_mode&amp;quot;: &amp;quot;Chức năng này không khả dụng trong chế độ nội tuyến&amp;quot;
    },
    &amp;quot;zh-cn&amp;quot;: {
        &amp;quot;help_description&amp;quot;:&amp;quot;显示帮助信息&amp;quot;,
        &amp;quot;reset_description&amp;quot;:&amp;quot;重置对话。可以选择传递高级指令（例如/reset 你是一个有用的助手）&amp;quot;,
        &amp;quot;image_description&amp;quot;:&amp;quot;根据提示生成图像（例如/image 猫）&amp;quot;,
        &amp;quot;tts_description&amp;quot;:&amp;quot;将文本转换为语音（例如/tts 我的房子）&amp;quot;,
        &amp;quot;stats_description&amp;quot;:&amp;quot;获取您当前的使用统计&amp;quot;,
        &amp;quot;resend_description&amp;quot;:&amp;quot;重新发送最近的消息&amp;quot;,
        &amp;quot;chat_description&amp;quot;:&amp;quot;与机器人聊天！&amp;quot;,
        &amp;quot;disallowed&amp;quot;:&amp;quot;对不起，您不被允许使用该机器人。您可以查看源代码：https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;,
        &amp;quot;budget_limit&amp;quot;:&amp;quot;对不起，您已经达到了使用限制。&amp;quot;,
        &amp;quot;help_text&amp;quot;:[&amp;quot;我是一个ChatGPT机器人，请和我交谈！&amp;quot;, &amp;quot;发送语音消息或文件，我会为您进行转录。&amp;quot;, &amp;quot;源代码：https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;],
        &amp;quot;stats_conversation&amp;quot;:[&amp;quot;当前对话&amp;quot;, &amp;quot;历史聊天消息&amp;quot;, &amp;quot;历史聊天token&amp;quot;],
        &amp;quot;usage_today&amp;quot;:&amp;quot;今日使用情况&amp;quot;,
        &amp;quot;usage_month&amp;quot;:&amp;quot;本月使用情况&amp;quot;,
        &amp;quot;stats_tokens&amp;quot;:&amp;quot;使用的token&amp;quot;,
        &amp;quot;stats_images&amp;quot;:&amp;quot;生成图像的数量&amp;quot;,
        &amp;quot;stats_vision&amp;quot;:&amp;quot;图像令牌已解释&amp;quot;,
        &amp;quot;stats_tts&amp;quot;:&amp;quot;转换为语音的字符&amp;quot;,
        &amp;quot;stats_transcribe&amp;quot;:[&amp;quot;分钟&amp;quot;, &amp;quot;秒转录时长&amp;quot;],
        &amp;quot;stats_total&amp;quot;:&amp;quot;💰 总计金额 $&amp;quot;,
        &amp;quot;stats_budget&amp;quot;:&amp;quot;您的剩余预算&amp;quot;,
        &amp;quot;monthly&amp;quot;:&amp;quot;本月&amp;quot;,
        &amp;quot;daily&amp;quot;:&amp;quot;今日&amp;quot;,
        &amp;quot;all-time&amp;quot;:&amp;quot;&amp;quot;,
        &amp;quot;stats_openai&amp;quot;:&amp;quot;本月您的OpenAI账户已使用 $&amp;quot;,
        &amp;quot;resend_failed&amp;quot;:&amp;quot;没有消息需要重发&amp;quot;,
        &amp;quot;reset_done&amp;quot;:&amp;quot;完成！&amp;quot;,
        &amp;quot;image_no_prompt&amp;quot;:&amp;quot;请提供提示！（例如/image 猫）&amp;quot;,
        &amp;quot;image_fail&amp;quot;:&amp;quot;生成图像失败&amp;quot;,
        &amp;quot;vision_fail&amp;quot;:&amp;quot;图像解释失败&amp;quot;,
        &amp;quot;tts_no_prompt&amp;quot;:&amp;quot;请提供文本！（例如/tts 我的房子）&amp;quot;,
        &amp;quot;tts_fail&amp;quot;:&amp;quot;生成语音失败&amp;quot;,
        &amp;quot;media_download_fail&amp;quot;:[&amp;quot;下载音频文件失败&amp;quot;, &amp;quot;请确保文件不要太大（最大20MB）&amp;quot;],
        &amp;quot;media_type_fail&amp;quot;:&amp;quot;不支持的文件类型&amp;quot;,
        &amp;quot;transcript&amp;quot;:&amp;quot;转录&amp;quot;,
        &amp;quot;answer&amp;quot;:&amp;quot;回答&amp;quot;,
        &amp;quot;transcribe_fail&amp;quot;:&amp;quot;转录文本失败&amp;quot;,
        &amp;quot;chat_fail&amp;quot;:&amp;quot;获取响应失败&amp;quot;,
        &amp;quot;prompt&amp;quot;:&amp;quot;提示&amp;quot;,
        &amp;quot;completion&amp;quot;:&amp;quot;补全&amp;quot;,
        &amp;quot;openai_rate_limit&amp;quot;:&amp;quot;OpenAI请求频率超限&amp;quot;,
        &amp;quot;openai_invalid&amp;quot;:&amp;quot;OpenAI请求无效&amp;quot;,
        &amp;quot;error&amp;quot;:&amp;quot;发生错误&amp;quot;,
        &amp;quot;try_again&amp;quot;:&amp;quot;请稍后再试&amp;quot;,
        &amp;quot;answer_with_chatgpt&amp;quot;:&amp;quot;使用ChatGPT回答&amp;quot;,
        &amp;quot;ask_chatgpt&amp;quot;:&amp;quot;询问ChatGPT&amp;quot;,
        &amp;quot;loading&amp;quot;:&amp;quot;载入中...&amp;quot;,
        &amp;quot;function_unavailable_in_inline_mode&amp;quot;: &amp;quot;此功能在内联模式下不可用&amp;quot;
    },
    &amp;quot;zh-tw&amp;quot;: {
        &amp;quot;help_description&amp;quot;:&amp;quot;顯示幫助訊息&amp;quot;,
        &amp;quot;reset_description&amp;quot;:&amp;quot;重設對話。可以選擇傳遞進階指令（例如 /reset 你是一個樂於助人的助手）&amp;quot;,
        &amp;quot;image_description&amp;quot;:&amp;quot;根據提示生成圖片（例如 /image 貓）&amp;quot;,
        &amp;quot;tts_description&amp;quot;:&amp;quot;將文字轉換為語音（例如 /tts 我的房子）&amp;quot;,
        &amp;quot;stats_description&amp;quot;:&amp;quot;取得當前使用統計&amp;quot;,
        &amp;quot;resend_description&amp;quot;:&amp;quot;重新傳送最後一則訊息&amp;quot;,
        &amp;quot;chat_description&amp;quot;:&amp;quot;與機器人聊天！&amp;quot;,
        &amp;quot;disallowed&amp;quot;:&amp;quot;抱歉，您不被允許使用此機器人。你可以在以下網址檢視原始碼：https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;,
        &amp;quot;budget_limit&amp;quot;:&amp;quot;抱歉，已達到用量上限。&amp;quot;,
        &amp;quot;help_text&amp;quot;:[&amp;quot;我是一個 ChatGPT 機器人，跟我聊天吧！&amp;quot;, &amp;quot;傳送語音訊息或檔案給我，我會為您進行轉錄&amp;quot;, &amp;quot;開放原始碼在：https://github.com/n3d1117/chatgpt-telegram-bot&amp;quot;],
        &amp;quot;stats_conversation&amp;quot;:[&amp;quot;目前的對話&amp;quot;, &amp;quot;聊天訊息紀錄&amp;quot;, &amp;quot;聊天 Token 紀錄&amp;quot;],
        &amp;quot;usage_today&amp;quot;:&amp;quot;今日用量&amp;quot;,
        &amp;quot;usage_month&amp;quot;:&amp;quot;本月用量&amp;quot;,
        &amp;quot;stats_tokens&amp;quot;:&amp;quot;Token 已使用&amp;quot;,
        &amp;quot;stats_images&amp;quot;:&amp;quot;圖片已生成&amp;quot;,
        &amp;quot;stats_vision&amp;quot;:&amp;quot;圖片令牌已解釋&amp;quot;,
        &amp;quot;stats_tts&amp;quot;:&amp;quot;轉換為語音的字元&amp;quot;,
        &amp;quot;stats_transcribe&amp;quot;:[&amp;quot;分&amp;quot;, &amp;quot;秒已轉錄&amp;quot;],
        &amp;quot;stats_total&amp;quot;:&amp;quot;💰 總計金額 $&amp;quot;,
        &amp;quot;stats_budget&amp;quot;:&amp;quot;剩餘預算&amp;quot;,
        &amp;quot;monthly&amp;quot;:&amp;quot;本月&amp;quot;,
        &amp;quot;daily&amp;quot;:&amp;quot;今日&amp;quot;,
        &amp;quot;all-time&amp;quot;:&amp;quot;&amp;quot;,
        &amp;quot;stats_openai&amp;quot;:&amp;quot;本月您的 OpenAI 帳戶總共計費 $&amp;quot;,
        &amp;quot;resend_failed&amp;quot;:&amp;quot;沒有訊息可以重新傳送&amp;quot;,
        &amp;quot;reset_done&amp;quot;:&amp;quot;重設完成！&amp;quot;,
        &amp;quot;image_no_prompt&amp;quot;:&amp;quot;請輸入提示！（例如 /image 貓）&amp;quot;,
        &amp;quot;image_fail&amp;quot;:&amp;quot;圖片生成失敗&amp;quot;,
        &amp;quot;vision_fail&amp;quot;:&amp;quot;圖片解釋失敗&amp;quot;,
        &amp;quot;tts_no_prompt&amp;quot;:&amp;quot;請輸入文字！（例如 /tts 我的房子）&amp;quot;,
        &amp;quot;tts_fail&amp;quot;:&amp;quot;語音生成失敗&amp;quot;,
        &amp;quot;media_download_fail&amp;quot;:[&amp;quot;下載音訊檔案失敗&amp;quot;, &amp;quot;請確保檔案大小不超過 20MB&amp;quot;],
        &amp;quot;media_type_fail&amp;quot;:&amp;quot;不支援的檔案類型&amp;quot;,
        &amp;quot;transcript&amp;quot;:&amp;quot;轉錄&amp;quot;,
        &amp;quot;answer&amp;quot;:&amp;quot;回答&amp;quot;,
        &amp;quot;transcribe_fail&amp;quot;:&amp;quot;轉錄文字失敗&amp;quot;,
        &amp;quot;chat_fail&amp;quot;:&amp;quot;無法取得回應&amp;quot;,
        &amp;quot;prompt&amp;quot;:&amp;quot;提示&amp;quot;,
        &amp;quot;completion&amp;quot;:&amp;quot;填充&amp;quot;,
        &amp;quot;openai_rate_limit&amp;quot;:&amp;quot;OpenAI 的請求數量已超過上限&amp;quot;,
        &amp;quot;openai_invalid&amp;quot;:&amp;quot;OpenAI 的請求無效&amp;quot;,
        &amp;quot;error&amp;quot;:&amp;quot;發生錯誤&amp;quot;,
        &amp;quot;try_again&amp;quot;:&amp;quot;請稍後重試&amp;quot;,
        &amp;quot;answer_with_chatgpt&amp;quot;:&amp;quot;使用 ChatGPT 回答&amp;quot;,
        &amp;quot;ask_chatgpt&amp;quot;:&amp;quot;詢問 ChatGPT&amp;quot;,
        &amp;quot;loading&amp;quot;:&amp;quot;載入中...&amp;quot;,
        &amp;quot;function_unavailable_in_inline_mode&amp;quot;: &amp;quot;此功能在內嵌模式下不可用&amp;quot;
    }
}&lt;/pre&gt;

</content></entry><entry><id>codeonplate:djcNq4T-ej1</id><link rel="alternate" type="text/html" href="https://teletype.in/@codeonplate/djcNq4T-ej1?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=codeonplate"></link><title>Туристический Бот</title><published>2025-10-13T10:56:57.227Z</published><updated>2025-10-18T04:45:50.105Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img1.teletype.in/files/cd/6e/cd6e9e20-a274-4f8f-a4a3-e8adc4b9a8ac.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img2.teletype.in/files/d2/af/d2afeb51-01cf-47e3-aad1-437d34d44249.jpeg&quot;&gt;Многофункциональный Telegram-бот для путешественников. Помогает узнавать погоду, переводить текст, получать случайные фотографии, космические снимки, вести учёт расходов, отслеживать курсы валют, а также просто развлекаться в дороге.</summary><content type="html">
  &lt;p id=&quot;skWS&quot;&gt;Многофункциональный Telegram-бот для путешественников. Помогает узнавать погоду, переводить текст, получать случайные фотографии, космические снимки, вести учёт расходов, отслеживать курсы валют, а также просто развлекаться в дороге.&lt;/p&gt;
  &lt;h2 id=&quot;jXbi&quot;&gt;🚀 Основные возможности&lt;/h2&gt;
  &lt;h3 id=&quot;bTeM&quot;&gt;📊 Информационные сервисы&lt;/h3&gt;
  &lt;ul id=&quot;q1cM&quot;&gt;
    &lt;li id=&quot;ErRf&quot;&gt;&lt;strong&gt;☁️ Погода&lt;/strong&gt;: получение актуальной информации о погоде в любом городе с возможностью обновления данных&lt;/li&gt;
    &lt;li id=&quot;Ikce&quot;&gt;&lt;strong&gt;🌐 Перевод&lt;/strong&gt;: перевод текста на разные языки&lt;/li&gt;
    &lt;li id=&quot;5pdS&quot;&gt;&lt;strong&gt;💱 Валюта&lt;/strong&gt;: актуальные курсы валют (доллар США, евро к рублю)&lt;/li&gt;
    &lt;li id=&quot;ulOY&quot;&gt;&lt;strong&gt;💰 Финансы&lt;/strong&gt;: ведение учета расходов по категориям&lt;/li&gt;
    &lt;li id=&quot;inyk&quot;&gt;&lt;strong&gt;📊 Статистика расходов&lt;/strong&gt;: подробный отчет о ваших тратах с процентами и диаграммой (кнопка в информационном меню)&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;Dz0N&quot;&gt;🎉 Развлечения&lt;/h3&gt;
  &lt;ul id=&quot;VtXy&quot;&gt;
    &lt;li id=&quot;lSNQ&quot;&gt;&lt;strong&gt;🖼️ Фото&lt;/strong&gt;: случайные красивые фотографии с Unsplash&lt;/li&gt;
    &lt;li id=&quot;gsik&quot;&gt;&lt;strong&gt;🚀 Космос&lt;/strong&gt;: фото дня и случайные снимки от NASA&lt;/li&gt;
    &lt;li id=&quot;9i7h&quot;&gt;&lt;strong&gt;🐱 Коты&lt;/strong&gt;: случайные породы котов с описанием и фото&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;eorP&quot;&gt;👤 Пользователи&lt;/h3&gt;
  &lt;ul id=&quot;xmOq&quot;&gt;
    &lt;li id=&quot;zwkC&quot;&gt;&lt;strong&gt;📝 Регистрация&lt;/strong&gt;: система регистрации пользователей&lt;/li&gt;
    &lt;li id=&quot;h0gp&quot;&gt;&lt;strong&gt;💾 Сохранение данных&lt;/strong&gt;: автоматическое сохранение данных о городе и финансовых данных&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;SrRV&quot;&gt;🏗️ Структура проекта&lt;/h2&gt;
  &lt;pre id=&quot;hrsf&quot;&gt;travel-bot/
├── main.py                 # Основной файл запуска бота
├── config.py              # Конфигурация и переменные окружения
├── requirements.txt       # Зависимости проекта
├── README.md             # Документация
├── routers/              # Обработчики команд и кнопок
│   ├── start.py          # Стартовое меню и навигация
│   ├── weather.py        # Работа с погодой
│   ├── translate.py      # Перевод текста
│   ├── currency.py       # Курсы валют
│   ├── finance.py        # Учет финансов
│   ├── registration.py   # Регистрация пользователей
│   ├── photo.py          # Случайные фото
│   ├── nasa.py           # Космические фото
│   └── cat.py            # Коты и их породы
├── keyboards/            # Генерация inline-клавиатур
│   └── inline_keyboard.py
├── services/             # Интеграция с внешними API
│   ├── weather.py        # API погоды (OpenWeatherMap)
│   ├── currency.py       # API валют (ExchangeRate-API)
│   ├── translate.py      # Перевод (Google Translator)
│   ├── photo.py          # Фото (Unsplash)
│   ├── nasa.py           # Космос (NASA API)
│   └── cat.py            # Коты (The Cat API)
└── db/                   # Работа с базой данных
    └── users.py          # Функции для работы с пользователями
&lt;/pre&gt;
  &lt;h2 id=&quot;8DZa&quot;&gt;⚙️ Установка&lt;/h2&gt;
  &lt;ol id=&quot;VUJ7&quot;&gt;
    &lt;li id=&quot;cfVO&quot;&gt;&lt;strong&gt;Клонируйте репозиторий:&lt;/strong&gt;git clone https://github.com/ваше_имя_пользователя/travel-bot.git cd travel-bot&lt;/li&gt;
    &lt;li id=&quot;qQcf&quot;&gt;&lt;strong&gt;Установите зависимости:&lt;/strong&gt;pip install -r requirements.txt&lt;/li&gt;
    &lt;li id=&quot;pf7j&quot;&gt;&lt;strong&gt;Создайте файл &lt;code&gt;.env&lt;/code&gt; в корне проекта и добавьте ключи API:&lt;/strong&gt;BOT_TOKEN=ваш_токен_telegram_бота API_KEY2=ваш_api_ключ_openweathermap UNSPLASH_ACCESS_KEY=ваш_access_key_unsplash CAT_API_KEY=ваш_cat_api_key NASA_API_KEY=ваш_nasa_api_key&lt;/li&gt;
  &lt;/ol&gt;
  &lt;h2 id=&quot;zVq3&quot;&gt;🚀 Запуск&lt;/h2&gt;
  &lt;pre id=&quot;coaT&quot;&gt;python main.py&lt;/pre&gt;
  &lt;p id=&quot;MdWt&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;Howx&quot;&gt;📱 Использование&lt;/h2&gt;
  &lt;ol id=&quot;ipFL&quot;&gt;
    &lt;li id=&quot;JJyK&quot;&gt;&lt;strong&gt;Запустите бота в Telegram&lt;/strong&gt;&lt;/li&gt;
    &lt;li id=&quot;p6gx&quot;&gt;&lt;strong&gt;Нажмите «👤 Регистрация»&lt;/strong&gt;, чтобы создать учётную запись&lt;/li&gt;
    &lt;li id=&quot;7x9w&quot;&gt;&lt;strong&gt;Используйте главное меню&lt;/strong&gt; для выбора функций:&lt;/li&gt;
    &lt;ul id=&quot;J0X4&quot;&gt;
      &lt;li id=&quot;9sa9&quot;&gt;🧭 &lt;strong&gt;Информация&lt;/strong&gt; — погода, перевод, валюта, финансы, статистика расходов&lt;/li&gt;
      &lt;li id=&quot;H92D&quot;&gt;🎉 &lt;strong&gt;Развлечения&lt;/strong&gt; — фото, космос, коты&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/ol&gt;
  &lt;h3 id=&quot;Ghcn&quot;&gt;💰 Финансы и статистика&lt;/h3&gt;
  &lt;ul id=&quot;eePl&quot;&gt;
    &lt;li id=&quot;PyCL&quot;&gt;Выберите в меню пункт «💰 Финансы», чтобы ввести данные о новых расходах&lt;/li&gt;
    &lt;li id=&quot;NRI9&quot;&gt;Чтобы просмотреть отчёт о расходах, нажмите «📊 Статистика расходов» в информационном меню&lt;/li&gt;
    &lt;li id=&quot;XeqX&quot;&gt;Получите красивый отчёт с процентами и диаграммой&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h3 id=&quot;0BBu&quot;&gt;☁️ Погода&lt;/h3&gt;
  &lt;ul id=&quot;LxgL&quot;&gt;
    &lt;li id=&quot;LKiY&quot;&gt;Выберите «☁️ Погода» в меню&lt;/li&gt;
    &lt;li id=&quot;D9F5&quot;&gt;Введите название города&lt;/li&gt;
    &lt;li id=&quot;O6ZA&quot;&gt;Получайте актуальную информацию о погоде с возможностью обновления&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;sc9F&quot;&gt;🛠️ Технические особенности&lt;/h2&gt;
  &lt;ul id=&quot;rcCq&quot;&gt;
    &lt;li id=&quot;u1SY&quot;&gt;&lt;strong&gt;Асинхронная архитектура&lt;/strong&gt;: все HTTP-запросы используют &lt;code&gt;aiohttp&lt;/code&gt; для повышения производительности&lt;/li&gt;
    &lt;li id=&quot;3S2p&quot;&gt;&lt;strong&gt;База данных&lt;/strong&gt;: SQLite для хранения данных пользователей&lt;/li&gt;
    &lt;li id=&quot;K1ZZ&quot;&gt;&lt;strong&gt;FSM (конечный автомат)&lt;/strong&gt;: для пошагового ввода данных (финансы)&lt;/li&gt;
    &lt;li id=&quot;FgsQ&quot;&gt;&lt;strong&gt;Встроенная клавиатура&lt;/strong&gt;: удобная навигация по функциям&lt;/li&gt;
    &lt;li id=&quot;4XmZ&quot;&gt;&lt;strong&gt;Обработка ошибок&lt;/strong&gt;: корректная обработка ошибок API и сети&lt;/li&gt;
  &lt;/ul&gt;
  &lt;h2 id=&quot;40rT&quot;&gt;📋 Зависимости&lt;/h2&gt;
  &lt;ul id=&quot;Og1o&quot;&gt;
    &lt;li id=&quot;CCXc&quot;&gt;&lt;code&gt;aiogram&lt;/code&gt; — Платформа API Telegram-бота&lt;/li&gt;
    &lt;li id=&quot;Lyjq&quot;&gt;&lt;code&gt;aiohttp&lt;/code&gt; — Асинхронный HTTP-клиент&lt;/li&gt;
    &lt;li id=&quot;2XL1&quot;&gt;&lt;code&gt;sqlite3&lt;/code&gt; — База данных (встроенная)&lt;/li&gt;
    &lt;li id=&quot;pik3&quot;&gt;&lt;code&gt;deep-translator&lt;/code&gt; — Перевод текста&lt;/li&gt;
    &lt;li id=&quot;kHWq&quot;&gt;&lt;code&gt;python-dotenv&lt;/code&gt; — Загрузка переменных окружения&lt;/li&gt;
  &lt;/ul&gt;
  &lt;p id=&quot;NphT&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;cLkn&quot;&gt;Код:&lt;/h2&gt;
  &lt;p id=&quot;TZ4O&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;Gvlg&quot;&gt;✉ db  &lt;/h2&gt;
  &lt;p id=&quot;1gz3&quot;&gt;            ↪️users.py:&lt;/p&gt;
  &lt;pre id=&quot;uwsb&quot; data-lang=&quot;python&quot;&gt;# === ФАЙЛ: db/users.py ===
import sqlite3&lt;/pre&gt;
  &lt;pre id=&quot;rTq4&quot; data-lang=&quot;python&quot;&gt;# Функция инициализации БД
def init_db():
    with sqlite3.connect(&amp;#x27;users.db&amp;#x27;) as conn:
        cursor = conn.cursor()
        cursor.execute(&amp;#x27;&amp;#x27;&amp;#x27;
            CREATE TABLE IF NOT EXISTS users (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                tg_id INTEGER UNIQUE,
                name TEXT,
                city TEXT,
                category1 TEXT,
                category2 TEXT,
                category3 TEXT,
                expenses1 REAL,
                expenses2 REAL,
                expenses3 REAL
            )
        &amp;#x27;&amp;#x27;&amp;#x27;)
        conn.commit()&lt;/pre&gt;
  &lt;pre id=&quot;Qzi8&quot; data-lang=&quot;python&quot;&gt;
# Функция сохранения данных о пользователе
def save_user(tg_id: int, name: str, city: str):
    with sqlite3.connect(&amp;#x27;users.db&amp;#x27;) as conn:
        cursor = conn.cursor()
        cursor.execute(&amp;#x27;&amp;#x27;&amp;#x27;
            INSERT OR REPLACE INTO users (tg_id, name, city)
            VALUES (?, ?, ?)
        &amp;#x27;&amp;#x27;&amp;#x27;, (tg_id, name, city))
        conn.commit()&lt;/pre&gt;
  &lt;pre id=&quot;eHE9&quot; data-lang=&quot;python&quot;&gt;
# Функция получения данных о пользователе
def get_user(tg_id: int):
    with sqlite3.connect(&amp;#x27;users.db&amp;#x27;) as conn:
        cursor = conn.cursor()
        cursor.execute(&amp;#x27;SELECT * FROM users WHERE tg_id = ?&amp;#x27;, (tg_id,))
        return cursor.fetchone()&lt;/pre&gt;
  &lt;pre id=&quot;4Xz8&quot; data-lang=&quot;python&quot;&gt;
# Функция обновления финансовых данных
def update_finance(tg_id: int, data: dict):
    with sqlite3.connect(&amp;#x27;users.db&amp;#x27;) as conn:
        cursor = conn.cursor()
        cursor.execute(&amp;#x27;&amp;#x27;&amp;#x27;
            UPDATE users
            SET category1 = ?, category2 = ?, category3 = ?,
                expenses1 = ?, expenses2 = ?, expenses3 = ?
            WHERE tg_id = ?
        &amp;#x27;&amp;#x27;&amp;#x27;, (
            data.get(&amp;#x27;category1&amp;#x27;),
            data.get(&amp;#x27;category2&amp;#x27;),
            data.get(&amp;#x27;category3&amp;#x27;),
            data.get(&amp;#x27;expenses1&amp;#x27;),
            data.get(&amp;#x27;expenses2&amp;#x27;),
            data.get(&amp;#x27;expenses3&amp;#x27;),
            tg_id
        ))
        conn.commit()&lt;/pre&gt;
  &lt;pre id=&quot;czwY&quot; data-lang=&quot;python&quot;&gt;
# Функция получения финансовых данных с расчетом статистики
def get_finance_data(tg_id: int):
    with sqlite3.connect(&amp;#x27;users.db&amp;#x27;) as conn:
        cursor = conn.cursor()
        cursor.execute(&amp;#x27;&amp;#x27;&amp;#x27;
            SELECT category1, category2, category3, expenses1, expenses2, expenses3
            FROM users WHERE tg_id = ?
        &amp;#x27;&amp;#x27;&amp;#x27;, (tg_id,))
        result = cursor.fetchone()
        
        if not result:
            return None
            
        categories = [result[0], result[1], result[2]]
        expenses = [result[3] or 0, result[4] or 0, result[5] or 0]
        
        # Фильтруем только заполненные категории
        valid_data = [(cat, exp) for cat, exp in zip(categories, expenses) if cat and exp &amp;gt; 0]
        
        if not valid_data:
            return None
            
        total = sum(exp for _, exp in valid_data)
        
        # Рассчитываем проценты
        percentages = []
        for cat, exp in valid_data:
            percentage = (exp / total * 100) if total &amp;gt; 0 else 0
            percentages.append((cat, exp, percentage))
            
        return {
            &amp;#x27;total&amp;#x27;: total,
            &amp;#x27;breakdown&amp;#x27;: percentages
        }&lt;/pre&gt;
  &lt;pre id=&quot;h0F1&quot;&gt;           &lt;/pre&gt;
  &lt;h2 id=&quot;QCie&quot;&gt;✉ keyboards&lt;/h2&gt;
  &lt;p id=&quot;dWL9&quot;&gt;                   ↪️inline_keyboard.py&lt;/p&gt;
  &lt;pre id=&quot;X1Tv&quot; data-lang=&quot;python&quot;&gt;from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder&lt;/pre&gt;
  &lt;pre id=&quot;63GK&quot;&gt;&lt;/pre&gt;
  &lt;pre id=&quot;nczt&quot; data-lang=&quot;python&quot;&gt;# Главное меню
async def get_main_menu():
    keyboard_main =InlineKeyboardBuilder()
    keyboard_main.add(
        InlineKeyboardButton(text=&amp;quot;🧭 Информация&amp;quot;, callback_data=&amp;quot;info_menu&amp;quot;),
                InlineKeyboardButton(text=&amp;quot;🎉 Развлечения&amp;quot;, callback_data=&amp;quot;fun_menu&amp;quot;),
                InlineKeyboardButton(text=&amp;quot;👤 Регистрация&amp;quot;, callback_data=&amp;quot;registration&amp;quot;),
                InlineKeyboardButton(text=&amp;quot;❓ Что я умею&amp;quot;, callback_data=&amp;quot;help&amp;quot;)
    )
    return keyboard_main.adjust(2).as_markup()&lt;/pre&gt;
  &lt;pre id=&quot;qcNL&quot; data-lang=&quot;python&quot;&gt;# Подменю &amp;quot;Информация для путешествия&amp;quot;
async def get_info_menu():
    keyboard = InlineKeyboardBuilder()
    keyboard.add(
                #InlineKeyboardButton(text=&amp;quot;📍 Где я?&amp;quot;, callback_data=&amp;quot;location&amp;quot;),
                InlineKeyboardButton(text=&amp;quot;☁️ Погода&amp;quot;, callback_data=&amp;quot;weather&amp;quot;),
                InlineKeyboardButton(text=&amp;quot;🌐 Перевод&amp;quot;, callback_data=&amp;quot;translate&amp;quot;),
                InlineKeyboardButton(text=&amp;quot;💱 Валюта&amp;quot;, callback_data=&amp;quot;currency&amp;quot;),
                InlineKeyboardButton(text=&amp;quot;💰 Финансы&amp;quot;, callback_data=&amp;quot;finance&amp;quot;),
                InlineKeyboardButton(text=&amp;quot;📊 Статистика расходов&amp;quot;, callback_data=&amp;quot;show_finance&amp;quot;),
                InlineKeyboardButton(text=&amp;quot;🔙 Назад&amp;quot;, callback_data=&amp;quot;main_menu&amp;quot;)
                 )
    return keyboard.adjust(2).as_markup()&lt;/pre&gt;
  &lt;pre id=&quot;588Z&quot; data-lang=&quot;python&quot;&gt;# Подменю &amp;quot;Развлечения в дороге&amp;quot;
async def get_fun_menu():
    keyboard = InlineKeyboardBuilder()
    keyboard.add(InlineKeyboardButton(text=&amp;quot;🖼️ Фото&amp;quot;, callback_data=&amp;quot;photo&amp;quot;),
                InlineKeyboardButton(text=&amp;quot;🚀 Космос&amp;quot;, callback_data=&amp;quot;space&amp;quot;),
                InlineKeyboardButton(text=&amp;quot;🐱 Коты&amp;quot;, callback_data=&amp;quot;cat&amp;quot;),
                InlineKeyboardButton(text=&amp;quot;🔙 Назад&amp;quot;, callback_data=&amp;quot;main_menu&amp;quot;)
                )
    return keyboard.adjust(2).as_markup()&lt;/pre&gt;
  &lt;pre id=&quot;7ndV&quot; data-lang=&quot;python&quot;&gt;# Кнопка &amp;quot;Ещё фото&amp;quot;
def photo_again_keyboard():
    keyboard = InlineKeyboardBuilder()
    keyboard.add(InlineKeyboardButton(text=&amp;quot;🖼️ Ещё фото&amp;quot;, callback_data=&amp;quot;photo_again&amp;quot;),
                InlineKeyboardButton(text=&amp;quot;🔙 Назад&amp;quot;, callback_data=&amp;quot;fun_menu&amp;quot;)
                )
    return keyboard.adjust(2).as_markup()&lt;/pre&gt;
  &lt;pre id=&quot;Rgex&quot; data-lang=&quot;python&quot;&gt;# Кнопка &amp;quot;Ещё фото из космоса&amp;quot;
def nasa_again_keyboard():
    keyboard = InlineKeyboardBuilder()
    keyboard.add(InlineKeyboardButton(text=&amp;quot;🖼️ Ещё фото из космоса&amp;quot;, callback_data=&amp;quot;nasa_again&amp;quot;),
                InlineKeyboardButton(text=&amp;quot;🔙 Назад&amp;quot;, callback_data=&amp;quot;fun_menu&amp;quot;)
                )
    return keyboard.adjust(2).as_markup()&lt;/pre&gt;
  &lt;pre id=&quot;l30u&quot; data-lang=&quot;python&quot;&gt;# Кнопка &amp;quot;Ещё кота&amp;quot;
def cat_again_keyboard():
    keyboard = InlineKeyboardBuilder()
    keyboard.add(
        InlineKeyboardButton(text=&amp;quot;🐾 Ещё кота&amp;quot;, callback_data=&amp;quot;cat_again&amp;quot;),
        InlineKeyboardButton(text=&amp;quot;🔙 Назад&amp;quot;, callback_data=&amp;quot;fun_menu&amp;quot;)
    )
    return keyboard.adjust(2).as_markup()&lt;/pre&gt;
  &lt;pre id=&quot;imED&quot; data-lang=&quot;python&quot;&gt;# Кнопка &amp;quot;Обновить погоду&amp;quot;
def weather_refresh_keyboard(city: str):
     keyboard = InlineKeyboardBuilder()
     keyboard.add(
        InlineKeyboardButton(text=&amp;quot;🔄 Обновить погоду&amp;quot;, callback_data=f&amp;quot;refresh_weather:{city}&amp;quot;),
        InlineKeyboardButton(text=&amp;quot;🔙 Назад&amp;quot;, callback_data=&amp;quot;main_menu&amp;quot;)
     )
     return keyboard.adjust(2).as_markup()&lt;/pre&gt;
  &lt;pre id=&quot;2AlN&quot; data-lang=&quot;python&quot;&gt;# Кнопки для финансов
def finance_keyboard():
    keyboard = InlineKeyboardBuilder()
    keyboard.add(
        InlineKeyboardButton(text=&amp;quot;📊 Показать статистику&amp;quot;, callback_data=&amp;quot;show_finance&amp;quot;),
        InlineKeyboardButton(text=&amp;quot;🔙 Назад&amp;quot;, callback_data=&amp;quot;main_menu&amp;quot;)
    )
    return keyboard.adjust(2).as_markup()&lt;/pre&gt;
  &lt;h3 id=&quot;c4Yd&quot;&gt;&lt;/h3&gt;
  &lt;h2 id=&quot;cBIo&quot;&gt;✉routers&lt;/h2&gt;
  &lt;p id=&quot;JhNa&quot;&gt;           ↪️cat.py&lt;/p&gt;
  &lt;pre id=&quot;B7CD&quot; data-lang=&quot;python&quot;&gt;# from aiogram import Router, F
# from aiogram.filters import Command
# from aiogram.types import Message, CallbackQuery
# from services.cat import get_breed_info, get_cat_image
#
# cat_router = Router()
#
# @cat_router.callback_query(F.data == &amp;quot;cat&amp;quot;)
# async def handle_cat(callback: CallbackQuery):
#     await callback.message.answer(&amp;quot;Напиши породу кота:&amp;quot;)
#     await callback.answer()
#
# @cat_router.message(F.text.regexp(r&amp;quot;^[^/].*&amp;quot;))
# async def cat_breed(message: Message):
#     breed_name = message.text
#     breed_info = get_breed_info(breed_name)
#     if breed_info:
#         cat_image = get_cat_image(breed_info[&amp;quot;id&amp;quot;])
#         info = (f&amp;quot;Порода: {breed_info[&amp;#x27;name&amp;#x27;]}\n&amp;quot;
#                 f&amp;quot;Описание: {breed_info[&amp;#x27;description&amp;#x27;]}\n&amp;quot;
#                 f&amp;quot;Темперамент: {breed_info[&amp;#x27;temperament&amp;#x27;]}\n&amp;quot;
#                 f&amp;quot;Страна: {breed_info[&amp;#x27;origin&amp;#x27;]}\n&amp;quot;
#                 f&amp;quot;Ссылка: {breed_info[&amp;#x27;wikipedia_url&amp;#x27;]}&amp;quot;)
#         await message.answer_photo(photo=cat_image, caption=info)
#     else:
#         await message.answer(&amp;quot;Порода не найдена.&amp;quot;)&lt;/pre&gt;
  &lt;pre id=&quot;xza9&quot; data-lang=&quot;python&quot;&gt;
from aiogram import Router, F
from aiogram.types import CallbackQuery
from keyboards.inline_keyboard import cat_again_keyboard
from services.cat import get_random_breed, get_cat_image&lt;/pre&gt;
  &lt;pre id=&quot;NU5i&quot; data-lang=&quot;python&quot;&gt;cat_router = Router()&lt;/pre&gt;
  &lt;pre id=&quot;Go1S&quot; data-lang=&quot;python&quot;&gt;@cat_router.callback_query(F.data == &amp;quot;cat&amp;quot;)
async def handle_cat(callback: CallbackQuery):
    breed_info = await get_random_breed()
    if breed_info:
        cat_image = await get_cat_image(breed_info[&amp;quot;id&amp;quot;])
        info = (
            f&amp;quot;🐱 Порода: {breed_info[&amp;#x27;name&amp;#x27;]}\n&amp;quot;
            f&amp;quot;📄 Описание: {breed_info[&amp;#x27;description&amp;#x27;]}\n&amp;quot;
            f&amp;quot;🎭 Темперамент: {breed_info[&amp;#x27;temperament&amp;#x27;]}\n&amp;quot;
            f&amp;quot;🌍 Страна: {breed_info[&amp;#x27;origin&amp;#x27;]}\n&amp;quot;
            f&amp;quot;🔗 Подробнее: {breed_info[&amp;#x27;wikipedia_url&amp;#x27;]}&amp;quot;
        )
        await callback.message.delete()
        await callback.message.answer_photo(photo=cat_image, caption=info, reply_markup=cat_again_keyboard())
    else:
        await callback.message.answer(&amp;quot;❌ Не удалось получить породу.&amp;quot;)
    await callback.answer()&lt;/pre&gt;
  &lt;pre id=&quot;48E8&quot; data-lang=&quot;python&quot;&gt;
@cat_router.callback_query(F.data == &amp;quot;cat_again&amp;quot;)
async def cat_again(callback: CallbackQuery):
    breed_info = await get_random_breed()
    if breed_info:
        cat_image = await get_cat_image(breed_info[&amp;quot;id&amp;quot;])
        info = (
            f&amp;quot;🐱 Порода: {breed_info[&amp;#x27;name&amp;#x27;]}\n&amp;quot;
            f&amp;quot;📄 Описание: {breed_info[&amp;#x27;description&amp;#x27;]}\n&amp;quot;
            f&amp;quot;🎭 Темперамент: {breed_info[&amp;#x27;temperament&amp;#x27;]}\n&amp;quot;
            f&amp;quot;🌍 Страна: {breed_info[&amp;#x27;origin&amp;#x27;]}\n&amp;quot;
            f&amp;quot;🔗 Подробнее: {breed_info[&amp;#x27;wikipedia_url&amp;#x27;]}&amp;quot;
        )
        await callback.message.answer_photo(photo=cat_image, caption=info, reply_markup=cat_again_keyboard())
    else:
        await callback.message.answer(&amp;quot;❌ Не удалось получить породу.&amp;quot;)
    await callback.answer()&lt;/pre&gt;
  &lt;p id=&quot;hwIg&quot;&gt;    &lt;/p&gt;
  &lt;p id=&quot;ffcX&quot;&gt;        ↪️currency.py&lt;/p&gt;
  &lt;pre id=&quot;GRdy&quot; data-lang=&quot;python&quot;&gt;from aiogram import Router, F
from aiogram.types import Message, CallbackQuery
from services.currency import get_currency&lt;/pre&gt;
  &lt;pre id=&quot;4Mzd&quot; data-lang=&quot;python&quot;&gt;
# === Инициализация роутера ===
currency_router = Router()&lt;/pre&gt;
  &lt;pre id=&quot;VUxj&quot; data-lang=&quot;python&quot;&gt;# Обработка кнопки &amp;quot;currency&amp;quot;
@currency_router.callback_query(F.data == &amp;quot;currency&amp;quot;)
async def handle_currency(callback: CallbackQuery):
    try:
        currency_data = await get_currency()
        await callback.message.answer(currency_data)
    except Exception as e:
        await callback.message.answer(&amp;quot;Произошла ошибка при получении курса валют&amp;quot;)
        print(f&amp;quot;Ошибка при получении курса валют: {e}&amp;quot;)
    await callback.answer()&lt;/pre&gt;
  &lt;p id=&quot;oWTo&quot;&gt;     &lt;/p&gt;
  &lt;p id=&quot;r5Dd&quot;&gt;        ↪️finance.py&lt;/p&gt;
  &lt;pre id=&quot;HogY&quot; data-lang=&quot;python&quot;&gt;from aiogram import Router, F
from aiogram.types import Message, CallbackQuery
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from keyboards.inline_keyboard import get_main_menu, finance_keyboard
from db.users import update_finance, get_finance_data&lt;/pre&gt;
  &lt;pre id=&quot;7gO4&quot; data-lang=&quot;python&quot;&gt;
# === Инициализация роутера ===
finance_router = Router()&lt;/pre&gt;
  &lt;pre id=&quot;bdmQ&quot; data-lang=&quot;python&quot;&gt;# Обработка кнопки &amp;quot;Финансы&amp;quot;
class FinanceForm(StatesGroup):
    category1 = State()
    expenses1 = State()
    category2 = State()
    expenses2 = State()
    category3 = State()
    expenses3 = State()&lt;/pre&gt;
  &lt;pre id=&quot;wPp3&quot; data-lang=&quot;python&quot;&gt;# Обработка кнопки &amp;quot;Финансы&amp;quot;
@finance_router.callback_query(F.data == &amp;quot;finance&amp;quot;)
async def start_finance(callback: CallbackQuery, state: FSMContext):
    print(f&amp;quot;DEBUG: Кнопка &amp;#x27;Финансы&amp;#x27; нажата пользователем {callback.from_user.id}&amp;quot;)
    await state.set_state(FinanceForm.category1)
    await callback.message.answer(&amp;quot;Введите первую категорию расходов:&amp;quot;)
    await callback.answer()&lt;/pre&gt;
  &lt;pre id=&quot;b7oR&quot; data-lang=&quot;python&quot;&gt;# Обработка кнопки &amp;quot;Показать статистику&amp;quot;
@finance_router.callback_query(F.data == &amp;quot;show_finance&amp;quot;)
async def show_finance_stats(callback: CallbackQuery):
    tg_id = callback.from_user.id
    finance_data = get_finance_data(tg_id)
    
    if finance_data:
        # Формируем красивый отчет
        report = f&amp;quot;💰 &amp;lt;b&amp;gt;Ваши расходы:&amp;lt;/b&amp;gt;\n\n&amp;quot;
        report += f&amp;quot;📊 &amp;lt;b&amp;gt;Общая сумма:&amp;lt;/b&amp;gt; {finance_data[&amp;#x27;total&amp;#x27;]:,.2f} ₽\n\n&amp;quot;
        
        for category, amount, percentage in finance_data[&amp;#x27;breakdown&amp;#x27;]:
            # Создаем визуальный индикатор процента
            bar_length = int(percentage / 5)  # 5% = 1 символ
            bar = &amp;quot;█&amp;quot; * bar_length + &amp;quot;░&amp;quot; * (20 - bar_length)
            
            report += f&amp;quot;📌 &amp;lt;b&amp;gt;{category}:&amp;lt;/b&amp;gt;\n&amp;quot;
            report += f&amp;quot;   💵 {amount:,.2f} ₽ ({percentage:.1f}%)\n&amp;quot;
            report += f&amp;quot;   {bar}\n\n&amp;quot;
        
        await callback.message.answer(report, parse_mode=&amp;quot;HTML&amp;quot;, reply_markup=finance_keyboard())
    else:
        await callback.message.answer(&amp;quot;📝 У вас пока нет данных о расходах. Нажмите &amp;#x27;💰 Финансы&amp;#x27; для добавления.&amp;quot;, reply_markup=finance_keyboard())
    
    await callback.answer()&lt;/pre&gt;
  &lt;pre id=&quot;I2HA&quot; data-lang=&quot;python&quot;&gt;# Обработка кнопки &amp;quot;Назад&amp;quot; в финансах
@finance_router.callback_query(F.data == &amp;quot;main_menu&amp;quot;)
async def back_to_main_menu(callback: CallbackQuery):
    await callback.message.edit_text(
        text=&amp;quot;📋 Главное меню. Выбери действие:&amp;quot;,
        reply_markup=await get_main_menu()
    )
    await callback.answer()&lt;/pre&gt;
  &lt;pre id=&quot;hyAB&quot; data-lang=&quot;python&quot;&gt;# Обработка ввода первой категории
@finance_router.message(FinanceForm.category1)
async def category1_handler(message: Message, state: FSMContext):
    await state.update_data(category1=message.text)
    await state.set_state(FinanceForm.expenses1)
    await message.answer(&amp;quot;Введите расходы для категории 1:&amp;quot;)&lt;/pre&gt;
  &lt;pre id=&quot;wWHQ&quot; data-lang=&quot;python&quot;&gt;# Обработка ввода расходов
@finance_router.message(FinanceForm.expenses1)
async def expenses1_handler(message: Message, state: FSMContext):
    try:
        expense = float(message.text)
        await state.update_data(expenses1=expense)
        await state.set_state(FinanceForm.category2)
        await message.answer(&amp;quot;Введите вторую категорию расходов:&amp;quot;)
    except ValueError:
        await message.answer(&amp;quot;❌ Пожалуйста, введите корректную сумму (например: 1000.50)&amp;quot;)&lt;/pre&gt;
  &lt;pre id=&quot;opnE&quot; data-lang=&quot;python&quot;&gt;# Обработка ввода второй категории
@finance_router.message(FinanceForm.category2)
async def category2_handler(message: Message, state: FSMContext):
    await state.update_data(category2=message.text)
    await state.set_state(FinanceForm.expenses2)
    await message.answer(&amp;quot;Введите расходы для категории 2:&amp;quot;)&lt;/pre&gt;
  &lt;pre id=&quot;MQL5&quot; data-lang=&quot;python&quot;&gt;# Обработка ввода расходов
@finance_router.message(FinanceForm.expenses2)
async def expenses2_handler(message: Message, state: FSMContext):
    try:
        expense = float(message.text)
        await state.update_data(expenses2=expense)
        await state.set_state(FinanceForm.category3)
        await message.answer(&amp;quot;Введите третью категорию расходов:&amp;quot;)
    except ValueError:
        await message.answer(&amp;quot;❌ Пожалуйста, введите корректную сумму (например: 1000.50)&amp;quot;)&lt;/pre&gt;
  &lt;pre id=&quot;Hebn&quot; data-lang=&quot;python&quot;&gt;# Обработка ввода третьей категории
@finance_router.message(FinanceForm.category3)
async def category3_handler(message: Message, state: FSMContext):
    await state.update_data(category3=message.text)
    await state.set_state(FinanceForm.expenses3)
    await message.answer(&amp;quot;Введите расходы для категории 3:&amp;quot;)&lt;/pre&gt;
  &lt;pre id=&quot;J4H9&quot; data-lang=&quot;python&quot;&gt;# Обработка ввода расходов
@finance_router.message(FinanceForm.expenses3)
async def expenses3_handler(message: Message, state: FSMContext):
    try:
        expense = float(message.text)
        await state.update_data(expenses3=expense)
        data = await state.get_data()
        tg_id = message.from_user.id
        
        # Сохраняем данные
        update_finance(tg_id, data)
        
        # Получаем статистику
        finance_data = get_finance_data(tg_id)
        
        if finance_data:
            # Формируем красивый отчет
            report = f&amp;quot;💰 &amp;lt;b&amp;gt;Ваши расходы:&amp;lt;/b&amp;gt;\n\n&amp;quot;
            report += f&amp;quot;📊 &amp;lt;b&amp;gt;Общая сумма:&amp;lt;/b&amp;gt; {finance_data[&amp;#x27;total&amp;#x27;]:,.2f} ₽\n\n&amp;quot;
            
            for category, amount, percentage in finance_data[&amp;#x27;breakdown&amp;#x27;]:
                # Создаем визуальный индикатор процента
                bar_length = int(percentage / 5)  # 5% = 1 символ
                bar = &amp;quot;█&amp;quot; * bar_length + &amp;quot;░&amp;quot; * (20 - bar_length)
                
                report += f&amp;quot;📌 &amp;lt;b&amp;gt;{category}:&amp;lt;/b&amp;gt;\n&amp;quot;
                report += f&amp;quot;   💵 {amount:,.2f} ₽ ({percentage:.1f}%)\n&amp;quot;
                report += f&amp;quot;   {bar}\n\n&amp;quot;
            
            await message.answer(report, parse_mode=&amp;quot;HTML&amp;quot;)
        else:
            await message.answer(&amp;quot;✅ Данные сохранены!&amp;quot;)
            
        await state.clear()
        
    except ValueError:
        await message.answer(&amp;quot;❌ Пожалуйста, введите корректную сумму (например: 1000.50)&amp;quot;)&lt;/pre&gt;
  &lt;p id=&quot;gY1m&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;6KbY&quot;&gt;          ↪️nasa.py&lt;/p&gt;
  &lt;pre id=&quot;Wpqd&quot; data-lang=&quot;puppet&quot;&gt;from aiogram import Router, F
from aiogram.types import Message, CallbackQuery
from services.nasa import get_random_apod
from keyboards.inline_keyboard import nasa_again_keyboard&lt;/pre&gt;
  &lt;pre id=&quot;9DDz&quot; data-lang=&quot;python&quot;&gt;nasa_router = Router()&lt;/pre&gt;
  &lt;pre id=&quot;vvmB&quot; data-lang=&quot;python&quot;&gt;
@nasa_router.callback_query(F.data == &amp;quot;space&amp;quot;)
async def handle_space(callback: CallbackQuery):
    try:
        apod = await get_random_apod()
        photo_url = apod[&amp;quot;url&amp;quot;]
        title = apod[&amp;quot;title&amp;quot;]
        explanation = apod[&amp;quot;explanation&amp;quot;]
        await callback.message.answer_photo(photo=photo_url, caption=f&amp;quot;{title}\n\n{explanation}&amp;quot;, reply_markup=nasa_again_keyboard())
    except Exception as e:
        await callback.message.answer(&amp;quot;Извините, не удалось получить фото из космоса. Попробуйте позже.&amp;quot;)
        print(f&amp;quot;Ошибка NASA API: {e}&amp;quot;)
    await callback.answer()&lt;/pre&gt;
  &lt;pre id=&quot;B27v&quot; data-lang=&quot;python&quot;&gt;
@nasa_router.callback_query(F.data == &amp;quot;nasa_again&amp;quot;)
async def handle_nasa_again(callback: CallbackQuery):
    try:
        apod = await get_random_apod()
        photo_url = apod[&amp;quot;url&amp;quot;]
        title = apod[&amp;quot;title&amp;quot;]
        explanation = apod[&amp;quot;explanation&amp;quot;]
        await callback.message.answer_photo(photo=photo_url, caption=f&amp;quot;{title}\n\n{explanation}&amp;quot;, reply_markup=nasa_again_keyboard())
    except Exception as e:
        await callback.message.answer(&amp;quot;Извините, не удалось получить фото из космоса. Попробуйте позже.&amp;quot;)
        print(f&amp;quot;Ошибка NASA API: {e}&amp;quot;)
    await callback.answer()&lt;/pre&gt;
  &lt;p id=&quot;kcVl&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;kRm0&quot;&gt;            ↪️photo.py&lt;/p&gt;
  &lt;pre id=&quot;4BEL&quot; data-lang=&quot;python&quot;&gt;from aiogram import Router, F
from aiogram.types import Message, CallbackQuery
from services.photo import get_random_photo
from keyboards.inline_keyboard import photo_again_keyboard&lt;/pre&gt;
  &lt;pre id=&quot;dYnn&quot; data-lang=&quot;python&quot;&gt;photo_router = Router()&lt;/pre&gt;
  &lt;pre id=&quot;rrmU&quot; data-lang=&quot;python&quot;&gt;# Обработка кнопки &amp;quot;photo&amp;quot;
@photo_router.callback_query(F.data == &amp;quot;photo&amp;quot;)
async def handle_photo(callback: CallbackQuery):
    url = await get_random_photo()
    if url:
        await callback.message.delete()
        await callback.message.answer_photo(photo=url, caption=&amp;quot;Вот случайное фото&amp;quot;, reply_markup = photo_again_keyboard())
    else:
        await callback.message.answer(&amp;quot;Не удалось получить фото.&amp;quot;)
    await callback.answer()&lt;/pre&gt;
  &lt;pre id=&quot;6vG6&quot; data-lang=&quot;python&quot;&gt;# Обработка кнопки &amp;quot;photo_again&amp;quot;
@photo_router.callback_query(F.data == &amp;quot;photo_again&amp;quot;)
async def photo_again(callback: CallbackQuery):
    url = await get_random_photo()
    if url:
        await callback.message.answer_photo(photo=url, caption=&amp;quot;Ещё одно фото&amp;quot;, reply_markup = photo_again_keyboard())
    else:
        await callback.message.answer(&amp;quot;Не удалось получить фото.&amp;quot;)
    await callback.answer()&lt;/pre&gt;
  &lt;p id=&quot;3R3g&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;huGT&quot;&gt;         ↪️registration.py&lt;/p&gt;
  &lt;pre id=&quot;C4C9&quot; data-lang=&quot;python&quot;&gt;from aiogram import Router, F
from aiogram.types import CallbackQuery
from db.users import save_user, get_user&lt;/pre&gt;
  &lt;pre id=&quot;0Uza&quot; data-lang=&quot;python&quot;&gt;
# === Инициализация роутера ===
registration_router = Router()&lt;/pre&gt;
  &lt;pre id=&quot;ILXH&quot; data-lang=&quot;python&quot;&gt;# Обработка кнопки &amp;quot;Регистрация&amp;quot;
@registration_router.callback_query(F.data == &amp;quot;registration&amp;quot;)
async def registration(callback: CallbackQuery):
    telegram_id = callback.from_user.id
    name = callback.from_user.full_name
    user = get_user(telegram_id)
    if user:
        await callback.message.answer(&amp;quot;Ты уже зарегистрирован!&amp;quot;)
    else:
        save_user(telegram_id, name, None)
        await callback.message.answer(&amp;quot;Ты зарегистрирован!&amp;quot;)
    await callback.answer()&lt;/pre&gt;
  &lt;p id=&quot;PoGR&quot;&gt;         ↪️start.py&lt;/p&gt;
  &lt;pre id=&quot;Zw3e&quot; data-lang=&quot;python&quot;&gt;from aiogram import Router, F
from aiogram.types import Message, CallbackQuery
from aiogram.filters import CommandStart
from aiogram.fsm.state import State, StatesGroup
from aiogram.fsm.context import FSMContext&lt;/pre&gt;
  &lt;pre id=&quot;6u1y&quot; data-lang=&quot;python&quot;&gt;from keyboards.inline_keyboard import get_main_menu, get_info_menu, get_fun_menu&lt;/pre&gt;
  &lt;pre id=&quot;hxaV&quot;&gt;&lt;/pre&gt;
  &lt;pre id=&quot;0wkL&quot; data-lang=&quot;python&quot;&gt;# === Инициализация роутера ===
start_router = Router()&lt;/pre&gt;
  &lt;pre id=&quot;shbn&quot;&gt;# === Хэндлер на /start ===
@start_router.message(CommandStart())
async def cmd_start(message: Message):
    keyboard = await get_main_menu()
    await message.answer(
        &amp;quot;Привет! Я бот-помощник.\nЧто бы ты хотел сделать дальше?&amp;quot;,
        reply_markup = keyboard
    )&lt;/pre&gt;
  &lt;pre id=&quot;huNB&quot; data-lang=&quot;python&quot;&gt;# Обработка кнопки &amp;quot;Информация&amp;quot;
@start_router.callback_query(F.data == &amp;quot;info_menu&amp;quot;)
async def handle_info(callback: CallbackQuery):
    await callback.message.edit_text(text=&amp;quot;Меню информации&amp;quot;, reply_markup = await get_info_menu())
    await callback.answer()&lt;/pre&gt;
  &lt;pre id=&quot;mblC&quot; data-lang=&quot;python&quot;&gt;# Обработка кнопки &amp;quot;Развлечения&amp;quot;
@start_router.callback_query(F.data == &amp;quot;fun_menu&amp;quot;)
async def handle_fun(callback: CallbackQuery):
    await callback.message.delete()
    await callback.message.answer(&amp;quot;Меню развлечений&amp;quot;, reply_markup=await get_fun_menu())&lt;/pre&gt;
  &lt;pre id=&quot;iz5m&quot; data-lang=&quot;python&quot;&gt;# Обработка кнопки &amp;quot;Назад&amp;quot;
@start_router.callback_query(F.data == &amp;quot;main_menu&amp;quot;)
async def handle_back(callback: CallbackQuery):
    await callback.message.edit_text(text=&amp;quot;📋 Главное меню. Выбери действие:&amp;quot;, reply_markup = await get_main_menu())
    await callback.answer()&lt;/pre&gt;
  &lt;pre id=&quot;PLUc&quot; data-lang=&quot;python&quot;&gt;# Обработка кнопки &amp;quot;❓ Что я умею&amp;quot;
@start_router.callback_query(F.data == &amp;quot;help&amp;quot;)
async def handle_help(callback: CallbackQuery):
    text = (
        &amp;quot;🤖 Я — ваш помощник в путешествиях!\n\n&amp;quot;
        &amp;quot;Вот что я умею:\n&amp;quot;
        &amp;quot;🧭 Информация — полезные сервисы для путешественника\n&amp;quot;
        &amp;quot;☁️ Погода — узнаю погоду в любом городе\n&amp;quot;
        &amp;quot;🌐 Перевод — помогу перевести фразы\n&amp;quot;
        &amp;quot;💱 Валюта — покажу актуальный курс валют\n&amp;quot;
        &amp;quot;💰 Финансы — помогу вести расходы\n&amp;quot;
        &amp;quot;🎉 Развлечения — фото, котики, космос и другое!\n\n&amp;quot;
        &amp;quot;Используй меню для выбора нужной функции.&amp;quot;
    )
    await callback.message.answer(text)
    await callback.answer()&lt;/pre&gt;
  &lt;p id=&quot;IsR8&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;0Yui&quot;&gt;           ↪️translate.py&lt;/p&gt;
  &lt;pre id=&quot;DkJ5&quot; data-lang=&quot;python&quot;&gt;from aiogram import Router, F
from aiogram.types import Message, CallbackQuery
from services.translate import translate_text&lt;/pre&gt;
  &lt;pre id=&quot;IJi5&quot; data-lang=&quot;python&quot;&gt;translate_router = Router()&lt;/pre&gt;
  &lt;pre id=&quot;558Q&quot; data-lang=&quot;python&quot;&gt;# Кнопка &amp;quot;Перевод&amp;quot; — предлагаем выбрать направление слово
@translate_router.callback_query(F.data == &amp;quot;translate&amp;quot;)
async def handle_translate(callback: CallbackQuery):
    await callback.message.answer(&amp;quot;Напиши язык и текст. Пример: &amp;#x60;/translate en Привет&amp;#x60;&amp;quot;, parse_mode=&amp;quot;Markdown&amp;quot;)
    await callback.answer()&lt;/pre&gt;
  &lt;pre id=&quot;d4s3&quot; data-lang=&quot;python&quot;&gt;
# Обработка ввода текста для перевода
@translate_router.message(lambda m: m.text.lower().startswith(&amp;quot;/translate&amp;quot;))
async def handle_translate(message: Message):
    parts = message.text.split(maxsplit=2)
    if len(parts) &amp;lt; 3:
        await message.answer(&amp;quot;Формат: /translate &amp;lt;язык&amp;gt; &amp;lt;текст&amp;gt;&amp;quot;)
        return&lt;/pre&gt;
  &lt;pre id=&quot;ozCx&quot; data-lang=&quot;python&quot;&gt;    lang = parts[1].lower()
    text = parts[2]&lt;/pre&gt;
  &lt;pre id=&quot;KkgD&quot; data-lang=&quot;python&quot;&gt;    translated = translate_text(text, lang)
    if translated:
        await message.answer(f&amp;quot;Перевод: {translated}&amp;quot;)
    else:
        await message.answer(&amp;quot;Произошла ошибка при переводе.&amp;quot;)&lt;/pre&gt;
  &lt;p id=&quot;ucsv&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;3B4G&quot;&gt;        ↪️weather.py&lt;/p&gt;
  &lt;pre id=&quot;5evS&quot; data-lang=&quot;python&quot;&gt;from aiogram import Router, F
from aiogram.types import Message, CallbackQuery
from services.weather import get_weather
from keyboards.inline_keyboard import weather_refresh_keyboard, get_main_menu
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from db.users import save_user&lt;/pre&gt;
  &lt;pre id=&quot;cJTo&quot; data-lang=&quot;python&quot;&gt;# === Состояние формы пользователя ===
class UserForm(StatesGroup):
    city = State()&lt;/pre&gt;
  &lt;pre id=&quot;GdPP&quot; data-lang=&quot;python&quot;&gt;weather_router = Router()&lt;/pre&gt;
  &lt;pre id=&quot;e55t&quot; data-lang=&quot;python&quot;&gt;# Обработка кнопки &amp;quot;Погода&amp;quot; — спрашиваем город
@weather_router.callback_query(F.data == &amp;quot;weather&amp;quot;)
async def ask_city(callback: CallbackQuery, state: FSMContext):
    await callback.message.answer(&amp;quot;В каком городе ты хочешь узнать погоду?&amp;quot;)
    await state.set_state(UserForm.city)
    await callback.answer()&lt;/pre&gt;
  &lt;pre id=&quot;fPsJ&quot; data-lang=&quot;python&quot;&gt;# Обработка ввода города
@weather_router.message(UserForm.city)
async def process_city(message: Message, state: FSMContext):
    city = message.text
    user_id = message.from_user.id
    name = message.from_user.full_name&lt;/pre&gt;
  &lt;pre id=&quot;AO96&quot; data-lang=&quot;python&quot;&gt;    # Сохраняем данные в БД
    save_user(user_id, name, city)&lt;/pre&gt;
  &lt;pre id=&quot;iWHl&quot; data-lang=&quot;python&quot;&gt;    await message.answer(f&amp;quot;✅ Город &amp;lt;b&amp;gt;{city}&amp;lt;/b&amp;gt; сохранён!&amp;quot;, parse_mode=&amp;quot;HTML&amp;quot;)&lt;/pre&gt;
  &lt;pre id=&quot;ziGo&quot; data-lang=&quot;python&quot;&gt;    # Показываем погоду сразу после сохранения
    await send_weather(message, city)&lt;/pre&gt;
  &lt;pre id=&quot;mDoT&quot; data-lang=&quot;python&quot;&gt;    # Сбрасываем состояние
    await state.clear()&lt;/pre&gt;
  &lt;pre id=&quot;scc0&quot; data-lang=&quot;python&quot;&gt;# Обработка кнопки &amp;quot;refresh_weather&amp;quot;
@weather_router.callback_query(F.data.startswith(&amp;quot;refresh_weather:&amp;quot;))
async def refresh_weather(callback: CallbackQuery):
    city = callback.data.split(&amp;quot;:&amp;quot;)[1]
    await send_weather(callback.message, city)
    await callback.answer()&lt;/pre&gt;
  &lt;pre id=&quot;gsVU&quot; data-lang=&quot;python&quot;&gt;# Обработка кнопки &amp;quot;Назад&amp;quot;
@weather_router.callback_query(F.data == &amp;quot;main_menu&amp;quot;)
async def back_to_menu(callback: CallbackQuery):
    await callback.message.edit_text(
        text=&amp;quot;📋 Главное меню. Выбери действие:&amp;quot;,
        reply_markup=await get_main_menu()
    )
    await callback.answer()&lt;/pre&gt;
  &lt;pre id=&quot;aV2u&quot; data-lang=&quot;python&quot;&gt;# Функция для отправки погоды
async def send_weather(target, city: str):
    data = await get_weather(city)
    if not data:
        await target.answer(
            f&amp;quot;Не удалось получить погоду для {city}&amp;quot;,
            reply_markup=weather_refresh_keyboard(city)
        )
        return
    temp = data[&amp;#x27;temp&amp;#x27;]
    desc = data[&amp;#x27;description&amp;#x27;].capitalize()
    await target.answer(
        f&amp;quot;🌤 Погода в {city}: {temp}°C, {desc}&amp;quot;,
        reply_markup=weather_refresh_keyboard(city)
    )&lt;/pre&gt;
  &lt;p id=&quot;55Aj&quot;&gt;&lt;/p&gt;
  &lt;h2 id=&quot;e2QM&quot;&gt;✉services&lt;/h2&gt;
  &lt;p id=&quot;8r6U&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;BdTG&quot;&gt;            ↪️cat.py&lt;/p&gt;
  &lt;pre id=&quot;TwEu&quot; data-lang=&quot;python&quot;&gt;import asyncio
import random
import aiohttp
from config import CAT_API_KEY&lt;/pre&gt;
  &lt;pre id=&quot;GIud&quot; data-lang=&quot;python&quot;&gt;# получить список пород котов
async def get_cat_breeds():
    url = f&amp;quot;https://api.thecatapi.com/v1/breeds&amp;quot;
    headers = {&amp;quot;x-api-key&amp;quot;: CAT_API_KEY}
    async with aiohttp.ClientSession() as session:
        async with session.get(url, headers=headers) as response:
            return await response.json()&lt;/pre&gt;
  &lt;pre id=&quot;7a8d&quot; data-lang=&quot;python&quot;&gt;# получить фото кота по породе
async def get_cat_image(breed_id):
    url = f&amp;quot;https://api.thecatapi.com/v1/images/search?breed_ids={breed_id}&amp;quot;
    headers = {&amp;quot;x-api-key&amp;quot;: CAT_API_KEY}
    async with aiohttp.ClientSession() as session:
        async with session.get(url, headers=headers) as response:
            data = await response.json()
            return data[0][&amp;quot;url&amp;quot;]&lt;/pre&gt;
  &lt;pre id=&quot;1i19&quot; data-lang=&quot;python&quot;&gt;# # получить информацию о породе
# def get_breed_info(breed_name):
#     breeds = get_cat_breeds()
#     for breed in breeds:
#         if breed[&amp;quot;name&amp;quot;].lower() == breed_name.lower():
#             return breed
#     return None&lt;/pre&gt;
  &lt;pre id=&quot;mOw3&quot; data-lang=&quot;python&quot;&gt;# получить случайную породу
async def get_random_breed():
    breeds = await get_cat_breeds()
    return random.choice(breeds)&lt;/pre&gt;
  &lt;p id=&quot;6IPC&quot;&gt;  &lt;/p&gt;
  &lt;p id=&quot;5Ojk&quot;&gt;       ↪️currency.py&lt;/p&gt;
  &lt;pre id=&quot;5Bpt&quot; data-lang=&quot;python&quot;&gt;import aiohttp&lt;/pre&gt;
  &lt;pre id=&quot;T2Q1&quot; data-lang=&quot;python&quot;&gt;
# код для получения курса валют
async def get_currency():
    url = &amp;quot;https://v6.exchangerate-api.com/v6/09edf8b2bb246e1f801cbfba/latest/USD&amp;quot;
    try:
        async with aiohttp.ClientSession() as session:
            async with session.get(url) as response:
                if response.status != 200:
                    return &amp;quot;Произошла ошибка при получении курса валют&amp;quot;
                data = await response.json()
                usd_to_rub = data[&amp;quot;conversion_rates&amp;quot;][&amp;quot;RUB&amp;quot;]
                eur_to_usd = data[&amp;quot;conversion_rates&amp;quot;][&amp;quot;EUR&amp;quot;]
                eur_to_rub = eur_to_usd * usd_to_rub
                return f&amp;quot;💵 1 USD = {usd_to_rub:.2f} RUB\n💶 1 EUR = {eur_to_rub:.2f} RUB&amp;quot;
    except Exception:
        return &amp;quot;Произошла ошибка при получении курса валют&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;TNQE&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;j5GV&quot;&gt;         ↪️nasa.py&lt;/p&gt;
  &lt;pre id=&quot;a0cr&quot; data-lang=&quot;python&quot;&gt;import asyncio
import random
import aiohttp
from datetime import datetime, timedelta
from config import NASA_API_KEY&lt;/pre&gt;
  &lt;pre id=&quot;Rumw&quot; data-lang=&quot;python&quot;&gt;
# получить изображение дня
async def get_random_apod():
    try:
        end_date = datetime.now()
        start_date = end_date - timedelta(days=365)
        random_date = start_date + (end_date - start_date) * random.random()
        date_str = random_date.strftime(&amp;quot;%Y-%m-%d&amp;quot;)
        url = f&amp;quot;https://api.nasa.gov/planetary/apod?date={date_str}&amp;amp;api_key={NASA_API_KEY}&amp;quot;
        async with aiohttp.ClientSession() as session:
            async with session.get(url, timeout=10) as response:
                response.raise_for_status()
                data = await response.json()
                # Проверяем наличие необходимых полей
                required_fields = [&amp;quot;url&amp;quot;, &amp;quot;title&amp;quot;, &amp;quot;explanation&amp;quot;]
                for field in required_fields:
                    if field not in data:
                        raise KeyError(f&amp;quot;Отсутствует поле {field} в ответе API&amp;quot;)
                return data
    except Exception as e:
        print(f&amp;quot;Ошибка при работе с NASA API: {e}&amp;quot;)
        raise&lt;/pre&gt;
  &lt;p id=&quot;j166&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;WTRX&quot;&gt;        ↪️photo.py&lt;/p&gt;
  &lt;pre id=&quot;Ze2C&quot; data-lang=&quot;python&quot;&gt;import aiohttp
from config import UNSPLASH_ACCESS_KEY&lt;/pre&gt;
  &lt;pre id=&quot;Umsn&quot; data-lang=&quot;python&quot;&gt;async def get_random_photo():
    try:
        url = &amp;quot;https://api.unsplash.com/photos/random&amp;quot;
        headers = {&amp;quot;Authorization&amp;quot;: f&amp;quot;Client-ID {UNSPLASH_ACCESS_KEY}&amp;quot;}
        async with aiohttp.ClientSession() as session:
            async with session.get(url, headers=headers, timeout=5) as response:
                data = await response.json()
                return data.get(&amp;quot;urls&amp;quot;, {}).get(&amp;quot;regular&amp;quot;)
    except Exception as e:
        print(f&amp;quot;Ошибка фото: {e}&amp;quot;)
        return None&lt;/pre&gt;
  &lt;p id=&quot;gtPj&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;e8VK&quot;&gt;        ↪️translate.py&lt;/p&gt;
  &lt;pre id=&quot;taf6&quot; data-lang=&quot;python&quot;&gt;from deep_translator import GoogleTranslator&lt;/pre&gt;
  &lt;pre id=&quot;70fC&quot; data-lang=&quot;python&quot;&gt;def translate_text(text: str, target_lang: str = &amp;quot;en&amp;quot;) -&amp;gt; str:
    try:
        return GoogleTranslator(source=&amp;quot;auto&amp;quot;, target=target_lang).translate(text)
    except Exception as e:
        print(f&amp;quot;Ошибка перевода: {e}&amp;quot;)
        return &amp;quot;&amp;quot;&lt;/pre&gt;
  &lt;p id=&quot;EPD9&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;xWnc&quot;&gt;         ↪️weather.py&lt;/p&gt;
  &lt;pre id=&quot;PmV0&quot; data-lang=&quot;python&quot;&gt;import aiohttp
from config import API_KEY2&lt;/pre&gt;
  &lt;pre id=&quot;K0Ss&quot; data-lang=&quot;python&quot;&gt;
async def get_weather(city: str):
    try:
        url = (
            f&amp;quot;https://api.openweathermap.org/data/2.5/weather?q={city}&amp;amp;appid={API_KEY2}&amp;amp;units=metric&amp;amp;lang=ru&amp;quot;
        )
        async with aiohttp.ClientSession() as session:
            async with session.get(url, timeout=15) as response:
                data = await response.json()
                if data.get(&amp;quot;cod&amp;quot;) != 200:
                    return None
                return {
                    &amp;quot;temp&amp;quot;: data[&amp;quot;main&amp;quot;][&amp;quot;temp&amp;quot;],
                    &amp;quot;description&amp;quot;: data[&amp;quot;weather&amp;quot;][0][&amp;quot;description&amp;quot;],
                }
    except Exception as e:
        print(f&amp;quot;Ошибка погоды: {e}&amp;quot;)
        return None&lt;/pre&gt;
  &lt;p id=&quot;xKHn&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;ReLg&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;iaBf&quot;&gt;↪.gitignore&lt;/p&gt;
  &lt;pre id=&quot;ddt8&quot; data-lang=&quot;python&quot;&gt;# Python
__pycache__/
*.py[cod]
*.pyo
*.pyd
*.pyc&lt;/pre&gt;
  &lt;pre id=&quot;ePnK&quot; data-lang=&quot;python&quot;&gt;# Virtual environment
.venv/
venv/
ENV/&lt;/pre&gt;
  &lt;pre id=&quot;1VYW&quot; data-lang=&quot;python&quot;&gt;# IDE
.idea/
.vscode/&lt;/pre&gt;
  &lt;pre id=&quot;gAod&quot; data-lang=&quot;python&quot;&gt;# OS
.DS_Store
Thumbs.db&lt;/pre&gt;
  &lt;pre id=&quot;GlNg&quot; data-lang=&quot;python&quot;&gt;# Database
*.db&lt;/pre&gt;
  &lt;pre id=&quot;4XxT&quot; data-lang=&quot;python&quot;&gt;# Environment variables
.env
.env.*&lt;/pre&gt;
  &lt;pre id=&quot;2tYS&quot; data-lang=&quot;python&quot;&gt;# Logs
*.log&lt;/pre&gt;
  &lt;pre id=&quot;6HO9&quot; data-lang=&quot;python&quot;&gt;# Jupyter
.ipynb_checkpoints/&lt;/pre&gt;
  &lt;pre id=&quot;pdqA&quot; data-lang=&quot;python&quot;&gt;# Backup files
*~&lt;/pre&gt;
  &lt;pre id=&quot;M5vn&quot; data-lang=&quot;python&quot;&gt;# Node.js (если вдруг есть фронтенд)
node_modules/&lt;/pre&gt;
  &lt;pre id=&quot;uJ0u&quot; data-lang=&quot;python&quot;&gt;# Output
/dist/
/build/&lt;/pre&gt;
  &lt;pre id=&quot;k93f&quot; data-lang=&quot;python&quot;&gt;# Misc
*.bak&lt;/pre&gt;
  &lt;pre id=&quot;U3c1&quot; data-lang=&quot;python&quot;&gt;# Ignore test cache
.pytest_cache/&lt;/pre&gt;
  &lt;pre id=&quot;3Aug&quot; data-lang=&quot;python&quot;&gt;# Ignore coverage reports
.coverage
htmlcov/
.coverage.*&lt;/pre&gt;
  &lt;pre id=&quot;fyti&quot; data-lang=&quot;python&quot;&gt;# Ignore compiled C extensions
*.so&lt;/pre&gt;
  &lt;pre id=&quot;I7yB&quot; data-lang=&quot;python&quot;&gt;# Ignore migrations (если используются)
migrations/ &lt;/pre&gt;
  &lt;p id=&quot;C2z4&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;IETN&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;ONOs&quot;&gt;↪️config.py&lt;/p&gt;
  &lt;pre id=&quot;wihP&quot; data-lang=&quot;python&quot;&gt;import os&lt;/pre&gt;
  &lt;pre id=&quot;ZLhW&quot; data-lang=&quot;python&quot;&gt;from dotenv import load_dotenv&lt;/pre&gt;
  &lt;pre id=&quot;Xa93&quot; data-lang=&quot;python&quot;&gt;load_dotenv()&lt;/pre&gt;
  &lt;pre id=&quot;4ZPH&quot; data-lang=&quot;python&quot;&gt;BOT_TOKEN = os.getenv(&amp;quot;BOT_TOKEN&amp;quot;)
API_KEY1 = &amp;quot;8cf9ed27dae755ecf7ed9d97d85a2615&amp;quot;
API_KEY2 = &amp;quot;3c46887207e54b72bedd56e1245243cc&amp;quot;
UNSPLASH_ACCESS_KEY = os.getenv(&amp;quot;UNSPLASH_ACCESS_KEY&amp;quot;)
CAT_API_KEY = os.getenv(&amp;quot;CAT_API_KEY&amp;quot;)
DOG_API_KEY = os.getenv(&amp;quot;DOG_API_KEY&amp;quot;)
NASA_API_KEY = os.getenv(&amp;quot;NASA_API_KEY&amp;quot;)&lt;/pre&gt;
  &lt;p id=&quot;tLrH&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;YJ1r&quot;&gt;&lt;/p&gt;
  &lt;p id=&quot;BGOb&quot;&gt;↪️main.py&lt;/p&gt;
  &lt;pre id=&quot;LL07&quot; data-lang=&quot;python&quot;&gt;import asyncio
import logging&lt;/pre&gt;
  &lt;pre id=&quot;cqEm&quot; data-lang=&quot;python&quot;&gt;from aiogram import Bot, Dispatcher
from aiogram.fsm.storage.memory import MemoryStorage&lt;/pre&gt;
  &lt;pre id=&quot;GEOJ&quot; data-lang=&quot;python&quot;&gt;# Импорт конфигурации токена и роутеров
from config import BOT_TOKEN
from routers.start import start_router
from routers.cat import cat_router
from routers.weather import weather_router
from routers.photo import photo_router
from routers.translate import translate_router
from routers.nasa import nasa_router
from routers.finance import finance_router
from routers.currency import currency_router
from routers.registration import registration_router
from db.users import init_db  # Функция инициализации БД&lt;/pre&gt;
  &lt;pre id=&quot;tRiQ&quot; data-lang=&quot;python&quot;&gt;# === НАСТРОЙКА ЛОГГЕРА ===
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)&lt;/pre&gt;
  &lt;pre id=&quot;8l2r&quot; data-lang=&quot;python&quot;&gt;# Инициализация бота и диспетчера
bot = Bot(token=BOT_TOKEN)
storage = MemoryStorage()  # Используем in-memory хранилище состояний (можно заменить на Redis)
dp = Dispatcher(storage=storage)&lt;/pre&gt;
  &lt;pre id=&quot;yw1g&quot; data-lang=&quot;python&quot;&gt;async def main():
    # Инициализация базы данных
    init_db()&lt;/pre&gt;
  &lt;pre id=&quot;8XCi&quot; data-lang=&quot;python&quot;&gt;    # Подключение всех роутеров
    dp.include_router(start_router)
    dp.include_router(weather_router)
    dp.include_router(photo_router)
    dp.include_router(translate_router)
    dp.include_router(cat_router)
    dp.include_router(nasa_router)
    dp.include_router(finance_router)
    dp.include_router(currency_router)
    dp.include_router(registration_router)&lt;/pre&gt;
  &lt;pre id=&quot;1zxA&quot; data-lang=&quot;python&quot;&gt;    logger.info(&amp;quot;Бот запущен&amp;quot;)
    # Запуск polling (постоянное получение апдейтов)
    await dp.start_polling(bot)&lt;/pre&gt;
  &lt;pre id=&quot;6LVs&quot; data-lang=&quot;python&quot;&gt;# Точка входа
if __name__ == &amp;quot;__main__&amp;quot;:
    asyncio.run(main())&lt;/pre&gt;

</content></entry></feed>