Добавь сообщение об ошибке если введена не верная команда

Код бота:
import logging
import requests
from telegram import (
    Update,
    InlineKeyboardButton,
    InlineKeyboardMarkup,
)
from telegram.ext import (
    Application,
    CommandHandler,
    MessageHandler,
    CallbackQueryHandler,
    ConversationHandler,
    ContextTypes,
    filters,
)
import validators

# Включаем логирование
logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    level=logging.INFO
)
logger = logging.getLogger(__name__)

# Замените на токен вашего бота
TELEGRAM_TOKEN = 'TOKEN'

# URL API для получения данных
API_BASE_URL = 'https://reestr.rublacklist.net/api/v3'
BLOCKED_DOMAINS_URL = f'{API_BASE_URL}/domains/'
BLOCKED_IPS_URL = f'{API_BASE_URL}/ips/'
STATISTICS_URL = f'{API_BASE_URL}/statistics/'
ORI_URL = f'{API_BASE_URL}/disseminators/'
DPI_URL = f'{API_BASE_URL}/dpi/'
RECORD_URL = f'{API_BASE_URL}/record/'  # Нужно добавить {ID}/
ALL_RECORDS_URL = f'{API_BASE_URL}/records/'

# Состояния для ConversationHandler
SEARCH = 1

async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Отправляет сообщение при вводе команды /start."""
    welcome_message = (
        '👋 Привет! Я бот для проверки сайтов и IP-адресов на блокировку в Роскомнадзоре.\n\n'
        '🔍 Вы можете отправить мне доменное имя или IP-адрес, и я проверю его статус.\n'
        '📋 Также доступны следующие команды:\n'
        '/help – показать меню помощи\n'
        '/search – получить информацию о блокировке по ID\n'
        '/dpi – показать список доменов, заблокированных по DPI\n'
        '/ori – показать список сайтов из реестра ОРИ\n'
        '/stats – показать общую статистику по ведомствам'
    )
    await update.message.reply_text(welcome_message)

async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Отправляет сообщение при вводе команды /help."""
    help_text = (
        'ℹ️ <b>Меню помощи</b>\n\n'
        '<b>📌 Как проверить домен или IP-адрес:</b>\n'
        '• Отправьте мне доменное имя (например, google.com) или IP-адрес (например, 8.8.8.8), и я проверю его статус блокировки.\n\n'
        '<b>📌 Доступные команды:</b>\n'
        '/start – начать использование бота\n'
        '/help – показать это меню помощи\n'
        '/search – получить информацию о блокировке по ID\n'
        '/dpi – показать список доменов, заблокированных по DPI\n'
        '/ori – показать список сайтов из реестра ОРИ\n'
        '/stats – показать общую статистику по ведомствам\n\n'
        '⚠️ Пожалуйста, убедитесь, что вы вводите корректное доменное имя или IP-адрес.'
    )
    await update.message.reply_text(help_text, parse_mode='HTML')

async def load_blocked_domains() -> set:
    """Загружает список заблокированных доменов и возвращает его."""
    try:
        response = requests.get(BLOCKED_DOMAINS_URL, timeout=10)
        response.raise_for_status()
        domains = response.json()
        logger.info('Список заблокированных доменов обновлён.')
        return set(domains)
    except Exception as e:
        logger.error(f'Ошибка при загрузке заблокированных доменов: {e}')
        return set()

async def load_blocked_ips() -> set:
    """Загружает список заблокированных IP и возвращает его."""
    try:
        response = requests.get(BLOCKED_IPS_URL, timeout=10)
        response.raise_for_status()
        ips = response.json()
        logger.info('Список заблокированных IP обновлён.')
        return set(ips)
    except Exception as e:
        logger.error(f'Ошибка при загрузке заблокированных IP: {e}')
        return set()

async def check_website(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Проверяет, заблокирован ли домен или IP."""
    user_input = update.message.text.strip().lower()

    # Определяем, является ли ввод IP или доменом
    if validators.ipv4(user_input) or validators.ipv6(user_input):
        ip = user_input
        blocked_ips = await load_blocked_ips()
        if ip in blocked_ips:
            await update.message.reply_text(
                f'⚠️ <b>IP:</b> <code>{ip}</code> <b>заблокирован в Роскомнадзоре.</b>', 
                parse_mode='HTML'
            )
        else:
            await update.message.reply_text(
                f'✅ <b>IP:</b> <code>{ip}</code> <b>не заблокирован в Роскомнадзоре.</b>', 
                parse_mode='HTML'
            )
    elif validators.domain(user_input):
        domain = user_input
        blocked_domains = await load_blocked_domains()
        if domain in blocked_domains:
            await update.message.reply_text(
                f'⚠️ <b>Домен:</b> <code>{domain}</code> <b>заблокирован в Роскомнадзоре.</b>', 
                parse_mode='HTML'
            )
        else:
            await update.message.reply_text(
                f'✅ <b>Домен:</b> <code>{domain}</code> <b>не заблокирован в Роскомнадзоре.</b>', 
                parse_mode='HTML'
            )
    else:
        await update.message.reply_text(
            '❌ Пожалуйста, отправьте корректное доменное имя или IP-адрес.'
        )

async def search_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Начинает диалог для поиска информации по ID блокировки."""
    await update.message.reply_text(
        '🔍 Пожалуйста, отправьте ID блокировки, которую вы хотите узнать.\nПример: <code>714872</code>',
        parse_mode='HTML'
    )
    return SEARCH

async def handle_search_id(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Обрабатывает введённый пользователем ID и возвращает информацию о блокировке."""
    search_id = update.message.text.strip()
    if not search_id.isdigit():
        await update.message.reply_text(
            '❌ ID блокировки должен содержать только цифры. Попробуйте снова.'
        )
        return ConversationHandler.END

    url = f'{RECORD_URL}{search_id}/'
    try:
        response = requests.get(url, timeout=10)
        response.raise_for_status()
        record = response.json()

        authority = record.get('authority', {}).get('name', 'Неизвестно')
        censorship_type = record.get('entryType', 'Неизвестно')  # Возможно, это тип блокировки
        date_added = record.get('applyDate', 'Не указано')  # Или 'appearDate' или 'decisionDate'
        decision_date = record.get('decisionDate', 'Не указано')
        appear_date = record.get('appearDate', 'Не указано')
        domains = record.get('domains', [])
        ips = record.get('ips', [])
        urls = record.get('urls', [])
        comment = record.get('comment', 'Нет комментариев')

        text = (
            f'📄 <b>Информация о блокировке (ID: {search_id}):</b>\n\n'
            f'<b>Ведомство:</b> {authority}\n'
            f'<b>Тип блокировки:</b> {censorship_type}\n'
            f'<b>Дата принятия решения:</b> {decision_date}\n'
            f'<b>Дата применения:</b> {date_added}\n'
            f'<b>Дата появления:</b> {appear_date}\n'
        )

        if domains:
            domains_text = ', '.join(domains)
            text += f'<b>Домен(ы):</b> <code>{domains_text}</code>\n'

        if ips:
            ips_text = ', '.join(ips)
            text += f'<b>IP-адреса:</b> <code>{ips_text}</code>\n'

        if urls:
            urls_text = ', '.join(urls)
            text += f'<b>URL-адреса:</b> {urls_text}\n'

        text += f'<b>Комментарий:</b> {comment}'

        await update.message.reply_text(text, parse_mode='HTML')
    except requests.HTTPError as http_err:
        if http_err.response.status_code == 404:
            await update.message.reply_text('❌ Блокировка с таким ID не найдена.')
        else:
            logger.error(f'HTTP ошибка при поиске ID {search_id}: {http_err}')
            await update.message.reply_text('❌ Произошла ошибка при получении информации.')
    except Exception as e:
        logger.error(f'Ошибка при поиске ID {search_id}: {e}')
        await update.message.reply_text('❌ Произошла ошибка при получении информации.')

    return ConversationHandler.END

async def cancel_search(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Отменяет диалог поиска."""
    await update.message.reply_text(
        '🔄 Поиск отменён.',
        reply_markup=None
    )
    return ConversationHandler.END

async def stats_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Обработчик команды /stats."""
    try:
        response = requests.get(STATISTICS_URL, timeout=10)
        response.raise_for_status()
        statistics_data = response.json()
    except Exception as e:
        logger.error(f'Ошибка при загрузке статистики: {e}')
        await update.message.reply_text('❌ Не удалось загрузить статистику.')
        return

    if not isinstance(statistics_data, list):
        await update.message.reply_text('❌ Некорректный формат данных статистики.')
        return

    context.user_data['statistics_data'] = statistics_data
    page = 0
    await send_statistics_page(update, context, page)

async def send_statistics_page(update: Update, context: ContextTypes.DEFAULT_TYPE, page: int):
    """Отправляет страницу статистики блокировок."""
    statistics_data = context.user_data.get('statistics_data', [])
    if not statistics_data:
        await update.message.reply_text('❌ Статистика недоступна.')
        return

    agencies = statistics_data  # Это список словарей
    items_per_page = 1  # Показываем по одной службе на странице
    total_pages = len(agencies)

    if page < 0 or page >= total_pages:
        await update.message.reply_text('❌ Неверная страница.')
        return

    agency_data = agencies[page]
    # Извлекаем данные
    authority = agency_data.get('authority', 'Неизвестно')
    authority_id = agency_data.get('authority_id', 'Неизвестно')
    blocked_company_count = agency_data.get('blocked_company_count', 0)
    unblocked_company_count = agency_data.get('unblocked_company_count', 0)
    blocked_url_count = agency_data.get('blocked_count', 0)
    unblocked_url_count = agency_data.get('unblocked_count', 0)
    blocked_ip_count = agency_data.get('blocked_illegally_count', 0)

    text = (
        f'📊 <b>Статистика блокировок</b> (Страница {page + 1} из {total_pages}):\n\n'
        f'<b>Ведомство:</b> {authority}\n'
        f'<b>ID Ведомства:</b> {authority_id}\n'
        f'<b>Заблокированных компаний:</b> {blocked_company_count}\n'
        f'<b>Разблокированных компаний:</b> {unblocked_company_count}\n'
        f'<b>Заблокированных сервисов:</b> {blocked_url_count}\n'
        f'<b>Разблокированных сервисов:</b> {unblocked_url_count}\n'
        f'<b>Заблокированных нелегальных компаний:</b> {blocked_ip_count}\n'
    )

    buttons = []
    if page > 0:
        buttons.append(InlineKeyboardButton('⬅️ Назад', callback_data=f'stats_{page - 1}'))
    if page < total_pages - 1:
        buttons.append(InlineKeyboardButton('Вперёд ➡️', callback_data=f'stats_{page + 1}'))
    keyboard = InlineKeyboardMarkup([buttons]) if buttons else None

    if update.callback_query:
        await update.callback_query.edit_message_text(
            text=text, reply_markup=keyboard, parse_mode='HTML'
        )
        await update.callback_query.answer()
    else:
        await update.message.reply_text(
            text=text, reply_markup=keyboard, parse_mode='HTML'
        )

async def ori_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Обработчик команды /ori."""
    try:
        response = requests.get(ORI_URL, timeout=10)
        response.raise_for_status()
        ori_list = response.json()
    except Exception as e:
        logger.error(f'Ошибка при загрузке списка ОРИ: {e}')
        await update.message.reply_text('❌ Не удалось загрузить список ОРИ.')
        return

    # Проверяем формат данных
    if not isinstance(ori_list, list):
        await update.message.reply_text('❌ Некорректный формат данных ОРИ.')
        return

    # Проверка первого элемента, чтобы определить формат
    if len(ori_list) == 0:
        await update.message.reply_text('📰 <b>Список ОРИ пуст.</b>', parse_mode='HTML')
        return

    first_item = ori_list[0]
    if isinstance(first_item, dict):
        # Если элементы списка — словари
        context.user_data['ori_list'] = ori_list
    elif isinstance(first_item, str):
        # Если элементы списка — строки (доменные имена)
        # Преобразуем в формат списка словарей для унинообразия
        ori_list_transformed = [{'domain': item} for item in ori_list]
        context.user_data['ori_list'] = ori_list_transformed
    else:
        await update.message.reply_text('❌ Неизвестный формат данных ОРИ.')
        return

    page = 0
    await send_ori_page(update, context, page)

async def send_ori_page(update: Update, context: ContextTypes.DEFAULT_TYPE, page: int):
    """Отправляет страницу списка ОРИ."""
    ori_list = context.user_data.get('ori_list', [])
    if not ori_list:
        await update.message.reply_text('❌ Список ОРИ недоступен.')
        return

    items_per_page = 20  # Измените по необходимости
    total_pages = (len(ori_list) - 1) // items_per_page + 1

    if page < 0 or page >= total_pages:
        await update.message.reply_text('❌ Неверная страница.')
        return

    start = page * items_per_page
    end = start + items_per_page
    page_ori = ori_list[start:end]
    text = f'📰 <b>Список ОРИ</b> (Страница {page + 1} из {total_pages}):\n\n'

    for item in page_ori:
        # Обработка каждого элемента в зависимости от его структуры
        if isinstance(item, dict):
            domain = item.get('domain', 'Не указано')
            text += f'• <code>{domain}</code>\n'
        else:
            # Если элемент — строка (домен)
            text += f'• <code>{item}</code>\n'

    # Проверяем, не превышает ли текст ограничение в 4096 символов
    if len(text) > 3900:
        text = text[:3900] + '...\n[Сообщение обрезано]'

    buttons = []
    if page > 0:
        buttons.append(InlineKeyboardButton('⬅️ Назад', callback_data=f'ori_{page - 1}'))
    if page < total_pages - 1:
        buttons.append(InlineKeyboardButton('Вперёд ➡️', callback_data=f'ori_{page + 1}'))
    keyboard = InlineKeyboardMarkup([buttons]) if buttons else None

    if update.callback_query:
        await update.callback_query.edit_message_text(
            text=text, reply_markup=keyboard, parse_mode='HTML'
        )
        await update.callback_query.answer()
    else:
        await update.message.reply_text(
            text=text, reply_markup=keyboard, parse_mode='HTML'
        )

async def dpi_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Обработчик команды /dpi."""
    try:
        response = requests.get(DPI_URL, timeout=10)
        response.raise_for_status()
        dpi_list = response.json()
    except Exception as e:
        logger.error(f'Ошибка при загрузке списка DPI: {e}')
        await update.message.reply_text('❌ Не удалось загрузить список DPI.')
        return

    # Проверяем формат данных
    if not isinstance(dpi_list, list):
        await update.message.reply_text('❌ Некорректный формат данных DPI.')
        return

    if len(dpi_list) == 0:
        await update.message.reply_text('🚫 <b>Список DPI пуст.</b>', parse_mode='HTML')
        return

    context.user_data['dpi_list'] = dpi_list
    page = 0
    await send_dpi_page(update, context, page)

async def send_dpi_page(update: Update, context: ContextTypes.DEFAULT_TYPE, page: int):
    """Отправляет страницу списка DPI."""
    dpi_list = context.user_data.get('dpi_list', [])
    if not dpi_list:
        await update.message.reply_text('🚫 Список DPI недоступен.')
        return

    items_per_page = 20  # Измените по необходимости
    total_pages = (len(dpi_list) - 1) // items_per_page + 1

    if page < 0 or page >= total_pages:
        await update.message.reply_text('❌ Неверная страница.')
        return

    start = page * items_per_page
    end = start + items_per_page
    page_dpi = dpi_list[start:end]
    text = f'🚫 <b>Список доменов, заблокированных по DPI</b> (Страница {page + 1} из {total_pages}):\n\n'

    for item in page_dpi:
        name = item.get('name', 'Не указано')
        domains = item.get('domains', [])
        restriction = item.get('restriction', {}).get('code', 'Неизвестно') if item.get('restriction') else 'Неизвестно'
        domains_text = ', '.join(domains) if domains else 'Не указано'
        text += (
            f'• <b>Название:</b> {name}\n'
            f'  <b>Домен(ы):</b> <code>{domains_text}</code>\n'
            f'  <b>Тип блокировки:</b> {restriction}\n\n'
        )

    # Проверяем, не превышает ли текст ограничение в 4096 символов
    if len(text) > 3900:
        text = text[:3900] + '...\n[Сообщение обрезано]'

    buttons = []
    if page > 0:
        buttons.append(InlineKeyboardButton('⬅️ Назад', callback_data=f'dpi_{page - 1}'))
    if page < total_pages - 1:
        buttons.append(InlineKeyboardButton('Вперёд ➡️', callback_data=f'dpi_{page + 1}'))
    keyboard = InlineKeyboardMarkup([buttons]) if buttons else None

    if update.callback_query:
        await update.callback_query.edit_message_text(
            text=text, reply_markup=keyboard, parse_mode='HTML'
        )
        await update.callback_query.answer()
    else:
        await update.message.reply_text(
            text=text, reply_markup=keyboard, parse_mode='HTML'
        )

async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
    """Обработчик нажатий на кнопки."""
    query = update.callback_query
    data = query.data

    if data.startswith('stats_'):
        try:
            page = int(data.split('_')[1])
            await send_statistics_page(update, context, page)
        except (IndexError, ValueError):
            await query.answer('❌ Некорректная команда.')
    elif data.startswith('ori_'):
        try:
            page = int(data.split('_')[1])
            await send_ori_page(update, context, page)
        except (IndexError, ValueError):
            await query.answer('❌ Некорректная команда.')
    elif data.startswith('dpi_'):
        try:
            page = int(data.split('_')[1])
            await send_dpi_page(update, context, page)
        except (IndexError, ValueError):
            await query.answer('❌ Некорректная команда.')
    else:
        await query.answer('❌ Неизвестная команда.')

async def error_handler(update: object, context: ContextTypes.DEFAULT_TYPE):
    """Логирует ошибки, вызванные обновлениями."""
    logger.error(msg="Исключение при обработке обновления:", exc_info=context.error)

def main():
    """Запускает бота."""
    # Создаём объект приложения
    application = Application.builder().token(TELEGRAM_TOKEN).build()

    # Обработчики команд
    application.add_handler(CommandHandler("start", start_command))
    application.add_handler(CommandHandler("help", help_command))

    # Обработчик команды /search с использованием ConversationHandler
    search_conv_handler = ConversationHandler(
        entry_points=[CommandHandler('search', search_command)],
        states={
            SEARCH: [MessageHandler(filters.TEXT & ~filters.COMMAND, handle_search_id)]
        },
        fallbacks=[CommandHandler('cancel', cancel_search)],
        allow_reentry=True
    )
    application.add_handler(search_conv_handler)

    # Другие команды
    application.add_handler(CommandHandler("stats", stats_command))
    application.add_handler(CommandHandler("ori", ori_command))
    application.add_handler(CommandHandler("dpi", dpi_command))

    # Обработчик сообщений с текстом (домен или IP)
    application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, check_website))

    # Обработчик нажатий на кнопки
    application.add_handler(CallbackQueryHandler(button_callback))

    # Логирование всех ошибок
    application.add_error_handler(error_handler)

    # Запускаем бота
    application.run_polling()

if __name__ == '__main__':
    main()