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:
                try:
                    await self.waiting_messages[user_id].delete()
                except Exception as e:
                    logger.error(f"Ошибка при удалении ожидания сообщения: {e}")
                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-2024-08-06'  # Исправлено: используем модель, связанную с 'gpt4o' API
            api_name = self.MODEL_API_MAPPING.get(model)
            api_client = self.api_clients.get(api_name)
            if not api_client:
                logger.error(f"API клиент не найден для модели {model}")
                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)
        except Exception as e:
            logger.error(f"Ошибка при обработке документа: {e}", exc_info=True)
            await message.reply(f"Произошла ошибка при обработке документа: {e}")
        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)
        except Exception as e:
            logger.error(f"Ошибка при обработке аудио: {e}", exc_info=True)
            await message.reply(f"Произошла ошибка при обработке аудио: {e}")
        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)
        except Exception as e:
            logger.error(f"Ошибка при обработке фотографии: {e}", exc_info=True)
            await message.reply(f"Произошла ошибка при обработке фотографии: {e}")
        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)
        except Exception as e:
            logger.error(f"Ошибка при обработке текстового сообщения: {e}", exc_info=True)
            await message.reply(f"Произошла ошибка при обработке вашего сообщения: {e}")
        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:
                try:
                    await self.waiting_messages[user_id].delete()
                except Exception as e:
                    logger.error(f"Ошибка при удалении ожидания сообщения: {e}")
                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()]
            if premium_chat_ids:
                chat_list = "\n".join([str(chat_id) for chat_id in premium_chat_ids])
                await message.reply(f"Список премиум-чатов:\n{chat_list}")
            else:
                await message.reply("Премиум-чаты отсутствуют.")
        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 ValueError:
            await message.reply("Пожалуйста, введите корректный числовой ID чата.")
        except sqlite3.IntegrityError:
            await message.reply("Этот чат уже добавлен в премиум-чаты.")
        except Exception as e:
            await message.reply(f"Ошибка при добавлении чата: {e}")
            logger.error(f"Ошибка при добавлении чата: {e}", exc_info=True)
            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,))
            if self.db_cursor.rowcount == 0:
                await message.reply(f"Чат {chat_id} не найден в премиум-чатах.")
            else:
                self.db_connection.commit()
                await message.reply(f"Чат {chat_id} успешно удалён из премиум-чатов.")
            await state.finish()
            await self.show_admin_panel(message)
        except ValueError:
            await message.reply("Пожалуйста, введите корректный числовой ID чата.")
        except Exception as e:
            await message.reply(f"Ошибка при удалении чата: {e}")
            logger.error(f"Ошибка при удалении чата: {e}", exc_info=True)
            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)
            tariff_info = self.get_tariff_info(tariff_plan)
            if tariff_info:
                daily_tokens = tariff_info['daily_tokens']
                tokens_used = daily_tokens - tokens_available if daily_tokens != -1 else 0
            else:
                tokens_used = 0
            worksheet.write(row, 3, tokens_used)
            row += 1

        workbook.close()

        try:
            with open('report.xlsx', 'rb') as f:
                await self.bot.send_document(chat_id=message.chat.id, document=f)
        except Exception as e:
            logger.error(f"Ошибка при отправке отчёта: {e}", exc_info=True)
            await message.reply(f"Произошла ошибка при отправке отчёта: {e}")

    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_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
                await asyncio.sleep(0.05)  # Чтобы избежать лимитов Telegram
            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` для асинхронного взаимодействия с Telegram API. Бот предназначен для общения с различными AI-моделями, управления пользователями через тарифные планы, генерации изображений, обработки документов и медиафайлов, а также предоставляет административную панель для управления тарифами и рассылками. Ниже представлен подробный анализ данного файла.

## 1. **Общая Структура и Назначение**

### **Основные Компоненты:**
- **Импорты:** Используются стандартные библиотеки Python (`logging`, `asyncio`, `sqlite3`, и др.), а также сторонние библиотеки (`aiogram`, `xlsxwriter`).
- **Классы Состояний:** Класс `Form` определяет различные состояния FSM (Finite State Machine) для управления последовательностью действий пользователей и администраторов.
- **Класс `EnhancedBot`:** Основной класс, реализующий всю функциональность бота, включая настройку базы данных, обработку команд и сообщений, управление тарифными планами и подписками.
- **Асинхронная функция `main`:** Инициализирует и запускает бота.

## 2. **Используемые Библиотеки и Модули**

### **Стандартные Библиотеки:**
- `logging`: Для логирования информации и ошибок.
- `asyncio`: Для асинхронного программирования.
- `os`, `sqlite3`: Для взаимодействия с операционной системой и базой данных SQLite.
- `datetime`, `timedelta`: Для работы с датами и временем.
- `typing`: Для аннотации типов.

### **Сторонние Библиотеки:**
- `aiogram`: Асинхронный фреймворк для создания Telegram-ботов.
- `xlsxwriter`: Для создания Excel-отчетов.
- Пользовательские модули: `sparrow_api`, `gpt4o_api`, `forgetapi`, `image_api`, `audio_processing`, `doc`.

### **Комментарии:**
- Важно удостовериться, что все пользовательские модули (`sparrow_api`, `gpt4o_api`, и т.д.) корректно реализованы и находятся в правильных директориях.

## 3. **Функциональность Бота**

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

### **Особенности:**
- **FSM (Finite State Machine):** Позволяет управлять последовательностью действий в различных процессах (например, генерация изображений, административные задачи).
- **Асинхронное Программирование:** Обеспечивает масштабируемость и отзывчивость бота при обработке множества запросов одновременно.

## 4. **Управление Базой Данных**

### **Использование SQLite:**
- **Таблицы:**
  - `users`: Хранит информацию о пользователях, подписках, выбранных моделях и токенах.
  - `tariff_plans`: Хранит информацию о тарифных планах.
  - `premium_chats`: Хранит ID премиум-чатов.

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

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

### **Замечания:**
- **Синхронизация:** Использование SQLite может стать узким местом при масштабировании бота. Рассмотрите возможность перехода на более производительные СУБД (например, PostgreSQL) при росте числа пользователей.
- **Безопасность:** Убедитесь, что база данных защищена от несанкционированного доступа, особенно если она хранит чувствительные данные.

## 5. **Обработчики Команд и Сообщений**

### **Основные Обработчики:**
- `cmd_start`: Обрабатывает команды `/start` и `/help`, проверяет подписку и отображает главное меню.
- `show_model_selection`: Показывает пользователю доступные модели AI.
- `process_model_selection`: Обрабатывает выбор модели пользователем.
- `handle_clear_context`: Очищает контекст беседы пользователя.
- `show_profile`: Отображает профиль пользователя, включая тарифный план и доступные токены.
- `start_image_generation` и связанные действия: Обрабатывают процесс генерации изображения.
- `handle_document`, `handle_audio`, `handle_photo`, `handle_text`: Обрабатывают различные типы контента от пользователя.
- **Административные Обработчики:** Управление тарифами, рассылками, премиум-чатами и генерация отчетов.

### **Функции FSM:**
- Используются для управления последовательностью шагов в процессах, требующих нескольких взаимодействий с пользователем (например, генерация изображения).

### **Асинхронность:**
- Большинство обработчиков используют асинхронные функции (`async def`) для обеспечения масштабируемости.
- Задачи запускаются через `asyncio.create_task` для выполнения фоновых операций.

## 6. **Управление Подписками и Тарифными Планами**

### **Проверка Подписки:**
- Бот проверяет, подписан ли пользователь на определенный канал, используя метод `get_chat_member`.
- Администраторы бота автоматически получают тарифный план `'admin'`.

### **Тарифные Планы:**
- **`free`**: Стандартный тариф с ограниченным количеством токенов.
- **`premium`**: Расширенный тариф с большим количеством токенов.
- **`admin`**: Неограниченный доступ для администраторов.

### **Обновление Тарифов:**
- При смене тарифного плана пользователю перерассчитываются токены.
- Ежедневно токены обновляются в зависимости от тарифного плана.

### **Замечания:**
- **Проверка Членства в Чатах:** Метод `get_chat_member` может быть подвержен ограничениям Telegram API, особенно при большом количестве запросов. Рассмотрите внедрение кеширования или более эффективных методов проверки.
- **Обработка Исключений:** Убедитесь, что все возможные исключения корректно обрабатываются, чтобы избежать сбоя бота.

## 7. **Генерация Изображений и Обработка Медиа**

### **Генерация Изображений:**
- Пользователь вводит описание изображения.
- Описание переводится на английский с помощью модели `GPT-4o`.
- Запрос к `ImageAPI` для генерации изображения по переведенному описанию.
- Токены списываются за генерацию изображения.

### **Обработка Документов:**
- Загружаемые документы обрабатываются функцией `process_file_content`.
- Содержимое анализируется и передается для обработки AI-моделями.
- Токены списываются на анализ документа.

### **Обработка Аудио и Фотографий:**
- Аудио сообщения обрабатываются через модуль `AudioProcessor`.
- Фотографии анализируются и при необходимости отправляются обратно пользователю с комментариями AI.

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

## 8. **Административная Панель**

### **Возможности:**
- **Изменение Тарифов:** Администраторы могут изменять названия и описания тарифных планов.
- **Изменение Токенов Тарифов:** Возможность устанавливать количество ежедневных токенов для каждого тарифа.
- **Рассылка Сообщений:** Отправка сообщений всем пользователям определенного тарифа или всем пользователям.
- **Управление Чатами:** Добавление или удаление чатов из списка премиум-чатов.
- **Генерация Отчетов:** Создание отчетов о пользователях и их активности в формате Excel.

### **Замечания:**
- **Безопасность:** Убедитесь, что только администраторы имеют доступ к административным функциям. Это реализовано через проверку `message.from_user.id` в `config.ADMIN_IDS`.
- **Эффективность Рассылок:** При большом количестве пользователей рассылка может занять значительное время. Рассмотрите использование очередей или ограничений скорости (`rate limiting`) для предотвращения перегрузки Telegram API.

## 9. **Обработка Исключений и Логирование**

### **Логирование:**
- Используется модуль `logging` для записи информационных сообщений и ошибок.
- `logger.error` применяется для регистрации ошибок с детальной информацией (`exc_info=True`).

### **Обработка Исключений:**
- В большинстве асинхронных функций используется блок `try-except` для отлова и логирования исключений.
- Это помогает предотвратить сбои бота и информировать пользователей о возникших ошибках.

### **Замечания:**
- **Мониторинг:** Рассмотрите интеграцию с системами мониторинга или уведомлениями (например, Sentry) для отслеживания ошибок в реальном времени.
- **Чистота Логов:** Убедитесь, что логи не содержат чувствительных данных пользователей.

## 10. **Безопасность и Конфиденциальность**

### **Хранение Конфиденциальных Данных:**
- Токен бота и другие конфиденциальные данные импортируются из файла `config.py`.
- Важно обеспечить, чтобы файл `config.py` не попадал в систему контроля версий (например, через `.gitignore`).

### **Идентификаторы Администраторов и Каналов:**
- `config.ADMIN_IDS` и `config.CHANNEL_USERNAME` используются для определения администраторов и проверок подписки.
- Убедитесь, что эти значения корректны и защищены от несанкционированного изменения.

### **Обработка Пользовательских Данных:**
- База данных `user_data.db` хранит информацию о пользователях, включая их тарифы и токены.
- Рассмотрите шифрование базы данных или отдельных полей для повышения безопасности.

## 11. **Возможные Улучшения и Рекомендации**

1. **Масштабируемость:**
   - Переключение на более мощную СУБД при росте числа пользователей.
   - Использование распределенных систем для обработки тяжелых задач (например, генерация изображений).

2. **Кеширование:**
   - Внедрение кеширования для часто запрашиваемых данных (например, тарифные планы) для снижения нагрузки на базу данных.

3. **Тестирование:**
   - Реализация модульных и интеграционных тестов для обеспечения стабильности и корректности функциональности бота.

4. **Документация:**
   - Создание подробной документации кода и API для упрощения дальнейшего развития и поддержки.

5. **Оптимизация Работа с Токенами:**
   - Использование более точного измерения токенов (например, с помощью специализированных библиотек для подсчета токенов моделей AI).

6. **Интернационализация:**
   - Добавление поддержки нескольких языков для расширения аудитории бота.

7. **Асинхронные Библиотеки для БД:**
   - Использование асинхронных библиотек для работы с базой данных (например, `aiosqlite`) для улучшения производительности.

8. **Улучшение UI:**
   - Использование более сложных клавиатур и рекламных кнопок для улучшения взаимодействия с пользователем.

## 12. **Заключение**

Файл `bot.py` реализует комплексного Telegram-бота с обширным функционалом, включая взаимодействие с различными AI-моделями, управление пользователями через тарифные планы, генерацию и обработку медиафайлов, а также административные функции. Код демонстрирует хорошую структуру и использование современных подходов, таких как асинхронное программирование и FSM для управления состояниями. Однако есть несколько областей для улучшения, особенно касающихся масштабируемости, безопасности и оптимизации производительности. Рекомендуется провести дополнительное тестирование и рассмотреть внедрение предложенных улучшений для повышения надежности и эффективности бота.
User: осталась ошибка Ошибка в настройке API. Пожалуйста, свяжитесь с администратором. Я администратор в конфиге и я могу писать ии моделям, а обычные пользователи (тарифы free и premium) нет
и ошибка 

ERROR:__main__:Ошибка при переводе описания: GPT4OAPI.send_message() got an unexpected keyword argument 'message'
Traceback (most recent call last):
  File "/root/bigbot/bot.py", line 841, 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: GPT4OAPI.send_message() got an unexpected keyword argument 'message'
исправь все и напиши код полностью без сокращений , смотри внималтельно весь код, как я использую там вызов к gpt-4o
ПЕРВОЙ ОШИБКЕ ПРИОРИТЕТ