# pyaileys

[![CI](https://github.com/atiti/pyaileys/actions/workflows/ci.yml/badge.svg)](https://github.com/atiti/pyaileys/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/pyaileys.svg)](https://pypi.org/project/pyaileys/)
[![Python Versions](https://img.shields.io/pypi/pyversions/pyaileys.svg)](https://pypi.org/project/pyaileys/)
[![License](https://img.shields.io/pypi/l/pyaileys.svg)](LICENSE)
[![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-261230.svg)](https://github.com/astral-sh/ruff)

Async WhatsApp Web (Multi-Device) protocol client in pure Python, inspired by Baileys.

## What This Is

- WebSocket protocol client (no browser automation)
- QR pairing + multi-device session persistence (Baileys-like auth folder)
- `asyncio` API for receiving stanzas/events and sending messages
- Minimal runtime deps: `websockets`, `protobuf`, `cryptography`

## Status

This is an early-stage protocol client.

What works today:

- MD session login + QR pairing
- 1:1 Signal E2E (`pkmsg`/`msg`) decrypt/encrypt
- Group Signal E2E (`skmsg`) decrypt/encrypt (Sender Keys)
- Text send (1:1 multi-device fanout, groups via Sender Keys)
- Typing/recording indications (`chatstate`)
- Media send (image, PTT voice note, documents, video, stickers, static location, contacts)
- Media download/decrypt (image, audio/PTT, documents, video, stickers)
- History Sync ingestion into an in-memory store
- Best-effort contact/profile metadata (names from history sync + `notify` push names, profile picture URL, status/about)

## Limitations (Important)

- Media thumbnails + waveform: not generated automatically (you can supply `jpeg_thumbnail` / `waveform`)
- App-state sync supports snapshot + patch processing and updates the in-memory store (best-effort model application)
- App-state sync depends on app-state keys from the primary phone; the client requests missing keys, but very old sessions may require re-pairing
- Chat/contact model: minimal demo store; contact names/profile are best-effort
- API stability: no guarantees yet (pre-1.0)

## Legal / Safety

This project is not affiliated with WhatsApp/Meta. Using unofficial clients may violate WhatsApp Terms of Service.
You are responsible for compliance and for preventing abuse (spam/automation).

## Installation

```bash
pip install pyaileys
```

Optional (pretty QR output in terminal + SVG QR file):

```bash
pip install "pyaileys[qrcode]"
```

## Quickstart (Pair + Connect)

```python
import asyncio

from pyaileys import WhatsAppClient


async def main() -> None:
    client, auth_state = await WhatsAppClient.from_auth_folder("./auth")

    async def on_update(update) -> None:
        # update is `pyaileys.socket.ConnectionUpdate`
        if update.qr:
            print("QR string:", update.qr)
        if update.connection:
            print("connection:", update.connection)

    async def on_creds_update(_creds) -> None:
        await auth_state.save_creds()

    client.on("connection.update", on_update)
    client.on("creds.update", on_creds_update)

    await client.connect()
    await auth_state.save_creds()

    # keep the process alive
    await asyncio.Event().wait()


asyncio.run(main())
```

## Contacts & Profiles (Best-Effort)

WhatsApp Web does not provide a simple "address book" API. In practice, name/profile info comes from multiple places:

- History sync conversations (`displayName`/`name`/`username`)
- Incoming message stanzas (`notify` push name)
- Explicit queries (e.g. profile picture URL, about/status)

This library exposes a small convenience layer:

```python
dn = client.get_display_name("12345@s.whatsapp.net")
contact = client.get_contact("12345@s.whatsapp.net")

pic = await client.profile_picture_url("12345@s.whatsapp.net", picture_type="preview")
statuses = await client.fetch_status("12345@s.whatsapp.net")
```

Notes:

- `get_display_name()` prefers a "saved name" (history sync) and falls back to push name (`notify`).
- `fetch_status()` may return `""` if the status is hidden/blocked, and `None` if unavailable.

## Examples

Kitchen sink (interactive):

```bash
python examples/demo_app.py --auth ./auth --log-nodes
```

Simple CLI (decrypt + store + send text/media, includes `appsync`):

```bash
python examples/simple_cli.py --auth ./auth
```

Automated end-to-end smoke test against your real linked account:

```bash
tools/e2e_smoke.sh --jid 4527148803@s.whatsapp.net --auth ./auth
```

This script drives `examples/simple_cli.py`, sends test messages/media, downloads media back, and prints a pass/fail checklist.

QR-only helper (writes `qr.svg` into the auth dir if `qrcode` extra is installed):

```bash
python examples/login_qr.py
```

## Development

```bash
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

ruff check .
ruff format .
mypy src/pyaileys
pytest -q
```

Optional (recommended): install git pre-commit hooks to auto-run formatting & lint on commit:

```bash
pre-commit install
```

## Releasing to PyPI (Trusted Publishing)

This repo includes a GitHub Actions workflow (`.github/workflows/release.yml`) that publishes to PyPI when you push a
tag like `v0.1.0`.

- Bump versions in `pyproject.toml` and `src/pyaileys/__init__.py`
- Tag and push: `git tag vX.Y.Z && git push --tags`

## Regenerating Generated Files

`wabinary` token tables are generated from a Baileys checkout:

```bash
git clone https://github.com/WhiskeySockets/Baileys.git /path/to/Baileys
python3 tools/gen_wabinary_constants.py --baileys /path/to/Baileys
```

`proto/WAProto.proto` is vendored from Baileys and patched to satisfy `protoc`:

```bash
python3 tools/patch_waproto_for_protoc.py
protoc -Iproto --python_out=src/pyaileys/proto proto/WAProto.proto
```

## Credits

- Inspired by the Baileys TypeScript library (MIT): https://github.com/WhiskeySockets/Baileys

## Contributing

See `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, and `SECURITY.md`.
Simple chat CLI (includes `appsync` for full app-state sync):

```bash
python examples/simple_cli.py --auth ./auth
```
