User: Анализ файла bot.py:

Содержимое:
import logging
import asyncio
import os
import sqlite3
from datetime import datetime, timedelta
from typing import Dict, List

from aiogram import Bot, Dispatcher, types
from aiogram.contrib.fsm_storage.memory import MemoryStorage
from aiogram.dispatcher import FSMContext
from aiogram.dispatcher.filters.state import State, StatesGroup

# Импортируем настройки из config.py
import config

# Импортируем клиенты API
from sparrow_api import SparrowAPI
from gpt4o_api import GPT4OAPI
from forgetapi import ForgetAPI
from image_api import ImageAPI

# Импортируем AudioProcessor из audio_processing.py
from audio_processing import AudioProcessor

# Импортируем функции из doc.py
from doc import process_file_content, send_long_message, build_context, SUPPORTED_EXTENSIONS

# Дополнительные импорты
import xlsxwriter

# Настройка логирования
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


# Классы состояний для FSM
class Form(StatesGroup):
    ImageGenerationPrompt = State()
    ImageGenerationSize = State()
    # Состояния для Админ-Панели
    AdminPanel = State()
    SelectTariffForEdit = State()
    EditTariffNameOrDescription = State()
    EditTariffTokens = State()
    BroadcastMessageState = State()
    BroadcastSelectTariff = State()
    EditTariffName = State()
    EditTariffDescription = State()
    EditTariffTokenAmount = State()
    # Новые состояния для управления чатами
    ManageChats = State()
    AddChat = State()
    DeleteChat = State()
    # Состояния для генерации отчёта
    GenerateReport = State()


class EnhancedBot:
    MAX_MESSAGE_LENGTH = 4000  # Максимальная длина сообщения

    # Доступные модели и их API
    ALL_AVAILABLE_MODELS = [
        ("GPT-4o", "gpt-4o-2024-08-06", "gpt4o"),
        ("o1-preview", "o1-preview", "forget"),
        ("o1-mini", "o1-mini", "forget"),
        ("GPT 4o Mini", "gpt-4o-mini", "forget"),
        ("GPT 3.5 turbo", "gpt-3.5-turbo", "forget"),
        # Добавьте другие модели по необходимости
    ]

    FREE_MODELS = ALL_AVAILABLE_MODELS  # Теперь все модели доступны бесплатно

    def __init__(self):
        self.bot = Bot(token=config.BOT_TOKEN)
        self.storage = MemoryStorage()
        self.dp = Dispatcher(self.bot, storage=self.storage)
        self.sparrow = SparrowAPI()
        self.gpt4o_api = GPT4OAPI()
        self.forget_api = ForgetAPI()
        self.image_api = ImageAPI()
        self.audio_processor = AudioProcessor(self.bot)
        self.conversations: Dict[str, List[str]] = {}
        self.waiting_messages: Dict[str, types.Message] = {}
        self.user_tasks: Dict[str, asyncio.Task] = {}
        self.db_connection = sqlite3.connect('user_data.db')
        self.db_cursor = self.db_connection.cursor()
        self._setup_database()
        self._setup_handlers()

        # Инициализация API клиентов
        self.api_clients = {
            "sparrow": self.sparrow,
            "gpt4o": self.gpt4o_api,
            "forget": self.forget_api,
            "image": self.image_api,
        }

        # Создание маппинга модель -> API
        self.MODEL_API_MAPPING = {
            model_id: api_name for (_, model_id, api_name) in self.ALL_AVAILABLE_MODELS
        }

    def _setup_database(self):
        # Создание таблицы для хранения данных пользователей
        self.db_cursor.execute('''
            CREATE TABLE IF NOT EXISTS users (
                user_id TEXT PRIMARY KEY,
                is_subscribed INTEGER DEFAULT 0,
                model TEXT DEFAULT 'gpt-4-turbo',
                tariff_plan TEXT DEFAULT 'free',
                tokens_available INTEGER DEFAULT 3000,
                last_token_refresh DATE,
                group_association TEXT
            )
        ''')
        # Создание таблицы для тарифных планов
        self.db_cursor.execute('''
            CREATE TABLE IF NOT EXISTS tariff_plans (
                tariff_id TEXT PRIMARY KEY,
                name TEXT,
                description TEXT,
                daily_tokens INTEGER
            )
        ''')
        # Создание таблицы для премиум-чатов
        self.db_cursor.execute('''
            CREATE TABLE IF NOT EXISTS premium_chats (
                chat_id INTEGER PRIMARY KEY
            )
        ''')
        # Инициализируем тарифные планы, если таблица пуста
        self.db_cursor.execute('SELECT COUNT(*) FROM tariff_plans')
        if self.db_cursor.fetchone()[0] == 0:
            self.db_cursor.executemany('''
                INSERT INTO tariff_plans (tariff_id, name, description, daily_tokens) VALUES (?, ?, ?, ?)
            ''', [
                ('free', 'FREE', 'Стандартный тариф с ограниченным набором функций.', 3000),
                ('premium', 'Premium', 'Расширенный тариф с дополнительными возможностями.', 100000),
                ('admin', 'ADMIN', 'Неограниченный доступ для администраторов.', -1)
            ])
            self.db_connection.commit()
        # Инициализируем премиум-чаты, если таблица пуста
        self.db_cursor.execute('SELECT COUNT(*) FROM premium_chats')
        if self.db_cursor.fetchone()[0] == 0:
            self.db_cursor.executemany('''
                INSERT INTO premium_chats (chat_id) VALUES (?)
            ''', [
                (-1002413809826,),
                (-1002482241267,)
            ])
            self.db_connection.commit()
        self.db_connection.commit()

    def get_tariff_info(self, tariff_id: str):
        self.db_cursor.execute('SELECT name, description, daily_tokens FROM tariff_plans WHERE tariff_id = ?', (tariff_id,))
        result = self.db_cursor.fetchone()
        if result:
            return {'name': result[0], 'description': result[1], 'daily_tokens': result[2]}
        else:
            return None

    def set_tariff_info(self, tariff_id: str, name: str = None, description: str = None, daily_tokens: int = None):
        if name:
            self.db_cursor.execute('UPDATE tariff_plans SET name = ? WHERE tariff_id = ?', (name, tariff_id))
        if description:
            self.db_cursor.execute('UPDATE tariff_plans SET description = ? WHERE tariff_id = ?', (description, tariff_id))
        if daily_tokens is not None:
            self.db_cursor.execute('UPDATE tariff_plans SET daily_tokens = ? WHERE tariff_id = ?', (daily_tokens, tariff_id))
        self.db_connection.commit()

    def is_user_subscribed(self, user_id: str) -> bool:
        # Проверка подписки пользователя
        self.db_cursor.execute('SELECT is_subscribed FROM users WHERE user_id = ?', (user_id,))
        result = self.db_cursor.fetchone()
        if result:
            return bool(result[0])
        else:
            return False

    def set_user_subscription(self, user_id: str, is_subscribed: bool):
        # Установка статуса подписки пользователя
        self.db_cursor.execute('''
            INSERT INTO users (user_id, is_subscribed) VALUES (?, ?)
            ON CONFLICT(user_id) DO UPDATE SET is_subscribed=?
        ''', (user_id, int(is_subscribed), int(is_subscribed)))
        self.db_connection.commit()

    def get_user_model(self, user_id: str) -> str:
        # Получение выбранной модели пользователя
        self.db_cursor.execute('SELECT model FROM users WHERE user_id = ?', (user_id,))
        result = self.db_cursor.fetchone()
        if result:
            return result[0]
        else:
            # Устанавливаем модель по умолчанию в соответствии с тарифом
            return 'gpt-4-turbo'  # Модель по умолчанию

    def set_user_model(self, user_id: str, model: str):
        # Установка выбранной модели пользователя
        self.db_cursor.execute('''
            INSERT INTO users (user_id, model) VALUES (?, ?)
            ON CONFLICT(user_id) DO UPDATE SET model=?
        ''', (user_id, model, model))
        self.db_connection.commit()

    def get_user_tariff_plan(self, user_id: str) -> str:
        # Получение тарифного плана пользователя
        self.db_cursor.execute('SELECT tariff_plan FROM users WHERE user_id = ?', (user_id,))
        result = self.db_cursor.fetchone()
        if result:
            return result[0]
        else:
            return 'free'  # Тариф по умолчанию

    def set_user_tariff_plan(self, user_id: str, tariff_plan: str):
        # Установка тарифного плана пользователя
        # Также выполняем перерасчет токенов при смене тарифного плана
        previous_tariff_plan = self.get_user_tariff_plan(user_id)
        self.db_cursor.execute('''
            INSERT INTO users (user_id, tariff_plan) VALUES (?, ?)
            ON CONFLICT(user_id) DO UPDATE SET tariff_plan=?
        ''', (user_id, tariff_plan, tariff_plan))
        self.db_connection.commit()
        if previous_tariff_plan != tariff_plan:
            # Перерасчет токенов
            self.set_last_token_refresh(user_id, None)
            asyncio.create_task(self.refresh_user_tokens(user_id))

    def get_user_tokens(self, user_id: str) -> int:
        # Получение количества токенов пользователя
        self.db_cursor.execute('SELECT tokens_available FROM users WHERE user_id = ?', (user_id,))
        result = self.db_cursor.fetchone()
        if result:
            return result[0]
        else:
            # Устанавливаем токены по умолчанию в соответствии с тарифом
            tariff_plan = self.get_user_tariff_plan(user_id)
            tariff_info = self.get_tariff_info(tariff_plan)
            return tariff_info['daily_tokens'] if tariff_info else 3000

    def set_user_tokens(self, user_id: str, tokens: int):
        # Установка количества токенов пользователя
        self.db_cursor.execute('''
            INSERT INTO users (user_id, tokens_available) VALUES (?, ?)
            ON CONFLICT(user_id) DO UPDATE SET tokens_available=?
        ''', (user_id, tokens, tokens))
        self.db_connection.commit()

    def get_last_token_refresh(self, user_id: str) -> datetime:
        # Получение даты последнего обновления токенов
        self.db_cursor.execute('SELECT last_token_refresh FROM users WHERE user_id = ?', (user_id,))
        result = self.db_cursor.fetchone()
        if result and result[0]:
            return datetime.strptime(result[0], '%Y-%m-%d %H:%M:%S.%f')
        else:
            return None

    def set_last_token_refresh(self, user_id: str, date: datetime):
        # Установка даты последнего обновления токенов
        date_str = date.strftime('%Y-%m-%d %H:%M:%S.%f') if date else None
        self.db_cursor.execute('''
            INSERT INTO users (user_id, last_token_refresh) VALUES (?, ?)
            ON CONFLICT(user_id) DO UPDATE SET last_token_refresh=?
        ''', (user_id, date_str, date_str))
        self.db_connection.commit()

    async def refresh_user_tokens(self, user_id: str):
        # Обновление токенов пользователя ежедневно
        last_refresh = self.get_last_token_refresh(user_id)
        now = datetime.now()
        if not last_refresh or (now - last_refresh).days >= 1:
            tariff_plan = self.get_user_tariff_plan(user_id)
            tariff_info = self.get_tariff_info(tariff_plan)
            tokens = tariff_info['daily_tokens'] if tariff_info else 3000
            self.set_user_tokens(user_id, tokens)
            self.set_last_token_refresh(user_id, now)

    async def check_and_update_tariff_plan(self, user_id: str):
        # Проверка и обновление тарифного плана пользователя
        telegram_user_id = int(user_id)
        is_admin = telegram_user_id in config.ADMIN_IDS

        if is_admin:
            self.set_user_tariff_plan(user_id, 'admin')
        else:
            # Получаем список премиум-чатов из базы данных
            self.db_cursor.execute('SELECT chat_id FROM premium_chats')
            premium_chat_ids = [row[0] for row in self.db_cursor.fetchall()]

            # Проверяем, состоит ли пользователь в одной из премиум-групп
            is_premium_member = False
            for group_id in premium_chat_ids:
                try:
                    chat_member = await self.bot.get_chat_member(chat_id=group_id, user_id=telegram_user_id)
                    if chat_member.status in ['member', 'administrator', 'creator']:
                        is_premium_member = True
                        # Обновляем информацию о принадлежности пользователя к группе
                        self.set_user_group_association(user_id, group_id)
                        break
                except Exception as e:
                    logger.error(f"Ошибка при проверке членства в группе {group_id} для пользователя {user_id}: {e}")

            if is_premium_member:
                self.set_user_tariff_plan(user_id, 'premium')
            else:
                self.set_user_tariff_plan(user_id, 'free')
                # Устанавливаем модель по умолчанию для бесплатного тарифа
                self.set_user_model(user_id, 'gpt-4-turbo')

    def set_user_group_association(self, user_id: str, group_id: int):
        # Устанавливаем информацию о принадлежности пользователя к группе
        self.db_cursor.execute('''
            INSERT INTO users (user_id, group_association) VALUES (?, ?)
            ON CONFLICT(user_id) DO UPDATE SET group_association=?
        ''', (user_id, str(group_id), str(group_id)))
        self.db_connection.commit()

    def get_user_group_association(self, user_id: str) -> str:
        # Получаем информацию о принадлежности пользователя к группе
        self.db_cursor.execute('SELECT group_association FROM users WHERE user_id = ?', (user_id,))
        result = self.db_cursor.fetchone()
        if result and result[0]:
            return result[0]
        else:
            return "None"

    def _setup_handlers(self):
        # Настройка обработчиков сообщений
        self.dp.register_message_handler(self.cmd_start, commands=['start', 'help'])
        self.dp.register_message_handler(
            self.process_model_selection,
            lambda msg: msg.text in [model[0] for model in self.ALL_AVAILABLE_MODELS]
        )
        self.dp.register_message_handler(
            self.handle_clear_context,
            lambda msg: msg.text == "🗑 Очистить контекст"
        )
        self.dp.register_message_handler(
            self.show_model_selection,
            lambda msg: msg.text == "🤖 Выбрать модель"
        )
        self.dp.register_message_handler(
            self.show_profile,
            lambda msg: msg.text == "👤 Профиль"
        )
        self.dp.register_message_handler(
            self.start_image_generation,
            lambda msg: msg.text == "🖼️ Генерация изображения"
        )
        self.dp.register_message_handler(
            self.check_subscription_button_handler,
            lambda msg: msg.text == "🔄 Проверить подписку"
        )
        self.dp.register_message_handler(
            self.cancel_handler,
            state='*',
            text='🔙 Назад'
        )
        # Обработчики для Админ-Панели
        self.dp.register_message_handler(
            self.show_admin_panel,
            lambda msg: msg.text == "🛠️ Админ-Панель"
        )
        self.dp.register_message_handler(
            self.handle_admin_selection,
            state=Form.AdminPanel
        )
        self.dp.register_message_handler(
            self.handle_tariff_selection_for_edit,
            state=Form.SelectTariffForEdit
        )
        self.dp.register_message_handler(
            self.handle_edit_tariff_name_or_description,
            state=Form.EditTariffNameOrDescription
        )
        self.dp.register_message_handler(
            self.handle_edit_tariff_name,
            state=Form.EditTariffName
        )
        self.dp.register_message_handler(
            self.handle_edit_tariff_description,
            state=Form.EditTariffDescription
        )
        self.dp.register_message_handler(
            self.handle_edit_tariff_tokens,
            state=Form.EditTariffTokens
        )
        self.dp.register_message_handler(
            self.handle_set_tariff_tokens,
            state=Form.EditTariffTokenAmount
        )
        self.dp.register_message_handler(
            self.handle_broadcast_message,
            state=Form.BroadcastSelectTariff
        )
        self.dp.register_message_handler(
            self.handle_broadcast_text,
            state=Form.BroadcastMessageState
        )
        # Новые обработчики для управления чатами
        self.dp.register_message_handler(
            self.handle_manage_chats,
            state=Form.ManageChats
        )
        self.dp.register_message_handler(
            self.handle_add_chat,
            state=Form.AddChat
        )
        self.dp.register_message_handler(
            self.handle_delete_chat,
            state=Form.DeleteChat
        )
        # Обработчики для генерации отчёта
        self.dp.register_message_handler(
            self.handle_generate_report,
            state=Form.GenerateReport
        )
        # Регистрация обработчиков состояний FSM
        self.dp.register_message_handler(
            self.process_image_prompt,
            state=Form.ImageGenerationPrompt
        )
        self.dp.register_message_handler(
            self.process_image_size,
            state=Form.ImageGenerationSize
        )
        self.dp.register_message_handler(
            self.handle_document,
            content_types=[types.ContentType.DOCUMENT]
        )
        # Обработка аудио и голосовых сообщений
        self.dp.register_message_handler(
            self.handle_audio,
            content_types=[types.ContentType.AUDIO, types.ContentType.VOICE]
        )
        # Обработка фотографий
        self.dp.register_message_handler(
            self.handle_photo,
            content_types=[types.ContentType.PHOTO]
        )
        self.dp.register_message_handler(
            self.handle_text,
            content_types=[types.ContentType.TEXT]
        )

    async def cancel_handler(self, message: types.Message, state: FSMContext):
        # Проверка типа чата
        if message.chat.type != 'private':
            return

        # Обработка нажатия кнопки "🔙 Назад" во время состояния
        user_id = str(message.from_user.id)
        await state.finish()
        tariff_plan = self.get_user_tariff_plan(user_id)
        await message.reply("Отменено. Вы вернулись в главное меню.", reply_markup=self.get_keyboard(tariff_plan, user_id))

    def get_keyboard(self, tariff_plan: str, user_id: str):
        # Создание основной клавиатуры
        keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True)
        keyboard.row("🗑 Очистить контекст", "🤖 Выбрать модель")
        keyboard.row("🖼️ Генерация изображения", "👤 Профиль")
        if int(user_id) in config.ADMIN_IDS:
            keyboard.add("🛠️ Админ-Панель")
        return keyboard

    def get_model_keyboard(self, tariff_plan: str):
        # Создание клавиатуры выбора модели
        keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True)
        models = self.ALL_AVAILABLE_MODELS
        buttons = [types.KeyboardButton(model_name) for model_name, _, _ in models]
        keyboard.add(*buttons)
        keyboard.add("🔙 Назад")
        return keyboard

    def get_subscription_keyboard(self):
        # Клавиатура с кнопкой проверки подписки
        keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True)
        keyboard.add("🔄 Проверить подписку")
        return keyboard

    async def prompt_subscription(self, message: types.Message):
        # Проверка типа чата
        if message.chat.type != 'private':
            return

        # Просьба пользователю подписаться на канал
        keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True)
        keyboard.add("🔄 Проверить подписку")
        subscribe_button = types.InlineKeyboardButton(
            text="📢 Подписаться на канал",
            url=f"https://t.me/{config.CHANNEL_USERNAME}"
        )
        inline_keyboard = types.InlineKeyboardMarkup().add(subscribe_button)
        await message.reply(
            "Чтобы пользоваться ботом, пожалуйста, подпишитесь на наш канал.",
            reply_markup=inline_keyboard
        )
        await message.answer(
            "После подписки нажмите кнопку ниже, чтобы проверить подписку.",
            reply_markup=keyboard
        )

    async def check_subscription_button_handler(self, message: types.Message):
        # Обработка нажатия кнопки "Проверить подписку"
        await self.cmd_start(message)

    async def cmd_start(self, message: types.Message):
        # Проверка типа чата
        if message.chat.type != 'private':
            return

        # Обработка команды /start
        user_id = str(message.from_user.id)
        telegram_user_id = message.from_user.id

        # Проверка подписки
        is_subscribed = await self.check_user_subscription(telegram_user_id)
        self.set_user_subscription(user_id, is_subscribed)

        if not is_subscribed:
            await self.prompt_subscription(message)
        else:
            # Проверяем и обновляем тарифный план пользователя
            await self.check_and_update_tariff_plan(user_id)
            await self.refresh_user_tokens(user_id)
            tariff_plan = self.get_user_tariff_plan(user_id)
            keyboard = self.get_keyboard(tariff_plan, user_id)

            welcome_text = (
                "👋 <b>Привет!</b>\n\n"
                "Я бот для общения с AI моделями.\n\n"
                "Выберите модель для общения и отправляйте сообщения.\n\n"
                "Чтобы сменить модель, нажмите <b>🤖 Выбрать модель</b>.\n"
                "Чтобы очистить контекст беседы, нажмите <b>🗑 Очистить контекст</b>.\n"
                "Для доступа к профилю нажмите <b>👤 Профиль</b>.\n"
                "Для генерации изображения нажмите <b>🖼️ Генерация изображения</b>."
            )

            await message.reply(welcome_text, reply_markup=keyboard, parse_mode=types.ParseMode.HTML)

    async def show_model_selection(self, message: types.Message):
        # Проверка типа чата
        if message.chat.type != 'private':
            return

        # Показать выбор моделей
        user_id = str(message.from_user.id)
        tariff_plan = self.get_user_tariff_plan(user_id)
        keyboard = self.get_model_keyboard(tariff_plan)
        await message.reply(
            "Выберите модель для общения:",
            reply_markup=keyboard
        )

    async def process_model_selection(self, message: types.Message):
        # Проверка типа чата
        if message.chat.type != 'private':
            return

        # Обработка выбора модели
        user_id = str(message.from_user.id)
        telegram_user_id = message.from_user.id

        # Проверка подписки
        is_subscribed = await self.check_user_subscription(telegram_user_id)
        self.set_user_subscription(user_id, is_subscribed)

        if not is_subscribed:
            await self.prompt_subscription(message)
            return

        # Проверяем и обновляем тарифный план пользователя
        await self.check_and_update_tariff_plan(user_id)
        await self.refresh_user_tokens(user_id)
        tariff_plan = self.get_user_tariff_plan(user_id)

        if message.text == "🔙 Назад":
            await message.reply("Вы вернулись в главное меню.", reply_markup=self.get_keyboard(tariff_plan, user_id))
            return

        available_models = [model[0] for model in self.ALL_AVAILABLE_MODELS]

        model_name = message.text
        if model_name not in available_models:
            await message.reply("Неверный выбор модели. Пожалуйста, выберите модель из списка.")
            return

        model_id = None
        for name, id, _ in self.ALL_AVAILABLE_MODELS:
            if name == model_name:
                model_id = id
                break

        if not model_id:
            await message.reply("Неверный выбор модели. Пожалуйста, выберите модель из списка.")
            return

        # Сохранение выбора модели пользователя
        self.set_user_model(user_id, model_id)

        # Очистка контекста беседы при смене модели
        self.conversations.pop(user_id, None)

        await message.reply(f"Выбрана модель: {model_name}", reply_markup=self.get_keyboard(tariff_plan, user_id))

    async def handle_clear_context(self, message: types.Message):
        # Проверка типа чата
        if message.chat.type != 'private':
            return

        # Очистка контекста беседы
        user_id = str(message.from_user.id)
        telegram_user_id = message.from_user.id

        # Проверка подписки
        is_subscribed = await self.check_user_subscription(telegram_user_id)
        self.set_user_subscription(user_id, is_subscribed)

        if not is_subscribed:
            await self.prompt_subscription(message)
            return

        self.conversations.pop(user_id, None)
        tariff_plan = self.get_user_tariff_plan(user_id)
        await message.reply(
            "Контекст очищен! Можете начать новый разговор.",
            reply_markup=self.get_keyboard(tariff_plan, user_id)
        )

    async def show_profile(self, message: types.Message):
        # Проверка типа чата
        if message.chat.type != 'private':
            return

        # Отображение профиля пользователя
        user_id = str(message.from_user.id)
        telegram_user_id = message.from_user.id

        # Проверка подписки и обновление тарифного плана
        is_subscribed = await self.check_user_subscription(telegram_user_id)
        self.set_user_subscription(user_id, is_subscribed)
        await self.check_and_update_tariff_plan(user_id)
        await self.refresh_user_tokens(user_id)

        tariff_plan = self.get_user_tariff_plan(user_id)
        tokens_available = self.get_user_tokens(user_id)
        last_refresh = self.get_last_token_refresh(user_id)
        now = datetime.now()
        if last_refresh:
            next_refresh_date = last_refresh + timedelta(days=1)
            hours_left = int((next_refresh_date - now).total_seconds() // 3600)
            if hours_left < 0:
                hours_left = 0
        else:
            hours_left = 24

        if tokens_available == -1:
            tokens_text = "∞"
        else:
            tokens_text = f"{tokens_available:,}".replace(",", " ")

        tariff_info = self.get_tariff_info(tariff_plan)
        if tariff_info:
            tariff_name = tariff_info['name']
            tariff_description = tariff_info['description']
        else:
            tariff_name = tariff_plan
            tariff_description = "Нет описания для этого тарифа."

        if tokens_available == -1:
            refresh_text = ""
        else:
            if hours_left == 0:
                refresh_text = "Обновление токенов: <b>скоро</b>"
            else:
                refresh_text = f"Обновление токенов через: <b>{hours_left} часов</b>"

        profile_text = (
            f"👤 <b>Профиль пользователя</b>\n\n"
            f"Ваш тариф: <b>{tariff_name}</b>\n\n"
            f"<b>Описание тарифа:</b>\n{tariff_description}\n\n"
            f"Токенов осталось: <b>{tokens_text}</b>\n"
            f"{refresh_text}"
        )

        await message.reply(profile_text, reply_markup=self.get_keyboard(tariff_plan, user_id), parse_mode=types.ParseMode.HTML)

    async def start_image_generation(self, message: types.Message):
        # Проверка типа чата
        if message.chat.type != 'private':
            return

        # Начало процесса генерации изображения
        user_id = str(message.from_user.id)
        telegram_user_id = message.from_user.id

        # Проверка подписки
        is_subscribed = await self.check_user_subscription(telegram_user_id)
        self.set_user_subscription(user_id, is_subscribed)

        if not is_subscribed:
            await self.prompt_subscription(message)
            return

        await self.check_and_update_tariff_plan(user_id)
        await self.refresh_user_tokens(user_id)
        tariff_plan = self.get_user_tariff_plan(user_id)

        # Проверяем, есть ли у пользователя достаточно токенов (100 токенов за генерацию)
        tokens_available = self.get_user_tokens(user_id)
        if tokens_available != -1 and tokens_available < 100:
            await message.reply("У вас недостаточно токенов для генерации изображения.")
            return

        await Form.ImageGenerationPrompt.set()
        keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True)
        keyboard.add("🔙 Назад")
        await message.reply(
            "Пожалуйста, введите описание изображения, которое вы хотите сгенерировать:",
            reply_markup=keyboard
        )

    async def process_image_prompt(self, message: types.Message, state: FSMContext):
        # Проверка типа чата
        if message.chat.type != 'private':
            return

        # Обработка описания изображения
        user_id = str(message.from_user.id)
        await self.check_and_update_tariff_plan(user_id)
        await self.refresh_user_tokens(user_id)
        tariff_plan = self.get_user_tariff_plan(user_id)

        if message.text == "🔙 Назад":
            await state.finish()
            await message.reply("Вы вернулись в главное меню.", reply_markup=self.get_keyboard(tariff_plan, user_id))
            return

        prompt = message.text

        # Переводим описание на английский с помощью GPT-4
        translated_prompt = await self.translate_prompt(prompt, user_id)

        if not translated_prompt:
            await message.reply("Не удалось перевести описание на английский.")
            await state.finish()
            return

        # Сохраняем переведенное описание в состоянии
        await state.update_data(prompt=translated_prompt)

        # Предлагаем выбрать соотношение сторон
        keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True)
        keyboard.add("1:1", "3:4", "16:9", "9:16")
        keyboard.add("🔙 Назад")
        await Form.next()
        await message.reply("Пожалуйста, выберите соотношение сторон изображения:", reply_markup=keyboard)

    async def process_image_size(self, message: types.Message, state: FSMContext):
        # Проверка типа чата
        if message.chat.type != 'private':
            return

        # Обработка выбора соотношения сторон
        user_id = str(message.from_user.id)
        await self.check_and_update_tariff_plan(user_id)
        await self.refresh_user_tokens(user_id)
        tariff_plan = self.get_user_tariff_plan(user_id)

        if message.text == "🔙 Назад":
            await state.finish()
            await message.reply("Вы вернулись в главное меню.", reply_markup=self.get_keyboard(tariff_plan, user_id))
            return

        size = message.text
        if size not in ["1:1", "3:4", "16:9", "9:16"]:
            await message.reply("Пожалуйста, выберите соотношение сторон из предложенных вариантов.")
            return

        await state.update_data(size=size)

        # Проверяем, есть ли у пользователя достаточно токенов (100 токенов за генерацию)
        tokens_available = self.get_user_tokens(user_id)
        if tokens_available != -1 and tokens_available < 100:
            await message.reply("У вас недостаточно токенов для генерации изображения.")
            await state.finish()
            return

        # Списываем 100 токенов
        if tokens_available != -1:
            self.set_user_tokens(user_id, tokens_available - 100)

        # Получаем данные из состояния
        data = await state.get_data()
        prompt = data['prompt']
        size = data['size']

        # Показываем, что бот набирает ответ
        await self.bot.send_chat_action(message.chat.id, types.ChatActions.UPLOAD_PHOTO)

        waiting_message = await message.reply("Генерирую изображение...")
        self.waiting_messages[user_id] = waiting_message

        try:
            # Используем ImageAPI для генерации изображения
            images = await asyncio.to_thread(
                self.image_api.generate_images,
                prompt=prompt,
                n=1,
                size=size
            )
            if images:
                for image_url in images:
                    await message.reply_photo(photo=image_url)
            else:
                await message.reply("Не удалось сгенерировать изображение.")
        except Exception as e:
            logger.error(f"Ошибка при генерации изображения: {str(e)}", exc_info=True)
            await message.reply(f"Произошла ошибка при генерации изображения: {str(e)}")
        finally:
            if user_id in self.waiting_messages:
                await self.waiting_messages[user_id].delete()
                del self.waiting_messages[user_id]
            # Завершаем состояние и возвращаем основную клавиатуру
            await state.finish()
            await message.reply("Вы можете продолжить общение.", reply_markup=self.get_keyboard(tariff_plan, user_id))

    async def translate_prompt(self, prompt: str, user_id: str) -> str:
        # Переводим текст на английский с помощью GPT-4
        try:
            model = 'gpt-4o-mini'
            api_name = self.MODEL_API_MAPPING.get(model)
            api_client = self.api_clients.get(api_name)
            if not api_client:
                return None

            response = await asyncio.to_thread(
                api_client.send_message,
                message=f"Please translate the following text to English:\n\n{prompt}",
                conversation_id=None,
                models=[model]
            )

            if not response:
                return None

            data = response.get('data')
            if not data:
                return None

            messages = data.get('messages')
            if not messages:
                return None

            assistant_message = next(
                (msg for msg in reversed(messages) if msg.get('role') == 'assistant'),
                None
            )

            if not assistant_message:
                return None

            content = assistant_message.get('content')
            return content.strip() if content else None
        except Exception as e:
            logger.error(f"Ошибка при переводе описания: {str(e)}", exc_info=True)
            return None

    async def handle_document(self, message: types.Message):
        # Проверка типа чата
        if message.chat.type != 'private':
            return

        # Обработка документов
        if not message.document:
            return

        user_id = str(message.from_user.id)
        telegram_user_id = message.from_user.id

        # Проверка подписки
        is_subscribed = await self.check_user_subscription(telegram_user_id)
        self.set_user_subscription(user_id, is_subscribed)

        if not is_subscribed:
            await self.prompt_subscription(message)
            return

        # Проверка занятости пользователя
        if user_id in self.user_tasks:
            await message.reply("Пожалуйста, дождитесь ответа на предыдущее сообщение.")
            return

        # Проверяем и обновляем тарифный план пользователя
        await self.check_and_update_tariff_plan(user_id)
        await self.refresh_user_tokens(user_id)

        # Проверяем, есть ли у пользователя достаточно токенов
        tokens_available = self.get_user_tokens(user_id)
        if tokens_available == 0:
            await message.reply("У вас закончились токены. Подождите до обновления лимита или измените тарифный план.")
            return

        # Создаем задачу для обработки документа
        task = asyncio.create_task(self._process_document(message))
        self.user_tasks[user_id] = task

    async def _process_document(self, message: types.Message):
        # Вспомогательная функция для обработки документа
        user_id = str(message.from_user.id)
        try:
            file_name = message.document.file_name or "document"
            content = await process_file_content(self.bot, message.document.file_id, file_name)

            prompt = f"Анализ файла {file_name}:\n\n"
            if message.caption:
                prompt += f"Подпись к файлу: {message.caption}\n\n"
            prompt += f"Содержимое:\n{content}"

            # Считаем количество токенов и обновляем баланс
            tokens_used = len(prompt)
            current_tokens = self.get_user_tokens(user_id)
            if current_tokens != -1:
                if tokens_used > current_tokens:
                    await message.reply("У вас недостаточно токенов для анализа этого файла.")
                    return
                self.set_user_tokens(user_id, current_tokens - tokens_used)

            await self.process_message(message, prompt)
        finally:
            # Удаляем задачу из списка после завершения
            if user_id in self.user_tasks:
                del self.user_tasks[user_id]

    async def handle_audio(self, message: types.Message):
        # Проверка типа чата
        if message.chat.type != 'private':
            return

        # Обработка аудио и голосовых сообщений
        user_id = str(message.from_user.id)
        telegram_user_id = message.from_user.id

        # Проверка подписки
        is_subscribed = await self.check_user_subscription(telegram_user_id)
        self.set_user_subscription(user_id, is_subscribed)

        if not is_subscribed:
            await self.prompt_subscription(message)
            return

        # Проверка занятости пользователя
        if user_id in self.user_tasks:
            await message.reply("Пожалуйста, дождитесь ответа на предыдущее сообщение.")
            return

        # Проверяем и обновляем тарифный план пользователя
        await self.check_and_update_tariff_plan(user_id)
        await self.refresh_user_tokens(user_id)

        # Проверяем, есть ли у пользователя достаточно токенов
        tokens_available = self.get_user_tokens(user_id)
        if tokens_available == 0:
            await message.reply("У вас закончились токенов. Подождите до обновления лимита или измените тарифный план.")
            return

        # Создаем задачу для обработки аудио
        task = asyncio.create_task(self._process_audio(message))
        self.user_tasks[user_id] = task

    async def _process_audio(self, message: types.Message):
        # Вспомогательная функция для обработки аудио
        user_id = str(message.from_user.id)
        try:
            await self.audio_processor.process_audio_message(message, self.process_message)
        finally:
            # Удаляем задачу из списка после завершения
            if user_id in self.user_tasks:
                del self.user_tasks[user_id]

    async def handle_photo(self, message: types.Message):
        # Проверка типа чата
        if message.chat.type != 'private':
            return

        # Обработка фотографий
        user_id = str(message.from_user.id)
        telegram_user_id = message.from_user.id

        # Проверка подписки
        is_subscribed = await self.check_user_subscription(telegram_user_id)
        self.set_user_subscription(user_id, is_subscribed)

        if not is_subscribed:
            await self.prompt_subscription(message)
            return

        # Проверка занятости пользователя
        if user_id in self.user_tasks:
            await message.reply("Пожалуйста, дождитесь ответа на предыдущее сообщение.")
            return

        # Проверяем и обновляем тарифный план пользователя
        await self.check_and_update_tariff_plan(user_id)
        await self.refresh_user_tokens(user_id)

        # Проверяем, есть ли у пользователя достаточно токенов
        tokens_available = self.get_user_tokens(user_id)
        if tokens_available == 0:
            await message.reply("У вас закончились токенов. Подождите до обновления лимита или измените тарифный план.")
            return

        # Создаем задачу для обработки фотографии
        task = asyncio.create_task(self._process_photo(message))
        self.user_tasks[user_id] = task

    async def _process_photo(self, message: types.Message):
        # Вспомогательная функция для обработки фотографии
        user_id = str(message.from_user.id)
        try:
            # Получаем файл изображения
            photo = message.photo[-1]  # Используем изображение с максимальным разрешением
            file_info = await self.bot.get_file(photo.file_id)
            image_url = f"https://api.telegram.org/file/bot{config.BOT_TOKEN}/{file_info.file_path}"

            # Подготавливаем контекст для gpt-4o
            prompt = message.caption or "Изображение для анализа."

            # Сохраняем контекст
            await self.process_message(message, prompt, image_url=image_url)
        finally:
            # Удаляем задачу из списка после завершения
            if user_id in self.user_tasks:
                del self.user_tasks[user_id]

    async def handle_text(self, message: types.Message):
        # Проверка типа чата
        if message.chat.type != 'private':
            return

        # Обработка текстовых сообщений
        user_id = str(message.from_user.id)
        telegram_user_id = message.from_user.id

        control_messages = [
            "🗑 Очистить контекст",
            "🤖 Выбрать модель",
            "🖼️ Генерация изображения",
            "👤 Профиль",
            "🛠️ Админ-Панель",
            "🔙 Назад",
            "🔄 Проверить подписку"
        ] + [model[0] for model in self.ALL_AVAILABLE_MODELS]

        if message.text in control_messages:
            return

        # Проверка подписки
        is_subscribed = await self.check_user_subscription(telegram_user_id)
        self.set_user_subscription(user_id, is_subscribed)

        if not is_subscribed:
            await self.prompt_subscription(message)
            return

        # Проверка занятости пользователя
        if user_id in self.user_tasks:
            await message.reply("Пожалуйста, дождитесь ответа на предыдущее сообщение.")
            return

        # Проверяем и обновляем тарифный план пользователя
        await self.check_and_update_tariff_plan(user_id)
        await self.refresh_user_tokens(user_id)

        # Проверяем, есть ли у пользователя достаточно токенов
        tokens_available = self.get_user_tokens(user_id)
        if tokens_available == 0:
            await message.reply("У вас закончились токенов. Подождите до обновления лимита или измените тарифный план.")
            return

        # Передаем обработку в отдельную функцию
        task = asyncio.create_task(self._process_text_message(message))
        self.user_tasks[user_id] = task

    async def _process_text_message(self, message: types.Message):
        # Вспомогательная функция для обработки текстового сообщения
        user_id = str(message.from_user.id)
        try:
            await self.process_message(message, message.text)
        finally:
            # Удаляем задачу из списка после завершения
            if user_id in self.user_tasks:
                del self.user_tasks[user_id]

    async def process_message(self, message: types.Message, text: str, image_url: str = None):
        # Проверка типа чата
        if message.chat.type != 'private':
            return

        # Обработка текстовых сообщений с контекстом
        user_id = str(message.from_user.id)
        telegram_user_id = message.from_user.id

        # Показываем, что бот набирает ответ
        await self.bot.send_chat_action(message.chat.id, types.ChatActions.TYPING)

        waiting_message = await message.reply("Пишу для вас ответ...")
        self.waiting_messages[user_id] = waiting_message

        try:
            # Проверяем и обновляем тарифный план пользователя
            await self.check_and_update_tariff_plan(user_id)
            await self.refresh_user_tokens(user_id)
            tariff_plan = self.get_user_tariff_plan(user_id)

            # Проверяем, есть ли у пользователя достаточно токенов
            tokens_available = self.get_user_tokens(user_id)
            if tokens_available == 0:
                await message.reply("У вас закончились токенов. Подождите до обновления лимита или измените тарифный план.")
                return

            # Получение выбранной модели пользователя
            model = self.get_user_model(user_id)
            # Получение API клиента
            api_name = self.MODEL_API_MAPPING.get(model)
            api_client = self.api_clients.get(api_name)

            if not api_client:
                await message.reply("Ошибка в настройке API. Пожалуйста, свяжитесь с администратором.")
                return

            # Получение предыдущего контекста для пользователя
            conversation = self.conversations.get(user_id, [])

            if api_name == 'sparrow':
                # Используем SparrowAPI
                conversation_id = self.conversations.get(user_id, {}).get('conversation_id')
                response = await asyncio.to_thread(
                    api_client.send_message,
                    message=text,
                    conversation_id=conversation_id,
                    models=[model]
                )

                if not response or 'data' not in response:
                    await message.reply("Получен некорректный ответ от API")
                    return

                data = response['data']
                messages = data.get('messages', [])

                if not messages:
                    await message.reply("Не получено сообщений от API")
                    return

                if 'conversationId' in data:
                    self.conversations.setdefault(user_id, {})['conversation_id'] = data['conversationId']

                assistant_message = next(
                    (msg for msg in reversed(messages) if msg.get('role') == 'assistant'),
                    None
                )

                if not assistant_message:
                    await message.reply("Не получен ответ от ассистента")
                    return

                content = assistant_message.get('content')
                if not content and 'contents' in assistant_message:
                    content = next(
                        (c.get('content') for c in assistant_message['contents']
                         if c.get('model') == model),
                        None
                    )

                if not content:
                    await message.reply("Не удалось получить текст ответа")
                    return

                # Считаем количество токенов и обновляем баланс
                tokens_used = len(content)
                current_tokens = self.get_user_tokens(user_id)
                if current_tokens != -1:
                    if tokens_used > current_tokens:
                        await message.reply("У вас недостаточно токенов для этого ответа.")
                        return
                    self.set_user_tokens(user_id, current_tokens - tokens_used)

                await send_long_message(message, content)

            elif api_name == 'gpt4o':
                # Используем GPT4OAPI
                if image_url:
                    # Если есть изображение, формируем контент специальным образом
                    content = [
                        {"text": text, "type": "text"},
                        {"type": "image_url", "image_url": {"url": image_url}}
                    ]

                    item = {
                        "role": "user",
                        "content": content
                    }

                    data = {
                        "model": model,
                        "item": item
                    }
                else:
                    # Иначе объединяем контекст и отправляем как текст
                    conversation.append(f"Пользователь: {text}")
                    context_text = build_context(conversation)

                    item = {
                        "role": "user",
                        "content": context_text
                    }

                    data = {
                        "model": model,
                        "item": item
                    }

                # Отправка запроса к GPT4OAPI
                response = await asyncio.to_thread(
                    api_client.send_message,
                    data=data
                )

                if not response:
                    await message.reply("Получен некорректный ответ от API")
                    return

                # Извлечение ответа ассистента
                choices = response.get('choices')
                if not choices:
                    await message.reply("Не удалось получить ответ от ассистента")
                    return

                assistant_message = choices[0].get('message')
                if not assistant_message:
                    await message.reply("Не удалось получить ответ от ассистента")
                    return

                assistant_content = assistant_message.get('content')
                if assistant_content:
                    if image_url:
                        # При обработке изображения сохраняем контент как есть
                        conversation.append(f"Пользователь: [Изображение]")
                        conversation.append(f"Ассистент: {assistant_content}")
                    else:
                        # Добавляем ответ ассистента в контекст
                        conversation.append(f"Ассистент: {assistant_content}")

                    self.conversations[user_id] = conversation

                    # Считаем количество токенов и обновляем баланс
                    tokens_used = len(str(assistant_content))
                    current_tokens = self.get_user_tokens(user_id)
                    if current_tokens != -1:
                        if tokens_used > current_tokens:
                            await message.reply("У вас недостаточно токенов для этого ответа.")
                            return
                        self.set_user_tokens(user_id, current_tokens - tokens_used)

                    # Отправка ответа пользователю
                    if isinstance(assistant_content, list):
                        # Обрабатываем список содержимого
                        for content_item in assistant_content:
                            if content_item.get('type') == 'text':
                                text = content_item.get('text')
                                await send_long_message(message, text)
                            elif content_item.get('type') == 'image_url':
                                image_data = content_item.get('image_url', {})
                                image_url = image_data.get('url')
                                if image_url:
                                    await message.reply_photo(photo=image_url)
                    else:
                        # Если контент представлен в виде текста
                        await send_long_message(message, assistant_content)
                else:
                    await message.reply("Не удалось получить текст ответа")

            elif api_name == 'forget':
                # Используем ForgetAPI
                conversation.append({"role": "user", "content": text})
                conversation = conversation[-10:]  # Ограничиваем контекст

                # Отправка запроса к ForgetAPI
                response = await asyncio.to_thread(
                    api_client.send_message,
                    model=model,
                    messages=conversation,
                    temperature=0.7,
                    max_tokens=10000
                )

                if not response:
                    await message.reply("Получен некорректный ответ от API")
                    return

                # Извлечение ответа ассистента
                choices = response.get('choices')
                if not choices:
                    await message.reply("Не удалось получить ответ от ассистента")
                    return

                assistant_message = choices[0].get('message')
                if not assistant_message:
                    await message.reply("Не удалось получить ответ от ассистента")
                    return

                content = assistant_message.get('content')
                if content:
                    conversation.append({"role": "assistant", "content": content})
                    self.conversations[user_id] = conversation

                    # Считаем количество токенов и обновляем баланс
                    tokens_used = len(content)
                    current_tokens = self.get_user_tokens(user_id)
                    if current_tokens != -1:
                        if tokens_used > current_tokens:
                            await message.reply("У вас недостаточно токенов для этого ответа.")
                            return
                        self.set_user_tokens(user_id, current_tokens - tokens_used)

                    # Отправка ответа пользователю
                    await send_long_message(message, content)
                else:
                    await message.reply("Не удалось получить текст ответа")
            else:
                await message.reply("Неизвестная модель или API. Пожалуйста, выберите модель из списка.")
                return

        except Exception as e:
            logger.error(f"Ошибка в process_message: {str(e)}", exc_info=True)
            await message.reply(f"Произошла ошибка при обработке вашего сообщения: {str(e)}")
        finally:
            if user_id in self.waiting_messages:
                await self.waiting_messages[user_id].delete()
                del self.waiting_messages[user_id]

    async def start(self):
        # Запуск бота
        logger.info("Запуск бота...")
        try:
            await self.dp.start_polling()
        except (KeyboardInterrupt, SystemExit):
            logger.info("Бот остановлен")
        except Exception as e:
            logger.error(f"Ошибка запуска бота: {str(e)}", exc_info=True)
            raise
        finally:
            # Закрываем соединение с базой данных при остановке бота
            self.db_connection.close()

    async def check_user_subscription(self, user_id: int) -> bool:
        # Проверка подписки пользователя на канал
        try:
            chat_member = await self.bot.get_chat_member(chat_id='@' + config.CHANNEL_USERNAME, user_id=user_id)
            if chat_member.status in ['member', 'administrator', 'creator']:
                return True
            else:
                return False
        except Exception as e:
            logger.error(f"Ошибка при проверке подписки для пользователя {user_id}: {e}")
            return False

    # ----- Обработчики для Админ-Панели -----

    async def show_admin_panel(self, message: types.Message):
        # Проверка, что пользователь является администратором
        if message.from_user.id not in config.ADMIN_IDS:
            return

        await Form.AdminPanel.set()
        keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True)
        keyboard.add("Изменить тариф")
        keyboard.add("Изменить токены тарифа")
        keyboard.add("Сделать рассылку")
        keyboard.add("Управление чатами")
        keyboard.add("Сгенерировать отчёт")
        keyboard.add("🔙 Назад")
        await message.reply("Добро пожаловать в Админ-Панель. Выберите действие:", reply_markup=keyboard)

    async def handle_admin_selection(self, message: types.Message, state: FSMContext):
        if message.from_user.id not in config.ADMIN_IDS:
            await state.finish()
            return

        if message.text == "Изменить тариф":
            # Переходим к выбору тарифа для редактирования
            await Form.SelectTariffForEdit.set()
            keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True)
            tariffs = ['free', 'premium', 'admin']
            keyboard.add(*tariffs)
            keyboard.add("🔙 Назад")
            await message.reply("Выберите тариф для редактирования:", reply_markup=keyboard)
        elif message.text == "Изменить токены тарифа":
            # Переходим к выбору тарифа для изменения токенов
            await Form.EditTariffTokens.set()
            keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True)
            tariffs = ['free', 'premium', 'admin']
            keyboard.add(*tariffs)
            keyboard.add("🔙 Назад")
            await message.reply("Выберите тариф, для которого хотите изменить количество ежедневных токенов:", reply_markup=keyboard)
        elif message.text == "Сделать рассылку":
            # Переходим к рассылке сообщений
            await Form.BroadcastSelectTariff.set()
            keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True)
            tariffs = ['free', 'premium', 'admin', 'Все']
            keyboard.add(*tariffs)
            keyboard.add("🔙 Назад")
            await message.reply("Выберите тариф, пользователям которого хотите отправить сообщение:", reply_markup=keyboard)
        elif message.text == "Управление чатами":
            # Переходим к управлению чатами
            await Form.ManageChats.set()
            keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True)
            keyboard.add("Добавить чат")
            keyboard.add("Удалить чат")
            keyboard.add("Показать чаты")
            keyboard.add("🔙 Назад")
            await message.reply("Выберите действие с чатами:", reply_markup=keyboard)
        elif message.text == "Сгенерировать отчёт":
            # Генерация отчёта
            await Form.GenerateReport.set()
            await self.generate_report(message)
            await state.finish()
        elif message.text == "🔙 Назад":
            await state.finish()
            await self.cmd_start(message)
        else:
            await message.reply("Пожалуйста, выберите действие из списка.")

    async def handle_manage_chats(self, message: types.Message, state: FSMContext):
        if message.text == "Добавить чат":
            await Form.AddChat.set()
            await message.reply("Пожалуйста, отправьте ID чата, который хотите добавить в премиум-чаты:")
        elif message.text == "Удалить чат":
            await Form.DeleteChat.set()
            await message.reply("Пожалуйста, отправьте ID чата, который хотите удалить из премиум-чатов:")
        elif message.text == "Показать чаты":
            # Показываем список премиум-чатов
            self.db_cursor.execute('SELECT chat_id FROM premium_chats')
            premium_chat_ids = [row[0] for row in self.db_cursor.fetchall()]
            chat_list = "\n".join([str(chat_id) for chat_id in premium_chat_ids])
            await message.reply(f"Список премиум-чатов:\n{chat_list}")
        elif message.text == "🔙 Назад":
            await Form.AdminPanel.set()
            await self.show_admin_panel(message)
        else:
            await message.reply("Пожалуйста, выберите действие из списка.")

    async def handle_add_chat(self, message: types.Message, state: FSMContext):
        try:
            chat_id = int(message.text)
            self.db_cursor.execute('INSERT INTO premium_chats (chat_id) VALUES (?)', (chat_id,))
            self.db_connection.commit()
            await message.reply(f"Чат {chat_id} успешно добавлен в премиум-чаты.")
            await state.finish()
            await self.show_admin_panel(message)
        except Exception as e:
            await message.reply(f"Ошибка при добавлении чата: {e}")
            await state.finish()
            await self.show_admin_panel(message)

    async def handle_delete_chat(self, message: types.Message, state: FSMContext):
        try:
            chat_id = int(message.text)
            self.db_cursor.execute('DELETE FROM premium_chats WHERE chat_id = ?', (chat_id,))
            self.db_connection.commit()
            await message.reply(f"Чат {chat_id} успешно удалён из премиум-чатов.")
            await state.finish()
            await self.show_admin_panel(message)
        except Exception as e:
            await message.reply(f"Ошибка при удалении чата: {e}")
            await state.finish()
            await self.show_admin_panel(message)

    async def generate_report(self, message: types.Message):
        # Генерация отчёта в формате xlsx
        await message.reply("Генерирую отчёт...")

        workbook = xlsxwriter.Workbook('report.xlsx')
        worksheet = workbook.add_worksheet()

        # Заголовки столбцов
        headers = ['Группа', 'Тариф', 'ID пользователя', 'Использовано токенов']
        for col_num, header in enumerate(headers):
            worksheet.write(0, col_num, header)

        # Получаем список пользователей
        self.db_cursor.execute('SELECT user_id, tariff_plan, tokens_available, group_association FROM users')
        users = self.db_cursor.fetchall()

        # Получаем список премиум-чатов
        self.db_cursor.execute('SELECT chat_id FROM premium_chats')
        premium_chat_ids = [str(row[0]) for row in self.db_cursor.fetchall()]

        # Группа, при старте на которую нужно подписаться
        start_group = '@' + config.CHANNEL_USERNAME

        # Заполняем данные
        row = 1
        for user_id, tariff_plan, tokens_available, group_association in users:
            if group_association in premium_chat_ids:
                group_name = f"Чат {group_association}"
            elif group_association == "None":
                group_name = start_group
            else:
                group_name = group_association

            worksheet.write(row, 0, group_name)
            worksheet.write(row, 1, tariff_plan)
            worksheet.write(row, 2, user_id)
            tokens_used = self.get_tariff_info(tariff_plan)['daily_tokens'] - tokens_available if tokens_available != -1 else 0
            worksheet.write(row, 3, tokens_used)
            row += 1

        workbook.close()

        with open('report.xlsx', 'rb') as f:
            await self.bot.send_document(chat_id=message.chat.id, document=f)

    async def handle_generate_report(self, message: types.Message, state: FSMContext):
        # Этот обработчик фактически не используется, так как генерация происходит сразу
        pass

    async def handle_tariff_selection_for_edit(self, message: types.Message, state: FSMContext):
        if message.text == "🔙 Назад":
            await state.finish()
            await self.show_admin_panel(message)
            return
        tariff_id = message.text
        if tariff_id not in ['free', 'premium', 'admin']:
            await message.reply("Пожалуйста, выберите тариф из списка.")
            return
        await state.update_data(tariff_id=tariff_id)
        await Form.EditTariffNameOrDescription.set()
        keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True)
        keyboard.add("Изменить название", "Изменить описание")
        keyboard.add("🔙 Назад")
        await message.reply("Выберите, что вы хотите изменить:", reply_markup=keyboard)

    async def handle_edit_tariff_name_or_description(self, message: types.Message, state: FSMContext):
        if message.text == "🔙 Назад":
            await Form.SelectTariffForEdit.set()
            keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True)
            tariffs = ['free', 'premium', 'admin']
            keyboard.add(*tariffs)
            keyboard.add("🔙 Назад")
            await message.reply("Выберите тариф для редактирования:", reply_markup=keyboard)
            return
        data = await state.get_data()
        tariff_id = data.get('tariff_id')

        if message.text == "Изменить название":
            await Form.EditTariffName.set()
            await message.reply("Введите новое название тарифа:")
        elif message.text == "Изменить описание":
            await Form.EditTariffDescription.set()
            await message.reply("Введите новое описание тарифа:")
        else:
            await message.reply("Пожалуйста, выберите действие из списка.")

    async def handle_edit_tariff_name(self, message: types.Message, state: FSMContext):
        new_name = message.text
        data = await state.get_data()
        tariff_id = data.get('tariff_id')
        self.set_tariff_info(tariff_id, name=new_name)
        await message.reply(f"Название тарифа '{tariff_id}' успешно изменено на '{new_name}'.")
        await state.finish()
        await self.show_admin_panel(message)

    async def handle_edit_tariff_description(self, message: types.Message, state: FSMContext):
        new_description = message.text
        data = await state.get_data()
        tariff_id = data.get('tariff_id')
        self.set_tariff_info(tariff_id, description=new_description)
        await message.reply(f"Описание тарифа '{tariff_id}' успешно изменено.")
        await state.finish()
        await self.show_admin_panel(message)

    async def handle_edit_tariff_name(self, message: types.Message, state: FSMContext):
        new_name = message.text
        data = await state.get_data()
        tariff_id = data.get('tariff_id')
        self.set_tariff_info(tariff_id, name=new_name)
        await message.reply(f"Название тарифа '{tariff_id}' успешно изменено на '{new_name}'.")
        await state.finish()
        await self.show_admin_panel(message)

    async def handle_edit_tariff_description(self, message: types.Message, state: FSMContext):
        new_description = message.text
        data = await state.get_data()
        tariff_id = data.get('tariff_id')
        self.set_tariff_info(tariff_id, description=new_description)
        await message.reply(f"Описание тарифа '{tariff_id}' успешно изменено.")
        await state.finish()
        await self.show_admin_panel(message)

    async def handle_edit_tariff_tokens(self, message: types.Message, state: FSMContext):
        if message.text == "🔙 Назад":
            await state.finish()
            await self.show_admin_panel(message)
            return
        tariff_id = message.text
        if tariff_id not in ['free', 'premium', 'admin']:
            await message.reply("Пожалуйста, выберите тариф из списка.")
            return
        await state.update_data(tariff_id=tariff_id)
        await Form.EditTariffTokenAmount.set()
        await message.reply("Введите новое количество ежедневных токенов (введите -1 для неограниченного количества):")

    async def handle_set_tariff_tokens(self, message: types.Message, state: FSMContext):
        try:
            new_token_amount = int(message.text)
            data = await state.get_data()
            tariff_id = data.get('tariff_id')
            self.set_tariff_info(tariff_id, daily_tokens=new_token_amount)
            await message.reply(f"Количество ежедневных токенов для тарифа '{tariff_id}' успешно изменено на {new_token_amount}.")
            await state.finish()
            await self.show_admin_panel(message)
        except ValueError:
            await message.reply("Пожалуйста, введите корректное число.")

    async def handle_broadcast_message(self, message: types.Message, state: FSMContext):
        if message.text == "🔙 Назад":
            await state.finish()
            await self.show_admin_panel(message)
            return
        tariff_id = message.text
        if tariff_id not in ['free', 'premium', 'admin', 'Все']:
            await message.reply("Пожалуйста, выберите тариф из списка.")
            return
        await state.update_data(tariff_id=tariff_id)
        await Form.BroadcastMessageState.set()
        await message.reply("Введите сообщение для рассылки:")

    async def handle_broadcast_text(self, message: types.Message, state: FSMContext):
        broadcast_text = message.text
        data = await state.get_data()
        tariff_id = data.get('tariff_id')

        # Получаем список пользователей
        if tariff_id == 'Все':
            self.db_cursor.execute('SELECT user_id FROM users')
        else:
            self.db_cursor.execute('SELECT user_id FROM users WHERE tariff_plan = ?', (tariff_id,))

        user_ids = [row[0] for row in self.db_cursor.fetchall()]
        success_count = 0
        for user_id in user_ids:
            try:
                await self.bot.send_message(chat_id=int(user_id), text=broadcast_text)
                success_count += 1
            except Exception as e:
                logger.error(f"Не удалось отправить сообщение пользователю {user_id}: {e}")

        await message.reply(f"Рассылка завершена. Сообщение отправлено {success_count} пользователям.")
        await state.finish()
        await self.show_admin_panel(message)

    # ----- Конец обработчиков для Админ-Панели -----

async def main():
    # Асинхронная функция main
    bot = EnhancedBot()
    try:
        await bot.start()
    except (KeyboardInterrupt, SystemExit):
        logger.info("Бот остановлен")
    except Exception as e:
        logger.error(f"Критическая ошибка: {str(e)}", exc_info=True)

if __name__ == '__main__':
    asyncio.run(main())

Assistant: The provided Python script defines a Telegram bot using the `aiogram` library. Below is an analysis of the code, highlighting its structure, functionality, and potential areas for improvement.

---

### **Overview**

The script creates an `EnhancedBot` class that handles various functionalities:

- **User Interaction**: Manages commands like `/start`, model selection, context clearing, profile viewing, image generation, and access to the admin panel.
- **State Management**: Utilizes `aiogram`'s FSM (Finite State Machine) to handle conversation states.
- **API Integrations**: Integrates with multiple APIs (`SparrowAPI`, `GPT4OAPI`, `ForgetAPI`, `ImageAPI`) for AI model interactions and image generation.
- **Database Management**: Uses `sqlite3` to store user data, including subscription status, selected models, tariff plans, tokens, and group associations.
- **Admin Panel**: Provides administrative functionalities to edit tariffs, manage chats, send broadcasts, and generate reports.
- **Media Handling**: Processes documents, audio messages, and photos, including transcription and content analysis.

---

### **Detailed Analysis**

#### **1. Imports and Dependencies**

- **Standard Libraries**: Imports essential modules like `logging`, `asyncio`, `os`, `sqlite3`, and date/time modules.
- **Aiogram Library**: Used for Telegram bot development.
- **Custom Modules**: Imports from `config`, `sparrow_api`, `gpt4o_api`, `forgetapi`, `image_api`, `audio_processing`, and `doc`.

**Suggestion**: Ensure all custom modules (`sparrow_api`, `gpt4o_api`, etc.) are available and correctly configured. Handle `ImportError` exceptions to provide informative messages if dependencies are missing.

#### **2. Logging Configuration**

```python
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
```

- Sets up basic logging to display informational messages.
- **Suggestion**: Configure logging to include timestamps and output to a file for better traceability in production environments.

#### **3. State Management with FSM**

Defines the `Form` class inheriting from `StatesGroup` to manage different conversation states:

- **User States**: For image generation, model selection, and profile interaction.
- **Admin States**: For tariff editing, broadcasting messages, chat management, and report generation.

**Suggestion**: As the number of states increases, consider organizing them into separate classes or modules for clarity.

#### **4. The `EnhancedBot` Class**

##### **a. Initialization (`__init__` method)**

- Initializes the bot, dispatcher, storage, API clients, audio processor, conversation history, task tracking, and database connection.
- Sets up the database and message handlers.
- Defines available models and maps them to their respective APIs.

**Suggestion**: Validate API clients during initialization to ensure all necessary configurations are in place. Handle exceptions where API keys or endpoints might be missing or incorrect.

##### **b. Database Setup (`_setup_database` method)**

- Creates tables for users, tariff plans, and premium chats if they don't exist.
- Initializes default tariff plans (`free`, `premium`, `admin`) and premium chat IDs.

**Suggestion**: Implement migrations or versioning for the database schema to handle future changes gracefully.

##### **c. User Management Methods**

- **Tariff Plans**: Methods to get and set tariff information.
- **Subscription Status**: Methods to check and update user subscriptions.
- **Model Selection**: Methods to get and set the user's selected AI model.
- **Token Management**: Methods to manage user tokens and schedule daily token refresh.
- **Group Association**: Methods to manage user's group associations for premium access.

**Suggestion**: Ensure thread safety when accessing or modifying shared resources like the database, especially when using async operations.

##### **d. Handlers Setup (`_setup_handlers` method)**

- Registers message handlers for various user interactions and admin functions.
- Separates handlers based on user commands and states.

**Suggestion**: Organize handlers into separate modules or classes to improve readability and maintainability.

##### **e. User Interaction Handlers**

- **Start/Help**: `cmd_start` method handles the `/start` and `/help` commands.
- **Model Selection**: Allows users to select an AI model for interaction.
- **Context Clearing**: Provides functionality to clear the conversation context.
- **Profile Viewing**: Displays user profile, tariff plan, and token balance.
- **Image Generation**: Guides users through the process of generating images using AI.

**Suggestion**: Provide more detailed feedback or guidance when users make invalid selections or inputs.

##### **f. Media Handling**

- **Documents**: Handles document uploads, processes content, and sends it for analysis.
- **Audio Messages**: Transcribes audio messages and processes the text.
- **Photos**: Handles photo uploads, possibly for analysis or other functions.

**Suggestion**: Ensure proper validation and security checks on uploaded content to prevent abuse or exploitation.

##### **g. Admin Panel Handlers**

- **Tariff Management**: Allows admins to edit tariff names, descriptions, and token amounts.
- **Chat Management**: Enables adding or removing chats from the premium list.
- **Broadcasting**: Facilitates sending messages to users based on their tariff plans.
- **Report Generation**: Generates reports on user activity and token usage.

**Suggestion**: Implement logging and auditing for admin actions to maintain transparency and traceability.

---

### **Potential Improvements**

#### **1. Exception Handling**

- **Current State**: The code uses try-except blocks in several places, especially around critical operations like database access and API calls.
- **Improvement**: Implement more comprehensive exception handling throughout the code. Provide user-friendly error messages and ensure that exceptions are logged for debugging purposes.

#### **2. Async Database Operations**

- **Current State**: Uses `sqlite3`, which might block the event loop in an asynchronous context.
- **Improvement**: Consider using an asynchronous database library like `aiosqlite`. This can prevent blocking and improve performance.

#### **3. Code Organization**

- **Current State**: The `EnhancedBot` class contains many methods, including user and admin handlers.
- **Improvement**: Separate concerns by organizing code into different modules or classes. For example, create separate modules for user commands, admin commands, and utility functions.

#### **4. Configuration Management**

- **Current State**: Configuration is imported from `config.py`.
- **Improvement**: Use environment variables or a configuration file (e.g., `.env`) for sensitive information like API keys. Utilize a library like `python-dotenv` to manage environment variables.

#### **5. Logging Enhancements**

- **Current State**: Basic logging is set up.
- **Improvement**:

  - Include timestamps and log levels in log messages.
  - Log to a file with log rotation to prevent log files from becoming too large.
  - Use different log levels (`DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`) appropriately.

#### **6. Security Considerations**

- **Sensitive Data**: Ensure that API keys and sensitive information are not exposed in the codebase.
- **Input Validation**: Sanitize all user inputs, especially in admin functionalities, to prevent SQL injection or other injection attacks.
- **Access Control**: Verify that only authorized users can access admin functionalities by cross-checking against a secure list of admin IDs.

#### **7. Scalability and Performance**

- **Database Optimization**: Index database tables where necessary to improve query performance.
- **Concurrency**: Be mindful of race conditions, especially when updating shared resources like token balances.
- **Rate Limiting**: Implement rate limiting to prevent misuse or overloading of the bot.

#### **8. User Experience**

- **Feedback Messages**: Provide clear and informative messages to users, especially when an error occurs or when an operation takes time to process.
- **Localization**: If targeting a multilingual audience, consider adding support for multiple languages.

#### **9. Testing**

- **Automated Tests**: Write unit tests for critical components to ensure reliability.
- **CI/CD Pipeline**: Implement continuous integration and deployment pipelines to automate testing and deployment.

#### **10. Documentation**

- **Docstrings**: Add docstrings to functions and classes to explain their purpose and usage.
- **README**: Provide a comprehensive README file with instructions on setting up and running the bot, including prerequisites and configuration.

---

### **Conclusion**

The `bot.py` script is a robust implementation of a Telegram bot with advanced features, including AI model interactions, state management, and administrative controls. By addressing the potential improvements outlined above, you can enhance the code's reliability, security, and maintainability.

If you have specific questions about any part of the code or need further assistance with implementation details, feel free to ask!
User: вот такая ошибка при переводе текста с gpt-4o-mini
ERROR:__main__:Ошибка при переводе описания: ForgetAPI.send_message() got an unexpected keyword argument 'message'
Traceback (most recent call last):
  File "/root/bigbot/bot.py", line 837, in translate_prompt 
    response = await asyncio.to_thread(
  File "/usr/lib/python3.10/asyncio/threads.py", line 25, in to_thread
    return await loop.run_in_executor(None, func_call)
  File "/usr/lib/python3.10/concurrent/futures/thread.py", line 58, in run
    result = self.fn(*self.args, **self.kwargs)
TypeError: ForgetAPI.send_message() got an unexpected keyword argument 'message' переводе описания: ForgetAPI.send_message() got an unexpected keyword argument 'message'
Traceback (most recent call last):
  File "/root/bigbot/bot.py", line 837, in translate_prompt 
    response = await asyncio.to_thread(
  File "/usr/lib/python3.10/asyncio/threads.py", line 25, in to_thread
    return await loop.run_in_executor(None, func_call)
  File "/usr/lib/python3.10/concurrent/futures/thread.py", line 58, in run
    result = self.fn(*self.args, **self.kwargs)
TypeError: ForgetAPI.send_message() got an unexpected keyword argument 'message'