Metadata-Version: 2.4
Name: procketapi
Version: 1.0.0
Summary: Official Python client for the PRocket API — mandatory subscription, tasks and ad views for Telegram bots
Author: PRocket
License: MIT
Project-URL: Homepage, https://app.procket.club
Project-URL: Documentation, https://procketapi.best
Project-URL: Source, https://github.com/RiderMorison/procketapi-python
Project-URL: Issues, https://github.com/RiderMorison/procketapi-python/issues
Keywords: telegram,bot,aiogram,telebot,monetization,procket,ads
Classifier: Development Status :: 5 - Production/Stable
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Communications :: Chat
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: aiohttp>=3.8
Provides-Extra: aiogram
Requires-Dist: aiogram>=3.0; extra == "aiogram"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
Requires-Dist: aioresponses>=0.7; extra == "dev"
Dynamic: license-file

# PRocket API — Python

Официальный клиент [PRocket](https://app.procket.club) для Telegram-ботов:
обязательная подписка, задания и показы.

```bash
pip install procketapi
```

Требования: Python 3.8+, `aiohttp`.

---

## Обязательная подписка за две строки

```python
from aiogram import Bot, Dispatcher, types
from procketapi import PRocket

bot = Bot("ТОКЕН_БОТА")
dp = Dispatcher()
procket = PRocket("ВАШ_КЛЮЧ")


@dp.message()
async def handler(message: types.Message):
    if not await procket.check(message.from_user.id, bot=bot):
        return          # спонсоры уже отправлены пользователю
    await message.answer("Доступ открыт")
```

`check()` возвращает объект, приводимый к `bool`. Если пользователь не
подписан и передан `bot`, клиент **сам** отправит сообщение со спонсорами,
клавиатурой и кнопкой «Проверить» — верстать ничего не нужно.

Отдельного метода «проверить ещё раз» нет: **повторный вызов `check()` и есть
проверка**. Его же вешают на кнопку.

---

## Middleware: подписка на весь бот одной строкой

```python
from procketapi import PRocket, PRocketMiddleware

procket = PRocket("ВАШ_КЛЮЧ")

dp.message.middleware(PRocketMiddleware(procket, skip_commands=("/help",)))
dp.callback_query.middleware(PRocketMiddleware(procket))
```

Ни один хендлер не выполнится, пока пользователь не подписан. Нажатия на
кнопку «Проверить» обрабатываются внутри.

---

## Задания

Пользователь получает награду за выполнение, а не доступ.

```python
result = await procket.get_tasks(user_id, limit=5)

for task in result:
    print(task.title, task.url, task.reward, task.currency)

if result.message:
    await bot.send_message(user_id, **result.message.as_kwargs())

# позже, по кнопке
state = await procket.check_task(task.ticket)
if state:                       # state.completed
    await give_reward(user_id, state.reward)
```

Состояния: `open`, `done`, `waiting`, `paid`, `expired`, `cancelled`, `reverted`.

---

## Показы и приветы

Сервер сам отправит рекламный пост через токен вашего бота.

```python
from procketapi import AdResult

result = await procket.send_ad(user_id, hi=True)   # привет после /start

if result.code == AdResult.USER_FORBIDDEN:
    await mark_user_blocked(user_id)
```

| Код | Константа | Значение |
|----|-----------|----------|
| 1 | `SUCCESS` | пост доставлен |
| 2 | `REVOKED_TOKEN` | токен бота недействителен |
| 3 | `USER_FORBIDDEN` | пользователь заблокировал бота |
| 4 | `TOO_MANY_REQUESTS` | превышен лимит |
| 5 | `BOT_API_ERROR` | ошибка Telegram |
| 6 | `OTHER_ERROR` | внутренняя ошибка |
| 7 | `AD_LIMITED` | лимит показов исчерпан |
| 8 | `NO_ADS` | нет подходящей рекламы |
| 9 | `BOT_NOT_ENABLED` | бот выключен в настройках |
| 10 | `BANNED` | бот заблокирован |
| 11 | `IN_REVIEW` | бот на модерации |

`hi=True` вызывать только после `/start` нового пользователя и не чаще раза в
сутки на человека. Обычные показы вешать на осмысленные действия, а не на
`/start`.

---

## Вебхуки

```python
from aiohttp import web
from procketapi import verify_webhook, WebhookSignatureError

async def handle(request: web.Request):
    body = await request.read()          # именно байты, не разобранный JSON
    try:
        verify_webhook(body, request.headers.get("X-Procket-Signature"), SECRET)
    except WebhookSignatureError:
        return web.Response(status=403)

    event = json.loads(body)
    if event["type"] == "task.completed":
        await give_reward(event["data"]["user_id"], event["data"]["reward"])
    return web.json_response({"ok": True})
```

Проверять подпись обязательно: без этого любой, кто узнал адрес обработчика,
пришлёт поддельное «задание выполнено».

Разбирать JSON **до** проверки нельзя — подпись считается по исходным байтам.

---

## Справочник

```python
PRocket(
    key,                      # ключ из раздела «Интеграция»
    base_url="https://app.procket.club",
    timeout=10.0,
    retries=3,
    raise_on_error=False,
)
```

| Метод | Возвращает |
|-------|-----------|
| `await check(user_id, *, bot=None, language_code=None, is_premium=None, limit=3, message=None, send=True)` | `CheckResult` |
| `await get_tasks(user_id, *, limit=5, language_code=None, is_premium=None)` | `TasksResult` |
| `await check_task(ticket)` | `TaskState` |
| `await send_ad(user_id, *, hi=False)` | `AdResult` |
| `await reset(user_id)` | `int` — сколько привязок закрыто |
| `await me()` | `BotInfo` |
| `await close()` | — закрыть сессию при остановке бота |

### Своё оформление сообщения

```python
await procket.check(user_id, bot=bot, message={
    "rows": 2,                          # кнопок в ряд
    "text": "<b>Подпишитесь</b>, $name", # $name, {count}, {reward}, {currency}
    "button_channel": "Подписаться",
    "button_bot": "Запустить",
    "button_check": "Готово ✅",
})
```

### Поведение при сбоях

По умолчанию клиент **не роняет бота**. Если PRocket недоступен, `check()`
возвращает «пропустить»: пользователь продолжает пользоваться ботом, владелец
теряет один показ вместо всех сразу.

`401` не повторяется — ключ после отказа считается недействительным до
перезапуска, чтобы неверный ключ не превратился в поток ошибок в логе.

Строгое поведение включается через `raise_on_error=True`.

---

## Примеры

- [`examples/aiogram_subscription.py`](examples/aiogram_subscription.py) — обязательная подписка
- [`examples/aiogram_middleware.py`](examples/aiogram_middleware.py) — подписка на весь бот
- [`examples/aiogram_tasks.py`](examples/aiogram_tasks.py) — задания с наградой
- [`examples/telebot_example.py`](examples/telebot_example.py) — pyTelegramBotAPI
- [`examples/webhook_server.py`](examples/webhook_server.py) — приём вебхуков

Полная документация API: <https://procketapi.best>

## Лицензия

MIT
