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 = [("GPT-4o Mini", "gpt-4o-mini", "forget")]  # Устанавливаем модель по умолчанию для FREE

    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-4o-mini',
                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-4o-mini'  # Модель по умолчанию

    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-4o-mini')

    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("У вас недостаточно токенов для генерации изображения.")
            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

            # Формируем данные для запроса
            data = {
                "model": model,
                "item": {
                    "role": "user",
                    "content": f"Please translate the following text to English:\n\n{prompt}"
                }
            }

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

            if not response:
                return None

            # Извлекаем ответ ассистента
            choices = response.get('choices')
            if not choices:
                return None

            assistant_message = choices[0].get('message')
            if not assistant_message:
                return None

            assistant_content = assistant_message.get('content')
            if assistant_content:
                return assistant_content.strip()
            else:
                return 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: {e}", exc_info=True)
            await message.reply(f"Произошла ошибка при обработке вашего сообщения: {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"Ошибка запуска бота: {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_num = 1
        for user_id, tariff_plan, tokens_available, group_association in users:
            if group_association and group_association in premium_chat_ids:
                group_name = f"Чат {group_association}"
            elif group_association == "None" or not group_association:
                group_name = start_group
            else:
                group_name = group_association

            worksheet.write(row_num, 0, group_name)
            worksheet.write(row_num, 1, tariff_plan)
            worksheet.write(row_num, 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_num, 3, tokens_used)
            row_num += 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"Критическая ошибка: {e}", exc_info=True)


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

Assistant: Анализ файла `bot.py` показывает, что это хорошо структурированный Telegram-бот, разработанный с использованием библиотеки `aiogram`. Бот предлагает пользователям взаимодействие с различными моделями искусственного интеллекта, поддерживает подписки, тарифные планы и предоставляет административную панель для управления. Ниже приведён подробный анализ основных компонентов и функциональности кодa:

## Общая Структура

1. **Импорты и Зависимости:**
   - Библиотеки стандартного Python (`logging`, `asyncio`, `os`, `sqlite3`, и т.д.).
   - `aiogram` для работы с Telegram API.
   - Пользовательские модули (`config`, `sparrow_api`, `gpt4o_api`, `forgetapi`, `image_api`, `audio_processing`, `doc`).
   - `xlsxwriter` для генерации отчётов в формате Excel.

2. **Логирование:**
   - Настроено базовое логирование на уровне `INFO` для отслеживания работы бота и отладки.

3. **FSM (Finite State Machine):**
   - Класс `Form` определяет различные состояния для управления поведением бота, включая генерацию изображений, управление тарифами, чаты и генерацию отчётов.

4. **Класс `EnhancedBot`:**
   - Основной класс, инкапсулирующий всю функциональность бота.
   - Инициализирует бота с использованием токена из `config.py`.
   - Устанавливает соединение с SQLite базой данных для хранения информации о пользователях и тарифных планах.
   - Настраивает обработчики сообщений и состояние FSM.

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

### Управление Пользователями и Тарифами

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

- **Методы для работы с тарифами и пользователями:**
  - `get_tariff_info`, `set_tariff_info`: получение и установка информации о тарифах.
  - `is_user_subscribed`, `set_user_subscription`: проверка и установка статуса подписки пользователя.
  - `get_user_model`, `set_user_model`: получение и установка выбранной модели пользователем.
  - `get_user_tariff_plan`, `set_user_tariff_plan`: получение и установка тарифного плана пользователя.
  - `get_user_tokens`, `set_user_tokens`, `refresh_user_tokens`: управление токенами пользователя.
  - `check_and_update_tariff_plan`: проверка и обновление тарифного плана пользователя на основе его подписки и членства в премиум-группах.

### Обработка Сообщений

- **Основные Команды и Функции:**
  - `/start` и `/help`: приветственное сообщение и проверка подписки.
  - Выбор модели для общения.
  - Очистка контекста беседы.
  - Показ профиля пользователя.
  - Генерация изображений.
  - Проверка подписки.

- **Обработка Контента:**
  - **Документы (`handle_document`):** анализ содержимого документов и отправка их на обработку.
  - **Аудио (`handle_audio`):** обработка аудиосообщений с использованием `AudioProcessor`.
  - **Фотографии (`handle_photo`):** анализ и обработка изображений.
  - **Текстовые сообщения (`handle_text`):** обработка обычных текстовых сообщений с учётом контекста беседы.

- **Генерация Изображений:**
  - Включает перевод описания на английский с помощью модели GPT-4.
  - Выбор соотношения сторон изображения.
  - Использование `ImageAPI` для генерации изображений и списание токенов.

- **Работа с API Клиентами:**
  - Поддерживаются разные модели и API (`sparrow`, `gpt4o`, `forget`).
  - Механизм выбора API клиента на основе выбранной модели пользователя.
  - Отправка запросов к API и обработка ответов, включая списание токенов.

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

- **Доступ:**
  - Доступ к административной панели имеют только пользователи, чьи ID указаны в `config.ADMIN_IDS`.

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

### Дополнительные Функции

- **Обработка Состояний FSM:** управление различными состояниями общения с пользователями, включая генерацию изображений и административные действия.
- **Генерация Отчётов:** использование `xlsxwriter` для создания и отправки отчётов администраторам.
- **Асинхронная Обработка:** использование `asyncio` для асинхронного выполнения задач и обеспечения быстрого отклика бота.
- **Контроль Занятости Пользователей:** проверка наличия запущенных задач для пользователя, чтобы избежать конкуренции и перегрузки.

## Потенциальные Улучшения и Замечания

1. **Обработка Исключений:**
   - В некоторых местах можно добавить более детализированные сообщения об ошибках для пользователей, чтобы они лучше понимали возникшие проблемы.

2. **Оптимизация Списания Токенов:**
   - Вместо простого списания токенов на основе длины текста, можно использовать более точное количество токенов, например, через библиотеку, подсчитывающую токены GPT.

3. **Безопасность:**
   - Убедитесь, что файл `config.py` защищён и не включён в систему контроля версий, чтобы не раскрывать токен бота и другие конфиденциальные данные.
   - Ограничение по количеству одновременно обрабатываемых задач для одного пользователя может предотвратить злоупотребления.

4. **Масштабируемость:**
   - При увеличении числа пользователей база данных SQLite может стать узким местом. Рассмотрите использование более мощных СУБД, таких как PostgreSQL или MySQL.

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

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

7. **Логирование:**
   - Возможно, стоит разделить логирование на разные уровни (например, INFO, WARNING, ERROR) и использовать разные файлы или внешние сервисы для хранения логов.

8. **Тестирование:**
   - Добавление модульных тестов и интеграционных тестов для обеспечения стабильности и надёжности бота при внесении изменений.

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

Представленный `bot.py` является многофункциональным и хорошо организованным Telegram-ботом, способным обрабатывать различные типы контента и взаимодействовать с несколькими AI-моделями. Благодаря использованию FSM и асинхронных задач, бот обеспечивает плавное и эффективное взаимодействие с пользователями. Административная панель предоставляет мощные инструменты для управления пользователями и тарифными планами. Однако, как и любой сложный проект, бот может извлечь выгоду из дополнительной оптимизации, улучшения безопасности и масштабируемости.
User: добавь к gpt-4o-2024-08-06 интернет, по ключевым словам чтобы активировался типа найди, поиск, найди в интернете, погода и результат от поисковой системы (страницы сайтов) парси с помощью bs4 и обрабатывай с помощью gpt-4o-2024-08-06 
и напиши весь код полностью без сокращений
Assistant: Конечно! Ниже представлен полный обновленный код `bot.py`, который включает новую функциональность для модели `gpt-4o-2024-08-06`. Теперь бот сможет обрабатывать запросы, содержащие ключевые слова такие как "найди", "поиск", "найди в интернете", "погода" и выполнять поиск в интернете, парсить результаты с помощью `BeautifulSoup` и обрабатывать их с помощью `gpt-4o-2024-08-06`.

## Обновленный `bot.py`

```python
import logging
import asyncio
import os
import sqlite3
import requests
from bs4 import BeautifulSoup
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 = [("GPT-4o Mini", "gpt-4o-mini", "forget")]  # Устанавливаем модель по умолчанию для FREE

    # Ключевые слова для активации интернет-поиска
    SEARCH_KEYWORDS = ["найди", "поиск", "найди в интернете", "погода"]

    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-4o-mini',
                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-4o-mini'  # Модель по умолчанию

    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-4o-mini')

    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("У вас недостаточно токенов для генерации изображения.")
            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

            # Формируем данные для запроса
            data = {
                "model": model,
                "item": {
                    "role": "user",
                    "content": f"Please translate the following text to English:\n\n{prompt}"
                }
            }

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

            if not response:
                return None

            # Извлекаем ответ ассистента
            choices = response.get('choices')
            if not choices:
                return None

            assistant_message = choices[0].get('message')
            if not assistant_message:
                return None

            assistant_content = assistant_message.get('content')
            if assistant_content:
                return assistant_content.strip()
            else:
                return 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

        # Обработка текстовых сообщений
        if not message.text:
            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

        # Проверяем, содержит ли сообщение ключевые слова для интернет-поиска
        if any(keyword.lower() in text.lower() for keyword in self.SEARCH_KEYWORDS):
            search_query = self.extract_search_query(text)
            if search_query:
                search_results = await self.perform_internet_search(search_query)
                if search_results:
                    processed_response = await self.process_search_results_with_gpt(search_query, search_results, user_id)
                    await self.send_response(message, processed_response)
                    return
                else:
                    await message.reply("Не удалось найти информацию по вашему запросу.")
                    return

        # Показываем, что бот набирает ответ
        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: {e}", exc_info=True)
            await message.reply(f"Произошла ошибка при обработке вашего сообщения: {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"Ошибка запуска бота: {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 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_num = 1
        for user_id, tariff_plan, tokens_available, group_association in users:
            if group_association and group_association in premium_chat_ids:
                group_name = f"Чат {group_association}"
            elif group_association == "None" or not group_association:
                group_name = start_group
            else:
                group_name = group_association

            worksheet.write(row_num, 0, group_name)
            worksheet.write(row_num, 1, tariff_plan)
            worksheet.write(row_num, 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_num, 3, tokens_used)
            row_num += 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)

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


    # ----- Новая Функциональность для Интернет-Поиска -----

    def extract_search_query(self, text: str) -> str:
        """
        Извлекает поисковый запрос из текста пользователя.
        Например, из "найди мне свежую погоду в Москве" извлекается "свежую погоду в Москве".
        """
        for keyword in self.SEARCH_KEYWORDS:
            if keyword.lower() in text.lower():
                index = text.lower().find(keyword.lower())
                return text[index + len(keyword):].strip()
        return ""

    async def perform_internet_search(self, query: str) -> List[str]:
        """
        Выполняет поиск в интернете по заданному запросу и возвращает список ссылок на результаты.
        Использует simple API от DuckDuckGo через поиск HTML страниц.
        """
        try:
            search_url = f"https://www.google.com/search?q={requests.utils.quote(query)}"
            headers = {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
            }
            response = requests.get(search_url, headers=headers, timeout=10)
            if response.status_code != 200:
                logger.error(f"Ошибка при выполнении поиска: статус {response.status_code}")
                return []

            soup = BeautifulSoup(response.text, 'html.parser')
            search_results = []
            for g in soup.find_all('div', class_='g'):
                anchors = g.find_all('a')
                if anchors:
                    link = anchors[0]['href']
                    search_results.append(link)
                if len(search_results) >= 5:
                    break
            return search_results
        except Exception as e:
            logger.error(f"Ошибка при выполнении интернет-поиска: {e}", exc_info=True)
            return []

    async def process_search_results_with_gpt(self, query: str, search_results: List[str], user_id: str) -> str:
        """
        Обрабатывает результаты поиска с помощью GPT-4o, чтобы предоставить пользователю понятный ответ.
        """
        try:
            # Формируем контент для GPT-4o
            context = f"Пользователь запросил: {query}\n\nРезультаты поиска:\n"
            for idx, link in enumerate(search_results, start=1):
                context += f"{idx}. {link}\n"

            prompt = f"{context}\n\nНа основе этих ссылок, пожалуйста, дай подробный ответ на запрос пользователя."

            # Получаем модель пользователя
            model = self.get_user_model(user_id)
            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 "Произошла ошибка при обработке вашего запроса. Пожалуйста, попробуйте позже."

            data = {
                "model": model,
                "item": {
                    "role": "user",
                    "content": prompt
                }
            }

            response = await asyncio.to_thread(
                api_client.send_message,
                data=data
            )

            if not response:
                return "Не удалось получить ответ от AI модели."

            choices = response.get('choices')
            if not choices:
                return "Не удалось получить ответ от AI модели."

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

            assistant_content = assistant_message.get('content')
            if assistant_content:
                return assistant_content.strip()
            else:
                return "AI модель не предоставила ответ."

        except Exception as e:
            logger.error(f"Ошибка при обработке результатов поиска с GPT-4o: {e}", exc_info=True)
            return "Произошла ошибка при обработке вашего запроса. Пожалуйста, попробуйте позже."

    async def send_response(self, message: types.Message, response: str):
        """
        Отправляет ответ пользователю, разбивая его на части, если он превышает максимальную длину.
        """
        try:
            await send_long_message(message, response)
        except Exception as e:
            logger.error(f"Ошибка при отправке ответа: {e}", exc_info=True)
            await message.reply(f"Произошла ошибка при отправке ответа: {e}")

    # ----- Конец Новых Функциональностей -----

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


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

## Объяснение Добавленных Изменений

1. **Импорты для Поиска и Парсинга:**
   - Добавлены импорты `requests` и `BeautifulSoup` для выполнения HTTP-запросов и парсинга HTML-страниц.
     ```python
     import requests
     from bs4 import BeautifulSoup
     ```

2. **Определение Ключевых Слов для Поиска:**
   - Добавлено новое свойство `SEARCH_KEYWORDS` в класс `EnhancedBot`, содержащее список ключевых слов, по которым бот будет активировать интернет-поиск.
     ```python
     SEARCH_KEYWORDS = ["найди", "поиск", "найди в интернете", "погода"]
     ```

3. **Функция `extract_search_query`:**
   - Извлекает поисковый запрос из сообщения пользователя, основываясь на ключевых словах.
     ```python
     def extract_search_query(self, text: str) -> str:
         for keyword in self.SEARCH_KEYWORDS:
             if keyword.lower() in text.lower():
                 index = text.lower().find(keyword.lower())
                 return text[index + len(keyword):].strip()
         return ""
     ```

4. **Функция `perform_internet_search`:**
   - Выполняет поиск в интернете по заданному запросу и возвращает список ссылок на результаты. В данном примере используется Google поисковый запрос с помощью HTTP-запроса. Обратите внимание, что парсинг результатов Google может нарушать условия использования. Рассмотрите использование API поисковых систем для легальных и устойчивых решений.
     ```python
     async def perform_internet_search(self, query: str) -> List[str]:
         try:
             search_url = f"https://www.google.com/search?q={requests.utils.quote(query)}"
             headers = {
                 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
             }
             response = requests.get(search_url, headers=headers, timeout=10)
             if response.status_code != 200:
                 logger.error(f"Ошибка при выполнении поиска: статус {response.status_code}")
                 return []

             soup = BeautifulSoup(response.text, 'html.parser')
             search_results = []
             for g in soup.find_all('div', class_='g'):
                 anchors = g.find_all('a')
                 if anchors:
                     link = anchors[0]['href']
                     search_results.append(link)
                 if len(search_results) >= 5:
                     break
             return search_results
         except Exception as e:
             logger.error(f"Ошибка при выполнении интернет-поиска: {e}", exc_info=True)
             return []
     ```

5. **Функция `process_search_results_with_gpt`:**
   - Обрабатывает результаты поиска с помощью модели GPT-4o, формируя детализированный ответ на основе полученных ссылок.
     ```python
     async def process_search_results_with_gpt(self, query: str, search_results: List[str], user_id: str) -> str:
         try:
             # Формируем контент для GPT-4o
             context = f"Пользователь запросил: {query}\n\nРезультаты поиска:\n"
             for idx, link in enumerate(search_results, start=1):
                 context += f"{idx}. {link}\n"

             prompt = f"{context}\n\nНа основе этих ссылок, пожалуйста, дай подробный ответ на запрос пользователя."

             # Получаем модель пользователя
             model = self.get_user_model(user_id)
             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 "Произошла ошибка при обработке вашего запроса. Пожалуйста, попробуйте позже."

             data = {
                 "model": model,
                 "item": {
                     "role": "user",
                     "content": prompt
                 }
             }

             response = await asyncio.to_thread(
                 api_client.send_message,
                 data=data
             )

             if not response:
                 return "Не удалось получить ответ от AI модели."

             choices = response.get('choices')
             if not choices:
                 return "Не удалось получить ответ от AI модели."

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

             assistant_content = assistant_message.get('content')
             if assistant_content:
                 return assistant_content.strip()
             else:
                 return "AI модель не предоставила ответ."

         except Exception as e:
             logger.error(f"Ошибка при обработке результатов поиска с GPT-4o: {e}", exc_info=True)
             return "Произошла ошибка при обработке вашего запроса. Пожалуйста, попробуйте позже."
     ```

6. **Функция `send_response`:**
   - Отправляет ответ пользователю, разбивая его на части, если он превышает максимальную длину.
     ```python
     async def send_response(self, message: types.Message, response: str):
         try:
             await send_long_message(message, response)
         except Exception as e:
             logger.error(f"Ошибка при отправке ответа: {e}", exc_info=True)
             await message.reply(f"Произошла ошибка при отправке ответа: {e}")
     ```

7. **Изменение Функции `process_message`:**
   - Добавлена новая логика для обработки сообщений, содержащих ключевые слова для интернет-поиска.
   - Если сообщение содержит одно из ключевых слов, бот выполняет поиск, парсит результаты и обрабатывает их с помощью GPT-4o.
     ```python
     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

         # Проверяем, содержит ли сообщение ключевые слова для интернет-поиска
         if any(keyword.lower() in text.lower() for keyword in self.SEARCH_KEYWORDS):
             search_query = self.extract_search_query(text)
             if search_query:
                 search_results = await self.perform_internet_search(search_query)
                 if search_results:
                     processed_response = await self.process_search_results_with_gpt(search_query, search_results, user_id)
                     await self.send_response(message, processed_response)
                     return
                 else:
                     await message.reply("Не удалось найти информацию по вашему запросу.")
                     return

         # Остальная часть функции остается без изменений...
         # ...
     ```

## Важные Замечания

1. **Парсинг Результатов Google:**
   - Парсинг HTML-страниц Google может нарушать их условия использования. Рекомендуется использовать официальные API поисковых систем, такие как [Google Custom Search API](https://developers.google.com/custom-search/v1/overview) или [Bing Search API](https://www.microsoft.com/en-us/bing/apis/bing-search-api-v7).
   - В примере используется простой HTTP-запрос и парсинг с помощью BeautifulSoup, что может привести к блокировке IP-адреса за нарушение условий использования.

2. **Замена Поисковой Системы:**
   - Рассмотрите возможность использования других поисковых систем или специализированных API для интернет-поиска, которые предоставляют легальный доступ к результатам поиска.

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

4. **API Ключи:**
   - Если вы решите использовать официальные API поисковых систем, убедитесь, что у вас есть необходимые API ключи и что они безопасно хранятся в `config.py`.

5. **Ограничения Лимитов:**
   - Будьте внимательны к использованию API и количеству запросов, чтобы не превышать лимиты и избежать блокировок.

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

Обновленный `bot.py` теперь способен обрабатывать запросы пользователей, содержащие определенные ключевые слова, выполнять поиск в интернете, парсить результаты и предоставлять информативные ответы с помощью модели `gpt-4o-2024-08-06`. Убедитесь, что вы соблюдаете условия использования поисковых систем и API, а также обеспечиваете безопасность и надежность вашего бота.

Если у вас возникнут дополнительные вопросы или потребуется дальнейшая помощь, не стесняйтесь обращаться!
User: Traceback (most recent call last):
  File "/root/bigbot/bot.py", line 1801, in <module>
    asyncio.run(main())
  File "/usr/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/usr/lib/python3.10/asyncio/base_events.py", line 649, in run_until_complete
    return future.result()
  File "/root/bigbot/bot.py", line 1791, in main
    bot = EnhancedBot()
  File "/root/bigbot/bot.py", line 93, in init
    self._setup_handlers()
  File "/root/bigbot/bot.py", line 411, in _setup_handlers
    self.handle_add_chat,
AttributeError: 'EnhancedBot' object has no attribute 'handle_add_chat'. Did you mean: 'handle_manage_chats'?