Metadata-Version: 2.4
Name: novdev
Version: 0.0.1
Summary: Библиотека для создания ботов NovCord
Author: XenonE
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Framework :: AsyncIO
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: aiohttp>=3.9
Requires-Dist: aiofiles>=23.0
Dynamic: author
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# NovDev

**Библиотека для создания ботов NovCord**  
Автор: XenonE · Версия: 0.0.1

---

## Установка

```bash
pip install novdev
```

**Зависимости:** Python ≥ 3.10, aiohttp, aiofiles

---

## Быстрый старт

```python
import asyncio
from novdev import Bot, Dispatcher, Router, F
from novdev.filters import Command
from novdev.keyboard import InlineKeyboardBuilder

dp = Dispatcher()
router = Router()
dp.include_router(router)


@router.message(Command("start"))
async def start(message):
    kb = InlineKeyboardBuilder()
    kb.button(text="Нажми меня", callback_data="hello")
    kb.button(text="Сайт", url="https://novcord.online")
    kb.adjust(2)
    await message.answer("Привет! Я бот на NovDev 🤖", reply_markup=kb.as_markup())


@router.callback_query(F.data == "hello")
async def on_hello(query):
    await query.answer("Принято!")
    await query.edit_message("Ты нажал кнопку 👍")


@router.message(F.text)
async def echo(message):
    await message.answer(f"Ты написал: {message.text}")


async def main():
    bot = Bot("ВСТАВЬ_ТОКЕН")  # Настройки → Мои боты
    await dp.start_polling(bot)


asyncio.run(main())
```

---

## Оглавление

1. [Bot — объект бота](#bot)
2. [Dispatcher и Router](#dispatcher-и-router)
3. [Фильтры](#фильтры)
4. [Magic-фильтр F](#magic-фильтр-f)
5. [Клавиатуры](#клавиатуры)
6. [FSM — машина состояний](#fsm--машина-состояний)
7. [Объекты Message и CallbackQuery](#объекты-message-и-callbackquery)
8. [Исключения](#исключения)

---

## Bot

```python
from novdev import Bot

bot = Bot(token="botId:secret", timeout=30)
```

### Методы

| Метод | Описание |
|---|---|
| `await bot.get_me()` | Информация о боте → `BotInfo` |
| `await bot.send_message(chat_id, text, reply_markup?, reply_to_message_id?)` | Отправить сообщение → `Message` |
| `await bot.send_photo(chat_id, photo=?, file_url=?, caption=?, reply_markup?)` | Отправить фото → `Message` |
| `await bot.send_document(chat_id, document=?, file_url=?, caption=?)` | Отправить файл → `Message` |
| `await bot.edit_message_text(chat_id, message_id, text, reply_markup?)` | Изменить текст → `Message` |
| `await bot.delete_message(chat_id, message_id)` | Удалить сообщение → `bool` |
| `await bot.answer_callback_query(callback_query_id, text?)` | Подтвердить кнопку → `bool` |
| `await bot.set_my_commands([(cmd, desc), ...])` | Задать команды бота |
| `await bot.get_my_commands()` | Получить команды бота |
| `await bot.get_updates(offset, limit, timeout)` | Низкоуровневый polling |
| `await bot.close()` | Закрыть HTTP-сессию |

---

## Dispatcher и Router

```python
from novdev import Dispatcher, Router

dp = Dispatcher()
router = Router(name="main")
dp.include_router(router)
```

Можно подключать несколько роутеров — хендлеры проверяются в порядке подключения.

```python
from novdev.fsm import MemoryStorage

dp = Dispatcher(storage=MemoryStorage())  # можно передать своё хранилище
```

### Запуск

```python
await dp.start_polling(bot, timeout=25, poll_interval=0.1)
```

---

## Фильтры

Импорт:

```python
from novdev.filters import Command, TextFilter, CallbackDataFilter, FromUserFilter
```

### Command

```python
@router.message(Command("start"))           # /start
@router.message(Command("help", "h"))       # /help или /h
```

### TextFilter

```python
@router.message(TextFilter("привет"))                   # точное совпадение
@router.message(TextFilter(startswith="!"))             # начинается с !
@router.message(TextFilter(contains="спам"))            # содержит
@router.message(TextFilter(regex=r"\d{4}"))             # 4 цифры
@router.message(TextFilter(endswith="?", ignore_case=False))
```

### CallbackDataFilter

```python
@router.callback_query(CallbackDataFilter("confirm"))
@router.callback_query(CallbackDataFilter(startswith="menu:"))
@router.callback_query(CallbackDataFilter(regex=r"^item:\d+$"))
```

### FromUserFilter

```python
@router.message(FromUserFilter("0be9c372", "a1b2c3d4"))
```

### Комбинирование

```python
from novdev.filters import Command, TextFilter

@router.message(Command("start") | TextFilter("старт"))
@router.message(TextFilter(startswith="!") & ~FromUserFilter("banned_id"))
```

---

## Magic-фильтр F

`F` — короткий способ фильтровать по полям объекта.

```python
from novdev import F

# В декораторе message: фильтрует по message.text
@router.message(F.text == "привет")
@router.message(F.text.startswith("!"))
@router.message(F.text.contains("help"))
@router.message(F.text.matches(r"\d+"))
@router.message(F.text)   # любой текст (не None, не пустой)

# В декораторе callback_query: фильтрует по query.data
@router.callback_query(F.data == "confirm")
@router.callback_query(F.data.startswith("action:"))
```

---

## Клавиатуры

```python
from novdev.keyboard import InlineKeyboardBuilder

kb = InlineKeyboardBuilder()
kb.button(text="Да",   callback_data="yes")
kb.button(text="Нет",  callback_data="no")
kb.button(text="Сайт", url="https://novcord.online")
kb.adjust(2)  # по 2 кнопки в ряду

await message.answer("Выбери:", reply_markup=kb.as_markup())
```

`adjust(*widths)` — разбивает кнопки по рядам:

```python
kb.adjust(2, 1)   # первый ряд: 2, потом: 1, 2, 1 ...
kb.adjust(3)      # по 3 кнопки в каждом ряду
```

Явный ряд:

```python
from novdev.keyboard import InlineKeyboardButton

kb.row(
    InlineKeyboardButton("A", callback_data="a"),
    InlineKeyboardButton("B", callback_data="b"),
)
```

---

## FSM — машина состояний

Используй для многошаговых диалогов.

```python
from novdev.fsm import StatesGroup, State, FSMContext

class OrderForm(StatesGroup):
    product = State()
    address = State()
    confirm = State()
```

```python
@router.message(Command("order"))
async def start_order(message, state: FSMContext):
    await state.set(OrderForm.product)
    await message.answer("Что заказать?")


@router.message(OrderForm.product)
async def ask_address(message, state: FSMContext):
    await state.update(product=message.text)
    await state.set(OrderForm.address)
    await message.answer("Адрес доставки?")


@router.message(OrderForm.address)
async def ask_confirm(message, state: FSMContext):
    await state.update(address=message.text)
    data = await state.get_data()

    kb = InlineKeyboardBuilder()
    kb.button(text="✅ Подтвердить", callback_data="confirm_order")
    kb.button(text="❌ Отмена",      callback_data="cancel_order")
    kb.adjust(2)

    await state.set(OrderForm.confirm)
    await message.answer(
        f"Заказ: {data['product']}\nАдрес: {data['address']}\nВсё верно?",
        reply_markup=kb.as_markup(),
    )


@router.callback_query(OrderForm.confirm, F.data == "confirm_order")
async def finish_order(query, state: FSMContext):
    await query.answer("Принято!")
    data = await state.get_data()
    await query.edit_message(f"✅ Заказ оформлен!\n{data['product']} → {data['address']}")
    await state.clear()


@router.callback_query(OrderForm.confirm, F.data == "cancel_order")
async def cancel_order(query, state: FSMContext):
    await query.answer()
    await query.edit_message("❌ Заказ отменён.")
    await state.clear()
```

### FSMContext API

| Метод | Описание |
|---|---|
| `await state.get()` | Текущее состояние |
| `await state.set(State)` | Установить состояние |
| `await state.clear()` | Сбросить состояние и данные |
| `await state.get_data()` | Получить данные (dict) |
| `await state.update(key=val, ...)` | Добавить/обновить данные |
| `await state.set_data(dict)` | Полностью заменить данные |

---

## Объекты Message и CallbackQuery

### Message

```python
message.message_id    # str
message.text          # str | None
message.from_user     # User (id, username, first_name, is_bot)
message.chat          # Chat (id, type)
message.chat_id       # str — id пользователя для ответа
message.date          # int (unix timestamp)
message.file_url      # str | None
message.file_name     # str | None

await message.answer("текст", reply_markup=...) # ответить
await message.reply("текст")                    # ответить реплаем
await message.edit("новый текст")               # изменить текст
await message.delete()                          # удалить
```

### CallbackQuery

```python
query.id              # str
query.data            # str | None — значение callback_data
query.from_user       # User
query.message         # Message (усечённый)
query.chat_id         # str

await query.answer("текст")              # подтвердить нажатие
await query.answer()                     # подтвердить без текста
await query.edit_message("новый текст") # изменить сообщение с кнопкой
```

### User

```python
user.id           # str
user.username     # str | None
user.first_name   # str | None
user.is_bot       # bool
user.mention      # "@username" или first_name
```

---

## Исключения

```python
from novdev import NovDevAPIError, NovDevNetworkError, NovDevError

try:
    await bot.send_message(chat_id, text)
except NovDevAPIError as e:
    print(e.method, e.code, e.description)
except NovDevNetworkError as e:
    print("Нет соединения:", e)
```

| Исключение | Когда |
|---|---|
| `NovDevAPIError` | Сервер вернул `"ok": false` |
| `NovDevNetworkError` | Ошибка сети / таймаут |
| `NovDevError` | Базовый класс для обоих |

---

## Структура пакета

```
novdev/
├── __init__.py        # Bot, Dispatcher, Router, F
├── bot.py             # Класс Bot (HTTP-клиент)
├── dispatcher.py      # Dispatcher, Router
├── exceptions.py      # Исключения
├── filters/
│   └── __init__.py    # Command, TextFilter, F, ...
├── keyboard/
│   └── __init__.py    # InlineKeyboardBuilder
├── fsm/
│   └── __init__.py    # StatesGroup, State, FSMContext, MemoryStorage
└── types/
    └── __init__.py    # Message, CallbackQuery, Update, User, Chat, BotInfo
```

---

*NovDev v0.0.1 · XenonE*
