Metadata-Version: 2.4
Name: telegram-init-data
Version: 1.1.0
Summary: Python library for working with Telegram Mini Apps initialization data
Author-email: Imran Gadzhiev <i.gadzhiev.m@gmail.com>
License: MIT
Project-URL: Homepage, https://github.com/iCodeCraft/telegram-init-data
Project-URL: Repository, https://github.com/iCodeCraft/telegram-init-data
Project-URL: Documentation, https://github.com/iCodeCraft/telegram-init-data#readme
Project-URL: Issues, https://github.com/iCodeCraft/telegram-init-data/issues
Project-URL: Downloads, https://pepy.tech/projects/telegram-init-data
Keywords: telegram,mini-apps,init-data,validation,signature,bot
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
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 :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Communications :: Chat
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Requires-Dist: black>=22.0; extra == "dev"
Requires-Dist: isort>=5.0; extra == "dev"
Requires-Dist: flake8>=4.0; extra == "dev"
Requires-Dist: mypy>=1.0; extra == "dev"
Provides-Extra: fastapi
Requires-Dist: fastapi>=0.68.0; extra == "fastapi"
Dynamic: license-file

# telegram-init-data

[![PyPI version](https://badge.fury.io/py/telegram-init-data.svg)](https://badge.fury.io/py/telegram-init-data)
[![Python versions](https://img.shields.io/pypi/pyversions/telegram-init-data.svg)](https://pypi.org/project/telegram-init-data)
[![PyPI Downloads](https://static.pepy.tech/personalized-badge/telegram-init-data?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/telegram-init-data)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

Python library for parsing, validating, and signing [Telegram Mini Apps](https://docs.telegram-mini-apps.com/) initialization data on the server side. API-compatible in spirit with [@tma.js/init-data-node](https://docs.telegram-mini-apps.com/packages/tma-js-init-data-node).

## Features

- Validate init data signature and expiration
- Parse URL-encoded init data into typed Python objects
- Sign init data for testing and development
- Full type hints
- Optional FastAPI integration
- Third-party validation (data signed by Telegram directly)

## Installation

```bash
pip install telegram-init-data
```

With FastAPI support:

```bash
pip install telegram-init-data[fastapi]
```

## Quick Start

### Validation

```python
from telegram_init_data import validate, parse

bot_token = "YOUR_BOT_TOKEN"
init_data = "query_id=AAHdF6IQAAAAAN0XohDhrOrc&user=%7B%22id%22%3A279058397%2C%22first_name%22%3A%22Vladislav%22%2C%22last_name%22%3A%22Kibenko%22%2C%22username%22%3A%22vdkfrost%22%2C%22language_code%22%3A%22ru%22%2C%22is_premium%22%3Atrue%7D&auth_date=1662771648&hash=c501b71e775f74ce10e377dea85a7ea24ecd640b223ea86dfe453e0eaed2e2b2"

try:
    validate(init_data, bot_token)
    parsed = parse(init_data)
    print(parsed["user"]["first_name"])
except Exception as e:
    print(f"Validation failed: {e}")
```

### FastAPI

```python
from fastapi import FastAPI, Depends, HTTPException
from telegram_init_data import validate, parse

app = FastAPI()

def verify_init_data(init_data: str) -> dict:
    bot_token = "YOUR_BOT_TOKEN"
    try:
        validate(init_data, bot_token)
        return parse(init_data)
    except Exception as e:
        raise HTTPException(status_code=401, detail=str(e))

@app.post("/user/profile")
async def get_profile(init_data: dict = Depends(verify_init_data)):
    user = init_data.get("user")
    if not user:
        raise HTTPException(status_code=400, detail="User data not found")
    return {"user_id": user["id"], "name": user["first_name"]}
```

### Signing (tests / development)

```python
from telegram_init_data import sign, is_valid
from datetime import datetime

bot_token = "YOUR_BOT_TOKEN"

test_data = {
    "query_id": "test_query_id",
    "user": {
        "id": 123456789,
        "first_name": "John",
        "last_name": "Doe",
        "username": "johndoe",
        "language_code": "en",
    },
    "auth_date": datetime.now(),
}

signed_data = sign(test_data, bot_token, datetime.now())

if is_valid(signed_data, bot_token):
    print("Valid")
```

## API Reference

### `validate(value, token, options=None)`

Validate Telegram Mini App init data.


| Parameter | Type             | Description                                         |
| --------- | ---------------- | --------------------------------------------------- |
| `value`   | `str | dict`     | Init data to validate                               |
| `token`   | `str`            | Bot token from [@BotFather](https://t.me/BotFather) |
| `options` | `dict`, optional | `expires_in` (seconds, default `86400`)             |


**Raises:** `SignatureMissingError`, `AuthDateInvalidError`, `ExpiredError`, `SignatureInvalidError`

### `is_valid(value, token, options=None)`

Same checks as `validate`, returns `bool` instead of raising.

### `parse(value)`

Parse init data into a structured object. Returns `InitData`.

### `sign(data, token, auth_date, options=None)`

Sign init data for testing. Returns a URL-encoded string.

### Types

```python
class InitData(TypedDict):
    query_id: Optional[str]
    user: Optional[User]
    receiver: Optional[User]
    chat: Optional[Chat]
    chat_type: Optional[ChatType]
    chat_instance: Optional[str]
    start_param: Optional[str]
    can_send_after: Optional[int]
    auth_date: int
    hash: str
    signature: Optional[str]

class User(TypedDict):
    id: int
    first_name: str
    last_name: Optional[str]
    username: Optional[str]
    language_code: Optional[str]
    is_bot: Optional[bool]
    is_premium: Optional[bool]
    added_to_attachment_menu: Optional[bool]
    allows_write_to_pm: Optional[bool]
    photo_url: Optional[str]

class Chat(TypedDict):
    id: int
    type: ChatType
    title: Optional[str]
    username: Optional[str]
    photo_url: Optional[str]

class ChatType(str, Enum):
    SENDER = "sender"
    PRIVATE = "private"
    GROUP = "group"
    SUPERGROUP = "supergroup"
    CHANNEL = "channel"
```

### Exceptions


| Exception               | When                           |
| ----------------------- | ------------------------------ |
| `TelegramInitDataError` | Base class                     |
| `AuthDateInvalidError`  | Invalid or missing `auth_date` |
| `SignatureInvalidError` | Signature mismatch             |
| `SignatureMissingError` | Missing `hash` / signature     |
| `ExpiredError`          | Init data expired              |


## Options

```python
# Custom TTL (1 hour)
validate(init_data, bot_token, {"expires_in": 3600})

# Disable expiration check
validate(init_data, bot_token, {"expires_in": 0})
```

## Testing

```bash
pip install -e ".[dev]"
pytest
pytest --cov=telegram_init_data --cov-report=html
```

## Examples

See `[examples/](examples/)` for basic usage and a FastAPI app.

### FastAPI with Authorization header

```python
from fastapi import FastAPI, Depends, HTTPException, Header
from telegram_init_data import parse, is_valid

app = FastAPI()

def get_init_data(authorization: str = Header(None)):
    if not authorization:
        raise HTTPException(status_code=401, detail="Authorization header missing")
    if not authorization.startswith("tma "):
        raise HTTPException(status_code=401, detail="Invalid authorization format")

    init_data = authorization[4:]
    bot_token = "YOUR_BOT_TOKEN"

    if not is_valid(init_data, bot_token):
        raise HTTPException(status_code=401, detail="Invalid init data")

    return parse(init_data)

@app.get("/me")
async def get_current_user(init_data: dict = Depends(get_init_data)):
    user = init_data.get("user")
    if not user:
        raise HTTPException(status_code=400, detail="User data not found")
    return {
        "id": user["id"],
        "name": user.get("first_name", ""),
        "username": user.get("username"),
        "is_premium": user.get("is_premium", False),
    }
```

## Development

```bash
git clone https://github.com/iCodeCraft/telegram-init-data.git
cd telegram-init-data
python -m venv venv
source venv/bin/activate
pip install -e ".[dev]"
pytest
black telegram_init_data tests
isort telegram_init_data tests
mypy telegram_init_data
```

## License

MIT. See [LICENSE](LICENSE).

## Related

- [@tma.js/init-data-node](https://docs.telegram-mini-apps.com/packages/tma-js-init-data-node) — Node.js counterpart
- [Telegram Mini Apps documentation](https://docs.telegram-mini-apps.com/)
- [Download stats (pepy.tech)](https://pepy.tech/projects/telegram-init-data)

## Changelog

See [CHANGELOG.md](CHANGELOG.md).
