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: Файл `bot.py` представляет собой полноценного Telegram-бота, разработанного с использованием библиотеки `aiogram`. Бот предоставляет функционал общения с различными AI-моделями, генерации изображений, обработки документов, аудио и фотографий, а также включает в себя административную панель для управления тарифными планами и рассылками. Ниже представлен детальный анализ структуры, функциональности, а также возможных улучшений и рекомендаций по безопасности.

## Структура и архитектура

### Импорты и настройки
Бот импортирует необходимые библиотеки и модули, включая `aiogram` для взаимодействия с Telegram API, `asyncio` для асинхронного программирования, `sqlite3` для работы с базой данных, а также кастомные модули и API-клиенты:

- `config` — содержит настройки бота, такие как `BOT_TOKEN`, `ADMIN_IDS`, `CHANNEL_USERNAME`.
- API-клиенты: `SparrowAPI`, `GPT4OAPI`, `ForgetAPI`, `ImageAPI`.
- Вспомогательные модули: `audio_processing`, `doc` — для обработки аудио и документов соответственно.

### Классы состояний
Используется `aiogram`'s FSM (Finite State Machine) для управления состояниями бота. Класс `Form` определяет различные состояния, такие как генерация изображений, административные действия и генерация отчетов.

### Основной класс `EnhancedBot`
Класс `EnhancedBot` инкапсулирует все основные функциональные возможности бота:

- **Инициализация**:
  - Настройка бота и диспетчера.
  - Инициализация API-клиентов.
  - Подключение к базе данных SQLite и настройка таблиц.
  - Регистрация обработчиков сообщений.

- **База данных**:
  - Таблицы `users`, `tariff_plans`, `premium_chats` для хранения данных пользователей, тарифных планов и премиум-чатов.
  - Методы для получения и установки информации о тарифах, подписках пользователей, моделях и токенах.

- **Обработчики сообщений**:
  - Основные команды (`/start`, `help`).
  - Обработка выбора модели, очистки контекста, генерации изображений, профиля пользователя.
  - Административные функции: изменение тарифов, управление чатами, рассылки, генерация отчетов.
  - Обработка документов, аудио, фотографий и текстовых сообщений с учетом состояния FSM.

- **Дополнительные функции**:
  - Проверка подписки пользователя на канал.
  - Асинхронное обновление токенов ежедневно.
  - Генерация отчетов в формате XLSX.
  - Перевод описаний изображений на английский язык перед генерацией.

## Функциональность

### Основной функционал
1. **Выбор и изменение модели**:
   - Пользователи могут выбрать из доступных AI-моделей для общения.
   - Возможность смены модели через главное меню.

2. **Генерация изображений**:
   - Запрос описания изображения и выбор соотношения сторон.
   - Использование `ImageAPI` для генерации изображений.
   - Списание токенов за генерацию изображения.

3. **Управление профилем**:
   - Просмотр текущего тарифного плана, оставшихся токенов и времени обновления токенов.
   - Возможность проверки подписки на канал.

4. **Обработка контента**:
   - Обработка текстовых сообщений с учетом контекста.
   - Обработка документов, аудио и фотографий с использованием соответствующих модулей.

### Административный функционал
1. **Админ-Панель**:
   - Изменение названий и описаний тарифных планов.
   - Управление количеством токенов для тарифов.
   - Рассылки сообщений пользователям по тарифным планам.
   - Управление премиум-чатами (добавление, удаление, просмотр).
   - Генерация отчетов о пользователях и их тарифных планах.

2. **Генерация отчетов**:
   - Создание отчетов в формате XLSX с информацией о пользователях, их тарифных планах и использовании токенов.

## Обзор базы данных

Используется SQLite с тремя основными таблицами:

1. **users**:
   - `user_id`: уникальный идентификатор пользователя.
   - `is_subscribed`: статус подписки (0 или 1).
   - `model`: выбранная модель AI.
   - `tariff_plan`: текущий тарифный план пользователя.
   - `tokens_available`: количество доступных токенов.
   - `last_token_refresh`: дата последнего обновления токенов.
   - `group_association`: ассоциация с группой или чатами.

2. **tariff_plans**:
   - `tariff_id`: уникальный идентификатор тарифа.
   - `name`: название тарифа.
   - `description`: описание тарифа.
   - `daily_tokens`: количество токенов, доступных ежедневно.

3. **premium_chats**:
   - `chat_id`: уникальный идентификатор премиум-чата.

### Инициализация базы данных
При первом запуске создаются таблицы, если они не существуют, и инициализируются тарифные планы и премиум-чаты, если таблицы пустые.

## Потенциальные проблемы и улучшения

### 1. Безопасность

- **Секреты и конфиденциальные данные**:
  - Предполагается, что `config.py` содержит чувствительные данные (токен бота, ID администраторов). Убедитесь, что этот файл не включен в систему контроля версий (например, `.gitignore`), чтобы избежать утечки информации.

- **Валидация пользовательского ввода**:
  - Некоторые обработчики, особенно для административных действий (например, добавление или удаление чатов), принимают прямой ввод `chat_id`. Рекомендуется добавить более строгую валидацию, чтобы предотвратить ввод неверных или вредоносных данных.

- **Обработка исключений**:
  - Во многих местах обрабатываются исключения, однако стоит удостовериться, что нет мест, где необработанные исключения могут привести к краху бота или утечке информации.

### 2. Производительность

- **Асинхронная работа с базой данных**:
  - В текущей реализации используется синхронный `sqlite3`. Для улучшения производительности и предотвращения блокировок рекомендуется использовать асинхронные библиотеки для работы с SQLite, такие как `aiosqlite`.

- **Обработка больших файлов**:
  - При обработке документов и фотографий нет ограничений на размер файла. Рекомендуется ограничить допустимый размер загружаемых файлов до разумных пределов, чтобы избежать перегрузки сервера.

### 3. Масштабируемость

- **Использование SQLite**:
  - SQLite подходит для малых проектов. Если предполагается, что бот будет обслуживать большое количество пользователей, стоит рассмотреть переход на более масштабируемую СУБД, такую как PostgreSQL или MySQL.

### 4. Улучшения функциональности

- **Лимиты и квоты**:
  - В настоящее время лимиты токенов фиксированы по тарифам. Можно добавить возможности для пользователей приобретать дополнительные пакеты токенов.

- **Логирование и мониторинг**:
  - Хотя настроено базовое логирование, можно интегрировать более продвинутые системы мониторинга и оповещений (например, Sentry) для отслеживания ошибок и производительности в реальном времени.

- **Международная поддержка**:
  - Бот полностью локализован на русском языке. Для расширения аудитории можно добавить поддержку других языков.

### 5. Кодовая база и поддержка

- **Дублирование методов**:
  - В коде присутствует дублирование методов `handle_edit_tariff_name` и `handle_edit_tariff_description`. Рекомендуется удалить повторяющиеся методы или объединить их логику.

- **Структуризация кода**:
  - По мере роста функциональности, может потребоваться разделение кода на несколько модулей или пакетов для облегчения поддержки и понимания.

- **Документация и комментарии**:
  - Хотя присутствуют комментарии, их можно улучшить, добавив более подробные описания функций и классов, а также использование типов аннотаций для лучшей читаемости и понимания кода.

## Заключение

Файл `bot.py` демонстрирует продуманный подход к созданию многофункционального Telegram-бота с использованием современных библиотек и подходов. Однако, как и любой проект, он может быть улучшен с точки зрения безопасности, производительности и масштабируемости. Рекомендуется провести аудит безопасности, оптимизировать работу с базой данных и структурировать код для облегчения дальнейшей поддержки и развития. Кроме того, внедрение более продвинутых методов мониторинга и международной поддержки может значительно повысить качество и востребованность бота среди пользователей.
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'

также нужно добавить к gpt-4o поиск в интернете, чтобы он именно парсил в интернете и выдавал запрос когда его об этом просят, т.е по ключевым словам каким-то
и напиши весь код полностью без сокращений