# pararamio-bot

Async webhook server for [pararam.io](https://pararam.io) bot events.

## Installation

```bash
pip install pararamio-bot
```

## Quick Start

```python
import asyncio
import os

from pararamio_bot import WebhookMessage, WebhookServer


async def on_message(message: WebhookMessage) -> None:
    print(f"@{message.user_unique_name}: {message.text}")


async def main() -> None:
    async with WebhookServer(bot_secret=os.environ["BOT_SECRET"], port=8080) as server:
        server.on_message(on_message)
        await asyncio.Event().wait()


asyncio.run(main())
```

Configure your bot's webhook URL in pararam.io to `http://<host>:8080/<url_key>`,
where `<url_key>` is the first 8 characters of your bot secret.

## Security

Every incoming request is verified in two steps:

1. **URL key** — the first 8 chars of the bot secret must be present in the request path. Requests with a wrong key get `404`.
2. **X-Signature** — HMAC-SHA256 signature of the request body, verified against the bot secret. Missing or invalid signature returns `401`.

Both checks are always enabled and cannot be disabled.

## API

### WebhookServer

```python
server = WebhookServer(
    bot_secret="...",  # Full bot secret from pararam.io
    host="0.0.0.0",    # Bind address (default)
    port=8080,          # Listen port (default)
)
```

**Context manager:**

```python
async with WebhookServer(bot_secret="...") as server:
    server.on_message(handler)
    # server is running
```

**Manual start/stop:**

```python
await server.start()
# ...
await server.stop()
```

### Callbacks

Register async callbacks for incoming events:

```python
server.on_message(callback)  # Called for each incoming message
server.on_action(callback)   # Called for bot:// link clicks
```

Multiple callbacks can be registered; they are called sequentially.

### Models

**WebhookMessage** — incoming message from a user:

| Field             | Type                     | Description                    |
|-------------------|--------------------------|--------------------------------|
| `user_id`         | `int`                    | Sender user ID                 |
| `user_unique_name`| `str`                    | Sender unique name             |
| `post_no`         | `int`                    | Post number in the chat        |
| `chat_id`         | `int`                    | Chat ID                        |
| `text`            | `str`                    | Message text                   |
| `organization_id` | `int \| None`            | Organization ID (optional)     |
| `text_parsed`     | `list[dict] \| None`     | Parsed text structure          |
| `reply_no`        | `int \| None`            | Replied-to post number         |
| `reply_text`      | `str \| None`            | Replied-to post text           |
| `file_guid`       | `str \| None`            | Attached file GUID             |
| `file_name`       | `str \| None`            | Attached file name             |
| `attachments`     | `list[str] \| None`      | List of attachment identifiers |

**WebhookAction** — user clicked a `bot://` link:

| Field             | Type              | Description                 |
|-------------------|-------------------|-----------------------------|
| `user_id`         | `int`             | User who clicked            |
| `chat_id`         | `int`             | Chat ID                     |
| `post_no`         | `int`             | Post number                 |
| `organization_id` | `int \| None`     | Organization ID (optional)  |
| `action`          | `str`             | Action name from the link   |
| `params`          | `dict[str, str]`  | Action parameters           |

### Utility Functions

```python
from pararamio_bot import extract_url_key, extract_api_key, make_signature, verify_signature

# Get the URL key (first 8 chars) from a bot secret
url_key = extract_url_key(bot_secret)

# Get the API key portion (chars 20+ for secrets > 50 chars)
api_key = extract_api_key(bot_secret)

# Compute / verify HMAC-SHA256 signatures
sig = make_signature(body, secret)
is_valid = verify_signature(body, sig, secret)
```

## HTTP Endpoints

| Route              | Description              |
|--------------------|--------------------------|
| `POST /{url_key}`     | Receive message webhooks |
| `POST /{url_key}/act` | Receive action webhooks  |

## Requirements

- Python >= 3.11
- aiohttp >= 3.9
- pydantic >= 2.0
