Metadata-Version: 2.4
Name: yuchatlib
Version: 0.2.2
Summary: Асинхронная библиотека для ботов YuChat
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: httpx<1,>=0.27
Requires-Dist: pydantic<3,>=2
Provides-Extra: test
Requires-Dist: pytest<10,>=8; extra == "test"
Requires-Dist: pytest-asyncio<2,>=0.23; extra == "test"
Requires-Dist: python-dotenv<2,>=1; extra == "test"
Requires-Dist: aiosqlite<1,>=0.20; extra == "test"
Provides-Extra: stand
Requires-Dist: python-dotenv<2,>=1; extra == "stand"
Provides-Extra: env
Requires-Dist: python-dotenv<2,>=1; extra == "env"
Provides-Extra: dev
Requires-Dist: ruff<1,>=0.11; extra == "dev"
Provides-Extra: sqlite
Requires-Dist: aiosqlite<1,>=0.20; extra == "sqlite"
Provides-Extra: webhook
Requires-Dist: aiohttp<4,>=3.10; extra == "webhook"

# yuchatlib

Асинхронная Python-библиотека для создания ботов YuChat. Она предоставляет обработчики команд и сообщений, роутеры, фильтры, middleware и управление состояниями диалога.

Библиотека поддерживает **Python 3.11 и новее**, работу с YuChat Cloud и On-Premises.

## Возможности

- Получение событий в режимах **polling** и **webhook**.
- Последовательная обработка событий через `Dispatcher`.
- Вложенные роутеры и асинхронные обработчики.
- Команды с префиксом `/`, фильтры и middleware.
- Командные кнопки и обработка нажатий.
- FSM для хранения состояния и данных диалога.
- Хранение FSM и курсора polling в памяти или SQLite.
- Отправка, чтение и редактирование сообщений.

## Настройки

Параметры можно указать в файле `.env` или в переменных окружения. Значения переменных окружения имеют приоритет над значениями из файла.

| Переменная | Назначение | Значение по умолчанию |
|---|---|---|
| `YUCHAT_TOKEN` | Токен бота. | Не задан |
| `YUCHAT_BASE_URL` | Базовый адрес API YuChat Cloud или вашего On-Premises-стенда. | Не задан |
| `YUCHAT_WEBHOOK_SECRET` | Секрет для проверки входящих webhook-запросов. | Не задан |
| `YUCHAT_WORKSPACE_ID` | Идентификатор рабочего пространства для сценариев, которым он необходим. | Не задан |
| `YUCHAT_CHAT_ID` | Идентификатор чата для сценариев, которым он необходим. | Не задан |
| `YUCHAT_FSM_NAMESPACE` | Постоянная идентичность бота для SQLite FSM и курсора. | Не задан |
| `YUCHAT_FSM_SQLITE_PATH` | Путь к файлу SQLite для FSM и курсора. | Не задан |
| `YUCHAT_HTTP_TIMEOUT` | Таймаут HTTP-запросов в секундах. | `30.0` |
| `YUCHAT_POLLING_INTERVAL` | Задержка между запросами polling в секундах. | `1.0` |
| `YUCHAT_POLLING_LIMIT` | Максимальное число событий в одном запросе. | `20` |
| `YUCHAT_POLLING_MAX_RETRIES` | Максимальное число повторных попыток при временных ошибках. | `5` |

## Быстрый старт: polling и команда

```python
import asyncio

from yuchatlib import Bot, Dispatcher, Router
from yuchatlib.api import MessageDto
from yuchatlib.config import Settings

router = Router()


@router.command("start")
async def start(message: MessageDto, bot: Bot) -> None:
    await bot.send_message(
        workspace_id=message.workspace_id,
        chat_id=message.chat_id,
        text="Привет! Я бот YuChat.",
    )


async def main() -> None:
    settings = Settings.from_env()

    dispatcher = Dispatcher()
    dispatcher.include_router(router)

    async with Bot(
        settings.require_token(),
        base_url=settings.require_base_url(),
        timeout=settings.http_timeout,
    ) as bot:
        await dispatcher.start_polling(
            bot,
            interval=settings.polling_interval,
            limit=settings.polling_limit,
            max_retries=settings.polling_max_retries,
        )


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

После запуска отправьте боту команду `/start`. В декораторе имя команды указывается **без** символа `/`.

## Состояния диалога

FSM позволяет сохранять состояние и данные отдельно для каждого участника чата. Обработчики ниже можно добавить в пример с polling.

```python
from yuchatlib.api import MessageDto
from yuchatlib.fsm import FSMContext, State, StatesGroup


class Form(StatesGroup):
    waiting_for_name = State()


@router.command("name")
async def ask_name(message: MessageDto, state: FSMContext, bot: Bot) -> None:
    await state.set_state(Form.waiting_for_name)
    await bot.send_message(
        workspace_id=message.workspace_id,
        chat_id=message.chat_id,
        text="Как вас зовут?",
    )


@router.message(Form.waiting_for_name)
async def save_name(message: MessageDto, state: FSMContext, bot: Bot) -> None:
    await state.update_data(name=message.content.text)
    await bot.send_message(
        workspace_id=message.workspace_id,
        chat_id=message.chat_id,
        text="Имя сохранено.",
    )
    await state.clear()
```

По умолчанию FSM хранится в памяти процесса и очищается при его завершении. `clear()` удаляет состояние и данные текущего контекста.

## Командные кнопки

Бот может отправить сообщение с кнопкой и обработать её нажатие.

```python
from yuchatlib.api import (
    ButtonBarDto,
    ButtonDto,
    ButtonGroupDto,
    CommandButtonDto,
    MessageActionDto,
    MessageDto,
)


@router.command("buttons")
async def show_buttons(message: MessageDto, bot: Bot) -> None:
    button_bar = ButtonBarDto(
        button_groups=[
            ButtonGroupDto(
                buttons=[
                    ButtonDto(
                        command_button=CommandButtonDto(
                            display_text="Подтвердить",
                            command_key="/confirm",
                        )
                    )
                ]
            )
        ]
    )
    await bot.send_message(
        workspace_id=message.workspace_id,
        chat_id=message.chat_id,
        text="Выберите действие:",
        button_bar=button_bar,
    )


@router.callback_query(command_key="/confirm")
async def confirm(action: MessageActionDto, bot: Bot) -> None:
    await bot.send_message(
        workspace_id=action.workspace_id,
        chat_id=action.chat_id,
        text="Действие подтверждено.",
    )
```

Для получения нажатий у бота должен быть включён тип обновлений `MESSAGE_ACTION`. Идентификатор нажавшего пользователя в модели действия кнопки не предусмотрен, поэтому персональный FSM-контекст для такого действия автоматически не создаётся.
