User:  User name is awinic. Current Date is Wednesday, October 23, 2024, current time is 09:52:23.import os
import re
import json
import logging
import asyncio
import aiohttp
import html
import os
import string
import random
import shodan
from dotenv import load_dotenv
from aiogram import types, Router, filters
from aiogram.filters import Command
from aiogram.types import FSInputFile
import subprocess
import aiofiles

# Настраиваем логирование (переносим сюда, если используется отдельно)
logger = logging.getLogger(__name__)

# Создаем маршрутизатор
router = Router()

DEFAULT_PASSWORD_LENGTH = 12

# Загрузка переменных из .env файла
load_dotenv()

# Получение Shodan API ключа из переменных окружения
SHODAN_API_KEY = os.getenv('SHODAN_API_KEY')

# Проверка наличия API-ключа
if not SHODAN_API_KEY:
    raise RuntimeError("Не удалось загрузить API-ключ Shodan. Убедитесь, что ваш файл .env настроен правильно.")

# Инициализация клиента Shodan
shodan_client = shodan.Shodan(SHODAN_API_KEY)


# Функция для выполнения системной команды
async def run_command(command: str) -> str:
    process = await asyncio.create_subprocess_shell(
        command,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE
    )
    stdout, stderr = await process.communicate()
    return stdout.decode() if stdout else stderr.decode()

# Обрабатывает команду /start
@router.message(Command("start"))
async def cmd_start(message: types.Message):
    await message.answer(
        "Привет! Я бот, который может выполнять команды dig, whois, traceroute, ping, curl, wget и другие.\n"
        "Используйте следующие команды:\n"
        "/dig <домен> - Выполнить команду dig для домена\n"
        "/whois <домен> - Получить информацию whois для домена\n"
        "/traceroute <хост> - Выполнить команду traceroute для хоста\n"
        "/ping <хост> - Проверить доступность хоста\n"
        "/curl <URL> - Выполнить запрос curl\n"
        "/wget <URL> - Скачать файл по URL\n"
        "/myip - Узнать свой IP-адрес\n"
        "/ip <IP-адрес> - Получить информацию об IP-адресе\n"
        "/nmap <хост> - Получить открытые порты хоста\n"
        "/sslscan <домен> - Просканировать SSL-сертификаты домена\n"
        "/shodan <IP-адрес> - Получить информацию об IP-адресе из Shodan\n"
        "/shodan_dns <домен> - Получить информацию о DNS домена через Shodan\n"
        "/generate_password [длина] - Сгенерировать случайный пароль (по умолчанию 12 символов)"
    )
    logger.info(f"Команда /start от пользователя {message.from_user.id}")

# Функция для симуляции "печатает" активность
async def send_typing_action(message: types.Message, action_type='typing'):
    await message.bot.send_chat_action(message.chat.id, action=action_type)


def generate_random_password(length=DEFAULT_PASSWORD_LENGTH):
    characters = string.ascii_letters + string.digits + string.punctuation
    password = ''.join(random.choice(characters) for i in range(length))
    return password

# Обработчик команды /generate_password
@router.message(Command("generate_password"))
async def cmd_generate_password(message: types.Message):
    # Определяем длину пароля из сообщения, если указана
    args = message.text.split()
    if len(args) > 1 and args[1].isdigit():
        length = int(args[1])
        if length < 6:
            await message.answer("Пароль должен быть длиной не менее 6 символов. Попробуйте еще раз.")
            return
    else:
        length = DEFAULT_PASSWORD_LENGTH

    # Генерируем пароль
    password = generate_random_password(length)
    await message.answer(f"Ваш случайный пароль: <code>{password}</code>", parse_mode='HTML')

# Обрабатывает команду /shodan
@router.message(Command("shodan"))
async def cmd_shodan(message: types.Message):
    if len(message.text.split()) < 2:
        await message.answer("Пожалуйста, укажите IP-адрес.")
        return

    ip_address = message.text.split(maxsplit=1)[1]
    
    try:
        host_info = shodan_client.host(ip_address)
        
        response = (
            f"Информация об IP {ip_address}:\n"
            f"Организация: {host_info.get('org', 'Неизвестно')}\n"
            f"Страна: {host_info.get('country_name', 'Неизвестно')}\n"
            f"Открытые порты: {host_info.get('ports', [])}\n"
        )

        await message.answer(response)
    except shodan.APIError as e:
        await message.answer(f"Ошибка Shodan API: {e}")

# Обрабатывает команду /shodan_dns
@router.message(Command("shodan_dns"))
async def cmd_shodan_dns(message: types.Message):
    if len(message.text.split()) < 2:
        await message.answer("Пожалуйста, укажите домен.")
        return

    domain = message.text.split(maxsplit=1)[1]
    
    try:
        dns_info = shodan_client.dns.domain_info(domain)
        response = f"Информация о домене {domain}:\n" + "\n".join(dns_info.get('subdomains', []))
        
        await message.answer(response)
    except shodan.APIError as e:
        await message.answer(f"Ошибка Shodan API: {e}")
    

    
# Обрабатывает команду /dig
@router.message(Command("dig"))
async def cmd_dig(message: types.Message):
    await send_typing_action(message)
    if len(message.text.split()) < 2:
        await message.answer("Пожалуйста, укажите домен.")
        return
    domain = message.text.split(maxsplit=1)[1]
    response = await run_command(f"dig {domain}")

    if response.strip():
        await message.answer(f"<pre>{response}</pre>", parse_mode="HTML")
    else:
        await message.answer("Ошибка: ответ от сервера пустой или недоступен.")
    logger.info(f"Команда /dig от пользователя {message.from_user.id} с аргументом: {domain}")

# Обрабатывает команду /whois
@router.message(Command("whois"))
async def cmd_whois(message: types.Message):
    await send_typing_action(message)
    if len(message.text.split()) < 2:
        await message.answer("Пожалуйста, укажите домен.")
        return
    domain = message.text.split(maxsplit=1)[1]
    response = await run_command(f"whois {domain}")

    if response.strip():
        await message.answer(f"<pre>{response}</pre>", parse_mode="HTML")
    else:
        await message.answer("Ошибка: ответ от сервера пустой или недоступен.")
    logger.info(f"Команда /whois от пользователя {message.from_user.id} с аргументом: {domain}")

# Обрабатывает команду /traceroute
@router.message(Command("traceroute"))
async def cmd_traceroute(message: types.Message):
    await send_typing_action(message)
    if len(message.text.split()) < 2:
        await message.answer("Пожалуйста, укажите хост.")
        return
    host = message.text.split(maxsplit=1)[1]
    response = await run_command(f"traceroute {host}")

    if response.strip():
        await message.answer(f"<pre>{response}</pre>", parse_mode="HTML")
    else:
        await message.answer("Ошибка: ответ от сервера пустой или недоступен.")
    logger.info(f"Команда /traceroute от пользователя {message.from_user.id} с аргументом: {host}")

# Обрабатывает команду /ping
@router.message(Command("ping"))
async def cmd_ping(message: types.Message):
    await send_typing_action(message)
    if len(message.text.split()) < 2:
        await message.answer("Пожалуйста, укажите хост.")
        return
    host = message.text.split(maxsplit=1)[1]
    response = await run_command(f"ping -c 4 {host}")

    if response.strip():
        await message.answer(f"<pre>{response}</pre>", parse_mode="HTML")
    else:
        await message.answer("Ошибка: ответ от сервера пустой или недоступен.")
    logger.info(f"Команда /ping от пользователя {message.from_user.id} с аргументом: {host}")

# Обрабатывает команду /curl
@router.message(Command("curl"))
async def cmd_curl(message: types.Message):
    await send_typing_action(message)
    if len(message.text.split()) < 2:
        await message.answer("Пожалуйста, укажите URL.")
        return
    url = message.text.split(maxsplit=1)[1]
    response = await run_command(f"curl -s {url}")

    if response.strip():
        if len(response) <= 4000:
            await message.answer(f"<pre>{response}</pre>", parse_mode="HTML")
        else:
            with open('response.txt', 'w') as f:
                f.write(response)
            input_file = FSInputFile('response.txt')
            await message.answer_document(input_file, caption="Ответ слишком большой, вот файл.")
            os.remove('response.txt')
    else:
        await message.answer("Ошибка: ответ от сервера пустой или недоступен.")
    logger.info(f"Команда /curl от пользователя {message.from_user.id} с URL: {url}")

# Обрабатывает команду /wget
@router.message(Command("wget"))
async def cmd_wget(message: types.Message):
    await send_typing_action(message, action_type='upload_document')
    if len(message.text.split()) < 2:
        await message.answer("Пожалуйста, укажите URL для загрузки.")
        return

    url = message.text.split(maxsplit=1)[1]

    if not re.match(r'http(s)?://', url):
        url = f"http://{url}"

    try:
        async with aiohttp.ClientSession() as session:
            async with session.head(url) as response:
                if response.status == 200:
                    content_length = response.headers.get('Content-Length')
                    if content_length:
                        file_size = int(content_length)
                        if file_size > 45 * 1024 * 1024:
                            await message.answer(f"Ошибка: размер файла {file_size / (1024 * 1024):.2f} МБ превышает лимит 45 МБ.")
                            return
                    else:
                        await message.answer("Ошибка: заголовок Content-Length отсутствует.")
                        return
                else:
                    await message.answer(f"Ошибка: не удалось получить информацию о файле. Статус: {response.status}")
                    return

            async with session.get(url) as response:
                if response.status == 200:
                    file_data = await response.read()
                    if len(file_data) > 45 * 1024 * 1024:
                        await message.answer("Ошибка: размер файла превышает 45 МБ после загрузки.")
                        return

                    async with aiofiles.tempfile.NamedTemporaryFile("wb", delete=False) as temp_file:
                        temp_file_name = temp_file.name
                        await temp_file.write(file_data)

                    input_file = FSInputFile(temp_file_name, filename=os.path.basename(url))
                    await message.answer_document(input_file)

                    os.remove(temp_file_name)
                else:
                    await message.answer(f"Ошибка: не удалось загрузить файл. Статус: {response.status}")
    except Exception as e:
        await message.answer(f"Ошибка: {str(e)}")
    logger.info(f"Команда /wget от пользователя {message.from_user.id} с URL: {url}")

# Обрабатывает команду /myip
@router.message(Command("myip"))
async def cmd_myip(message: types.Message):
    web_app_info = types.WebAppInfo(url="https://ifconfig.me/")
    button = types.InlineKeyboardButton(text="Узнать свой IP", web_app=web_app_info)
    markup = types.InlineKeyboardMarkup(inline_keyboard=[[button]])

    await message.answer("Нажмите на кнопку ниже, чтобы узнать свой IP:", reply_markup=markup)

# Обрабатывает команду /ip
@router.message(Command("ip"))
async def cmd_ip(message: types.Message):
    await send_typing_action(message)
    args = message.text.split()
    
    if len(args) < 2:
        await message.answer("Пожалуйста, укажите IP-адрес.")
        return

    ip_address = args[1]
    url = f"https://ipinfo.io/{ip_address}"
    
    try:
        response = await run_command(f"curl -s {url}")

        if response.strip():
            try:
                json_data = json.loads(response)
                if json_data:
                    formatted_data = json.dumps(json_data, indent=4)
                    await message.answer(f"Информация по IP {ip_address}:\n<pre>{formatted_data}</pre>", parse_mode="HTML")
                else:
                    await message.answer("Ошибка: не удалось получить данные по указанному IP.")
            except json.JSONDecodeError:
                await message.answer(f"Ошибка: некорректный ответ от сервера для IP {ip_address}.")
        else:
            await message.answer("Ошибка: не удалось получить данные по адресу IP.")
    except Exception as e:
        await message.answer(f"Ошибка: {str(e)}")
    logger.info(f"Команда /ip от пользователя {message.from_user.id} с IP: {ip_address}")

# Обрабатывает команду /nmap
@router.message(Command("nmap"))
async def cmd_nmap(message: types.Message):
    await send_typing_action(message)
    if len(message.text.split()) < 2:
        await message.answer("Пожалуйста, укажите хост.")
        return
    domain = message.text.split(maxsplit=1)[1]
    response = await run_command(f"nmap -sV {domain} 2>/dev/null | sed '/^# Starting/d; /^Starting Nmap/d'")

    if response.strip():
        await message.answer(f"<pre>{response}</pre>", parse_mode="HTML")
    else:
        await message.answer("Ошибка: ответ от сервера пустой или недоступен.")
    logger.info(f"Команда /nmap от пользователя {message.from_user.id} с аргументом: {domain}")


# Хендлер для нераспознанных команд
@router.message()
async def handle_unknown_command(message: types.Message):
    commands_list = (
        "/start - Приветствие и доступные команды\n"
        "/dig <домен> - Выполнить команду dig для домена\n"
        "/whois <домен> - Получить информацию whois для домена\n"
        "/traceroute <хост> - Выполнить команду traceroute для хоста\n"
        "/ping <хост> - Проверить доступность хоста\n"
        "/curl <URL> - Выполнить запрос curl\n"
        "/wget <URL> - Скачать файл по URL\n"
        "/myip - Узнать свой IP-адрес\n"
        "/ip <IP-адрес> - Получить информацию об IP-адресе\n"
        "/nmap <хост> - Получить открытые порты хоста\n"
        "/sslscan <домен> - Просканировать SSL-сертификаты домена\n"
        "/shodan <IP-адрес> - Получить информацию об IP-адресе из Shodan\n"
        "/generate_password [длина] - Сгенерировать случайный пароль (по умолчанию 12 символов)"
    )

    await send_typing_action(message)
    await message.answer(f"Неизвестная команда. Доступные команды:\n{commands_list}")
    logger.info(f"Нераспознанная команда от пользователя {message.from_user.id}: {message.text}")

# Другие хендлеры должны быть выше этого!
Assistant: You've provided a comprehensive script for an asynchronous Telegram bot using the `aiogram` framework, along with several libraries like `shodan`, `aiohttp`, and more. The bot is designed to offer a wide range of functionalities, such as executing network command-line tools, interacting with the Shodan API, generating random passwords, etc.

Here is a brief overview of the script's components:

1. **Imports:** The script imports various modules required for asynchronous operations, working with APIs, running shell commands, and handling Telegram bot messages.

2. **Logging and Environment Setup:**
   - Logging is configured to track bot activities.
   - Environment variables are loaded using `dotenv`, and the Shodan API key is retrieved.

3. **Asynchronous Command Execution:** 
   - The `run_command` function is used to run system shell commands asynchronously.

4. **Telegram Command Handlers:**
   - Commands like `/start`, `/generate_password`, `/dig`, `/whois`, `/traceroute`, `/ping`, etc., are defined to interact with users and provide corresponding functionalities.
   - Each command handler verifies the input, executes tasks, and sends back appropriate responses.
   - The bot uses different utilities to perform tasks such as querying domain information, network analysis, or generating passwords.

5. **Shodan API Integration:**
   - The script includes handlers for interaction with Shodan, providing information about IPs and domains.
   
6. **Error Handling and Logging:**
   - There's extensive logging for tracking user interactions and any errors during the execution of commands.

7. **Safety Checks:**
   - The script includes checks for invalid inputs and potential errors in processing commands.

To run this bot, you must ensure:
- Python and the required packages (`aiogram`, `aiohttp`, `dotenv`, `shodan`, etc.) are correctly installed.
- A valid Shodan API key is available in your `.env` file.
- You handle tokens securely, especially in a production environment.

Overall, the script appears suitable for network administrators or security professionals needing automated network-related command execution via a user-friendly Telegram interface.
User: привет, давай добавим команду nslookup
Assistant: Добавление команды `nslookup` в ваш Telegram бот можно сделать аналогично тому, как были реализованы другие команды. Ниже представлен пример реализации команды `/nslookup`:

```python
# Обрабатывает команду /nslookup
@router.message(Command("nslookup"))
async def cmd_nslookup(message: types.Message):
    await send_typing_action(message)
    if len(message.text.split()) < 2:
        await message.answer("Пожалуйста, укажите домен.")
        return
    domain = message.text.split(maxsplit=1)[1]
    response = await run_command(f"nslookup {domain}")

    if response.strip():
        await message.answer(f"<pre>{response}</pre>", parse_mode="HTML")
    else:
        await message.answer("Ошибка: ответ от сервера пустой или недоступен.")
    logger.info(f"Команда /nslookup от пользователя {message.from_user.id} с аргументом: {domain}")
```

### Объяснение:
1. **Определение командного обработчика:**
   - Используйте декоратор `@router.message(Command("nslookup"))`, чтобы обработать команду `/nslookup`.

2. **Проверка ввода:**
   - Убедитесь, что пользователь указал домен, который нужно проверить. Если нет, отправьте соответствующее сообщение.

3. **Выполнение команды:**
   - Используйте функцию `run_command`, чтобы выпустить системную команду `nslookup` для указанного домена.

4. **Обработка результата:**
   - Проверьте вывод команды и отправьте пользователю через Telegram.

5. **Логирование:**
   - Зарегистрируйте использование команды для последующего анализа.

### Интеграция:
- Не забудьте также обновить командный список, который отображает доступные команды пользователю, добавив `/nslookup <домен> - Выполнить команду nslookup для домена`.

Если у вас уже есть рабочий бот, просто добавьте новый обработчик команды в нужное место кода и проверьте работу команды.

Убедитесь, что у вас установлены все необходимые привилегии для выполнения системной команды `nslookup`, что может требовать дополнительных разрешений в зависимости от среды выполнения.
User: она буддет поддерживат ь доп аргументы -X?

Assistant: Команда `nslookup` в базовой версии не поддерживает аргумент `-X`. Однако в контексте системы с установленной утилитой `nslookup`, исполняемой через командную оболочку, такой аргумент не является стандартным или общепринятым. В этом случае, вы можете рассмотреть следующие варианты:

1. **Пользовательская обертка:** Если `-X` — это пользовательское требование или специальный скрипт, который работает на вашей системе, убедитесь, что у вас есть соответствующий обработчик для этого аргумента, и вы можете расширить функциональность `nslookup`.

2. **Альтернативные утилиты:** Возможно, у вас есть аналог ut конфигурация системных утилит, обеспечивающая специфический функционал; в этом случае можно создать отдельный обработчик команд для выполнения конкретных задач.

Если вы хотите поддержать дополнительные (не стандартные) аргументы с командой `nslookup`, вы можете расширить обработчик для проверки и использования этих аргументов. Например:

```python
# Обрабатывает команду /nslookup с дополнительными аргументами
@router.message(Command("nslookup"))
async def cmd_nslookup(message: types.Message):
    await send_typing_action(message)
    args = message.text.split(maxsplit=1)
    
    if len(args) < 2:
        await message.answer("Пожалуйста, укажите домен.")
        return

    # Здесь вы можете извлечь и обработать дополнительные аргументы, если они есть
    query = args[1]  # Например, 'example.com -X'
    response = await run_command(f"nslookup {query}")

    if response.strip():
        await message.answer(f"<pre>{response}</pre>", parse_mode="HTML")
    else:
        await message.answer("Ошибка: ответ от сервера пустой или недоступен.")
    logger.info(f"Команда /nslookup от пользователя {message.from_user.id} с аргументами: {query}")
```

### Рекомендации:
- **Тестирование:** Убедитесь, что серверная или локальная среда поддержки ведет себя ожидаемо с переданными аргументами.
- **Документация:** Обновите документацию бота, включив информацию о поддерживаемых аргументах для команды `nslookup`.
- **Безопасность:** Убедитесь в безопасности исполняемых команд, особенно при передаче параметров от пользователей бота, чтобы не допустить выполнения нежелательных операций.
User: да это Пользовательская обертка: Если `-X` — это пользовательское требование или специальный скрипт, который работает на вашей системе, убедитесь, что у вас есть соответствующий обработчик для этого аргумента, и вы можете расширить функциональность `nslookup`.
Assistant: Понятно. Если `-X` — это пользовательский параметр, который добавляет специфическую функциональность вашей версии `nslookup`, вам нужно настроить обработку этого аргумента в вашем боте. Мы можем сделать это, разрешив передавать дополнительные параметры через команду и корректно обрабатывать их.

Вам потребуется изменить код, чтобы поддерживать произвольные аргументы для команды `nslookup`. Вот пример того, как вы можете это реализовать:

```python
# Обрабатывает команду /nslookup с дополнительными аргументами
@router.message(Command("nslookup"))
async def cmd_nslookup(message: types.Message):
    await send_typing_action(message)
    args = message.text.split(maxsplit=1)
    
    if len(args) < 2:
        await message.answer("Пожалуйста, укажите домен.")
        return
    
    # Используем все, что после команды /nslookup, как аргументы команды
    query = args[1]  # Например, 'example.com -X'
    response = await run_command(f"nslookup {query}")

    if response.strip():
        await message.answer(f"<pre>{response}</pre>", parse_mode="HTML")
    else:
        await message.answer("Ошибка: ответ от сервера пустой или недоступен.")

    logger.info(f"Команда /nslookup от пользователя {message.from_user.id} с аргументами: {query}")
```

### Что важно учесть:
- **Корректность введенных данных:** Убедитесь, что пользователи вашего бота имеют четкие инструкции по использованию дополнительных аргументов, чтобы избежать ошибок.
- **Безопасность:** Всегда проверяйте переданные вами аргументы, чтобы гарантировать, что они не могут быть использованы для выполнения нежелательных операций (пример: инъекция команд).
- **Тестирование:** Проверьте, что ваша версия `nslookup` корректно обрабатывает включение параметра `-X` или других пользовательских расширений в тестовой среде.

Этот подход позволяет гибко расширять команды и обрабатывать необходимые аргументы, позволяя вам настраивать бота в соответствии с вашими специфическими требованиями и конфигурациями.
User: есть примеры аргументов для nslookup&
Assistant: `nslookup` — это стандартная утилита для выполнения запросов к DNS-серверам. В типичной конфигурации `nslookup` имеет несколько ключевых аргументов и опций. Вот некоторые из них:

1. **Основные команды**:
   - `nslookup [ip]` или `nslookup [domain]`: Простой запрос IP-адреса для доменного имени или доменного имени для IP-адреса.

2. **Сервер DNS**:
   - `nslookup [domain] [dns-server]`: Выполнить запрос DNS с указанием конкретного DNS-сервера.

3. **Интерактивный режим**:
   - Запуск `nslookup` без аргументов переводит утилиту в интерактивный режим, где команды вводятся поочередно.

4. **Типы записей DNS**:
   - `nslookup -type=a [domain]`: Запрос A-записей для домена.
   - `nslookup -type=mx [domain]`: Запрос MX-записей для почтового обмена.
   - `nslookup -type=ns [domain]`: Запрос NS-записей для указания серверов имен.
   - `nslookup -type=txt [domain]`: Запрос TXT-записей.
   
5. **Опции конфигурации**:
   - `set type=[record-type]`: Установить базовый тип записи для последующих запросов (в интерактивном режиме).
   - `set debug`: Включить режим отладки для получения больше информации.
   - `set port=[number]`: Установить номер порта для выполнения запросов DNS (по умолчанию 53).

Дополнительно, в некоторых системах или с определенными версиями `nslookup` могут присутствовать дальнейшие настройки и команды. Если у вас есть пользовательская версия `nslookup`, которая поддерживает специфические аргументы вроде `-X`, у вас, вероятно, есть доступ к документации или коду, который предоставляет детали об этих расширениях.

Запуск `nslookup` с неправильным или неподдерживаемым аргументом обычно приведет к отображению справочной информации, что может быть полезным для проверки доступных опций.
User: import re
import asyncio
import aiohttp
import logging
import json
import html
from aiogram import types, Router
from aiogram.types import InlineQueryResultArticle, InputTextMessageContent
from ip import run_command, logger

router = Router()

async def handle_inline_ping(query: types.InlineQuery):
    logger.info(f"Инлайн-запрос ping от пользователя {query.from_user.id}")

    if not query.query:
        await query.answer([], is_personal=True, cache_time=1)
        return

    domain = query.query.strip().split(" ", 1)[-1]
    logger.info(f"Инлайн-запрос ping для домена: {domain}")

    try:
        response = await run_command(f"ping -c 4 {domain}")
        response_text = f"<pre>{html.escape(response[:4000] if response else 'Запрос вернул пустой ответ.')}</pre>"

        results = [
            InlineQueryResultArticle(
                id="4",
                title=f"PING для {domain}",
                input_message_content=InputTextMessageContent(message_text=response_text, parse_mode="HTML")
            )
        ]
        await query.answer(results, is_personal=True, cache_time=1)
    except Exception as e:
        logger.error(f"Ошибка обработки команды ping: {str(e)}")
        await query.answer([], is_personal=True, cache_time=1)

async def handle_inline_whois(query: types.InlineQuery):
    logger.info(f"Инлайн-запрос whois от пользователя {query.from_user.id}")

    if not query.query:
        await query.answer([], is_personal=True, cache_time=1)
        return

    domain = query.query.strip()
    logger.info(f"Инлайн-запрос whois для домена: {domain}")

    try:
        response = await run_command(f"whois {domain}")
        response_text = f"<pre>{html.escape(response[:4000] if response else 'Запрос вернул пустой ответ.')}</pre>"

        results = [
            InlineQueryResultArticle(
                id="1",
                title=f"WHOIS для {domain}",
                input_message_content=InputTextMessageContent(message_text=response_text, parse_mode="HTML")
            )
        ]
        await query.answer(results, is_personal=True, cache_time=1)
    except Exception as e:
        logger.error(f"Ошибка обработки команды whois: {str(e)}")

async def handle_inline_dig(query: types.InlineQuery):
    logger.info(f"Инлайн-запрос dig от пользователя {query.from_user.id}")

    if not query.query:
        await query.answer([], is_personal=True, cache_time=1)
        return

    domain = query.query.strip()
    logger.info(f"Инлайн-запрос dig для домена: {domain}")

    try:
        response = await run_command(f"dig {domain}")
        response_text = f"<pre>{html.escape(response[:4000] if response else 'Запрос вернул пустой ответ.')}</pre>"

        results = [
            InlineQueryResultArticle(
                id="2",
                title=f"Поиск DNS для {domain}",
                input_message_content=InputTextMessageContent(message_text=response_text, parse_mode="HTML")
            )
        ]
        await query.answer(results, is_personal=True, cache_time=1)
    except Exception as e:
        logger.error(f"Ошибка обработки команды dig: {str(e)}")

async def handle_inline_ip(query: types.InlineQuery):
    ip_address = query.query.strip().split(" ", 1)[-1]

    if not re.match(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip_address):
        await query.answer([], is_personal=True, cache_time=1)
        return

    url = f"https://ipinfo.io/{ip_address}/"
    logger.info(f"Инлайн-запрос информации по IP: {ip_address}")

    try:
        async with aiohttp.ClientSession() as session:
            headers = {'Accept': 'application/json'}
            async with session.get(url, headers=headers) as response:
                if response.status == 200:
                    json_response = await response.json()
                    formatted_data = json.dumps(json_response, indent=4, ensure_ascii=False)

                    results = [
                        InlineQueryResultArticle(
                            id="3",
                            title=f"Информация для IP {ip_address}",
                            input_message_content=InputTextMessageContent(message_text=f"<pre>{html.escape(formatted_data)}</pre>", parse_mode="HTML")
                        )
                    ]
                    await query.answer(results, is_personal=True, cache_time=1)
                else:
                    logger.error(f"Не удалось получить ответ для {ip_address}, статус: {response.status}")
                    await query.answer([], is_personal=True, cache_time=1)

    except Exception as e:
        logger.error(f"Ошибка при получении информации по IP: {e}")
        await query.answer([], is_personal=True, cache_time=1)

async def handle_inline_curl(query: types.InlineQuery):
    logger.info(f"Инлайн-запрос curl от пользователя {query.from_user.id}")

    if not query.query:
        await query.answer([], is_personal=True, cache_time=1)
        return

    url = query.query.strip().split(" ", 1)[-1]
    logger.info(f"Инлайн-запрос curl для URL: {url}")

    try:
        response = await run_command(f"curl -s {url}")
        response_text = f"<pre>{html.escape(response[:4000] if response else 'Запрос вернул пустой ответ.')}</pre>"

        results = [
            InlineQueryResultArticle(
                id="5",
                title=f"CURL для {url}",
                input_message_content=InputTextMessageContent(message_text=response_text, parse_mode="HTML")
            )
        ]
        await query.answer(results, is_personal=True, cache_time=1)
    except Exception as e:
        logger.error(f"Ошибка обработки команды curl: {str(e)}")
        await query.answer([], is_personal=True, cache_time=1)

async def handle_inline_nmap(query: types.InlineQuery):
    logger.info(f"Инлайн-запрос nmap от пользователя {query.from_user.id}")

    if not query.query:
        await query.answer([], is_personal=True, cache_time=1)
        return

    domain = query.query.strip().split(" ", 1)[-1]
    logger.info(f"Инлайн-запрос nmap для домена: {domain}")

    try:
        response = await run_command(f"nmap -T5 --host-timeout 5s --min-rate 1000 --max-rate 10000 -oG - -n -sV {domain} 2>/dev/null | sed '/^# Nmap/d'")
        response_text = f"<pre>{html.escape(response[:4000] if response else 'Запрос вернул пустой ответ.')}</pre>"

        results = [
            InlineQueryResultArticle(
                id="6",
                title=f"NMAP для {domain}",
                input_message_content=InputTextMessageContent(message_text=response_text, parse_mode="HTML")
            )
        ]
        await query.answer(results, is_personal=True, cache_time=1)
    except Exception as e:
        logger.error(f"Ошибка обработки команды nmap: {str(e)}")
        await query.answer([], is_personal=True, cache_time=1)


async def handle_invalid_command(query: types.InlineQuery):
    logger.info(f"Некорректный инлайн-запрос от пользователя {query.from_user.id}: {query.query}")
    suggestions = [
        "Доступные команды:",
        "/whois <домен> - Получить информацию о домене.",
        "/dig <домен> - Выполнить DNS-запрос.",
        "/ip <IP-адрес> - Получить информацию о IP-адресе.",
        "/ping <домен> - Проверить доступность домена.",
        "/curl <URL> - Выполнить запрос к указанному URL."
    ]
    await query.answer([InlineQueryResultArticle(
        id="0",
        title="Недоступная команда",
        input_message_content=InputTextMessageContent(
            message_text="\n".join(suggestions)
        )
    )], is_personal=True, cache_time=1)


@router.inline_query()
async def inline_handler(query: types.InlineQuery):
    logger.info(f"Получен инлайн-запрос: {query.query}")
    if query.query.startswith("whois"):
        await handle_inline_whois(query)
    elif query.query.startswith("dig"):
        await handle_inline_dig(query)
    elif query.query.startswith("ip"):
        await handle_inline_ip(query)
    elif query.query.startswith("ping"):
        await handle_inline_ping(query)
    elif query.query.startswith("curl"):
        await handle_inline_curl(query)
    elif query.query.startswith("nmap"):
        await handle_inline_nmap(query)
    else:
        await handle_invalid_command(query)

Assistant: